Skip to content

Commit

Permalink
fix(hmr): fix hmr error for hoisted children array in v-for
Browse files Browse the repository at this point in the history
fix #6978
close #7114
  • Loading branch information
yyx990803 committed Oct 21, 2023
1 parent 6021d02 commit 7334376
Show file tree
Hide file tree
Showing 6 changed files with 67 additions and 2 deletions.
12 changes: 12 additions & 0 deletions packages/compiler-core/__tests__/transforms/hoistStatic.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -593,5 +593,17 @@ describe('compiler: hoistStatic transform', () => {
expect(root.hoists.length).toBe(2)
expect(generate(root).code).toMatchSnapshot()
})

test('clone hoisted array children in HMR mode', () => {
const root = transformWithHoist(`<div><span class="hi"></span></div>`, {
hmr: true
})
expect(root.hoists.length).toBe(2)
expect(root.codegenNode).toMatchObject({
children: {
content: '[..._hoisted_2]'
}
})
})
})
})
7 changes: 7 additions & 0 deletions packages/compiler-core/src/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,13 @@ export interface TransformOptions
* needed to render inline CSS variables on component root
*/
ssrCssVars?: string
/**
* Whether to compile the template assuming it needs to handle HMR.
* Some edge cases may need to generate different code for HMR to work
* correctly, e.g. #6938, #7138
* @internal
*/
hmr?: boolean
}

export interface CodegenOptions extends SharedTransformCodegenOptions {
Expand Down
2 changes: 2 additions & 0 deletions packages/compiler-core/src/transform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ export function createTransformContext(
filename = '',
prefixIdentifiers = false,
hoistStatic = false,
hmr = false,
cacheHandlers = false,
nodeTransforms = [],
directiveTransforms = {},
Expand All @@ -155,6 +156,7 @@ export function createTransformContext(
selfName: nameMatch && capitalize(camelize(nameMatch[1])),
prefixIdentifiers,
hoistStatic,
hmr,
cacheHandlers,
nodeTransforms,
directiveTransforms,
Expand Down
9 changes: 8 additions & 1 deletion packages/compiler-core/src/transforms/hoistStatic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,9 +140,16 @@ function walk(
node.codegenNode.type === NodeTypes.VNODE_CALL &&
isArray(node.codegenNode.children)
) {
node.codegenNode.children = context.hoist(
const hoisted = context.hoist(
createArrayExpression(node.codegenNode.children)
)
// #6978, #7138, #7114
// a hoisted children array inside v-for can caused HMR errors since
// it might be mutated when mounting the v-for list
if (context.hmr) {
hoisted.content = `[...${hoisted.content}]`
}
node.codegenNode.children = hoisted
}
}

Expand Down
1 change: 1 addition & 0 deletions packages/compiler-sfc/src/compileTemplate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,7 @@ function doCompileTemplate({
slotted,
sourceMap: true,
...compilerOptions,
hmr: !isProd,
nodeTransforms: nodeTransforms.concat(compilerOptions.nodeTransforms || []),
filename,
onError: e => errors.push(e),
Expand Down
38 changes: 37 additions & 1 deletion packages/runtime-core/__tests__/hmr.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ const { createRecord, rerender, reload } = __VUE_HMR_RUNTIME__
registerRuntimeCompiler(compileToFunction)

function compileToFunction(template: string) {
const { code } = baseCompile(template)
const { code } = baseCompile(template, { hoistStatic: true, hmr: true })
const render = new Function('Vue', code)(
runtimeTest
) as InternalRenderFunction
Expand Down Expand Up @@ -567,4 +567,40 @@ describe('hot module replacement', () => {
rerender(parentId, compileToFunction(`<Child>2</Child>`))
expect(serializeInner(root)).toBe(`2`)
})

// #6978, #7138, #7114
test('hoisted children array inside v-for', () => {
const root = nodeOps.createElement('div')
const appId = 'test-app-id'
const App: ComponentOptions = {
__hmrId: appId,
render: compileToFunction(
`<div v-for="item of 2">
<div>1</div>
</div>
<p>2</p>
<p>3</p>`
)
}
createRecord(appId, App)

render(h(App), root)
expect(serializeInner(root)).toBe(
`<div><div>1</div></div><div><div>1</div></div><p>2</p><p>3</p>`
)

// move the <p>3</p> into the <div>1</div>
rerender(
appId,
compileToFunction(
`<div v-for="item of 2">
<div>1<p>3</p></div>
</div>
<p>2</p>`
)
)
expect(serializeInner(root)).toBe(
`<div><div>1<p>3</p></div></div><div><div>1<p>3</p></div></div><p>2</p>`
)
})
})

0 comments on commit 7334376

Please sign in to comment.