wip: css var injection production mode

This commit is contained in:
Evan You 2020-11-17 15:58:46 -05:00
parent f92bc5a19a
commit 94736f7729
7 changed files with 96 additions and 19 deletions

View File

@ -49,6 +49,7 @@
"@rollup/plugin-json": "^4.0.0", "@rollup/plugin-json": "^4.0.0",
"@rollup/plugin-node-resolve": "^9.0.0", "@rollup/plugin-node-resolve": "^9.0.0",
"@rollup/plugin-replace": "^2.2.1", "@rollup/plugin-replace": "^2.2.1",
"@types/hash-sum": "^1.0.0",
"@types/jest": "^26.0.0", "@types/jest": "^26.0.0",
"@types/node": "^14.10.1", "@types/node": "^14.10.1",
"@types/puppeteer": "^2.0.0", "@types/puppeteer": "^2.0.0",

View File

@ -52,7 +52,7 @@ describe('CSS vars injection', () => {
assertCode(content) assertCode(content)
}) })
test('should rewrite CSS vars in scoped mode', () => { test('should rewrite CSS vars in compileStyle', () => {
const { code } = compileStyle({ const { code } = compileStyle({
source: `.foo { source: `.foo {
color: v-bind(color); color: v-bind(color);
@ -69,6 +69,37 @@ describe('CSS vars injection', () => {
`) `)
}) })
test('prod mode', () => {
const { content } = compileSFCScript(
`<script>const a = 1</script>\n` +
`<style>div{
color: v-bind(color);
font-size: v-bind('font.size');
}</style>`,
{ isProd: true }
)
expect(content).toMatch(`_useCssVars(_ctx => ({
"4003f1a6": (_ctx.color),
"41b6490a": (_ctx.font.size)
}))}`)
const { code } = compileStyle({
source: `.foo {
color: v-bind(color);
font-size: v-bind('font.size');
}`,
filename: 'test.css',
id: mockId,
isProd: true
})
expect(code).toMatchInlineSnapshot(`
".foo {
color: var(--4003f1a6);
font-size: var(--41b6490a);
}"
`)
})
describe('codegen', () => { describe('codegen', () => {
test('<script> w/ no default export', () => { test('<script> w/ no default export', () => {
assertCode( assertCode(

View File

@ -43,6 +43,7 @@
"@vue/shared": "3.0.2", "@vue/shared": "3.0.2",
"consolidate": "^0.16.0", "consolidate": "^0.16.0",
"estree-walker": "^2.0.1", "estree-walker": "^2.0.1",
"hash-sum": "^2.0.0",
"lru-cache": "^5.1.1", "lru-cache": "^5.1.1",
"magic-string": "^0.25.7", "magic-string": "^0.25.7",
"merge-source-map": "^1.1.0", "merge-source-map": "^1.1.0",

View File

@ -40,6 +40,10 @@ export interface SFCScriptCompileOptions {
* This must be consistent with the `id` passed to `compileStyle`. * This must be consistent with the `id` passed to `compileStyle`.
*/ */
id: string id: string
/**
* Production mode. Used to determine whether to generate hashed CSS variables
*/
isProd?: boolean
/** /**
* https://babeljs.io/docs/en/babel-parser#plugins * https://babeljs.io/docs/en/babel-parser#plugins
*/ */
@ -128,7 +132,14 @@ export function compileScript(
return { return {
...script, ...script,
content: cssVars.length content: cssVars.length
? injectCssVarsCalls(sfc, cssVars, bindings, scopeId, plugins) ? injectCssVarsCalls(
sfc,
cssVars,
bindings,
scopeId,
!!options.isProd,
plugins
)
: script.content, : script.content,
bindings, bindings,
scriptAst scriptAst
@ -511,6 +522,7 @@ export function compileScript(
node.body.type === 'ExpressionStatement' node.body.type === 'ExpressionStatement'
) { ) {
if (enableRefSugar) { if (enableRefSugar) {
if (__DEV__ && !__TEST__) {
warnOnce( warnOnce(
`ref: sugar is still an experimental proposal and is not ` + `ref: sugar is still an experimental proposal and is not ` +
`guaranteed to be a part of <script setup>.\n` + `guaranteed to be a part of <script setup>.\n` +
@ -518,6 +530,7 @@ export function compileScript(
`It's also recommended to pin your vue dependencies to exact versions ` + `It's also recommended to pin your vue dependencies to exact versions ` +
`to avoid breakage.` `to avoid breakage.`
) )
}
s.overwrite( s.overwrite(
node.label.start! + startOffset, node.label.start! + startOffset,
node.body.start! + startOffset, node.body.start! + startOffset,
@ -800,7 +813,12 @@ export function compileScript(
helperImports.add('unref') helperImports.add('unref')
s.prependRight( s.prependRight(
startOffset, startOffset,
`\n${genCssVarsCode(cssVars, bindingMetadata, scopeId)}\n` `\n${genCssVarsCode(
cssVars,
bindingMetadata,
scopeId,
!!options.isProd
)}\n`
) )
} }

View File

@ -23,6 +23,7 @@ export interface SFCStyleCompileOptions {
map?: RawSourceMap map?: RawSourceMap
scoped?: boolean scoped?: boolean
trim?: boolean trim?: boolean
isProd?: boolean
preprocessLang?: PreprocessLang preprocessLang?: PreprocessLang
preprocessOptions?: any preprocessOptions?: any
preprocessCustomRequire?: (id: string) => any preprocessCustomRequire?: (id: string) => any
@ -82,6 +83,7 @@ export function doCompileStyle(
id, id,
scoped = false, scoped = false,
trim = true, trim = true,
isProd = false,
modules = false, modules = false,
modulesOptions = {}, modulesOptions = {},
preprocessLang, preprocessLang,
@ -94,7 +96,7 @@ export function doCompileStyle(
const source = preProcessedSource ? preProcessedSource.code : options.source const source = preProcessedSource ? preProcessedSource.code : options.source
const plugins = (postcssPlugins || []).slice() const plugins = (postcssPlugins || []).slice()
plugins.unshift(cssVarsPlugin(id)) plugins.unshift(cssVarsPlugin({ id, isProd }))
if (trim) { if (trim) {
plugins.push(trimPlugin()) plugins.push(trimPlugin())
} }

View File

@ -11,12 +11,17 @@ import { SFCDescriptor } from './parse'
import { rewriteDefault } from './rewriteDefault' import { rewriteDefault } from './rewriteDefault'
import { ParserPlugin } from '@babel/parser' import { ParserPlugin } from '@babel/parser'
import postcss, { Root } from 'postcss' import postcss, { Root } from 'postcss'
import hash from 'hash-sum'
export const CSS_VARS_HELPER = `useCssVars` export const CSS_VARS_HELPER = `useCssVars`
export const cssVarRE = /\bv-bind\(\s*(?:'([^']+)'|"([^"]+)"|([^'"][^)]*))\s*\)/g export const cssVarRE = /\bv-bind\(\s*(?:'([^']+)'|"([^"]+)"|([^'"][^)]*))\s*\)/g
export function convertCssVarCasing(raw: string): string { export function genVarName(id: string, raw: string, isProd: boolean): string {
return raw.replace(/([^\w-])/g, '_') if (isProd) {
return hash(id + raw)
} else {
return `${id}-${raw.replace(/([^\w-])/g, '_')}`
}
} }
export function parseCssVars(sfc: SFCDescriptor): string[] { export function parseCssVars(sfc: SFCDescriptor): string[] {
@ -31,15 +36,21 @@ export function parseCssVars(sfc: SFCDescriptor): string[] {
} }
// for compileStyle // for compileStyle
export const cssVarsPlugin = postcss.plugin( export interface CssVarsPluginOptions {
id: string
isProd: boolean
}
export const cssVarsPlugin = postcss.plugin<CssVarsPluginOptions>(
'vue-scoped', 'vue-scoped',
(id: any) => (root: Root) => { opts => (root: Root) => {
const { id, isProd } = opts!
const shortId = id.replace(/^data-v-/, '') const shortId = id.replace(/^data-v-/, '')
root.walkDecls(decl => { root.walkDecls(decl => {
// rewrite CSS variables // rewrite CSS variables
if (cssVarRE.test(decl.value)) { if (cssVarRE.test(decl.value)) {
decl.value = decl.value.replace(cssVarRE, (_, $1, $2, $3) => { decl.value = decl.value.replace(cssVarRE, (_, $1, $2, $3) => {
return `var(--${shortId}-${convertCssVarCasing($1 || $2 || $3)})` return `var(--${genVarName(shortId, $1 || $2 || $3, isProd)})`
}) })
} }
}) })
@ -49,10 +60,11 @@ export const cssVarsPlugin = postcss.plugin(
export function genCssVarsCode( export function genCssVarsCode(
vars: string[], vars: string[],
bindings: BindingMetadata, bindings: BindingMetadata,
id: string id: string,
isProd: boolean
) { ) {
const varsExp = `{\n ${vars const varsExp = `{\n ${vars
.map(v => `"${id}-${convertCssVarCasing(v)}": (${v})`) .map(v => `"${genVarName(id, v, isProd)}": (${v})`)
.join(',\n ')}\n}` .join(',\n ')}\n}`
const exp = createSimpleExpression(varsExp, false) const exp = createSimpleExpression(varsExp, false)
const context = createTransformContext(createRoot([]), { const context = createTransformContext(createRoot([]), {
@ -82,6 +94,7 @@ export function injectCssVarsCalls(
cssVars: string[], cssVars: string[],
bindings: BindingMetadata, bindings: BindingMetadata,
id: string, id: string,
isProd: boolean,
parserPlugins: ParserPlugin[] parserPlugins: ParserPlugin[]
): string { ): string {
const script = rewriteDefault( const script = rewriteDefault(
@ -96,7 +109,8 @@ export function injectCssVarsCalls(
`const __injectCSSVars__ = () => {\n${genCssVarsCode( `const __injectCSSVars__ = () => {\n${genCssVarsCode(
cssVars, cssVars,
bindings, bindings,
id id,
isProd
)}}\n` + )}}\n` +
`const __setup__ = __default__.setup\n` + `const __setup__ = __default__.setup\n` +
`__default__.setup = __setup__\n` + `__default__.setup = __setup__\n` +

View File

@ -845,6 +845,11 @@
dependencies: dependencies:
"@types/node" "*" "@types/node" "*"
"@types/hash-sum@^1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@types/hash-sum/-/hash-sum-1.0.0.tgz#838f4e8627887d42b162d05f3d96ca636c2bc504"
integrity sha512-FdLBT93h3kcZ586Aee66HPCVJ6qvxVjBlDWNmxSGSbCZe9hTsjRKdSsl4y1T+3zfujxo9auykQMnFsfyHWD7wg==
"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1": "@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1":
version "2.0.1" version "2.0.1"
resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.1.tgz#42995b446db9a48a11a07ec083499a860e9138ff" resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.1.tgz#42995b446db9a48a11a07ec083499a860e9138ff"
@ -3398,6 +3403,11 @@ hash-base@^3.0.0:
inherits "^2.0.1" inherits "^2.0.1"
safe-buffer "^5.0.1" safe-buffer "^5.0.1"
hash-sum@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/hash-sum/-/hash-sum-2.0.0.tgz#81d01bb5de8ea4a214ad5d6ead1b523460b0b45a"
integrity sha512-WdZTbAByD+pHfl/g9QSsBIIwy8IT+EsPiKDs0KNX+zSHhdDLFKdZu0BQHljvO+0QI/BasbMSUa8wYNCZTvhslg==
hash.js@^1.0.0, hash.js@^1.0.3: hash.js@^1.0.0, hash.js@^1.0.3:
version "1.1.7" version "1.1.7"
resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.7.tgz#0babca538e8d4ee4a0f8988d68866537a003cf42" resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.7.tgz#0babca538e8d4ee4a0f8988d68866537a003cf42"