diff --git a/src/assets/LESSAsset.js b/src/assets/LESSAsset.js index a915de719e7..4cfc17f3bee 100644 --- a/src/assets/LESSAsset.js +++ b/src/assets/LESSAsset.js @@ -2,7 +2,6 @@ const Asset = require('../Asset'); const localRequire = require('../utils/localRequire'); const promisify = require('../utils/promisify'); const Resolver = require('../Resolver'); -const syncPromise = require('../utils/syncPromise'); const fs = require('../utils/fs'); const path = require('path'); @@ -72,21 +71,23 @@ function getFileManager(less, options) { }); class LessFileManager extends less.FileManager { - async resolve(filename, currentDirectory) { - return (await resolver.resolve( - filename, - path.join(currentDirectory, 'index') - )).path; + supports() { + return true; } - async loadFile(filename, currentDirectory) { - filename = await this.resolve(filename, currentDirectory); - let contents = await fs.readFile(filename, 'utf8'); - return {contents, filename}; + supportsSync() { + return false; } - loadFileSync(filename, currentDirectory) { - return syncPromise(this.loadFile(filename, currentDirectory)); + async loadFile(filename, currentDirectory) { + let resolved = await resolver.resolve( + filename, + path.join(currentDirectory, 'index') + ); + return { + contents: await fs.readFile(resolved.path, 'utf8'), + filename: resolved.path + }; } } diff --git a/src/assets/SASSAsset.js b/src/assets/SASSAsset.js index 69e6e71e7f6..ff60c0a43c5 100644 --- a/src/assets/SASSAsset.js +++ b/src/assets/SASSAsset.js @@ -4,7 +4,6 @@ const promisify = require('../utils/promisify'); const path = require('path'); const os = require('os'); const Resolver = require('../Resolver'); -const syncPromise = require('../utils/syncPromise'); class SASSAsset extends Asset { constructor(name, options) { @@ -37,26 +36,21 @@ class SASSAsset extends Asset { return new sass.types.String(`url(${JSON.stringify(filename)})`); } }); - + opts.importer = opts.importer || []; opts.importer = Array.isArray(opts.importer) ? opts.importer : [opts.importer]; opts.importer.push((url, prev, done) => { - let resolved; - try { - if (!/^(~|\.\/|\/)/.test(url)) { - url = './' + url; - } else if (!/^(~\/|\.\/|\/)/.test(url)) { - url = url.substring(1); - } - resolved = syncPromise( - resolver.resolve(url, prev === 'stdin' ? this.name : prev) - ).path; - } catch (e) { - resolved = url; + if (!/^(~|\.\/|\/)/.test(url)) { + url = './' + url; + } else if (!/^(~\/|\.\/|\/)/.test(url)) { + url = url.substring(1); } - return done({ - file: resolved - }); + resolver + .resolve(url, prev === 'stdin' ? this.name : prev) + .then(resolved => resolved.path) + .catch(() => url) + .then(file => done({file})) + .catch(err => done(normalizeError(err))); }); return await render(opts); @@ -80,3 +74,18 @@ class SASSAsset extends Asset { } module.exports = SASSAsset; + +// Ensures an error inherits from Error +function normalizeError(err) { + let message = 'Unknown error'; + + if (err) { + if (err instanceof Error) { + return err; + } + + message = err.stack || err.message || err; + } + + return new Error(message); +} diff --git a/src/assets/StylusAsset.js b/src/assets/StylusAsset.js index cb8a0d17c14..02bc025c4ca 100644 --- a/src/assets/StylusAsset.js +++ b/src/assets/StylusAsset.js @@ -2,7 +2,6 @@ const Asset = require('../Asset'); const localRequire = require('../utils/localRequire'); const Resolver = require('../Resolver'); -const syncPromise = require('../utils/syncPromise'); const URL_RE = /^(?:url\s*\(\s*)?['"]?(?:[#/]|(?:https?:)?\/\/)/i; @@ -21,13 +20,12 @@ class StylusAsset extends Asset { let style = stylus(code, opts); style.set('filename', this.name); style.set('include css', true); - style.set('Evaluator', await createEvaluator(this)); - // Setup a handler for the URL function so we add dependencies for linked assets. style.define('url', node => { let filename = this.addURLDependency(node.val, node.filename); return new stylus.nodes.Literal(`url(${JSON.stringify(filename)})`); }); + style.set('Evaluator', await createEvaluator(code, this, style.options)); return style; } @@ -50,18 +48,53 @@ class StylusAsset extends Asset { } } -async function createEvaluator(asset) { - const Evaluator = await localRequire( - 'stylus/lib/visitor/evaluator', - asset.name +async function getDependencies(code, asset, options) { + const [Parser, DepsResolver, nodes] = await Promise.all( + ['parser', 'visitor/deps-resolver', 'nodes'].map(dep => + localRequire('stylus/lib/' + dep, asset.name) + ) ); - const utils = await localRequire('stylus/lib/utils', asset.name); - const resolver = new Resolver( + + nodes.filename = asset.name; + class ImportVisitor extends DepsResolver { + visitImport(imported) { + let path = imported.path.first.string; + + if (!deps.has(path)) { + deps.set(path, resolver.resolve(path, asset.name).then(m => m.path)); + } + } + } + + let parser = new Parser(code, options); + let ast = parser.parse(); + let deps = new Map(); + let resolver = new Resolver( Object.assign({}, asset.options, { extensions: ['.styl', '.css'] }) ); + new ImportVisitor(ast, options).visit(ast); + + // Return a map with all await'd paths + return new Map( + await Promise.all( + Array.from(deps.entries()).map(async ([path, resolved]) => [ + path, + await resolved.catch(() => null) + ]) + ) + ); +} + +async function createEvaluator(code, asset, options) { + const deps = await getDependencies(code, asset, options); + const Evaluator = await localRequire( + 'stylus/lib/visitor/evaluator', + asset.name + ); + const utils = await localRequire('stylus/lib/utils', asset.name); // This is a custom stylus evaluator that extends stylus with support for the node // require resolution algorithm. It also adds all dependencies to the parcel asset // tree so the file watcher works correctly, etc. @@ -70,15 +103,15 @@ async function createEvaluator(asset) { let node = this.visit(imported.path).first; let path = node.string; if (node.name !== 'url' && path && !URL_RE.test(path)) { - try { - // First try resolving using the node require resolution algorithm. - // This allows stylus files in node_modules to be resolved properly. - // If we find something, update the AST so stylus gets the absolute path to load later. - node.string = syncPromise( - resolver.resolve(path, imported.filename) - ).path; + let resolved = deps.get(path); + + // First try resolving using the node require resolution algorithm. + // This allows stylus files in node_modules to be resolved properly. + // If we find something, update the AST so stylus gets the absolute path to load later. + if (resolved) { + node.string = resolved; asset.addDependency(node.string, {includedInParent: true}); - } catch (err) { + } else { // If we couldn't resolve, try the normal stylus resolver. // We just need to do this to keep track of the dependencies - stylus does the real work. diff --git a/src/utils/pipeSpawn.js b/src/utils/pipeSpawn.js index a89c1b9711b..823cbf9ac6e 100644 --- a/src/utils/pipeSpawn.js +++ b/src/utils/pipeSpawn.js @@ -2,13 +2,23 @@ const spawn = require('cross-spawn'); const logger = require('../Logger'); function pipeSpawn(cmd, params, opts) { - const cp = spawn(cmd, params, Object.assign({ - env: Object.assign({ - FORCE_COLOR: logger.color, - npm_config_color: logger.color ? 'always': '', - npm_config_progress: true - }, process.env) - }, opts)); + const cp = spawn( + cmd, + params, + Object.assign( + { + env: Object.assign( + { + FORCE_COLOR: logger.color, + npm_config_color: logger.color ? 'always' : '', + npm_config_progress: true + }, + process.env + ) + }, + opts + ) + ); cp.stdout.setEncoding('utf8').on('data', d => logger.writeRaw(d)); cp.stderr.setEncoding('utf8').on('data', d => logger.writeRaw(d));