From 230e06994c0ad0a1d7db2421b8f1d45ffb8d689e Mon Sep 17 00:00:00 2001 From: George Taveras Date: Sun, 20 Nov 2016 19:48:50 -0500 Subject: [PATCH] Introduce resolver The resolver module implements a path resolving algorithm that leverages node's own require. This has a couple benefits: * more likely to continue working as expected when features are introduced to npm/node * reduction of code duplication (npm/node already implements the module resolution we are interested in for this project) * allow the use of NODE_PATH variable to control module directory Additionally, `resolver` leverages `Package` and `Import` constructors which implement some of the same logic previously found on the `find` function in `lib/importer.js`. The benefit of creating these constructors will obvious when unit tests are introduced for this project. `Package`: implements logic related to retrieving package data, such as getting the package.json, and getting the sass 'main' file for a given package. `Import`: implements logic to make sense of a sass import. --- lib/importer.js | 63 +++-------------------------------------- lib/resolver.js | 19 +++++++++++++ lib/resolver/import.js | 34 ++++++++++++++++++++++ lib/resolver/package.js | 46 ++++++++++++++++++++++++++++++ package.json | 2 +- 5 files changed, 104 insertions(+), 60 deletions(-) create mode 100644 lib/resolver.js create mode 100644 lib/resolver/import.js create mode 100644 lib/resolver/package.js diff --git a/lib/importer.js b/lib/importer.js index 2ff09ff..1f3a63a 100644 --- a/lib/importer.js +++ b/lib/importer.js @@ -2,71 +2,16 @@ var findup = require('findup'), path = require('path'); var local = require('./local'); - -function find(dir, file, callback) { - var name, isScoped = false; - if (file.split('/')[0][0] === '@') { - name = file.split('/').slice(0, 2).join('/'); - isScoped = true; - } else { - name = file.split('/')[0]; - } - - var modulePath = './node_modules/' + name + '/package.json'; - - findup(dir, modulePath, function (err, moduleDir) { - if (err) { return callback(err); } - - var root = path.dirname(path.resolve(moduleDir, modulePath)); - var location; - // if import is just a module name - if (file === name) { - var json = require(path.resolve(moduleDir, modulePath)); - // look for "sass" declaration in package.json - if (json.sass) { - location = json.sass; - // look for "style" declaration in package.json - } else if (json.style) { - location = json.style; - // look for a css/sass/scss file in the "main" declaration in package.json - } else if (/\.(sa|c|sc)ss$/.test(json.main)) { - location = json.main; - // otherwise assume ./styles.scss - } else { - location = './styles'; - } - // if a full path is provided - } else { - if(isScoped) { - location = path.join('..', '..', file); - } else { - location = path.join('..', file); - } - } - callback(null, path.resolve(root, location)); - }); -} +var resolve = require('./resolver').Resolver(); function importer(url, file, done) { local(url, file, function (err, isLocal) { if (err || isLocal) { - done({ - file: url - }); + done({ file: url }); } else { - find(path.dirname(file), url, function (err, location) { - if (err) { - done({ - file: url - }); - } else { - done({ - file: location - }); - }; - }); + done({ file: resolve(url) }); } - }) + }); } module.exports = importer; diff --git a/lib/resolver.js b/lib/resolver.js new file mode 100644 index 0000000..9c96047 --- /dev/null +++ b/lib/resolver.js @@ -0,0 +1,19 @@ +var Package = require('./resolver/package'); +var Import = require('./resolver/import'); + +exports.Resolver = function Resolver(requireFn) { + /* Facilitate testing by allowing requireFn to be specified */ + requireFn = (requireFn || require) ; + + return function resolve(sassImportPath) { + var _import = new Import(sassImportPath); + var _package = new Package(_import.packageName(), requireFn); + + if (_import.isEntrypoint()) { + return _package.resolveEntrypoint(); + } else { + return _package.safeResolve(_import.specifiedFilePath()); + } + } +} + diff --git a/lib/resolver/import.js b/lib/resolver/import.js new file mode 100644 index 0000000..0ccd77c --- /dev/null +++ b/lib/resolver/import.js @@ -0,0 +1,34 @@ +var path = require('path'); + +module.exports = Import; + +function Import(sassImportPath) { + this.sassImportPath = sassImportPath; +}; + +Import.prototype.isScoped = function isScoped() { + return this.sassImportPath[0] === '@'; +}; + +Import.prototype.packageName = function packageName() { + if (this.isScoped()) { + return this.sassImportPath.split(path.sep, 2).join(path.sep); + } else { + return this.sassImportPath.split(path.sep, 1)[0]; + } +}; + +Import.prototype.isEntrypoint = function isEntrypoint() { + var safePathSplitPattern = new RegExp(path.sep + '.'); + var pathSegmentCount = this.sassImportPath.split(safePathSplitPattern).length; + + if (this.isScoped()) { + return pathSegmentCount === 2; + } else { + return pathSegmentCount === 1; + } +}; + +Import.prototype.specifiedFilePath = function specifiedFileName() { + return this.sassImportPath.slice(this.packageName().length); +}; diff --git a/lib/resolver/package.js b/lib/resolver/package.js new file mode 100644 index 0000000..3c15941 --- /dev/null +++ b/lib/resolver/package.js @@ -0,0 +1,46 @@ +var path = require('path'); + +module.exports = Package; + +function Package(name, requireFn) { + this.requireFn = requireFn; + + this.path = path.join.bind(null, name); +}; + +Package.prototype.json = function packageJSON() { + return this.requireFn(this.path('package.json')); +}; + +Package.prototype.resolve = function resolve(path) { + return this.requireFn.resolve(this.path(path)); +}; + +Package.prototype.safeResolve = function safeResolve(potentiallyNonExistentPath) { + return path.join(this.dir(), potentiallyNonExistentPath); +}; + +Package.prototype.dir = function dir() { + return path.dirname(this.resolve('package.json')); +}; + +Package.prototype.entrypoint = function entrypoint() { + var packageJson = this.json(); + + if (packageJson.sass) { + return packageJson.sass; + // look for "style" declaration in package.json + } else if (packageJson.style) { + return packageJson.style; + // look for a css/sass/scss file in the "main" declaration in package.json + } else if (/\.(sa|c|sc)ss$/.test(packageJson.main)) { + return packageJson.main; + // otherwise assume ./styles.scss + } else { + return 'styles'; + } +}; + +Package.prototype.resolveEntrypoint = function resolveEntrypoint() { + return this.safeResolve(this.entrypoint()); +}; diff --git a/package.json b/package.json index 8363424..efe45b9 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "main": "index.js", "bin": "./bin/npm-sass", "scripts": { - "test": "mocha test/index.js" + "test": "NODE_PATH=$NODE_PATH:\"$(git rev-parse --show-toplevel)/test/fixtures/node_modules\" mocha test/index.js" }, "repository": { "type": "git",