diff --git a/README.md b/README.md index 1227e456..78b2459a 100644 --- a/README.md +++ b/README.md @@ -683,7 +683,7 @@ Type: type api = "legacy" | "modern" | "modern-compiler"; ``` -Default: `"legacy"` +Default: `"modern"` for `sass` (`dart-sass`) and `sass-embedded` or `"legacy"` for `node-sass` Allows you to switch between the `legacy` and `modern` APIs. 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). @@ -709,7 +709,7 @@ module.exports = { { loader: "sass-loader", options: { - api: "modern", + api: "modern-compiler", sassOptions: { // Your sass options }, diff --git a/src/index.js b/src/index.js index 9d270560..7d1c16c4 100644 --- a/src/index.js +++ b/src/index.js @@ -34,21 +34,29 @@ async function loader(content) { const useSourceMap = typeof options.sourceMap === "boolean" ? options.sourceMap : this.sourceMap; + // Use `legacy` for `node-sass` and `modern` for `dart-sass` and `sass-embedded` + const apiType = + typeof implementation.compileStringAsync === "undefined" + ? "legacy" + : typeof options.api === "undefined" + ? "modern" + : options.api; const sassOptions = await getSassOptions( this, options, content, implementation, useSourceMap, + apiType, ); + const shouldUseWebpackImporter = typeof options.webpackImporter === "boolean" ? options.webpackImporter : true; if (shouldUseWebpackImporter) { - const isModernAPI = - options.api === "modern" || options.api === "modern-compiler"; + const isModernAPI = apiType === "modern" || apiType === "modern-compiler"; if (!isModernAPI) { const { includePaths } = sassOptions; @@ -67,7 +75,7 @@ async function loader(content) { let compile; try { - compile = getCompileFn(this, implementation, options); + compile = getCompileFn(this, implementation, apiType); } catch (error) { callback(error); return; diff --git a/src/utils.js b/src/utils.js index 41968374..9912b9a9 100644 --- a/src/utils.js +++ b/src/utils.js @@ -94,6 +94,7 @@ function proxyCustomImporters(importers, loaderContext) { * @param {string} content * @param {object} implementation * @param {boolean} useSourceMap + * @param {"legacy" | "modern" | "modern-compiler"} apiType * @returns {Object} */ async function getSassOptions( @@ -102,6 +103,7 @@ async function getSassOptions( content, implementation, useSourceMap, + apiType, ) { const options = loaderOptions.sassOptions ? typeof loaderOptions.sassOptions === "function" @@ -174,8 +176,7 @@ async function getSassOptions( }; } - const isModernAPI = - loaderOptions.api === "modern" || loaderOptions.api === "modern-compiler"; + const isModernAPI = apiType === "modern" || apiType === "modern-compiler"; const { resourcePath } = loaderContext; if (isModernAPI) { @@ -477,9 +478,7 @@ function getWebpackResolver( includePaths = [], ) { const isModernSass = - implementation && - (implementation.info.includes("dart-sass") || - implementation.info.includes("sass-embedded")); + implementation && typeof implementation.compileStringAsync !== "undefined"; // We only have one difference with the built-in sass resolution logic and out resolution logic: // First, we look at the files starting with `_`, then without `_` (i.e. `_name.sass`, `_name.scss`, `_name.css`, `name.sass`, `name.scss`, `name.css`), // although `sass` look together by extensions (i.e. `_name.sass`/`name.sass`/`_name.scss`/`name.scss`/`_name.css`/`name.css`). @@ -735,16 +734,12 @@ const sassModernCompilers = new WeakMap(); * * @param {Object} loaderContext * @param {Object} implementation - * @param {Object} options + * @param {"legacy" | "modern" | "modern-compiler"} apiType * @returns {Function} */ -function getCompileFn(loaderContext, implementation, options) { - const isNewSass = - implementation.info.includes("dart-sass") || - implementation.info.includes("sass-embedded"); - - if (isNewSass) { - if (options.api === "modern") { +function getCompileFn(loaderContext, implementation, apiType) { + if (typeof implementation.compileStringAsync !== "undefined") { + if (apiType === "modern") { return (sassOptions) => { const { data, ...rest } = sassOptions; @@ -752,7 +747,7 @@ function getCompileFn(loaderContext, implementation, options) { }; } - if (options.api === "modern-compiler") { + if (apiType === "modern-compiler") { return async (sassOptions) => { // eslint-disable-next-line no-underscore-dangle const webpackCompiler = loaderContext._compiler; @@ -799,7 +794,7 @@ function getCompileFn(loaderContext, implementation, options) { }); } - if (options.api === "modern" || options.api === "modern-compiler") { + if (apiType === "modern" || apiType === "modern-compiler") { throw new Error("Modern API is not supported for 'node-sass'"); } diff --git a/test/__snapshots__/implementation-option.test.js.no-node-sass.snap b/test/__snapshots__/implementation-option.test.js.no-node-sass.snap index 96cfe4c3..68f8787b 100644 --- a/test/__snapshots__/implementation-option.test.js.no-node-sass.snap +++ b/test/__snapshots__/implementation-option.test.js.no-node-sass.snap @@ -40,6 +40,10 @@ exports[`implementation option not specify with modern-compiler API: errors 1`] exports[`implementation option not specify with modern-compiler API: warnings 1`] = `[]`; +exports[`implementation option not specify with node-sass: errors 1`] = `[]`; + +exports[`implementation option not specify with node-sass: warnings 1`] = `[]`; + exports[`implementation option not specify: errors 1`] = `[]`; exports[`implementation option not specify: warnings 1`] = `[]`; diff --git a/test/__snapshots__/implementation-option.test.js.snap b/test/__snapshots__/implementation-option.test.js.snap index 2b458aec..21d95e88 100644 --- a/test/__snapshots__/implementation-option.test.js.snap +++ b/test/__snapshots__/implementation-option.test.js.snap @@ -44,6 +44,10 @@ exports[`implementation option not specify with modern-compiler API: errors 1`] exports[`implementation option not specify with modern-compiler API: warnings 1`] = `[]`; +exports[`implementation option not specify with node-sass: errors 1`] = `[]`; + +exports[`implementation option not specify with node-sass: warnings 1`] = `[]`; + exports[`implementation option not specify: errors 1`] = `[]`; exports[`implementation option not specify: warnings 1`] = `[]`; diff --git a/test/implementation-option.test.js b/test/implementation-option.test.js index 37fd9446..ba11835c 100644 --- a/test/implementation-option.test.js +++ b/test/implementation-option.test.js @@ -185,13 +185,49 @@ describe("implementation option", () => { expect(getWarnings(stats)).toMatchSnapshot("warnings"); expect(getErrors(stats)).toMatchSnapshot("errors"); - expect(sassEmbeddedSpy).toHaveBeenCalledTimes(1); + expect(sassEmbeddedSpy).toHaveBeenCalledTimes(0); + expect(sassEmbeddedSpyModernAPI).toHaveBeenCalledTimes(1); expect(nodeSassSpy).toHaveBeenCalledTimes(0); expect(dartSassSpy).toHaveBeenCalledTimes(0); + expect(dartSassSpyModernAPI).toHaveBeenCalledTimes(0); sassEmbeddedSpy.mockClear(); + sassEmbeddedSpyModernAPI.mockClear(); nodeSassSpy.mockClear(); dartSassSpy.mockClear(); + dartSassSpyModernAPI.mockClear(); + + await close(compiler); + }); + + it("not specify with node-sass", async () => { + const testId = getTestId("language", "scss"); + const options = { + implementation: nodeSass, + }; + const compiler = getCompiler(testId, { loader: { options } }); + const stats = await compile(compiler); + const { css, sourceMap } = getCodeFromBundle(stats, compiler); + + expect(css).toBeDefined(); + expect(sourceMap).toBeUndefined(); + + expect(getWarnings(stats)).toMatchSnapshot("warnings"); + expect(getErrors(stats)).toMatchSnapshot("errors"); + + expect(sassEmbeddedSpy).toHaveBeenCalledTimes(0); + expect(sassEmbeddedSpyModernAPI).toHaveBeenCalledTimes(0); + expect(nodeSassSpy).toHaveBeenCalledTimes(isNodeSassSupported() ? 1 : 0); + expect(dartSassSpy).toHaveBeenCalledTimes(0); + expect(dartSassSpyModernAPI).toHaveBeenCalledTimes( + isNodeSassSupported() ? 0 : 1, + ); + + sassEmbeddedSpy.mockClear(); + sassEmbeddedSpyModernAPI.mockClear(); + nodeSassSpy.mockClear(); + dartSassSpy.mockClear(); + dartSassSpyModernAPI.mockClear(); await close(compiler); }); @@ -216,8 +252,10 @@ describe("implementation option", () => { expect(dartSassSpy).toHaveBeenCalledTimes(0); sassEmbeddedSpy.mockClear(); + sassEmbeddedSpyModernAPI.mockClear(); nodeSassSpy.mockClear(); dartSassSpy.mockClear(); + dartSassSpyModernAPI.mockClear(); await close(compiler); }); @@ -241,8 +279,10 @@ describe("implementation option", () => { expect(nodeSassSpy).toHaveBeenCalledTimes(0); expect(dartSassSpyModernAPI).toHaveBeenCalledTimes(0); + sassEmbeddedSpy.mockClear(); sassEmbeddedSpyModernAPI.mockClear(); nodeSassSpy.mockClear(); + dartSassSpy.mockClear(); dartSassSpyModernAPI.mockClear(); await close(compiler); @@ -269,9 +309,11 @@ describe("implementation option", () => { expect(dartSassSpyModernAPI).toHaveBeenCalledTimes(0); expect(dartSassCompilerSpies.compileStringSpy).toHaveBeenCalledTimes(0); + sassEmbeddedSpy.mockClear(); + sassEmbeddedSpyModernAPI.mockClear(); nodeSassSpy.mockClear(); + dartSassSpy.mockClear(); dartSassSpyModernAPI.mockClear(); - dartSassCompilerSpies.mockClear(); await close(compiler); });