diff --git a/lib/importsToResolve.js b/lib/importsToResolve.js index 52dbab4f..ce62d588 100644 --- a/lib/importsToResolve.js +++ b/lib/importsToResolve.js @@ -11,7 +11,7 @@ const extPrecedence = [".scss", ".sass", ".css"]; * returns an array of import paths to try. * * @param {string} request - * @returns {Array} + * @returns {Array} */ function importsToResolve(request) { // libsass' import algorithm works like this: diff --git a/lib/loader.js b/lib/loader.js index 2925a0eb..604fa2da 100644 --- a/lib/loader.js +++ b/lib/loader.js @@ -9,6 +9,7 @@ const assign = require("object-assign"); const formatSassError = require("./formatSassError"); const proxyCustomImporters = require("./proxyCustomImporters"); const webpackImporter = require("./webpackImporter"); +const pify = require("pify"); // This queue makes sure node-sass leaves one thread available for executing // fs tasks when running the custom importer code. @@ -92,7 +93,7 @@ function sassLoader(content) { sassOptions.importer = sassOptions.importer ? proxyCustomImporters(sassOptions.importer, resourcePath) : []; sassOptions.importer.push(webpackImporter( this.resourcePath, - this.resolve.bind(this), + pify(this.resolve.bind(this)), addNormalizedDependency )); diff --git a/lib/webpackImporter.js b/lib/webpackImporter.js index 6444df22..44cbdfe8 100644 --- a/lib/webpackImporter.js +++ b/lib/webpackImporter.js @@ -1,7 +1,24 @@ "use strict"; +/** + * @name PromisedResolve + * @type {Function} + * @param {string} dir + * @param {string} request + * @returns Promise + */ + +/** + * @name Importer + * @type {Function} + * @param {string} url + * @param {string} prev + * @param {Function} done + */ + const path = require("path"); const utils = require("loader-utils"); +const tail = require("lodash.tail"); const importsToResolve = require("./importsToResolve"); const matchCss = /\.css$/; @@ -13,51 +30,11 @@ const matchCss = /\.css$/; * (based on whether the call is sync or async) because otherwise node-sass doesn't exit. * * @param {string} resourcePath - * @param {Function>} loaderResolve + * @param {PromisedResolve} resolve * @param {Function} addNormalizedDependency - * @returns {Function} + * @returns {Importer} */ -function webpackImporter(resourcePath, loaderResolve, addNormalizedDependency) { - /** - * Tries to resolve the first url of importsToResolve. If that resolve fails, the next url is tried. - * If all imports fail, the import is passed to libsass which also take includePaths into account. - * - * @param {string} dirContext - * @param {string} originalImport - * @param {Array} importsToResolve - * @param {Function} done - */ - function resolve(dirContext, originalImport, importsToResolve, done) { - const importToResolve = importsToResolve.shift(); - - if (!importToResolve) { - // No import possibilities left. Let's pass that one back to libsass... - done({ - file: originalImport - }); - return; - } - - loaderResolve(dirContext, importToResolve, (err, resolvedFilename) => { - if (err) { - resolve(dirContext, originalImport, importsToResolve, done); - return; - } - // Add the resolvedFilename as dependency. Although we're also using stats.includedFiles, this might come - // in handy when an error occurs. In this case, we don't get stats.includedFiles from node-sass. - addNormalizedDependency(resolvedFilename); - // By removing the CSS file extension, we trigger node-sass to include the CSS file instead of just linking it. - resolvedFilename = resolvedFilename.replace(matchCss, ""); - - // Use self.loadModule() before calling done() to make imported files available to - // other webpack tools like postLoaders etc.? - - done({ - file: resolvedFilename.replace(matchCss, "") - }); - }); - } - +function webpackImporter(resourcePath, resolve, addNormalizedDependency) { function dirContextFrom(fileContext) { return path.dirname( // The first file is 'stdin' when we're using the data option @@ -65,16 +42,31 @@ function webpackImporter(resourcePath, loaderResolve, addNormalizedDependency) { ); } + function startResolving(dir, importsToResolve) { + return importsToResolve.length === 0 ? + Promise.reject() : + resolve(dir, importsToResolve[0]) + .then(resolvedFile => { + // Add the resolvedFilename as dependency. Although we're also using stats.includedFiles, this might come + // in handy when an error occurs. In this case, we don't get stats.includedFiles from node-sass. + addNormalizedDependency(resolvedFile); + return { + // By removing the CSS file extension, we trigger node-sass to include the CSS file instead of just linking it. + file: resolvedFile.replace(matchCss, "") + }; + }, () => startResolving( + dir, + tail(importsToResolve) + )); + } + return (url, prev, done) => { - resolve( - dirContextFrom( - // node-sass returns UNIX-style paths - path.normalize(prev) - ), - url, - importsToResolve(utils.urlToRequest(url)), - done - ); + startResolving( + dirContextFrom(prev), + importsToResolve(utils.urlToRequest(url)) + ) // Catch all resolving errors, return the original file and pass responsibility back to other custom importers + .catch(() => ({ file: url })) + .then(done); }; } diff --git a/package.json b/package.json index 0fc42871..18249ab2 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,9 @@ "dependencies": { "async": "^2.0.1", "loader-utils": "^0.2.15", - "object-assign": "^4.1.0" + "lodash.tail": "^4.1.1", + "object-assign": "^4.1.0", + "pify": "^2.3.0" }, "devDependencies": { "bootstrap-sass": "^3.3.5",