Skip to content

Commit

Permalink
feat: support generate sourcemap
Browse files Browse the repository at this point in the history
  • Loading branch information
baiwusanyu-c committed Apr 21, 2023
1 parent b5b9742 commit 81374e5
Show file tree
Hide file tree
Showing 10 changed files with 1,978 additions and 2,312 deletions.
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@
"fs-extra": "^11.1.1",
"hash-sum": "^2.0.0",
"magic-string": "^0.30.0",
"source-map-diff": "^1.3.1",
"unplugin": "^1.3.1",
"vue": "^3.2.47"
},
Expand All @@ -86,6 +87,7 @@
"fs-extra": "^11.1.1",
"hash-sum": "^2.0.0",
"magic-string": "^0.30.0",
"source-map-diff": "^1.3.1",
"unplugin": "^1.3.1",
"vue": "^3.2.47"
},
Expand Down Expand Up @@ -120,12 +122,13 @@
"gulp": "^4.0.2",
"jsdom": "^21.1.1",
"lint-staged": "^13.1.1",
"magic-string-ast": "^0.1.2",
"npm-run-all": "^4.1.5",
"rimraf": "^5.0.0",
"rollup": "^3.19.1",
"simple-git-hooks": "^2.8.1",
"typescript": "5.0.4",
"vite": "^4.2.1",
"vite": "^4.3.0",
"vitest": "^0.30.1"
},
"simple-git-hooks": {
Expand Down
16 changes: 15 additions & 1 deletion packages/core/hmr/__test__/hmr.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { resolve } from 'path'
import { beforeEach, describe, expect, test } from 'vitest'
import { transformSymbol } from '@unplugin-vue-cssvars/utils'
import { triggerSFCUpdate, updatedCSSModules } from '../hmr'
import { triggerSFCUpdate, updatedCSSModules, viteHMR } from '../hmr'

const mockOption = {
rootDir: resolve(),
Expand Down Expand Up @@ -40,6 +40,20 @@ describe('HMR', () => {
expect(CSSFileModuleMap.get(file).vBindCode).toMatchObject(['test'])
})

test('HMR: viteHMR', () => {
const CSSFileModuleMap = new Map()
CSSFileModuleMap.set(file, {
importer: new Set(),
vBindCode: ['foo'],
sfcPath: new Set(['../D/test']),
})

viteHMR(CSSFileModuleMap, mockOption, file, mockServer as any)
expect(CSSFileModuleMap.get(file).content).toBeTruthy()
expect(CSSFileModuleMap.get(file).vBindCode).toMatchObject(['test'])
expect(hmrModule).toMatchObject({ id: 'foo.vue' })
})

test('HMR: triggerSFCUpdate basic', () => {
const CSSFileModuleMap = new Map()
CSSFileModuleMap.set(file, {
Expand Down
5 changes: 2 additions & 3 deletions packages/core/hmr/hmr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export function viteHMR(
) {
// 获取变化的样式文件的 CSSFileMap上有使用它的
const sfcModulesPathList = CSSFileModuleMap.get(file)
triggerSFCUpdate(CSSFileModuleMap, userOptions, sfcModulesPathList, file, server)
triggerSFCUpdate(CSSFileModuleMap, userOptions, sfcModulesPathList!, file, server)
}

/**
Expand All @@ -26,10 +26,9 @@ export function updatedCSSModules(
userOptions: Options,
file: string) {
const updatedCSSMS = preProcessCSS(userOptions, userOptions.alias, [file]).get(file)
CSSFileModuleMap.set(file, updatedCSSMS)
CSSFileModuleMap.set(file, updatedCSSMS!)
}

// TODO: unit test
/**
* triggerSFCUpdate
* @param CSSFileModuleMap
Expand Down
57 changes: 41 additions & 16 deletions packages/core/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { JSX_TSX_REG, NAME, SUPPORT_FILE_REG } from '@unplugin-vue-cssvars/utils
import { createFilter } from '@rollup/pluginutils'
import { parse } from '@vue/compiler-sfc'
import chalk from 'chalk'
import MagicString from 'magic-string'
import { preProcessCSS } from './runtime/pre-process-css'
import { getVBindVariableListByPath } from './runtime/process-css'
import { initOption } from './option'
Expand All @@ -14,7 +15,6 @@ import {
} from './inject'
import { viteHMR } from './hmr/hmr'
import type { HmrContext, ResolvedConfig } from 'vite'

import type { TMatchVariable } from './parser'
import type { Options } from './types'
// TODO: webpack hmr
Expand Down Expand Up @@ -43,27 +43,37 @@ const unplugin = createUnplugin<Options>(
return filter(id)
},
async transform(code: string, id: string) {
let mgcStr = new MagicString(code)
try {
// ⭐TODO: 只支持 .vue ? jsx, tsx, js, ts ?
if (id.endsWith('.vue')) {
const { descriptor } = parse(code)
const lang = descriptor?.script?.lang ?? 'js'
// ⭐TODO: 只支持 .vue ? jsx, tsx, js, ts ?
if (JSX_TSX_REG.test(`.${lang}`))
return code
if (!JSX_TSX_REG.test(`.${lang}`)) {
isScriptSetup = !!descriptor.scriptSetup
const {
vbindVariableListByPath,
injectCSSContent,
} = getVBindVariableListByPath(descriptor, id, CSSFileModuleMap, isServer, userOptions.alias)
const variableName = getVariable(descriptor)
vbindVariableList.set(id, matchVariable(vbindVariableListByPath, variableName))

isScriptSetup = !!descriptor.scriptSetup
const {
vbindVariableListByPath,
injectCSSContent,
} = getVBindVariableListByPath(descriptor, id, CSSFileModuleMap, isServer, userOptions.alias)
const variableName = getVariable(descriptor)
vbindVariableList.set(id, matchVariable(vbindVariableListByPath, variableName))
if (!isServer)
mgcStr = injectCssOnBuild(mgcStr, injectCSSContent, descriptor)
}
}

if (!isServer)
code = injectCssOnBuild(code, injectCSSContent, descriptor)
return {
code: mgcStr.toString(),
get map() {
return mgcStr.generateMap({
source: id,
includeContent: true,
hires: true,
})
},
}
return code
} catch (err: unknown) {
this.error(`[${NAME}] ${err}`)
}
Expand Down Expand Up @@ -93,20 +103,35 @@ const unplugin = createUnplugin<Options>(
name: `${NAME}:inject`,
enforce: 'post',
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
if (isServer) {
if (id.endsWith('.vue')) {
const injectRes = injectCSSVars(code, vbindVariableList.get(id), isScriptSetup)
code = injectRes.code
mgcStr = mgcStr.overwrite(0, mgcStr.length(), injectRes.code)
injectRes.vbindVariableList && vbindVariableList.set(id, injectRes.vbindVariableList)
isHmring = false
}
if (id.includes('type=style'))
code = injectCssOnServer(code, vbindVariableList.get(id.split('?vue')[0]), isHmring)
mgcStr = injectCssOnServer(mgcStr, vbindVariableList.get(id.split('?vue')[0]), isHmring)
}

return {
code: mgcStr.toString(),
get map() {
return mgcStr.generateMap({
source: id,
includeContent: true,
hires: true,
})
},
}
return code
} catch (err: unknown) {
this.error(`[${NAME}] ${err}`)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html

exports[`inject-css > injectCssOnBuild: basic 1`] = `
"
<style lang=\\"scss\\" >
"<style lang=\\"scss\\" >
body { background-color: black; }
/* foo.scss -> test2.css -> test.css */
/* foo.scss -> test.scss -> test2.css */
Expand All @@ -14,8 +15,9 @@ div {
`;

exports[`inject-css > injectCssOnBuild: no lang 1`] = `
"
<style lang=\\"css\\" >
"<style lang=\\"css\\" >
body { background-color: black; }
/* foo.scss -> test2.css -> test.css */
/* foo.scss -> test.scss -> test2.css */
Expand All @@ -26,7 +28,4 @@ div {
</style>"
`;

exports[`inject-css > injectCssOnBuild: no styles 1`] = `
"test
"
`;
exports[`inject-css > injectCssOnBuild: no styles 1`] = `"test"`;
51 changes: 35 additions & 16 deletions packages/core/inject/__test__/inject-css.spec.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
import { describe, expect, test } from 'vitest'
import MagicString from 'magic-string'
import { injectCssOnBuild, injectCssOnServer, removeStyleTagsAndContent } from '../inject-css'
describe('inject-css', () => {
test('injectCssOnServer: basic', () => {
const code = 'v-bind-m(foo)'
const mgcStr = new MagicString(code)
const vbindVariableList = [{ value: 'foo', hash: 'hash' }]
expect(injectCssOnServer(code, vbindVariableList as any)).toBe('var(--hash)')
expect(injectCssOnServer(mgcStr, vbindVariableList as any, false).toString()).toBe('var(--hash)')
})

test('injectCssOnServer: vbindVariableList is undefined', () => {
const code = 'v-bind-m(foo)'
expect(injectCssOnServer(code, undefined)).toBe(code)
const mgcStr = new MagicString(code)
expect(injectCssOnServer(mgcStr, undefined, false).toString()).toBe(code)
})

test('removeStyleTagsAndContent: basic', () => {
Expand Down Expand Up @@ -48,12 +51,13 @@ describe('inject-css', () => {
</body>
</html>
`

expect(removeStyleTagsAndContent(html)).toEqual(expectedHtml)
const mgcStr = new MagicString(html)
expect(removeStyleTagsAndContent(mgcStr).toString()).toEqual(expectedHtml)
})

test('removeStyleTagsAndContent: empty HTML', () => {
expect(removeStyleTagsAndContent('')).toEqual('')
const mgcStr = new MagicString('')
expect(removeStyleTagsAndContent(mgcStr).toString()).toEqual('')
})

test('removeStyleTagsAndContent: not modify HTML without style tags', () => {
Expand All @@ -68,8 +72,8 @@ describe('inject-css', () => {
</body>
</html>
`

expect(removeStyleTagsAndContent(html)).toEqual(html)
const mgcStr = new MagicString(html)
expect(removeStyleTagsAndContent(mgcStr).toString()).toEqual(html)
})

test('injectCssOnBuild: basic', () => {
Expand All @@ -83,7 +87,12 @@ describe('inject-css', () => {
+ '}\n'
+ "@import './assets/scss/foo.scss';\n"
+ '</style>'
const injectCSSContent = new Set([{ content: '@import \'./assets/scss/foo.scss\';\nbody { background-color: black; }', lang: 'scss' }])
const mgcStr = new MagicString(code)
const injectCSSContent = new Set([{
content: '@import \'./assets/scss/foo.scss\';\nbody { background-color: black; }',
lang: 'scss',
styleTagIndex: 0,
}])
const descriptor = {
styles: [
{
Expand All @@ -99,18 +108,23 @@ describe('inject-css', () => {
},
],
}
const result = injectCssOnBuild(code, injectCSSContent, descriptor as any)
expect(result).toMatchSnapshot()
const result = injectCssOnBuild(mgcStr, injectCSSContent, descriptor as any)
expect(result.toString()).toMatchSnapshot()
})

test('injectCssOnBuild: no styles', () => {
const code = 'test'
const injectCSSContent = new Set([{ content: '@import \'./assets/scss/foo.scss\';\nbody { background-color: black; }', lang: 'scss' }])
const injectCSSContent = new Set([{
content: '@import \'./assets/scss/foo.scss\';\nbody { background-color: black; }',
lang: 'scss',
styleTagIndex: 0,
}])
const descriptor = {
styles: null,
}
const result = injectCssOnBuild(code, injectCSSContent, descriptor as any)
expect(result).toMatchSnapshot()
const mgcStr = new MagicString(code)
const result = injectCssOnBuild(mgcStr, injectCSSContent, descriptor as any)
expect(result.toString()).toMatchSnapshot()
})

test('injectCssOnBuild: no lang', () => {
Expand All @@ -124,7 +138,11 @@ describe('inject-css', () => {
+ '}\n'
+ "@import './assets/scss/foo.scss';\n"
+ '</style>'
const injectCSSContent = new Set([{ content: '@import \'./assets/scss/foo.scss\';\nbody { background-color: black; }', lang: 'scss' }])
const injectCSSContent = new Set([{
content: '@import \'./assets/scss/foo.scss\';\nbody { background-color: black; }',
lang: 'scss',
styleTagIndex: 0,
}])
const descriptor = {
styles: [
{
Expand All @@ -140,7 +158,8 @@ describe('inject-css', () => {
},
],
}
const result = injectCssOnBuild(code, injectCSSContent, descriptor as any)
expect(result).toMatchSnapshot()
const mgcStr = new MagicString(code)
const result = injectCssOnBuild(mgcStr, injectCSSContent, descriptor as any)
expect(result.toString()).toMatchSnapshot()
})
})
21 changes: 10 additions & 11 deletions packages/core/inject/inject-css.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import hash from 'hash-sum'
import { type MagicStringBase } from 'magic-string-ast'
import { transformInjectCSS } from '../transform/transform-inject-css'
import { parseImports } from '../parser'
import type { TInjectCSSContent } from '../runtime/process-css'
import type { SFCDescriptor } from '@vue/compiler-sfc'
import type { TMatchVariable } from '../parser'
export function injectCssOnServer(
code: string,
mgcStr: MagicStringBase,
vbindVariableList: TMatchVariable | undefined,
isHmring: boolean,
) {
Expand All @@ -15,13 +16,13 @@ export function injectCssOnServer(
if (!vbVar.hash && isHmring)
vbVar.hash = hash(vbVar.value + vbVar.has)

code = code.replaceAll(`v-bind-m(${vbVar.value})`, `var(--${vbVar.hash})`)
mgcStr = mgcStr.replaceAll(`v-bind-m(${vbVar.value})`, `var(--${vbVar.hash})`)
})
return code
return mgcStr
}

export function injectCssOnBuild(
code: string,
mgcStr: MagicStringBase,
injectCSSContent: TInjectCSSContent,
descriptor: SFCDescriptor) {
const cssContent = [...injectCSSContent]
Expand All @@ -37,13 +38,11 @@ export function injectCssOnBuild(
const scoped = value.scoped ? 'scoped' : ''
resCode = `<style lang="${lang}" ${scoped}> ${injectCssCode}\n${transformInjectCSS(value.content, parseImports(value.content).imports)} </style>`
})
code = removeStyleTagsAndContent(code)
return `${code}\n ${resCode}`
mgcStr = removeStyleTagsAndContent(mgcStr)
return mgcStr.prependRight(mgcStr.length(), resCode)
}

export function removeStyleTagsAndContent(html: string): string {
// 使用正则表达式匹配所有的style标签并替换为空字符串
const newHtml = html.replace(/<style\b[^>]*>[\s\S]*?<\/style>/gi, '')
// 使用正则表达式匹配所有的style标签并替换为空字符串
return newHtml.replace(/<style\b[^>]*>/gi, '').replace(/<\/style>/gi, '')
export function removeStyleTagsAndContent(mgcStr: MagicStringBase): MagicStringBase {
return mgcStr.replace(/<style\b[^>]*>[\s\S]*?<\/style>/gi, '')
.replace(/<style\b[^>]*>/gi, '').replace(/<\/style>/gi, '')
}
2 changes: 1 addition & 1 deletion packages/core/runtime/process-css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export const getCSSFileRecursion = (
if (!cssFile.sfcPath)
cssFile.sfcPath = new Set()

cssFile.sfcPath?.add(sfcPath)
cssFile.sfcPath?.add(sfcPath || '')
matchedMark.add(key)
cb(cssFile)
if (cssFile.importer.size > 0) {
Expand Down

0 comments on commit 81374e5

Please sign in to comment.