Permalink
Cannot retrieve contributors at this time
436 lines (389 sloc)
11.1 KB
| let { isComplete, isClean } = require('./symbols') | |
| let MapGenerator = require('./map-generator') | |
| let stringify = require('./stringify') | |
| let warnOnce = require('./warn-once') | |
| let Result = require('./result') | |
| let parse = require('./parse') | |
| let Root = require('./root') | |
| function isPromise (obj) { | |
| return typeof obj === 'object' && typeof obj.then === 'function' | |
| } | |
| function walkVisitor (node, callback) { | |
| return node.each((child, i) => { | |
| if (child[isClean]) return | |
| child[isClean] = true | |
| callback(child, i, 'enter') | |
| if (child.nodes) walkVisitor(child, callback) | |
| callback(child, i, 'exit') | |
| if (!child[isClean]) return 'start-again' | |
| child[isComplete] = true | |
| }) | |
| } | |
| /** | |
| * A Promise proxy for the result of PostCSS transformations. | |
| * | |
| * A `LazyResult` instance is returned by {@link Processor#process}. | |
| * | |
| * @example | |
| * const lazy = postcss([autoprefixer]).process(css) | |
| */ | |
| class LazyResult { | |
| constructor (processor, css, opts) { | |
| this.stringified = false | |
| this.processed = false | |
| let root | |
| if (typeof css === 'object' && css !== null && css.type === 'root') { | |
| root = css | |
| } else if (css instanceof LazyResult || css instanceof Result) { | |
| root = css.root | |
| if (css.map) { | |
| if (typeof opts.map === 'undefined') opts.map = { } | |
| if (!opts.map.inline) opts.map.inline = false | |
| opts.map.prev = css.map | |
| } | |
| } else { | |
| let parser = parse | |
| if (opts.syntax) parser = opts.syntax.parse | |
| if (opts.parser) parser = opts.parser | |
| if (parser.parse) parser = parser.parse | |
| try { | |
| root = parser(css, opts) | |
| } catch (error) { | |
| this.error = error | |
| } | |
| } | |
| this.result = new Result(processor, root, opts) | |
| } | |
| /** | |
| * Returns a {@link Processor} instance, which will be used | |
| * for CSS transformations. | |
| * | |
| * @type {Processor} | |
| */ | |
| get processor () { | |
| return this.result.processor | |
| } | |
| /** | |
| * Options from the {@link Processor#process} call. | |
| * | |
| * @type {processOptions} | |
| */ | |
| get opts () { | |
| return this.result.opts | |
| } | |
| /** | |
| * Processes input CSS through synchronous plugins, converts `Root` | |
| * to a CSS string and returns {@link Result#css}. | |
| * | |
| * This property will only work with synchronous plugins. | |
| * If the processor contains any asynchronous plugins | |
| * it will throw an error. This is why this method is only | |
| * for debug purpose, you should always use {@link LazyResult#then}. | |
| * | |
| * @type {string} | |
| * @see Result#css | |
| */ | |
| get css () { | |
| return this.stringify().css | |
| } | |
| /** | |
| * An alias for the `css` property. Use it with syntaxes | |
| * that generate non-CSS output. | |
| * | |
| * This property will only work with synchronous plugins. | |
| * If the processor contains any asynchronous plugins | |
| * it will throw an error. This is why this method is only | |
| * for debug purpose, you should always use {@link LazyResult#then}. | |
| * | |
| * @type {string} | |
| * @see Result#content | |
| */ | |
| get content () { | |
| return this.stringify().content | |
| } | |
| /** | |
| * Processes input CSS through synchronous plugins | |
| * and returns {@link Result#map}. | |
| * | |
| * This property will only work with synchronous plugins. | |
| * If the processor contains any asynchronous plugins | |
| * it will throw an error. This is why this method is only | |
| * for debug purpose, you should always use {@link LazyResult#then}. | |
| * | |
| * @type {SourceMapGenerator} | |
| * @see Result#map | |
| */ | |
| get map () { | |
| return this.stringify().map | |
| } | |
| /** | |
| * Processes input CSS through synchronous plugins | |
| * and returns {@link Result#root}. | |
| * | |
| * This property will only work with synchronous plugins. If the processor | |
| * contains any asynchronous plugins it will throw an error. | |
| * | |
| * This is why this method is only for debug purpose, | |
| * you should always use {@link LazyResult#then}. | |
| * | |
| * @type {Root} | |
| * @see Result#root | |
| */ | |
| get root () { | |
| return this.sync().root | |
| } | |
| /** | |
| * Processes input CSS through synchronous plugins | |
| * and returns {@link Result#messages}. | |
| * | |
| * This property will only work with synchronous plugins. If the processor | |
| * contains any asynchronous plugins it will throw an error. | |
| * | |
| * This is why this method is only for debug purpose, | |
| * you should always use {@link LazyResult#then}. | |
| * | |
| * @type {Message[]} | |
| * @see Result#messages | |
| */ | |
| get messages () { | |
| return this.sync().messages | |
| } | |
| /** | |
| * Processes input CSS through synchronous plugins | |
| * and calls {@link Result#warnings()}. | |
| * | |
| * @return {Warning[]} Warnings from plugins. | |
| */ | |
| warnings () { | |
| return this.sync().warnings() | |
| } | |
| /** | |
| * Alias for the {@link LazyResult#css} property. | |
| * | |
| * @example | |
| * lazy + '' === lazy.css | |
| * | |
| * @return {string} Output CSS. | |
| */ | |
| toString () { | |
| return this.css | |
| } | |
| /** | |
| * Processes input CSS through synchronous and asynchronous plugins | |
| * and calls `onFulfilled` with a Result instance. If a plugin throws | |
| * an error, the `onRejected` callback will be executed. | |
| * | |
| * It implements standard Promise API. | |
| * | |
| * @param {onFulfilled} onFulfilled Callback will be executed | |
| * when all plugins will finish work. | |
| * @param {onRejected} onRejected Callback will be executed on any error. | |
| * | |
| * @return {Promise} Promise API to make queue. | |
| * | |
| * @example | |
| * postcss([autoprefixer]).process(css, { from: cssPath }).then(result => { | |
| * console.log(result.css) | |
| * }) | |
| */ | |
| then (onFulfilled, onRejected) { | |
| if (process.env.NODE_ENV !== 'production') { | |
| if (!('from' in this.opts)) { | |
| warnOnce( | |
| 'Without `from` option PostCSS could generate wrong source map ' + | |
| 'and will not find Browserslist config. Set it to CSS file path ' + | |
| 'or to `undefined` to prevent this warning.' | |
| ) | |
| } | |
| } | |
| return this.async().then(onFulfilled, onRejected) | |
| } | |
| /** | |
| * Processes input CSS through synchronous and asynchronous plugins | |
| * and calls onRejected for each error thrown in any plugin. | |
| * | |
| * It implements standard Promise API. | |
| * | |
| * @param {onRejected} onRejected Callback will be executed on any error. | |
| * | |
| * @return {Promise} Promise API to make queue. | |
| * | |
| * @example | |
| * postcss([autoprefixer]).process(css).then(result => { | |
| * console.log(result.css) | |
| * }).catch(error => { | |
| * console.error(error) | |
| * }) | |
| */ | |
| catch (onRejected) { | |
| return this.async().catch(onRejected) | |
| } | |
| /** | |
| * Processes input CSS through synchronous and asynchronous plugins | |
| * and calls onFinally on any error or when all plugins will finish work. | |
| * | |
| * It implements standard Promise API. | |
| * | |
| * @param {onFinally} onFinally Callback will be executed on any error or | |
| * when all plugins will finish work. | |
| * | |
| * @return {Promise} Promise API to make queue. | |
| * | |
| * @example | |
| * postcss([autoprefixer]).process(css).finally(() => { | |
| * console.log('processing ended') | |
| * }) | |
| */ | |
| finally (onFinally) { | |
| return this.async().then(onFinally, onFinally) | |
| } | |
| handleError (error, plugin) { | |
| try { | |
| this.error = error | |
| if (error.name === 'CssSyntaxError' && !error.plugin) { | |
| error.plugin = plugin.postcssPlugin | |
| error.setMessage() | |
| } else if (plugin.postcssVersion) { | |
| if (process.env.NODE_ENV !== 'production') { | |
| let pluginName = plugin.postcssPlugin | |
| let pluginVer = plugin.postcssVersion | |
| let runtimeVer = this.result.processor.version | |
| let a = pluginVer.split('.') | |
| let b = runtimeVer.split('.') | |
| if (a[0] !== b[0] || parseInt(a[1]) > parseInt(b[1])) { | |
| console.error( | |
| 'Unknown error from PostCSS plugin. Your current PostCSS ' + | |
| 'version is ' + runtimeVer + ', but ' + pluginName + ' uses ' + | |
| pluginVer + '. Perhaps this is the source of the error below.' | |
| ) | |
| } | |
| } | |
| } | |
| } catch (err) { | |
| if (console && console.error) console.error(err) | |
| } | |
| } | |
| asyncTick (resolve, reject) { | |
| if (this.plugin >= this.processor.plugins.length) { | |
| this.runVisitorPlugins() | |
| this.processed = true | |
| return resolve() | |
| } | |
| try { | |
| let plugin = this.processor.plugins[this.plugin] | |
| let promise = this.run(plugin) | |
| this.plugin += 1 | |
| if (isPromise(promise)) { | |
| promise.then(() => { | |
| this.asyncTick(resolve, reject) | |
| }).catch(error => { | |
| this.handleError(error, plugin) | |
| this.processed = true | |
| reject(error) | |
| }) | |
| } else { | |
| this.asyncTick(resolve, reject) | |
| } | |
| } catch (error) { | |
| this.processed = true | |
| reject(error) | |
| } | |
| } | |
| async () { | |
| if (this.processed) { | |
| return new Promise((resolve, reject) => { | |
| if (this.error) { | |
| reject(this.error) | |
| } else { | |
| resolve(this.stringify()) | |
| } | |
| }) | |
| } | |
| if (this.processing) { | |
| return this.processing | |
| } | |
| this.processing = new Promise((resolve, reject) => { | |
| if (this.error) return reject(this.error) | |
| this.plugin = 0 | |
| this.asyncTick(resolve, reject) | |
| }).then(() => { | |
| this.processed = true | |
| return this.stringify() | |
| }) | |
| return this.processing | |
| } | |
| sync () { | |
| if (this.processed) return this.result | |
| this.processed = true | |
| if (this.processing) { | |
| throw new Error( | |
| 'Use process(css).then(cb) to work with async plugins' | |
| ) | |
| } | |
| if (this.error) throw this.error | |
| for (let plugin of this.result.processor.plugins) { | |
| let promise = this.run(plugin) | |
| if (isPromise(promise)) { | |
| throw new Error( | |
| 'Use process(css).then(cb) to work with async plugins' | |
| ) | |
| } | |
| } | |
| this.runVisitorPlugins() | |
| return this.result | |
| } | |
| run (plugin) { | |
| this.result.lastPlugin = plugin | |
| try { | |
| return plugin(this.result.root, this.result) | |
| } catch (error) { | |
| this.handleError(error, plugin) | |
| throw error | |
| } | |
| } | |
| stringify () { | |
| if (this.stringified) return this.result | |
| this.stringified = true | |
| this.sync() | |
| let opts = this.result.opts | |
| let str = stringify | |
| if (opts.syntax) str = opts.syntax.stringify | |
| if (opts.stringifier) str = opts.stringifier | |
| if (str.stringify) str = str.stringify | |
| let map = new MapGenerator(str, this.result.root, this.result.opts) | |
| let data = map.generate() | |
| this.result.css = data[0] | |
| this.result.map = data[1] | |
| return this.result | |
| } | |
| runVisitorPlugins () { | |
| let root = this.result.root | |
| if (!root.listeners) return | |
| while (!root[isClean]) { | |
| root[isClean] = true | |
| walkVisitor(root, (node, index, phase) => { | |
| let visitors = root.listeners[node.type + '.' + phase] | |
| if (!visitors) return | |
| let proxy = node.toProxy() | |
| for (let visitor of visitors) { | |
| try { | |
| visitor(proxy, index) | |
| } catch (e) { | |
| throw node.addToError(e) | |
| } | |
| } | |
| }) | |
| } | |
| root[isComplete] = true | |
| } | |
| } | |
| module.exports = LazyResult | |
| Root.registerLazyResult(LazyResult) | |
| /** | |
| * @callback onFulfilled | |
| * @param {Result} result | |
| */ | |
| /** | |
| * @callback onRejected | |
| * @param {Error} error | |
| */ |