Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion build/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const baseConfig = {
'fs-extra',
'magic-string',
],
noExternal: ['estree-walker'],
noExternal: ['estree-walker-ts'],
format: ['cjs', 'esm'],
clean: true,
minify: true,
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@
},
"peerDependencies": {
"chalk": "^4.1.2",
"estree-walker": "^3.0.3",
"estree-walker-ts": "^1.0.0-beta.2",
"fast-glob": "^3.2.12",
"fs-extra": "^11.1.1",
"hash-sum": "^2.0.0",
Expand All @@ -83,7 +83,7 @@
},
"dependencies": {
"chalk": "^4.1.2",
"estree-walker": "^3.0.3",
"estree-walker-ts": "^1.0.0-beta.2",
"fast-glob": "^3.2.12",
"fs-extra": "^11.1.1",
"hash-sum": "^2.0.0",
Expand Down
55 changes: 42 additions & 13 deletions packages/core/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { createUnplugin } from 'unplugin'
import { JSX_TSX_REG, NAME, SUPPORT_FILE_REG } from '@unplugin-vue-cssvars/utils'
import { JSX_TSX_REG, NAME, SUPPORT_FILE_REG, setTArray } from '@unplugin-vue-cssvars/utils'
import { createFilter } from '@rollup/pluginutils'
import { parse } from '@vue/compiler-sfc'
import chalk from 'chalk'
Expand All @@ -19,7 +19,8 @@ import type { TMatchVariable } from './parser'
import type { Options } from './types'
// TODO: webpack hmr
const unplugin = createUnplugin<Options>(
(options: Options = {}): any => {
(options: Options = {}, meta): any => {
const framework = meta.framework
const userOptions = initOption(options)
const filter = createFilter(
userOptions.include,
Expand Down Expand Up @@ -58,12 +59,12 @@ const unplugin = createUnplugin<Options>(
} = getVBindVariableListByPath(descriptor, id, CSSFileModuleMap, isServer, userOptions.alias)
const variableName = getVariable(descriptor)
vbindVariableList.set(id, matchVariable(vbindVariableListByPath, variableName))

if (!isServer)
// TODO: webpack
// 'vite' | 'rollup' | 'esbuild'
if (!isServer && framework !== 'webpack' && framework !== 'rspack')
mgcStr = injectCssOnBuild(mgcStr, injectCSSContent, descriptor)
}
}

return {
code: mgcStr.toString(),
get map() {
Expand Down Expand Up @@ -102,24 +103,52 @@ const unplugin = createUnplugin<Options>(
{
name: `${NAME}:inject`,
enforce: 'post',
transformInclude(id: string) {
return filter(id)
},
async transform(code: string, id: string) {
if (id.includes('node_modules'))
return

let mgcStr = new MagicString(code)

// ⭐TODO: 只支持 .vue ? jsx, tsx, js, ts ?
try {
// transform in dev
// 'vite' | 'rollup' | 'esbuild'
if (isServer) {
if (id.endsWith('.vue')) {
const injectRes = injectCSSVars(code, vbindVariableList.get(id), isScriptSetup)
function injectCSSVarsFn(idKey: string) {
const injectRes = injectCSSVars(code, vbindVariableList.get(idKey), isScriptSetup, framework)
mgcStr = mgcStr.overwrite(0, mgcStr.length(), injectRes.code)
injectRes.vbindVariableList && vbindVariableList.set(id, injectRes.vbindVariableList)
isHmring = false
}
if (id.includes('type=style'))
mgcStr = injectCssOnServer(mgcStr, vbindVariableList.get(id.split('?vue')[0]), isHmring)

if (framework === 'vite' ||
framework === 'rollup' ||
framework === 'esbuild') {
if (id.endsWith('.vue'))
injectCSSVarsFn(id)

if (id.includes('vue&type=style')) {
mgcStr = injectCssOnServer(
mgcStr,
vbindVariableList.get(id.split('?vue')[0]),
isHmring,
)
}
}

if (framework === 'webpack') {
if (id.includes('vue&type=script')) {
const transId = id.split('?vue&type=script')[0]
// todo 重复注入了
injectCSSVarsFn(transId)
}
const cssFMM = CSSFileModuleMap.get(id)
if (cssFMM && cssFMM.sfcPath && cssFMM.sfcPath.size > 0) {
const sfcPathIdList = setTArray(cssFMM.sfcPath)
sfcPathIdList.forEach((v) => {
mgcStr = injectCssOnServer(mgcStr, vbindVariableList.get(v), isHmring)
})
}
}
}

return {
Expand Down
191 changes: 147 additions & 44 deletions packages/core/inject/inject-cssvars.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@

import hash from 'hash-sum'
import type { IFramework } from '../types'
import type { TMatchVariable } from '../parser'

const importer = 'import { useCssVars as _useCssVars } from "vue"\n'
Expand All @@ -8,57 +9,114 @@ export const injectCSSVars = (
code: string,
vbindVariableList: TMatchVariable | undefined,
isScriptSetup: boolean,
framework: IFramework,
) => {
if (!vbindVariableList || vbindVariableList.length === 0) return { code, vbindVariableList }
return injectCSSVarsOnServer(code, vbindVariableList, isScriptSetup)
return injectCSSVarsOnServer(code, vbindVariableList, isScriptSetup, framework)
}

// TODO use ast to impl
// code 是 @vitejs/plugin-vue 编译后的代码
// 分为三种种情况
// 1. setup script
// 1.1 有 useCssVars 的情况
// 1.2 无 useCssVars 的情况
// 2. option api
// 2.1 有 useCssVars 的情况
// 2.2 无 useCssVars 的情况
// 3. composition api
// 3.1 有 useCssVars 的情况
// 3.2 无 useCssVars 的情况
export function injectCSSVarsOnServer(
code: string,
vbindVariableList: TMatchVariable,
isScriptSetup: boolean) {
isScriptSetup: boolean,
framework: IFramework) {
let resCode = ''
const hasUseCssVars = code.includes('useCssVars')
const useCssVars = createUseCssVarsCode(code, vbindVariableList, hasUseCssVars, isScriptSetup)
// 1
if (isScriptSetup) {
// setup script
if (!hasUseCssVars) {
(resCode = code.replaceAll(
const useCssVars = createUseCssVarsCode(
code,
vbindVariableList,
hasUseCssVars,
true)

resCode = injectUseCssVarsSetup(code, useCssVars, hasUseCssVars)
} else {
// 2 and 3
const useCssVars = createUseCssVarsCode(
code,
vbindVariableList,
hasUseCssVars,
false)

resCode = injectUseCssVarsOption(code, useCssVars, hasUseCssVars)
}

return { code: resCode, vbindVariableList }
}

// TODO: unit test
export function injectUseCssVarsSetup(
code: string,
useCssVars: string,
hasUseCssVars: boolean,
) {
let resCode = ''
if (!hasUseCssVars) {
// TODO: vite unit test
if (code.includes('setup(__props, { expose }) {')) {
resCode = code.replaceAll(
'setup(__props, { expose }) {',
`setup(__props, { expose }) {${useCssVars}`,
))
resCode = `${importer}${resCode}`
} else {
resCode = useCssVars
`setup(__props, { expose }) {${useCssVars}`)
}

// TODO unit test webpack
if (code.includes('setup: function (__props, _a) {')) {
resCode = code.replaceAll(
'setup: function (__props, _a) {',
`setup: function (__props, _a) {${useCssVars}`)
}

resCode = resCode ? `${importer}${resCode}` : code
} else {
// option api
if (!hasUseCssVars) {
resCode = code.replaceAll('const _sfc_main', 'const __default__')
resCode = resCode.replaceAll(
'function _sfc_render',
`${useCssVars}\n
resCode = useCssVars
}
return resCode
}

// TODO: unit test
export function injectUseCssVarsOption(
code: string,
useCssVars: string,
hasUseCssVars: boolean,
) {
let resCode = ''
if (!hasUseCssVars) {
resCode = code.replaceAll('const _sfc_main', 'const __default__')
resCode = resCode.replaceAll(
'function _sfc_render',
`${useCssVars}\n
const __setup__ = __default__.setup
__default__.setup = __setup__
? (props, ctx) => { __injectCSSVars__(); return __setup__(props, ctx) }
: __injectCSSVars__
const _sfc_main = __default__
function _sfc_render`)
resCode = `${importer}${resCode}`
} else {
resCode = useCssVars
}
resCode = `${importer}${resCode}`
} else {
resCode = useCssVars
}

return { code: resCode, vbindVariableList }
return resCode
}

export function createUseCssVarsCode(
code: string,
// TODO: unit test
export function createCSSVarsObjCode(
vbindVariableList: TMatchVariable,
isHas: boolean,
isScriptSetup: boolean) {
let cssvarsObjectCode = ''
isScriptSetup: boolean,
) {
let resCode = ''
vbindVariableList.forEach((vbVar) => {
// 如果 hash 存在,则说明是由热更新引起的,不需要重新设置 hash
const hashVal = vbVar.hash || hash(vbVar.value + vbVar.has)
Expand All @@ -73,29 +131,74 @@ export function createUseCssVarsCode(
// ref 用.value
varStr = vbVar.isRef ? `${vbVar.value}.value` : varStr
}
cssvarsObjectCode = `"${hashVal}": ${varStr},\n ${cssvarsObjectCode}`
resCode = `"${hashVal}": ${varStr},\n ${resCode}`
})
let resCode = ''
if (isHas) {
resCode = code.includes('_useCssVars((_ctx') ? code.replaceAll(
'_useCssVars((_ctx) => ({',
`_useCssVars((_ctx) => ({\n ${cssvarsObjectCode}`)
: code.replaceAll(
'_useCssVars(_ctx => ({',
`_useCssVars((_ctx) => ({\n ${cssvarsObjectCode}`)
} else {
// setup script
resCode = `
return resCode
}

// TODO: unit test
export function createUCVCSetupUnHas(cssvarsObjectCode: string) {
return `
_useCssVars((_ctx) => ({
${cssvarsObjectCode}
}));`
}));`
}

// composition api 和 option api
if (!isScriptSetup) {
resCode = `
// TODO: unit test
export function createUCVCOptionUnHas(resCode: string) {
return `
const __injectCSSVars__ = () => {\n
${resCode}\n
};`
}

// TODO: unit test
export function createUCVCHas(
code: string,
cssvarsObjectCode: string,
) {
let resCode = ''
// TODO: vite unit test
if (code.includes('_useCssVars((_ctx')) {
resCode = code.replaceAll(
'_useCssVars((_ctx) => ({',
`_useCssVars((_ctx) => ({\n ${cssvarsObjectCode}`)
}

// TODO: vite unit test
if (code.includes('_useCssVars(_ctx => ({')) {
resCode = code.replaceAll(
'_useCssVars(_ctx => ({',
`_useCssVars((_ctx) => ({\n ${cssvarsObjectCode}`)
}

// TODO: vite unit webpack
if (code.includes('_useCssVars(function (_ctx) { return ({')) {
resCode = code.replaceAll(
'_useCssVars(function (_ctx) { return ({',
`_useCssVars(function (_ctx) { return ({\n ${cssvarsObjectCode}`)
}
return resCode
}

export function createUseCssVarsCode(
code: string,
vbindVariableList: TMatchVariable,
isHas: boolean,
isScriptSetup: boolean,
) {
const cssvarsObjectCode = createCSSVarsObjCode(vbindVariableList, isScriptSetup)

let resCode = ''
if (isHas) {
resCode = createUCVCHas(code, cssvarsObjectCode)
} else {
if (isScriptSetup) {
// setup script
resCode = createUCVCSetupUnHas(cssvarsObjectCode)
} else {
// composition api 和 option api
resCode = createUCVCOptionUnHas(cssvarsObjectCode)
}
}
return resCode
Expand Down
4 changes: 2 additions & 2 deletions packages/core/parser/parser-variable.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { parse as babelParse } from '@babel/parser'
import { walk } from 'estree-walker'
import { walk } from 'estree-walker-ts'
import { extend, isEmptyObj } from '@unplugin-vue-cssvars/utils'
import type { VariableName } from '../types'
import type { ParseResult } from '@babel/parser'
Expand All @@ -13,7 +13,7 @@ import type {
ReturnStatement,
VariableDeclarator,
} from '@babel/types'
import type { Node } from 'estree-walker'
import type { Node } from 'estree-walker-ts'
/**
* 获取变量
* @param descriptor
Expand Down
1 change: 1 addition & 0 deletions packages/core/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,4 @@ export interface InjectStrItem {
content: string
}
export declare type InjectStr = Array<InjectStrItem>
export declare type IFramework = 'rollup' | 'vite' | 'webpack' | 'esbuild' | 'rspack'
3 changes: 3 additions & 0 deletions play/webpack/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,7 @@ const fooColor = appAsd()

<style scoped>
@import '@/assets/css/foo.css';
#foo{
background: v-bind(fooColor);
}
</style>
1 change: 0 additions & 1 deletion play/webpack/src/assets/css/foo.css
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
#foo{
color: v-bind-m(color);
background: #ffebf8;
width: 200px;
height: 30px;
}
Loading