sass-loader 14.1.1 → 14.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -657,16 +657,16 @@ module.exports = {
657
657
  Type:
658
658
 
659
659
  ```ts
660
- type api = "legacy" | "modern";
660
+ type api = "legacy" | "modern" | "modern-compiler";
661
661
  ```
662
662
 
663
663
  Default: `"legacy"`
664
664
 
665
- Allows you to switch between `legacy` and `modern` API. You can find more information [here](https://sass-lang.com/documentation/js-api).
665
+ Allows you to switch between `legacy` and `modern` API. You can find more information [here](https://sass-lang.com/documentation/js-api). The `modern-compiler` option enables the modern API with support for [Shared Resources](https://github.com/sass/sass/blob/main/accepted/shared-resources.d.ts.md).
666
666
 
667
- > **Warning**
667
+ > **Note**
668
668
  >
669
- > "modern" API is experimental, so some features may not work (known: built-in `importer` is not working and files with errors is not watching on initial run), you can follow this [here](https://github.com/webpack-contrib/sass-loader/issues/774).
669
+ > Using `modern-compiler` and `sass-embedded` together significantly improve performance and decrease built time. We strongly recommend their use. We will enable them by default in a future major release.
670
670
 
671
671
  > **Warning**
672
672
  >
package/dist/index.js CHANGED
@@ -29,26 +29,28 @@ async function loader(content) {
29
29
  const sassOptions = await (0, _utils.getSassOptions)(this, options, content, implementation, useSourceMap);
30
30
  const shouldUseWebpackImporter = typeof options.webpackImporter === "boolean" ? options.webpackImporter : true;
31
31
  if (shouldUseWebpackImporter) {
32
- const isModernAPI = options.api === "modern";
32
+ const isModernAPI = options.api === "modern" || options.api === "modern-compiler";
33
33
  if (!isModernAPI) {
34
34
  const {
35
35
  includePaths
36
36
  } = sassOptions;
37
37
  sassOptions.importer.push((0, _utils.getWebpackImporter)(this, implementation, includePaths));
38
38
  } else {
39
- sassOptions.importers.push((0, _utils.getModernWebpackImporter)(this, implementation));
39
+ sassOptions.importers.push(
40
+ // No need to pass `loadPaths`, because modern API handle them itself
41
+ (0, _utils.getModernWebpackImporter)(this, implementation, []));
40
42
  }
41
43
  }
42
44
  let compile;
43
45
  try {
44
- compile = (0, _utils.getCompileFn)(implementation, options);
46
+ compile = (0, _utils.getCompileFn)(this, implementation, options);
45
47
  } catch (error) {
46
48
  callback(error);
47
49
  return;
48
50
  }
49
51
  let result;
50
52
  try {
51
- result = await compile(sassOptions, options);
53
+ result = await compile(sassOptions);
52
54
  } catch (error) {
53
55
  // There are situations when the `file`/`span.url` property do not exist
54
56
  // Modern API
package/dist/options.json CHANGED
@@ -17,7 +17,7 @@
17
17
  "api": {
18
18
  "description": "Switch between old and modern API for `sass` (`Dart Sass`) and `Sass Embedded` implementations.",
19
19
  "link": "https://github.com/webpack-contrib/sass-loader#sassoptions",
20
- "enum": ["legacy", "modern"]
20
+ "enum": ["legacy", "modern", "modern-compiler"]
21
21
  },
22
22
  "sassOptions": {
23
23
  "description": "Options for `node-sass` or `sass` (`Dart Sass`) implementation.",
package/dist/utils.js CHANGED
@@ -145,7 +145,7 @@ async function getSassOptions(loaderContext, loaderOptions, content, implementat
145
145
  }
146
146
  };
147
147
  }
148
- const isModernAPI = loaderOptions.api === "modern";
148
+ const isModernAPI = loaderOptions.api === "modern" || loaderOptions.api === "modern-compiler";
149
149
  const {
150
150
  resourcePath
151
151
  } = loaderContext;
@@ -171,6 +171,9 @@ async function getSassOptions(loaderContext, loaderOptions, content, implementat
171
171
  sassOptions.syntax = "css";
172
172
  }
173
173
  }
174
+ sassOptions.loadPaths = [].concat(
175
+ // We use `loadPaths` in context for resolver, so it should be always absolute
176
+ (sassOptions.loadPaths ? sassOptions.loadPaths.slice() : []).map(includePath => _path.default.isAbsolute(includePath) ? includePath : _path.default.join(process.cwd(), includePath))).concat(process.env.SASS_PATH ? process.env.SASS_PATH.split(process.platform === "win32" ? ";" : ":") : []);
174
177
  sassOptions.importers = sassOptions.importers ? Array.isArray(sassOptions.importers) ? sassOptions.importers.slice() : [sassOptions.importers] : [];
175
178
  } else {
176
179
  sassOptions.file = resourcePath;
@@ -346,7 +349,7 @@ const IS_NATIVE_WIN32_PATH = /^[a-z]:[/\\]|^\\\\/i;
346
349
  * @throws If a compatible Sass implementation cannot be found.
347
350
  */
348
351
  function getWebpackResolver(resolverFactory, implementation, includePaths = []) {
349
- const isDartSass = implementation && implementation.info.includes("dart-sass");
352
+ const isModernSass = implementation && (implementation.info.includes("dart-sass") || implementation.info.includes("sass-embedded"));
350
353
  // We only have one difference with the built-in sass resolution logic and out resolution logic:
351
354
  // First, we look at the files starting with `_`, then without `_` (i.e. `_name.sass`, `_name.scss`, `_name.css`, `name.sass`, `name.scss`, `name.css`),
352
355
  // although `sass` look together by extensions (i.e. `_name.sass`/`name.sass`/`_name.scss`/`name.scss`/`_name.css`/`name.css`).
@@ -403,7 +406,7 @@ function getWebpackResolver(resolverFactory, implementation, includePaths = [])
403
406
  // See https://github.com/webpack/webpack/issues/12340
404
407
  // Because `node-sass` calls our importer before `1. Filesystem imports relative to the base file.`
405
408
  // custom importer may not return `{ file: '/path/to/name.ext' }` and therefore our `context` will be relative
406
- if (!isDartSass && !_path.default.isAbsolute(context)) {
409
+ if (!isModernSass && !_path.default.isAbsolute(context)) {
407
410
  return Promise.reject();
408
411
  }
409
412
  const originalRequest = request;
@@ -441,7 +444,7 @@ function getWebpackResolver(resolverFactory, implementation, includePaths = [])
441
444
  const sassPossibleRequests = getPossibleRequests(request, false, fromImport);
442
445
 
443
446
  // `node-sass` calls our importer before `1. Filesystem imports relative to the base file.`, so we need emulate this too
444
- if (!isDartSass) {
447
+ if (!isModernSass) {
445
448
  resolutionMap = resolutionMap.concat({
446
449
  resolve: fromImport ? sassImportResolve : sassModuleResolve,
447
450
  context: _path.default.dirname(context),
@@ -468,13 +471,57 @@ function getWebpackResolver(resolverFactory, implementation, includePaths = [])
468
471
  };
469
472
  }
470
473
  const MATCH_CSS = /\.css$/i;
471
- function getModernWebpackImporter() {
474
+ function getModernWebpackImporter(loaderContext, implementation, loadPaths) {
475
+ const resolve = getWebpackResolver(loaderContext.getResolve, implementation, loadPaths);
472
476
  return {
473
- async canonicalize() {
474
- return null;
477
+ async canonicalize(originalUrl, context) {
478
+ const {
479
+ fromImport
480
+ } = context;
481
+ const prev = context.containingUrl ? _url.default.fileURLToPath(context.containingUrl.toString()) : loaderContext.resourcePath;
482
+ let result;
483
+ try {
484
+ result = await resolve(prev, originalUrl, fromImport);
485
+ } catch (err) {
486
+ // If no stylesheets are found, the importer should return null.
487
+ return null;
488
+ }
489
+ loaderContext.addDependency(_path.default.normalize(result));
490
+ return _url.default.pathToFileURL(result);
475
491
  },
476
- load() {
477
- // TODO implement
492
+ async load(canonicalUrl) {
493
+ const ext = _path.default.extname(canonicalUrl.pathname);
494
+ let syntax;
495
+ if (ext && ext.toLowerCase() === ".scss") {
496
+ syntax = "scss";
497
+ } else if (ext && ext.toLowerCase() === ".sass") {
498
+ syntax = "indented";
499
+ } else if (ext && ext.toLowerCase() === ".css") {
500
+ syntax = "css";
501
+ } else {
502
+ // Fallback to default value
503
+ syntax = "scss";
504
+ }
505
+ try {
506
+ const contents = await new Promise((resolve, reject) => {
507
+ // Old version of `enhanced-resolve` supports only path as a string
508
+ // TODO simplify in the next major release and pass URL
509
+ const canonicalPath = _url.default.fileURLToPath(canonicalUrl);
510
+ loaderContext.fs.readFile(canonicalPath, "utf8", (err, content) => {
511
+ if (err) {
512
+ reject(err);
513
+ return;
514
+ }
515
+ resolve(content);
516
+ });
517
+ });
518
+ return {
519
+ contents,
520
+ syntax
521
+ };
522
+ } catch (err) {
523
+ return null;
524
+ }
478
525
  }
479
526
  };
480
527
  }
@@ -504,15 +551,17 @@ function getWebpackImporter(loaderContext, implementation, includePaths) {
504
551
  };
505
552
  }
506
553
  let nodeSassJobQueue = null;
554
+ const sassModernCompilers = new WeakMap();
507
555
 
508
556
  /**
509
557
  * Verifies that the implementation and version of Sass is supported by this loader.
510
558
  *
559
+ * @param {Object} loaderContext
511
560
  * @param {Object} implementation
512
561
  * @param {Object} options
513
562
  * @returns {Function}
514
563
  */
515
- function getCompileFn(implementation, options) {
564
+ function getCompileFn(loaderContext, implementation, options) {
516
565
  const isNewSass = implementation.info.includes("dart-sass") || implementation.info.includes("sass-embedded");
517
566
  if (isNewSass) {
518
567
  if (options.api === "modern") {
@@ -524,6 +573,37 @@ function getCompileFn(implementation, options) {
524
573
  return implementation.compileStringAsync(data, rest);
525
574
  };
526
575
  }
576
+ if (options.api === "modern-compiler") {
577
+ return async sassOptions => {
578
+ // eslint-disable-next-line no-underscore-dangle
579
+ const webpackCompiler = loaderContext._compiler;
580
+ const {
581
+ data,
582
+ ...rest
583
+ } = sassOptions;
584
+
585
+ // Some people can run the loader in a multi-threading way;
586
+ // there is no webpack compiler object in such case.
587
+ if (webpackCompiler) {
588
+ if (!sassModernCompilers.has(webpackCompiler)) {
589
+ // Create a long-running compiler process that can be reused
590
+ // for compiling individual files.
591
+ const compiler = await implementation.initAsyncCompiler();
592
+
593
+ // Check again because awaiting the initialization function
594
+ // introduces a race condition.
595
+ if (!sassModernCompilers.has(webpackCompiler)) {
596
+ sassModernCompilers.set(webpackCompiler, compiler);
597
+ webpackCompiler.hooks.shutdown.tap("sass-loader", () => {
598
+ compiler.dispose();
599
+ });
600
+ }
601
+ }
602
+ return sassModernCompilers.get(webpackCompiler).compileStringAsync(data, rest);
603
+ }
604
+ return implementation.compileStringAsync(data, rest);
605
+ };
606
+ }
527
607
  return sassOptions => new Promise((resolve, reject) => {
528
608
  implementation.render(sassOptions, (error, result) => {
529
609
  if (error) {
@@ -534,7 +614,7 @@ function getCompileFn(implementation, options) {
534
614
  });
535
615
  });
536
616
  }
537
- if (options.api === "modern") {
617
+ if (options.api === "modern" || options.api === "modern-compiler") {
538
618
  throw new Error("Modern API is not supported for 'node-sass'");
539
619
  }
540
620
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sass-loader",
3
- "version": "14.1.1",
3
+ "version": "14.2.1",
4
4
  "description": "Sass loader for webpack",
5
5
  "license": "MIT",
6
6
  "repository": "webpack-contrib/sass-loader",
@@ -70,8 +70,8 @@
70
70
  },
71
71
  "devDependencies": {
72
72
  "@babel/cli": "^7.23.4",
73
- "@babel/core": "^7.23.7",
74
- "@babel/preset-env": "^7.23.8",
73
+ "@babel/core": "^7.24.0",
74
+ "@babel/preset-env": "^7.24.0",
75
75
  "@commitlint/cli": "^18.4.4",
76
76
  "@commitlint/config-conventional": "^18.4.4",
77
77
  "@webpack-contrib/eslint-config-webpack": "^3.0.0",
@@ -80,12 +80,12 @@
80
80
  "bootstrap-v4": "npm:bootstrap@^4.5.3",
81
81
  "bootstrap-v5": "npm:bootstrap@^5.0.1",
82
82
  "cross-env": "^7.0.3",
83
- "cspell": "^8.3.2",
83
+ "cspell": "^8.6.0",
84
84
  "css-loader": "^6.9.0",
85
85
  "del": "^6.1.1",
86
86
  "del-cli": "^5.1.0",
87
- "enhanced-resolve": "^5.15.0",
88
- "eslint": "^8.46.0",
87
+ "enhanced-resolve": "^5.15.1",
88
+ "eslint": "^8.57.0",
89
89
  "eslint-config-prettier": "^9.1.0",
90
90
  "eslint-plugin-import": "^2.28.0",
91
91
  "file-loader": "^6.2.0",
@@ -95,17 +95,19 @@
95
95
  "jest-environment-node-single-context": "^29.1.0",
96
96
  "lint-staged": "^15.2.0",
97
97
  "material-components-web": "^9.0.0",
98
- "memfs": "^4.6.0",
98
+ "memfs": "^4.7.7",
99
99
  "node-sass": "^9.0.0",
100
100
  "node-sass-glob-importer": "^5.3.2",
101
101
  "npm-run-all": "^4.1.5",
102
102
  "prettier": "^3.2.2",
103
- "sass": "^1.69.7",
104
- "sass-embedded": "^1.69.7",
103
+ "sass": "^1.71.1",
104
+ "sass-embedded": "^1.71.1",
105
105
  "semver": "^7.5.4",
106
106
  "standard-version": "^9.3.1",
107
107
  "style-loader": "^3.3.4",
108
- "webpack": "^5.88.2"
108
+ "webpack": "^5.88.2",
109
+ "webpack-cli": "^5.1.4",
110
+ "webpack-dev-server": "^5.0.4"
109
111
  },
110
112
  "keywords": [
111
113
  "sass",