UNPKG

@mpxjs/webpack-plugin

Version:

mpx compile core

452 lines (360 loc) 11.9 kB
const valueParser = require('postcss-value-parser') const { resolveRequests, normalizeUrl, requestify, isURLRequestable, WEBPACK_IGNORE_COMMENT_REGEXP } = require('../utils') const addQuery = require('../../utils/add-query') const isUrlFunc = /url/i const isImageSetFunc = /^(?:-webkit-)?image-set$/i const needParseDeclaration = /(?:url|(?:-webkit-)?image-set)\(/i function getNodeFromUrlFunc (node) { return node.nodes && node.nodes[0] } function getWebpackIgnoreCommentValue (index, nodes, inBetween) { if (index === 0 && typeof inBetween !== 'undefined') { return inBetween } let prevValueNode = nodes[index - 1] if (!prevValueNode) { // eslint-disable-next-line consistent-return return } if (prevValueNode.type === 'space') { if (!nodes[index - 2]) { // eslint-disable-next-line consistent-return return } prevValueNode = nodes[index - 2] } if (prevValueNode.type !== 'comment') { // eslint-disable-next-line consistent-return return } const matched = prevValueNode.value.match(WEBPACK_IGNORE_COMMENT_REGEXP) return matched && matched[2] === 'true' } function shouldHandleURL (url, declaration, result, options) { if (url.length === 0) { result.warn(`Unable to find uri in '${declaration.toString()}'`, { node: declaration }) return { requestable: false, needResolve: false } } return isURLRequestable(url, options) } function parseDeclaration (declaration, key, result, options) { if (!needParseDeclaration.test(declaration[key])) { return } const parsed = valueParser( declaration.raws && declaration.raws.value && declaration.raws.value.raw ? declaration.raws.value.raw : declaration[key] ) let inBetween if (declaration.raws && declaration.raws.between) { const lastCommentIndex = declaration.raws.between.lastIndexOf('/*') const matched = declaration.raws.between .slice(lastCommentIndex) .match(WEBPACK_IGNORE_COMMENT_REGEXP) if (matched) { inBetween = matched[2] === 'true' } } let isIgnoreOnDeclaration = false const prevNode = declaration.prev() if (prevNode && prevNode.type === 'comment') { const matched = prevNode.text.match(WEBPACK_IGNORE_COMMENT_REGEXP) if (matched) { isIgnoreOnDeclaration = matched[2] === 'true' } } let needIgnore const parsedURLs = [] parsed.walk((valueNode, index, valueNodes) => { if (valueNode.type !== 'function') { return } if (isUrlFunc.test(valueNode.value)) { needIgnore = getWebpackIgnoreCommentValue(index, valueNodes, inBetween) if ( (isIgnoreOnDeclaration && typeof needIgnore === 'undefined') || needIgnore ) { if (needIgnore) { // eslint-disable-next-line no-undefined needIgnore = undefined } return } const { nodes } = valueNode const isStringValue = nodes.length !== 0 && nodes[0].type === 'string' let url = isStringValue ? nodes[0].value : valueParser.stringify(nodes) url = normalizeUrl(url, isStringValue) const { requestable, needResolve } = shouldHandleURL( url, declaration, result, options ) // Do not traverse inside `url` if (!requestable) { // eslint-disable-next-line consistent-return return false } const queryParts = url.split('!') let prefix if (queryParts.length > 1) { url = queryParts.pop() prefix = queryParts.join('!') } parsedURLs.push({ declaration, parsed, node: getNodeFromUrlFunc(valueNode), prefix, url, needQuotes: false, needResolve }) // eslint-disable-next-line consistent-return return false } else if (isImageSetFunc.test(valueNode.value)) { for (const [innerIndex, nNode] of valueNode.nodes.entries()) { const { type, value } = nNode if (type === 'function' && isUrlFunc.test(value)) { needIgnore = getWebpackIgnoreCommentValue( innerIndex, valueNode.nodes ) if ( (isIgnoreOnDeclaration && typeof needIgnore === 'undefined') || needIgnore ) { if (needIgnore) { // eslint-disable-next-line no-undefined needIgnore = undefined } // eslint-disable-next-line no-continue continue } const { nodes } = nNode const isStringValue = nodes.length !== 0 && nodes[0].type === 'string' let url = isStringValue ? nodes[0].value : valueParser.stringify(nodes) url = normalizeUrl(url, isStringValue) const { requestable, needResolve } = shouldHandleURL( url, declaration, result, options ) // Do not traverse inside `url` if (!requestable) { // eslint-disable-next-line consistent-return return false } const queryParts = url.split('!') let prefix if (queryParts.length > 1) { url = queryParts.pop() prefix = queryParts.join('!') } parsedURLs.push({ declaration, parsed, node: getNodeFromUrlFunc(nNode), prefix, url, needQuotes: false, needResolve }) } else if (type === 'string') { needIgnore = getWebpackIgnoreCommentValue( innerIndex, valueNode.nodes ) if ( (isIgnoreOnDeclaration && typeof needIgnore === 'undefined') || needIgnore ) { if (needIgnore) { // eslint-disable-next-line no-undefined needIgnore = undefined } // eslint-disable-next-line no-continue continue } let url = normalizeUrl(value, true) const { requestable, needResolve } = shouldHandleURL( url, declaration, result, options ) // Do not traverse inside `url` if (!requestable) { // eslint-disable-next-line consistent-return return false } const queryParts = url.split('!') let prefix if (queryParts.length > 1) { url = queryParts.pop() prefix = queryParts.join('!') } parsedURLs.push({ declaration, parsed, node: nNode, prefix, url, needQuotes: true, needResolve }) } } // Do not traverse inside `image-set` // eslint-disable-next-line consistent-return return false } }) // eslint-disable-next-line consistent-return return parsedURLs } const plugin = (options = {}) => { return { postcssPlugin: 'postcss-url-parser', prepare (result) { const parsedDeclarations = [] return { Declaration (declaration) { const { isSupportDataURL, isSupportAbsoluteURL, externals, root } = options const parsedURL = parseDeclaration(declaration, 'value', result, { isSupportDataURL, isSupportAbsoluteURL, externals, root }) if (!parsedURL) { return } parsedDeclarations.push(...parsedURL) }, async OnceExit () { if (parsedDeclarations.length === 0) { return } const resolvedDeclarations = await Promise.all( parsedDeclarations.map(async (parsedDeclaration) => { const { url, needResolve } = parsedDeclaration if (options.filter) { const needKeep = await options.filter(url) if (!needKeep) { // eslint-disable-next-line consistent-return return } } if (!needResolve) { // eslint-disable-next-line consistent-return return parsedDeclaration } const splittedUrl = url.split(/(\?)?#/) const [pathname, query, hashOrQuery] = splittedUrl let hash = query ? '?' : '' hash += hashOrQuery ? `#${hashOrQuery}` : '' const { resolver, rootContext } = options const request = requestify( pathname, rootContext, Boolean(resolver) ) if (!resolver) { // eslint-disable-next-line consistent-return return { ...parsedDeclaration, url: request, hash } } const resolvedURL = await resolveRequests( resolver, options.context, [...new Set([request, url])] ) if (!resolvedURL) { // eslint-disable-next-line consistent-return return } // eslint-disable-next-line consistent-return return { ...parsedDeclaration, url: resolvedURL, hash } }) ) const urlToNameMap = new Map() const urlToReplacementMap = new Map() let hasUrlImportHelper = false for ( let index = 0; index <= resolvedDeclarations.length - 1; index++ ) { const item = resolvedDeclarations[index] if (!item) { // eslint-disable-next-line no-continue continue } if (!hasUrlImportHelper) { options.imports.push({ type: 'get_url_import', importName: '___CSS_LOADER_GET_URL_IMPORT___', url: options.urlHandler( require.resolve('../runtime/getUrl.js') ), index: -1 }) hasUrlImportHelper = true } const { url, prefix } = item const newUrl = prefix ? `${prefix}!${url}` : url let importName = urlToNameMap.get(newUrl) if (!importName) { importName = `___CSS_LOADER_URL_IMPORT_${urlToNameMap.size}___` urlToNameMap.set(newUrl, importName) const finalUrl = addQuery(newUrl, { isStyle: true }) options.imports.push({ type: 'url', importName, url: options.resolver ? options.urlHandler(finalUrl) : JSON.stringify(finalUrl), index }) } const { hash, needQuotes } = item const replacementKey = JSON.stringify({ newUrl, hash, needQuotes }) let replacementName = urlToReplacementMap.get(replacementKey) if (!replacementName) { replacementName = `___CSS_LOADER_URL_REPLACEMENT_${urlToReplacementMap.size}___` urlToReplacementMap.set(replacementKey, replacementName) options.replacements.push({ replacementName, importName, hash, needQuotes }) } // eslint-disable-next-line no-param-reassign item.node.type = 'word' // eslint-disable-next-line no-param-reassign item.node.value = replacementName // eslint-disable-next-line no-param-reassign item.declaration.value = item.parsed.toString() } } } } } } plugin.postcss = true module.exports = plugin