Yet another Sass/SCSS plugin for Eleventy.
There are several Eleventy plugins to support Sass/SCSS files, already. Even the official Eleventy website has a page which describes how to handle Sass/SCSS files with your Eleventy project.
I'm not satisfied with the above solutions. Because:
-
The existing plugins do watch Sass/SCSS files and write CSS files by themselves or by using another toolkit, such as gulp.js, instead of using Eleventy's file watching and writing functionality. They might work well, but doesn't seem to be integrated enough with Eleventy.
-
The page in the official Docs is great if you only have Sass/SCSS files which do not have
@use
or@forward
rules.
If you use@use
in your Sass/SCSS files, for example, and you change a dependency Sass/SCSS file, Eleventy will compile it (if its filename doesn't start with "_"), but won't compile the dependant Sass/SCSS files, if you are following the instructions in the page of the official Docs.
In contrast to the existing solutions, eleventy-sass manages dependencies between Sass/SCSS files and makes maximum use of Eleventy's functionality. It is highly configurable, but it just works if you add it in your Eleventy config file.
For those of you who are curious about how eleventy-sass handles Sass/SCSS files, here is a brief explanation.
npm install eleventy-sass
Open up your Eleventy config file (probably .eleventy.js
) and use addPlugin()
:
const eleventySass = require("eleventy-sass");
module.exports = function(eleventyConfig) {
eleventyConfig.addPlugin(eleventySass);
};
eleventy-sass allows you to customize the behavior by options like follows:
const path = require("path");
const eleventySass = require("eleventy-sass");
const options = {
compileOptions: {
permalink: function(contents, inputPath) {
return path.format({
dir: "stylesheets",
name: path.basename(inputPath, path.extname(inputPath)),
ext: ".css"
});
}
},
sass: {
loadPaths: ["src/_includes"],
style: "expanded",
sourceMap: true,
},
defaultEleventyEnv: "development"
};
module.exports = function(eleventyConfig) {
eleventyConfig.addPlugin(eleventySass, options);
};
Basically, options
you pass as the second argument for addPlugin()
is used for options for eleventyConfig.addExtension()
, which eleventy-sass calls internally. The full options list is provided in the official Docs.
However, there are two exceptions.
One is defaultEleventyEnv
key. As described later, eleventy-sass changes behavior based on the environment/shell variable ELEVENTY_ENV
and if ELEVENTY_ENV
is not supplied, it considers the environment production
. If you want to change this default behavior, you can set the default environment to the value for defaultEleventyEnv
key.
The other exception is sass
key. The value for sass
key is used for options for sass.compileString()
, a dart-sass API function, which also eleventy-sass calls internally. For details, please read the Sass documentation.
You can use includes
key instead of loadPaths
key in this sass
options, for convenience. The value for includes
key should be of type Array or String and relative to the input
directory.
For example, when your input
directory is the project root,
// the following code:
eleventyConfig.addPlugin(eleventySass, {
sass: {
includes: "_includes/stylesheets"
}
});
// is equivalent to:
eleventyConfig.addPlugin(eleventySass, {
sass: {
loadPaths: ["_includes/stylesheets"]
}
});
But, when your input
directory is src
,
// the following code:
eleventyConfig.addPlugin(eleventySass, {
sass: {
includes: "_includes/stylesheets"
}
});
// is equivalent to:
eleventyConfig.addPlugin(eleventySass, {
sass: {
loadPaths: ["src/_includes/stylesheets"]
}
});
The value of includes
should be of type Array or String.
// The following:
eleventyConfig.addPlugin(eleventySass, {
sass: {
includes: "_includes/stylesheets, _some/other/directory"
}
});
// is equivalent to:
eleventyConfig.addPlugin(eleventySass, {
sass: {
includes: ["_includes/stylesheets", "_some/other/directory"]
}
});
If you do not specify any options, eleventy-sass uses the default options. By default, eleventy-sass checks ELEVENTY_ENV
environment/shell variable, and if it is "production", "prod", "p", or something like that or if there is no ELEVENTY_ENV
variable, it considers the environment production
.
// If the environment is production, the default options are:
{
outputFileExtension: "css",
compile: async function(inputContent, inputPath) { /* snip */ },
compileOptions: {
cache: true,
getCacheKey: function(contents, inputPath) { /* snip */ },
},
getData: async function(inputPath) {
return { eleventyComputed: { layout: false } };
},
sass: {
loadPaths: [/* The includes directory of your Eleventy project */],
style: "compressed",
sourceMap: false,
sourceMapIncludeSources: true
},
defaultEleventyEnv: "production"
}
// If not, the default options are:
{
outputFileExtension: "css",
compile: async function(inputContent, inputPath) { /* snip */ },
compileOptions: {
cache: true,
getCacheKey: function(contents, inputPath) { /* snip */ },
},
getData: async function(inputPath) {
return { eleventyComputed: { layout: false } };
},
sass: {
loadPaths: [/* The includes directory of your Eleventy project */],
style: "expanded",
sourceMap: true,
sourceMapIncludeSources: true
},
defaultEleventyEnv: "production"
}
For example, if you add the following line in your .bash_profile
, .bashrc
, .zshrc
, etc. on your local PC,
export ELEVENTY_ENV=development
the environment is development
locally, and you have a production
environment, for example, in GitHub Actions by default, because eleventy-sass regards no ELEVENTY_ENV
variable as production
. (This default behavior can also be modified by setting the value for defaultEleventyEnv
key in the options for addPlugin()
.)
And, of course, you can specify an environment from your shell like this:
ELEVENTY_ENV=production npx @11ty/eleventy --serve
For example, you have Sass/SCSS files in scss
directory in your input
directory. To output CSS files in css
directory in your output
directory, you can write a permalink function like this:
const eleventySass = require("eleventy-sass");
module.exports = function(eleventyConfig) {
eleventyConfig.addPlugin(eleventySass, {
compileOptions: {
permalink: function(contents, inputPath) {
return (data) => {
return data.page.filePathStem.replace(/^\/scss/, "/css") + ".css";
};
}
}
});
};
For details, please refer to the compileOptions.permalink section in the official documentation.
By default, eleventy-sass minifies CSSes if ELEVENTY_ENV
is production
.
To enable minification whatever ELEVENTY_ENV
is:
const eleventySass = require("eleventy-sass");
module.exports = function(eleventyConfig) {
eleventyConfig.addPlugin(eleventySass, {
sass: {
style: "compressed"
}
});
};
To disable minification:
const eleventySass = require("eleventy-sass");
module.exports = function(eleventyConfig) {
eleventyConfig.addPlugin(eleventySass, {
sass: {
style: "expanded"
}
});
};
By default, eleventy-sass adds source maps if ELEVENTY_ENV
is not production
.
To add source maps whatever ELEVENTY_ENV
is:
const eleventySass = require("eleventy-sass");
module.exports = function(eleventyConfig) {
eleventyConfig.addPlugin(eleventySass, {
sass: {
sourceMap: true
}
});
};
How eleventy-sass handles Sass/SCSS files
Eleventy watches files.
When Eleventy detects a file update, it emits an eleventy.beforeWatch
event, and eleventy-sass will receive the event and check if the file is a Sass/SCSS file.
If the file is a Sass/SCSS file, eleventy-sass changes the cache keys for the updated file and its dependant Sass/SCSS files, which invalidates the cached CSSes, so that Eleventy will compile all of the files affected.
When compiling Sass/SCSS files, Eleventy calls compile
function of the elventy-sass.
The compile
function of elventy-sass will compile Sass/SCSS files if they don't start with "_".
The actual compilation is done by sass, which is dart-sass.
By using the result of the compilation from dart-sass, eleventy-sass not only returns the compiled CSS, but also, if necessary, writes its source map file and updates the dependency map, which eleventy-sass has internally and uses for invalidating cached CSSes of dependant files as described above.