]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
feat(compiler): ensure interpolation expressions are wrapped with toString()
authorEvan You <yyx990803@gmail.com>
Mon, 23 Sep 2019 19:36:30 +0000 (15:36 -0400)
committerEvan You <yyx990803@gmail.com>
Mon, 23 Sep 2019 19:36:30 +0000 (15:36 -0400)
23 files changed:
packages/compiler-core/__tests__/__snapshots__/parse.spec.ts.snap
packages/compiler-core/__tests__/codegen.spec.ts
packages/compiler-core/__tests__/parse.spec.ts
packages/compiler-core/__tests__/transforms/expression.spec.ts
packages/compiler-core/src/ast.ts
packages/compiler-core/src/codegen.ts
packages/compiler-core/src/parse.ts
packages/compiler-core/src/runtimeConstants.ts
packages/compiler-core/src/transform.ts
packages/compiler-core/src/transforms/element.ts
packages/compiler-core/src/transforms/expression.ts
packages/compiler-core/src/transforms/optimizeClass.ts [deleted file]
packages/compiler-core/src/transforms/optimizeStyle.ts [deleted file]
packages/compiler-core/src/transforms/vBindClass.ts [new file with mode: 0644]
packages/compiler-core/src/transforms/vBindStyle.ts [new file with mode: 0644]
packages/compiler-core/src/transforms/vFor.ts
packages/compiler-dom/__tests__/parse.spec.ts
packages/reactivity/src/reactive.ts
packages/runtime-core/src/componentProps.ts
packages/runtime-core/src/helpers/toString.ts [new file with mode: 0644]
packages/runtime-core/src/index.ts
packages/shared/src/index.ts
packages/vue/__tests__/index.spec.ts

index 324a3fc329eac3e8f196e6b76ba9d674efb5bc5a..695935fa2926e0362e9a8ab2f1ffa8b360223676 100644 (file)
@@ -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": "'</div>'",
+          "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 {
index d5f17f175e5d10622fdd6cda59fe3a582f4f5446..16c846fc01981da59c12aedb325ff4d598a4af11 100644 (file)
@@ -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,
index 2bf25e6785ecc5429e2641cabb9fad0e6b205bb3..692f89bd8f77071e12c233f8a284f8718624e058 100644 (file)
@@ -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<b',
         isStatic: false,
+        isInterpolation: true,
         loc: {
           start: { offset: 0, line: 1, column: 1 },
           end: { offset: 9, line: 1, column: 10 },
@@ -329,6 +331,7 @@ describe('compiler: parse', () => {
         type: NodeTypes.EXPRESSION,
         content: 'a<b',
         isStatic: false,
+        isInterpolation: true,
         loc: {
           start: { offset: 0, line: 1, column: 1 },
           end: { offset: 9, line: 1, column: 10 },
@@ -339,6 +342,7 @@ describe('compiler: parse', () => {
         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: '"</div>"',
         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 },
index a54c9b23b475e6f1ae9d3f838d5116cc72783aa3..9fe4e49030ad2e7576a9d75d67e56e46ff1aade3 100644 (file)
@@ -2,16 +2,9 @@ import { SourceMapConsumer } from 'source-map'
 import { compile } from '../../src'
 
 test(`should work`, async () => {
-  const { code, map } = compile(
-    `<div v-for="i in foo">
-      {{ ({ a }, b) => a + b + i + c }} {{ i + 'fe' }} {{ i }}
-    </div>
-    <p>{{ i }}</p>
-    `,
-    {
-      prefixIdentifiers: true
-    }
-  )
+  const { code, map } = compile(`<div>{{ foo }} bar</div>`, {
+    prefixIdentifiers: true
+  })
   console.log(code)
   const consumer = await new SourceMapConsumer(map!)
   const pos = consumer.originalPositionFor({
index 1c5e87a59fe513febe75c4edba1fa9fb206aaadc..68d2a288d6c3577da4ece7004cb77b69e61f2d1e 100644 (file)
@@ -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
   }
 }
 
index f62bf69b786e98875d4c8d82b53d9e85547ef995..3b3e54786911014924751451afc2d4716cfd61df 100644 (file)
@@ -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(
index 61df5cb9a342b38903a41d7316598727f6b66d4b..0df48ed5188350ee94c5d9c093803e66ae425cc9 100644 (file)
@@ -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
   }
 }
 
index fa708c2d0a216dd234f7f69c2d5830cdbccea97d..c7a393be52527f2db44bc47aebeb486b14255ffa 100644 (file)
@@ -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`
index f6f51c2cb0c2fadf5853b902570a6f3e13e84448..d5840f9126d33f832cd51d8e7068b18793d1638e 100644 (file)
@@ -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)
index 115d9a0281e5152e7cf4cdeb8472f20909d9e95c..2c6ff252e664f1b4dc1089a04172064094a670e2 100644 (file)
@@ -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)
index 3859b13a7bf3978a556a75cffd8e593be2a9c1a7..08e83b690e38be70e1e6735bff2e8c5189f87634 100644 (file)
@@ -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 (file)
index 70b786d..0000000
+++ /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 (file)
index 70b786d..0000000
+++ /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 (file)
index 0000000..e785858
--- /dev/null
@@ -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 (file)
index 0000000..b3f23cf
--- /dev/null
@@ -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
index d46005e484e26eebbe8a9d2fab32dfa71d17cbd3..fdb9d7a2cd34dc8f29c70803ec57935df13423ff 100644 (file)
@@ -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({
index c4ff1d03c9c4d4adeafbfee6071a69ce737409bb..f9054ab450d0e44dc1c497709a27890abd4684b5 100644 (file)
@@ -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 },
index 650525fedc0d8da041cc97415909c3bb2d6f4d95..653aac4477a28595312758e7d0189e86fb74ffc8 100644 (file)
@@ -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)
   )
 }
index e7969bb8579759ae5aab77ab1b865d70ec44a9d4..2ed15514f177478898c06a88cca523cf188285bc 100644 (file)
@@ -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 (file)
index 0000000..bb3b997
--- /dev/null
@@ -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)
+}
index bb881f3736f3d958a47eab4f97c252f3f1905ed3..4d42f3acf39464ff2caa37bcf95d65d230ed31ea 100644 (file)
@@ -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
index 06afee317977102f40c89f4951b32a95f0b1e9ba..211c72d3d02963c996b40d10c2c330c2f6f228b2 100644 (file)
@@ -30,6 +30,13 @@ export const isString = (val: any): val is string => typeof val === 'string'
 export const isObject = (val: any): val is Record<any, any> =>
   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)
index eea111ed049ed7f73a80c162350e966ac5d1aa05..85b27cf18dc312d5b87619ac035be088dc17b8fc 100644 (file)
@@ -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 = {