import postcss, { ProcessOptions, Result, SourceMap, Message, LazyResult } from 'postcss' import trimPlugin from './stylePluginTrim' import scopedPlugin from './stylePluginScoped' import { processors, StylePreprocessor, StylePreprocessorResults, PreprocessLang } from './stylePreprocessors' import { RawSourceMap } from 'source-map' import { cssVarsPlugin } from './cssVars' export interface SFCStyleCompileOptions { source: string filename: string id: string scoped?: boolean trim?: boolean isProd?: boolean inMap?: RawSourceMap preprocessLang?: PreprocessLang preprocessOptions?: any preprocessCustomRequire?: (id: string) => any postcssOptions?: any postcssPlugins?: any[] /** * @deprecated */ map?: RawSourceMap } /** * Aligns with postcss-modules * https://github.com/css-modules/postcss-modules */ export interface CSSModulesOptions { scopeBehaviour?: 'global' | 'local' generateScopedName?: | string | ((name: string, filename: string, css: string) => string) hashPrefix?: string localsConvention?: 'camelCase' | 'camelCaseOnly' | 'dashes' | 'dashesOnly' exportGlobals?: boolean globalModulePaths?: string[] } export interface SFCAsyncStyleCompileOptions extends SFCStyleCompileOptions { isAsync?: boolean // css modules support, note this requires async so that we can get the // resulting json modules?: boolean modulesOptions?: CSSModulesOptions } export interface SFCStyleCompileResults { code: string map: RawSourceMap | undefined rawResult: Result | LazyResult | undefined errors: Error[] modules?: Record dependencies: Set } export function compileStyle( options: SFCStyleCompileOptions ): SFCStyleCompileResults { return doCompileStyle({ ...options, isAsync: false }) as SFCStyleCompileResults } export function compileStyleAsync( options: SFCAsyncStyleCompileOptions ): Promise { return doCompileStyle({ ...options, isAsync: true }) as Promise< SFCStyleCompileResults > } export function doCompileStyle( options: SFCAsyncStyleCompileOptions ): SFCStyleCompileResults | Promise { const { filename, id, scoped = false, trim = true, isProd = false, modules = false, modulesOptions = {}, preprocessLang, postcssOptions, postcssPlugins } = options const preprocessor = preprocessLang && processors[preprocessLang] const preProcessedSource = preprocessor && preprocess(options, preprocessor) const map = preProcessedSource ? preProcessedSource.map : options.inMap || options.map const source = preProcessedSource ? preProcessedSource.code : options.source const shortId = id.replace(/^data-v-/, '') const longId = `data-v-${shortId}` const plugins = (postcssPlugins || []).slice() plugins.unshift(cssVarsPlugin({ id: shortId, isProd })) if (trim) { plugins.push(trimPlugin()) } if (scoped) { plugins.push(scopedPlugin(longId)) } let cssModules: Record | undefined if (modules) { if (__GLOBAL__ || __ESM_BROWSER__) { throw new Error( '[@vue/compiler-sfc] `modules` option is not supported in the browser build.' ) } if (!options.isAsync) { throw new Error( '[@vue/compiler-sfc] `modules` option can only be used with compileStyleAsync().' ) } plugins.push( require('postcss-modules')({ ...modulesOptions, getJSON: (_cssFileName: string, json: Record) => { cssModules = json } }) ) } const postCSSOptions: ProcessOptions = { ...postcssOptions, to: filename, from: filename } if (map) { postCSSOptions.map = { inline: false, annotation: false, prev: map } } let result: LazyResult | undefined let code: string | undefined let outMap: SourceMap | undefined // stylus output include plain css. so need remove the repeat item const dependencies = new Set( preProcessedSource ? preProcessedSource.dependencies : [] ) // sass has filename self when provided filename option dependencies.delete(filename) const errors: Error[] = [] if (preProcessedSource && preProcessedSource.errors.length) { errors.push(...preProcessedSource.errors) } const recordPlainCssDependencies = (messages: Message[]) => { messages.forEach(msg => { if (msg.type === 'dependency') { // postcss output path is absolute position path dependencies.add(msg.file) } }) return dependencies } try { result = postcss(plugins).process(source, postCSSOptions) // In async mode, return a promise. if (options.isAsync) { return result .then(result => ({ code: result.css || '', map: result.map && (result.map.toJSON() as any), errors, modules: cssModules, rawResult: result, dependencies: recordPlainCssDependencies(result.messages) })) .catch(error => ({ code: '', map: undefined, errors: [...errors, error], rawResult: undefined, dependencies })) } recordPlainCssDependencies(result.messages) // force synchronous transform (we know we only have sync plugins) code = result.css outMap = result.map } catch (e) { errors.push(e) } return { code: code || ``, map: outMap && (outMap.toJSON() as any), errors, rawResult: result, dependencies } } function preprocess( options: SFCStyleCompileOptions, preprocessor: StylePreprocessor ): StylePreprocessorResults { if ((__ESM_BROWSER__ || __GLOBAL__) && !options.preprocessCustomRequire) { throw new Error( `[@vue/compiler-sfc] Style preprocessing in the browser build must ` + `provide the \`preprocessCustomRequire\` option to return the in-browser ` + `version of the preprocessor.` ) } return preprocessor( options.source, options.inMap || options.map, { filename: options.filename, ...options.preprocessOptions }, options.preprocessCustomRequire ) }