vue3-core/packages/compiler-sfc/src/compileStyle.ts

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

244 lines
6.2 KiB
TypeScript
Raw Normal View History

import postcss, {
type LazyResult,
type Message,
type ProcessOptions,
type Result,
type SourceMap,
} from 'postcss'
import trimPlugin from './style/pluginTrim'
import scopedPlugin from './style/pluginScoped'
2019-11-07 10:58:15 +08:00
import {
type PreprocessLang,
type StylePreprocessor,
type StylePreprocessorResults,
2019-11-07 10:58:15 +08:00
processors,
} from './style/preprocessors'
import type { RawSourceMap } from '@vue/compiler-core'
import { cssVarsPlugin } from './style/cssVars'
import postcssModules from 'postcss-modules'
2019-11-07 10:58:15 +08:00
2019-12-14 00:24:09 +08:00
export interface SFCStyleCompileOptions {
2019-11-07 10:58:15 +08:00
source: string
filename: string
id: string
scoped?: boolean
trim?: boolean
2020-11-18 04:58:46 +08:00
isProd?: boolean
inMap?: RawSourceMap
preprocessLang?: PreprocessLang
2019-11-07 10:58:15 +08:00
preprocessOptions?: any
preprocessCustomRequire?: (id: string) => any
2019-11-07 10:58:15 +08:00
postcssOptions?: any
postcssPlugins?: any[]
/**
* @deprecated use `inMap` instead.
*/
map?: RawSourceMap
2019-11-07 10:58:15 +08:00
}
/**
* 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?: RegExp[]
}
2019-12-14 00:24:09 +08:00
export interface SFCAsyncStyleCompileOptions extends SFCStyleCompileOptions {
2019-11-07 10:58:15 +08:00
isAsync?: boolean
// css modules support, note this requires async so that we can get the
// resulting json
modules?: boolean
modulesOptions?: CSSModulesOptions
2019-11-07 10:58:15 +08:00
}
2019-12-14 00:24:09 +08:00
export interface SFCStyleCompileResults {
2019-11-07 10:58:15 +08:00
code: string
2019-12-12 02:33:45 +08:00
map: RawSourceMap | undefined
rawResult: Result | LazyResult | undefined
2019-12-12 02:33:45 +08:00
errors: Error[]
modules?: Record<string, string>
dependencies: Set<string>
2019-11-07 10:58:15 +08:00
}
export function compileStyle(
2019-12-14 00:24:09 +08:00
options: SFCStyleCompileOptions,
): SFCStyleCompileResults {
return doCompileStyle({
...options,
isAsync: false,
}) as SFCStyleCompileResults
2019-11-07 10:58:15 +08:00
}
export function compileStyleAsync(
options: SFCAsyncStyleCompileOptions,
2019-12-14 00:24:09 +08:00
): Promise<SFCStyleCompileResults> {
2021-07-20 06:24:18 +08:00
return doCompileStyle({
...options,
isAsync: true,
}) as Promise<SFCStyleCompileResults>
2019-11-07 10:58:15 +08:00
}
export function doCompileStyle(
2019-12-14 00:24:09 +08:00
options: SFCAsyncStyleCompileOptions,
): SFCStyleCompileResults | Promise<SFCStyleCompileResults> {
2019-11-07 10:58:15 +08:00
const {
filename,
id,
scoped = false,
2019-11-07 10:58:15 +08:00
trim = true,
2020-11-18 04:58:46 +08:00
isProd = false,
modules = false,
modulesOptions = {},
2019-11-07 10:58:15 +08:00
preprocessLang,
postcssOptions,
postcssPlugins,
} = options
const preprocessor = preprocessLang && processors[preprocessLang]
const preProcessedSource = preprocessor && preprocess(options, preprocessor)
const map = preProcessedSource
? preProcessedSource.map
: options.inMap || options.map
2019-11-07 10:58:15 +08:00
const source = preProcessedSource ? preProcessedSource.code : options.source
const shortId = id.replace(/^data-v-/, '')
const longId = `data-v-${shortId}`
2019-11-07 10:58:15 +08:00
const plugins = (postcssPlugins || []).slice()
plugins.unshift(cssVarsPlugin({ id: shortId, isProd }))
2019-11-07 10:58:15 +08:00
if (trim) {
plugins.push(trimPlugin())
}
if (scoped) {
plugins.push(scopedPlugin(longId))
2019-11-07 10:58:15 +08:00
}
let cssModules: Record<string, string> | 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(
postcssModules({
...modulesOptions,
getJSON: (_cssFileName: string, json: Record<string, string>) => {
cssModules = json
},
}),
)
}
2019-11-07 10:58:15 +08:00
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)
2019-11-07 10:58:15 +08:00
2019-12-12 02:33:45 +08:00
const errors: Error[] = []
2019-11-07 10:58:15 +08:00
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
}
2019-11-07 10:58:15 +08:00
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(),
2019-11-07 10:58:15 +08:00
errors,
modules: cssModules,
rawResult: result,
dependencies: recordPlainCssDependencies(result.messages),
2019-11-07 10:58:15 +08:00
}))
.catch(error => ({
code: '',
map: undefined,
2019-12-12 02:33:45 +08:00
errors: [...errors, error],
rawResult: undefined,
dependencies,
2019-11-07 10:58:15 +08:00
}))
}
recordPlainCssDependencies(result.messages)
2019-11-07 10:58:15 +08:00
// force synchronous transform (we know we only have sync plugins)
code = result.css
outMap = result.map
} catch (e: any) {
2019-11-07 10:58:15 +08:00
errors.push(e)
}
return {
code: code || ``,
map: outMap && outMap.toJSON(),
2019-11-07 10:58:15 +08:00
errors,
rawResult: result,
dependencies,
2019-11-07 10:58:15 +08:00
}
}
function preprocess(
2019-12-14 00:24:09 +08:00
options: SFCStyleCompileOptions,
2019-11-07 10:58:15 +08:00
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,
)
2019-11-07 10:58:15 +08:00
}