// @flow import type {Root} from 'postcss'; import type {FilePath, MutableAsset, PluginOptions} from '@parcel/types'; import {hashString} from '@parcel/hash'; import SourceMap from '@parcel/source-map'; import {Transformer} from '@parcel/plugin'; import {createDependencyLocation, remapSourceLocation} from '@parcel/utils'; import postcss from 'postcss'; import nullthrows from 'nullthrows'; import valueParser from 'postcss-value-parser'; import semver from 'semver'; import path from 'path'; const URL_RE = /url\s*\(/; const IMPORT_RE = /@import/; const COMPOSES_RE = /composes:.+from\s*("|').*("|')\s*;?/; const FROM_IMPORT_RE = /.+from\s*(?:"|')(.*)(?:"|')\s*;?/; const MODULE_BY_NAME_RE = /\.module\./; function canHaveDependencies(filePath: FilePath, code: string) { return !/\.css$/.test(filePath) || IMPORT_RE.test(code) || URL_RE.test(code); } export default (new Transformer({ canReuseAST({ast}) { return ast.type === 'postcss' && semver.satisfies(ast.version, '^8.2.1'); }, async parse({asset}) { // This is set by other transformers (e.g. Stylus) to indicate that it has already processed // all dependencies, and that the CSS transformer can skip this asset completely. This is // required because when stylus processes e.g. url() it replaces them with a dependency id // to be filled in later. When the CSS transformer runs, it would pick that up and try to // resolve a dependency for the id which obviously doesn't exist. Also, it's faster to do // it this way since the resulting CSS doesn't need to be re-parsed. let isCSSModule = asset.meta.cssModulesCompiled !== true && MODULE_BY_NAME_RE.test(asset.filePath); if (asset.meta.hasDependencies === false && !isCSSModule) { return null; } let code = await asset.getCode(); if ( code != null && !canHaveDependencies(asset.filePath, code) && !isCSSModule ) { return null; } return { type: 'postcss', version: '8.2.1', program: postcss .parse(code, { from: asset.filePath, }) .toJSON(), }; }, async transform({asset, resolve, options}) { // Normalize the asset's environment so that properties that only affect JS don't cause CSS to be duplicated. // For example, with ESModule and CommonJS targets, only a single shared CSS bundle should be produced. let env = asset.env; asset.setEnvironment({ context: 'browser', engines: { browsers: asset.env.engines.browsers, }, shouldOptimize: asset.env.shouldOptimize, sourceMap: asset.env.sourceMap, }); let isCSSModule = asset.meta.cssModulesCompiled !== true && MODULE_BY_NAME_RE.test(asset.filePath); // Check for `hasDependencies` being false here as well, as it's possible // another transformer (such as PostCSSTransformer) has already parsed an // ast and CSSTransformer's parse was never called. let ast = await asset.getAST(); if (!ast || (asset.meta.hasDependencies === false && !isCSSModule)) { return [asset]; } let program: Root = postcss.fromJSON(ast.program); let assets = [asset]; if (isCSSModule) { assets = await compileCSSModules(asset, env, program, resolve, options); } if (asset.meta.hasDependencies === false) { return assets; } let originalSourceMap = await asset.getMap(); let createLoc = (start, specifier, lineOffset, colOffset, o) => { let loc = createDependencyLocation( start, specifier, lineOffset, colOffset, o, ); if (originalSourceMap) { loc = remapSourceLocation(loc, originalSourceMap); } return loc; }; let isDirty = false; program.walkAtRules('import', rule => { let params = valueParser(rule.params); let [name, ...media] = params.nodes; let specifier; if ( name.type === 'function' && name.value === 'url' && name.nodes.length ) { name = name.nodes[0]; } specifier = name.value; if (!specifier) { throw new Error('Could not find import name for ' + String(rule)); } // If this came from an inline