Skip to content

Commit bb0c105

Browse files
committed
wip: port rollup-plugin-vue to vite plugin
1 parent 9eef197 commit bb0c105

13 files changed

+1027
-3
lines changed

LICENSE

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
MIT License
22

3-
Copyright (c) 2019-present, Yuxi (Evan) You
3+
Copyright (c) 2019-present, Yuxi (Evan) You and Vite contributors
44

55
Permission is hereby granted, free of charge, to any person obtaining a copy
66
of this software and associated documentation files (the "Software"), to deal

package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
"packages/*"
66
],
77
"scripts": {
8-
"lint": "eslint --ext .ts packages/*/src/**"
8+
"lint": "eslint --ext .ts packages/*/src/**",
9+
"bundle": "esbuild packages/vite/src/node/index.ts packages/vite/src/node/cli.ts --bundle --platform=node --target=node12 --external:fsevents --external:sugarss --external:bufferutil --external:utf-8-validate --outdir=esbuild"
910
},
1011
"devDependencies": {
1112
"@types/node": "^14.14.10",

packages/plugin-vue/package.json

+33-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,36 @@
11
{
22
"name": "@vitejs/plugin-vue",
3-
"version": "1.0.0"
3+
"version": "1.0.0",
4+
"license": "MIT",
5+
"files": [
6+
"dist"
7+
],
8+
"main": "dist/index.js",
9+
"types": "dist/index.d.ts",
10+
"scripts": {
11+
"dev": "tsc -w --incremental -p ."
12+
},
13+
"engines": {
14+
"node": ">=12.0.0"
15+
},
16+
"repository": {
17+
"type": "git",
18+
"url": "git+https://github.com/vitejs/vite.git"
19+
},
20+
"bugs": {
21+
"url": "https://github.com/vitejs/vite/issues"
22+
},
23+
"homepage": "https://github.com/vitejs/vite/tree/master/#readme",
24+
"peerDependencies": {
25+
"@vue/compiler-sfc": "^3.0.4"
26+
},
27+
"devDependencies": {
28+
"@rollup/pluginutils": "^4.1.0",
29+
"@types/hash-sum": "^1.0.0",
30+
"@vue/compiler-sfc": "^3.0.4",
31+
"debug": "^4.3.1",
32+
"hash-sum": "^2.0.0",
33+
"rollup": "^2.35.1",
34+
"source-map": "^0.6.1"
35+
}
436
}
+194
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
import fs from 'fs'
2+
import _debug from 'debug'
3+
import { parse, SFCBlock, SFCDescriptor } from '@vue/compiler-sfc'
4+
import {
5+
getDescriptor,
6+
setDescriptor,
7+
setPrevDescriptor
8+
} from './utils/descriptorCache'
9+
import { getResolvedScript, setResolvedScript } from './script'
10+
import { ModuleNode } from 'vite'
11+
12+
const debug = _debug('vite:hmr')
13+
14+
/**
15+
* Vite-specific HMR handling
16+
*/
17+
export async function handleHotUpdate(
18+
file: string,
19+
modules: ModuleNode[]
20+
): Promise<ModuleNode[] | void> {
21+
if (!file.endsWith('.vue')) {
22+
return
23+
}
24+
25+
const prevDescriptor = getDescriptor(file)
26+
if (!prevDescriptor) {
27+
// file hasn't been requested yet (e.g. async component)
28+
return
29+
}
30+
31+
let content = fs.readFileSync(file, 'utf-8')
32+
if (!content) {
33+
await untilModified(file)
34+
content = fs.readFileSync(file, 'utf-8')
35+
}
36+
37+
const { descriptor } = parse(content, {
38+
filename: file,
39+
sourceMap: true
40+
})
41+
setDescriptor(file, descriptor)
42+
setPrevDescriptor(file, prevDescriptor)
43+
44+
let needRerender = false
45+
const affectedModules = new Set<ModuleNode | undefined>()
46+
const mainModule = modules.find(
47+
(m) => !/type=/.test(m.url) || /type=script/.test(m.url)
48+
)
49+
const templateModule = modules.find((m) => /type=template/.test(m.url))
50+
51+
if (
52+
!isEqualBlock(descriptor.script, prevDescriptor.script) ||
53+
!isEqualBlock(descriptor.scriptSetup, prevDescriptor.scriptSetup)
54+
) {
55+
affectedModules.add(mainModule)
56+
}
57+
58+
if (!isEqualBlock(descriptor.template, prevDescriptor.template)) {
59+
// when a <script setup> component's template changes, it will need correct
60+
// binding metadata. However, when reloading the template alone the binding
61+
// metadata will not be available since the script part isn't loaded.
62+
// in this case, reuse the compiled script from previous descriptor.
63+
if (mainModule && !affectedModules.has(mainModule)) {
64+
setResolvedScript(descriptor, getResolvedScript(prevDescriptor)!)
65+
}
66+
affectedModules.add(templateModule)
67+
needRerender = true
68+
}
69+
70+
let didUpdateStyle = false
71+
const prevStyles = prevDescriptor.styles || []
72+
const nextStyles = descriptor.styles || []
73+
74+
// force reload if CSS vars injection changed
75+
if (prevDescriptor.cssVars.join('') !== descriptor.cssVars.join('')) {
76+
affectedModules.add(mainModule)
77+
}
78+
79+
// force reload if scoped status has changed
80+
if (prevStyles.some((s) => s.scoped) !== nextStyles.some((s) => s.scoped)) {
81+
// template needs to be invalidated as well
82+
affectedModules.add(templateModule)
83+
affectedModules.add(mainModule)
84+
}
85+
86+
// only need to update styles if not reloading, since reload forces
87+
// style updates as well.
88+
for (let i = 0; i < nextStyles.length; i++) {
89+
const prev = prevStyles[i]
90+
const next = nextStyles[i]
91+
if (!prev || !isEqualBlock(prev, next)) {
92+
didUpdateStyle = true
93+
const mod = modules.find((m) => m.url.includes(`type=style&index=${i}`))
94+
if (mod) {
95+
affectedModules.add(mod)
96+
} else {
97+
// new style block - force reload
98+
affectedModules.add(mainModule)
99+
}
100+
}
101+
}
102+
if (prevStyles.length > nextStyles.length) {
103+
// style block removed - force reload
104+
affectedModules.add(mainModule)
105+
}
106+
107+
const prevCustoms = prevDescriptor.customBlocks || []
108+
const nextCustoms = descriptor.customBlocks || []
109+
110+
// custom blocks update causes a reload
111+
// because the custom block contents is changed and it may be used in JS.
112+
for (let i = 0; i < nextCustoms.length; i++) {
113+
const prev = prevCustoms[i]
114+
const next = nextCustoms[i]
115+
if (!prev || !isEqualBlock(prev, next)) {
116+
const mod = modules.find((m) =>
117+
m.url.includes(`type=${prev.type}&index=${i}`)
118+
)
119+
if (mod) {
120+
affectedModules.add(mod)
121+
} else {
122+
affectedModules.add(mainModule)
123+
}
124+
}
125+
}
126+
if (prevCustoms.length > nextCustoms.length) {
127+
// block rmeoved, force reload
128+
affectedModules.add(mainModule)
129+
}
130+
131+
let updateType = []
132+
if (needRerender) {
133+
updateType.push(`template`)
134+
// template is inlined into main, add main module instead
135+
if (!templateModule) {
136+
affectedModules.add(mainModule)
137+
}
138+
}
139+
if (didUpdateStyle) {
140+
updateType.push(`style`)
141+
}
142+
if (updateType.length) {
143+
debug(`[vue:update(${updateType.join('&')})] ${file}`)
144+
}
145+
return [...affectedModules].filter(Boolean) as ModuleNode[]
146+
}
147+
148+
// vitejs/vite#610 when hot-reloading Vue files, we read immediately on file
149+
// change event and sometimes this can be too early and get an empty buffer.
150+
// Poll until the file's modified time has changed before reading again.
151+
async function untilModified(file: string) {
152+
const mtime = fs.statSync(file).mtimeMs
153+
return new Promise((r) => {
154+
let n = 0
155+
const poll = async () => {
156+
n++
157+
const newMtime = fs.statSync(file).mtimeMs
158+
if (newMtime !== mtime || n > 10) {
159+
r(0)
160+
} else {
161+
setTimeout(poll, 10)
162+
}
163+
}
164+
setTimeout(poll, 10)
165+
})
166+
}
167+
168+
function isEqualBlock(a: SFCBlock | null, b: SFCBlock | null) {
169+
if (!a && !b) return true
170+
if (!a || !b) return false
171+
// src imports will trigger their own updates
172+
if (a.src && b.src && a.src === b.src) return true
173+
if (a.content !== b.content) return false
174+
const keysA = Object.keys(a.attrs)
175+
const keysB = Object.keys(b.attrs)
176+
if (keysA.length !== keysB.length) {
177+
return false
178+
}
179+
return keysA.every((key) => a.attrs[key] === b.attrs[key])
180+
}
181+
182+
export function isOnlyTemplateChanged(
183+
prev: SFCDescriptor,
184+
next: SFCDescriptor
185+
) {
186+
return (
187+
isEqualBlock(prev.script, next.script) &&
188+
isEqualBlock(prev.scriptSetup, next.scriptSetup) &&
189+
prev.styles.length === next.styles.length &&
190+
prev.styles.every((s, i) => isEqualBlock(s, next.styles[i])) &&
191+
prev.customBlocks.length === next.customBlocks.length &&
192+
prev.customBlocks.every((s, i) => isEqualBlock(s, next.customBlocks[i]))
193+
)
194+
}

0 commit comments

Comments
 (0)