Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 96184e1

Browse files
authoredAug 24, 2019
feat: automatically use the fibers package if it is possible (#744)
1 parent ac14fd5 commit 96184e1

11 files changed

+554
-6
lines changed
 

‎README.md

+44-1
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,50 @@ module.exports = {
172172
Note that when using `sass` (`Dart Sass`), **synchronous compilation is twice as fast as asynchronous compilation** by default, due to the overhead of asynchronous callbacks.
173173
To avoid this overhead, you can use the [fibers](https://www.npmjs.com/package/fibers) package to call asynchronous importers from the synchronous code path.
174174

175-
To enable this, pass the `Fiber` class to the `sassOptions.fiber` option:
175+
We automatically inject the [`fibers`](https://github.com/laverdet/node-fibers) package (setup `sassOptions.fiber`) if is possible (i.e. you need install the [`fibers`](https://github.com/laverdet/node-fibers) package).
176+
177+
**package.json**
178+
179+
```json
180+
{
181+
"devDependencies": {
182+
"sass-loader": "^7.2.0",
183+
"sass": "^1.22.10",
184+
"fibers": "^4.0.1"
185+
}
186+
}
187+
```
188+
189+
You can disable automatically inject the [`fibers`](https://github.com/laverdet/node-fibers) package pass the `false` value for the `sassOptions.fiber` option.
190+
191+
**webpack.config.js**
192+
193+
```js
194+
module.exports = {
195+
module: {
196+
rules: [
197+
{
198+
test: /\.s[ac]ss$/i,
199+
use: [
200+
'style-loader',
201+
'css-loader',
202+
{
203+
loader: 'sass-loader',
204+
options: {
205+
implementation: require('sass'),
206+
sassOptions: {
207+
fiber: false,
208+
},
209+
},
210+
},
211+
],
212+
},
213+
],
214+
},
215+
};
216+
```
217+
218+
Also you can pass own the `fiber` value using this code:
176219

177220
**webpack.config.js**
178221

‎src/getSassOptions.js

+30-2
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,13 @@ function isProductionLikeMode(loaderContext) {
1616
/**
1717
* Derives the sass options from the loader context and normalizes its values with sane defaults.
1818
*
19-
* @param {LoaderContext} loaderContext
19+
* @param {object} loaderContext
2020
* @param {object} loaderOptions
2121
* @param {string} content
22+
* @param {object} implementation
2223
* @returns {Object}
2324
*/
24-
function getSassOptions(loaderContext, loaderOptions, content) {
25+
function getSassOptions(loaderContext, loaderOptions, content, implementation) {
2526
const options = cloneDeep(
2627
loaderOptions.sassOptions
2728
? typeof loaderOptions.sassOptions === 'function'
@@ -30,6 +31,33 @@ function getSassOptions(loaderContext, loaderOptions, content) {
3031
: {}
3132
);
3233

34+
const isDartSass = implementation.info.includes('dart-sass');
35+
36+
if (isDartSass) {
37+
const shouldTryToResolveFibers = !options.fiber && options.fiber !== false;
38+
39+
if (shouldTryToResolveFibers) {
40+
let fibers;
41+
42+
try {
43+
fibers = require.resolve('fibers');
44+
} catch (_error) {
45+
// Nothing
46+
}
47+
48+
if (fibers) {
49+
// eslint-disable-next-line global-require, import/no-dynamic-require
50+
options.fiber = require(fibers);
51+
}
52+
} else if (options.fiber === false) {
53+
// Don't pass the `fiber` option for `sass` (`Dart Sass`)
54+
delete options.fiber;
55+
}
56+
} else {
57+
// Don't pass the `fiber` option for `node-sass`
58+
delete options.fiber;
59+
}
60+
3361
options.data = loaderOptions.prependData
3462
? typeof loaderOptions.prependData === 'function'
3563
? loaderOptions.prependData(loaderContext) + os.EOL + content

‎src/index.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import SassError from './SassError';
1313
/**
1414
* The sass-loader makes node-sass and dart-sass available to webpack modules.
1515
*
16-
* @this {LoaderContext}
16+
* @this {object}
1717
* @param {string} content
1818
*/
1919
function loader(content) {
@@ -32,7 +32,7 @@ function loader(content) {
3232
this.addDependency(path.normalize(file));
3333
};
3434

35-
const sassOptions = getSassOptions(this, options, content);
35+
const sassOptions = getSassOptions(this, options, content, implementation);
3636

3737
const shouldUseWebpackImporter =
3838
typeof options.webpackImporter === 'boolean'

‎test/__snapshots__/sassOptions-option.test.js.snap

+376
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,381 @@
11
// Jest Snapshot v1, https://goo.gl/fbAQLP
22

3+
exports[`sassOptions option should don't use the "fibers" package when the "fiber" option is "false" (dart-sass) (sass): css 1`] = `
4+
"@charset \\"UTF-8\\";
5+
body {
6+
font: 100% Helvetica, sans-serif;
7+
color: #333;
8+
}
9+
10+
nav ul {
11+
margin: 0;
12+
padding: 0;
13+
list-style: none;
14+
}
15+
nav li {
16+
display: inline-block;
17+
}
18+
nav a {
19+
display: block;
20+
padding: 6px 12px;
21+
text-decoration: none;
22+
}
23+
24+
.box {
25+
-webkit-border-radius: 10px;
26+
-moz-border-radius: 10px;
27+
-ms-border-radius: 10px;
28+
border-radius: 10px;
29+
}
30+
31+
.message, .warning, .error, .success {
32+
border: 1px solid #ccc;
33+
padding: 10px;
34+
color: #333;
35+
}
36+
37+
.success {
38+
border-color: green;
39+
}
40+
41+
.error {
42+
border-color: red;
43+
}
44+
45+
.warning {
46+
border-color: yellow;
47+
}
48+
49+
.foo:before {
50+
content: \\"\\";
51+
}
52+
53+
.bar:before {
54+
content: \\"\\";
55+
}"
56+
`;
57+
58+
exports[`sassOptions option should don't use the "fibers" package when the "fiber" option is "false" (dart-sass) (sass): errors 1`] = `Array []`;
59+
60+
exports[`sassOptions option should don't use the "fibers" package when the "fiber" option is "false" (dart-sass) (sass): warnings 1`] = `Array []`;
61+
62+
exports[`sassOptions option should don't use the "fibers" package when the "fiber" option is "false" (dart-sass) (scss): css 1`] = `
63+
"@charset \\"UTF-8\\";
64+
body {
65+
font: 100% Helvetica, sans-serif;
66+
color: #333;
67+
}
68+
69+
nav ul {
70+
margin: 0;
71+
padding: 0;
72+
list-style: none;
73+
}
74+
nav li {
75+
display: inline-block;
76+
}
77+
nav a {
78+
display: block;
79+
padding: 6px 12px;
80+
text-decoration: none;
81+
}
82+
83+
.box {
84+
-webkit-border-radius: 10px;
85+
-moz-border-radius: 10px;
86+
-ms-border-radius: 10px;
87+
border-radius: 10px;
88+
}
89+
90+
.foo:before {
91+
content: \\"\\";
92+
}
93+
94+
.bar:before {
95+
content: \\"\\";
96+
}"
97+
`;
98+
99+
exports[`sassOptions option should don't use the "fibers" package when the "fiber" option is "false" (dart-sass) (scss): errors 1`] = `Array []`;
100+
101+
exports[`sassOptions option should don't use the "fibers" package when the "fiber" option is "false" (dart-sass) (scss): warnings 1`] = `Array []`;
102+
103+
exports[`sassOptions option should don't use the "fibers" package when the "fiber" option is "false" (node-sass) (sass): css 1`] = `
104+
"@charset \\"UTF-8\\";
105+
body {
106+
font: 100% Helvetica, sans-serif;
107+
color: #333; }
108+
109+
nav ul {
110+
margin: 0;
111+
padding: 0;
112+
list-style: none; }
113+
114+
nav li {
115+
display: inline-block; }
116+
117+
nav a {
118+
display: block;
119+
padding: 6px 12px;
120+
text-decoration: none; }
121+
122+
.box {
123+
-webkit-border-radius: 10px;
124+
-moz-border-radius: 10px;
125+
-ms-border-radius: 10px;
126+
border-radius: 10px; }
127+
128+
.message, .success, .error, .warning {
129+
border: 1px solid #ccc;
130+
padding: 10px;
131+
color: #333; }
132+
133+
.success {
134+
border-color: green; }
135+
136+
.error {
137+
border-color: red; }
138+
139+
.warning {
140+
border-color: yellow; }
141+
142+
.foo:before {
143+
content: \\"\\"; }
144+
145+
.bar:before {
146+
content: \\"\\"; }
147+
"
148+
`;
149+
150+
exports[`sassOptions option should don't use the "fibers" package when the "fiber" option is "false" (node-sass) (sass): errors 1`] = `Array []`;
151+
152+
exports[`sassOptions option should don't use the "fibers" package when the "fiber" option is "false" (node-sass) (sass): warnings 1`] = `Array []`;
153+
154+
exports[`sassOptions option should don't use the "fibers" package when the "fiber" option is "false" (node-sass) (scss): css 1`] = `
155+
"@charset \\"UTF-8\\";
156+
body {
157+
font: 100% Helvetica, sans-serif;
158+
color: #333; }
159+
160+
nav ul {
161+
margin: 0;
162+
padding: 0;
163+
list-style: none; }
164+
165+
nav li {
166+
display: inline-block; }
167+
168+
nav a {
169+
display: block;
170+
padding: 6px 12px;
171+
text-decoration: none; }
172+
173+
.box {
174+
-webkit-border-radius: 10px;
175+
-moz-border-radius: 10px;
176+
-ms-border-radius: 10px;
177+
border-radius: 10px; }
178+
179+
.foo:before {
180+
content: \\"\\"; }
181+
182+
.bar:before {
183+
content: \\"\\"; }
184+
"
185+
`;
186+
187+
exports[`sassOptions option should don't use the "fibers" package when the "fiber" option is "false" (node-sass) (scss): errors 1`] = `Array []`;
188+
189+
exports[`sassOptions option should don't use the "fibers" package when the "fiber" option is "false" (node-sass) (scss): warnings 1`] = `Array []`;
190+
191+
exports[`sassOptions option should use the "fibers" package if it is possible (dart-sass) (sass): css 1`] = `
192+
"@charset \\"UTF-8\\";
193+
body {
194+
font: 100% Helvetica, sans-serif;
195+
color: #333;
196+
}
197+
198+
nav ul {
199+
margin: 0;
200+
padding: 0;
201+
list-style: none;
202+
}
203+
nav li {
204+
display: inline-block;
205+
}
206+
nav a {
207+
display: block;
208+
padding: 6px 12px;
209+
text-decoration: none;
210+
}
211+
212+
.box {
213+
-webkit-border-radius: 10px;
214+
-moz-border-radius: 10px;
215+
-ms-border-radius: 10px;
216+
border-radius: 10px;
217+
}
218+
219+
.message, .warning, .error, .success {
220+
border: 1px solid #ccc;
221+
padding: 10px;
222+
color: #333;
223+
}
224+
225+
.success {
226+
border-color: green;
227+
}
228+
229+
.error {
230+
border-color: red;
231+
}
232+
233+
.warning {
234+
border-color: yellow;
235+
}
236+
237+
.foo:before {
238+
content: \\"\\";
239+
}
240+
241+
.bar:before {
242+
content: \\"\\";
243+
}"
244+
`;
245+
246+
exports[`sassOptions option should use the "fibers" package if it is possible (dart-sass) (sass): errors 1`] = `Array []`;
247+
248+
exports[`sassOptions option should use the "fibers" package if it is possible (dart-sass) (sass): warnings 1`] = `Array []`;
249+
250+
exports[`sassOptions option should use the "fibers" package if it is possible (dart-sass) (scss): css 1`] = `
251+
"@charset \\"UTF-8\\";
252+
body {
253+
font: 100% Helvetica, sans-serif;
254+
color: #333;
255+
}
256+
257+
nav ul {
258+
margin: 0;
259+
padding: 0;
260+
list-style: none;
261+
}
262+
nav li {
263+
display: inline-block;
264+
}
265+
nav a {
266+
display: block;
267+
padding: 6px 12px;
268+
text-decoration: none;
269+
}
270+
271+
.box {
272+
-webkit-border-radius: 10px;
273+
-moz-border-radius: 10px;
274+
-ms-border-radius: 10px;
275+
border-radius: 10px;
276+
}
277+
278+
.foo:before {
279+
content: \\"\\";
280+
}
281+
282+
.bar:before {
283+
content: \\"\\";
284+
}"
285+
`;
286+
287+
exports[`sassOptions option should use the "fibers" package if it is possible (dart-sass) (scss): errors 1`] = `Array []`;
288+
289+
exports[`sassOptions option should use the "fibers" package if it is possible (dart-sass) (scss): warnings 1`] = `Array []`;
290+
291+
exports[`sassOptions option should use the "fibers" package if it is possible (node-sass) (sass): css 1`] = `
292+
"@charset \\"UTF-8\\";
293+
body {
294+
font: 100% Helvetica, sans-serif;
295+
color: #333; }
296+
297+
nav ul {
298+
margin: 0;
299+
padding: 0;
300+
list-style: none; }
301+
302+
nav li {
303+
display: inline-block; }
304+
305+
nav a {
306+
display: block;
307+
padding: 6px 12px;
308+
text-decoration: none; }
309+
310+
.box {
311+
-webkit-border-radius: 10px;
312+
-moz-border-radius: 10px;
313+
-ms-border-radius: 10px;
314+
border-radius: 10px; }
315+
316+
.message, .success, .error, .warning {
317+
border: 1px solid #ccc;
318+
padding: 10px;
319+
color: #333; }
320+
321+
.success {
322+
border-color: green; }
323+
324+
.error {
325+
border-color: red; }
326+
327+
.warning {
328+
border-color: yellow; }
329+
330+
.foo:before {
331+
content: \\"\\"; }
332+
333+
.bar:before {
334+
content: \\"\\"; }
335+
"
336+
`;
337+
338+
exports[`sassOptions option should use the "fibers" package if it is possible (node-sass) (sass): errors 1`] = `Array []`;
339+
340+
exports[`sassOptions option should use the "fibers" package if it is possible (node-sass) (sass): warnings 1`] = `Array []`;
341+
342+
exports[`sassOptions option should use the "fibers" package if it is possible (node-sass) (scss): css 1`] = `
343+
"@charset \\"UTF-8\\";
344+
body {
345+
font: 100% Helvetica, sans-serif;
346+
color: #333; }
347+
348+
nav ul {
349+
margin: 0;
350+
padding: 0;
351+
list-style: none; }
352+
353+
nav li {
354+
display: inline-block; }
355+
356+
nav a {
357+
display: block;
358+
padding: 6px 12px;
359+
text-decoration: none; }
360+
361+
.box {
362+
-webkit-border-radius: 10px;
363+
-moz-border-radius: 10px;
364+
-ms-border-radius: 10px;
365+
border-radius: 10px; }
366+
367+
.foo:before {
368+
content: \\"\\"; }
369+
370+
.bar:before {
371+
content: \\"\\"; }
372+
"
373+
`;
374+
375+
exports[`sassOptions option should use the "fibers" package if it is possible (node-sass) (scss): errors 1`] = `Array []`;
376+
377+
exports[`sassOptions option should use the "fibers" package if it is possible (node-sass) (scss): warnings 1`] = `Array []`;
378+
3379
exports[`sassOptions option should work when the option is empty "Object" (dart-sass) (sass): css 1`] = `
4380
"@charset \\"UTF-8\\";
5381
body {

‎test/implementation-option.test.js

+3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import nodeSass from 'node-sass';
22
import dartSass from 'sass';
3+
import Fiber from 'fibers';
34

45
import {
56
compile,
@@ -13,6 +14,8 @@ const implementations = [nodeSass, dartSass];
1314

1415
describe('implementation option', () => {
1516
beforeEach(() => {
17+
// The `sass` (`Dart Sass`) package modify the `Function` prototype, but the `jest` lose a prototype
18+
Object.setPrototypeOf(Fiber, Function.prototype);
1619
jest.clearAllMocks();
1720
});
1821

‎test/loader.test.js

+6
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import path from 'path';
22

33
import nodeSass from 'node-sass';
44
import dartSass from 'sass';
5+
import Fiber from 'fibers';
56

67
import {
78
compile,
@@ -16,6 +17,11 @@ const implementations = [nodeSass, dartSass];
1617
const syntaxStyles = ['scss', 'sass'];
1718

1819
describe('loader', () => {
20+
beforeEach(() => {
21+
// The `sass` (`Dart Sass`) package modify the `Function` prototype, but the `jest` lose a prototype
22+
Object.setPrototypeOf(Fiber, Function.prototype);
23+
});
24+
1925
implementations.forEach((implementation) => {
2026
const [implementationName] = implementation.info.split('\t');
2127

‎test/prependData-option.test.js

+6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import nodeSass from 'node-sass';
22
import dartSass from 'sass';
3+
import Fiber from 'fibers';
34

45
import {
56
compile,
@@ -13,6 +14,11 @@ const implementations = [nodeSass, dartSass];
1314
const syntaxStyles = ['scss', 'sass'];
1415

1516
describe('prependData option', () => {
17+
beforeEach(() => {
18+
// The `sass` (`Dart Sass`) package modify the `Function` prototype, but the `jest` lose a prototype
19+
Object.setPrototypeOf(Fiber, Function.prototype);
20+
});
21+
1622
implementations.forEach((implementation) => {
1723
const [implementationName] = implementation.info.split('\t');
1824

‎test/sassOptions-option.test.js

+68-1
Original file line numberDiff line numberDiff line change
@@ -161,13 +161,17 @@ describe('sassOptions option', () => {
161161
});
162162

163163
it(`should work with the "fiber" option (${implementationName}) (${syntax})`, async () => {
164+
const dartSassSpy = jest.spyOn(dartSass, 'render');
164165
const testId = getTestId('language', syntax);
165166
const options = {
166167
implementation: getImplementationByName(implementationName),
167168
sassOptions: {},
168169
};
169170

170-
if (semver.satisfies(process.version, '>= 10')) {
171+
if (
172+
implementationName === 'dart-sass' &&
173+
semver.satisfies(process.version, '>= 10')
174+
) {
171175
// eslint-disable-next-line global-require
172176
options.sassOptions.fiber = Fiber;
173177
}
@@ -176,10 +180,73 @@ describe('sassOptions option', () => {
176180
const codeFromBundle = getCodeFromBundle(stats);
177181
const codeFromSass = getCodeFromSass(testId, options);
178182

183+
if (
184+
implementationName === 'dart-sass' &&
185+
semver.satisfies(process.version, '>= 10')
186+
) {
187+
expect(dartSassSpy.mock.calls[0][0]).toHaveProperty('fiber');
188+
}
189+
190+
expect(codeFromBundle.css).toBe(codeFromSass.css);
191+
expect(codeFromBundle.css).toMatchSnapshot('css');
192+
expect(stats.compilation.warnings).toMatchSnapshot('warnings');
193+
expect(stats.compilation.errors).toMatchSnapshot('errors');
194+
195+
dartSassSpy.mockRestore();
196+
});
197+
198+
it(`should use the "fibers" package if it is possible (${implementationName}) (${syntax})`, async () => {
199+
const dartSassSpy = jest.spyOn(dartSass, 'render');
200+
const testId = getTestId('language', syntax);
201+
const options = {
202+
implementation: getImplementationByName(implementationName),
203+
sassOptions: {},
204+
};
205+
206+
const stats = await compile(testId, { loader: { options } });
207+
const codeFromBundle = getCodeFromBundle(stats);
208+
const codeFromSass = getCodeFromSass(testId, options);
209+
210+
if (
211+
implementationName === 'dart-sass' &&
212+
semver.satisfies(process.version, '>= 10')
213+
) {
214+
expect(dartSassSpy.mock.calls[0][0]).toHaveProperty('fiber');
215+
}
216+
217+
expect(codeFromBundle.css).toBe(codeFromSass.css);
218+
expect(codeFromBundle.css).toMatchSnapshot('css');
219+
expect(stats.compilation.warnings).toMatchSnapshot('warnings');
220+
expect(stats.compilation.errors).toMatchSnapshot('errors');
221+
222+
dartSassSpy.mockRestore();
223+
});
224+
225+
it(`should don't use the "fibers" package when the "fiber" option is "false" (${implementationName}) (${syntax})`, async () => {
226+
const dartSassSpy = jest.spyOn(dartSass, 'render');
227+
const testId = getTestId('language', syntax);
228+
const options = {
229+
implementation: getImplementationByName(implementationName),
230+
sassOptions: { fiber: false },
231+
};
232+
233+
const stats = await compile(testId, { loader: { options } });
234+
const codeFromBundle = getCodeFromBundle(stats);
235+
const codeFromSass = getCodeFromSass(testId, options);
236+
237+
if (
238+
implementationName === 'dart-sass' &&
239+
semver.satisfies(process.version, '>= 10')
240+
) {
241+
expect(dartSassSpy.mock.calls[0][0]).not.toHaveProperty('fiber');
242+
}
243+
179244
expect(codeFromBundle.css).toBe(codeFromSass.css);
180245
expect(codeFromBundle.css).toMatchSnapshot('css');
181246
expect(stats.compilation.warnings).toMatchSnapshot('warnings');
182247
expect(stats.compilation.errors).toMatchSnapshot('errors');
248+
249+
dartSassSpy.mockRestore();
183250
});
184251
});
185252
});

‎test/sourceMap-options.test.js

+6
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import path from 'path';
33

44
import nodeSass from 'node-sass';
55
import dartSass from 'sass';
6+
import Fiber from 'fibers';
67

78
import {
89
compile,
@@ -15,6 +16,11 @@ const implementations = [nodeSass, dartSass];
1516
const syntaxStyles = ['scss', 'sass'];
1617

1718
describe('sourceMap option', () => {
19+
beforeEach(() => {
20+
// The `sass` (`Dart Sass`) package modify the `Function` prototype, but the `jest` lose a prototype
21+
Object.setPrototypeOf(Fiber, Function.prototype);
22+
});
23+
1824
implementations.forEach((implementation) => {
1925
syntaxStyles.forEach((syntax) => {
2026
const [implementationName] = implementation.info.split('\t');

‎test/validate-options.test.js

+7
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
1+
import Fiber from 'fibers';
2+
13
import loader from '../src/cjs';
24

5+
beforeEach(() => {
6+
// The `sass` (`Dart Sass`) package modify the `Function` prototype, but the `jest` lose a prototype
7+
Object.setPrototypeOf(Fiber, Function.prototype);
8+
});
9+
310
it('validate options', () => {
411
const validate = (options) =>
512
loader.call(

‎test/webpackImporter-options.test.js

+6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import nodeSass from 'node-sass';
22
import dartSass from 'sass';
3+
import Fiber from 'fibers';
34

45
import {
56
compile,
@@ -13,6 +14,11 @@ const implementations = [nodeSass, dartSass];
1314
const syntaxStyles = ['scss', 'sass'];
1415

1516
describe('webpackImporter option', () => {
17+
beforeEach(() => {
18+
// The `sass` (`Dart Sass`) package modify the `Function` prototype, but the `jest` lose a prototype
19+
Object.setPrototypeOf(Fiber, Function.prototype);
20+
});
21+
1622
implementations.forEach((implementation) => {
1723
syntaxStyles.forEach((syntax) => {
1824
const [implementationName] = implementation.info.split('\t');

0 commit comments

Comments
 (0)
Please sign in to comment.