Skip to content

Commit

Permalink
Refactor path resolving algorithm
Browse files Browse the repository at this point in the history
#138

New algorithm:

- Imports with no file extension:
  - Prefer modules starting with '_'
  - File extension precedence: .scss, .sass, .css
- Imports with file extension:
  - If the file is a CSS-file, do not include it all
  - The exact file name must match

This is how node-sass does it.
  • Loading branch information
jhnns committed Aug 4, 2015
1 parent aceaf03 commit 80944cc
Showing 1 changed file with 95 additions and 102 deletions.
197 changes: 95 additions & 102 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ var SassError = {
file: 'stdin',
status: 1
};
var resolveError = /Cannot resolve/;

var extPrecedence = ['.scss', '.sass', '.css'];
var matchCss = /\.css$/;

/**
* The sass-loader makes node-sass available to webpack modules.
Expand All @@ -27,12 +29,8 @@ module.exports = function (content) {
var isSync = typeof callback !== 'function';
var self = this;
var resourcePath = this.resourcePath;
var extensionMatcher = /\.(sass|scss|css)$/;
var result;
var fileExt;
var opt;
var contextMatch;
var extension;

/**
* Enhances the sass error with additional information about what actually went wrong.
Expand Down Expand Up @@ -70,31 +68,82 @@ module.exports = function (content) {
function getWebpackImporter() {
if (isSync) {
return function syncWebpackImporter(url, context) {
url = urlToRequest(url, context);
url = utils.urlToRequest(url, opt.root);
context = normalizeContext(context);

return syncResolve(self, url, context);
return syncResolve(context, url, getImportsToResolve(url));
};
}
return function asyncWebpackImporter(url, context, done) {
url = urlToRequest(url, context);
url = utils.urlToRequest(url, opt.root);
context = normalizeContext(context);

asyncResolve(self, url, context, done);
asyncResolve(context, url, getImportsToResolve(url), done);
};
}

function urlToRequest(url, context) {
contextMatch = context.match(extensionMatcher);
/**
* Tries to resolve the given url synchronously. If a resolve error occurs, a second try for the same
* module prefixed with an underscore is started.
*
* @param {object} loaderContext
* @param {string} url
//* @param {string} context
* @returns {object}
*/
function syncResolve(fileContext, originalImport, importsToResolve) {
var importToResolve = importsToResolve.shift();
var resolvedFilename;

// Add sass/scss/css extension if it is missing
// The extension is inherited from importing resource or the default is used
if (!url.match(extensionMatcher)) {
extension = contextMatch && contextMatch[0] || fileExt;
url = url + extension;
if (!importToResolve) {
return {
file: originalImport
};
}

return utils.urlToRequest(url, opt.root);
try {
resolvedFilename = self.resolveSync(fileContext, importToResolve);
resolvedFilename = resolvedFilename.replace(matchCss, '');
return {
file: resolvedFilename
};
} catch (err) {
return syncResolve(fileContext, originalImport, importsToResolve);
}
}

/**
* Tries to resolve the given url asynchronously. If a resolve error occurs, a second try for the same
* module prefixed with an underscore is started.
*
* @param {object} loaderContext
* @param {string} url
* @param {string} fileContext
* @param {function} done
*/
function asyncResolve(fileContext, originalImport, importsToResolve, done) {
var importToResolve = importsToResolve.shift();

if (!importToResolve) {
done({
file: originalImport
});
return;
}

self.resolve(fileContext, importToResolve, function onWebpackResolve(err, resolvedFilename) {
if (err) {
asyncResolve(fileContext, originalImport, importsToResolve, done);
return;
}
// Use self.loadModule() before calling done() to make imported files available to
// other webpack tools like postLoaders etc.?

resolvedFilename = resolvedFilename.replace(matchCss, '');
done({
file: resolvedFilename.replace(matchCss, '')
});
});
}

function normalizeContext(context) {
Expand Down Expand Up @@ -145,7 +194,6 @@ module.exports = function (content) {

// indentedSyntax is a boolean flag
opt.indentedSyntax = Boolean(opt.indentedSyntax);
fileExt = '.' + (opt.indentedSyntax? 'sass' : 'scss');

// opt.importer
opt.importer = getWebpackImporter();
Expand Down Expand Up @@ -208,94 +256,39 @@ function getFileExcerptIfPossible(err) {
}
}

/**
* Tries to resolve the given url synchronously. If a resolve error occurs, a second try for the same
* module prefixed with an underscore is started.
*
* @param {object} loaderContext
* @param {string} url
* @param {string} context
* @returns {object}
*/
function syncResolve(loaderContext, url, context) {
var filename;
var basename;

try {
filename = loaderContext.resolveSync(context, url);
} catch (err) {
basename = path.basename(url);
if (requiresLookupForUnderscoreModule(err, basename)) {
url = addUnderscoreToBasename(url, basename);
return syncResolve(loaderContext, url, context);
}
function getImportsToResolve(originalImport) {
var ext = path.extname(originalImport);
var basename = path.basename(originalImport);
var dirname = path.dirname(originalImport);
var startsWithUnderscore = basename.charAt(0) === '_';
var paths = [];

// let the libsass do the rest job, e.g. search module in includePaths
filename = path.join(path.dirname(url), removeUnderscoreFromBasename(basename));
function add(file) {
paths.push(dirname + path.sep + file);
}

return {
file: filename
};
}

/**
* Tries to resolve the given url asynchronously. If a resolve error occurs, a second try for the same
* module prefixed with an underscore is started.
*
* @param {object} loaderContext
* @param {string} url
* @param {string} context
* @param {function} done
*/
function asyncResolve(loaderContext, url, context, done) {
loaderContext.resolve(context, url, function onWebpackResolve(err, filename) {
var basename;

if (err) {
basename = path.basename(url);
if (requiresLookupForUnderscoreModule(err, basename)) {
url = addUnderscoreToBasename(url, basename);
return asyncResolve(loaderContext, url, context, done);
}

// Let libsass do the rest of the job, like searching for the module in includePaths
filename = path.join(path.dirname(url), removeUnderscoreFromBasename(basename));
if (originalImport.charAt(0) !== '.') {
if (dirname === '.') {
return [originalImport];
}

// Use self.loadModule() before calling done() to make imported files available to
// other webpack tools like postLoaders etc.?

done({
file: filename
}
if (ext) {
if (ext === '.scss' || ext === '.sass') {
add(basename);
if (!startsWithUnderscore) {
add('_' + basename);
}
}/* else {
Leave unknown extensions (like .css) untouched
}*/
} else {
extPrecedence.forEach(function (ext) {
add('_' + basename + ext);
});
});
}

/**
* Check whether its a resolve error and the basename does *not* start with an underscore.
*
* @param {Error} err
* @param {string} basename
* @returns {boolean}
*/
function requiresLookupForUnderscoreModule(err, basename) {
return resolveError.test(err.message) && basename.charAt(0) !== '_';
}

/**
* @param {string} url
* @param {string} basename
* @returns {string}
*/
function addUnderscoreToBasename(url, basename) {
return url.slice(0, -basename.length) + '_' + basename;
}
extPrecedence.forEach(function (ext) {
add(basename + ext);
});
}

/**
* @param {string} basename
* @returns {string}
*/
function removeUnderscoreFromBasename(basename) {
return basename[0] === '_' ? basename.substring(1) : basename;
return paths;
}

0 comments on commit 80944cc

Please sign in to comment.