Skip to content

Commit

Permalink
Move importsToResolve to dedicated module
Browse files Browse the repository at this point in the history
And refactor imperative code in favor of a functional approach
  • Loading branch information
jhnns committed Dec 26, 2016
1 parent 227a1d3 commit a796ace
Show file tree
Hide file tree
Showing 2 changed files with 51 additions and 61 deletions.
49 changes: 49 additions & 0 deletions lib/importsToResolve.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
"use strict";

const path = require("path");

// libsass uses this precedence when importing files without extension
const extPrecedence = [".scss", ".sass", ".css"];

/**
* When libsass tries to resolve an import, it uses a special algorithm.
* Since the sass-loader uses webpack to resolve the modules, we need to simulate that algorithm. This function
* returns an array of import paths to try.
*
* @param {string} request
* @returns {Array}
*/
function importsToResolve(request) {
// libsass' import algorithm works like this:
// In case there is no file extension...
// - Prefer modules starting with '_'.
// - File extension precedence: .scss, .sass, .css.
// In case there is a file extension...
// - If the file is a CSS-file, do not include it all, but just link it via @import url().
// - The exact file name must match (no auto-resolving of '_'-modules).

// Keep in mind: ext can also be something like '.datepicker' when the true extension is omitted and the filename contains a dot.
// @see https://github.com/jtangelder/sass-loader/issues/167
const ext = path.extname(request);
const basename = path.basename(request);
const dirname = path.dirname(request);
const startsWithUnderscore = basename.charAt(0) === "_";
// a module import is an identifier like 'bootstrap-sass'
// We also need to check for dirname since it might also be a deep import like 'bootstrap-sass/something'
const isModuleImport = request.charAt(0) !== "." && dirname === ".";
const hasCssExt = ext === ".css";
const hasSassExt = ext === ".scss" || ext === ".sass";

return (isModuleImport && [request]) || // Do not modify module imports
(hasCssExt && []) || // Do not import css files
(hasSassExt && [request]) || // Do not modify imports with explicit extensions
(startsWithUnderscore ? [] : extPrecedence) // Do not add underscore imports if there is already an underscore
.map(ext => "_" + basename + ext)
.concat(
extPrecedence.map(ext => basename + ext)
).map(
file => dirname + "/" + file // No path.sep required here, because imports inside SASS are usually with /
);
}

module.exports = importsToResolve;
63 changes: 2 additions & 61 deletions lib/loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,9 @@ const os = require("os");
const async = require("async");
const assign = require("object-assign");
const formatSassError = require("./formatSassError");
const importsToResolve = require("./importsToResolve");

// libsass uses this precedence when importing files without extension
const slice = Array.prototype.slice;
const extPrecedence = [".scss", ".sass", ".css"];
const matchCss = /\.css$/;

// This queue makes sure node-sass leaves one thread available for executing
Expand Down Expand Up @@ -47,7 +46,7 @@ function sassLoader(content) {
const request = utils.urlToRequest(url, sassOptions.root);
const dirContext = fileToDirContext(fileContext);

resolve(dirContext, url, getImportsToResolve(request), done);
resolve(dirContext, url, importsToResolve(request), done);
};
}

Expand Down Expand Up @@ -191,64 +190,6 @@ function sassLoader(content) {
});
}

/**
* When libsass tries to resolve an import, it uses a special algorithm.
* Since the sass-loader uses webpack to resolve the modules, we need to simulate that algorithm. This function
* returns an array of import paths to try.
*
* @param {string} originalImport
* @returns {Array}
*/
function getImportsToResolve(originalImport) {
// libsass' import algorithm works like this:
// In case there is no file extension...
// - Prefer modules starting with '_'.
// - File extension precedence: .scss, .sass, .css.
// In case there is a file extension...
// - If the file is a CSS-file, do not include it all, but just link it via @import url().
// - The exact file name must match (no auto-resolving of '_'-modules).
const ext = path.extname(originalImport);
const basename = path.basename(originalImport);
const dirname = path.dirname(originalImport);
const startsWithUnderscore = basename.charAt(0) === "_";
const paths = [];

function add(file) {
// No path.sep required here, because imports inside SASS are usually with /
paths.push(dirname + "/" + file);
}

if (originalImport.charAt(0) !== ".") {
// If true: originalImport is a module import like 'bootstrap-sass...'
if (dirname === ".") {
// If true: originalImport is just a module import without a path like 'bootstrap-sass'
// In this case we don't do that auto-resolving dance at all.
return [originalImport];
}
}

// We can't just check for ext being defined because ext can also be something like '.datepicker'
// when the true extension is omitted and the filename contains a dot.
// @see https://github.com/jtangelder/sass-loader/issues/167
if (ext === ".css") {
// do not import css files
} else if (ext === ".scss" || ext === ".sass") {
add(basename);
} else {
if (!startsWithUnderscore) {
// Prefer modules starting with '_' first
extPrecedence.forEach((ext) => {
add("_" + basename + ext);
});
}
extPrecedence.forEach((ext) => {
add(basename + ext);
});
}

return paths;
}

/**
* Check the loader query and webpack config for loader options. If an option is defined in both places,
* the loader query takes precedence.
Expand Down

0 comments on commit a796ace

Please sign in to comment.