From d487683221fcd1e5a173e083b4b40644751c8cb1 Mon Sep 17 00:00:00 2001 From: Evilebot Tnawi Date: Mon, 22 Jun 2020 18:35:21 +0300 Subject: [PATCH] feat: pass the loader context to custom importers under `this.webpackLoaderContext` property (#853) --- README.md | 2 + src/getSassOptions.js | 17 ++++++-- src/webpackImporter.js | 10 +++-- .../sassOptions-option.test.js.snap | 40 +++++++++++++++++++ test/sassOptions-option.test.js | 23 +++++++++++ 5 files changed, 85 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 29481f41..af1d0353 100644 --- a/README.md +++ b/README.md @@ -266,6 +266,8 @@ Options for [Dart Sass](http://sass-lang.com/dart-sass) or [Node Sass](https://g > ℹ We recommend not to set the `outFile`, `sourceMapContents`, `sourceMapEmbed`, `sourceMapRoot` options because `sass-loader` automatically sets these options when the `sourceMap` option is `true`. +> ℹ️ Access to the [loader context](https://webpack.js.org/api/loaders/#the-loader-context) inside the custom importer can be done using the `this.webpackLoaderContext` property. + There is a slight difference between the `sass` (`dart-sass`) and `node-sass` options. Please consult documentation before using them: diff --git a/src/getSassOptions.js b/src/getSassOptions.js index 4e403164..c00b3a92 100644 --- a/src/getSassOptions.js +++ b/src/getSassOptions.js @@ -6,6 +6,16 @@ function isProductionLikeMode(loaderContext) { return loaderContext.mode === 'production' || !loaderContext.mode; } +function proxyCustomImporters(importers, loaderContext) { + return [].concat(importers).map((importer) => { + return function proxyImporter(...args) { + this.webpackLoaderContext = loaderContext; + + return importer.apply(this, args); + }; + }); +} + /** * Derives the sass options from the loader context and normalizes its values with sane defaults. * @@ -98,9 +108,10 @@ function getSassOptions(loaderContext, loaderOptions, content, implementation) { // Allow passing custom importers to `sass`/`node-sass`. Accepts `Function` or an array of `Function`s. options.importer = options.importer - ? Array.isArray(options.importer) - ? options.importer - : [options.importer] + ? proxyCustomImporters( + Array.isArray(options.importer) ? options.importer : [options.importer], + loaderContext + ) : []; options.includePaths = [] diff --git a/src/webpackImporter.js b/src/webpackImporter.js index 7e6498b6..36d1e872 100644 --- a/src/webpackImporter.js +++ b/src/webpackImporter.js @@ -11,7 +11,7 @@ import path from 'path'; import getPossibleRequests from './getPossibleRequests'; const matchCss = /\.css$/i; -const isModuleImport = /^~([^/]+|[^/]+\/|@[^/]+[/][^/]+|@[^/]+\/?|@[^/]+[/][^/]+\/)$/; +const isSpecialModuleImport = /^~[^/]+$/; /** * Returns an importer that uses webpack's resolving algorithm. @@ -64,8 +64,6 @@ function webpackImporter(loaderContext, includePaths) { mainFiles: [], modules: [], }); - // TODO avoid resolsing `_index`, `index` and files without extensions - // TODO avoid resolving with multiple extensions - `file.sass.sass`/`file.sass.scss`/`file.sass.css` const webpackResolve = loaderContext.getResolve({ mainFields: ['sass', 'style', 'main', '...'], mainFiles: ['_index', 'index', '...'], @@ -85,7 +83,11 @@ function webpackImporter(loaderContext, includePaths) { let resolutionMap = []; - if (includePaths.length > 0 && !isFileScheme && !isModuleImport.test(url)) { + if ( + includePaths.length > 0 && + !isFileScheme && + !isSpecialModuleImport.test(url) + ) { // The order of import precedence is as follows: // // 1. Filesystem imports relative to the base file. diff --git a/test/__snapshots__/sassOptions-option.test.js.snap b/test/__snapshots__/sassOptions-option.test.js.snap index b279c772..17ad2ad9 100644 --- a/test/__snapshots__/sassOptions-option.test.js.snap +++ b/test/__snapshots__/sassOptions-option.test.js.snap @@ -1758,28 +1758,68 @@ exports[`sassOptions option should work with the "importer" as a array of functi exports[`sassOptions option should work with the "importer" as a single function option (dart-sass) (sass): css 1`] = `""`; +exports[`sassOptions option should work with the "importer" as a single function option (dart-sass) (sass): css 2`] = ` +".a { + color: red; +}" +`; + exports[`sassOptions option should work with the "importer" as a single function option (dart-sass) (sass): errors 1`] = `Array []`; +exports[`sassOptions option should work with the "importer" as a single function option (dart-sass) (sass): errors 2`] = `Array []`; + exports[`sassOptions option should work with the "importer" as a single function option (dart-sass) (sass): warnings 1`] = `Array []`; +exports[`sassOptions option should work with the "importer" as a single function option (dart-sass) (sass): warnings 2`] = `Array []`; + exports[`sassOptions option should work with the "importer" as a single function option (dart-sass) (scss): css 1`] = `""`; +exports[`sassOptions option should work with the "importer" as a single function option (dart-sass) (scss): css 2`] = ` +".a { + color: red; +}" +`; + exports[`sassOptions option should work with the "importer" as a single function option (dart-sass) (scss): errors 1`] = `Array []`; +exports[`sassOptions option should work with the "importer" as a single function option (dart-sass) (scss): errors 2`] = `Array []`; + exports[`sassOptions option should work with the "importer" as a single function option (dart-sass) (scss): warnings 1`] = `Array []`; +exports[`sassOptions option should work with the "importer" as a single function option (dart-sass) (scss): warnings 2`] = `Array []`; + exports[`sassOptions option should work with the "importer" as a single function option (node-sass) (sass): css 1`] = `""`; +exports[`sassOptions option should work with the "importer" as a single function option (node-sass) (sass): css 2`] = ` +".a { + color: red; } +" +`; + exports[`sassOptions option should work with the "importer" as a single function option (node-sass) (sass): errors 1`] = `Array []`; +exports[`sassOptions option should work with the "importer" as a single function option (node-sass) (sass): errors 2`] = `Array []`; + exports[`sassOptions option should work with the "importer" as a single function option (node-sass) (sass): warnings 1`] = `Array []`; +exports[`sassOptions option should work with the "importer" as a single function option (node-sass) (sass): warnings 2`] = `Array []`; + exports[`sassOptions option should work with the "importer" as a single function option (node-sass) (scss): css 1`] = `""`; +exports[`sassOptions option should work with the "importer" as a single function option (node-sass) (scss): css 2`] = ` +".a { + color: red; } +" +`; + exports[`sassOptions option should work with the "importer" as a single function option (node-sass) (scss): errors 1`] = `Array []`; +exports[`sassOptions option should work with the "importer" as a single function option (node-sass) (scss): errors 2`] = `Array []`; + exports[`sassOptions option should work with the "importer" as a single function option (node-sass) (scss): warnings 1`] = `Array []`; +exports[`sassOptions option should work with the "importer" as a single function option (node-sass) (scss): warnings 2`] = `Array []`; + exports[`sassOptions option should work with the "includePaths" option (dart-sass) (sass): css 1`] = ` ".include-path-module { background: hotpink; diff --git a/test/sassOptions-option.test.js b/test/sassOptions-option.test.js index 2bf511ac..6b10874a 100644 --- a/test/sassOptions-option.test.js +++ b/test/sassOptions-option.test.js @@ -208,6 +208,29 @@ describe('sassOptions option', () => { expect(getErrors(stats)).toMatchSnapshot('errors'); }); + it(`should work with the "importer" as a single function option (${implementationName}) (${syntax})`, async () => { + expect.assertions(4); + + const testId = getTestId('custom-importer', syntax); + const options = { + implementation: getImplementationByName(implementationName), + sassOptions: { + importer(url, prev, done) { + expect(this.webpackLoaderContext).toBeDefined(); + + return done({ contents: '.a { color: red; }' }); + }, + }, + }; + const compiler = getCompiler(testId, { loader: { options } }); + const stats = await compile(compiler); + const codeFromBundle = getCodeFromBundle(stats, compiler); + + expect(codeFromBundle.css).toMatchSnapshot('css'); + expect(getWarnings(stats)).toMatchSnapshot('warnings'); + expect(getErrors(stats)).toMatchSnapshot('errors'); + }); + it(`should work with the "includePaths" option (${implementationName}) (${syntax})`, async () => { const testId = getTestId('import-include-paths', syntax); const options = {