eyeglass
Version:
Sass modules for npm.
210 lines • 8.88 kB
JavaScript
;
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
result["default"] = mod;
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const fs_1 = require("fs");
const perf_1 = require("../util/perf");
const path = __importStar(require("path"));
const NameExpander_1 = require("../util/NameExpander");
const ImportUtilities_1 = __importDefault(require("./ImportUtilities"));
const assertions_1 = require("../util/assertions");
const typescriptUtils_1 = require("../util/typescriptUtils");
const errorFor_1 = __importDefault(require("../util/errorFor"));
const MODULE_PARSER = /^((?:@[^/]+\/[^/]+)|(?:[^/]+))\/?(.*)/;
/*
* Walks the file list until a match is found. If
* no matches are found, calls the callback with an error
*/
function readFirstFile(buildCache, uri, possibleFiles, callback) {
for (let nextFile of possibleFiles) {
try {
// We only read from the cache here, we do not set it.
// the set will occur for common imports by the calling context
let data = buildCache.get(`fs.readFileSync(${nextFile})`);
data = data || fs_1.readFileSync(nextFile, "utf8");
// if it didn't fail, we found the first file so return it
callback(null, {
contents: data.toString(),
file: nextFile
});
return;
}
catch (_a) {
// pass
}
}
let errorMsg = [
`\`${uri}\` was not found in any of the following locations:`
].concat(...possibleFiles).join("\n ");
callback(new Error(errorMsg));
return;
}
/**
* The goal of this cache method is to cache the files that are commonly
* imported. If we see the same import lookups more than once we can
* assume the import is part of a commonly accessed library and put it into cache.
* In many cases, this cache ignores the entry point file to a library from
* outside the library because the first files looked for are relative to the
* current file (the exception would be if several fils in the same directory
* import the shared import).
*/
function readFirstFileCached(buildCache, uri, files, callback) {
let readCacheKey = files.join(";");
let countKey = `readAbstractFile(${readCacheKey})-count`;
let invocationCount = buildCache.get(countKey);
if (typeof invocationCount === "undefined") {
invocationCount = 0;
}
invocationCount += 1;
buildCache.set(countKey, invocationCount);
if (invocationCount === 1) {
readFirstFile(buildCache, uri, files, callback);
}
else {
let fileKey = `readAbstractFile(${readCacheKey})-file`;
let file = buildCache.get(fileKey);
let contents;
let contentsKey;
if (file) {
contentsKey = `fs.readFileSync(${file})`;
contents = buildCache.get(contentsKey);
}
if (file && contents) {
callback(null, { file, contents });
}
else {
readFirstFile(buildCache, uri, files, (err, data) => {
if (data && !err) {
buildCache.set(fileKey, data.file);
contentsKey = `fs.readFileSync(${data.file})`;
buildCache.set(contentsKey, data.contents);
}
callback(err, data);
});
}
}
}
// This is a bootstrap function for calling readFirstFile.
function readAbstractFile(originalUri, uri, location, includePaths, moduleName, buildCache, callback) {
// start a name expander to get the names of possible file locations
let nameExpander = new NameExpander_1.NameExpander(uri);
// add the current location to the name expander
nameExpander.addLocation(location);
// if we have a module name, add it as an additional location
if (moduleName) {
nameExpander.addLocation(path.join(location, moduleName));
}
// if we have includePaths...
if (includePaths) {
// add each of includePaths to the name expander
includePaths.forEach(function (includePath) {
nameExpander.addLocation(path.resolve(location, includePath));
});
}
let files = new Array(...nameExpander.files);
readFirstFileCached(buildCache, originalUri, files, callback);
}
/*
* Returns an importer suitable for passing to node-sass.
* options are the eyeglass/node-sass options.
* fallback importer is the importer that was specified
* in the node-sass options if one was there.
*/
const ModuleImporter = function (eyeglass, sass, options, fallbackImporter) {
let includePaths = options.includePaths;
let root = options.eyeglass.root;
let buildCache = options.eyeglass.buildCache;
return ImportUtilities_1.default.createImporter("module", function (uri, prev, done) {
let importUtils = new ImportUtilities_1.default(eyeglass, sass, options, fallbackImporter, this);
let isRealFile = perf_1.existsSync(prev);
// pattern to match moduleName/relativePath
// $1 = moduleName (foo or @scope/foo)
// $2 = relativePath
let match = MODULE_PARSER.exec(uri);
if (!match) {
throw new Error("invalid uri: " + uri);
}
let moduleName = match[1];
let relativePath = match[2];
let mod = eyeglass.modules.access(moduleName, isRealFile ? prev : root);
// for back-compat with previous suggestion @see
// https://github.com/sass-eyeglass/eyeglass/issues/131#issuecomment-210728946
// if the module was not found and the name starts with `@`...
if (!mod && moduleName[0] === "@") {
// reconstruct the moduleName and relativePath the way we would have previously
let pieces = moduleName.split("/");
relativePath = pieces[1] + "/" + relativePath;
moduleName = pieces[0];
// and try to find it again
mod = eyeglass.modules.access(moduleName, isRealFile ? prev : root);
}
let sassDir;
if (mod) {
sassDir = mod.sassDir;
if (!sassDir && !isRealFile) {
// No sass directory, give an error
importUtils.fallback(uri, prev, done, () => {
if (!mod) {
return assertions_1.unreachable();
}
let missingMessage = "sassDir is not specified in " + mod.name + "'s package.json";
if (mod.mainPath) {
missingMessage += " or " + mod.mainPath;
}
return done(new Error(missingMessage));
});
return;
}
}
function createHandler(errorHandler) {
let errHandler = errorHandler || defaultErrorHandler(done);
return function (err, data) {
if (err || !typescriptUtils_1.isPresent(data)) {
importUtils.fallback(uri, prev, done, function () {
errHandler(err || "[internal error] No data returned.");
});
}
else {
importUtils.importOnce(data, done);
}
};
}
function handleRelativeImports(includePaths = null) {
if (isRealFile) {
// relative file import, potentially relative to the previous import
readAbstractFile(uri, uri, path.dirname(prev), includePaths, null, buildCache, createHandler());
}
else {
readAbstractFile(uri, uri, root, includePaths, null, buildCache, createHandler(function (err) {
done(errorFor_1.default(err, "Could not import " + uri + " from " + prev));
}));
}
}
if (sassDir) {
// read uri from location. pass no includePaths as this is an eyeglass module
readAbstractFile(uri, relativePath, sassDir, null, moduleName, buildCache, createHandler(
// if it fails to find a module import,
// try to import relative to the current location
// this handles #37
handleRelativeImports.bind(null, null)));
}
else {
handleRelativeImports(includePaths);
}
});
};
function defaultErrorHandler(done) {
return function (err) {
done(errorFor_1.default(err));
};
}
exports.default = ModuleImporter;
//# sourceMappingURL=ModuleImporter.js.map