123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550 |
- 'use strict'
- let Container = require('./container')
- let Document = require('./document')
- let MapGenerator = require('./map-generator')
- let parse = require('./parse')
- let Result = require('./result')
- let Root = require('./root')
- let stringify = require('./stringify')
- let { isClean, my } = require('./symbols')
- let warnOnce = require('./warn-once')
- const TYPE_TO_CLASS_NAME = {
- atrule: 'AtRule',
- comment: 'Comment',
- decl: 'Declaration',
- document: 'Document',
- root: 'Root',
- rule: 'Rule'
- }
- const PLUGIN_PROPS = {
- AtRule: true,
- AtRuleExit: true,
- Comment: true,
- CommentExit: true,
- Declaration: true,
- DeclarationExit: true,
- Document: true,
- DocumentExit: true,
- Once: true,
- OnceExit: true,
- postcssPlugin: true,
- prepare: true,
- Root: true,
- RootExit: true,
- Rule: true,
- RuleExit: true
- }
- const NOT_VISITORS = {
- Once: true,
- postcssPlugin: true,
- prepare: true
- }
- const CHILDREN = 0
- function isPromise(obj) {
- return typeof obj === 'object' && typeof obj.then === 'function'
- }
- function getEvents(node) {
- let key = false
- let type = TYPE_TO_CLASS_NAME[node.type]
- if (node.type === 'decl') {
- key = node.prop.toLowerCase()
- } else if (node.type === 'atrule') {
- key = node.name.toLowerCase()
- }
- if (key && node.append) {
- return [
- type,
- type + '-' + key,
- CHILDREN,
- type + 'Exit',
- type + 'Exit-' + key
- ]
- } else if (key) {
- return [type, type + '-' + key, type + 'Exit', type + 'Exit-' + key]
- } else if (node.append) {
- return [type, CHILDREN, type + 'Exit']
- } else {
- return [type, type + 'Exit']
- }
- }
- function toStack(node) {
- let events
- if (node.type === 'document') {
- events = ['Document', CHILDREN, 'DocumentExit']
- } else if (node.type === 'root') {
- events = ['Root', CHILDREN, 'RootExit']
- } else {
- events = getEvents(node)
- }
- return {
- eventIndex: 0,
- events,
- iterator: 0,
- node,
- visitorIndex: 0,
- visitors: []
- }
- }
- function cleanMarks(node) {
- node[isClean] = false
- if (node.nodes) node.nodes.forEach(i => cleanMarks(i))
- return node
- }
- let postcss = {}
- class LazyResult {
- constructor(processor, css, opts) {
- this.stringified = false
- this.processed = false
- let root
- if (
- typeof css === 'object' &&
- css !== null &&
- (css.type === 'root' || css.type === 'document')
- ) {
- root = cleanMarks(css)
- } else if (css instanceof LazyResult || css instanceof Result) {
- root = cleanMarks(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.processed = true
- this.error = error
- }
- if (root && !root[my]) {
- /* c8 ignore next 2 */
- Container.rebuild(root)
- }
- }
- this.result = new Result(processor, root, opts)
- this.helpers = { ...postcss, postcss, result: this.result }
- this.plugins = this.processor.plugins.map(plugin => {
- if (typeof plugin === 'object' && plugin.prepare) {
- return { ...plugin, ...plugin.prepare(this.result) }
- } else {
- return plugin
- }
- })
- }
- async() {
- if (this.error) return Promise.reject(this.error)
- if (this.processed) return Promise.resolve(this.result)
- if (!this.processing) {
- this.processing = this.runAsync()
- }
- return this.processing
- }
- catch(onRejected) {
- return this.async().catch(onRejected)
- }
- finally(onFinally) {
- return this.async().then(onFinally, onFinally)
- }
- getAsyncError() {
- throw new Error('Use process(css).then(cb) to work with async plugins')
- }
- handleError(error, node) {
- let plugin = this.result.lastPlugin
- try {
- if (node) node.addToError(error)
- 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])) {
- // eslint-disable-next-line no-console
- 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) {
- /* c8 ignore next 3 */
- // eslint-disable-next-line no-console
- if (console && console.error) console.error(err)
- }
- return error
- }
- prepareVisitors() {
- this.listeners = {}
- let add = (plugin, type, cb) => {
- if (!this.listeners[type]) this.listeners[type] = []
- this.listeners[type].push([plugin, cb])
- }
- for (let plugin of this.plugins) {
- if (typeof plugin === 'object') {
- for (let event in plugin) {
- if (!PLUGIN_PROPS[event] && /^[A-Z]/.test(event)) {
- throw new Error(
- `Unknown event ${event} in ${plugin.postcssPlugin}. ` +
- `Try to update PostCSS (${this.processor.version} now).`
- )
- }
- if (!NOT_VISITORS[event]) {
- if (typeof plugin[event] === 'object') {
- for (let filter in plugin[event]) {
- if (filter === '*') {
- add(plugin, event, plugin[event][filter])
- } else {
- add(
- plugin,
- event + '-' + filter.toLowerCase(),
- plugin[event][filter]
- )
- }
- }
- } else if (typeof plugin[event] === 'function') {
- add(plugin, event, plugin[event])
- }
- }
- }
- }
- }
- this.hasListener = Object.keys(this.listeners).length > 0
- }
- async runAsync() {
- this.plugin = 0
- for (let i = 0; i < this.plugins.length; i++) {
- let plugin = this.plugins[i]
- let promise = this.runOnRoot(plugin)
- if (isPromise(promise)) {
- try {
- await promise
- } catch (error) {
- throw this.handleError(error)
- }
- }
- }
- this.prepareVisitors()
- if (this.hasListener) {
- let root = this.result.root
- while (!root[isClean]) {
- root[isClean] = true
- let stack = [toStack(root)]
- while (stack.length > 0) {
- let promise = this.visitTick(stack)
- if (isPromise(promise)) {
- try {
- await promise
- } catch (e) {
- let node = stack[stack.length - 1].node
- throw this.handleError(e, node)
- }
- }
- }
- }
- if (this.listeners.OnceExit) {
- for (let [plugin, visitor] of this.listeners.OnceExit) {
- this.result.lastPlugin = plugin
- try {
- if (root.type === 'document') {
- let roots = root.nodes.map(subRoot =>
- visitor(subRoot, this.helpers)
- )
- await Promise.all(roots)
- } else {
- await visitor(root, this.helpers)
- }
- } catch (e) {
- throw this.handleError(e)
- }
- }
- }
- }
- this.processed = true
- return this.stringify()
- }
- runOnRoot(plugin) {
- this.result.lastPlugin = plugin
- try {
- if (typeof plugin === 'object' && plugin.Once) {
- if (this.result.root.type === 'document') {
- let roots = this.result.root.nodes.map(root =>
- plugin.Once(root, this.helpers)
- )
- if (isPromise(roots[0])) {
- return Promise.all(roots)
- }
- return roots
- }
- return plugin.Once(this.result.root, this.helpers)
- } else if (typeof plugin === 'function') {
- return plugin(this.result.root, this.result)
- }
- } catch (error) {
- throw this.handleError(error)
- }
- }
- stringify() {
- if (this.error) throw this.error
- 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
- }
- sync() {
- if (this.error) throw this.error
- if (this.processed) return this.result
- this.processed = true
- if (this.processing) {
- throw this.getAsyncError()
- }
- for (let plugin of this.plugins) {
- let promise = this.runOnRoot(plugin)
- if (isPromise(promise)) {
- throw this.getAsyncError()
- }
- }
- this.prepareVisitors()
- if (this.hasListener) {
- let root = this.result.root
- while (!root[isClean]) {
- root[isClean] = true
- this.walkSync(root)
- }
- if (this.listeners.OnceExit) {
- if (root.type === 'document') {
- for (let subRoot of root.nodes) {
- this.visitSync(this.listeners.OnceExit, subRoot)
- }
- } else {
- this.visitSync(this.listeners.OnceExit, root)
- }
- }
- }
- return this.result
- }
- 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)
- }
- toString() {
- return this.css
- }
- visitSync(visitors, node) {
- for (let [plugin, visitor] of visitors) {
- this.result.lastPlugin = plugin
- let promise
- try {
- promise = visitor(node, this.helpers)
- } catch (e) {
- throw this.handleError(e, node.proxyOf)
- }
- if (node.type !== 'root' && node.type !== 'document' && !node.parent) {
- return true
- }
- if (isPromise(promise)) {
- throw this.getAsyncError()
- }
- }
- }
- visitTick(stack) {
- let visit = stack[stack.length - 1]
- let { node, visitors } = visit
- if (node.type !== 'root' && node.type !== 'document' && !node.parent) {
- stack.pop()
- return
- }
- if (visitors.length > 0 && visit.visitorIndex < visitors.length) {
- let [plugin, visitor] = visitors[visit.visitorIndex]
- visit.visitorIndex += 1
- if (visit.visitorIndex === visitors.length) {
- visit.visitors = []
- visit.visitorIndex = 0
- }
- this.result.lastPlugin = plugin
- try {
- return visitor(node.toProxy(), this.helpers)
- } catch (e) {
- throw this.handleError(e, node)
- }
- }
- if (visit.iterator !== 0) {
- let iterator = visit.iterator
- let child
- while ((child = node.nodes[node.indexes[iterator]])) {
- node.indexes[iterator] += 1
- if (!child[isClean]) {
- child[isClean] = true
- stack.push(toStack(child))
- return
- }
- }
- visit.iterator = 0
- delete node.indexes[iterator]
- }
- let events = visit.events
- while (visit.eventIndex < events.length) {
- let event = events[visit.eventIndex]
- visit.eventIndex += 1
- if (event === CHILDREN) {
- if (node.nodes && node.nodes.length) {
- node[isClean] = true
- visit.iterator = node.getIterator()
- }
- return
- } else if (this.listeners[event]) {
- visit.visitors = this.listeners[event]
- return
- }
- }
- stack.pop()
- }
- walkSync(node) {
- node[isClean] = true
- let events = getEvents(node)
- for (let event of events) {
- if (event === CHILDREN) {
- if (node.nodes) {
- node.each(child => {
- if (!child[isClean]) this.walkSync(child)
- })
- }
- } else {
- let visitors = this.listeners[event]
- if (visitors) {
- if (this.visitSync(visitors, node.toProxy())) return
- }
- }
- }
- }
- warnings() {
- return this.sync().warnings()
- }
- get content() {
- return this.stringify().content
- }
- get css() {
- return this.stringify().css
- }
- get map() {
- return this.stringify().map
- }
- get messages() {
- return this.sync().messages
- }
- get opts() {
- return this.result.opts
- }
- get processor() {
- return this.result.processor
- }
- get root() {
- return this.sync().root
- }
- get [Symbol.toStringTag]() {
- return 'LazyResult'
- }
- }
- LazyResult.registerPostcss = dependant => {
- postcss = dependant
- }
- module.exports = LazyResult
- LazyResult.default = LazyResult
- Root.registerLazyResult(LazyResult)
- Document.registerLazyResult(LazyResult)
|