UNPKG

17.7 kBJavaScriptView Raw
1"use strict";
2
3Object.defineProperty(exports, "__esModule", {
4 value: true
5});
6exports.createEvaluator = createEvaluator;
7exports.getStylusImplementation = getStylusImplementation;
8exports.getStylusOptions = getStylusOptions;
9exports.normalizeSourceMap = normalizeSourceMap;
10exports.readFile = readFile;
11exports.resolveFilename = resolveFilename;
12exports.urlResolver = urlResolver;
13
14var _url = require("url");
15
16var _path = _interopRequireDefault(require("path"));
17
18var _stylus = require("stylus");
19
20var _depsResolver = _interopRequireDefault(require("stylus/lib/visitor/deps-resolver"));
21
22var _full = require("klona/full");
23
24var _fastGlob = _interopRequireDefault(require("fast-glob"));
25
26var _normalizePath = _interopRequireDefault(require("normalize-path"));
27
28function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
29
30// Examples:
31// - ~package
32// - ~package/
33// - ~@org
34// - ~@org/
35// - ~@org/package
36// - ~@org/package/
37const IS_MODULE_IMPORT = /^~([^/]+|[^/]+\/|@[^/]+[/][^/]+|@[^/]+\/?|@[^/]+[/][^/]+\/)$/;
38const MODULE_REQUEST_REGEX = /^[^?]*~/;
39
40function isProductionLikeMode(loaderContext) {
41 return loaderContext.mode === "production" || !loaderContext.mode;
42}
43
44function getStylusOptions(loaderContext, loaderOptions) {
45 const stylusOptions = (0, _full.klona)(typeof loaderOptions.stylusOptions === "function" ? loaderOptions.stylusOptions(loaderContext) || {} : loaderOptions.stylusOptions || {});
46 stylusOptions.filename = loaderContext.resourcePath;
47 stylusOptions.dest = _path.default.dirname(loaderContext.resourcePath); // Keep track of imported files (used by Stylus CLI watch mode)
48 // eslint-disable-next-line no-underscore-dangle
49
50 stylusOptions._imports = []; // https://github.com/stylus/stylus/issues/2119
51
52 stylusOptions.resolveURL = typeof stylusOptions.resolveURL === "boolean" && !stylusOptions.resolveURL ? false : typeof stylusOptions.resolveURL === "object" ? stylusOptions.resolveURL : {
53 nocheck: true
54 };
55
56 if (typeof stylusOptions.compress === "undefined" && isProductionLikeMode(loaderContext)) {
57 stylusOptions.compress = true;
58 }
59
60 return stylusOptions;
61}
62
63function getStylusImplementation(loaderContext, implementation) {
64 let resolvedImplementation = implementation;
65
66 if (!implementation || typeof implementation === "string") {
67 const stylusImplPkg = implementation || "stylus";
68
69 try {
70 // eslint-disable-next-line import/no-dynamic-require, global-require
71 resolvedImplementation = require(stylusImplPkg);
72 } catch (error) {
73 loaderContext.emitError(error); // eslint-disable-next-line consistent-return
74
75 return;
76 }
77 } // eslint-disable-next-line consistent-return
78
79
80 return resolvedImplementation;
81}
82
83function getPossibleRequests(loaderContext, filename) {
84 let request = filename; // A `~` makes the url an module
85
86 if (MODULE_REQUEST_REGEX.test(filename)) {
87 request = request.replace(MODULE_REQUEST_REGEX, "");
88 }
89
90 if (IS_MODULE_IMPORT.test(filename)) {
91 request = request[request.length - 1] === "/" ? request : `${request}/`;
92 }
93
94 return [...new Set([request, filename])];
95}
96
97async function resolveFilename(loaderContext, fileResolver, globResolver, isGlob, context, filename) {
98 const possibleRequests = getPossibleRequests(loaderContext, filename);
99 let result;
100
101 try {
102 result = await resolveRequests(context, possibleRequests, fileResolver);
103 } catch (error) {
104 if (isGlob) {
105 const [globTask] = _fastGlob.default.generateTasks(filename);
106
107 if (globTask.base === ".") {
108 throw new Error('Glob resolving without a glob base ("~**/*") is not supported, please specify a glob base ("~package/**/*")');
109 }
110
111 const possibleGlobRequests = getPossibleRequests(loaderContext, globTask.base);
112 let globResult;
113
114 try {
115 globResult = await resolveRequests(context, possibleGlobRequests, globResolver);
116 } catch (globError) {
117 throw globError;
118 }
119
120 loaderContext.addContextDependency(globResult);
121 const patterns = filename.replace(new RegExp(`^${globTask.base}`), (0, _normalizePath.default)(globResult));
122 const paths = await (0, _fastGlob.default)(patterns, {
123 absolute: true,
124 cwd: globResult
125 });
126 return paths.sort().filter(file => /\.styl$/i.test(file));
127 }
128
129 throw error;
130 }
131
132 return result;
133}
134
135async function resolveRequests(context, possibleRequests, resolve) {
136 if (possibleRequests.length === 0) {
137 return Promise.reject();
138 }
139
140 let result;
141
142 try {
143 result = await resolve(context, possibleRequests[0]);
144 } catch (error) {
145 const [, ...tailPossibleRequests] = possibleRequests;
146
147 if (tailPossibleRequests.length === 0) {
148 throw error;
149 }
150
151 result = await resolveRequests(context, tailPossibleRequests, resolve);
152 }
153
154 return result;
155}
156
157const URL_RE = /^(?:url\s*\(\s*)?['"]?(?:[#/]|(?:https?:)?\/\/)/i;
158
159async function getDependencies(resolvedDependencies, loaderContext, fileResolver, globResolver, seen, code, filename, options) {
160 seen.add(filename); // See https://github.com/stylus/stylus/issues/2108
161
162 const newOptions = (0, _full.klona)({ ...options,
163 filename,
164 cache: false
165 });
166 const parser = new _stylus.Parser(code, newOptions);
167 let ast;
168
169 try {
170 ast = parser.parse();
171 } catch (error) {
172 loaderContext.emitError(error);
173 return;
174 }
175
176 const dependencies = [];
177
178 class ImportVisitor extends _depsResolver.default {
179 // eslint-disable-next-line class-methods-use-this
180 visitImport(node) {
181 let firstNode = node.path.first;
182
183 if (firstNode.name === "url") {
184 return;
185 }
186
187 if (!firstNode.val) {
188 const evaluator = new _stylus.Evaluator(ast);
189 firstNode = evaluator.visit.call(evaluator, firstNode).first;
190 }
191
192 const originalNodePath = !firstNode.val.isNull && firstNode.val || firstNode.name;
193 let nodePath = originalNodePath;
194
195 if (!nodePath) {
196 return;
197 }
198
199 let found;
200 let oldNodePath;
201 const literal = /\.css(?:"|$)/.test(nodePath);
202
203 if (!literal && !/\.styl$/i.test(nodePath)) {
204 oldNodePath = nodePath;
205 nodePath += ".styl";
206 }
207
208 const isGlob = _fastGlob.default.isDynamicPattern(nodePath);
209
210 found = _stylus.utils.find(nodePath, this.paths, this.filename);
211
212 if (found && isGlob) {
213 const [globTask] = _fastGlob.default.generateTasks(nodePath);
214
215 const context = globTask.base === "." ? _path.default.dirname(this.filename) : _path.default.join(_path.default.dirname(this.filename), globTask.base);
216 loaderContext.addContextDependency(context);
217 }
218
219 if (!found && oldNodePath) {
220 found = _stylus.utils.lookupIndex(oldNodePath, this.paths, this.filename);
221 }
222
223 if (found) {
224 dependencies.push({
225 originalLineno: firstNode.lineno,
226 originalColumn: firstNode.column,
227 originalNodePath,
228 resolved: found.map(item => _path.default.isAbsolute(item) ? item : _path.default.join(process.cwd(), item))
229 });
230 return;
231 }
232
233 dependencies.push({
234 originalLineno: firstNode.lineno,
235 originalColumn: firstNode.column,
236 originalNodePath,
237 resolved: resolveFilename(loaderContext, fileResolver, globResolver, isGlob, _path.default.dirname(this.filename), originalNodePath)
238 });
239 }
240
241 }
242
243 new ImportVisitor(ast, newOptions).visit(ast);
244 await Promise.all(Array.from(dependencies).map(async result => {
245 let {
246 resolved
247 } = result;
248
249 try {
250 resolved = await resolved;
251 } catch (ignoreError) {
252 // eslint-disable-next-line no-param-reassign
253 delete result.resolved; // eslint-disable-next-line no-param-reassign
254
255 result.error = ignoreError;
256 return;
257 }
258
259 const isArray = Array.isArray(resolved); // `stylus` returns forward slashes on windows
260 // eslint-disable-next-line no-param-reassign
261
262 result.resolved = isArray ? resolved.map(item => _path.default.normalize(item)) : _path.default.normalize(resolved);
263 const dependenciesOfDependencies = [];
264
265 for (const dependency of isArray ? result.resolved : [result.resolved]) {
266 // Avoid loop, the file is imported by itself
267 if (seen.has(dependency)) {
268 return;
269 } // Avoid search nested imports in .css
270
271
272 if (_path.default.extname(dependency) === ".css") {
273 return;
274 }
275
276 loaderContext.addDependency(dependency);
277 dependenciesOfDependencies.push((async () => {
278 let dependencyCode;
279
280 try {
281 dependencyCode = (await readFile(loaderContext.fs, dependency)).toString();
282 } catch (error) {
283 loaderContext.emitError(error);
284 }
285
286 await getDependencies(resolvedDependencies, loaderContext, fileResolver, globResolver, seen, dependencyCode, dependency, options);
287 })());
288 }
289
290 await Promise.all(dependenciesOfDependencies);
291 }));
292
293 if (dependencies.length > 0) {
294 resolvedDependencies.set(filename, dependencies);
295 }
296}
297
298function mergeBlocks(blocks) {
299 let finalBlock;
300
301 const adding = item => {
302 finalBlock.push(item);
303 };
304
305 for (const block of blocks) {
306 if (finalBlock) {
307 block.nodes.forEach(adding);
308 } else {
309 finalBlock = block;
310 }
311 }
312
313 return finalBlock;
314}
315
316async function createEvaluator(loaderContext, code, options) {
317 const fileResolve = loaderContext.getResolve({
318 dependencyType: "stylus",
319 conditionNames: ["styl", "stylus", "style"],
320 mainFields: ["styl", "style", "stylus", "main", "..."],
321 mainFiles: ["index", "..."],
322 extensions: [".styl", ".css"],
323 restrictions: [/\.(css|styl)$/i],
324 preferRelative: true
325 }); // Get cwd for `fastGlob()`
326 // No need extra options, because they do not used when `resolveToContext` is `true`
327
328 const globResolve = loaderContext.getResolve({
329 conditionNames: ["styl", "stylus", "style"],
330 resolveToContext: true,
331 preferRelative: true
332 });
333 const resolvedImportDependencies = new Map();
334 const resolvedDependencies = new Map();
335 const seen = new Set();
336 await getDependencies(resolvedDependencies, loaderContext, fileResolve, globResolve, seen, code, loaderContext.resourcePath, options);
337 const optionsImports = [];
338
339 for (const importPath of options.imports) {
340 const isGlob = _fastGlob.default.isDynamicPattern(importPath);
341
342 optionsImports.push({
343 importPath,
344 resolved: resolveFilename(loaderContext, fileResolve, globResolve, isGlob, _path.default.dirname(loaderContext.resourcePath), importPath)
345 });
346 }
347
348 await Promise.all(optionsImports.map(async result => {
349 const {
350 importPath
351 } = result;
352 let {
353 resolved
354 } = result;
355
356 try {
357 resolved = await resolved;
358 } catch (ignoreError) {
359 return;
360 }
361
362 const isArray = Array.isArray(resolved); // `stylus` returns forward slashes on windows
363 // eslint-disable-next-line no-param-reassign
364
365 result.resolved = isArray ? resolved.map(item => _path.default.normalize(item)) : _path.default.normalize(resolved);
366 resolvedImportDependencies.set(importPath, result);
367 const dependenciesOfImportDependencies = [];
368
369 for (const dependency of isArray ? result.resolved : [result.resolved]) {
370 dependenciesOfImportDependencies.push((async () => {
371 let dependencyCode;
372
373 try {
374 dependencyCode = (await readFile(loaderContext.fs, dependency)).toString();
375 } catch (error) {
376 loaderContext.emitError(error);
377 }
378
379 await getDependencies(resolvedDependencies, loaderContext, fileResolve, globResolve, seen, dependencyCode, dependency, options);
380 })());
381 }
382
383 await Promise.all(dependenciesOfImportDependencies);
384 }));
385 return class CustomEvaluator extends _stylus.Evaluator {
386 visitImport(imported) {
387 this.return += 1;
388 const node = this.visit(imported.path).first;
389 const nodePath = !node.val.isNull && node.val || node.name;
390 this.return -= 1;
391 let webpackResolveError;
392
393 if (node.name !== "url" && nodePath && !URL_RE.test(nodePath)) {
394 let dependency;
395 const isEntrypoint = loaderContext.resourcePath === node.filename;
396
397 if (isEntrypoint) {
398 dependency = resolvedImportDependencies.get(nodePath);
399 }
400
401 if (!dependency) {
402 const dependencies = resolvedDependencies.get(_path.default.normalize(node.filename));
403
404 if (dependencies) {
405 dependency = dependencies.find(item => {
406 if (item.originalLineno === node.lineno && item.originalColumn === node.column && item.originalNodePath === nodePath) {
407 if (item.error) {
408 webpackResolveError = item.error;
409 } else {
410 return item.resolved;
411 }
412 }
413
414 return false;
415 });
416 }
417 }
418
419 if (dependency) {
420 const {
421 resolved
422 } = dependency;
423
424 if (!Array.isArray(resolved)) {
425 // Avoid re globbing when resolved import contains glob characters
426 node.string = _fastGlob.default.escapePath(resolved);
427 } else if (resolved.length > 0) {
428 let hasError = false;
429 const blocks = resolved.map(item => {
430 const clonedImported = imported.clone();
431 const clonedNode = this.visit(clonedImported.path).first; // Avoid re globbing when resolved import contains glob characters
432
433 clonedNode.string = _fastGlob.default.escapePath(item);
434 let result;
435
436 try {
437 result = super.visitImport(clonedImported);
438 } catch (error) {
439 hasError = true;
440 }
441
442 return result;
443 });
444
445 if (!hasError) {
446 return mergeBlocks(blocks);
447 }
448 }
449 }
450 }
451
452 let result;
453
454 try {
455 result = super.visitImport(imported);
456 } catch (error) {
457 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")}` : ""}` : ""}`));
458 return imported;
459 }
460
461 return result;
462 }
463
464 };
465}
466
467function urlResolver(options = {}) {
468 function resolver(url) {
469 const compiler = new _stylus.Compiler(url);
470 const {
471 filename
472 } = url;
473 compiler.isURL = true;
474 const visitedUrl = url.nodes.map(node => compiler.visit(node)).join("");
475 const splitted = visitedUrl.split("!");
476 const parsedUrl = (0, _url.parse)(splitted.pop()); // Parse literal
477
478 const literal = new _stylus.nodes.Literal(`url("${parsedUrl.href}")`);
479 let {
480 pathname
481 } = parsedUrl;
482 let {
483 dest
484 } = this.options;
485 let tail = "";
486 let res; // Absolute or hash
487
488 if (parsedUrl.protocol || !pathname || pathname[0] === "/") {
489 return literal;
490 } // Check that file exists
491
492
493 if (!options.nocheck) {
494 // eslint-disable-next-line no-underscore-dangle
495 const _paths = options.paths || [];
496
497 pathname = _stylus.utils.lookup(pathname, _paths.concat(this.paths));
498
499 if (!pathname) {
500 return literal;
501 }
502 }
503
504 if (this.includeCSS && _path.default.extname(pathname) === ".css") {
505 return new _stylus.nodes.Literal(parsedUrl.href);
506 }
507
508 if (parsedUrl.search) {
509 tail += parsedUrl.search;
510 }
511
512 if (parsedUrl.hash) {
513 tail += parsedUrl.hash;
514 }
515
516 if (dest && _path.default.extname(dest) === ".css") {
517 dest = _path.default.dirname(dest);
518 }
519
520 res = _path.default.relative(dest || _path.default.dirname(this.filename), options.nocheck ? _path.default.join(_path.default.dirname(filename), pathname) : pathname) + tail;
521
522 if (_path.default.sep === "\\") {
523 res = res.replace(/\\/g, "/");
524 }
525
526 splitted.push(res);
527 return new _stylus.nodes.Literal(`url("${splitted.join("!")}")`);
528 }
529
530 resolver.options = options;
531 resolver.raw = true;
532 return resolver;
533}
534
535function readFile(inputFileSystem, filepath) {
536 return new Promise((resolve, reject) => {
537 inputFileSystem.readFile(filepath, (error, stats) => {
538 if (error) {
539 reject(error);
540 }
541
542 resolve(stats);
543 });
544 });
545}
546
547const IS_NATIVE_WIN32_PATH = /^[a-z]:[/\\]|^\\\\/i;
548const ABSOLUTE_SCHEME = /^[A-Za-z0-9+\-.]+:/;
549
550function getURLType(source) {
551 if (source[0] === "/") {
552 if (source[1] === "/") {
553 return "scheme-relative";
554 }
555
556 return "path-absolute";
557 }
558
559 if (IS_NATIVE_WIN32_PATH.test(source)) {
560 return "path-absolute";
561 }
562
563 return ABSOLUTE_SCHEME.test(source) ? "absolute" : "path-relative";
564}
565
566function normalizeSourceMap(map, rootContext) {
567 const newMap = map; // result.map.file is an optional property that provides the output filename.
568 // Since we don't know the final filename in the webpack build chain yet, it makes no sense to have it.
569 // eslint-disable-next-line no-param-reassign
570
571 delete newMap.file; // eslint-disable-next-line no-param-reassign
572
573 newMap.sourceRoot = ""; // eslint-disable-next-line no-param-reassign
574
575 newMap.sources = newMap.sources.map(source => {
576 const sourceType = getURLType(source); // Do no touch `scheme-relative`, `path-absolute` and `absolute` types
577
578 if (sourceType === "path-relative") {
579 return _path.default.resolve(rootContext, _path.default.normalize(source));
580 }
581
582 return source;
583 });
584 return newMap;
585}
\No newline at end of file