Skip to content

Commit 81374e5

Browse files
committed
feat: support generate sourcemap
1 parent b5b9742 commit 81374e5

File tree

10 files changed

+1978
-2312
lines changed

10 files changed

+1978
-2312
lines changed

package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@
7676
"fs-extra": "^11.1.1",
7777
"hash-sum": "^2.0.0",
7878
"magic-string": "^0.30.0",
79+
"source-map-diff": "^1.3.1",
7980
"unplugin": "^1.3.1",
8081
"vue": "^3.2.47"
8182
},
@@ -86,6 +87,7 @@
8687
"fs-extra": "^11.1.1",
8788
"hash-sum": "^2.0.0",
8889
"magic-string": "^0.30.0",
90+
"source-map-diff": "^1.3.1",
8991
"unplugin": "^1.3.1",
9092
"vue": "^3.2.47"
9193
},
@@ -120,12 +122,13 @@
120122
"gulp": "^4.0.2",
121123
"jsdom": "^21.1.1",
122124
"lint-staged": "^13.1.1",
125+
"magic-string-ast": "^0.1.2",
123126
"npm-run-all": "^4.1.5",
124127
"rimraf": "^5.0.0",
125128
"rollup": "^3.19.1",
126129
"simple-git-hooks": "^2.8.1",
127130
"typescript": "5.0.4",
128-
"vite": "^4.2.1",
131+
"vite": "^4.3.0",
129132
"vitest": "^0.30.1"
130133
},
131134
"simple-git-hooks": {

packages/core/hmr/__test__/hmr.spec.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { resolve } from 'path'
22
import { beforeEach, describe, expect, test } from 'vitest'
33
import { transformSymbol } from '@unplugin-vue-cssvars/utils'
4-
import { triggerSFCUpdate, updatedCSSModules } from '../hmr'
4+
import { triggerSFCUpdate, updatedCSSModules, viteHMR } from '../hmr'
55

66
const mockOption = {
77
rootDir: resolve(),
@@ -40,6 +40,20 @@ describe('HMR', () => {
4040
expect(CSSFileModuleMap.get(file).vBindCode).toMatchObject(['test'])
4141
})
4242

43+
test('HMR: viteHMR', () => {
44+
const CSSFileModuleMap = new Map()
45+
CSSFileModuleMap.set(file, {
46+
importer: new Set(),
47+
vBindCode: ['foo'],
48+
sfcPath: new Set(['../D/test']),
49+
})
50+
51+
viteHMR(CSSFileModuleMap, mockOption, file, mockServer as any)
52+
expect(CSSFileModuleMap.get(file).content).toBeTruthy()
53+
expect(CSSFileModuleMap.get(file).vBindCode).toMatchObject(['test'])
54+
expect(hmrModule).toMatchObject({ id: 'foo.vue' })
55+
})
56+
4357
test('HMR: triggerSFCUpdate basic', () => {
4458
const CSSFileModuleMap = new Map()
4559
CSSFileModuleMap.set(file, {

packages/core/hmr/hmr.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ export function viteHMR(
1111
) {
1212
// 获取变化的样式文件的 CSSFileMap上有使用它的
1313
const sfcModulesPathList = CSSFileModuleMap.get(file)
14-
triggerSFCUpdate(CSSFileModuleMap, userOptions, sfcModulesPathList, file, server)
14+
triggerSFCUpdate(CSSFileModuleMap, userOptions, sfcModulesPathList!, file, server)
1515
}
1616

1717
/**
@@ -26,10 +26,9 @@ export function updatedCSSModules(
2626
userOptions: Options,
2727
file: string) {
2828
const updatedCSSMS = preProcessCSS(userOptions, userOptions.alias, [file]).get(file)
29-
CSSFileModuleMap.set(file, updatedCSSMS)
29+
CSSFileModuleMap.set(file, updatedCSSMS!)
3030
}
3131

32-
// TODO: unit test
3332
/**
3433
* triggerSFCUpdate
3534
* @param CSSFileModuleMap

packages/core/index.ts

Lines changed: 41 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { JSX_TSX_REG, NAME, SUPPORT_FILE_REG } from '@unplugin-vue-cssvars/utils
33
import { createFilter } from '@rollup/pluginutils'
44
import { parse } from '@vue/compiler-sfc'
55
import chalk from 'chalk'
6+
import MagicString from 'magic-string'
67
import { preProcessCSS } from './runtime/pre-process-css'
78
import { getVBindVariableListByPath } from './runtime/process-css'
89
import { initOption } from './option'
@@ -14,7 +15,6 @@ import {
1415
} from './inject'
1516
import { viteHMR } from './hmr/hmr'
1617
import type { HmrContext, ResolvedConfig } from 'vite'
17-
1818
import type { TMatchVariable } from './parser'
1919
import type { Options } from './types'
2020
// TODO: webpack hmr
@@ -43,27 +43,37 @@ const unplugin = createUnplugin<Options>(
4343
return filter(id)
4444
},
4545
async transform(code: string, id: string) {
46+
let mgcStr = new MagicString(code)
4647
try {
4748
// ⭐TODO: 只支持 .vue ? jsx, tsx, js, ts ?
4849
if (id.endsWith('.vue')) {
4950
const { descriptor } = parse(code)
5051
const lang = descriptor?.script?.lang ?? 'js'
5152
// ⭐TODO: 只支持 .vue ? jsx, tsx, js, ts ?
52-
if (JSX_TSX_REG.test(`.${lang}`))
53-
return code
53+
if (!JSX_TSX_REG.test(`.${lang}`)) {
54+
isScriptSetup = !!descriptor.scriptSetup
55+
const {
56+
vbindVariableListByPath,
57+
injectCSSContent,
58+
} = getVBindVariableListByPath(descriptor, id, CSSFileModuleMap, isServer, userOptions.alias)
59+
const variableName = getVariable(descriptor)
60+
vbindVariableList.set(id, matchVariable(vbindVariableListByPath, variableName))
5461

55-
isScriptSetup = !!descriptor.scriptSetup
56-
const {
57-
vbindVariableListByPath,
58-
injectCSSContent,
59-
} = getVBindVariableListByPath(descriptor, id, CSSFileModuleMap, isServer, userOptions.alias)
60-
const variableName = getVariable(descriptor)
61-
vbindVariableList.set(id, matchVariable(vbindVariableListByPath, variableName))
62+
if (!isServer)
63+
mgcStr = injectCssOnBuild(mgcStr, injectCSSContent, descriptor)
64+
}
65+
}
6266

63-
if (!isServer)
64-
code = injectCssOnBuild(code, injectCSSContent, descriptor)
67+
return {
68+
code: mgcStr.toString(),
69+
get map() {
70+
return mgcStr.generateMap({
71+
source: id,
72+
includeContent: true,
73+
hires: true,
74+
})
75+
},
6576
}
66-
return code
6777
} catch (err: unknown) {
6878
this.error(`[${NAME}] ${err}`)
6979
}
@@ -93,20 +103,35 @@ const unplugin = createUnplugin<Options>(
93103
name: `${NAME}:inject`,
94104
enforce: 'post',
95105
async transform(code: string, id: string) {
106+
if (id.includes('node_modules'))
107+
return
108+
109+
let mgcStr = new MagicString(code)
110+
96111
// ⭐TODO: 只支持 .vue ? jsx, tsx, js, ts ?
97112
try {
98113
// transform in dev
99114
if (isServer) {
100115
if (id.endsWith('.vue')) {
101116
const injectRes = injectCSSVars(code, vbindVariableList.get(id), isScriptSetup)
102-
code = injectRes.code
117+
mgcStr = mgcStr.overwrite(0, mgcStr.length(), injectRes.code)
103118
injectRes.vbindVariableList && vbindVariableList.set(id, injectRes.vbindVariableList)
104119
isHmring = false
105120
}
106121
if (id.includes('type=style'))
107-
code = injectCssOnServer(code, vbindVariableList.get(id.split('?vue')[0]), isHmring)
122+
mgcStr = injectCssOnServer(mgcStr, vbindVariableList.get(id.split('?vue')[0]), isHmring)
123+
}
124+
125+
return {
126+
code: mgcStr.toString(),
127+
get map() {
128+
return mgcStr.generateMap({
129+
source: id,
130+
includeContent: true,
131+
hires: true,
132+
})
133+
},
108134
}
109-
return code
110135
} catch (err: unknown) {
111136
this.error(`[${NAME}] ${err}`)
112137
}
Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
22

33
exports[`inject-css > injectCssOnBuild: basic 1`] = `
4-
"
5-
<style lang=\\"scss\\" >
4+
"<style lang=\\"scss\\" >
5+
6+
body { background-color: black; }
67
/* foo.scss -> test2.css -> test.css */
78
/* foo.scss -> test.scss -> test2.css */
89
@@ -14,8 +15,9 @@ div {
1415
`;
1516

1617
exports[`inject-css > injectCssOnBuild: no lang 1`] = `
17-
"
18-
<style lang=\\"css\\" >
18+
"<style lang=\\"css\\" >
19+
20+
body { background-color: black; }
1921
/* foo.scss -> test2.css -> test.css */
2022
/* foo.scss -> test.scss -> test2.css */
2123
@@ -26,7 +28,4 @@ div {
2628
</style>"
2729
`;
2830

29-
exports[`inject-css > injectCssOnBuild: no styles 1`] = `
30-
"test
31-
"
32-
`;
31+
exports[`inject-css > injectCssOnBuild: no styles 1`] = `"test"`;

packages/core/inject/__test__/inject-css.spec.ts

Lines changed: 35 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
11
import { describe, expect, test } from 'vitest'
2+
import MagicString from 'magic-string'
23
import { injectCssOnBuild, injectCssOnServer, removeStyleTagsAndContent } from '../inject-css'
34
describe('inject-css', () => {
45
test('injectCssOnServer: basic', () => {
56
const code = 'v-bind-m(foo)'
7+
const mgcStr = new MagicString(code)
68
const vbindVariableList = [{ value: 'foo', hash: 'hash' }]
7-
expect(injectCssOnServer(code, vbindVariableList as any)).toBe('var(--hash)')
9+
expect(injectCssOnServer(mgcStr, vbindVariableList as any, false).toString()).toBe('var(--hash)')
810
})
911

1012
test('injectCssOnServer: vbindVariableList is undefined', () => {
1113
const code = 'v-bind-m(foo)'
12-
expect(injectCssOnServer(code, undefined)).toBe(code)
14+
const mgcStr = new MagicString(code)
15+
expect(injectCssOnServer(mgcStr, undefined, false).toString()).toBe(code)
1316
})
1417

1518
test('removeStyleTagsAndContent: basic', () => {
@@ -48,12 +51,13 @@ describe('inject-css', () => {
4851
</body>
4952
</html>
5053
`
51-
52-
expect(removeStyleTagsAndContent(html)).toEqual(expectedHtml)
54+
const mgcStr = new MagicString(html)
55+
expect(removeStyleTagsAndContent(mgcStr).toString()).toEqual(expectedHtml)
5356
})
5457

5558
test('removeStyleTagsAndContent: empty HTML', () => {
56-
expect(removeStyleTagsAndContent('')).toEqual('')
59+
const mgcStr = new MagicString('')
60+
expect(removeStyleTagsAndContent(mgcStr).toString()).toEqual('')
5761
})
5862

5963
test('removeStyleTagsAndContent: not modify HTML without style tags', () => {
@@ -68,8 +72,8 @@ describe('inject-css', () => {
6872
</body>
6973
</html>
7074
`
71-
72-
expect(removeStyleTagsAndContent(html)).toEqual(html)
75+
const mgcStr = new MagicString(html)
76+
expect(removeStyleTagsAndContent(mgcStr).toString()).toEqual(html)
7377
})
7478

7579
test('injectCssOnBuild: basic', () => {
@@ -83,7 +87,12 @@ describe('inject-css', () => {
8387
+ '}\n'
8488
+ "@import './assets/scss/foo.scss';\n"
8589
+ '</style>'
86-
const injectCSSContent = new Set([{ content: '@import \'./assets/scss/foo.scss\';\nbody { background-color: black; }', lang: 'scss' }])
90+
const mgcStr = new MagicString(code)
91+
const injectCSSContent = new Set([{
92+
content: '@import \'./assets/scss/foo.scss\';\nbody { background-color: black; }',
93+
lang: 'scss',
94+
styleTagIndex: 0,
95+
}])
8796
const descriptor = {
8897
styles: [
8998
{
@@ -99,18 +108,23 @@ describe('inject-css', () => {
99108
},
100109
],
101110
}
102-
const result = injectCssOnBuild(code, injectCSSContent, descriptor as any)
103-
expect(result).toMatchSnapshot()
111+
const result = injectCssOnBuild(mgcStr, injectCSSContent, descriptor as any)
112+
expect(result.toString()).toMatchSnapshot()
104113
})
105114

106115
test('injectCssOnBuild: no styles', () => {
107116
const code = 'test'
108-
const injectCSSContent = new Set([{ content: '@import \'./assets/scss/foo.scss\';\nbody { background-color: black; }', lang: 'scss' }])
117+
const injectCSSContent = new Set([{
118+
content: '@import \'./assets/scss/foo.scss\';\nbody { background-color: black; }',
119+
lang: 'scss',
120+
styleTagIndex: 0,
121+
}])
109122
const descriptor = {
110123
styles: null,
111124
}
112-
const result = injectCssOnBuild(code, injectCSSContent, descriptor as any)
113-
expect(result).toMatchSnapshot()
125+
const mgcStr = new MagicString(code)
126+
const result = injectCssOnBuild(mgcStr, injectCSSContent, descriptor as any)
127+
expect(result.toString()).toMatchSnapshot()
114128
})
115129

116130
test('injectCssOnBuild: no lang', () => {
@@ -124,7 +138,11 @@ describe('inject-css', () => {
124138
+ '}\n'
125139
+ "@import './assets/scss/foo.scss';\n"
126140
+ '</style>'
127-
const injectCSSContent = new Set([{ content: '@import \'./assets/scss/foo.scss\';\nbody { background-color: black; }', lang: 'scss' }])
141+
const injectCSSContent = new Set([{
142+
content: '@import \'./assets/scss/foo.scss\';\nbody { background-color: black; }',
143+
lang: 'scss',
144+
styleTagIndex: 0,
145+
}])
128146
const descriptor = {
129147
styles: [
130148
{
@@ -140,7 +158,8 @@ describe('inject-css', () => {
140158
},
141159
],
142160
}
143-
const result = injectCssOnBuild(code, injectCSSContent, descriptor as any)
144-
expect(result).toMatchSnapshot()
161+
const mgcStr = new MagicString(code)
162+
const result = injectCssOnBuild(mgcStr, injectCSSContent, descriptor as any)
163+
expect(result.toString()).toMatchSnapshot()
145164
})
146165
})

packages/core/inject/inject-css.ts

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import hash from 'hash-sum'
2+
import { type MagicStringBase } from 'magic-string-ast'
23
import { transformInjectCSS } from '../transform/transform-inject-css'
34
import { parseImports } from '../parser'
45
import type { TInjectCSSContent } from '../runtime/process-css'
56
import type { SFCDescriptor } from '@vue/compiler-sfc'
67
import type { TMatchVariable } from '../parser'
78
export function injectCssOnServer(
8-
code: string,
9+
mgcStr: MagicStringBase,
910
vbindVariableList: TMatchVariable | undefined,
1011
isHmring: boolean,
1112
) {
@@ -15,13 +16,13 @@ export function injectCssOnServer(
1516
if (!vbVar.hash && isHmring)
1617
vbVar.hash = hash(vbVar.value + vbVar.has)
1718

18-
code = code.replaceAll(`v-bind-m(${vbVar.value})`, `var(--${vbVar.hash})`)
19+
mgcStr = mgcStr.replaceAll(`v-bind-m(${vbVar.value})`, `var(--${vbVar.hash})`)
1920
})
20-
return code
21+
return mgcStr
2122
}
2223

2324
export function injectCssOnBuild(
24-
code: string,
25+
mgcStr: MagicStringBase,
2526
injectCSSContent: TInjectCSSContent,
2627
descriptor: SFCDescriptor) {
2728
const cssContent = [...injectCSSContent]
@@ -37,13 +38,11 @@ export function injectCssOnBuild(
3738
const scoped = value.scoped ? 'scoped' : ''
3839
resCode = `<style lang="${lang}" ${scoped}> ${injectCssCode}\n${transformInjectCSS(value.content, parseImports(value.content).imports)} </style>`
3940
})
40-
code = removeStyleTagsAndContent(code)
41-
return `${code}\n ${resCode}`
41+
mgcStr = removeStyleTagsAndContent(mgcStr)
42+
return mgcStr.prependRight(mgcStr.length(), resCode)
4243
}
4344

44-
export function removeStyleTagsAndContent(html: string): string {
45-
// 使用正则表达式匹配所有的style标签并替换为空字符串
46-
const newHtml = html.replace(/<style\b[^>]*>[\s\S]*?<\/style>/gi, '')
47-
// 使用正则表达式匹配所有的style标签并替换为空字符串
48-
return newHtml.replace(/<style\b[^>]*>/gi, '').replace(/<\/style>/gi, '')
45+
export function removeStyleTagsAndContent(mgcStr: MagicStringBase): MagicStringBase {
46+
return mgcStr.replace(/<style\b[^>]*>[\s\S]*?<\/style>/gi, '')
47+
.replace(/<style\b[^>]*>/gi, '').replace(/<\/style>/gi, '')
4948
}

packages/core/runtime/process-css.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export const getCSSFileRecursion = (
2424
if (!cssFile.sfcPath)
2525
cssFile.sfcPath = new Set()
2626

27-
cssFile.sfcPath?.add(sfcPath)
27+
cssFile.sfcPath?.add(sfcPath || '')
2828
matchedMark.add(key)
2929
cb(cssFile)
3030
if (cssFile.importer.size > 0) {

0 commit comments

Comments
 (0)