You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

585 lines
17 KiB

"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.getStylusOptions = getStylusOptions;
exports.urlResolver = urlResolver;
exports.createEvaluator = createEvaluator;
exports.resolveFilename = resolveFilename;
exports.readFile = readFile;
exports.normalizeSourceMap = normalizeSourceMap;
exports.getStylusImplementation = getStylusImplementation;
var _url = require("url");
var _path = _interopRequireDefault(require("path"));
var _stylus = require("stylus");
var _depsResolver = _interopRequireDefault(require("stylus/lib/visitor/deps-resolver"));
var _full = require("klona/full");
var _fastGlob = _interopRequireDefault(require("fast-glob"));
var _normalizePath = _interopRequireDefault(require("normalize-path"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
// Examples:
// - ~package
// - ~package/
// - ~@org
// - ~@org/
// - ~@org/package
// - ~@org/package/
const IS_MODULE_IMPORT = /^~([^/]+|[^/]+\/|@[^/]+[/][^/]+|@[^/]+\/?|@[^/]+[/][^/]+\/)$/;
const MODULE_REQUEST_REGEX = /^[^?]*~/;
function isProductionLikeMode(loaderContext) {
return loaderContext.mode === "production" || !loaderContext.mode;
}
function getStylusOptions(loaderContext, loaderOptions) {
const stylusOptions = (0, _full.klona)(typeof loaderOptions.stylusOptions === "function" ? loaderOptions.stylusOptions(loaderContext) || {} : loaderOptions.stylusOptions || {});
stylusOptions.filename = loaderContext.resourcePath;
stylusOptions.dest = _path.default.dirname(loaderContext.resourcePath); // Keep track of imported files (used by Stylus CLI watch mode)
// eslint-disable-next-line no-underscore-dangle
stylusOptions._imports = []; // https://github.com/stylus/stylus/issues/2119
stylusOptions.resolveURL = typeof stylusOptions.resolveURL === "boolean" && !stylusOptions.resolveURL ? false : typeof stylusOptions.resolveURL === "object" ? stylusOptions.resolveURL : {
nocheck: true
};
if (typeof stylusOptions.compress === "undefined" && isProductionLikeMode(loaderContext)) {
stylusOptions.compress = true;
}
return stylusOptions;
}
function getStylusImplementation(loaderContext, implementation) {
let resolvedImplementation = implementation;
if (!implementation || typeof implementation === "string") {
const stylusImplPkg = implementation || "stylus";
try {
// eslint-disable-next-line import/no-dynamic-require, global-require
resolvedImplementation = require(stylusImplPkg);
} catch (error) {
loaderContext.emitError(error); // eslint-disable-next-line consistent-return
return;
}
} // eslint-disable-next-line consistent-return
return resolvedImplementation;
}
function getPossibleRequests(loaderContext, filename) {
let request = filename; // A `~` makes the url an module
if (MODULE_REQUEST_REGEX.test(filename)) {
request = request.replace(MODULE_REQUEST_REGEX, "");
}
if (IS_MODULE_IMPORT.test(filename)) {
request = request[request.length - 1] === "/" ? request : `${request}/`;
}
return [...new Set([request, filename])];
}
async function resolveFilename(loaderContext, fileResolver, globResolver, isGlob, context, filename) {
const possibleRequests = getPossibleRequests(loaderContext, filename);
let result;
try {
result = await resolveRequests(context, possibleRequests, fileResolver);
} catch (error) {
if (isGlob) {
const [globTask] = _fastGlob.default.generateTasks(filename);
if (globTask.base === ".") {
throw new Error('Glob resolving without a glob base ("~**/*") is not supported, please specify a glob base ("~package/**/*")');
}
const possibleGlobRequests = getPossibleRequests(loaderContext, globTask.base);
let globResult;
try {
globResult = await resolveRequests(context, possibleGlobRequests, globResolver);
} catch (globError) {
throw globError;
}
loaderContext.addContextDependency(globResult);
const patterns = filename.replace(new RegExp(`^${globTask.base}`), (0, _normalizePath.default)(globResult));
const paths = await (0, _fastGlob.default)(patterns, {
absolute: true,
cwd: globResult
});
return paths.sort().filter(file => /\.styl$/i.test(file));
}
throw error;
}
return result;
}
async function resolveRequests(context, possibleRequests, resolve) {
if (possibleRequests.length === 0) {
return Promise.reject();
}
let result;
try {
result = await resolve(context, possibleRequests[0]);
} catch (error) {
const [, ...tailPossibleRequests] = possibleRequests;
if (tailPossibleRequests.length === 0) {
throw error;
}
result = await resolveRequests(context, tailPossibleRequests, resolve);
}
return result;
}
const URL_RE = /^(?:url\s*\(\s*)?['"]?(?:[#/]|(?:https?:)?\/\/)/i;
async function getDependencies(resolvedDependencies, loaderContext, fileResolver, globResolver, seen, code, filename, options) {
seen.add(filename); // See https://github.com/stylus/stylus/issues/2108
const newOptions = (0, _full.klona)({ ...options,
filename,
cache: false
});
const parser = new _stylus.Parser(code, newOptions);
let ast;
try {
ast = parser.parse();
} catch (error) {
loaderContext.emitError(error);
return;
}
const dependencies = [];
class ImportVisitor extends _depsResolver.default {
// eslint-disable-next-line class-methods-use-this
visitImport(node) {
let firstNode = node.path.first;
if (firstNode.name === "url") {
return;
}
if (!firstNode.val) {
const evaluator = new _stylus.Evaluator(ast);
firstNode = evaluator.visit.call(evaluator, firstNode).first;
}
const originalNodePath = !firstNode.val.isNull && firstNode.val || firstNode.name;
let nodePath = originalNodePath;
if (!nodePath) {
return;
}
let found;
let oldNodePath;
const literal = /\.css(?:"|$)/.test(nodePath);
if (!literal && !/\.styl$/i.test(nodePath)) {
oldNodePath = nodePath;
nodePath += ".styl";
}
const isGlob = _fastGlob.default.isDynamicPattern(nodePath);
found = _stylus.utils.find(nodePath, this.paths, this.filename);
if (found && isGlob) {
const [globTask] = _fastGlob.default.generateTasks(nodePath);
const context = globTask.base === "." ? _path.default.dirname(this.filename) : _path.default.join(_path.default.dirname(this.filename), globTask.base);
loaderContext.addContextDependency(context);
}
if (!found && oldNodePath) {
found = _stylus.utils.lookupIndex(oldNodePath, this.paths, this.filename);
}
if (found) {
dependencies.push({
originalLineno: firstNode.lineno,
originalColumn: firstNode.column,
originalNodePath,
resolved: found.map(item => _path.default.isAbsolute(item) ? item : _path.default.join(process.cwd(), item))
});
return;
}
dependencies.push({
originalLineno: firstNode.lineno,
originalColumn: firstNode.column,
originalNodePath,
resolved: resolveFilename(loaderContext, fileResolver, globResolver, isGlob, _path.default.dirname(this.filename), originalNodePath)
});
}
}
new ImportVisitor(ast, newOptions).visit(ast);
await Promise.all(Array.from(dependencies).map(async result => {
let {
resolved
} = result;
try {
resolved = await resolved;
} catch (ignoreError) {
// eslint-disable-next-line no-param-reassign
delete result.resolved; // eslint-disable-next-line no-param-reassign
result.error = ignoreError;
return;
}
const isArray = Array.isArray(resolved); // `stylus` returns forward slashes on windows
// eslint-disable-next-line no-param-reassign
result.resolved = isArray ? resolved.map(item => _path.default.normalize(item)) : _path.default.normalize(resolved);
const dependenciesOfDependencies = [];
for (const dependency of isArray ? result.resolved : [result.resolved]) {
// Avoid loop, the file is imported by itself
if (seen.has(dependency)) {
return;
} // Avoid search nested imports in .css
if (_path.default.extname(dependency) === ".css") {
return;
}
loaderContext.addDependency(dependency);
dependenciesOfDependencies.push((async () => {
let dependencyCode;
try {
dependencyCode = (await readFile(loaderContext.fs, dependency)).toString();
} catch (error) {
loaderContext.emitError(error);
}
await getDependencies(resolvedDependencies, loaderContext, fileResolver, globResolver, seen, dependencyCode, dependency, options);
})());
}
await Promise.all(dependenciesOfDependencies);
}));
if (dependencies.length > 0) {
resolvedDependencies.set(filename, dependencies);
}
}
function mergeBlocks(blocks) {
let finalBlock;
const adding = item => {
finalBlock.push(item);
};
for (const block of blocks) {
if (finalBlock) {
block.nodes.forEach(adding);
} else {
finalBlock = block;
}
}
return finalBlock;
}
async function createEvaluator(loaderContext, code, options) {
const fileResolve = loaderContext.getResolve({
dependencyType: "stylus",
conditionNames: ["styl", "stylus", "style"],
mainFields: ["styl", "style", "stylus", "main", "..."],
mainFiles: ["index", "..."],
extensions: [".styl", ".css"],
restrictions: [/\.(css|styl)$/i],
preferRelative: true
}); // Get cwd for `fastGlob()`
// No need extra options, because they do not used when `resolveToContext` is `true`
const globResolve = loaderContext.getResolve({
conditionNames: ["styl", "stylus", "style"],
resolveToContext: true,
preferRelative: true
});
const resolvedImportDependencies = new Map();
const resolvedDependencies = new Map();
const seen = new Set();
await getDependencies(resolvedDependencies, loaderContext, fileResolve, globResolve, seen, code, loaderContext.resourcePath, options);
const optionsImports = [];
for (const importPath of options.imports) {
const isGlob = _fastGlob.default.isDynamicPattern(importPath);
optionsImports.push({
importPath,
resolved: resolveFilename(loaderContext, fileResolve, globResolve, isGlob, _path.default.dirname(loaderContext.resourcePath), importPath)
});
}
await Promise.all(optionsImports.map(async result => {
const {
importPath
} = result;
let {
resolved
} = result;
try {
resolved = await resolved;
} catch (ignoreError) {
return;
}
const isArray = Array.isArray(resolved); // `stylus` returns forward slashes on windows
// eslint-disable-next-line no-param-reassign
result.resolved = isArray ? resolved.map(item => _path.default.normalize(item)) : _path.default.normalize(resolved);
resolvedImportDependencies.set(importPath, result);
const dependenciesOfImportDependencies = [];
for (const dependency of isArray ? result.resolved : [result.resolved]) {
dependenciesOfImportDependencies.push((async () => {
let dependencyCode;
try {
dependencyCode = (await readFile(loaderContext.fs, dependency)).toString();
} catch (error) {
loaderContext.emitError(error);
}
await getDependencies(resolvedDependencies, loaderContext, fileResolve, globResolve, seen, dependencyCode, dependency, options);
})());
}
await Promise.all(dependenciesOfImportDependencies);
}));
return class CustomEvaluator extends _stylus.Evaluator {
visitImport(imported) {
this.return += 1;
const node = this.visit(imported.path).first;
const nodePath = !node.val.isNull && node.val || node.name;
this.return -= 1;
let webpackResolveError;
if (node.name !== "url" && nodePath && !URL_RE.test(nodePath)) {
let dependency;
const isEntrypoint = loaderContext.resourcePath === node.filename;
if (isEntrypoint) {
dependency = resolvedImportDependencies.get(nodePath);
}
if (!dependency) {
const dependencies = resolvedDependencies.get(_path.default.normalize(node.filename));
if (dependencies) {
dependency = dependencies.find(item => {
if (item.originalLineno === node.lineno && item.originalColumn === node.column && item.originalNodePath === nodePath) {
if (item.error) {
webpackResolveError = item.error;
} else {
return item.resolved;
}
}
return false;
});
}
}
if (dependency) {
const {
resolved
} = dependency;
if (!Array.isArray(resolved)) {
// Avoid re globbing when resolved import contains glob characters
node.string = _fastGlob.default.escapePath(resolved);
} else if (resolved.length > 0) {
let hasError = false;
const blocks = resolved.map(item => {
const clonedImported = imported.clone();
const clonedNode = this.visit(clonedImported.path).first; // Avoid re globbing when resolved import contains glob characters
clonedNode.string = _fastGlob.default.escapePath(item);
let result;
try {
result = super.visitImport(clonedImported);
} catch (error) {
hasError = true;
}
return result;
});
if (!hasError) {
return mergeBlocks(blocks);
}
}
}
}
let result;
try {
result = super.visitImport(imported);
} catch (error) {
loaderContext.emitError(new Error(`Stylus resolver error: ${error.message}${webpackResolveError ? `\n\nWebpack resolver error: ${webpackResolveError.message}${webpackResolveError.details ? `\n\nWebpack resolver error details:\n${webpackResolveError.details}` : ""}${webpackResolveError.missing ? `\n\nWebpack resolver error missing:\n${webpackResolveError.missing.join("\n")}` : ""}` : ""}`));
return imported;
}
return result;
}
};
}
function urlResolver(options = {}) {
function resolver(url) {
const compiler = new _stylus.Compiler(url);
const {
filename
} = url;
compiler.isURL = true;
const visitedUrl = url.nodes.map(node => compiler.visit(node)).join("");
const splitted = visitedUrl.split("!");
const parsedUrl = (0, _url.parse)(splitted.pop()); // Parse literal
const literal = new _stylus.nodes.Literal(`url("${parsedUrl.href}")`);
let {
pathname
} = parsedUrl;
let {
dest
} = this.options;
let tail = "";
let res; // Absolute or hash
if (parsedUrl.protocol || !pathname || pathname[0] === "/") {
return literal;
} // Check that file exists
if (!options.nocheck) {
// eslint-disable-next-line no-underscore-dangle
const _paths = options.paths || [];
pathname = _stylus.utils.lookup(pathname, _paths.concat(this.paths));
if (!pathname) {
return literal;
}
}
if (this.includeCSS && _path.default.extname(pathname) === ".css") {
return new _stylus.nodes.Literal(parsedUrl.href);
}
if (parsedUrl.search) {
tail += parsedUrl.search;
}
if (parsedUrl.hash) {
tail += parsedUrl.hash;
}
if (dest && _path.default.extname(dest) === ".css") {
dest = _path.default.dirname(dest);
}
res = _path.default.relative(dest || _path.default.dirname(this.filename), options.nocheck ? _path.default.join(_path.default.dirname(filename), pathname) : pathname) + tail;
if (_path.default.sep === "\\") {
res = res.replace(/\\/g, "/");
}
splitted.push(res);
return new _stylus.nodes.Literal(`url("${splitted.join("!")}")`);
}
resolver.options = options;
resolver.raw = true;
return resolver;
}
function readFile(inputFileSystem, filepath) {
return new Promise((resolve, reject) => {
inputFileSystem.readFile(filepath, (error, stats) => {
if (error) {
reject(error);
}
resolve(stats);
});
});
}
const IS_NATIVE_WIN32_PATH = /^[a-z]:[/\\]|^\\\\/i;
const ABSOLUTE_SCHEME = /^[A-Za-z0-9+\-.]+:/;
function getURLType(source) {
if (source[0] === "/") {
if (source[1] === "/") {
return "scheme-relative";
}
return "path-absolute";
}
if (IS_NATIVE_WIN32_PATH.test(source)) {
return "path-absolute";
}
return ABSOLUTE_SCHEME.test(source) ? "absolute" : "path-relative";
}
function normalizeSourceMap(map, rootContext) {
const newMap = map; // result.map.file is an optional property that provides the output filename.
// Since we don't know the final filename in the webpack build chain yet, it makes no sense to have it.
// eslint-disable-next-line no-param-reassign
delete newMap.file; // eslint-disable-next-line no-param-reassign
newMap.sourceRoot = ""; // eslint-disable-next-line no-param-reassign
newMap.sources = newMap.sources.map(source => {
const sourceType = getURLType(source); // Do no touch `scheme-relative`, `path-absolute` and `absolute` types
if (sourceType === "path-relative") {
return _path.default.resolve(rootContext, _path.default.normalize(source));
}
return source;
});
return newMap;
}