OPTEN, das einzige Umbraco-zertifizierte Unternehmen der Schweiz

Our Octopus Odyssey: Part 7. less & CSS

This blog is part 7 in a series.

Part 1. The Basics
Part 2. Build Script
Part 3. Variables and Powershell
Part 4. Database
Part 5. web.config
Part 6. NuGet Repository

Less variables

We use less to manage our CSS files. Because we deploy the same solution to several websites, we manage to produce different CSS by changing a “theme” variable in a less file named appropriately _variables.less. Depending on which theme we want to use, we import different variables. This is what the master _variables.less file looks like.

// Variables
// --------------------------------------------------
/***DO NOT CHANGE THE FORMAT OF THIS FILE IT IS READ UPON BUILD BY POWERSHELL SCRIPT***/

// Change the Theme by including the respective variables-file

/*ThemeNames*/
@import "Site1/_variables.less";
//@import "Site2/_variables.less";
//@import "Site3/_variables.less";
/*EndThemeNames*/

If we want to build the css for site2, we simply comment the top line and uncomment the second line then rebuild the solution.

The problem

This method works great, but is a problem when it comes to Octopus Deploy. We want to be able to deploy the same NuGet package to every site, not have to rebuild the NuGet package afresh for each site and uncomment a different line in the variables.less file.

Powershell

To solve this situation I wrote a powershell file named CompileLess.ps1 which is run as a part of the build script. This powershell file fetches the _variables.less file from the solution using the following code:

$ThemesFile = Get-Content _variables.less

Then it gets the actual themes from all the comments by looking for the start and end comments using the following code:

#for each line in the themes file, loop, we want to get the actual themes, so we set the line the themes start and the line they end.
for ($i=0; $i -le $ThemesFile.Count; $i++)
{
    $line = $ThemesFile[$i]
    if($line -eq'/*ThemeNames*/')
    {
        $ThemesStart = $i + 1
    }
    if($line -eq'/*EndThemeNames*/')
    {
        $ThemesEnd = $i - 1
    }
}

The next step is to take these lines as a new array:

$Themes = $ThemesFile[$ThemesStart..$ThemesEnd]

There is one line which will not be commented out, this was the current theme being used in development. I want to comment this line out, so that all lines are then the same and I can loop through them. I do this with the following code:

# One theme will be uncommented, comment out this line so all lines are commented.
for ($i=0; $i -le $Themes.Count; $i++)
{
    $currentTheme = $Themes[$i]
    if(!$currentTheme) { continue }
    if($currentTheme.StartsWith('@import'))
    {
        $Themes[$i] = "//" + $Themes[$i]
    }
}

Then I can loop through all the lines and for each line, uncomment that line, then overwrite the “_variables.less” file with the contents of this array. It is important when writing the array to the less file to use the –encoding ASCII argument, otherwise the less compilation will fail. Next compile the less to css using the current theme name, so if line 1 is uncommented the less will compile the site1.css and site1.min.css. The less is compiled using lessc which runs under node.js. So before running this script I made sure that node.js was installed on my machine http://nodejs.org/ and then used npm to install lessc by running the following command at the command prompt:

npm install -g less

I also use the less-plugin-clean-css to generate the minified css files, this can also be installed from npm:

npm install less-plugin-clean-css

Once the css files have been compiled, then I recomment the uncommented out line, rewrite the array to the _variables.less file, so everything is reset. Then loop to the next line and repeat the process. Here is the code:

$ThemesAllCommentedOut = $Themes

for ($i=0; $i -le $Themes.Count; $i++)
{
    $currentTheme = $Themes[$i]
    if(!$currentTheme) { continue }
    $Themes[$i] = $currentTheme.Replace("//", "")

    # important to write out array to file using ASCII encoding
    $Themes|out-file "_variables.less" -encoding ASCII

    $currentThemeName = $Themes[$i].Replace("@import """, "").Replace("/_variables.less"";","").toLower()
    Write-Host "currentThemeName: " $currentThemeName
    $currentLessFile = "shopten-"+$currentThemeName+".less"
    $currentCssFile = "shopten-"+$currentThemeName+".css"
    $currentMinCssFile = "shopten-"+$currentThemeName+".min.css"

    Write-Host "compiling " $currentThemeName " css"
    lessc $currentLessFile $currentCssFile
    Write-Host "compiling " $currentThemeName " minified css"
    lessc --clean-css $currentLessFile $currentMinCssFile
    Write-Host "generated " $currentThemeName " css successfully"

    $Themes[$i] = "//" + $Themes[$i]
    $ThemesAllCommentedOut|out-file "_variables.less" -encoding ASCII
}

Then at the end of the script I make sure to revert the _variables.less file to the original so that nothing is changed for the developer using Visual Studio:

$ThemesFile|out-file "_variables.less" -encoding ASCII
Write-Host "CompileLess Finished"

Build Script

This powershell script is run from the build script (see part 2). I added a new Target named CompileLess which the main BuildSolution target depends on, this target simply logs a message to the output and then runs the Powershell script. I saved the powershell file into the same directory as the build scripts. Here is the code:

<Target Name="CompileLess" Condition="$(ProjectToPack) == 'OctoPackWeb'">
    <Message Text="Compiling less files" Importance="high"/>
    <Exec Command='powershell -ExecutionPolicy Bypass -f CompileLess.ps1'/>
</Target>

Summary

Now when running the build script, this new powershell script runs and makes sure that a CSS file is compiled for every possible theme. So the same NuGet package can be deployed to any website and the styles will be correct.

Complete Powershell Script

Here is the final version of the code for CompileLess.ps1:

Write-Host "CompileLess Started"
# change to the less directory
cd..
cd WebsiteRootDir\Content\Css\less

# read the _variables file
$ThemesFile = Get-Content _variables.less
$ThemesStart = 0
$ThemesEnd = 0

Write-Host "themefile line count: " $ThemesFile.Count

#for each line in the themes file, loop, we want to get the actual themes,
#so we set the line the themes start and the line they end.
for ($i=0; $i -le $ThemesFile.Count; $i++)
{
    $line = $ThemesFile[$i]
    if($line -eq'/*ThemeNames*/')
    {
        $ThemesStart = $i + 1
    }
    if($line -eq'/*EndThemeNames*/')
    {
        $ThemesEnd = $i - 1
    }
}

Write-Host "themes start line: " $ThemesStart
Write-Host "themes end line: " $ThemesEnd

# take a subset of the array of lines which consists of the themes.
$Themes = $ThemesFile[$ThemesStart..$ThemesEnd]

# One theme will be uncommented, comment out this line so all lines are commented.
for ($i=0; $i -le $Themes.Count; $i++)
{
    $currentTheme = $Themes[$i]
    if(!$currentTheme) { continue }
    if($currentTheme.StartsWith('@import'))
    {
        $Themes[$i] = "//" + $Themes[$i]
    }
}

# loop through and for each line, uncomment that line,
# then write this to the variables file
# then compile the less with that line uncommented
# then recomment that line.
# then rewrite the commented out themes to the variables file ready for the next loop iteration.
$ThemesAllCommentedOut = $Themes

for ($i=0; $i -le $Themes.Count; $i++)
{
    $currentTheme = $Themes[$i]
    if(!$currentTheme) { continue }
    $Themes[$i] = $currentTheme.Replace("//", "")

    # important to write out array to file using ASCII encoding
    $Themes|out-file "_variables.less" -encoding ASCII

    $currentThemeName = $Themes[$i].Replace("@import """, "")
                                                        .Replace("/_variables.less"";","").toLower()
    Write-Host "currentThemeName: " $currentThemeName
    $currentLessFile = "shopten-"+$currentThemeName+".less"
    $currentCssFile = "shopten-"+$currentThemeName+".css"
    $currentMinCssFile = "shopten-"+$currentThemeName+".min.css"

    Write-Host "compiling " $currentThemeName " css"
    lessc $currentLessFile $currentCssFile
    Write-Host "compiling " $currentThemeName " minified css"
    lessc --clean-css $currentLessFile $currentMinCssFile
    Write-Host "generated " $currentThemeName " css successfully"

    $Themes[$i] = "//" + $Themes[$i]
    $ThemesAllCommentedOut|out-file "_variables.less" -encoding ASCII
}

$ThemesFile|out-file "_variables.less" -encoding ASCII
Write-Host "CompileLess Finished"

Next: Production

Precompiling the CSS files for all themes was the last piece of the puzzle. We now use Octopus Deploy every day to handle all deployments to the staging and live enviroments. In the next blog post I will wrap up the series with some comments and observations now that we are using Octopus Deploy in production.


kommentieren


0 Kommentar(e):