Skip to content

Commit bed9fb5

Browse files
nex3jhnns
authored andcommitted
feat: Make this package implementation-agnostic (#573)
Rather than always loading Node Sass, this now requires users to pass in either Dart Sass or Node Sass as an option to the loader. Closes #435
1 parent 714f5c6 commit bed9fb5

File tree

19 files changed

+528
-341
lines changed

19 files changed

+528
-341
lines changed

README.md

+59-6
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,14 @@ Looking for the webpack 1 loader? Check out the [archive/webpack-1 branch](https
2727
npm install sass-loader node-sass webpack --save-dev
2828
```
2929

30-
The sass-loader requires [node-sass](https://github.com/sass/node-sass) and [webpack](https://github.com/webpack)
31-
as [`peerDependency`](https://docs.npmjs.com/files/package.json#peerdependencies). Thus you are able to control the versions accurately.
30+
The sass-loader requires [webpack](https://github.com/webpack) as a
31+
[`peerDependency`](https://docs.npmjs.com/files/package.json#peerdependencies)
32+
and it requires you to install either [Node Sass][] or [Dart Sass][] on your
33+
own. This allows you to control the versions of all your dependencies, and to
34+
choose which Sass implementation to use.
35+
36+
[Node Sass]: https://github.com/sass/node-sass
37+
[Dart Sass]: http://sass-lang.com/dart-sass
3238

3339
<h2 align="center">Examples</h2>
3440

@@ -48,14 +54,14 @@ module.exports = {
4854
use: [
4955
"style-loader", // creates style nodes from JS strings
5056
"css-loader", // translates CSS into CommonJS
51-
"sass-loader" // compiles Sass to CSS
57+
"sass-loader" // compiles Sass to CSS, using Node Sass by default
5258
]
5359
}]
5460
}
5561
};
5662
```
5763

58-
You can also pass options directly to [node-sass](https://github.com/andrew/node-sass) by specifying an `options` property like this:
64+
You can also pass options directly to [Node Sass][] or [Dart Sass][]:
5965

6066
```js
6167
// webpack.config.js
@@ -79,7 +85,54 @@ module.exports = {
7985
};
8086
```
8187

82-
See [node-sass](https://github.com/andrew/node-sass) for all available Sass options.
88+
See [the Node Sass documentation](https://github.com/sass/node-sass/blob/master/README.md#options) for all available Sass options.
89+
90+
The special `implementation` option determines which implementation of Sass to
91+
use. It takes either a [Node Sass][] or a [Dart Sass][] module. For example, to
92+
use Dart Sass, you'd pass:
93+
94+
```js
95+
// ...
96+
{
97+
loader: "sass-loader",
98+
options: {
99+
implementation: require("sass")
100+
}
101+
}
102+
// ...
103+
```
104+
105+
Note that when using Dart Sass, **synchronous compilation is twice as fast as
106+
asynchronous compilation** by default, due to the overhead of asynchronous
107+
callbacks. To avoid this overhead, you can use the
108+
[`fibers`](https://www.npmjs.com/package/fibers) package to call asynchronous
109+
importers from the synchronous code path. To enable this, pass the `Fiber` class
110+
to the `fiber` option:
111+
112+
```js
113+
// webpack.config.js
114+
const Fiber = require('fibers');
115+
116+
module.exports = {
117+
...
118+
module: {
119+
rules: [{
120+
test: /\.scss$/,
121+
use: [{
122+
loader: "style-loader"
123+
}, {
124+
loader: "css-loader"
125+
}, {
126+
loader: "sass-loader",
127+
options: {
128+
implementation: require("sass"),
129+
fiber: Fiber
130+
}
131+
}]
132+
}]
133+
}
134+
};
135+
```
83136

84137
### In production
85138

@@ -116,7 +169,7 @@ module.exports = {
116169

117170
### Imports
118171

119-
webpack provides an [advanced mechanism to resolve files](https://webpack.js.org/concepts/module-resolution/). The sass-loader uses node-sass' custom importer feature to pass all queries to the webpack resolving engine. Thus you can import your Sass modules from `node_modules`. Just prepend them with a `~` to tell webpack that this is not a relative import:
172+
webpack provides an [advanced mechanism to resolve files](https://webpack.js.org/concepts/module-resolution/). The sass-loader uses Sass's custom importer feature to pass all queries to the webpack resolving engine. Thus you can import your Sass modules from `node_modules`. Just prepend them with a `~` to tell webpack that this is not a relative import:
120173

121174
```css
122175
@import "~bootstrap/dist/css/bootstrap";

lib/loader.js

+49-20
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,9 @@ const formatSassError = require("./formatSassError");
66
const webpackImporter = require("./webpackImporter");
77
const normalizeOptions = require("./normalizeOptions");
88
const pify = require("pify");
9+
const semver = require("semver");
910

10-
// This queue makes sure node-sass leaves one thread available for executing
11-
// fs tasks when running the custom importer code.
12-
// This can be removed as soon as node-sass implements a fix for this.
13-
const threadPoolSize = process.env.UV_THREADPOOL_SIZE || 4;
14-
let asyncSassJobQueue = null;
11+
let nodeSassJobQueue = null;
1512

1613
/**
1714
* The sass-loader makes node-sass available to webpack modules.
@@ -20,19 +17,6 @@ let asyncSassJobQueue = null;
2017
* @param {string} content
2118
*/
2219
function sassLoader(content) {
23-
if (asyncSassJobQueue === null) {
24-
const sass = require("node-sass");
25-
const sassVersion = /^(\d+)/.exec(require("node-sass/package.json").version).pop();
26-
27-
if (Number(sassVersion) < 4) {
28-
throw new Error(
29-
"The installed version of `node-sass` is not compatible (expected: >= 4, actual: " + sassVersion + ")."
30-
);
31-
}
32-
33-
asyncSassJobQueue = async.queue(sass.render, threadPoolSize - 1);
34-
}
35-
3620
const callback = this.async();
3721
const isSync = typeof callback !== "function";
3822
const self = this;
@@ -59,8 +43,9 @@ function sassLoader(content) {
5943
return;
6044
}
6145

62-
// start the actual rendering
63-
asyncSassJobQueue.push(options, (err, result) => {
46+
const render = getRenderFuncFromSassImpl(options.implementation || require("node-sass"));
47+
48+
render(options, (err, result) => {
6449
if (err) {
6550
formatSassError(err, this.resourcePath);
6651
err.file && this.dependency(err.file);
@@ -92,4 +77,48 @@ function sassLoader(content) {
9277
});
9378
}
9479

80+
/**
81+
* Verifies that the implementation and version of Sass is supported by this loader.
82+
*
83+
* @param {Object} module
84+
* @returns {Function}
85+
*/
86+
function getRenderFuncFromSassImpl(module) {
87+
const info = module.info;
88+
const components = info.split("\t");
89+
90+
if (components.length < 2) {
91+
throw new Error("Unknown Sass implementation \"" + info + "\".");
92+
}
93+
94+
const implementation = components[0];
95+
const version = components[1];
96+
97+
if (!semver.valid(version)) {
98+
throw new Error("Invalid Sass version \"" + version + "\".");
99+
}
100+
101+
if (implementation === "dart-sass") {
102+
if (!semver.satisfies(version, "^1.3.0")) {
103+
throw new Error("Dart Sass version " + version + " is incompatible with ^1.3.0.");
104+
}
105+
return module.render.bind(module);
106+
} else if (implementation === "node-sass") {
107+
if (!semver.satisfies(version, "^4.0.0")) {
108+
throw new Error("Node Sass version " + version + " is incompatible with ^4.0.0.");
109+
}
110+
// There is an issue with node-sass when async custom importers are used
111+
// See https://github.com/sass/node-sass/issues/857#issuecomment-93594360
112+
// We need to use a job queue to make sure that one thread is always available to the UV lib
113+
if (nodeSassJobQueue === null) {
114+
const threadPoolSize = Number(process.env.UV_THREADPOOL_SIZE || 4);
115+
116+
nodeSassJobQueue = async.queue(module.render.bind(module), threadPoolSize - 1);
117+
}
118+
119+
return nodeSassJobQueue.push.bind(nodeSassJobQueue);
120+
}
121+
throw new Error("Unknown Sass implementation \"" + implementation + "\".");
122+
}
123+
95124
module.exports = sassLoader;

package-lock.json

+21-8
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+3-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@
3030
"loader-utils": "^1.0.1",
3131
"lodash.tail": "^4.1.1",
3232
"neo-async": "^2.5.0",
33-
"pify": "^3.0.0"
33+
"pify": "^3.0.0",
34+
"semver": "^5.5.0"
3435
},
3536
"devDependencies": {
3637
"bootstrap-sass": "^3.3.5",
@@ -44,6 +45,7 @@
4445
"node-sass": "^4.5.0",
4546
"nyc": "^11.0.2",
4647
"raw-loader": "^0.5.1",
48+
"sass": "^1.3.0",
4749
"should": "^11.2.0",
4850
"standard-version": "^4.2.0",
4951
"style-loader": "^0.18.2",

0 commit comments

Comments
 (0)