Skip to content

feat: support webpack hmr #54

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Apr 28, 2023
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
6 changes: 0 additions & 6 deletions .idea/inspectionProfiles/Project_Default.xml

This file was deleted.

16 changes: 7 additions & 9 deletions 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, viteHMR } from '../hmr'
import { reloadSFCModules, updatedCSSModules, viteHMR } from '../hmr'

const mockOption = {
rootDir: resolve(),
Expand Down Expand Up @@ -54,33 +54,31 @@ describe('HMR', () => {
expect(hmrModule).toMatchObject({ id: 'foo.vue' })
})

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

triggerSFCUpdate(CSSFileModuleMap, mockOption, {
reloadSFCModules(CSSFileModuleMap, mockOption, {
importer: new Set(),
vBindCode: ['foo'],
sfcPath: new Set(['../D/test']),
} as any, 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 sfcPath is undefined', () => {
test('HMR: reloadSFCModules sfcPath is undefined', () => {
const CSSFileModuleMap = new Map()
CSSFileModuleMap.set(file, {
importer: new Set(),
vBindCode: ['foo'],
sfcPath: new Set(['../D/test']),
})

triggerSFCUpdate(CSSFileModuleMap, mockOption, {
reloadSFCModules(CSSFileModuleMap, mockOption, {
importer: new Set(),
vBindCode: ['foo'],
} as any, file, mockServer as any)
Expand All @@ -89,15 +87,15 @@ describe('HMR', () => {
expect(hmrModule).not.toBeTruthy()
})

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

triggerSFCUpdate(CSSFileModuleMap, mockOption, {
reloadSFCModules(CSSFileModuleMap, mockOption, {
importer: new Set(),
vBindCode: ['foo'],
sfcPath: new Set(),
Expand Down
22 changes: 8 additions & 14 deletions packages/core/hmr/hmr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,11 @@ export function viteHMR(
) {
// 获取变化的样式文件的 CSSFileMap上有使用它的
const sfcModulesPathList = CSSFileModuleMap.get(file)
triggerSFCUpdate(CSSFileModuleMap, userOptions, sfcModulesPathList!, file, server)
if (!(sfcModulesPathList && sfcModulesPathList.sfcPath)) return
// update CSSModules
updatedCSSModules(CSSFileModuleMap, userOptions, file)
// reload sfc Module
reloadSFCModules(CSSFileModuleMap, userOptions, sfcModulesPathList!, file, server)
}

// TODO: unit test
Expand All @@ -20,15 +24,7 @@ export function webpackHMR(
userOptions: Options,
file: string,
) {
// 获取变化的样式文件的 CSSFileMap上有使用它的
const sfcModulesPathList = CSSFileModuleMap.get(file)
if (sfcModulesPathList && sfcModulesPathList.sfcPath) {
const ls = setTArray(sfcModulesPathList.sfcPath)
ls.forEach(() => {
// updated CSSModules
updatedCSSModules(CSSFileModuleMap, userOptions, file)
})
}
updatedCSSModules(CSSFileModuleMap, userOptions, file)
}

/**
Expand All @@ -51,14 +47,14 @@ export function updatedCSSModules(
}

/**
* triggerSFCUpdate
* reloadSFCModules
* @param CSSFileModuleMap
* @param userOptions
* @param sfcModulesPathList
* @param file
* @param server
*/
export function triggerSFCUpdate(
export function reloadSFCModules(
CSSFileModuleMap: ICSSFileMap,
userOptions: Options,
sfcModulesPathList: ICSSFile,
Expand All @@ -68,8 +64,6 @@ export function triggerSFCUpdate(
// 变化的样式文件的 CSSFileMap上有使用它的 sfc 的信息
const ls = setTArray(sfcModulesPathList.sfcPath)
ls.forEach((sfcp: string) => {
// updatedCSSModules
updatedCSSModules(CSSFileModuleMap, userOptions, file)
// update sfc
const modules = server.moduleGraph.fileToModulesMap.get(sfcp) || new Set()
const modulesList = setTArray(modules)
Expand Down
23 changes: 15 additions & 8 deletions packages/core/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import {
JSX_TSX_REG, NAME,
SUPPORT_FILE_REG,
log,
runAsyncTaskList,
setTArray,
transformSymbol,
} from '@unplugin-vue-cssvars/utils'
Expand All @@ -25,7 +24,7 @@ import type { MagicStringBase } from 'magic-string-ast'
import type { HmrContext, ResolvedConfig } from 'vite'
import type { TMatchVariable } from './parser'
import type { Options } from './types'
// TODO: webpack hmr
// TODO refactor
const unplugin = createUnplugin<Options>(
(options: Options = {}, meta): any => {
const framework = meta.framework
Expand All @@ -39,12 +38,13 @@ const unplugin = createUnplugin<Options>(
const vbindVariableList = new Map<string, TMatchVariable>()
let isScriptSetup = false
if (userOptions.server === undefined) {
log('warning', 'The server of option is not set, you need to specify whether you are using the development server or building the project')
log('warning', 'The server of option is not set, you need to specify whether you are using the development server or building the project')
console.warn(chalk.yellowBright.bold(`[${NAME}] The server of option is not set, you need to specify whether you are using the development server or building the project`))
console.warn(chalk.yellowBright.bold(`[${NAME}] See: https://github.com/baiwusanyu-c/unplugin-vue-cssvars/blob/master/README.md#option`))
}
let isServer = !!userOptions.server
let isHMR = false
const cacheWebpackModule = new Map<string, any>()

function handleVBindVariable(
code: string,
Expand Down Expand Up @@ -93,8 +93,9 @@ const unplugin = createUnplugin<Options>(
mgcStr = res
}

if ((transId.includes('?vue&type=style') && isHMR && framework === 'webpack')) {
transId = transId.split('?vue&type=style')[0]
if ((transId.includes('?vue&type=style') || transId.includes('?vue&type=script'))
&& isHMR && framework === 'webpack') {
transId = transId.split('?vue')[0]
const res = handleVBindVariable(code, transId, mgcStr)
if (res)
mgcStr = res
Expand Down Expand Up @@ -134,10 +135,12 @@ const unplugin = createUnplugin<Options>(
}
},
},

// TODO unit test
webpack(compiler) {
// mark webpack hmr
let modifiedFile = ''
compiler.hooks.watchRun.tap(NAME, (compilation1) => {
compiler.hooks.watchRun.tapAsync(NAME, (compilation1, watchRunCallBack) => {
if (compilation1.modifiedFiles) {
modifiedFile = transformSymbol(setTArray(compilation1.modifiedFiles)[0] as string)
if (SUPPORT_FILE_REG.test(modifiedFile)) {
Expand All @@ -149,6 +152,7 @@ const unplugin = createUnplugin<Options>(
)
}
}
watchRunCallBack()
})

compiler.hooks.compilation.tap(NAME, (compilation) => {
Expand Down Expand Up @@ -180,6 +184,8 @@ const unplugin = createUnplugin<Options>(
Promise.all(promises)
.then(() => {
callback()
// hmr end
isHMR = false
})
.catch((e) => {
log('error', e)
Expand All @@ -202,6 +208,7 @@ const unplugin = createUnplugin<Options>(
return filter(id)
},
async transform(code: string, id: string) {
console.log(id)
let transId = transformSymbol(id)
let mgcStr = new MagicString(code)
// ⭐TODO: 只支持 .vue ? jsx, tsx, js, ts ?
Expand All @@ -211,7 +218,7 @@ const unplugin = createUnplugin<Options>(
const injectRes = injectCSSVars(vbindVariableList.get(idKey), isScriptSetup, parseRes, mgcStr)
mgcStr = injectRes.mgcStr
injectRes.vbindVariableList && vbindVariableList.set(transId, injectRes.vbindVariableList)
isHMR = false
// TODO vite hmr close ? isHMR -> false
}

// transform in dev
Expand All @@ -237,7 +244,7 @@ const unplugin = createUnplugin<Options>(
// webpack dev 和 build 都回进入这里
if (framework === 'webpack') {
if (transId.includes('?vue&type=script')) {
transId = transId.split('?vue&type=script')[0]
transId = transId.split('?vue')[0]
injectCSSVarsFn(transId)
}

Expand Down
4 changes: 2 additions & 2 deletions packages/core/inject/inject-css.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
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 { MagicStringBase } from 'magic-string-ast'
import type { TInjectCSSContent } from '../runtime/process-css'
import type { SFCDescriptor } from '@vue/compiler-sfc'
import type { TMatchVariable } from '../parser'
Expand All @@ -11,7 +11,7 @@ export function injectCSSOnServer(
isHMR: boolean,
) {
vbindVariableList && vbindVariableList.forEach((vbVar) => {
// 样式文件修改后,热更新会先于 sfc 热更新运行,这里先设置hash
// 样式文件修改后,style热更新可能会先于 sfc 热更新运行,这里先设置hash
// 详见 packages/core/index.ts的 handleHotUpdate
if (!vbVar.hash && isHMR)
vbVar.hash = hash(vbVar.value + vbVar.has)
Expand Down
5 changes: 4 additions & 1 deletion play/vite/src/assets/css/foo.css
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
#foo{
color: v-bind-m(color);
color: v-bind-m(sassColor);
background: #ffebf8;
width: 200px;
height: 30px;
}
p {
color: v-bind-m(color);
}
36 changes: 6 additions & 30 deletions play/vite/src/comp.vue
Original file line number Diff line number Diff line change
@@ -1,36 +1,12 @@
<script lang="tsx">
/* import { reactive, ref } from 'vue'
const compAsd = () => 'red'
const color = 'red'
const compTheme1 = compAsd()
const compTheme2 = 'red'
const sassColor = 'pink'
const compTheme3 = ref('red')
const compTheme4 = reactive({ color: 'red' })
const compTheme5 = { color: 'red' }
const compTheme6 = () => 'red' */
import { defineComponent } from 'vue'
export default defineComponent({
setup() {
return () => (<div>test</div>)
},
})
<script setup lang="ts">
import { ref } from 'vue'
const color = ref('red')
</script>

<template>
<div class="test">
comp
</div>
<p>comp</p>
</template>

<style lang="scss">
/*@import "./assets/test.css";*/
div {
color: v-bind(color)
}
// @import './assets/scss/bar.scss';
.blue-btn {

color: v-bind(sassColor);
}
<style scoped>
@import "./assets/css/foo.css";
</style>
12 changes: 12 additions & 0 deletions play/vite/src/comp2.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<script setup lang="ts">
import { ref } from 'vue'
const color = ref('red')
</script>

<template>
<p>comp2</p>
</template>

<style scoped>
@import "./assets/css/foo.css";
</style>
12 changes: 7 additions & 5 deletions play/vite/src/views/app/App.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
<script setup lang="ts">
import { reactive, ref } from 'vue'
import Comp from '../../comp.vue'
const color = ref('blue')
import Comp2 from '../../comp2.vue'
/* const color = ref('blue')
const appAsd = () => 'red'
const fooColor = appAsd()
const appTheme2 = 'blue'
Expand All @@ -11,7 +12,7 @@ const stylColor = '#fd1d7c'
const appTheme3 = ref('red')
const appTheme4 = reactive({ color: 'red' })
const appTheme5 = { color: 'red' }
const appTheme6 = () => 'red'
const appTheme6 = () => 'red' */
</script>
<!--
<script lang="ts">
Expand Down Expand Up @@ -74,10 +75,11 @@ export default defineComponent({
<template>
<div id="foo" class="scss" @click="sassColor = 'red'">
app122
<!-- <Comp /> -->
<Comp />
<Comp2 />
</div>
</template>

<style lang="scss" scoped>
<!-- <style lang="scss" scoped>
@import '@/assets/css/foo.css';
</style>
</style> -->
4 changes: 2 additions & 2 deletions play/webpack/src/App.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<script setup lang="ts">
import { ref } from 'vue'
import Comp from '@/comp.vue'
import Comp2 from '@/comp2.vue'
import Comp from './comp.vue'
import Comp2 from './comp2.vue'
const color = ref('red')
const appAsd = () => 'green'
const fooColor = appAsd()
Expand Down
2 changes: 1 addition & 1 deletion play/webpack/src/assets/css/foo.css
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
p{
color: v-bind-m(wfwfw);
color: v-bind-m(color);
width: 200px;
height: 30px;
}
12 changes: 12 additions & 0 deletions play/webpack/src/comp.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<script setup lang="ts">
import { ref } from 'vue'
const color = ref('red')
</script>

<template>
<p>comp</p>
</template>

<style scoped>
@import "./assets/css/foo.css";
</style>
12 changes: 12 additions & 0 deletions play/webpack/src/comp2.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<script setup lang="ts">
import { ref } from 'vue'
const color = ref('red')
</script>

<template>
<p>comp2</p>
</template>

<style scoped>
@import "./assets/css/foo.css";
</style>