From b3b67b8c7d2d32c66c89158ff177f75baf950a2d Mon Sep 17 00:00:00 2001 From: Evan You Date: Mon, 23 Sep 2019 15:36:30 -0400 Subject: [PATCH] feat(compiler): ensure interpolation expressions are wrapped with toString() --- .../__snapshots__/parse.spec.ts.snap | 11 +++++ .../compiler-core/__tests__/codegen.spec.ts | 4 +- .../compiler-core/__tests__/parse.spec.ts | 28 ++++++++--- .../__tests__/transforms/expression.spec.ts | 13 ++--- packages/compiler-core/src/ast.ts | 4 +- packages/compiler-core/src/codegen.ts | 48 ++++++++++++------- packages/compiler-core/src/parse.ts | 5 +- .../compiler-core/src/runtimeConstants.ts | 3 +- packages/compiler-core/src/transform.ts | 10 +++- .../compiler-core/src/transforms/element.ts | 6 +-- .../src/transforms/expression.ts | 7 ++- .../src/transforms/optimizeClass.ts | 1 - .../src/transforms/optimizeStyle.ts | 1 - .../src/transforms/vBindClass.ts | 9 ++++ .../src/transforms/vBindStyle.ts | 14 ++++++ packages/compiler-core/src/transforms/vFor.ts | 2 +- packages/compiler-dom/__tests__/parse.spec.ts | 1 + packages/reactivity/src/reactive.ts | 4 +- packages/runtime-core/src/componentProps.ts | 5 +- packages/runtime-core/src/helpers/toString.ts | 10 ++++ packages/runtime-core/src/index.ts | 2 + packages/shared/src/index.ts | 7 +++ packages/vue/__tests__/index.spec.ts | 3 ++ 23 files changed, 148 insertions(+), 50 deletions(-) delete mode 100644 packages/compiler-core/src/transforms/optimizeClass.ts delete mode 100644 packages/compiler-core/src/transforms/optimizeStyle.ts create mode 100644 packages/compiler-core/src/transforms/vBindClass.ts create mode 100644 packages/compiler-core/src/transforms/vBindStyle.ts create mode 100644 packages/runtime-core/src/helpers/toString.ts 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 = {