Skip to content
This repository was archived by the owner on Jan 18, 2022. It is now read-only.

Commit 11ee1c8

Browse files
committed
perf: generate single module if possible
1 parent 59d1c25 commit 11ee1c8

File tree

5 files changed

+213
-97
lines changed

5 files changed

+213
-97
lines changed

src/handleHotUpdate.ts

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
import fs from 'fs'
22
import _debug from 'debug'
3-
import { parse, SFCBlock } from '@vue/compiler-sfc'
4-
import { getDescriptor, setDescriptor } from './utils/descriptorCache'
3+
import { parse, SFCBlock, SFCDescriptor } from '@vue/compiler-sfc'
4+
import {
5+
getDescriptor,
6+
setDescriptor,
7+
setPrevDescriptor,
8+
} from './utils/descriptorCache'
59
import { getResolvedScript, setResolvedScript } from './script'
610

711
const debug = _debug('vite:hmr')
@@ -32,6 +36,7 @@ export async function handleHotUpdate(file: string, modules: any[]) {
3236
sourceRoot: process.cwd(),
3337
})
3438
setDescriptor(file, descriptor)
39+
setPrevDescriptor(file, prevDescriptor)
3540

3641
let needRerender = false
3742
const filteredModules = new Set()
@@ -52,8 +57,11 @@ export async function handleHotUpdate(file: string, modules: any[]) {
5257
// binding metadata. However, when reloading the template alone the binding
5358
// metadata will not be available since the script part isn't loaded.
5459
// in this case, reuse the compiled script from previous descriptor.
55-
setResolvedScript(descriptor, getResolvedScript(prevDescriptor)!)
60+
if (!filteredModules.has(mainModule)) {
61+
setResolvedScript(descriptor, getResolvedScript(prevDescriptor)!)
62+
}
5663
filteredModules.add(templateModule)
64+
needRerender = true
5765
}
5866

5967
let didUpdateStyle = false
@@ -122,6 +130,10 @@ export async function handleHotUpdate(file: string, modules: any[]) {
122130
let updateType = []
123131
if (needRerender) {
124132
updateType.push(`template`)
133+
// template is inlined into main, add main module instead
134+
if (!templateModule) {
135+
filteredModules.add(mainModule)
136+
}
125137
}
126138
if (didUpdateStyle) {
127139
updateType.push(`style`)
@@ -165,3 +177,17 @@ function isEqualBlock(a: SFCBlock | null, b: SFCBlock | null) {
165177
}
166178
return keysA.every((key) => a.attrs[key] === b.attrs[key])
167179
}
180+
181+
export function isOnlyTemplateChanged(
182+
prev: SFCDescriptor,
183+
next: SFCDescriptor
184+
) {
185+
return (
186+
isEqualBlock(prev.script, next.script) &&
187+
isEqualBlock(prev.scriptSetup, next.scriptSetup) &&
188+
prev.styles.length === next.styles.length &&
189+
prev.styles.every((s, i) => isEqualBlock(s, next.styles[i])) &&
190+
prev.customBlocks.length === next.customBlocks.length &&
191+
prev.customBlocks.every((s, i) => isEqualBlock(s, next.customBlocks[i]))
192+
)
193+
}

src/index.ts

Lines changed: 19 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ import fs from 'fs'
1515
import createDebugger from 'debug'
1616
import { Plugin } from 'rollup'
1717
import { createFilter } from '@rollup/pluginutils'
18-
import { transformSFCEntry } from './sfc'
19-
import { transformTemplate } from './template'
18+
import { genSfcFacade } from './sfcFacade'
19+
import { transformTemplateAsModule } from './template'
2020
import { transformStyle } from './style'
2121
import { createCustomBlockFilter } from './utils/customBlockFilter'
2222
import { getDescriptor, setDescriptor } from './utils/descriptorCache'
@@ -117,23 +117,21 @@ export default function PluginVue(userOptions: Partial<Options> = {}): Plugin {
117117
return fs.readFileSync(query.filename, 'utf-8')
118118
}
119119
const descriptor = getDescriptor(query.filename)
120-
if (descriptor) {
121-
const block =
122-
query.type === 'template'
123-
? descriptor.template!
124-
: query.type === 'script'
125-
? getResolvedScript(descriptor, isServer)
126-
: query.type === 'style'
127-
? descriptor.styles[query.index]
128-
: typeof query.index === 'number'
129-
? descriptor.customBlocks[query.index]
130-
: null
131-
132-
if (block) {
133-
return {
134-
code: block.content,
135-
map: normalizeSourceMap(block.map, id),
136-
}
120+
const block =
121+
query.type === 'template'
122+
? descriptor.template!
123+
: query.type === 'script'
124+
? getResolvedScript(descriptor, isServer)
125+
: query.type === 'style'
126+
? descriptor.styles[query.index]
127+
: typeof query.index === 'number'
128+
? descriptor.customBlocks[query.index]
129+
: null
130+
131+
if (block) {
132+
return {
133+
code: block.content,
134+
map: normalizeSourceMap(block.map, id),
137135
}
138136
}
139137
}
@@ -147,7 +145,7 @@ export default function PluginVue(userOptions: Partial<Options> = {}): Plugin {
147145
// generate an entry module that imports the actual blocks of the SFC
148146
if (!query.vue && filter(id)) {
149147
debug(`transform SFC entry (${id})`)
150-
const output = transformSFCEntry(
148+
const output = genSfcFacade(
151149
code,
152150
id,
153151
options,
@@ -173,7 +171,7 @@ export default function PluginVue(userOptions: Partial<Options> = {}): Plugin {
173171
}
174172
if (query.type === 'template') {
175173
debug(`transform template (${id})`)
176-
return transformTemplate(code, id, options, query, this)
174+
return transformTemplateAsModule(code, id, options, query, this)
177175
} else if (query.type === 'style') {
178176
debug(`transform style (${id})`)
179177
return transformStyle(code, id, options, query, isProduction, this)

src/sfc.ts renamed to src/sfcFacade.ts

Lines changed: 97 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,21 @@
11
import hash from 'hash-sum'
22
import path from 'path'
33
import qs from 'querystring'
4-
import { parse, SFCBlock, SFCDescriptor } from '@vue/compiler-sfc'
4+
import {
5+
parse,
6+
rewriteDefault,
7+
SFCBlock,
8+
SFCDescriptor,
9+
} from '@vue/compiler-sfc'
510
import { Options } from '.'
6-
import { setDescriptor } from './utils/descriptorCache'
7-
import { TransformPluginContext } from 'rollup'
11+
import { getPrevDescriptor, setDescriptor } from './utils/descriptorCache'
12+
import { PluginContext, TransformPluginContext } from 'rollup'
813
import { createRollupError } from './utils/error'
914
import { resolveScript } from './script'
15+
import { transformTemplateInMain } from './template'
16+
import { isOnlyTemplateChanged } from './handleHotUpdate'
1017

11-
export function transformSFCEntry(
18+
export function genSfcFacade(
1219
code: string,
1320
filename: string,
1421
options: Options,
@@ -18,6 +25,8 @@ export function transformSFCEntry(
1825
filterCustomBlock: (type: string) => boolean,
1926
pluginContext: TransformPluginContext
2027
) {
28+
// prev descriptor is only set and used for hmr
29+
const prevDescriptor = getPrevDescriptor(filename)
2130
const { descriptor, errors } = parse(code, {
2231
sourceMap: true,
2332
filename,
@@ -42,69 +51,86 @@ export function transformSFCEntry(
4251
// feature information
4352
const hasScoped = descriptor.styles.some((s) => s.scoped)
4453

54+
// script
55+
const { code: scriptCode, map } = genScriptCode(
56+
descriptor,
57+
scopeId,
58+
isProduction,
59+
isServer,
60+
options,
61+
pluginContext
62+
)
63+
64+
// template
4565
const useInlineTemplate =
4666
!options.hmr &&
4767
descriptor.scriptSetup &&
4868
!(descriptor.template && descriptor.template.src)
4969
const hasTemplateImport = descriptor.template && !useInlineTemplate
5070

51-
const templateImport = hasTemplateImport
52-
? genTemplateCode(descriptor, scopeId, isServer)
71+
const templateCode = hasTemplateImport
72+
? genTemplateCode(descriptor, scopeId, options, isServer, pluginContext)
5373
: ''
5474

5575
const renderReplace = hasTemplateImport
5676
? isServer
57-
? `script.ssrRender = ssrRender`
58-
: `script.render = render`
77+
? `_sfc_main.ssrRender = _sfc_ssrRender`
78+
: `_sfc_main.render = _sfc_render`
5979
: ''
6080

61-
const scriptImport = genScriptCode(
62-
descriptor,
63-
scopeId,
64-
isProduction,
65-
isServer,
66-
options,
67-
pluginContext
68-
)
81+
// styles
6982
const stylesCode = genStyleCode(
7083
descriptor,
7184
scopeId,
7285
options.preprocessStyles,
7386
options.vite
7487
)
88+
89+
// custom blocks
7590
const customBlocksCode = getCustomBlock(descriptor, filterCustomBlock)
76-
const output = [
77-
scriptImport,
78-
templateImport,
91+
92+
const output: string[] = [
93+
scriptCode,
94+
templateCode,
7995
stylesCode,
8096
customBlocksCode,
8197
renderReplace,
8298
]
8399
if (hasScoped) {
84-
output.push(`script.__scopeId = ${JSON.stringify(`data-v-${scopeId}`)}`)
100+
output.push(`_sfc_main.__scopeId = ${JSON.stringify(`data-v-${scopeId}`)}`)
85101
}
86102
if (!isProduction) {
87-
output.push(`script.__file = ${JSON.stringify(shortFilePath)}`)
103+
output.push(`_sfc_main.__file = ${JSON.stringify(shortFilePath)}`)
88104
} else if (options.exposeFilename) {
89105
output.push(
90-
`script.__file = ${JSON.stringify(path.basename(shortFilePath))}`
106+
`_sfc_main.__file = ${JSON.stringify(path.basename(shortFilePath))}`
91107
)
92108
}
93-
output.push('export default script')
109+
output.push('export default _sfc_main')
94110

95111
if (options.hmr) {
96-
output.push(`script.__hmrId = ${JSON.stringify(scopeId)}`)
97-
output.push(`__VUE_HMR_RUNTIME__.createRecord(script.__hmrId, script)`)
112+
// check if the template is the only thing that changed
113+
if (prevDescriptor && isOnlyTemplateChanged(prevDescriptor, descriptor)) {
114+
output.push(`export const _rerender_only = true`)
115+
}
116+
output.push(`_sfc_main.__hmrId = ${JSON.stringify(scopeId)}`)
98117
output.push(
99-
`import.meta.hot.accept(({ default: script }) => {
100-
__VUE_HMR_RUNTIME__.reload(script.__hmrId, script)
101-
})`
118+
`__VUE_HMR_RUNTIME__.createRecord(_sfc_main.__hmrId, _sfc_main)`
119+
)
120+
output.push(
121+
`import.meta.hot.accept(({ default: updated, _rerender_only }) => {`,
122+
` if (_rerender_only) {`,
123+
` __VUE_HMR_RUNTIME__.rerender(updated.__hmrId, updated.render)`,
124+
` } else {`,
125+
` __VUE_HMR_RUNTIME__.reload(updated.__hmrId, updated)`,
126+
` }`,
127+
`})`
102128
)
103129
}
104130

105131
return {
106132
code: output.join('\n'),
107-
map: {
133+
map: map || {
108134
mappings: '',
109135
},
110136
}
@@ -113,22 +139,31 @@ export function transformSFCEntry(
113139
function genTemplateCode(
114140
descriptor: SFCDescriptor,
115141
id: string,
116-
isServer: boolean
142+
options: Options,
143+
isServer: boolean,
144+
pluginContext: PluginContext
117145
) {
118146
const renderFnName = isServer ? 'ssrRender' : 'render'
119-
let templateImport = `const ${renderFnName} = () => {}`
120-
let templateRequest
121-
if (descriptor.template) {
122-
const src = descriptor.template.src || descriptor.filename
147+
const template = descriptor.template!
148+
149+
if (!template.lang && !template.src) {
150+
return transformTemplateInMain(
151+
template.content,
152+
descriptor,
153+
id,
154+
options,
155+
pluginContext
156+
)
157+
} else {
158+
const src = template.src || descriptor.filename
123159
const idQuery = `&id=${id}`
124-
const srcQuery = descriptor.template.src ? `&src` : ``
125-
const attrsQuery = attrsToQuery(descriptor.template.attrs, 'js', true)
160+
const srcQuery = template.src ? `&src` : ``
161+
const attrsQuery = attrsToQuery(template.attrs, 'js', true)
126162
const query = `?vue&type=template${idQuery}${srcQuery}${attrsQuery}`
127-
templateRequest = JSON.stringify(src + query)
128-
templateImport = `import { ${renderFnName} } from ${templateRequest}`
163+
return `import { ${renderFnName} as _sfc_${renderFnName} } from ${JSON.stringify(
164+
src + query
165+
)}`
129166
}
130-
131-
return templateImport
132167
}
133168

134169
function genScriptCode(
@@ -139,7 +174,8 @@ function genScriptCode(
139174
options: Options,
140175
pluginContext: TransformPluginContext
141176
) {
142-
let scriptImport = `const script = {}`
177+
let scriptCode = `const _sfc_main = {}`
178+
let map
143179
const script = resolveScript(
144180
descriptor,
145181
scopeId,
@@ -149,15 +185,25 @@ function genScriptCode(
149185
pluginContext
150186
)
151187
if (script) {
152-
const src = script.src || descriptor.filename
153-
const attrsQuery = attrsToQuery(script.attrs, 'js')
154-
const srcQuery = script.src ? `&src` : ``
155-
const query = `?vue&type=script${srcQuery}${attrsQuery}`
156-
const scriptRequest = JSON.stringify(src + query)
157-
scriptImport =
158-
`import script from ${scriptRequest}\n` + `export * from ${scriptRequest}` // support named exports
188+
// js or ts can be directly placed in the main module
189+
if ((!script.lang || script.lang === 'ts') && !script.src) {
190+
scriptCode = rewriteDefault(script.content, `_sfc_main`)
191+
map = script.map
192+
} else {
193+
const src = script.src || descriptor.filename
194+
const attrsQuery = attrsToQuery(script.attrs, 'js')
195+
const srcQuery = script.src ? `&src` : ``
196+
const query = `?vue&type=script${srcQuery}${attrsQuery}`
197+
const scriptRequest = JSON.stringify(src + query)
198+
scriptCode =
199+
`import _sfc_main from ${scriptRequest}\n` +
200+
`export * from ${scriptRequest}` // support named exports
201+
}
202+
}
203+
return {
204+
code: scriptCode,
205+
map,
159206
}
160-
return scriptImport
161207
}
162208

163209
function genStyleCode(
@@ -187,7 +233,7 @@ function genStyleCode(
187233
const styleRequestWithoutModule = src + query + attrsQueryWithoutModule
188234
if (style.module) {
189235
if (!hasCSSModules) {
190-
stylesCode += `\nconst cssModules = script.__cssModules = {}`
236+
stylesCode += `\nconst cssModules = _sfc_main.__cssModules = {}`
191237
hasCSSModules = true
192238
}
193239
stylesCode += genCSSModulesCode(
@@ -220,7 +266,7 @@ function getCustomBlock(
220266
const query = `?vue&type=${block.type}&index=${index}${srcQuery}${attrsQuery}`
221267
const request = JSON.stringify(src + query)
222268
code += `import block${index} from ${request}\n`
223-
code += `if (typeof block${index} === 'function') block${index}(script)\n`
269+
code += `if (typeof block${index} === 'function') block${index}(_sfc_main)\n`
224270
}
225271
})
226272

0 commit comments

Comments
 (0)