Skip to content

Commit

Permalink
feat: pass the loader context to custom importers under `this.webpack…
Browse files Browse the repository at this point in the history
…LoaderContext` property (#853)
  • Loading branch information
evilebottnawi committed Jun 22, 2020
1 parent b3ffd5b commit d487683
Show file tree
Hide file tree
Showing 5 changed files with 85 additions and 7 deletions.
2 changes: 2 additions & 0 deletions README.md
Expand Up @@ -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:
Expand Down
17 changes: 14 additions & 3 deletions src/getSassOptions.js
Expand Up @@ -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.
*
Expand Down Expand Up @@ -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 = []
Expand Down
10 changes: 6 additions & 4 deletions src/webpackImporter.js
Expand Up @@ -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.
Expand Down Expand Up @@ -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', '...'],
Expand All @@ -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.
Expand Down
40 changes: 40 additions & 0 deletions test/__snapshots__/sassOptions-option.test.js.snap
Expand Up @@ -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;
Expand Down
23 changes: 23 additions & 0 deletions test/sassOptions-option.test.js
Expand Up @@ -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 = {
Expand Down

0 comments on commit d487683

Please sign in to comment.