diff --git a/src/utils.js b/src/utils.js index 395777d5..c6bbfb2a 100644 --- a/src/utils.js +++ b/src/utils.js @@ -358,7 +358,7 @@ function getWebpackResolver( } const isDartSass = implementation.info.includes("dart-sass"); - const sassResolve = promiseResolve( + const sassModuleResolve = promiseResolve( resolverFactory({ alias: [], aliasFields: [], @@ -373,7 +373,22 @@ function getWebpackResolver( preferRelative: true, }) ); - const webpackResolve = promiseResolve( + const sassImportResolve = promiseResolve( + resolverFactory({ + alias: [], + aliasFields: [], + conditionNames: [], + descriptionFiles: [], + extensions: [".sass", ".scss", ".css"], + exportsFields: [], + mainFields: [], + mainFiles: ["_index.import", "_index", "index.import", "index"], + modules: [], + restrictions: [/\.((sa|sc|c)ss)$/i], + preferRelative: true, + }) + ); + const webpackModuleResolve = promiseResolve( resolverFactory({ dependencyType: "sass", conditionNames: ["sass", "style"], @@ -384,8 +399,19 @@ function getWebpackResolver( preferRelative: true, }) ); + const webpackImportResolve = promiseResolve( + resolverFactory({ + dependencyType: "sass", + conditionNames: ["sass", "style"], + mainFields: ["sass", "style", "main", "..."], + mainFiles: ["_index.import", "_index", "index.import", "index", "..."], + extensions: [".sass", ".scss", ".css"], + restrictions: [/\.((sa|sc|c)ss)$/i], + preferRelative: true, + }) + ); - return (context, request) => { + return (context, request, fromImport) => { // See https://github.com/webpack/webpack/issues/12340 // Because `node-sass` calls our importer before `1. Filesystem imports relative to the base file.` // custom importer may not return `{ file: '/path/to/name.ext' }` and therefore our `context` will be relative @@ -434,7 +460,7 @@ function getWebpackResolver( // `node-sass` calls our importer before `1. Filesystem imports relative to the base file.`, so we need emulate this too if (!isDartSass) { resolutionMap = resolutionMap.concat({ - resolve: sassResolve, + resolve: fromImport ? sassImportResolve : sassModuleResolve, context: path.dirname(context), possibleRequests: sassPossibleRequests, }); @@ -444,7 +470,7 @@ function getWebpackResolver( // eslint-disable-next-line no-shadow includePaths.map((context) => { return { - resolve: sassResolve, + resolve: fromImport ? sassImportResolve : sassModuleResolve, context, possibleRequests: sassPossibleRequests, }; @@ -455,7 +481,7 @@ function getWebpackResolver( const webpackPossibleRequests = getPossibleRequests(request, true); resolutionMap = resolutionMap.concat({ - resolve: webpackResolve, + resolve: fromImport ? webpackImportResolve : webpackModuleResolve, context: path.dirname(context), possibleRequests: webpackPossibleRequests, }); @@ -464,7 +490,7 @@ function getWebpackResolver( }; } -const matchCss = /\.css$/i; +const MATCH_CSS = /\.css$/i; function getWebpackImporter(loaderContext, implementation, includePaths) { const resolve = getWebpackResolver( @@ -473,8 +499,10 @@ function getWebpackImporter(loaderContext, implementation, includePaths) { includePaths ); - return (originalUrl, prev, done) => { - resolve(prev, originalUrl) + return function importer(originalUrl, prev, done) { + const { fromImport } = this; + + resolve(prev, originalUrl, fromImport) .then((result) => { // Add the result as dependency. // Although we're also using stats.includedFiles, this might come in handy when an error occurs. @@ -482,7 +510,7 @@ function getWebpackImporter(loaderContext, implementation, includePaths) { loaderContext.addDependency(path.normalize(result)); // By removing the CSS file extension, we trigger node-sass to include the CSS file instead of just linking it. - done({ file: result.replace(matchCss, "") }); + done({ file: result.replace(MATCH_CSS, "") }); }) // Catch all resolving errors, return the original file and pass responsibility back to other custom importers .catch(() => { diff --git a/test/__snapshots__/loader.test.js.snap b/test/__snapshots__/loader.test.js.snap index 78a2fb92..ef894527 100644 --- a/test/__snapshots__/loader.test.js.snap +++ b/test/__snapshots__/loader.test.js.snap @@ -1,5 +1,45 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`loader should import .import.sass files (dart-sass) (sass): css 1`] = ` +"a { + display: block; +}" +`; + +exports[`loader should import .import.sass files (dart-sass) (sass): errors 1`] = `Array []`; + +exports[`loader should import .import.sass files (dart-sass) (sass): warnings 1`] = `Array []`; + +exports[`loader should import .import.sass files from a package (dart-sass) (sass): css 1`] = ` +"a { + display: block; +}" +`; + +exports[`loader should import .import.sass files from a package (dart-sass) (sass): errors 1`] = `Array []`; + +exports[`loader should import .import.sass files from a package (dart-sass) (sass): warnings 1`] = `Array []`; + +exports[`loader should import .import.scss files (dart-sass) (scss): css 1`] = ` +"a { + display: block; +}" +`; + +exports[`loader should import .import.scss files (dart-sass) (scss): errors 1`] = `Array []`; + +exports[`loader should import .import.scss files (dart-sass) (scss): warnings 1`] = `Array []`; + +exports[`loader should import .import.scss files from a package (dart-sass) (scss): css 1`] = ` +"a { + display: block; +}" +`; + +exports[`loader should import .import.scss files from a package (dart-sass) (scss): errors 1`] = `Array []`; + +exports[`loader should import .import.scss files from a package (dart-sass) (scss): warnings 1`] = `Array []`; + exports[`loader should load files with underscore in the name (dart-sass) (sass): css 1`] = ` "a { color: red; @@ -80,6 +120,24 @@ exports[`loader should load only sass/scss files for the "mainFiles" (node-sass) exports[`loader should load only sass/scss files for the "mainFiles" (node-sass) (scss): warnings 1`] = `Array []`; +exports[`loader should not use .import.sass files (dart-sass) (sass): errors 1`] = ` +Array [ + "ModuleBuildError: Module build failed (from ../src/cjs.js): +SassError: Can't find stylesheet to import.", +] +`; + +exports[`loader should not use .import.sass files (dart-sass) (sass): warnings 1`] = `Array []`; + +exports[`loader should not use .import.scss files (dart-sass) (scss): errors 1`] = ` +Array [ + "ModuleBuildError: Module build failed (from ../src/cjs.js): +SassError: Can't find stylesheet to import.", +] +`; + +exports[`loader should not use .import.scss files (dart-sass) (scss): warnings 1`] = `Array []`; + exports[`loader should output an understandable error (dart-sass) (sass): errors 1`] = ` Array [ "ModuleBuildError: Module build failed (from ../src/cjs.js): diff --git a/test/loader.test.js b/test/loader.test.js index 5ab6dc6f..91713da2 100644 --- a/test/loader.test.js +++ b/test/loader.test.js @@ -1599,6 +1599,50 @@ describe("loader", () => { expect(getErrors(stats)).toMatchSnapshot("errors"); }); + it(`should import .import.${syntax} files (${implementationName}) (${syntax})`, async () => { + const testId = getTestId("import-index-import", syntax); + const options = { + implementation: getImplementationByName(implementationName), + }; + const compiler = getCompiler(testId, { loader: { options } }); + const stats = await compile(compiler); + const codeFromBundle = getCodeFromBundle(stats, compiler); + const codeFromSass = getCodeFromSass(testId, options); + + expect(codeFromBundle.css).toBe(codeFromSass.css); + expect(codeFromBundle.css).toMatchSnapshot("css"); + expect(getWarnings(stats)).toMatchSnapshot("warnings"); + expect(getErrors(stats)).toMatchSnapshot("errors"); + }); + + it(`should import .import.${syntax} files from a package (${implementationName}) (${syntax})`, async () => { + const testId = getTestId("import-index-import-from-package", syntax); + const options = { + implementation: getImplementationByName(implementationName), + }; + const compiler = getCompiler(testId, { loader: { options } }); + const stats = await compile(compiler); + const codeFromBundle = getCodeFromBundle(stats, compiler); + const codeFromSass = getCodeFromSass(testId, options); + + expect(codeFromBundle.css).toBe(codeFromSass.css); + expect(codeFromBundle.css).toMatchSnapshot("css"); + expect(getWarnings(stats)).toMatchSnapshot("warnings"); + expect(getErrors(stats)).toMatchSnapshot("errors"); + }); + + it(`should not use .import.${syntax} files (${implementationName}) (${syntax})`, async () => { + const testId = getTestId("use-index-import", syntax); + const options = { + implementation: getImplementationByName(implementationName), + }; + const compiler = getCompiler(testId, { loader: { options } }); + const stats = await compile(compiler); + + expect(getWarnings(stats)).toMatchSnapshot("warnings"); + expect(getErrors(stats)).toMatchSnapshot("errors"); + }); + it(`should work and output deprecation message (${implementationName})`, async () => { const testId = getTestId("deprecation", syntax); const options = { diff --git a/test/node_modules/index-import-package/_index.import.scss b/test/node_modules/index-import-package/_index.import.scss new file mode 100644 index 00000000..d1308905 --- /dev/null +++ b/test/node_modules/index-import-package/_index.import.scss @@ -0,0 +1,3 @@ +a { + display: block; +} diff --git a/test/node_modules/index-import-package/package.json b/test/node_modules/index-import-package/package.json new file mode 100644 index 00000000..96e57e0a --- /dev/null +++ b/test/node_modules/index-import-package/package.json @@ -0,0 +1,11 @@ +{ + "name": "index-import", + "version": "1.0.0", + "description": "test", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "MIT" +} \ No newline at end of file diff --git a/test/sass/import-index-import-from-package.sass b/test/sass/import-index-import-from-package.sass new file mode 100644 index 00000000..eabfda99 --- /dev/null +++ b/test/sass/import-index-import-from-package.sass @@ -0,0 +1 @@ +@import '~index-import-package' diff --git a/test/sass/import-index-import.sass b/test/sass/import-index-import.sass new file mode 100644 index 00000000..ceae2016 --- /dev/null +++ b/test/sass/import-index-import.sass @@ -0,0 +1 @@ +@import 'index-import' diff --git a/test/sass/index-import/_index.import.sass b/test/sass/index-import/_index.import.sass new file mode 100644 index 00000000..15c710df --- /dev/null +++ b/test/sass/index-import/_index.import.sass @@ -0,0 +1,2 @@ +a + display: block \ No newline at end of file diff --git a/test/sass/use-index-import.sass b/test/sass/use-index-import.sass new file mode 100644 index 00000000..4823f286 --- /dev/null +++ b/test/sass/use-index-import.sass @@ -0,0 +1 @@ +@use 'index-import' diff --git a/test/scss/import-index-import-from-package.scss b/test/scss/import-index-import-from-package.scss new file mode 100644 index 00000000..32a5f5cf --- /dev/null +++ b/test/scss/import-index-import-from-package.scss @@ -0,0 +1 @@ +@import '~index-import-package'; diff --git a/test/scss/import-index-import.scss b/test/scss/import-index-import.scss new file mode 100644 index 00000000..57332724 --- /dev/null +++ b/test/scss/import-index-import.scss @@ -0,0 +1 @@ +@import 'index-import'; diff --git a/test/scss/index-import/_index.import.scss b/test/scss/index-import/_index.import.scss new file mode 100644 index 00000000..4ad5620f --- /dev/null +++ b/test/scss/index-import/_index.import.scss @@ -0,0 +1,3 @@ +a { + display: block; +} \ No newline at end of file diff --git a/test/scss/use-index-import.scss b/test/scss/use-index-import.scss new file mode 100644 index 00000000..01554558 --- /dev/null +++ b/test/scss/use-index-import.scss @@ -0,0 +1 @@ +@use 'index-import';