Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).

Expand All @@ -709,7 +709,7 @@ module.exports = {
{
loader: "sass-loader",
options: {
api: "modern",
api: "modern-compiler",
sassOptions: {
// Your sass options
},
Expand Down
14 changes: 11 additions & 3 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down
25 changes: 10 additions & 15 deletions src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -102,6 +103,7 @@ async function getSassOptions(
content,
implementation,
useSourceMap,
apiType,
) {
const options = loaderOptions.sassOptions
? typeof loaderOptions.sassOptions === "function"
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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`).
Expand Down Expand Up @@ -735,24 +734,20 @@ 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;

return implementation.compileStringAsync(data, rest);
};
}

if (options.api === "modern-compiler") {
if (apiType === "modern-compiler") {
return async (sassOptions) => {
// eslint-disable-next-line no-underscore-dangle
const webpackCompiler = loaderContext._compiler;
Expand Down Expand Up @@ -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'");
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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`] = `[]`;
Expand Down
4 changes: 4 additions & 0 deletions test/__snapshots__/implementation-option.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -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`] = `[]`;
Expand Down
46 changes: 44 additions & 2 deletions test/implementation-option.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
Expand All @@ -216,8 +252,10 @@ describe("implementation option", () => {
expect(dartSassSpy).toHaveBeenCalledTimes(0);

sassEmbeddedSpy.mockClear();
sassEmbeddedSpyModernAPI.mockClear();
nodeSassSpy.mockClear();
dartSassSpy.mockClear();
dartSassSpyModernAPI.mockClear();

await close(compiler);
});
Expand All @@ -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);
Expand All @@ -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);
});
Expand Down