diff --git a/packages/compiler-core/__tests__/__snapshots__/parse.spec.ts.snap b/packages/compiler-core/__tests__/__snapshots__/parse.spec.ts.snap
index 324a3fc32..695935fa2 100644
--- a/packages/compiler-core/__tests__/__snapshots__/parse.spec.ts.snap
+++ b/packages/compiler-core/__tests__/__snapshots__/parse.spec.ts.snap
@@ -3835,6 +3835,7 @@ Object {
"children": Array [
Object {
"content": "a < b",
+ "isInterpolation": true,
"isStatic": false,
"loc": Object {
"end": Object {
@@ -6873,6 +6874,7 @@ Object {
"children": Array [
Object {
"content": "''",
+ "isInterpolation": true,
"isStatic": false,
"loc": Object {
"end": Object {
@@ -7223,6 +7225,7 @@ Object {
"children": Array [
Object {
"content": "",
+ "isInterpolation": true,
"isStatic": true,
"loc": Object {
"end": Object {
@@ -7360,6 +7363,7 @@ Object {
Object {
"arg": Object {
"content": "class",
+ "isInterpolation": false,
"isStatic": true,
"loc": Object {
"end": Object {
@@ -7378,6 +7382,7 @@ Object {
},
"exp": Object {
"content": "{ some: condition }",
+ "isInterpolation": false,
"isStatic": false,
"loc": Object {
"end": Object {
@@ -7438,6 +7443,7 @@ Object {
Object {
"arg": Object {
"content": "style",
+ "isInterpolation": false,
"isStatic": true,
"loc": Object {
"end": Object {
@@ -7456,6 +7462,7 @@ Object {
},
"exp": Object {
"content": "{ color: 'red' }",
+ "isInterpolation": false,
"isStatic": false,
"loc": Object {
"end": Object {
@@ -7542,6 +7549,7 @@ Object {
Object {
"arg": Object {
"content": "style",
+ "isInterpolation": false,
"isStatic": true,
"loc": Object {
"end": Object {
@@ -7560,6 +7568,7 @@ Object {
},
"exp": Object {
"content": "{ color: 'red' }",
+ "isInterpolation": false,
"isStatic": false,
"loc": Object {
"end": Object {
@@ -7639,6 +7648,7 @@ Object {
Object {
"arg": Object {
"content": "class",
+ "isInterpolation": false,
"isStatic": true,
"loc": Object {
"end": Object {
@@ -7657,6 +7667,7 @@ Object {
},
"exp": Object {
"content": "{ some: condition }",
+ "isInterpolation": false,
"isStatic": false,
"loc": Object {
"end": Object {
diff --git a/packages/compiler-core/__tests__/codegen.spec.ts b/packages/compiler-core/__tests__/codegen.spec.ts
index d5f17f175..16c846fc0 100644
--- a/packages/compiler-core/__tests__/codegen.spec.ts
+++ b/packages/compiler-core/__tests__/codegen.spec.ts
@@ -13,7 +13,7 @@ describe('compiler: codegen', () => {
with (this) {
return [
"hello ",
- world
+ toString(world)
]
}
}`
@@ -25,7 +25,7 @@ describe('compiler: codegen', () => {
const consumer = await new SourceMapConsumer(map as RawSourceMap)
const pos = consumer.originalPositionFor({
line: 5,
- column: 6
+ column: 15
})
expect(pos).toMatchObject({
line: 1,
diff --git a/packages/compiler-core/__tests__/parse.spec.ts b/packages/compiler-core/__tests__/parse.spec.ts
index 2bf25e678..692f89bd8 100644
--- a/packages/compiler-core/__tests__/parse.spec.ts
+++ b/packages/compiler-core/__tests__/parse.spec.ts
@@ -296,6 +296,7 @@ describe('compiler: parse', () => {
type: NodeTypes.EXPRESSION,
content: 'message',
isStatic: false,
+ isInterpolation: true,
loc: {
start: { offset: 0, line: 1, column: 1 },
end: { offset: 11, line: 1, column: 12 },
@@ -312,6 +313,7 @@ describe('compiler: parse', () => {
type: NodeTypes.EXPRESSION,
content: 'a {
type: NodeTypes.EXPRESSION,
content: 'a {
type: NodeTypes.EXPRESSION,
content: 'c>d',
isStatic: false,
+ isInterpolation: true,
loc: {
start: { offset: 9, line: 1, column: 10 },
end: { offset: 18, line: 1, column: 19 },
@@ -356,6 +360,7 @@ describe('compiler: parse', () => {
type: NodeTypes.EXPRESSION,
content: '""',
isStatic: false,
+ isInterpolation: true,
loc: {
start: { offset: 5, line: 1, column: 6 },
end: { offset: 19, line: 1, column: 20 },
@@ -887,6 +892,7 @@ describe('compiler: parse', () => {
type: NodeTypes.EXPRESSION,
content: 'a',
isStatic: false,
+ isInterpolation: false,
loc: {
start: { offset: 10, line: 1, column: 11 },
end: { offset: 13, line: 1, column: 14 },
@@ -909,9 +915,10 @@ describe('compiler: parse', () => {
type: NodeTypes.DIRECTIVE,
name: 'on',
arg: {
- type: 4,
+ type: NodeTypes.EXPRESSION,
content: 'click',
isStatic: true,
+ isInterpolation: false,
loc: {
source: 'click',
start: {
@@ -980,9 +987,10 @@ describe('compiler: parse', () => {
type: NodeTypes.DIRECTIVE,
name: 'on',
arg: {
- type: 4,
+ type: NodeTypes.EXPRESSION,
content: 'click',
isStatic: true,
+ isInterpolation: false,
loc: {
source: 'click',
start: {
@@ -1015,9 +1023,10 @@ describe('compiler: parse', () => {
type: NodeTypes.DIRECTIVE,
name: 'bind',
arg: {
- type: 4,
+ type: NodeTypes.EXPRESSION,
content: 'a',
isStatic: true,
+ isInterpolation: false,
loc: {
source: 'a',
start: {
@@ -1037,6 +1046,7 @@ describe('compiler: parse', () => {
type: NodeTypes.EXPRESSION,
content: 'b',
isStatic: false,
+ isInterpolation: false,
loc: {
start: { offset: 8, line: 1, column: 9 },
end: { offset: 9, line: 1, column: 10 },
@@ -1059,9 +1069,10 @@ describe('compiler: parse', () => {
type: NodeTypes.DIRECTIVE,
name: 'bind',
arg: {
- type: 4,
+ type: NodeTypes.EXPRESSION,
content: 'a',
isStatic: true,
+ isInterpolation: false,
loc: {
source: 'a',
start: {
@@ -1081,6 +1092,7 @@ describe('compiler: parse', () => {
type: NodeTypes.EXPRESSION,
content: 'b',
isStatic: false,
+ isInterpolation: false,
loc: {
start: { offset: 13, line: 1, column: 14 },
end: { offset: 14, line: 1, column: 15 },
@@ -1103,9 +1115,10 @@ describe('compiler: parse', () => {
type: NodeTypes.DIRECTIVE,
name: 'on',
arg: {
- type: 4,
+ type: NodeTypes.EXPRESSION,
content: 'a',
isStatic: true,
+ isInterpolation: false,
loc: {
source: 'a',
start: {
@@ -1125,6 +1138,7 @@ describe('compiler: parse', () => {
type: NodeTypes.EXPRESSION,
content: 'b',
isStatic: false,
+ isInterpolation: false,
loc: {
start: { offset: 8, line: 1, column: 9 },
end: { offset: 9, line: 1, column: 10 },
@@ -1147,9 +1161,10 @@ describe('compiler: parse', () => {
type: NodeTypes.DIRECTIVE,
name: 'on',
arg: {
- type: 4,
+ type: NodeTypes.EXPRESSION,
content: 'a',
isStatic: true,
+ isInterpolation: false,
loc: {
source: 'a',
start: {
@@ -1169,6 +1184,7 @@ describe('compiler: parse', () => {
type: NodeTypes.EXPRESSION,
content: 'b',
isStatic: false,
+ isInterpolation: false,
loc: {
start: { offset: 14, line: 1, column: 15 },
end: { offset: 15, line: 1, column: 16 },
diff --git a/packages/compiler-core/__tests__/transforms/expression.spec.ts b/packages/compiler-core/__tests__/transforms/expression.spec.ts
index a54c9b23b..9fe4e4903 100644
--- a/packages/compiler-core/__tests__/transforms/expression.spec.ts
+++ b/packages/compiler-core/__tests__/transforms/expression.spec.ts
@@ -2,16 +2,9 @@ import { SourceMapConsumer } from 'source-map'
import { compile } from '../../src'
test(`should work`, async () => {
- const { code, map } = compile(
- `
- {{ ({ a }, b) => a + b + i + c }} {{ i + 'fe' }} {{ i }}
-
- {{ i }}
- `,
- {
- prefixIdentifiers: true
- }
- )
+ const { code, map } = compile(`{{ foo }} bar
`, {
+ prefixIdentifiers: true
+ })
console.log(code)
const consumer = await new SourceMapConsumer(map!)
const pos = consumer.originalPositionFor({
diff --git a/packages/compiler-core/src/ast.ts b/packages/compiler-core/src/ast.ts
index 1c5e87a59..68d2a288d 100644
--- a/packages/compiler-core/src/ast.ts
+++ b/packages/compiler-core/src/ast.ts
@@ -110,6 +110,7 @@ export interface ExpressionNode extends Node {
type: NodeTypes.EXPRESSION
content: string
isStatic: boolean
+ isInterpolation: boolean
children?: (ExpressionNode | string)[]
}
@@ -208,7 +209,8 @@ export function createExpression(
type: NodeTypes.EXPRESSION,
loc,
content,
- isStatic
+ isStatic,
+ isInterpolation: false
}
}
diff --git a/packages/compiler-core/src/codegen.ts b/packages/compiler-core/src/codegen.ts
index f62bf69b7..3b3e54786 100644
--- a/packages/compiler-core/src/codegen.ts
+++ b/packages/compiler-core/src/codegen.ts
@@ -17,7 +17,7 @@ import {
import { SourceMapGenerator, RawSourceMap } from 'source-map'
import { advancePositionWithMutation, assert } from './utils'
import { isString, isArray } from '@vue/shared'
-import { RENDER_LIST } from './runtimeConstants'
+import { RENDER_LIST, TO_STRING } from './runtimeConstants'
type CodegenNode = ChildNode | JSChildNode
@@ -149,7 +149,7 @@ export function generate(
indent()
}
push(`return `)
- genChildren(ast.children, context)
+ genChildren(ast.children, context, true /* asRoot */)
if (!prefixIdentifiers) {
deindent()
push(`}`)
@@ -162,10 +162,23 @@ export function generate(
}
}
-// This will generate a single vnode call if the list has length === 1.
-function genChildren(children: ChildNode[], context: CodegenContext) {
- if (children.length === 1) {
- genNode(children[0], context)
+// This will generate a single vnode call if:
+// - The list has length === 1, AND:
+// - This is a root node, OR:
+// - The only child is a text or expression.
+function genChildren(
+ children: ChildNode[],
+ context: CodegenContext,
+ asRoot: boolean = false
+) {
+ const child = children[0]
+ if (
+ children.length === 1 &&
+ (asRoot ||
+ child.type === NodeTypes.TEXT ||
+ child.type == NodeTypes.EXPRESSION)
+ ) {
+ genNode(child, context)
} else {
genNodeListAsArray(children, context)
}
@@ -192,14 +205,9 @@ function genNodeList(
for (let i = 0; i < nodes.length; i++) {
const node = nodes[i]
if (isString(node)) {
- // plain code string
- // note not adding quotes here because this can be any code,
- // not just plain strings.
push(node)
} else if (isArray(node)) {
- // child VNodes in a h() call
- // not using genChildren here because we want them to always be an array
- genNodeListAsArray(node, context)
+ genChildren(node, context)
} else {
genNode(node, context)
}
@@ -264,11 +272,19 @@ function genText(node: TextNode | ExpressionNode, context: CodegenContext) {
}
function genExpression(node: ExpressionNode, context: CodegenContext) {
- if (node.children) {
- return genCompoundExpression(node, context)
+ const { push } = context
+ const { content, children, isStatic, isInterpolation } = node
+ if (isInterpolation) {
+ push(`${TO_STRING}(`)
+ }
+ if (children) {
+ genCompoundExpression(node, context)
+ } else {
+ push(isStatic ? JSON.stringify(content) : content, node)
+ }
+ if (isInterpolation) {
+ push(`)`)
}
- const text = node.isStatic ? JSON.stringify(node.content) : node.content
- context.push(text, node)
}
function genExpressionAsPropertyKey(
diff --git a/packages/compiler-core/src/parse.ts b/packages/compiler-core/src/parse.ts
index 61df5cb9a..0df48ed51 100644
--- a/packages/compiler-core/src/parse.ts
+++ b/packages/compiler-core/src/parse.ts
@@ -523,6 +523,7 @@ function parseAttribute(
type: NodeTypes.EXPRESSION,
content,
isStatic,
+ isInterpolation: false,
loc
}
}
@@ -540,6 +541,7 @@ function parseAttribute(
type: NodeTypes.EXPRESSION,
content: value.content,
isStatic: false,
+ isInterpolation: false,
loc: value.loc
},
arg,
@@ -626,7 +628,8 @@ function parseInterpolation(
type: NodeTypes.EXPRESSION,
content,
loc: getSelection(context, start),
- isStatic: content === ''
+ isStatic: content === '',
+ isInterpolation: true
}
}
diff --git a/packages/compiler-core/src/runtimeConstants.ts b/packages/compiler-core/src/runtimeConstants.ts
index fa708c2d0..c7a393be5 100644
--- a/packages/compiler-core/src/runtimeConstants.ts
+++ b/packages/compiler-core/src/runtimeConstants.ts
@@ -1,8 +1,9 @@
// Name mapping constants for runtime helpers that need to be imported in
// generated code. Make sure these are correctly exported in the runtime!
-export const CREATE_ELEMENT = `h`
+export const CREATE_VNODE = `createVNode`
export const RESOLVE_COMPONENT = `resolveComponent`
export const RESOLVE_DIRECTIVE = `resolveDirective`
export const APPLY_DIRECTIVES = `applyDirectives`
export const RENDER_LIST = `renderList`
export const CAPITALIZE = `capitalize`
+export const TO_STRING = `toString`
diff --git a/packages/compiler-core/src/transform.ts b/packages/compiler-core/src/transform.ts
index f6f51c2cb..d5840f912 100644
--- a/packages/compiler-core/src/transform.ts
+++ b/packages/compiler-core/src/transform.ts
@@ -10,6 +10,7 @@ import {
} from './ast'
import { isString, isArray } from '@vue/shared'
import { CompilerError, defaultOnError } from './errors'
+import { TO_STRING } from './runtimeConstants'
// There are two types of transforms:
//
@@ -178,8 +179,15 @@ export function traverseNode(node: ChildNode, context: TransformContext) {
}
}
- // further traverse downwards
switch (node.type) {
+ case NodeTypes.EXPRESSION:
+ // no need to traverse, but we need to inject toString helper
+ if (node.isInterpolation) {
+ context.imports.add(TO_STRING)
+ }
+ break
+
+ // for container types, further traverse downwards
case NodeTypes.IF:
for (let i = 0; i < node.branches.length; i++) {
traverseChildren(node.branches[i], context)
diff --git a/packages/compiler-core/src/transforms/element.ts b/packages/compiler-core/src/transforms/element.ts
index 115d9a028..2c6ff252e 100644
--- a/packages/compiler-core/src/transforms/element.ts
+++ b/packages/compiler-core/src/transforms/element.ts
@@ -17,7 +17,7 @@ import {
import { isArray } from '@vue/shared'
import { createCompilerError, ErrorCodes } from '../errors'
import {
- CREATE_ELEMENT,
+ CREATE_VNODE,
APPLY_DIRECTIVES,
RESOLVE_DIRECTIVE,
RESOLVE_COMPONENT
@@ -67,8 +67,8 @@ export const prepareElementForCodegen: NodeTransform = (node, context) => {
}
const { loc } = node
- context.imports.add(CREATE_ELEMENT)
- const vnode = createCallExpression(CREATE_ELEMENT, args, loc)
+ context.imports.add(CREATE_VNODE)
+ const vnode = createCallExpression(CREATE_VNODE, args, loc)
if (runtimeDirectives && runtimeDirectives.length) {
context.imports.add(APPLY_DIRECTIVES)
diff --git a/packages/compiler-core/src/transforms/expression.ts b/packages/compiler-core/src/transforms/expression.ts
index 3859b13a7..08e83b690 100644
--- a/packages/compiler-core/src/transforms/expression.ts
+++ b/packages/compiler-core/src/transforms/expression.ts
@@ -40,6 +40,9 @@ const simpleIdRE = /^[a-zA-Z$_][\w$]*$/
let _parseScript: typeof parseScript
let _walk: typeof walk
+// Important: since this function uses Node.js only dependencies, it should
+// always be used with a leading !__BROWSER__ check so that it can be
+// tree-shaken from the browser build.
export function processExpression(
node: ExpressionNode,
context: TransformContext
@@ -73,7 +76,7 @@ export function processExpression(
if (node.type === 'Identifier') {
if (ids.indexOf(node) === -1) {
ids.push(node)
- if (!knownIds[node.name] && shouldPrependContext(node, parent)) {
+ if (!knownIds[node.name] && shouldPrefix(node, parent)) {
node.name = `_ctx.${node.name}`
}
}
@@ -141,7 +144,7 @@ const globals = new Set(
const isFunction = (node: Node): node is Function =>
/Function(Expression|Declaration)$/.test(node.type)
-function shouldPrependContext(identifier: Identifier, parent: Node) {
+function shouldPrefix(identifier: Identifier, parent: Node) {
if (
// not id of a FunctionDeclaration
!(parent.type === 'FunctionDeclaration' && parent.id === identifier) &&
diff --git a/packages/compiler-core/src/transforms/optimizeClass.ts b/packages/compiler-core/src/transforms/optimizeClass.ts
deleted file mode 100644
index 70b786d12..000000000
--- a/packages/compiler-core/src/transforms/optimizeClass.ts
+++ /dev/null
@@ -1 +0,0 @@
-// TODO
diff --git a/packages/compiler-core/src/transforms/optimizeStyle.ts b/packages/compiler-core/src/transforms/optimizeStyle.ts
deleted file mode 100644
index 70b786d12..000000000
--- a/packages/compiler-core/src/transforms/optimizeStyle.ts
+++ /dev/null
@@ -1 +0,0 @@
-// TODO
diff --git a/packages/compiler-core/src/transforms/vBindClass.ts b/packages/compiler-core/src/transforms/vBindClass.ts
new file mode 100644
index 000000000..e785858d3
--- /dev/null
+++ b/packages/compiler-core/src/transforms/vBindClass.ts
@@ -0,0 +1,9 @@
+// Optimizations
+// - b -> normalize(b)
+// - ['foo', b] -> 'foo' + normalize(b)
+// - { a, b: c } -> (a ? a : '') + (b ? c : '')
+// - ['a', b, { c }] -> 'a' + normalize(b) + (c ? c : '')
+
+// Also merge dynamic and static class into a single prop
+
+// Attach CLASS patchFlag if necessary
diff --git a/packages/compiler-core/src/transforms/vBindStyle.ts b/packages/compiler-core/src/transforms/vBindStyle.ts
new file mode 100644
index 000000000..b3f23cf48
--- /dev/null
+++ b/packages/compiler-core/src/transforms/vBindStyle.ts
@@ -0,0 +1,14 @@
+// Optimizations
+// The compiler pre-compiles static string styles into static objects
+// + detects and hoists inline static objects
+
+// e.g. `style="color: red"` and `:style="{ color: 'red' }"` both get hoisted as
+
+// ``` js
+// const style = { color: 'red' }
+// render() { return e('div', { style }) }
+// ```
+
+// Also nerge dynamic and static style into a single prop
+
+// Attach STYLE patchFlag if necessary
diff --git a/packages/compiler-core/src/transforms/vFor.ts b/packages/compiler-core/src/transforms/vFor.ts
index d46005e48..fdb9d7a2c 100644
--- a/packages/compiler-core/src/transforms/vFor.ts
+++ b/packages/compiler-core/src/transforms/vFor.ts
@@ -21,10 +21,10 @@ export const transformFor = createStructuralDirectiveTransform(
'for',
(node, dir, context) => {
if (dir.exp) {
- context.imports.add(RENDER_LIST)
const parseResult = parseForExpression(dir.exp, context)
if (parseResult) {
+ context.imports.add(RENDER_LIST)
const { source, value, key, index } = parseResult
context.replaceNode({
diff --git a/packages/compiler-dom/__tests__/parse.spec.ts b/packages/compiler-dom/__tests__/parse.spec.ts
index c4ff1d03c..f9054ab45 100644
--- a/packages/compiler-dom/__tests__/parse.spec.ts
+++ b/packages/compiler-dom/__tests__/parse.spec.ts
@@ -115,6 +115,7 @@ describe('DOM parser', () => {
type: NodeTypes.EXPRESSION,
content: 'a < b',
isStatic: false,
+ isInterpolation: true,
loc: {
start: { offset: 5, line: 1, column: 6 },
end: { offset: 19, line: 1, column: 20 },
diff --git a/packages/reactivity/src/reactive.ts b/packages/reactivity/src/reactive.ts
index 650525fed..653aac447 100644
--- a/packages/reactivity/src/reactive.ts
+++ b/packages/reactivity/src/reactive.ts
@@ -1,4 +1,4 @@
-import { isObject } from '@vue/shared'
+import { isObject, toTypeString } from '@vue/shared'
import { mutableHandlers, readonlyHandlers } from './baseHandlers'
import {
@@ -35,7 +35,7 @@ const canObserve = (value: any): boolean => {
return (
!value._isVue &&
!value._isVNode &&
- observableValueRE.test(Object.prototype.toString.call(value)) &&
+ observableValueRE.test(toTypeString(value)) &&
!nonReactiveValues.has(value)
)
}
diff --git a/packages/runtime-core/src/componentProps.ts b/packages/runtime-core/src/componentProps.ts
index e7969bb85..2ed15514f 100644
--- a/packages/runtime-core/src/componentProps.ts
+++ b/packages/runtime-core/src/componentProps.ts
@@ -9,7 +9,8 @@ import {
isArray,
isObject,
isReservedProp,
- hasOwn
+ hasOwn,
+ toTypeString
} from '@vue/shared'
import { warn } from './warning'
import { Data, ComponentInternalInstance } from './component'
@@ -374,7 +375,7 @@ function styleValue(value: any, type: string): string {
}
function toRawType(value: any): string {
- return Object.prototype.toString.call(value).slice(8, -1)
+ return toTypeString(value).slice(8, -1)
}
function isExplicable(type: string): boolean {
diff --git a/packages/runtime-core/src/helpers/toString.ts b/packages/runtime-core/src/helpers/toString.ts
new file mode 100644
index 000000000..bb3b997e0
--- /dev/null
+++ b/packages/runtime-core/src/helpers/toString.ts
@@ -0,0 +1,10 @@
+import { isArray, isPlainObject, objectToString } from '@vue/shared'
+
+// for conversting {{ interpolation }} values to displayed strings.
+export function toString(val: any): string {
+ return val == null
+ ? ''
+ : isArray(val) || (isPlainObject(val) && val.toString === objectToString)
+ ? JSON.stringify(val, null, 2)
+ : String(val)
+}
diff --git a/packages/runtime-core/src/index.ts b/packages/runtime-core/src/index.ts
index bb881f373..4d42f3acf 100644
--- a/packages/runtime-core/src/index.ts
+++ b/packages/runtime-core/src/index.ts
@@ -36,9 +36,11 @@ export {
} from './errorHandling'
// Internal, for compiler generated code
+// should sync with '@vue/compiler-core/src/runtimeConstants.ts'
export { applyDirectives } from './directives'
export { resolveComponent, resolveDirective } from './helpers/resolveAssets'
export { renderList } from './helpers/renderList'
+export { toString } from './helpers/toString'
export { capitalize } from '@vue/shared'
// Internal, for integration with runtime compiler
diff --git a/packages/shared/src/index.ts b/packages/shared/src/index.ts
index 06afee317..211c72d3d 100644
--- a/packages/shared/src/index.ts
+++ b/packages/shared/src/index.ts
@@ -30,6 +30,13 @@ export const isString = (val: any): val is string => typeof val === 'string'
export const isObject = (val: any): val is Record =>
val !== null && typeof val === 'object'
+export const objectToString = Object.prototype.toString
+export const toTypeString = (value: unknown): string =>
+ objectToString.call(value)
+
+export const isPlainObject = (val: any): val is object =>
+ toTypeString(val) === '[object Object]'
+
const vnodeHooksRE = /^vnode/
export const isReservedProp = (key: string): boolean =>
key === 'key' || key === 'ref' || vnodeHooksRE.test(key)
diff --git a/packages/vue/__tests__/index.spec.ts b/packages/vue/__tests__/index.spec.ts
index eea111ed0..85b27cf18 100644
--- a/packages/vue/__tests__/index.spec.ts
+++ b/packages/vue/__tests__/index.spec.ts
@@ -1,5 +1,8 @@
+import * as Vue from '../src'
import { createApp } from '../src'
+;(window as any).Vue = Vue
+
it('should support on-the-fly template compilation', () => {
const container = document.createElement('div')
const App = {