[
  {
    "path": ".flowconfig",
    "content": "[ignore]\n.*/node_modules/.*\n.*/test/.*\n.*/build/.*\n.*/examples/.*\n.*/benchmarks/.*\n\n[include]\n\n[libs]\nflow\n\n[options]\nunsafe.enable_getters_and_setters=true\nmodule.name_mapper='^compiler/\\(.*\\)$' -> '<PROJECT_ROOT>/src/compiler/\\1'\nmodule.name_mapper='^core/\\(.*\\)$' -> '<PROJECT_ROOT>/src/core/\\1'\nmodule.name_mapper='^shared/\\(.*\\)$' -> '<PROJECT_ROOT>/src/shared/\\1'\nmodule.name_mapper='^web/\\(.*\\)$' -> '<PROJECT_ROOT>/src/platforms/web/\\1'\nmodule.name_mapper='^weex/\\(.*\\)$' -> '<PROJECT_ROOT>/src/platforms/weex/\\1'\nmodule.name_mapper='^server/\\(.*\\)$' -> '<PROJECT_ROOT>/src/server/\\1'\nmodule.name_mapper='^entries/\\(.*\\)$' -> '<PROJECT_ROOT>/src/entries/\\1'\nmodule.name_mapper='^sfc/\\(.*\\)$' -> '<PROJECT_ROOT>/src/sfc/\\1'\nsuppress_comment= \\\\(.\\\\|\\n\\\\)*\\\\$flow-disable-line\n"
  },
  {
    "path": ".gitignore",
    "content": ".idea/\nnode_modules\ndist\nexample/\n.DS_Store\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2017 HuangYi\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# Vue.js 技术揭秘\n\n[电子书](https://ustbhuangyi.github.io/vue-analysis/)\n\n目前社区有很多 Vue.js 的源码解析文章，但是质量层次不齐，不够系统和全面，这本电子书的目标是全方位细致深度解析 Vue.js 的实现原理，让同学们可以彻底掌握 Vue.js。目前分析的版本是 Vue.js 的最新版本 Vue.js 2.5.17-beta.0，并且之后会随着版本升级而做相应的更新，充分发挥电子书的优势。\n\n这本电子书是作为 [《Vue.js 源码揭秘》](http://coding.imooc.com/class/228.html)视频课程的辅助教材。电子书是开源的，同学们可以免费阅读，视频是收费的，25+小时纯干货课程，如果有需要的同学可以购买来学习，**但请务必支持正版，请尊重作者的劳动成果**。\n\n## 章节目录\n\n为了把 Vue.js 的源码讲明白，课程设计成由浅入深，分为核心、编译、扩展、生态四个方面去讲，并拆成了八个章节，如下图：\n\n<img src=\"https://ustbhuangyi.github.io/vue-analysis/assets/mind.png\">\n\n**第一章：准备工作**\n\n介绍了 Flow、Vue.js 的源码目录设计、Vue.js 的源码构建方式，以及从入口开始分析了 Vue.js 的初始化过程。\n\n**第二章：数据驱动**\n\n详细讲解了模板数据到 DOM 渲染的过程，从 `new Vue` 开始，分析了 `mount`、`render`、`update`、`patch` 等流程。\n\n**第三章：组件化**\n\n分析了组件化的实现原理，并且分析了组件周边的原理实现，包括合并配置、生命周期、组件注册、异步组件。\n\n**第四章：深入响应式原理**\n\n详细讲解了数据的变化如何驱动视图的变化，分析了响应式对象的创建，依赖收集、派发更新的实现过程，一些特殊情况的处理，并对比了计算属性和侦听属性的实现，最后分析了组件更新的过程。\n\n**第五章：编译**\n\n从编译的入口函数开始，分析了编译的三个核心流程的实现：`parse` -> `optimize` -> `codegen`。\n\n**第六章：扩展**\n\n详细讲解了 `event`、`v-model`、`slot`、`keep-alive`、`transition`、`transition-group` 等常用功能的原理实现，该章节作为一个可扩展章节，未来会分析更多 Vue 提供的特性。\n\n**第七章：Vue-Router**\n\n分析了 Vue-Router 的实现原理，从路由注册开始，分析了路由对象、`matcher`，并深入分析了整个路径切换的实现过程和细节。\n\n**第八章：Vuex**\n\n分析了 Vuex 的实现原理，深入分析了它的初始化过程，常用 API 以及插件部分的实现。\n\n\n"
  },
  {
    "path": "build.sh",
    "content": "#!/usr/bin/env sh\n\nset -e\nnpm run build\n\ncd dist\n\ngit init\ngit add -A\ngit commit -m 'deploy'\n\ngit push -f git@github.com:ustbhuangyi/vue-analysis.git master:gh-pages\n\ncd -\n"
  },
  {
    "path": "docs/.vuepress/config.js",
    "content": "module.exports = {\n  base: '/vue-analysis/',\n  dest: 'dist',\n  title: 'Vue.js 技术揭秘',\n  description: 'Analysis vue.js deeply',\n  head: [\n    ['link', { rel: 'icon', href: `/logo.png` }],\n    ['link', { rel: 'manifest', href: '/manifest.json' }],\n    ['meta', { name: 'theme-color', content: '#3eaf7c' }],\n    ['meta', { name: 'apple-mobile-web-app-capable', content: 'yes' }],\n    ['meta', { name: 'apple-mobile-web-app-status-bar-style', content: 'black' }],\n    ['link', { rel: 'apple-touch-icon', href: `/icons/apple-touch-icon-152x152.png` }],\n    ['link', { rel: 'mask-icon', href: '/icons/safari-pinned-tab.svg', color: '#3eaf7c' }],\n    ['meta', { name: 'msapplication-TileImage', content: '/icons/msapplication-icon-144x144.png' }],\n    ['meta', { name: 'msapplication-TileColor', content: '#000000' }]\n  ],\n  serviceWorker: false,\n  themeConfig: {\n    repo: 'ustbhuangyi/vue-analysis',\n    editLinks: true,\n    docsDir: 'docs',\n    editLinkText: '在 GitHub 上编辑此页',\n    lastUpdated: '上次更新',\n    nav: [\n      {\n        text: '2.x 版本',\n        link: '/v2/prepare/'\n      },\n      {\n        text: '3.x 版本',\n        link: '/v3/new/'\n      },\n      {\n        text: '2.x 源码配套视频',\n        link: 'https://coding.imooc.com/class/228.html'\n      },\n      {\n        text: '3.x 源码解析课程',\n        link: 'https://kaiwu.lagou.com/course/courseInfo.htm?courseId=326#/content'\n      },\n      {\n        text: 'React技术揭秘',\n        link: 'https://react.iamkasong.com/'\n      }\n    ],\n    sidebar: {\n      '/v2/': [\n        {\n          title: '准备工作',\n          collapsable: false,\n          children: [\n            ['prepare/', 'Introduction'],\n            'prepare/flow',\n            'prepare/directory',\n            'prepare/build',\n            'prepare/entrance'\n          ]\n        },\n        {\n          title: '数据驱动',\n          collapsable: false,\n          children: [\n            ['data-driven/', 'Introduction'],\n            'data-driven/new-vue',\n            'data-driven/mounted',\n            'data-driven/render',\n            'data-driven/virtual-dom',\n            'data-driven/create-element',\n            'data-driven/update'\n          ]\n        },\n        {\n          title: '组件化',\n          collapsable: false,\n          children: [\n            ['components/', 'Introduction'],\n            'components/create-component',\n            'components/patch',\n            'components/merge-option',\n            'components/lifecycle',\n            'components/component-register',\n            'components/async-component'\n          ]\n        },\n        {\n          title: '深入响应式原理',\n          collapsable: false,\n          children: [\n            ['reactive/', 'Introduction'],\n            'reactive/reactive-object',\n            'reactive/getters',\n            'reactive/setters',\n            'reactive/next-tick',\n            'reactive/questions',\n            'reactive/computed-watcher',\n            'reactive/component-update',\n            'reactive/props',\n            'reactive/summary'\n          ]\n        },\n        {\n          title: '编译',\n          collapsable: false,\n          children: [\n            ['compile/', 'Introduction'],\n            'compile/entrance',\n            'compile/parse',\n            'compile/optimize',\n            'compile/codegen'\n          ]\n        },\n        {\n          title: '扩展',\n          collapsable: false,\n          children: [\n            ['extend/', 'Introduction'],\n            'extend/event',\n            'extend/v-model',\n            'extend/slot',\n            'extend/keep-alive',\n            'extend/tansition',\n            'extend/tansition-group'\n          ]\n        },\n        {\n          title: 'Vue Router',\n          collapsable: false,\n          children: [\n            ['vue-router/', 'Introduction'],\n            'vue-router/install',\n            'vue-router/router',\n            'vue-router/matcher',\n            'vue-router/transition-to'\n          ]\n        },\n        {\n          title: 'Vuex',\n          collapsable: false,\n          children: [\n            ['vuex/', 'Introduction'],\n            'vuex/init',\n            'vuex/api',\n            'vuex/plugin'\n          ]\n        }\n      ],\n      '/v3/': [\n        {\n          title: '先导篇',\n          collapsable: false,\n          children: [\n            ['guide/', 'Introduction']\n          ]\n        },\n        {\n          title: 'Vue.js 3.0 核心源码解析​',\n          collapsable: false,\n          children: [\n            ['new/', 'Introduction']\n          ]\n        }\n      ]\n    }\n  }\n}\n"
  },
  {
    "path": "docs/.vuepress/public/manifest.json",
    "content": "{\n  \"name\": \"VueAnalysis\",\n  \"short_name\": \"VueAnalysis\",\n  \"icons\": [\n    {\n      \"src\": \"/icons/android-chrome-192x192.png\",\n      \"sizes\": \"192x192\",\n      \"type\": \"image/png\"\n    },\n    {\n      \"src\": \"/icons/android-chrome-512x512.png\",\n      \"sizes\": \"512x512\",\n      \"type\": \"image/png\"\n    }\n  ],\n  \"start_url\": \"/vue-analysis/\",\n  \"display\": \"standalone\",\n  \"background_color\": \"#fff\",\n  \"theme_color\": \"#3eaf7c\"\n}\n"
  },
  {
    "path": "docs/README.md",
    "content": "## 前言\n\n目前社区有很多 Vue.js 的源码解析文章，但是质量层次不齐，不够系统和全面，这本电子书的目标是全方位细致深度解析 Vue.js 的实现原理，让同学们可以彻底掌握 Vue.js。目前分析的版本是 Vue.js 的最新版本 Vue.js 2.5.17-beta.0，并且之后会随着版本升级而做相应的更新，充分发挥电子书的优势。\n\n这本电子书是作为 [《Vue.js 源码揭秘》](http://coding.imooc.com/class/228.html)视频课程的辅助教材。电子书是开源的，同学们可以免费阅读，视频是收费的，25+小时纯干货课程，如果有需要的同学可以购买来学习，**但请务必支持正版，请尊重作者的劳动成果**。\n\n## 章节目录\n\n为了把 Vue.js 的源码讲明白，课程设计成由浅入深，分为核心、编译、扩展、生态四个方面去讲，并拆成了八个章节，如下图：\n\n<img :src=\"$withBase('/assets/mind.png')\">\n\n**第一章：准备工作**\n\n介绍了 Flow、Vue.js 的源码目录设计、Vue.js 的源码构建方式，以及从\b入口开始分析了 Vue.js 的初始化过程。\n\n**第二章：数据驱动**\n\n详细讲解了模板数据到 DOM 渲染的过程，从 `new Vue` 开始，分析了 `mount`、`render`、`update`、`patch` 等流程。\n\n**第三章：组件化**\n\n分析了组件化的实现原理，并且分析了组件周边的原理实现，包括合并配置、生命周期、组件注册、异步组件。\n\n**第四章：深入响应式原理**\n\n详细讲解了数据的变化如何驱动视图的变化，分析了响应式对象的创建，依赖收集、派发更新的实现过程，一些特殊情况的处理，并对比了计算属性和侦听属性的实现，最后分析了组件更新的过程。\n\n**第五章：编译**\n\n从编译的入口函数开始，分析了编译的三个核心流程的实现：`parse` -> `optimize` -> `codegen`。\n\n**第六章：扩展**\n\n详细讲解了 `event`、`v-model`、`slot`、`keep-alive`、`transition`、`transition-group` 等常用功能的原理实现，该章节作为一个可扩展章节，未来会分析更多 Vue 提供的特性。\n\n**第七章：Vue-Router**\n\n分析了 Vue-Router 的实现原理，从路由注册开始，分析了路由对象、`matcher`，并深入分析了整个路径切换的实现过程和细节。\n\n**第八章：Vuex**\n\n分析了 Vuex 的实现原理，深入分析了它的初始化过程，常用 API 以及插件部分的实现。\n\n\n"
  },
  {
    "path": "docs/v2/compile/codegen.md",
    "content": "# codegen\n\n编译的最后一步就是把优化后的 AST 树转换成可执行的代码，这部分内容也比较多，我并不打算把所有的细节都讲了，了解整体流程即可。部分细节我们会在之后的章节配合一个具体 case 去详细讲。\n\n为了方便理解，我们还是用之前的例子：\n\n```html\n<ul :class=\"bindCls\" class=\"list\" v-if=\"isShow\">\n    <li v-for=\"(item,index) in data\" @click=\"clickItem(index)\">{{item}}:{{index}}</li>\n</ul>\n```\n\n它经过编译，执行 `const code = generate(ast, options)`，生成的 `render` 代码串如下：\n\n```js\nwith(this){\n  return (isShow) ?\n    _c('ul', {\n        staticClass: \"list\",\n        class: bindCls\n      },\n      _l((data), function(item, index) {\n        return _c('li', {\n          on: {\n            \"click\": function($event) {\n              clickItem(index)\n            }\n          }\n        },\n        [_v(_s(item) + \":\" + _s(index))])\n      })\n    ) : _e()\n}\n```\n\n这里的 `_c` 函数定义在 `src/core/instance/render.js` 中。\n\n```js\nvm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)\n```\n\n而 `_l`、`_v` 定义在 `src/core/instance/render-helpers/index.js` 中：\n\n```js\nexport function installRenderHelpers (target: any) {\n  target._o = markOnce\n  target._n = toNumber\n  target._s = toString\n  target._l = renderList\n  target._t = renderSlot\n  target._q = looseEqual\n  target._i = looseIndexOf\n  target._m = renderStatic\n  target._f = resolveFilter\n  target._k = checkKeyCodes\n  target._b = bindObjectProps\n  target._v = createTextVNode\n  target._e = createEmptyVNode\n  target._u = resolveScopedSlots\n  target._g = bindObjectListeners\n}\n```\n顾名思义，`_c` 就是执行 `createElement` 去创建 VNode，而 `_l` 对应 `renderList` 渲染列表；`_v` 对应 `createTextVNode` 创建文本 VNode；`_e` 对于 `createEmptyVNode`创建空的 VNode。 \n\n在 `compileToFunctions` 中，会把这个 `render` 代码串转换成函数，它的定义在 `src/compler/to-function.js` 中：\n\n```js\nconst compiled = compile(template, options)\nres.render = createFunction(compiled.render, fnGenErrors)\n\nfunction createFunction (code, errors) {\n  try {\n    return new Function(code)\n  } catch (err) {\n    errors.push({ err, code })\n    return noop\n  }\n}\n```\n\n实际上就是把 `render` 代码串通过 `new Function` 的方式转换成可执行的函数，赋值给 `vm.options.render`，这样当组件通过 `vm._render` 的时候，就会执行这个 `render` 函数。那么接下来我们就重点关注一下这个 `render` 代码串的生成过程。\n\n## generate\n\n```js\nconst code = generate(ast, options)\n```\n\n`generate` 函数的定义在 `src/compiler/codegen/index.js` 中：\n\n```js\nexport function generate (\n  ast: ASTElement | void,\n  options: CompilerOptions\n): CodegenResult {\n  const state = new CodegenState(options)\n  const code = ast ? genElement(ast, state) : '_c(\"div\")'\n  return {\n    render: `with(this){return ${code}}`,\n    staticRenderFns: state.staticRenderFns\n  }\n}\n```\n\n`generate` 函数首先通过 `genElement(ast, state)` 生成 `code`，再把 `code` 用 `with(this){return ${code}}}` 包裹起来。这里的 `state` 是 `CodegenState` 的一个实例，稍后我们在用到它的时候会介绍它。先来看一下 `genElement`：\n\n```js\nexport function genElement (el: ASTElement, state: CodegenState): string {\n  if (el.staticRoot && !el.staticProcessed) {\n    return genStatic(el, state)\n  } else if (el.once && !el.onceProcessed) {\n    return genOnce(el, state)\n  } else if (el.for && !el.forProcessed) {\n    return genFor(el, state)\n  } else if (el.if && !el.ifProcessed) {\n    return genIf(el, state)\n  } else if (el.tag === 'template' && !el.slotTarget) {\n    return genChildren(el, state) || 'void 0'\n  } else if (el.tag === 'slot') {\n    return genSlot(el, state)\n  } else {\n    // component or element\n    let code\n    if (el.component) {\n      code = genComponent(el.component, el, state)\n    } else {\n      const data = el.plain ? undefined : genData(el, state)\n\n      const children = el.inlineTemplate ? null : genChildren(el, state, true)\n      code = `_c('${el.tag}'${\n        data ? `,${data}` : '' // data\n      }${\n        children ? `,${children}` : '' // children\n      })`\n    }\n    // module transforms\n    for (let i = 0; i < state.transforms.length; i++) {\n      code = state.transforms[i](el, code)\n    }\n    return code\n  }\n}\n```\n基本就是判断当前 AST 元素节点的属性执行不同的代码生成函数，在我们的例子中，我们先了解一下 `genFor` 和 `genIf`。\n\n## `genIf`\n\n```js\nexport function genIf (\n  el: any,\n  state: CodegenState,\n  altGen?: Function,\n  altEmpty?: string\n): string {\n  el.ifProcessed = true // avoid recursion\n  return genIfConditions(el.ifConditions.slice(), state, altGen, altEmpty)\n}\n\nfunction genIfConditions (\n  conditions: ASTIfConditions,\n  state: CodegenState,\n  altGen?: Function,\n  altEmpty?: string\n): string {\n  if (!conditions.length) {\n    return altEmpty || '_e()'\n  }\n\n  const condition = conditions.shift()\n  if (condition.exp) {\n    return `(${condition.exp})?${\n      genTernaryExp(condition.block)\n    }:${\n      genIfConditions(conditions, state, altGen, altEmpty)\n    }`\n  } else {\n    return `${genTernaryExp(condition.block)}`\n  }\n\n  // v-if with v-once should generate code like (a)?_m(0):_m(1)\n  function genTernaryExp (el) {\n    return altGen\n      ? altGen(el, state)\n      : el.once\n        ? genOnce(el, state)\n        : genElement(el, state)\n  }\n}\n```\n\n`genIf` 主要是通过执行 `genIfConditions`，它是依次从 `conditions` 获取第一个 `condition`，然后通过对 `condition.exp` 去生成一段三元运算符的代码，`:` 后是递归调用 `genIfConditions`，这样如果有多个 `conditions`，就生成多层三元运算逻辑。这里我们暂时不考虑 `v-once` 的情况，所以\n`genTernaryExp` 最终是调用了 `genElement`。\n\n在我们的例子中，只有一个 `condition`，`exp` 为 `isShow`，因此生成如下伪代码：\n\n```js\nreturn (isShow) ? genElement(el, state) : _e()\n```\n\n## `genFor`\n\n```js\nexport function genFor (\n  el: any,\n  state: CodegenState,\n  altGen?: Function,\n  altHelper?: string\n): string {\n  const exp = el.for\n  const alias = el.alias\n  const iterator1 = el.iterator1 ? `,${el.iterator1}` : ''\n  const iterator2 = el.iterator2 ? `,${el.iterator2}` : ''\n\n  if (process.env.NODE_ENV !== 'production' &&\n    state.maybeComponent(el) &&\n    el.tag !== 'slot' &&\n    el.tag !== 'template' &&\n    !el.key\n  ) {\n    state.warn(\n      `<${el.tag} v-for=\"${alias} in ${exp}\">: component lists rendered with ` +\n      `v-for should have explicit keys. ` +\n      `See https://vuejs.org/guide/list.html#key for more info.`,\n      true /* tip */\n    )\n  }\n\n  el.forProcessed = true // avoid recursion\n  return `${altHelper || '_l'}((${exp}),` +\n    `function(${alias}${iterator1}${iterator2}){` +\n      `return ${(altGen || genElement)(el, state)}` +\n    '})'\n}\n```\n\n`genFor` 的逻辑很简单，首先 AST 元素节点中获取了和 `for` 相关的一些属性，然后返回了一个代码字符串。\n\n在我们的例子中，`exp` 是 `data`，`alias` 是 `item`，`iterator1` ，因此生成如下伪代码：\n\n```js\n_l((data), function(item, index) {\n  return genElememt(el, state)\n})\n```\n\n## `genData` & `genChildren`\n\n再次回顾我们的例子，它的最外层是 `ul`，首先执行 `genIf`，它最终调用了 `genElement(el, state)` 去生成子节点，注意，这里的 `el` 仍然指向的是 `ul` 对应的 AST 节点，但是此时的 `el.ifProcessed` 为 true，所以命中最后一个 else 逻辑：\n\n```js\n// component or element\nlet code\nif (el.component) {\n  code = genComponent(el.component, el, state)\n} else {\n  const data = el.plain ? undefined : genData(el, state)\n\n  const children = el.inlineTemplate ? null : genChildren(el, state, true)\n  code = `_c('${el.tag}'${\n    data ? `,${data}` : '' // data\n  }${\n    children ? `,${children}` : '' // children\n  })`\n}\n// module transforms\nfor (let i = 0; i < state.transforms.length; i++) {\n  code = state.transforms[i](el, code)\n}\nreturn code\n```\n\n这里我们只关注 2 个逻辑，`genData` 和 `genChildren`：\n\n- genData\n\n```js\nexport function genData (el: ASTElement, state: CodegenState): string {\n  let data = '{'\n\n  // directives first.\n  // directives may mutate the el's other properties before they are generated.\n  const dirs = genDirectives(el, state)\n  if (dirs) data += dirs + ','\n\n  // key\n  if (el.key) {\n    data += `key:${el.key},`\n  }\n  // ref\n  if (el.ref) {\n    data += `ref:${el.ref},`\n  }\n  if (el.refInFor) {\n    data += `refInFor:true,`\n  }\n  // pre\n  if (el.pre) {\n    data += `pre:true,`\n  }\n  // record original tag name for components using \"is\" attribute\n  if (el.component) {\n    data += `tag:\"${el.tag}\",`\n  }\n  // module data generation functions\n  for (let i = 0; i < state.dataGenFns.length; i++) {\n    data += state.dataGenFns[i](el)\n  }\n  // attributes\n  if (el.attrs) {\n    data += `attrs:{${genProps(el.attrs)}},`\n  }\n  // DOM props\n  if (el.props) {\n    data += `domProps:{${genProps(el.props)}},`\n  }\n  // event handlers\n  if (el.events) {\n    data += `${genHandlers(el.events, false, state.warn)},`\n  }\n  if (el.nativeEvents) {\n    data += `${genHandlers(el.nativeEvents, true, state.warn)},`\n  }\n  // slot target\n  // only for non-scoped slots\n  if (el.slotTarget && !el.slotScope) {\n    data += `slot:${el.slotTarget},`\n  }\n  // scoped slots\n  if (el.scopedSlots) {\n    data += `${genScopedSlots(el.scopedSlots, state)},`\n  }\n  // component v-model\n  if (el.model) {\n    data += `model:{value:${\n      el.model.value\n    },callback:${\n      el.model.callback\n    },expression:${\n      el.model.expression\n    }},`\n  }\n  // inline-template\n  if (el.inlineTemplate) {\n    const inlineTemplate = genInlineTemplate(el, state)\n    if (inlineTemplate) {\n      data += `${inlineTemplate},`\n    }\n  }\n  data = data.replace(/,$/, '') + '}'\n  // v-bind data wrap\n  if (el.wrapData) {\n    data = el.wrapData(data)\n  }\n  // v-on data wrap\n  if (el.wrapListeners) {\n    data = el.wrapListeners(data)\n  }\n  return data\n}\n```\n\n`genData` 函数就是根据 AST 元素节点的属性构造出一个 `data` 对象字符串，这个在后面创建 VNode 的时候的时候会作为参数传入。\n\n之前我们提到了 `CodegenState` 的实例 `state`，这里有一段关于 `state` 的逻辑：\n\n```js\nfor (let i = 0; i < state.dataGenFns.length; i++) {\n  data += state.dataGenFns[i](el)\n}\n```\n\n`state.dataGenFns` 的初始化在它的构造器中。\n\n```js\nexport class CodegenState {\n  constructor (options: CompilerOptions) {\n    // ...\n    this.dataGenFns = pluckModuleFunction(options.modules, 'genData')\n    // ...\n  }\n}\n```\n\n实际上就是获取所有 `modules` 中的 `genData` 函数，其中，`class module` 和 `style module` 定义了 `genData` 函数。比如定义在 `src/platforms/web/compiler/modules/class.js` 中的 `genData` 方法：\n\n```js\nfunction genData (el: ASTElement): string {\n  let data = ''\n  if (el.staticClass) {\n    data += `staticClass:${el.staticClass},`\n  }\n  if (el.classBinding) {\n    data += `class:${el.classBinding},`\n  }\n  return data\n}\n```\n\n在我们的例子中，`ul` AST 元素节点定义了 `el.staticClass` 和 `el.classBinding`，因此最终生成的 `data` 字符串如下：\n\n```js\n{\n  staticClass: \"list\",\n  class: bindCls\n}\n```\n\n- genChildren\n\n接下来我们再来看一下 `genChildren`，它的定义在 `src/compiler/codegen/index.js` 中：\n\n```js\nexport function genChildren (\n  el: ASTElement,\n  state: CodegenState,\n  checkSkip?: boolean,\n  altGenElement?: Function,\n  altGenNode?: Function\n): string | void {\n  const children = el.children\n  if (children.length) {\n    const el: any = children[0]\n    if (children.length === 1 &&\n      el.for &&\n      el.tag !== 'template' &&\n      el.tag !== 'slot'\n    ) {\n      return (altGenElement || genElement)(el, state)\n    }\n    const normalizationType = checkSkip\n      ? getNormalizationType(children, state.maybeComponent)\n      : 0\n    const gen = altGenNode || genNode\n    return `[${children.map(c => gen(c, state)).join(',')}]${\n      normalizationType ? `,${normalizationType}` : ''\n    }`\n  }\n}\n```\n\n在我们的例子中，`li` AST 元素节点是 `ul` AST 元素节点的 `children` 之一，满足 `(children.length === 1 && el.for && el.tag !== 'template' && el.tag !== 'slot')` 条件，因此通过 `genElement(el, state)` 生成 `li` AST元素节点的代码，也就回到了我们之前调用 `genFor` 生成的代码，把它们拼在一起生成的伪代码如下：\n\n```js\nreturn (isShow) ?\n    _c('ul', {\n        staticClass: \"list\",\n        class: bindCls\n      },\n      _l((data), function(item, index) {\n        return genElememt(el, state)\n      })\n    ) : _e()\n```\n\n在我们的例子中，在执行 `genElememt(el, state)` 的时候，`el` 还是 `li` AST 元素节点，`el.forProcessed` 已为 true，所以会继续执行 `genData` 和 `genChildren` 的逻辑。由于 `el.events` 不为空，在执行 `genData` 的时候，会执行 如下逻辑：\n\n```js\nif (el.events) {\n  data += `${genHandlers(el.events, false, state.warn)},`\n}\n```\n\n`genHandlers` 的定义在 `src/compiler/codegen/events.js` 中：\n\n```js\nexport function genHandlers (\n  events: ASTElementHandlers,\n  isNative: boolean,\n  warn: Function\n): string {\n  let res = isNative ? 'nativeOn:{' : 'on:{'\n  for (const name in events) {\n    res += `\"${name}\":${genHandler(name, events[name])},`\n  }\n  return res.slice(0, -1) + '}'\n}\n```\n`genHandler` 的逻辑就不介绍了，很大部分都是对修饰符 `modifier` 的处理，感兴趣同学可以自己看，对于我们的例子，它最终 `genData` 生成的 `data` 字符串如下：\n\n```js\n{\n  on: {\n    \"click\": function($event) {\n      clickItem(index)\n    }\n  }\n}\n```\n\n`genChildren` 的时候，会执行到如下逻辑：\n\n```js\nexport function genChildren (\n  el: ASTElement,\n  state: CodegenState,\n  checkSkip?: boolean,\n  altGenElement?: Function,\n  altGenNode?: Function\n): string | void {\n  // ...\n  const normalizationType = checkSkip\n    ? getNormalizationType(children, state.maybeComponent)\n    : 0\n  const gen = altGenNode || genNode\n  return `[${children.map(c => gen(c, state)).join(',')}]${\n    normalizationType ? `,${normalizationType}` : ''\n  }`\n}\n\nfunction genNode (node: ASTNode, state: CodegenState): string {\n  if (node.type === 1) {\n    return genElement(node, state)\n  } if (node.type === 3 && node.isComment) {\n    return genComment(node)\n  } else {\n    return genText(node)\n  }\n}\n```\n`genChildren` 的就是遍历 `children`，然后执行 `genNode` 方法，根据不同的 `type` 执行具体的方法。在我们的例子中，`li` AST 元素节点的 `children` 是 type 为 2 的表达式 AST 元素节点，那么会执行到 `genText(node)` 逻辑。\n\n```js\nexport function genText (text: ASTText | ASTExpression): string {\n  return `_v(${text.type === 2\n    ? text.expression\n    : transformSpecialNewlines(JSON.stringify(text.text))\n  })`\n}\n```\n\n因此在我们的例子中，`genChildren` 生成的代码串如下：\n\n```js\n[_v(_s(item) + \":\" + _s(index))]\n```\n\n和之前拼在一起，最终生成的 `code` 如下：\n\n```js\n return (isShow) ?\n    _c('ul', {\n        staticClass: \"list\",\n        class: bindCls\n      },\n      _l((data), function(item, index) {\n        return _c('li', {\n          on: {\n            \"click\": function($event) {\n              clickItem(index)\n            }\n          }\n        },\n        [_v(_s(item) + \":\" + _s(index))])\n      })\n    ) : _e()\n```\n\n## 总结\n\n这一节通过例子配合解析，我们对从 `ast -> code ` 这一步有了一些了解，编译后生成的代码就是在运行时执行的代码。由于 `genCode` 的内容有很多，所以我对大家的建议是没必要把所有的细节都一次性看完，我们应该根据具体一个 case，走完一条主线即可。\n\n在之后的章节我们会对 `slot` 的实现做解析，我们会重新复习编译的章节，针对具体问题做具体分析，有利于我们排除干扰，对编译过程的学习有更深入的理解。\n"
  },
  {
    "path": "docs/v2/compile/entrance.md",
    "content": "# 编译入口\n\n当我们使用 Runtime + Compiler 的 Vue.js，它的入口是 `src/platforms/web/entry-runtime-with-compiler.js`，看一下它对 `$mount` 函数的定义：\n\n```js\nconst mount = Vue.prototype.$mount\nVue.prototype.$mount = function (\n  el?: string | Element,\n  hydrating?: boolean\n): Component {\n  el = el && query(el)\n\n  /* istanbul ignore if */\n  if (el === document.body || el === document.documentElement) {\n    process.env.NODE_ENV !== 'production' && warn(\n      `Do not mount Vue to <html> or <body> - mount to normal elements instead.`\n    )\n    return this\n  }\n\n  const options = this.$options\n  // resolve template/el and convert to render function\n  if (!options.render) {\n    let template = options.template\n    if (template) {\n      if (typeof template === 'string') {\n        if (template.charAt(0) === '#') {\n          template = idToTemplate(template)\n          /* istanbul ignore if */\n          if (process.env.NODE_ENV !== 'production' && !template) {\n            warn(\n              `Template element not found or is empty: ${options.template}`,\n              this\n            )\n          }\n        }\n      } else if (template.nodeType) {\n        template = template.innerHTML\n      } else {\n        if (process.env.NODE_ENV !== 'production') {\n          warn('invalid template option:' + template, this)\n        }\n        return this\n      }\n    } else if (el) {\n      template = getOuterHTML(el)\n    }\n    if (template) {\n      /* istanbul ignore if */\n      if (process.env.NODE_ENV !== 'production' && config.performance && mark) {\n        mark('compile')\n      }\n\n      const { render, staticRenderFns } = compileToFunctions(template, {\n        shouldDecodeNewlines,\n        shouldDecodeNewlinesForHref,\n        delimiters: options.delimiters,\n        comments: options.comments\n      }, this)\n      options.render = render\n      options.staticRenderFns = staticRenderFns\n\n      /* istanbul ignore if */\n      if (process.env.NODE_ENV !== 'production' && config.performance && mark) {\n        mark('compile end')\n        measure(`vue ${this._name} compile`, 'compile', 'compile end')\n      }\n    }\n  }\n  return mount.call(this, el, hydrating)\n}\n```\n\n这段函数逻辑之前分析过，关于编译的入口就是在这里：\n\n```js\nconst { render, staticRenderFns } =  compileToFunctions(template, {\n    shouldDecodeNewlines,\n    shouldDecodeNewlinesForHref,\n    delimiters: options.delimiters,\n    comments: options.comments\n  }, this)\noptions.render = render\noptions.staticRenderFns = staticRenderFns\n```\n\n`compileToFunctions` 方法就是把模板 `template` 编译生成 `render` 以及 `staticRenderFns`，它的定义在 `src/platforms/web/compiler/index.js` 中：\n\n```js\nimport { baseOptions } from './options'\nimport { createCompiler } from 'compiler/index'\n\nconst { compile, compileToFunctions } = createCompiler(baseOptions)\n\nexport { compile, compileToFunctions }\n```\n可以看到 `compileToFunctions` 方法实际上是 `createCompiler` 方法的返回值，该方法接收一个编译配置参数，接下来我们来看一下 `createCompiler` 方法的定义，在 `src/compiler/index.js` 中：\n\n```js\n// `createCompilerCreator` allows creating compilers that use alternative\n// parser/optimizer/codegen, e.g the SSR optimizing compiler.\n// Here we just export a default compiler using the default parts.\nexport const createCompiler = createCompilerCreator(function baseCompile (\n  template: string,\n  options: CompilerOptions\n): CompiledResult {\n  const ast = parse(template.trim(), options)\n  if (options.optimize !== false) {\n    optimize(ast, options)\n  }\n  const code = generate(ast, options)\n  return {\n    ast,\n    render: code.render,\n    staticRenderFns: code.staticRenderFns\n  }\n})\n```\n\n`createCompiler` 方法实际上是通过调用 `createCompilerCreator` 方法返回的，该方法传入的参数是一个函数，真正的编译过程都在这个 `baseCompile` 函数里执行，那么 `createCompilerCreator` 又是什么呢，它的定义在 `src/compiler/create-compiler.js` 中：\n\n```js\nexport function createCompilerCreator (baseCompile: Function): Function {\n  return function createCompiler (baseOptions: CompilerOptions) {\n    function compile (\n      template: string,\n      options?: CompilerOptions\n    ): CompiledResult {\n      const finalOptions = Object.create(baseOptions)\n      const errors = []\n      const tips = []\n      finalOptions.warn = (msg, tip) => {\n        (tip ? tips : errors).push(msg)\n      }\n\n      if (options) {\n        // merge custom modules\n        if (options.modules) {\n          finalOptions.modules =\n            (baseOptions.modules || []).concat(options.modules)\n        }\n        // merge custom directives\n        if (options.directives) {\n          finalOptions.directives = extend(\n            Object.create(baseOptions.directives || null),\n            options.directives\n          )\n        }\n        // copy other options\n        for (const key in options) {\n          if (key !== 'modules' && key !== 'directives') {\n            finalOptions[key] = options[key]\n          }\n        }\n      }\n\n      const compiled = baseCompile(template, finalOptions)\n      if (process.env.NODE_ENV !== 'production') {\n        errors.push.apply(errors, detectErrors(compiled.ast))\n      }\n      compiled.errors = errors\n      compiled.tips = tips\n      return compiled\n    }\n\n    return {\n      compile,\n      compileToFunctions: createCompileToFunctionFn(compile)\n    }\n  }\n}\n```\n\n可以看到该方法返回了一个 `createCompiler` 的函数，它接收一个 `baseOptions` 的参数，返回的是一个对象，包括 `compile` 方法属性和 `compileToFunctions` 属性，这个 `compileToFunctions` 对应的就是 `$mount` 函数调用的 `compileToFunctions` 方法，它是调用 `createCompileToFunctionFn` 方法的返回值，我们接下来看一下 `createCompileToFunctionFn` 方法，它的定义在 `src/compiler/to-function/js` 中：\n\n```js\nexport function createCompileToFunctionFn (compile: Function): Function {\n  const cache = Object.create(null)\n\n  return function compileToFunctions (\n    template: string,\n    options?: CompilerOptions,\n    vm?: Component\n  ): CompiledFunctionResult {\n    options = extend({}, options)\n    const warn = options.warn || baseWarn\n    delete options.warn\n\n    /* istanbul ignore if */\n    if (process.env.NODE_ENV !== 'production') {\n      // detect possible CSP restriction\n      try {\n        new Function('return 1')\n      } catch (e) {\n        if (e.toString().match(/unsafe-eval|CSP/)) {\n          warn(\n            'It seems you are using the standalone build of Vue.js in an ' +\n            'environment with Content Security Policy that prohibits unsafe-eval. ' +\n            'The template compiler cannot work in this environment. Consider ' +\n            'relaxing the policy to allow unsafe-eval or pre-compiling your ' +\n            'templates into render functions.'\n          )\n        }\n      }\n    }\n\n    // check cache\n    const key = options.delimiters\n      ? String(options.delimiters) + template\n      : template\n    if (cache[key]) {\n      return cache[key]\n    }\n\n    // compile\n    const compiled = compile(template, options)\n\n    // check compilation errors/tips\n    if (process.env.NODE_ENV !== 'production') {\n      if (compiled.errors && compiled.errors.length) {\n        warn(\n          `Error compiling template:\\n\\n${template}\\n\\n` +\n          compiled.errors.map(e => `- ${e}`).join('\\n') + '\\n',\n          vm\n        )\n      }\n      if (compiled.tips && compiled.tips.length) {\n        compiled.tips.forEach(msg => tip(msg, vm))\n      }\n    }\n\n    // turn code into functions\n    const res = {}\n    const fnGenErrors = []\n    res.render = createFunction(compiled.render, fnGenErrors)\n    res.staticRenderFns = compiled.staticRenderFns.map(code => {\n      return createFunction(code, fnGenErrors)\n    })\n\n    // check function generation errors.\n    // this should only happen if there is a bug in the compiler itself.\n    // mostly for codegen development use\n    /* istanbul ignore if */\n    if (process.env.NODE_ENV !== 'production') {\n      if ((!compiled.errors || !compiled.errors.length) && fnGenErrors.length) {\n        warn(\n          `Failed to generate render function:\\n\\n` +\n          fnGenErrors.map(({ err, code }) => `${err.toString()} in\\n\\n${code}\\n`).join('\\n'),\n          vm\n        )\n      }\n    }\n\n    return (cache[key] = res)\n  }\n}\n```\n\n至此我们总算找到了 `compileToFunctions` 的最终定义，它接收 3 个参数、编译模板 `template`，编译配置 `options` 和 Vue 实例 `vm`。核心的编译过程就一行代码：\n\n```js\nconst compiled = compile(template, options)\n```\n\n`compile` 函数在执行 `createCompileToFunctionFn` 的时候作为参数传入，它是 `createCompiler` 函数中定义的 `compile` 函数，如下：\n\n```js\nfunction compile (\n  template: string,\n  options?: CompilerOptions\n): CompiledResult {\n  const finalOptions = Object.create(baseOptions)\n  const errors = []\n  const tips = []\n  finalOptions.warn = (msg, tip) => {\n    (tip ? tips : errors).push(msg)\n  }\n\n  if (options) {\n    // merge custom modules\n    if (options.modules) {\n      finalOptions.modules =\n        (baseOptions.modules || []).concat(options.modules)\n    }\n    // merge custom directives\n    if (options.directives) {\n      finalOptions.directives = extend(\n        Object.create(baseOptions.directives || null),\n        options.directives\n      )\n    }\n    // copy other options\n    for (const key in options) {\n      if (key !== 'modules' && key !== 'directives') {\n        finalOptions[key] = options[key]\n      }\n    }\n  }\n\n  const compiled = baseCompile(template, finalOptions)\n  if (process.env.NODE_ENV !== 'production') {\n    errors.push.apply(errors, detectErrors(compiled.ast))\n  }\n  compiled.errors = errors\n  compiled.tips = tips\n  return compiled\n}\n```\n\n`compile` 函数执行的逻辑是先处理配置参数，真正执行编译过程就一行代码：\n\n```js\nconst compiled = baseCompile(template, finalOptions)\n```\n\n`baseCompile` 在执行 `createCompilerCreator` 方法时作为参数传入，如下：\n\n````js\nexport const createCompiler = createCompilerCreator(function baseCompile (\n  template: string,\n  options: CompilerOptions\n): CompiledResult {\n  const ast = parse(template.trim(), options)\n  optimize(ast, options)\n  const code = generate(ast, options)\n  return {\n    ast,\n    render: code.render,\n    staticRenderFns: code.staticRenderFns\n  }\n})\n````\n\n所以编译的入口我们终于找到了，它主要就是执行了如下几个逻辑：\n\n- 解析模板字符串生成 AST\n\n```js\nconst ast = parse(template.trim(), options)\n```\n\n- 优化语法树\n\n```js\noptimize(ast, options)\n```\n\n- 生成代码\n\n```js\nconst code = generate(ast, options)\n```\n\n那么接下来的章节我会带大家去逐步分析这几个过程。\n \n## 总结\n\n编译入口逻辑之所以这么绕，是因为 Vue.js 在不同的平台下都会有编译的过程，因此编译过程中的依赖的配置 `baseOptions` 会有所不同。而编译过程会多次执行，但这同一个平台下每一次的编译过程配置又是相同的，为了不让这些配置在每次编译过程都通过参数传入，Vue.js 利用了函数柯里化的技巧很好的实现了 `baseOptions` 的参数保留。同样，Vue.js 也是利用函数柯里化技巧把基础的编译过程函数抽出来，通过 `createCompilerCreator(baseCompile)` 的方式把真正编译的过程和其它逻辑如对编译配置处理、缓存处理等剥离开，这样的设计还是非常巧妙的。"
  },
  {
    "path": "docs/v2/compile/index.md",
    "content": "# 编译\n\n之前我们分析过模板到真实 DOM 渲染的过程，中间有一个环节是把模板编译成 `render` 函数，这个过程我们把它称作编译。\n\n虽然我们可以直接为组件编写 `render` 函数，但是编写 `template` 模板更加直观，也更符合我们的开发习惯。\n\nVue.js 提供了 2 个版本，一个是 Runtime + Compiler 的，一个是 Runtime only 的，前者是包含编译代码的，可以把编译过程放在运行时做，后者是不包含编译代码的，需要借助 webpack 的 `vue-loader` 事先把模板编译成 `render `函数。\n\n这一章我们就来分析编译的过程，对编译过程的了解会让我们对 Vue 的指令、内置组件等有更好的理解。不过由于编译的过程是一个相对复杂的过程，我们只要求理解整体的流程、输入和输出即可，对于细节我们不必抠太细。有些细节比如对于 `slot` 的处理我们可以在之后去分析插槽实现的时候再详细分析。"
  },
  {
    "path": "docs/v2/compile/optimize.md",
    "content": "# optimize\n\n当我们的模板 `template` 经过 `parse` 过程后，会输出生成 AST 树，那么接下来我们需要对这颗树做优化，`optimize` 的逻辑是远简单于 `parse` 的逻辑，所以理解起来会轻松很多。\n\n为什么要有优化过程，因为我们知道 Vue 是数据驱动，是响应式的，但是我们的模板并不是所有数据都是响应式的，也有很多数据是首次渲染后就永远不会变化的，那么这部分数据生成的 DOM 也不会变化，我们可以在 `patch` 的过程跳过对他们的比对。\n\n来看一下 `optimize` 方法的定义，在 `src/compiler/optimizer.js` 中：\n\n```js\n/**\n * Goal of the optimizer: walk the generated template AST tree\n * and detect sub-trees that are purely static, i.e. parts of\n * the DOM that never needs to change.\n *\n * Once we detect these sub-trees, we can:\n *\n * 1. Hoist them into constants, so that we no longer need to\n *    create fresh nodes for them on each re-render;\n * 2. Completely skip them in the patching process.\n */\nexport function optimize (root: ?ASTElement, options: CompilerOptions) {\n  if (!root) return\n  isStaticKey = genStaticKeysCached(options.staticKeys || '')\n  isPlatformReservedTag = options.isReservedTag || no\n  // first pass: mark all non-static nodes.\n  markStatic(root)\n  // second pass: mark static roots.\n  markStaticRoots(root, false)\n}\n\nfunction genStaticKeys (keys: string): Function {\n  return makeMap(\n    'type,tag,attrsList,attrsMap,plain,parent,children,attrs' +\n    (keys ? ',' + keys : '')\n  )\n}\n```\n\n我们在编译阶段可以把一些 AST 节点优化成静态节点，所以整个 `optimize` 的过程实际上就干 2 件事情，`markStatic(root)` 标记静态节点 ，`markStaticRoots(root, false)` 标记静态根。\n\n## 标记静态节点\n\n```js\nfunction markStatic (node: ASTNode) {\n  node.static = isStatic(node)\n  if (node.type === 1) {\n    // do not make component slot content static. this avoids\n    // 1. components not able to mutate slot nodes\n    // 2. static slot content fails for hot-reloading\n    if (\n      !isPlatformReservedTag(node.tag) &&\n      node.tag !== 'slot' &&\n      node.attrsMap['inline-template'] == null\n    ) {\n      return\n    }\n    for (let i = 0, l = node.children.length; i < l; i++) {\n      const child = node.children[i]\n      markStatic(child)\n      if (!child.static) {\n        node.static = false\n      }\n    }\n    if (node.ifConditions) {\n      for (let i = 1, l = node.ifConditions.length; i < l; i++) {\n        const block = node.ifConditions[i].block\n        markStatic(block)\n        if (!block.static) {\n          node.static = false\n        }\n      }\n    }\n  }\n}\n\nfunction isStatic (node: ASTNode): boolean {\n  if (node.type === 2) { // expression\n    return false\n  }\n  if (node.type === 3) { // text\n    return true\n  }\n  return !!(node.pre || (\n    !node.hasBindings && // no dynamic bindings\n    !node.if && !node.for && // not v-if or v-for or v-else\n    !isBuiltInTag(node.tag) && // not a built-in\n    isPlatformReservedTag(node.tag) && // not a component\n    !isDirectChildOfTemplateFor(node) &&\n    Object.keys(node).every(isStaticKey)\n  ))\n}\n```\n\n首先执行 `node.static = isStatic(node)`\n\n`isStatic` 是对一个 AST 元素节点是否是静态的判断，如果是表达式，就是非静态；如果是纯文本，就是静态；对于一个普通元素，如果有 pre 属性，那么它使用了 `v-pre` 指令，是静态，否则要同时满足以下条件：没有使用 `v-if`、`v-for`，没有使用其它指令（不包括 `v-once`），非内置组件，是平台保留的标签，非带有 `v-for` 的 `template` 标签的直接子节点，节点的所有属性的 `key` 都满足静态 key；这些都满足则这个 AST 节点是一个静态节点。\n\n如果这个节点是一个普通元素，则遍历它的所有 `children`，递归执行 `markStatic`。因为所有的 `elseif` 和 `else` 节点都不在 `children` 中， 如果节点的 `ifConditions` 不为空，则遍历 `ifConditions` 拿到所有条件中的 `block`，也就是它们对应的 AST 节点，递归执行 `markStatic`。在这些递归过程中，一旦子节点有不是 `static` 的情况，则它的父节点的 `static` 均变成 false。\n\n## 标记静态根\n\n```js\nfunction markStaticRoots (node: ASTNode, isInFor: boolean) {\n  if (node.type === 1) {\n    if (node.static || node.once) {\n      node.staticInFor = isInFor\n    }\n    // For a node to qualify as a static root, it should have children that\n    // are not just static text. Otherwise the cost of hoisting out will\n    // outweigh the benefits and it's better off to just always render it fresh.\n    if (node.static && node.children.length && !(\n      node.children.length === 1 &&\n      node.children[0].type === 3\n    )) {\n      node.staticRoot = true\n      return\n    } else {\n      node.staticRoot = false\n    }\n    if (node.children) {\n      for (let i = 0, l = node.children.length; i < l; i++) {\n        markStaticRoots(node.children[i], isInFor || !!node.for)\n      }\n    }\n    if (node.ifConditions) {\n      for (let i = 1, l = node.ifConditions.length; i < l; i++) {\n        markStaticRoots(node.ifConditions[i].block, isInFor)\n      }\n    }\n  }\n}\n```\n\n`markStaticRoots` 第二个参数是 `isInFor`，对于已经是 `static` 的节点或者是 `v-once` 指令的节点，`node.staticInFor = isInFor`。\n接着就是对于 `staticRoot` 的判断逻辑，从注释中我们可以看到，对于有资格成为 `staticRoot` 的节点，除了本身是一个静态节点外，必须满足拥有 `children`，并且 `children` 不能只是一个文本节点，不然的话把它标记成静态根节点的收益就很小了。\n\n接下来和标记静态节点的逻辑一样，遍历 `children` 以及 `ifConditions`，递归执行 `markStaticRoots`。\n\n回归我们之前的例子，经过 `optimize` 后，AST 树变成了如下：\n\n```js\nast = {\n  'type': 1,\n  'tag': 'ul',\n  'attrsList': [],\n  'attrsMap': {\n    ':class': 'bindCls',\n    'class': 'list',\n    'v-if': 'isShow'\n  },\n  'if': 'isShow',\n  'ifConditions': [{\n    'exp': 'isShow',\n    'block': // ul ast element\n  }],\n  'parent': undefined,\n  'plain': false,\n  'staticClass': 'list',\n  'classBinding': 'bindCls',\n  'static': false,\n  'staticRoot': false,\n  'children': [{\n    'type': 1,\n    'tag': 'li',\n    'attrsList': [{\n      'name': '@click',\n      'value': 'clickItem(index)'\n    }],\n    'attrsMap': {\n      '@click': 'clickItem(index)',\n      'v-for': '(item,index) in data'\n     },\n    'parent': // ul ast element\n    'plain': false,\n    'events': {\n      'click': {\n        'value': 'clickItem(index)'\n      }\n    },\n    'hasBindings': true,\n    'for': 'data',\n    'alias': 'item',\n    'iterator1': 'index',\n    'static': false,\n    'staticRoot': false,\n    'children': [\n      'type': 2,\n      'expression': '_s(item)+\":\"+_s(index)'\n      'text': '{{item}}:{{index}}',\n      'tokens': [\n        {'@binding':'item'},\n        ':',\n        {'@binding':'index'}\n      ],\n      'static': false\n    ]\n  }]\n}\n```\n\n我们发现每一个 AST 元素节点都多了 `staic` 属性，并且 `type` 为 1 的普通元素 AST 节点多了 `staticRoot` 属性。\n\n## 总结\n\n那么至此我们分析完了 `optimize` 的过程，就是深度遍历这个 AST 树，去检测它的每一颗子树是不是静态节点，如果是静态节点则它们生成 DOM 永远不需要改变，这对运行时对模板的更新起到极大的优化作用。\n\n我们通过 `optimize` 我们把整个 AST 树中的每一个 AST 元素节点标记了 `static` 和 `staticRoot`，它会影响我们接下来执行代码生成的过程。"
  },
  {
    "path": "docs/v2/compile/parse.md",
    "content": "# parse\n\n编译过程首先就是对模板做解析，生成 AST，它是一种抽象语法树，是对源代码的抽象语法结构的树状表现形式。在很多编译技术中，如 babel 编译 ES6 的代码都会先生成 AST。\n\n这个过程是比较复杂的，它会用到大量正则表达式对字符串解析，如果对正则不是很了解，建议先去补习正则表达式的知识。为了直观地演示 `parse` 的过程，我们先来看一个例子：\n\n```html\n<ul :class=\"bindCls\" class=\"list\" v-if=\"isShow\">\n    <li v-for=\"(item,index) in data\" @click=\"clickItem(index)\">{{item}}:{{index}}</li>\n</ul>\n```\n\n经过 `parse` 过程后，生成的 AST 如下：\n\n```js\nast = {\n  'type': 1,\n  'tag': 'ul',\n  'attrsList': [],\n  'attrsMap': {\n    ':class': 'bindCls',\n    'class': 'list',\n    'v-if': 'isShow'\n  },\n  'if': 'isShow',\n  'ifConditions': [{\n    'exp': 'isShow',\n    'block': // ul ast element\n  }],\n  'parent': undefined,\n  'plain': false,\n  'staticClass': 'list',\n  'classBinding': 'bindCls',\n  'children': [{\n    'type': 1,\n    'tag': 'li',\n    'attrsList': [{\n      'name': '@click',\n      'value': 'clickItem(index)'\n    }],\n    'attrsMap': {\n      '@click': 'clickItem(index)',\n      'v-for': '(item,index) in data'\n     },\n    'parent': // ul ast element\n    'plain': false,\n    'events': {\n      'click': {\n        'value': 'clickItem(index)'\n      }\n    },\n    'hasBindings': true,\n    'for': 'data',\n    'alias': 'item',\n    'iterator1': 'index',\n    'children': [\n      'type': 2,\n      'expression': '_s(item)+\":\"+_s(index)'\n      'text': '{{item}}:{{index}}',\n      'tokens': [\n        {'@binding':'item'},\n        ':',\n        {'@binding':'index'}\n      ]\n    ]\n  }]\n}\n```\n\n可以看到，生成的 AST 是一个树状结构，每一个节点都是一个 `ast element`，除了它自身的一些属性，还维护了它的父子关系，如 `parent` 指向它的父节点，`children` 指向它的所有子节点。先对 AST 有一些直观的印象，那么接下来我们来分析一下这个 AST 是如何得到的。\n\n## 整体流程\n\n首先来看一下 `parse` 的定义，在 `src/compiler/parser/index.js` 中：\n\n```js\nexport function parse (\n  template: string,\n  options: CompilerOptions\n): ASTElement | void {\n  getFnsAndConfigFromOptions(options)\n\n  parseHTML(template, {\n    // options ...\n    start (tag, attrs, unary) {\n      let element = createASTElement(tag, attrs)\n      processElement(element)\n      treeManagement()\n    },\n\n    end () {\n      treeManagement()\n      closeElement()\n    },\n\n    chars (text: string) {\n      handleText()\n      createChildrenASTOfText()\n    },\n    comment (text: string) {\n      createChildrenASTOfComment()\n    }\n  })\n  return astRootElement\n}\n```\n\n`parse` 函数的代码很长，贴一遍对同学的理解没有好处，我先把它拆成伪代码的形式，方便同学们对整体流程先有一个大致的了解。接下来我们就来分解分析每段伪代码的作用。\n\n### 从 options 中获取方法和配置\n\n对应伪代码：\n\n```js\ngetFnsAndConfigFromOptions(options)\n```\n\n`parse` 函数的输入是 `template` 和 `options`，输出是 AST 的根节点。`template` 就是我们的模板字符串，而 `options` 实际上是和平台相关的一些配置，它的定义在 `src/platforms/web/compiler/options` 中：\n\n```js\nimport {\n  isPreTag,\n  mustUseProp,\n  isReservedTag,\n  getTagNamespace\n} from '../util/index'\n\nimport modules from './modules/index'\nimport directives from './directives/index'\nimport { genStaticKeys } from 'shared/util'\nimport { isUnaryTag, canBeLeftOpenTag } from './util'\n\nexport const baseOptions: CompilerOptions = {\n  expectHTML: true,\n  modules,\n  directives,\n  isPreTag,\n  isUnaryTag,\n  mustUseProp,\n  canBeLeftOpenTag,\n  isReservedTag,\n  getTagNamespace,\n  staticKeys: genStaticKeys(modules)\n}\n```\n这些属性和方法之所以放到 `platforms` 目录下是因为它们在不同的平台（web 和 weex）的实现是不同的。\n\n我们用伪代码 `getFnsAndConfigFromOptions` 表示了这一过程，它的实际代码如下：\n\n```js\nwarn = options.warn || baseWarn\n\nplatformIsPreTag = options.isPreTag || no\nplatformMustUseProp = options.mustUseProp || no\nplatformGetTagNamespace = options.getTagNamespace || no\n\ntransforms = pluckModuleFunction(options.modules, 'transformNode')\npreTransforms = pluckModuleFunction(options.modules, 'preTransformNode')\npostTransforms = pluckModuleFunction(options.modules, 'postTransformNode')\n\ndelimiters = options.delimiters\n```\n\n这些方法和配置都是后续解析时候需要的，可以不用去管它们的具体作用，我们先往后看。\n\n### 解析 HTML 模板\n\n对应伪代码：\n\n```js\nparseHTML(template, options)\n```\n\n对于 `template` 模板的解析主要是通过 `parseHTML` 函数，它的定义在 `src/compiler/parser/html-parser` 中：\n\n```js\nexport function parseHTML (html, options) {\n  let lastTag\n  while (html) {\n    if (!lastTag || !isPlainTextElement(lastTag)){\n      let textEnd = html.indexOf('<')\n      if (textEnd === 0) {\n         if(matchComment) {\n           advance(commentLength)\n           continue\n         }\n         if(matchDoctype) {\n           advance(doctypeLength)\n           continue\n         }\n         if(matchEndTag) {\n           advance(endTagLength)\n           parseEndTag()\n           continue\n         }\n         if(matchStartTag) {\n           parseStartTag()\n           handleStartTag()\n           continue\n         }\n      }\n      handleText()\n      advance(textLength)\n    } else {\n       handlePlainTextElement()\n       parseEndTag()\n    }\n  }\n}\n```\n由于 `parseHTML` 的逻辑也非常复杂，因此我也用了伪代码的方式表达，整体来说它的逻辑就是循环解析 `template` ，用正则做各种匹配，对于不同情况分别进行不同的处理，直到整个 template 被解析完毕。\n在匹配的过程中会利用 `advance` 函数不断前进整个模板字符串，直到字符串末尾。\n\n```js\nfunction advance (n) {\n  index += n\n  html = html.substring(n)\n}\n```\n\n为了更加直观地说明 `advance` 的作用，可以通过一副图表示：\n\n<img :src=\"$withBase('/assets/advance-1.png')\">\n\n调用 `advance` 函数：\n\n```js\nadvance(4)\n```\n\n得到结果：\n\n\n<img :src=\"$withBase('/assets/advance-2.png')\">\n\n\n匹配的过程中主要利用了正则表达式，如下：\n\n```js\nconst attribute = /^\\s*([^\\s\"'<>\\/=]+)(?:\\s*(=)\\s*(?:\"([^\"]*)\"+|'([^']*)'+|([^\\s\"'=<>`]+)))?/\nconst ncname = '[a-zA-Z_][\\\\w\\\\-\\\\.]*'\nconst qnameCapture = `((?:${ncname}\\\\:)?${ncname})`\nconst startTagOpen = new RegExp(`^<${qnameCapture}`)\nconst startTagClose = /^\\s*(\\/?)>/\nconst endTag = new RegExp(`^<\\\\/${qnameCapture}[^>]*>`)\nconst doctype = /^<!DOCTYPE [^>]+>/i\nconst comment = /^<!\\--/\nconst conditionalComment = /^<!\\[/\n```\n通过这些正则表达式，我们可以匹配注释节点、文档类型节点、开始闭合标签等。\n\n- 注释节点、文档类型节点\n\n对于注释节点和文档类型节点的匹配，如果匹配到我们仅仅做的是做前进即可。\n\n```js\nif (comment.test(html)) {\n  const commentEnd = html.indexOf('-->')\n\n  if (commentEnd >= 0) {\n    if (options.shouldKeepComment) {\n      options.comment(html.substring(4, commentEnd))\n    }\n    advance(commentEnd + 3)\n    continue\n  }\n}\n\nif (conditionalComment.test(html)) {\n  const conditionalEnd = html.indexOf(']>')\n\n  if (conditionalEnd >= 0) {\n    advance(conditionalEnd + 2)\n    continue\n  }\n}\n\nconst doctypeMatch = html.match(doctype)\nif (doctypeMatch) {\n  advance(doctypeMatch[0].length)\n  continue\n}\n```\n\n对于注释和条件注释节点，前进至它们的末尾位置；对于文档类型节点，则前进它自身长度的距离。\n\n- 开始标签\n\n```js\nconst startTagMatch = parseStartTag()\nif (startTagMatch) {\n  handleStartTag(startTagMatch)\n  if (shouldIgnoreFirstNewline(lastTag, html)) {\n    advance(1)\n  }\n  continue\n}\n```\n\n首先通过 `parseStartTag` 解析开始标签：\n\n```js\nfunction parseStartTag () {\n  const start = html.match(startTagOpen)\n  if (start) {\n    const match = {\n      tagName: start[1],\n      attrs: [],\n      start: index\n    }\n    advance(start[0].length)\n    let end, attr\n    while (!(end = html.match(startTagClose)) && (attr = html.match(attribute))) {\n      advance(attr[0].length)\n      match.attrs.push(attr)\n    }\n    if (end) {\n      match.unarySlash = end[1]\n      advance(end[0].length)\n      match.end = index\n      return match\n    }\n  }\n}\n```\n对于开始标签，除了标签名之外，还有一些标签相关的属性。函数先通过正则表达式 `startTagOpen` 匹配到开始标签，然后定义了 `match` 对象，接着循环去匹配开始标签中的属性并添加到 `match.attrs` 中，直到匹配的开始标签的闭合符结束。如果匹配到闭合符，则获取一元斜线符，前进到闭合符尾，并把当前索引赋值给 `match.end`。\n\n`parseStartTag` 对开始标签解析拿到 `match` 后，紧接着会执行 `handleStartTag` 对 `match` 做处理：\n\n```js\nfunction handleStartTag (match) {\n  const tagName = match.tagName\n  const unarySlash = match.unarySlash\n  \n  if (expectHTML) {\n    if (lastTag === 'p' && isNonPhrasingTag(tagName)) {\n      parseEndTag(lastTag)\n    }\n    if (canBeLeftOpenTag(tagName) && lastTag === tagName) {\n      parseEndTag(tagName)\n    }\n  }\n  \n  const unary = isUnaryTag(tagName) || !!unarySlash\n  \n  const l = match.attrs.length\n  const attrs = new Array(l)\n  for (let i = 0; i < l; i++) {\n    const args = match.attrs[i]\n    if (IS_REGEX_CAPTURING_BROKEN && args[0].indexOf('\"\"') === -1) {\n      if (args[3] === '') { delete args[3] }\n      if (args[4] === '') { delete args[4] }\n      if (args[5] === '') { delete args[5] }\n    }\n    const value = args[3] || args[4] || args[5] || ''\n    const shouldDecodeNewlines = tagName === 'a' && args[1] === 'href'\n      ? options.shouldDecodeNewlinesForHref\n      : options.shouldDecodeNewlines\n    attrs[i] = {\n      name: args[1],\n      value: decodeAttr(value, shouldDecodeNewlines)\n    }\n  }\n  \n  if (!unary) {\n    stack.push({ tag: tagName, lowerCasedTag: tagName.toLowerCase(), attrs: attrs })\n    lastTag = tagName\n  }\n  \n  if (options.start) {\n    options.start(tagName, attrs, unary, match.start, match.end)\n  }\n}\n```\n\n`handleStartTag` 的核心逻辑很简单，先判断开始标签是否是一元标签，类似 `<img>、<br/>` 这样，接着对 `match.attrs` 遍历并做了一些处理，最后判断如果非一元标签，则往 `stack` 里 push 一个对象，并且把 `tagName` 赋值给 `lastTag`。至于 `stack` 的作用，稍后我会介绍。\n\n最后调用了 `options.start` 回调函数，并传入一些参数，这个回调函数的作用稍后我会详细介绍。\n \n- 闭合标签\n\n```js\nconst endTagMatch = html.match(endTag)\nif (endTagMatch) {\n  const curIndex = index\n  advance(endTagMatch[0].length)\n  parseEndTag(endTagMatch[1], curIndex, index)\n  continue\n}\n```\n\n先通过正则 `endTag` 匹配到闭合标签，然后前进到闭合标签末尾，然后执行 `parseEndTag` 方法对闭合标签做解析。\n\n```js\n\nfunction parseEndTag (tagName, start, end) {\n  let pos, lowerCasedTagName\n  if (start == null) start = index\n  if (end == null) end = index\n  \n  if (tagName) {\n    lowerCasedTagName = tagName.toLowerCase()\n  }\n  \n  if (tagName) {\n    for (pos = stack.length - 1; pos >= 0; pos--) {\n      if (stack[pos].lowerCasedTag === lowerCasedTagName) {\n        break\n      }\n    }\n  } else {\n    pos = 0\n  }\n  \n  if (pos >= 0) {\n    for (let i = stack.length - 1; i >= pos; i--) {\n      if (process.env.NODE_ENV !== 'production' &&\n        (i > pos || !tagName) &&\n        options.warn\n      ) {\n        options.warn(\n          `tag <${stack[i].tag}> has no matching end tag.`\n        )\n      }\n      if (options.end) {\n        options.end(stack[i].tag, start, end)\n      }\n    }\n    stack.length = pos\n    lastTag = pos && stack[pos - 1].tag\n  } else if (lowerCasedTagName === 'br') {\n    if (options.start) {\n      options.start(tagName, [], true, start, end)\n    }\n  } else if (lowerCasedTagName === 'p') {\n    if (options.start) {\n      options.start(tagName, [], false, start, end)\n    }\n    if (options.end) {\n      options.end(tagName, start, end)\n    }\n  }\n}\n```\n\n`parseEndTag` 的核心逻辑很简单，在介绍之前我们回顾一下在执行 `handleStartTag` 的时候，对于非一元标签（有 endTag）我们都把它构造成一个对象压入到 `stack` 中，如图所示：\n\n<img :src=\"$withBase('/assets/stack.png')\">\n\n那么对于闭合标签的解析，就是倒序 `stack`，找到第一个和当前 `endTag` 匹配的元素。如果是正常的标签匹配，那么 `stack` 的最后一个元素应该和当前的 `endTag` 匹配，但是考虑到如下错误情况：\n\n```html\n<div><span></div>\n```\n这个时候当 `endTag` 为 `</div>` 的时候，从 `stack` 尾部找到的标签是 `<span>`，就不能匹配，因此这种情况会报警告。匹配后把栈到 `pos` 位置的都弹出，并从 `stack` 尾部拿到 `lastTag`。\n\n最后调用了 `options.end` 回调函数，并传入一些参数，这个回调函数的作用稍后我会详细介绍。\n\n- 文本\n\n```js\nlet text, rest, next\nif (textEnd >= 0) {\n  rest = html.slice(textEnd)\n  while (\n    !endTag.test(rest) &&\n    !startTagOpen.test(rest) &&\n    !comment.test(rest) &&\n    !conditionalComment.test(rest)\n  ) {\n    next = rest.indexOf('<', 1)\n    if (next < 0) break\n    textEnd += next\n    rest = html.slice(textEnd)\n  }\n  text = html.substring(0, textEnd)\n  advance(textEnd)\n}\n\nif (textEnd < 0) {\n  text = html\n  html = ''\n}\n\nif (options.chars && text) {\n  options.chars(text)\n}\n```\n\n接下来判断 `textEnd` 是否大于等于 0 的，满足则说明到从当前位置到 `textEnd` 位置都是文本，并且如果 `<` 是纯文本中的字符，就继续找到真正的文本结束的位置，然后前进到结束的位置。\n\n再继续判断 `textEnd` 小于 0 的情况，则说明整个 `template` 解析完毕了，把剩余的 `html` 都赋值给了 `text`。\n\n最后调用了 `options.chars` 回调函数，并传 `text` 参数，这个回调函数的作用稍后我会详细介绍。\n\n因此，在循环解析整个 `template` 的过程中，会根据不同的情况，去执行不同的回调函数，下面我们来看看这些回调函数的作用。\n\n### 处理开始标签 \n\n对应伪代码：\n\n```js\nstart (tag, attrs, unary) {\n  let element = createASTElement(tag, attrs)\n  processElement(element)\n  treeManagement()\n}\n```\n\n当解析到开始标签的时候，最后会执行 `start` 回调函数，函数主要就做 3 件事情，创建 AST 元素，处理 AST 元素，AST 树管理。下面我们来分别来看这几个过程。\n\n- 创建 AST 元素\n\n```js\n// check namespace.\n// inherit parent ns if there is one\nconst ns = (currentParent && currentParent.ns) || platformGetTagNamespace(tag)\n\n// handle IE svg bug\n/* istanbul ignore if */\nif (isIE && ns === 'svg') {\n  attrs = guardIESVGBug(attrs)\n}\n\nlet element: ASTElement = createASTElement(tag, attrs, currentParent)\nif (ns) {\n  element.ns = ns\n}\n\nexport function createASTElement (\n  tag: string,\n  attrs: Array<Attr>,\n  parent: ASTElement | void\n): ASTElement {\n  return {\n    type: 1,\n    tag,\n    attrsList: attrs,\n    attrsMap: makeAttrsMap(attrs),\n    parent,\n    children: []\n  }\n}\n```\n\n通过 `createASTElement` 方法去创建一个 AST 元素，并添加了 namespace。可以看到，每一个 AST 元素就是一个普通的 JavaScript 对象，其中，`type` 表示 AST 元素类型，`tag` 表示标签名，`attrsList` 表示属性列表，`attrsMap` 表示属性映射表，`parent` 表示父的 AST 元素，`children` 表示子 AST 元素集合。\n\n- 处理 AST 元素\n\n```js\nif (isForbiddenTag(element) && !isServerRendering()) {\n  element.forbidden = true\n  process.env.NODE_ENV !== 'production' && warn(\n    'Templates should only be responsible for mapping the state to the ' +\n    'UI. Avoid placing tags with side-effects in your templates, such as ' +\n    `<${tag}>` + ', as they will not be parsed.'\n  )\n}\n\n// apply pre-transforms\nfor (let i = 0; i < preTransforms.length; i++) {\n  element = preTransforms[i](element, options) || element\n}\n\nif (!inVPre) {\n  processPre(element)\n  if (element.pre) {\n    inVPre = true\n  }\n}\nif (platformIsPreTag(element.tag)) {\n  inPre = true\n}\nif (inVPre) {\n  processRawAttrs(element)\n} else if (!element.processed) {\n  // structural directives\n  processFor(element)\n  processIf(element)\n  processOnce(element)\n  // element-scope stuff\n  processElement(element, options)\n}\n```\n首先是对模块 `preTransforms` 的调用，其实所有模块的 `preTransforms`、 `transforms` 和 `postTransforms` 的定义都在 `src/platforms/web/compiler/modules` 目录中，这部分我们暂时不会介绍，之后会结合具体的例子说。接着判断 `element` 是否包含各种指令通过 `processXXX` 做相应的处理，处理的结果就是扩展 AST 元素的属性。这里我并不会一一介绍所有的指令处理，而是结合我们当前的例子，我们来看一下 `processFor` 和 `processIf`：\n\n```js\nexport function processFor (el: ASTElement) {\n  let exp\n  if ((exp = getAndRemoveAttr(el, 'v-for'))) {\n    const res = parseFor(exp)\n    if (res) {\n      extend(el, res)\n    } else if (process.env.NODE_ENV !== 'production') {\n      warn(\n        `Invalid v-for expression: ${exp}`\n      )\n    }\n  }\n}\n\nexport const forAliasRE = /(.*?)\\s+(?:in|of)\\s+(.*)/\nexport const forIteratorRE = /,([^,\\}\\]]*)(?:,([^,\\}\\]]*))?$/\nconst stripParensRE = /^\\(|\\)$/g\nexport function parseFor (exp: string): ?ForParseResult {\n  const inMatch = exp.match(forAliasRE)\n  if (!inMatch) return\n  const res = {}\n  res.for = inMatch[2].trim()\n  const alias = inMatch[1].trim().replace(stripParensRE, '')\n  const iteratorMatch = alias.match(forIteratorRE)\n  if (iteratorMatch) {\n    res.alias = alias.replace(forIteratorRE, '')\n    res.iterator1 = iteratorMatch[1].trim()\n    if (iteratorMatch[2]) {\n      res.iterator2 = iteratorMatch[2].trim()\n    }\n  } else {\n    res.alias = alias\n  }\n  return res\n}\n```\n\n`processFor` 就是从元素中拿到 `v-for` 指令的内容，然后分别解析出 `for`、`alias`、`iterator1`、`iterator2` 等属性的值添加到 AST 的元素上。就我们的示例 `v-for=\"(item,index) in data\"` 而言，解析出的的 `for` 是 `data`，`alias` 是 `item`，`iterator1` 是 `index`，没有 `iterator2`。\n\n```js\nfunction processIf (el) {\n  const exp = getAndRemoveAttr(el, 'v-if')\n  if (exp) {\n    el.if = exp\n    addIfCondition(el, {\n      exp: exp,\n      block: el\n    })\n  } else {\n    if (getAndRemoveAttr(el, 'v-else') != null) {\n      el.else = true\n    }\n    const elseif = getAndRemoveAttr(el, 'v-else-if')\n    if (elseif) {\n      el.elseif = elseif\n    }\n  }\n}\nexport function addIfCondition (el: ASTElement, condition: ASTIfCondition) {\n  if (!el.ifConditions) {\n    el.ifConditions = []\n  }\n  el.ifConditions.push(condition)\n}\n```\n\n`processIf` 就是从元素中拿 `v-if` 指令的内容，如果拿到则给 AST 元素添加 `if` 属性和 `ifConditions` 属性；否则尝试拿 `v-else` 指令及 `v-else-if` 指令的内容，如果拿到则给 AST 元素分别添加 `else` 和 `elseif` 属性。 \n\n- AST 树管理\n\n我们在处理开始标签的时候为每一个标签创建了一个 AST 元素，在不断解析模板创建 AST 元素的时候，我们也要为它们建立父子关系，就像 DOM 元素的父子关系那样。\n\nAST 树管理相关代码如下：\n\n```js\nfunction checkRootConstraints (el) {\n  if (process.env.NODE_ENV !== 'production') {\n    if (el.tag === 'slot' || el.tag === 'template') {\n      warnOnce(\n        `Cannot use <${el.tag}> as component root element because it may ` +\n        'contain multiple nodes.'\n      )\n    }\n    if (el.attrsMap.hasOwnProperty('v-for')) {\n      warnOnce(\n        'Cannot use v-for on stateful component root element because ' +\n        'it renders multiple elements.'\n      )\n    }\n  }\n}\n\n\n// tree management\nif (!root) {\n  root = element\n  checkRootConstraints(root)\n} else if (!stack.length) {\n  // allow root elements with v-if, v-else-if and v-else\n  if (root.if && (element.elseif || element.else)) {\n    checkRootConstraints(element)\n    addIfCondition(root, {\n      exp: element.elseif,\n      block: element\n    })\n  } else if (process.env.NODE_ENV !== 'production') {\n    warnOnce(\n      `Component template should contain exactly one root element. ` +\n      `If you are using v-if on multiple elements, ` +\n      `use v-else-if to chain them instead.`\n    )\n  }\n}\nif (currentParent && !element.forbidden) {\n  if (element.elseif || element.else) {\n    processIfConditions(element, currentParent)\n  } else if (element.slotScope) { // scoped slot\n    currentParent.plain = false\n    const name = element.slotTarget || '\"default\"'\n    ;(currentParent.scopedSlots || (currentParent.scopedSlots = {}))[name] = element\n  } else {\n    currentParent.children.push(element)\n    element.parent = currentParent\n  }\n}\nif (!unary) {\n  currentParent = element\n  stack.push(element)\n} else {\n  closeElement(element)\n}\n```\n\nAST 树管理的目标是构建一颗 AST 树，本质上它要维护 `root` 根节点和当前父节点 `currentParent`。为了保证元素可以正确闭合，这里也利用了 `stack` 栈的数据结构，和我们之前解析模板时用到的 `stack` 类似。\n\n当我们在处理开始标签的时候，判断如果有 `currentParent`，会把当前 AST 元素 push 到 `currentParent.chilldren` 中，同时把 AST 元素的 `parent` 指向 `currentParent`。\n\n 接着就是更新 `currentParent` 和 `stack` ，判断当前如果不是一个一元标签，我们要把它生成的 AST 元素 push 到 `stack` 中，并且把当前的 AST 元素赋值给 `currentParent`。\n\n`stack` 和 `currentParent` 除了在处理开始标签的时候会变化，在处理闭合标签的时候也会变化，因此整个 AST 树管理要结合闭合标签的处理逻辑看。\n\n### 处理闭合标签\n\n对应伪代码：\n\n```js\nend () {\n  treeManagement()\n  closeElement()\n}\n```\n\n当解析到闭合标签的时候，最后会执行 `end` 回调函数：\n\n```js\n// remove trailing whitespace\nconst element = stack[stack.length - 1]\nconst lastNode = element.children[element.children.length - 1]\nif (lastNode && lastNode.type === 3 && lastNode.text === ' ' && !inPre) {\n  element.children.pop()\n}\n// pop stack\nstack.length -= 1\ncurrentParent = stack[stack.length - 1]\ncloseElement(element)\n```\n\n首先处理了尾部空格的情况，然后把 `stack` 的元素弹一个出栈，并把 `stack` 最后一个元素赋值给 `currentParent`，这样就保证了当遇到闭合标签的时候，可以正确地更新 `stack` 的长度以及 `currentParent` 的值，这样就维护了整个 AST 树。\n\n最后执行了 `closeElement(element)`：\n\n```js\nfunction closeElement (element) {\n  // check pre state\n  if (element.pre) {\n    inVPre = false\n  }\n  if (platformIsPreTag(element.tag)) {\n    inPre = false\n  }\n  // apply post-transforms\n  for (let i = 0; i < postTransforms.length; i++) {\n    postTransforms[i](element, options)\n  }\n}\n```\n`closeElement` 逻辑很简单，就是更新一下 `inVPre` 和 `inPre` 的状态，以及执行 `postTransforms` 函数，这些我们暂时都不必了解。\n\n### 处理文本内容\n\n对应伪代码：\n\n```js\nchars (text: string) {\n  handleText()\n  createChildrenASTOfText()\n}\n```\n\n除了处理开始标签和闭合标签，我们还会在解析模板的过程中去处理一些文本内容：\n\n```js\nconst children = currentParent.children\ntext = inPre || text.trim()\n  ? isTextTag(currentParent) ? text : decodeHTMLCached(text)\n  // only preserve whitespace if its not right after a starting tag\n  : preserveWhitespace && children.length ? ' ' : ''\nif (text) {\n  let res\n  if (!inVPre && text !== ' ' && (res = parseText(text, delimiters))) {\n    children.push({\n      type: 2,\n      expression: res.expression,\n      tokens: res.tokens,\n      text\n    })\n  } else if (text !== ' ' || !children.length || children[children.length - 1].text !== ' ') {\n    children.push({\n      type: 3,\n      text\n    })\n  }\n}\n```\n文本构造的 AST 元素有 2 种类型，一种是有表达式的，`type` 为 2，一种是纯文本，`type` 为 3。在我们的例子中，文本就是 `{{item}}:{{index}}`，是个表达式，通过执行 `parseText(text, delimiters)` 对文本解析，它的定义在 `src/compiler/parser/text-parser.js` 中：\n\n```js\nconst defaultTagRE = /\\{\\{((?:.|\\n)+?)\\}\\}/g\nconst regexEscapeRE = /[-.*+?^${}()|[\\]\\/\\\\]/g\n\nconst buildRegex = cached(delimiters => {\n  const open = delimiters[0].replace(regexEscapeRE, '\\\\$&')\n  const close = delimiters[1].replace(regexEscapeRE, '\\\\$&')\n  return new RegExp(open + '((?:.|\\\\n)+?)' + close, 'g')\n})\n\nexport function parseText (\n  text: string,\n  delimiters?: [string, string]\n): TextParseResult | void {\n  const tagRE = delimiters ? buildRegex(delimiters) : defaultTagRE\n  if (!tagRE.test(text)) {\n    return\n  }\n  const tokens = []\n  const rawTokens = []\n  let lastIndex = tagRE.lastIndex = 0\n  let match, index, tokenValue\n  while ((match = tagRE.exec(text))) {\n    index = match.index\n    // push text token\n    if (index > lastIndex) {\n      rawTokens.push(tokenValue = text.slice(lastIndex, index))\n      tokens.push(JSON.stringify(tokenValue))\n    }\n    // tag token\n    const exp = parseFilters(match[1].trim())\n    tokens.push(`_s(${exp})`)\n    rawTokens.push({ '@binding': exp })\n    lastIndex = index + match[0].length\n  }\n  if (lastIndex < text.length) {\n    rawTokens.push(tokenValue = text.slice(lastIndex))\n    tokens.push(JSON.stringify(tokenValue))\n  }\n  return {\n    expression: tokens.join('+'),\n    tokens: rawTokens\n  }\n}\n```\n\n`parseText` 首先根据分隔符（默认是 `{{}}`）构造了文本匹配的正则表达式，然后再循环匹配文本，遇到普通文本就 push 到 `rawTokens` 和 `tokens` 中，如果是表达式就转换成 `_s(${exp})` push 到 `tokens` 中，以及转换成 `{@binding:exp}` push 到 `rawTokens` 中。\n  \n 对于我们的例子 `{{item}}:{{index}}`，`tokens` 就是 `[_s(item),'\":\"',_s(index)]`；`rawTokens` 就是 `[{'@binding':'item'},':',{'@binding':'index'}]`。那么返回的对象如下：\n \n ```js\nreturn {\n  expression: '_s(item)+\":\"+_s(index)',\n  tokens: [{'@binding':'item'},':',{'@binding':'index'}]\n}\n```\n\n## 流程图\n\n<img :src=\"$withBase('/assets/parse.png')\">\n\n## 总结\n\n那么至此，`parse` 的过程就分析完了，看似复杂，但我们可以抛开细节理清它的整体流程。`parse` 的目标是把 `template` 模板字符串转换成 AST 树，它是一种用 JavaScript 对象的形式来描述整个模板。那么整个 `parse` 的过程是利用正则表达式顺序解析模板，当解析到开始标签、闭合标签、文本的时候都会分别执行对应的回调函数，来达到构造 AST 树的目的。\n\nAST 元素节点总共有 3 种类型，`type` 为 1 表示是普通元素，为 2 表示是表达式，为 3 表示是纯文本。其实这里我觉得源码写的不够友好，这种是典型的魔术数字，如果转换成用常量表达会更利于源码阅读。\n\n当 AST 树构造完毕，下一步就是 `optimize` 优化这颗树。\n"
  },
  {
    "path": "docs/v2/components/async-component.md",
    "content": "# 异步组件\n\n在我们平时的开发工作中，为了减少首屏代码体积，往往会把一些非首屏的组件设计成异步组件，按需加载。Vue 也原生支持了异步组件的能力，如下：\n\n```js\nVue.component('async-example', function (resolve, reject) {\n   // 这个特殊的 require 语法告诉 webpack\n   // 自动将编译后的代码分割成不同的块，\n   // 这些块将通过 Ajax 请求自动下载。\n   require(['./my-async-component'], resolve)\n})\n```\n\n示例中可以看到，Vue 注册的组件不再是一个对象，而是一个工厂函数，函数有两个参数 `resolve` 和 `reject`，函数内部用 `setTimout` 模拟了异步，实际使用可能是通过动态请求异步组件的 JS 地址，最终通过执行 `resolve` 方法，它的参数就是我们的异步组件对象。\n\n在了解了异步组件如何注册后，我们从源码的角度来分析一下它的实现。\n\n上一节我们分析了组件的注册逻辑，由于组件的定义并不是一个普通对象，所以不会执行 `Vue.extend` 的逻辑把它变成一个组件的构造函数，但是它仍然可以执行到 `createComponent` 函数，我们再来对这个函数做回顾，它的定义在 `src/core/vdom/create-component/js` 中：\n\n```js\nexport function createComponent (\n  Ctor: Class<Component> | Function | Object | void,\n  data: ?VNodeData,\n  context: Component,\n  children: ?Array<VNode>,\n  tag?: string\n): VNode | Array<VNode> | void {\n  if (isUndef(Ctor)) {\n    return\n  }\n\n  const baseCtor = context.$options._base\n\n  // plain options object: turn it into a constructor\n  if (isObject(Ctor)) {\n    Ctor = baseCtor.extend(Ctor)\n  }\n  \n  // ...\n\n  // async component\n  let asyncFactory\n  if (isUndef(Ctor.cid)) {\n    asyncFactory = Ctor\n    Ctor = resolveAsyncComponent(asyncFactory, baseCtor, context)\n    if (Ctor === undefined) {\n      // return a placeholder node for async component, which is rendered\n      // as a comment node but preserves all the raw information for the node.\n      // the information will be used for async server-rendering and hydration.\n      return createAsyncPlaceholder(\n        asyncFactory,\n        data,\n        context,\n        children,\n        tag\n      )\n    }\n  }\n}\n```\n\n我们省略了不必要的逻辑，只保留关键逻辑，由于我们这个时候传入的 `Ctor` 是一个函数，那么它也并不会执行 `Vue.extend` 逻辑，因此它的 `cid` 是 `undefiend`，进入了异步组件创建的逻辑。这里首先执行了 `Ctor = resolveAsyncComponent(asyncFactory, baseCtor, context)` 方法，它的定义在 `src/core/vdom/helpers/resolve-async-component.js` 中：\n\n```js\nexport function resolveAsyncComponent (\n  factory: Function,\n  baseCtor: Class<Component>,\n  context: Component\n): Class<Component> | void {\n  if (isTrue(factory.error) && isDef(factory.errorComp)) {\n    return factory.errorComp\n  }\n\n  if (isDef(factory.resolved)) {\n    return factory.resolved\n  }\n\n  if (isTrue(factory.loading) && isDef(factory.loadingComp)) {\n    return factory.loadingComp\n  }\n\n  if (isDef(factory.contexts)) {\n    // already pending\n    factory.contexts.push(context)\n  } else {\n    const contexts = factory.contexts = [context]\n    let sync = true\n\n    const forceRender = () => {\n      for (let i = 0, l = contexts.length; i < l; i++) {\n        contexts[i].$forceUpdate()\n      }\n    }\n\n    const resolve = once((res: Object | Class<Component>) => {\n      // cache resolved\n      factory.resolved = ensureCtor(res, baseCtor)\n      // invoke callbacks only if this is not a synchronous resolve\n      // (async resolves are shimmed as synchronous during SSR)\n      if (!sync) {\n        forceRender()\n      }\n    })\n\n    const reject = once(reason => {\n      process.env.NODE_ENV !== 'production' && warn(\n        `Failed to resolve async component: ${String(factory)}` +\n        (reason ? `\\nReason: ${reason}` : '')\n      )\n      if (isDef(factory.errorComp)) {\n        factory.error = true\n        forceRender()\n      }\n    })\n\n    const res = factory(resolve, reject)\n\n    if (isObject(res)) {\n      if (typeof res.then === 'function') {\n        // () => Promise\n        if (isUndef(factory.resolved)) {\n          res.then(resolve, reject)\n        }\n      } else if (isDef(res.component) && typeof res.component.then === 'function') {\n        res.component.then(resolve, reject)\n\n        if (isDef(res.error)) {\n          factory.errorComp = ensureCtor(res.error, baseCtor)\n        }\n\n        if (isDef(res.loading)) {\n          factory.loadingComp = ensureCtor(res.loading, baseCtor)\n          if (res.delay === 0) {\n            factory.loading = true\n          } else {\n            setTimeout(() => {\n              if (isUndef(factory.resolved) && isUndef(factory.error)) {\n                factory.loading = true\n                forceRender()\n              }\n            }, res.delay || 200)\n          }\n        }\n\n        if (isDef(res.timeout)) {\n          setTimeout(() => {\n            if (isUndef(factory.resolved)) {\n              reject(\n                process.env.NODE_ENV !== 'production'\n                  ? `timeout (${res.timeout}ms)`\n                  : null\n              )\n            }\n          }, res.timeout)\n        }\n      }\n    }\n\n    sync = false\n    // return in case resolved synchronously\n    return factory.loading\n      ? factory.loadingComp\n      : factory.resolved\n  }\n}\n```\n\n`resolveAsyncComponent` 函数的逻辑略复杂，因为它实际上处理了 3 种异步组件的创建方式，除了刚才示例的组件注册方式，还支持 2 种，一种是支持 `Promise` 创建组件的方式，如下：\n\n```js\nVue.component(\n  'async-webpack-example',\n  // 该 `import` 函数返回一个 `Promise` 对象。\n  () => import('./my-async-component')\n)\n```\n\n另一种是高级异步组件，如下：\n\n```js\nconst AsyncComp = () => ({\n  // 需要加载的组件。应当是一个 Promise\n  component: import('./MyComp.vue'),\n  // 加载中应当渲染的组件\n  loading: LoadingComp,\n  // 出错时渲染的组件\n  error: ErrorComp,\n  // 渲染加载中组件前的等待时间。默认：200ms。\n  delay: 200,\n  // 最长等待时间。超出此时间则渲染错误组件。默认：Infinity\n  timeout: 3000\n})\nVue.component('async-example', AsyncComp)\n```\n\n那么解下来，我们就根据这 3 种异步组件的情况，来分别去分析 `resolveAsyncComponent` 的逻辑。\n\n## 普通函数异步组件\n\n针对普通函数的情况，前面几个 if 判断可以忽略，它们是为高级组件所用，对于 `factory.contexts` 的判断，是考虑到多个地方同时初始化一个异步组件，那么它的实际加载应该只有一次。接着进入实际加载逻辑，定义了 `forceRender`、`resolve` 和 `reject` 函数，注意 `resolve` 和 `reject` 函数用 `once` 函数做了一层包装，它的定义在 `src/shared/util.js` 中：\n\n````js\n/**\n * Ensure a function is called only once.\n */\nexport function once (fn: Function): Function {\n  let called = false\n  return function () {\n    if (!called) {\n      called = true\n      fn.apply(this, arguments)\n    }\n  }\n}\n````\n`once` 逻辑非常简单，传入一个函数，并返回一个新函数，它非常巧妙地利用闭包和一个标志位保证了它包装的函数只会执行一次，也就是确保 `resolve` 和 `reject` 函数只执行一次。\n\n接下来执行 `const res = factory(resolve, reject)` 逻辑，这块儿就是执行我们组件的工厂函数，同时把 `resolve` 和 `reject` 函数作为参数传入，组件的工厂函数通常会先发送请求去加载我们的异步组件的 JS 文件，拿到组件定义的对象 `res` 后，执行 `resolve(res)` 逻辑，它会先执行 `factory.resolved = ensureCtor(res, baseCtor)`：\n\n```js\nfunction ensureCtor (comp: any, base) {\n  if (\n    comp.__esModule ||\n    (hasSymbol && comp[Symbol.toStringTag] === 'Module')\n  ) {\n    comp = comp.default\n  }\n  return isObject(comp)\n    ? base.extend(comp)\n    : comp\n}\n```\n这个函数目的是为了保证能找到异步组件 JS 定义的组件对象，并且如果它是一个普通对象，则调用 `Vue.extend` 把它转换成一个组件的构造函数。\n\n`resolve` 逻辑最后判断了 `sync`，显然我们这个场景下 `sync` 为 false，那么就会执行 `forceRender` 函数，它会遍历 `factory.contexts`，拿到每一个调用异步组件的实例 `vm`, 执行 `vm.$forceUpdate()` 方法，它的定义在 `src/core/instance/lifecycle.js` 中：\n \n```js\nVue.prototype.$forceUpdate = function () {\n  const vm: Component = this\n  if (vm._watcher) {\n    vm._watcher.update()\n  }\n}\n```\n\n`$forceUpdate` 的逻辑非常简单，就是调用渲染 `watcher` 的 `update` 方法，让渲染 `watcher` 对应的回调函数执行，也就是触发了组件的重新渲染。之所以这么做是因为 Vue 通常是数据驱动视图重新渲染，但是在整个异步组件加载过程中是没有数据发生变化的，所以通过执行 `$forceUpdate` 可以强制组件重新渲染一次。\n\n## `Promise` 异步组件\n\n```js\nVue.component(\n  'async-webpack-example',\n  // 该 `import` 函数返回一个 `Promise` 对象。\n  () => import('./my-async-component')\n)\n```\nwebpack 2+ 支持了异步加载的语法糖：`() => import('./my-async-component')`，当执行完 `res = factory(resolve, reject)`，返回的值就是 ` import('./my-async-component')` 的返回值，它是一个 `Promise` 对象。接着进入 if 条件，又判断了 `typeof res.then === 'function')`，条件满足，执行：\n\n```js\nif (isUndef(factory.resolved)) {\n  res.then(resolve, reject)\n}\n```\n当组件异步加载成功后，执行 `resolve`，加载失败则执行 `reject`，这样就非常巧妙地实现了配合 webpack 2+ 的异步加载组件的方式（`Promise`）加载异步组件。\n\n## 高级异步组件\n\n由于异步加载组件需要动态加载 JS，有一定网络延时，而且有加载失败的情况，所以通常我们在开发异步组件相关逻辑的时候需要设计 loading 组件和 error 组件，并在适当的时机渲染它们。Vue.js 2.3+ 支持了一种高级异步组件的方式，它通过一个简单的对象配置，帮你搞定 loading 组件和 error 组件的渲染时机，你完全不用关心细节，非常方便。接下来我们就从源码的角度来分析高级异步组件是怎么实现的。\n\n```js\nconst AsyncComp = () => ({\n  // 需要加载的组件。应当是一个 Promise\n  component: import('./MyComp.vue'),\n  // 加载中应当渲染的组件\n  loading: LoadingComp,\n  // 出错时渲染的组件\n  error: ErrorComp,\n  // 渲染加载中组件前的等待时间。默认：200ms。\n  delay: 200,\n  // 最长等待时间。超出此时间则渲染错误组件。默认：Infinity\n  timeout: 3000\n})\nVue.component('async-example', AsyncComp)\n```\n高级异步组件的初始化逻辑和普通异步组件一样，也是执行 `resolveAsyncComponent`，当执行完 `res = factory(resolve, reject)`，返回值就是定义的组件对象，显然满足 `else if (isDef(res.component) && typeof res.component.then === 'function')` 的逻辑，接着执行 `res.component.then(resolve, reject)`，当异步组件加载成功后，执行 `resolve`，失败执行 `reject`。\n\n因为异步组件加载是一个异步过程，它接着又同步执行了如下逻辑：\n\n```js\nif (isDef(res.error)) {\n  factory.errorComp = ensureCtor(res.error, baseCtor)\n}\n\nif (isDef(res.loading)) {\n  factory.loadingComp = ensureCtor(res.loading, baseCtor)\n  if (res.delay === 0) {\n    factory.loading = true\n  } else {\n    setTimeout(() => {\n      if (isUndef(factory.resolved) && isUndef(factory.error)) {\n        factory.loading = true\n        forceRender()\n      }\n    }, res.delay || 200)\n  }\n}\n\nif (isDef(res.timeout)) {\n  setTimeout(() => {\n    if (isUndef(factory.resolved)) {\n      reject(\n        process.env.NODE_ENV !== 'production'\n          ? `timeout (${res.timeout}ms)`\n          : null\n      )\n    }\n  }, res.timeout)\n}\n```\n先判断 `res.error` 是否定义了 error 组件，如果有的话则赋值给 `factory.errorComp`。\n接着判断 `res.loading` 是否定义了 loading 组件，如果有的话则赋值给 `factory.loadingComp`，如果设置了 `res.delay` 且为 0，则设置 `factory.loading = true`，否则延时 `delay` 的时间执行：\n\n```js\nif (isUndef(factory.resolved) && isUndef(factory.error)) {\n    factory.loading = true\n    forceRender()\n}\n```\n\n最后判断 `res.timeout`，如果配置了该项，则在 `res.timout` 时间后，如果组件没有成功加载，执行 `reject`。\n\n在 `resolveAsyncComponent` 的最后有一段逻辑：\n\n```js\nsync = false\nreturn factory.loading\n  ? factory.loadingComp\n  : factory.resolved\n```\n\n如果 `delay` 配置为 0，则这次直接渲染 loading 组件，否则则延时 `delay` 执行 `forceRender`，那么又会再一次执行到 `resolveAsyncComponent`。\n\n那么这时候我们有几种情况，按逻辑的执行顺序，对不同的情况做判断。\n\n### 异步组件加载失败\n当异步组件加载失败，会执行 `reject` 函数：\n\n```js\nconst reject = once(reason => {\n  process.env.NODE_ENV !== 'production' && warn(\n    `Failed to resolve async component: ${String(factory)}` +\n    (reason ? `\\nReason: ${reason}` : '')\n  )\n  if (isDef(factory.errorComp)) {\n    factory.error = true\n    forceRender()\n  }\n})\n```\n这个时候会把 `factory.error` 设置为 `true`，同时执行 `forceRender()` 再次执行到 `resolveAsyncComponent`：\n\n```js\nif (isTrue(factory.error) && isDef(factory.errorComp)) {\n  return factory.errorComp\n}\n```\n\n那么这个时候就返回 `factory.errorComp`，直接渲染 error 组件。\n  \n### 异步组件加载成功\n\n当异步组件加载成功，会执行 `resolve` 函数：\n\n```js\nconst resolve = once((res: Object | Class<Component>) => {\n  factory.resolved = ensureCtor(res, baseCtor)\n  if (!sync) {\n    forceRender()\n  }\n})\n```\n首先把加载结果缓存到 `factory.resolved` 中，这个时候因为 `sync` 已经为 false，则执行 `forceRender()` 再次执行到 `resolveAsyncComponent`：\n\n```js\nif (isDef(factory.resolved)) {\n  return factory.resolved\n}\n```\n那么这个时候直接返回 `factory.resolved`，渲染成功加载的组件。\n\n### 异步组件加载中\n\n如果异步组件加载中并未返回，这时候会走到这个逻辑：\n\n```js\nif (isTrue(factory.loading) && isDef(factory.loadingComp)) {\n  return factory.loadingComp\n}\n```\n\n那么则会返回 `factory.loadingComp`，渲染 loading 组件。\n\n### 异步组件加载超时\n\n如果超时，则走到了 `reject` 逻辑，之后逻辑和加载失败一样，渲染 error 组件。\n\n## 异步组件 patch\n\n回到 `createComponent` 的逻辑：\n\n```js\nCtor = resolveAsyncComponent(asyncFactory, baseCtor, context)\nif (Ctor === undefined) {\n  return createAsyncPlaceholder(\n    asyncFactory,\n    data,\n    context,\n    children,\n    tag\n  )\n}\n```\n\n如果是第一次执行 `resolveAsyncComponent`，除非使用高级异步组件 `0 delay` 去创建了一个 loading 组件，否则返回是 `undefiend`，接着通过 `createAsyncPlaceholder` 创建一个注释节点作为占位符。它的定义在 `src/core/vdom/helpers/resolve-async-components.js` 中：\n\n```js\nexport function createAsyncPlaceholder (\n  factory: Function,\n  data: ?VNodeData,\n  context: Component,\n  children: ?Array<VNode>,\n  tag: ?string\n): VNode {\n  const node = createEmptyVNode()\n  node.asyncFactory = factory\n  node.asyncMeta = { data, context, children, tag }\n  return node\n}\n```\n\n实际上就是就是创建了一个占位的注释 VNode，同时把 `asyncFactory` 和 `asyncMeta` 赋值给当前 `vnode`。\n\n当执行 `forceRender` 的时候，会触发组件的重新渲染，那么会再一次执行 `resolveAsyncComponent`，这时候就会根据不同的情况，可能返回 loading、error 或成功加载的异步组件，返回值不为 `undefined`，因此就走正常的组件 `render`、`patch` 过程，与组件第一次渲染流程不一样，这个时候是存在新旧 `vnode` 的，下一章我会分析组件更新的 `patch` 过程。\n\n\n## 总结\n\n通过以上代码分析，我们对 Vue 的异步组件的实现有了深入的了解，知道了 3 种异步组件的实现方式，并且看到高级异步组件的实现是非常巧妙的，它实现了 loading、resolve、reject、timeout 4 种状态。异步组件实现的本质是 2 次渲染，除了 0 delay 的高级异步组件第一次直接渲染成 loading 组件外，其它都是第一次渲染生成一个注释节点，当异步获取组件成功后，再通过 `forceRender` 强制重新渲染，这样就能正确渲染出我们异步加载的组件了。"
  },
  {
    "path": "docs/v2/components/component-register.md",
    "content": "# 组件注册\n\n在 Vue.js 中，除了它内置的组件如 `keep-alive`、`component`、`transition`、`transition-group` 等，其它用户自定义组件在使用前必须注册。很多同学在开发过程中可能会遇到如下报错信息：\n\n``` \n'Unknown custom element: <xxx> - did you register the component correctly?\n For recursive components, make sure to provide the \"name\" option.'\n```\n\n一般报这个错的原因都是我们使用了未注册的组件。Vue.js 提供了 2 种组件的注册方式，全局注册和局部注册。接下来我们从源码分析的角度来分析这两种注册方式。\n\n## 全局注册\n\n要注册一个全局组件，可以使用 `Vue.component(tagName, options)`。例如：\n\n```js\nVue.component('my-component', {\n  // 选项\n})\n```\n\n那么，`Vue.component` 函数是在什么时候定义的呢，它的定义过程发生在最开始初始化 Vue 的全局函数的时候，代码在 `src/core/global-api/assets.js` 中：\n\n```js\nimport { ASSET_TYPES } from 'shared/constants'\nimport { isPlainObject, validateComponentName } from '../util/index'\n\nexport function initAssetRegisters (Vue: GlobalAPI) {\n  /**\n   * Create asset registration methods.\n   */\n  ASSET_TYPES.forEach(type => {\n    Vue[type] = function (\n      id: string,\n      definition: Function | Object\n    ): Function | Object | void {\n      if (!definition) {\n        return this.options[type + 's'][id]\n      } else {\n        /* istanbul ignore if */\n        if (process.env.NODE_ENV !== 'production' && type === 'component') {\n          validateComponentName(id)\n        }\n        if (type === 'component' && isPlainObject(definition)) {\n          definition.name = definition.name || id\n          definition = this.options._base.extend(definition)\n        }\n        if (type === 'directive' && typeof definition === 'function') {\n          definition = { bind: definition, update: definition }\n        }\n        this.options[type + 's'][id] = definition\n        return definition\n      }\n    }\n  })\n}\n```\n函数首先遍历 `ASSET_TYPES`，得到 `type` 后挂载到 Vue 上 。`ASSET_TYPES` 的定义在 `src/shared/constants.js` 中：\n\n```js\nexport const ASSET_TYPES = [\n  'component',\n  'directive',\n  'filter'\n]\n```\n所以实际上 Vue 是初始化了 3 个全局函数，并且如果 `type` 是 `component` 且 `definition` 是一个对象的话，通过 `this.opitons._base.extend`， 相当于 `Vue.extend` 把这个对象转换成一个继承于 Vue 的构造函数，最后通过 `this.options[type + 's'][id] = definition` 把它挂载到 `Vue.options.components` 上。\n\n由于我们每个组件的创建都是通过 `Vue.extend` 继承而来，我们之前分析过在继承的过程中有这么一段逻辑：\n\n```js\nSub.options = mergeOptions(\n  Super.options,\n  extendOptions\n)\n```\n\n也就是说它会把 `Vue.options` 合并到 `Sub.options`，也就是组件的 `options` 上， 然后在组件的实例化阶段，会执行 `merge options` 逻辑，把 `Sub.options.components` 合并到 `vm.$options.components` 上。\n\n然后在创建 `vnode` 的过程中，会执行 `_createElement` 方法，我们再来回顾一下这部分的逻辑，它的定义在 `src/core/vdom/create-element.js` 中：\n\n```js\nexport function _createElement (\n  context: Component,\n  tag?: string | Class<Component> | Function | Object,\n  data?: VNodeData,\n  children?: any,\n  normalizationType?: number\n): VNode | Array<VNode> {\n  // ...\n  let vnode, ns\n  if (typeof tag === 'string') {\n    let Ctor\n    ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag)\n    if (config.isReservedTag(tag)) {\n      // platform built-in elements\n      vnode = new VNode(\n        config.parsePlatformTagName(tag), data, children,\n        undefined, undefined, context\n      )\n    } else if (isDef(Ctor = resolveAsset(context.$options, 'components', tag))) {\n      // component\n      vnode = createComponent(Ctor, data, context, children, tag)\n    } else {\n      // unknown or unlisted namespaced elements\n      // check at runtime because it may get assigned a namespace when its\n      // parent normalizes children\n      vnode = new VNode(\n        tag, data, children,\n        undefined, undefined, context\n      )\n    }\n  } else {\n    // direct component options / constructor\n    vnode = createComponent(tag, data, context, children)\n  }\n  // ...\n}\n```\n这里有一个判断逻辑 `isDef(Ctor = resolveAsset(context.$options, 'components', tag))`，先来看一下 `resolveAsset` 的定义，在 `src/core/utils/options.js` 中：\n\n```js\n/**\n * Resolve an asset.\n * This function is used because child instances need access\n * to assets defined in its ancestor chain.\n */\nexport function resolveAsset (\n  options: Object,\n  type: string,\n  id: string,\n  warnMissing?: boolean\n): any {\n  /* istanbul ignore if */\n  if (typeof id !== 'string') {\n    return\n  }\n  const assets = options[type]\n  // check local registration variations first\n  if (hasOwn(assets, id)) return assets[id]\n  const camelizedId = camelize(id)\n  if (hasOwn(assets, camelizedId)) return assets[camelizedId]\n  const PascalCaseId = capitalize(camelizedId)\n  if (hasOwn(assets, PascalCaseId)) return assets[PascalCaseId]\n  // fallback to prototype chain\n  const res = assets[id] || assets[camelizedId] || assets[PascalCaseId]\n  if (process.env.NODE_ENV !== 'production' && warnMissing && !res) {\n    warn(\n      'Failed to resolve ' + type.slice(0, -1) + ': ' + id,\n      options\n    )\n  }\n  return res\n}\n```\n这段逻辑很简单，先通过 `const assets = options[type]` 拿到 `assets`，然后再尝试拿 `assets[id]`，这里有个顺序，先直接使用 `id` 拿，如果不存在，则把 `id` 变成驼峰的形式再拿，如果仍然不存在则在驼峰的基础上把首字母再变成大写的形式再拿，如果仍然拿不到则报错。这样说明了我们在使用 `Vue.component(id, definition)` 全局注册组件的时候，id 可以是连字符、驼峰或首字母大写的形式。\n\n那么回到我们的调用 `resolveAsset(context.$options, 'components', tag)`，即拿 `vm.$options.components[tag]`，这样我们就可以在 `resolveAsset` 的时候拿到这个组件的构造函数，并作为 `createComponent` 的钩子的参数。\n\n## 局部注册\n\nVue.js 也同样支持局部注册，我们可以在一个组件内部使用 `components` 选项做组件的局部注册，例如：\n\n```js\nimport HelloWorld from './components/HelloWorld'\n\nexport default {\n  components: {\n    HelloWorld\n  }\n}\n```\n\n其实理解了全局注册的过程，局部注册是非常简单的。在组件的 Vue 的实例化阶段有一个合并 `option` 的逻辑，之前我们也分析过，所以就把 `components` 合并到 `vm.$options.components` 上，这样我们就可以在 `resolveAsset` 的时候拿到这个组件的构造函数，并作为 `createComponent` 的钩子的参数。\n\n注意，局部注册和全局注册不同的是，只有该类型的组件才可以访问局部注册的子组件，而全局注册是扩展到 `Vue.options` 下，所以在所有组件创建的过程中，都会从全局的 `Vue.options.components` 扩展到当前组件的 `vm.$options.components` 下，这就是全局注册的组件能被任意使用的原因。\n \n## 总结\n\n通过这一小节的分析，我们对组件的注册过程有了认识，并理解了全局注册和局部注册的差异。其实在平时的工作中，当我们使用到组件库的时候，往往更通用基础组件都是全局注册的，而编写的特例场景的业务组件都是局部注册的。了解了它们的原理，对我们在工作中到底使用全局注册组件还是局部注册组件是有这非常好的指导意义的。\n"
  },
  {
    "path": "docs/v2/components/create-component.md",
    "content": "# createComponent\n\n上一章我们在分析 `createElement` 的实现的时候，它最终会调用 `_createElement` 方法，其中有一段逻辑是对参数 `tag` 的判断，如果是一个普通的 html 标签，像上一章的例子那样是一个普通的 div，则会实例化一个普通 VNode 节点，否则通过 `createComponent` 方法创建一个组件 VNode。\n\n```js\nif (typeof tag === 'string') {\n  let Ctor\n  ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag)\n  if (config.isReservedTag(tag)) {\n    // platform built-in elements\n    vnode = new VNode(\n      config.parsePlatformTagName(tag), data, children,\n      undefined, undefined, context\n    )\n  } else if (isDef(Ctor = resolveAsset(context.$options, 'components', tag))) {\n    // component\n    vnode = createComponent(Ctor, data, context, children, tag)\n  } else {\n    // unknown or unlisted namespaced elements\n    // check at runtime because it may get assigned a namespace when its\n    // parent normalizes children\n    vnode = new VNode(\n      tag, data, children,\n      undefined, undefined, context\n    )\n  }\n} else {\n  // direct component options / constructor\n  vnode = createComponent(tag, data, context, children)\n}\n```\n\n在我们这一章传入的是一个 App 对象，它本质上是一个 `Component` 类型，那么它会走到上述代码的 else 逻辑，直接通过 `createComponent` 方法来创建 `vnode`。所以接下来我们来看一下 `createComponent` 方法的实现，它定义在 `src/core/vdom/create-component.js` 文件中：\n\n```js\nexport function createComponent (\n  Ctor: Class<Component> | Function | Object | void,\n  data: ?VNodeData,\n  context: Component,\n  children: ?Array<VNode>,\n  tag?: string\n): VNode | Array<VNode> | void {\n  if (isUndef(Ctor)) {\n    return\n  }\n\n  const baseCtor = context.$options._base\n\n  // plain options object: turn it into a constructor\n  if (isObject(Ctor)) {\n    Ctor = baseCtor.extend(Ctor)\n  }\n\n  // if at this stage it's not a constructor or an async component factory,\n  // reject.\n  if (typeof Ctor !== 'function') {\n    if (process.env.NODE_ENV !== 'production') {\n      warn(`Invalid Component definition: ${String(Ctor)}`, context)\n    }\n    return\n  }\n\n  // async component\n  let asyncFactory\n  if (isUndef(Ctor.cid)) {\n    asyncFactory = Ctor\n    Ctor = resolveAsyncComponent(asyncFactory, baseCtor, context)\n    if (Ctor === undefined) {\n      // return a placeholder node for async component, which is rendered\n      // as a comment node but preserves all the raw information for the node.\n      // the information will be used for async server-rendering and hydration.\n      return createAsyncPlaceholder(\n        asyncFactory,\n        data,\n        context,\n        children,\n        tag\n      )\n    }\n  }\n\n  data = data || {}\n\n  // resolve constructor options in case global mixins are applied after\n  // component constructor creation\n  resolveConstructorOptions(Ctor)\n\n  // transform component v-model data into props & events\n  if (isDef(data.model)) {\n    transformModel(Ctor.options, data)\n  }\n\n  // extract props\n  const propsData = extractPropsFromVNodeData(data, Ctor, tag)\n\n  // functional component\n  if (isTrue(Ctor.options.functional)) {\n    return createFunctionalComponent(Ctor, propsData, data, context, children)\n  }\n\n  // extract listeners, since these needs to be treated as\n  // child component listeners instead of DOM listeners\n  const listeners = data.on\n  // replace with listeners with .native modifier\n  // so it gets processed during parent component patch.\n  data.on = data.nativeOn\n\n  if (isTrue(Ctor.options.abstract)) {\n    // abstract components do not keep anything\n    // other than props & listeners & slot\n\n    // work around flow\n    const slot = data.slot\n    data = {}\n    if (slot) {\n      data.slot = slot\n    }\n  }\n\n  // install component management hooks onto the placeholder node\n  installComponentHooks(data)\n\n  // return a placeholder vnode\n  const name = Ctor.options.name || tag\n  const vnode = new VNode(\n    `vue-component-${Ctor.cid}${name ? `-${name}` : ''}`,\n    data, undefined, undefined, undefined, context,\n    { Ctor, propsData, listeners, tag, children },\n    asyncFactory\n  )\n\n  // Weex specific: invoke recycle-list optimized @render function for\n  // extracting cell-slot template.\n  // https://github.com/Hanks10100/weex-native-directive/tree/master/component\n  /* istanbul ignore if */\n  if (__WEEX__ && isRecyclableComponent(vnode)) {\n    return renderRecyclableComponentTemplate(vnode)\n  }\n\n  return vnode\n}\n```\n\n可以看到，`createComponent` 的逻辑也会有一些复杂，但是分析源码比较推荐的是只分析核心流程，分支流程可以之后针对性的看，所以这里针对组件渲染这个 case 主要就 3 个关键步骤：\n\n构造子类构造函数，安装组件钩子函数和实例化 `vnode`。\n\n## 构造子类构造函数\n\n```js\nconst baseCtor = context.$options._base\n\n// plain options object: turn it into a constructor\nif (isObject(Ctor)) {\n  Ctor = baseCtor.extend(Ctor)\n}\n```\n\n我们在编写一个组件的时候，通常都是创建一个普通对象，还是以我们的 App.vue 为例，代码如下：\n\n```js\nimport HelloWorld from './components/HelloWorld'\n\nexport default {\n  name: 'app',\n  components: {\n    HelloWorld\n  }\n}\n```\n这里 export 的是一个对象，所以 `createComponent` 里的代码逻辑会执行到 `baseCtor.extend(Ctor)`，在这里 `baseCtor` 实际上就是 Vue，这个的定义是在最开始初始化 Vue 的阶段，在 `src/core/global-api/index.js` 中的 `initGlobalAPI` 函数有这么一段逻辑：\n\n```js\n// this is used to identify the \"base\" constructor to extend all plain-object\n// components with in Weex's multi-instance scenarios.\nVue.options._base = Vue\n```\n细心的同学会发现，这里定义的是 `Vue.options`，而我们的 `createComponent` 取的是 `context.$options`，实际上在 `src/core/instance/init.js` 里 Vue 原型上的 `_init` 函数中有这么一段逻辑：\n\n```js\nvm.$options = mergeOptions(\n  resolveConstructorOptions(vm.constructor),\n  options || {},\n  vm\n)\n```\n\n这样就把 Vue 上的一些 `option` 扩展到了 vm.$options 上，所以我们也就能通过 `vm.$options._base` 拿到 Vue 这个构造函数了。`mergeOptions` 的实现我们会在后续章节中具体分析，现在只需要理解它的功能是把 Vue 构造函数的 `options` 和用户传入的 `options` 做一层合并，到 `vm.$options ` 上。\n\n在了解了 `baseCtor` 指向了 Vue 之后，我们来看一下 `Vue.extend` 函数的定义，在 `src/core/global-api/extend.js` 中。\n\n```js\n/**\n * Class inheritance\n */\nVue.extend = function (extendOptions: Object): Function {\n  extendOptions = extendOptions || {}\n  const Super = this\n  const SuperId = Super.cid\n  const cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {})\n  if (cachedCtors[SuperId]) {\n    return cachedCtors[SuperId]\n  }\n\n  const name = extendOptions.name || Super.options.name\n  if (process.env.NODE_ENV !== 'production' && name) {\n    validateComponentName(name)\n  }\n\n  const Sub = function VueComponent (options) {\n    this._init(options)\n  }\n  Sub.prototype = Object.create(Super.prototype)\n  Sub.prototype.constructor = Sub\n  Sub.cid = cid++\n  Sub.options = mergeOptions(\n    Super.options,\n    extendOptions\n  )\n  Sub['super'] = Super\n\n  // For props and computed properties, we define the proxy getters on\n  // the Vue instances at extension time, on the extended prototype. This\n  // avoids Object.defineProperty calls for each instance created.\n  if (Sub.options.props) {\n    initProps(Sub)\n  }\n  if (Sub.options.computed) {\n    initComputed(Sub)\n  }\n\n  // allow further extension/mixin/plugin usage\n  Sub.extend = Super.extend\n  Sub.mixin = Super.mixin\n  Sub.use = Super.use\n\n  // create asset registers, so extended classes\n  // can have their private assets too.\n  ASSET_TYPES.forEach(function (type) {\n    Sub[type] = Super[type]\n  })\n  // enable recursive self-lookup\n  if (name) {\n    Sub.options.components[name] = Sub\n  }\n\n  // keep a reference to the super options at extension time.\n  // later at instantiation we can check if Super's options have\n  // been updated.\n  Sub.superOptions = Super.options\n  Sub.extendOptions = extendOptions\n  Sub.sealedOptions = extend({}, Sub.options)\n\n  // cache constructor\n  cachedCtors[SuperId] = Sub\n  return Sub\n}\n```\n\n`Vue.extend` 的作用就是构造一个 `Vue` 的子类，它使用一种非常经典的原型继承的方式把一个纯对象转换一个继承于 `Vue` 的构造器 `Sub` 并返回，然后对 `Sub` 这个对象本身扩展了一些属性，如扩展 `options`、添加全局 API 等；并且对配置中的 `props` 和 `computed` 做了初始化工作；最后对于这个 `Sub` 构造函数做了缓存，避免多次执行 `Vue.extend` 的时候对同一个子组件重复构造。\n\n这样当我们去实例化 `Sub` 的时候，就会执行 `this._init` 逻辑再次走到了 `Vue` 实例的初始化逻辑，实例化子组件的逻辑在之后的章节会介绍。\n\n```js\nconst Sub = function VueComponent (options) {\n  this._init(options)\n}\n```\n\n## 安装组件钩子函数\n\n```js\n// install component management hooks onto the placeholder node\ninstallComponentHooks(data)\n```\n\n我们之前提到 Vue.js 使用的 Virtual DOM 参考的是开源库 [snabbdom](https://github.com/snabbdom/snabbdom)，它的一个特点是在 VNode 的 patch 流程中对外暴露了各种时机的钩子函数，方便我们做一些额外的事情，Vue.js 也是充分利用这一点，在初始化一个 Component 类型的 VNode 的过程中实现了几个钩子函数：\n\n```js\nconst componentVNodeHooks = {\n  init (vnode: VNodeWithData, hydrating: boolean): ?boolean {\n    if (\n      vnode.componentInstance &&\n      !vnode.componentInstance._isDestroyed &&\n      vnode.data.keepAlive\n    ) {\n      // kept-alive components, treat as a patch\n      const mountedNode: any = vnode // work around flow\n      componentVNodeHooks.prepatch(mountedNode, mountedNode)\n    } else {\n      const child = vnode.componentInstance = createComponentInstanceForVnode(\n        vnode,\n        activeInstance\n      )\n      child.$mount(hydrating ? vnode.elm : undefined, hydrating)\n    }\n  },\n\n  prepatch (oldVnode: MountedComponentVNode, vnode: MountedComponentVNode) {\n    const options = vnode.componentOptions\n    const child = vnode.componentInstance = oldVnode.componentInstance\n    updateChildComponent(\n      child,\n      options.propsData, // updated props\n      options.listeners, // updated listeners\n      vnode, // new parent vnode\n      options.children // new children\n    )\n  },\n\n  insert (vnode: MountedComponentVNode) {\n    const { context, componentInstance } = vnode\n    if (!componentInstance._isMounted) {\n      componentInstance._isMounted = true\n      callHook(componentInstance, 'mounted')\n    }\n    if (vnode.data.keepAlive) {\n      if (context._isMounted) {\n        // vue-router#1212\n        // During updates, a kept-alive component's child components may\n        // change, so directly walking the tree here may call activated hooks\n        // on incorrect children. Instead we push them into a queue which will\n        // be processed after the whole patch process ended.\n        queueActivatedComponent(componentInstance)\n      } else {\n        activateChildComponent(componentInstance, true /* direct */)\n      }\n    }\n  },\n\n  destroy (vnode: MountedComponentVNode) {\n    const { componentInstance } = vnode\n    if (!componentInstance._isDestroyed) {\n      if (!vnode.data.keepAlive) {\n        componentInstance.$destroy()\n      } else {\n        deactivateChildComponent(componentInstance, true /* direct */)\n      }\n    }\n  }\n}\n\nconst hooksToMerge = Object.keys(componentVNodeHooks)\n\nfunction installComponentHooks (data: VNodeData) {\n  const hooks = data.hook || (data.hook = {})\n  for (let i = 0; i < hooksToMerge.length; i++) {\n    const key = hooksToMerge[i]\n    const existing = hooks[key]\n    const toMerge = componentVNodeHooks[key]\n    if (existing !== toMerge && !(existing && existing._merged)) {\n      hooks[key] = existing ? mergeHook(toMerge, existing) : toMerge\n    }\n  }\n}\n\nfunction mergeHook (f1: any, f2: any): Function {\n  const merged = (a, b) => {\n    // flow complains about extra args which is why we use any\n    f1(a, b)\n    f2(a, b)\n  }\n  merged._merged = true\n  return merged\n}\n```\n整个 `installComponentHooks` 的过程就是把 `componentVNodeHooks` 的钩子函数合并到 `data.hook` 中，在 VNode 执行 `patch` 的过程中执行相关的钩子函数，具体的执行我们稍后在介绍 `patch` 过程中会详细介绍。这里要注意的是合并策略，在合并过程中，如果某个时机的钩子已经存在 `data.hook` 中，那么通过执行 `mergeHook` 函数做合并，这个逻辑很简单，就是在最终执行的时候，依次执行这两个钩子函数即可。\n\n## 实例化 VNode\n\n```js\nconst name = Ctor.options.name || tag\nconst vnode = new VNode(\n  `vue-component-${Ctor.cid}${name ? `-${name}` : ''}`,\n  data, undefined, undefined, undefined, context,\n  { Ctor, propsData, listeners, tag, children },\n  asyncFactory\n)\nreturn vnode\n```\n\n最后一步非常简单，通过 `new VNode` 实例化一个 `vnode` 并返回。需要注意的是和普通元素节点的 `vnode` 不同，组件的 `vnode` 是没有 `children` 的，这点很关键，在之后的 `patch` 过程中我们会再提。\n\n## 总结\n\n这一节我们分析了 `createComponent` 的实现，了解到它在渲染一个组件的时候的 3 个关键逻辑：构造子类构造函数，安装组件钩子函数和实例化 `vnode`。`createComponent` 后返回的是组件 `vnode`，它也一样走到 `vm._update` 方法，进而执行了 `patch` 函数，我们在上一章对 `patch` 函数做了简单的分析，那么下一节我们会对它做进一步的分析。\n"
  },
  {
    "path": "docs/v2/components/index.md",
    "content": "# 组件化\n\nVue.js 另一个核心思想是组件化。所谓组件化，就是把页面拆分成多个组件 (component)，每个组件依赖的 CSS、JavaScript、模板、图片等资源放在一起开发和维护。组件是资源独立的，组件在系统内部可复用，组件和组件之间可以嵌套。\n\n我们在用 Vue.js 开发实际项目的时候，就是像搭积木一样，编写一堆组件拼装生成页面。在 Vue.js 的官网中，也是花了大篇幅来介绍什么是组件，如何编写组件以及组件拥有的属性和特性。\n\n那么在这一章节，我们将从源码的角度来分析 Vue 的组件内部是如何工作的，只有了解了内部的工作原理，才能让我们使用它的时候更加得心应手。\n\n接下来我们会用 Vue-cli 初始化的代码为例，来分析一下 Vue 组件初始化的一个过程。\n\n```js\nimport Vue from 'vue'\nimport App from './App.vue'\n\nvar app = new Vue({\n  el: '#app',\n  // 这里的 h 是 createElement 方法\n  render: h => h(App)\n})\n```\n这段代码相信很多同学都很熟悉，它和我们上一章相同的点也是通过 `render` 函数去渲染的，不同的这次通过 `createElement` 传的参数是一个组件而不是一个原生的标签，那么接下来我们就开始分析这一过程。"
  },
  {
    "path": "docs/v2/components/lifecycle.md",
    "content": "# 生命周期\n\n每个 Vue 实例在被创建之前都要经过一系列的初始化过程。例如需要设置数据监听、编译模板、挂载实例到 DOM、在数据变化时更新 DOM 等。同时在这个过程中也会运行一些叫做生命周期钩子的函数，给予用户机会在一些特定的场景下添加他们自己的代码。\n\n<img :src=\"$withBase('/assets/lifecycle.png')\"/>\n\n在我们实际项目开发过程中，会非常频繁地和 Vue 组件的生命周期打交道，接下来我们就从源码的角度来看一下这些生命周期的钩子函数是如何被执行的。\n\n源码中最终执行生命周期的函数都是调用 `callHook` 方法，它的定义在 `src/core/instance/lifecycle` 中：\n\n```js\nexport function callHook (vm: Component, hook: string) {\n  // #7573 disable dep collection when invoking lifecycle hooks\n  pushTarget()\n  const handlers = vm.$options[hook]\n  if (handlers) {\n    for (let i = 0, j = handlers.length; i < j; i++) {\n      try {\n        handlers[i].call(vm)\n      } catch (e) {\n        handleError(e, vm, `${hook} hook`)\n      }\n    }\n  }\n  if (vm._hasHookEvent) {\n    vm.$emit('hook:' + hook)\n  }\n  popTarget()\n}\n```\n\n`callHook` 函数的逻辑很简单，根据传入的字符串 `hook`，去拿到 `vm.$options[hook]` 对应的回调函数数组，然后遍历执行，执行的时候把 `vm` 作为函数执行的上下文。\n\n在上一节中，我们详细地介绍了 Vue.js 合并 `options` 的过程，各个阶段的生命周期的函数也被合并到 `vm.$options` 里，并且是一个数组。因此 `callhook` 函数的功能就是调用某个生命周期钩子注册的所有回调函数。\n\n了解了生命周期的执行方式后，接下来我们会具体介绍每一个生命周期函数它的调用时机。\n\n## beforeCreate & created\n\n`beforeCreate` 和 `created` 函数都是在实例化 `Vue` 的阶段，在 `_init` 方法中执行的，它的定义在 `src/core/instance/init.js` 中：\n\n```js\nVue.prototype._init = function (options?: Object) {\n  // ...\n  initLifecycle(vm)\n  initEvents(vm)\n  initRender(vm)\n  callHook(vm, 'beforeCreate')\n  initInjections(vm) // resolve injections before data/props\n  initState(vm)\n  initProvide(vm) // resolve provide after data/props\n  callHook(vm, 'created')\n  // ...\n}\n```\n\n可以看到 `beforeCreate` 和 `created` 的钩子调用是在 `initState` 的前后，`initState` 的作用是初始化 `props`、`data`、`methods`、`watch`、`computed` 等属性，之后我们会详细分析。那么显然 `beforeCreate` 的钩子函数中就不能获取到 `props`、`data` 中定义的值，也不能调用 `methods` 中定义的函数。\n\n在这俩个钩子函数执行的时候，并没有渲染 DOM，所以我们也不能够访问 DOM，一般来说，如果组件在加载的时候需要和后端有交互，放在这俩个钩子函数执行都可以，如果是需要访问 `props`、`data` 等数据的话，就需要使用 `created` 钩子函数。之后我们会介绍 vue-router 和 vuex 的时候会发现它们都混合了 `beforeCreate` 钩子函数。\n\n## beforeMount & mounted\n\n顾名思义，`beforeMount` 钩子函数发生在 `mount`，也就是 DOM 挂载之前，它的调用时机是在 `mountComponent` 函数中，定义在 `src/core/instance/lifecycle.js` 中：\n\n```js\nexport function mountComponent (\n  vm: Component,\n  el: ?Element,\n  hydrating?: boolean\n): Component {\n  vm.$el = el\n  // ...\n  callHook(vm, 'beforeMount')\n\n  let updateComponent\n  /* istanbul ignore if */\n  if (process.env.NODE_ENV !== 'production' && config.performance && mark) {\n    updateComponent = () => {\n      const name = vm._name\n      const id = vm._uid\n      const startTag = `vue-perf-start:${id}`\n      const endTag = `vue-perf-end:${id}`\n\n      mark(startTag)\n      const vnode = vm._render()\n      mark(endTag)\n      measure(`vue ${name} render`, startTag, endTag)\n\n      mark(startTag)\n      vm._update(vnode, hydrating)\n      mark(endTag)\n      measure(`vue ${name} patch`, startTag, endTag)\n    }\n  } else {\n    updateComponent = () => {\n      vm._update(vm._render(), hydrating)\n    }\n  }\n\n  // we set this to vm._watcher inside the watcher's constructor\n  // since the watcher's initial patch may call $forceUpdate (e.g. inside child\n  // component's mounted hook), which relies on vm._watcher being already defined\n  new Watcher(vm, updateComponent, noop, {\n    before () {\n      if (vm._isMounted) {\n        callHook(vm, 'beforeUpdate')\n      }\n    }\n  }, true /* isRenderWatcher */)\n  hydrating = false\n\n  // manually mounted instance, call mounted on self\n  // mounted is called for render-created child components in its inserted hook\n  if (vm.$vnode == null) {\n    vm._isMounted = true\n    callHook(vm, 'mounted')\n  }\n  return vm\n}\n```\n在执行 `vm._render()` 函数渲染 VNode 之前，执行了 `beforeMount` 钩子函数，在执行完 `vm._update()` 把 VNode patch 到真实 DOM 后，执行 `mounted` 钩子。注意，这里对 `mounted` 钩子函数执行有一个判断逻辑，`vm.$vnode` 如果为 `null`，则表明这不是一次组件的初始化过程，而是我们通过外部 `new Vue` 初始化过程。那么对于组件，它的 `mounted` 时机在哪儿呢？\n\n之前我们提到过，组件的 VNode patch 到 DOM 后，会执行 `invokeInsertHook` 函数，把 `insertedVnodeQueue` 里保存的钩子函数依次执行一遍，它的定义在 `src/core/vdom/patch.js` 中：\n\n```js\nfunction invokeInsertHook (vnode, queue, initial) {\n  // delay insert hooks for component root nodes, invoke them after the\n  // element is really inserted\n  if (isTrue(initial) && isDef(vnode.parent)) {\n    vnode.parent.data.pendingInsert = queue\n  } else {\n    for (let i = 0; i < queue.length; ++i) {\n      queue[i].data.hook.insert(queue[i])\n    }\n  }\n}\n```\n该函数会执行 `insert` 这个钩子函数，对于组件而言，`insert` 钩子函数的定义在 `src/core/vdom/create-component.js` 中的 `componentVNodeHooks` 中：\n\n```js\nconst componentVNodeHooks = {\n  // ...\n  insert (vnode: MountedComponentVNode) {\n    const { context, componentInstance } = vnode\n    if (!componentInstance._isMounted) {\n      componentInstance._isMounted = true\n      callHook(componentInstance, 'mounted')\n    }\n    // ...\n  },\n}\n```\n我们可以看到，每个子组件都是在这个钩子函数中执行 `mounted` 钩子函数，并且我们之前分析过，`insertedVnodeQueue` 的添加顺序是先子后父，所以对于同步渲染的子组件而言，`mounted` 钩子函数的执行顺序也是先子后父。 \n\n## beforeUpdate & updated\n\n顾名思义，`beforeUpdate` 和 `updated` 的钩子函数执行时机都应该是在数据更新的时候，到目前为止，我们还没有分析 Vue 的数据双向绑定、更新相关，下一章我会详细介绍这个过程。\n\n`beforeUpdate` 的执行时机是在渲染 Watcher 的 `before` 函数中，我们刚才提到过：\n\n```js\nexport function mountComponent (\n  vm: Component,\n  el: ?Element,\n  hydrating?: boolean\n): Component {\n  // ...\n\n  // we set this to vm._watcher inside the watcher's constructor\n  // since the watcher's initial patch may call $forceUpdate (e.g. inside child\n  // component's mounted hook), which relies on vm._watcher being already defined\n  new Watcher(vm, updateComponent, noop, {\n    before () {\n      if (vm._isMounted) {\n        callHook(vm, 'beforeUpdate')\n      }\n    }\n  }, true /* isRenderWatcher */)\n  // ...\n}\n\n```\n注意这里有个判断，也就是在组件已经 `mounted` 之后，才会去调用这个钩子函数。\n\n`update` 的执行时机是在`flushSchedulerQueue` 函数调用的时候，它的定义在 `src/core/observer/scheduler.js` 中：\n\n```js\nfunction flushSchedulerQueue () {\n  // ...\n  // 获取到 updatedQueue\n  callUpdatedHooks(updatedQueue)\n}\n\nfunction callUpdatedHooks (queue) {\n  let i = queue.length\n  while (i--) {\n    const watcher = queue[i]\n    const vm = watcher.vm\n    if (vm._watcher === watcher && vm._isMounted) {\n      callHook(vm, 'updated')\n    }\n  }\n}\n```\n\n`flushSchedulerQueue` 函数我们之后会详细介绍，可以先大概了解一下，`updatedQueue` 是更新了的 `wathcer` 数组，那么在 `callUpdatedHooks` 函数中，它对这些数组做遍历，只有满足当前 `watcher` 为 `vm._watcher` 以及组件已经 `mounted` 这两个条件，才会执行 `updated` 钩子函数。\n\n我们之前提过，在组件 mount 的过程中，会实例化一个渲染的 `Watcher` 去监听 `vm` 上的数据变化重新渲染，这段逻辑发生在 `mountComponent` 函数执行的时候：\n\n```js\nexport function mountComponent (\n  vm: Component,\n  el: ?Element,\n  hydrating?: boolean\n): Component {\n  // ...\n  // 这里是简写\n  let updateComponent = () => {\n      vm._update(vm._render(), hydrating)\n  }\n  new Watcher(vm, updateComponent, noop, {\n    before () {\n      if (vm._isMounted) {\n        callHook(vm, 'beforeUpdate')\n      }\n    }\n  }, true /* isRenderWatcher */)\n  // ...\n}\n```\n那么在实例化 `Watcher` 的过程中，在它的构造函数里会判断 `isRenderWatcher`，接着把当前 `watcher` 的实例赋值给 `vm._watcher`，定义在 `src/core/observer/watcher.js` 中：\n\n```js\nexport default class Watcher {\n  // ...\n  constructor (\n    vm: Component,\n    expOrFn: string | Function,\n    cb: Function,\n    options?: ?Object,\n    isRenderWatcher?: boolean\n  ) {\n    this.vm = vm\n    if (isRenderWatcher) {\n      vm._watcher = this\n    }\n    vm._watchers.push(this)\n    // ...\n  }\n}\n```\n\n同时，还把当前 `wathcer` 实例 push 到 `vm._watchers` 中，`vm._watcher` 是专门用来监听 `vm` 上数据变化然后重新渲染的，所以它是一个渲染相关的 `watcher`，因此在 `callUpdatedHooks` 函数中，只有 `vm._watcher` 的回调执行完毕后，才会执行 `updated` 钩子函数。\n\n## beforeDestroy & destroyed\n\n顾名思义，`beforeDestroy` 和 `destroyed` 钩子函数的执行时机在组件销毁的阶段，组件的销毁过程之后会详细介绍，最终会调用 `$destroy` 方法，它的定义在 `src/core/instance/lifecycle.js` 中：\n \n```js\nVue.prototype.$destroy = function () {\n    const vm: Component = this\n    if (vm._isBeingDestroyed) {\n      return\n    }\n    callHook(vm, 'beforeDestroy')\n    vm._isBeingDestroyed = true\n    // remove self from parent\n    const parent = vm.$parent\n    if (parent && !parent._isBeingDestroyed && !vm.$options.abstract) {\n      remove(parent.$children, vm)\n    }\n    // teardown watchers\n    if (vm._watcher) {\n      vm._watcher.teardown()\n    }\n    let i = vm._watchers.length\n    while (i--) {\n      vm._watchers[i].teardown()\n    }\n    // remove reference from data ob\n    // frozen object may not have observer.\n    if (vm._data.__ob__) {\n      vm._data.__ob__.vmCount--\n    }\n    // call the last hook...\n    vm._isDestroyed = true\n    // invoke destroy hooks on current rendered tree\n    vm.__patch__(vm._vnode, null)\n    // fire destroyed hook\n    callHook(vm, 'destroyed')\n    // turn off all instance listeners.\n    vm.$off()\n    // remove __vue__ reference\n    if (vm.$el) {\n      vm.$el.__vue__ = null\n    }\n    // release circular reference (#6759)\n    if (vm.$vnode) {\n      vm.$vnode.parent = null\n    }\n  }\n```\n`beforeDestroy` 钩子函数的执行时机是在 `$destroy` 函数执行最开始的地方，接着执行了一系列的销毁动作，包括从 `parent` 的 `$children` 中删掉自身，删除 `watcher`，当前渲染的 VNode 执行销毁钩子函数等，执行完毕后再调用 `destroy` 钩子函数。\n\n在 `$destroy` 的执行过程中，它又会执行 ` vm.__patch__(vm._vnode, null)` 触发它子组件的销毁钩子函数，这样一层层的递归调用，所以 `destroy` 钩子函数执行顺序是先子后父，和 `mounted` 过程一样。\n\n## activated & deactivated\n\n`activated` 和 `deactivated` 钩子函数是专门为 `keep-alive` 组件定制的钩子，我们会在介绍 `keep-alive` 组件的时候详细介绍，这里先留个悬念。\n\n## 总结\n\n这一节主要介绍了 Vue 生命周期中各个钩子函数的执行时机以及顺序，通过分析，我们知道了如在 `created` 钩子函数中可以访问到数据，在 `mounted` 钩子函数中可以访问到 DOM，在 `destroy` 钩子函数中可以做一些定时器销毁工作，了解它们有利于我们在合适的生命周期去做不同的事情。\n\n"
  },
  {
    "path": "docs/v2/components/merge-option.md",
    "content": "# 合并配置\n\n通过之前章节的源码分析我们知道，`new Vue` 的过程通常有 2 种场景，一种是外部我们的代码主动调用 `new Vue(options)` 的方式实例化一个 Vue 对象；另一种是我们上一节分析的组件过程中内部通过 `new Vue(options)` 实例化子组件。\n\n无论哪种场景，都会执行实例的 `_init(options)` 方法，它首先会执行一个 ` merge options` 的逻辑，相关的代码在 `src/core/instance/init.js` 中：\n\n```js\nVue.prototype._init = function (options?: Object) {\n  // merge options\n  if (options && options._isComponent) {\n    // optimize internal component instantiation\n    // since dynamic options merging is pretty slow, and none of the\n    // internal component options needs special treatment.\n    initInternalComponent(vm, options)\n  } else {\n    vm.$options = mergeOptions(\n      resolveConstructorOptions(vm.constructor),\n      options || {},\n      vm\n    )\n  }\n  // ...\n}\n```\n\n可以看到不同场景对于 `options` 的合并逻辑是不一样的，并且传入的 `options` 值也有非常大的不同，接下来我会分开介绍 2 种场景的 options 合并过程。\n\n为了更直观，我们可以举个简单的示例：\n\n```js\nimport Vue from 'vue'\n\nlet childComp = {\n  template: '<div>{{msg}}</div>',\n  created() {\n    console.log('child created')\n  },\n  mounted() {\n    console.log('child mounted')\n  },\n  data() {\n    return {\n      msg: 'Hello Vue'\n    }\n  }\n}\n\nVue.mixin({\n  created() {\n    console.log('parent created')\n  }\n})\n\nlet app = new Vue({\n  el: '#app',\n  render: h => h(childComp)\n})\n```\n\n## 外部调用场景\n\n当执行 `new Vue` 的时候，在执行 `this._init(options)` 的时候，就会执行如下逻辑去合并 `options`：\n\n```js\nvm.$options = mergeOptions(\n  resolveConstructorOptions(vm.constructor),\n  options || {},\n  vm\n)\n```\n\n这里通过调用 `mergeOptions` 方法来合并，它实际上就是把 `resolveConstructorOptions(vm.constructor)` 的返回值和 `options` 做合并，`resolveConstructorOptions` 的实现先不考虑，在我们这个场景下，它还是简单返回 `vm.constructor.options`，相当于 `Vue.options`，那么这个值又是什么呢，其实在 `initGlobalAPI(Vue)` 的时候定义了这个值，代码在 `src/core/global-api/index.js` 中：\n\n```js\nexport function initGlobalAPI (Vue: GlobalAPI) {\n  // ...\n  Vue.options = Object.create(null)\n  ASSET_TYPES.forEach(type => {\n    Vue.options[type + 's'] = Object.create(null)\n  })\n\n  // this is used to identify the \"base\" constructor to extend all plain-object\n  // components with in Weex's multi-instance scenarios.\n  Vue.options._base = Vue\n\n  extend(Vue.options.components, builtInComponents)\n  // ...\n}\n```\n首先通过 `Vue.options = Object.create(null)` 创建一个空对象，然后遍历 `ASSET_TYPES`，`ASSET_TYPES` 的定义在 `src/shared/constants.js` 中：\n\n```js\nexport const ASSET_TYPES = [\n  'component',\n  'directive',\n  'filter'\n]\n```\n\n所以上面遍历 `ASSET_TYPES` 后的代码相当于：\n\n```js\nVue.options.components = {}\nVue.options.directives = {}\nVue.options.filters = {}\n```\n接着执行了 `Vue.options._base = Vue`，它的作用在我们上节实例化子组件的时候介绍了。\n\n最后通过 `extend(Vue.options.components, builtInComponents)` 把一些内置组件扩展到 `Vue.options.components` 上，Vue 的内置组件目前有 `<keep-alive>`、`<transition>` 和 `<transition-group>` 组件，这也就是为什么我们在其它组件中使用 `<keep-alive>` 组件不需要注册的原因，这块儿后续我们介绍 `<keep-alive>` 组件的时候会详细讲。\n\n那么回到 `mergeOptions` 这个函数，它的定义在 `src/core/util/options.js` 中：\n\n```js\n/**\n * Merge two option objects into a new one.\n * Core utility used in both instantiation and inheritance.\n */\nexport function mergeOptions (\n  parent: Object,\n  child: Object,\n  vm?: Component\n): Object {\n  if (process.env.NODE_ENV !== 'production') {\n    checkComponents(child)\n  }\n\n  if (typeof child === 'function') {\n    child = child.options\n  }\n\n  normalizeProps(child, vm)\n  normalizeInject(child, vm)\n  normalizeDirectives(child)\n  const extendsFrom = child.extends\n  if (extendsFrom) {\n    parent = mergeOptions(parent, extendsFrom, vm)\n  }\n  if (child.mixins) {\n    for (let i = 0, l = child.mixins.length; i < l; i++) {\n      parent = mergeOptions(parent, child.mixins[i], vm)\n    }\n  }\n  const options = {}\n  let key\n  for (key in parent) {\n    mergeField(key)\n  }\n  for (key in child) {\n    if (!hasOwn(parent, key)) {\n      mergeField(key)\n    }\n  }\n  function mergeField (key) {\n    const strat = strats[key] || defaultStrat\n    options[key] = strat(parent[key], child[key], vm, key)\n  }\n  return options\n}\n```\n\n`mergeOptions` 主要功能就是把 `parent` 和 `child` 这两个对象根据一些合并策略，合并成一个新对象并返回。比较核心的几步，先递归把 `extends` 和 `mixins` 合并到 `parent` 上，然后遍历 `parent`，调用 `mergeField`，然后再遍历 `child`，如果 `key` 不在 `parent` 的自身属性上，则调用 `mergeField`。\n\n这里有意思的是 `mergeField` 函数，它对不同的 `key` 有着不同的合并策略。举例来说，对于生命周期函数，它的合并策略是这样的：\n\n```js\nfunction mergeHook (\n  parentVal: ?Array<Function>,\n  childVal: ?Function | ?Array<Function>\n): ?Array<Function> {\n  return childVal\n    ? parentVal\n      ? parentVal.concat(childVal)\n      : Array.isArray(childVal)\n        ? childVal\n        : [childVal]\n    : parentVal\n}\n\nLIFECYCLE_HOOKS.forEach(hook => {\n  strats[hook] = mergeHook\n})\n```\n这其中的 `LIFECYCLE_HOOKS` 的定义在 `src/shared/constants.js` 中：\n\n```js\nexport const LIFECYCLE_HOOKS = [\n  'beforeCreate',\n  'created',\n  'beforeMount',\n  'mounted',\n  'beforeUpdate',\n  'updated',\n  'beforeDestroy',\n  'destroyed',\n  'activated',\n  'deactivated',\n  'errorCaptured'\n]\n```\n这里定义了 Vue.js 所有的钩子函数名称，所以对于钩子函数，他们的合并策略都是 `mergeHook` 函数。这个函数的实现也非常有意思，用了一个多层 3 元运算符，逻辑就是如果不存在 `childVal` ，就返回 `parentVal`；否则再判断是否存在 `parentVal`，如果存在就把 `childVal` 添加到 `parentVal` 后返回新数组；否则返回 `childVal` 的数组。所以回到 `mergeOptions` 函数，一旦 `parent` 和 `child` 都定义了相同的钩子函数，那么它们会把 2 个钩子函数合并成一个数组。\n\n关于其它属性的合并策略的定义都可以在 `src/core/util/options.js` 文件中看到，这里不一一介绍了，感兴趣的同学可以自己看。\n\n通过执行 `mergeField` 函数，把合并后的结果保存到 `options` 对象中，最终返回它。\n\n因此，在我们当前这个 case 下，执行完如下合并后：\n```js\nvm.$options = mergeOptions(\n  resolveConstructorOptions(vm.constructor),\n  options || {},\n  vm\n)\n```\n`vm.$options` 的值差不多是如下这样：\n\n```js\nvm.$options = {\n  components: { },\n  created: [\n    function created() {\n      console.log('parent created')\n    }\n  ],\n  directives: { },\n  filters: { },\n  _base: function Vue(options) {\n    // ...\n  },\n  el: \"#app\",\n  render: function (h) {\n    //...\n  }\n}\n```\n\n## 组件场景\n\n由于组件的构造函数是通过 `Vue.extend` 继承自 `Vue` 的，先回顾一下这个过程，代码定义在 `src/core/global-api/extend.js` 中。\n\n```js\n/**\n * Class inheritance\n */\nVue.extend = function (extendOptions: Object): Function {\n  // ...\n  Sub.options = mergeOptions(\n    Super.options,\n    extendOptions\n  )\n\n  // ...\n  // keep a reference to the super options at extension time.\n  // later at instantiation we can check if Super's options have\n  // been updated.\n  Sub.superOptions = Super.options\n  Sub.extendOptions = extendOptions\n  Sub.sealedOptions = extend({}, Sub.options)\n\n  // ...\n  return Sub\n}\n```\n我们只保留关键逻辑，这里的 `extendOptions` 对应的就是前面定义的组件对象，它会和 `Vue.options` 合并到 `Sub.opitons` 中。\n\n接下来我们再回忆一下子组件的初始化过程，代码定义在 `src/core/vdom/create-component.js` 中：\n\n```js\nexport function createComponentInstanceForVnode (\n  vnode: any, // we know it's MountedComponentVNode but flow doesn't\n  parent: any, // activeInstance in lifecycle state\n): Component {\n  const options: InternalComponentOptions = {\n    _isComponent: true,\n    _parentVnode: vnode,\n    parent\n  }\n  // ...\n  return new vnode.componentOptions.Ctor(options)\n}\n```\n\n这里的 `vnode.componentOptions.Ctor` 就是指向 `Vue.extend` 的返回值 `Sub`， 所以 执行 `new vnode.componentOptions.Ctor(options)` 接着执行 `this._init(options)`，因为 `options._isComponent` 为 true，那么合并 `options` 的过程走到了 ` initInternalComponent(vm, options)` 逻辑。先来看一下它的代码实现，在 `src/core/instance/init.js` 中：\n\n```js\nexport function initInternalComponent (vm: Component, options: InternalComponentOptions) {\n  const opts = vm.$options = Object.create(vm.constructor.options)\n  // doing this because it's faster than dynamic enumeration.\n  const parentVnode = options._parentVnode\n  opts.parent = options.parent\n  opts._parentVnode = parentVnode\n\n  const vnodeComponentOptions = parentVnode.componentOptions\n  opts.propsData = vnodeComponentOptions.propsData\n  opts._parentListeners = vnodeComponentOptions.listeners\n  opts._renderChildren = vnodeComponentOptions.children\n  opts._componentTag = vnodeComponentOptions.tag\n\n  if (options.render) {\n    opts.render = options.render\n    opts.staticRenderFns = options.staticRenderFns\n  }\n}\n```\n\n`initInternalComponent` 方法首先执行 `const opts = vm.$options = Object.create(vm.constructor.options)`，这里的 `vm.constructor` 就是子组件的构造函数 `Sub`，相当于 `vm.$options = Object.create(Sub.options)`。\n\n接着又把实例化子组件传入的子组件父 VNode 实例 `parentVnode`、子组件的父 Vue 实例 `parent` 保存到 `vm.$options` 中，另外还保留了 `parentVnode` 配置中的如 `propsData` 等其它的属性。\n\n这么看来，`initInternalComponent` 只是做了简单一层对象赋值，并不涉及到递归、合并策略等复杂逻辑。\n\n因此，在我们当前这个 case 下，执行完如下合并后：\n\n```js\ninitInternalComponent(vm, options)\n```\n\n`vm.$options` 的值差不多是如下这样：\n\n```js\nvm.$options = {\n  parent: Vue /*父Vue实例*/,\n  propsData: undefined,\n  _componentTag: undefined,\n  _parentVnode: VNode /*父VNode实例*/,\n  _renderChildren:undefined,\n  __proto__: {\n    components: { },\n    directives: { },\n    filters: { },\n    _base: function Vue(options) {\n        //...\n    },\n    _Ctor: {},\n    created: [\n      function created() {\n        console.log('parent created')\n      }, function created() {\n        console.log('child created')\n      }\n    ],\n    mounted: [\n      function mounted() {\n        console.log('child mounted')\n      }\n    ],\n    data() {\n       return {\n         msg: 'Hello Vue'\n       }\n    },\n    template: '<div>{{msg}}</div>'\n  }\n}\n```\n\n## 总结\n\n那么至此，Vue 初始化阶段对于 `options` 的合并过程就介绍完了，我们需要知道对于 `options` 的合并有 2 种方式，子组件初始化过程通过 `initInternalComponent` 方式要比外部初始化 Vue 通过 `mergeOptions` 的过程要快，合并完的结果保留在 `vm.$options` 中。\n\n纵观一些库、框架的设计几乎都是类似的，自身定义了一些默认配置，同时又可以在初始化阶段传入一些定义配置，然后去 merge 默认配置，来达到定制化不同需求的目的。只不过在 Vue 的场景下，会对 merge 的过程做一些精细化控制，虽然我们在开发自己的 JSSDK 的时候并没有 Vue 这么复杂，但这个设计思想是值得我们借鉴的。\n\n\n"
  },
  {
    "path": "docs/v2/components/patch.md",
    "content": "# patch\n\n通过前一章的分析我们知道，当我们通过 `createComponent` 创建了组件 VNode，接下来会走到 `vm._update`，执行 `vm.__patch__` 去把 VNode 转换成真正的 DOM 节点。这个过程我们在前一章已经分析过了，但是针对一个普通的 VNode 节点，接下来我们来看看组件的 VNode 会有哪些不一样的地方。\n\npatch 的过程会调用 `createElm` 创建元素节点，回顾一下 `createElm` 的实现，它的定义在 `src/core/vdom/patch.js` 中：\n\n```js\nfunction createElm (\n  vnode,\n  insertedVnodeQueue,\n  parentElm,\n  refElm,\n  nested,\n  ownerArray,\n  index\n) {\n  // ...\n  if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {\n    return\n  }\n  // ...\n}\n```\n\n## createComponent\n\n我们删掉多余的代码，只保留关键的逻辑，这里会判断 `createComponent(vnode, insertedVnodeQueue, parentElm, refElm)` 的返回值，如果为 `true` 则直接结束，那么接下来看一下 `createComponent` 方法的实现：\n\n```js\nfunction createComponent (vnode, insertedVnodeQueue, parentElm, refElm) {\n  let i = vnode.data\n  if (isDef(i)) {\n    const isReactivated = isDef(vnode.componentInstance) && i.keepAlive\n    if (isDef(i = i.hook) && isDef(i = i.init)) {\n      i(vnode, false /* hydrating */)\n    }\n    // after calling the init hook, if the vnode is a child component\n    // it should've created a child instance and mounted it. the child\n    // component also has set the placeholder vnode's elm.\n    // in that case we can just return the element and be done.\n    if (isDef(vnode.componentInstance)) {\n      initComponent(vnode, insertedVnodeQueue)\n      insert(parentElm, vnode.elm, refElm)\n      if (isTrue(isReactivated)) {\n        reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm)\n      }\n      return true\n    }\n  }\n}\n```\n\n`createComponent` 函数中，首先对 `vnode.data` 做了一些判断：\n\n```js\nlet i = vnode.data\nif (isDef(i)) {\n  // ...\n  if (isDef(i = i.hook) && isDef(i = i.init)) {\n    i(vnode, false /* hydrating */)\n    // ...\n  }\n  // ..\n}\n```\n\n如果 `vnode` 是一个组件 VNode，那么条件会满足，并且得到 `i` 就是 `init` 钩子函数，回顾上节我们在创建组件 VNode 的时候合并钩子函数中就包含 `init` 钩子函数，定义在 `src/core/vdom/create-component.js` 中：\n\n```js\ninit (vnode: VNodeWithData, hydrating: boolean): ?boolean {\n  if (\n    vnode.componentInstance &&\n    !vnode.componentInstance._isDestroyed &&\n    vnode.data.keepAlive\n  ) {\n    // kept-alive components, treat as a patch\n    const mountedNode: any = vnode // work around flow\n    componentVNodeHooks.prepatch(mountedNode, mountedNode)\n  } else {\n    const child = vnode.componentInstance = createComponentInstanceForVnode(\n      vnode,\n      activeInstance\n    )\n    child.$mount(hydrating ? vnode.elm : undefined, hydrating)\n  }\n},\n```\n\n`init` 钩子函数执行也很简单，我们先不考虑 `keepAlive` 的情况，它是通过 `createComponentInstanceForVnode` 创建一个 Vue 的实例，然后调用 `$mount` 方法挂载子组件，\n先来看一下 `createComponentInstanceForVnode` 的实现：\n\n```js\nexport function createComponentInstanceForVnode (\n  vnode: any, // we know it's MountedComponentVNode but flow doesn't\n  parent: any, // activeInstance in lifecycle state\n): Component {\n  const options: InternalComponentOptions = {\n    _isComponent: true,\n    _parentVnode: vnode,\n    parent\n  }\n  // check inline-template render functions\n  const inlineTemplate = vnode.data.inlineTemplate\n  if (isDef(inlineTemplate)) {\n    options.render = inlineTemplate.render\n    options.staticRenderFns = inlineTemplate.staticRenderFns\n  }\n  return new vnode.componentOptions.Ctor(options)\n}\n```\n\n`createComponentInstanceForVnode` 函数构造的一个内部组件的参数，然后执行 `new vnode.componentOptions.Ctor(options)`。这里的 `vnode.componentOptions.Ctor` 对应的就是子组件的构造函数，我们上一节分析了它实际上是继承于 Vue 的一个构造器 `Sub`，相当于 `new Sub(options)` 这里有几个关键参数要注意几个点，`_isComponent` 为 `true` 表示它是一个组件，`parent` 表示当前激活的组件实例（注意，这里比较有意思的是如何拿到组件实例，后面会介绍。\n\n所以子组件的实例化实际上就是在这个时机执行的，并且它会执行实例的 `_init` 方法，这个过程有一些和之前不同的地方需要挑出来说，代码在 `src/core/instance/init.js` 中：\n\n```js\nVue.prototype._init = function (options?: Object) {\n  const vm: Component = this\n  // merge options\n  if (options && options._isComponent) {\n    // optimize internal component instantiation\n    // since dynamic options merging is pretty slow, and none of the\n    // internal component options needs special treatment.\n    initInternalComponent(vm, options)\n  } else {\n    vm.$options = mergeOptions(\n      resolveConstructorOptions(vm.constructor),\n      options || {},\n      vm\n    )\n  }\n  // ...\n  if (vm.$options.el) {\n    vm.$mount(vm.$options.el)\n  } \n}\n```\n\n这里首先是合并 `options` 的过程有变化，`_isComponent` 为 true，所以走到了 `initInternalComponent` 过程，这个函数的实现也简单看一下：\n\n```js\nexport function initInternalComponent (vm: Component, options: InternalComponentOptions) {\n  const opts = vm.$options = Object.create(vm.constructor.options)\n  // doing this because it's faster than dynamic enumeration.\n  const parentVnode = options._parentVnode\n  opts.parent = options.parent\n  opts._parentVnode = parentVnode\n\n  const vnodeComponentOptions = parentVnode.componentOptions\n  opts.propsData = vnodeComponentOptions.propsData\n  opts._parentListeners = vnodeComponentOptions.listeners\n  opts._renderChildren = vnodeComponentOptions.children\n  opts._componentTag = vnodeComponentOptions.tag\n\n  if (options.render) {\n    opts.render = options.render\n    opts.staticRenderFns = options.staticRenderFns\n  }\n}\n```\n这个过程我们重点记住以下几个点即可：`opts.parent = options.parent`、`opts._parentVnode = parentVnode`，它们是把之前我们通过 `createComponentInstanceForVnode` 函数传入的几个参数合并到内部的选项 `$options` 里了。\n\n再来看一下 `_init` 函数最后执行的代码：\n\n```js\nif (vm.$options.el) {\n   vm.$mount(vm.$options.el)\n}\n```\n由于组件初始化的时候是不传 el 的，因此组件是自己接管了 `$mount` 的过程，这个过程的主要流程在上一章介绍过了，回到组件 `init` 的过程，`componentVNodeHooks` 的 `init` 钩子函数，在完成实例化的 `_init` 后，接着会执行 `child.$mount(hydrating ? vnode.elm : undefined, hydrating)` 。这里 `hydrating` 为 true 一般是服务端渲染的情况，我们只考虑客户端渲染，所以这里 `$mount` 相当于执行 `child.$mount(undefined, false)`，它最终会调用 `mountComponent` 方法，进而执行 `vm._render()` 方法：\n\n```js\nVue.prototype._render = function (): VNode {\n  const vm: Component = this\n  const { render, _parentVnode } = vm.$options\n\n  \n  // set parent vnode. this allows render functions to have access\n  // to the data on the placeholder node.\n  vm.$vnode = _parentVnode\n  // render self\n  let vnode\n  try {\n    vnode = render.call(vm._renderProxy, vm.$createElement)\n  } catch (e) {\n    // ...\n  }\n  // set parent\n  vnode.parent = _parentVnode\n  return vnode\n}\n```\n\n我们只保留关键部分的代码，这里的 `_parentVnode` 就是当前组件的父 VNode，而 `render` 函数生成的 `vnode` 当前组件的渲染 `vnode`，`vnode` 的 `parent` 指向了 `_parentVnode`，也就是 `vm.$vnode`，它们是一种父子的关系。\n\n我们知道在执行完 `vm._render` 生成 VNode 后，接下来就要执行 `vm._update` 去渲染 VNode 了。来看一下组件渲染的过程中有哪些需要注意的，`vm._update` 的定义在 `src/core/instance/lifecycle.js` 中：\n\n```js\nexport let activeInstance: any = null\nVue.prototype._update = function (vnode: VNode, hydrating?: boolean) {\n  const vm: Component = this\n  const prevEl = vm.$el\n  const prevVnode = vm._vnode\n  const prevActiveInstance = activeInstance\n  activeInstance = vm\n  vm._vnode = vnode\n  // Vue.prototype.__patch__ is injected in entry points\n  // based on the rendering backend used.\n  if (!prevVnode) {\n    // initial render\n    vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)\n  } else {\n    // updates\n    vm.$el = vm.__patch__(prevVnode, vnode)\n  }\n  activeInstance = prevActiveInstance\n  // update __vue__ reference\n  if (prevEl) {\n    prevEl.__vue__ = null\n  }\n  if (vm.$el) {\n    vm.$el.__vue__ = vm\n  }\n  // if parent is an HOC, update its $el as well\n  if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) {\n    vm.$parent.$el = vm.$el\n  }\n  // updated hook is called by the scheduler to ensure that children are\n  // updated in a parent's updated hook.\n}\n```\n`_update` 过程中有几个关键的代码，首先 `vm._vnode = vnode` 的逻辑，这个 `vnode` 是通过 `vm._render()` 返回的组件渲染 VNode，`vm._vnode` 和 `vm.$vnode` 的关系就是一种父子关系，用代码表达就是 `vm._vnode.parent === vm.$vnode`。还有一段比较有意思的代码：\n\n```js\nexport let activeInstance: any = null\nVue.prototype._update = function (vnode: VNode, hydrating?: boolean) {\n    // ...\n    const prevActiveInstance = activeInstance\n    activeInstance = vm\n    if (!prevVnode) {\n      // initial render\n      vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)\n    } else {\n      // updates\n      vm.$el = vm.__patch__(prevVnode, vnode)\n    }\n    activeInstance = prevActiveInstance\n    // ...\n}\n\n```\n这个 `activeInstance` 作用就是保持当前上下文的 Vue 实例，它是在 `lifecycle` 模块的全局变量，定义是 `export let activeInstance: any = null`，并且在之前我们调用 `createComponentInstanceForVnode` 方法的时候从 `lifecycle` 模块获取，并且作为参数传入的。因为实际上 JavaScript 是一个单线程，Vue 整个初始化是一个深度遍历的过程，在实例化子组件的过程中，它需要知道当前上下文的 Vue 实例是什么，并把它作为子组件的父 Vue 实例。之前我们提到过对子组件的实例化过程先会调用 `initInternalComponent(vm, options)` 合并 `options`，把 `parent` 存储在 `vm.$options` 中，在 `$mount` 之前会调用 `initLifecycle(vm)` 方法：\n\n```js\nexport function initLifecycle (vm: Component) {\n  const options = vm.$options\n\n  // locate first non-abstract parent\n  let parent = options.parent\n  if (parent && !options.abstract) {\n    while (parent.$options.abstract && parent.$parent) {\n      parent = parent.$parent\n    }\n    parent.$children.push(vm)\n  }\n\n  vm.$parent = parent\n  // ...\n}\n```\n可以看到 `vm.$parent` 就是用来保留当前 `vm` 的父实例，并且通过 `parent.$children.push(vm)` 来把当前的 `vm` 存储到父实例的 `$children` 中。\n\n在 `vm._update` 的过程中，把当前的 `vm` 赋值给 `activeInstance`，同时通过 `const prevActiveInstance = activeInstance` 用 `prevActiveInstance` 保留上一次的 `activeInstance`。实际上，`prevActiveInstance` 和当前的 `vm` 是一个父子关系，当一个 `vm` 实例完成它的所有子树的 patch 或者 update 过程后，`activeInstance` 会回到它的父实例，这样就完美地保证了 `createComponentInstanceForVnode` 整个深度遍历过程中，我们在实例化子组件的时候能传入当前子组件的父 Vue 实例，并在 `_init` 的过程中，通过 `vm.$parent` 把这个父子关系保留。\n\n那么回到 `_update`，最后就是调用 `__patch__` 渲染 VNode 了。\n\n```js\nvm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)\n \nfunction patch (oldVnode, vnode, hydrating, removeOnly) {\n  // ...\n  let isInitialPatch = false\n  const insertedVnodeQueue = []\n\n  if (isUndef(oldVnode)) {\n    // empty mount (likely as component), create new root element\n    isInitialPatch = true\n    createElm(vnode, insertedVnodeQueue)\n  } else {\n    // ...\n  }\n  // ...\n}\n\n```\n\n这里又回到了本节开始的过程，之前分析过负责渲染成 DOM 的函数是 `createElm`，注意这里我们只传了 2 个参数，所以对应的 `parentElm` 是 `undefined`。我们再来看看它的定义：\n\n```js\nfunction createElm (\n  vnode,\n  insertedVnodeQueue,\n  parentElm,\n  refElm,\n  nested,\n  ownerArray,\n  index\n) {\n  // ...\n  if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {\n    return\n  }\n\n  const data = vnode.data\n  const children = vnode.children\n  const tag = vnode.tag\n  if (isDef(tag)) {\n    // ...\n\n    vnode.elm = vnode.ns\n      ? nodeOps.createElementNS(vnode.ns, tag)\n      : nodeOps.createElement(tag, vnode)\n    setScope(vnode)\n\n    /* istanbul ignore if */\n    if (__WEEX__) {\n      // ...\n    } else {\n      createChildren(vnode, children, insertedVnodeQueue)\n      if (isDef(data)) {\n        invokeCreateHooks(vnode, insertedVnodeQueue)\n      }\n      insert(parentElm, vnode.elm, refElm)\n    }\n    \n    // ...\n  } else if (isTrue(vnode.isComment)) {\n    vnode.elm = nodeOps.createComment(vnode.text)\n    insert(parentElm, vnode.elm, refElm)\n  } else {\n    vnode.elm = nodeOps.createTextNode(vnode.text)\n    insert(parentElm, vnode.elm, refElm)\n  }\n}\n```\n注意，这里我们传入的 `vnode` 是组件渲染的 `vnode`，也就是我们之前说的 `vm._vnode`，如果组件的根节点是个普通元素，那么 `vm._vnode` 也是普通的 `vnode`，这里 `createComponent(vnode, insertedVnodeQueue, parentElm, refElm)` 的返回值是 false。接下来的过程就和我们上一章一样了，先创建一个父节点占位符，然后再遍历所有子 VNode 递归调用 `createElm`，在遍历的过程中，如果遇到子 VNode 是一个组件的 VNode，则重复本节开始的过程，这样通过一个递归的方式就可以完整地构建了整个组件树。\n\n由于我们这个时候传入的 `parentElm` 是空，所以对组件的插入，在 `createComponent` 有这么一段逻辑：\n\n```js\nfunction createComponent (vnode, insertedVnodeQueue, parentElm, refElm) {\n  let i = vnode.data\n  if (isDef(i)) {\n    // ....\n    if (isDef(i = i.hook) && isDef(i = i.init)) {\n      i(vnode, false /* hydrating */)\n    }\n    // ...\n    if (isDef(vnode.componentInstance)) {\n      initComponent(vnode, insertedVnodeQueue)\n      insert(parentElm, vnode.elm, refElm)\n      if (isTrue(isReactivated)) {\n        reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm)\n      }\n      return true\n    }\n  }\n}\n```\n\n在完成组件的整个 `patch` 过程后，最后执行 `insert(parentElm, vnode.elm, refElm)` 完成组件的 DOM 插入，如果组件 `patch` 过程中又创建了子组件，那么DOM 的插入顺序是先子后父。\n\n## 总结\n\n那么到此，一个组件的 VNode 是如何创建、初始化、渲染的过程也就介绍完毕了。在对组件化的实现有一个大概了解后，接下来我们来介绍一下这其中的一些细节。我们知道编写一个组件实际上是编写一个 JavaScript 对象，对象的描述就是各种配置，之前我们提到在 `_init` 的最初阶段执行的就是 `merge options` 的逻辑，那么下一节我们从源码角度来分析合并配置的过程。\n\n\n\n\n\n\n\n \n \n"
  },
  {
    "path": "docs/v2/data-driven/create-element.md",
    "content": "# createElement\n\nVue.js 利用 createElement 方法创建 VNode，它定义在 `src/core/vdom/create-element.js` 中：\n\n```js\n// wrapper function for providing a more flexible interface\n// without getting yelled at by flow\nexport function createElement (\n  context: Component,\n  tag: any,\n  data: any,\n  children: any,\n  normalizationType: any,\n  alwaysNormalize: boolean\n): VNode | Array<VNode> {\n  if (Array.isArray(data) || isPrimitive(data)) {\n    normalizationType = children\n    children = data\n    data = undefined\n  }\n  if (isTrue(alwaysNormalize)) {\n    normalizationType = ALWAYS_NORMALIZE\n  }\n  return _createElement(context, tag, data, children, normalizationType)\n}\n```\n\n`createElement` 方法实际上是对 `_createElement` 方法的封装，它允许传入的参数更加灵活，在处理这些参数后，调用真正创建 VNode 的函数 `_createElement`：\n\n```js\nexport function _createElement (\n  context: Component,\n  tag?: string | Class<Component> | Function | Object,\n  data?: VNodeData,\n  children?: any,\n  normalizationType?: number\n): VNode | Array<VNode> {\n  if (isDef(data) && isDef((data: any).__ob__)) {\n    process.env.NODE_ENV !== 'production' && warn(\n      `Avoid using observed data object as vnode data: ${JSON.stringify(data)}\\n` +\n      'Always create fresh vnode data objects in each render!',\n      context\n    )\n    return createEmptyVNode()\n  }\n  // object syntax in v-bind\n  if (isDef(data) && isDef(data.is)) {\n    tag = data.is\n  }\n  if (!tag) {\n    // in case of component :is set to falsy value\n    return createEmptyVNode()\n  }\n  // warn against non-primitive key\n  if (process.env.NODE_ENV !== 'production' &&\n    isDef(data) && isDef(data.key) && !isPrimitive(data.key)\n  ) {\n    if (!__WEEX__ || !('@binding' in data.key)) {\n      warn(\n        'Avoid using non-primitive value as key, ' +\n        'use string/number value instead.',\n        context\n      )\n    }\n  }\n  // support single function children as default scoped slot\n  if (Array.isArray(children) &&\n    typeof children[0] === 'function'\n  ) {\n    data = data || {}\n    data.scopedSlots = { default: children[0] }\n    children.length = 0\n  }\n  if (normalizationType === ALWAYS_NORMALIZE) {\n    children = normalizeChildren(children)\n  } else if (normalizationType === SIMPLE_NORMALIZE) {\n    children = simpleNormalizeChildren(children)\n  }\n  let vnode, ns\n  if (typeof tag === 'string') {\n    let Ctor\n    ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag)\n    if (config.isReservedTag(tag)) {\n      // platform built-in elements\n      vnode = new VNode(\n        config.parsePlatformTagName(tag), data, children,\n        undefined, undefined, context\n      )\n    } else if (isDef(Ctor = resolveAsset(context.$options, 'components', tag))) {\n      // component\n      vnode = createComponent(Ctor, data, context, children, tag)\n    } else {\n      // unknown or unlisted namespaced elements\n      // check at runtime because it may get assigned a namespace when its\n      // parent normalizes children\n      vnode = new VNode(\n        tag, data, children,\n        undefined, undefined, context\n      )\n    }\n  } else {\n    // direct component options / constructor\n    vnode = createComponent(tag, data, context, children)\n  }\n  if (Array.isArray(vnode)) {\n    return vnode\n  } else if (isDef(vnode)) {\n    if (isDef(ns)) applyNS(vnode, ns)\n    if (isDef(data)) registerDeepBindings(data)\n    return vnode\n  } else {\n    return createEmptyVNode()\n  }\n}\n```\n\n`_createElement` 方法有 5 个参数，`context` 表示 VNode 的上下文环境，它是 `Component` 类型；`tag` 表示标签，它可以是一个字符串，也可以是一个 `Component`；`data` 表示 VNode 的数据，它是一个 `VNodeData` 类型，可以在 `flow/vnode.js` 中找到它的定义，这里先不展开说；`children` 表示当前 VNode 的子节点，它是任意类型的，它接下来需要被规范为标准的 VNode 数组；`normalizationType` 表示子节点规范的类型，类型不同规范的方法也就不一样，它主要是参考 `render` 函数是编译生成的还是用户手写的。\n\n`createElement` 函数的流程略微有点多，我们接下来主要分析 2 个重点的流程 —— `children` 的规范化以及 VNode 的创建。\n\n## children 的规范化\n\n由于 Virtual DOM 实际上是一个树状结构，每一个 VNode 可能会有若干个子节点，这些子节点应该也是 VNode 的类型。`_createElement` 接收的第 4 个参数 children 是任意类型的，因此我们需要把它们规范成 VNode 类型。\n\n这里根据 `normalizationType` 的不同，调用了 `normalizeChildren(children)` 和 `simpleNormalizeChildren(children)` 方法，它们的定义都在 `src/core/vdom/helpers/normalzie-children.js` 中：\n\n```js\n// The template compiler attempts to minimize the need for normalization by\n// statically analyzing the template at compile time.\n//\n// For plain HTML markup, normalization can be completely skipped because the\n// generated render function is guaranteed to return Array<VNode>. There are\n// two cases where extra normalization is needed:\n\n// 1. When the children contains components - because a functional component\n// may return an Array instead of a single root. In this case, just a simple\n// normalization is needed - if any child is an Array, we flatten the whole\n// thing with Array.prototype.concat. It is guaranteed to be only 1-level deep\n// because functional components already normalize their own children.\nexport function simpleNormalizeChildren (children: any) {\n  for (let i = 0; i < children.length; i++) {\n    if (Array.isArray(children[i])) {\n      return Array.prototype.concat.apply([], children)\n    }\n  }\n  return children\n}\n\n// 2. When the children contains constructs that always generated nested Arrays,\n// e.g. <template>, <slot>, v-for, or when the children is provided by user\n// with hand-written render functions / JSX. In such cases a full normalization\n// is needed to cater to all possible types of children values.\nexport function normalizeChildren (children: any): ?Array<VNode> {\n  return isPrimitive(children)\n    ? [createTextVNode(children)]\n    : Array.isArray(children)\n      ? normalizeArrayChildren(children)\n      : undefined\n}\n```\n\n`simpleNormalizeChildren` 方法调用场景是 `render` 函数是编译生成的。理论上编译生成的 `children` 都已经是 VNode 类型的，但这里有一个例外，就是 `functional component` 函数式组件返回的是一个数组而不是一个根节点，所以会通过 `Array.prototype.concat` 方法把整个 `children` 数组打平，让它的深度只有一层。\n\n`normalizeChildren` 方法的调用场景有 2 种，一个场景是 `render` 函数是用户手写的，当 `children` 只有一个节点的时候，Vue.js 从接口层面允许用户把 `children` 写成基础类型用来创建单个简单的文本节点，这种情况会调用 `createTextVNode` 创建一个文本节点的 VNode；另一个场景是当编译 `slot`、`v-for` 的时候会产生嵌套数组的情况，会调用 `normalizeArrayChildren` 方法，接下来看一下它的实现：\n\n```js\nfunction normalizeArrayChildren (children: any, nestedIndex?: string): Array<VNode> {\n  const res = []\n  let i, c, lastIndex, last\n  for (i = 0; i < children.length; i++) {\n    c = children[i]\n    if (isUndef(c) || typeof c === 'boolean') continue\n    lastIndex = res.length - 1\n    last = res[lastIndex]\n    //  nested\n    if (Array.isArray(c)) {\n      if (c.length > 0) {\n        c = normalizeArrayChildren(c, `${nestedIndex || ''}_${i}`)\n        // merge adjacent text nodes\n        if (isTextNode(c[0]) && isTextNode(last)) {\n          res[lastIndex] = createTextVNode(last.text + (c[0]: any).text)\n          c.shift()\n        }\n        res.push.apply(res, c)\n      }\n    } else if (isPrimitive(c)) {\n      if (isTextNode(last)) {\n        // merge adjacent text nodes\n        // this is necessary for SSR hydration because text nodes are\n        // essentially merged when rendered to HTML strings\n        res[lastIndex] = createTextVNode(last.text + c)\n      } else if (c !== '') {\n        // convert primitive to vnode\n        res.push(createTextVNode(c))\n      }\n    } else {\n      if (isTextNode(c) && isTextNode(last)) {\n        // merge adjacent text nodes\n        res[lastIndex] = createTextVNode(last.text + c.text)\n      } else {\n        // default key for nested array children (likely generated by v-for)\n        if (isTrue(children._isVList) &&\n          isDef(c.tag) &&\n          isUndef(c.key) &&\n          isDef(nestedIndex)) {\n          c.key = `__vlist${nestedIndex}_${i}__`\n        }\n        res.push(c)\n      }\n    }\n  }\n  return res\n}\n\n```\n\n`normalizeArrayChildren` 接收 2 个参数，`children` 表示要规范的子节点，`nestedIndex` 表示嵌套的索引，因为单个 `child` 可能是一个数组类型。 `normalizeArrayChildren` 主要的逻辑就是遍历 `children`，获得单个节点 `c`，然后对 `c` 的类型判断，如果是一个数组类型，则递归调用 `normalizeArrayChildren`; 如果是基础类型，则通过 `createTextVNode` 方法转换成 VNode 类型；否则就已经是 VNode 类型了，如果 `children` 是一个列表并且列表还存在嵌套的情况，则根据 `nestedIndex` 去更新它的 key。这里需要注意一点，在遍历的过程中，对这 3 种情况都做了如下处理：如果存在两个连续的 `text` 节点，会把它们合并成一个 `text` 节点。\n\n经过对 `children` 的规范化，`children` 变成了一个类型为 VNode 的 Array。\n\n## VNode 的创建\n\n回到 `createElement` 函数，规范化 `children` 后，接下来会去创建一个 VNode 的实例：\n\n```js\nlet vnode, ns\nif (typeof tag === 'string') {\n  let Ctor\n  ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag)\n  if (config.isReservedTag(tag)) {\n    // platform built-in elements\n    vnode = new VNode(\n      config.parsePlatformTagName(tag), data, children,\n      undefined, undefined, context\n    )\n  } else if (isDef(Ctor = resolveAsset(context.$options, 'components', tag))) {\n    // component\n    vnode = createComponent(Ctor, data, context, children, tag)\n  } else {\n    // unknown or unlisted namespaced elements\n    // check at runtime because it may get assigned a namespace when its\n    // parent normalizes children\n    vnode = new VNode(\n      tag, data, children,\n      undefined, undefined, context\n    )\n  }\n} else {\n  // direct component options / constructor\n  vnode = createComponent(tag, data, context, children)\n}\n```\n\n这里先对 `tag` 做判断，如果是 `string` 类型，则接着判断如果是内置的一些节点，则直接创建一个普通 VNode，如果是为已注册的组件名，则通过 `createComponent` 创建一个组件类型的 VNode，否则创建一个未知的标签的 VNode。 如果 `tag` 是一个 `Component` 类型，则直接调用 `createComponent` 创建一个组件类型的 VNode 节点。对于 `createComponent` 创建组件类型的 VNode 的过程，我们之后会去介绍，本质上它还是返回了一个 VNode。\n\n## 总结\n\n那么至此，我们大致了解了 `createElement` 创建 VNode 的过程，每个 VNode 有 `children`，`children` 每个元素也是一个 VNode，这样就形成了一个 VNode Tree，它很好的描述了我们的 DOM Tree。\n\n回到 `mountComponent` 函数的过程，我们已经知道 `vm._render` 是如何创建了一个 VNode，接下来就是要把这个 VNode 渲染成一个真实的 DOM 并渲染出来，这个过程是通过 `vm._update` 完成的，接下来分析一下这个过程。\n"
  },
  {
    "path": "docs/v2/data-driven/index.md",
    "content": "# 数据驱动\n\nVue.js 一个核心思想是数据驱动。所谓数据驱动，是指视图是由数据驱动生成的，我们对视图的修改，不会直接操作 DOM，而是通过修改数据。它相比我们传统的前端开发，如使用 jQuery 等前端库直接修改 DOM，大大简化了代码量。特别是当交互复杂的时候，只关心数据的修改会让代码的逻辑变的非常清晰，因为 DOM 变成了数据的映射，我们所有的逻辑都是对数据的修改，而不用碰触 DOM，这样的代码非常利于维护。\n\n在 Vue.js 中我们可以采用简洁的模板语法来声明式的将数据渲染为 DOM：\n\n```html\n<div id=\"app\">\n  {{ message }}\n</div>\n```\n\n```js\nvar app = new Vue({\n  el: '#app',\n  data: {\n    message: 'Hello Vue!'\n  }\n})\n```\n\n最终它会在页面上渲染出 `Hello Vue`。接下来，我们会从源码角度来分析 Vue 是如何实现的，分析过程会以主线代码为主，重要的分支逻辑会放在之后单独分析。数据驱动还有一部分是数据更新驱动视图变化，这一块内容我们也会在之后的章节分析，这一章我们的目标是弄清楚模板和数据如何渲染成最终的 DOM。\n\n\n\n"
  },
  {
    "path": "docs/v2/data-driven/mounted.md",
    "content": "# Vue 实例挂载的实现\n\nVue 中我们是通过 `$mount` 实例方法去挂载 `vm` 的，`$mount` 方法在多个文件中都有定义，如 `src/platform/web/entry-runtime-with-compiler.js`、`src/platform/web/runtime/index.js`、`src/platform/weex/runtime/index.js`。因为 `$mount` 这个方法的实现是和平台、构建方式都相关的。接下来我们重点分析带 `compiler` 版本的 `$mount` 实现，因为抛开 webpack 的 vue-loader，我们在纯前端浏览器环境分析 Vue 的工作原理，有助于我们对原理理解的深入。\n\n`compiler` 版本的 `$mount` 实现非常有意思，先来看一下 `src/platform/web/entry-runtime-with-compiler.js` 文件中定义：\n\n```js\nconst mount = Vue.prototype.$mount\nVue.prototype.$mount = function (\n  el?: string | Element,\n  hydrating?: boolean\n): Component {\n  el = el && query(el)\n\n  /* istanbul ignore if */\n  if (el === document.body || el === document.documentElement) {\n    process.env.NODE_ENV !== 'production' && warn(\n      `Do not mount Vue to <html> or <body> - mount to normal elements instead.`\n    )\n    return this\n  }\n\n  const options = this.$options\n  // resolve template/el and convert to render function\n  if (!options.render) {\n    let template = options.template\n    if (template) {\n      if (typeof template === 'string') {\n        if (template.charAt(0) === '#') {\n          template = idToTemplate(template)\n          /* istanbul ignore if */\n          if (process.env.NODE_ENV !== 'production' && !template) {\n            warn(\n              `Template element not found or is empty: ${options.template}`,\n              this\n            )\n          }\n        }\n      } else if (template.nodeType) {\n        template = template.innerHTML\n      } else {\n        if (process.env.NODE_ENV !== 'production') {\n          warn('invalid template option:' + template, this)\n        }\n        return this\n      }\n    } else if (el) {\n      template = getOuterHTML(el)\n    }\n    if (template) {\n      /* istanbul ignore if */\n      if (process.env.NODE_ENV !== 'production' && config.performance && mark) {\n        mark('compile')\n      }\n\n      const { render, staticRenderFns } = compileToFunctions(template, {\n        shouldDecodeNewlines,\n        shouldDecodeNewlinesForHref,\n        delimiters: options.delimiters,\n        comments: options.comments\n      }, this)\n      options.render = render\n      options.staticRenderFns = staticRenderFns\n\n      /* istanbul ignore if */\n      if (process.env.NODE_ENV !== 'production' && config.performance && mark) {\n        mark('compile end')\n        measure(`vue ${this._name} compile`, 'compile', 'compile end')\n      }\n    }\n  }\n  return mount.call(this, el, hydrating)\n}\n```\n这段代码首先缓存了原型上的 `$mount` 方法，再重新定义该方法，我们先来分析这段代码。首先，它对 `el` 做了限制，Vue 不能挂载在 `body`、`html` 这样的根节点上。接下来的是很关键的逻辑 —— 如果没有定义 `render` 方法，则会把 `el` 或者 `template` 字符串转换成 `render` 方法。这里我们要牢记，在 Vue 2.0 版本中，所有 Vue 的组件的渲染最终都需要 `render` 方法，无论我们是用单文件 .vue 方式开发组件，还是写了 `el` 或者 `template` 属性，最终都会转换成 `render` 方法，那么这个过程是 Vue 的一个“在线编译”的过程，它是调用 `compileToFunctions` 方法实现的，编译过程我们之后会介绍。最后，调用原先原型上的 `$mount` 方法挂载。\n\n原先原型上的 `$mount` 方法在 `src/platform/web/runtime/index.js` 中定义，之所以这么设计完全是为了复用，因为它是可以被 `runtime only` 版本的 Vue 直接使用的。\n\n```js\n// public mount method\nVue.prototype.$mount = function (\n  el?: string | Element,\n  hydrating?: boolean\n): Component {\n  el = el && inBrowser ? query(el) : undefined\n  return mountComponent(this, el, hydrating)\n}\n```\n\n`$mount` 方法支持传入 2 个参数，第一个是 `el`，它表示挂载的元素，可以是字符串，也可以是 DOM 对象，如果是字符串在浏览器环境下会调用 `query` 方法转换成 DOM 对象的。第二个参数是和服务端渲染相关，在浏览器环境下我们不需要传第二个参数。\n\n`$mount` 方法实际上会去调用 `mountComponent` 方法，这个方法定义在 `src/core/instance/lifecycle.js` 文件中：\n\n```js\nexport function mountComponent (\n  vm: Component,\n  el: ?Element,\n  hydrating?: boolean\n): Component {\n  vm.$el = el\n  if (!vm.$options.render) {\n    vm.$options.render = createEmptyVNode\n    if (process.env.NODE_ENV !== 'production') {\n      /* istanbul ignore if */\n      if ((vm.$options.template && vm.$options.template.charAt(0) !== '#') ||\n        vm.$options.el || el) {\n        warn(\n          'You are using the runtime-only build of Vue where the template ' +\n          'compiler is not available. Either pre-compile the templates into ' +\n          'render functions, or use the compiler-included build.',\n          vm\n        )\n      } else {\n        warn(\n          'Failed to mount component: template or render function not defined.',\n          vm\n        )\n      }\n    }\n  }\n  callHook(vm, 'beforeMount')\n\n  let updateComponent\n  /* istanbul ignore if */\n  if (process.env.NODE_ENV !== 'production' && config.performance && mark) {\n    updateComponent = () => {\n      const name = vm._name\n      const id = vm._uid\n      const startTag = `vue-perf-start:${id}`\n      const endTag = `vue-perf-end:${id}`\n\n      mark(startTag)\n      const vnode = vm._render()\n      mark(endTag)\n      measure(`vue ${name} render`, startTag, endTag)\n\n      mark(startTag)\n      vm._update(vnode, hydrating)\n      mark(endTag)\n      measure(`vue ${name} patch`, startTag, endTag)\n    }\n  } else {\n    updateComponent = () => {\n      vm._update(vm._render(), hydrating)\n    }\n  }\n\n  // we set this to vm._watcher inside the watcher's constructor\n  // since the watcher's initial patch may call $forceUpdate (e.g. inside child\n  // component's mounted hook), which relies on vm._watcher being already defined\n  new Watcher(vm, updateComponent, noop, {\n    before () {\n      if (vm._isMounted) {\n        callHook(vm, 'beforeUpdate')\n      }\n    }\n  }, true /* isRenderWatcher */)\n  hydrating = false\n\n  // manually mounted instance, call mounted on self\n  // mounted is called for render-created child components in its inserted hook\n  if (vm.$vnode == null) {\n    vm._isMounted = true\n    callHook(vm, 'mounted')\n  }\n  return vm\n}\n```\n从上面的代码可以看到，`mountComponent` 核心就是先实例化一个渲染`Watcher`，在它的回调函数中会调用 `updateComponent` 方法，在此方法中调用 `vm._render` 方法先生成虚拟 Node，最终调用 `vm._update` 更新 DOM。\n\n`Watcher` 在这里起到两个作用，一个是初始化的时候会执行回调函数，另一个是当 vm 实例中的监测的数据发生变化的时候执行回调函数，这块儿我们会在之后的章节中介绍。\n\n函数最后判断为根节点的时候设置 `vm._isMounted` 为 `true`， 表示这个实例已经挂载了，同时执行 `mounted` 钩子函数。 这里注意 `vm.$vnode` 表示 Vue 实例的父虚拟 Node，所以它为 `Null` 则表示当前是根 Vue 的实例。\n\n\n## 总结\n\n`mountComponent` 方法的逻辑也是非常清晰的，它会完成整个渲染工作，接下来我们要重点分析其中的细节，也就是最核心的 2 个方法：`vm._render` 和 `vm._update`。\n"
  },
  {
    "path": "docs/v2/data-driven/new-vue.md",
    "content": "# new Vue 发生了什么\n \n 从入口代码开始分析，我们先来分析 `new Vue` 背后发生了哪些事情。我们都知道，`new` 关键字在 Javascript 语言中代表实例化是一个对象，而 `Vue` 实际上是一个类，类在 Javascript 中是用 Function 来实现的，来看一下源码，在`src/core/instance/index.js` 中。\n \n```js\nfunction Vue (options) {\n  if (process.env.NODE_ENV !== 'production' &&\n    !(this instanceof Vue)\n  ) {\n    warn('Vue is a constructor and should be called with the `new` keyword')\n  }\n  this._init(options)\n}\n```\n可以看到 `Vue` 只能通过 new 关键字初始化，然后会调用 `this._init` 方法， 该方法在 `src/core/instance/init.js` 中定义。\n\n```js\nVue.prototype._init = function (options?: Object) {\n  const vm: Component = this\n  // a uid\n  vm._uid = uid++\n\n  let startTag, endTag\n  /* istanbul ignore if */\n  if (process.env.NODE_ENV !== 'production' && config.performance && mark) {\n    startTag = `vue-perf-start:${vm._uid}`\n    endTag = `vue-perf-end:${vm._uid}`\n    mark(startTag)\n  }\n\n  // a flag to avoid this being observed\n  vm._isVue = true\n  // merge options\n  if (options && options._isComponent) {\n    // optimize internal component instantiation\n    // since dynamic options merging is pretty slow, and none of the\n    // internal component options needs special treatment.\n    initInternalComponent(vm, options)\n  } else {\n    vm.$options = mergeOptions(\n      resolveConstructorOptions(vm.constructor),\n      options || {},\n      vm\n    )\n  }\n  /* istanbul ignore else */\n  if (process.env.NODE_ENV !== 'production') {\n    initProxy(vm)\n  } else {\n    vm._renderProxy = vm\n  }\n  // expose real self\n  vm._self = vm\n  initLifecycle(vm)\n  initEvents(vm)\n  initRender(vm)\n  callHook(vm, 'beforeCreate')\n  initInjections(vm) // resolve injections before data/props\n  initState(vm)\n  initProvide(vm) // resolve provide after data/props\n  callHook(vm, 'created')\n\n  /* istanbul ignore if */\n  if (process.env.NODE_ENV !== 'production' && config.performance && mark) {\n    vm._name = formatComponentName(vm, false)\n    mark(endTag)\n    measure(`vue ${vm._name} init`, startTag, endTag)\n  }\n\n  if (vm.$options.el) {\n    vm.$mount(vm.$options.el)\n  }\n}\n```\n\nVue 初始化主要就干了几件事情，合并配置，初始化生命周期，初始化事件中心，初始化渲染，初始化 data、props、computed、watcher 等等。\n\n## 总结\n\nVue 的初始化逻辑写的非常清楚，把不同的功能逻辑拆成一些单独的函数执行，让主线逻辑一目了然，这样的编程思想是非常值得借鉴和学习的。\n\n由于我们这一章的目标是弄清楚模板和数据如何渲染成最终的 DOM，所以各种初始化逻辑我们先不看。在初始化的最后，检测到如果有 `el` 属性，则调用 `vm.$mount` 方法挂载 `vm`，挂载的目标就是把模板渲染成最终的 DOM，那么接下来我们来分析 Vue 的挂载过程。"
  },
  {
    "path": "docs/v2/data-driven/render.md",
    "content": "# render\n\nVue 的 `_render` 方法是实例的一个私有方法，它用来把实例渲染成一个虚拟 Node。它的定义在 `src/core/instance/render.js` 文件中：\n\n```js\nVue.prototype._render = function (): VNode {\n  const vm: Component = this\n  const { render, _parentVnode } = vm.$options\n\n  // reset _rendered flag on slots for duplicate slot check\n  if (process.env.NODE_ENV !== 'production') {\n    for (const key in vm.$slots) {\n      // $flow-disable-line\n      vm.$slots[key]._rendered = false\n    }\n  }\n\n  if (_parentVnode) {\n    vm.$scopedSlots = _parentVnode.data.scopedSlots || emptyObject\n  }\n\n  // set parent vnode. this allows render functions to have access\n  // to the data on the placeholder node.\n  vm.$vnode = _parentVnode\n  // render self\n  let vnode\n  try {\n    vnode = render.call(vm._renderProxy, vm.$createElement)\n  } catch (e) {\n    handleError(e, vm, `render`)\n    // return error render result,\n    // or previous vnode to prevent render error causing blank component\n    /* istanbul ignore else */\n    if (process.env.NODE_ENV !== 'production') {\n      if (vm.$options.renderError) {\n        try {\n          vnode = vm.$options.renderError.call(vm._renderProxy, vm.$createElement, e)\n        } catch (e) {\n          handleError(e, vm, `renderError`)\n          vnode = vm._vnode\n        }\n      } else {\n        vnode = vm._vnode\n      }\n    } else {\n      vnode = vm._vnode\n    }\n  }\n  // return empty vnode in case the render function errored out\n  if (!(vnode instanceof VNode)) {\n    if (process.env.NODE_ENV !== 'production' && Array.isArray(vnode)) {\n      warn(\n        'Multiple root nodes returned from render function. Render function ' +\n        'should return a single root node.',\n        vm\n      )\n    }\n    vnode = createEmptyVNode()\n  }\n  // set parent\n  vnode.parent = _parentVnode\n  return vnode\n}\n```\n\n这段代码最关键的是 `render` 方法的调用，我们在平时的开发工作中手写 `render` 方法的场景比较少，而写的比较多的是 `template` 模板，在之前的 `mounted` 方法的实现中，会把 `template` 编译成 `render` 方法，但这个编译过程是非常复杂的，我们不打算在这里展开讲，之后会专门花一个章节来分析 Vue 的编译过程。 \n\n在 Vue 的官方文档中介绍了 `render` 函数的第一个参数是 `createElement`，那么结合之前的例子：\n\n```html\n<div id=\"app\">\n  {{ message }}\n</div>\n```\n\n相当于我们编写如下 `render` 函数：\n\n```js\nrender: function (createElement) {\n  return createElement('div', {\n     attrs: {\n        id: 'app'\n      },\n  }, this.message)\n}\n```\n\n再回到 `_render` 函数中的 `render` 方法的调用：\n\n```js\nvnode = render.call(vm._renderProxy, vm.$createElement)\n```\n\n可以看到，`render` 函数中的 `createElement` 方法就是 `vm.$createElement` 方法：\n\n```js\nexport function initRender (vm: Component) {\n  // ...\n  // bind the createElement fn to this instance\n  // so that we get proper render context inside it.\n  // args order: tag, data, children, normalizationType, alwaysNormalize\n  // internal version is used by render functions compiled from templates\n  vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)\n  // normalization is always applied for the public version, used in\n  // user-written render functions.\n  vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)\n}\n\n```\n\n实际上，`vm.$createElement` 方法定义是在执行 `initRender` 方法的时候，可以看到除了 `vm.$createElement` 方法，还有一个 `vm._c` 方法，它是被模板编译成的 `render` 函数使用，而 `vm.$createElement` 是用户手写 `render` 方法使用的， 这俩个方法支持的参数相同，并且内部都调用了 `createElement` 方法。\n\n\n## 总结\n\n`vm._render` 最终是通过执行 `createElement` 方法并返回的是 `vnode`，它是一个虚拟 Node。Vue 2.0 相比 Vue 1.0 最大的升级就是利用了 Virtual DOM。因此在分析 `createElement` 的实现前，我们先了解一下 Virtual DOM 的概念。"
  },
  {
    "path": "docs/v2/data-driven/update.md",
    "content": "# update\n\nVue 的 `_update` 是实例的一个私有方法，它被调用的时机有 2 个，一个是首次渲染，一个是数据更新的时候；由于我们这一章节只分析首次渲染部分，数据更新部分会在之后分析响应式原理的时候涉及。`_update` 方法的作用是把 VNode 渲染成真实的 DOM，它的定义在 `src/core/instance/lifecycle.js` 中：\n\n```js\nVue.prototype._update = function (vnode: VNode, hydrating?: boolean) {\n  const vm: Component = this\n  const prevEl = vm.$el\n  const prevVnode = vm._vnode\n  const prevActiveInstance = activeInstance\n  activeInstance = vm\n  vm._vnode = vnode\n  // Vue.prototype.__patch__ is injected in entry points\n  // based on the rendering backend used.\n  if (!prevVnode) {\n    // initial render\n    vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)\n  } else {\n    // updates\n    vm.$el = vm.__patch__(prevVnode, vnode)\n  }\n  activeInstance = prevActiveInstance\n  // update __vue__ reference\n  if (prevEl) {\n    prevEl.__vue__ = null\n  }\n  if (vm.$el) {\n    vm.$el.__vue__ = vm\n  }\n  // if parent is an HOC, update its $el as well\n  if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) {\n    vm.$parent.$el = vm.$el\n  }\n  // updated hook is called by the scheduler to ensure that children are\n  // updated in a parent's updated hook.\n}\n```\n\n`_update` 的核心就是调用 `vm.__patch__` 方法，这个方法实际上在不同的平台，比如 web 和 weex 上的定义是不一样的，因此在 web 平台中它的定义在 `src/platforms/web/runtime/index.js` 中：\n\n```js\nVue.prototype.__patch__ = inBrowser ? patch : noop\n```\n\n可以看到，甚至在 web 平台上，是否是服务端渲染也会对这个方法产生影响。因为在服务端渲染中，没有真实的浏览器 DOM 环境，所以不需要把 VNode 最终转换成 DOM，因此是一个空函数，而在浏览器端渲染中，它指向了 `patch` 方法，它的定义在 `src/platforms/web/runtime/patch.js`中：\n\n```js\nimport * as nodeOps from 'web/runtime/node-ops'\nimport { createPatchFunction } from 'core/vdom/patch'\nimport baseModules from 'core/vdom/modules/index'\nimport platformModules from 'web/runtime/modules/index'\n\n// the directive module should be applied last, after all\n// built-in modules have been applied.\nconst modules = platformModules.concat(baseModules)\n\nexport const patch: Function = createPatchFunction({ nodeOps, modules })\n```\n\n该方法的定义是调用 `createPatchFunction` 方法的返回值，这里传入了一个对象，包含 `nodeOps` 参数和 `modules` 参数。其中，`nodeOps` 封装了一系列 DOM 操作的方法，`modules` 定义了一些模块的钩子函数的实现，我们这里先不详细介绍，来看一下 `createPatchFunction` 的实现，它定义在 `src/core/vdom/patch.js` 中：\n\n```js\nconst hooks = ['create', 'activate', 'update', 'remove', 'destroy']\n\nexport function createPatchFunction (backend) {\n  let i, j\n  const cbs = {}\n\n  const { modules, nodeOps } = backend\n\n  for (i = 0; i < hooks.length; ++i) {\n    cbs[hooks[i]] = []\n    for (j = 0; j < modules.length; ++j) {\n      if (isDef(modules[j][hooks[i]])) {\n        cbs[hooks[i]].push(modules[j][hooks[i]])\n      }\n    }\n  }\n\n  // ...\n\n  return function patch (oldVnode, vnode, hydrating, removeOnly) {\n    if (isUndef(vnode)) {\n      if (isDef(oldVnode)) invokeDestroyHook(oldVnode)\n      return\n    }\n\n    let isInitialPatch = false\n    const insertedVnodeQueue = []\n\n    if (isUndef(oldVnode)) {\n      // empty mount (likely as component), create new root element\n      isInitialPatch = true\n      createElm(vnode, insertedVnodeQueue)\n    } else {\n      const isRealElement = isDef(oldVnode.nodeType)\n      if (!isRealElement && sameVnode(oldVnode, vnode)) {\n        // patch existing root node\n        patchVnode(oldVnode, vnode, insertedVnodeQueue, removeOnly)\n      } else {\n        if (isRealElement) {\n          // mounting to a real element\n          // check if this is server-rendered content and if we can perform\n          // a successful hydration.\n          if (oldVnode.nodeType === 1 && oldVnode.hasAttribute(SSR_ATTR)) {\n            oldVnode.removeAttribute(SSR_ATTR)\n            hydrating = true\n          }\n          if (isTrue(hydrating)) {\n            if (hydrate(oldVnode, vnode, insertedVnodeQueue)) {\n              invokeInsertHook(vnode, insertedVnodeQueue, true)\n              return oldVnode\n            } else if (process.env.NODE_ENV !== 'production') {\n              warn(\n                'The client-side rendered virtual DOM tree is not matching ' +\n                'server-rendered content. This is likely caused by incorrect ' +\n                'HTML markup, for example nesting block-level elements inside ' +\n                '<p>, or missing <tbody>. Bailing hydration and performing ' +\n                'full client-side render.'\n              )\n            }\n          }\n          // either not server-rendered, or hydration failed.\n          // create an empty node and replace it\n          oldVnode = emptyNodeAt(oldVnode)\n        }\n\n        // replacing existing element\n        const oldElm = oldVnode.elm\n        const parentElm = nodeOps.parentNode(oldElm)\n\n        // create new node\n        createElm(\n          vnode,\n          insertedVnodeQueue,\n          // extremely rare edge case: do not insert if old element is in a\n          // leaving transition. Only happens when combining transition +\n          // keep-alive + HOCs. (#4590)\n          oldElm._leaveCb ? null : parentElm,\n          nodeOps.nextSibling(oldElm)\n        )\n\n        // update parent placeholder node element, recursively\n        if (isDef(vnode.parent)) {\n          let ancestor = vnode.parent\n          const patchable = isPatchable(vnode)\n          while (ancestor) {\n            for (let i = 0; i < cbs.destroy.length; ++i) {\n              cbs.destroy[i](ancestor)\n            }\n            ancestor.elm = vnode.elm\n            if (patchable) {\n              for (let i = 0; i < cbs.create.length; ++i) {\n                cbs.create[i](emptyNode, ancestor)\n              }\n              // #6513\n              // invoke insert hooks that may have been merged by create hooks.\n              // e.g. for directives that uses the \"inserted\" hook.\n              const insert = ancestor.data.hook.insert\n              if (insert.merged) {\n                // start at index 1 to avoid re-invoking component mounted hook\n                for (let i = 1; i < insert.fns.length; i++) {\n                  insert.fns[i]()\n                }\n              }\n            } else {\n              registerRef(ancestor)\n            }\n            ancestor = ancestor.parent\n          }\n        }\n\n        // destroy old node\n        if (isDef(parentElm)) {\n          removeVnodes(parentElm, [oldVnode], 0, 0)\n        } else if (isDef(oldVnode.tag)) {\n          invokeDestroyHook(oldVnode)\n        }\n      }\n    }\n\n    invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch)\n    return vnode.elm\n  }\n}\n```\n\n`createPatchFunction` 内部定义了一系列的辅助方法，最终返回了一个 `patch` 方法，这个方法就赋值给了 `vm._update` 函数里调用的 `vm.__patch__`。\n\n在介绍 `patch` 的方法实现之前，我们可以思考一下为何 Vue.js 源码绕了这么一大圈，把相关代码分散到各个目录。因为前面介绍过，`patch` 是平台相关的，在 Web 和 Weex 环境，它们把虚拟 DOM 映射到 “平台 DOM” 的方法是不同的，并且对 “DOM” 包括的属性模块创建和更新也不尽相同。因此每个平台都有各自的 `nodeOps` 和 `modules`，它们的代码需要托管在 `src/platforms` 这个大目录下。\n\n而不同平台的 `patch` 的主要逻辑部分是相同的，所以这部分公共的部分托管在 `core` 这个大目录下。差异化部分只需要通过参数来区别，这里用到了一个函数柯里化的技巧，通过 `createPatchFunction` 把差异化参数提前固化，这样不用每次调用 `patch` 的时候都传递 `nodeOps` 和 `modules` 了，这种编程技巧也非常值得学习。\n\n在这里，`nodeOps` 表示对 “平台 DOM” 的一些操作方法，`modules` 表示平台的一些模块，它们会在整个 `patch` 过程的不同阶段执行相应的钩子函数。这些代码的具体实现会在之后的章节介绍。\n\n回到 `patch` 方法本身，它接收 4个参数，`oldVnode` 表示旧的 VNode 节点，它也可以不存在或者是一个 DOM 对象；`vnode` 表示执行 `_render` 后返回的 VNode 的节点；`hydrating` 表示是否是服务端渲染；`removeOnly` 是给 `transition-group` 用的，之后会介绍。\n\n`patch` 的逻辑看上去相对复杂，因为它有着非常多的分支逻辑，为了方便理解，我们并不会在这里介绍所有的逻辑，仅会针对我们之前的例子分析它的执行逻辑。之后我们对其它场景做源码分析的时候会再次回顾 `patch` 方法。\n\n先来回顾我们的例子：\n\n```js\nvar app = new Vue({\n  el: '#app',\n  render: function (createElement) {\n    return createElement('div', {\n      attrs: {\n        id: 'app'\n      },\n    }, this.message)\n  },\n  data: {\n    message: 'Hello Vue!'\n  }\n})\n```\n\n然后我们在 `vm._update` 的方法里是这么调用 `patch` 方法的：\n\n```js\n// initial render\nvm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)\n```\n\n结合我们的例子，我们的场景是首次渲染，所以在执行 `patch` 函数的时候，传入的 `vm.$el` 对应的是例子中 id 为 `app` 的 DOM 对象，这个也就是我们在 index.html 模板中写的 `<div id=\"app\">`， `vm.$el` 的赋值是在之前 `mountComponent` 函数做的，`vnode` 对应的是调用 `render` 函数的返回值，`hydrating` 在非服务端渲染情况下为 false，`removeOnly` 为 false。\n\n确定了这些入参后，我们回到 `patch` 函数的执行过程，看几个关键步骤。\n\n```js\nconst isRealElement = isDef(oldVnode.nodeType)\nif (!isRealElement && sameVnode(oldVnode, vnode)) {\n  // patch existing root node\n  patchVnode(oldVnode, vnode, insertedVnodeQueue, removeOnly)\n} else {\n  if (isRealElement) {\n    // mounting to a real element\n    // check if this is server-rendered content and if we can perform\n    // a successful hydration.\n    if (oldVnode.nodeType === 1 && oldVnode.hasAttribute(SSR_ATTR)) {\n      oldVnode.removeAttribute(SSR_ATTR)\n      hydrating = true\n    }\n    if (isTrue(hydrating)) {\n      if (hydrate(oldVnode, vnode, insertedVnodeQueue)) {\n        invokeInsertHook(vnode, insertedVnodeQueue, true)\n        return oldVnode\n      } else if (process.env.NODE_ENV !== 'production') {\n        warn(\n          'The client-side rendered virtual DOM tree is not matching ' +\n          'server-rendered content. This is likely caused by incorrect ' +\n          'HTML markup, for example nesting block-level elements inside ' +\n          '<p>, or missing <tbody>. Bailing hydration and performing ' +\n          'full client-side render.'\n        )\n      }\n    }      \n    // either not server-rendered, or hydration failed.\n    // create an empty node and replace it\n    oldVnode = emptyNodeAt(oldVnode)\n  }\n\n  // replacing existing element\n  const oldElm = oldVnode.elm\n  const parentElm = nodeOps.parentNode(oldElm)\n\n  // create new node\n  createElm(\n    vnode,\n    insertedVnodeQueue,\n    // extremely rare edge case: do not insert if old element is in a\n    // leaving transition. Only happens when combining transition +\n    // keep-alive + HOCs. (#4590)\n    oldElm._leaveCb ? null : parentElm,\n    nodeOps.nextSibling(oldElm)\n  )\n}\n```\n\n由于我们传入的 `oldVnode` 实际上是一个 DOM container，所以 `isRealElement` 为 true，接下来又通过 `emptyNodeAt` 方法把 `oldVnode` 转换成 `VNode` 对象，然后再调用 `createElm` 方法，这个方法在这里非常重要，来看一下它的实现：\n\n```js\nfunction createElm (\n  vnode,\n  insertedVnodeQueue,\n  parentElm,\n  refElm,\n  nested,\n  ownerArray,\n  index\n) {\n  if (isDef(vnode.elm) && isDef(ownerArray)) {\n    // This vnode was used in a previous render!\n    // now it's used as a new node, overwriting its elm would cause\n    // potential patch errors down the road when it's used as an insertion\n    // reference node. Instead, we clone the node on-demand before creating\n    // associated DOM element for it.\n    vnode = ownerArray[index] = cloneVNode(vnode)\n  }\n\n  vnode.isRootInsert = !nested // for transition enter check\n  if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {\n    return\n  }\n\n  const data = vnode.data\n  const children = vnode.children\n  const tag = vnode.tag\n  if (isDef(tag)) {\n    if (process.env.NODE_ENV !== 'production') {\n      if (data && data.pre) {\n        creatingElmInVPre++\n      }\n      if (isUnknownElement(vnode, creatingElmInVPre)) {\n        warn(\n          'Unknown custom element: <' + tag + '> - did you ' +\n          'register the component correctly? For recursive components, ' +\n          'make sure to provide the \"name\" option.',\n          vnode.context\n        )\n      }\n    }\n\n    vnode.elm = vnode.ns\n      ? nodeOps.createElementNS(vnode.ns, tag)\n      : nodeOps.createElement(tag, vnode)\n    setScope(vnode)\n\n    /* istanbul ignore if */\n    if (__WEEX__) {\n      // ...\n    } else {\n      createChildren(vnode, children, insertedVnodeQueue)\n      if (isDef(data)) {\n        invokeCreateHooks(vnode, insertedVnodeQueue)\n      }\n      insert(parentElm, vnode.elm, refElm)\n    }\n\n    if (process.env.NODE_ENV !== 'production' && data && data.pre) {\n      creatingElmInVPre--\n    }\n  } else if (isTrue(vnode.isComment)) {\n    vnode.elm = nodeOps.createComment(vnode.text)\n    insert(parentElm, vnode.elm, refElm)\n  } else {\n    vnode.elm = nodeOps.createTextNode(vnode.text)\n    insert(parentElm, vnode.elm, refElm)\n  }\n}\n```\n\n`createElm` 的作用是通过虚拟节点创建真实的 DOM 并插入到它的父节点中。 我们来看一下它的一些关键逻辑，`createComponent` 方法目的是尝试创建子组件，这个逻辑在之后组件的章节会详细介绍，在当前这个 case 下它的返回值为 false；接下来判断 `vnode` 是否包含 tag，如果包含，先简单对 tag 的合法性在非生产环境下做校验，看是否是一个合法标签；然后再去调用平台 DOM 的操作去创建一个占位符元素。\n\n```js\nvnode.elm = vnode.ns\n  ? nodeOps.createElementNS(vnode.ns, tag)\n  : nodeOps.createElement(tag, vnode)\n```\n\n接下来调用 `createChildren` 方法去创建子元素：\n\n```js\ncreateChildren(vnode, children, insertedVnodeQueue)\n\nfunction createChildren (vnode, children, insertedVnodeQueue) {\n  if (Array.isArray(children)) {\n    if (process.env.NODE_ENV !== 'production') {\n      checkDuplicateKeys(children)\n    }\n    for (let i = 0; i < children.length; ++i) {\n      createElm(children[i], insertedVnodeQueue, vnode.elm, null, true, children, i)\n    }\n  } else if (isPrimitive(vnode.text)) {\n    nodeOps.appendChild(vnode.elm, nodeOps.createTextNode(String(vnode.text)))\n  }\n}\n```\n\n`createChildren` 的逻辑很简单，实际上是遍历子虚拟节点，递归调用 `createElm`，这是一种常用的深度优先的遍历算法，这里要注意的一点是在遍历过程中会把 `vnode.elm` 作为父容器的 DOM 节点占位符传入。\n\n接着再调用 `invokeCreateHooks` 方法执行所有的 create 的钩子并把 `vnode` push 到 `insertedVnodeQueue` 中。\n\n```js\n if (isDef(data)) {\n  invokeCreateHooks(vnode, insertedVnodeQueue)\n}\n\nfunction invokeCreateHooks (vnode, insertedVnodeQueue) {\n  for (let i = 0; i < cbs.create.length; ++i) {\n    cbs.create[i](emptyNode, vnode)\n  }\n  i = vnode.data.hook // Reuse variable\n  if (isDef(i)) {\n    if (isDef(i.create)) i.create(emptyNode, vnode)\n    if (isDef(i.insert)) insertedVnodeQueue.push(vnode)\n  }\n}\n```\n\n最后调用 `insert` 方法把 `DOM` 插入到父节点中，因为是递归调用，子元素会优先调用 `insert`，所以整个 `vnode` 树节点的插入顺序是先子后父。来看一下 `insert` 方法，它的定义在 `src/core/vdom/patch.js` 上。\n\n```js\ninsert(parentElm, vnode.elm, refElm)\n\nfunction insert (parent, elm, ref) {\n  if (isDef(parent)) {\n    if (isDef(ref)) {\n      if (ref.parentNode === parent) {\n        nodeOps.insertBefore(parent, elm, ref)\n      }\n    } else {\n      nodeOps.appendChild(parent, elm)\n    }\n  }\n}\n```\n`insert` 逻辑很简单，调用一些 `nodeOps` 把子节点插入到父节点中，这些辅助方法定义在 `src/platforms/web/runtime/node-ops.js` 中：\n\n```js\nexport function insertBefore (parentNode: Node, newNode: Node, referenceNode: Node) {\n  parentNode.insertBefore(newNode, referenceNode)\n}\n\nexport function appendChild (node: Node, child: Node) {\n  node.appendChild(child)\n}\n```\n\n其实就是调用原生 DOM 的 API 进行 DOM 操作，看到这里，很多同学恍然大悟，原来 Vue 是这样动态创建的 DOM。\n\n在 `createElm` 过程中，如果 `vnode` 节点不包含 `tag`，则它有可能是一个注释或者纯文本节点，可以直接插入到父元素中。在我们这个例子中，最内层就是一个文本 `vnode`，它的 `text` 值取的就是之前的 `this.message` 的值 `Hello Vue!`。\n\n再回到 `patch` 方法，首次渲染我们调用了 `createElm` 方法，这里传入的 `parentElm` 是 `oldVnode.elm` 的父元素，在我们的例子是 id 为 `#app` div 的父元素，也就是 Body；实际上整个过程就是递归创建了一个完整的 DOM 树并插入到 Body 上。\n\n最后，我们根据之前递归 `createElm` 生成的 `vnode` 插入顺序队列，执行相关的 `insert` 钩子函数，这部分内容我们之后会详细介绍。\n\n## 总结\n\n那么至此我们从主线上把模板和数据如何渲染成最终的 DOM 的过程分析完毕了，我们可以通过下图更直观地看到从初始化 Vue 到最终渲染的整个过程。 \n\n<img :src=\"$withBase('/assets/new-vue.png')\"/>\n\n我们这里只是分析了最简单和最基础的场景，在实际项目中，我们是把页面拆成很多组件的，Vue 另一个核心思想就是组件化。那么下一章我们就来分析 Vue 的组件化过程。\n\n"
  },
  {
    "path": "docs/v2/data-driven/virtual-dom.md",
    "content": "# Virtual DOM\n\nVirtual DOM 这个概念相信大部分人都不会陌生，它产生的前提是浏览器中的 DOM 是很“昂贵\"的，为了更直观的感受，我们可以简单的把一个简单的 div 元素的属性都打印出来，如图所示：\n\n<img :src=\"$withBase('/assets/dom.png')\">\n\n可以看到，真正的 DOM 元素是非常庞大的，因为浏览器的标准就把 DOM 设计的非常复杂。当我们频繁的去做 DOM 更新，会产生一定的性能问题。\n\n而 Virtual DOM 就是用一个原生的 JS 对象去描述一个 DOM 节点，所以它比创建一个 DOM 的代价要小很多。在 Vue.js 中，Virtual DOM 是用 `VNode` 这么一个 Class 去描述，它是定义在 `src/core/vdom/vnode.js` 中的。\n\n```js\nexport default class VNode {\n  tag: string | void;\n  data: VNodeData | void;\n  children: ?Array<VNode>;\n  text: string | void;\n  elm: Node | void;\n  ns: string | void;\n  context: Component | void; // rendered in this component's scope\n  key: string | number | void;\n  componentOptions: VNodeComponentOptions | void;\n  componentInstance: Component | void; // component instance\n  parent: VNode | void; // component placeholder node\n\n  // strictly internal\n  raw: boolean; // contains raw HTML? (server only)\n  isStatic: boolean; // hoisted static node\n  isRootInsert: boolean; // necessary for enter transition check\n  isComment: boolean; // empty comment placeholder?\n  isCloned: boolean; // is a cloned node?\n  isOnce: boolean; // is a v-once node?\n  asyncFactory: Function | void; // async component factory function\n  asyncMeta: Object | void;\n  isAsyncPlaceholder: boolean;\n  ssrContext: Object | void;\n  fnContext: Component | void; // real context vm for functional nodes\n  fnOptions: ?ComponentOptions; // for SSR caching\n  fnScopeId: ?string; // functional scope id support\n\n  constructor (\n    tag?: string,\n    data?: VNodeData,\n    children?: ?Array<VNode>,\n    text?: string,\n    elm?: Node,\n    context?: Component,\n    componentOptions?: VNodeComponentOptions,\n    asyncFactory?: Function\n  ) {\n    this.tag = tag\n    this.data = data\n    this.children = children\n    this.text = text\n    this.elm = elm\n    this.ns = undefined\n    this.context = context\n    this.fnContext = undefined\n    this.fnOptions = undefined\n    this.fnScopeId = undefined\n    this.key = data && data.key\n    this.componentOptions = componentOptions\n    this.componentInstance = undefined\n    this.parent = undefined\n    this.raw = false\n    this.isStatic = false\n    this.isRootInsert = true\n    this.isComment = false\n    this.isCloned = false\n    this.isOnce = false\n    this.asyncFactory = asyncFactory\n    this.asyncMeta = undefined\n    this.isAsyncPlaceholder = false\n  }\n\n  // DEPRECATED: alias for componentInstance for backwards compat.\n  /* istanbul ignore next */\n  get child (): Component | void {\n    return this.componentInstance\n  }\n}\n```\n\n可以看到 Vue.js 中的 Virtual DOM 的定义还是略微复杂一些的，因为它这里包含了很多 Vue.js 的特性。这里千万不要被这些茫茫多的属性吓到，实际上 Vue.js 中 Virtual DOM 是借鉴了一个开源库 [snabbdom](https://github.com/snabbdom/snabbdom) 的实现，然后加入了一些 Vue.js 特色的东西。我建议大家如果想深入了解 Vue.js 的 Virtual DOM 前不妨先阅读这个库的源码，因为它更加简单和纯粹。\n\n## 总结\n\n其实 VNode 是对真实 DOM 的一种抽象描述，它的核心定义无非就几个关键属性，标签名、数据、子节点、键值等，其它属性都是用来扩展 VNode 的灵活性以及实现一些特殊 feature 的。由于 VNode 只是用来映射到真实 DOM 的渲染，不需要包含操作 DOM 的方法，因此它是非常轻量和简单的。\n\nVirtual DOM 除了它的数据结构的定义，映射到真实的 DOM 实际上要经历 VNode 的 create、diff、patch 等过程。那么在 Vue.js 中，VNode 的 create 是通过之前提到的 `createElement` 方法创建的，我们接下来分析这部分的实现。\n"
  },
  {
    "path": "docs/v2/extend/event.md",
    "content": "# event\n\n我们平时开发工作中，处理组件间的通讯，原生的交互，都离不开事件。对于一个组件元素，我们不仅仅可以绑定原生的 DOM 事件，还可以绑定自定义事件，非常灵活和方便。那么接下来我们从源码角度来看看它的实现原理。\n\n为了更加直观，我们通过一个例子来分析它的实现：\n\n```js\nlet Child = {\n  template: '<button @click=\"clickHandler($event)\">' +\n  'click me' +\n  '</button>',\n  methods: {\n    clickHandler(e) {\n      console.log('Button clicked!', e)\n      this.$emit('select')\n    }\n  }\n}\n\nlet vm = new Vue({\n  el: '#app',\n  template: '<div>' +\n  '<child @select=\"selectHandler\" @click.native.prevent=\"clickHandler\"></child>' +\n  '</div>',\n  methods: {\n    clickHandler() {\n      console.log('Child clicked!')\n    },\n    selectHandler() {\n      console.log('Child select!')\n    }\n  },\n  components: {\n    Child\n  }\n})\n```\n\n## 编译\n\n先从编译阶段开始看起，在 `parse` 阶段，会执行 `processAttrs` 方法，它的定义在 `src/compiler/parser/index.js` 中：\n\n```js\nexport const onRE = /^@|^v-on:/\nexport const dirRE = /^v-|^@|^:/\nexport const bindRE = /^:|^v-bind:/\nfunction processAttrs (el) {\n  const list = el.attrsList\n  let i, l, name, rawName, value, modifiers, isProp\n  for (i = 0, l = list.length; i < l; i++) {\n    name = rawName = list[i].name\n    value = list[i].value\n    if (dirRE.test(name)) {   \n      el.hasBindings = true\n      modifiers = parseModifiers(name)\n      if (modifiers) {\n        name = name.replace(modifierRE, '')\n      }\n      if (bindRE.test(name)) {\n        // ..\n      } else if (onRE.test(name)) {\n        name = name.replace(onRE, '')\n        addHandler(el, name, value, modifiers, false, warn)\n      } else {\n        // ...\n      }\n    } else {\n      // ...\n    }\n  }\n}\n\nfunction parseModifiers (name: string): Object | void {\n  const match = name.match(modifierRE)\n  if (match) {\n    const ret = {}\n    match.forEach(m => { ret[m.slice(1)] = true })\n    return ret\n  }\n}\n```\n\n在对标签属性的处理过程中，判断如果是指令，首先通过 `parseModifiers` 解析出修饰符，然后判断如果事件的指令，则执行 `addHandler(el, name, value, modifiers, false, warn)` 方法，它的定义在 `src/compiler/helpers.js` 中：\n\n```js\nexport function addHandler (\n  el: ASTElement,\n  name: string,\n  value: string,\n  modifiers: ?ASTModifiers,\n  important?: boolean,\n  warn?: Function\n) {\n  modifiers = modifiers || emptyObject\n  // warn prevent and passive modifier\n  /* istanbul ignore if */\n  if (\n    process.env.NODE_ENV !== 'production' && warn &&\n    modifiers.prevent && modifiers.passive\n  ) {\n    warn(\n      'passive and prevent can\\'t be used together. ' +\n      'Passive handler can\\'t prevent default event.'\n    )\n  }\n\n  // check capture modifier\n  if (modifiers.capture) {\n    delete modifiers.capture\n    name = '!' + name // mark the event as captured\n  }\n  if (modifiers.once) {\n    delete modifiers.once\n    name = '~' + name // mark the event as once\n  }\n  /* istanbul ignore if */\n  if (modifiers.passive) {\n    delete modifiers.passive\n    name = '&' + name // mark the event as passive\n  }\n\n  // normalize click.right and click.middle since they don't actually fire\n  // this is technically browser-specific, but at least for now browsers are\n  // the only target envs that have right/middle clicks.\n  if (name === 'click') {\n    if (modifiers.right) {\n      name = 'contextmenu'\n      delete modifiers.right\n    } else if (modifiers.middle) {\n      name = 'mouseup'\n    }\n  }\n\n  let events\n  if (modifiers.native) {\n    delete modifiers.native\n    events = el.nativeEvents || (el.nativeEvents = {})\n  } else {\n    events = el.events || (el.events = {})\n  }\n\n  const newHandler: any = {\n    value: value.trim()\n  }\n  if (modifiers !== emptyObject) {\n    newHandler.modifiers = modifiers\n  }\n\n  const handlers = events[name]\n  /* istanbul ignore if */\n  if (Array.isArray(handlers)) {\n    important ? handlers.unshift(newHandler) : handlers.push(newHandler)\n  } else if (handlers) {\n    events[name] = important ? [newHandler, handlers] : [handlers, newHandler]\n  } else {\n    events[name] = newHandler\n  }\n\n  el.plain = false\n}\n```\n\n`addHandler` 函数看起来长，实际上就做了 3 件事情，首先根据 `modifier` 修饰符对事件名 `name` 做处理，接着根据 `modifier.native` 判断是一个纯原生事件还是普通事件，分别对应 `el.nativeEvents` 和 `el.events`，最后按照 `name` 对事件做归类，并把回调函数的字符串保留到对应的事件中。\n\n在我们的例子中，父组件的 `child` 节点生成的 `el.events` 和 `el.nativeEvents` 如下：\n\n```js\nel.events = {\n  select: {\n    value: 'selectHandler'\n  }\n}\n\nel.nativeEvents = {\n  click: {\n    value: 'clickHandler',\n    modifiers: {\n      prevent: true\n    }\n  }\n}\n```\n\n子组件的 `button` 节点生成的 `el.events` 如下：\n\n```js\nel.events = {\n  click: {\n    value: 'clickHandler($event)'\n  }\n}\n```\n \n然后在 `codegen` 的阶段，会在 `genData` 函数中根据 AST 元素节点上的 `events` 和 `nativeEvents` 生成 `data` 数据，它的定义在 `src/compiler/codegen/index.js` 中：\n\n```js\nexport function genData (el: ASTElement, state: CodegenState): string {\n  let data = '{'\n  // ...\n  if (el.events) {\n    data += `${genHandlers(el.events, false, state.warn)},`\n  }\n  if (el.nativeEvents) {\n    data += `${genHandlers(el.nativeEvents, true, state.warn)},`\n  }\n  // ...\n  return data\n}\n```\n对于这两个属性，会调用 `genHandlers` 函数，定义在 `src/compiler/codegen/events.js` 中：\n\n```js\nexport function genHandlers (\n  events: ASTElementHandlers,\n  isNative: boolean,\n  warn: Function\n): string {\n  let res = isNative ? 'nativeOn:{' : 'on:{'\n  for (const name in events) {\n    res += `\"${name}\":${genHandler(name, events[name])},`\n  }\n  return res.slice(0, -1) + '}'\n}\n\nconst fnExpRE = /^\\s*([\\w$_]+|\\([^)]*?\\))\\s*=>|^function\\s*\\(/\nconst simplePathRE = /^\\s*[A-Za-z_$][\\w$]*(?:\\.[A-Za-z_$][\\w$]*|\\['.*?']|\\[\".*?\"]|\\[\\d+]|\\[[A-Za-z_$][\\w$]*])*\\s*$/\nfunction genHandler (\n  name: string,\n  handler: ASTElementHandler | Array<ASTElementHandler>\n): string {\n  if (!handler) {\n    return 'function(){}'\n  }\n\n  if (Array.isArray(handler)) {\n    return `[${handler.map(handler => genHandler(name, handler)).join(',')}]`\n  }\n\n  const isMethodPath = simplePathRE.test(handler.value)\n  const isFunctionExpression = fnExpRE.test(handler.value)\n\n  if (!handler.modifiers) {\n    if (isMethodPath || isFunctionExpression) {\n      return handler.value\n    }\n    /* istanbul ignore if */\n    if (__WEEX__ && handler.params) {\n      return genWeexHandler(handler.params, handler.value)\n    }\n    return `function($event){${handler.value}}` // inline statement\n  } else {\n    let code = ''\n    let genModifierCode = ''\n    const keys = []\n    for (const key in handler.modifiers) {\n      if (modifierCode[key]) {\n        genModifierCode += modifierCode[key]\n        // left/right\n        if (keyCodes[key]) {\n          keys.push(key)\n        }\n      } else if (key === 'exact') {\n        const modifiers: ASTModifiers = (handler.modifiers: any)\n        genModifierCode += genGuard(\n          ['ctrl', 'shift', 'alt', 'meta']\n            .filter(keyModifier => !modifiers[keyModifier])\n            .map(keyModifier => `$event.${keyModifier}Key`)\n            .join('||')\n        )\n      } else {\n        keys.push(key)\n      }\n    }\n    if (keys.length) {\n      code += genKeyFilter(keys)\n    }\n    // Make sure modifiers like prevent and stop get executed after key filtering\n    if (genModifierCode) {\n      code += genModifierCode\n    }\n    const handlerCode = isMethodPath\n      ? `return ${handler.value}($event)`\n      : isFunctionExpression\n        ? `return (${handler.value})($event)`\n        : handler.value\n    /* istanbul ignore if */\n    if (__WEEX__ && handler.params) {\n      return genWeexHandler(handler.params, code + handlerCode)\n    }\n    return `function($event){${code}${handlerCode}}`\n  }\n}\n\n```\n\n`genHandlers` 方法遍历事件对象 `events`，对同一个事件名称的事件调用 `genHandler(name, events[name])` 方法，它的内容看起来多，但实际上逻辑很简单，首先先判断如果 `handler` 是一个数组，就遍历它然后递归调用 `genHandler` 方法并拼接结果，然后判断 `hanlder.value` 是一个函数的调用路径还是一个函数表达式， 接着对 `modifiers` 做判断，对于没有 `modifiers` 的情况，就根据 `handler.value` 不同情况处理，要么直接返回，要么返回一个函数包裹的表达式；对于有 `modifiers` 的情况，则对各种不同的 `modifer` 情况做不同处理，添加相应的代码串。\n\n那么对于我们的例子而言，父组件生成的 `data` 串为：\n\n```js\n{\n  on: {\"select\": selectHandler},\n  nativeOn: {\"click\": function($event) {\n      $event.preventDefault();\n      return clickHandler($event)\n    }\n  }\n}\n```\n子组件生成的 `data` 串为：\n\n```js\n{\n  on: {\"click\": function($event) {\n      clickHandler($event)\n    }\n  }\n}\n```\n\n那么到这里，编译部分完了，接下来我们来看一下运行时部分是如何实现的。其实 Vue 的事件有 2 种，一种是原生 DOM 事件，一种是用户自定义事件，我们分别来看。\n\n## DOM 事件\n\n还记得我们之前在 `patch` 的时候执行各种 `module` 的钩子函数吗，当时这部分是略过的，我们之前只分析了 DOM 是如何渲染的，而 DOM 元素相关的属性、样式、事件等都是通过这些 `module` 的钩子函数完成设置的。\n\n所有和 web 相关的 `module` 都定义在 `src/platforms/web/runtime/modules` 目录下，我们这次只关注目录下的 `events.js` 即可。\n\n在 `patch` 过程中的创建阶段和更新阶段都会执行 `updateDOMListeners`：\n\n```js\nlet target: any\nfunction updateDOMListeners (oldVnode: VNodeWithData, vnode: VNodeWithData) {\n  if (isUndef(oldVnode.data.on) && isUndef(vnode.data.on)) {\n    return\n  }\n  const on = vnode.data.on || {}\n  const oldOn = oldVnode.data.on || {}\n  target = vnode.elm\n  normalizeEvents(on)\n  updateListeners(on, oldOn, add, remove, vnode.context)\n  target = undefined\n}\n```\n\n首先获取 `vnode.data.on`，这就是我们之前的生成的 `data` 中对应的事件对象，`target` 是当前 `vnode` 对于的 DOM 对象，`normalizeEvents` 主要是对 `v-model` 相关的处理，我们之后分析 `v-model` 的时候会介绍，接着调用 `updateListeners(on, oldOn, add, remove, vnode.context)` 方法，它的定义在 `src/core/vdom/helpers/update-listeners.js` 中：\n\n```js\nexport function updateListeners (\n  on: Object,\n  oldOn: Object,\n  add: Function,\n  remove: Function,\n  vm: Component\n) {\n  let name, def, cur, old, event\n  for (name in on) {\n    def = cur = on[name]\n    old = oldOn[name]\n    event = normalizeEvent(name)\n    /* istanbul ignore if */\n    if (__WEEX__ && isPlainObject(def)) {\n      cur = def.handler\n      event.params = def.params\n    }\n    if (isUndef(cur)) {\n      process.env.NODE_ENV !== 'production' && warn(\n        `Invalid handler for event \"${event.name}\": got ` + String(cur),\n        vm\n      )\n    } else if (isUndef(old)) {\n      if (isUndef(cur.fns)) {\n        cur = on[name] = createFnInvoker(cur)\n      }\n      add(event.name, cur, event.once, event.capture, event.passive, event.params)\n    } else if (cur !== old) {\n      old.fns = cur\n      on[name] = old\n    }\n  }\n  for (name in oldOn) {\n    if (isUndef(on[name])) {\n      event = normalizeEvent(name)\n      remove(event.name, oldOn[name], event.capture)\n    }\n  }\n}\n```\n\n`updateListeners` 的逻辑很简单，遍历 `on` 去添加事件监听，遍历 `oldOn` 去移除事件监听，关于监听和移除事件的方法都是外部传入的，因为它既处理原生 DOM 事件的添加删除，也处理自定义事件的添加删除。\n\n对于 `on` 的遍历，首先获得每一个事件名，然后做 `normalizeEvent` 的处理：\n\n```js\nconst normalizeEvent = cached((name: string): {\n  name: string,\n  once: boolean,\n  capture: boolean,\n  passive: boolean,\n  handler?: Function,\n  params?: Array<any>\n} => {\n  const passive = name.charAt(0) === '&'\n  name = passive ? name.slice(1) : name\n  const once = name.charAt(0) === '~' // Prefixed last, checked first\n  name = once ? name.slice(1) : name\n  const capture = name.charAt(0) === '!'\n  name = capture ? name.slice(1) : name\n  return {\n    name,\n    once,\n    capture,\n    passive\n  }\n})\n```\n\n根据我们的的事件名的一些特殊标识（之前在 `addHandler` 的时候添加上的）区分出这个事件是否有 `once`、`capture`、`passive` 等修饰符。\n\n处理完事件名后，又对事件回调函数做处理，对于第一次，满足 `isUndef(old)` 并且 `isUndef(cur.fns)`，会执行 `cur = on[name] = createFnInvoker(cur)` 方法去创建一个回调函数，然后在执行 `add(event.name, cur, event.once, event.capture, event.passive, event.params)` 完成一次事件绑定。我们先看一下 `createFnInvoker` 的实现：\n\n```js\nexport function createFnInvoker (fns: Function | Array<Function>): Function {\n  function invoker () {\n    const fns = invoker.fns\n    if (Array.isArray(fns)) {\n      const cloned = fns.slice()\n      for (let i = 0; i < cloned.length; i++) {\n        cloned[i].apply(null, arguments)\n      }\n    } else {\n      return fns.apply(null, arguments)\n    }\n  }\n  invoker.fns = fns\n  return invoker\n}\n```\n\n这里定义了 `invoker` 方法并返回，由于一个事件可能会对应多个回调函数，所以这里做了数组的判断，多个回调函数就依次调用。注意最后的赋值逻辑， `invoker.fns = fns`，每一次执行 `invoker` 函数都是从 `invoker.fns` 里取执行的回调函数，回到 `updateListeners`，当我们第二次执行该函数的时候，判断如果 `cur !== old`，那么只需要更改 `old.fns = cur` 把之前绑定的 `involer.fns`  赋值为新的回调函数即可，并且 通过 `on[name] = old` 保留引用关系，这样就保证了事件回调只添加一次，之后仅仅去修改它的回调函数的引用。\n\n`updateListeners` 函数的最后遍历 `oldOn` 拿到事件名称，判断如果满足 `isUndef(on[name])`，则执行 `remove(event.name, oldOn[name], event.capture)` 去移除事件回调。\n\n了解了 `updateListeners` 的实现后，我们来看一下在原生 DOM 事件中真正添加回调和移除回调函数的实现，它们的定义都在 `src/platforms/web/runtime/modules/event.js` 中：\n\n```js\nfunction add (\n  event: string,\n  handler: Function,\n  once: boolean,\n  capture: boolean,\n  passive: boolean\n) {\n  handler = withMacroTask(handler)\n  if (once) handler = createOnceHandler(handler, event, capture)\n  target.addEventListener(\n    event,\n    handler,\n    supportsPassive\n      ? { capture, passive }\n      : capture\n  )\n}\n\nfunction remove (\n  event: string,\n  handler: Function,\n  capture: boolean,\n  _target?: HTMLElement\n) {\n  (_target || target).removeEventListener(\n    event,\n    handler._withTask || handler,\n    capture\n  )\n}\n```\n\n`add` 和 `remove` 的逻辑很简单，就是实际上调用原生 `addEventListener` 和 `removeEventListener`，并根据参数传递一些配置，注意这里的 `hanlder` 会用 `withMacroTask(hanlder)` 包裹一下，它的定义在 `src/core/util/next-tick.js` 中：\n\n```js\nexport function withMacroTask (fn: Function): Function {\n  return fn._withTask || (fn._withTask = function () {\n    useMacroTask = true\n    const res = fn.apply(null, arguments)\n    useMacroTask = false\n    return res\n  })\n}\n```\n\n实际上就是强制在 DOM 事件的回调函数执行期间如果修改了数据，那么这些数据更改推入的队列会被当做 `macroTask` 在 `nextTick` 后执行。\n\n## 自定义事件\n\n除了原生 DOM 事件，Vue 还支持了自定义事件，并且自定义事件只能作用在组件上，如果在组件上使用原生事件，需要加 `.native` 修饰符，普通元素上使用 `.native` 修饰符无效，接下来我们就来分析它的实现。\n\n在 `render` 阶段，如果是一个组件节点，则通过 `createComponent` 创建一个组件 `vnode`，我们再来回顾这个方法，定义在 `src/core/vdom/create-component.js` 中：\n\n```js\nexport function createComponent (\n  Ctor: Class<Component> | Function | Object | void,\n  data: ?VNodeData,\n  context: Component,\n  children: ?Array<VNode>,\n  tag?: string\n): VNode | Array<VNode> | void {\n  // ...\n  const listeners = data.on\n  \n  data.on = data.nativeOn\n  \n  // ...\n  const name = Ctor.options.name || tag\n  const vnode = new VNode(\n    `vue-component-${Ctor.cid}${name ? `-${name}` : ''}`,\n    data, undefined, undefined, undefined, context,\n    { Ctor, propsData, listeners, tag, children },\n    asyncFactory\n  )\n\n  return vnode\n}\n```\n我们只关注事件相关的逻辑，可以看到，它把 `data.on` 赋值给了 `listeners`，把 `data.nativeOn` 赋值给了 `data.on`，这样所有的原生 DOM 事件处理跟我们刚才介绍的一样，它是在当前组件环境中处理的。而对于自定义事件，我们把 `listeners` 作为 `vnode` 的 `componentOptions` 传入，它是在子组件初始化阶段中处理的，所以它的处理环境是子组件。\n\n然后在子组件的初始化的时候，会执行 `initInternalComponent` 方法，它的定义在 `src/core/instance/init.js` 中：\n\n```js\nexport function initInternalComponent (vm: Component, options: InternalComponentOptions) {\n  const opts = vm.$options = Object.create(vm.constructor.options)\n  // ....\n  const vnodeComponentOptions = parentVnode.componentOptions\n \n  opts._parentListeners = vnodeComponentOptions.listeners\n  // ...\n}\n```\n这里拿到了父组件传入的 `listeners`，然后在执行 `initEvents` 的过程中，会处理这个 `listeners`，定义在 `src/core/instance/events.js` 中：\n\n```js\nexport function initEvents (vm: Component) {\n  vm._events = Object.create(null)\n  vm._hasHookEvent = false\n  // init parent attached events\n  const listeners = vm.$options._parentListeners\n  if (listeners) {\n    updateComponentListeners(vm, listeners)\n  }\n}\n```\n\n拿到 `listeners` 后，执行 `updateComponentListeners(vm, listeners)` 方法：\n\n```js\nlet target: any\nexport function updateComponentListeners (\n  vm: Component,\n  listeners: Object,\n  oldListeners: ?Object\n) {\n  target = vm\n  updateListeners(listeners, oldListeners || {}, add, remove, vm)\n  target = undefined\n}\n```\n\n`updateListeners` 我们之前介绍过，所以对于自定义事件和原生 DOM 事件处理的差异就在事件添加和删除的实现上，来看一下自定义事件 `add` 和 `remove` 的实现：\n\n```js\nfunction add (event, fn, once) {\n  if (once) {\n    target.$once(event, fn)\n  } else {\n    target.$on(event, fn)\n  }\n}\n\nfunction remove (event, fn) {\n  target.$off(event, fn)\n}\n```\n\n实际上是利用 Vue 定义的事件中心，简单分析一下它的实现：\n\n```js\nexport function eventsMixin (Vue: Class<Component>) {\n  const hookRE = /^hook:/\n  Vue.prototype.$on = function (event: string | Array<string>, fn: Function): Component {\n    const vm: Component = this\n    if (Array.isArray(event)) {\n      for (let i = 0, l = event.length; i < l; i++) {\n        this.$on(event[i], fn)\n      }\n    } else {\n      (vm._events[event] || (vm._events[event] = [])).push(fn)\n      // optimize hook:event cost by using a boolean flag marked at registration\n      // instead of a hash lookup\n      if (hookRE.test(event)) {\n        vm._hasHookEvent = true\n      }\n    }\n    return vm\n  }\n\n  Vue.prototype.$once = function (event: string, fn: Function): Component {\n    const vm: Component = this\n    function on () {\n      vm.$off(event, on)\n      fn.apply(vm, arguments)\n    }\n    on.fn = fn\n    vm.$on(event, on)\n    return vm\n  }\n\n  Vue.prototype.$off = function (event?: string | Array<string>, fn?: Function): Component {\n    const vm: Component = this\n    // all\n    if (!arguments.length) {\n      vm._events = Object.create(null)\n      return vm\n    }\n    // array of events\n    if (Array.isArray(event)) {\n      for (let i = 0, l = event.length; i < l; i++) {\n        this.$off(event[i], fn)\n      }\n      return vm\n    }\n    // specific event\n    const cbs = vm._events[event]\n    if (!cbs) {\n      return vm\n    }\n    if (!fn) {\n      vm._events[event] = null\n      return vm\n    }\n    if (fn) {\n      // specific handler\n      let cb\n      let i = cbs.length\n      while (i--) {\n        cb = cbs[i]\n        if (cb === fn || cb.fn === fn) {\n          cbs.splice(i, 1)\n          break\n        }\n      }\n    }\n    return vm\n  }\n\n  Vue.prototype.$emit = function (event: string): Component {\n    const vm: Component = this\n    if (process.env.NODE_ENV !== 'production') {\n      const lowerCaseEvent = event.toLowerCase()\n      if (lowerCaseEvent !== event && vm._events[lowerCaseEvent]) {\n        tip(\n          `Event \"${lowerCaseEvent}\" is emitted in component ` +\n          `${formatComponentName(vm)} but the handler is registered for \"${event}\". ` +\n          `Note that HTML attributes are case-insensitive and you cannot use ` +\n          `v-on to listen to camelCase events when using in-DOM templates. ` +\n          `You should probably use \"${hyphenate(event)}\" instead of \"${event}\".`\n        )\n      }\n    }\n    let cbs = vm._events[event]\n    if (cbs) {\n      cbs = cbs.length > 1 ? toArray(cbs) : cbs\n      const args = toArray(arguments, 1)\n      for (let i = 0, l = cbs.length; i < l; i++) {\n        try {\n          cbs[i].apply(vm, args)\n        } catch (e) {\n          handleError(e, vm, `event handler for \"${event}\"`)\n        }\n      }\n    }\n    return vm\n  }\n}\n```\n非常经典的事件中心的实现，把所有的事件用 `vm._events` 存储起来，当执行 `vm.$on(event,fn)` 的时候，按事件的名称 `event` 把回调函数 `fn` 存储起来 `vm._events[event].push(fn)`。当执行 `vm.$emit(event)` 的时候，根据事件名 `event` 找到所有的回调函数 `let cbs = vm._events[event]`，然后遍历执行所有的回调函数。当执行 `vm.$off(event,fn)` 的时候会移除指定事件名 `event` 和指定的 `fn` 当执行 `vm.$once(event,fn)` 的时候，内部就是执行 `vm.$on`，并且当回调函数执行一次后再通过 `vm.$off` 移除事件的回调，这样就确保了回调函数只执行一次。\n\n所以对于用户自定义的事件添加和删除就是利用了这几个事件中心的 API。需要注意的事一点，`vm.$emit` 是给当前的 `vm` 上派发的实例，之所以我们常用它做父子组件通讯，是因为它的回调函数的定义是在父组件中，对于我们这个例子而言，当子组件的 `button` 被点击了，它通过 `this.$emit('select')` 派发事件，那么子组件的实例就监听到了这个 `select` 事件，并执行它的回调函数——定义在父组件中的 `selectHandler` 方法，这样就相当于完成了一次父子组件的通讯。\n\n## 总结\n\n那么至此我们对 Vue 的事件实现有了进一步的了解，Vue 支持 2 种事件类型，原生 DOM 事件和自定义事件，它们主要的区别在于添加和删除事件的方式不一样，并且自定义事件的派发是往当前实例上派发，但是可以利用在父组件环境定义回调函数来实现父子组件的通讯。另外要注意一点，只有组件节点才可以添加自定义事件，并且添加原生 DOM 事件需要使用 `native` 修饰符；而普通元素使用 `.native` 修饰符是没有作用的，也只能添加原生 DOM 事件。\n  \n  \n\n\n\n"
  },
  {
    "path": "docs/v2/extend/index.md",
    "content": "# 扩展\n\n前面几章我们分析了 Vue 的核心以及编译过程，除此之外，Vue 还提供了很多好用的 feature 如 `event`、`v-model`、`slot`、`keep-alive`、`transition` 等等。对他们的理解有助于我们在平时开发中更好地应用这些 feature，即使出现 bug 我们也可以很从容地应对。\n\n这一章是一个可扩展的章节，除了已分析的这些 feature 外，未来我们可能会扩展更多的内容。"
  },
  {
    "path": "docs/v2/extend/keep-alive.md",
    "content": "# keep-alive\n\n在我们的平时开发工作中，经常为了组件的缓存优化而使用 `<keep-alive>` 组件，乐此不疲，但很少有人关注它的实现原理，下面就让我们来一探究竟。\n\n## 内置组件\n\n`<keep-alive>` 是 Vue 源码中实现的一个组件，也就是说 Vue 源码不仅实现了一套组件化的机制，也实现了一些内置组件，它的定义在 `src/core/components/keep-alive.js` 中：\n\n```js\nexport default {\n  name: 'keep-alive',\n  abstract: true,\n\n  props: {\n    include: patternTypes,\n    exclude: patternTypes,\n    max: [String, Number]\n  },\n\n  created () {\n    this.cache = Object.create(null)\n    this.keys = []\n  },\n\n  destroyed () {\n    for (const key in this.cache) {\n      pruneCacheEntry(this.cache, key, this.keys)\n    }\n  },\n\n  mounted () {\n    this.$watch('include', val => {\n      pruneCache(this, name => matches(val, name))\n    })\n    this.$watch('exclude', val => {\n      pruneCache(this, name => !matches(val, name))\n    })\n  },\n\n  render () {\n    const slot = this.$slots.default\n    const vnode: VNode = getFirstComponentChild(slot)\n    const componentOptions: ?VNodeComponentOptions = vnode && vnode.componentOptions\n    if (componentOptions) {\n      // check pattern\n      const name: ?string = getComponentName(componentOptions)\n      const { include, exclude } = this\n      if (\n        // not included\n        (include && (!name || !matches(include, name))) ||\n        // excluded\n        (exclude && name && matches(exclude, name))\n      ) {\n        return vnode\n      }\n\n      const { cache, keys } = this\n      const key: ?string = vnode.key == null\n        // same constructor may get registered as different local components\n        // so cid alone is not enough (#3269)\n        ? componentOptions.Ctor.cid + (componentOptions.tag ? `::${componentOptions.tag}` : '')\n        : vnode.key\n      if (cache[key]) {\n        vnode.componentInstance = cache[key].componentInstance\n        // make current key freshest\n        remove(keys, key)\n        keys.push(key)\n      } else {\n        cache[key] = vnode\n        keys.push(key)\n        // prune oldest entry\n        if (this.max && keys.length > parseInt(this.max)) {\n          pruneCacheEntry(cache, keys[0], keys, this._vnode)\n        }\n      }\n\n      vnode.data.keepAlive = true\n    }\n    return vnode || (slot && slot[0])\n  }\n}\n```\n\n可以看到 `<keep-alive>` 组件的实现也是一个对象，注意它有一个属性 `abstract` 为 true，是一个抽象组件，Vue 的文档没有提这个概念，实际上它在组件实例建立父子关系的时候会被忽略，发生在 `initLifecycle` 的过程中：\n\n```js\n// locate first non-abstract parent\nlet parent = options.parent\nif (parent && !options.abstract) {\n  while (parent.$options.abstract && parent.$parent) {\n    parent = parent.$parent\n  }\n  parent.$children.push(vm)\n}\nvm.$parent = parent\n```\n\n`<keep-alive>` 在 `created` 钩子里定义了 `this.cache` 和 `this.keys`，本质上它就是去缓存已经创建过的 `vnode`。它的 `props` 定义了 `include`，`exclude`，它们可以字符串或者表达式，`include` 表示只有匹配的组件会被缓存，而 `exclude` 表示任何匹配的组件都不会被缓存，`props` 还定义了 `max`，它表示缓存的大小，因为我们是缓存的 `vnode` 对象，它也会持有 DOM，当我们缓存很多的时候，会比较占用内存，所以该配置允许我们指定缓存大小。\n\n`<keep-alive>` 直接实现了 `render` 函数，而不是我们常规模板的方式，执行 `<keep-alive>` 组件渲染的时候，就会执行到这个 `render` 函数，接下来我们分析一下它的实现。\n\n首先获取第一个子元素的 `vnode`：\n\n```js\nconst slot = this.$slots.default\nconst vnode: VNode = getFirstComponentChild(slot)\n```\n\n由于我们也是在 `<keep-alive>` 标签内部写 DOM，所以可以先获取到它的默认插槽，然后再获取到它的第一个子节点。`<keep-alive>` 只处理第一个子元素，所以一般和它搭配使用的有 `component` 动态组件或者是 `router-view`，这点要牢记。\n\n然后又判断了当前组件的名称和 `include`、`exclude` 的关系：\n\n```js\n// check pattern\nconst name: ?string = getComponentName(componentOptions)\nconst { include, exclude } = this\nif (\n  // not included\n  (include && (!name || !matches(include, name))) ||\n  // excluded\n  (exclude && name && matches(exclude, name))\n) {\n  return vnode\n}\n\nfunction matches (pattern: string | RegExp | Array<string>, name: string): boolean {\n  if (Array.isArray(pattern)) {\n    return pattern.indexOf(name) > -1\n  } else if (typeof pattern === 'string') {\n    return pattern.split(',').indexOf(name) > -1\n  } else if (isRegExp(pattern)) {\n    return pattern.test(name)\n  }\n  return false\n}\n```\n\n`matches` 的逻辑很简单，就是做匹配，分别处理了数组、字符串、正则表达式的情况，也就是说我们平时传的 `include` 和 `exclude` 可以是这三种类型的任意一种。并且我们的组件名如果满足了配置 `include` 且不匹配或者是配置了 `exclude` 且匹配，那么就直接返回这个组件的 `vnode`，否则的话走下一步缓存：\n\n```js\nconst { cache, keys } = this\nconst key: ?string = vnode.key == null\n  // same constructor may get registered as different local components\n  // so cid alone is not enough (#3269)\n  ? componentOptions.Ctor.cid + (componentOptions.tag ? `::${componentOptions.tag}` : '')\n  : vnode.key\nif (cache[key]) {\n  vnode.componentInstance = cache[key].componentInstance\n  // make current key freshest\n  remove(keys, key)\n  keys.push(key)\n} else {\n  cache[key] = vnode\n  keys.push(key)\n  // prune oldest entry\n  if (this.max && keys.length > parseInt(this.max)) {\n    pruneCacheEntry(cache, keys[0], keys, this._vnode)\n  }\n}\n```\n\n这部分逻辑很简单，如果命中缓存，则直接从缓存中拿 `vnode` 的组件实例，并且重新调整了 key 的顺序放在了最后一个；否则把 `vnode` 设置进缓存，最后还有一个逻辑，如果配置了 `max` 并且缓存的长度超过了 `this.max`，还要从缓存中删除第一个：\n\n```js\nfunction pruneCacheEntry (\n  cache: VNodeCache,\n  key: string,\n  keys: Array<string>,\n  current?: VNode\n) {\n  const cached = cache[key]\n  if (cached && (!current || cached.tag !== current.tag)) {\n    cached.componentInstance.$destroy()\n  }\n  cache[key] = null \n  remove(keys, key)\n}\n```\n除了从缓存中删除外，还要判断如果要删除的缓存并的组件 `tag` 不是当前渲染组件 `tag`，也执行删除缓存的组件实例的 `$destroy` 方法。\n\n最后设置 `vnode.data.keepAlive = true` ，这个作用稍后我们介绍。\n\n注意，`<keep-alive>` 组件也是为观测 `include` 和 `exclude` 的变化，对缓存做处理：\n\n```js\nwatch: {\n  include (val: string | RegExp | Array<string>) {\n    pruneCache(this, name => matches(val, name))\n  },\n  exclude (val: string | RegExp | Array<string>) {\n    pruneCache(this, name => !matches(val, name))\n  }\n}\n\nfunction pruneCache (keepAliveInstance: any, filter: Function) {\n  const { cache, keys, _vnode } = keepAliveInstance\n  for (const key in cache) {\n    const cachedNode: ?VNode = cache[key]\n    if (cachedNode) {\n      const name: ?string = getComponentName(cachedNode.componentOptions)\n      if (name && !filter(name)) {\n        pruneCacheEntry(cache, key, keys, _vnode)\n      }\n    }\n  }\n}\n```\n逻辑很简单，观测他们的变化执行 `pruneCache` 函数，其实就是对 `cache` 做遍历，发现缓存的节点名称和新的规则没有匹配上的时候，就把这个缓存节点从缓存中摘除。\n\n## 组件渲染\n\n到此为止，我们只了解了 `<keep-alive>` 的组件实现，但并不知道它包裹的子组件渲染和普通组件有什么不一样的地方。我们关注 2 个方面，首次渲染和缓存渲染。\n\n同样为了更好地理解，我们也结合一个示例来分析：\n\n```js\nlet A = {\n  template: '<div class=\"a\">' +\n  '<p>A Comp</p>' +\n  '</div>',\n  name: 'A'\n}\n\nlet B = {\n  template: '<div class=\"b\">' +\n  '<p>B Comp</p>' +\n  '</div>',\n  name: 'B'\n}\n\nlet vm = new Vue({\n  el: '#app',\n  template: '<div>' +\n  '<keep-alive>' +\n  '<component :is=\"currentComp\">' +\n  '</component>' +\n  '</keep-alive>' +\n  '<button @click=\"change\">switch</button>' +\n  '</div>',\n  data: {\n    currentComp: 'A'\n  },\n  methods: {\n    change() {\n      this.currentComp = this.currentComp === 'A' ? 'B' : 'A'\n    }\n  },\n  components: {\n    A,\n    B\n  }\n})\n```\n\n### 首次渲染\n\n我们知道 Vue 的渲染最后都会到 `patch` 过程，而组件的 `patch` 过程会执行 `createComponent` 方法，它的定义在 `src/core/vdom/patch.js` 中：\n\n```js\nfunction createComponent (vnode, insertedVnodeQueue, parentElm, refElm) {\n  let i = vnode.data\n  if (isDef(i)) {\n    const isReactivated = isDef(vnode.componentInstance) && i.keepAlive\n    if (isDef(i = i.hook) && isDef(i = i.init)) {\n      i(vnode, false /* hydrating */)\n    }\n    // after calling the init hook, if the vnode is a child component\n    // it should've created a child instance and mounted it. the child\n    // component also has set the placeholder vnode's elm.\n    // in that case we can just return the element and be done.\n    if (isDef(vnode.componentInstance)) {\n      initComponent(vnode, insertedVnodeQueue)\n      insert(parentElm, vnode.elm, refElm)\n      if (isTrue(isReactivated)) {\n        reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm)\n      }\n      return true\n    }\n  }\n}\n```\n\n`createComponent` 定义了 `isReactivated` 的变量，它是根据 `vnode.componentInstance` 以及 `vnode.data.keepAlive` 的判断，第一次渲染的时候，`vnode.componentInstance` 为 `undefined`，`vnode.data.keepAlive` 为 true，因为它的父组件 `<keep-alive>` 的 `render` 函数会先执行，那么该 `vnode` 缓存到内存中，并且设置 `vnode.data.keepAlive` 为 true，因此 `isReactivated` 为 `false`，那么走正常的 `init` 的钩子函数执行组件的 `mount`。当 `vnode` 已经执行完 `patch` 后，执行 `initComponent` 函数：\n\n```js\nfunction initComponent (vnode, insertedVnodeQueue) {\n  if (isDef(vnode.data.pendingInsert)) {\n    insertedVnodeQueue.push.apply(insertedVnodeQueue, vnode.data.pendingInsert)\n    vnode.data.pendingInsert = null\n  }\n  vnode.elm = vnode.componentInstance.$el\n  if (isPatchable(vnode)) {\n    invokeCreateHooks(vnode, insertedVnodeQueue)\n    setScope(vnode)\n  } else {\n    // empty component root.\n    // skip all element-related modules except for ref (#3455)\n    registerRef(vnode)\n    // make sure to invoke the insert hook\n    insertedVnodeQueue.push(vnode)\n  }\n}\n```\n这里会有 `vnode.elm` 缓存了 `vnode` 创建生成的 DOM 节点。所以对于首次渲染而言，除了在 `<keep-alive>` 中建立缓存，和普通组件渲染没什么区别。\n\n所以对我们的例子，初始化渲染 `A` 组件以及第一次点击 `switch` 渲染 `B` 组件，都是首次渲染。\n\n### 缓存渲染\n\n当我们从 `B` 组件再次点击 `switch` 切换到 `A` 组件，就会命中缓存渲染。\n\n我们之前分析过，当数据发送变化，在 `patch` 的过程中会执行 `patchVnode` 的逻辑，它会对比新旧 `vnode` 节点，甚至对比它们的子节点去做更新逻辑，但是对于组件 `vnode` 而言，是没有 `children` 的，那么对于 `<keep-alive>` 组件而言，如何更新它包裹的内容呢？\n\n原来 `patchVnode` 在做各种 diff 之前，会先执行 `prepatch` 的钩子函数，它的定义在 `src/core/vdom/create-component` 中：\n\n```js\nconst componentVNodeHooks = {\n  prepatch (oldVnode: MountedComponentVNode, vnode: MountedComponentVNode) {\n    const options = vnode.componentOptions\n    const child = vnode.componentInstance = oldVnode.componentInstance\n    updateChildComponent(\n      child,\n      options.propsData, // updated props\n      options.listeners, // updated listeners\n      vnode, // new parent vnode\n      options.children // new children\n    )\n  },\n  // ...\n}\n```\n\n`prepatch` 核心逻辑就是执行 `updateChildComponent` 方法，它的定义在 `src/core/instance/lifecycle.js` 中：\n\n```js\nexport function updateChildComponent (\n  vm: Component,\n  propsData: ?Object,\n  listeners: ?Object,\n  parentVnode: MountedComponentVNode,\n  renderChildren: ?Array<VNode>\n) {\n  const hasChildren = !!(\n    renderChildren ||          \n    vm.$options._renderChildren ||\n    parentVnode.data.scopedSlots || \n    vm.$scopedSlots !== emptyObject \n  )\n\n  // ...\n  if (hasChildren) {\n    vm.$slots = resolveSlots(renderChildren, parentVnode.context)\n    vm.$forceUpdate()\n  }\n}\n```\n\n`updateChildComponent` 方法主要是去更新组件实例的一些属性，这里我们重点关注一下 `slot` 部分，由于 `<keep-alive>` 组件本质上支持了 `slot`，所以它执行 `prepatch` 的时候，需要对自己的 `children`，也就是这些 `slots` 做重新解析，并触发 `<keep-alive>` 组件实例 `$forceUpdate` 逻辑，也就是重新执行 `<keep-alive>` 的 `render` 方法，这个时候如果它包裹的第一个组件 `vnode` 命中缓存，则直接返回缓存中的 `vnode.componentInstance`，在我们的例子中就是缓存的 `A` 组件，接着又会执行 `patch` 过程，再次执行到 `createComponent` 方法，我们再回顾一下：\n\n```js\nfunction createComponent (vnode, insertedVnodeQueue, parentElm, refElm) {\n  let i = vnode.data\n  if (isDef(i)) {\n    const isReactivated = isDef(vnode.componentInstance) && i.keepAlive\n    if (isDef(i = i.hook) && isDef(i = i.init)) {\n      i(vnode, false /* hydrating */)\n    }\n    // after calling the init hook, if the vnode is a child component\n    // it should've created a child instance and mounted it. the child\n    // component also has set the placeholder vnode's elm.\n    // in that case we can just return the element and be done.\n    if (isDef(vnode.componentInstance)) {\n      initComponent(vnode, insertedVnodeQueue)\n      insert(parentElm, vnode.elm, refElm)\n      if (isTrue(isReactivated)) {\n        reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm)\n      }\n      return true\n    }\n  }\n}\n```\n这个时候 `isReactivated` 为 true，并且在执行 `init` 钩子函数的时候不会再执行组件的 `mount` 过程了，相关逻辑在 `src/core/vdom/create-component.js` 中：\n\n```js\nconst componentVNodeHooks = {\n  init (vnode: VNodeWithData, hydrating: boolean): ?boolean {\n    if (\n      vnode.componentInstance &&\n      !vnode.componentInstance._isDestroyed &&\n      vnode.data.keepAlive\n    ) {\n      // kept-alive components, treat as a patch\n      const mountedNode: any = vnode // work around flow\n      componentVNodeHooks.prepatch(mountedNode, mountedNode)\n    } else {\n      const child = vnode.componentInstance = createComponentInstanceForVnode(\n        vnode,\n        activeInstance\n      )\n      child.$mount(hydrating ? vnode.elm : undefined, hydrating)\n    }\n  },\n  // ...\n}\n```\n\n这也就是被 `<keep-alive>` 包裹的组件在有缓存的时候就不会在执行组件的 `created`、`mounted` 等钩子函数的原因了。回到 `createComponent` 方法，在 `isReactivated` 为 true 的情况下会执行 `reactivateComponent` 方法：\n\n```js\nfunction reactivateComponent (vnode, insertedVnodeQueue, parentElm, refElm) {\n  let i\n  // hack for #4339: a reactivated component with inner transition\n  // does not trigger because the inner node's created hooks are not called\n  // again. It's not ideal to involve module-specific logic in here but\n  // there doesn't seem to be a better way to do it.\n  let innerNode = vnode\n  while (innerNode.componentInstance) {\n    innerNode = innerNode.componentInstance._vnode\n    if (isDef(i = innerNode.data) && isDef(i = i.transition)) {\n      for (i = 0; i < cbs.activate.length; ++i) {\n        cbs.activate[i](emptyNode, innerNode)\n      }\n      insertedVnodeQueue.push(innerNode)\n      break\n    }\n  }\n  // unlike a newly created component,\n  // a reactivated keep-alive component doesn't insert itself\n  insert(parentElm, vnode.elm, refElm)\n}\n```\n前面部分的逻辑是解决对 `reactived` 组件 `transition` 动画不触发的问题，可以先不关注，最后通过执行 `insert(parentElm, vnode.elm, refElm)` 就把缓存的 DOM 对象直接插入到目标元素中，这样就完成了在数据更新的情况下的渲染过程。\n\n## 生命周期\n\n之前我们提到，组件一旦被 `<keep-alive>` 缓存，那么再次渲染的时候就不会执行 `created`、`mounted` 等钩子函数，但是我们很多业务场景都是希望在我们被缓存的组件再次被渲染的时候做一些事情，好在 Vue 提供了 `activated` 钩子函数，它的执行时机是 `<keep-alive>` 包裹的组件渲染的时候，接下来我们从源码角度来分析一下它的实现原理。\n\n在渲染的最后一步，会执行 `invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch)` 函数执行 `vnode` 的 `insert` 钩子函数，它的定义在 `src/core/vdom/create-component.js` 中：\n\n```js\nconst componentVNodeHooks = {\n  insert (vnode: MountedComponentVNode) {\n    const { context, componentInstance } = vnode\n    if (!componentInstance._isMounted) {\n      componentInstance._isMounted = true\n      callHook(componentInstance, 'mounted')\n    }\n    if (vnode.data.keepAlive) {\n      if (context._isMounted) {\n        // vue-router#1212\n        // During updates, a kept-alive component's child components may\n        // change, so directly walking the tree here may call activated hooks\n        // on incorrect children. Instead we push them into a queue which will\n        // be processed after the whole patch process ended.\n        queueActivatedComponent(componentInstance)\n      } else {\n        activateChildComponent(componentInstance, true /* direct */)\n      }\n    }\n  },\n  // ...\n}\n```\n\n这里判断如果是被 `<keep-alive>` 包裹的组件已经 `mounted`，那么则执行 `queueActivatedComponent(componentInstance)` ，否则执行 `activateChildComponent(componentInstance, true)`。我们先分析非 `mounted` 的情况，`activateChildComponent` 的定义在 `src/core/instance/lifecycle.js` 中：\n\n```js\nexport function activateChildComponent (vm: Component, direct?: boolean) {\n  if (direct) {\n    vm._directInactive = false\n    if (isInInactiveTree(vm)) {\n      return\n    }\n  } else if (vm._directInactive) {\n    return\n  }\n  if (vm._inactive || vm._inactive === null) {\n    vm._inactive = false\n    for (let i = 0; i < vm.$children.length; i++) {\n      activateChildComponent(vm.$children[i])\n    }\n    callHook(vm, 'activated')\n  }\n}\n```\n\n可以看到这里就是执行组件的 `acitvated` 钩子函数，并且递归去执行它的所有子组件的 `activated` 钩子函数。\n\n那么再看 `queueActivatedComponent` 的逻辑，它定义在 `src/core/observer/scheduler.js` 中：\n\n```js\nexport function queueActivatedComponent (vm: Component) {\n  vm._inactive = false\n  activatedChildren.push(vm)\n}\n```\n这个逻辑很简单，把当前 `vm` 实例添加到 `activatedChildren` 数组中，等所有的渲染完毕，在 `nextTick`后会执行 `flushSchedulerQueue`，这个时候就会执行：\n\n```js\nfunction flushSchedulerQueue () {\n  // ...\n  const activatedQueue = activatedChildren.slice()\n  callActivatedHooks(activatedQueue)\n  // ...\n} \n\nfunction callActivatedHooks (queue) {\n  for (let i = 0; i < queue.length; i++) {\n    queue[i]._inactive = true\n    activateChildComponent(queue[i], true)  }\n}\n```\n也就是遍历所有的 `activatedChildren`，执行 `activateChildComponent` 方法，通过队列调的方式就是把整个 `activated` 时机延后了。\n\n有 `activated` 钩子函数，也就有对应的 `deactivated` 钩子函数，它是发生在 `vnode` 的 `destory` 钩子函数，定义在 `src/core/vdom/create-component.js` 中：\n\n```js\nconst componentVNodeHooks = {\n  destroy (vnode: MountedComponentVNode) {\n    const { componentInstance } = vnode\n    if (!componentInstance._isDestroyed) {\n      if (!vnode.data.keepAlive) {\n        componentInstance.$destroy()\n      } else {\n        deactivateChildComponent(componentInstance, true /* direct */)\n      }\n    }\n  }\n}\n```\n\n对于 `<keep-alive>` 包裹的组件而言，它会执行 `deactivateChildComponent(componentInstance, true)` 方法，定义在 `src/core/instance/lifecycle.js` 中：\n\n```js\nexport function deactivateChildComponent (vm: Component, direct?: boolean) {\n  if (direct) {\n    vm._directInactive = true\n    if (isInInactiveTree(vm)) {\n      return\n    }\n  }\n  if (!vm._inactive) {\n    vm._inactive = true\n    for (let i = 0; i < vm.$children.length; i++) {\n      deactivateChildComponent(vm.$children[i])\n    }\n    callHook(vm, 'deactivated')\n  }\n}\n```\n\n和 `activateChildComponent` 方法类似，就是执行组件的 `deacitvated` 钩子函数，并且递归去执行它的所有子组件的 `deactivated` 钩子函数。\n\n## 总结\n\n那么至此，`<keep-alive>` 的实现原理就介绍完了，通过分析我们知道了 `<keep-alive>` 组件是一个抽象组件，它的实现通过自定义 `render` 函数并且利用了插槽，并且知道了 `<keep-alive>` 缓存 `vnode`，了解组件包裹的子元素——也就是插槽是如何做更新的。且在 `patch` 过程中对于已缓存的组件不会执行 `mounted`，所以不会有一般的组件的生命周期函数但是又提供了 `activated` 和 `deactivated` 钩子函数。另外我们还知道了 `<keep-alive>` 的 `props` 除了 `include` 和 `exclude` 还有文档中没有提到的 `max`，它能控制我们缓存的个数。 \n"
  },
  {
    "path": "docs/v2/extend/slot.md",
    "content": "# slot\n\nVue 的组件提供了一个非常有用的特性 —— `slot` 插槽，它让组件的实现变的更加灵活。我们平时在开发组件库的时候，为了让组件更加灵活可定制，经常用插槽的方式让用户可以自定义内容。插槽分为普通插槽和作用域插槽，它们可以解决不同的场景，但它是怎么实现的呢，下面我们就从源码的角度来分析插槽的实现原理。\n\n## 普通插槽\n\n为了更加直观，我们还是通过一个例子来分析插槽的实现：\n\n```js\nlet AppLayout = {\n  template: '<div class=\"container\">' +\n  '<header><slot name=\"header\"></slot></header>' +\n  '<main><slot>默认内容</slot></main>' +\n  '<footer><slot name=\"footer\"></slot></footer>' +\n  '</div>'\n}\n\nlet vm = new Vue({\n  el: '#app',\n  template: '<div>' +\n  '<app-layout>' +\n  '<h1 slot=\"header\">{{title}}</h1>' +\n  '<p>{{msg}}</p>' +\n  '<p slot=\"footer\">{{desc}}</p>' +\n  '</app-layout>' +\n  '</div>',\n  data() {\n    return {\n      title: '我是标题',\n      msg: '我是内容',\n      desc: '其它信息'\n    }\n  },\n  components: {\n    AppLayout\n  }\n})\n```\n\n这里我们定义了 `AppLayout` 子组件，它内部定义了 3 个插槽，2 个为具名插槽，一个 `name` 为 `header`，一个 `name` 为 `footer`，还有一个没有定义 `name` 的是默认插槽。 `<slot>` 和 `</slot>` 之前填写的内容为默认内容。我们的父组件注册和引用了 `AppLayout` 的组件，并在组件内部定义了一些元素，用来替换插槽，那么它最终生成的 DOM 如下：\n\n```html\n<div>\n  <div class=\"container\">\n    <header><h1>我是标题</h1></header>\n    <main><p>我是内容</p></main>\n    <footer><p>其它信息</p></footer>\n  </div>\n</div>\n``` \n## 编译\n\n还是先从编译说起，我们知道编译是发生在调用 `vm.$mount` 的时候，所以编译的顺序是先编译父组件，再编译子组件。\n\n首先编译父组件，在 `parse` 阶段，会执行 `processSlot` 处理 `slot`，它的定义在 `src/compiler/parser/index.js` 中：\n\n```js\nfunction processSlot (el) {\n  if (el.tag === 'slot') {\n    el.slotName = getBindingAttr(el, 'name')\n    if (process.env.NODE_ENV !== 'production' && el.key) {\n      warn(\n        `\\`key\\` does not work on <slot> because slots are abstract outlets ` +\n        `and can possibly expand into multiple elements. ` +\n        `Use the key on a wrapping element instead.`\n      )\n    }\n  } else {\n    let slotScope\n    if (el.tag === 'template') {\n      slotScope = getAndRemoveAttr(el, 'scope')\n      /* istanbul ignore if */\n      if (process.env.NODE_ENV !== 'production' && slotScope) {\n        warn(\n          `the \"scope\" attribute for scoped slots have been deprecated and ` +\n          `replaced by \"slot-scope\" since 2.5. The new \"slot-scope\" attribute ` +\n          `can also be used on plain elements in addition to <template> to ` +\n          `denote scoped slots.`,\n          true\n        )\n      }\n      el.slotScope = slotScope || getAndRemoveAttr(el, 'slot-scope')\n    } else if ((slotScope = getAndRemoveAttr(el, 'slot-scope'))) {\n      /* istanbul ignore if */\n      if (process.env.NODE_ENV !== 'production' && el.attrsMap['v-for']) {\n        warn(\n          `Ambiguous combined usage of slot-scope and v-for on <${el.tag}> ` +\n          `(v-for takes higher priority). Use a wrapper <template> for the ` +\n          `scoped slot to make it clearer.`,\n          true\n        )\n      }\n      el.slotScope = slotScope\n    }\n    const slotTarget = getBindingAttr(el, 'slot')\n    if (slotTarget) {\n      el.slotTarget = slotTarget === '\"\"' ? '\"default\"' : slotTarget\n      // preserve slot as an attribute for native shadow DOM compat\n      // only for non-scoped slots.\n      if (el.tag !== 'template' && !el.slotScope) {\n        addAttr(el, 'slot', slotTarget)\n      }\n    }\n  }\n}\n```\n当解析到标签上有 `slot` 属性的时候，会给对应的 AST\n元素节点添加 `slotTarget` 属性，然后在 `codegen` 阶段，在 `genData` 中会处理 `slotTarget`，相关代码在 `src/compiler/codegen/index.js` 中：\n\n```js\nif (el.slotTarget && !el.slotScope) {\n  data += `slot:${el.slotTarget},`\n}\n```\n会给 `data` 添加一个 `slot` 属性，并指向 `slotTarget`，之后会用到。在我们的例子中，父组件最终生成的代码如下：\n\n```js\nwith(this){\n  return _c('div',\n    [_c('app-layout',\n      [_c('h1',{attrs:{\"slot\":\"header\"},slot:\"header\"},\n         [_v(_s(title))]),\n       _c('p',[_v(_s(msg))]),\n       _c('p',{attrs:{\"slot\":\"footer\"},slot:\"footer\"},\n         [_v(_s(desc))]\n         )\n       ])\n     ],\n   1)}\n```\n\n接下来编译子组件，同样在 `parser` 阶段会执行 `processSlot` 处理函数，它的定义在 `src/compiler/parser/index.js` 中：\n\n```js\nfunction processSlot (el) {\n  if (el.tag === 'slot') {\n    el.slotName = getBindingAttr(el, 'name')\n  }\n  // ...\n}\n```\n当遇到 `slot` 标签的时候会给对应的 AST 元素节点添加 `slotName` 属性，然后在 `codegen` 阶段，会判断如果当前 AST 元素节点是 `slot` 标签，则执行 `genSlot` 函数，它的定义在 `src/compiler/codegen/index.js` 中：\n\n```js\nfunction genSlot (el: ASTElement, state: CodegenState): string {\n  const slotName = el.slotName || '\"default\"'\n  const children = genChildren(el, state)\n  let res = `_t(${slotName}${children ? `,${children}` : ''}`\n  const attrs = el.attrs && `{${el.attrs.map(a => `${camelize(a.name)}:${a.value}`).join(',')}}`\n  const bind = el.attrsMap['v-bind']\n  if ((attrs || bind) && !children) {\n    res += `,null`\n  }\n  if (attrs) {\n    res += `,${attrs}`\n  }\n  if (bind) {\n    res += `${attrs ? '' : ',null'},${bind}`\n  }\n  return res + ')'\n}\n```\n\n我们先不考虑 `slot` 标签上有 `attrs` 以及 `v-bind` 的情况，那么它生成的代码实际上就只有：\n\n```js\nconst slotName = el.slotName || '\"default\"'\nconst children = genChildren(el, state)\nlet res = `_t(${slotName}${children ? `,${children}` : ''}`\n```\n\n这里的 `slotName` 从 AST 元素节点对应的属性上取，默认是 `default`，而 `children` 对应的就是 `slot` 开始和闭合标签包裹的内容。来看一下我们例子的子组件最终生成的代码，如下：\n\n```js\nwith(this) {\n  return _c('div',{\n    staticClass:\"container\"\n    },[\n      _c('header',[_t(\"header\")],2),\n      _c('main',[_t(\"default\",[_v(\"默认内容\")])],2),\n      _c('footer',[_t(\"footer\")],2)\n      ]\n   )\n}\n```\n在编译章节我们了解到，`_t` 函数对应的就是 `renderSlot` 方法，它的定义在 `src/core/instance/render-heplpers/render-slot.js` 中：\n\n```js\n/**\n * Runtime helper for rendering <slot>\n */\nexport function renderSlot (\n  name: string,\n  fallback: ?Array<VNode>,\n  props: ?Object,\n  bindObject: ?Object\n): ?Array<VNode> {\n  const scopedSlotFn = this.$scopedSlots[name]\n  let nodes\n  if (scopedSlotFn) { // scoped slot\n    props = props || {}\n    if (bindObject) {\n      if (process.env.NODE_ENV !== 'production' && !isObject(bindObject)) {\n        warn(\n          'slot v-bind without argument expects an Object',\n          this\n        )\n      }\n      props = extend(extend({}, bindObject), props)\n    }\n    nodes = scopedSlotFn(props) || fallback\n  } else {\n    const slotNodes = this.$slots[name]\n    // warn duplicate slot usage\n    if (slotNodes) {\n      if (process.env.NODE_ENV !== 'production' && slotNodes._rendered) {\n        warn(\n          `Duplicate presence of slot \"${name}\" found in the same render tree ` +\n          `- this will likely cause render errors.`,\n          this\n        )\n      }\n      slotNodes._rendered = true\n    }\n    nodes = slotNodes || fallback\n  }\n\n  const target = props && props.slot\n  if (target) {\n    return this.$createElement('template', { slot: target }, nodes)\n  } else {\n    return nodes\n  }\n}\n```\n\n`render-slot` 的参数 `name` 代表插槽名称 `slotName`，`fallback` 代表插槽的默认内容生成的 `vnode` 数组。先忽略 `scoped-slot`，只看默认插槽逻辑。如果 `this.$slot[name]` 有值，就返回它对应的 `vnode` 数组，否则返回 `fallback`。那么这个 `this.$slot` 是哪里来的呢？我们知道子组件的 `init` 时机是在父组件执行 `patch` 过程的时候，那这个时候父组件已经编译完成了。并且子组件在 `init` 过程中会执行 `initRender` 函数，`initRender` 的时候获取到 `  vm.$slot`，相关代码在 `src/core/instance/render.js` 中：\n\n```js\nexport function initRender (vm: Component) {\n  // ...\n  const parentVnode = vm.$vnode = options._parentVnode // the placeholder node in parent tree\n  const renderContext = parentVnode && parentVnode.context\n  vm.$slots = resolveSlots(options._renderChildren, renderContext)\n}\n```\n\n`vm.$slots` 是通过执行 `resolveSlots(options._renderChildren, renderContext)` 返回的，它的定义在 `src/core/instance/render-helpers/resolve-slots.js` 中：\n\n```js\n/**\n * Runtime helper for resolving raw children VNodes into a slot object.\n */\nexport function resolveSlots (\n  children: ?Array<VNode>,\n  context: ?Component\n): { [key: string]: Array<VNode> } {\n  const slots = {}\n  if (!children) {\n    return slots\n  }\n  for (let i = 0, l = children.length; i < l; i++) {\n    const child = children[i]\n    const data = child.data\n    // remove slot attribute if the node is resolved as a Vue slot node\n    if (data && data.attrs && data.attrs.slot) {\n      delete data.attrs.slot\n    }\n    // named slots should only be respected if the vnode was rendered in the\n    // same context.\n    if ((child.context === context || child.fnContext === context) &&\n      data && data.slot != null\n    ) {\n      const name = data.slot\n      const slot = (slots[name] || (slots[name] = []))\n      if (child.tag === 'template') {\n        slot.push.apply(slot, child.children || [])\n      } else {\n        slot.push(child)\n      }\n    } else {\n      (slots.default || (slots.default = [])).push(child)\n    }\n  }\n  // ignore slots that contains only whitespace\n  for (const name in slots) {\n    if (slots[name].every(isWhitespace)) {\n      delete slots[name]\n    }\n  }\n  return slots\n}\n```\n\n`resolveSlots` 方法接收 2 个参数，第一个参数 `chilren` 对应的是父 `vnode` 的 `children`，在我们的例子中就是 `<app-layout>` 和 `</app-layout>` 包裹的内容。第二个参数 `context` 是父 `vnode` 的上下文，也就是父组件的 `vm` 实例。\n\n`resolveSlots` 函数的逻辑就是遍历 `chilren`，拿到每一个 `child` 的 `data`，然后通过 `data.slot` 获取到插槽名称，这个 `slot` 就是我们之前编译父组件在 `codegen` 阶段设置的 `data.slot`。接着以插槽名称为 `key` 把 `child` 添加到 `slots` 中，如果 `data.slot` 不存在，则是默认插槽的内容，则把对应的 `child` 添加到 `slots.defaults` 中。这样就获取到整个 `slots`，它是一个对象，`key` 是插槽名称，`value` 是一个 `vnode` 类型的数组，因为它可以有多个同名插槽。\n\n这样我们就拿到了 `vm.$slots` 了，回到 `renderSlot` 函数，`const slotNodes = this.$slots[name]`，我们也就能根据插槽名称获取到对应的 `vnode` 数组了，这个数组里的 `vnode` 都是在父组件创建的，这样就实现了在父组件替换子组件插槽的内容了。\n\n对应的 `slot` 渲染成 `vnodes`，作为当前组件渲染 `vnode` 的 `children`，之后的渲染过程之前分析过，不再赘述。\n\n我们知道在普通插槽中，父组件应用到子组件插槽里的数据都是绑定到父组件的，因为它渲染成 `vnode` 的时机的上下文是父组件的实例。但是在一些实际开发中，我们想通过子组件的一些数据来决定父组件实现插槽的逻辑，Vue 提供了另一种插槽——作用域插槽，接下来我们就来分析一下它的实现原理。\n\n## 作用域插槽\n\n为了更加直观，我们也是通过一个例子来分析作用域插槽的实现：\n\n```js\nlet Child = {\n  template: '<div class=\"child\">' +\n  '<slot text=\"Hello \" :msg=\"msg\"></slot>' +\n  '</div>',\n  data() {\n    return {\n      msg: 'Vue'\n    }\n  }\n}\n\nlet vm = new Vue({\n  el: '#app',\n  template: '<div>' +\n  '<child>' +\n  '<template slot-scope=\"props\">' +\n  '<p>Hello from parent</p>' +\n  '<p>{{ props.text + props.msg}}</p>' +\n  '</template>' +\n  '</child>' +\n  '</div>',\n  components: {\n    Child\n  }\n})\n```\n\n最终生成的 DOM 结构如下：\n\n```html\n<div>\n  <div class=\"child\">\n    <p>Hello from parent</p>\n    <p>Hello Vue</p>\n  </div>\n</div>\n```\n\n我们可以看到子组件的 `slot` 标签多了 `text` 属性，以及 `:msg` 属性。父组件实现插槽的部分多了一个 `template` 标签，以及 `scope-slot` 属性，其实在 Vue 2.5+ 版本，`scoped-slot` 可以作用在普通元素上。这些就是作用域插槽和普通插槽在写法上的差别。\n\n在编译阶段，仍然是先编译父组件，同样是通过 `processSlot` 函数去处理 `scoped-slot`，它的定义在在 `src/compiler/parser/index.js` 中：\n\n```js\nfunction processSlot (el) {\n  // ...\n  let slotScope\n  if (el.tag === 'template') {\n    slotScope = getAndRemoveAttr(el, 'scope')\n    /* istanbul ignore if */\n    if (process.env.NODE_ENV !== 'production' && slotScope) {\n      warn(\n        `the \"scope\" attribute for scoped slots have been deprecated and ` +\n        `replaced by \"slot-scope\" since 2.5. The new \"slot-scope\" attribute ` +\n        `can also be used on plain elements in addition to <template> to ` +\n        `denote scoped slots.`,\n        true\n      )\n    }\n    el.slotScope = slotScope || getAndRemoveAttr(el, 'slot-scope')\n  } else if ((slotScope = getAndRemoveAttr(el, 'slot-scope'))) {\n    /* istanbul ignore if */\n    if (process.env.NODE_ENV !== 'production' && el.attrsMap['v-for']) {\n      warn(\n        `Ambiguous combined usage of slot-scope and v-for on <${el.tag}> ` +\n        `(v-for takes higher priority). Use a wrapper <template> for the ` +\n        `scoped slot to make it clearer.`,\n        true\n      )\n    }\n    el.slotScope = slotScope\n  }\n  // ...\n} \n```\n这块逻辑很简单，读取 `scoped-slot` 属性并赋值给当前 AST 元素节点的 `slotScope` 属性，接下来在构造 AST 树的时候，会执行以下逻辑：\n\n```js\nif (element.elseif || element.else) {\n  processIfConditions(element, currentParent)\n} else if (element.slotScope) { \n  currentParent.plain = false\n  const name = element.slotTarget || '\"default\"'\n  ;(currentParent.scopedSlots || (currentParent.scopedSlots = {}))[name] = element\n} else {\n  currentParent.children.push(element)\n  element.parent = currentParent\n}\n```\n可以看到对于拥有 `scopedSlot` 属性的 AST 元素节点而言，是不会作为 `children` 添加到当前 AST 树中，而是存到父 AST 元素节点的 `scopedSlots` 属性上，它是一个对象，以插槽名称 `name` 为 `key`。\n\n然后在 `genData` 的过程，会对 `scopedSlots` 做处理：\n\n```js\nif (el.scopedSlots) {\n  data += `${genScopedSlots(el.scopedSlots, state)},`\n}\n\nfunction genScopedSlots (\n  slots: { [key: string]: ASTElement },\n  state: CodegenState\n): string {\n  return `scopedSlots:_u([${\n    Object.keys(slots).map(key => {\n      return genScopedSlot(key, slots[key], state)\n    }).join(',')\n  }])`\n}\n\nfunction genScopedSlot (\n  key: string,\n  el: ASTElement,\n  state: CodegenState\n): string {\n  if (el.for && !el.forProcessed) {\n    return genForScopedSlot(key, el, state)\n  }\n  const fn = `function(${String(el.slotScope)}){` +\n    `return ${el.tag === 'template'\n      ? el.if\n        ? `${el.if}?${genChildren(el, state) || 'undefined'}:undefined`\n        : genChildren(el, state) || 'undefined'\n      : genElement(el, state)\n    }}`\n  return `{key:${key},fn:${fn}}`\n}\n```\n\n`genScopedSlots` 就是对 `scopedSlots` 对象遍历，执行 `genScopedSlot`，并把结果用逗号拼接，而 `genScopedSlot` 是先生成一段函数代码，并且函数的参数就是我们的 `slotScope`，也就是写在标签属性上的 `scoped-slot` 对应的值，然后再返回一个对象，`key` 为插槽名称，`fn` 为生成的函数代码。\n\n对于我们这个例子而言，父组件最终生成的代码如下：\n\n```js\nwith(this){\n  return _c('div',\n    [_c('child',\n      {scopedSlots:_u([\n        {\n          key: \"default\",\n          fn: function(props) {\n            return [\n              _c('p',[_v(\"Hello from parent\")]),\n              _c('p',[_v(_s(props.text + props.msg))])\n            ]\n          }\n        }])\n      }\n    )],\n  1)\n}\n```\n\n可以看到它和普通插槽父组件编译结果的一个很明显的区别就是没有 `children` 了，`data` 部分多了一个对象，并且执行了 `_u` 方法，在编译章节我们了解到，`_u` 函数对的就是 `resolveScopedSlots` 方法，它的定义在 `src/core/instance/render-heplpers/resolve-slots.js` 中：\n\n```js\nexport function resolveScopedSlots (\n  fns: ScopedSlotsData, // see flow/vnode\n  res?: Object\n): { [key: string]: Function } {\n  res = res || {}\n  for (let i = 0; i < fns.length; i++) {\n    if (Array.isArray(fns[i])) {\n      resolveScopedSlots(fns[i], res)\n    } else {\n      res[fns[i].key] = fns[i].fn\n    }\n  }\n  return res\n}\n```\n\n其中，`fns` 是一个数组，每一个数组元素都有一个 `key` 和一个 `fn`，`key` 对应的是插槽的名称，`fn` 对应一个函数。整个逻辑就是遍历这个 `fns` 数组，生成一个对象，对象的 `key` 就是插槽名称，`value` 就是函数。这个函数的执行时机稍后我们会介绍。\n\n接着我们再来看一下子组件的编译，和普通插槽的过程基本相同，唯一一点区别是在 `genSlot` 的时候：\n\n```js\nfunction genSlot (el: ASTElement, state: CodegenState): string {\n  const slotName = el.slotName || '\"default\"'\n  const children = genChildren(el, state)\n  let res = `_t(${slotName}${children ? `,${children}` : ''}`\n  const attrs = el.attrs && `{${el.attrs.map(a => `${camelize(a.name)}:${a.value}`).join(',')}}`\n  const bind = el.attrsMap['v-bind']\n  if ((attrs || bind) && !children) {\n    res += `,null`\n  }\n  if (attrs) {\n    res += `,${attrs}`\n  }\n  if (bind) {\n    res += `${attrs ? '' : ',null'},${bind}`\n  }\n  return res + ')'\n}\n```\n它会对 `attrs` 和 `v-bind` 做处理，对应到我们的例子，最终生成的代码如下：\n\n```js\nwith(this){\n  return _c('div',\n    {staticClass:\"child\"},\n    [_t(\"default\",null,\n      {text:\"Hello \",msg:msg}\n    )],\n  2)}\n```\n\n`_t` 方法我们之前介绍过，对应的是 `renderSlot` 方法：\n\n```js\nexport function renderSlot (\n  name: string,\n  fallback: ?Array<VNode>,\n  props: ?Object,\n  bindObject: ?Object\n): ?Array<VNode> {\n  const scopedSlotFn = this.$scopedSlots[name]\n  let nodes\n  if (scopedSlotFn) {\n    props = props || {}\n    if (bindObject) {\n      if (process.env.NODE_ENV !== 'production' && !isObject(bindObject)) {\n        warn(\n          'slot v-bind without argument expects an Object',\n          this\n        )\n      }\n      props = extend(extend({}, bindObject), props)\n    }\n    nodes = scopedSlotFn(props) || fallback\n  } else {\n    // ...\n  }\n\n  const target = props && props.slot\n  if (target) {\n    return this.$createElement('template', { slot: target }, nodes)\n  } else {\n    return nodes\n  }\n}\n```\n\n我们只关注作用域插槽的逻辑，那么这个 `this.$scopedSlots` 又是在什么地方定义的呢，原来在子组件的渲染函数执行前，在 `vm_render` 方法内，有这么一段逻辑，定义在 `src/core/instance/render.js` 中：\n\n```js\n if (_parentVnode) {\n  vm.$scopedSlots = _parentVnode.data.scopedSlots || emptyObject\n}\n```\n这个 `_parentVNode.data.scopedSlots` 对应的就是我们在父组件通过执行 `resolveScopedSlots` 返回的对象。所以回到 `genSlot` 函数，我们就可以通过插槽的名称拿到对应的 `scopedSlotFn`，然后把相关的数据扩展到 `props` 上，作为函数的参数传入，原来之前我们提到的函数这个时候执行，然后返回生成的 `vnodes`，为后续渲染节点用。\n\n后续流程之前已介绍过，不再赘述，那么至此，作用域插槽的实现也就分析完毕。\n\n## 总结\n\n通过这一章的分析，我们了解了普通插槽和作用域插槽的实现。它们有一个很大的差别是数据作用域，普通插槽是在父组件编译和渲染阶段生成 `vnodes`，所以数据的作用域是父组件实例，子组件渲染的时候直接拿到这些渲染好的 `vnodes`。而对于作用域插槽，父组件在编译和渲染阶段并不会直接生成 `vnodes`，而是在父节点 `vnode` 的 `data` 中保留一个 `scopedSlots` 对象，存储着不同名称的插槽以及它们对应的渲染函数，只有在编译和渲染子组件阶段才会执行这个渲染函数生成 `vnodes`，由于是在子组件环境执行的，所以对应的数据作用域是子组件实例。\n\n简单地说，两种插槽的目的都是让子组件 `slot` 占位符生成的内容由父组件来决定，但数据的作用域会根据它们 `vnodes` 渲染时机不同而不同。\n\n\n\n\n\n"
  },
  {
    "path": "docs/v2/extend/tansition-group.md",
    "content": "# transition-group\n\n前一节我们介绍了 `<transiiton>` 组件的实现原理，它只能针对单一元素实现过渡效果。我们做前端开发经常会遇到列表的需求，我们对列表元素进行添加和删除，有时候也希望有过渡效果，Vue.js 提供了 `<transition-group>` 组件，很好地帮助我们实现了列表的过渡效果。那么接下来我们就来分析一下它的实现原理。\n\n为了更直观，我们也是通过一个示例来说明：\n\n```js\nlet vm = new Vue({\n  el: '#app',\n  template: '<div id=\"list-complete-demo\" class=\"demo\">' +\n  '<button v-on:click=\"add\">Add</button>' +\n  '<button v-on:click=\"remove\">Remove</button>' +\n  '<transition-group name=\"list-complete\" tag=\"p\">' +\n  '<span v-for=\"item in items\" v-bind:key=\"item\" class=\"list-complete-item\">' +\n  '{{ item }}' +\n  '</span>' +\n  '</transition-group>' +\n  '</div>',\n  data: {\n    items: [1, 2, 3, 4, 5, 6, 7, 8, 9],\n    nextNum: 10\n  },\n  methods: {\n    randomIndex: function () {\n      return Math.floor(Math.random() * this.items.length)\n    },\n    add: function () {\n      this.items.splice(this.randomIndex(), 0, this.nextNum++)\n    },\n    remove: function () {\n      this.items.splice(this.randomIndex(), 1)\n    }\n  }\n})\n```\n\n```css\n .list-complete-item {\n  display: inline-block;\n  margin-right: 10px;\n}\n.list-complete-move {\n  transition: all 1s;\n}\n.list-complete-enter, .list-complete-leave-to {\n  opacity: 0;\n  transform: translateY(30px);\n}\n.list-complete-enter-active {\n  transition: all 1s;\n}\n.list-complete-leave-active {\n  transition: all 1s;\n  position: absolute;\n}\n```\n\n这个示例初始会展现 1-9 十个数字，当我们点击 `Add` 按钮时，会生成 `nextNum` 并随机在当前数列表中插入；当我们点击 `Remove` 按钮时，会随机删除掉一个数。我们会发现在数添加删除的过程中在列表中会有过渡动画，这就是 `<transition-group>` 组件配合我们定义的 CSS 产生的效果。\n\n我们首先还是来分析 `<transtion-group>` 组件的实现，它的定义在 `src/platforms/web/runtime/components/transitions.js` 中：\n\n```js\nconst props = extend({\n  tag: String,\n  moveClass: String\n}, transitionProps)\n\ndelete props.mode\n\nexport default {\n  props,\n\n  beforeMount () {\n    const update = this._update\n    this._update = (vnode, hydrating) => {\n      // force removing pass\n      this.__patch__(\n        this._vnode,\n        this.kept,\n        false, // hydrating\n        true // removeOnly (!important, avoids unnecessary moves)\n      )\n      this._vnode = this.kept\n      update.call(this, vnode, hydrating)\n    }\n  },\n\n  render (h: Function) {\n    const tag: string = this.tag || this.$vnode.data.tag || 'span'\n    const map: Object = Object.create(null)\n    const prevChildren: Array<VNode> = this.prevChildren = this.children\n    const rawChildren: Array<VNode> = this.$slots.default || []\n    const children: Array<VNode> = this.children = []\n    const transitionData: Object = extractTransitionData(this)\n\n    for (let i = 0; i < rawChildren.length; i++) {\n      const c: VNode = rawChildren[i]\n      if (c.tag) {\n        if (c.key != null && String(c.key).indexOf('__vlist') !== 0) {\n          children.push(c)\n          map[c.key] = c\n          ;(c.data || (c.data = {})).transition = transitionData\n        } else if (process.env.NODE_ENV !== 'production') {\n          const opts: ?VNodeComponentOptions = c.componentOptions\n          const name: string = opts ? (opts.Ctor.options.name || opts.tag || '') : c.tag\n          warn(`<transition-group> children must be keyed: <${name}>`)\n        }\n      }\n    }\n\n    if (prevChildren) {\n      const kept: Array<VNode> = []\n      const removed: Array<VNode> = []\n      for (let i = 0; i < prevChildren.length; i++) {\n        const c: VNode = prevChildren[i]\n        c.data.transition = transitionData\n        c.data.pos = c.elm.getBoundingClientRect()\n        if (map[c.key]) {\n          kept.push(c)\n        } else {\n          removed.push(c)\n        }\n      }\n      this.kept = h(tag, null, kept)\n      this.removed = removed\n    }\n\n    return h(tag, null, children)\n  },\n\n  updated () {\n    const children: Array<VNode> = this.prevChildren\n    const moveClass: string = this.moveClass || ((this.name || 'v') + '-move')\n    if (!children.length || !this.hasMove(children[0].elm, moveClass)) {\n      return\n    }\n\n    // we divide the work into three loops to avoid mixing DOM reads and writes\n    // in each iteration - which helps prevent layout thrashing.\n    children.forEach(callPendingCbs)\n    children.forEach(recordPosition)\n    children.forEach(applyTranslation)\n\n    // force reflow to put everything in position\n    // assign to this to avoid being removed in tree-shaking\n    // $flow-disable-line\n    this._reflow = document.body.offsetHeight\n\n    children.forEach((c: VNode) => {\n      if (c.data.moved) {\n        var el: any = c.elm\n        var s: any = el.style\n        addTransitionClass(el, moveClass)\n        s.transform = s.WebkitTransform = s.transitionDuration = ''\n        el.addEventListener(transitionEndEvent, el._moveCb = function cb (e) {\n          if (!e || /transform$/.test(e.propertyName)) {\n            el.removeEventListener(transitionEndEvent, cb)\n            el._moveCb = null\n            removeTransitionClass(el, moveClass)\n          }\n        })\n      }\n    })\n  },\n\n  methods: {\n    hasMove (el: any, moveClass: string): boolean {\n      /* istanbul ignore if */\n      if (!hasTransition) {\n        return false\n      }\n      /* istanbul ignore if */\n      if (this._hasMove) {\n        return this._hasMove\n      }\n      // Detect whether an element with the move class applied has\n      // CSS transitions. Since the element may be inside an entering\n      // transition at this very moment, we make a clone of it and remove\n      // all other transition classes applied to ensure only the move class\n      // is applied.\n      const clone: HTMLElement = el.cloneNode()\n      if (el._transitionClasses) {\n        el._transitionClasses.forEach((cls: string) => { removeClass(clone, cls) })\n      }\n      addClass(clone, moveClass)\n      clone.style.display = 'none'\n      this.$el.appendChild(clone)\n      const info: Object = getTransitionInfo(clone)\n      this.$el.removeChild(clone)\n      return (this._hasMove = info.hasTransform)\n    }\n  }\n}\n```\n\n## render 函数\n\n`<transition-group>` 组件也是由 `render` 函数渲染生成 `vnode`，接下来我们先分析 `render` 的实现。\n\n- 定义一些变量\n \n```js\nconst tag: string = this.tag || this.$vnode.data.tag || 'span'\nconst map: Object = Object.create(null)\nconst prevChildren: Array<VNode> = this.prevChildren = this.children\nconst rawChildren: Array<VNode> = this.$slots.default || []\nconst children: Array<VNode> = this.children = []\nconst transitionData: Object = extractTransitionData(this)\n```\n不同于 `<transition>` 组件，`<transition-group>` 组件非抽象组件，它会渲染成一个真实元素，默认 `tag` 是 `span`。 `prevChildren` 用来存储上一次的子节点；`children` 用来存储当前的子节点；`rawChildren` 表示 `<transtition-group>` 包裹的原始子节点；`transtionData` 是从 `<transtition-group>` 组件上提取出来的一些渲染数据，这点和 `<transition>` 组件的实现是一样的。\n\n- 遍历 `rawChidren`，初始化 `children`\n\n```js\nfor (let i = 0; i < rawChildren.length; i++) {\n  const c: VNode = rawChildren[i]\n  if (c.tag) {\n    if (c.key != null && String(c.key).indexOf('__vlist') !== 0) {\n      children.push(c)\n      map[c.key] = c\n      ;(c.data || (c.data = {})).transition = transitionData\n    } else if (process.env.NODE_ENV !== 'production') {\n      const opts: ?VNodeComponentOptions = c.componentOptions\n      const name: string = opts ? (opts.Ctor.options.name || opts.tag || '') : c.tag\n      warn(`<transition-group> children must be keyed: <${name}>`)\n    }\n  }\n}\n```\n\n其实就是对 `rawChildren` 遍历，拿到每个 `vnode`，然后会判断每个 `vnode` 是否设置了 `key`，这个是 `<transition-group>` 对列表元素的要求。然后把 `vnode` 添加到 `children` 中，然后把刚刚提取的过渡数据 `transitionData` 添加的 `vnode.data.transition` 中，这点很关键，只有这样才能实现列表中单个元素的过渡动画。\n\n- 处理 prevChildren\n\n```js\nif (prevChildren) {\n  const kept: Array<VNode> = []\n  const removed: Array<VNode> = []\n  for (let i = 0; i < prevChildren.length; i++) {\n    const c: VNode = prevChildren[i]\n    c.data.transition = transitionData\n    c.data.pos = c.elm.getBoundingClientRect()\n    if (map[c.key]) {\n      kept.push(c)\n    } else {\n      removed.push(c)\n    }\n  }\n  this.kept = h(tag, null, kept)\n  this.removed = removed\n}\n\nreturn h(tag, null, children)\n```\n\n当有 `prevChildren` 的时候，我们会对它做遍历，获取到每个 `vnode`，然后把 `transitionData` 赋值到 `vnode.data.transition`，这个是为了当它在 `enter` 和 `leave` 的钩子函数中有过渡动画，我们在上节介绍 `transition` 的实现中说过。接着又调用了原生 DOM 的 `getBoundingClientRect` 方法获取到原生 DOM 的位置信息，记录到 `vnode.data.pos` 中，然后判断一下 `vnode.key` 是否在 `map` 中，如果在则放入 `kept` 中，否则表示该节点已被删除，放入 `removed` 中，然后通过执行 `h(tag, null, kept)` 渲染后放入 `this.kept` 中，把 `removed` 用 `this.removed` 保存。最后整个 `render` 函数通过 `h(tag, null, children)` 生成渲染 `vnode`。\n\n如果 `transition-group` 只实现了这个 `render` 函数，那么每次插入和删除的元素的缓动动画是可以实现的，在我们的例子中，当新增一个元素，它的插入的过渡动画是有的，但是剩余元素平移的过渡效果是出不来的，所以接下来我们来分析 `<transition-group>` 组件是如何实现剩余元素平移的过渡效果的。\n\n## move 过渡实现\n\n其实我们在实现元素的插入和删除，无非就是操作数据，控制它们的添加和删除。比如我们新增数据的时候，会添加一条数据，除了重新执行 `render` 函数渲染新的节点外，还要触发 `updated` 钩子函数，接着我们就来分析 `updated` 钩子函数的实现。\n\n- 判断子元素是否定义 `move` 相关样式\n\n```js\nconst children: Array<VNode> = this.prevChildren\nconst moveClass: string = this.moveClass || ((this.name || 'v') + '-move')\nif (!children.length || !this.hasMove(children[0].elm, moveClass)) {\n  return\n}\n\nhasMove (el: any, moveClass: string): boolean {\n  /* istanbul ignore if */\n  if (!hasTransition) {\n    return false\n  }\n  /* istanbul ignore if */\n  if (this._hasMove) {\n    return this._hasMove\n  }\n  // Detect whether an element with the move class applied has\n  // CSS transitions. Since the element may be inside an entering\n  // transition at this very moment, we make a clone of it and remove\n  // all other transition classes applied to ensure only the move class\n  // is applied.\n  const clone: HTMLElement = el.cloneNode()\n  if (el._transitionClasses) {\n    el._transitionClasses.forEach((cls: string) => { removeClass(clone, cls) })\n  }\n  addClass(clone, moveClass)\n  clone.style.display = 'none'\n  this.$el.appendChild(clone)\n  const info: Object = getTransitionInfo(clone)\n  this.$el.removeChild(clone)\n  return (this._hasMove = info.hasTransform)\n}\n```\n核心就是 `hasMove` 的判断，首先克隆一个 DOM 节点，然后为了避免影响，移除它的所有其他的过渡 `Class`；接着添加了 `moveClass` 样式，设置 `display` 为 `none`，添加到组件根节点上；接下来通过 `getTransitionInfo` 获取它的一些缓动相关的信息，这个函数在上一节我们也介绍过，然后从组件根节点上删除这个克隆节点，并通过判断 `info.hasTransform` 来判断 `hasMove`，在我们的例子中，该值为 `true`。\n\n- 子节点预处理\n\n```js\nchildren.forEach(callPendingCbs)\nchildren.forEach(recordPosition)\nchildren.forEach(applyTranslation)\n```\n\n对 `children` 做了 3 轮循环，分别做了如下一些处理：\n\n```js\nfunction callPendingCbs (c: VNode) {\n  if (c.elm._moveCb) {\n    c.elm._moveCb()\n  }\n  if (c.elm._enterCb) {\n    c.elm._enterCb()\n  }\n}\n\nfunction recordPosition (c: VNode) {\n  c.data.newPos = c.elm.getBoundingClientRect()\n}\n\nfunction applyTranslation (c: VNode) {\n  const oldPos = c.data.pos\n  const newPos = c.data.newPos\n  const dx = oldPos.left - newPos.left\n  const dy = oldPos.top - newPos.top\n  if (dx || dy) {\n    c.data.moved = true\n    const s = c.elm.style\n    s.transform = s.WebkitTransform = `translate(${dx}px,${dy}px)`\n    s.transitionDuration = '0s'\n  }\n}\n```\n\n`callPendingCbs` 方法是在前一个过渡动画没执行完又再次执行到该方法的时候，会提前执行 `_moveCb` 和 `_enterCb`。\n\n`recordPosition` 的作用是记录节点的新位置。\n\n`applyTranslation` 的作用是先计算节点新位置和旧位置的差值，如果差值不为 0，则说明这些节点是需要移动的，所以记录 `vnode.data.moved` 为 true，并且通过设置 `transform` 把需要移动的节点的位置又偏移到之前的旧位置，目的是为了做 `move` 缓动做准备。\n\n- 遍历子元素实现 move 过渡\n\n```js\nthis._reflow = document.body.offsetHeight\n\nchildren.forEach((c: VNode) => {\n  if (c.data.moved) {\n    var el: any = c.elm\n    var s: any = el.style\n    addTransitionClass(el, moveClass)\n    s.transform = s.WebkitTransform = s.transitionDuration = ''\n    el.addEventListener(transitionEndEvent, el._moveCb = function cb (e) {\n      if (!e || /transform$/.test(e.propertyName)) {\n        el.removeEventListener(transitionEndEvent, cb)\n        el._moveCb = null\n        removeTransitionClass(el, moveClass)\n      }\n    })\n  }\n})\n```\n\n首先通过 `document.body.offsetHeight` 强制触发浏览器重绘，接着再次对 `children` 遍历，先给子节点添加 `moveClass`，在我们的例子中，`moveClass` 定义了 `transition: all 1s;` 缓动；接着把子节点的 `style.transform` 设置为空，由于我们前面把这些节点偏移到之前的旧位置，所以它就会从旧位置按照 `1s` 的缓动时间过渡偏移到它的当前目标位置，这样就实现了 move 的过渡动画。并且接下来会监听 `transitionEndEvent` 过渡结束的事件，做一些清理的操作。\n\n另外，由于虚拟 DOM 的子元素更新算法是不稳定的，它不能保证被移除元素的相对位置，所以我们强制 `<transition-group>` 组件更新子节点通过 2 个步骤：第一步我们移除需要移除的 `vnode`，同时触发它们的 `leaving` 过渡；第二步我们需要把插入和移动的节点达到它们的最终态，同时还要保证移除的节点保留在应该的位置，而这个是通过 `beforeMount` 钩子函数来实现的：\n\n```js\nbeforeMount () {\n  const update = this._update\n  this._update = (vnode, hydrating) => {\n    // force removing pass\n    this.__patch__(\n      this._vnode,\n      this.kept,\n      false, // hydrating\n      true // removeOnly (!important, avoids unnecessary moves)\n    )\n    this._vnode = this.kept\n    update.call(this, vnode, hydrating)\n  }\n}\n```\n\n通过把 `__patch__` 方法的第四个参数 `removeOnly` 设置为 true，这样在 `updateChildren` 阶段，是不会移动 `vnode` 节点的。\n\n## 总结\n\n那么到此，`<transtion-group>` 组件的实现原理就介绍完毕了，它和 `<transition>` 组件相比，实现了列表的过渡，以及它会渲染成真实的元素。当我们去修改列表的数据的时候，如果是添加或者删除数据，则会触发相应元素本身的过渡动画，这点和 `<transition>` 组件实现效果一样，除此之外 `<transtion-group>` 还实现了 move 的过渡效果，让我们的列表过渡动画更加丰富。"
  },
  {
    "path": "docs/v2/extend/tansition.md",
    "content": "# transition\n\n在我们平时的前端项目开发中，经常会遇到如下需求，一个 DOM 节点的插入和删除或者是显示和隐藏，我们不想让它特别生硬，通常会考虑加一些过渡效果。\n\nVue.js 除了实现了强大的数据驱动，组件化的能力，也给我们提供了一整套过渡的解决方案。它内置了 `<transition>` 组件，我们可以利用它配合一些 CSS3 样式很方便地实现过渡动画，也可以利用它配合 JavaScript 的钩子函数实现过渡动画，在下列情形中，可以给任何元素和组件添加 entering/leaving 过渡：\n\n- 条件渲染 (使用 `v-if`)\n- 条件展示 (使用 `v-show`)\n- 动态组件\n- 组件根节点\n\n那么举一个最简单的实例，如下：\n\n```js\nlet vm = new Vue({\n  el: '#app',\n  template: '<div id=\"demo\">' +\n  '<button v-on:click=\"show = !show\">' +\n  'Toggle' +\n  '</button>' +\n  '<transition :appear=\"true\" name=\"fade\">' +\n  '<p v-if=\"show\">hello</p>' +\n  '</transition>' +\n  '</div>',\n  data() {\n    return {\n      show: true\n    }\n  }\n})\n```\n\n```css\n.fade-enter-active, .fade-leave-active {\n  transition: opacity .5s;\n}\n.fade-enter, .fade-leave-to {\n  opacity: 0;\n}\n```\n\n当我们点击按钮切换显示状态的时候，被 `<transition>` 包裹的内容会有过渡动画。那么接下来我们从源码的角度来分析它的实现原理。\n\n## 内置组件\n\n`<transition>` 组件和 `<keep-alive>` 组件一样，都是 Vue 的内置组件，而 `<transition>` 的定义在 `src/platforms/web/runtime/component/transtion.js` 中，之所以在这里定义，是因为 `<transition>` 组件是 web 平台独有的，先来看一下它的实现：\n\n```js\nexport default {\n  name: 'transition',\n  props: transitionProps,\n  abstract: true,\n\n  render (h: Function) {\n    let children: any = this.$slots.default\n    if (!children) {\n      return\n    }\n\n    // filter out text nodes (possible whitespaces)\n    children = children.filter((c: VNode) => c.tag || isAsyncPlaceholder(c))\n    /* istanbul ignore if */\n    if (!children.length) {\n      return\n    }\n\n    // warn multiple elements\n    if (process.env.NODE_ENV !== 'production' && children.length > 1) {\n      warn(\n        '<transition> can only be used on a single element. Use ' +\n        '<transition-group> for lists.',\n        this.$parent\n      )\n    }\n\n    const mode: string = this.mode\n\n    // warn invalid mode\n    if (process.env.NODE_ENV !== 'production' &&\n      mode && mode !== 'in-out' && mode !== 'out-in'\n    ) {\n      warn(\n        'invalid <transition> mode: ' + mode,\n        this.$parent\n      )\n    }\n\n    const rawChild: VNode = children[0]\n\n    // if this is a component root node and the component's\n    // parent container node also has transition, skip.\n    if (hasParentTransition(this.$vnode)) {\n      return rawChild\n    }\n\n    // apply transition data to child\n    // use getRealChild() to ignore abstract components e.g. keep-alive\n    const child: ?VNode = getRealChild(rawChild)\n    /* istanbul ignore if */\n    if (!child) {\n      return rawChild\n    }\n\n    if (this._leaving) {\n      return placeholder(h, rawChild)\n    }\n\n    // ensure a key that is unique to the vnode type and to this transition\n    // component instance. This key will be used to remove pending leaving nodes\n    // during entering.\n    const id: string = `__transition-${this._uid}-`\n    child.key = child.key == null\n      ? child.isComment\n        ? id + 'comment'\n        : id + child.tag\n      : isPrimitive(child.key)\n        ? (String(child.key).indexOf(id) === 0 ? child.key : id + child.key)\n        : child.key\n\n    const data: Object = (child.data || (child.data = {})).transition = extractTransitionData(this)\n    const oldRawChild: VNode = this._vnode\n    const oldChild: VNode = getRealChild(oldRawChild)\n\n    // mark v-show\n    // so that the transition module can hand over the control to the directive\n    if (child.data.directives && child.data.directives.some(d => d.name === 'show')) {\n      child.data.show = true\n    }\n\n    if (\n      oldChild &&\n      oldChild.data &&\n      !isSameChild(child, oldChild) &&\n      !isAsyncPlaceholder(oldChild) &&\n      // #6687 component root is a comment node\n      !(oldChild.componentInstance && oldChild.componentInstance._vnode.isComment)\n    ) {\n      // replace old child transition data with fresh one\n      // important for dynamic transitions!\n      const oldData: Object = oldChild.data.transition = extend({}, data)\n      // handle transition mode\n      if (mode === 'out-in') {\n        // return placeholder node and queue update when leave finishes\n        this._leaving = true\n        mergeVNodeHook(oldData, 'afterLeave', () => {\n          this._leaving = false\n          this.$forceUpdate()\n        })\n        return placeholder(h, rawChild)\n      } else if (mode === 'in-out') {\n        if (isAsyncPlaceholder(child)) {\n          return oldRawChild\n        }\n        let delayedLeave\n        const performLeave = () => { delayedLeave() }\n        mergeVNodeHook(data, 'afterEnter', performLeave)\n        mergeVNodeHook(data, 'enterCancelled', performLeave)\n        mergeVNodeHook(oldData, 'delayLeave', leave => { delayedLeave = leave })\n      }\n    }\n\n    return rawChild\n  }\n}\n```\n\n`<transition>` 组件和 `<keep-alive>` 组件有几点实现类似，同样是抽象组件，同样直接实现 `render` 函数，同样利用了默认插槽。`<transition>` 组件非常灵活，支持的 `props` 非常多：\n\n```js\nexport const transitionProps = {\n  name: String,\n  appear: Boolean,\n  css: Boolean,\n  mode: String,\n  type: String,\n  enterClass: String,\n  leaveClass: String,\n  enterToClass: String,\n  leaveToClass: String,\n  enterActiveClass: String,\n  leaveActiveClass: String,\n  appearClass: String,\n  appearActiveClass: String,\n  appearToClass: String,\n  duration: [Number, String, Object]\n}\n```\n\n这些配置我们稍后会分析它们的作用，`<transition>` 组件另一个重要的就是 `render` 函数的实现，`render` 函数主要作用就是渲染生成 `vnode`，下面来看一下这部分的逻辑。\n\n- 处理 `children`\n\n```js\nlet children: any = this.$slots.default\nif (!children) {\n  return\n}\n\n// filter out text nodes (possible whitespaces)\nchildren = children.filter((c: VNode) => c.tag || isAsyncPlaceholder(c))\n/* istanbul ignore if */\nif (!children.length) {\n  return\n}\n\n// warn multiple elements\nif (process.env.NODE_ENV !== 'production' && children.length > 1) {\n  warn(\n    '<transition> can only be used on a single element. Use ' +\n    '<transition-group> for lists.',\n    this.$parent\n  )\n}\n```\n\n先从默认插槽中获取 `<transition>` 包裹的子节点，并且判断了子节点的长度，如果长度为 0，则直接返回，否则判断长度如果大于 1，也会在开发环境报警告，因为 `<transition>` 组件是只能包裹一个子节点的。\n\n- 处理 `model`\n\n```js\nconst mode: string = this.mode\n\n// warn invalid mode\nif (process.env.NODE_ENV !== 'production' &&\n  mode && mode !== 'in-out' && mode !== 'out-in'\n) {\n  warn(\n    'invalid <transition> mode: ' + mode,\n    this.$parent\n  )\n}\n```\n\n过渡组件的对 `mode` 的支持只有 2 种，`in-out` 或者是 `out-in`。\n\n- 获取 `rawChild` & `child`\n\n```js\nconst rawChild: VNode = children[0]\n\n// if this is a component root node and the component's\n// parent container node also has transition, skip.\nif (hasParentTransition(this.$vnode)) {\n  return rawChild\n}\n\n// apply transition data to child\n// use getRealChild() to ignore abstract components e.g. keep-alive\nconst child: ?VNode = getRealChild(rawChild)\n/* istanbul ignore if */\nif (!child) {\n  return rawChild\n}\n\n```\n\n`rawChild` 就是第一个子节点 `vnode`，接着判断当前 `<transition>` 如果是组件根节点并且外面包裹该组件的容器也是 `<transition>` 的时候要跳过。来看一下 `hasParentTransition` 的实现：\n\n```js\nfunction hasParentTransition (vnode: VNode): ?boolean {\n  while ((vnode = vnode.parent)) {\n    if (vnode.data.transition) {\n      return true\n    }\n  }\n}\n```\n因为传入的是 `this.$vnode`，也就是 `<transition>` 组件的 占位 `vnode`，只有当它同时作为根 `vnode`，也就是 `vm._vnode` 的时候，它的 `parent` 才不会为空，并且判断 `parent` 也是 `<transition>` 组件，才返回 true，`vnode.data.transition` 我们稍后会介绍。\n\n`getRealChild` 的目的是获取组件的非抽象子节点，因为 `<transition>` 很可能会包裹一个 `keep-alive`，它的实现如下：\n\n```js\n// in case the child is also an abstract component, e.g. <keep-alive>\n// we want to recursively retrieve the real component to be rendered\nfunction getRealChild (vnode: ?VNode): ?VNode {\n  const compOptions: ?VNodeComponentOptions = vnode && vnode.componentOptions\n  if (compOptions && compOptions.Ctor.options.abstract) {\n    return getRealChild(getFirstComponentChild(compOptions.children))\n  } else {\n    return vnode\n  }\n}\n```\n会递归找到第一个非抽象组件的 `vnode` 并返回，在我们这个 case 下，`rawChild === child`。\n\n- 处理 `id` & `data`\n\n```js\n// ensure a key that is unique to the vnode type and to this transition\n// component instance. This key will be used to remove pending leaving nodes\n// during entering.\nconst id: string = `__transition-${this._uid}-`\nchild.key = child.key == null\n  ? child.isComment\n    ? id + 'comment'\n    : id + child.tag\n  : isPrimitive(child.key)\n    ? (String(child.key).indexOf(id) === 0 ? child.key : id + child.key)\n    : child.key\n\nconst data: Object = (child.data || (child.data = {})).transition = extractTransitionData(this)\nconst oldRawChild: VNode = this._vnode\nconst oldChild: VNode = getRealChild(oldRawChild)\n\n// mark v-show\n// so that the transition module can hand over the control to the directive\nif (child.data.directives && child.data.directives.some(d => d.name === 'show')) {\n  child.data.show = true\n}\n```\n\n先根据 `key` 等一系列条件获取 `id`，接着从当前通过 `extractTransitionData` 组件实例上提取出过渡所需要的数据：\n\n```js\nexport function extractTransitionData (comp: Component): Object {\n  const data = {}\n  const options: ComponentOptions = comp.$options\n  // props\n  for (const key in options.propsData) {\n    data[key] = comp[key]\n  }\n  // events.\n  // extract listeners and pass them directly to the transition methods\n  const listeners: ?Object = options._parentListeners\n  for (const key in listeners) {\n    data[camelize(key)] = listeners[key]\n  }\n  return data\n}\n```\n首先是遍历 `props` 赋值到 `data` 中，接着是遍历所有父组件的事件也把事件回调赋值到 `data` 中。\n\n这样 `child.data.transition` 中就包含了过渡所需的一些数据，这些稍后都会用到，对于 `child` 如果使用了 `v-show` 指令，也会把 `child.data.show` 设置为 true，在我们的例子中，得到的 `child.data` 如下：\n\n```js\n{\n  transition: {\n    appear: true,\n    name: 'fade'\n  }\n}\n```\n\n至于 `oldRawChild` 和 `oldChild` 是与后面的判断逻辑相关，这些我们这里先不介绍。\n\n## transition module\n\n刚刚我们介绍完 `<transition>` 组件的实现，它的 `render` 阶段只获取了一些数据，并且返回了渲染的 `vnode`，并没有任何和动画相关，而动画相关的逻辑全部在 `src/platforms/web/modules/transition.js` 中：\n\n```js\nfunction _enter (_: any, vnode: VNodeWithData) {\n  if (vnode.data.show !== true) {\n    enter(vnode)\n  }\n}\n\nexport default inBrowser ? {\n  create: _enter,\n  activate: _enter,\n  remove (vnode: VNode, rm: Function) {\n    /* istanbul ignore else */\n    if (vnode.data.show !== true) {\n      leave(vnode, rm)\n    } else {\n      rm()\n    }\n  }\n} : {}\n```\n\n在之前介绍事件实现的章节中我们提到过在 `vnode patch` 的过程中，会执行很多钩子函数，那么对于过渡的实现，它只接收了 `create` 和 `activate` 2 个钩子函数，我们知道 `create` 钩子函数只有当节点的创建过程才会执行，而 `remove` 会在节点销毁的时候执行，这也就印证了 `<transition>` 必须要满足 `v-if` 、动态组件、组件根节点条件之一了，对于 `v-show` 在它的指令的钩子函数中也会执行相关逻辑，这块儿先不介绍。\n\n过渡动画提供了 2 个时机，一个是 `create` 和 `activate` 的时候提供了 entering 进入动画，一个是 `remove` 的时候提供了 leaving 离开动画，那么接下来我们就来分别去分析这两个过程。\n\n## entering\n\n整个 entering 过程的实现是 `enter` 函数：\n\n```js\nexport function enter (vnode: VNodeWithData, toggleDisplay: ?() => void) {\n  const el: any = vnode.elm\n\n  // call leave callback now\n  if (isDef(el._leaveCb)) {\n    el._leaveCb.cancelled = true\n    el._leaveCb()\n  }\n\n  const data = resolveTransition(vnode.data.transition)\n  if (isUndef(data)) {\n    return\n  }\n\n  /* istanbul ignore if */\n  if (isDef(el._enterCb) || el.nodeType !== 1) {\n    return\n  }\n\n  const {\n    css,\n    type,\n    enterClass,\n    enterToClass,\n    enterActiveClass,\n    appearClass,\n    appearToClass,\n    appearActiveClass,\n    beforeEnter,\n    enter,\n    afterEnter,\n    enterCancelled,\n    beforeAppear,\n    appear,\n    afterAppear,\n    appearCancelled,\n    duration\n  } = data\n\n  // activeInstance will always be the <transition> component managing this\n  // transition. One edge case to check is when the <transition> is placed\n  // as the root node of a child component. In that case we need to check\n  // <transition>'s parent for appear check.\n  let context = activeInstance\n  let transitionNode = activeInstance.$vnode\n  while (transitionNode && transitionNode.parent) {\n    transitionNode = transitionNode.parent\n    context = transitionNode.context\n  }\n\n  const isAppear = !context._isMounted || !vnode.isRootInsert\n\n  if (isAppear && !appear && appear !== '') {\n    return\n  }\n\n  const startClass = isAppear && appearClass\n    ? appearClass\n    : enterClass\n  const activeClass = isAppear && appearActiveClass\n    ? appearActiveClass\n    : enterActiveClass\n  const toClass = isAppear && appearToClass\n    ? appearToClass\n    : enterToClass\n\n  const beforeEnterHook = isAppear\n    ? (beforeAppear || beforeEnter)\n    : beforeEnter\n  const enterHook = isAppear\n    ? (typeof appear === 'function' ? appear : enter)\n    : enter\n  const afterEnterHook = isAppear\n    ? (afterAppear || afterEnter)\n    : afterEnter\n  const enterCancelledHook = isAppear\n    ? (appearCancelled || enterCancelled)\n    : enterCancelled\n\n  const explicitEnterDuration: any = toNumber(\n    isObject(duration)\n      ? duration.enter\n      : duration\n  )\n\n  if (process.env.NODE_ENV !== 'production' && explicitEnterDuration != null) {\n    checkDuration(explicitEnterDuration, 'enter', vnode)\n  }\n\n  const expectsCSS = css !== false && !isIE9\n  const userWantsControl = getHookArgumentsLength(enterHook)\n\n  const cb = el._enterCb = once(() => {\n    if (expectsCSS) {\n      removeTransitionClass(el, toClass)\n      removeTransitionClass(el, activeClass)\n    }\n    if (cb.cancelled) {\n      if (expectsCSS) {\n        removeTransitionClass(el, startClass)\n      }\n      enterCancelledHook && enterCancelledHook(el)\n    } else {\n      afterEnterHook && afterEnterHook(el)\n    }\n    el._enterCb = null\n  })\n\n  if (!vnode.data.show) {\n    // remove pending leave element on enter by injecting an insert hook\n    mergeVNodeHook(vnode, 'insert', () => {\n      const parent = el.parentNode\n      const pendingNode = parent && parent._pending && parent._pending[vnode.key]\n      if (pendingNode &&\n        pendingNode.tag === vnode.tag &&\n        pendingNode.elm._leaveCb\n      ) {\n        pendingNode.elm._leaveCb()\n      }\n      enterHook && enterHook(el, cb)\n    })\n  }\n\n  // start enter transition\n  beforeEnterHook && beforeEnterHook(el)\n  if (expectsCSS) {\n    addTransitionClass(el, startClass)\n    addTransitionClass(el, activeClass)\n    nextFrame(() => {\n      removeTransitionClass(el, startClass)\n      if (!cb.cancelled) {\n        addTransitionClass(el, toClass)\n        if (!userWantsControl) {\n          if (isValidDuration(explicitEnterDuration)) {\n            setTimeout(cb, explicitEnterDuration)\n          } else {\n            whenTransitionEnds(el, type, cb)\n          }\n        }\n      }\n    })\n  }\n\n  if (vnode.data.show) {\n    toggleDisplay && toggleDisplay()\n    enterHook && enterHook(el, cb)\n  }\n\n  if (!expectsCSS && !userWantsControl) {\n    cb()\n  }\n}\n```\n\n`enter` 的代码很长，我们先分析其中的核心逻辑。\n\n- 解析过渡数据\n\n```js\nconst data = resolveTransition(vnode.data.transition)\n  if (isUndef(data)) {\n    return\n}\n\nconst {\n  css,\n  type,\n  enterClass,\n  enterToClass,\n  enterActiveClass,\n  appearClass,\n  appearToClass,\n  appearActiveClass,\n  beforeEnter,\n  enter,\n  afterEnter,\n  enterCancelled,\n  beforeAppear,\n  appear,\n  afterAppear,\n  appearCancelled,\n  duration\n} = data\n```\n\n从 `vnode.data.transition` 中解析出过渡相关的一些数据，`resolveTransition` 的定义在 `src/platforms/web/transition-util.js` 中：\n\n```js\nexport function resolveTransition (def?: string | Object): ?Object {\n  if (!def) {\n    return\n  }\n  /* istanbul ignore else */\n  if (typeof def === 'object') {\n    const res = {}\n    if (def.css !== false) {\n      extend(res, autoCssTransition(def.name || 'v'))\n    }\n    extend(res, def)\n    return res\n  } else if (typeof def === 'string') {\n    return autoCssTransition(def)\n  }\n}\n\nconst autoCssTransition: (name: string) => Object = cached(name => {\n  return {\n    enterClass: `${name}-enter`,\n    enterToClass: `${name}-enter-to`,\n    enterActiveClass: `${name}-enter-active`,\n    leaveClass: `${name}-leave`,\n    leaveToClass: `${name}-leave-to`,\n    leaveActiveClass: `${name}-leave-active`\n  }\n})\n```\n`resolveTransition` 会通过 `autoCssTransition` 处理 `name` 属性，生成一个用来描述各个阶段的 `Class` 名称的对象，扩展到 `def` 中并返回给 `data`，这样我们就可以从 `data` 中获取到过渡相关的所有数据。\n\n- 处理边界情况\n\n```js\n// activeInstance will always be the <transition> component managing this\n// transition. One edge case to check is when the <transition> is placed\n// as the root node of a child component. In that case we need to check\n// <transition>'s parent for appear check.\nlet context = activeInstance\nlet transitionNode = activeInstance.$vnode\nwhile (transitionNode && transitionNode.parent) {\n  transitionNode = transitionNode.parent\n  context = transitionNode.context\n}\n\nconst isAppear = !context._isMounted || !vnode.isRootInsert\n\nif (isAppear && !appear && appear !== '') {\n  return\n}\n```\n\n这是为了处理当 `<transition>` 作为子组件的根节点，那么我们需要检查它的父组件作为 `appear` 的检查。`isAppear` 表示当前上下文实例还没有 `mounted`，第一次出现的时机。如果是第一次并且 `<transition>` 组件没有配置 `appear` 的话，直接返回。\n\n- 定义过渡类名、钩子函数和其它配置\n\n```js\nconst startClass = isAppear && appearClass\n    ? appearClass\n    : enterClass\nconst activeClass = isAppear && appearActiveClass\n  ? appearActiveClass\n  : enterActiveClass\nconst toClass = isAppear && appearToClass\n  ? appearToClass\n  : enterToClass\n\nconst beforeEnterHook = isAppear\n  ? (beforeAppear || beforeEnter)\n  : beforeEnter\nconst enterHook = isAppear\n  ? (typeof appear === 'function' ? appear : enter)\n  : enter\nconst afterEnterHook = isAppear\n  ? (afterAppear || afterEnter)\n  : afterEnter\nconst enterCancelledHook = isAppear\n  ? (appearCancelled || enterCancelled)\n  : enterCancelled\n\nconst explicitEnterDuration: any = toNumber(\n  isObject(duration)\n    ? duration.enter\n    : duration\n)\n\nif (process.env.NODE_ENV !== 'production' && explicitEnterDuration != null) {\n  checkDuration(explicitEnterDuration, 'enter', vnode)\n}\n\nconst expectsCSS = css !== false && !isIE9\nconst userWantsControl = getHookArgumentsLength(enterHook)\n\nconst cb = el._enterCb = once(() => {\n  if (expectsCSS) {\n    removeTransitionClass(el, toClass)\n    removeTransitionClass(el, activeClass)\n  }\n  if (cb.cancelled) {\n    if (expectsCSS) {\n      removeTransitionClass(el, startClass)\n    }\n    enterCancelledHook && enterCancelledHook(el)\n  } else {\n    afterEnterHook && afterEnterHook(el)\n  }\n  el._enterCb = null\n})\n```\n\n对于过渡类名方面，`startClass` 定义进入过渡的开始状态，在元素被插入时生效，在下一个帧移除；`activeClass` 定义过渡的状态，在元素整个过渡过程中作用，在元素被插入时生效，在 `transition/animation` 完成之后移除；`toClass` 定义进入过渡的结束状态，在元素被插入一帧后生效 (与此同时 `startClass` 被删除)，在 `<transition>/animation` 完成之后移除。\n\n对于过渡钩子函数方面，`beforeEnterHook` 是过渡开始前执行的钩子函数，`enterHook` 是在元素插入后或者是 `v-show` 显示切换后执行的钩子函数。`afterEnterHook` 是在过渡动画执行完后的钩子函数。\n\n`explicitEnterDuration` 表示 enter 动画执行的时间。\n\n`expectsCSS` 表示过渡动画是受 CSS 的影响。\n\n`cb` 定义的是过渡完成执行的回调函数。\n\n- 合并 `insert` 钩子函数\n\n```js\nif (!vnode.data.show) {\n  // remove pending leave element on enter by injecting an insert hook\n  mergeVNodeHook(vnode, 'insert', () => {\n    const parent = el.parentNode\n    const pendingNode = parent && parent._pending && parent._pending[vnode.key]\n    if (pendingNode &&\n      pendingNode.tag === vnode.tag &&\n      pendingNode.elm._leaveCb\n    ) {\n      pendingNode.elm._leaveCb()\n    }\n    enterHook && enterHook(el, cb)\n  })\n}\n```\n\n`mergeVNodeHook` 的定义在 `src/core/vdom/helpers/merge-hook.js` 中：\n\n```js\nexport function mergeVNodeHook (def: Object, hookKey: string, hook: Function) {\n  if (def instanceof VNode) {\n    def = def.data.hook || (def.data.hook = {})\n  }\n  let invoker\n  const oldHook = def[hookKey]\n\n  function wrappedHook () {\n    hook.apply(this, arguments)\n    // important: remove merged hook to ensure it's called only once\n    // and prevent memory leak\n    remove(invoker.fns, wrappedHook)\n  }\n\n  if (isUndef(oldHook)) {\n    // no existing hook\n    invoker = createFnInvoker([wrappedHook])\n  } else {\n    /* istanbul ignore if */\n    if (isDef(oldHook.fns) && isTrue(oldHook.merged)) {\n      // already a merged invoker\n      invoker = oldHook\n      invoker.fns.push(wrappedHook)\n    } else {\n      // existing plain hook\n      invoker = createFnInvoker([oldHook, wrappedHook])\n    }\n  }\n\n  invoker.merged = true\n  def[hookKey] = invoker\n}\n```\n`mergeVNodeHook` 的逻辑很简单，就是把 `hook` 函数合并到 `def.data.hook[hookey]` 中，生成新的 `invoker`，`createFnInvoker` 方法我们在分析事件章节的时候已经介绍过了。\n\n我们之前知道组件的 `vnode` 原本定义了 `init`、`prepatch`、`insert`、`destroy` 四个钩子函数，而 `mergeVNodeHook` 函数就是把一些新的钩子函数合并进来，例如在 `<transition>` 过程中合并的 `insert` 钩子函数，就会合并到组件 `vnode` 的 `insert` 钩子函数中，这样当组件插入后，就会执行我们定义的 `enterHook` 了。\n\n- 开始执行过渡动画\n\n```js\n// start enter transition\nbeforeEnterHook && beforeEnterHook(el)\nif (expectsCSS) {\n  addTransitionClass(el, startClass)\n  addTransitionClass(el, activeClass)\n  nextFrame(() => {\n    removeTransitionClass(el, startClass)\n    if (!cb.cancelled) {\n      addTransitionClass(el, toClass)\n      if (!userWantsControl) {\n        if (isValidDuration(explicitEnterDuration)) {\n          setTimeout(cb, explicitEnterDuration)\n        } else {\n          whenTransitionEnds(el, type, cb)\n        }\n      }\n    }\n  })\n}\n```\n\n首先执行 `beforeEnterHook` 钩子函数，把当前元素的 DOM 节点 `el` 传入，然后判断 `expectsCSS`，如果为 true 则表明希望用 CSS 来控制动画，那么会执行 ` addTransitionClass(el, startClass)` 和 ` addTransitionClass(el, activeClass)`，它的定义在 `src/platforms/runtime/transition-util.js` 中：\n\n```js\nexport function addTransitionClass (el: any, cls: string) {\n  const transitionClasses = el._transitionClasses || (el._transitionClasses = [])\n  if (transitionClasses.indexOf(cls) < 0) {\n    transitionClasses.push(cls)\n    addClass(el, cls)\n  }\n}\n```\n\n其实非常简单，就是给当前 DOM 元素 `el` 添加样式 `cls`，所以这里添加了 `startClass` 和 `activeClass`，在我们的例子中就是给 `p` 标签添加了 `fade-enter` 和 `fade-enter-active` 2 个样式。\n\n接下来执行了 `nextFrame`：\n\n```js\nconst raf = inBrowser\n  ? window.requestAnimationFrame\n    ? window.requestAnimationFrame.bind(window)\n    : setTimeout\n  : fn => fn()\n\nexport function nextFrame (fn: Function) {\n  raf(() => {\n    raf(fn)\n  })\n}\n```\n\n它就是一个简单的 `requestAnimationFrame` 的实现，它的参数 fn 会在下一帧执行，因此下一帧执行了 `removeTransitionClass(el, startClass)`：\n\n```js\nexport function removeTransitionClass (el: any, cls: string) {\n  if (el._transitionClasses) {\n    remove(el._transitionClasses, cls)\n  }\n  removeClass(el, cls)\n}\n```\n\n把 `startClass` 移除，在我们的等例子中就是移除 `fade-enter` 样式。然后判断此时过渡没有被取消，则执行 `addTransitionClass(el, toClass)` 添加 `toClass`，在我们的例子中就是添加了 `fade-enter-to`。然后判断 `!userWantsControl`，也就是用户不通过 `enterHook` 钩子函数控制动画，这时候如果用户指定了 `explicitEnterDuration`，则延时这个时间执行 `cb`，否则通过 `whenTransitionEnds(el, type, cb)` 决定执行 `cb` 的时机：\n\n```js\nexport function whenTransitionEnds (\n  el: Element,\n  expectedType: ?string,\n  cb: Function\n) {\n  const { type, timeout, propCount } = getTransitionInfo(el, expectedType)\n  if (!type) return cb()\n  const event: string = type === <transition> ? transitionEndEvent : animationEndEvent\n  let ended = 0\n  const end = () => {\n    el.removeEventListener(event, onEnd)\n    cb()\n  }\n  const onEnd = e => {\n    if (e.target === el) {\n      if (++ended >= propCount) {\n        end()\n      }\n    }\n  }\n  setTimeout(() => {\n    if (ended < propCount) {\n      end()\n    }\n  }, timeout + 1)\n  el.addEventListener(event, onEnd)\n}\n```\n`whenTransitionEnds` 的逻辑具体不深讲了，本质上就利用了过渡动画的结束事件来决定 `cb` 函数的执行。\n\n最后再回到 `cb` 函数：\n\n```js\nconst cb = el._enterCb = once(() => {\n  if (expectsCSS) {\n    removeTransitionClass(el, toClass)\n    removeTransitionClass(el, activeClass)\n  }\n  if (cb.cancelled) {\n    if (expectsCSS) {\n      removeTransitionClass(el, startClass)\n    }\n    enterCancelledHook && enterCancelledHook(el)\n  } else {\n    afterEnterHook && afterEnterHook(el)\n  }\n  el._enterCb = null\n})\n```\n\n其实很简单，执行了 `removeTransitionClass(el, toClass)` 和 `removeTransitionClass(el, activeClass)` 把 `toClass` 和 `activeClass` 移除，然后判断如果有没有取消，如果取消则移除 `startClass` 并执行 `enterCancelledHook`，否则执行 `afterEnterHook(el)`。\n\n那么到这里，`entering` 的过程就介绍完了。\n \n## leaving\n\n与 `entering` 相对的就是 `leaving` 阶段了，`entering` 主要发生在组件插入后，而 `leaving` 主要发生在组件销毁前。\n\n```js\nexport function leave (vnode: VNodeWithData, rm: Function) {\n  const el: any = vnode.elm\n\n  // call enter callback now\n  if (isDef(el._enterCb)) {\n    el._enterCb.cancelled = true\n    el._enterCb()\n  }\n\n  const data = resolveTransition(vnode.data.transition)\n  if (isUndef(data) || el.nodeType !== 1) {\n    return rm()\n  }\n\n  /* istanbul ignore if */\n  if (isDef(el._leaveCb)) {\n    return\n  }\n\n  const {\n    css,\n    type,\n    leaveClass,\n    leaveToClass,\n    leaveActiveClass,\n    beforeLeave,\n    leave,\n    afterLeave,\n    leaveCancelled,\n    delayLeave,\n    duration\n  } = data\n\n  const expectsCSS = css !== false && !isIE9\n  const userWantsControl = getHookArgumentsLength(leave)\n\n  const explicitLeaveDuration: any = toNumber(\n    isObject(duration)\n      ? duration.leave\n      : duration\n  )\n\n  if (process.env.NODE_ENV !== 'production' && isDef(explicitLeaveDuration)) {\n    checkDuration(explicitLeaveDuration, 'leave', vnode)\n  }\n\n  const cb = el._leaveCb = once(() => {\n    if (el.parentNode && el.parentNode._pending) {\n      el.parentNode._pending[vnode.key] = null\n    }\n    if (expectsCSS) {\n      removeTransitionClass(el, leaveToClass)\n      removeTransitionClass(el, leaveActiveClass)\n    }\n    if (cb.cancelled) {\n      if (expectsCSS) {\n        removeTransitionClass(el, leaveClass)\n      }\n      leaveCancelled && leaveCancelled(el)\n    } else {\n      rm()\n      afterLeave && afterLeave(el)\n    }\n    el._leaveCb = null\n  })\n\n  if (delayLeave) {\n    delayLeave(performLeave)\n  } else {\n    performLeave()\n  }\n\n  function performLeave () {\n    // the delayed leave may have already been cancelled\n    if (cb.cancelled) {\n      return\n    }\n    // record leaving element\n    if (!vnode.data.show) {\n      (el.parentNode._pending || (el.parentNode._pending = {}))[(vnode.key: any)] = vnode\n    }\n    beforeLeave && beforeLeave(el)\n    if (expectsCSS) {\n      addTransitionClass(el, leaveClass)\n      addTransitionClass(el, leaveActiveClass)\n      nextFrame(() => {\n        removeTransitionClass(el, leaveClass)\n        if (!cb.cancelled) {\n          addTransitionClass(el, leaveToClass)\n          if (!userWantsControl) {\n            if (isValidDuration(explicitLeaveDuration)) {\n              setTimeout(cb, explicitLeaveDuration)\n            } else {\n              whenTransitionEnds(el, type, cb)\n            }\n          }\n        }\n      })\n    }\n    leave && leave(el, cb)\n    if (!expectsCSS && !userWantsControl) {\n      cb()\n    }\n  }\n}\n```\n\n纵观 `leave` 的实现，和 `enter` 的实现几乎是一个镜像过程，不同的是从 `data` 中解析出来的是 `leave` 相关的样式类名和钩子函数。还有一点不同是可以配置 `delayLeave`，它是一个函数，可以延时执行 `leave` 的相关过渡动画，在 `leave` 动画执行完后，它会执行 `rm` 函数把节点从 DOM 中真正做移除。\n\n## 总结\n\n那么到此为止基本的 `<transition>` 过渡的实现分析完毕了，总结起来，Vue 的过渡实现分为以下几个步骤：\n\n1. 自动嗅探目标元素是否应用了 CSS 过渡或动画，如果是，在恰当的时机添加/删除 CSS 类名。\n\n2. 如果过渡组件提供了 JavaScript 钩子函数，这些钩子函数将在恰当的时机被调用。\n\n3. 如果没有找到 JavaScript 钩子并且也没有检测到 CSS 过渡/动画，DOM 操作 (插入/删除) 在下一帧中立即执行。\n\n所以真正执行动画的是我们写的 CSS 或者是 JavaScript 钩子函数，而 Vue 的 `<transition>` 只是帮我们很好地管理了这些 CSS 的添加/删除，以及钩子函数的执行时机。\n\n\n \n\n\n\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "docs/v2/extend/v-model.md",
    "content": "# v-model\n\n很多同学在理解 Vue 的时候都把 Vue 的数据响应原理理解为双向绑定，但实际上这是不准确的，我们之前提到的数据响应，都是通过数据的改变去驱动 DOM 视图的变化，而双向绑定除了数据驱动 DOM 外， DOM 的变化反过来影响数据，是一个双向关系，在 Vue 中，我们可以通过 `v-model` 来实现双向绑定。\n\n`v-model` 即可以作用在普通表单元素上，又可以作用在组件上，它其实是一个语法糖，接下来我们就来分析 `v-model` 的实现原理。\n\n## 表单元素\n\n为了更加直观，我们还是结合示例来分析:\n\n```js\nlet vm = new Vue({\n  el: '#app',\n  template: '<div>'\n  + '<input v-model=\"message\" placeholder=\"edit me\">' +\n  '<p>Message is: {{ message }}</p>' +\n  '</div>',\n  data() {\n    return {\n      message: ''\n    }\n  }\n})\n```\n\n这是一个非常简单 demo，我们在 `input` 元素上设置了 `v-model` 属性，绑定了 `message`，当我们在 `input` 上输入了内容，`message` 也会同步变化。接下来我们就来分析 Vue 是如何实现这一效果的，其实非常简单。\n\n也是先从编译阶段分析，首先是 `parse` 阶段， `v-model` 被当做普通的指令解析到 `el.directives` 中，然后在 `codegen` 阶段，执行 `genData` 的时候，会执行 `const dirs = genDirectives(el, state)`，它的定义在 `src/compiler/codegen/index.js` 中：\n\n```js\nfunction genDirectives (el: ASTElement, state: CodegenState): string | void {\n  const dirs = el.directives\n  if (!dirs) return\n  let res = 'directives:['\n  let hasRuntime = false\n  let i, l, dir, needRuntime\n  for (i = 0, l = dirs.length; i < l; i++) {\n    dir = dirs[i]\n    needRuntime = true\n    const gen: DirectiveFunction = state.directives[dir.name]\n    if (gen) {\n      // compile-time directive that manipulates AST.\n      // returns true if it also needs a runtime counterpart.\n      needRuntime = !!gen(el, dir, state.warn)\n    }\n    if (needRuntime) {\n      hasRuntime = true\n      res += `{name:\"${dir.name}\",rawName:\"${dir.rawName}\"${\n        dir.value ? `,value:(${dir.value}),expression:${JSON.stringify(dir.value)}` : ''\n      }${\n        dir.arg ? `,arg:\"${dir.arg}\"` : ''\n      }${\n        dir.modifiers ? `,modifiers:${JSON.stringify(dir.modifiers)}` : ''\n      }},`\n    }\n  }\n  if (hasRuntime) {\n    return res.slice(0, -1) + ']'\n  }\n}\n```\n\n`genDrirectives` 方法就是遍历 `el.directives`，然后获取每一个指令对应的方法 `\nconst gen: DirectiveFunction = state.directives[dir.name]`，这个指令方法实际上是在实例化 `CodegenState` 的时候通过 `option`\n传入的，这个 `option` 就是编译相关的配置，它在不同的平台下配置不同，在 `web` 环境下的定义在 `src/platforms/web/compiler/options.js` 下：\n\n```js\nexport const baseOptions: CompilerOptions = {\n  expectHTML: true,\n  modules,\n  directives,\n  isPreTag,\n  isUnaryTag,\n  mustUseProp,\n  canBeLeftOpenTag,\n  isReservedTag,\n  getTagNamespace,\n  staticKeys: genStaticKeys(modules)\n}\n```\n\n`directives` 定义在 `src/platforms/web/compiler/directives/index.js` 中：\n\n```js\nexport default {\n  model,\n  text,\n  html\n}\n```\n\n那么对于 `v-model` 而言，对应的 `directive` 函数是在 `src/platforms/web/compiler/directives/model.js` 中定义的 `model` 函数：\n\n```js\nexport default function model (\n  el: ASTElement,\n  dir: ASTDirective,\n  _warn: Function\n): ?boolean {\n  warn = _warn\n  const value = dir.value\n  const modifiers = dir.modifiers\n  const tag = el.tag\n  const type = el.attrsMap.type\n\n  if (process.env.NODE_ENV !== 'production') {\n    // inputs with type=\"file\" are read only and setting the input's\n    // value will throw an error.\n    if (tag === 'input' && type === 'file') {\n      warn(\n        `<${el.tag} v-model=\"${value}\" type=\"file\">:\\n` +\n        `File inputs are read only. Use a v-on:change listener instead.`\n      )\n    }\n  }\n\n  if (el.component) {\n    genComponentModel(el, value, modifiers)\n    // component v-model doesn't need extra runtime\n    return false\n  } else if (tag === 'select') {\n    genSelect(el, value, modifiers)\n  } else if (tag === 'input' && type === 'checkbox') {\n    genCheckboxModel(el, value, modifiers)\n  } else if (tag === 'input' && type === 'radio') {\n    genRadioModel(el, value, modifiers)\n  } else if (tag === 'input' || tag === 'textarea') {\n    genDefaultModel(el, value, modifiers)\n  } else if (!config.isReservedTag(tag)) {\n    genComponentModel(el, value, modifiers)\n    // component v-model doesn't need extra runtime\n    return false\n  } else if (process.env.NODE_ENV !== 'production') {\n    warn(\n      `<${el.tag} v-model=\"${value}\">: ` +\n      `v-model is not supported on this element type. ` +\n      'If you are working with contenteditable, it\\'s recommended to ' +\n      'wrap a library dedicated for that purpose inside a custom component.'\n    )\n  }\n\n  // ensure runtime directive metadata\n  return true\n}\n```\n也就是说我们执行 `needRuntime = !!gen(el, dir, state.warn)` 就是在执行 `model` 函数，它会根据 AST 元素节点的不同情况去执行不同的逻辑，对于我们这个 case 而言，它会命中 `genDefaultModel(el, value, modifiers)` 的逻辑，稍后我们也会介绍组件的处理，其它分支同学们可以自行去看。我们来看一下 `genDefaultModel` 的实现：\n\n```js\nfunction genDefaultModel (\n  el: ASTElement,\n  value: string,\n  modifiers: ?ASTModifiers\n): ?boolean {\n  const type = el.attrsMap.type\n\n  // warn if v-bind:value conflicts with v-model\n  // except for inputs with v-bind:type\n  if (process.env.NODE_ENV !== 'production') {\n    const value = el.attrsMap['v-bind:value'] || el.attrsMap[':value']\n    const typeBinding = el.attrsMap['v-bind:type'] || el.attrsMap[':type']\n    if (value && !typeBinding) {\n      const binding = el.attrsMap['v-bind:value'] ? 'v-bind:value' : ':value'\n      warn(\n        `${binding}=\"${value}\" conflicts with v-model on the same element ` +\n        'because the latter already expands to a value binding internally'\n      )\n    }\n  }\n\n  const { lazy, number, trim } = modifiers || {}\n  const needCompositionGuard = !lazy && type !== 'range'\n  const event = lazy\n    ? 'change'\n    : type === 'range'\n      ? RANGE_TOKEN\n      : 'input'\n\n  let valueExpression = '$event.target.value'\n  if (trim) {\n    valueExpression = `$event.target.value.trim()`\n  }\n  if (number) {\n    valueExpression = `_n(${valueExpression})`\n  }\n\n  let code = genAssignmentCode(value, valueExpression)\n  if (needCompositionGuard) {\n    code = `if($event.target.composing)return;${code}`\n  }\n\n  addProp(el, 'value', `(${value})`)\n  addHandler(el, event, code, null, true)\n  if (trim || number) {\n    addHandler(el, 'blur', '$forceUpdate()')\n  }\n}\n```\n`genDefaultModel` 函数先处理了 `modifiers`，它的不同主要影响的是 `event` 和 `valueExpression` 的值，对于我们的例子，`event` 为 `input`，`valueExpression` 为 `$event.target.value`。然后去执行 `genAssignmentCode` 去生成代码，它的定义在 `src/compiler/directives/model.js` 中：\n\n```js\n/**\n * Cross-platform codegen helper for generating v-model value assignment code.\n */\nexport function genAssignmentCode (\n  value: string,\n  assignment: string\n): string {\n  const res = parseModel(value)\n  if (res.key === null) {\n    return `${value}=${assignment}`\n  } else {\n    return `$set(${res.exp}, ${res.key}, ${assignment})`\n  }\n}\n```\n\n该方法首先对 `v-model` 对应的 `value` 做了解析，它处理了非常多的情况，对我们的例子，`value` 就是 `messgae`，所以返回的 `res.key` 为 `null`，然后我们就得到 `${value}=${assignment}`，也就是 `message=$event.target.value`。然后我们又命中了 `needCompositionGuard` 为 true 的逻辑，所以最终的 `code` 为 `if($event.target.composing)return;message=$event.target.value`。\n\n`code` 生成完后，又执行了 2 句非常关键的代码：\n\n```js\naddProp(el, 'value', `(${value})`)\naddHandler(el, event, code, null, true)\n```\n\n这实际上就是 `input` 实现 `v-model` 的精髓，通过修改 AST 元素，给 `el` 添加一个 `prop`，相当于我们在 `input` 上动态绑定了 `value`，又给 `el` 添加了事件处理，相当于在 `input` 上绑定了 `input` 事件，其实转换成模板如下：\n\n```js\n<input\n  v-bind:value=\"message\"\n  v-on:input=\"message=$event.target.value\">\n```\n\n其实就是动态绑定了 `input` 的 `value` 指向了 `messgae` 变量，并且在触发 `input` 事件的时候去动态把 `message` 设置为目标值，这样实际上就完成了数据双向绑定了，所以说 `v-model` 实际上就是语法糖。\n \n再回到 `genDirectives`，它接下来的逻辑就是根据指令生成一些 `data` 的代码：\n\n```js\nif (needRuntime) {\n  hasRuntime = true\n  res += `{name:\"${dir.name}\",rawName:\"${dir.rawName}\"${\n    dir.value ? `,value:(${dir.value}),expression:${JSON.stringify(dir.value)}` : ''\n  }${\n    dir.arg ? `,arg:\"${dir.arg}\"` : ''\n  }${\n    dir.modifiers ? `,modifiers:${JSON.stringify(dir.modifiers)}` : ''\n  }},`\n}\n```\n\n对我们的例子而言，最终生成的 `render` 代码如下：\n\n```js\nwith(this) {\n  return _c('div',[_c('input',{\n    directives:[{\n      name:\"model\",\n      rawName:\"v-model\",\n      value:(message),\n      expression:\"message\"\n    }],\n    attrs:{\"placeholder\":\"edit me\"},\n    domProps:{\"value\":(message)},\n    on:{\"input\":function($event){\n      if($event.target.composing)\n        return;\n      message=$event.target.value\n    }}}),_c('p',[_v(\"Message is: \"+_s(message))])\n    ])\n}\n```\n\n关于事件的处理我们之前的章节已经分析过了，所以对于 `input` 的 `v-model` 而言，完全就是语法糖，并且对于其它表单元素套路都是一样，区别在于生成的事件代码会略有不同。\n\n`v-model` 除了作用在表单元素上，新版的 Vue 还把这一语法糖用在了组件上，接下来我们来分析它的实现。\n\n## 组件\n\n为了更加直观，我们也是通过一个例子分析：\n\n```js\nlet Child = {\n  template: '<div>'\n  + '<input :value=\"value\" @input=\"updateValue\" placeholder=\"edit me\">' +\n  '</div>',\n  props: ['value'],\n  methods: {\n    updateValue(e) {\n      this.$emit('input', e.target.value)\n    }\n  }\n}\n\nlet vm = new Vue({\n  el: '#app',\n  template: '<div>' +\n  '<child v-model=\"message\"></child>' +\n  '<p>Message is: {{ message }}</p>' +\n  '</div>',\n  data() {\n    return {\n      message: ''\n    }\n  },\n  components: {\n    Child\n  }\n})\n```\n\n可以看到，父组件引用 `child` 子组件的地方使用了 `v-model` 关联了数据 `message`；而子组件定义了一个 `value` 的 `prop`，并且在 `input` 事件的回调函数中，通过 `this.$emit('input', e.target.value)` 派发了一个事件，为了让 `v-model` 生效，这两点是必须的。\n\n接着我们从源码角度分析实现原理，还是从编译阶段说起，对于父组件而言，在编译阶段会解析 `v-model` 指令，依然会执行 `genData` 函数中的 `genDirectives` 函数，接着执行 `src/platforms/web/compiler/directives/model.js` 中定义的 `model` 函数，并命中如下逻辑：\n\n```js\nelse if (!config.isReservedTag(tag)) {\n  genComponentModel(el, value, modifiers);\n  return false\n}\n```\n\n`genComponentModel` 函数定义在 `src/compiler/directives/model.js` 中：\n\n```js\nexport function genComponentModel (\n  el: ASTElement,\n  value: string,\n  modifiers: ?ASTModifiers\n): ?boolean {\n  const { number, trim } = modifiers || {}\n\n  const baseValueExpression = '$$v'\n  let valueExpression = baseValueExpression\n  if (trim) {\n    valueExpression =\n      `(typeof ${baseValueExpression} === 'string'` +\n        `? ${baseValueExpression}.trim()` +\n        `: ${baseValueExpression})`\n  }\n  if (number) {\n    valueExpression = `_n(${valueExpression})`\n  }\n  const assignment = genAssignmentCode(value, valueExpression)\n\n  el.model = {\n    value: `(${value})`,\n    expression: `\"${value}\"`,\n    callback: `function (${baseValueExpression}) {${assignment}}`\n  }\n}\n```\n\n`genComponentModel` 的逻辑很简单，对我们的例子而言，生成的 `el.model` 的值为：\n\n```js\nel.model = {\n  callback:'function ($$v) {message=$$v}',\n  expression:'\"message\"',\n  value:'(message)'\n}\n```\n\n那么在 `genDirectives` 之后，`genData` 函数中有一段逻辑如下：\n\n```js\nif (el.model) {\n  data += `model:{value:${\n    el.model.value\n  },callback:${\n    el.model.callback\n  },expression:${\n    el.model.expression\n  }},`\n}\n```\n\n那么父组件最终生成的 `render` 代码如下：\n\n```js\nwith(this){\n  return _c('div',[_c('child',{\n    model:{\n      value:(message),\n      callback:function ($$v) {\n        message=$$v\n      },\n      expression:\"message\"\n    }\n  }),\n  _c('p',[_v(\"Message is: \"+_s(message))])],1)\n}\n```\n\n然后在创建子组件 `vnode` 阶段，会执行 `createComponent` 函数，它的定义在 `src/core/vdom/create-component.js` 中：\n \n ```js\nexport function createComponent (\n  Ctor: Class<Component> | Function | Object | void,\n  data: ?VNodeData,\n  context: Component,\n  children: ?Array<VNode>,\n  tag?: string\n): VNode | Array<VNode> | void {\n  // ...\n  // transform component v-model data into props & events\n  if (isDef(data.model)) {\n    transformModel(Ctor.options, data)\n  }\n \n  // extract props\n  const propsData = extractPropsFromVNodeData(data, Ctor, tag)\n  // ...\n  // extract listeners, since these needs to be treated as\n  // child component listeners instead of DOM listeners\n  const listeners = data.on\n  // ...\n  const vnode = new VNode(\n    `vue-component-${Ctor.cid}${name ? `-${name}` : ''}`,\n    data, undefined, undefined, undefined, context,\n    { Ctor, propsData, listeners, tag, children },\n    asyncFactory\n  )\n  \n  return vnode\n}\n```\n\n其中会对 `data.model` 的情况做处理，执行 `transformModel(Ctor.options, data)` 方法：\n\n```js\n// transform component v-model info (value and callback) into\n// prop and event handler respectively.\nfunction transformModel (options, data: any) {\n  const prop = (options.model && options.model.prop) || 'value'\n  const event = (options.model && options.model.event) || 'input'\n  ;(data.props || (data.props = {}))[prop] = data.model.value\n  const on = data.on || (data.on = {})\n  if (isDef(on[event])) {\n    on[event] = [data.model.callback].concat(on[event])\n  } else {\n    on[event] = data.model.callback\n  }\n}\n```\n\n`transformModel` 逻辑很简单，给 `data.props` 添加 `data.model.value`，并且给`data.on` 添加 `data.model.callback`，对我们的例子而言，扩展结果如下：\n \n```js\ndata.props = {\n  value: (message),\n}\ndata.on = {\n  input: function ($$v) {\n    message=$$v\n  }\n} \n```\n\n其实就相当于我们在这样编写父组件：\n\n```js\nlet vm = new Vue({\n  el: '#app',\n  template: '<div>' +\n  '<child :value=\"message\" @input=\"message=arguments[0]\"></child>' +\n  '<p>Message is: {{ message }}</p>' +\n  '</div>',\n  data() {\n    return {\n      message: ''\n    }\n  },\n  components: {\n    Child\n  }\n})\n```\n\n子组件传递的 `value` 绑定到当前父组件的 `message`，同时监听自定义 `input` 事件，当子组件派发 `input` 事件的时候，父组件会在事件回调函数中修改 `message` 的值，同时 `value` 也会发生变化，子组件的 `input` 值被更新。\n\n这就是典型的 Vue 的父子组件通讯模式，父组件通过 `prop` 把数据传递到子组件，子组件修改了数据后把改变通过 `$emit` 事件的方式通知父组件，所以说组件上的 `v-model` 也是一种语法糖。\n\n另外我们注意到组件 `v-model` 的实现，子组件的 `value prop` 以及派发的 `input` 事件名是可配的，可以看到 `transformModel` 中对这部分的处理：\n\n```js\nfunction transformModel (options, data: any) {\n  const prop = (options.model && options.model.prop) || 'value'\n  const event = (options.model && options.model.event) || 'input'\n  // ...\n}\n```\n\n也就是说可以在定义子组件的时候通过 `model` 选项配置子组件接收的 `prop` 名以及派发的事件名，举个例子：\n\n```js\nlet Child = {\n  template: '<div>'\n  + '<input :value=\"msg\" @input=\"updateValue\" placeholder=\"edit me\">' +\n  '</div>',\n  props: ['msg'],\n  model: {\n    prop: 'msg',\n    event: 'change'\n  },\n  methods: {\n    updateValue(e) {\n      this.$emit('change', e.target.value)\n    }\n  }\n}\n\nlet vm = new Vue({\n  el: '#app',\n  template: '<div>' +\n  '<child v-model=\"message\"></child>' +\n  '<p>Message is: {{ message }}</p>' +\n  '</div>',\n  data() {\n    return {\n      message: ''\n    }\n  },\n  components: {\n    Child\n  }\n})\n```\n\n子组件修改了接收的 `prop` 名以及派发的事件名，然而这一切父组件作为调用方是不用关心的，这样做的好处是我们可以把 `value` 这个 `prop` 作为其它的用途。\n\n## 总结\n\n那么至此，`v-model` 的实现就分析完了，我们了解到它是 Vue 双向绑定的真正实现，但本质上就是一种语法糖，它即可以支持原生表单元素，也可以支持自定义组件。在组件的实现中，我们是可以配置子组件接收的 `prop` 名称，以及派发的事件名称。\n \n  \n\n\n"
  },
  {
    "path": "docs/v2/prepare/build.md",
    "content": "# Vue.js 源码构建\n\nVue.js 源码是基于 [Rollup](https://github.com/rollup/rollup) 构建的，它的构建相关配置都在 scripts 目录下。\n\n## 构建脚本\n\n通常一个基于 NPM 托管的项目都会有一个 package.json 文件，它是对项目的描述文件，它的内容实际上是一个标准的 JSON 对象。\n\n我们通常会配置 `script` 字段作为 NPM 的执行脚本，Vue.js 源码构建的脚本如下：\n\n```json\n{\n  \"script\": {\n    \"build\": \"node scripts/build.js\",\n    \"build:ssr\": \"npm run build -- web-runtime-cjs,web-server-renderer\",\n    \"build:weex\": \"npm run build -- weex\"\n  }\n}\n \n```\n\n这里总共有 3 条命令，作用都是构建 Vue.js，后面 2 条是在第一条命令的基础上，添加一些环境参数。\n\n当在命令行运行 `npm run build` 的时候，实际上就会执行 `node scripts/build.js`，接下来我们来看看它实际是怎么构建的。\n\n## 构建过程\n\n我们对于构建过程分析是基于源码的，先打开构建的入口 JS 文件，在 `scripts/build.js` 中：\n```js\nlet builds = require('./config').getAllBuilds()\n\n// filter builds via command line arg\nif (process.argv[2]) {\n  const filters = process.argv[2].split(',')\n  builds = builds.filter(b => {\n    return filters.some(f => b.output.file.indexOf(f) > -1 || b._name.indexOf(f) > -1)\n  })\n} else {\n  // filter out weex builds by default\n  builds = builds.filter(b => {\n    return b.output.file.indexOf('weex') === -1\n  })\n}\n\nbuild(builds)\n```\n\n这段代码逻辑非常简单，先从配置文件读取配置，再通过命令行参数对构建配置做过滤，这样就可以构建出不同用途的 Vue.js 了。接下来我们看一下配置文件，在 `scripts/config.js` 中：\n\n```js\nconst builds = {\n  // Runtime only (CommonJS). Used by bundlers e.g. Webpack & Browserify\n  'web-runtime-cjs': {\n    entry: resolve('web/entry-runtime.js'),\n    dest: resolve('dist/vue.runtime.common.js'),\n    format: 'cjs',\n    banner\n  },\n  // Runtime+compiler CommonJS build (CommonJS)\n  'web-full-cjs': {\n    entry: resolve('web/entry-runtime-with-compiler.js'),\n    dest: resolve('dist/vue.common.js'),\n    format: 'cjs',\n    alias: { he: './entity-decoder' },\n    banner\n  },\n  // Runtime only (ES Modules). Used by bundlers that support ES Modules,\n  // e.g. Rollup & Webpack 2\n  'web-runtime-esm': {\n    entry: resolve('web/entry-runtime.js'),\n    dest: resolve('dist/vue.runtime.esm.js'),\n    format: 'es',\n    banner\n  },\n  // Runtime+compiler CommonJS build (ES Modules)\n  'web-full-esm': {\n    entry: resolve('web/entry-runtime-with-compiler.js'),\n    dest: resolve('dist/vue.esm.js'),\n    format: 'es',\n    alias: { he: './entity-decoder' },\n    banner\n  },\n  // runtime-only build (Browser)\n  'web-runtime-dev': {\n    entry: resolve('web/entry-runtime.js'),\n    dest: resolve('dist/vue.runtime.js'),\n    format: 'umd',\n    env: 'development',\n    banner\n  },\n  // runtime-only production build (Browser)\n  'web-runtime-prod': {\n    entry: resolve('web/entry-runtime.js'),\n    dest: resolve('dist/vue.runtime.min.js'),\n    format: 'umd',\n    env: 'production',\n    banner\n  },\n  // Runtime+compiler development build (Browser)\n  'web-full-dev': {\n    entry: resolve('web/entry-runtime-with-compiler.js'),\n    dest: resolve('dist/vue.js'),\n    format: 'umd',\n    env: 'development',\n    alias: { he: './entity-decoder' },\n    banner\n  },\n  // Runtime+compiler production build  (Browser)\n  'web-full-prod': {\n    entry: resolve('web/entry-runtime-with-compiler.js'),\n    dest: resolve('dist/vue.min.js'),\n    format: 'umd',\n    env: 'production',\n    alias: { he: './entity-decoder' },\n    banner\n  },\n  // ...\n}\n````\n\n这里列举了一些 Vue.js 构建的配置，关于还有一些服务端渲染 webpack 插件以及 weex 的打包配置就不列举了。\n\n对于单个配置，它是遵循 Rollup 的构建规则的。其中 `entry` 属性表示构建的入口 JS 文件地址，`dest` 属性表示构建后的 JS 文件地址。`format` 属性表示构建的格式，`cjs` 表示构建出来的文件遵循 [CommonJS](http://wiki.commonjs.org/wiki/Modules/1.1) 规范，`es` 表示构建出来的文件遵循 [ES Module](http://exploringjs.com/es6/ch_modules.html) 规范。 `umd` 表示构建出来的文件遵循 [UMD](https://github.com/umdjs/umd) 规范。\n\n以 `web-runtime-cjs` 配置为例，它的 `entry` 是\n`resolve('web/entry-runtime.js')`，先来看一下 `resolve` 函数的定义。\n\n源码目录：`scripts/config.js`\n\n```js\nconst aliases = require('./alias')\nconst resolve = p => {\n  const base = p.split('/')[0]\n  if (aliases[base]) {\n    return path.resolve(aliases[base], p.slice(base.length + 1))\n  } else {\n    return path.resolve(__dirname, '../', p)\n  }\n}\n```\n\n这里的 `resolve` 函数实现非常简单，它先把 `resolve` 函数传入的参数 `p` 通过 `/` 做了分割成数组，然后取数组第一个元素设置为 `base`。在我们这个例子中，参数 `p` 是 `web/entry-runtime.js`，那么 `base` 则为 `web`。`base` 并不是实际的路径，它的真实路径借助了别名的配置，我们来看一下别名配置的代码，在 `scripts/alias` 中：\n\n```js\nconst path = require('path')\n\nmodule.exports = {\n  vue: path.resolve(__dirname, '../src/platforms/web/entry-runtime-with-compiler'),\n  compiler: path.resolve(__dirname, '../src/compiler'),\n  core: path.resolve(__dirname, '../src/core'),\n  shared: path.resolve(__dirname, '../src/shared'),\n  web: path.resolve(__dirname, '../src/platforms/web'),\n  weex: path.resolve(__dirname, '../src/platforms/weex'),\n  server: path.resolve(__dirname, '../src/server'),\n  entries: path.resolve(__dirname, '../src/entries'),\n  sfc: path.resolve(__dirname, '../src/sfc')\n}\n```\n很显然，这里 `web` 对应的真实的路径是 `path.resolve(__dirname, '../src/platforms/web')`，这个路径就找到了 Vue.js 源码的 web 目录。然后 `resolve` 函数通过 `path.resolve(aliases[base], p.slice(base.length + 1))` 找到了最终路径，它就是 Vue.js 源码 web 目录下的 `entry-runtime.js`。因此，`web-runtime-cjs` 配置对应的入口文件就找到了。\n\n它经过 Rollup 的构建打包后，最终会在 dist 目录下生成 `vue.runtime.common.js`。\n\n## Runtime Only VS Runtime + Compiler \n\n通常我们利用 vue-cli 去初始化我们的 Vue.js 项目的时候会询问我们用 Runtime Only 版本的还是 Runtime + Compiler 版本。下面我们来对比这两个版本。\n \n- Runtime Only\n\n我们在使用 Runtime Only 版本的 Vue.js 的时候，通常需要借助如 webpack 的 vue-loader 工具把 .vue 文件编译成 JavaScript，因为是在编译阶段做的，所以它只包含运行时的 Vue.js 代码，因此代码体积也会更轻量。\n\n- Runtime + Compiler\n\n我们如果没有对代码做预编译，但又使用了 Vue 的 template 属性并传入一个字符串，则需要在客户端编译模板，如下所示：\n\n```js\n// 需要编译器的版本\nnew Vue({\n  template: '<div>{{ hi }}</div>'\n})\n\n// 这种情况不需要\nnew Vue({\n  render (h) {\n    return h('div', this.hi)\n  }\n})\n```\n\n因为在 Vue.js 2.0 中，最终渲染都是通过 `render` 函数，如果写 `template` 属性，则需要编译成 `render` 函数，那么这个编译过程会发生运行时，所以需要带有编译器的版本。\n\n很显然，这个编译过程对性能会有一定损耗，所以通常我们更推荐使用 Runtime-Only 的 Vue.js。\n\n## 总结\n\n通过这一节的分析，我们可以了解到 Vue.js 的构建打包过程，也知道了不同作用和功能的 Vue.js 它们对应的入口以及最终编译生成的 JS 文件。尽管在实际开发过程中我们会用 Runtime Only 版本开发比较多，但为了分析 Vue 的编译过程，我们这门课重点分析的源码是 Runtime + Compiler 的 Vue.js。\n\n"
  },
  {
    "path": "docs/v2/prepare/directory.md",
    "content": "# Vue.js 源码目录设计\n\nVue.js 的源码都在 src 目录下，其目录结构如下。\n\n```\nsrc\n├── compiler        # 编译相关 \n├── core            # 核心代码 \n├── platforms       # 不同平台的支持\n├── server          # 服务端渲染\n├── sfc             # .vue 文件解析\n├── shared          # 共享代码\n```\n\n## compiler\n\ncompiler 目录包含 Vue.js 所有编译相关的代码。它包括把模板解析成 ast 语法树，ast 语法树优化，代码生成等功能。\n\n编译的工作可以在构建时做（借助 webpack、vue-loader 等辅助插件）；也可以在运行时做，使用包含构建功能的 Vue.js。显然，编译是一项耗性能的工作，所以更推荐前者——离线编译。\n\n## core\n\ncore 目录包含了 Vue.js 的核心代码，包括内置组件、全局 API 封装，Vue 实例化、观察者、虚拟 DOM、工具函数等等。\n\n这里的代码可谓是 Vue.js 的灵魂，也是我们之后需要重点分析的地方。\n\n## platform\n\nVue.js 是一个跨平台的 MVVM 框架，它可以跑在 web 上，也可以配合 weex 跑在 native 客户端上。platform 是 Vue.js 的入口，2 个目录代表 2 个主要入口，分别打包成运行在 web 上和 weex 上的 Vue.js。\n\n我们会重点分析 web 入口打包后的 Vue.js，对于 weex 入口打包的 Vue.js，感兴趣的同学可以自行研究。\n\n## server\n\nVue.js 2.0 支持了服务端渲染，所有服务端渲染相关的逻辑都在这个目录下。注意：这部分代码是跑在服务端的 Node.js，不要和跑在浏览器端的 Vue.js 混为一谈。\n\n服务端渲染主要的工作是把组件渲染为服务器端的 HTML 字符串，将它们直接发送到浏览器，最后将静态标记\"混合\"为客户端上完全交互的应用程序。\n\n## sfc\n\n通常我们开发 Vue.js 都会借助 webpack 构建， 然后通过 .vue 单文件来编写组件。\n\n这个目录下的代码逻辑会把 .vue 文件内容解析成一个 JavaScript 的对象。\n\n## shared\n\nVue.js 会定义一些工具方法，这里定义的工具方法都是会被浏览器端的 Vue.js 和服务端的 Vue.js 所共享的。\n\n## 总结\n\n从 Vue.js 的目录设计可以看到，作者把功能模块拆分的非常清楚，相关的逻辑放在一个独立的目录下维护，并且把复用的代码也抽成一个独立目录。\n\n这样的目录设计让代码的阅读性和可维护性都变强，是非常值得学习和推敲的。\n\n"
  },
  {
    "path": "docs/v2/prepare/entrance.md",
    "content": "# 从入口开始\n\n我们之前提到过 Vue.js 构建过程，在 web 应用下，我们来分析 Runtime + Compiler 构建出来的 Vue.js，它的入口是 `src/platforms/web/entry-runtime-with-compiler.js`：\n\n````js\n/* @flow */\n\nimport config from 'core/config'\nimport { warn, cached } from 'core/util/index'\nimport { mark, measure } from 'core/util/perf'\n\nimport Vue from './runtime/index'\nimport { query } from './util/index'\nimport { compileToFunctions } from './compiler/index'\nimport { shouldDecodeNewlines, shouldDecodeNewlinesForHref } from './util/compat'\n\nconst idToTemplate = cached(id => {\n  const el = query(id)\n  return el && el.innerHTML\n})\n\nconst mount = Vue.prototype.$mount\nVue.prototype.$mount = function (\n  el?: string | Element,\n  hydrating?: boolean\n): Component {\n  el = el && query(el)\n\n  /* istanbul ignore if */\n  if (el === document.body || el === document.documentElement) {\n    process.env.NODE_ENV !== 'production' && warn(\n      `Do not mount Vue to <html> or <body> - mount to normal elements instead.`\n    )\n    return this\n  }\n\n  const options = this.$options\n  // resolve template/el and convert to render function\n  if (!options.render) {\n    let template = options.template\n    if (template) {\n      if (typeof template === 'string') {\n        if (template.charAt(0) === '#') {\n          template = idToTemplate(template)\n          /* istanbul ignore if */\n          if (process.env.NODE_ENV !== 'production' && !template) {\n            warn(\n              `Template element not found or is empty: ${options.template}`,\n              this\n            )\n          }\n        }\n      } else if (template.nodeType) {\n        template = template.innerHTML\n      } else {\n        if (process.env.NODE_ENV !== 'production') {\n          warn('invalid template option:' + template, this)\n        }\n        return this\n      }\n    } else if (el) {\n      template = getOuterHTML(el)\n    }\n    if (template) {\n      /* istanbul ignore if */\n      if (process.env.NODE_ENV !== 'production' && config.performance && mark) {\n        mark('compile')\n      }\n\n      const { render, staticRenderFns } = compileToFunctions(template, {\n        shouldDecodeNewlines,\n        shouldDecodeNewlinesForHref,\n        delimiters: options.delimiters,\n        comments: options.comments\n      }, this)\n      options.render = render\n      options.staticRenderFns = staticRenderFns\n\n      /* istanbul ignore if */\n      if (process.env.NODE_ENV !== 'production' && config.performance && mark) {\n        mark('compile end')\n        measure(`vue ${this._name} compile`, 'compile', 'compile end')\n      }\n    }\n  }\n  return mount.call(this, el, hydrating)\n}\n\n/**\n * Get outerHTML of elements, taking care\n * of SVG elements in IE as well.\n */\nfunction getOuterHTML (el: Element): string {\n  if (el.outerHTML) {\n    return el.outerHTML\n  } else {\n    const container = document.createElement('div')\n    container.appendChild(el.cloneNode(true))\n    return container.innerHTML\n  }\n}\n\nVue.compile = compileToFunctions\n\nexport default Vue\n\n````\n\n那么，当我们的代码执行 `import Vue from 'vue'` 的时候，就是从这个入口执行代码来初始化 Vue，\n那么 Vue 到底是什么，它是怎么初始化的，我们来一探究竟。\n\n## Vue 的入口\n\n在这个入口 JS 的上方我们可以找到 `Vue` 的来源：`import Vue from './runtime/index'`，我们先来看一下这块儿的实现，它定义在 `src/platforms/web/runtime/index.js` 中：\n\n```js\nimport Vue from 'core/index'\nimport config from 'core/config'\nimport { extend, noop } from 'shared/util'\nimport { mountComponent } from 'core/instance/lifecycle'\nimport { devtools, inBrowser, isChrome } from 'core/util/index'\n\nimport {\n  query,\n  mustUseProp,\n  isReservedTag,\n  isReservedAttr,\n  getTagNamespace,\n  isUnknownElement\n} from 'web/util/index'\n\nimport { patch } from './patch'\nimport platformDirectives from './directives/index'\nimport platformComponents from './components/index'\n\n// install platform specific utils\nVue.config.mustUseProp = mustUseProp\nVue.config.isReservedTag = isReservedTag\nVue.config.isReservedAttr = isReservedAttr\nVue.config.getTagNamespace = getTagNamespace\nVue.config.isUnknownElement = isUnknownElement\n\n// install platform runtime directives & components\nextend(Vue.options.directives, platformDirectives)\nextend(Vue.options.components, platformComponents)\n\n// install platform patch function\nVue.prototype.__patch__ = inBrowser ? patch : noop\n\n// public mount method\nVue.prototype.$mount = function (\n  el?: string | Element,\n  hydrating?: boolean\n): Component {\n  el = el && inBrowser ? query(el) : undefined\n  return mountComponent(this, el, hydrating)\n}\n\n// ...\n\nexport default Vue\n\n```\n这里关键的代码是 `import Vue from 'core/index'`，之后的逻辑都是对 Vue 这个对象做一些扩展，可以先不用看，我们来看一下真正初始化 Vue 的地方，在 `src/core/index.js` 中：\n\n```js\nimport Vue from './instance/index'\nimport { initGlobalAPI } from './global-api/index'\nimport { isServerRendering } from 'core/util/env'\nimport { FunctionalRenderContext } from 'core/vdom/create-functional-component'\n\ninitGlobalAPI(Vue)\n\nObject.defineProperty(Vue.prototype, '$isServer', {\n  get: isServerRendering\n})\n\nObject.defineProperty(Vue.prototype, '$ssrContext', {\n  get () {\n    /* istanbul ignore next */\n    return this.$vnode && this.$vnode.ssrContext\n  }\n})\n\n// expose FunctionalRenderContext for ssr runtime helper installation\nObject.defineProperty(Vue, 'FunctionalRenderContext', {\n  value: FunctionalRenderContext\n})\n\nVue.version = '__VERSION__'\n\nexport default Vue\n```\n\n这里有 2 处关键的代码，`import Vue from './instance/index'` 和 `initGlobalAPI(Vue)`，初始化全局 Vue API（我们稍后介绍），我们先来看第一部分，在 `src/core/instance/index.js` 中：\n\n### Vue 的定义\n\n```js\nimport { initMixin } from './init'\nimport { stateMixin } from './state'\nimport { renderMixin } from './render'\nimport { eventsMixin } from './events'\nimport { lifecycleMixin } from './lifecycle'\nimport { warn } from '../util/index'\n\nfunction Vue (options) {\n  if (process.env.NODE_ENV !== 'production' &&\n    !(this instanceof Vue)\n  ) {\n    warn('Vue is a constructor and should be called with the `new` keyword')\n  }\n  this._init(options)\n}\n\ninitMixin(Vue)\nstateMixin(Vue)\neventsMixin(Vue)\nlifecycleMixin(Vue)\nrenderMixin(Vue)\n\nexport default Vue\n```\n在这里，我们终于看到了 Vue 的庐山真面目，它实际上就是一个用 Function 实现的类，我们只能通过 `new Vue` 去实例化它。\n\n有些同学看到这不禁想问，为何 Vue 不用 ES6 的 Class 去实现呢？我们往后看这里有很多 ```xxxMixin``` 的函数调用，并把 `Vue` 当参数传入，它们的功能都是给 Vue 的 prototype 上扩展一些方法（这里具体的细节会在之后的文章介绍，这里不展开），Vue 按功能把这些扩展分散到多个模块中去实现，而不是在一个模块里实现所有，这种方式是用 Class 难以实现的。这么做的好处是非常方便代码的维护和管理，这种编程技巧也非常值得我们去学习。\n\n### `initGlobalAPI`\n\nVue.js 在整个初始化过程中，除了给它的原型 prototype 上扩展方法，还会给 `Vue` 这个对象本身扩展全局的静态方法，它的定义在 `src/core/global-api/index.js` 中：\n\n```js\nexport function initGlobalAPI (Vue: GlobalAPI) {\n  // config\n  const configDef = {}\n  configDef.get = () => config\n  if (process.env.NODE_ENV !== 'production') {\n    configDef.set = () => {\n      warn(\n        'Do not replace the Vue.config object, set individual fields instead.'\n      )\n    }\n  }\n  Object.defineProperty(Vue, 'config', configDef)\n\n  // exposed util methods.\n  // NOTE: these are not considered part of the public API - avoid relying on\n  // them unless you are aware of the risk.\n  Vue.util = {\n    warn,\n    extend,\n    mergeOptions,\n    defineReactive\n  }\n\n  Vue.set = set\n  Vue.delete = del\n  Vue.nextTick = nextTick\n\n  Vue.options = Object.create(null)\n  ASSET_TYPES.forEach(type => {\n    Vue.options[type + 's'] = Object.create(null)\n  })\n\n  // this is used to identify the \"base\" constructor to extend all plain-object\n  // components with in Weex's multi-instance scenarios.\n  Vue.options._base = Vue\n\n  extend(Vue.options.components, builtInComponents)\n\n  initUse(Vue)\n  initMixin(Vue)\n  initExtend(Vue)\n  initAssetRegisters(Vue)\n}\n```\n这里就是在 Vue 上扩展的一些全局方法的定义，Vue 官网中关于全局 API 都可以在这里找到，这里不会介绍细节，会在之后的章节我们具体介绍到某个 API 的时候会详细介绍。有一点要注意的是，`Vue.util` 暴露的方法最好不要依赖，因为它可能经常会发生变化，是不稳定的。\n\n## 总结\n\n那么至此，Vue 的初始化过程基本介绍完毕。这一节的目的是让同学们对 Vue 是什么有一个直观的认识，它本质上就是一个用 Function 实现的 Class，然后它的原型 prototype 以及它本身都扩展了一系列的方法和属性，那么 Vue 能做什么，它是怎么做的，我们会在后面的章节一层层帮大家揭开 Vue 的神秘面纱。\n"
  },
  {
    "path": "docs/v2/prepare/flow.md",
    "content": "# 认识 Flow\n\n[Flow](https://flow.org/en/docs/getting-started/) 是 facebook 出品的 JavaScript 静态类型检查工具。Vue.js 的源码利用了 Flow 做了静态类型检查，所以了解 Flow 有助于我们阅读源码。\n\n## 为什么用 Flow\n\nJavaScript 是动态类型语言，它的灵活性有目共睹，但是过于灵活的副作用是很容易就写出非常隐蔽的隐患代码，在编译期甚至看上去都不会报错，但在运行阶段就可能出现各种奇怪的 bug。\n\n类型检查是当前动态类型语言的发展趋势，所谓类型检查，就是在编译期尽早发现（由类型错误引起的）bug，又不影响代码运行（不需要运行时动态检查类型），使编写 JavaScript 具有和编写 Java 等强类型语言相近的体验。 \n\n项目越复杂就越需要通过工具的手段来保证项目的维护性和增强代码的可读性。 Vue.js 在做 2.0 重构的时候，在 ES2015 的基础上，除了 ESLint 保证代码风格之外，也引入了 Flow 做静态类型检查。之所以选择 Flow，主要是因为 Babel 和 ESLint 都有对应的 Flow 插件以支持语法，可以完全沿用现有的构建配置，非常小成本的改动就可以拥有静态类型检查的能力。             \n## Flow 的工作方式\n\n通常类型检查分成 2 种方式：\n\n- **类型推断**：通过变量的使用上下文来推断出变量类型，然后根据这些推断来检查类型。\n\n- **类型注释**：事先注释好我们期待的类型，Flow 会基于这些注释来判断。\n\n### 类型推断\n\n它不需要任何代码修改即可进行类型检查，最小化开发者的工作量。它不会强制你改变开发习惯，因为它会自动推断出变量的类型。这就是所谓的类型推断，Flow 最重要的特性之一。\n\n通过一个简单例子说明一下：\n\n```js\n/*@flow*/\n\nfunction split(str) {\n  return str.split(' ')\n}\n\nsplit(11)\n```\n\nFlow 检查上述代码后会报错，因为函数 `split`\n期待的参数是字符串，而我们输入了数字。\n\n### 类型注释\n\n如上所述，类型推断是 Flow 最有用的特性之一，不需要编写类型注释就能获取有用的反馈。但在某些特定的场景下，添加类型注释可以提供更好更明确的检查依据。\n\n考虑如下代码：\n\n```js\n/*@flow*/\n\nfunction add(x, y){\n  return x + y\n}\n\nadd('Hello', 11)\n```\n\nFlow 检查上述代码时检查不出任何错误，因为从语法层面考虑， `+` 既可以用在字符串上，也可以用在数字上，我们并没有明确指出 `add()` 的参数必须为数字。\n\n在这种情况下，我们可以借助类型注释来指明期望的类型。类型注释是以冒号 `:` 开头，可以在函数参数，返回值，变量声明中使用。\n\n如果我们在上段代码中添加类型注释，就会变成如下：\n\n```js\n/*@flow*/\n\nfunction add(x: number, y: number): number {\n  return x + y\n}\n\nadd('Hello', 11)\n```\n\n现在 Flow 就能检查出错误，因为函数参数的期待类型为数字，而我们提供了字符串。\n\n上面的例子是针对函数的类型注释。接下来我们来看看 Flow 能支持的一些常见的类型注释。\n\n#### 数组\n\n```js\n/*@flow*/\n\nvar arr: Array<number> = [1, 2, 3]\n\narr.push('Hello')\n```\n\n数组类型注释的格式是 `Array<T>`，`T` 表示数组中每项的数据类型。在上述代码中，arr 是每项均为数字的数组。如果我们给这个数组添加了一个字符串，Flow 能检查出错误。\n\n#### 类和对象\n\n```js\n/*@flow*/\n\nclass Bar {\n  x: string;           // x 是字符串\n  y: string | number;  // y 可以是字符串或者数字\n  z: boolean;\n\n  constructor(x: string, y: string | number) {\n    this.x = x\n    this.y = y\n    this.z = false\n  }\n}\n\nvar bar: Bar = new Bar('hello', 4)\n\nvar obj: { a: string, b: number, c: Array<string>, d: Bar } = {\n  a: 'hello',\n  b: 11,\n  c: ['hello', 'world'],\n  d: new Bar('hello', 3)\n}\n\n```\n\n类的类型注释格式如上，可以对类自身的属性做类型检查，也可以对构造函数的参数做类型检查。这里需要注意的是，属性 `y` 的类型中间用 `|` 做间隔，表示 `y` 的类型即可以是字符串也可以是数字。\n\n 对象的注释类型类似于类，需要指定对象属性的类型。\n\n#### Null\n\n若想任意类型 `T` 可以为 `null` 或者 `undefined`，只需类似如下写成 `?T` 的格式即可。\n\n```js\n/*@flow*/\n\nvar foo: ?string = null\n```\n\n此时，`foo` 可以为字符串，也可以为 `null`。\n\n目前我们只列举了 Flow 的一些常见的类型注释。如果想了解所有类型注释，请移步 Flow 的[官方文档](https://flow.org/en/docs/types/)。\n\n## Flow 在 Vue.js 源码中的应用\n\n有时候我们想引用第三方库，或者自定义一些类型，但 Flow 并不认识，因此检查的时候会报错。为了解决这类问题，Flow 提出了一个 `libdef` 的概念，可以用来识别这些第三方库或者是自定义类型，而 Vue.js 也利用了这一特性。\n\n在 Vue.js 的主目录下有 `.flowconfig` 文件， 它是 Flow 的配置文件，感兴趣的同学可以看[官方文档](https://flow.org/en/docs/config/)。这其中的 `[libs]` 部分用来描述包含指定库定义的目录，默认是名为 `flow-typed` 的目录。\n\n这里 `[libs]` 配置的是 `flow`，表示指定的库定义都在 `flow` 文件夹内。我们打开这个目录，会发现文件如下：\n\n```\nflow\n├── compiler.js        # 编译相关\n├── component.js       # 组件数据结构\n├── global-api.js      # Global API 结构\n├── modules.js         # 第三方库定义\n├── options.js         # 选项相关\n├── ssr.js             # 服务端渲染相关\n├── vnode.js           # 虚拟 node 相关\n```\n\n可以看到，Vue.js 有很多自定义类型的定义，在阅读源码的时候，如果遇到某个类型并想了解它完整的数据结构的时候，可以回来翻阅这些数据结构的定义。\n\n## 总结\n\n通过对 Flow 的认识，有助于我们阅读 Vue 的源码，并且这种静态类型检查的方式非常有利于大型项目源码的开发和维护。类似 Flow 的工具还有如 TypeScript，感兴趣的同学也可以自行去了解一下。\n\n\n\n\n\n"
  },
  {
    "path": "docs/v2/prepare/index.md",
    "content": "# 准备工作\n\n那么从这一章开始我们即将分析 Vue 的源码，我们将会介绍一些前置知识如 flow、源码目录、构建方式、编译入口等。\n\n除此之外，我希望你已经用过 Vue 做过 2 个以上的实际项目，对 Vue 的思想有了一定的了解，对绝大部分的 API 都已经有使用。同时，我也要求你有一定的原生 JavaScript 的功底，并对代码调试有一定的了解。\n\n如果你具备了以上条件，并且对 Vue 的实现原理很感兴趣，那么就可以开始这门课程的学习了，我将会为你打开 Vue 的底层世界大门，对它的实现细节一探究竟。"
  },
  {
    "path": "docs/v2/reactive/component-update.md",
    "content": "# 组件更新\n\n在组件化章节，我们介绍了 Vue 的组件化实现过程，不过我们只讲了 Vue 组件的创建过程，并没有涉及到组件数据发生变化，更新组件的过程。而通过我们这一章对数据响应式原理的分析，了解到当数据发生变化的时候，会触发渲染 `watcher` 的回调函数，进而执行组件的更新过程，接下来我们来详细分析这一过程。\n\n```js\nupdateComponent = () => {\n  vm._update(vm._render(), hydrating)\n}\nnew Watcher(vm, updateComponent, noop, {\n  before () {\n    if (vm._isMounted) {\n      callHook(vm, 'beforeUpdate')\n    }\n  }\n}, true /* isRenderWatcher */)\n```\n\n组件的更新还是调用了 `vm._update` 方法，我们再回顾一下这个方法，它的定义在 `src/core/instance/lifecycle.js` 中：\n\n```js\nVue.prototype._update = function (vnode: VNode, hydrating?: boolean) {\n  const vm: Component = this\n  // ...\n  const prevVnode = vm._vnode\n  if (!prevVnode) {\n     // initial render\n    vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)\n  } else {\n    // updates\n    vm.$el = vm.__patch__(prevVnode, vnode)\n  }\n  // ...\n}\n```\n\n组件更新的过程，会执行 `vm.$el = vm.__patch__(prevVnode, vnode)`，它仍然会调用 `patch` 函数，在 `src/core/vdom/patch.js` 中定义：\n\n```js\nreturn function patch (oldVnode, vnode, hydrating, removeOnly) {\n  if (isUndef(vnode)) {\n    if (isDef(oldVnode)) invokeDestroyHook(oldVnode)\n    return\n  }\n\n  let isInitialPatch = false\n  const insertedVnodeQueue = []\n\n  if (isUndef(oldVnode)) {\n    // empty mount (likely as component), create new root element\n    isInitialPatch = true\n    createElm(vnode, insertedVnodeQueue)\n  } else {\n    const isRealElement = isDef(oldVnode.nodeType)\n    if (!isRealElement && sameVnode(oldVnode, vnode)) {\n      // patch existing root node\n      patchVnode(oldVnode, vnode, insertedVnodeQueue, removeOnly)\n    } else {\n      if (isRealElement) {\n         // ...\n      }\n\n      // replacing existing element\n      const oldElm = oldVnode.elm\n      const parentElm = nodeOps.parentNode(oldElm)\n\n      // create new node\n      createElm(\n        vnode,\n        insertedVnodeQueue,\n        // extremely rare edge case: do not insert if old element is in a\n        // leaving transition. Only happens when combining transition +\n        // keep-alive + HOCs. (#4590)\n        oldElm._leaveCb ? null : parentElm,\n        nodeOps.nextSibling(oldElm)\n      )\n\n      // update parent placeholder node element, recursively\n      if (isDef(vnode.parent)) {\n        let ancestor = vnode.parent\n        const patchable = isPatchable(vnode)\n        while (ancestor) {\n          for (let i = 0; i < cbs.destroy.length; ++i) {\n            cbs.destroy[i](ancestor)\n          }\n          ancestor.elm = vnode.elm\n          if (patchable) {\n            for (let i = 0; i < cbs.create.length; ++i) {\n              cbs.create[i](emptyNode, ancestor)\n            }\n            // #6513\n            // invoke insert hooks that may have been merged by create hooks.\n            // e.g. for directives that uses the \"inserted\" hook.\n            const insert = ancestor.data.hook.insert\n            if (insert.merged) {\n              // start at index 1 to avoid re-invoking component mounted hook\n              for (let i = 1; i < insert.fns.length; i++) {\n                insert.fns[i]()\n              }\n            }\n          } else {\n            registerRef(ancestor)\n          }\n          ancestor = ancestor.parent\n        }\n      }\n\n      // destroy old node\n      if (isDef(parentElm)) {\n        removeVnodes(parentElm, [oldVnode], 0, 0)\n      } else if (isDef(oldVnode.tag)) {\n        invokeDestroyHook(oldVnode)\n      }\n    }\n  }\n\n  invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch)\n  return vnode.elm\n}\n```\n这里执行 `patch` 的逻辑和首次渲染是不一样的，因为 `oldVnode` 不为空，并且它和 `vnode` 都是 VNode 类型，接下来会通过 `sameVNode(oldVnode, vnode)` 判断它们是否是相同的 VNode 来决定走不同的更新逻辑：\n\n```js\nfunction sameVnode (a, b) {\n  return (\n    a.key === b.key && (\n      (\n        a.tag === b.tag &&\n        a.isComment === b.isComment &&\n        isDef(a.data) === isDef(b.data) &&\n        sameInputType(a, b)\n      ) || (\n        isTrue(a.isAsyncPlaceholder) &&\n        a.asyncFactory === b.asyncFactory &&\n        isUndef(b.asyncFactory.error)\n      )\n    )\n  )\n}\n```\n`sameVnode` 的逻辑非常简单，如果两个 `vnode` 的 `key` 不相等，则是不同的；否则继续判断对于同步组件，则判断 `isComment`、`data`、`input` 类型等是否相同，对于异步组件，则判断 `asyncFactory` 是否相同。\n\n所以根据新旧 `vnode` 是否为 `sameVnode`，会走到不同的更新逻辑，我们先来说一下不同的情况。\n\n## 新旧节点不同\n\n如果新旧 `vnode` 不同，那么更新的逻辑非常简单，它本质上是要替换已存在的节点，大致分为 3 步\n\n- 创建新节点\n\n```js\nconst oldElm = oldVnode.elm\nconst parentElm = nodeOps.parentNode(oldElm)\n// create new node\ncreateElm(\n  vnode,\n  insertedVnodeQueue,\n  // extremely rare edge case: do not insert if old element is in a\n  // leaving transition. Only happens when combining  transition +\n  // keep-alive + HOCs. (#4590)\n  oldElm._leaveCb ? null : parentElm,\n  nodeOps.nextSibling(oldElm)\n)\n```\n\n以当前旧节点为参考节点，创建新的节点，并插入到 DOM 中，`createElm` 的逻辑我们之前分析过。\n\n- 更新父的占位符节点\n\n```js\n// update parent placeholder node element, recursively\nif (isDef(vnode.parent)) {\n  let ancestor = vnode.parent\n  const patchable = isPatchable(vnode)\n  while (ancestor) {\n    for (let i = 0; i < cbs.destroy.length; ++i) {\n      cbs.destroy[i](ancestor)\n    }\n    ancestor.elm = vnode.elm\n    if (patchable) {\n      for (let i = 0; i < cbs.create.length; ++i) {\n        cbs.create[i](emptyNode, ancestor)\n      }\n      // #6513\n      // invoke insert hooks that may have been merged by create hooks.\n      // e.g. for directives that uses the \"inserted\" hook.\n      const insert = ancestor.data.hook.insert\n      if (insert.merged) {\n        // start at index 1 to avoid re-invoking component mounted hook\n        for (let i = 1; i < insert.fns.length; i++) {\n          insert.fns[i]()\n        }\n      }\n    } else {\n      registerRef(ancestor)\n    }\n    ancestor = ancestor.parent\n  }\n}\n```\n我们只关注主要逻辑即可，找到当前 `vnode` 的父的占位符节点，先执行各个 `module` 的 `destroy` 的钩子函数，如果当前占位符是一个可挂载的节点，则执行 `module` 的 `create` 钩子函数。对于这些钩子函数的作用，在之后的章节会详细介绍。\n\n- 删除旧节点\n\n```js\n// destroy old node\nif (isDef(parentElm)) {\n  removeVnodes(parentElm, [oldVnode], 0, 0)\n} else if (isDef(oldVnode.tag)) {\n  invokeDestroyHook(oldVnode)\n}\n```\n把 `oldVnode` 从当前 DOM 树中删除，如果父节点存在，则执行 `removeVnodes` 方法：\n\n```js\nfunction removeVnodes (parentElm, vnodes, startIdx, endIdx) {\n  for (; startIdx <= endIdx; ++startIdx) {\n    const ch = vnodes[startIdx]\n    if (isDef(ch)) {\n      if (isDef(ch.tag)) {\n        removeAndInvokeRemoveHook(ch)\n        invokeDestroyHook(ch)\n      } else { // Text node\n        removeNode(ch.elm)\n      }\n    }\n  }\n}\n\nfunction removeAndInvokeRemoveHook (vnode, rm) {\n  if (isDef(rm) || isDef(vnode.data)) {\n    let i\n    const listeners = cbs.remove.length + 1\n    if (isDef(rm)) {\n      // we have a recursively passed down rm callback\n      // increase the listeners count\n      rm.listeners += listeners\n    } else {\n      // directly removing\n      rm = createRmCb(vnode.elm, listeners)\n    }\n    // recursively invoke hooks on child component root node\n    if (isDef(i = vnode.componentInstance) && isDef(i = i._vnode) && isDef(i.data)) {\n      removeAndInvokeRemoveHook(i, rm)\n    }\n    for (i = 0; i < cbs.remove.length; ++i) {\n      cbs.remove[i](vnode, rm)\n    }\n    if (isDef(i = vnode.data.hook) && isDef(i = i.remove)) {\n      i(vnode, rm)\n    } else {\n      rm()\n    }\n  } else {\n    removeNode(vnode.elm)\n  }\n}\n\nfunction invokeDestroyHook (vnode) {\n  let i, j\n  const data = vnode.data\n  if (isDef(data)) {\n    if (isDef(i = data.hook) && isDef(i = i.destroy)) i(vnode)\n    for (i = 0; i < cbs.destroy.length; ++i) cbs.destroy[i](vnode)\n  }\n  if (isDef(i = vnode.children)) {\n    for (j = 0; j < vnode.children.length; ++j) {\n      invokeDestroyHook(vnode.children[j])\n    }\n  }\n}\n```\n\n删除节点逻辑很简单，就是遍历待删除的 `vnodes` 做删除，其中 `removeAndInvokeRemoveHook`  的作用是从 DOM 中移除节点并执行 `module` 的 `remove` 钩子函数，并对它的子节点递归调用 `removeAndInvokeRemoveHook` 函数；`invokeDestroyHook` 是执行 `module` 的 `destory` 钩子函数以及 `vnode` 的 `destory` 钩子函数，并对它的子 `vnode` 递归调用 `invokeDestroyHook` 函数；`removeNode` 就是调用平台的 DOM API 去把真正的 DOM 节点移除。\n\n在之前介绍组件生命周期的时候提到 `beforeDestroy & destroyed` 这两个生命周期钩子函数，它们就是在执行 `invokeDestroyHook` 过程中，执行了 `vnode` 的 `destory` 钩子函数，它的定义在 `src/core/vdom/create-component.js` 中：\n\n```js\nconst componentVNodeHooks = {\n  destroy (vnode: MountedComponentVNode) {\n    const { componentInstance } = vnode\n    if (!componentInstance._isDestroyed) {\n      if (!vnode.data.keepAlive) {\n        componentInstance.$destroy()\n      } else {\n        deactivateChildComponent(componentInstance, true /* direct */)\n      }\n    }\n  }\n}\n```\n\n当组件并不是 `keepAlive` 的时候，会执行 `componentInstance.$destroy()` 方法，然后就会执行 `beforeDestroy & destroyed` 两个钩子函数。\n\n## 新旧节点相同\n\n对于新旧节点不同的情况，这种创建新节点 -> 更新占位符节点 -> 删除旧节点的逻辑是很容易理解的。还有一种组件 `vnode` 的更新情况是新旧节点相同，它会调用 `patchVNode` 方法，它的定义在 `src/core/vdom/patch.js` 中：\n\n```js\nfunction patchVnode (oldVnode, vnode, insertedVnodeQueue, removeOnly) {\n  if (oldVnode === vnode) {\n    return\n  }\n\n  const elm = vnode.elm = oldVnode.elm\n\n  if (isTrue(oldVnode.isAsyncPlaceholder)) {\n    if (isDef(vnode.asyncFactory.resolved)) {\n      hydrate(oldVnode.elm, vnode, insertedVnodeQueue)\n    } else {\n      vnode.isAsyncPlaceholder = true\n    }\n    return\n  }\n\n  // reuse element for static trees.\n  // note we only do this if the vnode is cloned -\n  // if the new node is not cloned it means the render functions have been\n  // reset by the hot-reload-api and we need to do a proper re-render.\n  if (isTrue(vnode.isStatic) &&\n    isTrue(oldVnode.isStatic) &&\n    vnode.key === oldVnode.key &&\n    (isTrue(vnode.isCloned) || isTrue(vnode.isOnce))\n  ) {\n    vnode.componentInstance = oldVnode.componentInstance\n    return\n  }\n\n  let i\n  const data = vnode.data\n  if (isDef(data) && isDef(i = data.hook) && isDef(i = i.prepatch)) {\n    i(oldVnode, vnode)\n  }\n\n  const oldCh = oldVnode.children\n  const ch = vnode.children\n  if (isDef(data) && isPatchable(vnode)) {\n    for (i = 0; i < cbs.update.length; ++i) cbs.update[i](oldVnode, vnode)\n    if (isDef(i = data.hook) && isDef(i = i.update)) i(oldVnode, vnode)\n  }\n  if (isUndef(vnode.text)) {\n    if (isDef(oldCh) && isDef(ch)) {\n      if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly)\n    } else if (isDef(ch)) {\n      if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, '')\n      addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue)\n    } else if (isDef(oldCh)) {\n      removeVnodes(elm, oldCh, 0, oldCh.length - 1)\n    } else if (isDef(oldVnode.text)) {\n      nodeOps.setTextContent(elm, '')\n    }\n  } else if (oldVnode.text !== vnode.text) {\n    nodeOps.setTextContent(elm, vnode.text)\n  }\n  if (isDef(data)) {\n    if (isDef(i = data.hook) && isDef(i = i.postpatch)) i(oldVnode, vnode)\n  }\n}\n```\n`patchVnode` 的作用就是把新的 `vnode` `patch` 到旧的 `vnode` 上，这里我们只关注关键的核心逻辑，我把它拆成四步骤：\n\n- 执行 `prepatch` 钩子函数\n\n```js\nlet i\nconst data = vnode.data\nif (isDef(data) && isDef(i = data.hook) && isDef(i = i.prepatch)) {\n  i(oldVnode, vnode)\n}\n```\n\n当更新的 `vnode` 是一个组件 `vnode` 的时候，会执行 `prepatch` 的方法，它的定义在 `src/core/vdom/create-component.js` 中：\n\n```js\nconst componentVNodeHooks = {\n  prepatch (oldVnode: MountedComponentVNode, vnode: MountedComponentVNode) {\n    const options = vnode.componentOptions\n    const child = vnode.componentInstance = oldVnode.componentInstance\n    updateChildComponent(\n      child,\n      options.propsData, // updated props\n      options.listeners, // updated listeners\n      vnode, // new parent vnode\n      options.children // new children\n    )\n  }\n}\n```\n\n`prepatch` 方法就是拿到新的 `vnode` 的组件配置以及组件实例，去执行 `updateChildComponent` 方法，它的定义在 `src/core/instance/lifecycle.js` 中：\n\n```js\nexport function updateChildComponent (\n  vm: Component,\n  propsData: ?Object,\n  listeners: ?Object,\n  parentVnode: MountedComponentVNode,\n  renderChildren: ?Array<VNode>\n) {\n  if (process.env.NODE_ENV !== 'production') {\n    isUpdatingChildComponent = true\n  }\n\n  // determine whether component has slot children\n  // we need to do this before overwriting $options._renderChildren\n  const hasChildren = !!(\n    renderChildren ||               // has new static slots\n    vm.$options._renderChildren ||  // has old static slots\n    parentVnode.data.scopedSlots || // has new scoped slots\n    vm.$scopedSlots !== emptyObject // has old scoped slots\n  )\n\n  vm.$options._parentVnode = parentVnode\n  vm.$vnode = parentVnode // update vm's placeholder node without re-render\n\n  if (vm._vnode) { // update child tree's parent\n    vm._vnode.parent = parentVnode\n  }\n  vm.$options._renderChildren = renderChildren\n\n  // update $attrs and $listeners hash\n  // these are also reactive so they may trigger child update if the child\n  // used them during render\n  vm.$attrs = parentVnode.data.attrs || emptyObject\n  vm.$listeners = listeners || emptyObject\n\n  // update props\n  if (propsData && vm.$options.props) {\n    toggleObserving(false)\n    const props = vm._props\n    const propKeys = vm.$options._propKeys || []\n    for (let i = 0; i < propKeys.length; i++) {\n      const key = propKeys[i]\n      const propOptions: any = vm.$options.props // wtf flow?\n      props[key] = validateProp(key, propOptions, propsData, vm)\n    }\n    toggleObserving(true)\n    // keep a copy of raw propsData\n    vm.$options.propsData = propsData\n  }\n\n  // update listeners\n  listeners = listeners || emptyObject\n  const oldListeners = vm.$options._parentListeners\n  vm.$options._parentListeners = listeners\n  updateComponentListeners(vm, listeners, oldListeners)\n\n  // resolve slots + force update if has children\n  if (hasChildren) {\n    vm.$slots = resolveSlots(renderChildren, parentVnode.context)\n    vm.$forceUpdate()\n  }\n\n  if (process.env.NODE_ENV !== 'production') {\n    isUpdatingChildComponent = false\n  }\n}\n```\n\n`updateChildComponent` 的逻辑也非常简单，由于更新了 `vnode`，那么 `vnode` 对应的实例 `vm` 的一系列属性也会发生变化，包括占位符 `vm.$vnode` 的更新、`slot` 的更新，`listeners` 的更新，`props` 的更新等等。 \n\n- 执行 `update` 钩子函数\n\n```js\nif (isDef(data) && isPatchable(vnode)) {\n  for (i = 0; i < cbs.update.length; ++i) cbs.update[i](oldVnode, vnode)\n  if (isDef(i = data.hook) && isDef(i = i.update)) i(oldVnode, vnode)\n}\n```\n\n回到 `patchVNode` 函数，在执行完新的 `vnode` 的 `prepatch` 钩子函数，会执行所有 `module` 的 `update` 钩子函数以及用户自定义 `update` 钩子函数，对于 `module` 的钩子函数，之后我们会有具体的章节针对一些具体的 case 分析。\n\n- 完成 `patch` 过程\n\n```js\nconst oldCh = oldVnode.children\nconst ch = vnode.children\nif (isDef(data) && isPatchable(vnode)) {\n  for (i = 0; i < cbs.update.length; ++i) cbs.update[i](oldVnode, vnode)\n  if (isDef(i = data.hook) && isDef(i = i.update)) i(oldVnode, vnode)\n}\nif (isUndef(vnode.text)) {\n  if (isDef(oldCh) && isDef(ch)) {\n    if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly)\n  } else if (isDef(ch)) {\n    if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, '')\n    addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue)\n  } else if (isDef(oldCh)) {\n    removeVnodes(elm, oldCh, 0, oldCh.length - 1)\n  } else if (isDef(oldVnode.text)) {\n    nodeOps.setTextContent(elm, '')\n  }\n} else if (oldVnode.text !== vnode.text) {\n  nodeOps.setTextContent(elm, vnode.text)\n}\n```\n\n如果 `vnode` 是个文本节点且新旧文本不相同，则直接替换文本内容。如果不是文本节点，则判断它们的子节点，并分了几种情况处理：\n\n1. `oldCh` 与 `ch` 都存在且不相同时，使用 `updateChildren` 函数来更新子节点，这个后面重点讲。\n\n2.如果只有 `ch` 存在，表示旧节点不需要了。如果旧的节点是文本节点则先将节点的文本清除，然后通过 `addVnodes` 将 `ch` 批量插入到新节点 `elm` 下。\n                    \n3.如果只有 `oldCh` 存在，表示更新的是空节点，则需要将旧的节点通过 `removeVnodes` 全部清除。\n          \n4.当只有旧节点是文本节点的时候，则清除其节点文本内容。\n\n- 执行 `postpatch` 钩子函数\n\n```js\nif (isDef(data)) {\n  if (isDef(i = data.hook) && isDef(i = i.postpatch)) i(oldVnode, vnode)\n}\n```\n再执行完 `patch` 过程后，会执行 `postpatch` 钩子函数，它是组件自定义的钩子函数，有则执行。\n\n那么在整个 `pathVnode` 过程中，最复杂的就是 `updateChildren` 方法了，下面我们来单独介绍它。\n\n## updateChildren\n\n```js\nfunction updateChildren (parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly) {\n  let oldStartIdx = 0\n  let newStartIdx = 0\n  let oldEndIdx = oldCh.length - 1\n  let oldStartVnode = oldCh[0]\n  let oldEndVnode = oldCh[oldEndIdx]\n  let newEndIdx = newCh.length - 1\n  let newStartVnode = newCh[0]\n  let newEndVnode = newCh[newEndIdx]\n  let oldKeyToIdx, idxInOld, vnodeToMove, refElm\n\n  // removeOnly is a special flag used only by <transition-group>\n  // to ensure removed elements stay in correct relative positions\n  // during leaving transitions\n  const canMove = !removeOnly\n\n  if (process.env.NODE_ENV !== 'production') {\n    checkDuplicateKeys(newCh)\n  }\n\n  while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {\n    if (isUndef(oldStartVnode)) {\n      oldStartVnode = oldCh[++oldStartIdx] // Vnode has been moved left\n    } else if (isUndef(oldEndVnode)) {\n      oldEndVnode = oldCh[--oldEndIdx]\n    } else if (sameVnode(oldStartVnode, newStartVnode)) {\n      patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue)\n      oldStartVnode = oldCh[++oldStartIdx]\n      newStartVnode = newCh[++newStartIdx]\n    } else if (sameVnode(oldEndVnode, newEndVnode)) {\n      patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue)\n      oldEndVnode = oldCh[--oldEndIdx]\n      newEndVnode = newCh[--newEndIdx]\n    } else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right\n      patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue)\n      canMove && nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm))\n      oldStartVnode = oldCh[++oldStartIdx]\n      newEndVnode = newCh[--newEndIdx]\n    } else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left\n      patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue)\n      canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm)\n      oldEndVnode = oldCh[--oldEndIdx]\n      newStartVnode = newCh[++newStartIdx]\n    } else {\n      if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)\n      idxInOld = isDef(newStartVnode.key)\n        ? oldKeyToIdx[newStartVnode.key]\n        : findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx)\n      if (isUndef(idxInOld)) { // New element\n        createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)\n      } else {\n        vnodeToMove = oldCh[idxInOld]\n        if (sameVnode(vnodeToMove, newStartVnode)) {\n          patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue)\n          oldCh[idxInOld] = undefined\n          canMove && nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm)\n        } else {\n          // same key but different element. treat as new element\n          createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)\n        }\n      }\n      newStartVnode = newCh[++newStartIdx]\n    }\n  }\n  if (oldStartIdx > oldEndIdx) {\n    refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm\n    addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, insertedVnodeQueue)\n  } else if (newStartIdx > newEndIdx) {\n    removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx)\n  }\n}\n```\n\n`updateChildren` 的逻辑比较复杂，直接读源码比较晦涩，我们可以通过一个具体的示例来分析它。\n\n```js\n<template>\n  <div id=\"app\">\n    <div>\n      <ul>\n        <li v-for=\"item in items\" :key=\"item.id\">{{ item.val }}</li>\n      </ul>\n    </div>\n    <button @click=\"change\">change</button>\n  </div>\n</template>\n\n<script>\n  export default {\n    name: 'App',\n    data() {\n      return {\n        items: [\n          {id: 0, val: 'A'},\n          {id: 1, val: 'B'},\n          {id: 2, val: 'C'},\n          {id: 3, val: 'D'}\n        ]\n      }\n    },\n    methods: {\n      change() {\n        this.items.reverse().push({id: 4, val: 'E'})\n      }\n    }\n  }\n</script>\n```\n\n当我们点击 `change` 按钮去改变数据，最终会执行到 `updateChildren` 去更新 `li` 部分的列表数据，我们通过图的方式来描述一下它的更新过程：\n\n第一步：\n<img :src=\"$withBase('/assets/update-children-1.png')\">\n\n第二步：\n<img :src=\"$withBase('/assets/update-children-2.png')\">\n\n第三步：\n<img :src=\"$withBase('/assets/update-children-3.png')\">\n\n第四步：\n<img :src=\"$withBase('/assets/update-children-4.png')\">\n\n第五步：\n<img :src=\"$withBase('/assets/update-children-5.png')\">\n\n第六步：\n<img :src=\"$withBase('/assets/update-children-6.png')\">\n\n## 总结\n\n组件更新的过程核心就是新旧 vnode diff，对新旧节点相同以及不同的情况分别做不同的处理。新旧节点不同的更新流程是创建新节点->更新父占位符节点->删除旧节点；而新旧节点相同的更新流程是去获取它们的 children，根据不同情况做不同的更新逻辑。最复杂的情况是新旧节点相同且它们都存在子节点，那么会执行 `updateChildren` 逻辑，这块儿可以借助画图的方式配合理解。\n\n"
  },
  {
    "path": "docs/v2/reactive/computed-watcher.md",
    "content": "# 计算属性 VS 侦听属性\n\nVue 的组件对象支持了计算属性 `computed` 和侦听属性 `watch` 2 个选项，很多同学不了解什么时候该用 `computed` 什么时候该用 `watch`。先不回答这个问题，我们接下来从源码实现的角度来分析它们两者有什么区别。\n\n## `computed`\n\n计算属性的初始化是发生在 Vue 实例初始化阶段的 `initState` 函数中，执行了 `if (opts.computed) initComputed(vm, opts.computed)`，`initComputed` 的定义在 `src/core/instance/state.js` 中：\n\n```js\nconst computedWatcherOptions = { computed: true }\nfunction initComputed (vm: Component, computed: Object) {\n  // $flow-disable-line\n  const watchers = vm._computedWatchers = Object.create(null)\n  // computed properties are just getters during SSR\n  const isSSR = isServerRendering()\n\n  for (const key in computed) {\n    const userDef = computed[key]\n    const getter = typeof userDef === 'function' ? userDef : userDef.get\n    if (process.env.NODE_ENV !== 'production' && getter == null) {\n      warn(\n        `Getter is missing for computed property \"${key}\".`,\n        vm\n      )\n    }\n\n    if (!isSSR) {\n      // create internal watcher for the computed property.\n      watchers[key] = new Watcher(\n        vm,\n        getter || noop,\n        noop,\n        computedWatcherOptions\n      )\n    }\n\n    // component-defined computed properties are already defined on the\n    // component prototype. We only need to define computed properties defined\n    // at instantiation here.\n    if (!(key in vm)) {\n      defineComputed(vm, key, userDef)\n    } else if (process.env.NODE_ENV !== 'production') {\n      if (key in vm.$data) {\n        warn(`The computed property \"${key}\" is already defined in data.`, vm)\n      } else if (vm.$options.props && key in vm.$options.props) {\n        warn(`The computed property \"${key}\" is already defined as a prop.`, vm)\n      }\n    }\n  }\n}\n```\n\n函数首先创建 `vm._computedWatchers` 为一个空对象，接着对 `computed` 对象做遍历，拿到计算属性的每一个 `userDef`，然后尝试获取这个 `userDef` 对应的 `getter` 函数，拿不到则在开发环境下报警告。接下来为每一个 `getter` 创建一个 `watcher`，这个 `watcher` 和渲染 `watcher` 有一点很大的不同，它是一个 `computed watcher`，因为 `const computedWatcherOptions = { computed: true }`。`computed watcher` 和普通 `watcher` 的差别我稍后会介绍。最后对判断如果 `key` 不是 `vm` 的属性，则调用 `defineComputed(vm, key, userDef)`，否则判断计算属性对于的 `key` 是否已经被 `data` 或者 `prop` 所占用，如果是的话则在开发环境报相应的警告。\n \n 那么接下来需要重点关注 `defineComputed` 的实现：\n \n```js\nexport function defineComputed (\n  target: any,\n  key: string,\n  userDef: Object | Function\n) {\n  const shouldCache = !isServerRendering()\n  if (typeof userDef === 'function') {\n    sharedPropertyDefinition.get = shouldCache\n      ? createComputedGetter(key)\n      : userDef\n    sharedPropertyDefinition.set = noop\n  } else {\n    sharedPropertyDefinition.get = userDef.get\n      ? shouldCache && userDef.cache !== false\n        ? createComputedGetter(key)\n        : userDef.get\n      : noop\n    sharedPropertyDefinition.set = userDef.set\n      ? userDef.set\n      : noop\n  }\n  if (process.env.NODE_ENV !== 'production' &&\n      sharedPropertyDefinition.set === noop) {\n    sharedPropertyDefinition.set = function () {\n      warn(\n        `Computed property \"${key}\" was assigned to but it has no setter.`,\n        this\n      )\n    }\n  }\n  Object.defineProperty(target, key, sharedPropertyDefinition)\n}\n```\n\n这段逻辑很简单，其实就是利用 `Object.defineProperty` 给计算属性对应的 `key` 值添加 getter 和 setter，setter 通常是计算属性是一个对象，并且拥有 `set` 方法的时候才有，否则是一个空函数。在平时的开发场景中，计算属性有 setter 的情况比较少，我们重点关注一下 getter 部分，缓存的配置也先忽略，最终 getter 对应的是 `createComputedGetter(key)` 的返回值，来看一下它的定义：\n\n```js\nfunction createComputedGetter (key) {\n  return function computedGetter () {\n    const watcher = this._computedWatchers && this._computedWatchers[key]\n    if (watcher) {\n      watcher.depend()\n      return watcher.evaluate()\n    }\n  }\n}\n```\n`createComputedGetter` 返回一个函数 `computedGetter`，它就是计算属性对应的 getter。\n\n整个计算属性的初始化过程到此结束，我们知道计算属性是一个 `computed watcher`，它和普通的 `watcher` 有什么区别呢，为了更加直观，接下来来我们来通过一个例子来分析 `computed watcher` 的实现。\n\n```js\nvar vm = new Vue({\n  data: {\n    firstName: 'Foo',\n    lastName: 'Bar'\n  },\n  computed: {\n    fullName: function () {\n      return this.firstName + ' ' + this.lastName\n    }\n  }\n})\n```\n\n当初始化这个 `computed watcher` 实例的时候，构造函数部分逻辑稍有不同：\n\n```js\nconstructor (\n  vm: Component,\n  expOrFn: string | Function,\n  cb: Function,\n  options?: ?Object,\n  isRenderWatcher?: boolean\n) {\n  // ...\n  if (this.computed) {\n    this.value = undefined\n    this.dep = new Dep()\n  } else {\n    this.value = this.get()\n  }\n}  \n```\n\n可以发现 `computed watcher` 会并不会立刻求值，同时持有一个 `dep` 实例。\n\n然后当我们的 `render` 函数执行访问到 `this.fullName` 的时候，就触发了计算属性的 `getter`，它会拿到计算属性对应的 `watcher`，然后执行 `watcher.depend()`，来看一下它的定义：\n\n```js\n/**\n  * Depend on this watcher. Only for computed property watchers.\n  */\ndepend () {\n  if (this.dep && Dep.target) {\n    this.dep.depend()\n  }\n}\n```\n\n注意，这时候的 `Dep.target` 是渲染 `watcher`，所以 `this.dep.depend()` 相当于渲染 `watcher` 订阅了这个 `computed watcher` 的变化。\n\n然后再执行 `watcher.evaluate()` 去求值，来看一下它的定义：\n\n```js\n/**\n  * Evaluate and return the value of the watcher.\n  * This only gets called for computed property watchers.\n  */\nevaluate () {\n  if (this.dirty) {\n    this.value = this.get()\n    this.dirty = false\n  }\n  return this.value\n}\n```\n`evaluate` 的逻辑非常简单，判断 `this.dirty`，如果为 `true` 则通过 `this.get()` 求值，然后把 `this.dirty` 设置为 false。在求值过程中，会执行 `value = this.getter.call(vm, vm)`，这实际上就是执行了计算属性定义的 `getter` 函数，在我们这个例子就是执行了 `return this.firstName + ' ' + this.lastName`。\n\n这里需要特别注意的是，由于 `this.firstName` 和 `this.lastName` 都是响应式对象，这里会触发它们的 getter，根据我们之前的分析，它们会把自身持有的 `dep` 添加到当前正在计算的 `watcher` 中，这个时候 `Dep.target` 就是这个 `computed watcher`。\n\n最后通过 `return this.value` 拿到计算属性对应的值。我们知道了计算属性的求值过程，那么接下来看一下它依赖的数据变化后的逻辑。\n\n一旦我们对计算属性依赖的数据做修改，则会触发 setter 过程，通知所有订阅它变化的 `watcher` 更新，执行 `watcher.update()` 方法：\n\n````js\n/* istanbul ignore else */\nif (this.computed) {\n  // A computed property watcher has two modes: lazy and activated.\n  // It initializes as lazy by default, and only becomes activated when\n  // it is depended on by at least one subscriber, which is typically\n  // another computed property or a component's render function.\n  if (this.dep.subs.length === 0) {\n    // In lazy mode, we don't want to perform computations until necessary,\n    // so we simply mark the watcher as dirty. The actual computation is\n    // performed just-in-time in this.evaluate() when the computed property\n    // is accessed.\n    this.dirty = true\n  } else {\n    // In activated mode, we want to proactively perform the computation\n    // but only notify our subscribers when the value has indeed changed.\n    this.getAndInvoke(() => {\n      this.dep.notify()\n    })\n  }\n} else if (this.sync) {\n  this.run()\n} else {\n  queueWatcher(this)\n}\n````\n\n那么对于计算属性这样的 `computed watcher`，它实际上是有 2 种模式，lazy 和 active。如果 `this.dep.subs.length === 0` 成立，则说明没有人去订阅这个 `computed watcher` 的变化，仅仅把 `this.dirty = true`，只有当下次再访问这个计算属性的时候才会重新求值。在我们的场景下，渲染 `watcher` 订阅了这个 `computed watcher` 的变化，那么它会执行：\n\n```js\nthis.getAndInvoke(() => {\n  this.dep.notify()\n})\n\ngetAndInvoke (cb: Function) {\n  const value = this.get()\n  if (\n    value !== this.value ||\n    // Deep watchers and watchers on Object/Arrays should fire even\n    // when the value is the same, because the value may\n    // have mutated.\n    isObject(value) ||\n    this.deep\n  ) {\n    // set new value\n    const oldValue = this.value\n    this.value = value\n    this.dirty = false\n    if (this.user) {\n      try {\n        cb.call(this.vm, value, oldValue)\n      } catch (e) {\n        handleError(e, this.vm, `callback for watcher \"${this.expression}\"`)\n      }\n    } else {\n      cb.call(this.vm, value, oldValue)\n    }\n  }\n}\n\n```\n\n`getAndInvoke` 函数会重新计算，然后对比新旧值，如果变化了则执行回调函数，那么这里这个回调函数是 `this.dep.notify()`，在我们这个场景下就是触发了渲染 `watcher` 重新渲染。\n\n通过以上的分析，我们知道计算属性本质上就是一个 `computed watcher`，也了解了它的创建过程和被访问触发 getter 以及依赖更新的过程，其实这是最新的计算属性的实现，之所以这么设计是因为 Vue 想确保不仅仅是计算属性依赖的值发生变化，而是当计算属性最终计算的值发生变化才会触发渲染 `watcher` 重新渲染，本质上是一种优化。\n\n接下来我们来分析一下侦听属性 `watch` 是怎么实现的。\n\n## watch\n\n侦听属性的初始化也是发生在 Vue 的实例初始化阶段的 `initState` 函数中，在 `computed` 初始化之后，执行了：\n\n```js\nif (opts.watch && opts.watch !== nativeWatch) {\n  initWatch(vm, opts.watch)\n}\n```\n来看一下 `initWatch` 的实现，它的定义在 `src/core/instance/state.js` 中：\n\n```js\nfunction initWatch (vm: Component, watch: Object) {\n  for (const key in watch) {\n    const handler = watch[key]\n    if (Array.isArray(handler)) {\n      for (let i = 0; i < handler.length; i++) {\n        createWatcher(vm, key, handler[i])\n      }\n    } else {\n      createWatcher(vm, key, handler)\n    }\n  }\n}\n```\n这里就是对 `watch` 对象做遍历，拿到每一个  `handler`，因为 Vue 是支持 `watch` 的同一个 `key` 对应多个 `handler`，所以如果 `handler` 是一个数组，则遍历这个数组，调用 `createWatcher` 方法，否则直接调用 `createWatcher`：\n\n```js\nfunction createWatcher (\n  vm: Component,\n  expOrFn: string | Function,\n  handler: any,\n  options?: Object\n) {\n  if (isPlainObject(handler)) {\n    options = handler\n    handler = handler.handler\n  }\n  if (typeof handler === 'string') {\n    handler = vm[handler]\n  }\n  return vm.$watch(expOrFn, handler, options)\n}\n```\n这里的逻辑也很简单，首先对 `hanlder` 的类型做判断，拿到它最终的回调函数，最后调用 `vm.$watch(keyOrFn, handler, options)` 函数，`$watch` 是 Vue 原型上的方法，它是在执行 `stateMixin` 的时候定义的：\n\n```js\nVue.prototype.$watch = function (\n  expOrFn: string | Function,\n  cb: any,\n  options?: Object\n): Function {\n  const vm: Component = this\n  if (isPlainObject(cb)) {\n    return createWatcher(vm, expOrFn, cb, options)\n  }\n  options = options || {}\n  options.user = true\n  const watcher = new Watcher(vm, expOrFn, cb, options)\n  if (options.immediate) {\n    cb.call(vm, watcher.value)\n  }\n  return function unwatchFn () {\n    watcher.teardown()\n  }\n}\n```\n\n也就是说，侦听属性 `watch` 最终会调用 `$watch` 方法，这个方法首先判断 `cb` 如果是一个对象，则调用 `createWatcher` 方法，这是因为 `$watch` 方法是用户可以直接调用的，它可以传递一个对象，也可以传递函数。接着执行 `const watcher = new Watcher(vm, expOrFn, cb, options)` 实例化了一个 `watcher`，这里需要注意一点这是一个 `user watcher`，因为 `options.user = true`。通过实例化 `watcher` 的方式，一旦我们 `watch` 的数据发送变化，它最终会执行 `watcher` 的 `run` 方法，执行回调函数 `cb`，并且如果我们设置了 `immediate` 为 true，则直接会执行回调函数 `cb`。最后返回了一个 `unwatchFn` 方法，它会调用 `teardown` 方法去移除这个 `watcher`。\n\n所以本质上侦听属性也是基于 `Watcher` 实现的，它是一个 `user watcher`。其实 `Watcher` 支持了不同的类型，下面我们梳理一下它有哪些类型以及它们的作用。\n\n## Watcher options\n\n`Watcher` 的构造函数对 `options` 做的了处理，代码如下：\n\n```js\nif (options) {\n  this.deep = !!options.deep\n  this.user = !!options.user\n  this.computed = !!options.computed\n  this.sync = !!options.sync\n  // ...\n} else {\n  this.deep = this.user = this.computed = this.sync = false\n}\n```\n所以 `watcher` 总共有 4 种类型，我们来一一分析它们，看看不同的类型执行的逻辑有哪些差别。\n\n### deep watcher\n\n通常，如果我们想对一下对象做深度观测的时候，需要设置这个属性为 true，考虑到这种情况：\n\n```js\nvar vm = new Vue({\n  data() {\n    a: {\n      b: 1\n    }\n  },\n  watch: {\n    a: {\n      handler(newVal) {\n        console.log(newVal)\n      }\n    }\n  }\n})\nvm.a.b = 2\n```\n这个时候是不会 log 任何数据的，因为我们是 watch 了 `a` 对象，只触发了 `a` 的 getter，并没有触发 `a.b` 的 getter，所以并没有订阅它的变化，导致我们对 `vm.a.b = 2` 赋值的时候，虽然触发了 setter，但没有可通知的对象，所以也并不会触发 watch 的回调函数了。\n\n而我们只需要对代码做稍稍修改，就可以观测到这个变化了\n\n```js\nwatch: {\n  a: {\n    deep: true,\n    handler(newVal) {\n      console.log(newVal)\n    }\n  }\n}\n```\n\n这样就创建了一个 `deep watcher` 了，在 `watcher ` 执行 `get` 求值的过程中有一段逻辑：\n\n```js\nget() {\n  let value = this.getter.call(vm, vm)\n  // ...\n  if (this.deep) {\n    traverse(value)\n  }\n}\n```\n在对 watch 的表达式或者函数求值后，会调用 `traverse` 函数，它的定义在 `src/core/observer/traverse.js` 中：\n\n```js\nimport { _Set as Set, isObject } from '../util/index'\nimport type { SimpleSet } from '../util/index'\nimport VNode from '../vdom/vnode'\n\nconst seenObjects = new Set()\n\n/**\n * Recursively traverse an object to evoke all converted\n * getters, so that every nested property inside the object\n * is collected as a \"deep\" dependency.\n */\nexport function traverse (val: any) {\n  _traverse(val, seenObjects)\n  seenObjects.clear()\n}\n\nfunction _traverse (val: any, seen: SimpleSet) {\n  let i, keys\n  const isA = Array.isArray(val)\n  if ((!isA && !isObject(val)) || Object.isFrozen(val) || val instanceof VNode) {\n    return\n  }\n  if (val.__ob__) {\n    const depId = val.__ob__.dep.id\n    if (seen.has(depId)) {\n      return\n    }\n    seen.add(depId)\n  }\n  if (isA) {\n    i = val.length\n    while (i--) _traverse(val[i], seen)\n  } else {\n    keys = Object.keys(val)\n    i = keys.length\n    while (i--) _traverse(val[keys[i]], seen)\n  }\n}\n```\n\n`traverse` 的逻辑也很简单，它实际上就是对一个对象做深层递归遍历，因为遍历过程中就是对一个子对象的访问，会触发它们的 getter 过程，这样就可以收集到依赖，也就是订阅它们变化的 `watcher`，这个函数实现还有一个小的优化，遍历过程中会把子响应式对象通过它们的 `dep id` 记录到 `seenObjects`，避免以后重复访问。\n\n那么在执行了 `traverse` 后，我们再对 watch 的对象内部任何一个值做修改，也会调用 `watcher` 的回调函数了。\n\n对 `deep watcher` 的理解非常重要，今后工作中如果大家观测了一个复杂对象，并且会改变对象内部深层某个值的时候也希望触发回调，一定要设置 `deep` 为 true，但是因为设置了 `deep` 后会执行 `traverse` 函数，会有一定的性能开销，所以一定要根据应用场景权衡是否要开启这个配置。\n\n### user watcher\n\n前面我们分析过，通过 `vm.$watch` 创建的 `watcher` 是一个 `user watcher`，其实它的功能很简单，在对 `watcher` 求值以及在执行回调函数的时候，会处理一下错误，如下：\n\n```js\nget() {\n  if (this.user) {\n    handleError(e, vm, `getter for watcher \"${this.expression}\"`)\n  } else {\n    throw e\n  }\n},\ngetAndInvoke() {\n  // ...\n  if (this.user) {\n    try {\n      this.cb.call(this.vm, value, oldValue)\n    } catch (e) {\n      handleError(e, this.vm, `callback for watcher \"${this.expression}\"`)\n    }\n  } else {\n    this.cb.call(this.vm, value, oldValue)\n  }\n}\n```\n`handleError` 在 Vue 中是一个错误捕获并且暴露给用户的一个利器。\n\n### computed watcher\n\n`computed watcher` 几乎就是为计算属性量身定制的，我们刚才已经对它做了详细的分析，这里不再赘述了。\n\n### sync watcher\n\n在我们之前对 `setter` 的分析过程知道，当响应式数据发送变化后，触发了 `watcher.update()`，只是把这个 `watcher` 推送到一个队列中，在 `nextTick` 后才会真正执行 `watcher` 的回调函数。而一旦我们设置了 `sync`，就可以在当前 `Tick` 中同步执行 `watcher` 的回调函数。\n\n```js\nupdate () {\n  if (this.computed) {\n    // ...\n  } else if (this.sync) {\n    this.run()\n  } else {\n    queueWatcher(this)\n  }\n}\n```\n只有当我们需要 watch 的值的变化到执行 `watcher` 的回调函数是一个同步过程的时候才会去设置该属性为 true。\n\n## 总结\n\n通过这一小节的分析我们对计算属性和侦听属性的实现有了深入的了解，计算属性本质上是 `computed watcher`，而侦听属性本质上是 `user watcher`。就应用场景而言，计算属性适合用在模板渲染中，某个值是依赖了其它的响应式对象甚至是计算属性计算而来；而侦听属性适用于观测某个值的变化去完成一段复杂的业务逻辑。\n\n同时我们又了解了 `watcher` 的 4 个 `options`，通常我们会在创建 `user watcher` 的时候配置 `deep` 和 `sync`，可以根据不同的场景做相应的配置。\n\n\n\n\n\n\n\n\n \n"
  },
  {
    "path": "docs/v2/reactive/getters.md",
    "content": "# 依赖收集\n\n通过上一节的分析我们了解 Vue 会把普通对象变成响应式对象，响应式对象 getter 相关的逻辑就是做依赖收集，这一节我们来详细分析这个过程。\n\n我们先来回顾一下 getter 部分的逻辑：\n\n```js\nexport function defineReactive (\n  obj: Object,\n  key: string,\n  val: any,\n  customSetter?: ?Function,\n  shallow?: boolean\n) {\n  const dep = new Dep()\n\n  const property = Object.getOwnPropertyDescriptor(obj, key)\n  if (property && property.configurable === false) {\n    return\n  }\n\n  // cater for pre-defined getter/setters\n  const getter = property && property.get\n  const setter = property && property.set\n  if ((!getter || setter) && arguments.length === 2) {\n    val = obj[key]\n  }\n\n  let childOb = !shallow && observe(val)\n  Object.defineProperty(obj, key, {\n    enumerable: true,\n    configurable: true,\n    get: function reactiveGetter () {\n      const value = getter ? getter.call(obj) : val\n      if (Dep.target) {\n        dep.depend()\n        if (childOb) {\n          childOb.dep.depend()\n          if (Array.isArray(value)) {\n            dependArray(value)\n          }\n        }\n      }\n      return value\n    },\n    // ...\n  })\n}\n```\n这段代码我们只需要关注 2 个地方，一个是 `const dep = new Dep()` 实例化一个 `Dep` 的实例，另一个是在 `get` 函数中通过 `dep.depend` 做依赖收集，这里还有个对 `childOb` 判断的逻辑，我们之后会介绍它的作用。\n\n## Dep\n\n`Dep` 是整个 getter 依赖收集的核心，它的定义在 `src/core/observer/dep.js` 中：\n\n```js\nimport type Watcher from './watcher'\nimport { remove } from '../util/index'\n\nlet uid = 0\n\n/**\n * A dep is an observable that can have multiple\n * directives subscribing to it.\n */\nexport default class Dep {\n  static target: ?Watcher;\n  id: number;\n  subs: Array<Watcher>;\n\n  constructor () {\n    this.id = uid++\n    this.subs = []\n  }\n\n  addSub (sub: Watcher) {\n    this.subs.push(sub)\n  }\n\n  removeSub (sub: Watcher) {\n    remove(this.subs, sub)\n  }\n\n  depend () {\n    if (Dep.target) {\n      Dep.target.addDep(this)\n    }\n  }\n\n  notify () {\n    // stabilize the subscriber list first\n    const subs = this.subs.slice()\n    for (let i = 0, l = subs.length; i < l; i++) {\n      subs[i].update()\n    }\n  }\n}\n\n// the current target watcher being evaluated.\n// this is globally unique because there could be only one\n// watcher being evaluated at any time.\nDep.target = null\nconst targetStack = []\n\nexport function pushTarget (_target: ?Watcher) {\n  if (Dep.target) targetStack.push(Dep.target)\n  Dep.target = _target\n}\n\nexport function popTarget () {\n  Dep.target = targetStack.pop()\n}\n```\n\n`Dep` 是一个 Class，它定义了一些属性和方法，这里需要特别注意的是它有一个静态属性 `target`，这是一个全局唯一 `Watcher`，这是一个非常巧妙的设计，因为在同一时间只能有一个全局的 `Watcher` 被计算，另外它的自身属性 `subs` 也是 `Watcher` 的数组。\n\n`Dep` 实际上就是对 `Watcher` 的一种管理，`Dep`  脱离 `Watcher` 单独存在是没有意义的，为了完整地讲清楚依赖收集过程，我们有必要看一下 `Watcher` 的一些相关实现，它的定义在 `src/core/observer/watcher.js` 中：\n\n\n## `Watcher`\n\n```js\nlet uid = 0\n\n/**\n * A watcher parses an expression, collects dependencies,\n * and fires callback when the expression value changes.\n * This is used for both the $watch() api and directives.\n */\nexport default class Watcher {\n  vm: Component;\n  expression: string;\n  cb: Function;\n  id: number;\n  deep: boolean;\n  user: boolean;\n  computed: boolean;\n  sync: boolean;\n  dirty: boolean;\n  active: boolean;\n  dep: Dep;\n  deps: Array<Dep>;\n  newDeps: Array<Dep>;\n  depIds: SimpleSet;\n  newDepIds: SimpleSet;\n  before: ?Function;\n  getter: Function;\n  value: any;\n\n  constructor (\n    vm: Component,\n    expOrFn: string | Function,\n    cb: Function,\n    options?: ?Object,\n    isRenderWatcher?: boolean\n  ) {\n    this.vm = vm\n    if (isRenderWatcher) {\n      vm._watcher = this\n    }\n    vm._watchers.push(this)\n    // options\n    if (options) {\n      this.deep = !!options.deep\n      this.user = !!options.user\n      this.computed = !!options.computed\n      this.sync = !!options.sync\n      this.before = options.before\n    } else {\n      this.deep = this.user = this.computed = this.sync = false\n    }\n    this.cb = cb\n    this.id = ++uid // uid for batching\n    this.active = true\n    this.dirty = this.computed // for computed watchers\n    this.deps = []\n    this.newDeps = []\n    this.depIds = new Set()\n    this.newDepIds = new Set()\n    this.expression = process.env.NODE_ENV !== 'production'\n      ? expOrFn.toString()\n      : ''\n    // parse expression for getter\n    if (typeof expOrFn === 'function') {\n      this.getter = expOrFn\n    } else {\n      this.getter = parsePath(expOrFn)\n      if (!this.getter) {\n        this.getter = function () {}\n        process.env.NODE_ENV !== 'production' && warn(\n          `Failed watching path: \"${expOrFn}\" ` +\n          'Watcher only accepts simple dot-delimited paths. ' +\n          'For full control, use a function instead.',\n          vm\n        )\n      }\n    }\n    if (this.computed) {\n      this.value = undefined\n      this.dep = new Dep()\n    } else {\n      this.value = this.get()\n    }\n  }\n\n  /**\n   * Evaluate the getter, and re-collect dependencies.\n   */\n  get () {\n    pushTarget(this)\n    let value\n    const vm = this.vm\n    try {\n      value = this.getter.call(vm, vm)\n    } catch (e) {\n      if (this.user) {\n        handleError(e, vm, `getter for watcher \"${this.expression}\"`)\n      } else {\n        throw e\n      }\n    } finally {\n      // \"touch\" every property so they are all tracked as\n      // dependencies for deep watching\n      if (this.deep) {\n        traverse(value)\n      }\n      popTarget()\n      this.cleanupDeps()\n    }\n    return value\n  }\n\n  /**\n   * Add a dependency to this directive.\n   */\n  addDep (dep: Dep) {\n    const id = dep.id\n    if (!this.newDepIds.has(id)) {\n      this.newDepIds.add(id)\n      this.newDeps.push(dep)\n      if (!this.depIds.has(id)) {\n        dep.addSub(this)\n      }\n    }\n  }\n\n  /**\n   * Clean up for dependency collection.\n   */\n  cleanupDeps () {\n    let i = this.deps.length\n    while (i--) {\n      const dep = this.deps[i]\n      if (!this.newDepIds.has(dep.id)) {\n        dep.removeSub(this)\n      }\n    }\n    let tmp = this.depIds\n    this.depIds = this.newDepIds\n    this.newDepIds = tmp\n    this.newDepIds.clear()\n    tmp = this.deps\n    this.deps = this.newDeps\n    this.newDeps = tmp\n    this.newDeps.length = 0\n  }\n  // ...\n}\n```\n\n`Watcher` 是一个 Class，在它的构造函数中，定义了一些和 `Dep` 相关的属性：\n\n```js\nthis.deps = []\nthis.newDeps = []\nthis.depIds = new Set()\nthis.newDepIds = new Set()\n```\n\n其中，`this.deps` 和 `this.newDeps` 表示 `Watcher` 实例持有的 `Dep` 实例的数组；而 `this.depIds` 和 `this.newDepIds` 分别代表 `this.deps` 和 `this.newDeps` 的 `id` Set（这个 Set 是 ES6 的数据结构，它的实现在 `src/core/util/env.js` 中）。那么这里为何需要有 2 个 `Dep` 实例数组呢，稍后我们会解释。\n\n`Watcher` 还定义了一些原型的方法，和依赖收集相关的有 `get`、`addDep` 和 `cleanupDeps` 方法，单个介绍它们的实现不方便理解，我会结合整个依赖收集的过程把这几个方法讲清楚。\n\n## 过程分析\n\n之前我们介绍当对数据对象的访问会触发他们的 getter 方法，那么这些对象什么时候被访问呢？还记得之前我们介绍过 Vue 的 mount 过程是通过 `mountComponent` 函数，其中有一段比较重要的逻辑，大致如下：\n\n```js\nupdateComponent = () => {\n  vm._update(vm._render(), hydrating)\n}\nnew Watcher(vm, updateComponent, noop, {\n  before () {\n    if (vm._isMounted) {\n      callHook(vm, 'beforeUpdate')\n    }\n  }\n}, true /* isRenderWatcher */)\n```\n\n当我们去实例化一个渲染 `watcher` 的时候，首先进入 `watcher` 的构造函数逻辑，然后会执行它的 `this.get()` 方法，进入 `get` 函数，首先会执行：\n\n```js\npushTarget(this)\n```\n\n`pushTarget` 的定义在 `src/core/observer/dep.js` 中：\n \n```js\nexport function pushTarget (_target: Watcher) {\n  if (Dep.target) targetStack.push(Dep.target)\n  Dep.target = _target\n}\n```\n实际上就是把 `Dep.target` 赋值为当前的渲染 `watcher` 并压栈（为了恢复用）。接着又执行了：\n\n```js\nvalue = this.getter.call(vm, vm)\n```\n\n`this.getter` 对应就是 `updateComponent` 函数，这实际上就是在执行：\n```js\nvm._update(vm._render(), hydrating)\n```\n\n它会先执行 `vm._render()` 方法，因为之前分析过这个方法会生成 渲染 VNode，并且在这个过程中会对 `vm` 上的数据访问，这个时候就触发了数据对象的 getter。\n\n那么每个对象值的 getter 都持有一个 `dep`，在触发 getter 的时候会调用 `dep.depend()` 方法，也就会执行 `Dep.target.addDep(this)`。\n\n刚才我们提到这个时候 `Dep.target` 已经被赋值为渲染 `watcher`，那么就执行到 `addDep` 方法：\n\n```js\naddDep (dep: Dep) {\n  const id = dep.id\n  if (!this.newDepIds.has(id)) {\n    this.newDepIds.add(id)\n    this.newDeps.push(dep)\n    if (!this.depIds.has(id)) {\n      dep.addSub(this)\n    }\n  }\n}\n```\n\n这时候会做一些逻辑判断（保证同一数据不会被添加多次）后执行 `dep.addSub(this)`，那么就会执行 `this.subs.push(sub)`，也就是说把当前的 `watcher` 订阅到这个数据持有的 `dep` 的 `subs` 中，这个目的是为后续数据变化时候能通知到哪些 `subs` 做准备。\n\n所以在 `vm._render()` 过程中，会触发所有数据的 getter，这样实际上已经完成了一个依赖收集的过程。那么到这里就结束了么，其实并没有，在完成依赖收集后，还有几个逻辑要执行，首先是：\n\n```js\nif (this.deep) {\n  traverse(value)\n}\n```\n这个是要递归去访问 `value`，触发它所有子项的 `getter`，这个之后会详细讲。接下来执行：\n\n```js\npopTarget()\n```\n`popTarget` 的定义在 `src/core/observer/dep.js` 中：\n\n```js\nDep.target = targetStack.pop()\n```\n\n实际上就是把 `Dep.target` 恢复成上一个状态，因为当前 vm 的数据依赖收集已经完成，那么对应的渲染`Dep.target` 也需要改变。最后执行：\n\n```js\nthis.cleanupDeps()\n```\n\n其实很多人都分析过并了解到 Vue 有依赖收集的过程，但我几乎没有看到有人分析依赖清空的过程，其实这是大部分同学会忽视的一点，也是 Vue 考虑特别细的一点。\n\n```js\ncleanupDeps () {\n  let i = this.deps.length\n  while (i--) {\n    const dep = this.deps[i]\n    if (!this.newDepIds.has(dep.id)) {\n      dep.removeSub(this)\n    }\n  }\n  let tmp = this.depIds\n  this.depIds = this.newDepIds\n  this.newDepIds = tmp\n  this.newDepIds.clear()\n  tmp = this.deps\n  this.deps = this.newDeps\n  this.newDeps = tmp\n  this.newDeps.length = 0\n}\n```\n\n考虑到 Vue 是数据驱动的，所以每次数据变化都会重新 render，那么 `vm._render()` 方法又会再次执行，并再次触发数据的 getters，所以 `Watcher` 在构造函数中会初始化 2 个 `Dep` 实例数组，`newDeps` 表示新添加的 `Dep` 实例数组，而 `deps` 表示上一次添加的 `Dep` 实例数组。\n\n在执行 `cleanupDeps` 函数的时候，会首先遍历 `deps`，移除对 `dep.subs` 数组中 `Wathcer` 的订阅，然后把 `newDepIds` 和 `depIds` 交换，`newDeps` 和 `deps` 交换，并把 `newDepIds` 和 `newDeps` 清空。\n\n那么为什么需要做 `deps` 订阅的移除呢，在添加 `deps` 的订阅过程，已经能通过 `id` 去重避免重复订阅了。\n\n考虑到一种场景，我们的模板会根据 `v-if` 去渲染不同子模板 a 和 b，当我们满足某种条件的时候渲染 a 的时候，会访问到 a 中的数据，这时候我们对 a 使用的数据添加了 getter，做了依赖收集，那么当我们去修改 a 的数据的时候，理应通知到这些订阅者。那么如果我们一旦改变了条件渲染了 b 模板，又会对 b 使用的数据添加了 getter，如果我们没有依赖移除的过程，那么这时候我去修改 a 模板的数据，会通知 a 数据的订阅的回调，这显然是有浪费的。\n\n因此 Vue 设计了在每次添加完新的订阅，会移除掉旧的订阅，这样就保证了在我们刚才的场景中，如果渲染 b 模板的时候去修改 a 模板的数据，a 数据订阅回调已经被移除了，所以不会有任何浪费，真的是非常赞叹 Vue 对一些细节上的处理。\n\n## 总结\n\n通过这一节的分析，我们对 Vue 数据的依赖收集过程已经有了认识，并且对这其中的一些细节做了分析。收集依赖的目的是为了当这些响应式数据发生变化，触发它们的 setter 的时候，能知道应该通知哪些订阅者去做相应的逻辑处理，我们把这个过程叫派发更新，其实 `Watcher` 和 `Dep` 就是一个非常经典的观察者设计模式的实现，下一节我们来详细分析一下派发更新的过程。\n"
  },
  {
    "path": "docs/v2/reactive/index.md",
    "content": "# 深入响应式原理\n\n前面 2 章介绍的都是 Vue 怎么实现数据渲染和组件化的，主要讲的是初始化的过程，把原始的数据最终映射到 DOM 中，但并没有涉及到数据变化到 DOM 变化的部分。而 Vue 的数据驱动除了数据渲染 DOM 之外，还有一个很重要的体现就是数据的变更会触发 DOM 的变化。\n\n其实前端开发最重要的 2 个工作，一个是把数据渲染到页面，另一个是处理用户交互。Vue 把数据渲染到页面的能力我们已经通过源码分析出其中的原理了，但是由于一些用户交互或者是其它方面导致数据发生变化重新对页面渲染的原理我们还未分析。\n\n考虑如下示例：\n\n```html\n<div id=\"app\" @click=\"changeMsg\">\n  {{ message }}\n</div>\n```\n\n```js\nvar app = new Vue({\n  el: '#app',\n  data: {\n    message: 'Hello Vue!'\n  },\n  methods: {\n    changeMsg() {\n      this.message = 'Hello World!'\n    }\n  }\n})\n```\n当我们去修改 `this.message` 的时候，模板对应的插值也会渲染成新的数据，那么这一切是怎么做到的呢？\n\n在分析前，我们先直观的想一下，如果不用 Vue 的话，我们会通过最简单的方法实现这个需求：监听点击事件，修改数据，手动操作 DOM 重新渲染。这个过程和使用 Vue 的最大区别就是多了一步“手动操作 DOM 重新渲染”。这一步看上去并不多，但它背后又潜在的几个要处理的问题：\n\n1. 我需要修改哪块的 DOM？\n\n2. 我的修改效率和性能是不是最优的？\n\n3. 我需要对数据每一次的修改都去操作 DOM 吗？\n\n4. 我需要 case by case 去写修改 DOM 的逻辑吗？\n\n如果我们使用了 Vue，那么上面几个问题 Vue 内部就帮你做了，那么 Vue 是如何在我们对数据修改后自动做这些事情呢，接下来我们将进入一些 Vue 响应式系统的底层的细节。"
  },
  {
    "path": "docs/v2/reactive/next-tick.md",
    "content": "# nextTick\n\n`nextTick` 是 Vue 的一个核心实现，在介绍 Vue 的 nextTick 之前，为了方便大家理解，我先简单介绍一下 JS 的运行机制。\n\n## JS 运行机制\n\nJS 执行是单线程的，它是基于事件循环的。事件循环大致分为以下几个步骤：\n\n（1）所有同步任务都在主线程上执行，形成一个执行栈（execution context stack）。\n\n（2）主线程之外，还存在一个\"任务队列\"（task queue）。只要异步任务有了运行结果，就在\"任务队列\"之中放置一个事件。\n\n（3）一旦\"执行栈\"中的所有同步任务执行完毕，系统就会读取\"任务队列\"，看看里面有哪些事件。那些对应的异步任务，于是结束等待状态，进入执行栈，开始执行。\n\n（4）主线程不断重复上面的第三步。\n\n<img :src=\"$withBase('/assets/event-loop.png')\"/>\n\n主线程的执行过程就是一个 tick，而所有的异步结果都是通过 “任务队列” 来调度。 消息队列中存放的是一个个的任务（task）。 规范中规定 task 分为两大类，分别是 macro task 和 micro task，并且每个 macro task 结束后，都要清空所有的 micro task。\n\n关于 macro task 和 micro task 的概念，这里不会细讲，简单通过一段代码演示他们的执行顺序：\n\n```js\nfor (macroTask of macroTaskQueue) {\n    // 1. Handle current MACRO-TASK\n    handleMacroTask();\n      \n    // 2. Handle all MICRO-TASK\n    for (microTask of microTaskQueue) {\n        handleMicroTask(microTask);\n    }\n}\n```\n在浏览器环境中，常见的 macro task 有 setTimeout、MessageChannel、postMessage、setImmediate；常见的 micro task 有 MutationObsever 和 Promise.then。\n\n## Vue 的实现\n\n在 Vue 源码 2.5+ 后，`nextTick` 的实现单独有一个 JS 文件来维护它，它的源码并不多，总共也就 100 多行。接下来我们来看一下它的实现，在 `src/core/util/next-tick.js` 中：\n\n```js\nimport { noop } from 'shared/util'\nimport { handleError } from './error'\nimport { isIOS, isNative } from './env'\n\nconst callbacks = []\nlet pending = false\n\nfunction flushCallbacks () {\n  pending = false\n  const copies = callbacks.slice(0)\n  callbacks.length = 0\n  for (let i = 0; i < copies.length; i++) {\n    copies[i]()\n  }\n}\n\n// Here we have async deferring wrappers using both microtasks and (macro) tasks.\n// In < 2.4 we used microtasks everywhere, but there are some scenarios where\n// microtasks have too high a priority and fire in between supposedly\n// sequential events (e.g. #4521, #6690) or even between bubbling of the same\n// event (#6566). However, using (macro) tasks everywhere also has subtle problems\n// when state is changed right before repaint (e.g. #6813, out-in transitions).\n// Here we use microtask by default, but expose a way to force (macro) task when\n// needed (e.g. in event handlers attached by v-on).\nlet microTimerFunc\nlet macroTimerFunc\nlet useMacroTask = false\n\n// Determine (macro) task defer implementation.\n// Technically setImmediate should be the ideal choice, but it's only available\n// in IE. The only polyfill that consistently queues the callback after all DOM\n// events triggered in the same loop is by using MessageChannel.\n/* istanbul ignore if */\nif (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {\n  macroTimerFunc = () => {\n    setImmediate(flushCallbacks)\n  }\n} else if (typeof MessageChannel !== 'undefined' && (\n  isNative(MessageChannel) ||\n  // PhantomJS\n  MessageChannel.toString() === '[object MessageChannelConstructor]'\n)) {\n  const channel = new MessageChannel()\n  const port = channel.port2\n  channel.port1.onmessage = flushCallbacks\n  macroTimerFunc = () => {\n    port.postMessage(1)\n  }\n} else {\n  /* istanbul ignore next */\n  macroTimerFunc = () => {\n    setTimeout(flushCallbacks, 0)\n  }\n}\n\n// Determine microtask defer implementation.\n/* istanbul ignore next, $flow-disable-line */\nif (typeof Promise !== 'undefined' && isNative(Promise)) {\n  const p = Promise.resolve()\n  microTimerFunc = () => {\n    p.then(flushCallbacks)\n    // in problematic UIWebViews, Promise.then doesn't completely break, but\n    // it can get stuck in a weird state where callbacks are pushed into the\n    // microtask queue but the queue isn't being flushed, until the browser\n    // needs to do some other work, e.g. handle a timer. Therefore we can\n    // \"force\" the microtask queue to be flushed by adding an empty timer.\n    if (isIOS) setTimeout(noop)\n  }\n} else {\n  // fallback to macro\n  microTimerFunc = macroTimerFunc\n}\n\n/**\n * Wrap a function so that if any code inside triggers state change,\n * the changes are queued using a (macro) task instead of a microtask.\n */\nexport function withMacroTask (fn: Function): Function {\n  return fn._withTask || (fn._withTask = function () {\n    useMacroTask = true\n    const res = fn.apply(null, arguments)\n    useMacroTask = false\n    return res\n  })\n}\n\nexport function nextTick (cb?: Function, ctx?: Object) {\n  let _resolve\n  callbacks.push(() => {\n    if (cb) {\n      try {\n        cb.call(ctx)\n      } catch (e) {\n        handleError(e, ctx, 'nextTick')\n      }\n    } else if (_resolve) {\n      _resolve(ctx)\n    }\n  })\n  if (!pending) {\n    pending = true\n    if (useMacroTask) {\n      macroTimerFunc()\n    } else {\n      microTimerFunc()\n    }\n  }\n  // $flow-disable-line\n  if (!cb && typeof Promise !== 'undefined') {\n    return new Promise(resolve => {\n      _resolve = resolve\n    })\n  }\n}\n```\n`next-tick.js` 申明了 `microTimerFunc` 和 `macroTimerFunc` 2 个变量，它们分别对应的是 micro task 的函数和 macro task 的函数。对于 macro task 的实现，优先检测是否支持原生 `setImmediate`，这是一个高版本 IE 和 Edge 才支持的特性，不支持的话再去检测是否支持原生的 `MessageChannel`，如果也不支持的话就会降级为 `setTimeout 0`；而对于 micro task 的实现，则检测浏览器是否原生支持 Promise，不支持的话直接指向 macro task 的实现。\n\n`next-tick.js` 对外暴露了 2 个函数，先来看 `nextTick`，这就是我们在上一节执行 `nextTick(flushSchedulerQueue)` 所用到的函数。它的逻辑也很简单，把传入的回调函数 `cb` 压入 `callbacks` 数组，最后一次性地根据 `useMacroTask` 条件执行 `macroTimerFunc` 或者是 `microTimerFunc`，而它们都会在下一个 tick 执行 `flushCallbacks`，`flushCallbacks` 的逻辑非常简单，对 `callbacks` 遍历，然后执行相应的回调函数。\n\n这里使用 `callbacks` 而不是直接在 `nextTick` 中执行回调函数的原因是保证在同一个 tick 内多次执行 `nextTick`，不会开启多个异步任务，而把这些异步任务都压成一个同步任务，在下一个 tick 执行完毕。\n\n`nextTick` 函数最后还有一段逻辑：\n```js\n if (!cb && typeof Promise !== 'undefined') {\n  return new Promise(resolve => {\n    _resolve = resolve\n  })\n}\n```\n这是当 `nextTick` 不传 `cb` 参数的时候，提供一个 Promise 化的调用，比如：\n```js\nnextTick().then(() => {})\n```\n当 `_resolve` 函数执行，就会跳到 `then` 的逻辑中。\n\n`next-tick.js` 还对外暴露了 `withMacroTask` 函数，它是对函数做一层包装，确保函数执行过程中对数据任意的修改，触发变化执行 `nextTick` 的时候强制走 `macroTimerFunc`。比如对于一些 DOM 交互事件，如 `v-on` 绑定的事件回调函数的处理，会强制走 macro task。\n\n## 总结\n\n通过这一节对 `nextTick` 的分析，并结合上一节的 setter 分析，我们了解到数据的变化到 DOM 的重新渲染是一个异步过程，发生在下一个 tick。这就是我们平时在开发的过程中，比如从服务端接口去获取数据的时候，数据做了修改，如果我们的某些方法去依赖了数据修改后的 DOM 变化，我们就必须在 `nextTick` 后执行。比如下面的伪代码：\n\n```js\ngetData(res).then(()=>{\n  this.xxx = res.data\n  this.$nextTick(() => {\n    // 这里我们可以获取变化后的 DOM\n  })\n})\n```\n\nVue.js 提供了 2 种调用 `nextTick` 的方式，一种是全局 API `Vue.nextTick`，一种是实例上的方法 `vm.$nextTick`，无论我们使用哪一种，最后都是调用 `next-tick.js` 中实现的 `nextTick` 方法。\n\n\n"
  },
  {
    "path": "docs/v2/reactive/props.md",
    "content": "# Props (v2.6.11)\n\n`Props` 作为组件的核心特性之一，也是我们平时开发 Vue 项目中接触最多的特性之一，它可以让组件的功能变得丰富，也是父子组件通讯的一个渠道。那么它的实现原理是怎样的，我们来一探究竟。\n\n## 规范化\n\n在初始化 `props` 之前，首先会对 `props` 做一次 `normalize`，它发生在 `mergeOptions` 的时候，在 `src/core/util/options.js` 中：\n\n```js\nexport function mergeOptions (\n  parent: Object,\n  child: Object,\n  vm?: Component\n): Object {\n  // ...\n  normalizeProps(child, vm)\n  // ...\n}\n\nfunction normalizeProps (options: Object, vm: ?Component) {\n  const props = options.props\n  if (!props) return\n  const res = {}\n  let i, val, name\n  if (Array.isArray(props)) {\n    i = props.length\n    while (i--) {\n      val = props[i]\n      if (typeof val === 'string') {\n        name = camelize(val)\n        res[name] = { type: null }\n      } else if (process.env.NODE_ENV !== 'production') {\n        warn('props must be strings when using array syntax.')\n      }\n    }\n  } else if (isPlainObject(props)) {\n    for (const key in props) {\n      val = props[key]\n      name = camelize(key)\n      res[name] = isPlainObject(val)\n        ? val\n        : { type: val }\n    }\n  } else if (process.env.NODE_ENV !== 'production') {\n    warn(\n      `Invalid value for option \"props\": expected an Array or an Object, ` +\n      `but got ${toRawType(props)}.`,\n      vm\n    )\n  }\n  options.props = res\n}\n```\n\n合并配置我们在组件化章节讲过，它主要就是处理我们定义组件的对象 `option`，然后挂载到组件的实例 `this.$options` 中。\n\n我们接下来重点看 `normalizeProps` 的实现，其实这个函数的主要目的就是把我们编写的 `props` 转成对象格式，因为实际上 `props` 除了对象格式，还允许写成数组格式。\n\n当 `props` 是一个数组，每一个数组元素 `prop` 只能是一个 `string`，表示 `prop` 的 `key`，转成驼峰格式，`prop` 的类型为空。\n\n当 `props` 是一个对象，对于 `props` 中每个 `prop` 的 `key`，我们会转驼峰格式，而它的 `value`，如果不是一个对象，我们就把它规范成一个对象。\n\n如果 `props` 既不是数组也不是对象，就抛出一个警告。\n\n举个例子：\n\n```js\nexport default {\n  props: ['name', 'nick-name']\n}\n```\n\n经过 `normalizeProps` 后，会被规范成：\n\n```js\noptions.props = {\n  name: { type: null },\n  nickName: { type: null }\n}\n```\n\n```js\nexport default {\n  props: {\n    name: String,\n    nickName: {\n      type: Boolean\n    }\n  }\n}\n```\n\n经过 `normalizeProps` 后，会被规范成：\n\n```js\noptions.props = {\n  name: { type: String },\n  nickName: { type: Boolean }\n}\n```\n\n由于对象形式的 `props` 可以指定每个 `prop` 的类型和定义其它的一些属性，推荐用对象形式定义 `props`。\n\n## 初始化\n\n`Props` 的初始化主要发生在 `new Vue` 中的 `initState` 阶段，在 `src/core/instance/state.js` 中：\n\n```js\nexport function initState (vm: Component) {\n  // ....\n  const opts = vm.$options\n  if (opts.props) initProps(vm, opts.props)\n  // ...\n}\n\n\nfunction initProps (vm: Component, propsOptions: Object) {\n  const propsData = vm.$options.propsData || {}\n  const props = vm._props = {}\n  // cache prop keys so that future props updates can iterate using Array\n  // instead of dynamic object key enumeration.\n  const keys = vm.$options._propKeys = []\n  const isRoot = !vm.$parent\n  // root instance props should be converted\n  if (!isRoot) {\n    toggleObserving(false)\n  }\n  for (const key in propsOptions) {\n    keys.push(key)\n    const value = validateProp(key, propsOptions, propsData, vm)\n    /* istanbul ignore else */\n    if (process.env.NODE_ENV !== 'production') {\n      const hyphenatedKey = hyphenate(key)\n      if (isReservedAttribute(hyphenatedKey) ||\n          config.isReservedAttr(hyphenatedKey)) {\n        warn(\n          `\"${hyphenatedKey}\" is a reserved attribute and cannot be used as component prop.`,\n          vm\n        )\n      }\n      defineReactive(props, key, value, () => {\n        if (!isRoot && !isUpdatingChildComponent) {\n          warn(\n            `Avoid mutating a prop directly since the value will be ` +\n            `overwritten whenever the parent component re-renders. ` +\n            `Instead, use a data or computed property based on the prop's ` +\n            `value. Prop being mutated: \"${key}\"`,\n            vm\n          )\n        }\n      })\n    } else {\n      defineReactive(props, key, value)\n    }\n    // static props are already proxied on the component's prototype\n    // during Vue.extend(). We only need to proxy props defined at\n    // instantiation here.\n    if (!(key in vm)) {\n      proxy(vm, `_props`, key)\n    }\n  }\n  toggleObserving(true)\n}\n``` \n\n`initProps` 主要做 3 件事情：校验、响应式和代理。\n\n### 校验\n\n校验的逻辑很简单，遍历 `propsOptions`，执行 `validateProp(key, propsOptions, propsData, vm)` 方法。这里的 `propsOptions` 就是我们定义的 `props` 在规范后生成的 `options.props` 对象，`propsData` 是从父组件传递的 `prop` 数据。所谓校验的目的就是检查一下我们传递的数据是否满足 `prop `的定义规范。再来看一下 `validateProp` 方法，它定义在 `src/core/util/props.js` 中：\n\n```js\nexport function validateProp (\n  key: string,\n  propOptions: Object,\n  propsData: Object,\n  vm?: Component\n): any {\n  const prop = propOptions[key]\n  const absent = !hasOwn(propsData, key)\n  let value = propsData[key]\n  // boolean casting\n  const booleanIndex = getTypeIndex(Boolean, prop.type)\n  if (booleanIndex > -1) {\n    if (absent && !hasOwn(prop, 'default')) {\n      value = false\n    } else if (value === '' || value === hyphenate(key)) {\n      // only cast empty string / same name to boolean if\n      // boolean has higher priority\n      const stringIndex = getTypeIndex(String, prop.type)\n      if (stringIndex < 0 || booleanIndex < stringIndex) {\n        value = true\n      }\n    }\n  }\n  // check default value\n  if (value === undefined) {\n    value = getPropDefaultValue(vm, prop, key)\n    // since the default value is a fresh copy,\n    // make sure to observe it.\n    const prevShouldObserve = shouldObserve\n    toggleObserving(true)\n    observe(value)\n    toggleObserving(prevShouldObserve)\n  }\n  if (\n    process.env.NODE_ENV !== 'production' &&\n    // skip validation for weex recycle-list child component props\n    !(__WEEX__ && isObject(value) && ('@binding' in value))\n  ) {\n    assertProp(prop, key, value, vm, absent)\n  }\n  return value\n}\n```\n\n`validateProp` 主要就做 3 件事情：处理 `Boolean` 类型的数据，处理默认数据，`prop` 断言，并最终返回 `prop` 的值。\n\n先来看 `Boolean` 类型数据的处理逻辑：\n\n```js\nconst prop = propOptions[key]\nconst absent = !hasOwn(propsData, key)\nlet value = propsData[key]\n// boolean casting\nconst booleanIndex = getTypeIndex(Boolean, prop.type)\nif (booleanIndex > -1) {\n  if (absent && !hasOwn(prop, 'default')) {\n    value = false\n  } else if (value === '' || value === hyphenate(key)) {\n    // only cast empty string / same name to boolean if\n    // boolean has higher priority\n    const stringIndex = getTypeIndex(String, prop.type)\n    if (stringIndex < 0 || booleanIndex < stringIndex) {\n      value = true\n    }\n  }\n}\n```\n\n先通过 `const booleanIndex = getTypeIndex(Boolean, prop.type)` 来判断 `prop` 的定义是否是 `Boolean` 类型的。\n\n```js\nfunction getType (fn) {\n  const match = fn && fn.toString().match(/^\\s*function (\\w+)/)\n  return match ? match[1] : ''\n}\n\nfunction isSameType (a, b) {\n  return getType(a) === getType(b)\n}\n\nfunction getTypeIndex (type, expectedTypes): number {\n  if (!Array.isArray(expectedTypes)) {\n    return isSameType(expectedTypes, type) ? 0 : -1\n  }\n  for (let i = 0, len = expectedTypes.length; i < len; i++) {\n    if (isSameType(expectedTypes[i], type)) {\n      return i\n    }\n  }\n  return -1\n}\n```\n\n`getTypeIndex` 函数就是找到 `type` 和 `expectedTypes` 匹配的索引并返回。\n\n`prop` 类型定义的时候可以是某个原生构造函数，也可以是原生构造函数的数组，比如：\n\n```js\nexport default {\n  props: {\n    name: String,\n    value: [String, Boolean]\n  }\n}\n```\n\n如果 `expectedTypes` 是单个构造函数，就执行 `isSameType` 去判断是否是同一个类型；如果是数组，那么就遍历这个数组，找到第一个同类型的，返回它的索引。\n\n回到 `validateProp` 函数，通过 `const booleanIndex = getTypeIndex(Boolean, prop.type)` 得到 `booleanIndex`，如果 `prop.type` 是一个 `Boolean` 类型，则通过 `absent && !hasOwn(prop, 'default')` 来判断如果父组件没有传递这个 `prop` 数据并且没有设置 `default` 的情况，则 `value` 为 false。\n\n接着判断`value === '' || value === hyphenate(key)` 的情况，如果满足则先通过 `const stringIndex = getTypeIndex(String, prop.type)` 获取匹配 `String` 类型的索引，然后判断 `stringIndex < 0 || booleanIndex < stringIndex` 的值来决定 `value` 的值是否为 `true`。这块逻辑稍微有点绕，我们举 2 个例子来说明：\n\n例如你定义一个组件 `Student`:\n\n```js\nexport default {\n  name: String,\n  nickName: [Boolean, String]\n}\n```\n\n然后在父组件中引入这个组件：\n\n```vue\n<template>\n  <div>\n    <student name=\"Kate\" nick-name></student>\n  </div>\n</template>\n```\n\n或者是：\n\n```vue\n<template>\n  <div>\n    <student name=\"Kate\" nick-name=\"nick-name\"></student>\n  </div>\n</template>\n```\n\n第一种情况没有写属性的值，满足 `value === ''`，第二种满足 `value === hyphenate(key)` 的情况，另外 `nickName` 这个 `prop` 的类型是 `Boolean` 或者是 `String`，并且满足 `booleanIndex < stringIndex`，所以对 `nickName` 这个 `prop` 的 `value` 为 `true`。\n\n接下来看一下默认数据处理逻辑：\n\n```js\n// check default value\nif (value === undefined) {\n  value = getPropDefaultValue(vm, prop, key)\n  // since the default value is a fresh copy,\n  // make sure to observe it.\n  const prevShouldObserve = shouldObserve\n  toggleObserving(true)\n  observe(value)\n  toggleObserving(prevShouldObserve)\n}\n```\n\n当 `value` 的值为 `undefined` 的时候，说明父组件根本就没有传这个 `prop`，那么我们就需要通过 `getPropDefaultValue(vm, prop, key)` 获取这个 `prop` 的默认值。我们这里只关注 `getPropDefaultValue` 的实现，`toggleObserving` 和 `observe` 的作用我们之后会说。\n\n```js\nfunction getPropDefaultValue (vm: ?Component, prop: PropOptions, key: string): any {\n  // no default, return undefined\n  if (!hasOwn(prop, 'default')) {\n    return undefined\n  }\n  const def = prop.default\n  // warn against non-factory defaults for Object & Array\n  if (process.env.NODE_ENV !== 'production' && isObject(def)) {\n    warn(\n      'Invalid default value for prop \"' + key + '\": ' +\n      'Props with type Object/Array must use a factory function ' +\n      'to return the default value.',\n      vm\n    )\n  }\n  // the raw prop value was also undefined from previous render,\n  // return previous default value to avoid unnecessary watcher trigger\n  if (vm && vm.$options.propsData &&\n    vm.$options.propsData[key] === undefined &&\n    vm._props[key] !== undefined\n  ) {\n    return vm._props[key]\n  }\n  // call factory function for non-Function types\n  // a value is Function if its prototype is function even across different execution context\n  return typeof def === 'function' && getType(prop.type) !== 'Function'\n    ? def.call(vm)\n    : def\n}\n```\n\n检测如果 `prop` 没有定义 `default` 属性，那么返回 `undefined`，通过这块逻辑我们知道除了 `Boolean` 类型的数据，其余没有设置 `default` 属性的 `prop` 默认值都是 `undefined`。\n\n接着是开发环境下对 `prop` 的默认值是否为对象或者数组类型的判断，如果是的话会报警告，因为对象和数组类型的 `prop`，他们的默认值必须要返回一个工厂函数。\n\n接下来的判断是如果上一次组件渲染父组件传递的 `prop` 的值是 `undefined`，则直接返回 上一次的默认值 `vm._props[key]`，这样可以避免触发不必要的 `watcher` 的更新。\n\n最后就是判断 `def` 如果是工厂函数且 `prop` 的类型不是 `Function` 的时候，返回工厂函数的返回值，否则直接返回 `def`。\n\n至此，我们讲完了 `validateProp` 函数的 `Boolean` 类型数据的处理逻辑和默认数据处理逻辑，最后来看一下 `prop` 断言逻辑。\n\n```js\nif (\nprocess.env.NODE_ENV !== 'production' &&\n// skip validation for weex recycle-list child component props\n!(__WEEX__ && isObject(value) && ('@binding' in value))\n) {\n  assertProp(prop, key, value, vm, absent)\n}\n```\n\n在开发环境且非 `weex` 的某种环境下，执行 `assertProp` 做属性断言。\n\n```js\nfunction assertProp (\n  prop: PropOptions,\n  name: string,\n  value: any,\n  vm: ?Component,\n  absent: boolean\n) {\n  if (prop.required && absent) {\n    warn(\n      'Missing required prop: \"' + name + '\"',\n      vm\n    )\n    return\n  }\n  if (value == null && !prop.required) {\n    return\n  }\n  let type = prop.type\n  let valid = !type || type === true\n  const expectedTypes = []\n  if (type) {\n    if (!Array.isArray(type)) {\n      type = [type]\n    }\n    for (let i = 0; i < type.length && !valid; i++) {\n      const assertedType = assertType(value, type[i])\n      expectedTypes.push(assertedType.expectedType || '')\n      valid = assertedType.valid\n    }\n  }\n\n  if (!valid) {\n    warn(\n      getInvalidTypeMessage(name, value, expectedTypes),\n      vm\n    )\n    return\n  }\n  const validator = prop.validator\n  if (validator) {\n    if (!validator(value)) {\n      warn(\n        'Invalid prop: custom validator check failed for prop \"' + name + '\".',\n        vm\n      )\n    }\n  }\n}\n```\n\n`assertProp` 函数的目的是断言这个 `prop` 是否合法。\n\n首先判断如果 `prop` 定义了 `required` 属性但父组件没有传递这个 `prop` 数据的话会报一个警告。\n\n接着判断如果 `value` 为空且 `prop` 没有定义 `required` 属性则直接返回。\n\n然后再去对 `prop` 的类型做校验，先是拿到 `prop` 中定义的类型 `type`，并尝试把它转成一个类型数组，然后依次遍历这个数组，执行 `assertType(value, type[i])` 去获取断言的结果，直到遍历完成或者是 `valid` 为 `true` 的时候跳出循环。\n\n```js\nconst simpleCheckRE = /^(String|Number|Boolean|Function|Symbol)$/\nfunction assertType (value: any, type: Function): {\n  valid: boolean;\n  expectedType: string;\n} {\n  let valid\n  const expectedType = getType(type)\n  if (simpleCheckRE.test(expectedType)) {\n    const t = typeof value\n    valid = t === expectedType.toLowerCase()\n    // for primitive wrapper objects\n    if (!valid && t === 'object') {\n      valid = value instanceof type\n    }\n  } else if (expectedType === 'Object') {\n    valid = isPlainObject(value)\n  } else if (expectedType === 'Array') {\n    valid = Array.isArray(value)\n  } else {\n    valid = value instanceof type\n  }\n  return {\n    valid,\n    expectedType\n  }\n}\n```\n\n`assertType` 的逻辑很简单，先通过 `getType(type)` 获取 `prop` 期望的类型 `expectedType`，然后再去根据几种不同的情况对比 `prop` 的值 `value` 是否和 `expectedType` 匹配，最后返回匹配的结果。\n\n如果循环结束后 `valid` 仍然为 `false`，那么说明 `prop` 的值 `value` 与 `prop` 定义的类型都不匹配，那么就会输出一段通过 `getInvalidTypeMessage(name, value, expectedTypes)` 生成的警告信息，就不细说了。\n\n最后判断当 `prop` 自己定义了 `validator` 自定义校验器，则执行 `validator` 校验器方法，如果校验不通过则输出警告信息。\n\n### 响应式\n\n回到 `initProps` 方法，当我们通过 `const value = validateProp(key, propsOptions, propsData, vm)` 对 `prop` 做验证并且获取到 `prop` 的值后，接下来需要通过 `defineReactive` 把 `prop` 变成响应式。\n\n`defineReactive` 我们之前已经介绍过，这里要注意的是，在开发环境中我们会校验 `prop` 的 `key` 是否是 `HTML` 的保留属性，并且在 `defineReactive` 的时候会添加一个自定义 `setter`，当我们直接对 `prop` 赋值的时候会输出警告：\n\n```js\nif (process.env.NODE_ENV !== 'production') {\n  const hyphenatedKey = hyphenate(key)\n  if (isReservedAttribute(hyphenatedKey) ||\n      config.isReservedAttr(hyphenatedKey)) {\n    warn(\n      `\"${hyphenatedKey}\" is a reserved attribute and cannot be used as component prop.`,\n      vm\n    )\n  }\n  defineReactive(props, key, value, () => {\n    if (!isRoot && !isUpdatingChildComponent) {\n      warn(\n        `Avoid mutating a prop directly since the value will be ` +\n        `overwritten whenever the parent component re-renders. ` +\n        `Instead, use a data or computed property based on the prop's ` +\n        `value. Prop being mutated: \"${key}\"`,\n        vm\n      )\n    }\n  })\n} \n```\n\n关于 `prop` 的响应式有一点不同的是当 `vm` 是非根实例的时候，会先执行 `toggleObserving(false)`，它的目的是为了响应式的优化，我们先跳过，之后会详细说明。\n\n### 代理\n\n在经过响应式处理后，我们会把 `prop` 的值添加到 `vm._props` 中，比如 key 为 `name` 的 `prop`，它的值保存在 `vm._props.name` 中，但是我们在组件中可以通过 `this.name` 访问到这个 `prop`，这就是代理做的事情。\n\n```js\n// static props are already proxied on the component's prototype\n// during Vue.extend(). We only need to proxy props defined at\n// instantiation here.\nif (!(key in vm)) {\n  proxy(vm, `_props`, key)\n}\n```\n\n通过 `proxy` 函数实现了上述需求。\n\n```js\nconst sharedPropertyDefinition = {\n  enumerable: true,\n  configurable: true,\n  get: noop,\n  set: noop\n}\n\nexport function proxy (target: Object, sourceKey: string, key: string) {\n  sharedPropertyDefinition.get = function proxyGetter () {\n    return this[sourceKey][key]\n  }\n  sharedPropertyDefinition.set = function proxySetter (val) {\n    this[sourceKey][key] = val\n  }\n  Object.defineProperty(target, key, sharedPropertyDefinition)\n}\n```\n\n当访问 `this.name` 的时候就相当于访问 `this._props.name`。\n\n其实对于非根实例的子组件而言，`prop` 的代理发生在 `Vue.extend` 阶段，在 `src/core/global-api/extend.js` 中：\n\n```js\nVue.extend = function (extendOptions: Object): Function {\n  // ...\n  const Sub = function VueComponent (options) {\n    this._init(options)\n  }\n  // ...\n\n  // For props and computed properties, we define the proxy getters on\n  // the Vue instances at extension time, on the extended prototype. This\n  // avoids Object.defineProperty calls for each instance created.\n  if (Sub.options.props) {\n    initProps(Sub)\n  }\n  if (Sub.options.computed) {\n    initComputed(Sub)\n  }\n\n  // ...\n  return Sub\n}\n\nfunction initProps (Comp) {\n  const props = Comp.options.props\n  for (const key in props) {\n    proxy(Comp.prototype, `_props`, key)\n  }\n}\n```\n\n这么做的好处是不用为每个组件实例都做一层 `proxy`，是一种优化手段。\n\n## Props 更新\n\n我们知道，当父组件传递给子组件的 `props` 值变化，子组件对应的值也会改变，同时会触发子组件的重新渲染。那么接下来我们就从源码角度来分析这两个过程。\n\n### 子组件 props 更新\n\n首先，`prop` 数据的值变化在父组件，我们知道在父组件的 `render` 过程中会访问到这个 `prop` 数据，所以当 `prop` 数据变化一定会触发父组件的重新渲染，那么重新渲染是如何更新子组件对应的 `prop` 的值呢？\n\n在父组件重新渲染的最后，会执行 `patch` 过程，进而执行 `patchVnode` 函数，`patchVnode` 通常是一个递归过程，当它遇到组件 `vnode` 的时候，会执行组件更新过程的 `prepatch` 钩子函数，在 `src/core/vdom/patch.js` 中：\n\n```js\nfunction patchVnode (\n  oldVnode,\n  vnode,\n  insertedVnodeQueue,\n  ownerArray,\n  index,\n  removeOnly\n) {\n  // ...\n\n  let i\n  const data = vnode.data\n  if (isDef(data) && isDef(i = data.hook) && isDef(i = i.prepatch)) {\n    i(oldVnode, vnode)\n  }\n  // ...\n}\n```\n\n`prepatch` 函数定义在 `src/core/vdom/create-component.js` 中：\n\n```js\nprepatch (oldVnode: MountedComponentVNode, vnode: MountedComponentVNode) {\n  const options = vnode.componentOptions\n  const child = vnode.componentInstance = oldVnode.componentInstance\n  updateChildComponent(\n    child,\n    options.propsData, // updated props\n    options.listeners, // updated listeners\n    vnode, // new parent vnode\n    options.children // new children\n  )\n}\n```\n\n内部会调用 `updateChildComponent` 方法来更新 `props`，注意第二个参数就是父组件的 `propData`，那么为什么 `vnode.componentOptions.propsData` 就是父组件传递给子组件的 `prop` 数据呢（这个也同样解释了第一次渲染的 `propsData` 来源）？原来在组件的 `render` 过程中，对于组件节点会通过 `createComponent` 方法来创建组件 `vnode`：\n\n```js\nexport function createComponent (\n  Ctor: Class<Component> | Function | Object | void,\n  data: ?VNodeData,\n  context: Component,\n  children: ?Array<VNode>,\n  tag?: string\n): VNode | Array<VNode> | void {\n  // ...\n\n  // extract props\n  const propsData = extractPropsFromVNodeData(data, Ctor, tag)\n\n  // ...\n  \n  const vnode = new VNode(\n    `vue-component-${Ctor.cid}${name ? `-${name}` : ''}`,\n    data, undefined, undefined, undefined, context,\n    { Ctor, propsData, listeners, tag, children },\n    asyncFactory\n  )\n\n  // ...\n  \n  return vnode\n}\n```\n\n在创建组件 `vnode` 的过程中，首先从 `data` 中提取出 `propData`，然后在 `new VNode` 的时候，作为第七个参数 `VNodeComponentOptions` 中的一个属性传入，所以我们可以通过 `vnode.componentOptions.propsData` 拿到 `prop` 数据。\n\n接着看 `updateChildComponent` 函数，它的定义在 `src/core/instance/lifecycle.js` 中：\n\n```js\nexport function updateChildComponent (\n  vm: Component,\n  propsData: ?Object,\n  listeners: ?Object,\n  parentVnode: MountedComponentVNode,\n  renderChildren: ?Array<VNode>\n) {\n  // ...\n\n  // update props\n  if (propsData && vm.$options.props) {\n    toggleObserving(false)\n    const props = vm._props\n    const propKeys = vm.$options._propKeys || []\n    for (let i = 0; i < propKeys.length; i++) {\n      const key = propKeys[i]\n      const propOptions: any = vm.$options.props // wtf flow?\n      props[key] = validateProp(key, propOptions, propsData, vm)\n    }\n    toggleObserving(true)\n    // keep a copy of raw propsData\n    vm.$options.propsData = propsData\n  }\n\n  // ...\n}\n```\n\n我们重点来看更新 `props` 的相关逻辑，这里的 `propsData` 是父组件传递的 `props` 数据，`vm` 是子组件的实例。`vm._props` 指向的就是子组件的 `props` 值，`propKeys` 就是在之前 `initProps` 过程中，缓存的子组件中定义的所有 `prop` 的 `key`。主要逻辑就是遍历 `propKeys`，然后执行 `props[key] = validateProp(key, propOptions, propsData, vm)` 重新验证和计算新的 `prop` 数据，更新 `vm._props`，也就是子组件的 `props`，这个就是子组件  `props` 的更新过程。\n\n### 子组件重新渲染\n\n其实子组件的重新渲染有 2 种情况，一个是 `prop` 值被修改，另一个是对象类型的 `prop` 内部属性的变化。\n\n先来看一下 `prop` 值被修改的情况，当执行 `props[key] = validateProp(key, propOptions, propsData, vm)` 更新子组件 `prop` 的时候，会触发 `prop` 的 `setter` 过程，只要在渲染子组件的时候访问过这个 `prop` 值，那么根据响应式原理，就会触发子组件的重新渲染。\n\n再来看一下当对象类型的 `prop` 的内部属性发生变化的时候，这个时候其实并没有触发子组件 `prop` 的更新。但是在子组件的渲染过程中，访问过这个对象 `prop`，所以这个对象 `prop` 在触发 `getter` 的时候会把子组件的 `render watcher` 收集到依赖中，然后当我们在父组件更新这个对象 `prop` 的某个属性的时候，会触发 `setter` 过程，也就会通知子组件 `render watcher` 的 `update`，进而触发子组件的重新渲染。\n\n以上就是当父组件 `props` 更新，触发子组件重新渲染的 2 种情况。\n \n## toggleObserving\n\n最后我们在来聊一下 `toggleObserving`，它的定义在 `src/core/observer/index.js` 中：\n\n```js\nexport let shouldObserve: boolean = true\n\nexport function toggleObserving (value: boolean) {\n  shouldObserve = value\n}\n```\n\n它在当前模块中定义了 `shouldObserve` 变量，用来控制在 `observe` 的过程中是否需要把当前值变成一个 `Observer` 对象。\n\n那么为什么在 `props` 的初始化和更新过程中，多次执行 `toggleObserving(false)` 呢，接下来我们就来分析这几种情况。\n\n在 `initProps` 的过程中：\n\n```js\nconst isRoot = !vm.$parent\n// root instance props should be converted\nif (!isRoot) {\n  toggleObserving(false)\n}\nfor (const key in propsOptions) {\n  // ...\n  const value = validateProp(key, propsOptions, propsData, vm)\n  defineReactive(props, key, value)\n  // ...\n}\ntoggleObserving(true)\n```\n\n对于非根实例的情况，我们会执行 `toggleObserving(false)`，然后对于每一个 `prop` 值，去执行 `defineReactive(props, key, value)` 去把它变成响应式。\n\n回顾一下 `defineReactive` 的定义：\n\n```js\nexport function defineReactive (\n  obj: Object,\n  key: string,\n  val: any,\n  customSetter?: ?Function,\n  shallow?: boolean\n) {\n  // ...\n  \n  let childOb = !shallow && observe(val)\n  Object.defineProperty(obj, key, {\n    enumerable: true,\n    configurable: true,\n    get: function reactiveGetter () {\n      // ...\n    },\n    set: function reactiveSetter (newVal) {\n      // ...\n    }\n  })\n}\n```\n\n通常对于值 `val` 会执行 `observe` 函数，然后遇到 `val` 是对象或者数组的情况会递归执行 `defineReactive` 把它们的子属性都变成响应式的，但是由于 `shouldObserve` 的值变成了 `false`，这个递归过程被省略了。为什么会这样呢？\n\n因为正如我们前面分析的，对于对象的 `prop` 值，子组件的 `prop` 值始终指向父组件的 `prop` 值，只要父组件的 `prop` 值变化，就会触发子组件的重新渲染，所以这个 `observe` 过程是可以省略的。\n \n最后再执行 `toggleObserving(true)` 恢复 `shouldObserve` 为 `true`。\n\n在 `validateProp` 的过程中：\n\n```js\n// check default value\nif (value === undefined) {\n  value = getPropDefaultValue(vm, prop, key)\n  // since the default value is a fresh copy,\n  // make sure to observe it.\n  const prevShouldObserve = shouldObserve\n  toggleObserving(true)\n  observe(value)\n  toggleObserving(prevShouldObserve)\n}\n```\n\n这种是父组件没有传递 `prop` 值对默认值的处理逻辑，因为这个值是一个拷贝，所以我们需要 `toggleObserving(true)`，然后执行 `observe(value)` 把值变成响应式。\n\n在 `updateChildComponent` 过程中：\n\n```js\n// update props\nif (propsData && vm.$options.props) {\n  toggleObserving(false)\n  const props = vm._props\n  const propKeys = vm.$options._propKeys || []\n  for (let i = 0; i < propKeys.length; i++) {\n    const key = propKeys[i]\n    const propOptions: any = vm.$options.props // wtf flow?\n    props[key] = validateProp(key, propOptions, propsData, vm)\n  }\n  toggleObserving(true)\n  // keep a copy of raw propsData\n  vm.$options.propsData = propsData\n}\n```\n\n其实和 `initProps` 的逻辑一样，不需要对引用类型 `props` 递归做响应式处理，所以也需要 `toggleObserving(false)`。\n\n## 总结\n\n通过这一节的分析，我们了解了 `props` 的规范化、初始化、更新等过程的实现原理；也了解了 Vue 内部对 `props` 如何做响应式的优化；同时还了解到 `props` 的变化是如何触发子组件的更新。了解这些对我们平时对 `props` 的应用，遇到问题时的定位追踪会有很大的帮助。\n"
  },
  {
    "path": "docs/v2/reactive/questions.md",
    "content": "# 检测变化的注意事项\n\n通过前面几节的分析，我们对响应式数据对象以及它的 getter 和 setter 部分做了了解，但是对于一些特殊情况是需要注意的，接下来我们就从源码的角度来看 Vue 是如何处理这些特殊情况的。\n\n## 对象添加属性\n\n对于使用 `Object.defineProperty` 实现响应式的对象，当我们去给这个对象添加一个新的属性的时候，是不能够触发它的 setter 的，比如：\n\n```js\nvar vm = new Vue({\n  data:{\n    a:1\n  }\n})\n// vm.b 是非响应的\nvm.b = 2\n```\n\n但是添加新属性的场景我们在平时开发中会经常遇到，那么 Vue 为了解决这个问题，定义了一个全局 API `Vue.set` 方法，它在 `src/core/global-api/index.js` 中初始化：\n\n```js\nVue.set = set\n```\n这个 `set` 方法的定义在 `src/core/observer/index.js` 中：\n\n```js\n/**\n * Set a property on an object. Adds the new property and\n * triggers change notification if the property doesn't\n * already exist.\n */\nexport function set (target: Array<any> | Object, key: any, val: any): any {\n  if (process.env.NODE_ENV !== 'production' &&\n    (isUndef(target) || isPrimitive(target))\n  ) {\n    warn(`Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}`)\n  }\n  if (Array.isArray(target) && isValidArrayIndex(key)) {\n    target.length = Math.max(target.length, key)\n    target.splice(key, 1, val)\n    return val\n  }\n  if (key in target && !(key in Object.prototype)) {\n    target[key] = val\n    return val\n  }\n  const ob = (target: any).__ob__\n  if (target._isVue || (ob && ob.vmCount)) {\n    process.env.NODE_ENV !== 'production' && warn(\n      'Avoid adding reactive properties to a Vue instance or its root $data ' +\n      'at runtime - declare it upfront in the data option.'\n    )\n    return val\n  }\n  if (!ob) {\n    target[key] = val\n    return val\n  }\n  defineReactive(ob.value, key, val)\n  ob.dep.notify()\n  return val\n}\n```\n\n`set` 方法接收 3个参数，`target` 可能是数组或者是普通对象，`key` 代表的是数组的下标或者是对象的键值，`val` 代表添加的值。首先判断如果 `target` 是数组且 `key` 是一个合法的下标，则之前通过 `splice` 去添加进数组然后返回，这里的 `splice` 其实已经不仅仅是原生数组的 `splice` 了，稍后我会详细介绍数组的逻辑。接着又判断 `key` 已经存在于 `target` 中，则直接赋值返回，因为这样的变化是可以观测到了。接着再获取到 `target.__ob__` 并赋值给 `ob`，之前分析过它是在 `Observer` 的构造函数执行的时候初始化的，表示 `Observer` 的一个实例，如果它不存在，则说明 `target` 不是一个响应式的对象，则直接赋值并返回。最后通过 ` defineReactive(ob.value, key, val)` 把新添加的属性变成响应式对象，然后再通过 `ob.dep.notify()` 手动的触发依赖通知，还记得我们在给对象添加 getter 的时候有这么一段逻辑：\n\n```js\nexport function defineReactive (\n  obj: Object,\n  key: string,\n  val: any,\n  customSetter?: ?Function,\n  shallow?: boolean\n) {\n  // ...\n  let childOb = !shallow && observe(val)\n  Object.defineProperty(obj, key, {\n    enumerable: true,\n    configurable: true,\n    get: function reactiveGetter () {\n      const value = getter ? getter.call(obj) : val\n      if (Dep.target) {\n        dep.depend()\n        if (childOb) {\n          childOb.dep.depend()\n          if (Array.isArray(value)) {\n            dependArray(value)\n          }\n        }\n      }\n      return value\n    },\n    // ...\n  })\n}\n```\n\n在 getter 过程中判断了 `childOb`，并调用了 `childOb.dep.depend()` 收集了依赖，这就是为什么执行 `Vue.set` 的时候通过 `ob.dep.notify()` 能够通知到 `watcher `，从而让添加新的属性到对象也可以检测到变化。这里如果 `value` 是个数组，那么就通过 `dependArray` 把数组每个元素也去做依赖收集。\n\n## 数组\n\n接着说一下数组的情况，Vue 也是不能检测到以下变动的数组：\n\n1.当你利用索引直接设置一个项时，例如：`vm.items[indexOfItem] = newValue`\n\n2.当你修改数组的长度时，例如：`vm.items.length = newLength`\n\n对于第一种情况，可以使用：`Vue.set(example1.items, indexOfItem, newValue)`；而对于第二种情况，可以使用 `vm.items.splice(newLength)`。\n\n我们刚才也分析到，对于 `Vue.set` 的实现，当 `target` 是数组的时候，也是通过 `target.splice(key, 1, val)` 来添加的，那么这里的 `splice` 到底有什么黑魔法，能让添加的对象变成响应式的呢。\n\n其实之前我们也分析过，在通过 `observe` 方法去观察对象的时候会实例化 `Observer`，在它的构造函数中是专门对数组做了处理，它的定义在 `src/core/observer/index.js` 中。\n\n```js\nexport class Observer {\n  constructor (value: any) {\n    this.value = value\n    this.dep = new Dep()\n    this.vmCount = 0\n    def(value, '__ob__', this)\n    if (Array.isArray(value)) {\n      const augment = hasProto\n        ? protoAugment\n        : copyAugment\n      augment(value, arrayMethods, arrayKeys)\n      this.observeArray(value)\n    } else {\n      // ...\n    }\n  }\n}\n```\n这里我们只需要关注 `value` 是 Array 的情况，首先获取 `augment`，这里的 `hasProto` 实际上就是判断对象中是否存在 `__proto__`，如果存在则 `augment` 指向 `protoAugment`， 否则指向 `copyAugment`，来看一下这两个函数的定义：\n\n```js\n/**\n * Augment an target Object or Array by intercepting\n * the prototype chain using __proto__\n */\nfunction protoAugment (target, src: Object, keys: any) {\n  /* eslint-disable no-proto */\n  target.__proto__ = src\n  /* eslint-enable no-proto */\n}\n\n/**\n * Augment an target Object or Array by defining\n * hidden properties.\n */\n/* istanbul ignore next */\nfunction copyAugment (target: Object, src: Object, keys: Array<string>) {\n  for (let i = 0, l = keys.length; i < l; i++) {\n    const key = keys[i]\n    def(target, key, src[key])\n  }\n}\n```\n\n`protoAugment` 方法是直接把 `target.__proto__` 原型直接修改为 `src`，而 `copyAugment` 方法是遍历 keys，通过 `def`，也就是 `Object.defineProperty` 去定义它自身的属性值。对于大部分现代浏览器都会走到 `protoAugment`，那么它实际上就把 `value` 的原型指向了 `arrayMethods`，`arrayMethods` 的定义在 `src/core/observer/array.js` 中：\n\n```js\nimport { def } from '../util/index'\n\nconst arrayProto = Array.prototype\nexport const arrayMethods = Object.create(arrayProto)\n\nconst methodsToPatch = [\n  'push',\n  'pop',\n  'shift',\n  'unshift',\n  'splice',\n  'sort',\n  'reverse'\n]\n\n/**\n * Intercept mutating methods and emit events\n */\nmethodsToPatch.forEach(function (method) {\n  // cache original method\n  const original = arrayProto[method]\n  def(arrayMethods, method, function mutator (...args) {\n    const result = original.apply(this, args)\n    const ob = this.__ob__\n    let inserted\n    switch (method) {\n      case 'push':\n      case 'unshift':\n        inserted = args\n        break\n      case 'splice':\n        inserted = args.slice(2)\n        break\n    }\n    if (inserted) ob.observeArray(inserted)\n    // notify change\n    ob.dep.notify()\n    return result\n  })\n})\n```\n可以看到，`arrayMethods` 首先继承了 `Array`，然后对数组中所有能改变数组自身的方法，如 `push、pop` 等这些方法进行重写。重写后的方法会先执行它们本身原有的逻辑，并对能增加数组长度的 3 个方法 `push、unshift、splice` 方法做了判断，获取到插入的值，然后把新添加的值变成一个响应式对象，并且再调用 ` ob.dep.notify()` 手动触发依赖通知，这就很好地解释了之前的示例中调用 `vm.items.splice(newLength)` 方法可以检测到变化。\n\n## 总结\n\n通过这一节的分析，我们对响应式对象又有了更全面的认识，如果在实际工作中遇到了这些特殊情况，我们就可以知道如何把它们也变成响应式的对象。其实对于对象属性的删除也会用同样的问题，Vue 同样提供了 `Vue.del` 的全局 API，它的实现和 `Vue.set` 大同小异，甚至还要更简单一些，这里我就不去分析了，感兴趣的同学可以自行去了解。\n"
  },
  {
    "path": "docs/v2/reactive/reactive-object.md",
    "content": "# 响应式对象\n\n可能很多小伙伴之前都了解过 Vue.js 实现响应式的核心是利用了 ES5 的 `Object.defineProperty`，这也是为什么 Vue.js 不能兼容 IE8 及以下浏览器的原因，我们先来对它有个直观的认识。\n\n## Object.defineProperty    \n\n`Object.defineProperty` 方法会直接在一个对象上定义一个新属性，或者修改一个对象的现有属性， 并返回这个对象，先来看一下它的语法：\n\n```js\nObject.defineProperty(obj, prop, descriptor)\n```\n\n`obj` 是要在其上定义属性的对象；`prop` 是要定义或修改的属性的名称；`descriptor` 是将被定义或修改的属性描述符。\n\n比较核心的是 `descriptor`，它有很多可选键值，具体的可以去参阅它的[文档](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty)。这里我们最关心的是 `get` 和 `set`，`get` 是一个给属性提供的 getter 方法，当我们访问了该属性的时候会触发 getter 方法；`set` 是一个给属性提供的 setter 方法，当我们对该属性做修改的时候会触发 setter 方法。\n\n一旦对象拥有了 getter 和 setter，我们可以简单地把这个对象称为响应式对象。那么 Vue.js 把哪些对象变成了响应式对象了呢，接下来我们从源码层面分析。\n\n## initState\n\n在 Vue 的初始化阶段，`_init` 方法执行的时候，会执行 `initState(vm)` 方法，它的定义在 `src/core/instance/state.js` 中。\n\n```js\nexport function initState (vm: Component) {\n  vm._watchers = []\n  const opts = vm.$options\n  if (opts.props) initProps(vm, opts.props)\n  if (opts.methods) initMethods(vm, opts.methods)\n  if (opts.data) {\n    initData(vm)\n  } else {\n    observe(vm._data = {}, true /* asRootData */)\n  }\n  if (opts.computed) initComputed(vm, opts.computed)\n  if (opts.watch && opts.watch !== nativeWatch) {\n    initWatch(vm, opts.watch)\n  }\n}\n```\n`initState` 方法主要是对 `props`、`methods`、`data`、`computed` 和 `wathcer` 等属性做了初始化操作。这里我们重点分析 `props` 和 `data`，对于其它属性的初始化我们之后再详细分析。\n\n- initProps\n\n```js\nfunction initProps (vm: Component, propsOptions: Object) {\n  const propsData = vm.$options.propsData || {}\n  const props = vm._props = {}\n  // cache prop keys so that future props updates can iterate using Array\n  // instead of dynamic object key enumeration.\n  const keys = vm.$options._propKeys = []\n  const isRoot = !vm.$parent\n  // root instance props should be converted\n  if (!isRoot) {\n    toggleObserving(false)\n  }\n  for (const key in propsOptions) {\n    keys.push(key)\n    const value = validateProp(key, propsOptions, propsData, vm)\n    /* istanbul ignore else */\n    if (process.env.NODE_ENV !== 'production') {\n      const hyphenatedKey = hyphenate(key)\n      if (isReservedAttribute(hyphenatedKey) ||\n          config.isReservedAttr(hyphenatedKey)) {\n        warn(\n          `\"${hyphenatedKey}\" is a reserved attribute and cannot be used as component prop.`,\n          vm\n        )\n      }\n      defineReactive(props, key, value, () => {\n        if (vm.$parent && !isUpdatingChildComponent) {\n          warn(\n            `Avoid mutating a prop directly since the value will be ` +\n            `overwritten whenever the parent component re-renders. ` +\n            `Instead, use a data or computed property based on the prop's ` +\n            `value. Prop being mutated: \"${key}\"`,\n            vm\n          )\n        }\n      })\n    } else {\n      defineReactive(props, key, value)\n    }\n    // static props are already proxied on the component's prototype\n    // during Vue.extend(). We only need to proxy props defined at\n    // instantiation here.\n    if (!(key in vm)) {\n      proxy(vm, `_props`, key)\n    }\n  }\n  toggleObserving(true)\n}\n```\n`props` 的初始化主要过程，就是遍历定义的 `props` 配置。遍历的过程主要做两件事情：一个是调用 `defineReactive` 方法把每个 `prop` 对应的值变成响应式，可以通过 `vm._props.xxx` 访问到定义 `props` 中对应的属性。对于 `defineReactive` 方法，我们稍后会介绍；另一个是通过 `proxy` 把 `vm._props.xxx` 的访问代理到 `vm.xxx` 上，我们稍后也会介绍。\n\n- initData\n\n```js\nfunction initData (vm: Component) {\n  let data = vm.$options.data\n  data = vm._data = typeof data === 'function'\n    ? getData(data, vm)\n    : data || {}\n  if (!isPlainObject(data)) {\n    data = {}\n    process.env.NODE_ENV !== 'production' && warn(\n      'data functions should return an object:\\n' +\n      'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',\n      vm\n    )\n  }\n  // proxy data on instance\n  const keys = Object.keys(data)\n  const props = vm.$options.props\n  const methods = vm.$options.methods\n  let i = keys.length\n  while (i--) {\n    const key = keys[i]\n    if (process.env.NODE_ENV !== 'production') {\n      if (methods && hasOwn(methods, key)) {\n        warn(\n          `Method \"${key}\" has already been defined as a data property.`,\n          vm\n        )\n      }\n    }\n    if (props && hasOwn(props, key)) {\n      process.env.NODE_ENV !== 'production' && warn(\n        `The data property \"${key}\" is already declared as a prop. ` +\n        `Use prop default value instead.`,\n        vm\n      )\n    } else if (!isReserved(key)) {\n      proxy(vm, `_data`, key)\n    }\n  }\n  // observe data\n  observe(data, true /* asRootData */)\n}\n```\n\n`data` 的初始化主要过程也是做两件事，一个是对定义 `data` 函数返回对象的遍历，通过 `proxy` 把每一个值 `vm._data.xxx` 都代理到 `vm.xxx` 上；另一个是调用 `observe` 方法观测整个 `data` 的变化，把 `data` 也变成响应式，可以通过 `vm._data.xxx` 访问到定义 `data` 返回函数中对应的属性，`observe` 我们稍后会介绍。\n\n可以看到，无论是 `props` 或是 `data` 的初始化都是把它们变成响应式对象，这个过程我们接触到几个函数，接下来我们来详细分析它们。\n\n## proxy\n\n首先介绍一下代理，代理的作用是把 `props` 和 `data` 上的属性代理到 `vm` 实例上，这也就是为什么比如我们定义了如下 props，却可以通过 vm 实例访问到它。\n\n```js\nlet comP = {\n  props: {\n    msg: 'hello'\n  },\n  methods: {\n    say() {\n      console.log(this.msg)\n    }\n  }\n}\n```\n\n我们可以在 `say` 函数中通过 `this.msg` 访问到我们定义在 `props` 中的 `msg`，这个过程发生在 `proxy` 阶段：\n\n```js\nconst sharedPropertyDefinition = {\n  enumerable: true,\n  configurable: true,\n  get: noop,\n  set: noop\n}\n\nexport function proxy (target: Object, sourceKey: string, key: string) {\n  sharedPropertyDefinition.get = function proxyGetter () {\n    return this[sourceKey][key]\n  }\n  sharedPropertyDefinition.set = function proxySetter (val) {\n    this[sourceKey][key] = val\n  }\n  Object.defineProperty(target, key, sharedPropertyDefinition)\n}\n```\n\n`proxy` 方法的实现很简单，通过 `Object.defineProperty` 把 `target[sourceKey][key]` 的读写变成了对 `target[key]`  的读写。所以对于 `props` 而言，对 `vm._props.xxx` 的读写变成了 `vm.xxx` 的读写，而对于 `vm._props.xxx` 我们可以访问到定义在 `props` 中的属性，所以我们就可以通过 `vm.xxx` 访问到定义在 `props` 中的 `xxx` 属性了。同理，对于 `data` 而言，对 `vm._data.xxxx` 的读写变成了对 `vm.xxxx` 的读写，而对于 `vm._data.xxxx` 我们可以访问到定义在 `data` 函数返回对象中的属性，所以我们就可以通过 `vm.xxxx` 访问到定义在 `data` 函数返回对象中的 `xxxx` 属性了。\n\n## `observe`\n\n`observe` 的功能就是用来监测数据的变化，它的定义在 `src/core/observer/index.js` 中：\n\n```js\n/**\n * Attempt to create an observer instance for a value,\n * returns the new observer if successfully observed,\n * or the existing observer if the value already has one.\n */\nexport function observe (value: any, asRootData: ?boolean): Observer | void {\n  if (!isObject(value) || value instanceof VNode) {\n    return\n  }\n  let ob: Observer | void\n  if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {\n    ob = value.__ob__\n  } else if (\n    shouldObserve &&\n    !isServerRendering() &&\n    (Array.isArray(value) || isPlainObject(value)) &&\n    Object.isExtensible(value) &&\n    !value._isVue\n  ) {\n    ob = new Observer(value)\n  }\n  if (asRootData && ob) {\n    ob.vmCount++\n  }\n  return ob\n}\n```\n\n`observe` 方法的作用就是给非 VNode 的对象类型数据添加一个 `Observer`，如果已经添加过则直接返回，否则在满足一定条件下去实例化一个 `Observer` 对象实例。接下来我们来看一下 `Observer` 的作用。\n\n## Observer\n\n`Observer` 是一个类，它的作用是给对象的属性添加 getter 和 setter，用于依赖收集和派发更新：\n\n```js\n/**\n * Observer class that is attached to each observed\n * object. Once attached, the observer converts the target\n * object's property keys into getter/setters that\n * collect dependencies and dispatch updates.\n */\nexport class Observer {\n  value: any;\n  dep: Dep;\n  vmCount: number; // number of vms that has this object as root $data\n\n  constructor (value: any) {\n    this.value = value\n    this.dep = new Dep()\n    this.vmCount = 0\n    def(value, '__ob__', this)\n    if (Array.isArray(value)) {\n      const augment = hasProto\n        ? protoAugment\n        : copyAugment\n      augment(value, arrayMethods, arrayKeys)\n      this.observeArray(value)\n    } else {\n      this.walk(value)\n    }\n  }\n\n  /**\n   * Walk through each property and convert them into\n   * getter/setters. This method should only be called when\n   * value type is Object.\n   */\n  walk (obj: Object) {\n    const keys = Object.keys(obj)\n    for (let i = 0; i < keys.length; i++) {\n      defineReactive(obj, keys[i])\n    }\n  }\n\n  /**\n   * Observe a list of Array items.\n   */\n  observeArray (items: Array<any>) {\n    for (let i = 0, l = items.length; i < l; i++) {\n      observe(items[i])\n    }\n  }\n}\n```\n\n`Observer` 的构造函数逻辑很简单，首先实例化 `Dep` 对象，这块稍后会介绍，接着通过执行 `def` 函数把自身实例添加到数据对象 `value` 的 `__ob__` 属性上，`def` 的定义在 `src/core/util/lang.js` 中：\n\n```js\n/**\n * Define a property.\n */\nexport function def (obj: Object, key: string, val: any, enumerable?: boolean) {\n  Object.defineProperty(obj, key, {\n    value: val,\n    enumerable: !!enumerable,\n    writable: true,\n    configurable: true\n  })\n}\n```\n\n`def` 函数是一个非常简单的`Object.defineProperty` 的封装，这就是为什么我在开发中输出 `data` 上对象类型的数据，会发现该对象多了一个 `__ob__` 的属性。\n\n回到 `Observer` 的构造函数，接下来会对 `value` 做判断，对于数组会调用 `observeArray` 方法，否则对纯对象调用 `walk` 方法。可以看到 `observeArray` 是遍历数组再次调用 `observe` 方法，而 `walk` 方法是遍历对象的 key 调用 `defineReactive` 方法，那么我们来看一下这个方法是做什么的。\n\n## defineReactive\n\n`defineReactive` 的功能就是定义一个响应式对象，给对象动态添加 getter 和 setter，它的定义在 `src/core/observer/index.js` 中：\n\n```js\n/**\n * Define a reactive property on an Object.\n */\nexport function defineReactive (\n  obj: Object,\n  key: string,\n  val: any,\n  customSetter?: ?Function,\n  shallow?: boolean\n) {\n  const dep = new Dep()\n\n  const property = Object.getOwnPropertyDescriptor(obj, key)\n  if (property && property.configurable === false) {\n    return\n  }\n\n  // cater for pre-defined getter/setters\n  const getter = property && property.get\n  const setter = property && property.set\n  if ((!getter || setter) && arguments.length === 2) {\n    val = obj[key]\n  }\n\n  let childOb = !shallow && observe(val)\n  Object.defineProperty(obj, key, {\n    enumerable: true,\n    configurable: true,\n    get: function reactiveGetter () {\n      const value = getter ? getter.call(obj) : val\n      if (Dep.target) {\n        dep.depend()\n        if (childOb) {\n          childOb.dep.depend()\n          if (Array.isArray(value)) {\n            dependArray(value)\n          }\n        }\n      }\n      return value\n    },\n    set: function reactiveSetter (newVal) {\n      const value = getter ? getter.call(obj) : val\n      /* eslint-disable no-self-compare */\n      if (newVal === value || (newVal !== newVal && value !== value)) {\n        return\n      }\n      /* eslint-enable no-self-compare */\n      if (process.env.NODE_ENV !== 'production' && customSetter) {\n        customSetter()\n      }\n      if (setter) {\n        setter.call(obj, newVal)\n      } else {\n        val = newVal\n      }\n      childOb = !shallow && observe(newVal)\n      dep.notify()\n    }\n  })\n}\n```\n\n`defineReactive` 函数最开始初始化 `Dep` 对象的实例，接着拿到 `obj` 的属性描述符，然后对子对象递归调用 `observe` 方法，这样就保证了无论 `obj` 的结构多复杂，它的所有子属性也能变成响应式的对象，这样我们访问或修改 `obj` 中一个嵌套较深的属性，也能触发 getter 和 setter。最后利用 `Object.defineProperty` 去给 `obj` 的属性 `key` 添加 getter 和 setter。而关于 getter 和 setter 的具体实现，我们会在之后介绍。\n\n## 总结\n\n这一节我们介绍了响应式对象，核心就是利用 `Object.defineProperty` 给数据添加了 getter 和 setter，目的就是为了在我们访问数据以及写数据的时候能自动执行一些逻辑：getter 做的事情是依赖收集，setter 做的事情是派发更新，那么在接下来的章节我们会重点对这两个过程分析。\n \n\n\n"
  },
  {
    "path": "docs/v2/reactive/setters.md",
    "content": "# 派发更新\n\n通过上一节分析我们了解了响应式数据依赖收集过程，收集的目的就是为了当我们修改数据的时候，可以对相关的依赖派发更新，那么这一节我们来详细分析这个过程。\n\n我们先来回顾一下 setter 部分的逻辑：\n\n```js\n/**\n * Define a reactive property on an Object.\n */\nexport function defineReactive (\n  obj: Object,\n  key: string,\n  val: any,\n  customSetter?: ?Function,\n  shallow?: boolean\n) {\n  const dep = new Dep()\n\n  const property = Object.getOwnPropertyDescriptor(obj, key)\n  if (property && property.configurable === false) {\n    return\n  }\n\n  // cater for pre-defined getter/setters\n  const getter = property && property.get\n  const setter = property && property.set\n  if ((!getter || setter) && arguments.length === 2) {\n    val = obj[key]\n  }\n\n  let childOb = !shallow && observe(val)\n  Object.defineProperty(obj, key, {\n    enumerable: true,\n    configurable: true,\n    // ...\n    set: function reactiveSetter (newVal) {\n      const value = getter ? getter.call(obj) : val\n      /* eslint-disable no-self-compare */\n      if (newVal === value || (newVal !== newVal && value !== value)) {\n        return\n      }\n      /* eslint-enable no-self-compare */\n      if (process.env.NODE_ENV !== 'production' && customSetter) {\n        customSetter()\n      }\n      if (setter) {\n        setter.call(obj, newVal)\n      } else {\n        val = newVal\n      }\n      childOb = !shallow && observe(newVal)\n      dep.notify()\n    }\n  })\n}\n```\n\nsetter 的逻辑有 2 个关键的点，一个是 `childOb = !shallow && observe(newVal)`，如果 `shallow` 为 false 的情况，会对新设置的值变成一个响应式对象；另一个是 `dep.notify()`，通知所有的订阅者，这是本节的关键，接下来我会带大家完整的分析整个派发更新的过程。\n\n## 过程分析\n\n当我们在组件中对响应的数据做了修改，就会触发 setter 的逻辑，最后调用 `dep.notify()` 方法，\n它是 `Dep` 的一个实例方法，定义在 `src/core/observer/dep.js` 中：\n\n```js\nclass Dep {\n  // ...\n  notify () {\n  // stabilize the subscriber list first\n    const subs = this.subs.slice()\n    for (let i = 0, l = subs.length; i < l; i++) {\n      subs[i].update()\n    }\n  }\n}\n```\n\n这里的逻辑非常简单，遍历所有的 `subs`，也就是 `Watcher` 的实例数组，然后调用每一个 `watcher` 的 `update` 方法，它的定义在 `src/core/observer/watcher.js` 中：\n\n```js\nclass Watcher {\n  // ...\n  update () {\n    /* istanbul ignore else */\n    if (this.computed) {\n      // A computed property watcher has two modes: lazy and activated.\n      // It initializes as lazy by default, and only becomes activated when\n      // it is depended on by at least one subscriber, which is typically\n      // another computed property or a component's render function.\n      if (this.dep.subs.length === 0) {\n        // In lazy mode, we don't want to perform computations until necessary,\n        // so we simply mark the watcher as dirty. The actual computation is\n        // performed just-in-time in this.evaluate() when the computed property\n        // is accessed.\n        this.dirty = true\n      } else {\n        // In activated mode, we want to proactively perform the computation\n        // but only notify our subscribers when the value has indeed changed.\n        this.getAndInvoke(() => {\n          this.dep.notify()\n        })\n      }\n    } else if (this.sync) {\n      this.run()\n    } else {\n      queueWatcher(this)\n    }\n  }\n}  \n```\n\n这里对于 `Watcher` 的不同状态，会执行不同的逻辑，`computed` 和 `sync` 等状态的分析我会之后抽一小节详细介绍，在一般组件数据更新的场景，会走到最后一个 `queueWatcher(this)` 的逻辑，`queueWatcher` 的定义在 `src/core/observer/scheduler.js` 中：\n\n```js\nconst queue: Array<Watcher> = []\nlet has: { [key: number]: ?true } = {}\nlet waiting = false\nlet flushing = false\n/**\n * Push a watcher into the watcher queue.\n * Jobs with duplicate IDs will be skipped unless it's\n * pushed when the queue is being flushed.\n */\nexport function queueWatcher (watcher: Watcher) {\n  const id = watcher.id\n  if (has[id] == null) {\n    has[id] = true\n    if (!flushing) {\n      queue.push(watcher)\n    } else {\n      // if already flushing, splice the watcher based on its id\n      // if already past its id, it will be run next immediately.\n      let i = queue.length - 1\n      while (i > index && queue[i].id > watcher.id) {\n        i--\n      }\n      queue.splice(i + 1, 0, watcher)\n    }\n    // queue the flush\n    if (!waiting) {\n      waiting = true\n      nextTick(flushSchedulerQueue)\n    }\n  }\n}\n```\n\n这里引入了一个队列的概念，这也是 Vue 在做派发更新的时候的一个优化的点，它并不会每次数据改变都触发 `watcher` 的回调，而是把这些 `watcher` 先添加到一个队列里，然后在 `nextTick` 后执行 `flushSchedulerQueue`。\n\n这里有几个细节要注意一下，首先用 `has` 对象保证同一个 `Watcher` 只添加一次；接着对 `flushing` 的判断，else 部分的逻辑稍后我会讲；最后通过 `waiting` 保证对 `nextTick(flushSchedulerQueue)` 的调用逻辑只有一次，另外 `nextTick` 的实现我之后会抽一小节专门去讲，目前就可以理解它是在下一个 tick，也就是异步的去执行 `flushSchedulerQueue`。\n\n接下来我们来看 `flushSchedulerQueue` 的实现，它的定义在 `src/core/observer/scheduler.js` 中。\n\n```js\nlet flushing = false\nlet index = 0\n/**\n * Flush both queues and run the watchers.\n */\nfunction flushSchedulerQueue () {\n  flushing = true\n  let watcher, id\n\n  // Sort queue before flush.\n  // This ensures that:\n  // 1. Components are updated from parent to child. (because parent is always\n  //    created before the child)\n  // 2. A component's user watchers are run before its render watcher (because\n  //    user watchers are created before the render watcher)\n  // 3. If a component is destroyed during a parent component's watcher run,\n  //    its watchers can be skipped.\n  queue.sort((a, b) => a.id - b.id)\n\n  // do not cache length because more watchers might be pushed\n  // as we run existing watchers\n  for (index = 0; index < queue.length; index++) {\n    watcher = queue[index]\n    if (watcher.before) {\n      watcher.before()\n    }\n    id = watcher.id\n    has[id] = null\n    watcher.run()\n    // in dev build, check and stop circular updates.\n    if (process.env.NODE_ENV !== 'production' && has[id] != null) {\n      circular[id] = (circular[id] || 0) + 1\n      if (circular[id] > MAX_UPDATE_COUNT) {\n        warn(\n          'You may have an infinite update loop ' + (\n            watcher.user\n              ? `in watcher with expression \"${watcher.expression}\"`\n              : `in a component render function.`\n          ),\n          watcher.vm\n        )\n        break\n      }\n    }\n  }\n\n  // keep copies of post queues before resetting state\n  const activatedQueue = activatedChildren.slice()\n  const updatedQueue = queue.slice()\n\n  resetSchedulerState()\n\n  // call component updated and activated hooks\n  callActivatedHooks(activatedQueue)\n  callUpdatedHooks(updatedQueue)\n\n  // devtool hook\n  /* istanbul ignore if */\n  if (devtools && config.devtools) {\n    devtools.emit('flush')\n  }\n}\n```\n\n这里有几个重要的逻辑要梳理一下，对于一些分支逻辑如 `keep-alive` 组件相关和之前提到过的 `updated` 钩子函数的执行会略过。\n\n- 队列排序\n\n`queue.sort((a, b) => a.id - b.id)` 对队列做了从小到大的排序，这么做主要有以下要确保以下几点：\n\n1.组件的更新由父到子；因为父组件的创建过程是先于子的，所以 `watcher` 的创建也是先父后子，执行顺序也应该保持先父后子。\n\n2.用户的自定义 `watcher` 要优先于渲染 `watcher` 执行；因为用户自定义 `watcher` 是在渲染 `watcher` 之前创建的。\n\n3.如果一个组件在父组件的 `watcher` 执行期间被销毁，那么它对应的 `watcher` 执行都可以被跳过，所以父组件的 `watcher` 应该先执行。\n\n- 队列遍历\n\n在对 `queue` 排序后，接着就是要对它做遍历，拿到对应的 `watcher`，执行 `watcher.run()`。这里需要注意一个细节，在遍历的时候每次都会对 `queue.length` 求值，因为在 `watcher.run()` 的时候，很可能用户会再次添加新的 `watcher`，这样会再次执行到 `queueWatcher`，如下：\n\n```js\nexport function queueWatcher (watcher: Watcher) {\n  const id = watcher.id\n  if (has[id] == null) {\n    has[id] = true\n    if (!flushing) {\n      queue.push(watcher)\n    } else {\n      // if already flushing, splice the watcher based on its id\n      // if already past its id, it will be run next immediately.\n      let i = queue.length - 1\n      while (i > index && queue[i].id > watcher.id) {\n        i--\n      }\n      queue.splice(i + 1, 0, watcher)\n    }\n    // ...\n  }\n}\n```\n可以看到，这时候 `flushing` 为 true，就会执行到 else 的逻辑，然后就会从后往前找，找到第一个待插入 `watcher` 的 id 比当前队列中 `watcher` 的 id 大的位置。把 `watcher` 按照 `id `的插入到队列中，因此 `queue` 的长度发生了变化。\n\n- 状态恢复\n\n这个过程就是执行 `resetSchedulerState` 函数，它的定义在 `src/core/observer/scheduler.js` 中。\n\n```js\nconst queue: Array<Watcher> = []\nlet has: { [key: number]: ?true } = {}\nlet circular: { [key: number]: number } = {}\nlet waiting = false\nlet flushing = false\nlet index = 0\n/**\n * Reset the scheduler's state.\n */\nfunction resetSchedulerState () {\n  index = queue.length = activatedChildren.length = 0\n  has = {}\n  if (process.env.NODE_ENV !== 'production') {\n    circular = {}\n  }\n  waiting = flushing = false\n}\n```\n逻辑非常简单，就是把这些控制流程状态的一些变量恢复到初始值，把 `watcher` 队列清空。\n\n接下来我们继续分析 `watcher.run()` 的逻辑，它的定义在 `src/core/observer/watcher.js` 中。\n\n```js\nclass Watcher {\n  /**\n   * Scheduler job interface.\n   * Will be called by the scheduler.\n   */\n  run () {\n    if (this.active) {\n      this.getAndInvoke(this.cb)\n    }\n  }\n\n  getAndInvoke (cb: Function) {\n    const value = this.get()\n    if (\n      value !== this.value ||\n      // Deep watchers and watchers on Object/Arrays should fire even\n      // when the value is the same, because the value may\n      // have mutated.\n      isObject(value) ||\n      this.deep\n    ) {\n      // set new value\n      const oldValue = this.value\n      this.value = value\n      this.dirty = false\n      if (this.user) {\n        try {\n          cb.call(this.vm, value, oldValue)\n        } catch (e) {\n          handleError(e, this.vm, `callback for watcher \"${this.expression}\"`)\n        }\n      } else {\n        cb.call(this.vm, value, oldValue)\n      }\n    }\n  }\n}\n```\n\n`run` 函数实际上就是执行 `this.getAndInvoke` 方法，并传入 `watcher` 的回调函数。`getAndInvoke` 函数逻辑也很简单，先通过 `this.get()` 得到它当前的值，然后做判断，如果满足新旧值不等、新值是对象类型、`deep` 模式任何一个条件，则执行 `watcher` 的回调，注意回调函数执行的时候会把第一个和第二个参数传入新值 `value` 和旧值 `oldValue`，这就是当我们添加自定义 `watcher` 的时候能在回调函数的参数中拿到新旧值的原因。\n\n那么对于渲染 `watcher` 而言，它在执行 `this.get()` 方法求值的时候，会执行 `getter` 方法：\n\n```js\nupdateComponent = () => {\n  vm._update(vm._render(), hydrating)\n}\n```\n\n所以这就是当我们去修改组件相关的响应式数据的时候，会触发组件重新渲染的原因，接着就会重新执行 `patch` 的过程，但它和首次渲染有所不同，之后我们会花一小节去详细介绍。\n\n## 总结\n\n通过这一节的分析，我们对 Vue 数据修改派发更新的过程也有了认识，实际上就是当数据发生变化的时候，触发 setter 逻辑，把在依赖过程中订阅的的所有观察者，也就是 `watcher`，都触发它们的 `update` 过程，这个过程又利用了队列做了进一步优化，在 `nextTick` 后执行所有 `watcher` 的 `run`，最后执行它们的回调函数。`nextTick` 是 Vue 一个比较核心的实现了，下一节我们来重点分析它的实现。\n"
  },
  {
    "path": "docs/v2/reactive/summary.md",
    "content": "# 原理图\n\n<img :src=\"$withBase('/assets/reactive.png')\">\n"
  },
  {
    "path": "docs/v2/vue-router/index.md",
    "content": "# Vue-Router\n\n路由的概念相信大部分同学并不陌生，它的作用就是根据不同的路径映射到不同的视图。我们在用 Vue 开发过实际项目的时候都会用到 Vue-Router 这个官方插件来帮我们解决路由的问题。Vue-Router 的能力十分强大，它支持 `hash`、`history`、`abstract` 3 种路由方式，提供了 `<router-link>` 和 `<router-view>` 2 种组件，还提供了简单的路由配置和一系列好用的 API。\n\n大部分同学已经掌握了路由的基本使用，但使用的过程中也难免会遇到一些坑，那么这一章我们就来深挖 Vue-Router 的实现细节，一旦我们掌握了它的实现原理，那么就能在开发中对路由的使用更加游刃有余。\n\n同样我们也会通过一些具体的示例来配合讲解，先来看一个最基本使用例子：\n\n```html\n<div id=\"app\">\n  <h1>Hello App!</h1>\n  <p>\n    <!-- 使用 router-link 组件来导航. -->\n    <!-- 通过传入 `to` 属性指定链接. -->\n    <!-- <router-link> 默认会被渲染成一个 `<a>` 标签 -->\n    <router-link to=\"/foo\">Go to Foo</router-link>\n    <router-link to=\"/bar\">Go to Bar</router-link>\n  </p>\n  <!-- 路由出口 -->\n  <!-- 路由匹配到的组件将渲染在这里 -->\n  <router-view></router-view>\n</div>\n```\n\n```js\nimport Vue from 'vue'\nimport VueRouter from 'vue-router'\nimport App from './App'\n\nVue.use(VueRouter)\n\n// 1. 定义（路由）组件。\n// 可以从其他文件 import 进来\nconst Foo = { template: '<div>foo</div>' }\nconst Bar = { template: '<div>bar</div>' }\n\n// 2. 定义路由\n// 每个路由应该映射一个组件。 其中\"component\" 可以是\n// 通过 Vue.extend() 创建的组件构造器，\n// 或者，只是一个组件配置对象。\n// 我们晚点再讨论嵌套路由。\nconst routes = [\n  { path: '/foo', component: Foo },\n  { path: '/bar', component: Bar }\n]\n\n// 3. 创建 router 实例，然后传 `routes` 配置\n// 你还可以传别的配置参数, 不过先这么简单着吧。\nconst router = new VueRouter({\n  routes // （缩写）相当于 routes: routes\n})\n\n// 4. 创建和挂载根实例。\n// 记得要通过 router 配置参数注入路由，\n// 从而让整个应用都有路由功能\nconst app = new Vue({\n  el: '#app',\n  render(h) {\n    return h(App)\n  },\n  router\n})\n```\n\n这是一个非常简单的例子，接下来我们先从 `Vue.use(VueRouter)` 说起。"
  },
  {
    "path": "docs/v2/vue-router/install.md",
    "content": "# 路由注册\n\nVue 从它的设计上就是一个渐进式 JavaScript 框架，它本身的核心是解决视图渲染的问题，其它的能力就通过插件的方式来解决。Vue-Router 就是官方维护的路由插件，在介绍它的注册实现之前，我们先来分析一下 Vue 通用的插件注册原理。\n\n## `Vue.use`\n\nVue 提供了 `Vue.use` 的全局 API 来注册这些插件，所以我们先来分析一下它的实现原理，定义在 `vue/src/core/global-api/use.js` 中：\n\n```js\nexport function initUse (Vue: GlobalAPI) {\n  Vue.use = function (plugin: Function | Object) {\n    const installedPlugins = (this._installedPlugins || (this._installedPlugins = []))\n    if (installedPlugins.indexOf(plugin) > -1) {\n      return this\n    }\n\n    const args = toArray(arguments, 1)\n    args.unshift(this)\n    if (typeof plugin.install === 'function') {\n      plugin.install.apply(plugin, args)\n    } else if (typeof plugin === 'function') {\n      plugin.apply(null, args)\n    }\n    installedPlugins.push(plugin)\n    return this\n  }\n}\n```\n\n`Vue.use` 接受一个 `plugin` 参数，并且维护了一个 `_installedPlugins` 数组，它存储所有注册过的 `plugin`；接着又会判断 `plugin` 有没有定义 `install` 方法，如果有的话则调用该方法，并且该方法执行的第一个参数是 `Vue`；最后把 `plugin` 存储到 `installedPlugins` 中。\n\n可以看到 Vue 提供的插件注册机制很简单，每个插件都需要实现一个静态的 `install` 方法，当我们执行 `Vue.use` 注册插件的时候，就会执行这个 `install` 方法，并且在这个 `install` 方法的第一个参数我们可以拿到 `Vue` 对象，这样的好处就是作为插件的编写方不需要再额外去`import Vue` 了。\n\n## 路由安装\n\nVue-Router 的入口文件是 `src/index.js`，其中定义了 `VueRouter` 类，也实现了 `install` 的静态方法：`VueRouter.install = install`，它的定义在 `src/install.js` 中。\n\n```js\nexport let _Vue\nexport function install (Vue) {\n  if (install.installed && _Vue === Vue) return\n  install.installed = true\n\n  _Vue = Vue\n\n  const isDef = v => v !== undefined\n\n  const registerInstance = (vm, callVal) => {\n    let i = vm.$options._parentVnode\n    if (isDef(i) && isDef(i = i.data) && isDef(i = i.registerRouteInstance)) {\n      i(vm, callVal)\n    }\n  }\n\n  Vue.mixin({\n    beforeCreate () {\n      if (isDef(this.$options.router)) {\n        this._routerRoot = this\n        this._router = this.$options.router\n        this._router.init(this)\n        Vue.util.defineReactive(this, '_route', this._router.history.current)\n      } else {\n        this._routerRoot = (this.$parent && this.$parent._routerRoot) || this\n      }\n      registerInstance(this, this)\n    },\n    destroyed () {\n      registerInstance(this)\n    }\n  })\n\n  Object.defineProperty(Vue.prototype, '$router', {\n    get () { return this._routerRoot._router }\n  })\n\n  Object.defineProperty(Vue.prototype, '$route', {\n    get () { return this._routerRoot._route }\n  })\n\n  Vue.component('RouterView', View)\n  Vue.component('RouterLink', Link)\n\n  const strats = Vue.config.optionMergeStrategies\n  strats.beforeRouteEnter = strats.beforeRouteLeave = strats.beforeRouteUpdate = strats.created\n}\n```\n\n当用户执行 `Vue.use(VueRouter)` 的时候，实际上就是在执行 `install` 函数，为了确保 `install` 逻辑只执行一次，用了 `install.installed` 变量做已安装的标志位。另外用一个全局的 `_Vue` 来接收参数 `Vue`，因为作为 Vue 的插件对 `Vue` 对象是有依赖的，但又不能去单独去 `import Vue`，因为那样会增加包体积，所以就通过这种方式拿到 `Vue` 对象。\n\nVue-Router 安装最重要的一步就是利用 `Vue.mixin` 去把 `beforeCreate` 和 `destroyed` 钩子函数注入到每一个组件中。`Vue.mixin` 的定义，在 `vue/src/core/global-api/mixin.js` 中：\n\n```js\nexport function initMixin (Vue: GlobalAPI) {\n  Vue.mixin = function (mixin: Object) {\n    this.options = mergeOptions(this.options, mixin)\n    return this\n  }\n}\n```\n\n它的实现实际上非常简单，就是把要混入的对象通过 `mergeOptions` 合并到 `Vue` 的 `options` 中，由于每个组件的构造函数都会在 `extend` 阶段合并 `Vue.options` 到自身的 `options` 中，所以也就相当于每个组件都定义了 `mixin` 定义的选项。\n\n回到 `Vue-Router` 的 `install` 方法，先看混入的 `beforeCreate` 钩子函数，对于根 `Vue` 实例而言，执行该钩子函数时定义了 `this._routerRoot` 表示它自身；`this._router` 表示 `VueRouter` 的实例 `router`，它是在 `new Vue` 的时候传入的；另外执行了 `this._router.init()` 方法初始化 `router`，这个逻辑之后介绍，然后用 `defineReactive` 方法把 `this._route` 变成响应式对象，这个作用我们之后会介绍。而对于子组件而言，由于组件是树状结构，在遍历组件树的过程中，它们在执行该钩子函数的时候 `this._routerRoot` 始终指向的离它最近的传入了 `router` 对象作为配置而实例化的父实例。\n\n对于 `beforeCreate` 和 `destroyed` 钩子函数，它们都会执行 `registerInstance` 方法，这个方法的作用我们也是之后会介绍。\n\n接着给 Vue 原型上定义了 `$router` 和 `$route` 2 个属性的 get 方法，这就是为什么我们可以在组件实例上可以访问 `this.$router` 以及 `this.$route`，它们的作用之后介绍。\n\n接着又通过 `Vue.component` 方法定义了全局的 `<router-link>` 和 `<router-view>` 2 个组件，这也是为什么我们在写模板的时候可以使用这两个标签，它们的作用也是之后介绍。\n\n最后定义了路由中的钩子函数的合并策略，和普通的钩子函数一样。\n\n## 总结\n\n那么到此为止，我们分析了 Vue-Router 的安装过程，Vue 编写插件的时候通常要提供静态的 `install` 方法，我们通过 `Vue.use(plugin)` 时候，就是在执行 `install` 方法。`Vue-Router` 的 `install` 方法会给每一个组件注入 `beforeCreate` 和 `destoryed` 钩子函数，在 `beforeCreate` 做一些私有属性定义和路由初始化工作，下一节我们就来分析一下 `VueRouter` 对象的实现和它的初始化工作。\n \n"
  },
  {
    "path": "docs/v2/vue-router/matcher.md",
    "content": "# matcher\n\n`matcher` 相关的实现都在 `src/create-matcher.js` 中，我们先来看一下 `matcher` 的数据结构：\n\n```js\nexport type Matcher = {\n  match: (raw: RawLocation, current?: Route, redirectedFrom?: Location) => Route;\n  addRoutes: (routes: Array<RouteConfig>) => void;\n};\n```\n\n`Matcher` 返回了 2 个方法，`match` 和 `addRoutes`，在上一节我们接触到了 `match` 方法，顾名思义它是做匹配，那么匹配的是什么，在介绍之前，我们先了解路由中重要的 2 个概念，`Loaction` 和 `Route`，它们的数据结构定义在 `flow/declarations.js` 中。\n\n- Location\n\n```js\ndeclare type Location = {\n  _normalized?: boolean;\n  name?: string;\n  path?: string;\n  hash?: string;\n  query?: Dictionary<string>;\n  params?: Dictionary<string>;\n  append?: boolean;\n  replace?: boolean;\n}\n```\n\nVue-Router 中定义的 `Location` 数据结构和浏览器提供的 `window.location` 部分结构有点类似，它们都是对 `url` 的结构化描述。举个例子：`/abc?foo=bar&baz=qux#hello`，它的 `path` 是 `/abc`，`query` 是 `{foo:'bar',baz:'qux'}`。`Location` 的其他属性我们之后会介绍。\n \n- Route\n\n```js\ndeclare type Route = {\n  path: string;\n  name: ?string;\n  hash: string;\n  query: Dictionary<string>;\n  params: Dictionary<string>;\n  fullPath: string;\n  matched: Array<RouteRecord>;\n  redirectedFrom?: string;\n  meta?: any;\n}\n```\n\n`Route` 表示的是路由中的一条线路，它除了描述了类似 `Loctaion` 的 `path`、`query`、`hash` 这些概念，还有 `matched` 表示匹配到的所有的 `RouteRecord`。`Route` 的其他属性我们之后会介绍。\n\n## `createMatcher`\n\n在了解了 `Location` 和 `Route` 后，我们来看一下 `matcher` 的创建过程：\n\n```js\nexport function createMatcher (\n  routes: Array<RouteConfig>,\n  router: VueRouter\n): Matcher {\n  const { pathList, pathMap, nameMap } = createRouteMap(routes)\n\n  function addRoutes (routes) {\n    createRouteMap(routes, pathList, pathMap, nameMap)\n  }\n\n  function match (\n    raw: RawLocation,\n    currentRoute?: Route,\n    redirectedFrom?: Location\n  ): Route {\n    const location = normalizeLocation(raw, currentRoute, false, router)\n    const { name } = location\n\n    if (name) {\n      const record = nameMap[name]\n      if (process.env.NODE_ENV !== 'production') {\n        warn(record, `Route with name '${name}' does not exist`)\n      }\n      if (!record) return _createRoute(null, location)\n      const paramNames = record.regex.keys\n        .filter(key => !key.optional)\n        .map(key => key.name)\n\n      if (typeof location.params !== 'object') {\n        location.params = {}\n      }\n\n      if (currentRoute && typeof currentRoute.params === 'object') {\n        for (const key in currentRoute.params) {\n          if (!(key in location.params) && paramNames.indexOf(key) > -1) {\n            location.params[key] = currentRoute.params[key]\n          }\n        }\n      }\n\n      if (record) {\n        location.path = fillParams(record.path, location.params, `named route \"${name}\"`)\n        return _createRoute(record, location, redirectedFrom)\n      }\n    } else if (location.path) {\n      location.params = {}\n      for (let i = 0; i < pathList.length; i++) {\n        const path = pathList[i]\n        const record = pathMap[path]\n        if (matchRoute(record.regex, location.path, location.params)) {\n          return _createRoute(record, location, redirectedFrom)\n        }\n      }\n    }\n    return _createRoute(null, location)\n  }\n\n  // ...\n\n  function _createRoute (\n    record: ?RouteRecord,\n    location: Location,\n    redirectedFrom?: Location\n  ): Route {\n    if (record && record.redirect) {\n      return redirect(record, redirectedFrom || location)\n    }\n    if (record && record.matchAs) {\n      return alias(record, location, record.matchAs)\n    }\n    return createRoute(record, location, redirectedFrom, router)\n  }\n\n  return {\n    match,\n    addRoutes\n  }\n}\n```\n\n`createMatcher` 接收 2 个参数，一个是 `router`，它是我们 `new VueRouter` 返回的实例，一个是 `routes`，它是用户定义的路由配置，来看一下我们之前举的例子中的配置：\n\n```js\nconst Foo = { template: '<div>foo</div>' }\nconst Bar = { template: '<div>bar</div>' }\n\nconst routes = [\n  { path: '/foo', component: Foo },\n  { path: '/bar', component: Bar }\n]\n```\n\n`createMathcer` 首先执行的逻辑是 `const { pathList, pathMap, nameMap } = createRouteMap(routes)` 创建一个路由映射表，`createRouteMap` 的定义在 `src/create-route-map` 中：\n\n```js\nexport function createRouteMap (\n  routes: Array<RouteConfig>,\n  oldPathList?: Array<string>,\n  oldPathMap?: Dictionary<RouteRecord>,\n  oldNameMap?: Dictionary<RouteRecord>\n): {\n  pathList: Array<string>;\n  pathMap: Dictionary<RouteRecord>;\n  nameMap: Dictionary<RouteRecord>;\n} {\n  const pathList: Array<string> = oldPathList || []\n  const pathMap: Dictionary<RouteRecord> = oldPathMap || Object.create(null)\n  const nameMap: Dictionary<RouteRecord> = oldNameMap || Object.create(null)\n\n  routes.forEach(route => {\n    addRouteRecord(pathList, pathMap, nameMap, route)\n  })\n\n  for (let i = 0, l = pathList.length; i < l; i++) {\n    if (pathList[i] === '*') {\n      pathList.push(pathList.splice(i, 1)[0])\n      l--\n      i--\n    }\n  }\n\n  return {\n    pathList,\n    pathMap,\n    nameMap\n  }\n}\n\n```\n\n`createRouteMap` 函数的目标是把用户的路由配置转换成一张路由映射表，它包含 3 个部分，`pathList` 存储所有的 `path`，`pathMap` 表示一个 `path` 到 `RouteRecord` 的映射关系，而 `nameMap` 表示 `name` 到 `RouteRecord` 的映射关系。那么 `RouteRecord` 到底是什么，先来看一下它的数据结构：\n\n```js\ndeclare type RouteRecord = {\n  path: string;\n  regex: RouteRegExp;\n  components: Dictionary<any>;\n  instances: Dictionary<any>;\n  name: ?string;\n  parent: ?RouteRecord;\n  redirect: ?RedirectOption;\n  matchAs: ?string;\n  beforeEnter: ?NavigationGuard;\n  meta: any;\n  props: boolean | Object | Function | Dictionary<boolean | Object | Function>;\n}\n```\n\n它的创建是通过遍历 `routes` 为每一个 `route` 执行 `addRouteRecord` 方法生成一条记录，来看一下它的定义：\n\n```js\nfunction addRouteRecord (\n  pathList: Array<string>,\n  pathMap: Dictionary<RouteRecord>,\n  nameMap: Dictionary<RouteRecord>,\n  route: RouteConfig,\n  parent?: RouteRecord,\n  matchAs?: string\n) {\n  const { path, name } = route\n  if (process.env.NODE_ENV !== 'production') {\n    assert(path != null, `\"path\" is required in a route configuration.`)\n    assert(\n      typeof route.component !== 'string',\n      `route config \"component\" for path: ${String(path || name)} cannot be a ` +\n      `string id. Use an actual component instead.`\n    )\n  }\n\n  const pathToRegexpOptions: PathToRegexpOptions = route.pathToRegexpOptions || {}\n  const normalizedPath = normalizePath(\n    path,\n    parent,\n    pathToRegexpOptions.strict\n  )\n\n  if (typeof route.caseSensitive === 'boolean') {\n    pathToRegexpOptions.sensitive = route.caseSensitive\n  }\n\n  const record: RouteRecord = {\n    path: normalizedPath,\n    regex: compileRouteRegex(normalizedPath, pathToRegexpOptions),\n    components: route.components || { default: route.component },\n    instances: {},\n    name,\n    parent,\n    matchAs,\n    redirect: route.redirect,\n    beforeEnter: route.beforeEnter,\n    meta: route.meta || {},\n    props: route.props == null\n      ? {}\n      : route.components\n        ? route.props\n        : { default: route.props }\n  }\n\n  if (route.children) {\n    if (process.env.NODE_ENV !== 'production') {\n      if (route.name && !route.redirect && route.children.some(child => /^\\/?$/.test(child.path))) {\n        warn(\n          false,\n          `Named Route '${route.name}' has a default child route. ` +\n          `When navigating to this named route (:to=\"{name: '${route.name}'\"), ` +\n          `the default child route will not be rendered. Remove the name from ` +\n          `this route and use the name of the default child route for named ` +\n          `links instead.`\n        )\n      }\n    }\n    route.children.forEach(child => {\n      const childMatchAs = matchAs\n        ? cleanPath(`${matchAs}/${child.path}`)\n        : undefined\n      addRouteRecord(pathList, pathMap, nameMap, child, record, childMatchAs)\n    })\n  }\n\n  if (route.alias !== undefined) {\n    const aliases = Array.isArray(route.alias)\n      ? route.alias\n      : [route.alias]\n\n    aliases.forEach(alias => {\n      const aliasRoute = {\n        path: alias,\n        children: route.children\n      }\n      addRouteRecord(\n        pathList,\n        pathMap,\n        nameMap,\n        aliasRoute,\n        parent,\n        record.path || '/'\n      )\n    })\n  }\n\n  if (!pathMap[record.path]) {\n    pathList.push(record.path)\n    pathMap[record.path] = record\n  }\n\n  if (name) {\n    if (!nameMap[name]) {\n      nameMap[name] = record\n    } else if (process.env.NODE_ENV !== 'production' && !matchAs) {\n      warn(\n        false,\n        `Duplicate named routes definition: ` +\n        `{ name: \"${name}\", path: \"${record.path}\" }`\n      )\n    }\n  }\n}\n```\n\n我们只看几个关键逻辑，首先创建 `RouteRecord` 的代码如下：\n\n```js\n const record: RouteRecord = {\n  path: normalizedPath,\n  regex: compileRouteRegex(normalizedPath, pathToRegexpOptions),\n  components: route.components || { default: route.component },\n  instances: {},\n  name,\n  parent,\n  matchAs,\n  redirect: route.redirect,\n  beforeEnter: route.beforeEnter,\n  meta: route.meta || {},\n  props: route.props == null\n    ? {}\n    : route.components\n      ? route.props\n      : { default: route.props }\n}\n``` \n\n这里要注意几个点，`path` 是规范化后的路径，它会根据 `parent` 的 `path` 做计算；`regex` 是一个正则表达式的扩展，它利用了`path-to-regexp` 这个工具库，把 `path` 解析成一个正则表达式的扩展，举个例子：\n \n```js\nvar keys = []\nvar re = pathToRegexp('/foo/:bar', keys)\n// re = /^\\/foo\\/([^\\/]+?)\\/?$/i\n// keys = [{ name: 'bar', prefix: '/', delimiter: '/', optional: false, repeat: false, pattern: '[^\\\\/]+?' }]\n```\n \n `components` 是一个对象，通常我们在配置中写的 `component` 实际上这里会被转换成 `{components: route.component}`；`instances` 表示组件的实例，也是一个对象类型；`parent` 表示父的 `RouteRecord`，因为我们配置的时候有时候会配置子路由，所以整个 `RouteRecord` 也就是一个树型结构。\n\n```js\nif (route.children) {\n  // ...\n  route.children.forEach(child => {\n  const childMatchAs = matchAs\n    ? cleanPath(`${matchAs}/${child.path}`)\n    : undefined\n  addRouteRecord(pathList, pathMap, nameMap, child, record, childMatchAs)\n})\n}\n```\n\n如果配置了 `children`，那么递归执行 `addRouteRecord` 方法，并把当前的 `record` 作为 `parent` 传入，通过这样的深度遍历，我们就可以拿到一个 `route` 下的完整记录。\n\n```js\nif (!pathMap[record.path]) {\n  pathList.push(record.path)\n  pathMap[record.path] = record\n}\n```\n\n为 `pathList` 和 `pathMap` 各添加一条记录。\n\n```js\nif (name) {\n  if (!nameMap[name]) {\n    nameMap[name] = record\n  }\n  // ...\n}\n```\n\n如果我们在路由配置中配置了 `name`，则给 `nameMap` 添加一条记录。\n\n由于 `pathList`、`pathMap`、`nameMap` 都是引用类型，所以在遍历整个 `routes` 过程中去执行 `addRouteRecord` 方法，会不断给他们添加数据。那么经过整个 `createRouteMap` 方法的执行，我们得到的就是 `pathList`、`pathMap` 和 `nameMap`。其中 `pathList` 是为了记录路由配置中的所有 `path`，而 `pathMap` 和 `nameMap` 都是为了通过 `path` 和 `name` 能快速查到对应的 `RouteRecord`。\n\n再回到 `createMatcher` 函数，接下来就定义了一系列方法，最后返回了一个对象。\n\n```js\nreturn {\n  match,\n  addRoutes\n}\n```\n\n也就是说，`matcher` 是一个对象，它对外暴露了 `match` 和 `addRoutes` 方法。\n \n## addRoutes\n\n`addRoutes` 方法的作用是动态添加路由配置，因为在实际开发中有些场景是不能提前把路由写死的，需要根据一些条件动态添加路由，所以 Vue-Router 也提供了这一接口：\n\n```js\nfunction addRoutes (routes) {\n  createRouteMap(routes, pathList, pathMap, nameMap)\n}\n```\n\n`addRoutes` 的方法十分简单，再次调用 `createRouteMap` 即可，传入新的 `routes` 配置，由于 `pathList`、`pathMap`、`nameMap` 都是引用类型，执行 `addRoutes` 后会修改它们的值。\n\n## match\n\n```js\nfunction match (\n  raw: RawLocation,\n  currentRoute?: Route,\n  redirectedFrom?: Location\n): Route {\n  const location = normalizeLocation(raw, currentRoute, false, router)\n  const { name } = location\n\n  if (name) {\n    const record = nameMap[name]\n    if (process.env.NODE_ENV !== 'production') {\n      warn(record, `Route with name '${name}' does not exist`)\n    }\n    if (!record) return _createRoute(null, location)\n    const paramNames = record.regex.keys\n      .filter(key => !key.optional)\n      .map(key => key.name)\n\n    if (typeof location.params !== 'object') {\n      location.params = {}\n    }\n\n    if (currentRoute && typeof currentRoute.params === 'object') {\n      for (const key in currentRoute.params) {\n        if (!(key in location.params) && paramNames.indexOf(key) > -1) {\n          location.params[key] = currentRoute.params[key]\n        }\n      }\n    }\n\n    if (record) {\n      location.path = fillParams(record.path, location.params, `named route \"${name}\"`)\n      return _createRoute(record, location, redirectedFrom)\n    }\n  } else if (location.path) {\n    location.params = {}\n    for (let i = 0; i < pathList.length; i++) {\n      const path = pathList[i]\n      const record = pathMap[path]\n      if (matchRoute(record.regex, location.path, location.params)) {\n        return _createRoute(record, location, redirectedFrom)\n      }\n    }\n  }\n  \n  return _createRoute(null, location)\n}\n```\n\n`match` 方法接收 3 个参数，其中 `raw` 是 `RawLocation` 类型，它可以是一个 `url` 字符串，也可以是一个 `Location` 对象；`currentRoute` 是 `Route` 类型，它表示当前的路径；`redirectedFrom` 和重定向相关，这里先忽略。`match` 方法返回的是一个路径，它的作用是根据传入的 `raw` 和当前的路径 `currentRoute` 计算出一个新的路径并返回。\n \n 首先执行了 `normalizeLocation`，它的定义在 `src/util/location.js` 中：\n \n```js\nexport function normalizeLocation (\n  raw: RawLocation,\n  current: ?Route,\n  append: ?boolean,\n  router: ?VueRouter\n): Location {\n  let next: Location = typeof raw === 'string' ? { path: raw } : raw\n  if (next.name || next._normalized) {\n    return next\n  }\n\n  if (!next.path && next.params && current) {\n    next = assign({}, next)\n    next._normalized = true\n    const params: any = assign(assign({}, current.params), next.params)\n    if (current.name) {\n      next.name = current.name\n      next.params = params\n    } else if (current.matched.length) {\n      const rawPath = current.matched[current.matched.length - 1].path\n      next.path = fillParams(rawPath, params, `path ${current.path}`)\n    } else if (process.env.NODE_ENV !== 'production') {\n      warn(false, `relative params navigation requires a current route.`)\n    }\n    return next\n  }\n\n  const parsedPath = parsePath(next.path || '')\n  const basePath = (current && current.path) || '/'\n  const path = parsedPath.path\n    ? resolvePath(parsedPath.path, basePath, append || next.append)\n    : basePath\n\n  const query = resolveQuery(\n    parsedPath.query,\n    next.query,\n    router && router.options.parseQuery\n  )\n\n  let hash = next.hash || parsedPath.hash\n  if (hash && hash.charAt(0) !== '#') {\n    hash = `#${hash}`\n  }\n\n  return {\n    _normalized: true,\n    path,\n    query,\n    hash\n  }\n}\n```\n\n`normalizeLocation` 方法的作用是根据 `raw`，`current` 计算出新的 `location`，它主要处理了 `raw` 的两种情况，一种是有 `params` 且没有 `path`，一种是有 `path` 的，对于第一种情况，如果 `current` 有 `name`，则计算出的 `location` 也有 `name`。\n \n 计算出新的 `location` 后，对 `location` 的 `name` 和 `path` 的两种情况做了处理。\n \n - `name`\n \n有 `name` 的情况下就根据 `nameMap` 匹配到 `record`，它就是一个 `RouterRecord` 对象，如果 `record` 不存在，则匹配失败，返回一个空路径；然后拿到 `record` 对应的 `paramNames`，再对比 `currentRoute` 中的 `params`，把交集部分的 `params` 添加到 `location` 中，然后在通过 `fillParams` 方法根据 `record.path` 和 `location.path` 计算出 `location.path`，最后调用 `_createRoute(record, location, redirectedFrom)` 去生成一条新路径，该方法我们之后会介绍。\n\n- `path`\n\n通过 `name` 我们可以很快的找到 `record`，但是通过 `path` 并不能，因为我们计算后的 `location.path` 是一个真实路径，而 `record` 中的 `path` 可能会有 `param`，因此需要对所有的 `pathList` 做顺序遍历， 然后通过 `matchRoute` 方法根据 `record.regex`、`location.path`、`location.params` 匹配，如果匹配到则也通过 `_createRoute(record, location, redirectedFrom)` 去生成一条新路径。因为是顺序遍历，所以我们书写路由配置要注意路径的顺序，因为写在前面的会优先尝试匹配。\n \n最后我们来看一下 `_createRoute` 的实现：\n\n```js\nfunction _createRoute (\n  record: ?RouteRecord,\n  location: Location,\n  redirectedFrom?: Location\n): Route {\n  if (record && record.redirect) {\n    return redirect(record, redirectedFrom || location)\n  }\n  if (record && record.matchAs) {\n    return alias(record, location, record.matchAs)\n  }\n  return createRoute(record, location, redirectedFrom, router)\n}\n```\n\n我们先不考虑 `record.redirect` 和 `record.matchAs` 的情况，最终会调用 `createRoute` 方法，它的定义在 `src/uitl/route.js` 中：\n \n```js\nexport function createRoute (\n  record: ?RouteRecord,\n  location: Location,\n  redirectedFrom?: ?Location,\n  router?: VueRouter\n): Route {\n  const stringifyQuery = router && router.options.stringifyQuery\n\n  let query: any = location.query || {}\n  try {\n    query = clone(query)\n  } catch (e) {}\n\n  const route: Route = {\n    name: location.name || (record && record.name),\n    meta: (record && record.meta) || {},\n    path: location.path || '/',\n    hash: location.hash || '',\n    query,\n    params: location.params || {},\n    fullPath: getFullPath(location, stringifyQuery),\n    matched: record ? formatMatch(record) : []\n  }\n  if (redirectedFrom) {\n    route.redirectedFrom = getFullPath(redirectedFrom, stringifyQuery)\n  }\n  return Object.freeze(route)\n}\n```\n\n`createRoute` 可以根据 `record` 和 `location` 创建出来，最终返回的是一条 `Route` 路径，我们之前也介绍过它的数据结构。在 Vue-Router 中，所有的 `Route` 最终都会通过 `createRoute` 函数创建，并且它最后是不可以被外部修改的。`Route` 对象中有一个非常重要属性是 `matched`，它通过 `formatMatch(record)` 计算而来：\n\n```js\nfunction formatMatch (record: ?RouteRecord): Array<RouteRecord> {\n  const res = []\n  while (record) {\n    res.unshift(record)\n    record = record.parent\n  }\n  return res\n}\n```\n\n可以看它是通过 `record` 循环向上找 `parent`，直到找到最外层，并把所有的 `record` 都 push 到一个数组中，最终返回的就是 `record` 的数组，它记录了一条线路上的所有 `record`。`matched` 属性非常有用，它为之后渲染组件提供了依据。\n\n## 总结\n\n那么到此，`matcher` 相关的主流程的分析就结束了，我们了解了 `Location`、`Route`、`RouteRecord` 等概念。并通过 `matcher` 的 `match` 方法，我们会找到匹配的路径 `Route`，这个对 `Route` 的切换，组件的渲染都有非常重要的指导意义。下一节我们会回到 `transitionTo` 方法，看一看路径的切换都做了哪些事情。"
  },
  {
    "path": "docs/v2/vue-router/router.md",
    "content": "# VueRouter 对象\n\nVueRouter 的实现是一个类，我们先对它做一个简单地分析，它的定义在 `src/index.js` 中：\n\n```js\nexport default class VueRouter {\n  static install: () => void;\n  static version: string;\n\n  app: any;\n  apps: Array<any>;\n  ready: boolean;\n  readyCbs: Array<Function>;\n  options: RouterOptions;\n  mode: string;\n  history: HashHistory | HTML5History | AbstractHistory;\n  matcher: Matcher;\n  fallback: boolean;\n  beforeHooks: Array<?NavigationGuard>;\n  resolveHooks: Array<?NavigationGuard>;\n  afterHooks: Array<?AfterNavigationHook>;\n\n  constructor (options: RouterOptions = {}) {\n    this.app = null\n    this.apps = []\n    this.options = options\n    this.beforeHooks = []\n    this.resolveHooks = []\n    this.afterHooks = []\n    this.matcher = createMatcher(options.routes || [], this)\n\n    let mode = options.mode || 'hash'\n    this.fallback = mode === 'history' && !supportsPushState && options.fallback !== false\n    if (this.fallback) {\n      mode = 'hash'\n    }\n    if (!inBrowser) {\n      mode = 'abstract'\n    }\n    this.mode = mode\n\n    switch (mode) {\n      case 'history':\n        this.history = new HTML5History(this, options.base)\n        break\n      case 'hash':\n        this.history = new HashHistory(this, options.base, this.fallback)\n        break\n      case 'abstract':\n        this.history = new AbstractHistory(this, options.base)\n        break\n      default:\n        if (process.env.NODE_ENV !== 'production') {\n          assert(false, `invalid mode: ${mode}`)\n        }\n    }\n  }\n\n  match (\n    raw: RawLocation,\n    current?: Route,\n    redirectedFrom?: Location\n  ): Route {\n    return this.matcher.match(raw, current, redirectedFrom)\n  }\n\n  get currentRoute (): ?Route {\n    return this.history && this.history.current\n  }\n\n  init (app: any) {\n    process.env.NODE_ENV !== 'production' && assert(\n      install.installed,\n      `not installed. Make sure to call \\`Vue.use(VueRouter)\\` ` +\n      `before creating root instance.`\n    )\n\n    this.apps.push(app)\n\n    if (this.app) {\n      return\n    }\n\n    this.app = app\n\n    const history = this.history\n\n    if (history instanceof HTML5History) {\n      history.transitionTo(history.getCurrentLocation())\n    } else if (history instanceof HashHistory) {\n      const setupHashListener = () => {\n        history.setupListeners()\n      }\n      history.transitionTo(\n        history.getCurrentLocation(),\n        setupHashListener,\n        setupHashListener\n      )\n    }\n\n    history.listen(route => {\n      this.apps.forEach((app) => {\n        app._route = route\n      })\n    })\n  }\n\n  beforeEach (fn: Function): Function {\n    return registerHook(this.beforeHooks, fn)\n  }\n\n  beforeResolve (fn: Function): Function {\n    return registerHook(this.resolveHooks, fn)\n  }\n\n  afterEach (fn: Function): Function {\n    return registerHook(this.afterHooks, fn)\n  }\n\n  onReady (cb: Function, errorCb?: Function) {\n    this.history.onReady(cb, errorCb)\n  }\n\n  onError (errorCb: Function) {\n    this.history.onError(errorCb)\n  }\n\n  push (location: RawLocation, onComplete?: Function, onAbort?: Function) {\n    this.history.push(location, onComplete, onAbort)\n  }\n\n  replace (location: RawLocation, onComplete?: Function, onAbort?: Function) {\n    this.history.replace(location, onComplete, onAbort)\n  }\n\n  go (n: number) {\n    this.history.go(n)\n  }\n\n  back () {\n    this.go(-1)\n  }\n\n  forward () {\n    this.go(1)\n  }\n\n  getMatchedComponents (to?: RawLocation | Route): Array<any> {\n    const route: any = to\n      ? to.matched\n        ? to\n        : this.resolve(to).route\n      : this.currentRoute\n    if (!route) {\n      return []\n    }\n    return [].concat.apply([], route.matched.map(m => {\n      return Object.keys(m.components).map(key => {\n        return m.components[key]\n      })\n    }))\n  }\n\n  resolve (\n    to: RawLocation,\n    current?: Route,\n    append?: boolean\n  ): {\n    location: Location,\n    route: Route,\n    href: string,\n    normalizedTo: Location,\n    resolved: Route\n  } {\n    const location = normalizeLocation(\n      to,\n      current || this.history.current,\n      append,\n      this\n    )\n    const route = this.match(location, current)\n    const fullPath = route.redirectedFrom || route.fullPath\n    const base = this.history.base\n    const href = createHref(base, fullPath, this.mode)\n    return {\n      location,\n      route,\n      href,\n      normalizedTo: location,\n      resolved: route\n    }\n  }\n\n  addRoutes (routes: Array<RouteConfig>) {\n    this.matcher.addRoutes(routes)\n    if (this.history.current !== START) {\n      this.history.transitionTo(this.history.getCurrentLocation())\n    }\n  }\n}\n```\n\n`VueRouter` 定义了一些属性和方法，我们先从它的构造函数看，当我们执行 `new VueRouter` 的时候做了哪些事情。\n\n```js\nconstructor (options: RouterOptions = {}) {\n  this.app = null\n  this.apps = []\n  this.options = options\n  this.beforeHooks = []\n  this.resolveHooks = []\n  this.afterHooks = []\n  this.matcher = createMatcher(options.routes || [], this)\n\n  let mode = options.mode || 'hash'\n  this.fallback = mode === 'history' && !supportsPushState && options.fallback !== false\n  if (this.fallback) {\n    mode = 'hash'\n  }\n  if (!inBrowser) {\n    mode = 'abstract'\n  }\n  this.mode = mode\n\n  switch (mode) {\n    case 'history':\n      this.history = new HTML5History(this, options.base)\n      break\n    case 'hash':\n      this.history = new HashHistory(this, options.base, this.fallback)\n      break\n    case 'abstract':\n      this.history = new AbstractHistory(this, options.base)\n      break\n    default:\n      if (process.env.NODE_ENV !== 'production') {\n        assert(false, `invalid mode: ${mode}`)\n      }\n  }\n}\n```\n \n构造函数定义了一些属性，其中 `this.app` 表示根 `Vue` 实例，`this.apps` 保存持有 `$options.router` 属性的 `Vue` 实例，`this.options` 保存传入的路由配置，`this.beforeHooks`、\n`this.resolveHooks`、`this.afterHooks` 表示一些钩子函数，我们之后会介绍，`this.matcher` 表示路由匹配器，我们之后会介绍，`this.fallback` 表示在浏览器不支持 `history.pushState` 的情况下，根据传入的 `fallback` 配置参数，决定是否回退到hash模式，`this.mode` 表示路由创建的模式，`this.history` 表示路由历史的具体的实现实例，它是根据 `this.mode` 的不同实现不同，它有 `History` 基类，然后不同的 `history` 实现都是继承 `History`。\n\n实例化 `VueRouter` 后会返回它的实例 `router`，我们在 `new Vue` 的时候会把 `router` 作为配置的属性传入，回顾一下上一节我们讲 `beforeCreate` 混入的时候有这么一段代码：\n\n```js\nbeforeCreate() {\n  if (isDef(this.$options.router)) {\n    // ...\n    this._router = this.$options.router\n    this._router.init(this)\n    // ...\n  }\n}  \n```\n\n所以组件在执行 `beforeCreate` 钩子函数的时候，如果传入了 `router` 实例，都会执行 `router.init` 方法：\n\n```js\ninit (app: any) {\n  process.env.NODE_ENV !== 'production' && assert(\n    install.installed,\n    `not installed. Make sure to call \\`Vue.use(VueRouter)\\` ` +\n    `before creating root instance.`\n  )\n\n  this.apps.push(app)\n\n  if (this.app) {\n    return\n  }\n\n  this.app = app\n\n  const history = this.history\n\n  if (history instanceof HTML5History) {\n    history.transitionTo(history.getCurrentLocation())\n  } else if (history instanceof HashHistory) {\n    const setupHashListener = () => {\n      history.setupListeners()\n    }\n    history.transitionTo(\n      history.getCurrentLocation(),\n      setupHashListener,\n      setupHashListener\n    )\n  }\n\n  history.listen(route => {\n    this.apps.forEach((app) => {\n      app._route = route\n    })\n  })\n}\n```\n\n`init` 的逻辑很简单，它传入的参数是 `Vue` 实例，然后存储到 `this.apps` 中；只有根 `Vue` 实例会保存到 `this.app` 中，并且会拿到当前的 `this.history`，根据它的不同类型来执行不同逻辑，由于我们平时使用 `hash` 路由多一些，所以我们先看这部分逻辑，先定义了 `setupHashListener` 函数，接着执行了 `history.transitionTo` 方法，它是定义在 `History` 基类中，代码在 `src/history/base.js`：\n\n```js\ntransitionTo (location: RawLocation, onComplete?: Function, onAbort?: Function) {\n  const route = this.router.match(location, this.current)\n  // ...\n}\n```\n\n我们先不着急去看 `transitionTo` 的具体实现，先看第一行代码，它调用了 `this.router.match` 函数：\n\n```js\nmatch (\n  raw: RawLocation,\n  current?: Route,\n  redirectedFrom?: Location\n): Route {\n  return this.matcher.match(raw, current, redirectedFrom)\n}\n```\n\n实际上是调用了 `this.matcher.match` 方法去做匹配，所以接下来我们先来了解一下 `matcher` 的相关实现。\n\n## 总结\n\n通过这一节的分析，我们大致对 `VueRouter` 类有了大致了解，知道了它的一些属性和方法，同时了解到在组件的初始化阶段，执行到 `beforeCreate` 钩子函数的时候会执行 `router.init` 方法，然后又会执行 `history.transitionTo` 方法做路由过渡，进而引出了 `matcher` 的概念，接下来我们先研究一下 `matcher` 的相关实现。\n"
  },
  {
    "path": "docs/v2/vue-router/transition-to.md",
    "content": "# 路径切换\n\n`history.transitionTo` 是 Vue-Router 中非常重要的方法，当我们切换路由线路的时候，就会执行到该方法，前一节我们分析了 `matcher` 的相关实现，知道它是如何找到匹配的新线路，那么匹配到新线路后又做了哪些事情，接下来我们来完整分析一下 `transitionTo` 的实现，它的定义在 `src/history/base.js` 中：\n\n```js\ntransitionTo (location: RawLocation, onComplete?: Function, onAbort?: Function) {\n  const route = this.router.match(location, this.current)\n  this.confirmTransition(route, () => {\n    this.updateRoute(route)\n    onComplete && onComplete(route)\n    this.ensureURL()\n\n    if (!this.ready) {\n      this.ready = true\n      this.readyCbs.forEach(cb => { cb(route) })\n    }\n  }, err => {\n    if (onAbort) {\n      onAbort(err)\n    }\n    if (err && !this.ready) {\n      this.ready = true\n      this.readyErrorCbs.forEach(cb => { cb(err) })\n    }\n  })\n}\n```\n\n`transitionTo` 首先根据目标 `location` 和当前路径 `this.current` 执行 `this.router.match` 方法去匹配到目标的路径。这里 `this.current` 是 `history` 维护的当前路径，它的初始值是在 `history` 的构造函数中初始化的：\n\n```js\nthis.current = START\n```\n\n`START` 的定义在 `src/util/route.js` 中：\n\n```js\nexport const START = createRoute(null, {\n  path: '/'\n})\n```\n\n这样就创建了一个初始的 `Route`，而 `transitionTo` 实际上也就是在切换 `this.current`，稍后我们会看到。\n\n拿到新的路径后，那么接下来就会执行 `confirmTransition` 方法去做真正的切换，由于这个过程可能有一些异步的操作（如异步组件），所以整个 `confirmTransition` API 设计成带有成功回调函数和失败回调函数，先来看一下它的定义：\n\n```js\nconfirmTransition (route: Route, onComplete: Function, onAbort?: Function) {\n  const current = this.current\n  const abort = err => {\n    if (isError(err)) {\n      if (this.errorCbs.length) {\n        this.errorCbs.forEach(cb => { cb(err) })\n      } else {\n        warn(false, 'uncaught error during route navigation:')\n        console.error(err)\n      }\n    }\n    onAbort && onAbort(err)\n  }\n  if (\n    isSameRoute(route, current) &&\n    route.matched.length === current.matched.length\n  ) {\n    this.ensureURL()\n    return abort()\n  }\n\n  const {\n    updated,\n    deactivated,\n    activated\n  } = resolveQueue(this.current.matched, route.matched)\n\n  const queue: Array<?NavigationGuard> = [].concat(\n    extractLeaveGuards(deactivated),\n    this.router.beforeHooks,\n    extractUpdateHooks(updated),\n    activated.map(m => m.beforeEnter),\n    resolveAsyncComponents(activated)\n  )\n\n  this.pending = route\n  const iterator = (hook: NavigationGuard, next) => {\n    if (this.pending !== route) {\n      return abort()\n    }\n    try {\n      hook(route, current, (to: any) => {\n        if (to === false || isError(to)) {\n          this.ensureURL(true)\n          abort(to)\n        } else if (\n          typeof to === 'string' ||\n          (typeof to === 'object' && (\n            typeof to.path === 'string' ||\n            typeof to.name === 'string'\n          ))\n        ) {\n          abort()\n          if (typeof to === 'object' && to.replace) {\n            this.replace(to)\n          } else {\n            this.push(to)\n          }\n        } else {\n          next(to)\n        }\n      })\n    } catch (e) {\n      abort(e)\n    }\n  }\n\n  runQueue(queue, iterator, () => {\n    const postEnterCbs = []\n    const isValid = () => this.current === route\n    const enterGuards = extractEnterGuards(activated, postEnterCbs, isValid)\n    const queue = enterGuards.concat(this.router.resolveHooks)\n    runQueue(queue, iterator, () => {\n      if (this.pending !== route) {\n        return abort()\n      }\n      this.pending = null\n      onComplete(route)\n      if (this.router.app) {\n        this.router.app.$nextTick(() => {\n          postEnterCbs.forEach(cb => { cb() })\n        })\n      }\n    })\n  })\n}\n```\n\n首先定义了 `abort` 函数，然后判断如果满足计算后的 `route` 和 `current` 是相同路径的话，则直接调用 `this.ensureUrl` 和 `abort`，`ensureUrl` 这个函数我们之后会介绍。\n\n接着又根据 `current.matched` 和 `route.matched` 执行了 `resolveQueue` 方法解析出 3 个队列：\n\n```js\nfunction resolveQueue (\n  current: Array<RouteRecord>,\n  next: Array<RouteRecord>\n): {\n  updated: Array<RouteRecord>,\n  activated: Array<RouteRecord>,\n  deactivated: Array<RouteRecord>\n} {\n  let i\n  const max = Math.max(current.length, next.length)\n  for (i = 0; i < max; i++) {\n    if (current[i] !== next[i]) {\n      break\n    }\n  }\n  return {\n    updated: next.slice(0, i),\n    activated: next.slice(i),\n    deactivated: current.slice(i)\n  }\n}\n```\n\n因为 `route.matched` 是一个 `RouteRecord` 的数组，由于路径是由 `current` 变向 `route`，那么就遍历对比 2 边的 `RouteRecord`，找到一个不一样的位置 `i`，那么 `next` 中从 0 到 `i` 的 `RouteRecord` 是两边都一样，则为 `updated` 的部分；从 `i` 到最后的 `RouteRecord` 是 `next` 独有的，为 `activated` 的部分；而 `current` 中从 `i` 到最后的 `RouteRecord` 则没有了，为 `deactivated` 的部分。\n\n拿到 `updated`、`activated`、`deactivated` 3 个 `ReouteRecord` 数组后，接下来就是路径变换后的一个重要部分，执行一系列的钩子函数。\n\n## 导航守卫\n\n官方的说法叫导航守卫，实际上就是发生在路由路径切换的时候，执行的一系列钩子函数。\n\n我们先从整体上看一下这些钩子函数执行的逻辑，首先构造一个队列 `queue`，它实际上是一个数组；然后再定义一个迭代器函数 `iterator`；最后再执行 `runQueue` 方法来执行这个队列。我们先来看一下 `runQueue` 的定义，在 `src/util/async.js` 中：\n\n```js\nexport function runQueue (queue: Array<?NavigationGuard>, fn: Function, cb: Function) {\n  const step = index => { \n    if (index >= queue.length) {\n      cb()\n    } else {\n      if (queue[index]) {\n        fn(queue[index], () => {\n          step(index + 1)\n        })\n      } else {\n        step(index + 1)\n      }\n    }\n  }\n  step(0)\n}\n```\n\n这是一个非常经典的异步函数队列化执行的模式， `queue` 是一个 `NavigationGuard` 类型的数组，我们定义了 `step` 函数，每次根据 `index` 从 `queue` 中取一个 `guard`，然后执行 `fn` 函数，并且把 `guard` 作为参数传入，第二个参数是一个函数，当这个函数执行的时候再递归执行 `step` 函数，前进到下一个，注意这里的 `fn` 就是我们刚才的 `iterator` 函数，那么我们再回到 `iterator` 函数的定义：\n\n```js\nconst iterator = (hook: NavigationGuard, next) => {\n  if (this.pending !== route) {\n    return abort()\n  }\n  try {\n    hook(route, current, (to: any) => {\n      if (to === false || isError(to)) {\n        this.ensureURL(true)\n        abort(to)\n      } else if (\n        typeof to === 'string' ||\n        (typeof to === 'object' && (\n          typeof to.path === 'string' ||\n          typeof to.name === 'string'\n        ))\n      ) {\n        abort()\n        if (typeof to === 'object' && to.replace) {\n          this.replace(to)\n        } else {\n          this.push(to)\n        }\n      } else {\n        next(to)\n      }\n    })\n  } catch (e) {\n    abort(e)\n  }\n}\n```\n\n`iterator` 函数逻辑很简单，它就是去执行每一个 导航守卫 `hook`，并传入 `route`、`current` 和匿名函数，这些参数对应文档中的 `to`、`from`、`next`，当执行了匿名函数，会根据一些条件执行 `abort` 或 `next`，只有执行 `next` 的时候，才会前进到下一个导航守卫钩子函数中，这也就是为什么官方文档会说只有执行 `next` 方法来 `resolve` 这个钩子函数。\n\n那么最后我们来看 `queue` 是怎么构造的：\n\n```js\nconst queue: Array<?NavigationGuard> = [].concat(\n  extractLeaveGuards(deactivated),\n  this.router.beforeHooks,\n  extractUpdateHooks(updated),\n  activated.map(m => m.beforeEnter),\n  resolveAsyncComponents(activated)\n)\n```\n\n按照顺序如下：\n\n1. 在失活的组件里调用离开守卫。\n\n2. 调用全局的 `beforeEach` 守卫。\n\n3. 在重用的组件里调用 `beforeRouteUpdate` 守卫 \n\n4. 在激活的路由配置里调用 `beforeEnter`。\n\n5. 解析异步路由组件。\n\n接下来我们来分别介绍这 5 步的实现。\n\n第一步是通过执行 `extractLeaveGuards(deactivated)`，先来看一下 `extractLeaveGuards` 的定义：\n\n```js\nfunction extractLeaveGuards (deactivated: Array<RouteRecord>): Array<?Function> {\n  return extractGuards(deactivated, 'beforeRouteLeave', bindGuard, true)\n}\n```\n\n它内部调用了 `extractGuards` 的通用方法，可以从 `RouteRecord` 数组中提取各个阶段的守卫：\n\n```js\nfunction extractGuards (\n  records: Array<RouteRecord>,\n  name: string,\n  bind: Function,\n  reverse?: boolean\n): Array<?Function> {\n  const guards = flatMapComponents(records, (def, instance, match, key) => {\n    const guard = extractGuard(def, name)\n    if (guard) {\n      return Array.isArray(guard)\n        ? guard.map(guard => bind(guard, instance, match, key))\n        : bind(guard, instance, match, key)\n    }\n  })\n  return flatten(reverse ? guards.reverse() : guards)\n}\n```\n\n这里用到了 `flatMapComponents` 方法去从 `records` 中获取所有的导航，它的定义在 `src/util/resolve-components.js` 中：\n\n```js\nexport function flatMapComponents (\n  matched: Array<RouteRecord>,\n  fn: Function\n): Array<?Function> {\n  return flatten(matched.map(m => {\n    return Object.keys(m.components).map(key => fn(\n      m.components[key],\n      m.instances[key],\n      m, key\n    ))\n  }))\n}\n\nexport function flatten (arr: Array<any>): Array<any> {\n  return Array.prototype.concat.apply([], arr)\n}\n```\n\n`flatMapComponents` 的作用就是返回一个数组，数组的元素是从 `matched` 里获取到所有组件的 `key`，然后返回 `fn` 函数执行的结果，`flatten` 作用是把二维数组拍平成一维数组。\n\n那么对于 `extractGuards` 中 `flatMapComponents` 的调用，执行每个 `fn` 的时候，通过 `extractGuard(def, name)` 获取到组件中对应 `name` 的导航守卫：\n \n```js\nfunction extractGuard (\n  def: Object | Function,\n  key: string\n): NavigationGuard | Array<NavigationGuard> {\n  if (typeof def !== 'function') {\n    def = _Vue.extend(def)\n  }\n  return def.options[key]\n}\n```\n\n获取到 `guard` 后，还会调用 `bind` 方法把组件的实例 `instance` 作为函数执行的上下文绑定到 `guard` 上，`bind ` 方法的对应的是 `bindGuard`：\n\n```js\nfunction bindGuard (guard: NavigationGuard, instance: ?_Vue): ?NavigationGuard {\n  if (instance) {\n    return function boundRouteGuard () {\n      return guard.apply(instance, arguments)\n    }\n  }\n}\n```\n\n那么对于 `extractLeaveGuards(deactivated)` 而言，获取到的就是所有失活组件中定义的 `beforeRouteLeave` 钩子函数。\n\n第二步是 `this.router.beforeHooks`，在我们的 `VueRouter` 类中定义了 `beforeEach` 方法，在 `src/index.js` 中：\n\n```js\nbeforeEach (fn: Function): Function {\n  return registerHook(this.beforeHooks, fn)\n}\n\nfunction registerHook (list: Array<any>, fn: Function): Function {\n  list.push(fn)\n  return () => {\n    const i = list.indexOf(fn)\n    if (i > -1) list.splice(i, 1)\n  }\n}\n```\n\n当用户使用 `router.beforeEach` 注册了一个全局守卫，就会往 `router.beforeHooks` 添加一个钩子函数，这样 `this.router.beforeHooks` 获取的就是用户注册的全局 `beforeEach` 守卫。\n\n第三步执行了 `extractUpdateHooks(updated)`，来看一下 `extractUpdateHooks` 的定义：\n\n```js\nfunction extractUpdateHooks (updated: Array<RouteRecord>): Array<?Function> {\n  return extractGuards(updated, 'beforeRouteUpdate', bindGuard)\n}\n```\n和 `extractLeaveGuards(deactivated)` 类似，`extractUpdateHooks(updated)` 获取到的就是所有重用的组件中定义的 `beforeRouteUpdate` 钩子函数。\n\n第四步是执行 `activated.map(m => m.beforeEnter)`，获取的是在激活的路由配置中定义的 `beforeEnter` 函数。\n\n第五步是执行 `resolveAsyncComponents(activated)` 解析异步组件，先来看一下 `resolveAsyncComponents` 的定义，在 `src/util/resolve-components.js` 中：\n\n```js\nexport function resolveAsyncComponents (matched: Array<RouteRecord>): Function {\n  return (to, from, next) => {\n    let hasAsync = false\n    let pending = 0\n    let error = null\n\n    flatMapComponents(matched, (def, _, match, key) => {\n      if (typeof def === 'function' && def.cid === undefined) {\n        hasAsync = true\n        pending++\n\n        const resolve = once(resolvedDef => {\n          if (isESModule(resolvedDef)) {\n            resolvedDef = resolvedDef.default\n          }\n          def.resolved = typeof resolvedDef === 'function'\n            ? resolvedDef\n            : _Vue.extend(resolvedDef)\n          match.components[key] = resolvedDef\n          pending--\n          if (pending <= 0) {\n            next()\n          }\n        })\n\n        const reject = once(reason => {\n          const msg = `Failed to resolve async component ${key}: ${reason}`\n          process.env.NODE_ENV !== 'production' && warn(false, msg)\n          if (!error) {\n            error = isError(reason)\n              ? reason\n              : new Error(msg)\n            next(error)\n          }\n        })\n\n        let res\n        try {\n          res = def(resolve, reject)\n        } catch (e) {\n          reject(e)\n        }\n        if (res) {\n          if (typeof res.then === 'function') {\n            res.then(resolve, reject)\n          } else {\n            const comp = res.component\n            if (comp && typeof comp.then === 'function') {\n              comp.then(resolve, reject)\n            }\n          }\n        }\n      }\n    })\n\n    if (!hasAsync) next()\n  }\n}\n```\n\n`resolveAsyncComponents` 返回的是一个导航守卫函数，有标准的 `to`、`from`、`next` 参数。它的内部实现很简单，利用了 `flatMapComponents` 方法从 `matched` 中获取到每个组件的定义，判断如果是异步组件，则执行异步组件加载逻辑，这块和我们之前分析 `Vue` 加载异步组件很类似，加载成功后会执行 ` match.components[key] = resolvedDef` 把解析好的异步组件放到对应的 `components` 上，并且执行 `next` 函数。\n\n这样在 `resolveAsyncComponents(activated)` 解析完所有激活的异步组件后，我们就可以拿到这一次所有激活的组件。这样我们在做完这 5 步后又做了一些事情：\n\n```js\nrunQueue(queue, iterator, () => {\n  const postEnterCbs = []\n  const isValid = () => this.current === route\n  const enterGuards = extractEnterGuards(activated, postEnterCbs, isValid)\n  const queue = enterGuards.concat(this.router.resolveHooks)\n  runQueue(queue, iterator, () => {\n    if (this.pending !== route) {\n      return abort()\n    }\n    this.pending = null\n    onComplete(route)\n    if (this.router.app) {\n      this.router.app.$nextTick(() => {\n        postEnterCbs.forEach(cb => { cb() })\n      })\n    }\n  })\n})\n```\n\n6. 在被激活的组件里调用 `beforeRouteEnter`。\n\n7. 调用全局的 `beforeResolve` 守卫。\n\n8. 调用全局的 `afterEach` 钩子。\n\n对于第六步有这些相关的逻辑：\n\n```js\nconst postEnterCbs = []\nconst isValid = () => this.current === route\nconst enterGuards = extractEnterGuards(activated, postEnterCbs, isValid)\n\nfunction extractEnterGuards (\n  activated: Array<RouteRecord>,\n  cbs: Array<Function>,\n  isValid: () => boolean\n): Array<?Function> {\n  return extractGuards(activated, 'beforeRouteEnter', (guard, _, match, key) => {\n    return bindEnterGuard(guard, match, key, cbs, isValid)\n  })\n}\n\nfunction bindEnterGuard (\n  guard: NavigationGuard,\n  match: RouteRecord,\n  key: string,\n  cbs: Array<Function>,\n  isValid: () => boolean\n): NavigationGuard {\n  return function routeEnterGuard (to, from, next) {\n    return guard(to, from, cb => {\n      next(cb)\n      if (typeof cb === 'function') {\n        cbs.push(() => {\n          poll(cb, match.instances, key, isValid)\n        })\n      }\n    })\n  }\n}\n\nfunction poll (\n  cb: any,\n  instances: Object,\n  key: string,\n  isValid: () => boolean\n) {\n  if (instances[key]) {\n    cb(instances[key])\n  } else if (isValid()) {\n    setTimeout(() => {\n      poll(cb, instances, key, isValid)\n    }, 16)\n  }\n}\n```\n\n`extractEnterGuards` 函数的实现也是利用了 `extractGuards` 方法提取组件中的 `beforeRouteEnter` 导航钩子函数，和之前不同的是 `bind` 方法的不同。文档中特意强调了 `beforeRouteEnter` 钩子函数中是拿不到组件实例的，因为当守卫执行前，组件实例还没被创建，但是我们可以通过传一个回调给 `next` 来访问组件实例。在导航被确认的时候执行回调，并且把组件实例作为回调方法的参数：\n\n```js\nbeforeRouteEnter (to, from, next) {\n  next(vm => {\n    // 通过 `vm` 访问组件实例\n  })\n}\n```\n\n来看一下这是怎么实现的。\n\n在 `bindEnterGuard` 函数中，返回的是 `routeEnterGuard` 函数，所以在执行 `iterator` 中的 `hook` 函数的时候，就相当于执行 `routeEnterGuard` 函数，那么就会执行我们定义的导航守卫 `guard` 函数，并且当这个回调函数执行的时候，首先执行 `next` 函数 `rersolve` 当前导航钩子，然后把回调函数的参数，它也是一个回调函数用 `cbs` 收集起来，其实就是收集到外面定义的 `postEnterCbs` 中，然后在最后会执行：\n\n```js\nif (this.router.app) {\n  this.router.app.$nextTick(() => {\n    postEnterCbs.forEach(cb => { cb() })\n  })\n}\n```\n在根路由组件重新渲染后，遍历 `postEnterCbs` 执行回调，每一个回调执行的时候，其实是执行 ` poll(cb, match.instances, key, isValid)` 方法，因为考虑到一些了路由组件被套 `transition` 組件在一些缓动模式下不一定能拿到实例，所以用一个轮询方法不断去判断，直到能获取到组件实例，再去调用 `cb`，并把组件实例作为参数传入，这就是我们在回调函数中能拿到组件实例的原因。\n\n第七步是获取 `this.router.resolveHooks`，这个和\n`this.router.beforeHooks` 的获取类似，在我们的 `VueRouter` 类中定义了 `beforeResolve` 方法：\n\n```js\nbeforeResolve (fn: Function): Function {\n  return registerHook(this.resolveHooks, fn)\n}\n```\n\n当用户使用 `router.beforeResolve` 注册了一个全局守卫，就会往 `router.resolveHooks` 添加一个钩子函数，这样 `this.router.resolveHooks` 获取的就是用户注册的全局 `beforeResolve` 守卫。\n\n第八步是在最后执行了 `onComplete(route)` 后，会执行 `this.updateRoute(route)` 方法：\n\n```js\nupdateRoute (route: Route) {\n  const prev = this.current\n  this.current = route\n  this.cb && this.cb(route)\n  this.router.afterHooks.forEach(hook => {\n    hook && hook(route, prev)\n  })\n}\n```\n同样在我们的 `VueRouter` 类中定义了 `afterEach` 方法：\n\n```js\nafterEach (fn: Function): Function {\n  return registerHook(this.afterHooks, fn)\n}\n```\n\n当用户使用 `router.afterEach` 注册了一个全局守卫，就会往 `router.afterHooks` 添加一个钩子函数，这样 `this.router.afterHooks` 获取的就是用户注册的全局 `afterHooks` 守卫。\n\n那么至此我们把所有导航守卫的执行分析完毕了，我们知道路由切换除了执行这些钩子函数，从表象上有 2 个地方会发生变化，一个是 url 发生变化，一个是组件发生变化。接下来我们分别介绍这两块的实现原理。\n\n## url\n\n当我们点击 `router-link` 的时候，实际上最终会执行 `router.push`，如下：\n\n```js\npush (location: RawLocation, onComplete?: Function, onAbort?: Function) {\n  this.history.push(location, onComplete, onAbort)\n}\n```\n\n`this.history.push` 函数，这个函数是子类实现的，不同模式下该函数的实现略有不同，我们来看一下平时使用比较多的 `hash` 模式该函数的实现，在 `src/history/hash.js` 中：\n\n```js\npush (location: RawLocation, onComplete?: Function, onAbort?: Function) {\n  const { current: fromRoute } = this\n  this.transitionTo(location, route => {\n    pushHash(route.fullPath)\n    handleScroll(this.router, route, fromRoute, false)\n    onComplete && onComplete(route)\n  }, onAbort)\n}\n\n```\n\n`push` 函数会先执行 `this.transitionTo` 做路径切换，在切换完成的回调函数中，执行 `pushHash` 函数：\n\n```js\nfunction pushHash (path) {\n  if (supportsPushState) {\n    pushState(getUrl(path))\n  } else {\n    window.location.hash = path\n  }\n}\n```\n\n`supportsPushState` 的定义在 `src/util/push-state.js` 中：\n\n```js\nexport const supportsPushState = inBrowser && (function () {\n  const ua = window.navigator.userAgent\n\n  if (\n    (ua.indexOf('Android 2.') !== -1 || ua.indexOf('Android 4.0') !== -1) &&\n    ua.indexOf('Mobile Safari') !== -1 &&\n    ua.indexOf('Chrome') === -1 &&\n    ua.indexOf('Windows Phone') === -1\n  ) {\n    return false\n  }\n\n  return window.history && 'pushState' in window.history\n})()\n```\n\n如果支持的话，则获取当前完整的 `url`，执行 `pushState` 方法：\n\n```js\nexport function pushState (url?: string, replace?: boolean) {\n  saveScrollPosition()\n  const history = window.history\n  try {\n    if (replace) {\n      history.replaceState({ key: _key }, '', url)\n    } else {\n      _key = genKey()\n      history.pushState({ key: _key }, '', url)\n    }\n  } catch (e) {\n    window.location[replace ? 'replace' : 'assign'](url)\n  }\n}\n```\n\n`pushState` 会调用浏览器原生的 `history` 的 `pushState` 接口或者 `replaceState` 接口，更新浏览器的 url 地址，并把当前 url 压入历史栈中。\n\n然后在 `history` 的初始化中，会设置一个监听器，监听历史栈的变化：\n\n```js\nsetupListeners () {\n  const router = this.router\n  const expectScroll = router.options.scrollBehavior\n  const supportsScroll = supportsPushState && expectScroll\n\n  if (supportsScroll) {\n    setupScroll()\n  }\n\n  window.addEventListener(supportsPushState ? 'popstate' : 'hashchange', () => {\n    const current = this.current\n    if (!ensureSlash()) {\n      return\n    }\n    this.transitionTo(getHash(), route => {\n      if (supportsScroll) {\n        handleScroll(this.router, route, current, true)\n      }\n      if (!supportsPushState) {\n        replaceHash(route.fullPath)\n      }\n    })\n  })\n}\n```\n\n当点击浏览器返回按钮的时候，如果已经有 url 被压入历史栈，则会触发 `popstate` 事件，然后拿到当前要跳转的 `hash`，执行 `transtionTo` 方法做一次路径转换。\n\n同学们在使用 Vue-Router 开发项目的时候，打开调试页面 `http://localhost:8080` 后会自动把 url 修改为 `http://localhost:8080/#/`，这是怎么做到呢？原来在实例化 `HashHistory` 的时候，构造函数会执行 `ensureSlash()` 方法：\n\n```js\nfunction ensureSlash (): boolean {\n  const path = getHash()\n  if (path.charAt(0) === '/') {\n    return true\n  }\n  replaceHash('/' + path)\n  return false\n}\n\nexport function getHash (): string {\n  // We can't use window.location.hash here because it's not\n  // consistent across browsers - Firefox will pre-decode it!\n  const href = window.location.href\n  const index = href.indexOf('#')\n  return index === -1 ? '' : href.slice(index + 1)\n}\n\nfunction getUrl (path) {\n  const href = window.location.href\n  const i = href.indexOf('#')\n  const base = i >= 0 ? href.slice(0, i) : href\n  return `${base}#${path}`\n}\n\nfunction replaceHash (path) {\n  if (supportsPushState) {\n    replaceState(getUrl(path))\n  } else {\n    window.location.replace(getUrl(path))\n  }\n}\n\nexport function replaceState (url?: string) {\n  pushState(url, true)\n}\n```\n这个时候 `path` 为空，所以执行 `replaceHash('/' + path)`，然后内部会执行一次 `getUrl`，计算出来的新的 `url` 为 `http://localhost:8080/#/`，最终会执行 `pushState(url, true)`，这就是 url 会改变的原因。\n\n## 组件\n\n路由最终的渲染离不开组件，Vue-Router 内置了 `<router-view>` 组件，它的定义在 `src/components/view.js` 中。\n\n```js\nexport default {\n  name: 'RouterView',\n  functional: true,\n  props: {\n    name: {\n      type: String,\n      default: 'default'\n    }\n  },\n  render (_, { props, children, parent, data }) {\n    data.routerView = true\n   \n    const h = parent.$createElement\n    const name = props.name\n    const route = parent.$route\n    const cache = parent._routerViewCache || (parent._routerViewCache = {})\n\n    let depth = 0\n    let inactive = false\n    while (parent && parent._routerRoot !== parent) {\n      if (parent.$vnode && parent.$vnode.data.routerView) {\n        depth++\n      }\n      if (parent._inactive) {\n        inactive = true\n      }\n      parent = parent.$parent\n    }\n    data.routerViewDepth = depth\n\n    if (inactive) {\n      return h(cache[name], data, children)\n    }\n\n    const matched = route.matched[depth]\n    if (!matched) {\n      cache[name] = null\n      return h()\n    }\n\n    const component = cache[name] = matched.components[name]\n   \n    data.registerRouteInstance = (vm, val) => {     \n      const current = matched.instances[name]\n      if (\n        (val && current !== vm) ||\n        (!val && current === vm)\n      ) {\n        matched.instances[name] = val\n      }\n    }\n    \n    ;(data.hook || (data.hook = {})).prepatch = (_, vnode) => {\n      matched.instances[name] = vnode.componentInstance\n    }\n\n    let propsToPass = data.props = resolveProps(route, matched.props && matched.props[name])\n    if (propsToPass) {\n      propsToPass = data.props = extend({}, propsToPass)\n      const attrs = data.attrs = data.attrs || {}\n      for (const key in propsToPass) {\n        if (!component.props || !(key in component.props)) {\n          attrs[key] = propsToPass[key]\n          delete propsToPass[key]\n        }\n      }\n    }\n\n    return h(component, data, children)\n  }\n}\n```\n\n`<router-view>` 是一个 `functional` 组件，它的渲染也是依赖 `render` 函数，那么 `<router-view>` 具体应该渲染什么组件呢，首先获取当前的路径：\n\n```js\nconst route = parent.$route\n```\n\n我们之前分析过，在 `src/install.js` 中，我们给 Vue 的原型上定义了 `$route`：\n\n```js\nObject.defineProperty(Vue.prototype, '$route', {\n  get () { return this._routerRoot._route }\n})\n```\n\n然后在 `VueRouter` 的实例执行 `router.init` 方法的时候，会执行如下逻辑，定义在 `src/index.js` 中：\n\n```js\nhistory.listen(route => {\n  this.apps.forEach((app) => {\n    app._route = route\n  })\n})\n```\n\n而 `history.listen` 方法定义在 `src/history/base.js` 中：\n\n```js\nlisten (cb: Function) {\n  this.cb = cb\n}\n```\n\n然后在 `updateRoute` 的时候执行 `this.cb`：\n\n```js\nupdateRoute (route: Route) {\n  //. ..\n  this.current = route\n  this.cb && this.cb(route)\n  // ...\n}\n```\n\n也就是我们执行 `transitionTo` 方法最后执行 `updateRoute` 的时候会执行回调，然后会更新 `this.apps` 保存的组件实例的 `_route` 值，`this.apps` 数组保存的实例的特点都是在初始化的时候传入了 `router` 配置项，一般的场景数组只会保存根 Vue 实例，因为我们是在 `new Vue` 传入了 \b`router` 实例。`$route` 是定义在 `Vue.prototype` 上。每个组件实例访问 `$route` 属性，就是访问根实例的 `_route`，也就是当前的路由线路。\n\n`<router-view>` 是支持嵌套的，回到 `render` 函数，其中定义了 `depth` 的概念，它表示 `<router-view>` 嵌套的深度。每个 `<router-view>` 在渲染的时候，执行如下逻辑：\n\n```js\ndata.routerView = true\n// ...\nwhile (parent && parent._routerRoot !== parent) {\n  if (parent.$vnode && parent.$vnode.data.routerView) {\n    depth++\n  }\n  if (parent._inactive) {\n    inactive = true\n  }\n  parent = parent.$parent\n}\n\nconst matched = route.matched[depth]\n// ...\nconst component = cache[name] = matched.components[name]\n```\n\n`parent._routerRoot` 表示的是根 Vue 实例，那么这个循环就是从当前的 `<router-view>` 的父节点向上找，一直找到根 Vue 实例，在这个过程，如果碰到了父节点也是 `<router-view>` 的时候，说明 `<router-view>` 有嵌套的情况，`depth++`。遍历完成后，根据当前线路匹配的路径和 `depth` 找到对应的 `RouteRecord`，进而找到该渲染的组件。\n\n除了找到了应该渲染的组件，还定义了一个注册路由实例的方法：\n\n```js\ndata.registerRouteInstance = (vm, val) => {     \n  const current = matched.instances[name]\n  if (\n    (val && current !== vm) ||\n    (!val && current === vm)\n  ) {\n    matched.instances[name] = val\n  }\n}\n\n```\n\n给 `vnode` 的 `data` 定义了 `registerRouteInstance` 方法，在 `src/install.js` 中，我们会调用该方法去注册路由的实例：\n\n```js\nconst registerInstance = (vm, callVal) => {\n  let i = vm.$options._parentVnode\n  if (isDef(i) && isDef(i = i.data) && isDef(i = i.registerRouteInstance)) {\n    i(vm, callVal)\n  }\n}\n\nVue.mixin({\n  beforeCreate () {\n    // ...\n    registerInstance(this, this)\n  },\n  destroyed () {\n    registerInstance(this)\n  }\n})\n```\n\n在混入的 `beforeCreate` 钩子函数中，会执行 `registerInstance` 方法，进而执行 `render` 函数中定义的 `registerRouteInstance` 方法，从而给 `matched.instances[name]` 赋值当前组件的 `vm` 实例。\n\n`render` 函数的最后根据 `component` 渲染出对应的组件 `vonde`：\n\n```js\nreturn h(component, data, children)\n```\n\n那么当我们执行 `transitionTo` 来更改路由线路后，组件是如何重新渲染的呢？在我们混入的 `beforeCreate` 钩子函数中有这么一段逻辑：\n\n```js\nVue.mixin({\n  beforeCreate () {\n    if (isDef(this.$options.router)) {\n      Vue.util.defineReactive(this, '_route', this._router.history.current)\n    }\n    // ...\n  }\n})\n```\n\n由于我们把根 Vue 实例的 `_route` 属性定义成响应式的，我们在每个 `<router-view>` 执行 `render` 函数的时候，都会访问  `parent.$route`，如我们之前分析会访问 `this._routerRoot._route`，触发了它的 `getter`，相当于 `<router-view>` 对它有依赖，然后再执行完 `transitionTo` 后，修改 `app._route` 的时候，又触发了`setter`，因此会通知 `<router-view>` 的渲染 `watcher` 更新，重新渲染组件。\n\nVue-Router 还内置了另一个组件 `<router-link>`，\n 它支持用户在具有路由功能的应用中（点击）导航。 通过 `to` 属性指定目标地址，默认渲染成带有正确链接的 `<a>` 标签，可以通过配置 `tag` 属性生成别的标签。另外，当目标路由成功激活时，链接元素自动设置一个表示激活的 CSS 类名。\n\n`<router-link>` 比起写死的 `<a href=\"...\">` 会好一些，理由如下：\n\n无论是 HTML5 `history` 模式还是 `hash` 模式，它的表现行为一致，所以，当你要切换路由模式，或者在 IE9 降级使用 `hash` 模式，无须作任何变动。\n\n在 HTML5 `history` 模式下，`router-link` 会守卫点击事件，让浏览器不再重新加载页面。\n\n当你在 HTML5 `history` 模式下使用 `base` 选项之后，所有的 to 属性都不需要写（基路径）了。\n\n那么接下来我们就来分析它的实现，它的定义在 `src/components/link.js` 中：\n\n```js\nexport default {\n  name: 'RouterLink',\n  props: {\n    to: {\n      type: toTypes,\n      required: true\n    },\n    tag: {\n      type: String,\n      default: 'a'\n    },\n    exact: Boolean,\n    append: Boolean,\n    replace: Boolean,\n    activeClass: String,\n    exactActiveClass: String,\n    event: {\n      type: eventTypes,\n      default: 'click'\n    }\n  },\n  render (h: Function) {\n    const router = this.$router\n    const current = this.$route\n    const { location, route, href } = router.resolve(this.to, current, this.append)\n\n    const classes = {}\n    const globalActiveClass = router.options.linkActiveClass\n    const globalExactActiveClass = router.options.linkExactActiveClass\n    const activeClassFallback = globalActiveClass == null\n            ? 'router-link-active'\n            : globalActiveClass\n    const exactActiveClassFallback = globalExactActiveClass == null\n            ? 'router-link-exact-active'\n            : globalExactActiveClass\n    const activeClass = this.activeClass == null\n            ? activeClassFallback\n            : this.activeClass\n    const exactActiveClass = this.exactActiveClass == null\n            ? exactActiveClassFallback\n            : this.exactActiveClass\n    const compareTarget = location.path\n      ? createRoute(null, location, null, router)\n      : route\n\n    classes[exactActiveClass] = isSameRoute(current, compareTarget)\n    classes[activeClass] = this.exact\n      ? classes[exactActiveClass]\n      : isIncludedRoute(current, compareTarget)\n\n    const handler = e => {\n      if (guardEvent(e)) {\n        if (this.replace) {\n          router.replace(location)\n        } else {\n          router.push(location)\n        }\n      }\n    }\n\n    const on = { click: guardEvent }\n    if (Array.isArray(this.event)) {\n      this.event.forEach(e => { on[e] = handler })\n    } else {\n      on[this.event] = handler\n    }\n\n    const data: any = {\n      class: classes\n    }\n\n    if (this.tag === 'a') {\n      data.on = on\n      data.attrs = { href }\n    } else {\n      const a = findAnchor(this.$slots.default)\n      if (a) {\n        a.isStatic = false\n        const extend = _Vue.util.extend\n        const aData = a.data = extend({}, a.data)\n        aData.on = on\n        const aAttrs = a.data.attrs = extend({}, a.data.attrs)\n        aAttrs.href = href\n      } else {\n        data.on = on\n      }\n    }\n\n    return h(this.tag, data, this.$slots.default)\n  }\n}\n```\n\n`<router-link>` 标签的渲染也是基于 `render` 函数，它首先做了路由解析：\n\n```js\nconst router = this.$router\nconst current = this.$route\nconst { location, route, href } = router.resolve(this.to, current, this.append)\n```\n\n`router.resolve` 是 `VueRouter` 的实例方法，它的定义在 `src/index.js` 中：\n\n```js\nresolve (\n  to: RawLocation,\n  current?: Route,\n  append?: boolean\n): {\n  location: Location,\n  route: Route,\n  href: string,\n  normalizedTo: Location,\n  resolved: Route\n} {\n  const location = normalizeLocation(\n    to,\n    current || this.history.current,\n    append,\n    this\n  )\n  const route = this.match(location, current)\n  const fullPath = route.redirectedFrom || route.fullPath\n  const base = this.history.base\n  const href = createHref(base, fullPath, this.mode)\n  return {\n    location,\n    route,\n    href,\n    normalizedTo: location,\n    resolved: route\n  }\n}\n\nfunction createHref (base: string, fullPath: string, mode) {\n  var path = mode === 'hash' ? '#' + fullPath : fullPath\n  return base ? cleanPath(base + '/' + path) : path\n}\n```\n\n它先规范生成目标 `location`，再根据 `location` 和 `match` 通过 `this.match` 方法计算生成目标路径 `route`，然后再根据 `base`、`fullPath` 和 `this.mode` 通过 `createHref` 方法计算出最终跳转的 `href`。\n\n解析完 `router` 获得目标 `location`、`route`、`href` 后，接下来对 `exactActiveClass` 和 `activeClass` 做处理，当配置 `exact` 为 true 的时候，只有当目标路径和当前路径完全匹配的时候，会添加 `exactActiveClass`；而当目标路径包含当前路径的时候，会添加 `activeClass`。\n\n接着创建了一个守卫函数 ：\n\n```js\nconst handler = e => {\n  if (guardEvent(e)) {\n    if (this.replace) {\n      router.replace(location)\n    } else {\n      router.push(location)\n    }\n  }\n}\n\nfunction guardEvent (e) {\n  if (e.metaKey || e.altKey || e.ctrlKey || e.shiftKey) return\n  if (e.defaultPrevented) return\n  if (e.button !== undefined && e.button !== 0) return \n  if (e.currentTarget && e.currentTarget.getAttribute) {\n    const target = e.currentTarget.getAttribute('target')\n    if (/\\b_blank\\b/i.test(target)) return\n  }\n  if (e.preventDefault) {\n    e.preventDefault()\n  }\n  return true\n}\n\nconst on = { click: guardEvent }\n  if (Array.isArray(this.event)) {\n    this.event.forEach(e => { on[e] = handler })\n  } else {\n    on[this.event] = handler\n  }\n```\n\n最终会监听点击事件或者其它可以通过 `prop` 传入的事件类型，执行 `hanlder` 函数，最终执行 `router.push` 或者 `router.replace` 函数，它们的定义在 `src/index.js` 中：\n\n```js\npush (location: RawLocation, onComplete?: Function, onAbort?: Function) {\n  this.history.push(location, onComplete, onAbort)\n}\n\nreplace (location: RawLocation, onComplete?: Function, onAbort?: Function) {\n this.history.replace(location, onComplete, onAbort)\n}\n```\n\n实际上就是执行了 `history` 的 `push` 和 `replace` 方法做路由跳转。\n\n最后判断当前 `tag` 是否是 `<a>` 标签，`<router-link>` 默认会渲染成 `<a>` 标签，当然我们也可以修改 `tag` 的 `prop` 渲染成其他节点，这种情况下会尝试找它子元素的 `<a>` 标签，如果有则把事件绑定到 `<a>` 标签上并添加 `href` 属性，否则绑定到外层元素本身。\n\n## 总结\n\n那么至此我们把路由的 `transitionTo` 的主体过程分析完毕了，其他一些分支比如重定向、别名、滚动行为等同学们可以自行再去分析。\n\n路径变化是路由中最重要的功能，我们要记住以下内容：路由始终会维护当前的线路，路由切换的时候会把当前线路切换到目标线路，切换过程中会执行一系列的导航守卫钩子函数，会更改 url，同样也会渲染对应的组件，切换完毕后会把目标线路更新替换当前线路，这样就会作为下一次的路径切换的依据。"
  },
  {
    "path": "docs/v2/vuex/api.md",
    "content": "# API\n\n上一节我们对 Vuex 的初始化过程有了深入的分析，在我们构造好这个 `store` 后，需要提供一些 API 对这个 `store` 做存取的操作，那么这一节我们就从源码的角度对这些 API 做分析。\n \n## 数据获取\n\nVuex 最终存储的数据是在 `state` 上的，我们之前分析过在 `store.state` 存储的是 `root state`，那么对于模块上的 `state`，假设我们有 2 个嵌套的 `modules`，它们的 `key` 分别为 `a` 和 `b`，我们可以通过 `store.state.a.b.xxx` 的方式去获取。它的实现是在发生在 `installModule` 的时候：\n\n```js\nfunction installModule (store, rootState, path, module, hot) {\n  const isRoot = !path.length\n  \n  // ...\n  // set state\n  if (!isRoot && !hot) {\n    const parentState = getNestedState(rootState, path.slice(0, -1))\n    const moduleName = path[path.length - 1]\n    store._withCommit(() => {\n      Vue.set(parentState, moduleName, module.state)\n    })\n  }\n  // ...\n}\n```\n\n在递归执行 `installModule` 的过程中，就完成了整个 `state` 的建设，这样我们就可以通过 `module` 名的 `path` 去访问到一个深层 `module` 的 `state`。\n\n有些时候，我们获取的数据不仅仅是一个 `state`，而是由多个 `state` 计算而来，Vuex 提供了 `getters`，允许我们定义一个 `getter` 函数，如下：\n\n````js\ngetters: {\n  total (state, getters, localState, localGetters) {\n    // 可访问全局 state 和 getters，以及如果是在 modules 下面，可以访问到局部 state 和 局部 getters\n    return state.a + state.b\n  }\n}\n````\n\n我们在 `installModule` 的过程中，递归执行了所有 `getters` 定义的注册，在之后的 `resetStoreVM` 过程中，执行了 `store.getters` 的初始化工作：\n\n```js\nfunction installModule (store, rootState, path, module, hot) {\n  // ...\n  const namespace = store._modules.getNamespace(path)\n  // ...\n  const local = module.context = makeLocalContext(store, namespace, path)\n\n  // ...\n\n  module.forEachGetter((getter, key) => {\n    const namespacedType = namespace + key\n    registerGetter(store, namespacedType, getter, local)\n  })\n\n  // ...\n}\n\nfunction registerGetter (store, type, rawGetter, local) {\n  if (store._wrappedGetters[type]) {\n    if (process.env.NODE_ENV !== 'production') {\n      console.error(`[vuex] duplicate getter key: ${type}`)\n    }\n    return\n  }\n  store._wrappedGetters[type] = function wrappedGetter (store) {\n    return rawGetter(\n      local.state, // local state\n      local.getters, // local getters\n      store.state, // root state\n      store.getters // root getters\n    )\n  }\n}\n\n\nfunction resetStoreVM (store, state, hot) {\n  // ...\n  // bind store public getters\n  store.getters = {}\n  const wrappedGetters = store._wrappedGetters\n  const computed = {}\n  forEachValue(wrappedGetters, (fn, key) => {\n    // use computed to leverage its lazy-caching mechanism\n    computed[key] = () => fn(store)\n    Object.defineProperty(store.getters, key, {\n      get: () => store._vm[key],\n      enumerable: true // for local getters\n    })\n  })\n\n  // use a Vue instance to store the state tree\n  // suppress warnings just in case the user has added\n  // some funky global mixins\n  // ...\n  store._vm = new Vue({\n    data: {\n      $$state: state\n    },\n    computed\n  })\n  // ...\n}\n```\n\n在 `installModule` 的过程中，为建立了每个模块的上下文环境，\n因此当我们访问 `store.getters.xxx` 的时候，实际上就是执行了 `rawGetter(local.state,...)`，`rawGetter` 就是我们定义的 `getter` 方法，这也就是为什么我们的 `getter` 函数支持这四个参数，并且除了全局的 `state` 和 `getter` 外，我们还可以访问到当前 `module` 下的 `state` 和 `getter`。\n\n## 数据存储\n\nVuex 对数据存储的存储本质上就是对 `state` 做修改，并且只允许我们通过提交 `mutaion` 的形式去修改 `state`，`mutation` 是一个函数，如下：\n    \n```js\nmutations: {\n  increment (state) {\n    state.count++\n  }\n}\n```\n\n`mutations` 的初始化也是在 `installModule` 的时候：\n\n```js\nfunction installModule (store, rootState, path, module, hot) {\n  // ...\n  const namespace = store._modules.getNamespace(path)\n\n  // ...\n  const local = module.context = makeLocalContext(store, namespace, path)\n\n  module.forEachMutation((mutation, key) => {\n    const namespacedType = namespace + key\n    registerMutation(store, namespacedType, mutation, local)\n  })\n  // ...\n}\n\nfunction registerMutation (store, type, handler, local) {\n  const entry = store._mutations[type] || (store._mutations[type] = [])\n  entry.push(function wrappedMutationHandler (payload) {\n    handler.call(store, local.state, payload)\n  })\n}\n```\n\n`store` 提供了`commit` 方法让我们提交一个 `mutation`：\n\n```js\ncommit (_type, _payload, _options) {\n  // check object-style commit\n  const {\n    type,\n    payload,\n    options\n  } = unifyObjectStyle(_type, _payload, _options)\n\n  const mutation = { type, payload }\n  const entry = this._mutations[type]\n  if (!entry) {\n    if (process.env.NODE_ENV !== 'production') {\n      console.error(`[vuex] unknown mutation type: ${type}`)\n    }\n    return\n  }\n  this._withCommit(() => {\n    entry.forEach(function commitIterator (handler) {\n      handler(payload)\n    })\n  })\n  this._subscribers.forEach(sub => sub(mutation, this.state))\n\n  if (\n    process.env.NODE_ENV !== 'production' &&\n    options && options.silent\n  ) {\n    console.warn(\n      `[vuex] mutation type: ${type}. Silent option has been removed. ` +\n      'Use the filter functionality in the vue-devtools'\n    )\n  }\n}\n```\n\n这里传入的 `_type` 就是 `mutation` 的 `type`，我们可以从 `store._mutations` 找到对应的函数数组，遍历它们执行获取到每个 `handler` 然后执行，实际上就是执行了 `wrappedMutationHandler(playload)`，接着会执行我们定义的 `mutation` 函数，并传入当前模块的 `state`，所以我们的 `mutation` 函数也就是对当前模块的 `state` 做修改。\n\n需要注意的是， `mutation` 必须是同步函数，但是我们在开发实际项目中，经常会遇到要先去发送一个请求，然后根据请求的结果去修改 `state`，那么单纯只通过 `mutation` 是无法完成需求，因此 Vuex 又给我们设计了一个 `action` 的概念。\n\n`action` 类似于 `mutation`，不同在于 `action` 提交的是 `mutation`，而不是直接操作 `state`，并且它可以包含任意异步操作。例如：\n\n```js\nmutations: {\n  increment (state) {\n    state.count++\n  }\n},\nactions: {\n  increment (context) {\n    setTimeout(() => {\n      context.commit('increment')\n    }, 0)\n  }\n}\n```\n\n`actions` 的初始化也是在 `installModule` 的时候：\n\n```js\nfunction installModule (store, rootState, path, module, hot) {\n  // ...\n  const namespace = store._modules.getNamespace(path)\n\n  // ...\n  const local = module.context = makeLocalContext(store, namespace, path)\n\n  module.forEachAction((action, key) => {\n    const type = action.root ? key : namespace + key\n    const handler = action.handler || action\n    registerAction(store, type, handler, local)\n}  )\n  // ...\n}\n\nfunction registerAction (store, type, handler, local) {\n  const entry = store._actions[type] || (store._actions[type] = [])\n  entry.push(function wrappedActionHandler (payload, cb) {\n    let res = handler.call(store, {\n      dispatch: local.dispatch,\n      commit: local.commit,\n      getters: local.getters,\n      state: local.state,\n      rootGetters: store.getters,\n      rootState: store.state\n    }, payload, cb)\n    if (!isPromise(res)) {\n      res = Promise.resolve(res)\n    }\n    if (store._devtoolHook) {\n      return res.catch(err => {\n        store._devtoolHook.emit('vuex:error', err)\n        throw err\n      })\n    } else {\n      return res\n    }\n  })\n}\n```\n\n`store` 提供了`dispatch` 方法让我们提交一个 `action`：\n\n```js\ndispatch (_type, _payload) {\n  // check object-style dispatch\n  const {\n    type,\n    payload\n  } = unifyObjectStyle(_type, _payload)\n\n  const action = { type, payload }\n  const entry = this._actions[type]\n  if (!entry) {\n    if (process.env.NODE_ENV !== 'production') {\n      console.error(`[vuex] unknown action type: ${type}`)\n    }\n    return\n  }\n\n  this._actionSubscribers.forEach(sub => sub(action, this.state))\n\n  return entry.length > 1\n    ? Promise.all(entry.map(handler => handler(payload)))\n    : entry[0](payload)\n}\n```\n\n这里传入的 `_type` 就是 `action` 的 `type`，我们可以从 `store._actions` 找到对应的函数数组，遍历它们执行获取到每个 `handler` 然后执行，实际上就是执行了 `wrappedActionHandler(payload)`，接着会执行我们定义的 `action` 函数，并传入一个对象，包含了当前模块下的 `dispatch`、`commit`、`getters`、`state`，以及全局的 `rootState` 和 `rootGetters`，所以我们定义的 `action` 函数能拿到当前模块下的 `commit` 方法。\n\n因此 `action` 比我们自己写一个函数执行异步操作然后提交 `muataion` 的好处是在于它可以在参数中获取到当前模块的一些方法和状态，Vuex 帮我们做好了这些。\n\n\n## 语法糖\n\n我们知道 `store` 是 `Store` 对象的一个实例，它是一个原生的 Javascript 对象，我们可以在任意地方使用它们。但大部分的使用场景还是在组件中使用，那么我们之前介绍过，在 Vuex 安装阶段，它会往每一个组件实例上混入 `beforeCreate` 钩子函数，然后往组件实例上添加一个 `$store` 的实例，它指向的就是我们实例化的 `store`，因此我们可以在组件中访问到 `store` 的任何属性和方法。\n\n比如我们在组件中访问 `state`：\n\n```js\nconst Counter = {\n  template: `<div>{{ count }}</div>`,\n  computed: {\n    count () {\n      return this.$store.state.count\n    }\n  }\n}\n```\n\n但是当一个组件需要获取多个状态时候，将这些状态都声明为计算属性会有些重复和冗余。同样这些问题也在存于 `getter`、`mutation` 和 `action`。\n\n为了解决这个问题，Vuex 提供了一系列 `mapXXX` 辅助函数帮助我们实现在组件中可以很方便的注入 `store` 的属性和方法。\n\n### `mapState`\n\n我们先来看一下 `mapState` 的用法：\n\n```js\n// 在单独构建的版本中辅助函数为 Vuex.mapState\nimport { mapState } from 'vuex'\n\nexport default {\n  // ...\n  computed: mapState({\n    // 箭头函数可使代码更简练\n    count: state => state.count,\n\n    // 传字符串参数 'count' 等同于 `state => state.count`\n    countAlias: 'count',\n\n    // 为了能够使用 `this` 获取局部状态，必须使用常规函数\n    countPlusLocalState (state) {\n      return state.count + this.localCount\n    }\n  })\n}\n```\n\n再来看一下 `mapState` 方法的定义，在 `src/helpers.js` 中：\n\n```js\nexport const mapState = normalizeNamespace((namespace, states) => {\n  const res = {}\n  normalizeMap(states).forEach(({ key, val }) => {\n    res[key] = function mappedState () {\n      let state = this.$store.state\n      let getters = this.$store.getters\n      if (namespace) {\n        const module = getModuleByNamespace(this.$store, 'mapState', namespace)\n        if (!module) {\n          return\n        }\n        state = module.context.state\n        getters = module.context.getters\n      }\n      return typeof val === 'function'\n        ? val.call(this, state, getters)\n        : state[val]\n    }\n    // mark vuex getter for devtools\n    res[key].vuex = true\n  })\n  return res\n})\n\nfunction normalizeNamespace (fn) {\n  return (namespace, map) => {\n    if (typeof namespace !== 'string') {\n      map = namespace\n      namespace = ''\n    } else if (namespace.charAt(namespace.length - 1) !== '/') {\n      namespace += '/'\n    }\n    return fn(namespace, map)\n  }\n}\n\nfunction normalizeMap (map) {\n  return Array.isArray(map)\n    ? map.map(key => ({ key, val: key }))\n    : Object.keys(map).map(key => ({ key, val: map[key] }))\n}\n```\n\n首先 `mapState` 是通过执行 `normalizeNamespace` 返回的函数，它接收 2 个参数，其中 `namespace` 表示命名空间，`map` 表示具体的对象，`namespace` 可不传，稍后我们来介绍 `namespace` 的作用。\n\n当执行 `mapState(map)` 函数的时候，实际上就是执行 `normalizeNamespace` 包裹的函数，然后把 `map` 作为参数 `states` 传入。\n\n`mapState` 最终是要构造一个对象，每个对象的元素都是一个方法，因为这个对象是要扩展到组件的 `computed` 计算属性中的。函数首先执行 `normalizeMap` 方法，把这个 `states` 变成一个数组，数组的每个元素都是 `{key, val}` 的形式。接着再遍历这个数组，以 `key` 作为对象的 `key`，值为一个 `mappedState` 的函数，在这个函数的内部，获取到 `$store.getters` 和 `$store.state`，然后再判断数组的 `val` 如果是一个函数，执行该函数，传入 `state` 和 `getters`，否则直接访问 `state[val]`。\n\n比起一个个手动声明计算属性，`mapState` 确实要方便许多，下面我们来看一下 `namespace` 的作用。\n\n当我们想访问一个子模块的 `state` 的时候，我们可能需要这样访问：\n\n```js\ncomputed: {\n  mapState({\n    a: state => state.some.nested.module.a,\n    b: state => state.some.nested.module.b\n  })\n},\n```\n\n这样从写法上就很不友好，`mapState` 支持传入 `namespace`， 因此我们可以这么写：\n\n```js\ncomputed: {\n  mapState('some/nested/module', {\n    a: state => state.a,\n    b: state => state.b\n  })\n},\n```\n\n这样看起来就清爽许多。在 `mapState` 的实现中，如果有 `namespace`，则尝试去通过 `getModuleByNamespace(this.$store, 'mapState', namespace)` 对应的 `module`，然后把 `state` 和 `getters` 修改为 `module` 对应的 `state` 和 `getters`。\n\n```js\nfunction getModuleByNamespace (store, helper, namespace) {\n  const module = store._modulesNamespaceMap[namespace]\n  if (process.env.NODE_ENV !== 'production' && !module) {\n    console.error(`[vuex] module namespace not found in ${helper}(): ${namespace}`)\n  }\n  return module\n}\n```\n\n我们在 Vuex 初始化执行 `installModule` 的过程中，初始化了这个映射表：\n\n```js\nfunction installModule (store, rootState, path, module, hot) {\n  // ...\n  const namespace = store._modules.getNamespace(path)\n\n  // register in namespace map\n  if (module.namespaced) {\n    store._modulesNamespaceMap[namespace] = module\n  }\n\n  // ...\n}\n```\n\n### `mapGetters`\n\n我们先来看一下 `mapGetters` 的用法：\n\n```js\nimport { mapGetters } from 'vuex'\n\nexport default {\n  // ...\n  computed: {\n    // 使用对象展开运算符将 getter 混入 computed 对象中\n    mapGetters([\n      'doneTodosCount',\n      'anotherGetter',\n      // ...\n    ])\n  }\n}\n```\n\n和 `mapState` 类似，`mapGetters` 是将 `store` 中的 `getter` 映射到局部计算属性，来看一下它的定义：\n\n```js\nexport const mapGetters = normalizeNamespace((namespace, getters) => {\n  const res = {}\n  normalizeMap(getters).forEach(({ key, val }) => {\n    // thie namespace has been mutate by normalizeNamespace\n    val = namespace + val\n    res[key] = function mappedGetter () {\n      if (namespace && !getModuleByNamespace(this.$store, 'mapGetters', namespace)) {\n        return\n      }\n      if (process.env.NODE_ENV !== 'production' && !(val in this.$store.getters)) {\n        console.error(`[vuex] unknown getter: ${val}`)\n        return\n      }\n      return this.$store.getters[val]\n    }\n    // mark vuex getter for devtools\n    res[key].vuex = true\n  })\n  return res\n})\n```\n\n`mapGetters` 也同样支持 `namespace`，如果不写 `namespace` ，访问一个子 `module` 的属性需要写很长的 `key`，一旦我们使用了 `namespace`，就可以方便我们的书写，每个 `mappedGetter` 的实现实际上就是取 `this.$store.getters[val]`。\n\n### `mapMutations`\n\n我们可以在组件中使用 `this.$store.commit('xxx')` 提交 `mutation`，或者使用 `mapMutations` 辅助函数将组件中的 `methods` 映射为 `store.commit` 的调用。\n\n我们先来看一下 `mapMutations` 的用法：\n\n```js\nimport { mapMutations } from 'vuex'\n\nexport default {\n  // ...\n  methods: {\n    ...mapMutations([\n      'increment', // 将 `this.increment()` 映射为 `this.$store.commit('increment')`\n\n      // `mapMutations` 也支持载荷：\n      'incrementBy' // 将 `this.incrementBy(amount)` 映射为 `this.$store.commit('incrementBy', amount)`\n    ]),\n    ...mapMutations({\n      add: 'increment' // 将 `this.add()` 映射为 `this.$store.commit('increment')`\n    })\n  }\n}\n```\n\n`mapMutations` 支持传入一个数组或者一个对象，目标都是组件中对应的 `methods` 映射为 `store.commit` 的调用。来看一下它的定义：\n\n```js\nexport const mapMutations = normalizeNamespace((namespace, mutations) => {\n  const res = {}\n  normalizeMap(mutations).forEach(({ key, val }) => {\n    res[key] = function mappedMutation (...args) {\n      // Get the commit method from store\n      let commit = this.$store.commit\n      if (namespace) {\n        const module = getModuleByNamespace(this.$store, 'mapMutations', namespace)\n        if (!module) {\n          return\n        }\n        commit = module.context.commit\n      }\n      return typeof val === 'function'\n        ? val.apply(this, [commit].concat(args))\n        : commit.apply(this.$store, [val].concat(args))\n    }\n  })\n  return res\n})\n```\n\n可以看到 `mappedMutation` 同样支持了 `namespace`，并且支持了传入额外的参数 `args`，作为提交 `mutation` 的 `payload`，最终就是执行了 `store.commit` 方法，并且这个 `commit` 会根据传入的 `namespace` 映射到对应 `module` 的 `commit` 上。\n\n### `mapActions`\n\n我们可以在组件中使用 `this.$store.dispatch('xxx')` 提交 `action`，或者使用 `mapActions` 辅助函数将组件中的 `methods` 映射为 `store.dispatch` 的调用。\n\n`mapActions` 在用法上和 `mapMutations` 几乎一样，实现也很类似：\n\n```js\nexport const mapActions = normalizeNamespace((namespace, actions) => {\n  const res = {}\n  normalizeMap(actions).forEach(({ key, val }) => {\n    res[key] = function mappedAction (...args) {\n      // get dispatch function from store\n      let dispatch = this.$store.dispatch\n      if (namespace) {\n        const module = getModuleByNamespace(this.$store, 'mapActions', namespace)\n        if (!module) {\n          return\n        }\n        dispatch = module.context.dispatch\n      }\n      return typeof val === 'function'\n        ? val.apply(this, [dispatch].concat(args))\n        : dispatch.apply(this.$store, [val].concat(args))\n    }\n  })\n  return res\n})\n```\n\n和 `mapMutations` 的实现几乎一样，不同的是把 `commit` 方法换成了 `dispatch`。\n\n## 动态更新模块\n\n在 Vuex 初始化阶段我们构造了模块树，初始化了模块上各个部分。在有一些场景下，我们需要动态去注入一些新的模块，Vuex 提供了模块动态注册功能，在 `store` 上提供了一个 `registerModule` 的 API。\n\n```js\nregisterModule (path, rawModule, options = {}) {\n  if (typeof path === 'string') path = [path]\n\n  if (process.env.NODE_ENV !== 'production') {\n    assert(Array.isArray(path), `module path must be a string or an Array.`)\n    assert(path.length > 0, 'cannot register the root module by using registerModule.')\n  }\n\n  this._modules.register(path, rawModule)\n  installModule(this, this.state, path, this._modules.get(path), options.preserveState)\n  // reset store to update getters...\n  resetStoreVM(this, this.state)\n}\n```\n\n`registerModule` 支持传入一个 `path` 模块路径 和 `rawModule` 模块定义，首先执行 `register` 方法扩展我们的模块树，接着执行 `installModule` 去安装模块，最后执行 `resetStoreVM` 重新实例化 `store._vm`，并销毁旧的 `store._vm`。\n\n相对的，有动态注册模块的需求就有动态卸载模块的需求，Vuex 提供了模块动态卸载功能，在 `store` 上提供了一个 `unregisterModule` 的 API。\n\n```js\nunregisterModule (path) {\n  if (typeof path === 'string') path = [path]\n\n  if (process.env.NODE_ENV !== 'production') {\n    assert(Array.isArray(path), `module path must be a string or an Array.`)\n  }\n\n  this._modules.unregister(path)\n  this._withCommit(() => {\n    const parentState = getNestedState(this.state, path.slice(0, -1))\n    Vue.delete(parentState, path[path.length - 1])\n  })\n  resetStore(this)\n}\n```\n\n`unregisterModule` 支持传入一个 `path` 模块路径，首先执行 `unregister` 方法去修剪我们的模块树：\n\n```js\nunregister (path) {\n  const parent = this.get(path.slice(0, -1))\n  const key = path[path.length - 1]\n  if (!parent.getChild(key).runtime) return\n\n  parent.removeChild(key)\n}\n```\n注意，这里只会移除我们运行时动态创建的模块。\n\n接着会删除 `state` 在该路径下的引用，最后执行 `resetStore` 方法：\n\n```js\nfunction resetStore (store, hot) {\n  store._actions = Object.create(null)\n  store._mutations = Object.create(null)\n  store._wrappedGetters = Object.create(null)\n  store._modulesNamespaceMap = Object.create(null)\n  const state = store.state\n  // init all modules\n  installModule(store, state, [], store._modules.root, true)\n  // reset vm\n  resetStoreVM(store, state, hot)\n}\n```\n\n该方法就是把 `store` 下的对应存储的 `_actions`、`_mutations`、`_wrappedGetters` 和 `_modulesNamespaceMap` 都清空，然后重新执行 `installModule` 安装所有模块以及 `resetStoreVM` 重置 `store._vm`。\n\n## 总结\n\n那么至此，Vuex 提供的一些常用 API 我们就分析完了，包括数据的存取、语法糖、模块的动态更新等。要理解 Vuex 提供这些 API 都是方便我们在对 `store` 做各种操作来完成各种能力，尤其是 `mapXXX` 的设计，让我们在使用 API 的时候更加方便，这也是我们今后在设计一些 JavaScript 库的时候，从 API 设计角度中应该学习的方向。\n"
  },
  {
    "path": "docs/v2/vuex/index.md",
    "content": "# Vuex\n\nVuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态，并以相应的规则保证状态以一种可预测的方式发生变化。\n\n## 什么是“状态管理模式”？\n\n让我们从一个简单的 Vue 计数应用开始：\n\n```js\nnew Vue({\n  // state\n  data () {\n    return {\n      count: 0\n    }\n  },\n  // view\n  template: `\n    <div>{{ count }}</div>\n  `,\n  // actions\n  methods: {\n    increment () {\n      this.count++\n    }\n  }\n})\n```\n\n这个状态自管理应用包含以下几个部分：\n\n- state，驱动应用的数据源；\n- view，以声明方式将 state 映射到视图；\n- actions，响应在 view 上的用户输入导致的状态变化。\n\n以下是一个表示“单向数据流”理念的极简示意：\n\n<img :src=\"$withBase('/assets/vuex.png')\">\n\n但是，当我们的应用遇到多个组件共享状态时，单向数据流的简洁性很容易被破坏：\n\n- 多个视图依赖于同一状态。\n- 来自不同视图的行为需要变更同一状态。\n\n对于问题一，传参的方法对于多层嵌套的组件将会非常繁琐，并且对于兄弟组件间的状态传递无能为力。对于问题二，我们经常会采用父子组件直接引用或者通过事件来变更和同步状态的多份拷贝。以上的这些模式非常脆弱，通常会导致无法维护的代码。\n\n因此，我们为什么不把组件的共享状态抽取出来，以一个全局单例模式管理呢？在这种模式下，我们的组件树构成了一个巨大的“视图”，不管在树的哪个位置，任何组件都能获取状态或者触发行为。\n\n## Vuex 核心思想\n\nVuex 应用的核心就是 store（仓库）。“store”基本上就是一个容器，它包含着你的应用中大部分的状态 (state)。有些同学可能会问，那我定义一个全局对象，再去上层封装了一些数据存取的接口不也可以么？\n\nVuex 和单纯的全局对象有以下两点不同：\n\n- Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候，若 store 中的状态发生变化，那么相应的组件也会相应地得到高效更新。\n\n- 你不能直接改变 store 中的状态。改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。这样使得我们可以方便地跟踪每一个状态的变化，从而让我们能够实现一些工具帮助我们更好地了解我们的应用。\n\n\n另外，通过定义和隔离状态管理中的各种概念并强制遵守一定的规则，我们的代码将会变得更结构化且易维护。\n\n<img :src=\"$withBase('/assets/vuex1.png')\">\n\n"
  },
  {
    "path": "docs/v2/vuex/init.md",
    "content": "# Vuex 初始化\n\n这一节我们主要来分析 Vuex 的初始化过程，它包括安装、Store 实例化过程 2 个方面。\n\n## 安装\n\n当我们在代码中通过 `import Vuex from 'vuex'` 的时候，实际上引用的是一个对象，它的定义在 `src/index.js` 中：\n\n```js\nexport default {\n  Store,\n  install,\n  version: '__VERSION__',\n  mapState,\n  mapMutations,\n  mapGetters,\n  mapActions,\n  createNamespacedHelpers\n}\n```\n\n和 Vue-Router 一样，Vuex 也同样存在一个静态的 `install` 方法，它的定义在 `src/store.js` 中：\n\n```js\nexport function install (_Vue) {\n  if (Vue && _Vue === Vue) {\n    if (process.env.NODE_ENV !== 'production') {\n      console.error(\n        '[vuex] already installed. Vue.use(Vuex) should be called only once.'\n      )\n    }\n    return\n  }\n  Vue = _Vue\n  applyMixin(Vue)\n}\n```\n\n`install` 的逻辑很简单，把传入的 `_Vue` 赋值给 `Vue` 并执行了 `applyMixin(Vue)` 方法，它的定义在 `src/mixin.js` 中：\n\n```js\nexport default function (Vue) {\n  const version = Number(Vue.version.split('.')[0])\n\n  if (version >= 2) {\n    Vue.mixin({ beforeCreate: vuexInit })\n  } else {\n    // override init and inject vuex init procedure\n    // for 1.x backwards compatibility.\n    const _init = Vue.prototype._init\n    Vue.prototype._init = function (options = {}) {\n      options.init = options.init\n        ? [vuexInit].concat(options.init)\n        : vuexInit\n      _init.call(this, options)\n    }\n  }\n\n  /**\n   * Vuex init hook, injected into each instances init hooks list.\n   */\n\n  function vuexInit () {\n    const options = this.$options\n    // store injection\n    if (options.store) {\n      this.$store = typeof options.store === 'function'\n        ? options.store()\n        : options.store\n    } else if (options.parent && options.parent.$store) {\n      this.$store = options.parent.$store\n    }\n  }\n}\n```\n\n`applyMixin` 就是这个 `export default function`，它还兼容了 Vue 1.0 的版本，这里我们只关注 Vue 2.0 以上版本的逻辑，它其实就全局混入了一个 `beforeCreate` 钩子函数，它的实现非常简单，就是把 `options.store` 保存在所有组件的 `this.$store` 中，这个 `options.store` 就是我们在实例化 `Store` 对象的实例，稍后我们会介绍，这也是为什么我们在组件中可以通过 `this.$store` 访问到这个实例。\n\n## Store 实例化\n\n我们在 `import Vuex` 之后，会实例化其中的 `Store` 对象，返回 `store` 实例并传入 `new Vue` 的 `options` 中，也就是我们刚才提到的 `options.store`.\n\n举个简单的例子，如下： \n```js\nexport default new Vuex.Store({\n  actions,\n  getters,\n  state,\n  mutations,\n  modules\n  // ...\n})\n```\n\n`Store` 对象的构造函数接收一个对象参数，它包含 `actions`、`getters`、`state`、`mutations`、`modules` 等 Vuex 的核心概念，它的定义在 `src/store.js` 中：\n\n```js\nexport class Store {\n  constructor (options = {}) {\n    // Auto install if it is not done yet and `window` has `Vue`.\n    // To allow users to avoid auto-installation in some cases,\n    // this code should be placed here. See #731\n    if (!Vue && typeof window !== 'undefined' && window.Vue) {\n      install(window.Vue)\n    }\n\n    if (process.env.NODE_ENV !== 'production') {\n      assert(Vue, `must call Vue.use(Vuex) before creating a store instance.`)\n      assert(typeof Promise !== 'undefined', `vuex requires a Promise polyfill in this browser.`)\n      assert(this instanceof Store, `Store must be called with the new operator.`)\n    }\n\n    const {\n      plugins = [],\n      strict = false\n    } = options\n\n    // store internal state\n    this._committing = false\n    this._actions = Object.create(null)\n    this._actionSubscribers = []\n    this._mutations = Object.create(null)\n    this._wrappedGetters = Object.create(null)\n    this._modules = new ModuleCollection(options)\n    this._modulesNamespaceMap = Object.create(null)\n    this._subscribers = []\n    this._watcherVM = new Vue()\n\n    // bind commit and dispatch to self\n    const store = this\n    const { dispatch, commit } = this\n    this.dispatch = function boundDispatch (type, payload) {\n      return dispatch.call(store, type, payload)\n    }\n    this.commit = function boundCommit (type, payload, options) {\n      return commit.call(store, type, payload, options)\n    }\n\n    // strict mode\n    this.strict = strict\n\n    const state = this._modules.root.state\n\n    // init root module.\n    // this also recursively registers all sub-modules\n    // and collects all module getters inside this._wrappedGetters\n    installModule(this, state, [], this._modules.root)\n\n    // initialize the store vm, which is responsible for the reactivity\n    // (also registers _wrappedGetters as computed properties)\n    resetStoreVM(this, state)\n\n    // apply plugins\n    plugins.forEach(plugin => plugin(this))\n\n    if (Vue.config.devtools) {\n      devtoolPlugin(this)\n    }\n  }\n}  \n```\n\n我们把 `Store` 的实例化过程拆成 3 个部分，分别是初始化模块，安装模块和初始化 `store._vm`，接下来我们来分析这 3 部分的实现。\n\n### 初始化模块\n\n在分析模块初始化之前，我们先来了解一下模块对于 Vuex 的意义：由于使用单一状态树，应用的所有状态会集中到一个比较大的对象，当应用变得非常复杂时，`store` 对象就有可能变得相当臃肿。为了解决以上问题，Vuex 允许我们将 `store` 分割成模块（module）。每个模块拥有自己的 `state`、`mutation`、`action`、`getter`，甚至是嵌套子模块——从上至下进行同样方式的分割：\n\n```js\nconst moduleA = {\n  state: { ... },\n  mutations: { ... },\n  actions: { ... },\n  getters: { ... }\n}\n\nconst moduleB = {\n  state: { ... },\n  mutations: { ... },\n  actions: { ... },\n  getters: { ... },\n}\n\nconst store = new Vuex.Store({\n  modules: {\n    a: moduleA,\n    b: moduleB\n  }\n})\n\nstore.state.a // -> moduleA 的状态\nstore.state.b // -> moduleB 的状态\n```\n\n所以从数据结构上来看，模块的设计就是一个树型结构，`store` 本身可以理解为一个 `root module`，它下面的 `modules` 就是子模块，Vuex 需要完成这颗树的构建，构建过程的入口就是：\n\n```js\nthis._modules = new ModuleCollection(options)\n```\n\n`ModuleCollection` 的定义在 `src/module/module-collection.js` 中：\n\n```js\nexport default class ModuleCollection {\n  constructor (rawRootModule) {\n    // register root module (Vuex.Store options)\n    this.register([], rawRootModule, false)\n  }\n\n  get (path) {\n    return path.reduce((module, key) => {\n      return module.getChild(key)\n    }, this.root)\n  }\n\n  getNamespace (path) {\n    let module = this.root\n    return path.reduce((namespace, key) => {\n      module = module.getChild(key)\n      return namespace + (module.namespaced ? key + '/' : '')\n    }, '')\n  }\n\n  update (rawRootModule) {\n    update([], this.root, rawRootModule)\n  }\n\n  register (path, rawModule, runtime = true) {\n    if (process.env.NODE_ENV !== 'production') {\n      assertRawModule(path, rawModule)\n    }\n\n    const newModule = new Module(rawModule, runtime)\n    if (path.length === 0) {\n      this.root = newModule\n    } else {\n      const parent = this.get(path.slice(0, -1))\n      parent.addChild(path[path.length - 1], newModule)\n    }\n\n    // register nested modules\n    if (rawModule.modules) {\n      forEachValue(rawModule.modules, (rawChildModule, key) => {\n        this.register(path.concat(key), rawChildModule, runtime)\n      })\n    }\n  }\n\n  unregister (path) {\n    const parent = this.get(path.slice(0, -1))\n    const key = path[path.length - 1]\n    if (!parent.getChild(key).runtime) return\n\n    parent.removeChild(key)\n  }\n}\n```\n\n`ModuleCollection` 实例化的过程就是执行了 `register` 方法，\n`register` 接收 3 个参数，其中 `path` 表示路径，因为我们整体目标是要构建一颗模块树，`path` 是在构建树的过程中维护的路径；`rawModule` 表示定义模块的原始配置；`runtime` 表示是否是一个运行时创建的模块。\n\n`register` 方法首先通过 `const newModule = new Module(rawModule, runtime)` 创建了一个 `Module` 的实例，`Module` 是用来描述单个模块的类，它的定义在 `src/module/module.js` 中：\n\n```js\nexport default class Module {\n  constructor (rawModule, runtime) {\n    this.runtime = runtime\n    // Store some children item\n    this._children = Object.create(null)\n    // Store the origin module object which passed by programmer\n    this._rawModule = rawModule\n    const rawState = rawModule.state\n\n    // Store the origin module's state\n    this.state = (typeof rawState === 'function' ? rawState() : rawState) || {}\n  }\n\n  get namespaced () {\n    return !!this._rawModule.namespaced\n  }\n\n  addChild (key, module) {\n    this._children[key] = module\n  }\n\n  removeChild (key) {\n    delete this._children[key]\n  }\n\n  getChild (key) {\n    return this._children[key]\n  }\n\n  update (rawModule) {\n    this._rawModule.namespaced = rawModule.namespaced\n    if (rawModule.actions) {\n      this._rawModule.actions = rawModule.actions\n    }\n    if (rawModule.mutations) {\n      this._rawModule.mutations = rawModule.mutations\n    }\n    if (rawModule.getters) {\n      this._rawModule.getters = rawModule.getters\n    }\n  }\n\n  forEachChild (fn) {\n    forEachValue(this._children, fn)\n  }\n\n  forEachGetter (fn) {\n    if (this._rawModule.getters) {\n      forEachValue(this._rawModule.getters, fn)\n    }\n  }\n\n  forEachAction (fn) {\n    if (this._rawModule.actions) {\n      forEachValue(this._rawModule.actions, fn)\n    }\n  }\n\n  forEachMutation (fn) {\n    if (this._rawModule.mutations) {\n      forEachValue(this._rawModule.mutations, fn)\n    }\n  }\n}\n```\n\n来看一下 `Module` 的构造函数，对于每个模块而言，`this._rawModule` 表示模块的配置，`this._children` 表示它的所有子模块，`this.state` 表示这个模块定义的 `state`。\n\n回到 `register`，那么在实例化一个 `Module` 后，判断当前的 `path` 的长度如果为 0，则说明它是一个根模块，所以把 `newModule` 赋值给了 `this.root`，否则就需要建立父子关系了：\n\n```js\nconst parent = this.get(path.slice(0, -1))\nparent.addChild(path[path.length - 1], newModule)\n```\n\n我们先大体上了解它的逻辑：首先根据路径获取到父模块，然后再调用父模块的 `addChild` 方法建立父子关系。\n\n`register` 的最后一步，就是遍历当前模块定义中的所有 `modules`，根据 `key` 作为 `path`，递归调用 `register` 方法，这样我们再回过头看一下建立父子关系的逻辑，首先执行了 `this.get(path.slice(0, -1)` 方法：\n\n```js\nget (path) {\n  return path.reduce((module, key) => {\n    return module.getChild(key)\n  }, this.root)\n}\n```\n\n传入的 `path` 是它的父模块的 `path`，然后从根模块开始，通过 `reduce` 方法一层层去找到对应的模块，查找的过程中，执行的是 `module.getChild(key)` 方法：\n\n```js\ngetChild (key) {\n  return this._children[key]\n}\n```\n\n其实就是返回当前模块的 `_children` 中对应 `key` 的模块，那么每个模块的 `_children` 是如何添加的呢，是通过执行 `parent.addChild(path[path.length - 1], newModule)` 方法：\n\n```js\naddChild (key, module) {\n  this._children[key] = module\n}\n```\n\n所以说对于 `root module` 的下一层 `modules` 来说，它们的 `parent` 就是 `root module`，那么他们就会被添加的 `root module` 的 `_children` 中。每个子模块通过路径找到它的父模块，然后通过父模块的 `addChild` 方法建立父子关系，递归执行这样的过程，最终就建立一颗完整的模块树。\n\n### 安装模块\n\n初始化模块后，执行安装模块的相关逻辑，它的目标就是对模块中的 `state`、`getters`、`mutations`、`actions` 做初始化工作，它的入口代码是：\n\n```js\nconst state = this._modules.root.state\ninstallModule(this, state, [], this._modules.root)\n```\n\n来看一下 `installModule` 的定义：\n\n```js\nfunction installModule (store, rootState, path, module, hot) {\n  const isRoot = !path.length\n  const namespace = store._modules.getNamespace(path)\n\n  // register in namespace map\n  if (module.namespaced) {\n    store._modulesNamespaceMap[namespace] = module\n  }\n\n  // set state\n  if (!isRoot && !hot) {\n    const parentState = getNestedState(rootState, path.slice(0, -1))\n    const moduleName = path[path.length - 1]\n    store._withCommit(() => {\n      Vue.set(parentState, moduleName, module.state)\n    })\n  }\n\n  const local = module.context = makeLocalContext(store, namespace, path)\n\n  module.forEachMutation((mutation, key) => {\n    const namespacedType = namespace + key\n    registerMutation(store, namespacedType, mutation, local)\n  })\n\n  module.forEachAction((action, key) => {\n    const type = action.root ? key : namespace + key\n    const handler = action.handler || action\n    registerAction(store, type, handler, local)\n  })\n\n  module.forEachGetter((getter, key) => {\n    const namespacedType = namespace + key\n    registerGetter(store, namespacedType, getter, local)\n  })\n\n  module.forEachChild((child, key) => {\n    installModule(store, rootState, path.concat(key), child, hot)\n  })\n}\n```\n\n`installModule` 方法支持 5 个参数，`store` 表示 `root store`；`state` 表示 `root state`；`path` 表示模块的访问路径；`module` 表示当前的模块，`hot` 表示是否是热更新。\n\n接下来看函数逻辑，这里涉及到了命名空间的概念，默认情况下，模块内部的 `action`、`mutation` 和 `getter` 是注册在全局命名空间的——这样使得多个模块能够对同一 `mutation` 或 `action` 作出响应。如果我们希望模块具有更高的封装度和复用性，可以通过添加 `namespaced: true` 的方式使其成为带命名空间的模块。当模块被注册后，它的所有 `getter`、`action` 及 `mutation` 都会自动根据模块注册的路径调整命名。例如：\n\n```js\nconst store = new Vuex.Store({\n  modules: {\n    account: {\n      namespaced: true,\n\n      // 模块内容（module assets）\n      state: { ... }, // 模块内的状态已经是嵌套的了，使用 `namespaced` 属性不会对其产生影响\n      getters: {\n        isAdmin () { ... } // -> getters['account/isAdmin']\n      },\n      actions: {\n        login () { ... } // -> dispatch('account/login')\n      },\n      mutations: {\n        login () { ... } // -> commit('account/login')\n      },\n\n      // 嵌套模块\n      modules: {\n        // 继承父模块的命名空间\n        myPage: {\n          state: { ... },\n          getters: {\n            profile () { ... } // -> getters['account/profile']\n          }\n        },\n\n        // 进一步嵌套命名空间\n        posts: {\n          namespaced: true,\n\n          state: { ... },\n          getters: {\n            popular () { ... } // -> getters['account/posts/popular']\n          }\n        }\n      }\n    }\n  }\n})\n```\n\n回到 `installModule` 方法，我们首先根据 `path` 获取 `namespace`：\n\n```js\nconst namespace = store._modules.getNamespace(path)\n```\n\n`getNamespace` 的定义在 `src/module/module-collection.js` 中：\n\n```js\ngetNamespace (path) {\n  let module = this.root\n  return path.reduce((namespace, key) => {\n    module = module.getChild(key)\n    return namespace + (module.namespaced ? key + '/' : '')\n  }, '')\n}\n```\n\n从 `root module` 开始，通过 `reduce` 方法一层层找子模块，如果发现该模块配置了 `namespaced` 为 true，则把该模块的 `key` 拼到 `namesapce` 中，最终返回完整的 `namespace` 字符串。\n\n回到 `installModule` 方法，接下来把 `namespace` 对应的模块保存下来，为了方便以后能根据 `namespace` 查找模块：\n\n```js\nif (module.namespaced) {\n  store._modulesNamespaceMap[namespace] = module\n}\n```\n\n接下来判断非 `root module` 且非 `hot` 的情况执行一些逻辑，我们稍后再看。\n\n接着是很重要的逻辑，构造了一个本地上下文环境：\n\n```js\nconst local = module.context = makeLocalContext(store, namespace, path)\n```\n\n来看一下 `makeLocalContext` 实现：\n\n```js\nfunction makeLocalContext (store, namespace, path) {\n  const noNamespace = namespace === ''\n\n  const local = {\n    dispatch: noNamespace ? store.dispatch : (_type, _payload, _options) => {\n      const args = unifyObjectStyle(_type, _payload, _options)\n      const { payload, options } = args\n      let { type } = args\n\n      if (!options || !options.root) {\n        type = namespace + type\n        if (process.env.NODE_ENV !== 'production' && !store._actions[type]) {\n          console.error(`[vuex] unknown local action type: ${args.type}, global type: ${type}`)\n          return\n        }\n      }\n\n      return store.dispatch(type, payload)\n    },\n\n    commit: noNamespace ? store.commit : (_type, _payload, _options) => {\n      const args = unifyObjectStyle(_type, _payload, _options)\n      const { payload, options } = args\n      let { type } = args\n\n      if (!options || !options.root) {\n        type = namespace + type\n        if (process.env.NODE_ENV !== 'production' && !store._mutations[type]) {\n          console.error(`[vuex] unknown local mutation type: ${args.type}, global type: ${type}`)\n          return\n        }\n      }\n\n      store.commit(type, payload, options)\n    }\n  }\n\n  // getters and state object must be gotten lazily\n  // because they will be changed by vm update\n  Object.defineProperties(local, {\n    getters: {\n      get: noNamespace\n        ? () => store.getters\n        : () => makeLocalGetters(store, namespace)\n    },\n    state: {\n      get: () => getNestedState(store.state, path)\n    }\n  })\n\n  return local\n}\n```\n\n`makeLocalContext` 支持 3 个参数相关，`store` 表示 `root store`；`namespace` 表示模块的命名空间，`path` 表示模块的 `path`。\n\n该方法定义了 `local` 对象，对于 `dispatch` 和 `commit` 方法，如果没有 `namespace`，它们就直接指向了 `root store` 的 `dispatch` 和 `commit` 方法，否则会创建方法，把 `type` 自动拼接上 `namespace`，然后执行 `store` 上对应的方法。\n\n对于 `getters` 而言，如果没有 `namespace`，则直接返回 `root store` 的 `getters`，否则返回 `makeLocalGetters(store, namespace)` 的返回值：\n\n```js\nfunction makeLocalGetters (store, namespace) {\n  const gettersProxy = {}\n\n  const splitPos = namespace.length\n  Object.keys(store.getters).forEach(type => {\n    // skip if the target getter is not match this namespace\n    if (type.slice(0, splitPos) !== namespace) return\n\n    // extract local getter type\n    const localType = type.slice(splitPos)\n\n    // Add a port to the getters proxy.\n    // Define as getter property because\n    // we do not want to evaluate the getters in this time.\n    Object.defineProperty(gettersProxy, localType, {\n      get: () => store.getters[type],\n      enumerable: true\n    })\n  })\n\n  return gettersProxy\n}\n```\n\n`makeLocalGetters` 首先获取了 `namespace` 的长度，然后遍历 `root store` 下的所有 `getters`，先判断它的类型是否匹配 `namespace`，只有匹配的时候我们从 `namespace` 的位置截取后面的字符串得到 `localType`，接着用 `Object.defineProperty` 定义了 `gettersProxy`，获取 `localType` 实际上是访问了 `store.getters[type]`。\n\n回到 `makeLocalContext` 方法，再来看一下对 `state` 的实现，它的获取则是通过 `getNestedState(store.state, path)` 方法：\n\n```js\nfunction getNestedState (state, path) {\n  return path.length\n    ? path.reduce((state, key) => state[key], state)\n    : state\n}\n```\n\n`getNestedState` 逻辑很简单，从 `root state` 开始，通过 `path.reduce` 方法一层层查找子模块 `state`，最终找到目标模块的 `state`。\n\n\n那么构造完 `local` 上下文后，我们再回到 `installModule` 方法，接下来它就会遍历模块中定义的 `mutations`、`actions`、`getters`，分别执行它们的注册工作，它们的注册逻辑都大同小异。\n\n- `registerMutation`\n\n```js\nmodule.forEachMutation((mutation, key) => {\n  const namespacedType = namespace + key\n  registerMutation(store, namespacedType, mutation, local)\n})\n\nfunction registerMutation (store, type, handler, local) {\n  const entry = store._mutations[type] || (store._mutations[type] = [])\n  entry.push(function wrappedMutationHandler (payload) {\n    handler.call(store, local.state, payload)\n  })\n}\n```\n\n首先遍历模块中的 `mutations` 的定义，拿到每一个 `mutation` 和 `key`，并把 `key` 拼接上 `namespace`，然后执行 `registerMutation` 方法。该方法实际上就是给 `root store` 上的 `_mutations[types]` 添加 `wrappedMutationHandler` 方法，该方法的具体实现我们之后会提到。注意，同一 `type` 的 `_mutations` 可以对应多个方法。\n\n- `registerAction`\n \n````js\nmodule.forEachAction((action, key) => {\n  const type = action.root ? key : namespace + key\n  const handler = action.handler || action\n  registerAction(store, type, handler, local)\n})\n\nfunction registerAction (store, type, handler, local) {\n  const entry = store._actions[type] || (store._actions[type] = [])\n  entry.push(function wrappedActionHandler (payload, cb) {\n    let res = handler.call(store, {\n      dispatch: local.dispatch,\n      commit: local.commit,\n      getters: local.getters,\n      state: local.state,\n      rootGetters: store.getters,\n      rootState: store.state\n    }, payload, cb)\n    if (!isPromise(res)) {\n      res = Promise.resolve(res)\n    }\n    if (store._devtoolHook) {\n      return res.catch(err => {\n        store._devtoolHook.emit('vuex:error', err)\n        throw err\n      })\n    } else {\n      return res\n    }\n  })\n}\n```` \n\n首先遍历模块中的 `actions` 的定义，拿到每一个 `action` 和 `key`，并判断 `action.root`，如果否的情况把 `key` 拼接上 `namespace`，然后执行 `registerAction` 方法。该方法实际上就是给 `root store` 上的 `_actions[types]` 添加 `wrappedActionHandler` 方法，该方法的具体实现我们之后会提到。注意，同一 `type` 的 `_actions` 可以对应多个方法。\n\n- `registerGetter`\n\n```js\nmodule.forEachGetter((getter, key) => {\n  const namespacedType = namespace + key\n  registerGetter(store, namespacedType, getter, local)\n})\n\n\nfunction registerGetter (store, type, rawGetter, local) {\n  if (store._wrappedGetters[type]) {\n    if (process.env.NODE_ENV !== 'production') {\n      console.error(`[vuex] duplicate getter key: ${type}`)\n    }\n    return\n  }\n  store._wrappedGetters[type] = function wrappedGetter (store) {\n    return rawGetter(\n      local.state, // local state\n      local.getters, // local getters\n      store.state, // root state\n      store.getters // root getters\n    )\n  }\n}\n```\n\n首先遍历模块中的 `getters` 的定义，拿到每一个 `getter` 和 `key`，并把 `key` 拼接上 `namespace`，然后执行 `registerGetter` 方法。该方法实际上就是给 `root store` 上的 `_wrappedGetters[key]` 指定 `wrappedGetter` 方法，该方法的具体实现我们之后会提到。注意，同一 `type` 的 `_wrappedGetters` 只能定义一个。\n\n再回到 `installModule` 方法，最后一步就是遍历模块中的所有子 `modules`，递归执行 `installModule` 方法：\n\n````js\nmodule.forEachChild((child, key) => {\n  installModule(store, rootState, path.concat(key), child, hot)\n})\n````\n\n之前我们忽略了非 `root module` 下的 `state` 初始化逻辑，现在来看一下：\n\n```js\nif (!isRoot && !hot) {\n  const parentState = getNestedState(rootState, path.slice(0, -1))\n  const moduleName = path[path.length - 1]\n  store._withCommit(() => {\n    Vue.set(parentState, moduleName, module.state)\n  })\n}\n```\n\n之前我们提到过 `getNestedState` 方法，它是从 `root state` 开始，一层层根据模块名能访问到对应 `path` 的 `state`，那么它每一层关系的建立实际上就是通过这段 `state` 的初始化逻辑。`store._withCommit` 方法我们之后再介绍。\n\n所以 `installModule` 实际上就是完成了模块下的 `state`、`getters`、`actions`、`mutations` 的初始化工作，并且通过递归遍历的方式，就完成了所有子模块的安装工作。\n\n### 初始化 `store._vm`\n\n`Store` 实例化的最后一步，就是执行初始化 `store._vm` 的逻辑，它的入口代码是：\n\n```js\nresetStoreVM(this, state)\n```\n\n来看一下 `resetStoreVM` 的定义：\n\n```js\nfunction resetStoreVM (store, state, hot) {\n  const oldVm = store._vm\n\n  // bind store public getters\n  store.getters = {}\n  const wrappedGetters = store._wrappedGetters\n  const computed = {}\n  forEachValue(wrappedGetters, (fn, key) => {\n    // use computed to leverage its lazy-caching mechanism\n    computed[key] = () => fn(store)\n    Object.defineProperty(store.getters, key, {\n      get: () => store._vm[key],\n      enumerable: true // for local getters\n    })\n  })\n\n  // use a Vue instance to store the state tree\n  // suppress warnings just in case the user has added\n  // some funky global mixins\n  const silent = Vue.config.silent\n  Vue.config.silent = true\n  store._vm = new Vue({\n    data: {\n      $$state: state\n    },\n    computed\n  })\n  Vue.config.silent = silent\n\n  // enable strict mode for new vm\n  if (store.strict) {\n    enableStrictMode(store)\n  }\n\n  if (oldVm) {\n    if (hot) {\n      // dispatch changes in all subscribed watchers\n      // to force getter re-evaluation for hot reloading.\n      store._withCommit(() => {\n        oldVm._data.$$state = null\n      })\n    }\n    Vue.nextTick(() => oldVm.$destroy())\n  }\n}\n```\n\n`resetStoreVM` 的作用实际上是想建立 `getters` 和 `state` 的联系，因为从设计上  `getters` 的获取就依赖了 `state` ，并且希望它的依赖能被缓存起来，且只有当它的依赖值发生了改变才会被重新计算。因此这里利用了 Vue 中用 `computed` 计算属性来实现。\n\n`resetStoreVM` 首先遍历了 `_wrappedGetters` 获得每个 `getter` 的函数 `fn` 和 `key`，然后定义了 `computed[key] = () => fn(store)`。我们之前提到过 `_wrappedGetters` 的初始化过程，这里 `fn(store)` 相当于执行如下方法：\n\n```js\nstore._wrappedGetters[type] = function wrappedGetter (store) {\n  return rawGetter(\n    local.state, // local state\n    local.getters, // local getters\n    store.state, // root state\n    store.getters // root getters\n  )\n}\n```\n\n返回的就是 `rawGetter` 的执行函数，`rawGetter` 就是用户定义的 `getter` 函数，它的前 2 个参数是 `local state` 和 `local getters`，后 2 个参数是 `root state` 和 `root getters`。\n\n接着实例化一个 Vue 实例 `store._vm`，并把 `computed` 传入：\n\n```js\nstore._vm = new Vue({\n  data: {\n    $$state: state\n  },\n  computed\n})\n```\n\n我们发现 `data` 选项里定义了 `$$state` 属性，而我们访问 `store.state` 的时候，实际上会访问 `Store` 类上定义的 `state` 的 `get` 方法：\n\n```js\nget state () {\n  return this._vm._data.$$state\n}\n```\n\n它实际上就访问了 `store._vm._data.$$state`。那么 `getters` 和 `state` 是如何建立依赖逻辑的呢，我们再看这段代码逻辑：\n\n```js\nforEachValue(wrappedGetters, (fn, key) => {\n    // use computed to leverage its lazy-caching mechanism\n    computed[key] = () => fn(store)\n    Object.defineProperty(store.getters, key, {\n      get: () => store._vm[key],\n      enumerable: true // for local getters\n    })\n  })\n```\n\n当我根据 `key` 访问 `store.getters` 的某一个 `getter` 的时候，实际上就是访问了 `store._vm[key]`，也就是 `computed[key]`，在执行 `computed[key]` 对应的函数的时候，会执行 `rawGetter(local.state,...)` 方法，那么就会访问到 `store.state`，进而访问到 `store._vm._data.$$state`，这样就建立了一个依赖关系。当 `store.state` 发生变化的时候，下一次再访问 `store.getters` 的时候会重新计算。\n\n我们再来看一下 `strict mode` 的逻辑：\n \n```js\nif (store.strict) {\n  enableStrictMode(store)\n}\n\nfunction enableStrictMode (store) {\n  store._vm.$watch(function () { return this._data.$$state }, () => {\n    if (process.env.NODE_ENV !== 'production') {\n      assert(store._committing, `Do not mutate vuex store state outside mutation handlers.`)\n    }\n  }, { deep: true, sync: true })\n}\n```\n\n当严格模式下，`store._vm` 会添加一个 `wathcer` 来观测 `this._data.$$state` 的变化，也就是当 `store.state` 被修改的时候, `store._committing` 必须为 true，否则在开发阶段会报警告。`store._committing` 默认值是 `false`，那么它什么时候会 true 呢，`Store` 定义了 `_withCommit` 实例方法：\n\n```js\n_withCommit (fn) {\n  const committing = this._committing\n  this._committing = true\n  fn()\n  this._committing = committing\n}\n```\n\n它就是对 `fn` 包装了一个环境，确保在 `fn` 中执行任何逻辑的时候 `this._committing = true`。所以外部任何非通过 Vuex 提供的接口直接操作修改 `state` 的行为都会在开发阶段触发警告。\n\n## 总结\n\n那么至此，Vuex 的初始化过程就分析完毕了，除了安装部分，我们重点分析了 `Store` 的实例化过程。我们要把 `store` 想象成一个数据仓库，为了更方便的管理仓库，我们把一个大的 `store` 拆成一些 `modules`，整个 `modules` 是一个树型结构。每个 `module` 又分别定义了 `state`，`getters`，`mutations`、`actions`，我们也通过递归遍历模块的方式都完成了它们的初始化。为了 `module` 具有更高的封装度和复用性，还定义了 `namespace` 的概念。最后我们还定义了一个内部的 `Vue` 实例，用来建立 `state` 到 `getters` 的联系，并且可以在严格模式下监测 `state` 的变化是不是来自外部，确保改变 `state` 的唯一途径就是显式地提交 `mutation`。\n\n这一节我们已经建立好 `store`，接下来就是对外提供了一些 API 方便我们对这个 `store` 做数据存取的操作，下一节我们就来从源码角度来分析 `Vuex` 提供的一系列 API。"
  },
  {
    "path": "docs/v2/vuex/plugin.md",
    "content": "# 插件\n\nVuex 除了提供的存取能力，还提供了一种插件能力，让我们可以监控 `store` 的变化过程来做一些事情。\n\nVuex 的 `store` 接受 `plugins` 选项，我们在实例化 `Store` 的时候可以传入插件，它是一个数组，然后在执行 `Store` 构造函数的时候，会执行这些插件：\n\n```js\nconst {\n  plugins = [],\n  strict = false\n} = options\n// apply plugins\nplugins.forEach(plugin => plugin(this))\n```\n\n在我们实际项目中，我们用到的最多的就是 Vuex 内置的 `Logger` 插件，它能够帮我们追踪 `state` 变化，然后输出一些格式化日志。下面我们就来分析这个插件的实现。\n\n## `Logger` 插件\n\n`Logger` 插件的定义在 `src/plugins/logger.js` 中：\n\n```js\nexport default function createLogger ({\n  collapsed = true,\n  filter = (mutation, stateBefore, stateAfter) => true,\n  transformer = state => state,\n  mutationTransformer = mut => mut,\n  logger = console\n} = {}) {\n  return store => {\n    let prevState = deepCopy(store.state)\n\n    store.subscribe((mutation, state) => {\n      if (typeof logger === 'undefined') {\n        return\n      }\n      const nextState = deepCopy(state)\n\n      if (filter(mutation, prevState, nextState)) {\n        const time = new Date()\n        const formattedTime = ` @ ${pad(time.getHours(), 2)}:${pad(time.getMinutes(), 2)}:${pad(time.getSeconds(), 2)}.${pad(time.getMilliseconds(), 3)}`\n        const formattedMutation = mutationTransformer(mutation)\n        const message = `mutation ${mutation.type}${formattedTime}`\n        const startMessage = collapsed\n          ? logger.groupCollapsed\n          : logger.group\n\n        // render\n        try {\n          startMessage.call(logger, message)\n        } catch (e) {\n          console.log(message)\n        }\n\n        logger.log('%c prev state', 'color: #9E9E9E; font-weight: bold', transformer(prevState))\n        logger.log('%c mutation', 'color: #03A9F4; font-weight: bold', formattedMutation)\n        logger.log('%c next state', 'color: #4CAF50; font-weight: bold', transformer(nextState))\n\n        try {\n          logger.groupEnd()\n        } catch (e) {\n          logger.log('—— log end ——')\n        }\n      }\n\n      prevState = nextState\n    })\n  }\n}\n\nfunction repeat (str, times) {\n  return (new Array(times + 1)).join(str)\n}\n\nfunction pad (num, maxLength) {\n  return repeat('0', maxLength - num.toString().length) + num\n}\n```\n\n插件函数接收的参数是 `store` 实例，它执行了 `store.subscribe` 方法，先来看一下 `subscribe` 的定义：\n\n```js\nsubscribe (fn) {\n  return genericSubscribe(fn, this._subscribers)\n}\n\nfunction genericSubscribe (fn, subs) {\n  if (subs.indexOf(fn) < 0) {\n    subs.push(fn)\n  }\n  return () => {\n    const i = subs.indexOf(fn)\n    if (i > -1) {\n      subs.splice(i, 1)\n    }\n  }\n}\n```\n\n`subscribe` 的逻辑很简单，就是往 `this._subscribers` 去添加一个函数，并返回一个 `unsubscribe` 的方法。\n\n而我们在执行 `store.commit` 的方法的时候，会遍历 `this._subscribers` 执行它们对应的回调函数：\n\n```js\ncommit (_type, _payload, _options) {\n  const {\n    type,\n    payload,\n    options\n  } = unifyObjectStyle(_type, _payload, _options)\n\n  const mutation = { type, payload }\n  // ...\n  this._subscribers.forEach(sub => sub(mutation, this.state))  \n}\n```\n\n回到我们的 `Logger` 函数，它相当于订阅了 `mutation` 的提交，它的 `prevState` 表示之前的 `state`，`nextState` 表示提交 `mutation` 后的 `state`，这两个 `state` 都需要执行 `deepCopy` 方法拷贝一份对象的副本，这样对他们的修改就不会影响原始 `store.state`。\n\n接下来就构造一些格式化的消息，打印出一些时间消息 `message`， 之前的状态 `prevState`，对应的 `mutation` 操作 `formattedMutation` 以及下一个状态 `nextState`。\n\n最后更新 `prevState = nextState`，为下一次提交 `mutation` 输出日志做准备。\n\n## 总结\n\n那么至此 Vuex 的插件分析就结束了，Vuex 从设计上支持了插件，让我们很好地从外部追踪 `store` 内部的变化，`Logger` 插件在我们的开发阶段也提供了很好地指引作用。当然我们也可以自己去实现 `Vuex` 的插件，来帮助我们实现一些特定的需求。\n"
  },
  {
    "path": "docs/v3/guide/index.md",
    "content": "# Vue.js 3.x 源码解析先导\n\n## 前言\n\n2018 年 6 月我在慕课网发布了 Vue.js 2.x 的源码解析课程 [《Vue.js 源码全方位深入解析》](https://coding.imooc.com/class/228.html)，同时也开源了课程配套[电子书](https://ustbhuangyi.github.io/vue-analysis/)。时隔一年多，Vue 官方也开源了 Vue.js 3.x，那么在不久的将来，我也会系统化地做 Vue.js 3.x 的源码分析，同时更新我的这门课程视频以及电子书。\n\nVue.js 3.x 源码刚开源不久，很多人都非常兴奋，我也不例外。我写下这篇文章作为 Vue.js 3.x 源码解析课程的先导片，和大家聊聊我对 Vue.js 源码的一些感悟。\n\n## 聊聊 Vue.js 3.x \n\n###  Vue.js 3.x 目前的状态\n\nVue.js 3.x 目前处于 **Pre-Alpha** 的状态，从 Vue 官方的 [Roadmap](https://github.com/vuejs/vue/projects/6) 来看，2019 年 Q3 结束前开源 Vue 3.x 的源码，Q4 除了继续完善 Vue.js 核心源码之外，还要补齐周边的生态建设：如 `vue-router`、`vuex`、`vue-cli`、`Vue Devtools`、`JSX` 等，在 Q4 结束前才会发布 Alpha 版本。但是 Alpha 版本也只是内部测试版本，之后还要经历 Beta 对外测试版本，RC 候选发布版本、最后才会到正式的 Realase 版本，所以距离大家在生产环境投入使用还有很长的时间。那么这段时间，对于 Vue.js 3.x 我可以做哪些事情呢。\n\n###  Vue.js RFC\n\nVue.js 官方设立 RFC 的初衷是为了让 Vue.js 本身的开发流程更加规范化，当有一个新功能的想法出现，会先发布一份 RFC 的提案，由社区在一起讨论，当提案通过后再去开发实现。\n\nVue.js 3.x 在开发之前也发布了多份 RFC 提案，其中讨论比较多的是 Vue.js 3.x 关于组件的写法，由最初的 [Class-API](https://github.com/vuejs/rfcs/pull/17) 提案被废弃到之后热烈讨论的 [Function-based component API](https://github.com/vuejs/rfcs/pull/42)，再到最后确认的基于 `Function-based component API` 修订的 [Composition API](https://github.com/vuejs/rfcs/pull/78)，经历了很长一段的时间，期间社区出现了不少反对的声音，比如 “和 React 更像了，为啥我不直接用 React”、“Class API 更好”、“Vue.js 变得一点都不简单了” 等等，官方都做了很好的[回应](https://github.com/vuejs/rfcs/blob/function-apis/active-rfcs/0000-function-api.md)，因此学习 Vue.js 3.x，你应该先去学习这份 [RFC](https://vue-composition-api-rfc.netlify.com/)。\n\n通过这份 RFC 的学习，你会大致了解 Vue.js 3.x 组件的写法、详细设计、甚至是一些”缺点“。Vue.js 3.x 摒弃了 2.x `Options API`，拥抱了 `Composition API`，为了更好的逻辑复用、代码组织以及更好的类型推导。\n\n### Vue.js 3.x 尝鲜\n\nVue.js 3.x [源码](https://github.com/vuejs/vue-next)已经开放，虽然没有发布，但是我们可以 clone 下来，安装好相关依赖，构建一份打包后的代码为自己所用。\n\n在阅读完 `Composition API` 的 RFC 后，我们已经对 Vue.js 3.x 组件的写法有了一定了解，并且 2.x 的大部分 feature 3.x 都已经支持，我们用 3.x 写一个简单的 demo 问题应该不大。我前段时间就基于 Vue 3.x 写了一个 todomvc 的 demo，感兴趣的同学可以去 [GitHub](https://github.com/ustbhuangyi/vue-3.x-demos)  clone 下来跑跑看看。\n\n在写 demo 的时候我还遇到了 `v-model` 实现的坑，对源码一番调试后大致定位了原因，不过由于牵涉到核心的改动会比较多，所以我和尤大反馈了一下（微信提 issue），官方很快就修复了这个问题。\n\n### Vue.js 3.x 源码\n\nVue.js 3.x 源码放出来的第二天，社区就有出来源码分析的文章，不过看了好几篇都是在分析 `Reactive` 相关的 API，给人的错觉好像 Vue 只有响应式一样，甚至还有某些培训机构也跟着蹭起了热度。有些文章写的还是很不错的，比如我记得掘金有一篇是教大家从单测看起，确实是一个很好的学习源码的思路，但还有几篇也未免有蹭热度之嫌。对我而言，除了 `Reactive`，我更愿意去关注 `Setup` 函数的初始化逻辑、`Compile` 过程的优化、`Render` 写法的变化、以及 `Patch` 过程的优化。\n\nVue.js 3.x 源码采用了 monorepo 的管理方式，采用 TypeScript 编写，对于 Vue.js 的开发者而言，这种方式是更易于维护源码的。如果你想学习 Vue.js 3.x 的源码，首先你得学会 TypeScript。\n\n对于大部分人而言，现在去看 Vue.js 3.x 的源码还为时过早了，主要是你现在还用不到，我之前在掘金发布过一篇文章[来聊聊源码学习](https://juejin.im/post/5b18d2d7f265da6e410e0e20)，现在还不是学习 Vue.js 3.x 源码的好时机。\n\n但是如果你是一个对技术非常有热情的人，在早期去学习 Vue.js 3.x 源码，甚至去参与 Vue.js 3.x 的开发共建，对自己的技术提升还是有很大帮助的。\n\n\n## Vue.js 2.x 源码过时了吗\n\nVue.js 3.x 源码开放了，很多小伙伴不免担心，我现在学习 Vue.js 2.x 的源码过时了吗？\n\n### 成熟稳定的 Vue.js 2.x\n\nVue.js 2.x 从 16 年底发布距今已接近 3 年，有无数大厂已经使用 Vue.js 重构和开发项目，Vue.js 2.x 的 npm 下载量每月有 90 多万，Jsdelivr CDN 每月有 5 亿次引用，Chrome DevTools 每周有 90 万的活跃用户。如此庞大的用户量足以说明 Vue.js 是一个非常靠谱和成熟的框架，另外官网对 Issue、Pull Request 的响应也是比较快的，除了高达 97% 的单元测试之外，官方还尝试做了一些[回归测试](https://github.com/vuejs/regression-testing)。\n\n我们知道 Vue.js 是一个渐进式框架，除了官方提供的一些生态插件 `vue-router`、`vuex`、`vue-cli` 之外，社区还有非常多的优秀的轮子如 `element-ui`、`cube-ui`、`vue-lazyload`、`vue-i18n` 等，这些插件能很好地辅助我们平时的业务开发。\n\n### 升级的成本\n\nVue .js 2.x -> Vue.js 3.x 升级还是有一定的成本的，虽然说官方会出一个保留 `Options API` 的写法的版本，但是未免还会有一些 breaking change 的，比如手写 `render` 函数部分语法就已经发生了改变，模板写法也会发生一些变化。未来应该会出一个代码升级的指南，甚至会用工具帮我们做一部分工作，但是大规模的产线项目做核心框架升级，还是有相当大的成本和风险的。\n\n如果你的业务代码升级到 Vue.js 3.x，也就意味着你依赖的生态插件也需要升级到 Vue.js 3.x，比如 `element-ui` 这种大型项目，升级起来也是有相当大的工作量的，所以你需要先等到你依赖的生态插件升级到 Vue.js 3.x 并且稳定后，你才能考虑在你的业务中做框架升级。\n\nVue.js 1.x -> Vue.js 2.x 的升级似乎没有那么麻烦，那是因为 Vue.js 1.x 的时候用户规模还很小，生态也没有起来，甚至很多公司直接上手的 Vue.js 2.x，并没有历史包袱。\n\n### 痛点\n\nVue.js 1.x -> Vue.js 2.x 的升级变化还是很明显的，虚拟 DOM 在 Vue.js 2.x 中得以实现，它让服务端渲染、跨端渲染成为可能。我们来看一下 Vue.js 3.x 的设计目标：更小、更快、加强 TypeScript 支持、加强 API 设计一致性、提升自身可维护性、开放更多的底层功能。对大部分用户而言，更小更快是一个吸引点，对于 TypeScript 用户而言，加强 TypeScript 支持是一个吸引点，但是这些能解决开发中的痛点么？\n\n除非  Vue.js 3.x 能解决 Vue.js 2.x 开发中的痛点（比如我这个项目有性能瓶颈，性能的提升能帮助我解决这个性能瓶颈），否则重构的成本和它来带来的收益就是一个需要权衡的问题。另外考虑到 Vue.js 3.x 用了一些 ES6 的新特性如 Proxy，在浏览器兼容性这块也是需要考虑的。\n\n老板通常是不会允许你做这种纯技术重构的，如果你想用 Vue.js 3.x 做重构，一定要抓到痛点，把重构的收益和老板说清楚。\n\n虽然老项目用 Vue.js 3.x 重构会有很大的成本和风险，我们也可以在一些非核心的新项目中去尝试新技术，当然这一切也是需要等待 Vue.js 3.x 正式发布以及依赖的 Vue 插件都更新支持 Vue.js 3.x 才可以。\n\n### 结论\n\nVue.js 3.x 想全面替代 Vue.js 2.x 需要有相当长的路要走，未来相当长一段时间 Vue.js 2.x 仍然是主流，Vue.js 2.x 的源码学习并没有过时，如果你是一个 Vue.js 2.x 的使用者，就应该去学习 Vue.js 2.x 的源码。\n\n## 我应该学习源码吗\n\n很多人都有困惑，我会使用不就行了吗，为什么还要学习源码呢？\n\n### 学习源码的好处\n\n学习是为了更好的工作，工作中难免会遇到一些问题，学习源码最直接的好处是能帮你直接定位问题的根本原因，从而帮助你解决问题。很多人抱怨加班多，不妨问问自己，有多少时间是在写业务，多少时间是在写（找） bug。快速定位问题解决 bug，可以有效地提升你的工作效率，很可能就不用加班了，甚至会多出学习的时间，形成一个良性循环。\n\n学习源码可以很好地巩固基础，修炼内功，提升技术。前端几乎都会学习 JS 的基础知识，如类型、变量、函数、作用域、闭包、原型链、event loop 等知识，但很多人很难把这些知识在实践中运用自如，主要原因还是实践的少了，大部分时间都在写业务的胶水代码。学习 Vue.js 这类框架的源码，会不断去巩固这些知识点，如果你源码看熟练了，那么你的 JS 基础就会更扎实。\n\n学习源码有助于你更好地理解所用的技术栈，更熟练地在工作中运用。比如你深入学习了 Vue.js 的核心源码，你会理解 Vue.js 框架产生的意义、Vue.js 的职责边界、数据驱动的本质；你还会知道如何实现的组件化，在什么生命周期应该做什么事情，如何编写 Vue.js 的插件，如何和其它第三方 JS 库深度结合。你再也不会问“如何用 Vue 实现 XXX” 的傻问题了。\n\n学习源码可以让我们站在巨人的肩膀上，Vue.js 这么优秀，尤大也是参考了很多其他优秀源码的实现，比如 Vue.js 2.x  `Virtual DOM` 部分参考了 [snabbdom](https://github.com/snabbdom/snabbdom)，Vue.js 3.x `Reactive` 的实现参考了[Meteor Tracker](https://docs.meteor.com/api/tracker.html) 和 [salesforce/observable-membrane](https://github.com/salesforce/observable-membrane) 等。我们在阅读的源码的时候，也可以把源码中的优秀的设计思想、代码实现吸纳到我们平时的开发工作中。\n\n学习源码还有一个偏功利的作用，应付面试。越来越多的公司在面试环节会考察候选人对所用技术栈实现原理的考察，主要目的还是考察候选人的技术能力以及技术热情和追求，因为通常对技术热爱的人通常都会保持技术好奇，乐于探究所用到的技术栈的实现原理。但是往往以这个目的去学习源码的同学是学不好的，对源码的理解很浅，甚至出现了死记硬背的情况。所以学习好源码可以帮助我们在面试中应答自如，但是我们不应该为了面试去学习源码。\n\n### 学源码的时机\n\n通常我们去学习一个技术栈的源码的时机是在我们对他的使用已经很熟练的情况，比如你是一个 Vue.js 的一年以上经验的使用者，那么你已经可以去学习它的源码了，这时候你的学习应该是系统化地学习。\n\n当你工作中使用某一个新框架的时候遇到一个奇怪的问题，通过查阅文档也未能解决，这个时候你也可以去看源码，当然这个时候并不需要系统地去学习，只需要把和你问题相关的源码理解了，找到问题即可。当然想达到这一步就需要你有快速阅读源码定位问题的能力，这个能力也是在你不断去阅读大量优秀源码过程中锻炼的。\n\n### 学不动了怎么办\n\n源码学习的好处我们已经介绍了很多，但是源码学习的本身是枯燥的，抽象的，它没有直观酷炫的效果，学习起来费脑子，是很多人直呼学不动的原因。\n\n其实学不动的主要原因还是因为没掌握好的学习方法，好的方法能让你事半功倍，正如我在[来聊聊源码学习](https://juejin.im/post/5b18d2d7f265da6e410e0e20) 文章中提到的几个方法，全盘了解、问题驱动、主线优先、参与共建、阅读技巧、辅助资料。除了这些方法，根据我源码课程中一些学的不错的同学的经验，自己在学习的过程中多记笔记，多在课程问答区提问，甚至最后自己产出源码分析系列文章，都能非常好的辅助学习源码。其实做这些事情都是不断在帮助自己建立自信和成就感，激发学习兴趣，把无趣的事情变得有趣和有意义。\n\n## 课程后续计划\n\n~~源码课程会等待 Vue.js 3.x 发布正式版本且稳定后会开始准备重新录制，仍然是电子书和视频的方式。~~\n\n~~重新录制的源码课程不仅仅会讲清楚源码实现流程，还会多加入一些使用场景、设计原因的分析。~~\n\n~~翻新课程是在原课程的基础上增加 Vue.js 3.x 的章节，已购买课程的同学可以继续学习，无需购买新课程。~~\n\n~~先学会用再学原理，因此在 Vue.js 3.x 正式发布后，我会优先重新录制 [《Vue2.0开发企业级移动端音乐Web App》](https://coding.imooc.com/class/107.html)课程，同样是免费升级喔。~~\n\n\n~~音乐课程 + 源码课程的重新录制，是我明年的主要计划，暂无计划出新课程了。除了版本的升级，我会像[《Vue.js2.5 + cube-ui重构饿了么App》](https://coding.imooc.com/class/74.html)升级课程那样尽量往课程中加入一些新东西的，敬请期待~~\n\n参考 [聊聊我的新课《Vue.js 3.0 核心源码解析》​\n](../guide/index.md)\n\nVue.js 3.0 源码解析课程制作完毕后，我会启动[《Vue2.0开发企业级移动端音乐Web App》](https://coding.imooc.com/class/107.html)课程的 Vue.js 3.0 的重构 + 重新录制，免费升级。\n\n"
  },
  {
    "path": "docs/v3/new/index.md",
    "content": "# 聊聊我的新课《Vue.js 3.0 核心源码解析》​\n\n## 我为什么做这门课程\n\n2020 年 7 月 6 日，我的课程 《Vue.js 3.0 核心源码解析》在拉勾教育平台上线了，我想和大家聊聊我为什么做这门课，源码学习的心得以及与拉勾合作的一些感受。\n\n\n从 16 年底到现在，我每年都会出一门新课，截止到去年，我在慕课网已经上线了 4 门视频课程了。不过出视频课对我来说确实效率太低了，因为我个人的毛病，如果我在录制过程中有一些小瑕疵或者是讲错了，我是不会继续讲然后让后期剪辑，而是会停下来重新录制，结果就导致一小段视频不断地 NG，录制效率非常低下。\n\n\n所以 19 年我录完 《TypeScript 重构 Axios》课程后，就不打算做新课了，心想着维护一下现有的几门课程得了。但后来想想是不是还得产出点啥，于是 19 年下半年注册了公众号，写写原创文章应该也不错，因为工作相关的原因，我打算写系列 ElementUI 源码分析的文章，但不幸的是，我写着写着，突然有一天就不想写了。\n\n\n到现在我的草稿里还有一篇没发出去的文章，因为我发现 ElementUI 的源码对我来说太简单了，而且有些地方的实现也很粗糙，写完一看阅读数还没有一些撒鸡汤的文章多，评论也很少，我写下去没有动力和成就感了，对自己的提升也非常有限。于是，我鸽了，是的，我第一次在公开场合承认我鸽了，对不起。\n\n\n2020 年 4 月，拉勾的运营找到了我，问我愿不愿意合作一门 Vue.js 3.0 的课程。原本我是拒绝的，但是她说就是做一个专栏类的课程，主要通过文字 + 音频的形式呈现，我心想写文章那不是我擅长的么，也不会占用太多时间，于是我就说我考虑一下。\n\n\n从我 18 年做 《Vue.js 2.x 源码解析》课程的经验来看，源码这类抽象的技术用视频的方式呈现，对讲师来说是一个极大的挑战，我录完课程发现自己的发际线都明显高了一截，因为录起来实在太累了，一些比较深入的知识点，视频方式也不利于呈现。但是如果是写文章，那么就可以写的非常深入，而且不用担心过程中出错，因为写文章出错，改改就好了，最终呈现给用户就是完美的，而录视频的过程中是音画同步的，出一点错就得重来。\n\n\nVue.js 3.0 从去年下半年开始的 alpha 版本出来，社区就出来一些源码解析类文章，不过大部分都是分析 Vue.js 3.0 的响应式模块的实现，搞的就跟 Vue.js 3.0 就只有响应式一样。当然，响应式确实是一个很大的变化，但除此之外，Composition API，组件的渲染更新方式，编译的实现和优化，新的内置组件这些都是我感兴趣的东西，于是我系统地研究了一波 Vue.js 3.0 的源码，当然也希望自己能系统地输出 Vue.js 3.0 的源码解析文章。\n\n\n所以我最终答应了和拉勾的合作，并且你们也不用担心这门课程被鸽了，因为有合同呢，如果擅自鸽的话要支付一大笔违约金：）\n\n## 学习源码在工作中的收益\n\n有些人可能会好奇，我平时去研究这些源码有用吗？对我来说，用处非常大。\n\n今年 5 月份，我所在的公司 Zoom 在我们的 Web 项目中开启了 CSP 安全策略，其中把 `unsafe-eval` 从 `script-src` 中拿掉了，但是这么操作导致了一个很严重的问题，由于运行在 Web 的项目有一部分组件是通过 Vue.js 开发的，这部分代码全部不能正常工作了。\n\n虽然我到了 Zoom 后在公司推行了前后端分离的解决方案，并用该方案重构了十几个项目，但是还有很多项目并没有来得及重构，他们仍然是直接通过 CDN 的方式引入 Vue.js，并在后端的 Java 模板中写组件的 template，然后用在运行时编译模板。我们知道编译的过程最后是生成一段 code 字符串，然后通过 `new Function` 的方式转成 render 函数，但是 CSP 安全策略开启后，`new Function`  和 `eval` 都被禁用了，导致整个编译后的流程不能进行下去。\n\n\n既然有问题，我就得想办法解决。其实解决这个问题有两个思路，一个是全面推进前后端分离方案，使用 runtime-only 版本的 Vue.js，但这么做牵涉到所有使用 Vue.js 页面的改动，成本很高，短期不现实，是终极目标。另一个就是使用一个 CSP 兼容版本的 Vue.js，早在 Vue.js 1.x 版本的时代，Vue.js 官方提供了 Vue.js CSP 兼容版本，但是到了 Vue.js 2.x 后，官方就不再提供 CSP 兼容版本了，因为从官方的视角看，我都提供了 runtime-only 版本的解决方案了，完全没必要提供 CSP 兼容版本了。\n\n\n但实际上 CSP 兼容版本还是有需求的，比如社区有人提过一个 [issue](https://github.com/vuejs/vue/issues/9895)，然而官方压根就没搭理（从我做的经验来看，做这玩意成本还是不小的，花 99% 的精力解决 1% 的人的问题的买卖显然不会做）。\n\n虽然官方没有直接支持 CSP 兼容版本的 Vue.js，但对于我们来说，现阶段最小成本解决问题的方式就是使用一个 CSP 兼容版本的 Vue.js，所以只能魔改 Vue.js 了。\n\n那么，我们应该改哪些部分呢？\n\n首先，`new Function` 不能用了，那么生成的 code 字符串如何执行呢？经过调研，我选用了 notevil 这个库，它其实就是用 JavaScript 去实现 JavsScript 的解析引擎，大致原理是先把源码解析成 AST 树，再去遍历 AST 树，对不同类型的节点做不同的处理，达到最终执行 JavaScript 代码的目的。但是 notevil 的实现还是不够完整，比如一些 ES6 的语法，像箭头函数、数组对象的解构赋值，是不支持的；此外，还有一个致命的影响：对 `with` 的语法不支持。\n\nVue.js 2.x 组件模板最终编译的代码，是使用 `with` 语法做了一层包装，举个例子：\n\n```html\n<div>  \n  {{ message }}\n</div>\n```\n\n上述组件模板，最终编译生成的 code 如下：\n\n```js\nwith(this){return _c('div',[_v(_s(message))])}\n```\n\n这是什么意思呢，首先，Vue.js 为了让用户使用方便，在模板中访问数据不用手动加 `this`，但是实际上在模板中引用的变量都是定义在组件实例中的。比如我们上述例子中的 `message` 和 `text`，都是定义在组件实例上的，所以如果不用 `with(this)` 的话，我们需要生成如下的代码：\n\n```js\nfunction(_ctx) {\n  return _ctx._c('div,[_ctx._v(_ctx._s(_ctx.message))])\n}\n````\n\n我们定义一个函数，接受一个 `_ctx`  参数，这个 `_ctx` 在运行时就是组件传入的实例对象 `this`。\n\n这个时候，你可能会说，这有何难的，我们给所有的变量和函数的对象前面加上 `_ctx` 前缀不就可以了吗，但事情并没有你想的那么简单，我们简单地对上述示例做个变形：\n\n```html\n<div>  \n  {{ message + text }}\n</div>\n```\n\n显然，由于模板中可能存在复杂的表达式，我们最终希望的结果如下：\n\n```js\nfunction(_ctx) {\n  return _ctx._c('div,[_ctx._v(_ctx._s(_ctx.message + _ctx.text))])\n}\n```\n\n整个事情就变成了，我需要给生成代码中该加 `this` 的地方加 `this`。所以抛开了 `with this`，我们需要实现上述需求，怎么搞呢？\n\nVue.js 2.x 的编译会经过三个过程：template 解析生成 AST ——> AST 优化 ——> AST 生成 code。我的思路是尽量不改这三个过程，然后到最后再去加一个过程：转换生成的 code。对于前面的例子，也就是想办法把\n\n```js\nreturn _c('div',[_v(_s(message + text))])\n```\n\n转换成\n\n```js\nfunction(_ctx) {\n  return _ctx._c('div,[_ctx._v(_ctx._s(_ctx.message + _ctx.text))])\n}\n```\n\n具体怎么做呢，因为模板的可能性有千万种，所以最靠谱的方式就是先把转换前的代码解析生成 AST，再去遍历这颗 AST，根据语法在相关的位置上加上前缀（修改 AST 的 节点），最后再把修改后的 AST 转换成代码。\n\n\n我利用了 recast 库完成了code → AST 和  AST → code ，estree-walker 库去遍历 AST 的节点，通过一系列判断条件去判断这个节点需不需要加前缀。我们需要注意的是，函数的参数不能加前缀，局部变量不能加前缀，全局内置变量不能加前缀，已经加过前缀的节点不能加前缀等。\n\n这里比较有意思的地方是要考虑函数嵌套函数，也就是有闭包的情况。需要设计一个堆栈的数据结构，在函数进入入栈，函数退出出栈，如果是外层函数中定义的变量，内部函数是不能加前缀的。\n\n这里的第三方依赖 recast、estree-walker 原本都是在 node.js 端跑的，为了让它们在前端运行，我也分别 clone 了它们的代码， 用 rollup 对它们做打包，并删除了内部一些 node only 的代码和一定程度的魔改，最终编译出一份在 web 端跑的代码，放到了 lib 目录。\n\n\n具体代码细节我就不介绍了，我之所以有上述思路，其实是因为我研究过 Vue.js 3.0 的编译过程，发现它在离线编译的时候也会把结果编译成带前缀的，然后我就把它的实现原理搞清楚了，核心代码借过来，然后再做一些修改来支持自己特定的一些 feature，这个难题就被我解决了。最终魔改版的 Vue.js 也在主流浏览器下跑通了的 12 个 demo 以及跑通了 1300 多个单测。\n\n\n之前一直不太理解为什么 Vue.js 编译生成的代码需要用 `with` 包一层，因为 `with` 在 ECMAScript 5 的严格模式中是被禁用的，现在终于理解了对于模板内部用到了一些复杂表达式，利用 `with` 的特性动去指定的对象中查找即可，完全不用做多余的转换，也不用引入这些 AST 解析库了，因为引入这些库要让 Vue.js 最终打包的体积大了约四倍。\n\n\n另外，我们平时经常会强调技术选型的能力，其实技术选型的一个标准，就是你选择的第三方依赖，你能不能 hold 住。首先是你知道它的职责边界，知道它能做什么不能做什么，怎么利用它帮助你开发需求；其次是出了错你能不能快速定位到原因，知道是依赖的问题还是自身使用的问题；最后就是当它不能满足你的需求，并且官方不愿意解决或者不维护的情况下，你能不能去 fork 这个库，自己开发解决并实现。那么显然拥有这些能力就需要你对它的源码实现非常了解，所以这也是一些高阶岗位为什么会在面试中考察你对技术原理掌握的一方面原因。\n\n## 和拉勾合作的一些感受\n\n最后聊聊和拉勾合作的一些感受吧，拉勾的编辑们真的很用心，每一篇稿子，他们都会反复地跟我磨稿，我课程中的图，他们也会帮我再重新美化一遍，另外他们也会根据我的文字课程做成视频 PPT，一门课出来他们真的投入了很多资源，另外拉勾的编辑小姐姐为了能看懂我的文章还特地自学了 Vue.js。\n\n此外，拉勾教育真的是慈善教育，1 元购课的活动，对用户而言，几乎没有任何的决策成本，买就对了，另外买完课程还可以分销。\n\n对我而言，赚钱从来就不是我做课程的主要目的，我的目的就是去输出优质的课程，帮助更多的人进阶技术，并且也在做课程的过程中提升自己，而赚钱就是它的一个附属价值，你的课程质量好，有价值，自然就会有很多人来购买学习。\n\n相信通过和拉勾的合作，这门课会以最优质的状态呈现给大家，总之，如果你买了这门课程，一定要认真学习，并且有所收获，这样我和拉勾的付出就是值得的。\n\n## 写给我之前的学生\n\n我在慕课的《Vue.js 2.x 的源码解析》课程，之前是计划更新的，不过目前看来是不会再更新了，如果你是这门课的学生， 并且是冲着 Vue.js 3.0 来的，那么你看到这篇文章的时候，抓紧去拉勾花 1 元购买这门课，如果错过了 1 元活动，也不要紧，你可以在公众号后台或者是 issue 区给我留言，贴上你的慕课网源码课程的购买订单，我会给你发私信，帮你们去申请 1 元购的链接。\n\n另外，Vue.js 2.x 和 3.x 的源码学习不冲突，两者都很重要，并且很多思想是相同的，如果你是一个 Vue.js 的用户，你迟早都要去学习它们的。\n\n## 广告时间\n\n最后，放出[拉勾课程的链接](https://kaiwu.lagou.com/course/courseInfo.htm?courseId=326#/content)\n\n你可以扫下方二维码购买学习：\n\n<img :src=\"$withBase('/assets/qrcode.png')\" alt=\"qrcode\">\n\n也可以关注我的公众号，在后台给我留言\n\n<img :src=\"$withBase('/assets/qrcode_mp.jpg')\" alt=\"qrcode_mp\">\n\n\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"vue-analysis\",\n  \"version\": \"1.0.0\",\n  \"description\": \"analysis vue.js deeply\",\n  \"directories\": {\n    \"doc\": \"doc\"\n  },\n  \"scripts\": {\n    \"dev\": \"vuepress dev docs\",\n    \"build\": \"vuepress build docs\",\n    \"deploy\": \"sh build.sh\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/ustbhuangyi/vue-analysis.git\"\n  },\n  \"keywords\": [\n    \"vue\",\n    \"analysis\"\n  ],\n  \"author\": \"ustbuhuangyi\",\n  \"license\": \"MIT\",\n  \"bugs\": {\n    \"url\": \"https://github.com/ustbhuangyi/vue-analysis/issues\"\n  },\n  \"homepage\": \"https://github.com/ustbhuangyi/vue-analysis#readme\",\n  \"devDependencies\": {\n    \"vuepress\": \"^1.2.0\"\n  }\n}\n"
  },
  {
    "path": "vue/flow/compiler.js",
    "content": "declare type CompilerOptions = {\n  warn?: Function; // allow customizing warning in different environments; e.g. node\n  modules?: Array<ModuleOptions>; // platform specific modules; e.g. style; class\n  directives?: { [key: string]: Function }; // platform specific directives\n  staticKeys?: string; // a list of AST properties to be considered static; for optimization\n  isUnaryTag?: (tag: string) => ?boolean; // check if a tag is unary for the platform\n  canBeLeftOpenTag?: (tag: string) => ?boolean; // check if a tag can be left opened\n  isReservedTag?: (tag: string) => ?boolean; // check if a tag is a native for the platform\n  preserveWhitespace?: boolean; // preserve whitespace between elements?\n  optimize?: boolean; // optimize static content?\n\n  // web specific\n  mustUseProp?: (tag: string, type: ?string, name: string) => boolean; // check if an attribute should be bound as a property\n  isPreTag?: (attr: string) => ?boolean; // check if a tag needs to preserve whitespace\n  getTagNamespace?: (tag: string) => ?string; // check the namespace for a tag\n  expectHTML?: boolean; // only false for non-web builds\n  isFromDOM?: boolean;\n  shouldDecodeTags?: boolean;\n  shouldDecodeNewlines?:  boolean;\n  shouldDecodeNewlinesForHref?: boolean;\n\n  // runtime user-configurable\n  delimiters?: [string, string]; // template delimiters\n  comments?: boolean; // preserve comments in template\n\n  // for ssr optimization compiler\n  scopeId?: string;\n};\n\ndeclare type CompiledResult = {\n  ast: ?ASTElement;\n  render: string;\n  staticRenderFns: Array<string>;\n  stringRenderFns?: Array<string>;\n  errors?: Array<string>;\n  tips?: Array<string>;\n};\n\ndeclare type ModuleOptions = {\n  // transform an AST node before any attributes are processed\n  // returning an ASTElement from pre/transforms replaces the element\n  preTransformNode: (el: ASTElement) => ?ASTElement;\n  // transform an AST node after built-ins like v-if, v-for are processed\n  transformNode: (el: ASTElement) => ?ASTElement;\n  // transform an AST node after its children have been processed\n  // cannot return replacement in postTransform because tree is already finalized\n  postTransformNode: (el: ASTElement) => void;\n  genData: (el: ASTElement) => string; // generate extra data string for an element\n  transformCode?: (el: ASTElement, code: string) => string; // further transform generated code for an element\n  staticKeys?: Array<string>; // AST properties to be considered static\n};\n\ndeclare type ASTModifiers = { [key: string]: boolean };\ndeclare type ASTIfCondition = { exp: ?string; block: ASTElement };\ndeclare type ASTIfConditions = Array<ASTIfCondition>;\n\ndeclare type ASTElementHandler = {\n  value: string;\n  params?: Array<any>;\n  modifiers: ?ASTModifiers;\n};\n\ndeclare type ASTElementHandlers = {\n  [key: string]: ASTElementHandler | Array<ASTElementHandler>;\n};\n\ndeclare type ASTDirective = {\n  name: string;\n  rawName: string;\n  value: string;\n  arg: ?string;\n  modifiers: ?ASTModifiers;\n};\n\ndeclare type ASTNode = ASTElement | ASTText | ASTExpression;\n\ndeclare type ASTElement = {\n  type: 1;\n  tag: string;\n  attrsList: Array<{ name: string; value: any }>;\n  attrsMap: { [key: string]: any };\n  parent: ASTElement | void;\n  children: Array<ASTNode>;\n\n  processed?: true;\n\n  static?: boolean;\n  staticRoot?: boolean;\n  staticInFor?: boolean;\n  staticProcessed?: boolean;\n  hasBindings?: boolean;\n\n  text?: string;\n  attrs?: Array<{ name: string; value: any }>;\n  props?: Array<{ name: string; value: string }>;\n  plain?: boolean;\n  pre?: true;\n  ns?: string;\n\n  component?: string;\n  inlineTemplate?: true;\n  transitionMode?: string | null;\n  slotName?: ?string;\n  slotTarget?: ?string;\n  slotScope?: ?string;\n  scopedSlots?: { [name: string]: ASTElement };\n\n  ref?: string;\n  refInFor?: boolean;\n\n  if?: string;\n  ifProcessed?: boolean;\n  elseif?: string;\n  else?: true;\n  ifConditions?: ASTIfConditions;\n\n  for?: string;\n  forProcessed?: boolean;\n  key?: string;\n  alias?: string;\n  iterator1?: string;\n  iterator2?: string;\n\n  staticClass?: string;\n  classBinding?: string;\n  staticStyle?: string;\n  styleBinding?: string;\n  events?: ASTElementHandlers;\n  nativeEvents?: ASTElementHandlers;\n\n  transition?: string | true;\n  transitionOnAppear?: boolean;\n\n  model?: {\n    value: string;\n    callback: string;\n    expression: string;\n  };\n\n  directives?: Array<ASTDirective>;\n\n  forbidden?: true;\n  once?: true;\n  onceProcessed?: boolean;\n  wrapData?: (code: string) => string;\n  wrapListeners?: (code: string) => string;\n\n  // 2.4 ssr optimization\n  ssrOptimizability?: number;\n\n  // weex specific\n  appendAsTree?: boolean;\n};\n\ndeclare type ASTExpression = {\n  type: 2;\n  expression: string;\n  text: string;\n  tokens: Array<string | Object>;\n  static?: boolean;\n  // 2.4 ssr optimization\n  ssrOptimizability?: number;\n};\n\ndeclare type ASTText = {\n  type: 3;\n  text: string;\n  static?: boolean;\n  isComment?: boolean;\n  // 2.4 ssr optimization\n  ssrOptimizability?: number;\n};\n\n// SFC-parser related declarations\n\n// an object format describing a single-file component.\ndeclare type SFCDescriptor = {\n  template: ?SFCBlock;\n  script: ?SFCBlock;\n  styles: Array<SFCBlock>;\n  customBlocks: Array<SFCBlock>;\n};\n\ndeclare type SFCBlock = {\n  type: string;\n  content: string;\n  attrs: {[attribute:string]: string};\n  start?: number;\n  end?: number;\n  lang?: string;\n  src?: string;\n  scoped?: boolean;\n  module?: string | boolean;\n};\n"
  },
  {
    "path": "vue/flow/component.js",
    "content": "import type { Config } from '../src/core/config'\nimport type VNode from '../src/core/vdom/vnode'\nimport type Watcher from '../src/core/observer/watcher'\n\ndeclare interface Component {\n  // constructor information\n  static cid: number;\n  static options: Object;\n  // extend\n  static extend: (options: Object) => Function;\n  static superOptions: Object;\n  static extendOptions: Object;\n  static sealedOptions: Object;\n  static super: Class<Component>;\n  // assets\n  static directive: (id: string, def?: Function | Object) => Function | Object | void;\n  static component: (id: string, def?: Class<Component> | Object) => Class<Component>;\n  static filter: (id: string, def?: Function) => Function | void;\n  // functional context constructor\n  static FunctionalRenderContext: Function;\n\n  // public properties\n  $el: any; // so that we can attach __vue__ to it\n  $data: Object;\n  $props: Object;\n  $options: ComponentOptions;\n  $parent: Component | void;\n  $root: Component;\n  $children: Array<Component>;\n  $refs: { [key: string]: Component | Element | Array<Component | Element> | void };\n  $slots: { [key: string]: Array<VNode> };\n  $scopedSlots: { [key: string]: () => VNodeChildren };\n  $vnode: VNode; // the placeholder node for the component in parent's render tree\n  $attrs: { [key: string] : string };\n  $listeners: { [key: string]: Function | Array<Function> };\n  $isServer: boolean;\n\n  // public methods\n  $mount: (el?: Element | string, hydrating?: boolean) => Component;\n  $forceUpdate: () => void;\n  $destroy: () => void;\n  $set: <T>(target: Object | Array<T>, key: string | number, val: T) => T;\n  $delete: <T>(target: Object | Array<T>, key: string | number) => void;\n  $watch: (expOrFn: string | Function, cb: Function, options?: Object) => Function;\n  $on: (event: string | Array<string>, fn: Function) => Component;\n  $once: (event: string, fn: Function) => Component;\n  $off: (event?: string | Array<string>, fn?: Function) => Component;\n  $emit: (event: string, ...args: Array<mixed>) => Component;\n  $nextTick: (fn: Function) => void | Promise<*>;\n  $createElement: (tag?: string | Component, data?: Object, children?: VNodeChildren) => VNode;\n\n  // private properties\n  _uid: number | string;\n  _name: string; // this only exists in dev mode\n  _isVue: true;\n  _self: Component;\n  _renderProxy: Component;\n  _renderContext: ?Component;\n  _watcher: Watcher;\n  _watchers: Array<Watcher>;\n  _computedWatchers: { [key: string]: Watcher };\n  _data: Object;\n  _props: Object;\n  _events: Object;\n  _inactive: boolean | null;\n  _directInactive: boolean;\n  _isMounted: boolean;\n  _isDestroyed: boolean;\n  _isBeingDestroyed: boolean;\n  _vnode: ?VNode; // self root node\n  _staticTrees: ?Array<VNode>; // v-once cached trees\n  _hasHookEvent: boolean;\n  _provided: ?Object;\n  // _virtualComponents?: { [key: string]: Component };\n\n  // private methods\n\n  // lifecycle\n  _init: Function;\n  _mount: (el?: Element | void, hydrating?: boolean) => Component;\n  _update: (vnode: VNode, hydrating?: boolean) => void;\n\n  // rendering\n  _render: () => VNode;\n\n  __patch__: (\n    a: Element | VNode | void,\n    b: VNode,\n    hydrating?: boolean,\n    removeOnly?: boolean,\n    parentElm?: any,\n    refElm?: any\n  ) => any;\n\n  // createElement\n\n  // _c is internal that accepts `normalizationType` optimization hint\n  _c: (\n    vnode?: VNode,\n    data?: VNodeData,\n    children?: VNodeChildren,\n    normalizationType?: number\n  ) => VNode | void;\n\n  // renderStatic\n  _m: (index: number, isInFor?: boolean) => VNode | VNodeChildren;\n  // markOnce\n  _o: (vnode: VNode | Array<VNode>, index: number, key: string) => VNode | VNodeChildren;\n  // toString\n  _s: (value: mixed) => string;\n  // text to VNode\n  _v: (value: string | number) => VNode;\n  // toNumber\n  _n: (value: string) => number | string;\n  // empty vnode\n  _e: () => VNode;\n  // loose equal\n  _q: (a: mixed, b: mixed) => boolean;\n  // loose indexOf\n  _i: (arr: Array<mixed>, val: mixed) => number;\n  // resolveFilter\n  _f: (id: string) => Function;\n  // renderList\n  _l: (val: mixed, render: Function) => ?Array<VNode>;\n  // renderSlot\n  _t: (name: string, fallback: ?Array<VNode>, props: ?Object) => ?Array<VNode>;\n  // apply v-bind object\n  _b: (data: any, tag: string, value: any, asProp: boolean, isSync?: boolean) => VNodeData;\n  // apply v-on object\n  _g: (data: any, value: any) => VNodeData;\n  // check custom keyCode\n  _k: (eventKeyCode: number, key: string, builtInAlias?: number | Array<number>, eventKeyName?: string) => ?boolean;\n  // resolve scoped slots\n  _u: (scopedSlots: ScopedSlotsData, res?: Object) => { [key: string]: Function };\n\n  // SSR specific\n  _ssrNode: Function;\n  _ssrList: Function;\n  _ssrEscape: Function;\n  _ssrAttr: Function;\n  _ssrAttrs: Function;\n  _ssrDOMProps: Function;\n  _ssrClass: Function;\n  _ssrStyle: Function;\n\n  // allow dynamic method registration\n  [key: string]: any\n};\n"
  },
  {
    "path": "vue/flow/global-api.js",
    "content": "declare interface GlobalAPI {\n  cid: number;\n  options: Object;\n  config: Config;\n  util: Object;\n\n  extend: (options: Object) => Function;\n  set: <T>(target: Object | Array<T>, key: string | number, value: T) => T;\n  delete: <T>(target: Object| Array<T>, key: string | number) => void;\n  nextTick: (fn: Function, context?: Object) => void | Promise<*>;\n  use: (plugin: Function | Object) => void;\n  mixin: (mixin: Object) => void;\n  compile: (template: string) => { render: Function, staticRenderFns: Array<Function> };\n\n  directive: (id: string, def?: Function | Object) => Function | Object | void;\n  component: (id: string, def?: Class<Component> | Object) => Class<Component>;\n  filter: (id: string, def?: Function) => Function | void;\n\n  // allow dynamic method registration\n  [key: string]: any\n};\n"
  },
  {
    "path": "vue/flow/modules.js",
    "content": "declare module 'he' {\n  declare function escape(html: string): string;\n  declare function decode(html: string): string;\n}\n\ndeclare module 'source-map' {\n  declare class SourceMapGenerator {\n    setSourceContent(filename: string, content: string): void;\n    addMapping(mapping: Object): void;\n    toString(): string;\n  }\n  declare class SourceMapConsumer {\n    constructor (map: Object): void;\n    originalPositionFor(position: { line: number; column: number; }): {\n      source: ?string;\n      line: ?number;\n      column: ?number;\n    };\n  }\n}\n\ndeclare module 'lru-cache' {\n  declare var exports: {\n    (): any\n  }\n}\n\ndeclare module 'de-indent' {\n  declare var exports: {\n    (input: string): string\n  }\n}\n\ndeclare module 'serialize-javascript' {\n  declare var exports: {\n    (input: string, options: { isJSON: boolean }): string\n  }\n}\n\ndeclare module 'lodash.template' {\n  declare var exports: {\n    (input: string, options: { interpolate: RegExp, escape: RegExp }): Function\n  }\n}\n"
  },
  {
    "path": "vue/flow/options.js",
    "content": "declare type InternalComponentOptions = {\n  _isComponent: true;\n  parent: Component;\n  _parentVnode: VNode;\n  render?: Function;\n  staticRenderFns?: Array<Function>\n};\n\ntype InjectKey = string | Symbol;\n\ndeclare type ComponentOptions = {\n  componentId?: string;\n\n  // data\n  data: Object | Function | void;\n  props?: { [key: string]: PropOptions };\n  propsData?: ?Object;\n  computed?: {\n    [key: string]: Function | {\n      get?: Function;\n      set?: Function;\n      cache?: boolean\n    }\n  };\n  methods?: { [key: string]: Function };\n  watch?: { [key: string]: Function | string };\n\n  // DOM\n  el?: string | Element;\n  template?: string;\n  render: (h: () => VNode) => VNode;\n  renderError?: (h: () => VNode, err: Error) => VNode;\n  staticRenderFns?: Array<() => VNode>;\n\n  // lifecycle\n  beforeCreate?: Function;\n  created?: Function;\n  beforeMount?: Function;\n  mounted?: Function;\n  beforeUpdate?: Function;\n  updated?: Function;\n  activated?: Function;\n  deactivated?: Function;\n  beforeDestroy?: Function;\n  destroyed?: Function;\n  errorCaptured?: () => boolean | void;\n\n  // assets\n  directives?: { [key: string]: Object };\n  components?: { [key: string]: Class<Component> };\n  transitions?: { [key: string]: Object };\n  filters?: { [key: string]: Function };\n\n  // context\n  provide?: { [key: string | Symbol]: any } | () => { [key: string | Symbol]: any };\n  inject?: { [key: string]: InjectKey | { from?: InjectKey, default?: any }} | Array<string>;\n\n  // component v-model customization\n  model?: {\n    prop?: string;\n    event?: string;\n  };\n\n  // misc\n  parent?: Component;\n  mixins?: Array<Object>;\n  name?: string;\n  extends?: Class<Component> | Object;\n  delimiters?: [string, string];\n  comments?: boolean;\n  inheritAttrs?: boolean;\n\n  // private\n  _isComponent?: true;\n  _propKeys?: Array<string>;\n  _parentVnode?: VNode;\n  _parentListeners?: ?Object;\n  _renderChildren?: ?Array<VNode>;\n  _componentTag: ?string;\n  _scopeId: ?string;\n  _base: Class<Component>;\n};\n\ndeclare type PropOptions = {\n  type: Function | Array<Function> | null;\n  default: any;\n  required: ?boolean;\n  validator: ?Function;\n}\n"
  },
  {
    "path": "vue/flow/ssr.js",
    "content": "declare type ComponentWithCacheContext = {\n  type: 'ComponentWithCache';\n  bufferIndex: number;\n  buffer: Array<string>;\n  key: string;\n};\n\ndeclare type ElementContext = {\n  type: 'Element';\n  children: Array<VNode>;\n  rendered: number;\n  endTag: string;\n  total: number;\n};\n\ndeclare type ComponentContext = {\n  type: 'Component';\n  prevActive: Component;\n};\n\ndeclare type RenderState = ComponentContext | ComponentWithCacheContext | ElementContext;\n"
  },
  {
    "path": "vue/flow/vnode.js",
    "content": "declare type VNodeChildren = Array<?VNode | string | VNodeChildren> | string;\n\ndeclare type VNodeComponentOptions = {\n  Ctor: Class<Component>;\n  propsData: ?Object;\n  listeners: ?Object;\n  children: ?Array<VNode>;\n  tag?: string;\n};\n\ndeclare type MountedComponentVNode = {\n  context: Component;\n  componentOptions: VNodeComponentOptions;\n  componentInstance: Component;\n  parent: VNode;\n  data: VNodeData;\n};\n\n// interface for vnodes in update modules\ndeclare type VNodeWithData = {\n  tag: string;\n  data: VNodeData;\n  children: ?Array<VNode>;\n  text: void;\n  elm: any;\n  ns: string | void;\n  context: Component;\n  key: string | number | void;\n  parent?: VNodeWithData;\n  componentOptions?: VNodeComponentOptions;\n  componentInstance?: Component;\n  isRootInsert: boolean;\n};\n\ndeclare interface VNodeData {\n  key?: string | number;\n  slot?: string;\n  ref?: string;\n  is?: string;\n  pre?: boolean;\n  tag?: string;\n  staticClass?: string;\n  class?: any;\n  staticStyle?: { [key: string]: any };\n  style?: Array<Object> | Object;\n  normalizedStyle?: Object;\n  props?: { [key: string]: any };\n  attrs?: { [key: string]: string };\n  domProps?: { [key: string]: any };\n  hook?: { [key: string]: Function };\n  on?: ?{ [key: string]: Function | Array<Function> };\n  nativeOn?: { [key: string]: Function | Array<Function> };\n  transition?: Object;\n  show?: boolean; // marker for v-show\n  inlineTemplate?: {\n    render: Function;\n    staticRenderFns: Array<Function>;\n  };\n  directives?: Array<VNodeDirective>;\n  keepAlive?: boolean;\n  scopedSlots?: { [key: string]: Function };\n  model?: {\n    value: any;\n    callback: Function;\n  };\n};\n\ndeclare type VNodeDirective = {\n  name: string;\n  rawName: string;\n  value?: any;\n  oldValue?: any;\n  arg?: string;\n  modifiers?: ASTModifiers;\n  def?: Object;\n};\n\ndeclare type ScopedSlotsData = Array<{ key: string, fn: Function } | ScopedSlotsData>;\n"
  },
  {
    "path": "vue/flow/weex.js",
    "content": "// global flag to be compiled away\ndeclare var __WEEX__: boolean;\n\n// global object in Weex\ndeclare var WXEnvironment: WeexEnvironment;\n\ndeclare type Weex = {\n  config: WeexConfigAPI;\n  document: WeexDocument;\n  requireModule: (name: string) => Object | void;\n  supports: (condition: string) => boolean | void;\n  isRegisteredModule: (name: string, method?: string) => boolean;\n  isRegisteredComponent: (name: string) => boolean;\n};\n\ndeclare type WeexConfigAPI = {\n  bundleUrl: string; // === weex.document.URL\n  bundleType: string;\n  env: WeexEnvironment; // === WXEnvironment\n};\n\ndeclare type WeexEnvironment = {\n  platform: string; // could be \"Web\", \"iOS\", \"Android\"\n  weexVersion: string; // the version of WeexSDK\n\n  osName: string; // could be \"iOS\", \"Android\" or others\n  osVersion: string;\n  appName: string; // mobile app name or browser name\n  appVersion: string;\n\n  // informations of current running device\n  deviceModel: string; // phone device model\n  deviceWidth: number;\n  deviceHeight: number;\n  scale: number;\n\n  // only available on the web\n  userAgent?: string;\n  dpr?: number;\n  rem?: number;\n};\n\ndeclare interface WeexDocument {\n  id: string;\n  URL: string;\n  taskCenter: WeexTaskCenter;\n\n  open: () => void;\n  close: () => void;\n  createElement: (tagName: string, props?: Object) => WeexElement;\n  createComment: (text: string) => Object;\n  fireEvent: (type: string) => void;\n  destroy: () => void;\n};\n\ndeclare interface WeexTaskCenter {\n  instanceId: string;\n  callbackManager: Object;\n  send: (type: string, params: Object, args: Array<any>, options?: Object) => void;\n  registerHook: (componentId: string, type: string, hook: string, fn: Function) => void;\n  updateData: (componentId: string, data: Object | void, callback?: Function) => void;\n};\n\ndeclare interface WeexElement {\n  nodeType: number;\n  nodeId: string;\n  type: string;\n  ref: string;\n  text?: string;\n\n  parentNode: WeexElement | void;\n  children: Array<WeexElement>;\n  previousSibling: WeexElement | void;\n  nextSibling: WeexElement | void;\n\n  appendChild: (node: WeexElement) => void;\n  removeChild: (node: WeexElement, preserved?: boolean) => void;\n  insertBefore: (node: WeexElement, before: WeexElement) => void;\n  insertAfter: (node: WeexElement, after: WeexElement) => void;\n  setAttr: (key: string, value: any, silent?: boolean) => void;\n  setAttrs: (attrs: Object, silent?: boolean) => void;\n  setStyle: (key: string, value: any, silent?: boolean) => void;\n  setStyles: (attrs: Object, silent?: boolean) => void;\n  addEvent: (type: string, handler: Function, args?: Array<any>) => void;\n  removeEvent: (type: string) => void;\n  fireEvent: (type: string) => void;\n  destroy: () => void;\n};\n\ndeclare type WeexInstanceOption = {\n  instanceId: string;\n  config: WeexConfigAPI;\n  document: WeexDocument;\n  Vue?: GlobalAPI;\n  app?: Component;\n  data?: Object;\n};\n\ndeclare type WeexRuntimeContext = {\n  weex: Weex;\n  service: Object;\n  BroadcastChannel?: Function;\n};\n\ndeclare type WeexInstanceContext = {\n  Vue: GlobalAPI;\n\n  // DEPRECATED\n  setTimeout?: Function;\n  clearTimeout?: Function;\n  setInterval?: Function;\n  clearInterval?: Function;\n};\n\ndeclare type WeexCompilerOptions = CompilerOptions & {\n  // whether to compile special template for <recycle-list>\n  recyclable?: boolean;\n};\n\ndeclare type WeexCompiledResult = CompiledResult & {\n  '@render'?: string;\n};\n"
  },
  {
    "path": "vue/package.json",
    "content": "{\n  \"name\": \"vue\",\n  \"version\": \"2.5.17-beta.0\",\n  \"description\": \"Reactive, component-oriented view layer for modern web interfaces.\",\n  \"main\": \"dist/vue.runtime.common.js\",\n  \"module\": \"dist/vue.runtime.esm.js\",\n  \"unpkg\": \"dist/vue.js\",\n  \"jsdelivr\": \"dist/vue.js\",\n  \"typings\": \"types/index.d.ts\",\n  \"files\": [\n    \"src\",\n    \"dist/*.js\",\n    \"types/*.d.ts\"\n  ],\n  \"scripts\": {\n    \"dev\": \"rollup -w -c scripts/config.js --environment TARGET:web-full-dev\",\n    \"dev:cjs\": \"rollup -w -c scripts/config.js --environment TARGET:web-runtime-cjs\",\n    \"dev:esm\": \"rollup -w -c scripts/config.js --environment TARGET:web-runtime-esm\",\n    \"dev:test\": \"karma start test/unit/karma.dev.config.js\",\n    \"dev:ssr\": \"rollup -w -c scripts/config.js --environment TARGET:web-server-renderer\",\n    \"dev:compiler\": \"rollup -w -c scripts/config.js --environment TARGET:web-compiler \",\n    \"dev:weex\": \"rollup -w -c scripts/config.js --environment TARGET:weex-framework\",\n    \"dev:weex:factory\": \"rollup -w -c scripts/config.js --environment TARGET:weex-factory\",\n    \"dev:weex:compiler\": \"rollup -w -c scripts/config.js --environment TARGET:weex-compiler \",\n    \"build\": \"node scripts/build.js\",\n    \"build:ssr\": \"npm run build -- web-runtime-cjs,web-server-renderer\",\n    \"build:weex\": \"npm run build -- weex\",\n    \"test\": \"npm run lint && flow check && npm run test:types && npm run test:cover && npm run test:e2e -- --env phantomjs && npm run test:ssr && npm run test:weex\",\n    \"test:unit\": \"karma start test/unit/karma.unit.config.js\",\n    \"test:cover\": \"karma start test/unit/karma.cover.config.js\",\n    \"test:e2e\": \"npm run build -- web-full-prod,web-server-basic-renderer && node test/e2e/runner.js\",\n    \"test:weex\": \"npm run build:weex && jasmine JASMINE_CONFIG_PATH=test/weex/jasmine.json\",\n    \"test:ssr\": \"npm run build:ssr && jasmine JASMINE_CONFIG_PATH=test/ssr/jasmine.json\",\n    \"test:sauce\": \"npm run sauce -- 0 && npm run sauce -- 1 && npm run sauce -- 2\",\n    \"test:types\": \"tsc -p ./types/test/tsconfig.json\",\n    \"lint\": \"eslint --fix src scripts test\",\n    \"flow\": \"flow check\",\n    \"sauce\": \"karma start test/unit/karma.sauce.config.js\",\n    \"bench:ssr\": \"npm run build:ssr && node benchmarks/ssr/renderToString.js && node benchmarks/ssr/renderToStream.js\",\n    \"release\": \"bash scripts/release.sh\",\n    \"release:weex\": \"bash scripts/release-weex.sh\",\n    \"release:note\": \"node scripts/gen-release-note.js\",\n    \"commit\": \"git-cz\"\n  },\n  \"gitHooks\": {\n    \"pre-commit\": \"lint-staged\",\n    \"commit-msg\": \"node scripts/verify-commit-msg.js\"\n  },\n  \"lint-staged\": {\n    \"*.js\": [\n      \"eslint --fix\",\n      \"git add\"\n    ]\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/vuejs/vue.git\"\n  },\n  \"keywords\": [\n    \"vue\"\n  ],\n  \"author\": \"Evan You\",\n  \"license\": \"MIT\",\n  \"bugs\": {\n    \"url\": \"https://github.com/vuejs/vue/issues\"\n  },\n  \"homepage\": \"https://github.com/vuejs/vue#readme\",\n  \"devDependencies\": {\n    \"@types/node\": \"^8.0.33\",\n    \"@types/webpack\": \"^3.0.13\",\n    \"acorn\": \"^5.2.1\",\n    \"babel-core\": \"^6.25.0\",\n    \"babel-eslint\": \"^8.0.3\",\n    \"babel-helper-vue-jsx-merge-props\": \"^2.0.2\",\n    \"babel-loader\": \"^7.0.0\",\n    \"babel-plugin-istanbul\": \"^4.1.4\",\n    \"babel-plugin-syntax-dynamic-import\": \"^6.18.0\",\n    \"babel-plugin-syntax-jsx\": \"^6.18.0\",\n    \"babel-plugin-transform-vue-jsx\": \"^3.4.3\",\n    \"babel-preset-es2015\": \"^6.24.1\",\n    \"babel-preset-flow-vue\": \"^1.0.0\",\n    \"buble\": \"^0.19.3\",\n    \"chalk\": \"^2.3.0\",\n    \"chromedriver\": \"^2.30.1\",\n    \"codecov\": \"^3.0.0\",\n    \"commitizen\": \"^2.9.6\",\n    \"conventional-changelog\": \"^1.1.3\",\n    \"cross-spawn\": \"^5.1.0\",\n    \"cz-conventional-changelog\": \"^2.0.0\",\n    \"de-indent\": \"^1.0.2\",\n    \"es6-promise\": \"^4.1.0\",\n    \"escodegen\": \"^1.8.1\",\n    \"eslint\": \"^4.13.1\",\n    \"eslint-loader\": \"^1.7.1\",\n    \"eslint-plugin-flowtype\": \"^2.34.0\",\n    \"eslint-plugin-jasmine\": \"^2.8.4\",\n    \"eslint-plugin-vue-libs\": \"^2.0.1\",\n    \"file-loader\": \"^1.1.5\",\n    \"flow-bin\": \"^0.61.0\",\n    \"hash-sum\": \"^1.0.2\",\n    \"he\": \"^1.1.1\",\n    \"http-server\": \"^0.11.1\",\n    \"jasmine\": \"^2.99.0\",\n    \"jasmine-core\": \"^2.99.0\",\n    \"karma\": \"^2.0.0\",\n    \"karma-chrome-launcher\": \"^2.1.1\",\n    \"karma-coverage\": \"^1.1.1\",\n    \"karma-firefox-launcher\": \"^1.0.1\",\n    \"karma-jasmine\": \"^1.1.0\",\n    \"karma-mocha-reporter\": \"^2.2.3\",\n    \"karma-phantomjs-launcher\": \"^1.0.4\",\n    \"karma-safari-launcher\": \"^1.0.0\",\n    \"karma-sauce-launcher\": \"^1.1.0\",\n    \"karma-sourcemap-loader\": \"^0.3.7\",\n    \"karma-webpack\": \"^2.0.3\",\n    \"lint-staged\": \"^7.0.0\",\n    \"lodash\": \"^4.17.4\",\n    \"lodash.template\": \"^4.4.0\",\n    \"lodash.uniq\": \"^4.5.0\",\n    \"lru-cache\": \"^4.1.1\",\n    \"nightwatch\": \"^0.9.16\",\n    \"nightwatch-helpers\": \"^1.2.0\",\n    \"phantomjs-prebuilt\": \"^2.1.14\",\n    \"resolve\": \"^1.3.3\",\n    \"rollup\": \"^0.54.1\",\n    \"rollup-plugin-alias\": \"^1.3.1\",\n    \"rollup-plugin-babel\": \"^3.0.2\",\n    \"rollup-plugin-buble\": \"^0.19.2\",\n    \"rollup-plugin-commonjs\": \"^8.0.0\",\n    \"rollup-plugin-flow-no-whitespace\": \"^1.0.0\",\n    \"rollup-plugin-node-resolve\": \"^3.0.0\",\n    \"rollup-plugin-replace\": \"^2.0.0\",\n    \"rollup-watch\": \"^4.0.0\",\n    \"selenium-server\": \"^2.53.1\",\n    \"serialize-javascript\": \"^4.0.0\",\n    \"shelljs\": \"^0.8.1\",\n    \"typescript\": \"^2.7.1\",\n    \"uglify-js\": \"^3.0.15\",\n    \"webpack\": \"^3.11.0\",\n    \"weex-js-runtime\": \"^0.23.6\",\n    \"weex-styler\": \"^0.3.0\",\n    \"yorkie\": \"^1.0.1\"\n  },\n  \"config\": {\n    \"commitizen\": {\n      \"path\": \"./node_modules/cz-conventional-changelog\"\n    }\n  }\n}\n"
  },
  {
    "path": "vue/scripts/alias.js",
    "content": "const path = require('path')\n\nconst resolve = p => path.resolve(__dirname, '../', p)\n\nmodule.exports = {\n  vue: resolve('src/platforms/web/entry-runtime-with-compiler'),\n  compiler: resolve('src/compiler'),\n  core: resolve('src/core'),\n  shared: resolve('src/shared'),\n  web: resolve('src/platforms/web'),\n  weex: resolve('src/platforms/weex'),\n  server: resolve('src/server'),\n  entries: resolve('src/entries'),\n  sfc: resolve('src/sfc')\n}\n"
  },
  {
    "path": "vue/scripts/build.js",
    "content": "const fs = require('fs')\nconst path = require('path')\nconst zlib = require('zlib')\nconst rollup = require('rollup')\nconst uglify = require('uglify-js')\n\nif (!fs.existsSync('dist')) {\n  fs.mkdirSync('dist')\n}\n\nlet builds = require('./config').getAllBuilds()\n\n// filter builds via command line arg\nif (process.argv[2]) {\n  const filters = process.argv[2].split(',')\n  builds = builds.filter(b => {\n    return filters.some(f => b.output.file.indexOf(f) > -1 || b._name.indexOf(f) > -1)\n  })\n} else {\n  // filter out weex builds by default\n  builds = builds.filter(b => {\n    return b.output.file.indexOf('weex') === -1\n  })\n}\n\nbuild(builds)\n\nfunction build (builds) {\n  let built = 0\n  const total = builds.length\n  const next = () => {\n    buildEntry(builds[built]).then(() => {\n      built++\n      if (built < total) {\n        next()\n      }\n    }).catch(logError)\n  }\n\n  next()\n}\n\nfunction buildEntry (config) {\n  const output = config.output\n  const { file, banner } = output\n  const isProd = /min\\.js$/.test(file)\n  return rollup.rollup(config)\n    .then(bundle => bundle.generate(output))\n    .then(({ code }) => {\n      if (isProd) {\n        var minified = (banner ? banner + '\\n' : '') + uglify.minify(code, {\n          output: {\n            ascii_only: true\n          },\n          compress: {\n            pure_funcs: ['makeMap']\n          }\n        }).code\n        return write(file, minified, true)\n      } else {\n        return write(file, code)\n      }\n    })\n}\n\nfunction write (dest, code, zip) {\n  return new Promise((resolve, reject) => {\n    function report (extra) {\n      console.log(blue(path.relative(process.cwd(), dest)) + ' ' + getSize(code) + (extra || ''))\n      resolve()\n    }\n\n    fs.writeFile(dest, code, err => {\n      if (err) return reject(err)\n      if (zip) {\n        zlib.gzip(code, (err, zipped) => {\n          if (err) return reject(err)\n          report(' (gzipped: ' + getSize(zipped) + ')')\n        })\n      } else {\n        report()\n      }\n    })\n  })\n}\n\nfunction getSize (code) {\n  return (code.length / 1024).toFixed(2) + 'kb'\n}\n\nfunction logError (e) {\n  console.log(e)\n}\n\nfunction blue (str) {\n  return '\\x1b[1m\\x1b[34m' + str + '\\x1b[39m\\x1b[22m'\n}\n"
  },
  {
    "path": "vue/scripts/config.js",
    "content": "const path = require('path')\nconst buble = require('rollup-plugin-buble')\nconst alias = require('rollup-plugin-alias')\nconst cjs = require('rollup-plugin-commonjs')\nconst replace = require('rollup-plugin-replace')\nconst node = require('rollup-plugin-node-resolve')\nconst flow = require('rollup-plugin-flow-no-whitespace')\nconst version = process.env.VERSION || require('../package.json').version\nconst weexVersion = process.env.WEEX_VERSION || require('../packages/weex-vue-framework/package.json').version\n\nconst banner =\n  '/*!\\n' +\n  ' * Vue.js v' + version + '\\n' +\n  ' * (c) 2014-' + new Date().getFullYear() + ' Evan You\\n' +\n  ' * Released under the MIT License.\\n' +\n  ' */'\n\nconst weexFactoryPlugin = {\n  intro () {\n    return 'module.exports = function weexFactory (exports, document) {'\n  },\n  outro () {\n    return '}'\n  }\n}\n\nconst aliases = require('./alias')\nconst resolve = p => {\n  const base = p.split('/')[0]\n  if (aliases[base]) {\n    return path.resolve(aliases[base], p.slice(base.length + 1))\n  } else {\n    return path.resolve(__dirname, '../', p)\n  }\n}\n\nconst builds = {\n  // Runtime only (CommonJS). Used by bundlers e.g. Webpack & Browserify\n  'web-runtime-cjs': {\n    entry: resolve('web/entry-runtime.js'),\n    dest: resolve('dist/vue.runtime.common.js'),\n    format: 'cjs',\n    banner\n  },\n  // Runtime+compiler CommonJS build (CommonJS)\n  'web-full-cjs': {\n    entry: resolve('web/entry-runtime-with-compiler.js'),\n    dest: resolve('dist/vue.common.js'),\n    format: 'cjs',\n    alias: { he: './entity-decoder' },\n    banner\n  },\n  // Runtime only (ES Modules). Used by bundlers that support ES Modules,\n  // e.g. Rollup & Webpack 2\n  'web-runtime-esm': {\n    entry: resolve('web/entry-runtime.js'),\n    dest: resolve('dist/vue.runtime.esm.js'),\n    format: 'es',\n    banner\n  },\n  // Runtime+compiler CommonJS build (ES Modules)\n  'web-full-esm': {\n    entry: resolve('web/entry-runtime-with-compiler.js'),\n    dest: resolve('dist/vue.esm.js'),\n    format: 'es',\n    alias: { he: './entity-decoder' },\n    banner\n  },\n  // runtime-only build (Browser)\n  'web-runtime-dev': {\n    entry: resolve('web/entry-runtime.js'),\n    dest: resolve('dist/vue.runtime.js'),\n    format: 'umd',\n    env: 'development',\n    banner\n  },\n  // runtime-only production build (Browser)\n  'web-runtime-prod': {\n    entry: resolve('web/entry-runtime.js'),\n    dest: resolve('dist/vue.runtime.min.js'),\n    format: 'umd',\n    env: 'production',\n    banner\n  },\n  // Runtime+compiler development build (Browser)\n  'web-full-dev': {\n    entry: resolve('web/entry-runtime-with-compiler.js'),\n    dest: resolve('dist/vue.js'),\n    format: 'umd',\n    env: 'development',\n    alias: { he: './entity-decoder' },\n    banner\n  },\n  // Runtime+compiler production build  (Browser)\n  'web-full-prod': {\n    entry: resolve('web/entry-runtime-with-compiler.js'),\n    dest: resolve('dist/vue.min.js'),\n    format: 'umd',\n    env: 'production',\n    alias: { he: './entity-decoder' },\n    banner\n  },\n  // Web compiler (CommonJS).\n  'web-compiler': {\n    entry: resolve('web/entry-compiler.js'),\n    dest: resolve('packages/vue-template-compiler/build.js'),\n    format: 'cjs',\n    external: Object.keys(require('../packages/vue-template-compiler/package.json').dependencies)\n  },\n  // Web compiler (UMD for in-browser use).\n  'web-compiler-browser': {\n    entry: resolve('web/entry-compiler.js'),\n    dest: resolve('packages/vue-template-compiler/browser.js'),\n    format: 'umd',\n    env: 'development',\n    moduleName: 'VueTemplateCompiler',\n    plugins: [node(), cjs()]\n  },\n  // Web server renderer (CommonJS).\n  'web-server-renderer': {\n    entry: resolve('web/entry-server-renderer.js'),\n    dest: resolve('packages/vue-server-renderer/build.js'),\n    format: 'cjs',\n    external: Object.keys(require('../packages/vue-server-renderer/package.json').dependencies)\n  },\n  'web-server-renderer-basic': {\n    entry: resolve('web/entry-server-basic-renderer.js'),\n    dest: resolve('packages/vue-server-renderer/basic.js'),\n    format: 'umd',\n    env: 'development',\n    moduleName: 'renderVueComponentToString',\n    plugins: [node(), cjs()]\n  },\n  'web-server-renderer-webpack-server-plugin': {\n    entry: resolve('server/webpack-plugin/server.js'),\n    dest: resolve('packages/vue-server-renderer/server-plugin.js'),\n    format: 'cjs',\n    external: Object.keys(require('../packages/vue-server-renderer/package.json').dependencies)\n  },\n  'web-server-renderer-webpack-client-plugin': {\n    entry: resolve('server/webpack-plugin/client.js'),\n    dest: resolve('packages/vue-server-renderer/client-plugin.js'),\n    format: 'cjs',\n    external: Object.keys(require('../packages/vue-server-renderer/package.json').dependencies)\n  },\n  // Weex runtime factory\n  'weex-factory': {\n    weex: true,\n    entry: resolve('weex/entry-runtime-factory.js'),\n    dest: resolve('packages/weex-vue-framework/factory.js'),\n    format: 'cjs',\n    plugins: [weexFactoryPlugin]\n  },\n  // Weex runtime framework (CommonJS).\n  'weex-framework': {\n    weex: true,\n    entry: resolve('weex/entry-framework.js'),\n    dest: resolve('packages/weex-vue-framework/index.js'),\n    format: 'cjs'\n  },\n  // Weex compiler (CommonJS). Used by Weex's Webpack loader.\n  'weex-compiler': {\n    weex: true,\n    entry: resolve('weex/entry-compiler.js'),\n    dest: resolve('packages/weex-template-compiler/build.js'),\n    format: 'cjs',\n    external: Object.keys(require('../packages/weex-template-compiler/package.json').dependencies)\n  }\n}\n\nfunction genConfig (name) {\n  const opts = builds[name]\n  const config = {\n    input: opts.entry,\n    external: opts.external,\n    plugins: [\n      replace({\n        __WEEX__: !!opts.weex,\n        __WEEX_VERSION__: weexVersion,\n        __VERSION__: version\n      }),\n      flow(),\n      buble(),\n      alias(Object.assign({}, aliases, opts.alias))\n    ].concat(opts.plugins || []),\n    output: {\n      file: opts.dest,\n      format: opts.format,\n      banner: opts.banner,\n      name: opts.moduleName || 'Vue'\n    }\n  }\n\n  if (opts.env) {\n    config.plugins.push(replace({\n      'process.env.NODE_ENV': JSON.stringify(opts.env)\n    }))\n  }\n\n  Object.defineProperty(config, '_name', {\n    enumerable: false,\n    value: name\n  })\n\n  return config\n}\n\nif (process.env.TARGET) {\n  module.exports = genConfig(process.env.TARGET)\n} else {\n  exports.getBuild = genConfig\n  exports.getAllBuilds = () => Object.keys(builds).map(genConfig)\n}\n"
  },
  {
    "path": "vue/scripts/gen-release-note.js",
    "content": "const version = process.argv[2] || process.env.VERSION\nconst cc = require('conventional-changelog')\nconst file = `./RELEASE_NOTE${version ? `_${version}` : ``}.md`\nconst fileStream = require('fs').createWriteStream(file)\n\ncc({\n  preset: 'angular',\n  pkg: {\n    transform (pkg) {\n      pkg.version = `v${version}`\n      return pkg\n    }\n  }\n}).pipe(fileStream).on('close', () => {\n  console.log(`Generated release note at ${file}`)\n})\n"
  },
  {
    "path": "vue/scripts/get-weex-version.js",
    "content": "var coreVersion = require('../package.json').version\nvar weexVersion = require('../packages/weex-vue-framework/package.json').version\nvar weexBaseVersion = weexVersion.match(/^[\\d.]+/)[0]\nvar weexSubVersion = Number(weexVersion.match(/-weex\\.(\\d+)$/)[1])\n\nif (weexBaseVersion === coreVersion) {\n  // same core version, increment sub version\n  weexSubVersion++\n} else {\n  // new core version, reset sub version\n  weexBaseVersion = coreVersion\n  weexSubVersion = 1\n}\n\nif (process.argv[2] === '-c') {\n  console.log(weexVersion)\n} else {\n  console.log(weexBaseVersion + '-weex.' + weexSubVersion)\n}\n\nmodule.exports = {\n  base: weexBaseVersion,\n  sub: weexSubVersion\n}\n"
  },
  {
    "path": "vue/scripts/git-hooks/commit-msg",
    "content": "#!/usr/bin/env bash\n\n# Validate commit log\ncommit_regex='^Merge.+|(feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert|types)(\\(.+\\))?: .{1,50}'\n\nif ! grep -iqE \"$commit_regex\" \"$1\"; then\n    echo\n    echo \"  Error: proper commit message format is required for automated changelog generation.\"\n    echo\n    echo \"  - Use \\`npm run commit\\` to interactively generate a commit message.\"\n    echo \"  - See .github/COMMIT_CONVENTION.md for more details.\"\n    echo\n    exit 1\nfi\n"
  },
  {
    "path": "vue/scripts/git-hooks/pre-commit",
    "content": "#!/usr/bin/env bash\n\nfiles_to_lint=$(git diff --cached --name-only --diff-filter=ACM | grep '\\.js$')\n\nif [ -n \"$files_to_lint\" ]; then\n  NODE_ENV=production eslint --quiet $files_to_lint\nfi\n"
  },
  {
    "path": "vue/scripts/release-weex.sh",
    "content": "#!/bin/bash\nset -e\nCUR_VERSION=$(node build/get-weex-version.js -c)\nNEXT_VERSION=$(node build/get-weex-version.js)\n\necho \"Current: $CUR_VERSION\"\nread -p \"Enter new version ($NEXT_VERSION): \" -n 1 -r\nif ! [[ -z $REPLY ]]; then\n  NEXT_VERSION=$REPLY\nfi\n\nread -p \"Releasing weex-vue-framework@$NEXT_VERSION - are you sure? (y/n) \" -n 1 -r\necho\nif [[ $REPLY =~ ^[Yy]$ ]]; then\n  echo \"Releasing weex-vue-framework@$NEXT_VERSION ...\"\n  npm run lint\n  npm run flow\n  npm run test:weex\n\n  # build\n  WEEX_VERSION=$NEXT_VERSION npm run build:weex\n\n  # update package\n  # using subshells to avoid having to cd back\n  ( cd packages/weex-vue-framework\n  npm version \"$NEXT_VERSION\"\n  npm publish\n  )\n\n  ( cd packages/weex-template-compiler\n  npm version \"$NEXT_VERSION\"\n  npm publish\n  )\n\n  # commit\n  git add packages/weex*\n  git commit -m \"[release] weex-vue-framework@$NEXT_VERSION\"\nfi\n"
  },
  {
    "path": "vue/scripts/release.sh",
    "content": "#!/bin/bash\nset -e\n\nif [[ -z $1 ]]; then\n  echo \"Enter new version: \"\n  read -r VERSION\nelse\n  VERSION=$1\nfi\n\nread -p \"Releasing $VERSION - are you sure? (y/n) \" -n 1 -r\necho\nif [[ $REPLY =~ ^[Yy]$ ]]; then\n  echo \"Releasing $VERSION ...\"\n\n  if [[ -z $SKIP_TESTS ]]; then\n    npm run lint\n    npm run flow\n    npm run test:cover\n    npm run test:e2e -- --env phantomjs\n    npm run test:ssr\n  fi\n\n  # Sauce Labs tests has a decent chance of failing\n  # so we usually manually run them before running the release script.\n\n  # if [[ -z $SKIP_SAUCE ]]; then\n  #   export SAUCE_BUILD_ID=$VERSION:`date +\"%s\"`\n  #   npm run test:sauce\n  # fi\n\n  # build\n  VERSION=$VERSION npm run build\n\n  # update packages\n  # using subshells to avoid having to cd back\n  ( ( cd packages/vue-template-compiler\n  npm version \"$VERSION\"\n  if [[ -z $RELEASE_TAG ]]; then\n    npm publish\n  else\n    npm publish --tag \"$RELEASE_TAG\"\n  fi\n  )\n\n  cd packages/vue-server-renderer\n  npm version \"$VERSION\"\n  if [[ -z $RELEASE_TAG ]]; then\n    npm publish\n  else\n    npm publish --tag \"$RELEASE_TAG\"\n  fi\n  )\n\n  # commit\n  git add -A\n  git add -f \\\n    dist/*.js \\\n    packages/vue-server-renderer/basic.js \\\n    packages/vue-server-renderer/build.js \\\n    packages/vue-server-renderer/server-plugin.js \\\n    packages/vue-server-renderer/client-plugin.js \\\n    packages/vue-template-compiler/build.js\n  git commit -m \"build: build $VERSION\"\n  # generate release note\n  npm run release:note\n  # tag version\n  npm version \"$VERSION\" --message \"build: release $VERSION\"\n\n  # publish\n  git push origin refs/tags/v\"$VERSION\"\n  git push\n  if [[ -z $RELEASE_TAG ]]; then\n    npm publish\n  else\n    npm publish --tag \"$RELEASE_TAG\"\n  fi\nfi\n"
  },
  {
    "path": "vue/scripts/verify-commit-msg.js",
    "content": "const chalk = require('chalk')\nconst msgPath = process.env.GIT_PARAMS\nconst msg = require('fs').readFileSync(msgPath, 'utf-8').trim()\n\nconst commitRE = /^(revert: )?(feat|fix|polish|docs|style|refactor|perf|test|workflow|ci|chore|types|build)(\\(.+\\))?: .{1,50}/\n\nif (!commitRE.test(msg)) {\n  console.log()\n  console.error(\n    `  ${chalk.bgRed.white(' ERROR ')} ${chalk.red(`invalid commit message format.`)}\\n\\n` +\n    chalk.red(`  Proper commit message format is required for automated changelog generation. Examples:\\n\\n`) +\n    `    ${chalk.green(`feat(compiler): add 'comments' option`)}\\n` +\n    `    ${chalk.green(`fix(v-model): handle events on blur (close #28)`)}\\n\\n` +\n    chalk.red(`  See .github/COMMIT_CONVENTION.md for more details.\\n`) +\n    chalk.red(`  You can also use ${chalk.cyan(`npm run commit`)} to interactively generate a commit message.\\n`)\n  )\n  process.exit(1)\n}\n"
  },
  {
    "path": "vue/src/compiler/codegen/events.js",
    "content": "/* @flow */\n\nconst fnExpRE = /^([\\w$_]+|\\([^)]*?\\))\\s*=>|^function\\s*\\(/\nconst simplePathRE = /^[A-Za-z_$][\\w$]*(?:\\.[A-Za-z_$][\\w$]*|\\['[^']*?']|\\[\"[^\"]*?\"]|\\[\\d+]|\\[[A-Za-z_$][\\w$]*])*$/\n\n// KeyboardEvent.keyCode aliases\nconst keyCodes: { [key: string]: number | Array<number> } = {\n  esc: 27,\n  tab: 9,\n  enter: 13,\n  space: 32,\n  up: 38,\n  left: 37,\n  right: 39,\n  down: 40,\n  'delete': [8, 46]\n}\n\n// KeyboardEvent.key aliases\nconst keyNames: { [key: string]: string | Array<string> } = {\n  // #7880: IE11 and Edge use `Esc` for Escape key name.\n  esc: ['Esc', 'Escape'],\n  tab: 'Tab',\n  enter: 'Enter',\n  space: ' ',\n  // #7806: IE11 uses key names without `Arrow` prefix for arrow keys.\n  up: ['Up', 'ArrowUp'],\n  left: ['Left', 'ArrowLeft'],\n  right: ['Right', 'ArrowRight'],\n  down: ['Down', 'ArrowDown'],\n  'delete': ['Backspace', 'Delete']\n}\n\n// #4868: modifiers that prevent the execution of the listener\n// need to explicitly return null so that we can determine whether to remove\n// the listener for .once\nconst genGuard = condition => `if(${condition})return null;`\n\nconst modifierCode: { [key: string]: string } = {\n  stop: '$event.stopPropagation();',\n  prevent: '$event.preventDefault();',\n  self: genGuard(`$event.target !== $event.currentTarget`),\n  ctrl: genGuard(`!$event.ctrlKey`),\n  shift: genGuard(`!$event.shiftKey`),\n  alt: genGuard(`!$event.altKey`),\n  meta: genGuard(`!$event.metaKey`),\n  left: genGuard(`'button' in $event && $event.button !== 0`),\n  middle: genGuard(`'button' in $event && $event.button !== 1`),\n  right: genGuard(`'button' in $event && $event.button !== 2`)\n}\n\nexport function genHandlers (\n  events: ASTElementHandlers,\n  isNative: boolean,\n  warn: Function\n): string {\n  let res = isNative ? 'nativeOn:{' : 'on:{'\n  for (const name in events) {\n    res += `\"${name}\":${genHandler(name, events[name])},`\n  }\n  return res.slice(0, -1) + '}'\n}\n\n// Generate handler code with binding params on Weex\n/* istanbul ignore next */\nfunction genWeexHandler (params: Array<any>, handlerCode: string) {\n  let innerHandlerCode = handlerCode\n  const exps = params.filter(exp => simplePathRE.test(exp) && exp !== '$event')\n  const bindings = exps.map(exp => ({ '@binding': exp }))\n  const args = exps.map((exp, i) => {\n    const key = `$_${i + 1}`\n    innerHandlerCode = innerHandlerCode.replace(exp, key)\n    return key\n  })\n  args.push('$event')\n  return '{\\n' +\n    `handler:function(${args.join(',')}){${innerHandlerCode}},\\n` +\n    `params:${JSON.stringify(bindings)}\\n` +\n    '}'\n}\n\nfunction genHandler (\n  name: string,\n  handler: ASTElementHandler | Array<ASTElementHandler>\n): string {\n  if (!handler) {\n    return 'function(){}'\n  }\n\n  if (Array.isArray(handler)) {\n    return `[${handler.map(handler => genHandler(name, handler)).join(',')}]`\n  }\n\n  const isMethodPath = simplePathRE.test(handler.value)\n  const isFunctionExpression = fnExpRE.test(handler.value)\n\n  if (!handler.modifiers) {\n    if (isMethodPath || isFunctionExpression) {\n      return handler.value\n    }\n    /* istanbul ignore if */\n    if (__WEEX__ && handler.params) {\n      return genWeexHandler(handler.params, handler.value)\n    }\n    return `function($event){${handler.value}}` // inline statement\n  } else {\n    let code = ''\n    let genModifierCode = ''\n    const keys = []\n    for (const key in handler.modifiers) {\n      if (modifierCode[key]) {\n        genModifierCode += modifierCode[key]\n        // left/right\n        if (keyCodes[key]) {\n          keys.push(key)\n        }\n      } else if (key === 'exact') {\n        const modifiers: ASTModifiers = (handler.modifiers: any)\n        genModifierCode += genGuard(\n          ['ctrl', 'shift', 'alt', 'meta']\n            .filter(keyModifier => !modifiers[keyModifier])\n            .map(keyModifier => `$event.${keyModifier}Key`)\n            .join('||')\n        )\n      } else {\n        keys.push(key)\n      }\n    }\n    if (keys.length) {\n      code += genKeyFilter(keys)\n    }\n    // Make sure modifiers like prevent and stop get executed after key filtering\n    if (genModifierCode) {\n      code += genModifierCode\n    }\n    const handlerCode = isMethodPath\n      ? `return ${handler.value}($event)`\n      : isFunctionExpression\n        ? `return (${handler.value})($event)`\n        : handler.value\n    /* istanbul ignore if */\n    if (__WEEX__ && handler.params) {\n      return genWeexHandler(handler.params, code + handlerCode)\n    }\n    return `function($event){${code}${handlerCode}}`\n  }\n}\n\nfunction genKeyFilter (keys: Array<string>): string {\n  return `if(!('button' in $event)&&${keys.map(genFilterCode).join('&&')})return null;`\n}\n\nfunction genFilterCode (key: string): string {\n  const keyVal = parseInt(key, 10)\n  if (keyVal) {\n    return `$event.keyCode!==${keyVal}`\n  }\n  const keyCode = keyCodes[key]\n  const keyName = keyNames[key]\n  return (\n    `_k($event.keyCode,` +\n    `${JSON.stringify(key)},` +\n    `${JSON.stringify(keyCode)},` +\n    `$event.key,` +\n    `${JSON.stringify(keyName)}` +\n    `)`\n  )\n}\n"
  },
  {
    "path": "vue/src/compiler/codegen/index.js",
    "content": "/* @flow */\n\nimport { genHandlers } from './events'\nimport baseDirectives from '../directives/index'\nimport { camelize, no, extend } from 'shared/util'\nimport { baseWarn, pluckModuleFunction } from '../helpers'\n\ntype TransformFunction = (el: ASTElement, code: string) => string;\ntype DataGenFunction = (el: ASTElement) => string;\ntype DirectiveFunction = (el: ASTElement, dir: ASTDirective, warn: Function) => boolean;\n\nexport class CodegenState {\n  options: CompilerOptions;\n  warn: Function;\n  transforms: Array<TransformFunction>;\n  dataGenFns: Array<DataGenFunction>;\n  directives: { [key: string]: DirectiveFunction };\n  maybeComponent: (el: ASTElement) => boolean;\n  onceId: number;\n  staticRenderFns: Array<string>;\n\n  constructor (options: CompilerOptions) {\n    this.options = options\n    this.warn = options.warn || baseWarn\n    this.transforms = pluckModuleFunction(options.modules, 'transformCode')\n    this.dataGenFns = pluckModuleFunction(options.modules, 'genData')\n    this.directives = extend(extend({}, baseDirectives), options.directives)\n    const isReservedTag = options.isReservedTag || no\n    this.maybeComponent = (el: ASTElement) => !isReservedTag(el.tag)\n    this.onceId = 0\n    this.staticRenderFns = []\n  }\n}\n\nexport type CodegenResult = {\n  render: string,\n  staticRenderFns: Array<string>\n};\n\nexport function generate (\n  ast: ASTElement | void,\n  options: CompilerOptions\n): CodegenResult {\n  const state = new CodegenState(options)\n  const code = ast ? genElement(ast, state) : '_c(\"div\")'\n  return {\n    render: `with(this){return ${code}}`,\n    staticRenderFns: state.staticRenderFns\n  }\n}\n\nexport function genElement (el: ASTElement, state: CodegenState): string {\n  if (el.staticRoot && !el.staticProcessed) {\n    return genStatic(el, state)\n  } else if (el.once && !el.onceProcessed) {\n    return genOnce(el, state)\n  } else if (el.for && !el.forProcessed) {\n    return genFor(el, state)\n  } else if (el.if && !el.ifProcessed) {\n    return genIf(el, state)\n  } else if (el.tag === 'template' && !el.slotTarget) {\n    return genChildren(el, state) || 'void 0'\n  } else if (el.tag === 'slot') {\n    return genSlot(el, state)\n  } else {\n    // component or element\n    let code\n    if (el.component) {\n      code = genComponent(el.component, el, state)\n    } else {\n      const data = el.plain ? undefined : genData(el, state)\n\n      const children = el.inlineTemplate ? null : genChildren(el, state, true)\n      code = `_c('${el.tag}'${\n        data ? `,${data}` : '' // data\n      }${\n        children ? `,${children}` : '' // children\n      })`\n    }\n    // module transforms\n    for (let i = 0; i < state.transforms.length; i++) {\n      code = state.transforms[i](el, code)\n    }\n    return code\n  }\n}\n\n// hoist static sub-trees out\nfunction genStatic (el: ASTElement, state: CodegenState): string {\n  el.staticProcessed = true\n  state.staticRenderFns.push(`with(this){return ${genElement(el, state)}}`)\n  return `_m(${\n    state.staticRenderFns.length - 1\n  }${\n    el.staticInFor ? ',true' : ''\n  })`\n}\n\n// v-once\nfunction genOnce (el: ASTElement, state: CodegenState): string {\n  el.onceProcessed = true\n  if (el.if && !el.ifProcessed) {\n    return genIf(el, state)\n  } else if (el.staticInFor) {\n    let key = ''\n    let parent = el.parent\n    while (parent) {\n      if (parent.for) {\n        key = parent.key\n        break\n      }\n      parent = parent.parent\n    }\n    if (!key) {\n      process.env.NODE_ENV !== 'production' && state.warn(\n        `v-once can only be used inside v-for that is keyed. `\n      )\n      return genElement(el, state)\n    }\n    return `_o(${genElement(el, state)},${state.onceId++},${key})`\n  } else {\n    return genStatic(el, state)\n  }\n}\n\nexport function genIf (\n  el: any,\n  state: CodegenState,\n  altGen?: Function,\n  altEmpty?: string\n): string {\n  el.ifProcessed = true // avoid recursion\n  return genIfConditions(el.ifConditions.slice(), state, altGen, altEmpty)\n}\n\nfunction genIfConditions (\n  conditions: ASTIfConditions,\n  state: CodegenState,\n  altGen?: Function,\n  altEmpty?: string\n): string {\n  if (!conditions.length) {\n    return altEmpty || '_e()'\n  }\n\n  const condition = conditions.shift()\n  if (condition.exp) {\n    return `(${condition.exp})?${\n      genTernaryExp(condition.block)\n    }:${\n      genIfConditions(conditions, state, altGen, altEmpty)\n    }`\n  } else {\n    return `${genTernaryExp(condition.block)}`\n  }\n\n  // v-if with v-once should generate code like (a)?_m(0):_m(1)\n  function genTernaryExp (el) {\n    return altGen\n      ? altGen(el, state)\n      : el.once\n        ? genOnce(el, state)\n        : genElement(el, state)\n  }\n}\n\nexport function genFor (\n  el: any,\n  state: CodegenState,\n  altGen?: Function,\n  altHelper?: string\n): string {\n  const exp = el.for\n  const alias = el.alias\n  const iterator1 = el.iterator1 ? `,${el.iterator1}` : ''\n  const iterator2 = el.iterator2 ? `,${el.iterator2}` : ''\n\n  if (process.env.NODE_ENV !== 'production' &&\n    state.maybeComponent(el) &&\n    el.tag !== 'slot' &&\n    el.tag !== 'template' &&\n    !el.key\n  ) {\n    state.warn(\n      `<${el.tag} v-for=\"${alias} in ${exp}\">: component lists rendered with ` +\n      `v-for should have explicit keys. ` +\n      `See https://vuejs.org/guide/list.html#key for more info.`,\n      true /* tip */\n    )\n  }\n\n  el.forProcessed = true // avoid recursion\n  return `${altHelper || '_l'}((${exp}),` +\n    `function(${alias}${iterator1}${iterator2}){` +\n      `return ${(altGen || genElement)(el, state)}` +\n    '})'\n}\n\nexport function genData (el: ASTElement, state: CodegenState): string {\n  let data = '{'\n\n  // directives first.\n  // directives may mutate the el's other properties before they are generated.\n  const dirs = genDirectives(el, state)\n  if (dirs) data += dirs + ','\n\n  // key\n  if (el.key) {\n    data += `key:${el.key},`\n  }\n  // ref\n  if (el.ref) {\n    data += `ref:${el.ref},`\n  }\n  if (el.refInFor) {\n    data += `refInFor:true,`\n  }\n  // pre\n  if (el.pre) {\n    data += `pre:true,`\n  }\n  // record original tag name for components using \"is\" attribute\n  if (el.component) {\n    data += `tag:\"${el.tag}\",`\n  }\n  // module data generation functions\n  for (let i = 0; i < state.dataGenFns.length; i++) {\n    data += state.dataGenFns[i](el)\n  }\n  // attributes\n  if (el.attrs) {\n    data += `attrs:{${genProps(el.attrs)}},`\n  }\n  // DOM props\n  if (el.props) {\n    data += `domProps:{${genProps(el.props)}},`\n  }\n  // event handlers\n  if (el.events) {\n    data += `${genHandlers(el.events, false, state.warn)},`\n  }\n  if (el.nativeEvents) {\n    data += `${genHandlers(el.nativeEvents, true, state.warn)},`\n  }\n  // slot target\n  // only for non-scoped slots\n  if (el.slotTarget && !el.slotScope) {\n    data += `slot:${el.slotTarget},`\n  }\n  // scoped slots\n  if (el.scopedSlots) {\n    data += `${genScopedSlots(el.scopedSlots, state)},`\n  }\n  // component v-model\n  if (el.model) {\n    data += `model:{value:${\n      el.model.value\n    },callback:${\n      el.model.callback\n    },expression:${\n      el.model.expression\n    }},`\n  }\n  // inline-template\n  if (el.inlineTemplate) {\n    const inlineTemplate = genInlineTemplate(el, state)\n    if (inlineTemplate) {\n      data += `${inlineTemplate},`\n    }\n  }\n  data = data.replace(/,$/, '') + '}'\n  // v-bind data wrap\n  if (el.wrapData) {\n    data = el.wrapData(data)\n  }\n  // v-on data wrap\n  if (el.wrapListeners) {\n    data = el.wrapListeners(data)\n  }\n  return data\n}\n\nfunction genDirectives (el: ASTElement, state: CodegenState): string | void {\n  const dirs = el.directives\n  if (!dirs) return\n  let res = 'directives:['\n  let hasRuntime = false\n  let i, l, dir, needRuntime\n  for (i = 0, l = dirs.length; i < l; i++) {\n    dir = dirs[i]\n    needRuntime = true\n    const gen: DirectiveFunction = state.directives[dir.name]\n    if (gen) {\n      // compile-time directive that manipulates AST.\n      // returns true if it also needs a runtime counterpart.\n      needRuntime = !!gen(el, dir, state.warn)\n    }\n    if (needRuntime) {\n      hasRuntime = true\n      res += `{name:\"${dir.name}\",rawName:\"${dir.rawName}\"${\n        dir.value ? `,value:(${dir.value}),expression:${JSON.stringify(dir.value)}` : ''\n      }${\n        dir.arg ? `,arg:\"${dir.arg}\"` : ''\n      }${\n        dir.modifiers ? `,modifiers:${JSON.stringify(dir.modifiers)}` : ''\n      }},`\n    }\n  }\n  if (hasRuntime) {\n    return res.slice(0, -1) + ']'\n  }\n}\n\nfunction genInlineTemplate (el: ASTElement, state: CodegenState): ?string {\n  const ast = el.children[0]\n  if (process.env.NODE_ENV !== 'production' && (\n    el.children.length !== 1 || ast.type !== 1\n  )) {\n    state.warn('Inline-template components must have exactly one child element.')\n  }\n  if (ast.type === 1) {\n    const inlineRenderFns = generate(ast, state.options)\n    return `inlineTemplate:{render:function(){${\n      inlineRenderFns.render\n    }},staticRenderFns:[${\n      inlineRenderFns.staticRenderFns.map(code => `function(){${code}}`).join(',')\n    }]}`\n  }\n}\n\nfunction genScopedSlots (\n  slots: { [key: string]: ASTElement },\n  state: CodegenState\n): string {\n  return `scopedSlots:_u([${\n    Object.keys(slots).map(key => {\n      return genScopedSlot(key, slots[key], state)\n    }).join(',')\n  }])`\n}\n\nfunction genScopedSlot (\n  key: string,\n  el: ASTElement,\n  state: CodegenState\n): string {\n  if (el.for && !el.forProcessed) {\n    return genForScopedSlot(key, el, state)\n  }\n  const fn = `function(${String(el.slotScope)}){` +\n    `return ${el.tag === 'template'\n      ? el.if\n        ? `${el.if}?${genChildren(el, state) || 'undefined'}:undefined`\n        : genChildren(el, state) || 'undefined'\n      : genElement(el, state)\n    }}`\n  return `{key:${key},fn:${fn}}`\n}\n\nfunction genForScopedSlot (\n  key: string,\n  el: any,\n  state: CodegenState\n): string {\n  const exp = el.for\n  const alias = el.alias\n  const iterator1 = el.iterator1 ? `,${el.iterator1}` : ''\n  const iterator2 = el.iterator2 ? `,${el.iterator2}` : ''\n  el.forProcessed = true // avoid recursion\n  return `_l((${exp}),` +\n    `function(${alias}${iterator1}${iterator2}){` +\n      `return ${genScopedSlot(key, el, state)}` +\n    '})'\n}\n\nexport function genChildren (\n  el: ASTElement,\n  state: CodegenState,\n  checkSkip?: boolean,\n  altGenElement?: Function,\n  altGenNode?: Function\n): string | void {\n  const children = el.children\n  if (children.length) {\n    const el: any = children[0]\n    // optimize single v-for\n    if (children.length === 1 &&\n      el.for &&\n      el.tag !== 'template' &&\n      el.tag !== 'slot'\n    ) {\n      return (altGenElement || genElement)(el, state)\n    }\n    const normalizationType = checkSkip\n      ? getNormalizationType(children, state.maybeComponent)\n      : 0\n    const gen = altGenNode || genNode\n    return `[${children.map(c => gen(c, state)).join(',')}]${\n      normalizationType ? `,${normalizationType}` : ''\n    }`\n  }\n}\n\n// determine the normalization needed for the children array.\n// 0: no normalization needed\n// 1: simple normalization needed (possible 1-level deep nested array)\n// 2: full normalization needed\nfunction getNormalizationType (\n  children: Array<ASTNode>,\n  maybeComponent: (el: ASTElement) => boolean\n): number {\n  let res = 0\n  for (let i = 0; i < children.length; i++) {\n    const el: ASTNode = children[i]\n    if (el.type !== 1) {\n      continue\n    }\n    if (needsNormalization(el) ||\n        (el.ifConditions && el.ifConditions.some(c => needsNormalization(c.block)))) {\n      res = 2\n      break\n    }\n    if (maybeComponent(el) ||\n        (el.ifConditions && el.ifConditions.some(c => maybeComponent(c.block)))) {\n      res = 1\n    }\n  }\n  return res\n}\n\nfunction needsNormalization (el: ASTElement): boolean {\n  return el.for !== undefined || el.tag === 'template' || el.tag === 'slot'\n}\n\nfunction genNode (node: ASTNode, state: CodegenState): string {\n  if (node.type === 1) {\n    return genElement(node, state)\n  } if (node.type === 3 && node.isComment) {\n    return genComment(node)\n  } else {\n    return genText(node)\n  }\n}\n\nexport function genText (text: ASTText | ASTExpression): string {\n  return `_v(${text.type === 2\n    ? text.expression // no need for () because already wrapped in _s()\n    : transformSpecialNewlines(JSON.stringify(text.text))\n  })`\n}\n\nexport function genComment (comment: ASTText): string {\n  return `_e(${JSON.stringify(comment.text)})`\n}\n\nfunction genSlot (el: ASTElement, state: CodegenState): string {\n  const slotName = el.slotName || '\"default\"'\n  const children = genChildren(el, state)\n  let res = `_t(${slotName}${children ? `,${children}` : ''}`\n  const attrs = el.attrs && `{${el.attrs.map(a => `${camelize(a.name)}:${a.value}`).join(',')}}`\n  const bind = el.attrsMap['v-bind']\n  if ((attrs || bind) && !children) {\n    res += `,null`\n  }\n  if (attrs) {\n    res += `,${attrs}`\n  }\n  if (bind) {\n    res += `${attrs ? '' : ',null'},${bind}`\n  }\n  return res + ')'\n}\n\n// componentName is el.component, take it as argument to shun flow's pessimistic refinement\nfunction genComponent (\n  componentName: string,\n  el: ASTElement,\n  state: CodegenState\n): string {\n  const children = el.inlineTemplate ? null : genChildren(el, state, true)\n  return `_c(${componentName},${genData(el, state)}${\n    children ? `,${children}` : ''\n  })`\n}\n\nfunction genProps (props: Array<{ name: string, value: any }>): string {\n  let res = ''\n  for (let i = 0; i < props.length; i++) {\n    const prop = props[i]\n    /* istanbul ignore if */\n    if (__WEEX__) {\n      res += `\"${prop.name}\":${generateValue(prop.value)},`\n    } else {\n      res += `\"${prop.name}\":${transformSpecialNewlines(prop.value)},`\n    }\n  }\n  return res.slice(0, -1)\n}\n\n/* istanbul ignore next */\nfunction generateValue (value) {\n  if (typeof value === 'string') {\n    return transformSpecialNewlines(value)\n  }\n  return JSON.stringify(value)\n}\n\n// #3895, #4268\nfunction transformSpecialNewlines (text: string): string {\n  return text\n    .replace(/\\u2028/g, '\\\\u2028')\n    .replace(/\\u2029/g, '\\\\u2029')\n}\n"
  },
  {
    "path": "vue/src/compiler/create-compiler.js",
    "content": "/* @flow */\n\nimport { extend } from 'shared/util'\nimport { detectErrors } from './error-detector'\nimport { createCompileToFunctionFn } from './to-function'\n\nexport function createCompilerCreator (baseCompile: Function): Function {\n  return function createCompiler (baseOptions: CompilerOptions) {\n    function compile (\n      template: string,\n      options?: CompilerOptions\n    ): CompiledResult {\n      const finalOptions = Object.create(baseOptions)\n      const errors = []\n      const tips = []\n      finalOptions.warn = (msg, tip) => {\n        (tip ? tips : errors).push(msg)\n      }\n\n      if (options) {\n        // merge custom modules\n        if (options.modules) {\n          finalOptions.modules =\n            (baseOptions.modules || []).concat(options.modules)\n        }\n        // merge custom directives\n        if (options.directives) {\n          finalOptions.directives = extend(\n            Object.create(baseOptions.directives || null),\n            options.directives\n          )\n        }\n        // copy other options\n        for (const key in options) {\n          if (key !== 'modules' && key !== 'directives') {\n            finalOptions[key] = options[key]\n          }\n        }\n      }\n\n      const compiled = baseCompile(template, finalOptions)\n      if (process.env.NODE_ENV !== 'production') {\n        errors.push.apply(errors, detectErrors(compiled.ast))\n      }\n      compiled.errors = errors\n      compiled.tips = tips\n      return compiled\n    }\n\n    return {\n      compile,\n      compileToFunctions: createCompileToFunctionFn(compile)\n    }\n  }\n}\n"
  },
  {
    "path": "vue/src/compiler/directives/bind.js",
    "content": "/* @flow */\n\nexport default function bind (el: ASTElement, dir: ASTDirective) {\n  el.wrapData = (code: string) => {\n    return `_b(${code},'${el.tag}',${dir.value},${\n      dir.modifiers && dir.modifiers.prop ? 'true' : 'false'\n    }${\n      dir.modifiers && dir.modifiers.sync ? ',true' : ''\n    })`\n  }\n}\n"
  },
  {
    "path": "vue/src/compiler/directives/index.js",
    "content": "/* @flow */\n\nimport on from './on'\nimport bind from './bind'\nimport { noop } from 'shared/util'\n\nexport default {\n  on,\n  bind,\n  cloak: noop\n}\n"
  },
  {
    "path": "vue/src/compiler/directives/model.js",
    "content": "/* @flow */\n\n/**\n * Cross-platform code generation for component v-model\n */\nexport function genComponentModel (\n  el: ASTElement,\n  value: string,\n  modifiers: ?ASTModifiers\n): ?boolean {\n  const { number, trim } = modifiers || {}\n\n  const baseValueExpression = '$$v'\n  let valueExpression = baseValueExpression\n  if (trim) {\n    valueExpression =\n      `(typeof ${baseValueExpression} === 'string'` +\n      `? ${baseValueExpression}.trim()` +\n      `: ${baseValueExpression})`\n  }\n  if (number) {\n    valueExpression = `_n(${valueExpression})`\n  }\n  const assignment = genAssignmentCode(value, valueExpression)\n\n  el.model = {\n    value: `(${value})`,\n    expression: `\"${value}\"`,\n    callback: `function (${baseValueExpression}) {${assignment}}`\n  }\n}\n\n/**\n * Cross-platform codegen helper for generating v-model value assignment code.\n */\nexport function genAssignmentCode (\n  value: string,\n  assignment: string\n): string {\n  const res = parseModel(value)\n  if (res.key === null) {\n    return `${value}=${assignment}`\n  } else {\n    return `$set(${res.exp}, ${res.key}, ${assignment})`\n  }\n}\n\n/**\n * Parse a v-model expression into a base path and a final key segment.\n * Handles both dot-path and possible square brackets.\n *\n * Possible cases:\n *\n * - test\n * - test[key]\n * - test[test1[key]]\n * - test[\"a\"][key]\n * - xxx.test[a[a].test1[key]]\n * - test.xxx.a[\"asa\"][test1[key]]\n *\n */\n\nlet len, str, chr, index, expressionPos, expressionEndPos\n\ntype ModelParseResult = {\n  exp: string,\n  key: string | null\n}\n\nexport function parseModel (val: string): ModelParseResult {\n  // Fix https://github.com/vuejs/vue/pull/7730\n  // allow v-model=\"obj.val \" (trailing whitespace)\n  val = val.trim()\n  len = val.length\n\n  if (val.indexOf('[') < 0 || val.lastIndexOf(']') < len - 1) {\n    index = val.lastIndexOf('.')\n    if (index > -1) {\n      return {\n        exp: val.slice(0, index),\n        key: '\"' + val.slice(index + 1) + '\"'\n      }\n    } else {\n      return {\n        exp: val,\n        key: null\n      }\n    }\n  }\n\n  str = val\n  index = expressionPos = expressionEndPos = 0\n\n  while (!eof()) {\n    chr = next()\n    /* istanbul ignore if */\n    if (isStringStart(chr)) {\n      parseString(chr)\n    } else if (chr === 0x5B) {\n      parseBracket(chr)\n    }\n  }\n\n  return {\n    exp: val.slice(0, expressionPos),\n    key: val.slice(expressionPos + 1, expressionEndPos)\n  }\n}\n\nfunction next (): number {\n  return str.charCodeAt(++index)\n}\n\nfunction eof (): boolean {\n  return index >= len\n}\n\nfunction isStringStart (chr: number): boolean {\n  return chr === 0x22 || chr === 0x27\n}\n\nfunction parseBracket (chr: number): void {\n  let inBracket = 1\n  expressionPos = index\n  while (!eof()) {\n    chr = next()\n    if (isStringStart(chr)) {\n      parseString(chr)\n      continue\n    }\n    if (chr === 0x5B) inBracket++\n    if (chr === 0x5D) inBracket--\n    if (inBracket === 0) {\n      expressionEndPos = index\n      break\n    }\n  }\n}\n\nfunction parseString (chr: number): void {\n  const stringQuote = chr\n  while (!eof()) {\n    chr = next()\n    if (chr === stringQuote) {\n      break\n    }\n  }\n}\n"
  },
  {
    "path": "vue/src/compiler/directives/on.js",
    "content": "/* @flow */\n\nimport { warn } from 'core/util/index'\n\nexport default function on (el: ASTElement, dir: ASTDirective) {\n  if (process.env.NODE_ENV !== 'production' && dir.modifiers) {\n    warn(`v-on without argument does not support modifiers.`)\n  }\n  el.wrapListeners = (code: string) => `_g(${code},${dir.value})`\n}\n"
  },
  {
    "path": "vue/src/compiler/error-detector.js",
    "content": "/* @flow */\n\nimport { dirRE, onRE } from './parser/index'\n\n// these keywords should not appear inside expressions, but operators like\n// typeof, instanceof and in are allowed\nconst prohibitedKeywordRE = new RegExp('\\\\b' + (\n  'do,if,for,let,new,try,var,case,else,with,await,break,catch,class,const,' +\n  'super,throw,while,yield,delete,export,import,return,switch,default,' +\n  'extends,finally,continue,debugger,function,arguments'\n).split(',').join('\\\\b|\\\\b') + '\\\\b')\n\n// these unary operators should not be used as property/method names\nconst unaryOperatorsRE = new RegExp('\\\\b' + (\n  'delete,typeof,void'\n).split(',').join('\\\\s*\\\\([^\\\\)]*\\\\)|\\\\b') + '\\\\s*\\\\([^\\\\)]*\\\\)')\n\n// strip strings in expressions\nconst stripStringRE = /'(?:[^'\\\\]|\\\\.)*'|\"(?:[^\"\\\\]|\\\\.)*\"|`(?:[^`\\\\]|\\\\.)*\\$\\{|\\}(?:[^`\\\\]|\\\\.)*`|`(?:[^`\\\\]|\\\\.)*`/g\n\n// detect problematic expressions in a template\nexport function detectErrors (ast: ?ASTNode): Array<string> {\n  const errors: Array<string> = []\n  if (ast) {\n    checkNode(ast, errors)\n  }\n  return errors\n}\n\nfunction checkNode (node: ASTNode, errors: Array<string>) {\n  if (node.type === 1) {\n    for (const name in node.attrsMap) {\n      if (dirRE.test(name)) {\n        const value = node.attrsMap[name]\n        if (value) {\n          if (name === 'v-for') {\n            checkFor(node, `v-for=\"${value}\"`, errors)\n          } else if (onRE.test(name)) {\n            checkEvent(value, `${name}=\"${value}\"`, errors)\n          } else {\n            checkExpression(value, `${name}=\"${value}\"`, errors)\n          }\n        }\n      }\n    }\n    if (node.children) {\n      for (let i = 0; i < node.children.length; i++) {\n        checkNode(node.children[i], errors)\n      }\n    }\n  } else if (node.type === 2) {\n    checkExpression(node.expression, node.text, errors)\n  }\n}\n\nfunction checkEvent (exp: string, text: string, errors: Array<string>) {\n  const stipped = exp.replace(stripStringRE, '')\n  const keywordMatch: any = stipped.match(unaryOperatorsRE)\n  if (keywordMatch && stipped.charAt(keywordMatch.index - 1) !== '$') {\n    errors.push(\n      `avoid using JavaScript unary operator as property name: ` +\n      `\"${keywordMatch[0]}\" in expression ${text.trim()}`\n    )\n  }\n  checkExpression(exp, text, errors)\n}\n\nfunction checkFor (node: ASTElement, text: string, errors: Array<string>) {\n  checkExpression(node.for || '', text, errors)\n  checkIdentifier(node.alias, 'v-for alias', text, errors)\n  checkIdentifier(node.iterator1, 'v-for iterator', text, errors)\n  checkIdentifier(node.iterator2, 'v-for iterator', text, errors)\n}\n\nfunction checkIdentifier (\n  ident: ?string,\n  type: string,\n  text: string,\n  errors: Array<string>\n) {\n  if (typeof ident === 'string') {\n    try {\n      new Function(`var ${ident}=_`)\n    } catch (e) {\n      errors.push(`invalid ${type} \"${ident}\" in expression: ${text.trim()}`)\n    }\n  }\n}\n\nfunction checkExpression (exp: string, text: string, errors: Array<string>) {\n  try {\n    new Function(`return ${exp}`)\n  } catch (e) {\n    const keywordMatch = exp.replace(stripStringRE, '').match(prohibitedKeywordRE)\n    if (keywordMatch) {\n      errors.push(\n        `avoid using JavaScript keyword as property name: ` +\n        `\"${keywordMatch[0]}\"\\n  Raw expression: ${text.trim()}`\n      )\n    } else {\n      errors.push(\n        `invalid expression: ${e.message} in\\n\\n` +\n        `    ${exp}\\n\\n` +\n        `  Raw expression: ${text.trim()}\\n`\n      )\n    }\n  }\n}\n"
  },
  {
    "path": "vue/src/compiler/helpers.js",
    "content": "/* @flow */\n\nimport { emptyObject } from 'shared/util'\nimport { parseFilters } from './parser/filter-parser'\n\nexport function baseWarn (msg: string) {\n  console.error(`[Vue compiler]: ${msg}`)\n}\n\nexport function pluckModuleFunction<F: Function> (\n  modules: ?Array<Object>,\n  key: string\n): Array<F> {\n  return modules\n    ? modules.map(m => m[key]).filter(_ => _)\n    : []\n}\n\nexport function addProp (el: ASTElement, name: string, value: string) {\n  (el.props || (el.props = [])).push({ name, value })\n  el.plain = false\n}\n\nexport function addAttr (el: ASTElement, name: string, value: any) {\n  (el.attrs || (el.attrs = [])).push({ name, value })\n  el.plain = false\n}\n\n// add a raw attr (use this in preTransforms)\nexport function addRawAttr (el: ASTElement, name: string, value: any) {\n  el.attrsMap[name] = value\n  el.attrsList.push({ name, value })\n}\n\nexport function addDirective (\n  el: ASTElement,\n  name: string,\n  rawName: string,\n  value: string,\n  arg: ?string,\n  modifiers: ?ASTModifiers\n) {\n  (el.directives || (el.directives = [])).push({ name, rawName, value, arg, modifiers })\n  el.plain = false\n}\n\nexport function addHandler (\n  el: ASTElement,\n  name: string,\n  value: string,\n  modifiers: ?ASTModifiers,\n  important?: boolean,\n  warn?: Function\n) {\n  modifiers = modifiers || emptyObject\n  // warn prevent and passive modifier\n  /* istanbul ignore if */\n  if (\n    process.env.NODE_ENV !== 'production' && warn &&\n    modifiers.prevent && modifiers.passive\n  ) {\n    warn(\n      'passive and prevent can\\'t be used together. ' +\n      'Passive handler can\\'t prevent default event.'\n    )\n  }\n\n  // check capture modifier\n  if (modifiers.capture) {\n    delete modifiers.capture\n    name = '!' + name // mark the event as captured\n  }\n  if (modifiers.once) {\n    delete modifiers.once\n    name = '~' + name // mark the event as once\n  }\n  /* istanbul ignore if */\n  if (modifiers.passive) {\n    delete modifiers.passive\n    name = '&' + name // mark the event as passive\n  }\n\n  // normalize click.right and click.middle since they don't actually fire\n  // this is technically browser-specific, but at least for now browsers are\n  // the only target envs that have right/middle clicks.\n  if (name === 'click') {\n    if (modifiers.right) {\n      name = 'contextmenu'\n      delete modifiers.right\n    } else if (modifiers.middle) {\n      name = 'mouseup'\n    }\n  }\n\n  let events\n  if (modifiers.native) {\n    delete modifiers.native\n    events = el.nativeEvents || (el.nativeEvents = {})\n  } else {\n    events = el.events || (el.events = {})\n  }\n\n  const newHandler: any = {\n    value: value.trim()\n  }\n  if (modifiers !== emptyObject) {\n    newHandler.modifiers = modifiers\n  }\n\n  const handlers = events[name]\n  /* istanbul ignore if */\n  if (Array.isArray(handlers)) {\n    important ? handlers.unshift(newHandler) : handlers.push(newHandler)\n  } else if (handlers) {\n    events[name] = important ? [newHandler, handlers] : [handlers, newHandler]\n  } else {\n    events[name] = newHandler\n  }\n\n  el.plain = false\n}\n\nexport function getBindingAttr (\n  el: ASTElement,\n  name: string,\n  getStatic?: boolean\n): ?string {\n  const dynamicValue =\n    getAndRemoveAttr(el, ':' + name) ||\n    getAndRemoveAttr(el, 'v-bind:' + name)\n  if (dynamicValue != null) {\n    return parseFilters(dynamicValue)\n  } else if (getStatic !== false) {\n    const staticValue = getAndRemoveAttr(el, name)\n    if (staticValue != null) {\n      return JSON.stringify(staticValue)\n    }\n  }\n}\n\n// note: this only removes the attr from the Array (attrsList) so that it\n// doesn't get processed by processAttrs.\n// By default it does NOT remove it from the map (attrsMap) because the map is\n// needed during codegen.\nexport function getAndRemoveAttr (\n  el: ASTElement,\n  name: string,\n  removeFromMap?: boolean\n): ?string {\n  let val\n  if ((val = el.attrsMap[name]) != null) {\n    const list = el.attrsList\n    for (let i = 0, l = list.length; i < l; i++) {\n      if (list[i].name === name) {\n        list.splice(i, 1)\n        break\n      }\n    }\n  }\n  if (removeFromMap) {\n    delete el.attrsMap[name]\n  }\n  return val\n}\n"
  },
  {
    "path": "vue/src/compiler/index.js",
    "content": "/* @flow */\n\nimport { parse } from './parser/index'\nimport { optimize } from './optimizer'\nimport { generate } from './codegen/index'\nimport { createCompilerCreator } from './create-compiler'\n\n// `createCompilerCreator` allows creating compilers that use alternative\n// parser/optimizer/codegen, e.g the SSR optimizing compiler.\n// Here we just export a default compiler using the default parts.\nexport const createCompiler = createCompilerCreator(function baseCompile (\n  template: string,\n  options: CompilerOptions\n): CompiledResult {\n  const ast = parse(template.trim(), options)\n  if (options.optimize !== false) {\n    optimize(ast, options)\n  }\n  const code = generate(ast, options)\n  return {\n    ast,\n    render: code.render,\n    staticRenderFns: code.staticRenderFns\n  }\n})\n"
  },
  {
    "path": "vue/src/compiler/optimizer.js",
    "content": "/* @flow */\n\nimport { makeMap, isBuiltInTag, cached, no } from 'shared/util'\n\nlet isStaticKey\nlet isPlatformReservedTag\n\nconst genStaticKeysCached = cached(genStaticKeys)\n\n/**\n * Goal of the optimizer: walk the generated template AST tree\n * and detect sub-trees that are purely static, i.e. parts of\n * the DOM that never needs to change.\n *\n * Once we detect these sub-trees, we can:\n *\n * 1. Hoist them into constants, so that we no longer need to\n *    create fresh nodes for them on each re-render;\n * 2. Completely skip them in the patching process.\n */\nexport function optimize (root: ?ASTElement, options: CompilerOptions) {\n  if (!root) return\n  isStaticKey = genStaticKeysCached(options.staticKeys || '')\n  isPlatformReservedTag = options.isReservedTag || no\n  // first pass: mark all non-static nodes.\n  markStatic(root)\n  // second pass: mark static roots.\n  markStaticRoots(root, false)\n}\n\nfunction genStaticKeys (keys: string): Function {\n  return makeMap(\n    'type,tag,attrsList,attrsMap,plain,parent,children,attrs' +\n    (keys ? ',' + keys : '')\n  )\n}\n\nfunction markStatic (node: ASTNode) {\n  node.static = isStatic(node)\n  if (node.type === 1) {\n    // do not make component slot content static. this avoids\n    // 1. components not able to mutate slot nodes\n    // 2. static slot content fails for hot-reloading\n    if (\n      !isPlatformReservedTag(node.tag) &&\n      node.tag !== 'slot' &&\n      node.attrsMap['inline-template'] == null\n    ) {\n      return\n    }\n    for (let i = 0, l = node.children.length; i < l; i++) {\n      const child = node.children[i]\n      markStatic(child)\n      if (!child.static) {\n        node.static = false\n      }\n    }\n    if (node.ifConditions) {\n      for (let i = 1, l = node.ifConditions.length; i < l; i++) {\n        const block = node.ifConditions[i].block\n        markStatic(block)\n        if (!block.static) {\n          node.static = false\n        }\n      }\n    }\n  }\n}\n\nfunction markStaticRoots (node: ASTNode, isInFor: boolean) {\n  if (node.type === 1) {\n    if (node.static || node.once) {\n      node.staticInFor = isInFor\n    }\n    // For a node to qualify as a static root, it should have children that\n    // are not just static text. Otherwise the cost of hoisting out will\n    // outweigh the benefits and it's better off to just always render it fresh.\n    if (node.static && node.children.length && !(\n      node.children.length === 1 &&\n      node.children[0].type === 3\n    )) {\n      node.staticRoot = true\n      return\n    } else {\n      node.staticRoot = false\n    }\n    if (node.children) {\n      for (let i = 0, l = node.children.length; i < l; i++) {\n        markStaticRoots(node.children[i], isInFor || !!node.for)\n      }\n    }\n    if (node.ifConditions) {\n      for (let i = 1, l = node.ifConditions.length; i < l; i++) {\n        markStaticRoots(node.ifConditions[i].block, isInFor)\n      }\n    }\n  }\n}\n\nfunction isStatic (node: ASTNode): boolean {\n  if (node.type === 2) { // expression\n    return false\n  }\n  if (node.type === 3) { // text\n    return true\n  }\n  return !!(node.pre || (\n    !node.hasBindings && // no dynamic bindings\n    !node.if && !node.for && // not v-if or v-for or v-else\n    !isBuiltInTag(node.tag) && // not a built-in\n    isPlatformReservedTag(node.tag) && // not a component\n    !isDirectChildOfTemplateFor(node) &&\n    Object.keys(node).every(isStaticKey)\n  ))\n}\n\nfunction isDirectChildOfTemplateFor (node: ASTElement): boolean {\n  while (node.parent) {\n    node = node.parent\n    if (node.tag !== 'template') {\n      return false\n    }\n    if (node.for) {\n      return true\n    }\n  }\n  return false\n}\n"
  },
  {
    "path": "vue/src/compiler/parser/entity-decoder.js",
    "content": "/* @flow */\n\nlet decoder\n\nexport default {\n  decode (html: string): string {\n    decoder = decoder || document.createElement('div')\n    decoder.innerHTML = html\n    return decoder.textContent\n  }\n}\n"
  },
  {
    "path": "vue/src/compiler/parser/filter-parser.js",
    "content": "/* @flow */\n\nconst validDivisionCharRE = /[\\w).+\\-_$\\]]/\n\nexport function parseFilters (exp: string): string {\n  let inSingle = false\n  let inDouble = false\n  let inTemplateString = false\n  let inRegex = false\n  let curly = 0\n  let square = 0\n  let paren = 0\n  let lastFilterIndex = 0\n  let c, prev, i, expression, filters\n\n  for (i = 0; i < exp.length; i++) {\n    prev = c\n    c = exp.charCodeAt(i)\n    if (inSingle) {\n      if (c === 0x27 && prev !== 0x5C) inSingle = false\n    } else if (inDouble) {\n      if (c === 0x22 && prev !== 0x5C) inDouble = false\n    } else if (inTemplateString) {\n      if (c === 0x60 && prev !== 0x5C) inTemplateString = false\n    } else if (inRegex) {\n      if (c === 0x2f && prev !== 0x5C) inRegex = false\n    } else if (\n      c === 0x7C && // pipe\n      exp.charCodeAt(i + 1) !== 0x7C &&\n      exp.charCodeAt(i - 1) !== 0x7C &&\n      !curly && !square && !paren\n    ) {\n      if (expression === undefined) {\n        // first filter, end of expression\n        lastFilterIndex = i + 1\n        expression = exp.slice(0, i).trim()\n      } else {\n        pushFilter()\n      }\n    } else {\n      switch (c) {\n        case 0x22: inDouble = true; break         // \"\n        case 0x27: inSingle = true; break         // '\n        case 0x60: inTemplateString = true; break // `\n        case 0x28: paren++; break                 // (\n        case 0x29: paren--; break                 // )\n        case 0x5B: square++; break                // [\n        case 0x5D: square--; break                // ]\n        case 0x7B: curly++; break                 // {\n        case 0x7D: curly--; break                 // }\n      }\n      if (c === 0x2f) { // /\n        let j = i - 1\n        let p\n        // find first non-whitespace prev char\n        for (; j >= 0; j--) {\n          p = exp.charAt(j)\n          if (p !== ' ') break\n        }\n        if (!p || !validDivisionCharRE.test(p)) {\n          inRegex = true\n        }\n      }\n    }\n  }\n\n  if (expression === undefined) {\n    expression = exp.slice(0, i).trim()\n  } else if (lastFilterIndex !== 0) {\n    pushFilter()\n  }\n\n  function pushFilter () {\n    (filters || (filters = [])).push(exp.slice(lastFilterIndex, i).trim())\n    lastFilterIndex = i + 1\n  }\n\n  if (filters) {\n    for (i = 0; i < filters.length; i++) {\n      expression = wrapFilter(expression, filters[i])\n    }\n  }\n\n  return expression\n}\n\nfunction wrapFilter (exp: string, filter: string): string {\n  const i = filter.indexOf('(')\n  if (i < 0) {\n    // _f: resolveFilter\n    return `_f(\"${filter}\")(${exp})`\n  } else {\n    const name = filter.slice(0, i)\n    const args = filter.slice(i + 1)\n    return `_f(\"${name}\")(${exp}${args !== ')' ? ',' + args : args}`\n  }\n}\n"
  },
  {
    "path": "vue/src/compiler/parser/html-parser.js",
    "content": "/**\n * Not type-checking this file because it's mostly vendor code.\n */\n\n/*!\n * HTML Parser By John Resig (ejohn.org)\n * Modified by Juriy \"kangax\" Zaytsev\n * Original code by Erik Arvidsson, Mozilla Public License\n * http://erik.eae.net/simplehtmlparser/simplehtmlparser.js\n */\n\nimport { makeMap, no } from 'shared/util'\nimport { isNonPhrasingTag } from 'web/compiler/util'\n\n// Regular Expressions for parsing tags and attributes\nconst attribute = /^\\s*([^\\s\"'<>\\/=]+)(?:\\s*(=)\\s*(?:\"([^\"]*)\"+|'([^']*)'+|([^\\s\"'=<>`]+)))?/\n// could use https://www.w3.org/TR/1999/REC-xml-names-19990114/#NT-QName\n// but for Vue templates we can enforce a simple charset\nconst ncname = '[a-zA-Z_][\\\\w\\\\-\\\\.]*'\nconst qnameCapture = `((?:${ncname}\\\\:)?${ncname})`\nconst startTagOpen = new RegExp(`^<${qnameCapture}`)\nconst startTagClose = /^\\s*(\\/?)>/\nconst endTag = new RegExp(`^<\\\\/${qnameCapture}[^>]*>`)\nconst doctype = /^<!DOCTYPE [^>]+>/i\n// #7298: escape - to avoid being pased as HTML comment when inlined in page\nconst comment = /^<!\\--/\nconst conditionalComment = /^<!\\[/\n\nlet IS_REGEX_CAPTURING_BROKEN = false\n'x'.replace(/x(.)?/g, function (m, g) {\n  IS_REGEX_CAPTURING_BROKEN = g === ''\n})\n\n// Special Elements (can contain anything)\nexport const isPlainTextElement = makeMap('script,style,textarea', true)\nconst reCache = {}\n\nconst decodingMap = {\n  '&lt;': '<',\n  '&gt;': '>',\n  '&quot;': '\"',\n  '&amp;': '&',\n  '&#10;': '\\n',\n  '&#9;': '\\t'\n}\nconst encodedAttr = /&(?:lt|gt|quot|amp);/g\nconst encodedAttrWithNewLines = /&(?:lt|gt|quot|amp|#10|#9);/g\n\n// #5992\nconst isIgnoreNewlineTag = makeMap('pre,textarea', true)\nconst shouldIgnoreFirstNewline = (tag, html) => tag && isIgnoreNewlineTag(tag) && html[0] === '\\n'\n\nfunction decodeAttr (value, shouldDecodeNewlines) {\n  const re = shouldDecodeNewlines ? encodedAttrWithNewLines : encodedAttr\n  return value.replace(re, match => decodingMap[match])\n}\n\nexport function parseHTML (html, options) {\n  const stack = []\n  const expectHTML = options.expectHTML\n  const isUnaryTag = options.isUnaryTag || no\n  const canBeLeftOpenTag = options.canBeLeftOpenTag || no\n  let index = 0\n  let last, lastTag\n  while (html) {\n    last = html\n    // Make sure we're not in a plaintext content element like script/style\n    if (!lastTag || !isPlainTextElement(lastTag)) {\n      let textEnd = html.indexOf('<')\n      if (textEnd === 0) {\n        // Comment:\n        if (comment.test(html)) {\n          const commentEnd = html.indexOf('-->')\n\n          if (commentEnd >= 0) {\n            if (options.shouldKeepComment) {\n              options.comment(html.substring(4, commentEnd))\n            }\n            advance(commentEnd + 3)\n            continue\n          }\n        }\n\n        // http://en.wikipedia.org/wiki/Conditional_comment#Downlevel-revealed_conditional_comment\n        if (conditionalComment.test(html)) {\n          const conditionalEnd = html.indexOf(']>')\n\n          if (conditionalEnd >= 0) {\n            advance(conditionalEnd + 2)\n            continue\n          }\n        }\n\n        // Doctype:\n        const doctypeMatch = html.match(doctype)\n        if (doctypeMatch) {\n          advance(doctypeMatch[0].length)\n          continue\n        }\n\n        // End tag:\n        const endTagMatch = html.match(endTag)\n        if (endTagMatch) {\n          const curIndex = index\n          advance(endTagMatch[0].length)\n          parseEndTag(endTagMatch[1], curIndex, index)\n          continue\n        }\n\n        // Start tag:\n        const startTagMatch = parseStartTag()\n        if (startTagMatch) {\n          handleStartTag(startTagMatch)\n          if (shouldIgnoreFirstNewline(lastTag, html)) {\n            advance(1)\n          }\n          continue\n        }\n      }\n\n      let text, rest, next\n      if (textEnd >= 0) {\n        rest = html.slice(textEnd)\n        while (\n          !endTag.test(rest) &&\n          !startTagOpen.test(rest) &&\n          !comment.test(rest) &&\n          !conditionalComment.test(rest)\n        ) {\n          // < in plain text, be forgiving and treat it as text\n          next = rest.indexOf('<', 1)\n          if (next < 0) break\n          textEnd += next\n          rest = html.slice(textEnd)\n        }\n        text = html.substring(0, textEnd)\n        advance(textEnd)\n      }\n\n      if (textEnd < 0) {\n        text = html\n        html = ''\n      }\n\n      if (options.chars && text) {\n        options.chars(text)\n      }\n    } else {\n      let endTagLength = 0\n      const stackedTag = lastTag.toLowerCase()\n      const reStackedTag = reCache[stackedTag] || (reCache[stackedTag] = new RegExp('([\\\\s\\\\S]*?)(</' + stackedTag + '[^>]*>)', 'i'))\n      const rest = html.replace(reStackedTag, function (all, text, endTag) {\n        endTagLength = endTag.length\n        if (!isPlainTextElement(stackedTag) && stackedTag !== 'noscript') {\n          text = text\n            .replace(/<!\\--([\\s\\S]*?)-->/g, '$1') // #7298\n            .replace(/<!\\[CDATA\\[([\\s\\S]*?)]]>/g, '$1')\n        }\n        if (shouldIgnoreFirstNewline(stackedTag, text)) {\n          text = text.slice(1)\n        }\n        if (options.chars) {\n          options.chars(text)\n        }\n        return ''\n      })\n      index += html.length - rest.length\n      html = rest\n      parseEndTag(stackedTag, index - endTagLength, index)\n    }\n\n    if (html === last) {\n      options.chars && options.chars(html)\n      if (process.env.NODE_ENV !== 'production' && !stack.length && options.warn) {\n        options.warn(`Mal-formatted tag at end of template: \"${html}\"`)\n      }\n      break\n    }\n  }\n\n  // Clean up any remaining tags\n  parseEndTag()\n\n  function advance (n) {\n    index += n\n    html = html.substring(n)\n  }\n\n  function parseStartTag () {\n    const start = html.match(startTagOpen)\n    if (start) {\n      const match = {\n        tagName: start[1],\n        attrs: [],\n        start: index\n      }\n      advance(start[0].length)\n      let end, attr\n      while (!(end = html.match(startTagClose)) && (attr = html.match(attribute))) {\n        advance(attr[0].length)\n        match.attrs.push(attr)\n      }\n      if (end) {\n        match.unarySlash = end[1]\n        advance(end[0].length)\n        match.end = index\n        return match\n      }\n    }\n  }\n\n  function handleStartTag (match) {\n    const tagName = match.tagName\n    const unarySlash = match.unarySlash\n\n    if (expectHTML) {\n      if (lastTag === 'p' && isNonPhrasingTag(tagName)) {\n        parseEndTag(lastTag)\n      }\n      if (canBeLeftOpenTag(tagName) && lastTag === tagName) {\n        parseEndTag(tagName)\n      }\n    }\n\n    const unary = isUnaryTag(tagName) || !!unarySlash\n\n    const l = match.attrs.length\n    const attrs = new Array(l)\n    for (let i = 0; i < l; i++) {\n      const args = match.attrs[i]\n      // hackish work around FF bug https://bugzilla.mozilla.org/show_bug.cgi?id=369778\n      if (IS_REGEX_CAPTURING_BROKEN && args[0].indexOf('\"\"') === -1) {\n        if (args[3] === '') { delete args[3] }\n        if (args[4] === '') { delete args[4] }\n        if (args[5] === '') { delete args[5] }\n      }\n      const value = args[3] || args[4] || args[5] || ''\n      const shouldDecodeNewlines = tagName === 'a' && args[1] === 'href'\n        ? options.shouldDecodeNewlinesForHref\n        : options.shouldDecodeNewlines\n      attrs[i] = {\n        name: args[1],\n        value: decodeAttr(value, shouldDecodeNewlines)\n      }\n    }\n\n    if (!unary) {\n      stack.push({ tag: tagName, lowerCasedTag: tagName.toLowerCase(), attrs: attrs })\n      lastTag = tagName\n    }\n\n    if (options.start) {\n      options.start(tagName, attrs, unary, match.start, match.end)\n    }\n  }\n\n  function parseEndTag (tagName, start, end) {\n    let pos, lowerCasedTagName\n    if (start == null) start = index\n    if (end == null) end = index\n\n    if (tagName) {\n      lowerCasedTagName = tagName.toLowerCase()\n    }\n\n    // Find the closest opened tag of the same type\n    if (tagName) {\n      for (pos = stack.length - 1; pos >= 0; pos--) {\n        if (stack[pos].lowerCasedTag === lowerCasedTagName) {\n          break\n        }\n      }\n    } else {\n      // If no tag name is provided, clean shop\n      pos = 0\n    }\n\n    if (pos >= 0) {\n      // Close all the open elements, up the stack\n      for (let i = stack.length - 1; i >= pos; i--) {\n        if (process.env.NODE_ENV !== 'production' &&\n          (i > pos || !tagName) &&\n          options.warn\n        ) {\n          options.warn(\n            `tag <${stack[i].tag}> has no matching end tag.`\n          )\n        }\n        if (options.end) {\n          options.end(stack[i].tag, start, end)\n        }\n      }\n\n      // Remove the open elements from the stack\n      stack.length = pos\n      lastTag = pos && stack[pos - 1].tag\n    } else if (lowerCasedTagName === 'br') {\n      if (options.start) {\n        options.start(tagName, [], true, start, end)\n      }\n    } else if (lowerCasedTagName === 'p') {\n      if (options.start) {\n        options.start(tagName, [], false, start, end)\n      }\n      if (options.end) {\n        options.end(tagName, start, end)\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "vue/src/compiler/parser/index.js",
    "content": "/* @flow */\n\nimport he from 'he'\nimport { parseHTML } from './html-parser'\nimport { parseText } from './text-parser'\nimport { parseFilters } from './filter-parser'\nimport { genAssignmentCode } from '../directives/model'\nimport { extend, cached, no, camelize } from 'shared/util'\nimport { isIE, isEdge, isServerRendering } from 'core/util/env'\n\nimport {\n  addProp,\n  addAttr,\n  baseWarn,\n  addHandler,\n  addDirective,\n  getBindingAttr,\n  getAndRemoveAttr,\n  pluckModuleFunction\n} from '../helpers'\n\nexport const onRE = /^@|^v-on:/\nexport const dirRE = /^v-|^@|^:/\nexport const forAliasRE = /([^]*?)\\s+(?:in|of)\\s+([^]*)/\nexport const forIteratorRE = /,([^,\\}\\]]*)(?:,([^,\\}\\]]*))?$/\nconst stripParensRE = /^\\(|\\)$/g\n\nconst argRE = /:(.*)$/\nexport const bindRE = /^:|^v-bind:/\nconst modifierRE = /\\.[^.]+/g\n\nconst decodeHTMLCached = cached(he.decode)\n\n// configurable state\nexport let warn: any\nlet delimiters\nlet transforms\nlet preTransforms\nlet postTransforms\nlet platformIsPreTag\nlet platformMustUseProp\nlet platformGetTagNamespace\n\ntype Attr = { name: string; value: string };\n\nexport function createASTElement (\n  tag: string,\n  attrs: Array<Attr>,\n  parent: ASTElement | void\n): ASTElement {\n  return {\n    type: 1,\n    tag,\n    attrsList: attrs,\n    attrsMap: makeAttrsMap(attrs),\n    parent,\n    children: []\n  }\n}\n\n/**\n * Convert HTML string to AST.\n */\nexport function parse (\n  template: string,\n  options: CompilerOptions\n): ASTElement | void {\n  warn = options.warn || baseWarn\n\n  platformIsPreTag = options.isPreTag || no\n  platformMustUseProp = options.mustUseProp || no\n  platformGetTagNamespace = options.getTagNamespace || no\n\n  transforms = pluckModuleFunction(options.modules, 'transformNode')\n  preTransforms = pluckModuleFunction(options.modules, 'preTransformNode')\n  postTransforms = pluckModuleFunction(options.modules, 'postTransformNode')\n\n  delimiters = options.delimiters\n\n  const stack = []\n  const preserveWhitespace = options.preserveWhitespace !== false\n  let root\n  let currentParent\n  let inVPre = false\n  let inPre = false\n  let warned = false\n\n  function warnOnce (msg) {\n    if (!warned) {\n      warned = true\n      warn(msg)\n    }\n  }\n\n  function closeElement (element) {\n    // check pre state\n    if (element.pre) {\n      inVPre = false\n    }\n    if (platformIsPreTag(element.tag)) {\n      inPre = false\n    }\n    // apply post-transforms\n    for (let i = 0; i < postTransforms.length; i++) {\n      postTransforms[i](element, options)\n    }\n  }\n\n  parseHTML(template, {\n    warn,\n    expectHTML: options.expectHTML,\n    isUnaryTag: options.isUnaryTag,\n    canBeLeftOpenTag: options.canBeLeftOpenTag,\n    shouldDecodeNewlines: options.shouldDecodeNewlines,\n    shouldDecodeNewlinesForHref: options.shouldDecodeNewlinesForHref,\n    shouldKeepComment: options.comments,\n    start (tag, attrs, unary) {\n      // check namespace.\n      // inherit parent ns if there is one\n      const ns = (currentParent && currentParent.ns) || platformGetTagNamespace(tag)\n\n      // handle IE svg bug\n      /* istanbul ignore if */\n      if (isIE && ns === 'svg') {\n        attrs = guardIESVGBug(attrs)\n      }\n\n      let element: ASTElement = createASTElement(tag, attrs, currentParent)\n      if (ns) {\n        element.ns = ns\n      }\n\n      if (isForbiddenTag(element) && !isServerRendering()) {\n        element.forbidden = true\n        process.env.NODE_ENV !== 'production' && warn(\n          'Templates should only be responsible for mapping the state to the ' +\n          'UI. Avoid placing tags with side-effects in your templates, such as ' +\n          `<${tag}>` + ', as they will not be parsed.'\n        )\n      }\n\n      // apply pre-transforms\n      for (let i = 0; i < preTransforms.length; i++) {\n        element = preTransforms[i](element, options) || element\n      }\n\n      if (!inVPre) {\n        processPre(element)\n        if (element.pre) {\n          inVPre = true\n        }\n      }\n      if (platformIsPreTag(element.tag)) {\n        inPre = true\n      }\n      if (inVPre) {\n        processRawAttrs(element)\n      } else if (!element.processed) {\n        // structural directives\n        processFor(element)\n        processIf(element)\n        processOnce(element)\n        // element-scope stuff\n        processElement(element, options)\n      }\n\n      function checkRootConstraints (el) {\n        if (process.env.NODE_ENV !== 'production') {\n          if (el.tag === 'slot' || el.tag === 'template') {\n            warnOnce(\n              `Cannot use <${el.tag}> as component root element because it may ` +\n              'contain multiple nodes.'\n            )\n          }\n          if (el.attrsMap.hasOwnProperty('v-for')) {\n            warnOnce(\n              'Cannot use v-for on stateful component root element because ' +\n              'it renders multiple elements.'\n            )\n          }\n        }\n      }\n\n      // tree management\n      if (!root) {\n        root = element\n        checkRootConstraints(root)\n      } else if (!stack.length) {\n        // allow root elements with v-if, v-else-if and v-else\n        if (root.if && (element.elseif || element.else)) {\n          checkRootConstraints(element)\n          addIfCondition(root, {\n            exp: element.elseif,\n            block: element\n          })\n        } else if (process.env.NODE_ENV !== 'production') {\n          warnOnce(\n            `Component template should contain exactly one root element. ` +\n            `If you are using v-if on multiple elements, ` +\n            `use v-else-if to chain them instead.`\n          )\n        }\n      }\n      if (currentParent && !element.forbidden) {\n        if (element.elseif || element.else) {\n          processIfConditions(element, currentParent)\n        } else if (element.slotScope) { // scoped slot\n          currentParent.plain = false\n          const name = element.slotTarget || '\"default\"'\n          ;(currentParent.scopedSlots || (currentParent.scopedSlots = {}))[name] = element\n        } else {\n          currentParent.children.push(element)\n          element.parent = currentParent\n        }\n      }\n      if (!unary) {\n        currentParent = element\n        stack.push(element)\n      } else {\n        closeElement(element)\n      }\n    },\n\n    end () {\n      // remove trailing whitespace\n      const element = stack[stack.length - 1]\n      const lastNode = element.children[element.children.length - 1]\n      if (lastNode && lastNode.type === 3 && lastNode.text === ' ' && !inPre) {\n        element.children.pop()\n      }\n      // pop stack\n      stack.length -= 1\n      currentParent = stack[stack.length - 1]\n      closeElement(element)\n    },\n\n    chars (text: string) {\n      if (!currentParent) {\n        if (process.env.NODE_ENV !== 'production') {\n          if (text === template) {\n            warnOnce(\n              'Component template requires a root element, rather than just text.'\n            )\n          } else if ((text = text.trim())) {\n            warnOnce(\n              `text \"${text}\" outside root element will be ignored.`\n            )\n          }\n        }\n        return\n      }\n      // IE textarea placeholder bug\n      /* istanbul ignore if */\n      if (isIE &&\n        currentParent.tag === 'textarea' &&\n        currentParent.attrsMap.placeholder === text\n      ) {\n        return\n      }\n      const children = currentParent.children\n      text = inPre || text.trim()\n        ? isTextTag(currentParent) ? text : decodeHTMLCached(text)\n        // only preserve whitespace if its not right after a starting tag\n        : preserveWhitespace && children.length ? ' ' : ''\n      if (text) {\n        let res\n        if (!inVPre && text !== ' ' && (res = parseText(text, delimiters))) {\n          children.push({\n            type: 2,\n            expression: res.expression,\n            tokens: res.tokens,\n            text\n          })\n        } else if (text !== ' ' || !children.length || children[children.length - 1].text !== ' ') {\n          children.push({\n            type: 3,\n            text\n          })\n        }\n      }\n    },\n    comment (text: string) {\n      currentParent.children.push({\n        type: 3,\n        text,\n        isComment: true\n      })\n    }\n  })\n  return root\n}\n\nfunction processPre (el) {\n  if (getAndRemoveAttr(el, 'v-pre') != null) {\n    el.pre = true\n  }\n}\n\nfunction processRawAttrs (el) {\n  const l = el.attrsList.length\n  if (l) {\n    const attrs = el.attrs = new Array(l)\n    for (let i = 0; i < l; i++) {\n      attrs[i] = {\n        name: el.attrsList[i].name,\n        value: JSON.stringify(el.attrsList[i].value)\n      }\n    }\n  } else if (!el.pre) {\n    // non root node in pre blocks with no attributes\n    el.plain = true\n  }\n}\n\nexport function processElement (element: ASTElement, options: CompilerOptions) {\n  processKey(element)\n\n  // determine whether this is a plain element after\n  // removing structural attributes\n  element.plain = !element.key && !element.attrsList.length\n\n  processRef(element)\n  processSlot(element)\n  processComponent(element)\n  for (let i = 0; i < transforms.length; i++) {\n    element = transforms[i](element, options) || element\n  }\n  processAttrs(element)\n}\n\nfunction processKey (el) {\n  const exp = getBindingAttr(el, 'key')\n  if (exp) {\n    if (process.env.NODE_ENV !== 'production' && el.tag === 'template') {\n      warn(`<template> cannot be keyed. Place the key on real elements instead.`)\n    }\n    el.key = exp\n  }\n}\n\nfunction processRef (el) {\n  const ref = getBindingAttr(el, 'ref')\n  if (ref) {\n    el.ref = ref\n    el.refInFor = checkInFor(el)\n  }\n}\n\nexport function processFor (el: ASTElement) {\n  let exp\n  if ((exp = getAndRemoveAttr(el, 'v-for'))) {\n    const res = parseFor(exp)\n    if (res) {\n      extend(el, res)\n    } else if (process.env.NODE_ENV !== 'production') {\n      warn(\n        `Invalid v-for expression: ${exp}`\n      )\n    }\n  }\n}\n\ntype ForParseResult = {\n  for: string;\n  alias: string;\n  iterator1?: string;\n  iterator2?: string;\n};\n\nexport function parseFor (exp: string): ?ForParseResult {\n  const inMatch = exp.match(forAliasRE)\n  if (!inMatch) return\n  const res = {}\n  res.for = inMatch[2].trim()\n  const alias = inMatch[1].trim().replace(stripParensRE, '')\n  const iteratorMatch = alias.match(forIteratorRE)\n  if (iteratorMatch) {\n    res.alias = alias.replace(forIteratorRE, '')\n    res.iterator1 = iteratorMatch[1].trim()\n    if (iteratorMatch[2]) {\n      res.iterator2 = iteratorMatch[2].trim()\n    }\n  } else {\n    res.alias = alias\n  }\n  return res\n}\n\nfunction processIf (el) {\n  const exp = getAndRemoveAttr(el, 'v-if')\n  if (exp) {\n    el.if = exp\n    addIfCondition(el, {\n      exp: exp,\n      block: el\n    })\n  } else {\n    if (getAndRemoveAttr(el, 'v-else') != null) {\n      el.else = true\n    }\n    const elseif = getAndRemoveAttr(el, 'v-else-if')\n    if (elseif) {\n      el.elseif = elseif\n    }\n  }\n}\n\nfunction processIfConditions (el, parent) {\n  const prev = findPrevElement(parent.children)\n  if (prev && prev.if) {\n    addIfCondition(prev, {\n      exp: el.elseif,\n      block: el\n    })\n  } else if (process.env.NODE_ENV !== 'production') {\n    warn(\n      `v-${el.elseif ? ('else-if=\"' + el.elseif + '\"') : 'else'} ` +\n      `used on element <${el.tag}> without corresponding v-if.`\n    )\n  }\n}\n\nfunction findPrevElement (children: Array<any>): ASTElement | void {\n  let i = children.length\n  while (i--) {\n    if (children[i].type === 1) {\n      return children[i]\n    } else {\n      if (process.env.NODE_ENV !== 'production' && children[i].text !== ' ') {\n        warn(\n          `text \"${children[i].text.trim()}\" between v-if and v-else(-if) ` +\n          `will be ignored.`\n        )\n      }\n      children.pop()\n    }\n  }\n}\n\nexport function addIfCondition (el: ASTElement, condition: ASTIfCondition) {\n  if (!el.ifConditions) {\n    el.ifConditions = []\n  }\n  el.ifConditions.push(condition)\n}\n\nfunction processOnce (el) {\n  const once = getAndRemoveAttr(el, 'v-once')\n  if (once != null) {\n    el.once = true\n  }\n}\n\nfunction processSlot (el) {\n  if (el.tag === 'slot') {\n    el.slotName = getBindingAttr(el, 'name')\n    if (process.env.NODE_ENV !== 'production' && el.key) {\n      warn(\n        `\\`key\\` does not work on <slot> because slots are abstract outlets ` +\n        `and can possibly expand into multiple elements. ` +\n        `Use the key on a wrapping element instead.`\n      )\n    }\n  } else {\n    let slotScope\n    if (el.tag === 'template') {\n      slotScope = getAndRemoveAttr(el, 'scope')\n      /* istanbul ignore if */\n      if (process.env.NODE_ENV !== 'production' && slotScope) {\n        warn(\n          `the \"scope\" attribute for scoped slots have been deprecated and ` +\n          `replaced by \"slot-scope\" since 2.5. The new \"slot-scope\" attribute ` +\n          `can also be used on plain elements in addition to <template> to ` +\n          `denote scoped slots.`,\n          true\n        )\n      }\n      el.slotScope = slotScope || getAndRemoveAttr(el, 'slot-scope')\n    } else if ((slotScope = getAndRemoveAttr(el, 'slot-scope'))) {\n      /* istanbul ignore if */\n      if (process.env.NODE_ENV !== 'production' && el.attrsMap['v-for']) {\n        warn(\n          `Ambiguous combined usage of slot-scope and v-for on <${el.tag}> ` +\n          `(v-for takes higher priority). Use a wrapper <template> for the ` +\n          `scoped slot to make it clearer.`,\n          true\n        )\n      }\n      el.slotScope = slotScope\n    }\n    const slotTarget = getBindingAttr(el, 'slot')\n    if (slotTarget) {\n      el.slotTarget = slotTarget === '\"\"' ? '\"default\"' : slotTarget\n      // preserve slot as an attribute for native shadow DOM compat\n      // only for non-scoped slots.\n      if (el.tag !== 'template' && !el.slotScope) {\n        addAttr(el, 'slot', slotTarget)\n      }\n    }\n  }\n}\n\nfunction processComponent (el) {\n  let binding\n  if ((binding = getBindingAttr(el, 'is'))) {\n    el.component = binding\n  }\n  if (getAndRemoveAttr(el, 'inline-template') != null) {\n    el.inlineTemplate = true\n  }\n}\n\nfunction processAttrs (el) {\n  const list = el.attrsList\n  let i, l, name, rawName, value, modifiers, isProp\n  for (i = 0, l = list.length; i < l; i++) {\n    name = rawName = list[i].name\n    value = list[i].value\n    if (dirRE.test(name)) {\n      // mark element as dynamic\n      el.hasBindings = true\n      // modifiers\n      modifiers = parseModifiers(name)\n      if (modifiers) {\n        name = name.replace(modifierRE, '')\n      }\n      if (bindRE.test(name)) { // v-bind\n        name = name.replace(bindRE, '')\n        value = parseFilters(value)\n        isProp = false\n        if (modifiers) {\n          if (modifiers.prop) {\n            isProp = true\n            name = camelize(name)\n            if (name === 'innerHtml') name = 'innerHTML'\n          }\n          if (modifiers.camel) {\n            name = camelize(name)\n          }\n          if (modifiers.sync) {\n            addHandler(\n              el,\n              `update:${camelize(name)}`,\n              genAssignmentCode(value, `$event`)\n            )\n          }\n        }\n        if (isProp || (\n          !el.component && platformMustUseProp(el.tag, el.attrsMap.type, name)\n        )) {\n          addProp(el, name, value)\n        } else {\n          addAttr(el, name, value)\n        }\n      } else if (onRE.test(name)) { // v-on\n        name = name.replace(onRE, '')\n        addHandler(el, name, value, modifiers, false, warn)\n      } else { // normal directives\n        name = name.replace(dirRE, '')\n        // parse arg\n        const argMatch = name.match(argRE)\n        const arg = argMatch && argMatch[1]\n        if (arg) {\n          name = name.slice(0, -(arg.length + 1))\n        }\n        addDirective(el, name, rawName, value, arg, modifiers)\n        if (process.env.NODE_ENV !== 'production' && name === 'model') {\n          checkForAliasModel(el, value)\n        }\n      }\n    } else {\n      // literal attribute\n      if (process.env.NODE_ENV !== 'production') {\n        const res = parseText(value, delimiters)\n        if (res) {\n          warn(\n            `${name}=\"${value}\": ` +\n            'Interpolation inside attributes has been removed. ' +\n            'Use v-bind or the colon shorthand instead. For example, ' +\n            'instead of <div id=\"{{ val }}\">, use <div :id=\"val\">.'\n          )\n        }\n      }\n      addAttr(el, name, JSON.stringify(value))\n      // #6887 firefox doesn't update muted state if set via attribute\n      // even immediately after element creation\n      if (!el.component &&\n          name === 'muted' &&\n          platformMustUseProp(el.tag, el.attrsMap.type, name)) {\n        addProp(el, name, 'true')\n      }\n    }\n  }\n}\n\nfunction checkInFor (el: ASTElement): boolean {\n  let parent = el\n  while (parent) {\n    if (parent.for !== undefined) {\n      return true\n    }\n    parent = parent.parent\n  }\n  return false\n}\n\nfunction parseModifiers (name: string): Object | void {\n  const match = name.match(modifierRE)\n  if (match) {\n    const ret = {}\n    match.forEach(m => { ret[m.slice(1)] = true })\n    return ret\n  }\n}\n\nfunction makeAttrsMap (attrs: Array<Object>): Object {\n  const map = {}\n  for (let i = 0, l = attrs.length; i < l; i++) {\n    if (\n      process.env.NODE_ENV !== 'production' &&\n      map[attrs[i].name] && !isIE && !isEdge\n    ) {\n      warn('duplicate attribute: ' + attrs[i].name)\n    }\n    map[attrs[i].name] = attrs[i].value\n  }\n  return map\n}\n\n// for script (e.g. type=\"x/template\") or style, do not decode content\nfunction isTextTag (el): boolean {\n  return el.tag === 'script' || el.tag === 'style'\n}\n\nfunction isForbiddenTag (el): boolean {\n  return (\n    el.tag === 'style' ||\n    (el.tag === 'script' && (\n      !el.attrsMap.type ||\n      el.attrsMap.type === 'text/javascript'\n    ))\n  )\n}\n\nconst ieNSBug = /^xmlns:NS\\d+/\nconst ieNSPrefix = /^NS\\d+:/\n\n/* istanbul ignore next */\nfunction guardIESVGBug (attrs) {\n  const res = []\n  for (let i = 0; i < attrs.length; i++) {\n    const attr = attrs[i]\n    if (!ieNSBug.test(attr.name)) {\n      attr.name = attr.name.replace(ieNSPrefix, '')\n      res.push(attr)\n    }\n  }\n  return res\n}\n\nfunction checkForAliasModel (el, value) {\n  let _el = el\n  while (_el) {\n    if (_el.for && _el.alias === value) {\n      warn(\n        `<${el.tag} v-model=\"${value}\">: ` +\n        `You are binding v-model directly to a v-for iteration alias. ` +\n        `This will not be able to modify the v-for source array because ` +\n        `writing to the alias is like modifying a function local variable. ` +\n        `Consider using an array of objects and use v-model on an object property instead.`\n      )\n    }\n    _el = _el.parent\n  }\n}\n"
  },
  {
    "path": "vue/src/compiler/parser/text-parser.js",
    "content": "/* @flow */\n\nimport { cached } from 'shared/util'\nimport { parseFilters } from './filter-parser'\n\nconst defaultTagRE = /\\{\\{((?:.|\\n)+?)\\}\\}/g\nconst regexEscapeRE = /[-.*+?^${}()|[\\]\\/\\\\]/g\n\nconst buildRegex = cached(delimiters => {\n  const open = delimiters[0].replace(regexEscapeRE, '\\\\$&')\n  const close = delimiters[1].replace(regexEscapeRE, '\\\\$&')\n  return new RegExp(open + '((?:.|\\\\n)+?)' + close, 'g')\n})\n\ntype TextParseResult = {\n  expression: string,\n  tokens: Array<string | { '@binding': string }>\n}\n\nexport function parseText (\n  text: string,\n  delimiters?: [string, string]\n): TextParseResult | void {\n  const tagRE = delimiters ? buildRegex(delimiters) : defaultTagRE\n  if (!tagRE.test(text)) {\n    return\n  }\n  const tokens = []\n  const rawTokens = []\n  let lastIndex = tagRE.lastIndex = 0\n  let match, index, tokenValue\n  while ((match = tagRE.exec(text))) {\n    index = match.index\n    // push text token\n    if (index > lastIndex) {\n      rawTokens.push(tokenValue = text.slice(lastIndex, index))\n      tokens.push(JSON.stringify(tokenValue))\n    }\n    // tag token\n    const exp = parseFilters(match[1].trim())\n    tokens.push(`_s(${exp})`)\n    rawTokens.push({ '@binding': exp })\n    lastIndex = index + match[0].length\n  }\n  if (lastIndex < text.length) {\n    rawTokens.push(tokenValue = text.slice(lastIndex))\n    tokens.push(JSON.stringify(tokenValue))\n  }\n  return {\n    expression: tokens.join('+'),\n    tokens: rawTokens\n  }\n}\n"
  },
  {
    "path": "vue/src/compiler/to-function.js",
    "content": "/* @flow */\n\nimport { noop, extend } from 'shared/util'\nimport { warn as baseWarn, tip } from 'core/util/debug'\n\ntype CompiledFunctionResult = {\n  render: Function;\n  staticRenderFns: Array<Function>;\n};\n\nfunction createFunction (code, errors) {\n  try {\n    return new Function(code)\n  } catch (err) {\n    errors.push({ err, code })\n    return noop\n  }\n}\n\nexport function createCompileToFunctionFn (compile: Function): Function {\n  const cache = Object.create(null)\n\n  return function compileToFunctions (\n    template: string,\n    options?: CompilerOptions,\n    vm?: Component\n  ): CompiledFunctionResult {\n    options = extend({}, options)\n    const warn = options.warn || baseWarn\n    delete options.warn\n\n    /* istanbul ignore if */\n    if (process.env.NODE_ENV !== 'production') {\n      // detect possible CSP restriction\n      try {\n        new Function('return 1')\n      } catch (e) {\n        if (e.toString().match(/unsafe-eval|CSP/)) {\n          warn(\n            'It seems you are using the standalone build of Vue.js in an ' +\n            'environment with Content Security Policy that prohibits unsafe-eval. ' +\n            'The template compiler cannot work in this environment. Consider ' +\n            'relaxing the policy to allow unsafe-eval or pre-compiling your ' +\n            'templates into render functions.'\n          )\n        }\n      }\n    }\n\n    // check cache\n    const key = options.delimiters\n      ? String(options.delimiters) + template\n      : template\n    if (cache[key]) {\n      return cache[key]\n    }\n\n    // compile\n    const compiled = compile(template, options)\n\n    // check compilation errors/tips\n    if (process.env.NODE_ENV !== 'production') {\n      if (compiled.errors && compiled.errors.length) {\n        warn(\n          `Error compiling template:\\n\\n${template}\\n\\n` +\n          compiled.errors.map(e => `- ${e}`).join('\\n') + '\\n',\n          vm\n        )\n      }\n      if (compiled.tips && compiled.tips.length) {\n        compiled.tips.forEach(msg => tip(msg, vm))\n      }\n    }\n\n    // turn code into functions\n    const res = {}\n    const fnGenErrors = []\n    res.render = createFunction(compiled.render, fnGenErrors)\n    res.staticRenderFns = compiled.staticRenderFns.map(code => {\n      return createFunction(code, fnGenErrors)\n    })\n\n    // check function generation errors.\n    // this should only happen if there is a bug in the compiler itself.\n    // mostly for codegen development use\n    /* istanbul ignore if */\n    if (process.env.NODE_ENV !== 'production') {\n      if ((!compiled.errors || !compiled.errors.length) && fnGenErrors.length) {\n        warn(\n          `Failed to generate render function:\\n\\n` +\n          fnGenErrors.map(({ err, code }) => `${err.toString()} in\\n\\n${code}\\n`).join('\\n'),\n          vm\n        )\n      }\n    }\n\n    return (cache[key] = res)\n  }\n}\n"
  },
  {
    "path": "vue/src/core/components/index.js",
    "content": "import KeepAlive from './keep-alive'\n\nexport default {\n  KeepAlive\n}\n"
  },
  {
    "path": "vue/src/core/components/keep-alive.js",
    "content": "/* @flow */\n\nimport { isRegExp, remove } from 'shared/util'\nimport { getFirstComponentChild } from 'core/vdom/helpers/index'\n\ntype VNodeCache = { [key: string]: ?VNode };\n\nfunction getComponentName (opts: ?VNodeComponentOptions): ?string {\n  return opts && (opts.Ctor.options.name || opts.tag)\n}\n\nfunction matches (pattern: string | RegExp | Array<string>, name: string): boolean {\n  if (Array.isArray(pattern)) {\n    return pattern.indexOf(name) > -1\n  } else if (typeof pattern === 'string') {\n    return pattern.split(',').indexOf(name) > -1\n  } else if (isRegExp(pattern)) {\n    return pattern.test(name)\n  }\n  /* istanbul ignore next */\n  return false\n}\n\nfunction pruneCache (keepAliveInstance: any, filter: Function) {\n  const { cache, keys, _vnode } = keepAliveInstance\n  for (const key in cache) {\n    const cachedNode: ?VNode = cache[key]\n    if (cachedNode) {\n      const name: ?string = getComponentName(cachedNode.componentOptions)\n      if (name && !filter(name)) {\n        pruneCacheEntry(cache, key, keys, _vnode)\n      }\n    }\n  }\n}\n\nfunction pruneCacheEntry (\n  cache: VNodeCache,\n  key: string,\n  keys: Array<string>,\n  current?: VNode\n) {\n  const cached = cache[key]\n  if (cached && (!current || cached.tag !== current.tag)) {\n    cached.componentInstance.$destroy()\n  }\n  cache[key] = null\n  remove(keys, key)\n}\n\nconst patternTypes: Array<Function> = [String, RegExp, Array]\n\nexport default {\n  name: 'keep-alive',\n  abstract: true,\n\n  props: {\n    include: patternTypes,\n    exclude: patternTypes,\n    max: [String, Number]\n  },\n\n  created () {\n    this.cache = Object.create(null)\n    this.keys = []\n  },\n\n  destroyed () {\n    for (const key in this.cache) {\n      pruneCacheEntry(this.cache, key, this.keys)\n    }\n  },\n\n  mounted () {\n    this.$watch('include', val => {\n      pruneCache(this, name => matches(val, name))\n    })\n    this.$watch('exclude', val => {\n      pruneCache(this, name => !matches(val, name))\n    })\n  },\n\n  render () {\n    const slot = this.$slots.default\n    const vnode: VNode = getFirstComponentChild(slot)\n    const componentOptions: ?VNodeComponentOptions = vnode && vnode.componentOptions\n    if (componentOptions) {\n      // check pattern\n      const name: ?string = getComponentName(componentOptions)\n      const { include, exclude } = this\n      if (\n        // not included\n        (include && (!name || !matches(include, name))) ||\n        // excluded\n        (exclude && name && matches(exclude, name))\n      ) {\n        return vnode\n      }\n\n      const { cache, keys } = this\n      const key: ?string = vnode.key == null\n        // same constructor may get registered as different local components\n        // so cid alone is not enough (#3269)\n        ? componentOptions.Ctor.cid + (componentOptions.tag ? `::${componentOptions.tag}` : '')\n        : vnode.key\n      if (cache[key]) {\n        vnode.componentInstance = cache[key].componentInstance\n        // make current key freshest\n        remove(keys, key)\n        keys.push(key)\n      } else {\n        cache[key] = vnode\n        keys.push(key)\n        // prune oldest entry\n        if (this.max && keys.length > parseInt(this.max)) {\n          pruneCacheEntry(cache, keys[0], keys, this._vnode)\n        }\n      }\n\n      vnode.data.keepAlive = true\n    }\n    return vnode || (slot && slot[0])\n  }\n}\n"
  },
  {
    "path": "vue/src/core/config.js",
    "content": "/* @flow */\n\nimport {\n  no,\n  noop,\n  identity\n} from 'shared/util'\n\nimport { LIFECYCLE_HOOKS } from 'shared/constants'\n\nexport type Config = {\n  // user\n  optionMergeStrategies: { [key: string]: Function };\n  silent: boolean;\n  productionTip: boolean;\n  performance: boolean;\n  devtools: boolean;\n  errorHandler: ?(err: Error, vm: Component, info: string) => void;\n  warnHandler: ?(msg: string, vm: Component, trace: string) => void;\n  ignoredElements: Array<string | RegExp>;\n  keyCodes: { [key: string]: number | Array<number> };\n\n  // platform\n  isReservedTag: (x?: string) => boolean;\n  isReservedAttr: (x?: string) => boolean;\n  parsePlatformTagName: (x: string) => string;\n  isUnknownElement: (x?: string) => boolean;\n  getTagNamespace: (x?: string) => string | void;\n  mustUseProp: (tag: string, type: ?string, name: string) => boolean;\n\n  // legacy\n  _lifecycleHooks: Array<string>;\n};\n\nexport default ({\n  /**\n   * Option merge strategies (used in core/util/options)\n   */\n  // $flow-disable-line\n  optionMergeStrategies: Object.create(null),\n\n  /**\n   * Whether to suppress warnings.\n   */\n  silent: false,\n\n  /**\n   * Show production mode tip message on boot?\n   */\n  productionTip: process.env.NODE_ENV !== 'production',\n\n  /**\n   * Whether to enable devtools\n   */\n  devtools: process.env.NODE_ENV !== 'production',\n\n  /**\n   * Whether to record perf\n   */\n  performance: false,\n\n  /**\n   * Error handler for watcher errors\n   */\n  errorHandler: null,\n\n  /**\n   * Warn handler for watcher warns\n   */\n  warnHandler: null,\n\n  /**\n   * Ignore certain custom elements\n   */\n  ignoredElements: [],\n\n  /**\n   * Custom user key aliases for v-on\n   */\n  // $flow-disable-line\n  keyCodes: Object.create(null),\n\n  /**\n   * Check if a tag is reserved so that it cannot be registered as a\n   * component. This is platform-dependent and may be overwritten.\n   */\n  isReservedTag: no,\n\n  /**\n   * Check if an attribute is reserved so that it cannot be used as a component\n   * prop. This is platform-dependent and may be overwritten.\n   */\n  isReservedAttr: no,\n\n  /**\n   * Check if a tag is an unknown element.\n   * Platform-dependent.\n   */\n  isUnknownElement: no,\n\n  /**\n   * Get the namespace of an element\n   */\n  getTagNamespace: noop,\n\n  /**\n   * Parse the real tag name for the specific platform.\n   */\n  parsePlatformTagName: identity,\n\n  /**\n   * Check if an attribute must be bound using property, e.g. value\n   * Platform-dependent.\n   */\n  mustUseProp: no,\n\n  /**\n   * Exposed for legacy reasons\n   */\n  _lifecycleHooks: LIFECYCLE_HOOKS\n}: Config)\n"
  },
  {
    "path": "vue/src/core/global-api/assets.js",
    "content": "/* @flow */\n\nimport { ASSET_TYPES } from 'shared/constants'\nimport { isPlainObject, validateComponentName } from '../util/index'\n\nexport function initAssetRegisters (Vue: GlobalAPI) {\n  /**\n   * Create asset registration methods.\n   */\n  ASSET_TYPES.forEach(type => {\n    Vue[type] = function (\n      id: string,\n      definition: Function | Object\n    ): Function | Object | void {\n      if (!definition) {\n        return this.options[type + 's'][id]\n      } else {\n        /* istanbul ignore if */\n        if (process.env.NODE_ENV !== 'production' && type === 'component') {\n          validateComponentName(id)\n        }\n        if (type === 'component' && isPlainObject(definition)) {\n          definition.name = definition.name || id\n          definition = this.options._base.extend(definition)\n        }\n        if (type === 'directive' && typeof definition === 'function') {\n          definition = { bind: definition, update: definition }\n        }\n        this.options[type + 's'][id] = definition\n        return definition\n      }\n    }\n  })\n}\n"
  },
  {
    "path": "vue/src/core/global-api/extend.js",
    "content": "/* @flow */\n\nimport { ASSET_TYPES } from 'shared/constants'\nimport { defineComputed, proxy } from '../instance/state'\nimport { extend, mergeOptions, validateComponentName } from '../util/index'\n\nexport function initExtend (Vue: GlobalAPI) {\n  /**\n   * Each instance constructor, including Vue, has a unique\n   * cid. This enables us to create wrapped \"child\n   * constructors\" for prototypal inheritance and cache them.\n   */\n  Vue.cid = 0\n  let cid = 1\n\n  /**\n   * Class inheritance\n   */\n  Vue.extend = function (extendOptions: Object): Function {\n    extendOptions = extendOptions || {}\n    const Super = this\n    const SuperId = Super.cid\n    const cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {})\n    if (cachedCtors[SuperId]) {\n      return cachedCtors[SuperId]\n    }\n\n    const name = extendOptions.name || Super.options.name\n    if (process.env.NODE_ENV !== 'production' && name) {\n      validateComponentName(name)\n    }\n\n    const Sub = function VueComponent (options) {\n      this._init(options)\n    }\n    Sub.prototype = Object.create(Super.prototype)\n    Sub.prototype.constructor = Sub\n    Sub.cid = cid++\n    Sub.options = mergeOptions(\n      Super.options,\n      extendOptions\n    )\n    Sub['super'] = Super\n\n    // For props and computed properties, we define the proxy getters on\n    // the Vue instances at extension time, on the extended prototype. This\n    // avoids Object.defineProperty calls for each instance created.\n    if (Sub.options.props) {\n      initProps(Sub)\n    }\n    if (Sub.options.computed) {\n      initComputed(Sub)\n    }\n\n    // allow further extension/mixin/plugin usage\n    Sub.extend = Super.extend\n    Sub.mixin = Super.mixin\n    Sub.use = Super.use\n\n    // create asset registers, so extended classes\n    // can have their private assets too.\n    ASSET_TYPES.forEach(function (type) {\n      Sub[type] = Super[type]\n    })\n    // enable recursive self-lookup\n    if (name) {\n      Sub.options.components[name] = Sub\n    }\n\n    // keep a reference to the super options at extension time.\n    // later at instantiation we can check if Super's options have\n    // been updated.\n    Sub.superOptions = Super.options\n    Sub.extendOptions = extendOptions\n    Sub.sealedOptions = extend({}, Sub.options)\n\n    // cache constructor\n    cachedCtors[SuperId] = Sub\n    return Sub\n  }\n}\n\nfunction initProps (Comp) {\n  const props = Comp.options.props\n  for (const key in props) {\n    proxy(Comp.prototype, `_props`, key)\n  }\n}\n\nfunction initComputed (Comp) {\n  const computed = Comp.options.computed\n  for (const key in computed) {\n    defineComputed(Comp.prototype, key, computed[key])\n  }\n}\n"
  },
  {
    "path": "vue/src/core/global-api/index.js",
    "content": "/* @flow */\n\nimport config from '../config'\nimport { initUse } from './use'\nimport { initMixin } from './mixin'\nimport { initExtend } from './extend'\nimport { initAssetRegisters } from './assets'\nimport { set, del } from '../observer/index'\nimport { ASSET_TYPES } from 'shared/constants'\nimport builtInComponents from '../components/index'\n\nimport {\n  warn,\n  extend,\n  nextTick,\n  mergeOptions,\n  defineReactive\n} from '../util/index'\n\nexport function initGlobalAPI (Vue: GlobalAPI) {\n  // config\n  const configDef = {}\n  configDef.get = () => config\n  if (process.env.NODE_ENV !== 'production') {\n    configDef.set = () => {\n      warn(\n        'Do not replace the Vue.config object, set individual fields instead.'\n      )\n    }\n  }\n  Object.defineProperty(Vue, 'config', configDef)\n\n  // exposed util methods.\n  // NOTE: these are not considered part of the public API - avoid relying on\n  // them unless you are aware of the risk.\n  Vue.util = {\n    warn,\n    extend,\n    mergeOptions,\n    defineReactive\n  }\n\n  Vue.set = set\n  Vue.delete = del\n  Vue.nextTick = nextTick\n\n  Vue.options = Object.create(null)\n  ASSET_TYPES.forEach(type => {\n    Vue.options[type + 's'] = Object.create(null)\n  })\n\n  // this is used to identify the \"base\" constructor to extend all plain-object\n  // components with in Weex's multi-instance scenarios.\n  Vue.options._base = Vue\n\n  extend(Vue.options.components, builtInComponents)\n\n  initUse(Vue)\n  initMixin(Vue)\n  initExtend(Vue)\n  initAssetRegisters(Vue)\n}\n"
  },
  {
    "path": "vue/src/core/global-api/mixin.js",
    "content": "/* @flow */\n\nimport { mergeOptions } from '../util/index'\n\nexport function initMixin (Vue: GlobalAPI) {\n  Vue.mixin = function (mixin: Object) {\n    this.options = mergeOptions(this.options, mixin)\n    return this\n  }\n}\n"
  },
  {
    "path": "vue/src/core/global-api/use.js",
    "content": "/* @flow */\n\nimport { toArray } from '../util/index'\n\nexport function initUse (Vue: GlobalAPI) {\n  Vue.use = function (plugin: Function | Object) {\n    const installedPlugins = (this._installedPlugins || (this._installedPlugins = []))\n    if (installedPlugins.indexOf(plugin) > -1) {\n      return this\n    }\n\n    // additional parameters\n    const args = toArray(arguments, 1)\n    args.unshift(this)\n    if (typeof plugin.install === 'function') {\n      plugin.install.apply(plugin, args)\n    } else if (typeof plugin === 'function') {\n      plugin.apply(null, args)\n    }\n    installedPlugins.push(plugin)\n    return this\n  }\n}\n"
  },
  {
    "path": "vue/src/core/index.js",
    "content": "import Vue from './instance/index'\nimport { initGlobalAPI } from './global-api/index'\nimport { isServerRendering } from 'core/util/env'\nimport { FunctionalRenderContext } from 'core/vdom/create-functional-component'\n\ninitGlobalAPI(Vue)\n\nObject.defineProperty(Vue.prototype, '$isServer', {\n  get: isServerRendering\n})\n\nObject.defineProperty(Vue.prototype, '$ssrContext', {\n  get () {\n    /* istanbul ignore next */\n    return this.$vnode && this.$vnode.ssrContext\n  }\n})\n\n// expose FunctionalRenderContext for ssr runtime helper installation\nObject.defineProperty(Vue, 'FunctionalRenderContext', {\n  value: FunctionalRenderContext\n})\n\nVue.version = '__VERSION__'\n\nexport default Vue\n"
  },
  {
    "path": "vue/src/core/instance/events.js",
    "content": "/* @flow */\n\nimport {\n  tip,\n  toArray,\n  hyphenate,\n  handleError,\n  formatComponentName\n} from '../util/index'\nimport { updateListeners } from '../vdom/helpers/index'\n\nexport function initEvents (vm: Component) {\n  vm._events = Object.create(null)\n  vm._hasHookEvent = false\n  // init parent attached events\n  const listeners = vm.$options._parentListeners\n  if (listeners) {\n    updateComponentListeners(vm, listeners)\n  }\n}\n\nlet target: any\n\nfunction add (event, fn, once) {\n  if (once) {\n    target.$once(event, fn)\n  } else {\n    target.$on(event, fn)\n  }\n}\n\nfunction remove (event, fn) {\n  target.$off(event, fn)\n}\n\nexport function updateComponentListeners (\n  vm: Component,\n  listeners: Object,\n  oldListeners: ?Object\n) {\n  target = vm\n  updateListeners(listeners, oldListeners || {}, add, remove, vm)\n  target = undefined\n}\n\nexport function eventsMixin (Vue: Class<Component>) {\n  const hookRE = /^hook:/\n  Vue.prototype.$on = function (event: string | Array<string>, fn: Function): Component {\n    const vm: Component = this\n    if (Array.isArray(event)) {\n      for (let i = 0, l = event.length; i < l; i++) {\n        this.$on(event[i], fn)\n      }\n    } else {\n      (vm._events[event] || (vm._events[event] = [])).push(fn)\n      // optimize hook:event cost by using a boolean flag marked at registration\n      // instead of a hash lookup\n      if (hookRE.test(event)) {\n        vm._hasHookEvent = true\n      }\n    }\n    return vm\n  }\n\n  Vue.prototype.$once = function (event: string, fn: Function): Component {\n    const vm: Component = this\n    function on () {\n      vm.$off(event, on)\n      fn.apply(vm, arguments)\n    }\n    on.fn = fn\n    vm.$on(event, on)\n    return vm\n  }\n\n  Vue.prototype.$off = function (event?: string | Array<string>, fn?: Function): Component {\n    const vm: Component = this\n    // all\n    if (!arguments.length) {\n      vm._events = Object.create(null)\n      return vm\n    }\n    // array of events\n    if (Array.isArray(event)) {\n      for (let i = 0, l = event.length; i < l; i++) {\n        this.$off(event[i], fn)\n      }\n      return vm\n    }\n    // specific event\n    const cbs = vm._events[event]\n    if (!cbs) {\n      return vm\n    }\n    if (!fn) {\n      vm._events[event] = null\n      return vm\n    }\n    if (fn) {\n      // specific handler\n      let cb\n      let i = cbs.length\n      while (i--) {\n        cb = cbs[i]\n        if (cb === fn || cb.fn === fn) {\n          cbs.splice(i, 1)\n          break\n        }\n      }\n    }\n    return vm\n  }\n\n  Vue.prototype.$emit = function (event: string): Component {\n    const vm: Component = this\n    if (process.env.NODE_ENV !== 'production') {\n      const lowerCaseEvent = event.toLowerCase()\n      if (lowerCaseEvent !== event && vm._events[lowerCaseEvent]) {\n        tip(\n          `Event \"${lowerCaseEvent}\" is emitted in component ` +\n          `${formatComponentName(vm)} but the handler is registered for \"${event}\". ` +\n          `Note that HTML attributes are case-insensitive and you cannot use ` +\n          `v-on to listen to camelCase events when using in-DOM templates. ` +\n          `You should probably use \"${hyphenate(event)}\" instead of \"${event}\".`\n        )\n      }\n    }\n    let cbs = vm._events[event]\n    if (cbs) {\n      cbs = cbs.length > 1 ? toArray(cbs) : cbs\n      const args = toArray(arguments, 1)\n      for (let i = 0, l = cbs.length; i < l; i++) {\n        try {\n          cbs[i].apply(vm, args)\n        } catch (e) {\n          handleError(e, vm, `event handler for \"${event}\"`)\n        }\n      }\n    }\n    return vm\n  }\n}\n"
  },
  {
    "path": "vue/src/core/instance/index.js",
    "content": "import { initMixin } from './init'\nimport { stateMixin } from './state'\nimport { renderMixin } from './render'\nimport { eventsMixin } from './events'\nimport { lifecycleMixin } from './lifecycle'\nimport { warn } from '../util/index'\n\nfunction Vue (options) {\n  if (process.env.NODE_ENV !== 'production' &&\n    !(this instanceof Vue)\n  ) {\n    warn('Vue is a constructor and should be called with the `new` keyword')\n  }\n  this._init(options)\n}\n\ninitMixin(Vue)\nstateMixin(Vue)\neventsMixin(Vue)\nlifecycleMixin(Vue)\nrenderMixin(Vue)\n\nexport default Vue\n"
  },
  {
    "path": "vue/src/core/instance/init.js",
    "content": "/* @flow */\n\nimport config from '../config'\nimport { initProxy } from './proxy'\nimport { initState } from './state'\nimport { initRender } from './render'\nimport { initEvents } from './events'\nimport { mark, measure } from '../util/perf'\nimport { initLifecycle, callHook } from './lifecycle'\nimport { initProvide, initInjections } from './inject'\nimport { extend, mergeOptions, formatComponentName } from '../util/index'\n\nlet uid = 0\n\nexport function initMixin (Vue: Class<Component>) {\n  Vue.prototype._init = function (options?: Object) {\n    const vm: Component = this\n    // a uid\n    vm._uid = uid++\n\n    let startTag, endTag\n    /* istanbul ignore if */\n    if (process.env.NODE_ENV !== 'production' && config.performance && mark) {\n      startTag = `vue-perf-start:${vm._uid}`\n      endTag = `vue-perf-end:${vm._uid}`\n      mark(startTag)\n    }\n\n    // a flag to avoid this being observed\n    vm._isVue = true\n    // merge options\n    if (options && options._isComponent) {\n      // optimize internal component instantiation\n      // since dynamic options merging is pretty slow, and none of the\n      // internal component options needs special treatment.\n      initInternalComponent(vm, options)\n    } else {\n      vm.$options = mergeOptions(\n        resolveConstructorOptions(vm.constructor),\n        options || {},\n        vm\n      )\n    }\n    /* istanbul ignore else */\n    if (process.env.NODE_ENV !== 'production') {\n      initProxy(vm)\n    } else {\n      vm._renderProxy = vm\n    }\n    // expose real self\n    vm._self = vm\n    initLifecycle(vm)\n    initEvents(vm)\n    initRender(vm)\n    callHook(vm, 'beforeCreate')\n    initInjections(vm) // resolve injections before data/props\n    initState(vm)\n    initProvide(vm) // resolve provide after data/props\n    callHook(vm, 'created')\n\n    /* istanbul ignore if */\n    if (process.env.NODE_ENV !== 'production' && config.performance && mark) {\n      vm._name = formatComponentName(vm, false)\n      mark(endTag)\n      measure(`vue ${vm._name} init`, startTag, endTag)\n    }\n\n    if (vm.$options.el) {\n      vm.$mount(vm.$options.el)\n    }\n  }\n}\n\nexport function initInternalComponent (vm: Component, options: InternalComponentOptions) {\n  const opts = vm.$options = Object.create(vm.constructor.options)\n  // doing this because it's faster than dynamic enumeration.\n  const parentVnode = options._parentVnode\n  opts.parent = options.parent\n  opts._parentVnode = parentVnode\n\n  const vnodeComponentOptions = parentVnode.componentOptions\n  opts.propsData = vnodeComponentOptions.propsData\n  opts._parentListeners = vnodeComponentOptions.listeners\n  opts._renderChildren = vnodeComponentOptions.children\n  opts._componentTag = vnodeComponentOptions.tag\n\n  if (options.render) {\n    opts.render = options.render\n    opts.staticRenderFns = options.staticRenderFns\n  }\n}\n\nexport function resolveConstructorOptions (Ctor: Class<Component>) {\n  let options = Ctor.options\n  if (Ctor.super) {\n    const superOptions = resolveConstructorOptions(Ctor.super)\n    const cachedSuperOptions = Ctor.superOptions\n    if (superOptions !== cachedSuperOptions) {\n      // super option changed,\n      // need to resolve new options.\n      Ctor.superOptions = superOptions\n      // check if there are any late-modified/attached options (#4976)\n      const modifiedOptions = resolveModifiedOptions(Ctor)\n      // update base extend options\n      if (modifiedOptions) {\n        extend(Ctor.extendOptions, modifiedOptions)\n      }\n      options = Ctor.options = mergeOptions(superOptions, Ctor.extendOptions)\n      if (options.name) {\n        options.components[options.name] = Ctor\n      }\n    }\n  }\n  return options\n}\n\nfunction resolveModifiedOptions (Ctor: Class<Component>): ?Object {\n  let modified\n  const latest = Ctor.options\n  const extended = Ctor.extendOptions\n  const sealed = Ctor.sealedOptions\n  for (const key in latest) {\n    if (latest[key] !== sealed[key]) {\n      if (!modified) modified = {}\n      modified[key] = dedupe(latest[key], extended[key], sealed[key])\n    }\n  }\n  return modified\n}\n\nfunction dedupe (latest, extended, sealed) {\n  // compare latest and sealed to ensure lifecycle hooks won't be duplicated\n  // between merges\n  if (Array.isArray(latest)) {\n    const res = []\n    sealed = Array.isArray(sealed) ? sealed : [sealed]\n    extended = Array.isArray(extended) ? extended : [extended]\n    for (let i = 0; i < latest.length; i++) {\n      // push original options and not sealed options to exclude duplicated options\n      if (extended.indexOf(latest[i]) >= 0 || sealed.indexOf(latest[i]) < 0) {\n        res.push(latest[i])\n      }\n    }\n    return res\n  } else {\n    return latest\n  }\n}\n"
  },
  {
    "path": "vue/src/core/instance/inject.js",
    "content": "/* @flow */\n\nimport { hasOwn } from 'shared/util'\nimport { warn, hasSymbol } from '../util/index'\nimport { defineReactive, toggleObserving } from '../observer/index'\n\nexport function initProvide (vm: Component) {\n  const provide = vm.$options.provide\n  if (provide) {\n    vm._provided = typeof provide === 'function'\n      ? provide.call(vm)\n      : provide\n  }\n}\n\nexport function initInjections (vm: Component) {\n  const result = resolveInject(vm.$options.inject, vm)\n  if (result) {\n    toggleObserving(false)\n    Object.keys(result).forEach(key => {\n      /* istanbul ignore else */\n      if (process.env.NODE_ENV !== 'production') {\n        defineReactive(vm, key, result[key], () => {\n          warn(\n            `Avoid mutating an injected value directly since the changes will be ` +\n            `overwritten whenever the provided component re-renders. ` +\n            `injection being mutated: \"${key}\"`,\n            vm\n          )\n        })\n      } else {\n        defineReactive(vm, key, result[key])\n      }\n    })\n    toggleObserving(true)\n  }\n}\n\nexport function resolveInject (inject: any, vm: Component): ?Object {\n  if (inject) {\n    // inject is :any because flow is not smart enough to figure out cached\n    const result = Object.create(null)\n    const keys = hasSymbol\n      ? Reflect.ownKeys(inject).filter(key => {\n        /* istanbul ignore next */\n        return Object.getOwnPropertyDescriptor(inject, key).enumerable\n      })\n      : Object.keys(inject)\n\n    for (let i = 0; i < keys.length; i++) {\n      const key = keys[i]\n      const provideKey = inject[key].from\n      let source = vm\n      while (source) {\n        if (source._provided && hasOwn(source._provided, provideKey)) {\n          result[key] = source._provided[provideKey]\n          break\n        }\n        source = source.$parent\n      }\n      if (!source) {\n        if ('default' in inject[key]) {\n          const provideDefault = inject[key].default\n          result[key] = typeof provideDefault === 'function'\n            ? provideDefault.call(vm)\n            : provideDefault\n        } else if (process.env.NODE_ENV !== 'production') {\n          warn(`Injection \"${key}\" not found`, vm)\n        }\n      }\n    }\n    return result\n  }\n}\n"
  },
  {
    "path": "vue/src/core/instance/lifecycle.js",
    "content": "/* @flow */\n\nimport config from '../config'\nimport Watcher from '../observer/watcher'\nimport { mark, measure } from '../util/perf'\nimport { createEmptyVNode } from '../vdom/vnode'\nimport { updateComponentListeners } from './events'\nimport { resolveSlots } from './render-helpers/resolve-slots'\nimport { toggleObserving } from '../observer/index'\nimport { pushTarget, popTarget } from '../observer/dep'\n\nimport {\n  warn,\n  noop,\n  remove,\n  handleError,\n  emptyObject,\n  validateProp\n} from '../util/index'\n\nexport let activeInstance: any = null\nexport let isUpdatingChildComponent: boolean = false\n\nexport function initLifecycle (vm: Component) {\n  const options = vm.$options\n\n  // locate first non-abstract parent\n  let parent = options.parent\n  if (parent && !options.abstract) {\n    while (parent.$options.abstract && parent.$parent) {\n      parent = parent.$parent\n    }\n    parent.$children.push(vm)\n  }\n\n  vm.$parent = parent\n  vm.$root = parent ? parent.$root : vm\n\n  vm.$children = []\n  vm.$refs = {}\n\n  vm._watcher = null\n  vm._inactive = null\n  vm._directInactive = false\n  vm._isMounted = false\n  vm._isDestroyed = false\n  vm._isBeingDestroyed = false\n}\n\nexport function lifecycleMixin (Vue: Class<Component>) {\n  Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {\n    const vm: Component = this\n    const prevEl = vm.$el\n    const prevVnode = vm._vnode\n    const prevActiveInstance = activeInstance\n    activeInstance = vm\n    vm._vnode = vnode\n    // Vue.prototype.__patch__ is injected in entry points\n    // based on the rendering backend used.\n    if (!prevVnode) {\n      // initial render\n      vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)\n    } else {\n      // updates\n      vm.$el = vm.__patch__(prevVnode, vnode)\n    }\n    activeInstance = prevActiveInstance\n    // update __vue__ reference\n    if (prevEl) {\n      prevEl.__vue__ = null\n    }\n    if (vm.$el) {\n      vm.$el.__vue__ = vm\n    }\n    // if parent is an HOC, update its $el as well\n    if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) {\n      vm.$parent.$el = vm.$el\n    }\n    // updated hook is called by the scheduler to ensure that children are\n    // updated in a parent's updated hook.\n  }\n\n  Vue.prototype.$forceUpdate = function () {\n    const vm: Component = this\n    if (vm._watcher) {\n      vm._watcher.update()\n    }\n  }\n\n  Vue.prototype.$destroy = function () {\n    const vm: Component = this\n    if (vm._isBeingDestroyed) {\n      return\n    }\n    callHook(vm, 'beforeDestroy')\n    vm._isBeingDestroyed = true\n    // remove self from parent\n    const parent = vm.$parent\n    if (parent && !parent._isBeingDestroyed && !vm.$options.abstract) {\n      remove(parent.$children, vm)\n    }\n    // teardown watchers\n    if (vm._watcher) {\n      vm._watcher.teardown()\n    }\n    let i = vm._watchers.length\n    while (i--) {\n      vm._watchers[i].teardown()\n    }\n    // remove reference from data ob\n    // frozen object may not have observer.\n    if (vm._data.__ob__) {\n      vm._data.__ob__.vmCount--\n    }\n    // call the last hook...\n    vm._isDestroyed = true\n    // invoke destroy hooks on current rendered tree\n    vm.__patch__(vm._vnode, null)\n    // fire destroyed hook\n    callHook(vm, 'destroyed')\n    // turn off all instance listeners.\n    vm.$off()\n    // remove __vue__ reference\n    if (vm.$el) {\n      vm.$el.__vue__ = null\n    }\n    // release circular reference (#6759)\n    if (vm.$vnode) {\n      vm.$vnode.parent = null\n    }\n  }\n}\n\nexport function mountComponent (\n  vm: Component,\n  el: ?Element,\n  hydrating?: boolean\n): Component {\n  vm.$el = el\n  if (!vm.$options.render) {\n    vm.$options.render = createEmptyVNode\n    if (process.env.NODE_ENV !== 'production') {\n      /* istanbul ignore if */\n      if ((vm.$options.template && vm.$options.template.charAt(0) !== '#') ||\n        vm.$options.el || el) {\n        warn(\n          'You are using the runtime-only build of Vue where the template ' +\n          'compiler is not available. Either pre-compile the templates into ' +\n          'render functions, or use the compiler-included build.',\n          vm\n        )\n      } else {\n        warn(\n          'Failed to mount component: template or render function not defined.',\n          vm\n        )\n      }\n    }\n  }\n  callHook(vm, 'beforeMount')\n\n  let updateComponent\n  /* istanbul ignore if */\n  if (process.env.NODE_ENV !== 'production' && config.performance && mark) {\n    updateComponent = () => {\n      const name = vm._name\n      const id = vm._uid\n      const startTag = `vue-perf-start:${id}`\n      const endTag = `vue-perf-end:${id}`\n\n      mark(startTag)\n      const vnode = vm._render()\n      mark(endTag)\n      measure(`vue ${name} render`, startTag, endTag)\n\n      mark(startTag)\n      vm._update(vnode, hydrating)\n      mark(endTag)\n      measure(`vue ${name} patch`, startTag, endTag)\n    }\n  } else {\n    updateComponent = () => {\n      vm._update(vm._render(), hydrating)\n    }\n  }\n\n  // we set this to vm._watcher inside the watcher's constructor\n  // since the watcher's initial patch may call $forceUpdate (e.g. inside child\n  // component's mounted hook), which relies on vm._watcher being already defined\n  new Watcher(vm, updateComponent, noop, {\n    before () {\n      if (vm._isMounted) {\n        callHook(vm, 'beforeUpdate')\n      }\n    }\n  }, true /* isRenderWatcher */)\n  hydrating = false\n\n  // manually mounted instance, call mounted on self\n  // mounted is called for render-created child components in its inserted hook\n  if (vm.$vnode == null) {\n    vm._isMounted = true\n    callHook(vm, 'mounted')\n  }\n  return vm\n}\n\nexport function updateChildComponent (\n  vm: Component,\n  propsData: ?Object,\n  listeners: ?Object,\n  parentVnode: MountedComponentVNode,\n  renderChildren: ?Array<VNode>\n) {\n  if (process.env.NODE_ENV !== 'production') {\n    isUpdatingChildComponent = true\n  }\n\n  // determine whether component has slot children\n  // we need to do this before overwriting $options._renderChildren\n  const hasChildren = !!(\n    renderChildren ||               // has new static slots\n    vm.$options._renderChildren ||  // has old static slots\n    parentVnode.data.scopedSlots || // has new scoped slots\n    vm.$scopedSlots !== emptyObject // has old scoped slots\n  )\n\n  vm.$options._parentVnode = parentVnode\n  vm.$vnode = parentVnode // update vm's placeholder node without re-render\n\n  if (vm._vnode) { // update child tree's parent\n    vm._vnode.parent = parentVnode\n  }\n  vm.$options._renderChildren = renderChildren\n\n  // update $attrs and $listeners hash\n  // these are also reactive so they may trigger child update if the child\n  // used them during render\n  vm.$attrs = parentVnode.data.attrs || emptyObject\n  vm.$listeners = listeners || emptyObject\n\n  // update props\n  if (propsData && vm.$options.props) {\n    toggleObserving(false)\n    const props = vm._props\n    const propKeys = vm.$options._propKeys || []\n    for (let i = 0; i < propKeys.length; i++) {\n      const key = propKeys[i]\n      const propOptions: any = vm.$options.props // wtf flow?\n      props[key] = validateProp(key, propOptions, propsData, vm)\n    }\n    toggleObserving(true)\n    // keep a copy of raw propsData\n    vm.$options.propsData = propsData\n  }\n\n  // update listeners\n  listeners = listeners || emptyObject\n  const oldListeners = vm.$options._parentListeners\n  vm.$options._parentListeners = listeners\n  updateComponentListeners(vm, listeners, oldListeners)\n\n  // resolve slots + force update if has children\n  if (hasChildren) {\n    vm.$slots = resolveSlots(renderChildren, parentVnode.context)\n    vm.$forceUpdate()\n  }\n\n  if (process.env.NODE_ENV !== 'production') {\n    isUpdatingChildComponent = false\n  }\n}\n\nfunction isInInactiveTree (vm) {\n  while (vm && (vm = vm.$parent)) {\n    if (vm._inactive) return true\n  }\n  return false\n}\n\nexport function activateChildComponent (vm: Component, direct?: boolean) {\n  if (direct) {\n    vm._directInactive = false\n    if (isInInactiveTree(vm)) {\n      return\n    }\n  } else if (vm._directInactive) {\n    return\n  }\n  if (vm._inactive || vm._inactive === null) {\n    vm._inactive = false\n    for (let i = 0; i < vm.$children.length; i++) {\n      activateChildComponent(vm.$children[i])\n    }\n    callHook(vm, 'activated')\n  }\n}\n\nexport function deactivateChildComponent (vm: Component, direct?: boolean) {\n  if (direct) {\n    vm._directInactive = true\n    if (isInInactiveTree(vm)) {\n      return\n    }\n  }\n  if (!vm._inactive) {\n    vm._inactive = true\n    for (let i = 0; i < vm.$children.length; i++) {\n      deactivateChildComponent(vm.$children[i])\n    }\n    callHook(vm, 'deactivated')\n  }\n}\n\nexport function callHook (vm: Component, hook: string) {\n  // #7573 disable dep collection when invoking lifecycle hooks\n  pushTarget()\n  const handlers = vm.$options[hook]\n  if (handlers) {\n    for (let i = 0, j = handlers.length; i < j; i++) {\n      try {\n        handlers[i].call(vm)\n      } catch (e) {\n        handleError(e, vm, `${hook} hook`)\n      }\n    }\n  }\n  if (vm._hasHookEvent) {\n    vm.$emit('hook:' + hook)\n  }\n  popTarget()\n}\n"
  },
  {
    "path": "vue/src/core/instance/proxy.js",
    "content": "/* not type checking this file because flow doesn't play well with Proxy */\n\nimport config from 'core/config'\nimport { warn, makeMap, isNative } from '../util/index'\n\nlet initProxy\n\nif (process.env.NODE_ENV !== 'production') {\n  const allowedGlobals = makeMap(\n    'Infinity,undefined,NaN,isFinite,isNaN,' +\n    'parseFloat,parseInt,decodeURI,decodeURIComponent,encodeURI,encodeURIComponent,' +\n    'Math,Number,Date,Array,Object,Boolean,String,RegExp,Map,Set,JSON,Intl,' +\n    'require' // for Webpack/Browserify\n  )\n\n  const warnNonPresent = (target, key) => {\n    warn(\n      `Property or method \"${key}\" is not defined on the instance but ` +\n      'referenced during render. Make sure that this property is reactive, ' +\n      'either in the data option, or for class-based components, by ' +\n      'initializing the property. ' +\n      'See: https://vuejs.org/v2/guide/reactivity.html#Declaring-Reactive-Properties.',\n      target\n    )\n  }\n\n  const hasProxy =\n    typeof Proxy !== 'undefined' && isNative(Proxy)\n\n  if (hasProxy) {\n    const isBuiltInModifier = makeMap('stop,prevent,self,ctrl,shift,alt,meta,exact')\n    config.keyCodes = new Proxy(config.keyCodes, {\n      set (target, key, value) {\n        if (isBuiltInModifier(key)) {\n          warn(`Avoid overwriting built-in modifier in config.keyCodes: .${key}`)\n          return false\n        } else {\n          target[key] = value\n          return true\n        }\n      }\n    })\n  }\n\n  const hasHandler = {\n    has (target, key) {\n      const has = key in target\n      const isAllowed = allowedGlobals(key) || (typeof key === 'string' && key.charAt(0) === '_')\n      if (!has && !isAllowed) {\n        warnNonPresent(target, key)\n      }\n      return has || !isAllowed\n    }\n  }\n\n  const getHandler = {\n    get (target, key) {\n      if (typeof key === 'string' && !(key in target)) {\n        warnNonPresent(target, key)\n      }\n      return target[key]\n    }\n  }\n\n  initProxy = function initProxy (vm) {\n    if (hasProxy) {\n      // determine which proxy handler to use\n      const options = vm.$options\n      const handlers = options.render && options.render._withStripped\n        ? getHandler\n        : hasHandler\n      vm._renderProxy = new Proxy(vm, handlers)\n    } else {\n      vm._renderProxy = vm\n    }\n  }\n}\n\nexport { initProxy }\n"
  },
  {
    "path": "vue/src/core/instance/render-helpers/bind-object-listeners.js",
    "content": "/* @flow */\n\nimport { warn, extend, isPlainObject } from 'core/util/index'\n\nexport function bindObjectListeners (data: any, value: any): VNodeData {\n  if (value) {\n    if (!isPlainObject(value)) {\n      process.env.NODE_ENV !== 'production' && warn(\n        'v-on without argument expects an Object value',\n        this\n      )\n    } else {\n      const on = data.on = data.on ? extend({}, data.on) : {}\n      for (const key in value) {\n        const existing = on[key]\n        const ours = value[key]\n        on[key] = existing ? [].concat(existing, ours) : ours\n      }\n    }\n  }\n  return data\n}\n"
  },
  {
    "path": "vue/src/core/instance/render-helpers/bind-object-props.js",
    "content": "/* @flow */\n\nimport config from 'core/config'\n\nimport {\n  warn,\n  isObject,\n  toObject,\n  isReservedAttribute\n} from 'core/util/index'\n\n/**\n * Runtime helper for merging v-bind=\"object\" into a VNode's data.\n */\nexport function bindObjectProps (\n  data: any,\n  tag: string,\n  value: any,\n  asProp: boolean,\n  isSync?: boolean\n): VNodeData {\n  if (value) {\n    if (!isObject(value)) {\n      process.env.NODE_ENV !== 'production' && warn(\n        'v-bind without argument expects an Object or Array value',\n        this\n      )\n    } else {\n      if (Array.isArray(value)) {\n        value = toObject(value)\n      }\n      let hash\n      for (const key in value) {\n        if (\n          key === 'class' ||\n          key === 'style' ||\n          isReservedAttribute(key)\n        ) {\n          hash = data\n        } else {\n          const type = data.attrs && data.attrs.type\n          hash = asProp || config.mustUseProp(tag, type, key)\n            ? data.domProps || (data.domProps = {})\n            : data.attrs || (data.attrs = {})\n        }\n        if (!(key in hash)) {\n          hash[key] = value[key]\n\n          if (isSync) {\n            const on = data.on || (data.on = {})\n            on[`update:${key}`] = function ($event) {\n              value[key] = $event\n            }\n          }\n        }\n      }\n    }\n  }\n  return data\n}\n"
  },
  {
    "path": "vue/src/core/instance/render-helpers/check-keycodes.js",
    "content": "/* @flow */\n\nimport config from 'core/config'\nimport { hyphenate } from 'shared/util'\n\nfunction isKeyNotMatch<T> (expect: T | Array<T>, actual: T): boolean {\n  if (Array.isArray(expect)) {\n    return expect.indexOf(actual) === -1\n  } else {\n    return expect !== actual\n  }\n}\n\n/**\n * Runtime helper for checking keyCodes from config.\n * exposed as Vue.prototype._k\n * passing in eventKeyName as last argument separately for backwards compat\n */\nexport function checkKeyCodes (\n  eventKeyCode: number,\n  key: string,\n  builtInKeyCode?: number | Array<number>,\n  eventKeyName?: string,\n  builtInKeyName?: string | Array<string>\n): ?boolean {\n  const mappedKeyCode = config.keyCodes[key] || builtInKeyCode\n  if (builtInKeyName && eventKeyName && !config.keyCodes[key]) {\n    return isKeyNotMatch(builtInKeyName, eventKeyName)\n  } else if (mappedKeyCode) {\n    return isKeyNotMatch(mappedKeyCode, eventKeyCode)\n  } else if (eventKeyName) {\n    return hyphenate(eventKeyName) !== key\n  }\n}\n"
  },
  {
    "path": "vue/src/core/instance/render-helpers/index.js",
    "content": "/* @flow */\n\nimport { toNumber, toString, looseEqual, looseIndexOf } from 'shared/util'\nimport { createTextVNode, createEmptyVNode } from 'core/vdom/vnode'\nimport { renderList } from './render-list'\nimport { renderSlot } from './render-slot'\nimport { resolveFilter } from './resolve-filter'\nimport { checkKeyCodes } from './check-keycodes'\nimport { bindObjectProps } from './bind-object-props'\nimport { renderStatic, markOnce } from './render-static'\nimport { bindObjectListeners } from './bind-object-listeners'\nimport { resolveScopedSlots } from './resolve-slots'\n\nexport function installRenderHelpers (target: any) {\n  target._o = markOnce\n  target._n = toNumber\n  target._s = toString\n  target._l = renderList\n  target._t = renderSlot\n  target._q = looseEqual\n  target._i = looseIndexOf\n  target._m = renderStatic\n  target._f = resolveFilter\n  target._k = checkKeyCodes\n  target._b = bindObjectProps\n  target._v = createTextVNode\n  target._e = createEmptyVNode\n  target._u = resolveScopedSlots\n  target._g = bindObjectListeners\n}\n"
  },
  {
    "path": "vue/src/core/instance/render-helpers/render-list.js",
    "content": "/* @flow */\n\nimport { isObject, isDef } from 'core/util/index'\n\n/**\n * Runtime helper for rendering v-for lists.\n */\nexport function renderList (\n  val: any,\n  render: (\n    val: any,\n    keyOrIndex: string | number,\n    index?: number\n  ) => VNode\n): ?Array<VNode> {\n  let ret: ?Array<VNode>, i, l, keys, key\n  if (Array.isArray(val) || typeof val === 'string') {\n    ret = new Array(val.length)\n    for (i = 0, l = val.length; i < l; i++) {\n      ret[i] = render(val[i], i)\n    }\n  } else if (typeof val === 'number') {\n    ret = new Array(val)\n    for (i = 0; i < val; i++) {\n      ret[i] = render(i + 1, i)\n    }\n  } else if (isObject(val)) {\n    keys = Object.keys(val)\n    ret = new Array(keys.length)\n    for (i = 0, l = keys.length; i < l; i++) {\n      key = keys[i]\n      ret[i] = render(val[key], key, i)\n    }\n  }\n  if (isDef(ret)) {\n    (ret: any)._isVList = true\n  }\n  return ret\n}\n"
  },
  {
    "path": "vue/src/core/instance/render-helpers/render-slot.js",
    "content": "/* @flow */\n\nimport { extend, warn, isObject } from 'core/util/index'\n\n/**\n * Runtime helper for rendering <slot>\n */\nexport function renderSlot (\n  name: string,\n  fallback: ?Array<VNode>,\n  props: ?Object,\n  bindObject: ?Object\n): ?Array<VNode> {\n  const scopedSlotFn = this.$scopedSlots[name]\n  let nodes\n  if (scopedSlotFn) { // scoped slot\n    props = props || {}\n    if (bindObject) {\n      if (process.env.NODE_ENV !== 'production' && !isObject(bindObject)) {\n        warn(\n          'slot v-bind without argument expects an Object',\n          this\n        )\n      }\n      props = extend(extend({}, bindObject), props)\n    }\n    nodes = scopedSlotFn(props) || fallback\n  } else {\n    const slotNodes = this.$slots[name]\n    // warn duplicate slot usage\n    if (slotNodes) {\n      if (process.env.NODE_ENV !== 'production' && slotNodes._rendered) {\n        warn(\n          `Duplicate presence of slot \"${name}\" found in the same render tree ` +\n          `- this will likely cause render errors.`,\n          this\n        )\n      }\n      slotNodes._rendered = true\n    }\n    nodes = slotNodes || fallback\n  }\n\n  const target = props && props.slot\n  if (target) {\n    return this.$createElement('template', { slot: target }, nodes)\n  } else {\n    return nodes\n  }\n}\n"
  },
  {
    "path": "vue/src/core/instance/render-helpers/render-static.js",
    "content": "/* @flow */\n\n/**\n * Runtime helper for rendering static trees.\n */\nexport function renderStatic (\n  index: number,\n  isInFor: boolean\n): VNode | Array<VNode> {\n  const cached = this._staticTrees || (this._staticTrees = [])\n  let tree = cached[index]\n  // if has already-rendered static tree and not inside v-for,\n  // we can reuse the same tree.\n  if (tree && !isInFor) {\n    return tree\n  }\n  // otherwise, render a fresh tree.\n  tree = cached[index] = this.$options.staticRenderFns[index].call(\n    this._renderProxy,\n    null,\n    this // for render fns generated for functional component templates\n  )\n  markStatic(tree, `__static__${index}`, false)\n  return tree\n}\n\n/**\n * Runtime helper for v-once.\n * Effectively it means marking the node as static with a unique key.\n */\nexport function markOnce (\n  tree: VNode | Array<VNode>,\n  index: number,\n  key: string\n) {\n  markStatic(tree, `__once__${index}${key ? `_${key}` : ``}`, true)\n  return tree\n}\n\nfunction markStatic (\n  tree: VNode | Array<VNode>,\n  key: string,\n  isOnce: boolean\n) {\n  if (Array.isArray(tree)) {\n    for (let i = 0; i < tree.length; i++) {\n      if (tree[i] && typeof tree[i] !== 'string') {\n        markStaticNode(tree[i], `${key}_${i}`, isOnce)\n      }\n    }\n  } else {\n    markStaticNode(tree, key, isOnce)\n  }\n}\n\nfunction markStaticNode (node, key, isOnce) {\n  node.isStatic = true\n  node.key = key\n  node.isOnce = isOnce\n}\n"
  },
  {
    "path": "vue/src/core/instance/render-helpers/resolve-filter.js",
    "content": "/* @flow */\n\nimport { identity, resolveAsset } from 'core/util/index'\n\n/**\n * Runtime helper for resolving filters\n */\nexport function resolveFilter (id: string): Function {\n  return resolveAsset(this.$options, 'filters', id, true) || identity\n}\n"
  },
  {
    "path": "vue/src/core/instance/render-helpers/resolve-slots.js",
    "content": "/* @flow */\n\nimport type VNode from 'core/vdom/vnode'\n\n/**\n * Runtime helper for resolving raw children VNodes into a slot object.\n */\nexport function resolveSlots (\n  children: ?Array<VNode>,\n  context: ?Component\n): { [key: string]: Array<VNode> } {\n  const slots = {}\n  if (!children) {\n    return slots\n  }\n  for (let i = 0, l = children.length; i < l; i++) {\n    const child = children[i]\n    const data = child.data\n    // remove slot attribute if the node is resolved as a Vue slot node\n    if (data && data.attrs && data.attrs.slot) {\n      delete data.attrs.slot\n    }\n    // named slots should only be respected if the vnode was rendered in the\n    // same context.\n    if ((child.context === context || child.fnContext === context) &&\n      data && data.slot != null\n    ) {\n      const name = data.slot\n      const slot = (slots[name] || (slots[name] = []))\n      if (child.tag === 'template') {\n        slot.push.apply(slot, child.children || [])\n      } else {\n        slot.push(child)\n      }\n    } else {\n      (slots.default || (slots.default = [])).push(child)\n    }\n  }\n  // ignore slots that contains only whitespace\n  for (const name in slots) {\n    if (slots[name].every(isWhitespace)) {\n      delete slots[name]\n    }\n  }\n  return slots\n}\n\nfunction isWhitespace (node: VNode): boolean {\n  return (node.isComment && !node.asyncFactory) || node.text === ' '\n}\n\nexport function resolveScopedSlots (\n  fns: ScopedSlotsData, // see flow/vnode\n  res?: Object\n): { [key: string]: Function } {\n  res = res || {}\n  for (let i = 0; i < fns.length; i++) {\n    if (Array.isArray(fns[i])) {\n      resolveScopedSlots(fns[i], res)\n    } else {\n      res[fns[i].key] = fns[i].fn\n    }\n  }\n  return res\n}\n"
  },
  {
    "path": "vue/src/core/instance/render.js",
    "content": "/* @flow */\n\nimport {\n  warn,\n  nextTick,\n  emptyObject,\n  handleError,\n  defineReactive\n} from '../util/index'\n\nimport { createElement } from '../vdom/create-element'\nimport { installRenderHelpers } from './render-helpers/index'\nimport { resolveSlots } from './render-helpers/resolve-slots'\nimport VNode, { createEmptyVNode } from '../vdom/vnode'\n\nimport { isUpdatingChildComponent } from './lifecycle'\n\nexport function initRender (vm: Component) {\n  vm._vnode = null // the root of the child tree\n  vm._staticTrees = null // v-once cached trees\n  const options = vm.$options\n  const parentVnode = vm.$vnode = options._parentVnode // the placeholder node in parent tree\n  const renderContext = parentVnode && parentVnode.context\n  vm.$slots = resolveSlots(options._renderChildren, renderContext)\n  vm.$scopedSlots = emptyObject\n  // bind the createElement fn to this instance\n  // so that we get proper render context inside it.\n  // args order: tag, data, children, normalizationType, alwaysNormalize\n  // internal version is used by render functions compiled from templates\n  vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)\n  // normalization is always applied for the public version, used in\n  // user-written render functions.\n  vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)\n\n  // $attrs & $listeners are exposed for easier HOC creation.\n  // they need to be reactive so that HOCs using them are always updated\n  const parentData = parentVnode && parentVnode.data\n\n  /* istanbul ignore else */\n  if (process.env.NODE_ENV !== 'production') {\n    defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, () => {\n      !isUpdatingChildComponent && warn(`$attrs is readonly.`, vm)\n    }, true)\n    defineReactive(vm, '$listeners', options._parentListeners || emptyObject, () => {\n      !isUpdatingChildComponent && warn(`$listeners is readonly.`, vm)\n    }, true)\n  } else {\n    defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, null, true)\n    defineReactive(vm, '$listeners', options._parentListeners || emptyObject, null, true)\n  }\n}\n\nexport function renderMixin (Vue: Class<Component>) {\n  // install runtime convenience helpers\n  installRenderHelpers(Vue.prototype)\n\n  Vue.prototype.$nextTick = function (fn: Function) {\n    return nextTick(fn, this)\n  }\n\n  Vue.prototype._render = function (): VNode {\n    const vm: Component = this\n    const { render, _parentVnode } = vm.$options\n\n    // reset _rendered flag on slots for duplicate slot check\n    if (process.env.NODE_ENV !== 'production') {\n      for (const key in vm.$slots) {\n        // $flow-disable-line\n        vm.$slots[key]._rendered = false\n      }\n    }\n\n    if (_parentVnode) {\n      vm.$scopedSlots = _parentVnode.data.scopedSlots || emptyObject\n    }\n\n    // set parent vnode. this allows render functions to have access\n    // to the data on the placeholder node.\n    vm.$vnode = _parentVnode\n    // render self\n    let vnode\n    try {\n      vnode = render.call(vm._renderProxy, vm.$createElement)\n    } catch (e) {\n      handleError(e, vm, `render`)\n      // return error render result,\n      // or previous vnode to prevent render error causing blank component\n      /* istanbul ignore else */\n      if (process.env.NODE_ENV !== 'production') {\n        if (vm.$options.renderError) {\n          try {\n            vnode = vm.$options.renderError.call(vm._renderProxy, vm.$createElement, e)\n          } catch (e) {\n            handleError(e, vm, `renderError`)\n            vnode = vm._vnode\n          }\n        } else {\n          vnode = vm._vnode\n        }\n      } else {\n        vnode = vm._vnode\n      }\n    }\n    // return empty vnode in case the render function errored out\n    if (!(vnode instanceof VNode)) {\n      if (process.env.NODE_ENV !== 'production' && Array.isArray(vnode)) {\n        warn(\n          'Multiple root nodes returned from render function. Render function ' +\n          'should return a single root node.',\n          vm\n        )\n      }\n      vnode = createEmptyVNode()\n    }\n    // set parent\n    vnode.parent = _parentVnode\n    return vnode\n  }\n}\n"
  },
  {
    "path": "vue/src/core/instance/state.js",
    "content": "/* @flow */\n\nimport config from '../config'\nimport Watcher from '../observer/watcher'\nimport { pushTarget, popTarget } from '../observer/dep'\nimport { isUpdatingChildComponent } from './lifecycle'\n\nimport {\n  set,\n  del,\n  observe,\n  defineReactive,\n  toggleObserving\n} from '../observer/index'\n\nimport {\n  warn,\n  bind,\n  noop,\n  hasOwn,\n  hyphenate,\n  isReserved,\n  handleError,\n  nativeWatch,\n  validateProp,\n  isPlainObject,\n  isServerRendering,\n  isReservedAttribute\n} from '../util/index'\n\nconst sharedPropertyDefinition = {\n  enumerable: true,\n  configurable: true,\n  get: noop,\n  set: noop\n}\n\nexport function proxy (target: Object, sourceKey: string, key: string) {\n  sharedPropertyDefinition.get = function proxyGetter () {\n    return this[sourceKey][key]\n  }\n  sharedPropertyDefinition.set = function proxySetter (val) {\n    this[sourceKey][key] = val\n  }\n  Object.defineProperty(target, key, sharedPropertyDefinition)\n}\n\nexport function initState (vm: Component) {\n  vm._watchers = []\n  const opts = vm.$options\n  if (opts.props) initProps(vm, opts.props)\n  if (opts.methods) initMethods(vm, opts.methods)\n  if (opts.data) {\n    initData(vm)\n  } else {\n    observe(vm._data = {}, true /* asRootData */)\n  }\n  if (opts.computed) initComputed(vm, opts.computed)\n  if (opts.watch && opts.watch !== nativeWatch) {\n    initWatch(vm, opts.watch)\n  }\n}\n\nfunction initProps (vm: Component, propsOptions: Object) {\n  const propsData = vm.$options.propsData || {}\n  const props = vm._props = {}\n  // cache prop keys so that future props updates can iterate using Array\n  // instead of dynamic object key enumeration.\n  const keys = vm.$options._propKeys = []\n  const isRoot = !vm.$parent\n  // root instance props should be converted\n  if (!isRoot) {\n    toggleObserving(false)\n  }\n  for (const key in propsOptions) {\n    keys.push(key)\n    const value = validateProp(key, propsOptions, propsData, vm)\n    /* istanbul ignore else */\n    if (process.env.NODE_ENV !== 'production') {\n      const hyphenatedKey = hyphenate(key)\n      if (isReservedAttribute(hyphenatedKey) ||\n          config.isReservedAttr(hyphenatedKey)) {\n        warn(\n          `\"${hyphenatedKey}\" is a reserved attribute and cannot be used as component prop.`,\n          vm\n        )\n      }\n      defineReactive(props, key, value, () => {\n        if (vm.$parent && !isUpdatingChildComponent) {\n          warn(\n            `Avoid mutating a prop directly since the value will be ` +\n            `overwritten whenever the parent component re-renders. ` +\n            `Instead, use a data or computed property based on the prop's ` +\n            `value. Prop being mutated: \"${key}\"`,\n            vm\n          )\n        }\n      })\n    } else {\n      defineReactive(props, key, value)\n    }\n    // static props are already proxied on the component's prototype\n    // during Vue.extend(). We only need to proxy props defined at\n    // instantiation here.\n    if (!(key in vm)) {\n      proxy(vm, `_props`, key)\n    }\n  }\n  toggleObserving(true)\n}\n\nfunction initData (vm: Component) {\n  let data = vm.$options.data\n  data = vm._data = typeof data === 'function'\n    ? getData(data, vm)\n    : data || {}\n  if (!isPlainObject(data)) {\n    data = {}\n    process.env.NODE_ENV !== 'production' && warn(\n      'data functions should return an object:\\n' +\n      'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',\n      vm\n    )\n  }\n  // proxy data on instance\n  const keys = Object.keys(data)\n  const props = vm.$options.props\n  const methods = vm.$options.methods\n  let i = keys.length\n  while (i--) {\n    const key = keys[i]\n    if (process.env.NODE_ENV !== 'production') {\n      if (methods && hasOwn(methods, key)) {\n        warn(\n          `Method \"${key}\" has already been defined as a data property.`,\n          vm\n        )\n      }\n    }\n    if (props && hasOwn(props, key)) {\n      process.env.NODE_ENV !== 'production' && warn(\n        `The data property \"${key}\" is already declared as a prop. ` +\n        `Use prop default value instead.`,\n        vm\n      )\n    } else if (!isReserved(key)) {\n      proxy(vm, `_data`, key)\n    }\n  }\n  // observe data\n  observe(data, true /* asRootData */)\n}\n\nexport function getData (data: Function, vm: Component): any {\n  // #7573 disable dep collection when invoking data getters\n  pushTarget()\n  try {\n    return data.call(vm, vm)\n  } catch (e) {\n    handleError(e, vm, `data()`)\n    return {}\n  } finally {\n    popTarget()\n  }\n}\n\nconst computedWatcherOptions = { computed: true }\n\nfunction initComputed (vm: Component, computed: Object) {\n  // $flow-disable-line\n  const watchers = vm._computedWatchers = Object.create(null)\n  // computed properties are just getters during SSR\n  const isSSR = isServerRendering()\n\n  for (const key in computed) {\n    const userDef = computed[key]\n    const getter = typeof userDef === 'function' ? userDef : userDef.get\n    if (process.env.NODE_ENV !== 'production' && getter == null) {\n      warn(\n        `Getter is missing for computed property \"${key}\".`,\n        vm\n      )\n    }\n\n    if (!isSSR) {\n      // create internal watcher for the computed property.\n      watchers[key] = new Watcher(\n        vm,\n        getter || noop,\n        noop,\n        computedWatcherOptions\n      )\n    }\n\n    // component-defined computed properties are already defined on the\n    // component prototype. We only need to define computed properties defined\n    // at instantiation here.\n    if (!(key in vm)) {\n      defineComputed(vm, key, userDef)\n    } else if (process.env.NODE_ENV !== 'production') {\n      if (key in vm.$data) {\n        warn(`The computed property \"${key}\" is already defined in data.`, vm)\n      } else if (vm.$options.props && key in vm.$options.props) {\n        warn(`The computed property \"${key}\" is already defined as a prop.`, vm)\n      }\n    }\n  }\n}\n\nexport function defineComputed (\n  target: any,\n  key: string,\n  userDef: Object | Function\n) {\n  const shouldCache = !isServerRendering()\n  if (typeof userDef === 'function') {\n    sharedPropertyDefinition.get = shouldCache\n      ? createComputedGetter(key)\n      : userDef\n    sharedPropertyDefinition.set = noop\n  } else {\n    sharedPropertyDefinition.get = userDef.get\n      ? shouldCache && userDef.cache !== false\n        ? createComputedGetter(key)\n        : userDef.get\n      : noop\n    sharedPropertyDefinition.set = userDef.set\n      ? userDef.set\n      : noop\n  }\n  if (process.env.NODE_ENV !== 'production' &&\n      sharedPropertyDefinition.set === noop) {\n    sharedPropertyDefinition.set = function () {\n      warn(\n        `Computed property \"${key}\" was assigned to but it has no setter.`,\n        this\n      )\n    }\n  }\n  Object.defineProperty(target, key, sharedPropertyDefinition)\n}\n\nfunction createComputedGetter (key) {\n  return function computedGetter () {\n    const watcher = this._computedWatchers && this._computedWatchers[key]\n    if (watcher) {\n      watcher.depend()\n      return watcher.evaluate()\n    }\n  }\n}\n\nfunction initMethods (vm: Component, methods: Object) {\n  const props = vm.$options.props\n  for (const key in methods) {\n    if (process.env.NODE_ENV !== 'production') {\n      if (methods[key] == null) {\n        warn(\n          `Method \"${key}\" has an undefined value in the component definition. ` +\n          `Did you reference the function correctly?`,\n          vm\n        )\n      }\n      if (props && hasOwn(props, key)) {\n        warn(\n          `Method \"${key}\" has already been defined as a prop.`,\n          vm\n        )\n      }\n      if ((key in vm) && isReserved(key)) {\n        warn(\n          `Method \"${key}\" conflicts with an existing Vue instance method. ` +\n          `Avoid defining component methods that start with _ or $.`\n        )\n      }\n    }\n    vm[key] = methods[key] == null ? noop : bind(methods[key], vm)\n  }\n}\n\nfunction initWatch (vm: Component, watch: Object) {\n  for (const key in watch) {\n    const handler = watch[key]\n    if (Array.isArray(handler)) {\n      for (let i = 0; i < handler.length; i++) {\n        createWatcher(vm, key, handler[i])\n      }\n    } else {\n      createWatcher(vm, key, handler)\n    }\n  }\n}\n\nfunction createWatcher (\n  vm: Component,\n  expOrFn: string | Function,\n  handler: any,\n  options?: Object\n) {\n  if (isPlainObject(handler)) {\n    options = handler\n    handler = handler.handler\n  }\n  if (typeof handler === 'string') {\n    handler = vm[handler]\n  }\n  return vm.$watch(expOrFn, handler, options)\n}\n\nexport function stateMixin (Vue: Class<Component>) {\n  // flow somehow has problems with directly declared definition object\n  // when using Object.defineProperty, so we have to procedurally build up\n  // the object here.\n  const dataDef = {}\n  dataDef.get = function () { return this._data }\n  const propsDef = {}\n  propsDef.get = function () { return this._props }\n  if (process.env.NODE_ENV !== 'production') {\n    dataDef.set = function (newData: Object) {\n      warn(\n        'Avoid replacing instance root $data. ' +\n        'Use nested data properties instead.',\n        this\n      )\n    }\n    propsDef.set = function () {\n      warn(`$props is readonly.`, this)\n    }\n  }\n  Object.defineProperty(Vue.prototype, '$data', dataDef)\n  Object.defineProperty(Vue.prototype, '$props', propsDef)\n\n  Vue.prototype.$set = set\n  Vue.prototype.$delete = del\n\n  Vue.prototype.$watch = function (\n    expOrFn: string | Function,\n    cb: any,\n    options?: Object\n  ): Function {\n    const vm: Component = this\n    if (isPlainObject(cb)) {\n      return createWatcher(vm, expOrFn, cb, options)\n    }\n    options = options || {}\n    options.user = true\n    const watcher = new Watcher(vm, expOrFn, cb, options)\n    if (options.immediate) {\n      cb.call(vm, watcher.value)\n    }\n    return function unwatchFn () {\n      watcher.teardown()\n    }\n  }\n}\n"
  },
  {
    "path": "vue/src/core/observer/array.js",
    "content": "/*\n * not type checking this file because flow doesn't play well with\n * dynamically accessing methods on Array prototype\n */\n\nimport { def } from '../util/index'\n\nconst arrayProto = Array.prototype\nexport const arrayMethods = Object.create(arrayProto)\n\nconst methodsToPatch = [\n  'push',\n  'pop',\n  'shift',\n  'unshift',\n  'splice',\n  'sort',\n  'reverse'\n]\n\n/**\n * Intercept mutating methods and emit events\n */\nmethodsToPatch.forEach(function (method) {\n  // cache original method\n  const original = arrayProto[method]\n  def(arrayMethods, method, function mutator (...args) {\n    const result = original.apply(this, args)\n    const ob = this.__ob__\n    let inserted\n    switch (method) {\n      case 'push':\n      case 'unshift':\n        inserted = args\n        break\n      case 'splice':\n        inserted = args.slice(2)\n        break\n    }\n    if (inserted) ob.observeArray(inserted)\n    // notify change\n    ob.dep.notify()\n    return result\n  })\n})\n"
  },
  {
    "path": "vue/src/core/observer/dep.js",
    "content": "/* @flow */\n\nimport type Watcher from './watcher'\nimport { remove } from '../util/index'\n\nlet uid = 0\n\n/**\n * A dep is an observable that can have multiple\n * directives subscribing to it.\n */\nexport default class Dep {\n  static target: ?Watcher;\n  id: number;\n  subs: Array<Watcher>;\n\n  constructor () {\n    this.id = uid++\n    this.subs = []\n  }\n\n  addSub (sub: Watcher) {\n    this.subs.push(sub)\n  }\n\n  removeSub (sub: Watcher) {\n    remove(this.subs, sub)\n  }\n\n  depend () {\n    if (Dep.target) {\n      Dep.target.addDep(this)\n    }\n  }\n\n  notify () {\n    // stabilize the subscriber list first\n    const subs = this.subs.slice()\n    for (let i = 0, l = subs.length; i < l; i++) {\n      subs[i].update()\n    }\n  }\n}\n\n// the current target watcher being evaluated.\n// this is globally unique because there could be only one\n// watcher being evaluated at any time.\nDep.target = null\nconst targetStack = []\n\nexport function pushTarget (_target: ?Watcher) {\n  if (Dep.target) targetStack.push(Dep.target)\n  Dep.target = _target\n}\n\nexport function popTarget () {\n  Dep.target = targetStack.pop()\n}\n"
  },
  {
    "path": "vue/src/core/observer/index.js",
    "content": "/* @flow */\n\nimport Dep from './dep'\nimport VNode from '../vdom/vnode'\nimport { arrayMethods } from './array'\nimport {\n  def,\n  warn,\n  hasOwn,\n  hasProto,\n  isObject,\n  isPlainObject,\n  isPrimitive,\n  isUndef,\n  isValidArrayIndex,\n  isServerRendering\n} from '../util/index'\n\nconst arrayKeys = Object.getOwnPropertyNames(arrayMethods)\n\n/**\n * In some cases we may want to disable observation inside a component's\n * update computation.\n */\nexport let shouldObserve: boolean = true\n\nexport function toggleObserving (value: boolean) {\n  shouldObserve = value\n}\n\n/**\n * Observer class that is attached to each observed\n * object. Once attached, the observer converts the target\n * object's property keys into getter/setters that\n * collect dependencies and dispatch updates.\n */\nexport class Observer {\n  value: any;\n  dep: Dep;\n  vmCount: number; // number of vms that has this object as root $data\n\n  constructor (value: any) {\n    this.value = value\n    this.dep = new Dep()\n    this.vmCount = 0\n    def(value, '__ob__', this)\n    if (Array.isArray(value)) {\n      const augment = hasProto\n        ? protoAugment\n        : copyAugment\n      augment(value, arrayMethods, arrayKeys)\n      this.observeArray(value)\n    } else {\n      this.walk(value)\n    }\n  }\n\n  /**\n   * Walk through each property and convert them into\n   * getter/setters. This method should only be called when\n   * value type is Object.\n   */\n  walk (obj: Object) {\n    const keys = Object.keys(obj)\n    for (let i = 0; i < keys.length; i++) {\n      defineReactive(obj, keys[i])\n    }\n  }\n\n  /**\n   * Observe a list of Array items.\n   */\n  observeArray (items: Array<any>) {\n    for (let i = 0, l = items.length; i < l; i++) {\n      observe(items[i])\n    }\n  }\n}\n\n// helpers\n\n/**\n * Augment an target Object or Array by intercepting\n * the prototype chain using __proto__\n */\nfunction protoAugment (target, src: Object, keys: any) {\n  /* eslint-disable no-proto */\n  target.__proto__ = src\n  /* eslint-enable no-proto */\n}\n\n/**\n * Augment an target Object or Array by defining\n * hidden properties.\n */\n/* istanbul ignore next */\nfunction copyAugment (target: Object, src: Object, keys: Array<string>) {\n  for (let i = 0, l = keys.length; i < l; i++) {\n    const key = keys[i]\n    def(target, key, src[key])\n  }\n}\n\n/**\n * Attempt to create an observer instance for a value,\n * returns the new observer if successfully observed,\n * or the existing observer if the value already has one.\n */\nexport function observe (value: any, asRootData: ?boolean): Observer | void {\n  if (!isObject(value) || value instanceof VNode) {\n    return\n  }\n  let ob: Observer | void\n  if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {\n    ob = value.__ob__\n  } else if (\n    shouldObserve &&\n    !isServerRendering() &&\n    (Array.isArray(value) || isPlainObject(value)) &&\n    Object.isExtensible(value) &&\n    !value._isVue\n  ) {\n    ob = new Observer(value)\n  }\n  if (asRootData && ob) {\n    ob.vmCount++\n  }\n  return ob\n}\n\n/**\n * Define a reactive property on an Object.\n */\nexport function defineReactive (\n  obj: Object,\n  key: string,\n  val: any,\n  customSetter?: ?Function,\n  shallow?: boolean\n) {\n  const dep = new Dep()\n\n  const property = Object.getOwnPropertyDescriptor(obj, key)\n  if (property && property.configurable === false) {\n    return\n  }\n\n  // cater for pre-defined getter/setters\n  const getter = property && property.get\n  const setter = property && property.set\n  if ((!getter || setter) && arguments.length === 2) {\n    val = obj[key]\n  }\n\n  let childOb = !shallow && observe(val)\n  Object.defineProperty(obj, key, {\n    enumerable: true,\n    configurable: true,\n    get: function reactiveGetter () {\n      const value = getter ? getter.call(obj) : val\n      if (Dep.target) {\n        dep.depend()\n        if (childOb) {\n          childOb.dep.depend()\n          if (Array.isArray(value)) {\n            dependArray(value)\n          }\n        }\n      }\n      return value\n    },\n    set: function reactiveSetter (newVal) {\n      const value = getter ? getter.call(obj) : val\n      /* eslint-disable no-self-compare */\n      if (newVal === value || (newVal !== newVal && value !== value)) {\n        return\n      }\n      /* eslint-enable no-self-compare */\n      if (process.env.NODE_ENV !== 'production' && customSetter) {\n        customSetter()\n      }\n      if (setter) {\n        setter.call(obj, newVal)\n      } else {\n        val = newVal\n      }\n      childOb = !shallow && observe(newVal)\n      dep.notify()\n    }\n  })\n}\n\n/**\n * Set a property on an object. Adds the new property and\n * triggers change notification if the property doesn't\n * already exist.\n */\nexport function set (target: Array<any> | Object, key: any, val: any): any {\n  if (process.env.NODE_ENV !== 'production' &&\n    (isUndef(target) || isPrimitive(target))\n  ) {\n    warn(`Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}`)\n  }\n  if (Array.isArray(target) && isValidArrayIndex(key)) {\n    target.length = Math.max(target.length, key)\n    target.splice(key, 1, val)\n    return val\n  }\n  if (key in target && !(key in Object.prototype)) {\n    target[key] = val\n    return val\n  }\n  const ob = (target: any).__ob__\n  if (target._isVue || (ob && ob.vmCount)) {\n    process.env.NODE_ENV !== 'production' && warn(\n      'Avoid adding reactive properties to a Vue instance or its root $data ' +\n      'at runtime - declare it upfront in the data option.'\n    )\n    return val\n  }\n  if (!ob) {\n    target[key] = val\n    return val\n  }\n  defineReactive(ob.value, key, val)\n  ob.dep.notify()\n  return val\n}\n\n/**\n * Delete a property and trigger change if necessary.\n */\nexport function del (target: Array<any> | Object, key: any) {\n  if (process.env.NODE_ENV !== 'production' &&\n    (isUndef(target) || isPrimitive(target))\n  ) {\n    warn(`Cannot delete reactive property on undefined, null, or primitive value: ${(target: any)}`)\n  }\n  if (Array.isArray(target) && isValidArrayIndex(key)) {\n    target.splice(key, 1)\n    return\n  }\n  const ob = (target: any).__ob__\n  if (target._isVue || (ob && ob.vmCount)) {\n    process.env.NODE_ENV !== 'production' && warn(\n      'Avoid deleting properties on a Vue instance or its root $data ' +\n      '- just set it to null.'\n    )\n    return\n  }\n  if (!hasOwn(target, key)) {\n    return\n  }\n  delete target[key]\n  if (!ob) {\n    return\n  }\n  ob.dep.notify()\n}\n\n/**\n * Collect dependencies on array elements when the array is touched, since\n * we cannot intercept array element access like property getters.\n */\nfunction dependArray (value: Array<any>) {\n  for (let e, i = 0, l = value.length; i < l; i++) {\n    e = value[i]\n    e && e.__ob__ && e.__ob__.dep.depend()\n    if (Array.isArray(e)) {\n      dependArray(e)\n    }\n  }\n}\n"
  },
  {
    "path": "vue/src/core/observer/scheduler.js",
    "content": "/* @flow */\n\nimport type Watcher from './watcher'\nimport config from '../config'\nimport { callHook, activateChildComponent } from '../instance/lifecycle'\n\nimport {\n  warn,\n  nextTick,\n  devtools\n} from '../util/index'\n\nexport const MAX_UPDATE_COUNT = 100\n\nconst queue: Array<Watcher> = []\nconst activatedChildren: Array<Component> = []\nlet has: { [key: number]: ?true } = {}\nlet circular: { [key: number]: number } = {}\nlet waiting = false\nlet flushing = false\nlet index = 0\n\n/**\n * Reset the scheduler's state.\n */\nfunction resetSchedulerState () {\n  index = queue.length = activatedChildren.length = 0\n  has = {}\n  if (process.env.NODE_ENV !== 'production') {\n    circular = {}\n  }\n  waiting = flushing = false\n}\n\n/**\n * Flush both queues and run the watchers.\n */\nfunction flushSchedulerQueue () {\n  flushing = true\n  let watcher, id\n\n  // Sort queue before flush.\n  // This ensures that:\n  // 1. Components are updated from parent to child. (because parent is always\n  //    created before the child)\n  // 2. A component's user watchers are run before its render watcher (because\n  //    user watchers are created before the render watcher)\n  // 3. If a component is destroyed during a parent component's watcher run,\n  //    its watchers can be skipped.\n  queue.sort((a, b) => a.id - b.id)\n\n  // do not cache length because more watchers might be pushed\n  // as we run existing watchers\n  for (index = 0; index < queue.length; index++) {\n    watcher = queue[index]\n    if (watcher.before) {\n      watcher.before()\n    }\n    id = watcher.id\n    has[id] = null\n    watcher.run()\n    // in dev build, check and stop circular updates.\n    if (process.env.NODE_ENV !== 'production' && has[id] != null) {\n      circular[id] = (circular[id] || 0) + 1\n      if (circular[id] > MAX_UPDATE_COUNT) {\n        warn(\n          'You may have an infinite update loop ' + (\n            watcher.user\n              ? `in watcher with expression \"${watcher.expression}\"`\n              : `in a component render function.`\n          ),\n          watcher.vm\n        )\n        break\n      }\n    }\n  }\n\n  // keep copies of post queues before resetting state\n  const activatedQueue = activatedChildren.slice()\n  const updatedQueue = queue.slice()\n\n  resetSchedulerState()\n\n  // call component updated and activated hooks\n  callActivatedHooks(activatedQueue)\n  callUpdatedHooks(updatedQueue)\n\n  // devtool hook\n  /* istanbul ignore if */\n  if (devtools && config.devtools) {\n    devtools.emit('flush')\n  }\n}\n\nfunction callUpdatedHooks (queue) {\n  let i = queue.length\n  while (i--) {\n    const watcher = queue[i]\n    const vm = watcher.vm\n    if (vm._watcher === watcher && vm._isMounted) {\n      callHook(vm, 'updated')\n    }\n  }\n}\n\n/**\n * Queue a kept-alive component that was activated during patch.\n * The queue will be processed after the entire tree has been patched.\n */\nexport function queueActivatedComponent (vm: Component) {\n  // setting _inactive to false here so that a render function can\n  // rely on checking whether it's in an inactive tree (e.g. router-view)\n  vm._inactive = false\n  activatedChildren.push(vm)\n}\n\nfunction callActivatedHooks (queue) {\n  for (let i = 0; i < queue.length; i++) {\n    queue[i]._inactive = true\n    activateChildComponent(queue[i], true /* true */)\n  }\n}\n\n/**\n * Push a watcher into the watcher queue.\n * Jobs with duplicate IDs will be skipped unless it's\n * pushed when the queue is being flushed.\n */\nexport function queueWatcher (watcher: Watcher) {\n  const id = watcher.id\n  if (has[id] == null) {\n    has[id] = true\n    if (!flushing) {\n      queue.push(watcher)\n    } else {\n      // if already flushing, splice the watcher based on its id\n      // if already past its id, it will be run next immediately.\n      let i = queue.length - 1\n      while (i > index && queue[i].id > watcher.id) {\n        i--\n      }\n      queue.splice(i + 1, 0, watcher)\n    }\n    // queue the flush\n    if (!waiting) {\n      waiting = true\n      nextTick(flushSchedulerQueue)\n    }\n  }\n}\n"
  },
  {
    "path": "vue/src/core/observer/traverse.js",
    "content": "/* @flow */\n\nimport { _Set as Set, isObject } from '../util/index'\nimport type { SimpleSet } from '../util/index'\nimport VNode from '../vdom/vnode'\n\nconst seenObjects = new Set()\n\n/**\n * Recursively traverse an object to evoke all converted\n * getters, so that every nested property inside the object\n * is collected as a \"deep\" dependency.\n */\nexport function traverse (val: any) {\n  _traverse(val, seenObjects)\n  seenObjects.clear()\n}\n\nfunction _traverse (val: any, seen: SimpleSet) {\n  let i, keys\n  const isA = Array.isArray(val)\n  if ((!isA && !isObject(val)) || Object.isFrozen(val) || val instanceof VNode) {\n    return\n  }\n  if (val.__ob__) {\n    const depId = val.__ob__.dep.id\n    if (seen.has(depId)) {\n      return\n    }\n    seen.add(depId)\n  }\n  if (isA) {\n    i = val.length\n    while (i--) _traverse(val[i], seen)\n  } else {\n    keys = Object.keys(val)\n    i = keys.length\n    while (i--) _traverse(val[keys[i]], seen)\n  }\n}\n"
  },
  {
    "path": "vue/src/core/observer/watcher.js",
    "content": "/* @flow */\n\nimport {\n  warn,\n  remove,\n  isObject,\n  parsePath,\n  _Set as Set,\n  handleError\n} from '../util/index'\n\nimport { traverse } from './traverse'\nimport { queueWatcher } from './scheduler'\nimport Dep, { pushTarget, popTarget } from './dep'\n\nimport type { SimpleSet } from '../util/index'\n\nlet uid = 0\n\n/**\n * A watcher parses an expression, collects dependencies,\n * and fires callback when the expression value changes.\n * This is used for both the $watch() api and directives.\n */\nexport default class Watcher {\n  vm: Component;\n  expression: string;\n  cb: Function;\n  id: number;\n  deep: boolean;\n  user: boolean;\n  computed: boolean;\n  sync: boolean;\n  dirty: boolean;\n  active: boolean;\n  dep: Dep;\n  deps: Array<Dep>;\n  newDeps: Array<Dep>;\n  depIds: SimpleSet;\n  newDepIds: SimpleSet;\n  before: ?Function;\n  getter: Function;\n  value: any;\n\n  constructor (\n    vm: Component,\n    expOrFn: string | Function,\n    cb: Function,\n    options?: ?Object,\n    isRenderWatcher?: boolean\n  ) {\n    this.vm = vm\n    if (isRenderWatcher) {\n      vm._watcher = this\n    }\n    vm._watchers.push(this)\n    // options\n    if (options) {\n      this.deep = !!options.deep\n      this.user = !!options.user\n      this.computed = !!options.computed\n      this.sync = !!options.sync\n      this.before = options.before\n    } else {\n      this.deep = this.user = this.computed = this.sync = false\n    }\n    this.cb = cb\n    this.id = ++uid // uid for batching\n    this.active = true\n    this.dirty = this.computed // for computed watchers\n    this.deps = []\n    this.newDeps = []\n    this.depIds = new Set()\n    this.newDepIds = new Set()\n    this.expression = process.env.NODE_ENV !== 'production'\n      ? expOrFn.toString()\n      : ''\n    // parse expression for getter\n    if (typeof expOrFn === 'function') {\n      this.getter = expOrFn\n    } else {\n      this.getter = parsePath(expOrFn)\n      if (!this.getter) {\n        this.getter = function () {}\n        process.env.NODE_ENV !== 'production' && warn(\n          `Failed watching path: \"${expOrFn}\" ` +\n          'Watcher only accepts simple dot-delimited paths. ' +\n          'For full control, use a function instead.',\n          vm\n        )\n      }\n    }\n    if (this.computed) {\n      this.value = undefined\n      this.dep = new Dep()\n    } else {\n      this.value = this.get()\n    }\n  }\n\n  /**\n   * Evaluate the getter, and re-collect dependencies.\n   */\n  get () {\n    pushTarget(this)\n    let value\n    const vm = this.vm\n    try {\n      value = this.getter.call(vm, vm)\n    } catch (e) {\n      if (this.user) {\n        handleError(e, vm, `getter for watcher \"${this.expression}\"`)\n      } else {\n        throw e\n      }\n    } finally {\n      // \"touch\" every property so they are all tracked as\n      // dependencies for deep watching\n      if (this.deep) {\n        traverse(value)\n      }\n      popTarget()\n      this.cleanupDeps()\n    }\n    return value\n  }\n\n  /**\n   * Add a dependency to this directive.\n   */\n  addDep (dep: Dep) {\n    const id = dep.id\n    if (!this.newDepIds.has(id)) {\n      this.newDepIds.add(id)\n      this.newDeps.push(dep)\n      if (!this.depIds.has(id)) {\n        dep.addSub(this)\n      }\n    }\n  }\n\n  /**\n   * Clean up for dependency collection.\n   */\n  cleanupDeps () {\n    let i = this.deps.length\n    while (i--) {\n      const dep = this.deps[i]\n      if (!this.newDepIds.has(dep.id)) {\n        dep.removeSub(this)\n      }\n    }\n    let tmp = this.depIds\n    this.depIds = this.newDepIds\n    this.newDepIds = tmp\n    this.newDepIds.clear()\n    tmp = this.deps\n    this.deps = this.newDeps\n    this.newDeps = tmp\n    this.newDeps.length = 0\n  }\n\n  /**\n   * Subscriber interface.\n   * Will be called when a dependency changes.\n   */\n  update () {\n    /* istanbul ignore else */\n    if (this.computed) {\n      // A computed property watcher has two modes: lazy and activated.\n      // It initializes as lazy by default, and only becomes activated when\n      // it is depended on by at least one subscriber, which is typically\n      // another computed property or a component's render function.\n      if (this.dep.subs.length === 0) {\n        // In lazy mode, we don't want to perform computations until necessary,\n        // so we simply mark the watcher as dirty. The actual computation is\n        // performed just-in-time in this.evaluate() when the computed property\n        // is accessed.\n        this.dirty = true\n      } else {\n        // In activated mode, we want to proactively perform the computation\n        // but only notify our subscribers when the value has indeed changed.\n        this.getAndInvoke(() => {\n          this.dep.notify()\n        })\n      }\n    } else if (this.sync) {\n      this.run()\n    } else {\n      queueWatcher(this)\n    }\n  }\n\n  /**\n   * Scheduler job interface.\n   * Will be called by the scheduler.\n   */\n  run () {\n    if (this.active) {\n      this.getAndInvoke(this.cb)\n    }\n  }\n\n  getAndInvoke (cb: Function) {\n    const value = this.get()\n    if (\n      value !== this.value ||\n      // Deep watchers and watchers on Object/Arrays should fire even\n      // when the value is the same, because the value may\n      // have mutated.\n      isObject(value) ||\n      this.deep\n    ) {\n      // set new value\n      const oldValue = this.value\n      this.value = value\n      this.dirty = false\n      if (this.user) {\n        try {\n          cb.call(this.vm, value, oldValue)\n        } catch (e) {\n          handleError(e, this.vm, `callback for watcher \"${this.expression}\"`)\n        }\n      } else {\n        cb.call(this.vm, value, oldValue)\n      }\n    }\n  }\n\n  /**\n   * Evaluate and return the value of the watcher.\n   * This only gets called for computed property watchers.\n   */\n  evaluate () {\n    if (this.dirty) {\n      this.value = this.get()\n      this.dirty = false\n    }\n    return this.value\n  }\n\n  /**\n   * Depend on this watcher. Only for computed property watchers.\n   */\n  depend () {\n    if (this.dep && Dep.target) {\n      this.dep.depend()\n    }\n  }\n\n  /**\n   * Remove self from all dependencies' subscriber list.\n   */\n  teardown () {\n    if (this.active) {\n      // remove self from vm's watcher list\n      // this is a somewhat expensive operation so we skip it\n      // if the vm is being destroyed.\n      if (!this.vm._isBeingDestroyed) {\n        remove(this.vm._watchers, this)\n      }\n      let i = this.deps.length\n      while (i--) {\n        this.deps[i].removeSub(this)\n      }\n      this.active = false\n    }\n  }\n}\n"
  },
  {
    "path": "vue/src/core/util/debug.js",
    "content": "/* @flow */\n\nimport config from '../config'\nimport { noop } from 'shared/util'\n\nexport let warn = noop\nexport let tip = noop\nexport let generateComponentTrace = (noop: any) // work around flow check\nexport let formatComponentName = (noop: any)\n\nif (process.env.NODE_ENV !== 'production') {\n  const hasConsole = typeof console !== 'undefined'\n  const classifyRE = /(?:^|[-_])(\\w)/g\n  const classify = str => str\n    .replace(classifyRE, c => c.toUpperCase())\n    .replace(/[-_]/g, '')\n\n  warn = (msg, vm) => {\n    const trace = vm ? generateComponentTrace(vm) : ''\n\n    if (config.warnHandler) {\n      config.warnHandler.call(null, msg, vm, trace)\n    } else if (hasConsole && (!config.silent)) {\n      console.error(`[Vue warn]: ${msg}${trace}`)\n    }\n  }\n\n  tip = (msg, vm) => {\n    if (hasConsole && (!config.silent)) {\n      console.warn(`[Vue tip]: ${msg}` + (\n        vm ? generateComponentTrace(vm) : ''\n      ))\n    }\n  }\n\n  formatComponentName = (vm, includeFile) => {\n    if (vm.$root === vm) {\n      return '<Root>'\n    }\n    const options = typeof vm === 'function' && vm.cid != null\n      ? vm.options\n      : vm._isVue\n        ? vm.$options || vm.constructor.options\n        : vm || {}\n    let name = options.name || options._componentTag\n    const file = options.__file\n    if (!name && file) {\n      const match = file.match(/([^/\\\\]+)\\.vue$/)\n      name = match && match[1]\n    }\n\n    return (\n      (name ? `<${classify(name)}>` : `<Anonymous>`) +\n      (file && includeFile !== false ? ` at ${file}` : '')\n    )\n  }\n\n  const repeat = (str, n) => {\n    let res = ''\n    while (n) {\n      if (n % 2 === 1) res += str\n      if (n > 1) str += str\n      n >>= 1\n    }\n    return res\n  }\n\n  generateComponentTrace = vm => {\n    if (vm._isVue && vm.$parent) {\n      const tree = []\n      let currentRecursiveSequence = 0\n      while (vm) {\n        if (tree.length > 0) {\n          const last = tree[tree.length - 1]\n          if (last.constructor === vm.constructor) {\n            currentRecursiveSequence++\n            vm = vm.$parent\n            continue\n          } else if (currentRecursiveSequence > 0) {\n            tree[tree.length - 1] = [last, currentRecursiveSequence]\n            currentRecursiveSequence = 0\n          }\n        }\n        tree.push(vm)\n        vm = vm.$parent\n      }\n      return '\\n\\nfound in\\n\\n' + tree\n        .map((vm, i) => `${\n          i === 0 ? '---> ' : repeat(' ', 5 + i * 2)\n        }${\n          Array.isArray(vm)\n            ? `${formatComponentName(vm[0])}... (${vm[1]} recursive calls)`\n            : formatComponentName(vm)\n        }`)\n        .join('\\n')\n    } else {\n      return `\\n\\n(found in ${formatComponentName(vm)})`\n    }\n  }\n}\n"
  },
  {
    "path": "vue/src/core/util/env.js",
    "content": "/* @flow */\n\n// can we use __proto__?\nexport const hasProto = '__proto__' in {}\n\n// Browser environment sniffing\nexport const inBrowser = typeof window !== 'undefined'\nexport const inWeex = typeof WXEnvironment !== 'undefined' && !!WXEnvironment.platform\nexport const weexPlatform = inWeex && WXEnvironment.platform.toLowerCase()\nexport const UA = inBrowser && window.navigator.userAgent.toLowerCase()\nexport const isIE = UA && /msie|trident/.test(UA)\nexport const isIE9 = UA && UA.indexOf('msie 9.0') > 0\nexport const isEdge = UA && UA.indexOf('edge/') > 0\nexport const isAndroid = (UA && UA.indexOf('android') > 0) || (weexPlatform === 'android')\nexport const isIOS = (UA && /iphone|ipad|ipod|ios/.test(UA)) || (weexPlatform === 'ios')\nexport const isChrome = UA && /chrome\\/\\d+/.test(UA) && !isEdge\n\n// Firefox has a \"watch\" function on Object.prototype...\nexport const nativeWatch = ({}).watch\n\nexport let supportsPassive = false\nif (inBrowser) {\n  try {\n    const opts = {}\n    Object.defineProperty(opts, 'passive', ({\n      get () {\n        /* istanbul ignore next */\n        supportsPassive = true\n      }\n    }: Object)) // https://github.com/facebook/flow/issues/285\n    window.addEventListener('test-passive', null, opts)\n  } catch (e) {}\n}\n\n// this needs to be lazy-evaled because vue may be required before\n// vue-server-renderer can set VUE_ENV\nlet _isServer\nexport const isServerRendering = () => {\n  if (_isServer === undefined) {\n    /* istanbul ignore if */\n    if (!inBrowser && !inWeex && typeof global !== 'undefined') {\n      // detect presence of vue-server-renderer and avoid\n      // Webpack shimming the process\n      _isServer = global['process'].env.VUE_ENV === 'server'\n    } else {\n      _isServer = false\n    }\n  }\n  return _isServer\n}\n\n// detect devtools\nexport const devtools = inBrowser && window.__VUE_DEVTOOLS_GLOBAL_HOOK__\n\n/* istanbul ignore next */\nexport function isNative (Ctor: any): boolean {\n  return typeof Ctor === 'function' && /native code/.test(Ctor.toString())\n}\n\nexport const hasSymbol =\n  typeof Symbol !== 'undefined' && isNative(Symbol) &&\n  typeof Reflect !== 'undefined' && isNative(Reflect.ownKeys)\n\nlet _Set\n/* istanbul ignore if */ // $flow-disable-line\nif (typeof Set !== 'undefined' && isNative(Set)) {\n  // use native Set when available.\n  _Set = Set\n} else {\n  // a non-standard Set polyfill that only works with primitive keys.\n  _Set = class Set implements SimpleSet {\n    set: Object;\n    constructor () {\n      this.set = Object.create(null)\n    }\n    has (key: string | number) {\n      return this.set[key] === true\n    }\n    add (key: string | number) {\n      this.set[key] = true\n    }\n    clear () {\n      this.set = Object.create(null)\n    }\n  }\n}\n\ninterface SimpleSet {\n  has(key: string | number): boolean;\n  add(key: string | number): mixed;\n  clear(): void;\n}\n\nexport { _Set }\nexport type { SimpleSet }\n"
  },
  {
    "path": "vue/src/core/util/error.js",
    "content": "/* @flow */\n\nimport config from '../config'\nimport { warn } from './debug'\nimport { inBrowser, inWeex } from './env'\n\nexport function handleError (err: Error, vm: any, info: string) {\n  if (vm) {\n    let cur = vm\n    while ((cur = cur.$parent)) {\n      const hooks = cur.$options.errorCaptured\n      if (hooks) {\n        for (let i = 0; i < hooks.length; i++) {\n          try {\n            const capture = hooks[i].call(cur, err, vm, info) === false\n            if (capture) return\n          } catch (e) {\n            globalHandleError(e, cur, 'errorCaptured hook')\n          }\n        }\n      }\n    }\n  }\n  globalHandleError(err, vm, info)\n}\n\nfunction globalHandleError (err, vm, info) {\n  if (config.errorHandler) {\n    try {\n      return config.errorHandler.call(null, err, vm, info)\n    } catch (e) {\n      logError(e, null, 'config.errorHandler')\n    }\n  }\n  logError(err, vm, info)\n}\n\nfunction logError (err, vm, info) {\n  if (process.env.NODE_ENV !== 'production') {\n    warn(`Error in ${info}: \"${err.toString()}\"`, vm)\n  }\n  /* istanbul ignore else */\n  if ((inBrowser || inWeex) && typeof console !== 'undefined') {\n    console.error(err)\n  } else {\n    throw err\n  }\n}\n"
  },
  {
    "path": "vue/src/core/util/index.js",
    "content": "/* @flow */\n\nexport * from 'shared/util'\nexport * from './lang'\nexport * from './env'\nexport * from './options'\nexport * from './debug'\nexport * from './props'\nexport * from './error'\nexport * from './next-tick'\nexport { defineReactive } from '../observer/index'\n"
  },
  {
    "path": "vue/src/core/util/lang.js",
    "content": "/* @flow */\n\n/**\n * Check if a string starts with $ or _\n */\nexport function isReserved (str: string): boolean {\n  const c = (str + '').charCodeAt(0)\n  return c === 0x24 || c === 0x5F\n}\n\n/**\n * Define a property.\n */\nexport function def (obj: Object, key: string, val: any, enumerable?: boolean) {\n  Object.defineProperty(obj, key, {\n    value: val,\n    enumerable: !!enumerable,\n    writable: true,\n    configurable: true\n  })\n}\n\n/**\n * Parse simple path.\n */\nconst bailRE = /[^\\w.$]/\nexport function parsePath (path: string): any {\n  if (bailRE.test(path)) {\n    return\n  }\n  const segments = path.split('.')\n  return function (obj) {\n    for (let i = 0; i < segments.length; i++) {\n      if (!obj) return\n      obj = obj[segments[i]]\n    }\n    return obj\n  }\n}\n"
  },
  {
    "path": "vue/src/core/util/next-tick.js",
    "content": "/* @flow */\n/* globals MessageChannel */\n\nimport { noop } from 'shared/util'\nimport { handleError } from './error'\nimport { isIOS, isNative } from './env'\n\nconst callbacks = []\nlet pending = false\n\nfunction flushCallbacks () {\n  pending = false\n  const copies = callbacks.slice(0)\n  callbacks.length = 0\n  for (let i = 0; i < copies.length; i++) {\n    copies[i]()\n  }\n}\n\n// Here we have async deferring wrappers using both microtasks and (macro) tasks.\n// In < 2.4 we used microtasks everywhere, but there are some scenarios where\n// microtasks have too high a priority and fire in between supposedly\n// sequential events (e.g. #4521, #6690) or even between bubbling of the same\n// event (#6566). However, using (macro) tasks everywhere also has subtle problems\n// when state is changed right before repaint (e.g. #6813, out-in transitions).\n// Here we use microtask by default, but expose a way to force (macro) task when\n// needed (e.g. in event handlers attached by v-on).\nlet microTimerFunc\nlet macroTimerFunc\nlet useMacroTask = false\n\n// Determine (macro) task defer implementation.\n// Technically setImmediate should be the ideal choice, but it's only available\n// in IE. The only polyfill that consistently queues the callback after all DOM\n// events triggered in the same loop is by using MessageChannel.\n/* istanbul ignore if */\nif (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {\n  macroTimerFunc = () => {\n    setImmediate(flushCallbacks)\n  }\n} else if (typeof MessageChannel !== 'undefined' && (\n  isNative(MessageChannel) ||\n  // PhantomJS\n  MessageChannel.toString() === '[object MessageChannelConstructor]'\n)) {\n  const channel = new MessageChannel()\n  const port = channel.port2\n  channel.port1.onmessage = flushCallbacks\n  macroTimerFunc = () => {\n    port.postMessage(1)\n  }\n} else {\n  /* istanbul ignore next */\n  macroTimerFunc = () => {\n    setTimeout(flushCallbacks, 0)\n  }\n}\n\n// Determine microtask defer implementation.\n/* istanbul ignore next, $flow-disable-line */\nif (typeof Promise !== 'undefined' && isNative(Promise)) {\n  const p = Promise.resolve()\n  microTimerFunc = () => {\n    p.then(flushCallbacks)\n    // in problematic UIWebViews, Promise.then doesn't completely break, but\n    // it can get stuck in a weird state where callbacks are pushed into the\n    // microtask queue but the queue isn't being flushed, until the browser\n    // needs to do some other work, e.g. handle a timer. Therefore we can\n    // \"force\" the microtask queue to be flushed by adding an empty timer.\n    if (isIOS) setTimeout(noop)\n  }\n} else {\n  // fallback to macro\n  microTimerFunc = macroTimerFunc\n}\n\n/**\n * Wrap a function so that if any code inside triggers state change,\n * the changes are queued using a (macro) task instead of a microtask.\n */\nexport function withMacroTask (fn: Function): Function {\n  return fn._withTask || (fn._withTask = function () {\n    useMacroTask = true\n    const res = fn.apply(null, arguments)\n    useMacroTask = false\n    return res\n  })\n}\n\nexport function nextTick (cb?: Function, ctx?: Object) {\n  let _resolve\n  callbacks.push(() => {\n    if (cb) {\n      try {\n        cb.call(ctx)\n      } catch (e) {\n        handleError(e, ctx, 'nextTick')\n      }\n    } else if (_resolve) {\n      _resolve(ctx)\n    }\n  })\n  if (!pending) {\n    pending = true\n    if (useMacroTask) {\n      macroTimerFunc()\n    } else {\n      microTimerFunc()\n    }\n  }\n  // $flow-disable-line\n  if (!cb && typeof Promise !== 'undefined') {\n    return new Promise(resolve => {\n      _resolve = resolve\n    })\n  }\n}\n"
  },
  {
    "path": "vue/src/core/util/options.js",
    "content": "/* @flow */\n\nimport config from '../config'\nimport { warn } from './debug'\nimport { nativeWatch } from './env'\nimport { set } from '../observer/index'\n\nimport {\n  ASSET_TYPES,\n  LIFECYCLE_HOOKS\n} from 'shared/constants'\n\nimport {\n  extend,\n  hasOwn,\n  camelize,\n  toRawType,\n  capitalize,\n  isBuiltInTag,\n  isPlainObject\n} from 'shared/util'\n\n/**\n * Option overwriting strategies are functions that handle\n * how to merge a parent option value and a child option\n * value into the final value.\n */\nconst strats = config.optionMergeStrategies\n\n/**\n * Options with restrictions\n */\nif (process.env.NODE_ENV !== 'production') {\n  strats.el = strats.propsData = function (parent, child, vm, key) {\n    if (!vm) {\n      warn(\n        `option \"${key}\" can only be used during instance ` +\n        'creation with the `new` keyword.'\n      )\n    }\n    return defaultStrat(parent, child)\n  }\n}\n\n/**\n * Helper that recursively merges two data objects together.\n */\nfunction mergeData (to: Object, from: ?Object): Object {\n  if (!from) return to\n  let key, toVal, fromVal\n  const keys = Object.keys(from)\n  for (let i = 0; i < keys.length; i++) {\n    key = keys[i]\n    toVal = to[key]\n    fromVal = from[key]\n    if (!hasOwn(to, key)) {\n      set(to, key, fromVal)\n    } else if (isPlainObject(toVal) && isPlainObject(fromVal)) {\n      mergeData(toVal, fromVal)\n    }\n  }\n  return to\n}\n\n/**\n * Data\n */\nexport function mergeDataOrFn (\n  parentVal: any,\n  childVal: any,\n  vm?: Component\n): ?Function {\n  if (!vm) {\n    // in a Vue.extend merge, both should be functions\n    if (!childVal) {\n      return parentVal\n    }\n    if (!parentVal) {\n      return childVal\n    }\n    // when parentVal & childVal are both present,\n    // we need to return a function that returns the\n    // merged result of both functions... no need to\n    // check if parentVal is a function here because\n    // it has to be a function to pass previous merges.\n    return function mergedDataFn () {\n      return mergeData(\n        typeof childVal === 'function' ? childVal.call(this, this) : childVal,\n        typeof parentVal === 'function' ? parentVal.call(this, this) : parentVal\n      )\n    }\n  } else {\n    return function mergedInstanceDataFn () {\n      // instance merge\n      const instanceData = typeof childVal === 'function'\n        ? childVal.call(vm, vm)\n        : childVal\n      const defaultData = typeof parentVal === 'function'\n        ? parentVal.call(vm, vm)\n        : parentVal\n      if (instanceData) {\n        return mergeData(instanceData, defaultData)\n      } else {\n        return defaultData\n      }\n    }\n  }\n}\n\nstrats.data = function (\n  parentVal: any,\n  childVal: any,\n  vm?: Component\n): ?Function {\n  if (!vm) {\n    if (childVal && typeof childVal !== 'function') {\n      process.env.NODE_ENV !== 'production' && warn(\n        'The \"data\" option should be a function ' +\n        'that returns a per-instance value in component ' +\n        'definitions.',\n        vm\n      )\n\n      return parentVal\n    }\n    return mergeDataOrFn(parentVal, childVal)\n  }\n\n  return mergeDataOrFn(parentVal, childVal, vm)\n}\n\n/**\n * Hooks and props are merged as arrays.\n */\nfunction mergeHook (\n  parentVal: ?Array<Function>,\n  childVal: ?Function | ?Array<Function>\n): ?Array<Function> {\n  return childVal\n    ? parentVal\n      ? parentVal.concat(childVal)\n      : Array.isArray(childVal)\n        ? childVal\n        : [childVal]\n    : parentVal\n}\n\nLIFECYCLE_HOOKS.forEach(hook => {\n  strats[hook] = mergeHook\n})\n\n/**\n * Assets\n *\n * When a vm is present (instance creation), we need to do\n * a three-way merge between constructor options, instance\n * options and parent options.\n */\nfunction mergeAssets (\n  parentVal: ?Object,\n  childVal: ?Object,\n  vm?: Component,\n  key: string\n): Object {\n  const res = Object.create(parentVal || null)\n  if (childVal) {\n    process.env.NODE_ENV !== 'production' && assertObjectType(key, childVal, vm)\n    return extend(res, childVal)\n  } else {\n    return res\n  }\n}\n\nASSET_TYPES.forEach(function (type) {\n  strats[type + 's'] = mergeAssets\n})\n\n/**\n * Watchers.\n *\n * Watchers hashes should not overwrite one\n * another, so we merge them as arrays.\n */\nstrats.watch = function (\n  parentVal: ?Object,\n  childVal: ?Object,\n  vm?: Component,\n  key: string\n): ?Object {\n  // work around Firefox's Object.prototype.watch...\n  if (parentVal === nativeWatch) parentVal = undefined\n  if (childVal === nativeWatch) childVal = undefined\n  /* istanbul ignore if */\n  if (!childVal) return Object.create(parentVal || null)\n  if (process.env.NODE_ENV !== 'production') {\n    assertObjectType(key, childVal, vm)\n  }\n  if (!parentVal) return childVal\n  const ret = {}\n  extend(ret, parentVal)\n  for (const key in childVal) {\n    let parent = ret[key]\n    const child = childVal[key]\n    if (parent && !Array.isArray(parent)) {\n      parent = [parent]\n    }\n    ret[key] = parent\n      ? parent.concat(child)\n      : Array.isArray(child) ? child : [child]\n  }\n  return ret\n}\n\n/**\n * Other object hashes.\n */\nstrats.props =\nstrats.methods =\nstrats.inject =\nstrats.computed = function (\n  parentVal: ?Object,\n  childVal: ?Object,\n  vm?: Component,\n  key: string\n): ?Object {\n  if (childVal && process.env.NODE_ENV !== 'production') {\n    assertObjectType(key, childVal, vm)\n  }\n  if (!parentVal) return childVal\n  const ret = Object.create(null)\n  extend(ret, parentVal)\n  if (childVal) extend(ret, childVal)\n  return ret\n}\nstrats.provide = mergeDataOrFn\n\n/**\n * Default strategy.\n */\nconst defaultStrat = function (parentVal: any, childVal: any): any {\n  return childVal === undefined\n    ? parentVal\n    : childVal\n}\n\n/**\n * Validate component names\n */\nfunction checkComponents (options: Object) {\n  for (const key in options.components) {\n    validateComponentName(key)\n  }\n}\n\nexport function validateComponentName (name: string) {\n  if (!/^[a-zA-Z][\\w-]*$/.test(name)) {\n    warn(\n      'Invalid component name: \"' + name + '\". Component names ' +\n      'can only contain alphanumeric characters and the hyphen, ' +\n      'and must start with a letter.'\n    )\n  }\n  if (isBuiltInTag(name) || config.isReservedTag(name)) {\n    warn(\n      'Do not use built-in or reserved HTML elements as component ' +\n      'id: ' + name\n    )\n  }\n}\n\n/**\n * Ensure all props option syntax are normalized into the\n * Object-based format.\n */\nfunction normalizeProps (options: Object, vm: ?Component) {\n  const props = options.props\n  if (!props) return\n  const res = {}\n  let i, val, name\n  if (Array.isArray(props)) {\n    i = props.length\n    while (i--) {\n      val = props[i]\n      if (typeof val === 'string') {\n        name = camelize(val)\n        res[name] = { type: null }\n      } else if (process.env.NODE_ENV !== 'production') {\n        warn('props must be strings when using array syntax.')\n      }\n    }\n  } else if (isPlainObject(props)) {\n    for (const key in props) {\n      val = props[key]\n      name = camelize(key)\n      res[name] = isPlainObject(val)\n        ? val\n        : { type: val }\n    }\n  } else if (process.env.NODE_ENV !== 'production') {\n    warn(\n      `Invalid value for option \"props\": expected an Array or an Object, ` +\n      `but got ${toRawType(props)}.`,\n      vm\n    )\n  }\n  options.props = res\n}\n\n/**\n * Normalize all injections into Object-based format\n */\nfunction normalizeInject (options: Object, vm: ?Component) {\n  const inject = options.inject\n  if (!inject) return\n  const normalized = options.inject = {}\n  if (Array.isArray(inject)) {\n    for (let i = 0; i < inject.length; i++) {\n      normalized[inject[i]] = { from: inject[i] }\n    }\n  } else if (isPlainObject(inject)) {\n    for (const key in inject) {\n      const val = inject[key]\n      normalized[key] = isPlainObject(val)\n        ? extend({ from: key }, val)\n        : { from: val }\n    }\n  } else if (process.env.NODE_ENV !== 'production') {\n    warn(\n      `Invalid value for option \"inject\": expected an Array or an Object, ` +\n      `but got ${toRawType(inject)}.`,\n      vm\n    )\n  }\n}\n\n/**\n * Normalize raw function directives into object format.\n */\nfunction normalizeDirectives (options: Object) {\n  const dirs = options.directives\n  if (dirs) {\n    for (const key in dirs) {\n      const def = dirs[key]\n      if (typeof def === 'function') {\n        dirs[key] = { bind: def, update: def }\n      }\n    }\n  }\n}\n\nfunction assertObjectType (name: string, value: any, vm: ?Component) {\n  if (!isPlainObject(value)) {\n    warn(\n      `Invalid value for option \"${name}\": expected an Object, ` +\n      `but got ${toRawType(value)}.`,\n      vm\n    )\n  }\n}\n\n/**\n * Merge two option objects into a new one.\n * Core utility used in both instantiation and inheritance.\n */\nexport function mergeOptions (\n  parent: Object,\n  child: Object,\n  vm?: Component\n): Object {\n  if (process.env.NODE_ENV !== 'production') {\n    checkComponents(child)\n  }\n\n  if (typeof child === 'function') {\n    child = child.options\n  }\n\n  normalizeProps(child, vm)\n  normalizeInject(child, vm)\n  normalizeDirectives(child)\n  const extendsFrom = child.extends\n  if (extendsFrom) {\n    parent = mergeOptions(parent, extendsFrom, vm)\n  }\n  if (child.mixins) {\n    for (let i = 0, l = child.mixins.length; i < l; i++) {\n      parent = mergeOptions(parent, child.mixins[i], vm)\n    }\n  }\n  const options = {}\n  let key\n  for (key in parent) {\n    mergeField(key)\n  }\n  for (key in child) {\n    if (!hasOwn(parent, key)) {\n      mergeField(key)\n    }\n  }\n  function mergeField (key) {\n    const strat = strats[key] || defaultStrat\n    options[key] = strat(parent[key], child[key], vm, key)\n  }\n  return options\n}\n\n/**\n * Resolve an asset.\n * This function is used because child instances need access\n * to assets defined in its ancestor chain.\n */\nexport function resolveAsset (\n  options: Object,\n  type: string,\n  id: string,\n  warnMissing?: boolean\n): any {\n  /* istanbul ignore if */\n  if (typeof id !== 'string') {\n    return\n  }\n  const assets = options[type]\n  // check local registration variations first\n  if (hasOwn(assets, id)) return assets[id]\n  const camelizedId = camelize(id)\n  if (hasOwn(assets, camelizedId)) return assets[camelizedId]\n  const PascalCaseId = capitalize(camelizedId)\n  if (hasOwn(assets, PascalCaseId)) return assets[PascalCaseId]\n  // fallback to prototype chain\n  const res = assets[id] || assets[camelizedId] || assets[PascalCaseId]\n  if (process.env.NODE_ENV !== 'production' && warnMissing && !res) {\n    warn(\n      'Failed to resolve ' + type.slice(0, -1) + ': ' + id,\n      options\n    )\n  }\n  return res\n}\n"
  },
  {
    "path": "vue/src/core/util/perf.js",
    "content": "import { inBrowser } from './env'\n\nexport let mark\nexport let measure\n\nif (process.env.NODE_ENV !== 'production') {\n  const perf = inBrowser && window.performance\n  /* istanbul ignore if */\n  if (\n    perf &&\n    perf.mark &&\n    perf.measure &&\n    perf.clearMarks &&\n    perf.clearMeasures\n  ) {\n    mark = tag => perf.mark(tag)\n    measure = (name, startTag, endTag) => {\n      perf.measure(name, startTag, endTag)\n      perf.clearMarks(startTag)\n      perf.clearMarks(endTag)\n      perf.clearMeasures(name)\n    }\n  }\n}\n"
  },
  {
    "path": "vue/src/core/util/props.js",
    "content": "/* @flow */\n\nimport { warn } from './debug'\nimport { observe, toggleObserving, shouldObserve } from '../observer/index'\nimport {\n  hasOwn,\n  isObject,\n  toRawType,\n  hyphenate,\n  capitalize,\n  isPlainObject\n} from 'shared/util'\n\ntype PropOptions = {\n  type: Function | Array<Function> | null,\n  default: any,\n  required: ?boolean,\n  validator: ?Function\n};\n\nexport function validateProp (\n  key: string,\n  propOptions: Object,\n  propsData: Object,\n  vm?: Component\n): any {\n  const prop = propOptions[key]\n  const absent = !hasOwn(propsData, key)\n  let value = propsData[key]\n  // boolean casting\n  const booleanIndex = getTypeIndex(Boolean, prop.type)\n  if (booleanIndex > -1) {\n    if (absent && !hasOwn(prop, 'default')) {\n      value = false\n    } else if (value === '' || value === hyphenate(key)) {\n      // only cast empty string / same name to boolean if\n      // boolean has higher priority\n      const stringIndex = getTypeIndex(String, prop.type)\n      if (stringIndex < 0 || booleanIndex < stringIndex) {\n        value = true\n      }\n    }\n  }\n  // check default value\n  if (value === undefined) {\n    value = getPropDefaultValue(vm, prop, key)\n    // since the default value is a fresh copy,\n    // make sure to observe it.\n    const prevShouldObserve = shouldObserve\n    toggleObserving(true)\n    observe(value)\n    toggleObserving(prevShouldObserve)\n  }\n  if (\n    process.env.NODE_ENV !== 'production' &&\n    // skip validation for weex recycle-list child component props\n    !(__WEEX__ && isObject(value) && ('@binding' in value))\n  ) {\n    assertProp(prop, key, value, vm, absent)\n  }\n  return value\n}\n\n/**\n * Get the default value of a prop.\n */\nfunction getPropDefaultValue (vm: ?Component, prop: PropOptions, key: string): any {\n  // no default, return undefined\n  if (!hasOwn(prop, 'default')) {\n    return undefined\n  }\n  const def = prop.default\n  // warn against non-factory defaults for Object & Array\n  if (process.env.NODE_ENV !== 'production' && isObject(def)) {\n    warn(\n      'Invalid default value for prop \"' + key + '\": ' +\n      'Props with type Object/Array must use a factory function ' +\n      'to return the default value.',\n      vm\n    )\n  }\n  // the raw prop value was also undefined from previous render,\n  // return previous default value to avoid unnecessary watcher trigger\n  if (vm && vm.$options.propsData &&\n    vm.$options.propsData[key] === undefined &&\n    vm._props[key] !== undefined\n  ) {\n    return vm._props[key]\n  }\n  // call factory function for non-Function types\n  // a value is Function if its prototype is function even across different execution context\n  return typeof def === 'function' && getType(prop.type) !== 'Function'\n    ? def.call(vm)\n    : def\n}\n\n/**\n * Assert whether a prop is valid.\n */\nfunction assertProp (\n  prop: PropOptions,\n  name: string,\n  value: any,\n  vm: ?Component,\n  absent: boolean\n) {\n  if (prop.required && absent) {\n    warn(\n      'Missing required prop: \"' + name + '\"',\n      vm\n    )\n    return\n  }\n  if (value == null && !prop.required) {\n    return\n  }\n  let type = prop.type\n  let valid = !type || type === true\n  const expectedTypes = []\n  if (type) {\n    if (!Array.isArray(type)) {\n      type = [type]\n    }\n    for (let i = 0; i < type.length && !valid; i++) {\n      const assertedType = assertType(value, type[i])\n      expectedTypes.push(assertedType.expectedType || '')\n      valid = assertedType.valid\n    }\n  }\n  if (!valid) {\n    warn(\n      `Invalid prop: type check failed for prop \"${name}\".` +\n      ` Expected ${expectedTypes.map(capitalize).join(', ')}` +\n      `, got ${toRawType(value)}.`,\n      vm\n    )\n    return\n  }\n  const validator = prop.validator\n  if (validator) {\n    if (!validator(value)) {\n      warn(\n        'Invalid prop: custom validator check failed for prop \"' + name + '\".',\n        vm\n      )\n    }\n  }\n}\n\nconst simpleCheckRE = /^(String|Number|Boolean|Function|Symbol)$/\n\nfunction assertType (value: any, type: Function): {\n  valid: boolean;\n  expectedType: string;\n} {\n  let valid\n  const expectedType = getType(type)\n  if (simpleCheckRE.test(expectedType)) {\n    const t = typeof value\n    valid = t === expectedType.toLowerCase()\n    // for primitive wrapper objects\n    if (!valid && t === 'object') {\n      valid = value instanceof type\n    }\n  } else if (expectedType === 'Object') {\n    valid = isPlainObject(value)\n  } else if (expectedType === 'Array') {\n    valid = Array.isArray(value)\n  } else {\n    valid = value instanceof type\n  }\n  return {\n    valid,\n    expectedType\n  }\n}\n\n/**\n * Use function string name to check built-in types,\n * because a simple equality check will fail when running\n * across different vms / iframes.\n */\nfunction getType (fn) {\n  const match = fn && fn.toString().match(/^\\s*function (\\w+)/)\n  return match ? match[1] : ''\n}\n\nfunction isSameType (a, b) {\n  return getType(a) === getType(b)\n}\n\nfunction getTypeIndex (type, expectedTypes): number {\n  if (!Array.isArray(expectedTypes)) {\n    return isSameType(expectedTypes, type) ? 0 : -1\n  }\n  for (let i = 0, len = expectedTypes.length; i < len; i++) {\n    if (isSameType(expectedTypes[i], type)) {\n      return i\n    }\n  }\n  return -1\n}\n"
  },
  {
    "path": "vue/src/core/vdom/create-component.js",
    "content": "/* @flow */\n\nimport VNode from './vnode'\nimport { resolveConstructorOptions } from 'core/instance/init'\nimport { queueActivatedComponent } from 'core/observer/scheduler'\nimport { createFunctionalComponent } from './create-functional-component'\n\nimport {\n  warn,\n  isDef,\n  isUndef,\n  isTrue,\n  isObject\n} from '../util/index'\n\nimport {\n  resolveAsyncComponent,\n  createAsyncPlaceholder,\n  extractPropsFromVNodeData\n} from './helpers/index'\n\nimport {\n  callHook,\n  activeInstance,\n  updateChildComponent,\n  activateChildComponent,\n  deactivateChildComponent\n} from '../instance/lifecycle'\n\nimport {\n  isRecyclableComponent,\n  renderRecyclableComponentTemplate\n} from 'weex/runtime/recycle-list/render-component-template'\n\n// inline hooks to be invoked on component VNodes during patch\nconst componentVNodeHooks = {\n  init (vnode: VNodeWithData, hydrating: boolean): ?boolean {\n    if (\n      vnode.componentInstance &&\n      !vnode.componentInstance._isDestroyed &&\n      vnode.data.keepAlive\n    ) {\n      // kept-alive components, treat as a patch\n      const mountedNode: any = vnode // work around flow\n      componentVNodeHooks.prepatch(mountedNode, mountedNode)\n    } else {\n      const child = vnode.componentInstance = createComponentInstanceForVnode(\n        vnode,\n        activeInstance\n      )\n      child.$mount(hydrating ? vnode.elm : undefined, hydrating)\n    }\n  },\n\n  prepatch (oldVnode: MountedComponentVNode, vnode: MountedComponentVNode) {\n    const options = vnode.componentOptions\n    const child = vnode.componentInstance = oldVnode.componentInstance\n    updateChildComponent(\n      child,\n      options.propsData, // updated props\n      options.listeners, // updated listeners\n      vnode, // new parent vnode\n      options.children // new children\n    )\n  },\n\n  insert (vnode: MountedComponentVNode) {\n    const { context, componentInstance } = vnode\n    if (!componentInstance._isMounted) {\n      componentInstance._isMounted = true\n      callHook(componentInstance, 'mounted')\n    }\n    if (vnode.data.keepAlive) {\n      if (context._isMounted) {\n        // vue-router#1212\n        // During updates, a kept-alive component's child components may\n        // change, so directly walking the tree here may call activated hooks\n        // on incorrect children. Instead we push them into a queue which will\n        // be processed after the whole patch process ended.\n        queueActivatedComponent(componentInstance)\n      } else {\n        activateChildComponent(componentInstance, true /* direct */)\n      }\n    }\n  },\n\n  destroy (vnode: MountedComponentVNode) {\n    const { componentInstance } = vnode\n    if (!componentInstance._isDestroyed) {\n      if (!vnode.data.keepAlive) {\n        componentInstance.$destroy()\n      } else {\n        deactivateChildComponent(componentInstance, true /* direct */)\n      }\n    }\n  }\n}\n\nconst hooksToMerge = Object.keys(componentVNodeHooks)\n\nexport function createComponent (\n  Ctor: Class<Component> | Function | Object | void,\n  data: ?VNodeData,\n  context: Component,\n  children: ?Array<VNode>,\n  tag?: string\n): VNode | Array<VNode> | void {\n  if (isUndef(Ctor)) {\n    return\n  }\n\n  const baseCtor = context.$options._base\n\n  // plain options object: turn it into a constructor\n  if (isObject(Ctor)) {\n    Ctor = baseCtor.extend(Ctor)\n  }\n\n  // if at this stage it's not a constructor or an async component factory,\n  // reject.\n  if (typeof Ctor !== 'function') {\n    if (process.env.NODE_ENV !== 'production') {\n      warn(`Invalid Component definition: ${String(Ctor)}`, context)\n    }\n    return\n  }\n\n  // async component\n  let asyncFactory\n  if (isUndef(Ctor.cid)) {\n    asyncFactory = Ctor\n    Ctor = resolveAsyncComponent(asyncFactory, baseCtor, context)\n    if (Ctor === undefined) {\n      // return a placeholder node for async component, which is rendered\n      // as a comment node but preserves all the raw information for the node.\n      // the information will be used for async server-rendering and hydration.\n      return createAsyncPlaceholder(\n        asyncFactory,\n        data,\n        context,\n        children,\n        tag\n      )\n    }\n  }\n\n  data = data || {}\n\n  // resolve constructor options in case global mixins are applied after\n  // component constructor creation\n  resolveConstructorOptions(Ctor)\n\n  // transform component v-model data into props & events\n  if (isDef(data.model)) {\n    transformModel(Ctor.options, data)\n  }\n\n  // extract props\n  const propsData = extractPropsFromVNodeData(data, Ctor, tag)\n\n  // functional component\n  if (isTrue(Ctor.options.functional)) {\n    return createFunctionalComponent(Ctor, propsData, data, context, children)\n  }\n\n  // extract listeners, since these needs to be treated as\n  // child component listeners instead of DOM listeners\n  const listeners = data.on\n  // replace with listeners with .native modifier\n  // so it gets processed during parent component patch.\n  data.on = data.nativeOn\n\n  if (isTrue(Ctor.options.abstract)) {\n    // abstract components do not keep anything\n    // other than props & listeners & slot\n\n    // work around flow\n    const slot = data.slot\n    data = {}\n    if (slot) {\n      data.slot = slot\n    }\n  }\n\n  // install component management hooks onto the placeholder node\n  installComponentHooks(data)\n\n  // return a placeholder vnode\n  const name = Ctor.options.name || tag\n  const vnode = new VNode(\n    `vue-component-${Ctor.cid}${name ? `-${name}` : ''}`,\n    data, undefined, undefined, undefined, context,\n    { Ctor, propsData, listeners, tag, children },\n    asyncFactory\n  )\n\n  // Weex specific: invoke recycle-list optimized @render function for\n  // extracting cell-slot template.\n  // https://github.com/Hanks10100/weex-native-directive/tree/master/component\n  /* istanbul ignore if */\n  if (__WEEX__ && isRecyclableComponent(vnode)) {\n    return renderRecyclableComponentTemplate(vnode)\n  }\n\n  return vnode\n}\n\nexport function createComponentInstanceForVnode (\n  vnode: any, // we know it's MountedComponentVNode but flow doesn't\n  parent: any, // activeInstance in lifecycle state\n): Component {\n  const options: InternalComponentOptions = {\n    _isComponent: true,\n    _parentVnode: vnode,\n    parent\n  }\n  // check inline-template render functions\n  const inlineTemplate = vnode.data.inlineTemplate\n  if (isDef(inlineTemplate)) {\n    options.render = inlineTemplate.render\n    options.staticRenderFns = inlineTemplate.staticRenderFns\n  }\n  return new vnode.componentOptions.Ctor(options)\n}\n\nfunction installComponentHooks (data: VNodeData) {\n  const hooks = data.hook || (data.hook = {})\n  for (let i = 0; i < hooksToMerge.length; i++) {\n    const key = hooksToMerge[i]\n    const existing = hooks[key]\n    const toMerge = componentVNodeHooks[key]\n    if (existing !== toMerge && !(existing && existing._merged)) {\n      hooks[key] = existing ? mergeHook(toMerge, existing) : toMerge\n    }\n  }\n}\n\nfunction mergeHook (f1: any, f2: any): Function {\n  const merged = (a, b) => {\n    // flow complains about extra args which is why we use any\n    f1(a, b)\n    f2(a, b)\n  }\n  merged._merged = true\n  return merged\n}\n\n// transform component v-model info (value and callback) into\n// prop and event handler respectively.\nfunction transformModel (options, data: any) {\n  const prop = (options.model && options.model.prop) || 'value'\n  const event = (options.model && options.model.event) || 'input'\n  ;(data.props || (data.props = {}))[prop] = data.model.value\n  const on = data.on || (data.on = {})\n  if (isDef(on[event])) {\n    on[event] = [data.model.callback].concat(on[event])\n  } else {\n    on[event] = data.model.callback\n  }\n}\n"
  },
  {
    "path": "vue/src/core/vdom/create-element.js",
    "content": "/* @flow */\n\nimport config from '../config'\nimport VNode, { createEmptyVNode } from './vnode'\nimport { createComponent } from './create-component'\nimport { traverse } from '../observer/traverse'\n\nimport {\n  warn,\n  isDef,\n  isUndef,\n  isTrue,\n  isObject,\n  isPrimitive,\n  resolveAsset\n} from '../util/index'\n\nimport {\n  normalizeChildren,\n  simpleNormalizeChildren\n} from './helpers/index'\n\nconst SIMPLE_NORMALIZE = 1\nconst ALWAYS_NORMALIZE = 2\n\n// wrapper function for providing a more flexible interface\n// without getting yelled at by flow\nexport function createElement (\n  context: Component,\n  tag: any,\n  data: any,\n  children: any,\n  normalizationType: any,\n  alwaysNormalize: boolean\n): VNode | Array<VNode> {\n  if (Array.isArray(data) || isPrimitive(data)) {\n    normalizationType = children\n    children = data\n    data = undefined\n  }\n  if (isTrue(alwaysNormalize)) {\n    normalizationType = ALWAYS_NORMALIZE\n  }\n  return _createElement(context, tag, data, children, normalizationType)\n}\n\nexport function _createElement (\n  context: Component,\n  tag?: string | Class<Component> | Function | Object,\n  data?: VNodeData,\n  children?: any,\n  normalizationType?: number\n): VNode | Array<VNode> {\n  if (isDef(data) && isDef((data: any).__ob__)) {\n    process.env.NODE_ENV !== 'production' && warn(\n      `Avoid using observed data object as vnode data: ${JSON.stringify(data)}\\n` +\n      'Always create fresh vnode data objects in each render!',\n      context\n    )\n    return createEmptyVNode()\n  }\n  // object syntax in v-bind\n  if (isDef(data) && isDef(data.is)) {\n    tag = data.is\n  }\n  if (!tag) {\n    // in case of component :is set to falsy value\n    return createEmptyVNode()\n  }\n  // warn against non-primitive key\n  if (process.env.NODE_ENV !== 'production' &&\n    isDef(data) && isDef(data.key) && !isPrimitive(data.key)\n  ) {\n    if (!__WEEX__ || !('@binding' in data.key)) {\n      warn(\n        'Avoid using non-primitive value as key, ' +\n        'use string/number value instead.',\n        context\n      )\n    }\n  }\n  // support single function children as default scoped slot\n  if (Array.isArray(children) &&\n    typeof children[0] === 'function'\n  ) {\n    data = data || {}\n    data.scopedSlots = { default: children[0] }\n    children.length = 0\n  }\n  if (normalizationType === ALWAYS_NORMALIZE) {\n    children = normalizeChildren(children)\n  } else if (normalizationType === SIMPLE_NORMALIZE) {\n    children = simpleNormalizeChildren(children)\n  }\n  let vnode, ns\n  if (typeof tag === 'string') {\n    let Ctor\n    ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag)\n    if (config.isReservedTag(tag)) {\n      // platform built-in elements\n      vnode = new VNode(\n        config.parsePlatformTagName(tag), data, children,\n        undefined, undefined, context\n      )\n    } else if (isDef(Ctor = resolveAsset(context.$options, 'components', tag))) {\n      // component\n      vnode = createComponent(Ctor, data, context, children, tag)\n    } else {\n      // unknown or unlisted namespaced elements\n      // check at runtime because it may get assigned a namespace when its\n      // parent normalizes children\n      vnode = new VNode(\n        tag, data, children,\n        undefined, undefined, context\n      )\n    }\n  } else {\n    // direct component options / constructor\n    vnode = createComponent(tag, data, context, children)\n  }\n  if (Array.isArray(vnode)) {\n    return vnode\n  } else if (isDef(vnode)) {\n    if (isDef(ns)) applyNS(vnode, ns)\n    if (isDef(data)) registerDeepBindings(data)\n    return vnode\n  } else {\n    return createEmptyVNode()\n  }\n}\n\nfunction applyNS (vnode, ns, force) {\n  vnode.ns = ns\n  if (vnode.tag === 'foreignObject') {\n    // use default namespace inside foreignObject\n    ns = undefined\n    force = true\n  }\n  if (isDef(vnode.children)) {\n    for (let i = 0, l = vnode.children.length; i < l; i++) {\n      const child = vnode.children[i]\n      if (isDef(child.tag) && (\n        isUndef(child.ns) || (isTrue(force) && child.tag !== 'svg'))) {\n        applyNS(child, ns, force)\n      }\n    }\n  }\n}\n\n// ref #5318\n// necessary to ensure parent re-render when deep bindings like :style and\n// :class are used on slot nodes\nfunction registerDeepBindings (data) {\n  if (isObject(data.style)) {\n    traverse(data.style)\n  }\n  if (isObject(data.class)) {\n    traverse(data.class)\n  }\n}\n"
  },
  {
    "path": "vue/src/core/vdom/create-functional-component.js",
    "content": "/* @flow */\n\nimport VNode, { cloneVNode } from './vnode'\nimport { createElement } from './create-element'\nimport { resolveInject } from '../instance/inject'\nimport { normalizeChildren } from '../vdom/helpers/normalize-children'\nimport { resolveSlots } from '../instance/render-helpers/resolve-slots'\nimport { installRenderHelpers } from '../instance/render-helpers/index'\n\nimport {\n  isDef,\n  isTrue,\n  hasOwn,\n  camelize,\n  emptyObject,\n  validateProp\n} from '../util/index'\n\nexport function FunctionalRenderContext (\n  data: VNodeData,\n  props: Object,\n  children: ?Array<VNode>,\n  parent: Component,\n  Ctor: Class<Component>\n) {\n  const options = Ctor.options\n  // ensure the createElement function in functional components\n  // gets a unique context - this is necessary for correct named slot check\n  let contextVm\n  if (hasOwn(parent, '_uid')) {\n    contextVm = Object.create(parent)\n    // $flow-disable-line\n    contextVm._original = parent\n  } else {\n    // the context vm passed in is a functional context as well.\n    // in this case we want to make sure we are able to get a hold to the\n    // real context instance.\n    contextVm = parent\n    // $flow-disable-line\n    parent = parent._original\n  }\n  const isCompiled = isTrue(options._compiled)\n  const needNormalization = !isCompiled\n\n  this.data = data\n  this.props = props\n  this.children = children\n  this.parent = parent\n  this.listeners = data.on || emptyObject\n  this.injections = resolveInject(options.inject, parent)\n  this.slots = () => resolveSlots(children, parent)\n\n  // support for compiled functional template\n  if (isCompiled) {\n    // exposing $options for renderStatic()\n    this.$options = options\n    // pre-resolve slots for renderSlot()\n    this.$slots = this.slots()\n    this.$scopedSlots = data.scopedSlots || emptyObject\n  }\n\n  if (options._scopeId) {\n    this._c = (a, b, c, d) => {\n      const vnode = createElement(contextVm, a, b, c, d, needNormalization)\n      if (vnode && !Array.isArray(vnode)) {\n        vnode.fnScopeId = options._scopeId\n        vnode.fnContext = parent\n      }\n      return vnode\n    }\n  } else {\n    this._c = (a, b, c, d) => createElement(contextVm, a, b, c, d, needNormalization)\n  }\n}\n\ninstallRenderHelpers(FunctionalRenderContext.prototype)\n\nexport function createFunctionalComponent (\n  Ctor: Class<Component>,\n  propsData: ?Object,\n  data: VNodeData,\n  contextVm: Component,\n  children: ?Array<VNode>\n): VNode | Array<VNode> | void {\n  const options = Ctor.options\n  const props = {}\n  const propOptions = options.props\n  if (isDef(propOptions)) {\n    for (const key in propOptions) {\n      props[key] = validateProp(key, propOptions, propsData || emptyObject)\n    }\n  } else {\n    if (isDef(data.attrs)) mergeProps(props, data.attrs)\n    if (isDef(data.props)) mergeProps(props, data.props)\n  }\n\n  const renderContext = new FunctionalRenderContext(\n    data,\n    props,\n    children,\n    contextVm,\n    Ctor\n  )\n\n  const vnode = options.render.call(null, renderContext._c, renderContext)\n\n  if (vnode instanceof VNode) {\n    return cloneAndMarkFunctionalResult(vnode, data, renderContext.parent, options)\n  } else if (Array.isArray(vnode)) {\n    const vnodes = normalizeChildren(vnode) || []\n    const res = new Array(vnodes.length)\n    for (let i = 0; i < vnodes.length; i++) {\n      res[i] = cloneAndMarkFunctionalResult(vnodes[i], data, renderContext.parent, options)\n    }\n    return res\n  }\n}\n\nfunction cloneAndMarkFunctionalResult (vnode, data, contextVm, options) {\n  // #7817 clone node before setting fnContext, otherwise if the node is reused\n  // (e.g. it was from a cached normal slot) the fnContext causes named slots\n  // that should not be matched to match.\n  const clone = cloneVNode(vnode)\n  clone.fnContext = contextVm\n  clone.fnOptions = options\n  if (data.slot) {\n    (clone.data || (clone.data = {})).slot = data.slot\n  }\n  return clone\n}\n\nfunction mergeProps (to, from) {\n  for (const key in from) {\n    to[camelize(key)] = from[key]\n  }\n}\n"
  },
  {
    "path": "vue/src/core/vdom/helpers/extract-props.js",
    "content": "/* @flow */\n\nimport {\n  tip,\n  hasOwn,\n  isDef,\n  isUndef,\n  hyphenate,\n  formatComponentName\n} from 'core/util/index'\n\nexport function extractPropsFromVNodeData (\n  data: VNodeData,\n  Ctor: Class<Component>,\n  tag?: string\n): ?Object {\n  // we are only extracting raw values here.\n  // validation and default values are handled in the child\n  // component itself.\n  const propOptions = Ctor.options.props\n  if (isUndef(propOptions)) {\n    return\n  }\n  const res = {}\n  const { attrs, props } = data\n  if (isDef(attrs) || isDef(props)) {\n    for (const key in propOptions) {\n      const altKey = hyphenate(key)\n      if (process.env.NODE_ENV !== 'production') {\n        const keyInLowerCase = key.toLowerCase()\n        if (\n          key !== keyInLowerCase &&\n          attrs && hasOwn(attrs, keyInLowerCase)\n        ) {\n          tip(\n            `Prop \"${keyInLowerCase}\" is passed to component ` +\n            `${formatComponentName(tag || Ctor)}, but the declared prop name is` +\n            ` \"${key}\". ` +\n            `Note that HTML attributes are case-insensitive and camelCased ` +\n            `props need to use their kebab-case equivalents when using in-DOM ` +\n            `templates. You should probably use \"${altKey}\" instead of \"${key}\".`\n          )\n        }\n      }\n      checkProp(res, props, key, altKey, true) ||\n      checkProp(res, attrs, key, altKey, false)\n    }\n  }\n  return res\n}\n\nfunction checkProp (\n  res: Object,\n  hash: ?Object,\n  key: string,\n  altKey: string,\n  preserve: boolean\n): boolean {\n  if (isDef(hash)) {\n    if (hasOwn(hash, key)) {\n      res[key] = hash[key]\n      if (!preserve) {\n        delete hash[key]\n      }\n      return true\n    } else if (hasOwn(hash, altKey)) {\n      res[key] = hash[altKey]\n      if (!preserve) {\n        delete hash[altKey]\n      }\n      return true\n    }\n  }\n  return false\n}\n"
  },
  {
    "path": "vue/src/core/vdom/helpers/get-first-component-child.js",
    "content": "/* @flow */\n\nimport { isDef } from 'shared/util'\nimport { isAsyncPlaceholder } from './is-async-placeholder'\n\nexport function getFirstComponentChild (children: ?Array<VNode>): ?VNode {\n  if (Array.isArray(children)) {\n    for (let i = 0; i < children.length; i++) {\n      const c = children[i]\n      if (isDef(c) && (isDef(c.componentOptions) || isAsyncPlaceholder(c))) {\n        return c\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "vue/src/core/vdom/helpers/index.js",
    "content": "/* @flow */\n\nexport * from './merge-hook'\nexport * from './extract-props'\nexport * from './update-listeners'\nexport * from './normalize-children'\nexport * from './resolve-async-component'\nexport * from './get-first-component-child'\nexport * from './is-async-placeholder'\n"
  },
  {
    "path": "vue/src/core/vdom/helpers/is-async-placeholder.js",
    "content": "/* @flow */\n\nexport function isAsyncPlaceholder (node: VNode): boolean {\n  return node.isComment && node.asyncFactory\n}\n"
  },
  {
    "path": "vue/src/core/vdom/helpers/merge-hook.js",
    "content": "/* @flow */\n\nimport VNode from '../vnode'\nimport { createFnInvoker } from './update-listeners'\nimport { remove, isDef, isUndef, isTrue } from 'shared/util'\n\nexport function mergeVNodeHook (def: Object, hookKey: string, hook: Function) {\n  if (def instanceof VNode) {\n    def = def.data.hook || (def.data.hook = {})\n  }\n  let invoker\n  const oldHook = def[hookKey]\n\n  function wrappedHook () {\n    hook.apply(this, arguments)\n    // important: remove merged hook to ensure it's called only once\n    // and prevent memory leak\n    remove(invoker.fns, wrappedHook)\n  }\n\n  if (isUndef(oldHook)) {\n    // no existing hook\n    invoker = createFnInvoker([wrappedHook])\n  } else {\n    /* istanbul ignore if */\n    if (isDef(oldHook.fns) && isTrue(oldHook.merged)) {\n      // already a merged invoker\n      invoker = oldHook\n      invoker.fns.push(wrappedHook)\n    } else {\n      // existing plain hook\n      invoker = createFnInvoker([oldHook, wrappedHook])\n    }\n  }\n\n  invoker.merged = true\n  def[hookKey] = invoker\n}\n"
  },
  {
    "path": "vue/src/core/vdom/helpers/normalize-children.js",
    "content": "/* @flow */\n\nimport VNode, { createTextVNode } from 'core/vdom/vnode'\nimport { isFalse, isTrue, isDef, isUndef, isPrimitive } from 'shared/util'\n\n// The template compiler attempts to minimize the need for normalization by\n// statically analyzing the template at compile time.\n//\n// For plain HTML markup, normalization can be completely skipped because the\n// generated render function is guaranteed to return Array<VNode>. There are\n// two cases where extra normalization is needed:\n\n// 1. When the children contains components - because a functional component\n// may return an Array instead of a single root. In this case, just a simple\n// normalization is needed - if any child is an Array, we flatten the whole\n// thing with Array.prototype.concat. It is guaranteed to be only 1-level deep\n// because functional components already normalize their own children.\nexport function simpleNormalizeChildren (children: any) {\n  for (let i = 0; i < children.length; i++) {\n    if (Array.isArray(children[i])) {\n      return Array.prototype.concat.apply([], children)\n    }\n  }\n  return children\n}\n\n// 2. When the children contains constructs that always generated nested Arrays,\n// e.g. <template>, <slot>, v-for, or when the children is provided by user\n// with hand-written render functions / JSX. In such cases a full normalization\n// is needed to cater to all possible types of children values.\nexport function normalizeChildren (children: any): ?Array<VNode> {\n  return isPrimitive(children)\n    ? [createTextVNode(children)]\n    : Array.isArray(children)\n      ? normalizeArrayChildren(children)\n      : undefined\n}\n\nfunction isTextNode (node): boolean {\n  return isDef(node) && isDef(node.text) && isFalse(node.isComment)\n}\n\nfunction normalizeArrayChildren (children: any, nestedIndex?: string): Array<VNode> {\n  const res = []\n  let i, c, lastIndex, last\n  for (i = 0; i < children.length; i++) {\n    c = children[i]\n    if (isUndef(c) || typeof c === 'boolean') continue\n    lastIndex = res.length - 1\n    last = res[lastIndex]\n    //  nested\n    if (Array.isArray(c)) {\n      if (c.length > 0) {\n        c = normalizeArrayChildren(c, `${nestedIndex || ''}_${i}`)\n        // merge adjacent text nodes\n        if (isTextNode(c[0]) && isTextNode(last)) {\n          res[lastIndex] = createTextVNode(last.text + (c[0]: any).text)\n          c.shift()\n        }\n        res.push.apply(res, c)\n      }\n    } else if (isPrimitive(c)) {\n      if (isTextNode(last)) {\n        // merge adjacent text nodes\n        // this is necessary for SSR hydration because text nodes are\n        // essentially merged when rendered to HTML strings\n        res[lastIndex] = createTextVNode(last.text + c)\n      } else if (c !== '') {\n        // convert primitive to vnode\n        res.push(createTextVNode(c))\n      }\n    } else {\n      if (isTextNode(c) && isTextNode(last)) {\n        // merge adjacent text nodes\n        res[lastIndex] = createTextVNode(last.text + c.text)\n      } else {\n        // default key for nested array children (likely generated by v-for)\n        if (isTrue(children._isVList) &&\n          isDef(c.tag) &&\n          isUndef(c.key) &&\n          isDef(nestedIndex)) {\n          c.key = `__vlist${nestedIndex}_${i}__`\n        }\n        res.push(c)\n      }\n    }\n  }\n  return res\n}\n"
  },
  {
    "path": "vue/src/core/vdom/helpers/resolve-async-component.js",
    "content": "/* @flow */\n\nimport {\n  warn,\n  once,\n  isDef,\n  isUndef,\n  isTrue,\n  isObject,\n  hasSymbol\n} from 'core/util/index'\n\nimport { createEmptyVNode } from 'core/vdom/vnode'\n\nfunction ensureCtor (comp: any, base) {\n  if (\n    comp.__esModule ||\n    (hasSymbol && comp[Symbol.toStringTag] === 'Module')\n  ) {\n    comp = comp.default\n  }\n  return isObject(comp)\n    ? base.extend(comp)\n    : comp\n}\n\nexport function createAsyncPlaceholder (\n  factory: Function,\n  data: ?VNodeData,\n  context: Component,\n  children: ?Array<VNode>,\n  tag: ?string\n): VNode {\n  const node = createEmptyVNode()\n  node.asyncFactory = factory\n  node.asyncMeta = { data, context, children, tag }\n  return node\n}\n\nexport function resolveAsyncComponent (\n  factory: Function,\n  baseCtor: Class<Component>,\n  context: Component\n): Class<Component> | void {\n  if (isTrue(factory.error) && isDef(factory.errorComp)) {\n    return factory.errorComp\n  }\n\n  if (isDef(factory.resolved)) {\n    return factory.resolved\n  }\n\n  if (isTrue(factory.loading) && isDef(factory.loadingComp)) {\n    return factory.loadingComp\n  }\n\n  if (isDef(factory.contexts)) {\n    // already pending\n    factory.contexts.push(context)\n  } else {\n    const contexts = factory.contexts = [context]\n    let sync = true\n\n    const forceRender = () => {\n      for (let i = 0, l = contexts.length; i < l; i++) {\n        contexts[i].$forceUpdate()\n      }\n    }\n\n    const resolve = once((res: Object | Class<Component>) => {\n      // cache resolved\n      factory.resolved = ensureCtor(res, baseCtor)\n      // invoke callbacks only if this is not a synchronous resolve\n      // (async resolves are shimmed as synchronous during SSR)\n      if (!sync) {\n        forceRender()\n      }\n    })\n\n    const reject = once(reason => {\n      process.env.NODE_ENV !== 'production' && warn(\n        `Failed to resolve async component: ${String(factory)}` +\n        (reason ? `\\nReason: ${reason}` : '')\n      )\n      if (isDef(factory.errorComp)) {\n        factory.error = true\n        forceRender()\n      }\n    })\n\n    const res = factory(resolve, reject)\n\n    if (isObject(res)) {\n      if (typeof res.then === 'function') {\n        // () => Promise\n        if (isUndef(factory.resolved)) {\n          res.then(resolve, reject)\n        }\n      } else if (isDef(res.component) && typeof res.component.then === 'function') {\n        res.component.then(resolve, reject)\n\n        if (isDef(res.error)) {\n          factory.errorComp = ensureCtor(res.error, baseCtor)\n        }\n\n        if (isDef(res.loading)) {\n          factory.loadingComp = ensureCtor(res.loading, baseCtor)\n          if (res.delay === 0) {\n            factory.loading = true\n          } else {\n            setTimeout(() => {\n              if (isUndef(factory.resolved) && isUndef(factory.error)) {\n                factory.loading = true\n                forceRender()\n              }\n            }, res.delay || 200)\n          }\n        }\n\n        if (isDef(res.timeout)) {\n          setTimeout(() => {\n            if (isUndef(factory.resolved)) {\n              reject(\n                process.env.NODE_ENV !== 'production'\n                  ? `timeout (${res.timeout}ms)`\n                  : null\n              )\n            }\n          }, res.timeout)\n        }\n      }\n    }\n\n    sync = false\n    // return in case resolved synchronously\n    return factory.loading\n      ? factory.loadingComp\n      : factory.resolved\n  }\n}\n"
  },
  {
    "path": "vue/src/core/vdom/helpers/update-listeners.js",
    "content": "/* @flow */\n\nimport { warn } from 'core/util/index'\nimport { cached, isUndef, isPlainObject } from 'shared/util'\n\nconst normalizeEvent = cached((name: string): {\n  name: string,\n  once: boolean,\n  capture: boolean,\n  passive: boolean,\n  handler?: Function,\n  params?: Array<any>\n} => {\n  const passive = name.charAt(0) === '&'\n  name = passive ? name.slice(1) : name\n  const once = name.charAt(0) === '~' // Prefixed last, checked first\n  name = once ? name.slice(1) : name\n  const capture = name.charAt(0) === '!'\n  name = capture ? name.slice(1) : name\n  return {\n    name,\n    once,\n    capture,\n    passive\n  }\n})\n\nexport function createFnInvoker (fns: Function | Array<Function>): Function {\n  function invoker () {\n    const fns = invoker.fns\n    if (Array.isArray(fns)) {\n      const cloned = fns.slice()\n      for (let i = 0; i < cloned.length; i++) {\n        cloned[i].apply(null, arguments)\n      }\n    } else {\n      // return handler return value for single handlers\n      return fns.apply(null, arguments)\n    }\n  }\n  invoker.fns = fns\n  return invoker\n}\n\nexport function updateListeners (\n  on: Object,\n  oldOn: Object,\n  add: Function,\n  remove: Function,\n  vm: Component\n) {\n  let name, def, cur, old, event\n  for (name in on) {\n    def = cur = on[name]\n    old = oldOn[name]\n    event = normalizeEvent(name)\n    /* istanbul ignore if */\n    if (__WEEX__ && isPlainObject(def)) {\n      cur = def.handler\n      event.params = def.params\n    }\n    if (isUndef(cur)) {\n      process.env.NODE_ENV !== 'production' && warn(\n        `Invalid handler for event \"${event.name}\": got ` + String(cur),\n        vm\n      )\n    } else if (isUndef(old)) {\n      if (isUndef(cur.fns)) {\n        cur = on[name] = createFnInvoker(cur)\n      }\n      add(event.name, cur, event.once, event.capture, event.passive, event.params)\n    } else if (cur !== old) {\n      old.fns = cur\n      on[name] = old\n    }\n  }\n  for (name in oldOn) {\n    if (isUndef(on[name])) {\n      event = normalizeEvent(name)\n      remove(event.name, oldOn[name], event.capture)\n    }\n  }\n}\n"
  },
  {
    "path": "vue/src/core/vdom/modules/directives.js",
    "content": "/* @flow */\n\nimport { emptyNode } from 'core/vdom/patch'\nimport { resolveAsset, handleError } from 'core/util/index'\nimport { mergeVNodeHook } from 'core/vdom/helpers/index'\n\nexport default {\n  create: updateDirectives,\n  update: updateDirectives,\n  destroy: function unbindDirectives (vnode: VNodeWithData) {\n    updateDirectives(vnode, emptyNode)\n  }\n}\n\nfunction updateDirectives (oldVnode: VNodeWithData, vnode: VNodeWithData) {\n  if (oldVnode.data.directives || vnode.data.directives) {\n    _update(oldVnode, vnode)\n  }\n}\n\nfunction _update (oldVnode, vnode) {\n  const isCreate = oldVnode === emptyNode\n  const isDestroy = vnode === emptyNode\n  const oldDirs = normalizeDirectives(oldVnode.data.directives, oldVnode.context)\n  const newDirs = normalizeDirectives(vnode.data.directives, vnode.context)\n\n  const dirsWithInsert = []\n  const dirsWithPostpatch = []\n\n  let key, oldDir, dir\n  for (key in newDirs) {\n    oldDir = oldDirs[key]\n    dir = newDirs[key]\n    if (!oldDir) {\n      // new directive, bind\n      callHook(dir, 'bind', vnode, oldVnode)\n      if (dir.def && dir.def.inserted) {\n        dirsWithInsert.push(dir)\n      }\n    } else {\n      // existing directive, update\n      dir.oldValue = oldDir.value\n      callHook(dir, 'update', vnode, oldVnode)\n      if (dir.def && dir.def.componentUpdated) {\n        dirsWithPostpatch.push(dir)\n      }\n    }\n  }\n\n  if (dirsWithInsert.length) {\n    const callInsert = () => {\n      for (let i = 0; i < dirsWithInsert.length; i++) {\n        callHook(dirsWithInsert[i], 'inserted', vnode, oldVnode)\n      }\n    }\n    if (isCreate) {\n      mergeVNodeHook(vnode, 'insert', callInsert)\n    } else {\n      callInsert()\n    }\n  }\n\n  if (dirsWithPostpatch.length) {\n    mergeVNodeHook(vnode, 'postpatch', () => {\n      for (let i = 0; i < dirsWithPostpatch.length; i++) {\n        callHook(dirsWithPostpatch[i], 'componentUpdated', vnode, oldVnode)\n      }\n    })\n  }\n\n  if (!isCreate) {\n    for (key in oldDirs) {\n      if (!newDirs[key]) {\n        // no longer present, unbind\n        callHook(oldDirs[key], 'unbind', oldVnode, oldVnode, isDestroy)\n      }\n    }\n  }\n}\n\nconst emptyModifiers = Object.create(null)\n\nfunction normalizeDirectives (\n  dirs: ?Array<VNodeDirective>,\n  vm: Component\n): { [key: string]: VNodeDirective } {\n  const res = Object.create(null)\n  if (!dirs) {\n    // $flow-disable-line\n    return res\n  }\n  let i, dir\n  for (i = 0; i < dirs.length; i++) {\n    dir = dirs[i]\n    if (!dir.modifiers) {\n      // $flow-disable-line\n      dir.modifiers = emptyModifiers\n    }\n    res[getRawDirName(dir)] = dir\n    dir.def = resolveAsset(vm.$options, 'directives', dir.name, true)\n  }\n  // $flow-disable-line\n  return res\n}\n\nfunction getRawDirName (dir: VNodeDirective): string {\n  return dir.rawName || `${dir.name}.${Object.keys(dir.modifiers || {}).join('.')}`\n}\n\nfunction callHook (dir, hook, vnode, oldVnode, isDestroy) {\n  const fn = dir.def && dir.def[hook]\n  if (fn) {\n    try {\n      fn(vnode.elm, dir, vnode, oldVnode, isDestroy)\n    } catch (e) {\n      handleError(e, vnode.context, `directive ${dir.name} ${hook} hook`)\n    }\n  }\n}\n"
  },
  {
    "path": "vue/src/core/vdom/modules/index.js",
    "content": "import directives from './directives'\nimport ref from './ref'\n\nexport default [\n  ref,\n  directives\n]\n"
  },
  {
    "path": "vue/src/core/vdom/modules/ref.js",
    "content": "/* @flow */\n\nimport { remove, isDef } from 'shared/util'\n\nexport default {\n  create (_: any, vnode: VNodeWithData) {\n    registerRef(vnode)\n  },\n  update (oldVnode: VNodeWithData, vnode: VNodeWithData) {\n    if (oldVnode.data.ref !== vnode.data.ref) {\n      registerRef(oldVnode, true)\n      registerRef(vnode)\n    }\n  },\n  destroy (vnode: VNodeWithData) {\n    registerRef(vnode, true)\n  }\n}\n\nexport function registerRef (vnode: VNodeWithData, isRemoval: ?boolean) {\n  const key = vnode.data.ref\n  if (!isDef(key)) return\n\n  const vm = vnode.context\n  const ref = vnode.componentInstance || vnode.elm\n  const refs = vm.$refs\n  if (isRemoval) {\n    if (Array.isArray(refs[key])) {\n      remove(refs[key], ref)\n    } else if (refs[key] === ref) {\n      refs[key] = undefined\n    }\n  } else {\n    if (vnode.data.refInFor) {\n      if (!Array.isArray(refs[key])) {\n        refs[key] = [ref]\n      } else if (refs[key].indexOf(ref) < 0) {\n        // $flow-disable-line\n        refs[key].push(ref)\n      }\n    } else {\n      refs[key] = ref\n    }\n  }\n}\n"
  },
  {
    "path": "vue/src/core/vdom/patch.js",
    "content": "/**\n * Virtual DOM patching algorithm based on Snabbdom by\n * Simon Friis Vindum (@paldepind)\n * Licensed under the MIT License\n * https://github.com/paldepind/snabbdom/blob/master/LICENSE\n *\n * modified by Evan You (@yyx990803)\n *\n * Not type-checking this because this file is perf-critical and the cost\n * of making flow understand it is not worth it.\n */\n\nimport VNode, { cloneVNode } from './vnode'\nimport config from '../config'\nimport { SSR_ATTR } from 'shared/constants'\nimport { registerRef } from './modules/ref'\nimport { traverse } from '../observer/traverse'\nimport { activeInstance } from '../instance/lifecycle'\nimport { isTextInputType } from 'web/util/element'\n\nimport {\n  warn,\n  isDef,\n  isUndef,\n  isTrue,\n  makeMap,\n  isRegExp,\n  isPrimitive\n} from '../util/index'\n\nexport const emptyNode = new VNode('', {}, [])\n\nconst hooks = ['create', 'activate', 'update', 'remove', 'destroy']\n\nfunction sameVnode (a, b) {\n  return (\n    a.key === b.key && (\n      (\n        a.tag === b.tag &&\n        a.isComment === b.isComment &&\n        isDef(a.data) === isDef(b.data) &&\n        sameInputType(a, b)\n      ) || (\n        isTrue(a.isAsyncPlaceholder) &&\n        a.asyncFactory === b.asyncFactory &&\n        isUndef(b.asyncFactory.error)\n      )\n    )\n  )\n}\n\nfunction sameInputType (a, b) {\n  if (a.tag !== 'input') return true\n  let i\n  const typeA = isDef(i = a.data) && isDef(i = i.attrs) && i.type\n  const typeB = isDef(i = b.data) && isDef(i = i.attrs) && i.type\n  return typeA === typeB || isTextInputType(typeA) && isTextInputType(typeB)\n}\n\nfunction createKeyToOldIdx (children, beginIdx, endIdx) {\n  let i, key\n  const map = {}\n  for (i = beginIdx; i <= endIdx; ++i) {\n    key = children[i].key\n    if (isDef(key)) map[key] = i\n  }\n  return map\n}\n\nexport function createPatchFunction (backend) {\n  let i, j\n  const cbs = {}\n\n  const { modules, nodeOps } = backend\n\n  for (i = 0; i < hooks.length; ++i) {\n    cbs[hooks[i]] = []\n    for (j = 0; j < modules.length; ++j) {\n      if (isDef(modules[j][hooks[i]])) {\n        cbs[hooks[i]].push(modules[j][hooks[i]])\n      }\n    }\n  }\n\n  function emptyNodeAt (elm) {\n    return new VNode(nodeOps.tagName(elm).toLowerCase(), {}, [], undefined, elm)\n  }\n\n  function createRmCb (childElm, listeners) {\n    function remove () {\n      if (--remove.listeners === 0) {\n        removeNode(childElm)\n      }\n    }\n    remove.listeners = listeners\n    return remove\n  }\n\n  function removeNode (el) {\n    const parent = nodeOps.parentNode(el)\n    // element may have already been removed due to v-html / v-text\n    if (isDef(parent)) {\n      nodeOps.removeChild(parent, el)\n    }\n  }\n\n  function isUnknownElement (vnode, inVPre) {\n    return (\n      !inVPre &&\n      !vnode.ns &&\n      !(\n        config.ignoredElements.length &&\n        config.ignoredElements.some(ignore => {\n          return isRegExp(ignore)\n            ? ignore.test(vnode.tag)\n            : ignore === vnode.tag\n        })\n      ) &&\n      config.isUnknownElement(vnode.tag)\n    )\n  }\n\n  let creatingElmInVPre = 0\n\n  function createElm (\n    vnode,\n    insertedVnodeQueue,\n    parentElm,\n    refElm,\n    nested,\n    ownerArray,\n    index\n  ) {\n    if (isDef(vnode.elm) && isDef(ownerArray)) {\n      // This vnode was used in a previous render!\n      // now it's used as a new node, overwriting its elm would cause\n      // potential patch errors down the road when it's used as an insertion\n      // reference node. Instead, we clone the node on-demand before creating\n      // associated DOM element for it.\n      vnode = ownerArray[index] = cloneVNode(vnode)\n    }\n\n    vnode.isRootInsert = !nested // for transition enter check\n    if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {\n      return\n    }\n\n    const data = vnode.data\n    const children = vnode.children\n    const tag = vnode.tag\n    if (isDef(tag)) {\n      if (process.env.NODE_ENV !== 'production') {\n        if (data && data.pre) {\n          creatingElmInVPre++\n        }\n        if (isUnknownElement(vnode, creatingElmInVPre)) {\n          warn(\n            'Unknown custom element: <' + tag + '> - did you ' +\n            'register the component correctly? For recursive components, ' +\n            'make sure to provide the \"name\" option.',\n            vnode.context\n          )\n        }\n      }\n\n      vnode.elm = vnode.ns\n        ? nodeOps.createElementNS(vnode.ns, tag)\n        : nodeOps.createElement(tag, vnode)\n      setScope(vnode)\n\n      /* istanbul ignore if */\n      if (__WEEX__) {\n        // in Weex, the default insertion order is parent-first.\n        // List items can be optimized to use children-first insertion\n        // with append=\"tree\".\n        const appendAsTree = isDef(data) && isTrue(data.appendAsTree)\n        if (!appendAsTree) {\n          if (isDef(data)) {\n            invokeCreateHooks(vnode, insertedVnodeQueue)\n          }\n          insert(parentElm, vnode.elm, refElm)\n        }\n        createChildren(vnode, children, insertedVnodeQueue)\n        if (appendAsTree) {\n          if (isDef(data)) {\n            invokeCreateHooks(vnode, insertedVnodeQueue)\n          }\n          insert(parentElm, vnode.elm, refElm)\n        }\n      } else {\n        createChildren(vnode, children, insertedVnodeQueue)\n        if (isDef(data)) {\n          invokeCreateHooks(vnode, insertedVnodeQueue)\n        }\n        insert(parentElm, vnode.elm, refElm)\n      }\n\n      if (process.env.NODE_ENV !== 'production' && data && data.pre) {\n        creatingElmInVPre--\n      }\n    } else if (isTrue(vnode.isComment)) {\n      vnode.elm = nodeOps.createComment(vnode.text)\n      insert(parentElm, vnode.elm, refElm)\n    } else {\n      vnode.elm = nodeOps.createTextNode(vnode.text)\n      insert(parentElm, vnode.elm, refElm)\n    }\n  }\n\n  function createComponent (vnode, insertedVnodeQueue, parentElm, refElm) {\n    let i = vnode.data\n    if (isDef(i)) {\n      const isReactivated = isDef(vnode.componentInstance) && i.keepAlive\n      if (isDef(i = i.hook) && isDef(i = i.init)) {\n        i(vnode, false /* hydrating */)\n      }\n      // after calling the init hook, if the vnode is a child component\n      // it should've created a child instance and mounted it. the child\n      // component also has set the placeholder vnode's elm.\n      // in that case we can just return the element and be done.\n      if (isDef(vnode.componentInstance)) {\n        initComponent(vnode, insertedVnodeQueue)\n        insert(parentElm, vnode.elm, refElm)\n        if (isTrue(isReactivated)) {\n          reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm)\n        }\n        return true\n      }\n    }\n  }\n\n  function initComponent (vnode, insertedVnodeQueue) {\n    if (isDef(vnode.data.pendingInsert)) {\n      insertedVnodeQueue.push.apply(insertedVnodeQueue, vnode.data.pendingInsert)\n      vnode.data.pendingInsert = null\n    }\n    vnode.elm = vnode.componentInstance.$el\n    if (isPatchable(vnode)) {\n      invokeCreateHooks(vnode, insertedVnodeQueue)\n      setScope(vnode)\n    } else {\n      // empty component root.\n      // skip all element-related modules except for ref (#3455)\n      registerRef(vnode)\n      // make sure to invoke the insert hook\n      insertedVnodeQueue.push(vnode)\n    }\n  }\n\n  function reactivateComponent (vnode, insertedVnodeQueue, parentElm, refElm) {\n    let i\n    // hack for #4339: a reactivated component with inner transition\n    // does not trigger because the inner node's created hooks are not called\n    // again. It's not ideal to involve module-specific logic in here but\n    // there doesn't seem to be a better way to do it.\n    let innerNode = vnode\n    while (innerNode.componentInstance) {\n      innerNode = innerNode.componentInstance._vnode\n      if (isDef(i = innerNode.data) && isDef(i = i.transition)) {\n        for (i = 0; i < cbs.activate.length; ++i) {\n          cbs.activate[i](emptyNode, innerNode)\n        }\n        insertedVnodeQueue.push(innerNode)\n        break\n      }\n    }\n    // unlike a newly created component,\n    // a reactivated keep-alive component doesn't insert itself\n    insert(parentElm, vnode.elm, refElm)\n  }\n\n  function insert (parent, elm, ref) {\n    if (isDef(parent)) {\n      if (isDef(ref)) {\n        if (ref.parentNode === parent) {\n          nodeOps.insertBefore(parent, elm, ref)\n        }\n      } else {\n        nodeOps.appendChild(parent, elm)\n      }\n    }\n  }\n\n  function createChildren (vnode, children, insertedVnodeQueue) {\n    if (Array.isArray(children)) {\n      if (process.env.NODE_ENV !== 'production') {\n        checkDuplicateKeys(children)\n      }\n      for (let i = 0; i < children.length; ++i) {\n        createElm(children[i], insertedVnodeQueue, vnode.elm, null, true, children, i)\n      }\n    } else if (isPrimitive(vnode.text)) {\n      nodeOps.appendChild(vnode.elm, nodeOps.createTextNode(String(vnode.text)))\n    }\n  }\n\n  function isPatchable (vnode) {\n    while (vnode.componentInstance) {\n      vnode = vnode.componentInstance._vnode\n    }\n    return isDef(vnode.tag)\n  }\n\n  function invokeCreateHooks (vnode, insertedVnodeQueue) {\n    for (let i = 0; i < cbs.create.length; ++i) {\n      cbs.create[i](emptyNode, vnode)\n    }\n    i = vnode.data.hook // Reuse variable\n    if (isDef(i)) {\n      if (isDef(i.create)) i.create(emptyNode, vnode)\n      if (isDef(i.insert)) insertedVnodeQueue.push(vnode)\n    }\n  }\n\n  // set scope id attribute for scoped CSS.\n  // this is implemented as a special case to avoid the overhead\n  // of going through the normal attribute patching process.\n  function setScope (vnode) {\n    let i\n    if (isDef(i = vnode.fnScopeId)) {\n      nodeOps.setStyleScope(vnode.elm, i)\n    } else {\n      let ancestor = vnode\n      while (ancestor) {\n        if (isDef(i = ancestor.context) && isDef(i = i.$options._scopeId)) {\n          nodeOps.setStyleScope(vnode.elm, i)\n        }\n        ancestor = ancestor.parent\n      }\n    }\n    // for slot content they should also get the scopeId from the host instance.\n    if (isDef(i = activeInstance) &&\n      i !== vnode.context &&\n      i !== vnode.fnContext &&\n      isDef(i = i.$options._scopeId)\n    ) {\n      nodeOps.setStyleScope(vnode.elm, i)\n    }\n  }\n\n  function addVnodes (parentElm, refElm, vnodes, startIdx, endIdx, insertedVnodeQueue) {\n    for (; startIdx <= endIdx; ++startIdx) {\n      createElm(vnodes[startIdx], insertedVnodeQueue, parentElm, refElm, false, vnodes, startIdx)\n    }\n  }\n\n  function invokeDestroyHook (vnode) {\n    let i, j\n    const data = vnode.data\n    if (isDef(data)) {\n      if (isDef(i = data.hook) && isDef(i = i.destroy)) i(vnode)\n      for (i = 0; i < cbs.destroy.length; ++i) cbs.destroy[i](vnode)\n    }\n    if (isDef(i = vnode.children)) {\n      for (j = 0; j < vnode.children.length; ++j) {\n        invokeDestroyHook(vnode.children[j])\n      }\n    }\n  }\n\n  function removeVnodes (parentElm, vnodes, startIdx, endIdx) {\n    for (; startIdx <= endIdx; ++startIdx) {\n      const ch = vnodes[startIdx]\n      if (isDef(ch)) {\n        if (isDef(ch.tag)) {\n          removeAndInvokeRemoveHook(ch)\n          invokeDestroyHook(ch)\n        } else { // Text node\n          removeNode(ch.elm)\n        }\n      }\n    }\n  }\n\n  function removeAndInvokeRemoveHook (vnode, rm) {\n    if (isDef(rm) || isDef(vnode.data)) {\n      let i\n      const listeners = cbs.remove.length + 1\n      if (isDef(rm)) {\n        // we have a recursively passed down rm callback\n        // increase the listeners count\n        rm.listeners += listeners\n      } else {\n        // directly removing\n        rm = createRmCb(vnode.elm, listeners)\n      }\n      // recursively invoke hooks on child component root node\n      if (isDef(i = vnode.componentInstance) && isDef(i = i._vnode) && isDef(i.data)) {\n        removeAndInvokeRemoveHook(i, rm)\n      }\n      for (i = 0; i < cbs.remove.length; ++i) {\n        cbs.remove[i](vnode, rm)\n      }\n      if (isDef(i = vnode.data.hook) && isDef(i = i.remove)) {\n        i(vnode, rm)\n      } else {\n        rm()\n      }\n    } else {\n      removeNode(vnode.elm)\n    }\n  }\n\n  function updateChildren (parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly) {\n    let oldStartIdx = 0\n    let newStartIdx = 0\n    let oldEndIdx = oldCh.length - 1\n    let oldStartVnode = oldCh[0]\n    let oldEndVnode = oldCh[oldEndIdx]\n    let newEndIdx = newCh.length - 1\n    let newStartVnode = newCh[0]\n    let newEndVnode = newCh[newEndIdx]\n    let oldKeyToIdx, idxInOld, vnodeToMove, refElm\n\n    // removeOnly is a special flag used only by <transition-group>\n    // to ensure removed elements stay in correct relative positions\n    // during leaving transitions\n    const canMove = !removeOnly\n\n    if (process.env.NODE_ENV !== 'production') {\n      checkDuplicateKeys(newCh)\n    }\n\n    while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {\n      if (isUndef(oldStartVnode)) {\n        oldStartVnode = oldCh[++oldStartIdx] // Vnode has been moved left\n      } else if (isUndef(oldEndVnode)) {\n        oldEndVnode = oldCh[--oldEndIdx]\n      } else if (sameVnode(oldStartVnode, newStartVnode)) {\n        patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue)\n        oldStartVnode = oldCh[++oldStartIdx]\n        newStartVnode = newCh[++newStartIdx]\n      } else if (sameVnode(oldEndVnode, newEndVnode)) {\n        patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue)\n        oldEndVnode = oldCh[--oldEndIdx]\n        newEndVnode = newCh[--newEndIdx]\n      } else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right\n        patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue)\n        canMove && nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm))\n        oldStartVnode = oldCh[++oldStartIdx]\n        newEndVnode = newCh[--newEndIdx]\n      } else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left\n        patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue)\n        canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm)\n        oldEndVnode = oldCh[--oldEndIdx]\n        newStartVnode = newCh[++newStartIdx]\n      } else {\n        if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)\n        idxInOld = isDef(newStartVnode.key)\n          ? oldKeyToIdx[newStartVnode.key]\n          : findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx)\n        if (isUndef(idxInOld)) { // New element\n          createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)\n        } else {\n          vnodeToMove = oldCh[idxInOld]\n          if (sameVnode(vnodeToMove, newStartVnode)) {\n            patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue)\n            oldCh[idxInOld] = undefined\n            canMove && nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm)\n          } else {\n            // same key but different element. treat as new element\n            createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)\n          }\n        }\n        newStartVnode = newCh[++newStartIdx]\n      }\n    }\n    if (oldStartIdx > oldEndIdx) {\n      refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm\n      addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, insertedVnodeQueue)\n    } else if (newStartIdx > newEndIdx) {\n      removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx)\n    }\n  }\n\n  function checkDuplicateKeys (children) {\n    const seenKeys = {}\n    for (let i = 0; i < children.length; i++) {\n      const vnode = children[i]\n      const key = vnode.key\n      if (isDef(key)) {\n        if (seenKeys[key]) {\n          warn(\n            `Duplicate keys detected: '${key}'. This may cause an update error.`,\n            vnode.context\n          )\n        } else {\n          seenKeys[key] = true\n        }\n      }\n    }\n  }\n\n  function findIdxInOld (node, oldCh, start, end) {\n    for (let i = start; i < end; i++) {\n      const c = oldCh[i]\n      if (isDef(c) && sameVnode(node, c)) return i\n    }\n  }\n\n  function patchVnode (oldVnode, vnode, insertedVnodeQueue, removeOnly) {\n    if (oldVnode === vnode) {\n      return\n    }\n\n    const elm = vnode.elm = oldVnode.elm\n\n    if (isTrue(oldVnode.isAsyncPlaceholder)) {\n      if (isDef(vnode.asyncFactory.resolved)) {\n        hydrate(oldVnode.elm, vnode, insertedVnodeQueue)\n      } else {\n        vnode.isAsyncPlaceholder = true\n      }\n      return\n    }\n\n    // reuse element for static trees.\n    // note we only do this if the vnode is cloned -\n    // if the new node is not cloned it means the render functions have been\n    // reset by the hot-reload-api and we need to do a proper re-render.\n    if (isTrue(vnode.isStatic) &&\n      isTrue(oldVnode.isStatic) &&\n      vnode.key === oldVnode.key &&\n      (isTrue(vnode.isCloned) || isTrue(vnode.isOnce))\n    ) {\n      vnode.componentInstance = oldVnode.componentInstance\n      return\n    }\n\n    let i\n    const data = vnode.data\n    if (isDef(data) && isDef(i = data.hook) && isDef(i = i.prepatch)) {\n      i(oldVnode, vnode)\n    }\n\n    const oldCh = oldVnode.children\n    const ch = vnode.children\n    if (isDef(data) && isPatchable(vnode)) {\n      for (i = 0; i < cbs.update.length; ++i) cbs.update[i](oldVnode, vnode)\n      if (isDef(i = data.hook) && isDef(i = i.update)) i(oldVnode, vnode)\n    }\n    if (isUndef(vnode.text)) {\n      if (isDef(oldCh) && isDef(ch)) {\n        if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly)\n      } else if (isDef(ch)) {\n        if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, '')\n        addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue)\n      } else if (isDef(oldCh)) {\n        removeVnodes(elm, oldCh, 0, oldCh.length - 1)\n      } else if (isDef(oldVnode.text)) {\n        nodeOps.setTextContent(elm, '')\n      }\n    } else if (oldVnode.text !== vnode.text) {\n      nodeOps.setTextContent(elm, vnode.text)\n    }\n    if (isDef(data)) {\n      if (isDef(i = data.hook) && isDef(i = i.postpatch)) i(oldVnode, vnode)\n    }\n  }\n\n  function invokeInsertHook (vnode, queue, initial) {\n    // delay insert hooks for component root nodes, invoke them after the\n    // element is really inserted\n    if (isTrue(initial) && isDef(vnode.parent)) {\n      vnode.parent.data.pendingInsert = queue\n    } else {\n      for (let i = 0; i < queue.length; ++i) {\n        queue[i].data.hook.insert(queue[i])\n      }\n    }\n  }\n\n  let hydrationBailed = false\n  // list of modules that can skip create hook during hydration because they\n  // are already rendered on the client or has no need for initialization\n  // Note: style is excluded because it relies on initial clone for future\n  // deep updates (#7063).\n  const isRenderedModule = makeMap('attrs,class,staticClass,staticStyle,key')\n\n  // Note: this is a browser-only function so we can assume elms are DOM nodes.\n  function hydrate (elm, vnode, insertedVnodeQueue, inVPre) {\n    let i\n    const { tag, data, children } = vnode\n    inVPre = inVPre || (data && data.pre)\n    vnode.elm = elm\n\n    if (isTrue(vnode.isComment) && isDef(vnode.asyncFactory)) {\n      vnode.isAsyncPlaceholder = true\n      return true\n    }\n    // assert node match\n    if (process.env.NODE_ENV !== 'production') {\n      if (!assertNodeMatch(elm, vnode, inVPre)) {\n        return false\n      }\n    }\n    if (isDef(data)) {\n      if (isDef(i = data.hook) && isDef(i = i.init)) i(vnode, true /* hydrating */)\n      if (isDef(i = vnode.componentInstance)) {\n        // child component. it should have hydrated its own tree.\n        initComponent(vnode, insertedVnodeQueue)\n        return true\n      }\n    }\n    if (isDef(tag)) {\n      if (isDef(children)) {\n        // empty element, allow client to pick up and populate children\n        if (!elm.hasChildNodes()) {\n          createChildren(vnode, children, insertedVnodeQueue)\n        } else {\n          // v-html and domProps: innerHTML\n          if (isDef(i = data) && isDef(i = i.domProps) && isDef(i = i.innerHTML)) {\n            if (i !== elm.innerHTML) {\n              /* istanbul ignore if */\n              if (process.env.NODE_ENV !== 'production' &&\n                typeof console !== 'undefined' &&\n                !hydrationBailed\n              ) {\n                hydrationBailed = true\n                console.warn('Parent: ', elm)\n                console.warn('server innerHTML: ', i)\n                console.warn('client innerHTML: ', elm.innerHTML)\n              }\n              return false\n            }\n          } else {\n            // iterate and compare children lists\n            let childrenMatch = true\n            let childNode = elm.firstChild\n            for (let i = 0; i < children.length; i++) {\n              if (!childNode || !hydrate(childNode, children[i], insertedVnodeQueue, inVPre)) {\n                childrenMatch = false\n                break\n              }\n              childNode = childNode.nextSibling\n            }\n            // if childNode is not null, it means the actual childNodes list is\n            // longer than the virtual children list.\n            if (!childrenMatch || childNode) {\n              /* istanbul ignore if */\n              if (process.env.NODE_ENV !== 'production' &&\n                typeof console !== 'undefined' &&\n                !hydrationBailed\n              ) {\n                hydrationBailed = true\n                console.warn('Parent: ', elm)\n                console.warn('Mismatching childNodes vs. VNodes: ', elm.childNodes, children)\n              }\n              return false\n            }\n          }\n        }\n      }\n      if (isDef(data)) {\n        let fullInvoke = false\n        for (const key in data) {\n          if (!isRenderedModule(key)) {\n            fullInvoke = true\n            invokeCreateHooks(vnode, insertedVnodeQueue)\n            break\n          }\n        }\n        if (!fullInvoke && data['class']) {\n          // ensure collecting deps for deep class bindings for future updates\n          traverse(data['class'])\n        }\n      }\n    } else if (elm.data !== vnode.text) {\n      elm.data = vnode.text\n    }\n    return true\n  }\n\n  function assertNodeMatch (node, vnode, inVPre) {\n    if (isDef(vnode.tag)) {\n      return vnode.tag.indexOf('vue-component') === 0 || (\n        !isUnknownElement(vnode, inVPre) &&\n        vnode.tag.toLowerCase() === (node.tagName && node.tagName.toLowerCase())\n      )\n    } else {\n      return node.nodeType === (vnode.isComment ? 8 : 3)\n    }\n  }\n\n  return function patch (oldVnode, vnode, hydrating, removeOnly) {\n    if (isUndef(vnode)) {\n      if (isDef(oldVnode)) invokeDestroyHook(oldVnode)\n      return\n    }\n\n    let isInitialPatch = false\n    const insertedVnodeQueue = []\n\n    if (isUndef(oldVnode)) {\n      // empty mount (likely as component), create new root element\n      isInitialPatch = true\n      createElm(vnode, insertedVnodeQueue)\n    } else {\n      const isRealElement = isDef(oldVnode.nodeType)\n      if (!isRealElement && sameVnode(oldVnode, vnode)) {\n        // patch existing root node\n        patchVnode(oldVnode, vnode, insertedVnodeQueue, removeOnly)\n      } else {\n        if (isRealElement) {\n          // mounting to a real element\n          // check if this is server-rendered content and if we can perform\n          // a successful hydration.\n          if (oldVnode.nodeType === 1 && oldVnode.hasAttribute(SSR_ATTR)) {\n            oldVnode.removeAttribute(SSR_ATTR)\n            hydrating = true\n          }\n          if (isTrue(hydrating)) {\n            if (hydrate(oldVnode, vnode, insertedVnodeQueue)) {\n              invokeInsertHook(vnode, insertedVnodeQueue, true)\n              return oldVnode\n            } else if (process.env.NODE_ENV !== 'production') {\n              warn(\n                'The client-side rendered virtual DOM tree is not matching ' +\n                'server-rendered content. This is likely caused by incorrect ' +\n                'HTML markup, for example nesting block-level elements inside ' +\n                '<p>, or missing <tbody>. Bailing hydration and performing ' +\n                'full client-side render.'\n              )\n            }\n          }\n          // either not server-rendered, or hydration failed.\n          // create an empty node and replace it\n          oldVnode = emptyNodeAt(oldVnode)\n        }\n\n        // replacing existing element\n        const oldElm = oldVnode.elm\n        const parentElm = nodeOps.parentNode(oldElm)\n\n        // create new node\n        createElm(\n          vnode,\n          insertedVnodeQueue,\n          // extremely rare edge case: do not insert if old element is in a\n          // leaving transition. Only happens when combining transition +\n          // keep-alive + HOCs. (#4590)\n          oldElm._leaveCb ? null : parentElm,\n          nodeOps.nextSibling(oldElm)\n        )\n\n        // update parent placeholder node element, recursively\n        if (isDef(vnode.parent)) {\n          let ancestor = vnode.parent\n          const patchable = isPatchable(vnode)\n          while (ancestor) {\n            for (let i = 0; i < cbs.destroy.length; ++i) {\n              cbs.destroy[i](ancestor)\n            }\n            ancestor.elm = vnode.elm\n            if (patchable) {\n              for (let i = 0; i < cbs.create.length; ++i) {\n                cbs.create[i](emptyNode, ancestor)\n              }\n              // #6513\n              // invoke insert hooks that may have been merged by create hooks.\n              // e.g. for directives that uses the \"inserted\" hook.\n              const insert = ancestor.data.hook.insert\n              if (insert.merged) {\n                // start at index 1 to avoid re-invoking component mounted hook\n                for (let i = 1; i < insert.fns.length; i++) {\n                  insert.fns[i]()\n                }\n              }\n            } else {\n              registerRef(ancestor)\n            }\n            ancestor = ancestor.parent\n          }\n        }\n\n        // destroy old node\n        if (isDef(parentElm)) {\n          removeVnodes(parentElm, [oldVnode], 0, 0)\n        } else if (isDef(oldVnode.tag)) {\n          invokeDestroyHook(oldVnode)\n        }\n      }\n    }\n\n    invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch)\n    return vnode.elm\n  }\n}\n"
  },
  {
    "path": "vue/src/core/vdom/vnode.js",
    "content": "/* @flow */\n\nexport default class VNode {\n  tag: string | void;\n  data: VNodeData | void;\n  children: ?Array<VNode>;\n  text: string | void;\n  elm: Node | void;\n  ns: string | void;\n  context: Component | void; // rendered in this component's scope\n  key: string | number | void;\n  componentOptions: VNodeComponentOptions | void;\n  componentInstance: Component | void; // component instance\n  parent: VNode | void; // component placeholder node\n\n  // strictly internal\n  raw: boolean; // contains raw HTML? (server only)\n  isStatic: boolean; // hoisted static node\n  isRootInsert: boolean; // necessary for enter transition check\n  isComment: boolean; // empty comment placeholder?\n  isCloned: boolean; // is a cloned node?\n  isOnce: boolean; // is a v-once node?\n  asyncFactory: Function | void; // async component factory function\n  asyncMeta: Object | void;\n  isAsyncPlaceholder: boolean;\n  ssrContext: Object | void;\n  fnContext: Component | void; // real context vm for functional nodes\n  fnOptions: ?ComponentOptions; // for SSR caching\n  fnScopeId: ?string; // functional scope id support\n\n  constructor (\n    tag?: string,\n    data?: VNodeData,\n    children?: ?Array<VNode>,\n    text?: string,\n    elm?: Node,\n    context?: Component,\n    componentOptions?: VNodeComponentOptions,\n    asyncFactory?: Function\n  ) {\n    this.tag = tag\n    this.data = data\n    this.children = children\n    this.text = text\n    this.elm = elm\n    this.ns = undefined\n    this.context = context\n    this.fnContext = undefined\n    this.fnOptions = undefined\n    this.fnScopeId = undefined\n    this.key = data && data.key\n    this.componentOptions = componentOptions\n    this.componentInstance = undefined\n    this.parent = undefined\n    this.raw = false\n    this.isStatic = false\n    this.isRootInsert = true\n    this.isComment = false\n    this.isCloned = false\n    this.isOnce = false\n    this.asyncFactory = asyncFactory\n    this.asyncMeta = undefined\n    this.isAsyncPlaceholder = false\n  }\n\n  // DEPRECATED: alias for componentInstance for backwards compat.\n  /* istanbul ignore next */\n  get child (): Component | void {\n    return this.componentInstance\n  }\n}\n\nexport const createEmptyVNode = (text: string = '') => {\n  const node = new VNode()\n  node.text = text\n  node.isComment = true\n  return node\n}\n\nexport function createTextVNode (val: string | number) {\n  return new VNode(undefined, undefined, undefined, String(val))\n}\n\n// optimized shallow clone\n// used for static nodes and slot nodes because they may be reused across\n// multiple renders, cloning them avoids errors when DOM manipulations rely\n// on their elm reference.\nexport function cloneVNode (vnode: VNode): VNode {\n  const cloned = new VNode(\n    vnode.tag,\n    vnode.data,\n    vnode.children,\n    vnode.text,\n    vnode.elm,\n    vnode.context,\n    vnode.componentOptions,\n    vnode.asyncFactory\n  )\n  cloned.ns = vnode.ns\n  cloned.isStatic = vnode.isStatic\n  cloned.key = vnode.key\n  cloned.isComment = vnode.isComment\n  cloned.fnContext = vnode.fnContext\n  cloned.fnOptions = vnode.fnOptions\n  cloned.fnScopeId = vnode.fnScopeId\n  cloned.asyncMeta = vnode.asyncMeta\n  cloned.isCloned = true\n  return cloned\n}\n"
  },
  {
    "path": "vue/src/platforms/web/compiler/directives/html.js",
    "content": "/* @flow */\n\nimport { addProp } from 'compiler/helpers'\n\nexport default function html (el: ASTElement, dir: ASTDirective) {\n  if (dir.value) {\n    addProp(el, 'innerHTML', `_s(${dir.value})`)\n  }\n}\n"
  },
  {
    "path": "vue/src/platforms/web/compiler/directives/index.js",
    "content": "import model from './model'\nimport text from './text'\nimport html from './html'\n\nexport default {\n  model,\n  text,\n  html\n}\n"
  },
  {
    "path": "vue/src/platforms/web/compiler/directives/model.js",
    "content": "/* @flow */\n\nimport config from 'core/config'\nimport { addHandler, addProp, getBindingAttr } from 'compiler/helpers'\nimport { genComponentModel, genAssignmentCode } from 'compiler/directives/model'\n\nlet warn\n\n// in some cases, the event used has to be determined at runtime\n// so we used some reserved tokens during compile.\nexport const RANGE_TOKEN = '__r'\nexport const CHECKBOX_RADIO_TOKEN = '__c'\n\nexport default function model (\n  el: ASTElement,\n  dir: ASTDirective,\n  _warn: Function\n): ?boolean {\n  warn = _warn\n  const value = dir.value\n  const modifiers = dir.modifiers\n  const tag = el.tag\n  const type = el.attrsMap.type\n\n  if (process.env.NODE_ENV !== 'production') {\n    // inputs with type=\"file\" are read only and setting the input's\n    // value will throw an error.\n    if (tag === 'input' && type === 'file') {\n      warn(\n        `<${el.tag} v-model=\"${value}\" type=\"file\">:\\n` +\n        `File inputs are read only. Use a v-on:change listener instead.`\n      )\n    }\n  }\n\n  if (el.component) {\n    genComponentModel(el, value, modifiers)\n    // component v-model doesn't need extra runtime\n    return false\n  } else if (tag === 'select') {\n    genSelect(el, value, modifiers)\n  } else if (tag === 'input' && type === 'checkbox') {\n    genCheckboxModel(el, value, modifiers)\n  } else if (tag === 'input' && type === 'radio') {\n    genRadioModel(el, value, modifiers)\n  } else if (tag === 'input' || tag === 'textarea') {\n    genDefaultModel(el, value, modifiers)\n  } else if (!config.isReservedTag(tag)) {\n    genComponentModel(el, value, modifiers)\n    // component v-model doesn't need extra runtime\n    return false\n  } else if (process.env.NODE_ENV !== 'production') {\n    warn(\n      `<${el.tag} v-model=\"${value}\">: ` +\n      `v-model is not supported on this element type. ` +\n      'If you are working with contenteditable, it\\'s recommended to ' +\n      'wrap a library dedicated for that purpose inside a custom component.'\n    )\n  }\n\n  // ensure runtime directive metadata\n  return true\n}\n\nfunction genCheckboxModel (\n  el: ASTElement,\n  value: string,\n  modifiers: ?ASTModifiers\n) {\n  const number = modifiers && modifiers.number\n  const valueBinding = getBindingAttr(el, 'value') || 'null'\n  const trueValueBinding = getBindingAttr(el, 'true-value') || 'true'\n  const falseValueBinding = getBindingAttr(el, 'false-value') || 'false'\n  addProp(el, 'checked',\n    `Array.isArray(${value})` +\n    `?_i(${value},${valueBinding})>-1` + (\n      trueValueBinding === 'true'\n        ? `:(${value})`\n        : `:_q(${value},${trueValueBinding})`\n    )\n  )\n  addHandler(el, 'change',\n    `var $$a=${value},` +\n        '$$el=$event.target,' +\n        `$$c=$$el.checked?(${trueValueBinding}):(${falseValueBinding});` +\n    'if(Array.isArray($$a)){' +\n      `var $$v=${number ? '_n(' + valueBinding + ')' : valueBinding},` +\n          '$$i=_i($$a,$$v);' +\n      `if($$el.checked){$$i<0&&(${genAssignmentCode(value, '$$a.concat([$$v])')})}` +\n      `else{$$i>-1&&(${genAssignmentCode(value, '$$a.slice(0,$$i).concat($$a.slice($$i+1))')})}` +\n    `}else{${genAssignmentCode(value, '$$c')}}`,\n    null, true\n  )\n}\n\nfunction genRadioModel (\n  el: ASTElement,\n  value: string,\n  modifiers: ?ASTModifiers\n) {\n  const number = modifiers && modifiers.number\n  let valueBinding = getBindingAttr(el, 'value') || 'null'\n  valueBinding = number ? `_n(${valueBinding})` : valueBinding\n  addProp(el, 'checked', `_q(${value},${valueBinding})`)\n  addHandler(el, 'change', genAssignmentCode(value, valueBinding), null, true)\n}\n\nfunction genSelect (\n  el: ASTElement,\n  value: string,\n  modifiers: ?ASTModifiers\n) {\n  const number = modifiers && modifiers.number\n  const selectedVal = `Array.prototype.filter` +\n    `.call($event.target.options,function(o){return o.selected})` +\n    `.map(function(o){var val = \"_value\" in o ? o._value : o.value;` +\n    `return ${number ? '_n(val)' : 'val'}})`\n\n  const assignment = '$event.target.multiple ? $$selectedVal : $$selectedVal[0]'\n  let code = `var $$selectedVal = ${selectedVal};`\n  code = `${code} ${genAssignmentCode(value, assignment)}`\n  addHandler(el, 'change', code, null, true)\n}\n\nfunction genDefaultModel (\n  el: ASTElement,\n  value: string,\n  modifiers: ?ASTModifiers\n): ?boolean {\n  const type = el.attrsMap.type\n\n  // warn if v-bind:value conflicts with v-model\n  // except for inputs with v-bind:type\n  if (process.env.NODE_ENV !== 'production') {\n    const value = el.attrsMap['v-bind:value'] || el.attrsMap[':value']\n    const typeBinding = el.attrsMap['v-bind:type'] || el.attrsMap[':type']\n    if (value && !typeBinding) {\n      const binding = el.attrsMap['v-bind:value'] ? 'v-bind:value' : ':value'\n      warn(\n        `${binding}=\"${value}\" conflicts with v-model on the same element ` +\n        'because the latter already expands to a value binding internally'\n      )\n    }\n  }\n\n  const { lazy, number, trim } = modifiers || {}\n  const needCompositionGuard = !lazy && type !== 'range'\n  const event = lazy\n    ? 'change'\n    : type === 'range'\n      ? RANGE_TOKEN\n      : 'input'\n\n  let valueExpression = '$event.target.value'\n  if (trim) {\n    valueExpression = `$event.target.value.trim()`\n  }\n  if (number) {\n    valueExpression = `_n(${valueExpression})`\n  }\n\n  let code = genAssignmentCode(value, valueExpression)\n  if (needCompositionGuard) {\n    code = `if($event.target.composing)return;${code}`\n  }\n\n  addProp(el, 'value', `(${value})`)\n  addHandler(el, event, code, null, true)\n  if (trim || number) {\n    addHandler(el, 'blur', '$forceUpdate()')\n  }\n}\n"
  },
  {
    "path": "vue/src/platforms/web/compiler/directives/text.js",
    "content": "/* @flow */\n\nimport { addProp } from 'compiler/helpers'\n\nexport default function text (el: ASTElement, dir: ASTDirective) {\n  if (dir.value) {\n    addProp(el, 'textContent', `_s(${dir.value})`)\n  }\n}\n"
  },
  {
    "path": "vue/src/platforms/web/compiler/index.js",
    "content": "/* @flow */\n\nimport { baseOptions } from './options'\nimport { createCompiler } from 'compiler/index'\n\nconst { compile, compileToFunctions } = createCompiler(baseOptions)\n\nexport { compile, compileToFunctions }\n"
  },
  {
    "path": "vue/src/platforms/web/compiler/modules/class.js",
    "content": "/* @flow */\n\nimport { parseText } from 'compiler/parser/text-parser'\nimport {\n  getAndRemoveAttr,\n  getBindingAttr,\n  baseWarn\n} from 'compiler/helpers'\n\nfunction transformNode (el: ASTElement, options: CompilerOptions) {\n  const warn = options.warn || baseWarn\n  const staticClass = getAndRemoveAttr(el, 'class')\n  if (process.env.NODE_ENV !== 'production' && staticClass) {\n    const res = parseText(staticClass, options.delimiters)\n    if (res) {\n      warn(\n        `class=\"${staticClass}\": ` +\n        'Interpolation inside attributes has been removed. ' +\n        'Use v-bind or the colon shorthand instead. For example, ' +\n        'instead of <div class=\"{{ val }}\">, use <div :class=\"val\">.'\n      )\n    }\n  }\n  if (staticClass) {\n    el.staticClass = JSON.stringify(staticClass)\n  }\n  const classBinding = getBindingAttr(el, 'class', false /* getStatic */)\n  if (classBinding) {\n    el.classBinding = classBinding\n  }\n}\n\nfunction genData (el: ASTElement): string {\n  let data = ''\n  if (el.staticClass) {\n    data += `staticClass:${el.staticClass},`\n  }\n  if (el.classBinding) {\n    data += `class:${el.classBinding},`\n  }\n  return data\n}\n\nexport default {\n  staticKeys: ['staticClass'],\n  transformNode,\n  genData\n}\n"
  },
  {
    "path": "vue/src/platforms/web/compiler/modules/index.js",
    "content": "import klass from './class'\nimport style from './style'\nimport model from './model'\n\nexport default [\n  klass,\n  style,\n  model\n]\n"
  },
  {
    "path": "vue/src/platforms/web/compiler/modules/model.js",
    "content": "/* @flow */\n\n/**\n * Expand input[v-model] with dyanmic type bindings into v-if-else chains\n * Turn this:\n *   <input v-model=\"data[type]\" :type=\"type\">\n * into this:\n *   <input v-if=\"type === 'checkbox'\" type=\"checkbox\" v-model=\"data[type]\">\n *   <input v-else-if=\"type === 'radio'\" type=\"radio\" v-model=\"data[type]\">\n *   <input v-else :type=\"type\" v-model=\"data[type]\">\n */\n\nimport {\n  addRawAttr,\n  getBindingAttr,\n  getAndRemoveAttr\n} from 'compiler/helpers'\n\nimport {\n  processFor,\n  processElement,\n  addIfCondition,\n  createASTElement\n} from 'compiler/parser/index'\n\nfunction preTransformNode (el: ASTElement, options: CompilerOptions) {\n  if (el.tag === 'input') {\n    const map = el.attrsMap\n    if (!map['v-model']) {\n      return\n    }\n\n    let typeBinding\n    if (map[':type'] || map['v-bind:type']) {\n      typeBinding = getBindingAttr(el, 'type')\n    }\n    if (!map.type && !typeBinding && map['v-bind']) {\n      typeBinding = `(${map['v-bind']}).type`\n    }\n\n    if (typeBinding) {\n      const ifCondition = getAndRemoveAttr(el, 'v-if', true)\n      const ifConditionExtra = ifCondition ? `&&(${ifCondition})` : ``\n      const hasElse = getAndRemoveAttr(el, 'v-else', true) != null\n      const elseIfCondition = getAndRemoveAttr(el, 'v-else-if', true)\n      // 1. checkbox\n      const branch0 = cloneASTElement(el)\n      // process for on the main node\n      processFor(branch0)\n      addRawAttr(branch0, 'type', 'checkbox')\n      processElement(branch0, options)\n      branch0.processed = true // prevent it from double-processed\n      branch0.if = `(${typeBinding})==='checkbox'` + ifConditionExtra\n      addIfCondition(branch0, {\n        exp: branch0.if,\n        block: branch0\n      })\n      // 2. add radio else-if condition\n      const branch1 = cloneASTElement(el)\n      getAndRemoveAttr(branch1, 'v-for', true)\n      addRawAttr(branch1, 'type', 'radio')\n      processElement(branch1, options)\n      addIfCondition(branch0, {\n        exp: `(${typeBinding})==='radio'` + ifConditionExtra,\n        block: branch1\n      })\n      // 3. other\n      const branch2 = cloneASTElement(el)\n      getAndRemoveAttr(branch2, 'v-for', true)\n      addRawAttr(branch2, ':type', typeBinding)\n      processElement(branch2, options)\n      addIfCondition(branch0, {\n        exp: ifCondition,\n        block: branch2\n      })\n\n      if (hasElse) {\n        branch0.else = true\n      } else if (elseIfCondition) {\n        branch0.elseif = elseIfCondition\n      }\n\n      return branch0\n    }\n  }\n}\n\nfunction cloneASTElement (el) {\n  return createASTElement(el.tag, el.attrsList.slice(), el.parent)\n}\n\nexport default {\n  preTransformNode\n}\n"
  },
  {
    "path": "vue/src/platforms/web/compiler/modules/style.js",
    "content": "/* @flow */\n\nimport { parseText } from 'compiler/parser/text-parser'\nimport { parseStyleText } from 'web/util/style'\nimport {\n  getAndRemoveAttr,\n  getBindingAttr,\n  baseWarn\n} from 'compiler/helpers'\n\nfunction transformNode (el: ASTElement, options: CompilerOptions) {\n  const warn = options.warn || baseWarn\n  const staticStyle = getAndRemoveAttr(el, 'style')\n  if (staticStyle) {\n    /* istanbul ignore if */\n    if (process.env.NODE_ENV !== 'production') {\n      const res = parseText(staticStyle, options.delimiters)\n      if (res) {\n        warn(\n          `style=\"${staticStyle}\": ` +\n          'Interpolation inside attributes has been removed. ' +\n          'Use v-bind or the colon shorthand instead. For example, ' +\n          'instead of <div style=\"{{ val }}\">, use <div :style=\"val\">.'\n        )\n      }\n    }\n    el.staticStyle = JSON.stringify(parseStyleText(staticStyle))\n  }\n\n  const styleBinding = getBindingAttr(el, 'style', false /* getStatic */)\n  if (styleBinding) {\n    el.styleBinding = styleBinding\n  }\n}\n\nfunction genData (el: ASTElement): string {\n  let data = ''\n  if (el.staticStyle) {\n    data += `staticStyle:${el.staticStyle},`\n  }\n  if (el.styleBinding) {\n    data += `style:(${el.styleBinding}),`\n  }\n  return data\n}\n\nexport default {\n  staticKeys: ['staticStyle'],\n  transformNode,\n  genData\n}\n"
  },
  {
    "path": "vue/src/platforms/web/compiler/options.js",
    "content": "/* @flow */\n\nimport {\n  isPreTag,\n  mustUseProp,\n  isReservedTag,\n  getTagNamespace\n} from '../util/index'\n\nimport modules from './modules/index'\nimport directives from './directives/index'\nimport { genStaticKeys } from 'shared/util'\nimport { isUnaryTag, canBeLeftOpenTag } from './util'\n\nexport const baseOptions: CompilerOptions = {\n  expectHTML: true,\n  modules,\n  directives,\n  isPreTag,\n  isUnaryTag,\n  mustUseProp,\n  canBeLeftOpenTag,\n  isReservedTag,\n  getTagNamespace,\n  staticKeys: genStaticKeys(modules)\n}\n"
  },
  {
    "path": "vue/src/platforms/web/compiler/util.js",
    "content": "/* @flow */\n\nimport { makeMap } from 'shared/util'\n\nexport const isUnaryTag = makeMap(\n  'area,base,br,col,embed,frame,hr,img,input,isindex,keygen,' +\n  'link,meta,param,source,track,wbr'\n)\n\n// Elements that you can, intentionally, leave open\n// (and which close themselves)\nexport const canBeLeftOpenTag = makeMap(\n  'colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr,source'\n)\n\n// HTML5 tags https://html.spec.whatwg.org/multipage/indices.html#elements-3\n// Phrasing Content https://html.spec.whatwg.org/multipage/dom.html#phrasing-content\nexport const isNonPhrasingTag = makeMap(\n  'address,article,aside,base,blockquote,body,caption,col,colgroup,dd,' +\n  'details,dialog,div,dl,dt,fieldset,figcaption,figure,footer,form,' +\n  'h1,h2,h3,h4,h5,h6,head,header,hgroup,hr,html,legend,li,menuitem,meta,' +\n  'optgroup,option,param,rp,rt,source,style,summary,tbody,td,tfoot,th,thead,' +\n  'title,tr,track'\n)\n"
  },
  {
    "path": "vue/src/platforms/web/entry-compiler.js",
    "content": "/* @flow */\n\nexport { parseComponent } from 'sfc/parser'\nexport { compile, compileToFunctions } from './compiler/index'\nexport { ssrCompile, ssrCompileToFunctions } from './server/compiler'\n"
  },
  {
    "path": "vue/src/platforms/web/entry-runtime-with-compiler.js",
    "content": "/* @flow */\n\nimport config from 'core/config'\nimport { warn, cached } from 'core/util/index'\nimport { mark, measure } from 'core/util/perf'\n\nimport Vue from './runtime/index'\nimport { query } from './util/index'\nimport { compileToFunctions } from './compiler/index'\nimport { shouldDecodeNewlines, shouldDecodeNewlinesForHref } from './util/compat'\n\nconst idToTemplate = cached(id => {\n  const el = query(id)\n  return el && el.innerHTML\n})\n\nconst mount = Vue.prototype.$mount\nVue.prototype.$mount = function (\n  el?: string | Element,\n  hydrating?: boolean\n): Component {\n  el = el && query(el)\n\n  /* istanbul ignore if */\n  if (el === document.body || el === document.documentElement) {\n    process.env.NODE_ENV !== 'production' && warn(\n      `Do not mount Vue to <html> or <body> - mount to normal elements instead.`\n    )\n    return this\n  }\n\n  const options = this.$options\n  // resolve template/el and convert to render function\n  if (!options.render) {\n    let template = options.template\n    if (template) {\n      if (typeof template === 'string') {\n        if (template.charAt(0) === '#') {\n          template = idToTemplate(template)\n          /* istanbul ignore if */\n          if (process.env.NODE_ENV !== 'production' && !template) {\n            warn(\n              `Template element not found or is empty: ${options.template}`,\n              this\n            )\n          }\n        }\n      } else if (template.nodeType) {\n        template = template.innerHTML\n      } else {\n        if (process.env.NODE_ENV !== 'production') {\n          warn('invalid template option:' + template, this)\n        }\n        return this\n      }\n    } else if (el) {\n      template = getOuterHTML(el)\n    }\n    if (template) {\n      /* istanbul ignore if */\n      if (process.env.NODE_ENV !== 'production' && config.performance && mark) {\n        mark('compile')\n      }\n\n      const { render, staticRenderFns } = compileToFunctions(template, {\n        shouldDecodeNewlines,\n        shouldDecodeNewlinesForHref,\n        delimiters: options.delimiters,\n        comments: options.comments\n      }, this)\n      options.render = render\n      options.staticRenderFns = staticRenderFns\n\n      /* istanbul ignore if */\n      if (process.env.NODE_ENV !== 'production' && config.performance && mark) {\n        mark('compile end')\n        measure(`vue ${this._name} compile`, 'compile', 'compile end')\n      }\n    }\n  }\n  return mount.call(this, el, hydrating)\n}\n\n/**\n * Get outerHTML of elements, taking care\n * of SVG elements in IE as well.\n */\nfunction getOuterHTML (el: Element): string {\n  if (el.outerHTML) {\n    return el.outerHTML\n  } else {\n    const container = document.createElement('div')\n    container.appendChild(el.cloneNode(true))\n    return container.innerHTML\n  }\n}\n\nVue.compile = compileToFunctions\n\nexport default Vue\n"
  },
  {
    "path": "vue/src/platforms/web/entry-runtime.js",
    "content": "/* @flow */\n\nimport Vue from './runtime/index'\n\nexport default Vue\n"
  },
  {
    "path": "vue/src/platforms/web/entry-server-basic-renderer.js",
    "content": "/* @flow */\n\nimport modules from './server/modules/index'\nimport directives from './server/directives/index'\nimport { isUnaryTag, canBeLeftOpenTag } from './compiler/util'\nimport { createBasicRenderer } from 'server/create-basic-renderer'\n\nexport default createBasicRenderer({\n  modules,\n  directives,\n  isUnaryTag,\n  canBeLeftOpenTag\n})\n"
  },
  {
    "path": "vue/src/platforms/web/entry-server-renderer.js",
    "content": "/* @flow */\n\nprocess.env.VUE_ENV = 'server'\n\nimport { extend } from 'shared/util'\nimport modules from './server/modules/index'\nimport baseDirectives from './server/directives/index'\nimport { isUnaryTag, canBeLeftOpenTag } from './compiler/util'\n\nimport { createRenderer as _createRenderer } from 'server/create-renderer'\nimport { createBundleRendererCreator } from 'server/bundle-renderer/create-bundle-renderer'\n\nexport function createRenderer (options?: Object = {}): {\n  renderToString: Function,\n  renderToStream: Function\n} {\n  return _createRenderer(extend(extend({}, options), {\n    isUnaryTag,\n    canBeLeftOpenTag,\n    modules,\n    // user can provide server-side implementations for custom directives\n    // when creating the renderer.\n    directives: extend(baseDirectives, options.directives)\n  }))\n}\n\nexport const createBundleRenderer = createBundleRendererCreator(createRenderer)\n"
  },
  {
    "path": "vue/src/platforms/web/runtime/class-util.js",
    "content": "/* @flow */\n\n/**\n * Add class with compatibility for SVG since classList is not supported on\n * SVG elements in IE\n */\nexport function addClass (el: HTMLElement, cls: ?string) {\n  /* istanbul ignore if */\n  if (!cls || !(cls = cls.trim())) {\n    return\n  }\n\n  /* istanbul ignore else */\n  if (el.classList) {\n    if (cls.indexOf(' ') > -1) {\n      cls.split(/\\s+/).forEach(c => el.classList.add(c))\n    } else {\n      el.classList.add(cls)\n    }\n  } else {\n    const cur = ` ${el.getAttribute('class') || ''} `\n    if (cur.indexOf(' ' + cls + ' ') < 0) {\n      el.setAttribute('class', (cur + cls).trim())\n    }\n  }\n}\n\n/**\n * Remove class with compatibility for SVG since classList is not supported on\n * SVG elements in IE\n */\nexport function removeClass (el: HTMLElement, cls: ?string) {\n  /* istanbul ignore if */\n  if (!cls || !(cls = cls.trim())) {\n    return\n  }\n\n  /* istanbul ignore else */\n  if (el.classList) {\n    if (cls.indexOf(' ') > -1) {\n      cls.split(/\\s+/).forEach(c => el.classList.remove(c))\n    } else {\n      el.classList.remove(cls)\n    }\n    if (!el.classList.length) {\n      el.removeAttribute('class')\n    }\n  } else {\n    let cur = ` ${el.getAttribute('class') || ''} `\n    const tar = ' ' + cls + ' '\n    while (cur.indexOf(tar) >= 0) {\n      cur = cur.replace(tar, ' ')\n    }\n    cur = cur.trim()\n    if (cur) {\n      el.setAttribute('class', cur)\n    } else {\n      el.removeAttribute('class')\n    }\n  }\n}\n"
  },
  {
    "path": "vue/src/platforms/web/runtime/components/index.js",
    "content": "import Transition from './transition'\nimport TransitionGroup from './transition-group'\n\nexport default {\n  Transition,\n  TransitionGroup\n}\n"
  },
  {
    "path": "vue/src/platforms/web/runtime/components/transition-group.js",
    "content": "/* @flow */\n\n// Provides transition support for list items.\n// supports move transitions using the FLIP technique.\n\n// Because the vdom's children update algorithm is \"unstable\" - i.e.\n// it doesn't guarantee the relative positioning of removed elements,\n// we force transition-group to update its children into two passes:\n// in the first pass, we remove all nodes that need to be removed,\n// triggering their leaving transition; in the second pass, we insert/move\n// into the final desired state. This way in the second pass removed\n// nodes will remain where they should be.\n\nimport { warn, extend } from 'core/util/index'\nimport { addClass, removeClass } from '../class-util'\nimport { transitionProps, extractTransitionData } from './transition'\n\nimport {\n  hasTransition,\n  getTransitionInfo,\n  transitionEndEvent,\n  addTransitionClass,\n  removeTransitionClass\n} from '../transition-util'\n\nconst props = extend({\n  tag: String,\n  moveClass: String\n}, transitionProps)\n\ndelete props.mode\n\nexport default {\n  props,\n\n  beforeMount () {\n    const update = this._update\n    this._update = (vnode, hydrating) => {\n      // force removing pass\n      this.__patch__(\n        this._vnode,\n        this.kept,\n        false, // hydrating\n        true // removeOnly (!important, avoids unnecessary moves)\n      )\n      this._vnode = this.kept\n      update.call(this, vnode, hydrating)\n    }\n  },\n\n  render (h: Function) {\n    const tag: string = this.tag || this.$vnode.data.tag || 'span'\n    const map: Object = Object.create(null)\n    const prevChildren: Array<VNode> = this.prevChildren = this.children\n    const rawChildren: Array<VNode> = this.$slots.default || []\n    const children: Array<VNode> = this.children = []\n    const transitionData: Object = extractTransitionData(this)\n\n    for (let i = 0; i < rawChildren.length; i++) {\n      const c: VNode = rawChildren[i]\n      if (c.tag) {\n        if (c.key != null && String(c.key).indexOf('__vlist') !== 0) {\n          children.push(c)\n          map[c.key] = c\n          ;(c.data || (c.data = {})).transition = transitionData\n        } else if (process.env.NODE_ENV !== 'production') {\n          const opts: ?VNodeComponentOptions = c.componentOptions\n          const name: string = opts ? (opts.Ctor.options.name || opts.tag || '') : c.tag\n          warn(`<transition-group> children must be keyed: <${name}>`)\n        }\n      }\n    }\n\n    if (prevChildren) {\n      const kept: Array<VNode> = []\n      const removed: Array<VNode> = []\n      for (let i = 0; i < prevChildren.length; i++) {\n        const c: VNode = prevChildren[i]\n        c.data.transition = transitionData\n        c.data.pos = c.elm.getBoundingClientRect()\n        if (map[c.key]) {\n          kept.push(c)\n        } else {\n          removed.push(c)\n        }\n      }\n      this.kept = h(tag, null, kept)\n      this.removed = removed\n    }\n\n    return h(tag, null, children)\n  },\n\n  updated () {\n    const children: Array<VNode> = this.prevChildren\n    const moveClass: string = this.moveClass || ((this.name || 'v') + '-move')\n    if (!children.length || !this.hasMove(children[0].elm, moveClass)) {\n      return\n    }\n\n    // we divide the work into three loops to avoid mixing DOM reads and writes\n    // in each iteration - which helps prevent layout thrashing.\n    children.forEach(callPendingCbs)\n    children.forEach(recordPosition)\n    children.forEach(applyTranslation)\n\n    // force reflow to put everything in position\n    // assign to this to avoid being removed in tree-shaking\n    // $flow-disable-line\n    this._reflow = document.body.offsetHeight\n\n    children.forEach((c: VNode) => {\n      if (c.data.moved) {\n        var el: any = c.elm\n        var s: any = el.style\n        addTransitionClass(el, moveClass)\n        s.transform = s.WebkitTransform = s.transitionDuration = ''\n        el.addEventListener(transitionEndEvent, el._moveCb = function cb (e) {\n          if (!e || /transform$/.test(e.propertyName)) {\n            el.removeEventListener(transitionEndEvent, cb)\n            el._moveCb = null\n            removeTransitionClass(el, moveClass)\n          }\n        })\n      }\n    })\n  },\n\n  methods: {\n    hasMove (el: any, moveClass: string): boolean {\n      /* istanbul ignore if */\n      if (!hasTransition) {\n        return false\n      }\n      /* istanbul ignore if */\n      if (this._hasMove) {\n        return this._hasMove\n      }\n      // Detect whether an element with the move class applied has\n      // CSS transitions. Since the element may be inside an entering\n      // transition at this very moment, we make a clone of it and remove\n      // all other transition classes applied to ensure only the move class\n      // is applied.\n      const clone: HTMLElement = el.cloneNode()\n      if (el._transitionClasses) {\n        el._transitionClasses.forEach((cls: string) => { removeClass(clone, cls) })\n      }\n      addClass(clone, moveClass)\n      clone.style.display = 'none'\n      this.$el.appendChild(clone)\n      const info: Object = getTransitionInfo(clone)\n      this.$el.removeChild(clone)\n      return (this._hasMove = info.hasTransform)\n    }\n  }\n}\n\nfunction callPendingCbs (c: VNode) {\n  /* istanbul ignore if */\n  if (c.elm._moveCb) {\n    c.elm._moveCb()\n  }\n  /* istanbul ignore if */\n  if (c.elm._enterCb) {\n    c.elm._enterCb()\n  }\n}\n\nfunction recordPosition (c: VNode) {\n  c.data.newPos = c.elm.getBoundingClientRect()\n}\n\nfunction applyTranslation (c: VNode) {\n  const oldPos = c.data.pos\n  const newPos = c.data.newPos\n  const dx = oldPos.left - newPos.left\n  const dy = oldPos.top - newPos.top\n  if (dx || dy) {\n    c.data.moved = true\n    const s = c.elm.style\n    s.transform = s.WebkitTransform = `translate(${dx}px,${dy}px)`\n    s.transitionDuration = '0s'\n  }\n}\n"
  },
  {
    "path": "vue/src/platforms/web/runtime/components/transition.js",
    "content": "/* @flow */\n\n// Provides transition support for a single element/component.\n// supports transition mode (out-in / in-out)\n\nimport { warn } from 'core/util/index'\nimport { camelize, extend, isPrimitive } from 'shared/util'\nimport {\n  mergeVNodeHook,\n  isAsyncPlaceholder,\n  getFirstComponentChild\n} from 'core/vdom/helpers/index'\n\nexport const transitionProps = {\n  name: String,\n  appear: Boolean,\n  css: Boolean,\n  mode: String,\n  type: String,\n  enterClass: String,\n  leaveClass: String,\n  enterToClass: String,\n  leaveToClass: String,\n  enterActiveClass: String,\n  leaveActiveClass: String,\n  appearClass: String,\n  appearActiveClass: String,\n  appearToClass: String,\n  duration: [Number, String, Object]\n}\n\n// in case the child is also an abstract component, e.g. <keep-alive>\n// we want to recursively retrieve the real component to be rendered\nfunction getRealChild (vnode: ?VNode): ?VNode {\n  const compOptions: ?VNodeComponentOptions = vnode && vnode.componentOptions\n  if (compOptions && compOptions.Ctor.options.abstract) {\n    return getRealChild(getFirstComponentChild(compOptions.children))\n  } else {\n    return vnode\n  }\n}\n\nexport function extractTransitionData (comp: Component): Object {\n  const data = {}\n  const options: ComponentOptions = comp.$options\n  // props\n  for (const key in options.propsData) {\n    data[key] = comp[key]\n  }\n  // events.\n  // extract listeners and pass them directly to the transition methods\n  const listeners: ?Object = options._parentListeners\n  for (const key in listeners) {\n    data[camelize(key)] = listeners[key]\n  }\n  return data\n}\n\nfunction placeholder (h: Function, rawChild: VNode): ?VNode {\n  if (/\\d-keep-alive$/.test(rawChild.tag)) {\n    return h('keep-alive', {\n      props: rawChild.componentOptions.propsData\n    })\n  }\n}\n\nfunction hasParentTransition (vnode: VNode): ?boolean {\n  while ((vnode = vnode.parent)) {\n    if (vnode.data.transition) {\n      return true\n    }\n  }\n}\n\nfunction isSameChild (child: VNode, oldChild: VNode): boolean {\n  return oldChild.key === child.key && oldChild.tag === child.tag\n}\n\nexport default {\n  name: 'transition',\n  props: transitionProps,\n  abstract: true,\n\n  render (h: Function) {\n    let children: any = this.$slots.default\n    if (!children) {\n      return\n    }\n\n    // filter out text nodes (possible whitespaces)\n    children = children.filter((c: VNode) => c.tag || isAsyncPlaceholder(c))\n    /* istanbul ignore if */\n    if (!children.length) {\n      return\n    }\n\n    // warn multiple elements\n    if (process.env.NODE_ENV !== 'production' && children.length > 1) {\n      warn(\n        '<transition> can only be used on a single element. Use ' +\n        '<transition-group> for lists.',\n        this.$parent\n      )\n    }\n\n    const mode: string = this.mode\n\n    // warn invalid mode\n    if (process.env.NODE_ENV !== 'production' &&\n      mode && mode !== 'in-out' && mode !== 'out-in'\n    ) {\n      warn(\n        'invalid <transition> mode: ' + mode,\n        this.$parent\n      )\n    }\n\n    const rawChild: VNode = children[0]\n\n    // if this is a component root node and the component's\n    // parent container node also has transition, skip.\n    if (hasParentTransition(this.$vnode)) {\n      return rawChild\n    }\n\n    // apply transition data to child\n    // use getRealChild() to ignore abstract components e.g. keep-alive\n    const child: ?VNode = getRealChild(rawChild)\n    /* istanbul ignore if */\n    if (!child) {\n      return rawChild\n    }\n\n    if (this._leaving) {\n      return placeholder(h, rawChild)\n    }\n\n    // ensure a key that is unique to the vnode type and to this transition\n    // component instance. This key will be used to remove pending leaving nodes\n    // during entering.\n    const id: string = `__transition-${this._uid}-`\n    child.key = child.key == null\n      ? child.isComment\n        ? id + 'comment'\n        : id + child.tag\n      : isPrimitive(child.key)\n        ? (String(child.key).indexOf(id) === 0 ? child.key : id + child.key)\n        : child.key\n\n    const data: Object = (child.data || (child.data = {})).transition = extractTransitionData(this)\n    const oldRawChild: VNode = this._vnode\n    const oldChild: VNode = getRealChild(oldRawChild)\n\n    // mark v-show\n    // so that the transition module can hand over the control to the directive\n    if (child.data.directives && child.data.directives.some(d => d.name === 'show')) {\n      child.data.show = true\n    }\n\n    if (\n      oldChild &&\n      oldChild.data &&\n      !isSameChild(child, oldChild) &&\n      !isAsyncPlaceholder(oldChild) &&\n      // #6687 component root is a comment node\n      !(oldChild.componentInstance && oldChild.componentInstance._vnode.isComment)\n    ) {\n      // replace old child transition data with fresh one\n      // important for dynamic transitions!\n      const oldData: Object = oldChild.data.transition = extend({}, data)\n      // handle transition mode\n      if (mode === 'out-in') {\n        // return placeholder node and queue update when leave finishes\n        this._leaving = true\n        mergeVNodeHook(oldData, 'afterLeave', () => {\n          this._leaving = false\n          this.$forceUpdate()\n        })\n        return placeholder(h, rawChild)\n      } else if (mode === 'in-out') {\n        if (isAsyncPlaceholder(child)) {\n          return oldRawChild\n        }\n        let delayedLeave\n        const performLeave = () => { delayedLeave() }\n        mergeVNodeHook(data, 'afterEnter', performLeave)\n        mergeVNodeHook(data, 'enterCancelled', performLeave)\n        mergeVNodeHook(oldData, 'delayLeave', leave => { delayedLeave = leave })\n      }\n    }\n\n    return rawChild\n  }\n}\n"
  },
  {
    "path": "vue/src/platforms/web/runtime/directives/index.js",
    "content": "import model from './model'\nimport show from './show'\n\nexport default {\n  model,\n  show\n}\n"
  },
  {
    "path": "vue/src/platforms/web/runtime/directives/model.js",
    "content": "/**\n * Not type checking this file because flow doesn't like attaching\n * properties to Elements.\n */\n\nimport { isTextInputType } from 'web/util/element'\nimport { looseEqual, looseIndexOf } from 'shared/util'\nimport { mergeVNodeHook } from 'core/vdom/helpers/index'\nimport { warn, isIE9, isIE, isEdge } from 'core/util/index'\n\n/* istanbul ignore if */\nif (isIE9) {\n  // http://www.matts411.com/post/internet-explorer-9-oninput/\n  document.addEventListener('selectionchange', () => {\n    const el = document.activeElement\n    if (el && el.vmodel) {\n      trigger(el, 'input')\n    }\n  })\n}\n\nconst directive = {\n  inserted (el, binding, vnode, oldVnode) {\n    if (vnode.tag === 'select') {\n      // #6903\n      if (oldVnode.elm && !oldVnode.elm._vOptions) {\n        mergeVNodeHook(vnode, 'postpatch', () => {\n          directive.componentUpdated(el, binding, vnode)\n        })\n      } else {\n        setSelected(el, binding, vnode.context)\n      }\n      el._vOptions = [].map.call(el.options, getValue)\n    } else if (vnode.tag === 'textarea' || isTextInputType(el.type)) {\n      el._vModifiers = binding.modifiers\n      if (!binding.modifiers.lazy) {\n        el.addEventListener('compositionstart', onCompositionStart)\n        el.addEventListener('compositionend', onCompositionEnd)\n        // Safari < 10.2 & UIWebView doesn't fire compositionend when\n        // switching focus before confirming composition choice\n        // this also fixes the issue where some browsers e.g. iOS Chrome\n        // fires \"change\" instead of \"input\" on autocomplete.\n        el.addEventListener('change', onCompositionEnd)\n        /* istanbul ignore if */\n        if (isIE9) {\n          el.vmodel = true\n        }\n      }\n    }\n  },\n\n  componentUpdated (el, binding, vnode) {\n    if (vnode.tag === 'select') {\n      setSelected(el, binding, vnode.context)\n      // in case the options rendered by v-for have changed,\n      // it's possible that the value is out-of-sync with the rendered options.\n      // detect such cases and filter out values that no longer has a matching\n      // option in the DOM.\n      const prevOptions = el._vOptions\n      const curOptions = el._vOptions = [].map.call(el.options, getValue)\n      if (curOptions.some((o, i) => !looseEqual(o, prevOptions[i]))) {\n        // trigger change event if\n        // no matching option found for at least one value\n        const needReset = el.multiple\n          ? binding.value.some(v => hasNoMatchingOption(v, curOptions))\n          : binding.value !== binding.oldValue && hasNoMatchingOption(binding.value, curOptions)\n        if (needReset) {\n          trigger(el, 'change')\n        }\n      }\n    }\n  }\n}\n\nfunction setSelected (el, binding, vm) {\n  actuallySetSelected(el, binding, vm)\n  /* istanbul ignore if */\n  if (isIE || isEdge) {\n    setTimeout(() => {\n      actuallySetSelected(el, binding, vm)\n    }, 0)\n  }\n}\n\nfunction actuallySetSelected (el, binding, vm) {\n  const value = binding.value\n  const isMultiple = el.multiple\n  if (isMultiple && !Array.isArray(value)) {\n    process.env.NODE_ENV !== 'production' && warn(\n      `<select multiple v-model=\"${binding.expression}\"> ` +\n      `expects an Array value for its binding, but got ${\n        Object.prototype.toString.call(value).slice(8, -1)\n      }`,\n      vm\n    )\n    return\n  }\n  let selected, option\n  for (let i = 0, l = el.options.length; i < l; i++) {\n    option = el.options[i]\n    if (isMultiple) {\n      selected = looseIndexOf(value, getValue(option)) > -1\n      if (option.selected !== selected) {\n        option.selected = selected\n      }\n    } else {\n      if (looseEqual(getValue(option), value)) {\n        if (el.selectedIndex !== i) {\n          el.selectedIndex = i\n        }\n        return\n      }\n    }\n  }\n  if (!isMultiple) {\n    el.selectedIndex = -1\n  }\n}\n\nfunction hasNoMatchingOption (value, options) {\n  return options.every(o => !looseEqual(o, value))\n}\n\nfunction getValue (option) {\n  return '_value' in option\n    ? option._value\n    : option.value\n}\n\nfunction onCompositionStart (e) {\n  e.target.composing = true\n}\n\nfunction onCompositionEnd (e) {\n  // prevent triggering an input event for no reason\n  if (!e.target.composing) return\n  e.target.composing = false\n  trigger(e.target, 'input')\n}\n\nfunction trigger (el, type) {\n  const e = document.createEvent('HTMLEvents')\n  e.initEvent(type, true, true)\n  el.dispatchEvent(e)\n}\n\nexport default directive\n"
  },
  {
    "path": "vue/src/platforms/web/runtime/directives/show.js",
    "content": "/* @flow */\n\nimport { enter, leave } from '../modules/transition'\n\n// recursively search for possible transition defined inside the component root\nfunction locateNode (vnode: VNode): VNodeWithData {\n  return vnode.componentInstance && (!vnode.data || !vnode.data.transition)\n    ? locateNode(vnode.componentInstance._vnode)\n    : vnode\n}\n\nexport default {\n  bind (el: any, { value }: VNodeDirective, vnode: VNodeWithData) {\n    vnode = locateNode(vnode)\n    const transition = vnode.data && vnode.data.transition\n    const originalDisplay = el.__vOriginalDisplay =\n      el.style.display === 'none' ? '' : el.style.display\n    if (value && transition) {\n      vnode.data.show = true\n      enter(vnode, () => {\n        el.style.display = originalDisplay\n      })\n    } else {\n      el.style.display = value ? originalDisplay : 'none'\n    }\n  },\n\n  update (el: any, { value, oldValue }: VNodeDirective, vnode: VNodeWithData) {\n    /* istanbul ignore if */\n    if (!value === !oldValue) return\n    vnode = locateNode(vnode)\n    const transition = vnode.data && vnode.data.transition\n    if (transition) {\n      vnode.data.show = true\n      if (value) {\n        enter(vnode, () => {\n          el.style.display = el.__vOriginalDisplay\n        })\n      } else {\n        leave(vnode, () => {\n          el.style.display = 'none'\n        })\n      }\n    } else {\n      el.style.display = value ? el.__vOriginalDisplay : 'none'\n    }\n  },\n\n  unbind (\n    el: any,\n    binding: VNodeDirective,\n    vnode: VNodeWithData,\n    oldVnode: VNodeWithData,\n    isDestroy: boolean\n  ) {\n    if (!isDestroy) {\n      el.style.display = el.__vOriginalDisplay\n    }\n  }\n}\n"
  },
  {
    "path": "vue/src/platforms/web/runtime/index.js",
    "content": "/* @flow */\n\nimport Vue from 'core/index'\nimport config from 'core/config'\nimport { extend, noop } from 'shared/util'\nimport { mountComponent } from 'core/instance/lifecycle'\nimport { devtools, inBrowser, isChrome } from 'core/util/index'\n\nimport {\n  query,\n  mustUseProp,\n  isReservedTag,\n  isReservedAttr,\n  getTagNamespace,\n  isUnknownElement\n} from 'web/util/index'\n\nimport { patch } from './patch'\nimport platformDirectives from './directives/index'\nimport platformComponents from './components/index'\n\n// install platform specific utils\nVue.config.mustUseProp = mustUseProp\nVue.config.isReservedTag = isReservedTag\nVue.config.isReservedAttr = isReservedAttr\nVue.config.getTagNamespace = getTagNamespace\nVue.config.isUnknownElement = isUnknownElement\n\n// install platform runtime directives & components\nextend(Vue.options.directives, platformDirectives)\nextend(Vue.options.components, platformComponents)\n\n// install platform patch function\nVue.prototype.__patch__ = inBrowser ? patch : noop\n\n// public mount method\nVue.prototype.$mount = function (\n  el?: string | Element,\n  hydrating?: boolean\n): Component {\n  el = el && inBrowser ? query(el) : undefined\n  return mountComponent(this, el, hydrating)\n}\n\n// devtools global hook\n/* istanbul ignore next */\nif (inBrowser) {\n  setTimeout(() => {\n    if (config.devtools) {\n      if (devtools) {\n        devtools.emit('init', Vue)\n      } else if (\n        process.env.NODE_ENV !== 'production' &&\n        process.env.NODE_ENV !== 'test' &&\n        isChrome\n      ) {\n        console[console.info ? 'info' : 'log'](\n          'Download the Vue Devtools extension for a better development experience:\\n' +\n          'https://github.com/vuejs/vue-devtools'\n        )\n      }\n    }\n    if (process.env.NODE_ENV !== 'production' &&\n      process.env.NODE_ENV !== 'test' &&\n      config.productionTip !== false &&\n      typeof console !== 'undefined'\n    ) {\n      console[console.info ? 'info' : 'log'](\n        `You are running Vue in development mode.\\n` +\n        `Make sure to turn on production mode when deploying for production.\\n` +\n        `See more tips at https://vuejs.org/guide/deployment.html`\n      )\n    }\n  }, 0)\n}\n\nexport default Vue\n"
  },
  {
    "path": "vue/src/platforms/web/runtime/modules/attrs.js",
    "content": "/* @flow */\n\nimport { isIE, isIE9, isEdge } from 'core/util/env'\n\nimport {\n  extend,\n  isDef,\n  isUndef\n} from 'shared/util'\n\nimport {\n  isXlink,\n  xlinkNS,\n  getXlinkProp,\n  isBooleanAttr,\n  isEnumeratedAttr,\n  isFalsyAttrValue\n} from 'web/util/index'\n\nfunction updateAttrs (oldVnode: VNodeWithData, vnode: VNodeWithData) {\n  const opts = vnode.componentOptions\n  if (isDef(opts) && opts.Ctor.options.inheritAttrs === false) {\n    return\n  }\n  if (isUndef(oldVnode.data.attrs) && isUndef(vnode.data.attrs)) {\n    return\n  }\n  let key, cur, old\n  const elm = vnode.elm\n  const oldAttrs = oldVnode.data.attrs || {}\n  let attrs: any = vnode.data.attrs || {}\n  // clone observed objects, as the user probably wants to mutate it\n  if (isDef(attrs.__ob__)) {\n    attrs = vnode.data.attrs = extend({}, attrs)\n  }\n\n  for (key in attrs) {\n    cur = attrs[key]\n    old = oldAttrs[key]\n    if (old !== cur) {\n      setAttr(elm, key, cur)\n    }\n  }\n  // #4391: in IE9, setting type can reset value for input[type=radio]\n  // #6666: IE/Edge forces progress value down to 1 before setting a max\n  /* istanbul ignore if */\n  if ((isIE || isEdge) && attrs.value !== oldAttrs.value) {\n    setAttr(elm, 'value', attrs.value)\n  }\n  for (key in oldAttrs) {\n    if (isUndef(attrs[key])) {\n      if (isXlink(key)) {\n        elm.removeAttributeNS(xlinkNS, getXlinkProp(key))\n      } else if (!isEnumeratedAttr(key)) {\n        elm.removeAttribute(key)\n      }\n    }\n  }\n}\n\nfunction setAttr (el: Element, key: string, value: any) {\n  if (el.tagName.indexOf('-') > -1) {\n    baseSetAttr(el, key, value)\n  } else if (isBooleanAttr(key)) {\n    // set attribute for blank value\n    // e.g. <option disabled>Select one</option>\n    if (isFalsyAttrValue(value)) {\n      el.removeAttribute(key)\n    } else {\n      // technically allowfullscreen is a boolean attribute for <iframe>,\n      // but Flash expects a value of \"true\" when used on <embed> tag\n      value = key === 'allowfullscreen' && el.tagName === 'EMBED'\n        ? 'true'\n        : key\n      el.setAttribute(key, value)\n    }\n  } else if (isEnumeratedAttr(key)) {\n    el.setAttribute(key, isFalsyAttrValue(value) || value === 'false' ? 'false' : 'true')\n  } else if (isXlink(key)) {\n    if (isFalsyAttrValue(value)) {\n      el.removeAttributeNS(xlinkNS, getXlinkProp(key))\n    } else {\n      el.setAttributeNS(xlinkNS, key, value)\n    }\n  } else {\n    baseSetAttr(el, key, value)\n  }\n}\n\nfunction baseSetAttr (el, key, value) {\n  if (isFalsyAttrValue(value)) {\n    el.removeAttribute(key)\n  } else {\n    // #7138: IE10 & 11 fires input event when setting placeholder on\n    // <textarea>... block the first input event and remove the blocker\n    // immediately.\n    /* istanbul ignore if */\n    if (\n      isIE && !isIE9 &&\n      el.tagName === 'TEXTAREA' &&\n      key === 'placeholder' && !el.__ieph\n    ) {\n      const blocker = e => {\n        e.stopImmediatePropagation()\n        el.removeEventListener('input', blocker)\n      }\n      el.addEventListener('input', blocker)\n      // $flow-disable-line\n      el.__ieph = true /* IE placeholder patched */\n    }\n    el.setAttribute(key, value)\n  }\n}\n\nexport default {\n  create: updateAttrs,\n  update: updateAttrs\n}\n"
  },
  {
    "path": "vue/src/platforms/web/runtime/modules/class.js",
    "content": "/* @flow */\n\nimport {\n  isDef,\n  isUndef\n} from 'shared/util'\n\nimport {\n  concat,\n  stringifyClass,\n  genClassForVnode\n} from 'web/util/index'\n\nfunction updateClass (oldVnode: any, vnode: any) {\n  const el = vnode.elm\n  const data: VNodeData = vnode.data\n  const oldData: VNodeData = oldVnode.data\n  if (\n    isUndef(data.staticClass) &&\n    isUndef(data.class) && (\n      isUndef(oldData) || (\n        isUndef(oldData.staticClass) &&\n        isUndef(oldData.class)\n      )\n    )\n  ) {\n    return\n  }\n\n  let cls = genClassForVnode(vnode)\n\n  // handle transition classes\n  const transitionClass = el._transitionClasses\n  if (isDef(transitionClass)) {\n    cls = concat(cls, stringifyClass(transitionClass))\n  }\n\n  // set the class\n  if (cls !== el._prevClass) {\n    el.setAttribute('class', cls)\n    el._prevClass = cls\n  }\n}\n\nexport default {\n  create: updateClass,\n  update: updateClass\n}\n"
  },
  {
    "path": "vue/src/platforms/web/runtime/modules/dom-props.js",
    "content": "/* @flow */\n\nimport { isDef, isUndef, extend, toNumber } from 'shared/util'\n\nfunction updateDOMProps (oldVnode: VNodeWithData, vnode: VNodeWithData) {\n  if (isUndef(oldVnode.data.domProps) && isUndef(vnode.data.domProps)) {\n    return\n  }\n  let key, cur\n  const elm: any = vnode.elm\n  const oldProps = oldVnode.data.domProps || {}\n  let props = vnode.data.domProps || {}\n  // clone observed objects, as the user probably wants to mutate it\n  if (isDef(props.__ob__)) {\n    props = vnode.data.domProps = extend({}, props)\n  }\n\n  for (key in oldProps) {\n    if (isUndef(props[key])) {\n      elm[key] = ''\n    }\n  }\n  for (key in props) {\n    cur = props[key]\n    // ignore children if the node has textContent or innerHTML,\n    // as these will throw away existing DOM nodes and cause removal errors\n    // on subsequent patches (#3360)\n    if (key === 'textContent' || key === 'innerHTML') {\n      if (vnode.children) vnode.children.length = 0\n      if (cur === oldProps[key]) continue\n      // #6601 work around Chrome version <= 55 bug where single textNode\n      // replaced by innerHTML/textContent retains its parentNode property\n      if (elm.childNodes.length === 1) {\n        elm.removeChild(elm.childNodes[0])\n      }\n    }\n\n    if (key === 'value') {\n      // store value as _value as well since\n      // non-string values will be stringified\n      elm._value = cur\n      // avoid resetting cursor position when value is the same\n      const strCur = isUndef(cur) ? '' : String(cur)\n      if (shouldUpdateValue(elm, strCur)) {\n        elm.value = strCur\n      }\n    } else {\n      elm[key] = cur\n    }\n  }\n}\n\n// check platforms/web/util/attrs.js acceptValue\ntype acceptValueElm = HTMLInputElement | HTMLSelectElement | HTMLOptionElement;\n\nfunction shouldUpdateValue (elm: acceptValueElm, checkVal: string): boolean {\n  return (!elm.composing && (\n    elm.tagName === 'OPTION' ||\n    isNotInFocusAndDirty(elm, checkVal) ||\n    isDirtyWithModifiers(elm, checkVal)\n  ))\n}\n\nfunction isNotInFocusAndDirty (elm: acceptValueElm, checkVal: string): boolean {\n  // return true when textbox (.number and .trim) loses focus and its value is\n  // not equal to the updated value\n  let notInFocus = true\n  // #6157\n  // work around IE bug when accessing document.activeElement in an iframe\n  try { notInFocus = document.activeElement !== elm } catch (e) {}\n  return notInFocus && elm.value !== checkVal\n}\n\nfunction isDirtyWithModifiers (elm: any, newVal: string): boolean {\n  const value = elm.value\n  const modifiers = elm._vModifiers // injected by v-model runtime\n  if (isDef(modifiers)) {\n    if (modifiers.lazy) {\n      // inputs with lazy should only be updated when not in focus\n      return false\n    }\n    if (modifiers.number) {\n      return toNumber(value) !== toNumber(newVal)\n    }\n    if (modifiers.trim) {\n      return value.trim() !== newVal.trim()\n    }\n  }\n  return value !== newVal\n}\n\nexport default {\n  create: updateDOMProps,\n  update: updateDOMProps\n}\n"
  },
  {
    "path": "vue/src/platforms/web/runtime/modules/events.js",
    "content": "/* @flow */\n\nimport { isDef, isUndef } from 'shared/util'\nimport { updateListeners } from 'core/vdom/helpers/index'\nimport { withMacroTask, isIE, supportsPassive } from 'core/util/index'\nimport { RANGE_TOKEN, CHECKBOX_RADIO_TOKEN } from 'web/compiler/directives/model'\n\n// normalize v-model event tokens that can only be determined at runtime.\n// it's important to place the event as the first in the array because\n// the whole point is ensuring the v-model callback gets called before\n// user-attached handlers.\nfunction normalizeEvents (on) {\n  /* istanbul ignore if */\n  if (isDef(on[RANGE_TOKEN])) {\n    // IE input[type=range] only supports `change` event\n    const event = isIE ? 'change' : 'input'\n    on[event] = [].concat(on[RANGE_TOKEN], on[event] || [])\n    delete on[RANGE_TOKEN]\n  }\n  // This was originally intended to fix #4521 but no longer necessary\n  // after 2.5. Keeping it for backwards compat with generated code from < 2.4\n  /* istanbul ignore if */\n  if (isDef(on[CHECKBOX_RADIO_TOKEN])) {\n    on.change = [].concat(on[CHECKBOX_RADIO_TOKEN], on.change || [])\n    delete on[CHECKBOX_RADIO_TOKEN]\n  }\n}\n\nlet target: any\n\nfunction createOnceHandler (handler, event, capture) {\n  const _target = target // save current target element in closure\n  return function onceHandler () {\n    const res = handler.apply(null, arguments)\n    if (res !== null) {\n      remove(event, onceHandler, capture, _target)\n    }\n  }\n}\n\nfunction add (\n  event: string,\n  handler: Function,\n  once: boolean,\n  capture: boolean,\n  passive: boolean\n) {\n  handler = withMacroTask(handler)\n  if (once) handler = createOnceHandler(handler, event, capture)\n  target.addEventListener(\n    event,\n    handler,\n    supportsPassive\n      ? { capture, passive }\n      : capture\n  )\n}\n\nfunction remove (\n  event: string,\n  handler: Function,\n  capture: boolean,\n  _target?: HTMLElement\n) {\n  (_target || target).removeEventListener(\n    event,\n    handler._withTask || handler,\n    capture\n  )\n}\n\nfunction updateDOMListeners (oldVnode: VNodeWithData, vnode: VNodeWithData) {\n  if (isUndef(oldVnode.data.on) && isUndef(vnode.data.on)) {\n    return\n  }\n  const on = vnode.data.on || {}\n  const oldOn = oldVnode.data.on || {}\n  target = vnode.elm\n  normalizeEvents(on)\n  updateListeners(on, oldOn, add, remove, vnode.context)\n  target = undefined\n}\n\nexport default {\n  create: updateDOMListeners,\n  update: updateDOMListeners\n}\n"
  },
  {
    "path": "vue/src/platforms/web/runtime/modules/index.js",
    "content": "import attrs from './attrs'\nimport klass from './class'\nimport events from './events'\nimport domProps from './dom-props'\nimport style from './style'\nimport transition from './transition'\n\nexport default [\n  attrs,\n  klass,\n  events,\n  domProps,\n  style,\n  transition\n]\n"
  },
  {
    "path": "vue/src/platforms/web/runtime/modules/style.js",
    "content": "/* @flow */\n\nimport { getStyle, normalizeStyleBinding } from 'web/util/style'\nimport { cached, camelize, extend, isDef, isUndef } from 'shared/util'\n\nconst cssVarRE = /^--/\nconst importantRE = /\\s*!important$/\nconst setProp = (el, name, val) => {\n  /* istanbul ignore if */\n  if (cssVarRE.test(name)) {\n    el.style.setProperty(name, val)\n  } else if (importantRE.test(val)) {\n    el.style.setProperty(name, val.replace(importantRE, ''), 'important')\n  } else {\n    const normalizedName = normalize(name)\n    if (Array.isArray(val)) {\n      // Support values array created by autoprefixer, e.g.\n      // {display: [\"-webkit-box\", \"-ms-flexbox\", \"flex\"]}\n      // Set them one by one, and the browser will only set those it can recognize\n      for (let i = 0, len = val.length; i < len; i++) {\n        el.style[normalizedName] = val[i]\n      }\n    } else {\n      el.style[normalizedName] = val\n    }\n  }\n}\n\nconst vendorNames = ['Webkit', 'Moz', 'ms']\n\nlet emptyStyle\nconst normalize = cached(function (prop) {\n  emptyStyle = emptyStyle || document.createElement('div').style\n  prop = camelize(prop)\n  if (prop !== 'filter' && (prop in emptyStyle)) {\n    return prop\n  }\n  const capName = prop.charAt(0).toUpperCase() + prop.slice(1)\n  for (let i = 0; i < vendorNames.length; i++) {\n    const name = vendorNames[i] + capName\n    if (name in emptyStyle) {\n      return name\n    }\n  }\n})\n\nfunction updateStyle (oldVnode: VNodeWithData, vnode: VNodeWithData) {\n  const data = vnode.data\n  const oldData = oldVnode.data\n\n  if (isUndef(data.staticStyle) && isUndef(data.style) &&\n    isUndef(oldData.staticStyle) && isUndef(oldData.style)\n  ) {\n    return\n  }\n\n  let cur, name\n  const el: any = vnode.elm\n  const oldStaticStyle: any = oldData.staticStyle\n  const oldStyleBinding: any = oldData.normalizedStyle || oldData.style || {}\n\n  // if static style exists, stylebinding already merged into it when doing normalizeStyleData\n  const oldStyle = oldStaticStyle || oldStyleBinding\n\n  const style = normalizeStyleBinding(vnode.data.style) || {}\n\n  // store normalized style under a different key for next diff\n  // make sure to clone it if it's reactive, since the user likely wants\n  // to mutate it.\n  vnode.data.normalizedStyle = isDef(style.__ob__)\n    ? extend({}, style)\n    : style\n\n  const newStyle = getStyle(vnode, true)\n\n  for (name in oldStyle) {\n    if (isUndef(newStyle[name])) {\n      setProp(el, name, '')\n    }\n  }\n  for (name in newStyle) {\n    cur = newStyle[name]\n    if (cur !== oldStyle[name]) {\n      // ie9 setting to null has no effect, must use empty string\n      setProp(el, name, cur == null ? '' : cur)\n    }\n  }\n}\n\nexport default {\n  create: updateStyle,\n  update: updateStyle\n}\n"
  },
  {
    "path": "vue/src/platforms/web/runtime/modules/transition.js",
    "content": "/* @flow */\n\nimport { inBrowser, isIE9, warn } from 'core/util/index'\nimport { mergeVNodeHook } from 'core/vdom/helpers/index'\nimport { activeInstance } from 'core/instance/lifecycle'\n\nimport {\n  once,\n  isDef,\n  isUndef,\n  isObject,\n  toNumber\n} from 'shared/util'\n\nimport {\n  nextFrame,\n  resolveTransition,\n  whenTransitionEnds,\n  addTransitionClass,\n  removeTransitionClass\n} from '../transition-util'\n\nexport function enter (vnode: VNodeWithData, toggleDisplay: ?() => void) {\n  const el: any = vnode.elm\n\n  // call leave callback now\n  if (isDef(el._leaveCb)) {\n    el._leaveCb.cancelled = true\n    el._leaveCb()\n  }\n\n  const data = resolveTransition(vnode.data.transition)\n  if (isUndef(data)) {\n    return\n  }\n\n  /* istanbul ignore if */\n  if (isDef(el._enterCb) || el.nodeType !== 1) {\n    return\n  }\n\n  const {\n    css,\n    type,\n    enterClass,\n    enterToClass,\n    enterActiveClass,\n    appearClass,\n    appearToClass,\n    appearActiveClass,\n    beforeEnter,\n    enter,\n    afterEnter,\n    enterCancelled,\n    beforeAppear,\n    appear,\n    afterAppear,\n    appearCancelled,\n    duration\n  } = data\n\n  // activeInstance will always be the <transition> component managing this\n  // transition. One edge case to check is when the <transition> is placed\n  // as the root node of a child component. In that case we need to check\n  // <transition>'s parent for appear check.\n  let context = activeInstance\n  let transitionNode = activeInstance.$vnode\n  while (transitionNode && transitionNode.parent) {\n    transitionNode = transitionNode.parent\n    context = transitionNode.context\n  }\n\n  const isAppear = !context._isMounted || !vnode.isRootInsert\n\n  if (isAppear && !appear && appear !== '') {\n    return\n  }\n\n  const startClass = isAppear && appearClass\n    ? appearClass\n    : enterClass\n  const activeClass = isAppear && appearActiveClass\n    ? appearActiveClass\n    : enterActiveClass\n  const toClass = isAppear && appearToClass\n    ? appearToClass\n    : enterToClass\n\n  const beforeEnterHook = isAppear\n    ? (beforeAppear || beforeEnter)\n    : beforeEnter\n  const enterHook = isAppear\n    ? (typeof appear === 'function' ? appear : enter)\n    : enter\n  const afterEnterHook = isAppear\n    ? (afterAppear || afterEnter)\n    : afterEnter\n  const enterCancelledHook = isAppear\n    ? (appearCancelled || enterCancelled)\n    : enterCancelled\n\n  const explicitEnterDuration: any = toNumber(\n    isObject(duration)\n      ? duration.enter\n      : duration\n  )\n\n  if (process.env.NODE_ENV !== 'production' && explicitEnterDuration != null) {\n    checkDuration(explicitEnterDuration, 'enter', vnode)\n  }\n\n  const expectsCSS = css !== false && !isIE9\n  const userWantsControl = getHookArgumentsLength(enterHook)\n\n  const cb = el._enterCb = once(() => {\n    if (expectsCSS) {\n      removeTransitionClass(el, toClass)\n      removeTransitionClass(el, activeClass)\n    }\n    if (cb.cancelled) {\n      if (expectsCSS) {\n        removeTransitionClass(el, startClass)\n      }\n      enterCancelledHook && enterCancelledHook(el)\n    } else {\n      afterEnterHook && afterEnterHook(el)\n    }\n    el._enterCb = null\n  })\n\n  if (!vnode.data.show) {\n    // remove pending leave element on enter by injecting an insert hook\n    mergeVNodeHook(vnode, 'insert', () => {\n      const parent = el.parentNode\n      const pendingNode = parent && parent._pending && parent._pending[vnode.key]\n      if (pendingNode &&\n        pendingNode.tag === vnode.tag &&\n        pendingNode.elm._leaveCb\n      ) {\n        pendingNode.elm._leaveCb()\n      }\n      enterHook && enterHook(el, cb)\n    })\n  }\n\n  // start enter transition\n  beforeEnterHook && beforeEnterHook(el)\n  if (expectsCSS) {\n    addTransitionClass(el, startClass)\n    addTransitionClass(el, activeClass)\n    nextFrame(() => {\n      removeTransitionClass(el, startClass)\n      if (!cb.cancelled) {\n        addTransitionClass(el, toClass)\n        if (!userWantsControl) {\n          if (isValidDuration(explicitEnterDuration)) {\n            setTimeout(cb, explicitEnterDuration)\n          } else {\n            whenTransitionEnds(el, type, cb)\n          }\n        }\n      }\n    })\n  }\n\n  if (vnode.data.show) {\n    toggleDisplay && toggleDisplay()\n    enterHook && enterHook(el, cb)\n  }\n\n  if (!expectsCSS && !userWantsControl) {\n    cb()\n  }\n}\n\nexport function leave (vnode: VNodeWithData, rm: Function) {\n  const el: any = vnode.elm\n\n  // call enter callback now\n  if (isDef(el._enterCb)) {\n    el._enterCb.cancelled = true\n    el._enterCb()\n  }\n\n  const data = resolveTransition(vnode.data.transition)\n  if (isUndef(data) || el.nodeType !== 1) {\n    return rm()\n  }\n\n  /* istanbul ignore if */\n  if (isDef(el._leaveCb)) {\n    return\n  }\n\n  const {\n    css,\n    type,\n    leaveClass,\n    leaveToClass,\n    leaveActiveClass,\n    beforeLeave,\n    leave,\n    afterLeave,\n    leaveCancelled,\n    delayLeave,\n    duration\n  } = data\n\n  const expectsCSS = css !== false && !isIE9\n  const userWantsControl = getHookArgumentsLength(leave)\n\n  const explicitLeaveDuration: any = toNumber(\n    isObject(duration)\n      ? duration.leave\n      : duration\n  )\n\n  if (process.env.NODE_ENV !== 'production' && isDef(explicitLeaveDuration)) {\n    checkDuration(explicitLeaveDuration, 'leave', vnode)\n  }\n\n  const cb = el._leaveCb = once(() => {\n    if (el.parentNode && el.parentNode._pending) {\n      el.parentNode._pending[vnode.key] = null\n    }\n    if (expectsCSS) {\n      removeTransitionClass(el, leaveToClass)\n      removeTransitionClass(el, leaveActiveClass)\n    }\n    if (cb.cancelled) {\n      if (expectsCSS) {\n        removeTransitionClass(el, leaveClass)\n      }\n      leaveCancelled && leaveCancelled(el)\n    } else {\n      rm()\n      afterLeave && afterLeave(el)\n    }\n    el._leaveCb = null\n  })\n\n  if (delayLeave) {\n    delayLeave(performLeave)\n  } else {\n    performLeave()\n  }\n\n  function performLeave () {\n    // the delayed leave may have already been cancelled\n    if (cb.cancelled) {\n      return\n    }\n    // record leaving element\n    if (!vnode.data.show) {\n      (el.parentNode._pending || (el.parentNode._pending = {}))[(vnode.key: any)] = vnode\n    }\n    beforeLeave && beforeLeave(el)\n    if (expectsCSS) {\n      addTransitionClass(el, leaveClass)\n      addTransitionClass(el, leaveActiveClass)\n      nextFrame(() => {\n        removeTransitionClass(el, leaveClass)\n        if (!cb.cancelled) {\n          addTransitionClass(el, leaveToClass)\n          if (!userWantsControl) {\n            if (isValidDuration(explicitLeaveDuration)) {\n              setTimeout(cb, explicitLeaveDuration)\n            } else {\n              whenTransitionEnds(el, type, cb)\n            }\n          }\n        }\n      })\n    }\n    leave && leave(el, cb)\n    if (!expectsCSS && !userWantsControl) {\n      cb()\n    }\n  }\n}\n\n// only used in dev mode\nfunction checkDuration (val, name, vnode) {\n  if (typeof val !== 'number') {\n    warn(\n      `<transition> explicit ${name} duration is not a valid number - ` +\n      `got ${JSON.stringify(val)}.`,\n      vnode.context\n    )\n  } else if (isNaN(val)) {\n    warn(\n      `<transition> explicit ${name} duration is NaN - ` +\n      'the duration expression might be incorrect.',\n      vnode.context\n    )\n  }\n}\n\nfunction isValidDuration (val) {\n  return typeof val === 'number' && !isNaN(val)\n}\n\n/**\n * Normalize a transition hook's argument length. The hook may be:\n * - a merged hook (invoker) with the original in .fns\n * - a wrapped component method (check ._length)\n * - a plain function (.length)\n */\nfunction getHookArgumentsLength (fn: Function): boolean {\n  if (isUndef(fn)) {\n    return false\n  }\n  const invokerFns = fn.fns\n  if (isDef(invokerFns)) {\n    // invoker\n    return getHookArgumentsLength(\n      Array.isArray(invokerFns)\n        ? invokerFns[0]\n        : invokerFns\n    )\n  } else {\n    return (fn._length || fn.length) > 1\n  }\n}\n\nfunction _enter (_: any, vnode: VNodeWithData) {\n  if (vnode.data.show !== true) {\n    enter(vnode)\n  }\n}\n\nexport default inBrowser ? {\n  create: _enter,\n  activate: _enter,\n  remove (vnode: VNode, rm: Function) {\n    /* istanbul ignore else */\n    if (vnode.data.show !== true) {\n      leave(vnode, rm)\n    } else {\n      rm()\n    }\n  }\n} : {}\n"
  },
  {
    "path": "vue/src/platforms/web/runtime/node-ops.js",
    "content": "/* @flow */\n\nimport { namespaceMap } from 'web/util/index'\n\nexport function createElement (tagName: string, vnode: VNode): Element {\n  const elm = document.createElement(tagName)\n  if (tagName !== 'select') {\n    return elm\n  }\n  // false or null will remove the attribute but undefined will not\n  if (vnode.data && vnode.data.attrs && vnode.data.attrs.multiple !== undefined) {\n    elm.setAttribute('multiple', 'multiple')\n  }\n  return elm\n}\n\nexport function createElementNS (namespace: string, tagName: string): Element {\n  return document.createElementNS(namespaceMap[namespace], tagName)\n}\n\nexport function createTextNode (text: string): Text {\n  return document.createTextNode(text)\n}\n\nexport function createComment (text: string): Comment {\n  return document.createComment(text)\n}\n\nexport function insertBefore (parentNode: Node, newNode: Node, referenceNode: Node) {\n  parentNode.insertBefore(newNode, referenceNode)\n}\n\nexport function removeChild (node: Node, child: Node) {\n  node.removeChild(child)\n}\n\nexport function appendChild (node: Node, child: Node) {\n  node.appendChild(child)\n}\n\nexport function parentNode (node: Node): ?Node {\n  return node.parentNode\n}\n\nexport function nextSibling (node: Node): ?Node {\n  return node.nextSibling\n}\n\nexport function tagName (node: Element): string {\n  return node.tagName\n}\n\nexport function setTextContent (node: Node, text: string) {\n  node.textContent = text\n}\n\nexport function setStyleScope (node: Element, scopeId: string) {\n  node.setAttribute(scopeId, '')\n}\n"
  },
  {
    "path": "vue/src/platforms/web/runtime/patch.js",
    "content": "/* @flow */\n\nimport * as nodeOps from 'web/runtime/node-ops'\nimport { createPatchFunction } from 'core/vdom/patch'\nimport baseModules from 'core/vdom/modules/index'\nimport platformModules from 'web/runtime/modules/index'\n\n// the directive module should be applied last, after all\n// built-in modules have been applied.\nconst modules = platformModules.concat(baseModules)\n\nexport const patch: Function = createPatchFunction({ nodeOps, modules })\n"
  },
  {
    "path": "vue/src/platforms/web/runtime/transition-util.js",
    "content": "/* @flow */\n\nimport { inBrowser, isIE9 } from 'core/util/index'\nimport { addClass, removeClass } from './class-util'\nimport { remove, extend, cached } from 'shared/util'\n\nexport function resolveTransition (def?: string | Object): ?Object {\n  if (!def) {\n    return\n  }\n  /* istanbul ignore else */\n  if (typeof def === 'object') {\n    const res = {}\n    if (def.css !== false) {\n      extend(res, autoCssTransition(def.name || 'v'))\n    }\n    extend(res, def)\n    return res\n  } else if (typeof def === 'string') {\n    return autoCssTransition(def)\n  }\n}\n\nconst autoCssTransition: (name: string) => Object = cached(name => {\n  return {\n    enterClass: `${name}-enter`,\n    enterToClass: `${name}-enter-to`,\n    enterActiveClass: `${name}-enter-active`,\n    leaveClass: `${name}-leave`,\n    leaveToClass: `${name}-leave-to`,\n    leaveActiveClass: `${name}-leave-active`\n  }\n})\n\nexport const hasTransition = inBrowser && !isIE9\nconst TRANSITION = 'transition'\nconst ANIMATION = 'animation'\n\n// Transition property/event sniffing\nexport let transitionProp = 'transition'\nexport let transitionEndEvent = 'transitionend'\nexport let animationProp = 'animation'\nexport let animationEndEvent = 'animationend'\nif (hasTransition) {\n  /* istanbul ignore if */\n  if (window.ontransitionend === undefined &&\n    window.onwebkittransitionend !== undefined\n  ) {\n    transitionProp = 'WebkitTransition'\n    transitionEndEvent = 'webkitTransitionEnd'\n  }\n  if (window.onanimationend === undefined &&\n    window.onwebkitanimationend !== undefined\n  ) {\n    animationProp = 'WebkitAnimation'\n    animationEndEvent = 'webkitAnimationEnd'\n  }\n}\n\n// binding to window is necessary to make hot reload work in IE in strict mode\nconst raf = inBrowser\n  ? window.requestAnimationFrame\n    ? window.requestAnimationFrame.bind(window)\n    : setTimeout\n  : /* istanbul ignore next */ fn => fn()\n\nexport function nextFrame (fn: Function) {\n  raf(() => {\n    raf(fn)\n  })\n}\n\nexport function addTransitionClass (el: any, cls: string) {\n  const transitionClasses = el._transitionClasses || (el._transitionClasses = [])\n  if (transitionClasses.indexOf(cls) < 0) {\n    transitionClasses.push(cls)\n    addClass(el, cls)\n  }\n}\n\nexport function removeTransitionClass (el: any, cls: string) {\n  if (el._transitionClasses) {\n    remove(el._transitionClasses, cls)\n  }\n  removeClass(el, cls)\n}\n\nexport function whenTransitionEnds (\n  el: Element,\n  expectedType: ?string,\n  cb: Function\n) {\n  const { type, timeout, propCount } = getTransitionInfo(el, expectedType)\n  if (!type) return cb()\n  const event: string = type === TRANSITION ? transitionEndEvent : animationEndEvent\n  let ended = 0\n  const end = () => {\n    el.removeEventListener(event, onEnd)\n    cb()\n  }\n  const onEnd = e => {\n    if (e.target === el) {\n      if (++ended >= propCount) {\n        end()\n      }\n    }\n  }\n  setTimeout(() => {\n    if (ended < propCount) {\n      end()\n    }\n  }, timeout + 1)\n  el.addEventListener(event, onEnd)\n}\n\nconst transformRE = /\\b(transform|all)(,|$)/\n\nexport function getTransitionInfo (el: Element, expectedType?: ?string): {\n  type: ?string;\n  propCount: number;\n  timeout: number;\n  hasTransform: boolean;\n} {\n  const styles: any = window.getComputedStyle(el)\n  const transitionDelays: Array<string> = styles[transitionProp + 'Delay'].split(', ')\n  const transitionDurations: Array<string> = styles[transitionProp + 'Duration'].split(', ')\n  const transitionTimeout: number = getTimeout(transitionDelays, transitionDurations)\n  const animationDelays: Array<string> = styles[animationProp + 'Delay'].split(', ')\n  const animationDurations: Array<string> = styles[animationProp + 'Duration'].split(', ')\n  const animationTimeout: number = getTimeout(animationDelays, animationDurations)\n\n  let type: ?string\n  let timeout = 0\n  let propCount = 0\n  /* istanbul ignore if */\n  if (expectedType === TRANSITION) {\n    if (transitionTimeout > 0) {\n      type = TRANSITION\n      timeout = transitionTimeout\n      propCount = transitionDurations.length\n    }\n  } else if (expectedType === ANIMATION) {\n    if (animationTimeout > 0) {\n      type = ANIMATION\n      timeout = animationTimeout\n      propCount = animationDurations.length\n    }\n  } else {\n    timeout = Math.max(transitionTimeout, animationTimeout)\n    type = timeout > 0\n      ? transitionTimeout > animationTimeout\n        ? TRANSITION\n        : ANIMATION\n      : null\n    propCount = type\n      ? type === TRANSITION\n        ? transitionDurations.length\n        : animationDurations.length\n      : 0\n  }\n  const hasTransform: boolean =\n    type === TRANSITION &&\n    transformRE.test(styles[transitionProp + 'Property'])\n  return {\n    type,\n    timeout,\n    propCount,\n    hasTransform\n  }\n}\n\nfunction getTimeout (delays: Array<string>, durations: Array<string>): number {\n  /* istanbul ignore next */\n  while (delays.length < durations.length) {\n    delays = delays.concat(delays)\n  }\n\n  return Math.max.apply(null, durations.map((d, i) => {\n    return toMs(d) + toMs(delays[i])\n  }))\n}\n\nfunction toMs (s: string): number {\n  return Number(s.slice(0, -1)) * 1000\n}\n"
  },
  {
    "path": "vue/src/platforms/web/server/compiler.js",
    "content": "/* @flow */\n\nimport { baseOptions } from '../compiler/options'\nimport { createCompiler } from 'server/optimizing-compiler/index'\n\nconst { compile, compileToFunctions } = createCompiler(baseOptions)\n\nexport {\n  compile as ssrCompile,\n  compileToFunctions as ssrCompileToFunctions\n}\n"
  },
  {
    "path": "vue/src/platforms/web/server/directives/index.js",
    "content": "import show from './show'\nimport model from './model'\n\nexport default {\n  show,\n  model\n}\n"
  },
  {
    "path": "vue/src/platforms/web/server/directives/model.js",
    "content": "/* @flow */\n\nimport { looseEqual, looseIndexOf } from 'shared/util'\n\n// this is only applied for <select v-model> because it is the only edge case\n// that must be done at runtime instead of compile time.\nexport default function model (node: VNodeWithData, dir: VNodeDirective) {\n  if (!node.children) return\n  const value = dir.value\n  const isMultiple = node.data.attrs && node.data.attrs.multiple\n  for (let i = 0, l = node.children.length; i < l; i++) {\n    const option = node.children[i]\n    if (option.tag === 'option') {\n      if (isMultiple) {\n        const selected =\n          Array.isArray(value) &&\n          (looseIndexOf(value, getValue(option)) > -1)\n        if (selected) {\n          setSelected(option)\n        }\n      } else {\n        if (looseEqual(value, getValue(option))) {\n          setSelected(option)\n          return\n        }\n      }\n    }\n  }\n}\n\nfunction getValue (option) {\n  const data = option.data || {}\n  return (\n    (data.attrs && data.attrs.value) ||\n    (data.domProps && data.domProps.value) ||\n    (option.children && option.children[0] && option.children[0].text)\n  )\n}\n\nfunction setSelected (option) {\n  const data = option.data || (option.data = {})\n  const attrs = data.attrs || (data.attrs = {})\n  attrs.selected = ''\n}\n"
  },
  {
    "path": "vue/src/platforms/web/server/directives/show.js",
    "content": "/* @flow */\n\nexport default function show (node: VNodeWithData, dir: VNodeDirective) {\n  if (!dir.value) {\n    const style: any = node.data.style || (node.data.style = {})\n    if (Array.isArray(style)) {\n      style.push({ display: 'none' })\n    } else {\n      style.display = 'none'\n    }\n  }\n}\n"
  },
  {
    "path": "vue/src/platforms/web/server/modules/attrs.js",
    "content": "/* @flow */\n\nimport { escape } from '../util'\n\nimport {\n  isDef,\n  isUndef,\n  extend\n} from 'shared/util'\n\nimport {\n  isBooleanAttr,\n  isEnumeratedAttr,\n  isFalsyAttrValue\n} from 'web/util/attrs'\n\nexport default function renderAttrs (node: VNodeWithData): string {\n  let attrs = node.data.attrs\n  let res = ''\n\n  const opts = node.parent && node.parent.componentOptions\n  if (isUndef(opts) || opts.Ctor.options.inheritAttrs !== false) {\n    let parent = node.parent\n    while (isDef(parent)) {\n      if (isDef(parent.data) && isDef(parent.data.attrs)) {\n        attrs = extend(extend({}, attrs), parent.data.attrs)\n      }\n      parent = parent.parent\n    }\n  }\n\n  if (isUndef(attrs)) {\n    return res\n  }\n\n  for (const key in attrs) {\n    if (key === 'style') {\n      // leave it to the style module\n      continue\n    }\n    res += renderAttr(key, attrs[key])\n  }\n  return res\n}\n\nexport function renderAttr (key: string, value: string): string {\n  if (isBooleanAttr(key)) {\n    if (!isFalsyAttrValue(value)) {\n      return ` ${key}=\"${key}\"`\n    }\n  } else if (isEnumeratedAttr(key)) {\n    return ` ${key}=\"${isFalsyAttrValue(value) || value === 'false' ? 'false' : 'true'}\"`\n  } else if (!isFalsyAttrValue(value)) {\n    return ` ${key}=\"${escape(String(value))}\"`\n  }\n  return ''\n}\n"
  },
  {
    "path": "vue/src/platforms/web/server/modules/class.js",
    "content": "/* @flow */\n\nimport { escape } from '../util'\nimport { genClassForVnode } from 'web/util/index'\n\nexport default function renderClass (node: VNodeWithData): ?string {\n  const classList = genClassForVnode(node)\n  if (classList !== '') {\n    return ` class=\"${escape(classList)}\"`\n  }\n}\n"
  },
  {
    "path": "vue/src/platforms/web/server/modules/dom-props.js",
    "content": "/* @flow */\n\nimport VNode from 'core/vdom/vnode'\nimport { renderAttr } from './attrs'\nimport { isDef, isUndef, extend } from 'shared/util'\nimport { propsToAttrMap, isRenderableAttr } from '../util'\n\nexport default function renderDOMProps (node: VNodeWithData): string {\n  let props = node.data.domProps\n  let res = ''\n\n  let parent = node.parent\n  while (isDef(parent)) {\n    if (parent.data && parent.data.domProps) {\n      props = extend(extend({}, props), parent.data.domProps)\n    }\n    parent = parent.parent\n  }\n\n  if (isUndef(props)) {\n    return res\n  }\n\n  const attrs = node.data.attrs\n  for (const key in props) {\n    if (key === 'innerHTML') {\n      setText(node, props[key], true)\n    } else if (key === 'textContent') {\n      setText(node, props[key], false)\n    } else if (key === 'value' && node.tag === 'textarea') {\n      setText(node, props[key], false)\n    } else {\n      // $flow-disable-line (WTF?)\n      const attr = propsToAttrMap[key] || key.toLowerCase()\n      if (isRenderableAttr(attr) &&\n        // avoid rendering double-bound props/attrs twice\n        !(isDef(attrs) && isDef(attrs[attr]))\n      ) {\n        res += renderAttr(attr, props[key])\n      }\n    }\n  }\n  return res\n}\n\nfunction setText (node, text, raw) {\n  const child = new VNode(undefined, undefined, undefined, text)\n  child.raw = raw\n  node.children = [child]\n}\n"
  },
  {
    "path": "vue/src/platforms/web/server/modules/index.js",
    "content": "import attrs from './attrs'\nimport domProps from './dom-props'\nimport klass from './class'\nimport style from './style'\n\nexport default [\n  attrs,\n  domProps,\n  klass,\n  style\n]\n"
  },
  {
    "path": "vue/src/platforms/web/server/modules/style.js",
    "content": "/* @flow */\n\nimport { escape } from '../util'\nimport { hyphenate } from 'shared/util'\nimport { getStyle } from 'web/util/style'\n\nexport function genStyle (style: Object): string {\n  let styleText = ''\n  for (const key in style) {\n    const value = style[key]\n    const hyphenatedKey = hyphenate(key)\n    if (Array.isArray(value)) {\n      for (let i = 0, len = value.length; i < len; i++) {\n        styleText += `${hyphenatedKey}:${value[i]};`\n      }\n    } else {\n      styleText += `${hyphenatedKey}:${value};`\n    }\n  }\n  return styleText\n}\n\nexport default function renderStyle (vnode: VNodeWithData): ?string {\n  const styleText = genStyle(getStyle(vnode, false))\n  if (styleText !== '') {\n    return ` style=${JSON.stringify(escape(styleText))}`\n  }\n}\n"
  },
  {
    "path": "vue/src/platforms/web/server/util.js",
    "content": "/* @flow */\n\nimport { makeMap } from 'shared/util'\n\nconst isAttr = makeMap(\n  'accept,accept-charset,accesskey,action,align,alt,async,autocomplete,' +\n  'autofocus,autoplay,autosave,bgcolor,border,buffered,challenge,charset,' +\n  'checked,cite,class,code,codebase,color,cols,colspan,content,http-equiv,' +\n  'name,contenteditable,contextmenu,controls,coords,data,datetime,default,' +\n  'defer,dir,dirname,disabled,download,draggable,dropzone,enctype,method,for,' +\n  'form,formaction,headers,height,hidden,high,href,hreflang,http-equiv,' +\n  'icon,id,ismap,itemprop,keytype,kind,label,lang,language,list,loop,low,' +\n  'manifest,max,maxlength,media,method,GET,POST,min,multiple,email,file,' +\n  'muted,name,novalidate,open,optimum,pattern,ping,placeholder,poster,' +\n  'preload,radiogroup,readonly,rel,required,reversed,rows,rowspan,sandbox,' +\n  'scope,scoped,seamless,selected,shape,size,type,text,password,sizes,span,' +\n  'spellcheck,src,srcdoc,srclang,srcset,start,step,style,summary,tabindex,' +\n  'target,title,type,usemap,value,width,wrap'\n)\n\n/* istanbul ignore next */\nconst isRenderableAttr = (name: string): boolean => {\n  return (\n    isAttr(name) ||\n    name.indexOf('data-') === 0 ||\n    name.indexOf('aria-') === 0\n  )\n}\nexport { isRenderableAttr }\n\nexport const propsToAttrMap = {\n  acceptCharset: 'accept-charset',\n  className: 'class',\n  htmlFor: 'for',\n  httpEquiv: 'http-equiv'\n}\n\nconst ESC = {\n  '<': '&lt;',\n  '>': '&gt;',\n  '\"': '&quot;',\n  '&': '&amp;'\n}\n\nexport function escape (s: string) {\n  return s.replace(/[<>\"&]/g, escapeChar)\n}\n\nfunction escapeChar (a) {\n  return ESC[a] || a\n}\n"
  },
  {
    "path": "vue/src/platforms/web/util/attrs.js",
    "content": "/* @flow */\n\nimport { makeMap } from 'shared/util'\n\n// these are reserved for web because they are directly compiled away\n// during template compilation\nexport const isReservedAttr = makeMap('style,class')\n\n// attributes that should be using props for binding\nconst acceptValue = makeMap('input,textarea,option,select,progress')\nexport const mustUseProp = (tag: string, type: ?string, attr: string): boolean => {\n  return (\n    (attr === 'value' && acceptValue(tag)) && type !== 'button' ||\n    (attr === 'selected' && tag === 'option') ||\n    (attr === 'checked' && tag === 'input') ||\n    (attr === 'muted' && tag === 'video')\n  )\n}\n\nexport const isEnumeratedAttr = makeMap('contenteditable,draggable,spellcheck')\n\nexport const isBooleanAttr = makeMap(\n  'allowfullscreen,async,autofocus,autoplay,checked,compact,controls,declare,' +\n  'default,defaultchecked,defaultmuted,defaultselected,defer,disabled,' +\n  'enabled,formnovalidate,hidden,indeterminate,inert,ismap,itemscope,loop,multiple,' +\n  'muted,nohref,noresize,noshade,novalidate,nowrap,open,pauseonexit,readonly,' +\n  'required,reversed,scoped,seamless,selected,sortable,translate,' +\n  'truespeed,typemustmatch,visible'\n)\n\nexport const xlinkNS = 'http://www.w3.org/1999/xlink'\n\nexport const isXlink = (name: string): boolean => {\n  return name.charAt(5) === ':' && name.slice(0, 5) === 'xlink'\n}\n\nexport const getXlinkProp = (name: string): string => {\n  return isXlink(name) ? name.slice(6, name.length) : ''\n}\n\nexport const isFalsyAttrValue = (val: any): boolean => {\n  return val == null || val === false\n}\n"
  },
  {
    "path": "vue/src/platforms/web/util/class.js",
    "content": "/* @flow */\n\nimport { isDef, isObject } from 'shared/util'\n\nexport function genClassForVnode (vnode: VNodeWithData): string {\n  let data = vnode.data\n  let parentNode = vnode\n  let childNode = vnode\n  while (isDef(childNode.componentInstance)) {\n    childNode = childNode.componentInstance._vnode\n    if (childNode && childNode.data) {\n      data = mergeClassData(childNode.data, data)\n    }\n  }\n  while (isDef(parentNode = parentNode.parent)) {\n    if (parentNode && parentNode.data) {\n      data = mergeClassData(data, parentNode.data)\n    }\n  }\n  return renderClass(data.staticClass, data.class)\n}\n\nfunction mergeClassData (child: VNodeData, parent: VNodeData): {\n  staticClass: string,\n  class: any\n} {\n  return {\n    staticClass: concat(child.staticClass, parent.staticClass),\n    class: isDef(child.class)\n      ? [child.class, parent.class]\n      : parent.class\n  }\n}\n\nexport function renderClass (\n  staticClass: ?string,\n  dynamicClass: any\n): string {\n  if (isDef(staticClass) || isDef(dynamicClass)) {\n    return concat(staticClass, stringifyClass(dynamicClass))\n  }\n  /* istanbul ignore next */\n  return ''\n}\n\nexport function concat (a: ?string, b: ?string): string {\n  return a ? b ? (a + ' ' + b) : a : (b || '')\n}\n\nexport function stringifyClass (value: any): string {\n  if (Array.isArray(value)) {\n    return stringifyArray(value)\n  }\n  if (isObject(value)) {\n    return stringifyObject(value)\n  }\n  if (typeof value === 'string') {\n    return value\n  }\n  /* istanbul ignore next */\n  return ''\n}\n\nfunction stringifyArray (value: Array<any>): string {\n  let res = ''\n  let stringified\n  for (let i = 0, l = value.length; i < l; i++) {\n    if (isDef(stringified = stringifyClass(value[i])) && stringified !== '') {\n      if (res) res += ' '\n      res += stringified\n    }\n  }\n  return res\n}\n\nfunction stringifyObject (value: Object): string {\n  let res = ''\n  for (const key in value) {\n    if (value[key]) {\n      if (res) res += ' '\n      res += key\n    }\n  }\n  return res\n}\n"
  },
  {
    "path": "vue/src/platforms/web/util/compat.js",
    "content": "/* @flow */\n\nimport { inBrowser } from 'core/util/index'\n\n// check whether current browser encodes a char inside attribute values\nlet div\nfunction getShouldDecode (href: boolean): boolean {\n  div = div || document.createElement('div')\n  div.innerHTML = href ? `<a href=\"\\n\"/>` : `<div a=\"\\n\"/>`\n  return div.innerHTML.indexOf('&#10;') > 0\n}\n\n// #3663: IE encodes newlines inside attribute values while other browsers don't\nexport const shouldDecodeNewlines = inBrowser ? getShouldDecode(false) : false\n// #6828: chrome encodes content in a[href]\nexport const shouldDecodeNewlinesForHref = inBrowser ? getShouldDecode(true) : false\n"
  },
  {
    "path": "vue/src/platforms/web/util/element.js",
    "content": "/* @flow */\n\nimport { inBrowser } from 'core/util/env'\nimport { makeMap } from 'shared/util'\n\nexport const namespaceMap = {\n  svg: 'http://www.w3.org/2000/svg',\n  math: 'http://www.w3.org/1998/Math/MathML'\n}\n\nexport const isHTMLTag = makeMap(\n  'html,body,base,head,link,meta,style,title,' +\n  'address,article,aside,footer,header,h1,h2,h3,h4,h5,h6,hgroup,nav,section,' +\n  'div,dd,dl,dt,figcaption,figure,picture,hr,img,li,main,ol,p,pre,ul,' +\n  'a,b,abbr,bdi,bdo,br,cite,code,data,dfn,em,i,kbd,mark,q,rp,rt,rtc,ruby,' +\n  's,samp,small,span,strong,sub,sup,time,u,var,wbr,area,audio,map,track,video,' +\n  'embed,object,param,source,canvas,script,noscript,del,ins,' +\n  'caption,col,colgroup,table,thead,tbody,td,th,tr,' +\n  'button,datalist,fieldset,form,input,label,legend,meter,optgroup,option,' +\n  'output,progress,select,textarea,' +\n  'details,dialog,menu,menuitem,summary,' +\n  'content,element,shadow,template,blockquote,iframe,tfoot'\n)\n\n// this map is intentionally selective, only covering SVG elements that may\n// contain child elements.\nexport const isSVG = makeMap(\n  'svg,animate,circle,clippath,cursor,defs,desc,ellipse,filter,font-face,' +\n  'foreignObject,g,glyph,image,line,marker,mask,missing-glyph,path,pattern,' +\n  'polygon,polyline,rect,switch,symbol,text,textpath,tspan,use,view',\n  true\n)\n\nexport const isPreTag = (tag: ?string): boolean => tag === 'pre'\n\nexport const isReservedTag = (tag: string): ?boolean => {\n  return isHTMLTag(tag) || isSVG(tag)\n}\n\nexport function getTagNamespace (tag: string): ?string {\n  if (isSVG(tag)) {\n    return 'svg'\n  }\n  // basic support for MathML\n  // note it doesn't support other MathML elements being component roots\n  if (tag === 'math') {\n    return 'math'\n  }\n}\n\nconst unknownElementCache = Object.create(null)\nexport function isUnknownElement (tag: string): boolean {\n  /* istanbul ignore if */\n  if (!inBrowser) {\n    return true\n  }\n  if (isReservedTag(tag)) {\n    return false\n  }\n  tag = tag.toLowerCase()\n  /* istanbul ignore if */\n  if (unknownElementCache[tag] != null) {\n    return unknownElementCache[tag]\n  }\n  const el = document.createElement(tag)\n  if (tag.indexOf('-') > -1) {\n    // http://stackoverflow.com/a/28210364/1070244\n    return (unknownElementCache[tag] = (\n      el.constructor === window.HTMLUnknownElement ||\n      el.constructor === window.HTMLElement\n    ))\n  } else {\n    return (unknownElementCache[tag] = /HTMLUnknownElement/.test(el.toString()))\n  }\n}\n\nexport const isTextInputType = makeMap('text,number,password,search,email,tel,url')\n"
  },
  {
    "path": "vue/src/platforms/web/util/index.js",
    "content": "/* @flow */\n\nimport { warn } from 'core/util/index'\n\nexport * from './attrs'\nexport * from './class'\nexport * from './element'\n\n/**\n * Query an element selector if it's not an element already.\n */\nexport function query (el: string | Element): Element {\n  if (typeof el === 'string') {\n    const selected = document.querySelector(el)\n    if (!selected) {\n      process.env.NODE_ENV !== 'production' && warn(\n        'Cannot find element: ' + el\n      )\n      return document.createElement('div')\n    }\n    return selected\n  } else {\n    return el\n  }\n}\n"
  },
  {
    "path": "vue/src/platforms/web/util/style.js",
    "content": "/* @flow */\n\nimport { cached, extend, toObject } from 'shared/util'\n\nexport const parseStyleText = cached(function (cssText) {\n  const res = {}\n  const listDelimiter = /;(?![^(]*\\))/g\n  const propertyDelimiter = /:(.+)/\n  cssText.split(listDelimiter).forEach(function (item) {\n    if (item) {\n      var tmp = item.split(propertyDelimiter)\n      tmp.length > 1 && (res[tmp[0].trim()] = tmp[1].trim())\n    }\n  })\n  return res\n})\n\n// merge static and dynamic style data on the same vnode\nfunction normalizeStyleData (data: VNodeData): ?Object {\n  const style = normalizeStyleBinding(data.style)\n  // static style is pre-processed into an object during compilation\n  // and is always a fresh object, so it's safe to merge into it\n  return data.staticStyle\n    ? extend(data.staticStyle, style)\n    : style\n}\n\n// normalize possible array / string values into Object\nexport function normalizeStyleBinding (bindingStyle: any): ?Object {\n  if (Array.isArray(bindingStyle)) {\n    return toObject(bindingStyle)\n  }\n  if (typeof bindingStyle === 'string') {\n    return parseStyleText(bindingStyle)\n  }\n  return bindingStyle\n}\n\n/**\n * parent component style should be after child's\n * so that parent component's style could override it\n */\nexport function getStyle (vnode: VNodeWithData, checkChild: boolean): Object {\n  const res = {}\n  let styleData\n\n  if (checkChild) {\n    let childNode = vnode\n    while (childNode.componentInstance) {\n      childNode = childNode.componentInstance._vnode\n      if (\n        childNode && childNode.data &&\n        (styleData = normalizeStyleData(childNode.data))\n      ) {\n        extend(res, styleData)\n      }\n    }\n  }\n\n  if ((styleData = normalizeStyleData(vnode.data))) {\n    extend(res, styleData)\n  }\n\n  let parentNode = vnode\n  while ((parentNode = parentNode.parent)) {\n    if (parentNode.data && (styleData = normalizeStyleData(parentNode.data))) {\n      extend(res, styleData)\n    }\n  }\n  return res\n}\n\n"
  },
  {
    "path": "vue/src/platforms/weex/compiler/directives/index.js",
    "content": "import model from './model'\n\nexport default {\n  model\n}\n"
  },
  {
    "path": "vue/src/platforms/weex/compiler/directives/model.js",
    "content": "/* @flow */\n\nimport { addHandler, addAttr } from 'compiler/helpers'\nimport { genComponentModel, genAssignmentCode } from 'compiler/directives/model'\n\nexport default function model (\n  el: ASTElement,\n  dir: ASTDirective,\n  _warn: Function\n): ?boolean {\n  if (el.tag === 'input' || el.tag === 'textarea') {\n    genDefaultModel(el, dir.value, dir.modifiers)\n  } else {\n    genComponentModel(el, dir.value, dir.modifiers)\n  }\n}\n\nfunction genDefaultModel (\n  el: ASTElement,\n  value: string,\n  modifiers: ?ASTModifiers\n): ?boolean {\n  const { lazy, trim, number } = modifiers || {}\n  const event = lazy ? 'change' : 'input'\n\n  let valueExpression = `$event.target.attr.value${trim ? '.trim()' : ''}`\n  if (number) {\n    valueExpression = `_n(${valueExpression})`\n  }\n\n  const code = genAssignmentCode(value, valueExpression)\n  addAttr(el, 'value', `(${value})`)\n  addHandler(el, event, code, null, true)\n}\n"
  },
  {
    "path": "vue/src/platforms/weex/compiler/index.js",
    "content": "/* @flow */\n\nimport { genStaticKeys } from 'shared/util'\nimport { createCompiler } from 'compiler/index'\n\nimport modules from './modules/index'\nimport directives from './directives/index'\n\nimport {\n  isUnaryTag,\n  mustUseProp,\n  isReservedTag,\n  canBeLeftOpenTag,\n  getTagNamespace\n} from '../util/element'\n\nexport const baseOptions: WeexCompilerOptions = {\n  modules,\n  directives,\n  isUnaryTag,\n  mustUseProp,\n  canBeLeftOpenTag,\n  isReservedTag,\n  getTagNamespace,\n  preserveWhitespace: false,\n  recyclable: false,\n  staticKeys: genStaticKeys(modules)\n}\n\nconst compiler = createCompiler(baseOptions)\n\nexport function compile (\n  template: string,\n  options?: WeexCompilerOptions\n): WeexCompiledResult {\n  let generateAltRender = false\n  if (options && options.recyclable === true) {\n    generateAltRender = true\n    options.recyclable = false\n  }\n  const result = compiler.compile(template, options)\n\n  // generate @render function for <recycle-list>\n  if (options && generateAltRender) {\n    options.recyclable = true\n    // disable static optimizations\n    options.optimize = false\n    const { render } = compiler.compile(template, options)\n    result['@render'] = render\n  }\n  return result\n}\n"
  },
  {
    "path": "vue/src/platforms/weex/compiler/modules/append.js",
    "content": "/* @flow */\n\nimport { makeMap } from 'shared/util'\n\n// The \"unitary tag\" means that the tag node and its children\n// must be sent to the native together.\nconst isUnitaryTag = makeMap('cell,header,cell-slot,recycle-list', true)\n\nfunction preTransformNode (el: ASTElement, options: CompilerOptions) {\n  if (isUnitaryTag(el.tag) && !el.attrsList.some(item => item.name === 'append')) {\n    el.attrsMap.append = 'tree'\n    el.attrsList.push({ name: 'append', value: 'tree' })\n  }\n  if (el.attrsMap.append === 'tree') {\n    el.appendAsTree = true\n  }\n}\n\nfunction genData (el: ASTElement): string {\n  return el.appendAsTree ? `appendAsTree:true,` : ''\n}\n\nexport default {\n  staticKeys: ['appendAsTree'],\n  preTransformNode,\n  genData\n}\n"
  },
  {
    "path": "vue/src/platforms/weex/compiler/modules/class.js",
    "content": "/* @flow */\n\nimport { parseText } from 'compiler/parser/text-parser'\nimport {\n  getAndRemoveAttr,\n  getBindingAttr,\n  baseWarn\n} from 'compiler/helpers'\n\ntype StaticClassResult = {\n  dynamic: boolean,\n  classResult: string\n};\n\nfunction transformNode (el: ASTElement, options: CompilerOptions) {\n  const warn = options.warn || baseWarn\n  const staticClass = getAndRemoveAttr(el, 'class')\n  const { dynamic, classResult } = parseStaticClass(staticClass, options)\n  if (process.env.NODE_ENV !== 'production' && dynamic && staticClass) {\n    warn(\n      `class=\"${staticClass}\": ` +\n      'Interpolation inside attributes has been deprecated. ' +\n      'Use v-bind or the colon shorthand instead.'\n    )\n  }\n  if (!dynamic && classResult) {\n    el.staticClass = classResult\n  }\n  const classBinding = getBindingAttr(el, 'class', false /* getStatic */)\n  if (classBinding) {\n    el.classBinding = classBinding\n  } else if (dynamic) {\n    el.classBinding = classResult\n  }\n}\n\nfunction genData (el: ASTElement): string {\n  let data = ''\n  if (el.staticClass) {\n    data += `staticClass:${el.staticClass},`\n  }\n  if (el.classBinding) {\n    data += `class:${el.classBinding},`\n  }\n  return data\n}\n\nfunction parseStaticClass (staticClass: ?string, options: CompilerOptions): StaticClassResult {\n  // \"a b c\" -> [\"a\", \"b\", \"c\"] => staticClass: [\"a\", \"b\", \"c\"]\n  // \"a {{x}} c\" -> [\"a\", x, \"c\"] => classBinding: '[\"a\", x, \"c\"]'\n  let dynamic = false\n  let classResult = ''\n  if (staticClass) {\n    const classList = staticClass.trim().split(' ').map(name => {\n      const result = parseText(name, options.delimiters)\n      if (result) {\n        dynamic = true\n        return result.expression\n      }\n      return JSON.stringify(name)\n    })\n    if (classList.length) {\n      classResult = '[' + classList.join(',') + ']'\n    }\n  }\n  return { dynamic, classResult }\n}\n\nexport default {\n  staticKeys: ['staticClass'],\n  transformNode,\n  genData\n}\n"
  },
  {
    "path": "vue/src/platforms/weex/compiler/modules/index.js",
    "content": "import klass from './class'\nimport style from './style'\nimport props from './props'\nimport append from './append'\nimport recycleList from './recycle-list/index'\n\nexport default [\n  recycleList,\n  klass,\n  style,\n  props,\n  append\n]\n"
  },
  {
    "path": "vue/src/platforms/weex/compiler/modules/props.js",
    "content": "/* @flow */\n\nimport { cached, camelize } from 'shared/util'\n\nconst normalize = cached(camelize)\n\nfunction normalizeKeyName (str: string): string {\n  if (str.match(/^v\\-/)) {\n    return str.replace(/(v-[a-z\\-]+\\:)([a-z\\-]+)$/i, ($, directive, prop) => {\n      return directive + normalize(prop)\n    })\n  }\n  return normalize(str)\n}\n\nfunction transformNode (el: ASTElement, options: CompilerOptions) {\n  if (Array.isArray(el.attrsList)) {\n    el.attrsList.forEach(attr => {\n      if (attr.name && attr.name.match(/\\-/)) {\n        const realName = normalizeKeyName(attr.name)\n        if (el.attrsMap) {\n          el.attrsMap[realName] = el.attrsMap[attr.name]\n          delete el.attrsMap[attr.name]\n        }\n        attr.name = realName\n      }\n    })\n  }\n}\nexport default {\n  transformNode\n}\n"
  },
  {
    "path": "vue/src/platforms/weex/compiler/modules/recycle-list/component-root.js",
    "content": "/* @flow */\n\nimport { addAttr } from 'compiler/helpers'\n\n// mark component root nodes as\nexport function postTransformComponentRoot (\n  el: ASTElement,\n  options: WeexCompilerOptions\n) {\n  if (!el.parent) {\n    // component root\n    addAttr(el, '@isComponentRoot', 'true')\n    addAttr(el, '@templateId', '_uid')\n    addAttr(el, '@componentProps', '$props || {}')\n  }\n}\n"
  },
  {
    "path": "vue/src/platforms/weex/compiler/modules/recycle-list/component.js",
    "content": "/* @flow */\n\nimport { addAttr } from 'compiler/helpers'\nimport { RECYCLE_LIST_MARKER } from 'weex/util/index'\n\n// mark components as inside recycle-list so that we know we need to invoke\n// their special @render function instead of render in create-component.js\nexport function postTransformComponent (\n  el: ASTElement,\n  options: WeexCompilerOptions\n) {\n  // $flow-disable-line (we know isReservedTag is there)\n  if (!options.isReservedTag(el.tag) && el.tag !== 'cell-slot') {\n    addAttr(el, RECYCLE_LIST_MARKER, 'true')\n  }\n}\n"
  },
  {
    "path": "vue/src/platforms/weex/compiler/modules/recycle-list/index.js",
    "content": "/* @flow */\n\nimport { preTransformRecycleList } from './recycle-list'\nimport { postTransformComponent } from './component'\nimport { postTransformComponentRoot } from './component-root'\nimport { postTransformText } from './text'\nimport { preTransformVBind } from './v-bind'\nimport { preTransformVIf } from './v-if'\nimport { preTransformVFor } from './v-for'\nimport { postTransformVOn } from './v-on'\nimport { preTransformVOnce } from './v-once'\n\nlet currentRecycleList = null\n\nfunction shouldCompile (el: ASTElement, options: WeexCompilerOptions) {\n  return options.recyclable ||\n    (currentRecycleList && el !== currentRecycleList)\n}\n\nfunction preTransformNode (el: ASTElement, options: WeexCompilerOptions) {\n  if (el.tag === 'recycle-list') {\n    preTransformRecycleList(el, options)\n    currentRecycleList = el\n  }\n  if (shouldCompile(el, options)) {\n    preTransformVBind(el, options)\n    preTransformVIf(el, options) // also v-else-if and v-else\n    preTransformVFor(el, options)\n    preTransformVOnce(el, options)\n  }\n}\n\nfunction transformNode (el: ASTElement, options: WeexCompilerOptions) {\n  if (shouldCompile(el, options)) {\n    // do nothing yet\n  }\n}\n\nfunction postTransformNode (el: ASTElement, options: WeexCompilerOptions) {\n  if (shouldCompile(el, options)) {\n    // mark child component in parent template\n    postTransformComponent(el, options)\n    // mark root in child component template\n    postTransformComponentRoot(el, options)\n    // <text>: transform children text into value attr\n    if (el.tag === 'text') {\n      postTransformText(el, options)\n    }\n    postTransformVOn(el, options)\n  }\n  if (el === currentRecycleList) {\n    currentRecycleList = null\n  }\n}\n\nexport default {\n  preTransformNode,\n  transformNode,\n  postTransformNode\n}\n"
  },
  {
    "path": "vue/src/platforms/weex/compiler/modules/recycle-list/recycle-list.js",
    "content": "/* @flow */\n\nimport { parseFor } from 'compiler/parser/index'\nimport { getAndRemoveAttr, addRawAttr } from 'compiler/helpers'\n\n/**\n * Map the following syntax to corresponding attrs:\n *\n * <recycle-list for=\"(item, i) in longList\" switch=\"cellType\">\n *   <cell-slot case=\"A\"> ... </cell-slot>\n *   <cell-slot case=\"B\"> ... </cell-slot>\n * </recycle-list>\n */\n\nexport function preTransformRecycleList (\n  el: ASTElement,\n  options: WeexCompilerOptions\n) {\n  const exp = getAndRemoveAttr(el, 'for')\n  if (!exp) {\n    if (options.warn) {\n      options.warn(`Invalid <recycle-list> syntax: missing \"for\" expression.`)\n    }\n    return\n  }\n\n  const res = parseFor(exp)\n  if (!res) {\n    if (options.warn) {\n      options.warn(`Invalid <recycle-list> syntax: ${exp}.`)\n    }\n    return\n  }\n\n  addRawAttr(el, ':list-data', res.for)\n  addRawAttr(el, 'binding-expression', res.for)\n  addRawAttr(el, 'alias', res.alias)\n  if (res.iterator2) {\n    // (item, key, index) for object iteration\n    // is this even supported?\n    addRawAttr(el, 'index', res.iterator2)\n  } else if (res.iterator1) {\n    addRawAttr(el, 'index', res.iterator1)\n  }\n\n  const switchKey = getAndRemoveAttr(el, 'switch')\n  if (switchKey) {\n    addRawAttr(el, 'switch', switchKey)\n  }\n}\n"
  },
  {
    "path": "vue/src/platforms/weex/compiler/modules/recycle-list/text.js",
    "content": "/* @flow */\n\nimport { addAttr } from 'compiler/helpers'\n\nfunction genText (node: ASTNode) {\n  const value = node.type === 3\n    ? node.text\n    : node.type === 2\n      ? node.tokens.length === 1\n        ? node.tokens[0]\n        : node.tokens\n      : ''\n  return JSON.stringify(value)\n}\n\nexport function postTransformText (el: ASTElement, options: WeexCompilerOptions) {\n  // weex <text> can only contain text, so the parser\n  // always generates a single child.\n  if (el.children.length) {\n    addAttr(el, 'value', genText(el.children[0]))\n    el.children = []\n  }\n}\n"
  },
  {
    "path": "vue/src/platforms/weex/compiler/modules/recycle-list/v-bind.js",
    "content": "/* @flow */\n\nimport { camelize } from 'shared/util'\nimport { generateBinding } from 'weex/util/parser'\nimport { bindRE } from 'compiler/parser/index'\nimport { getAndRemoveAttr, addRawAttr } from 'compiler/helpers'\n\nfunction parseAttrName (name: string): string {\n  return camelize(name.replace(bindRE, ''))\n}\n\nexport function preTransformVBind (el: ASTElement, options: WeexCompilerOptions) {\n  for (const attr in el.attrsMap) {\n    if (bindRE.test(attr)) {\n      const name: string = parseAttrName(attr)\n      const value = generateBinding(getAndRemoveAttr(el, attr))\n      delete el.attrsMap[attr]\n      addRawAttr(el, name, value)\n    }\n  }\n}\n"
  },
  {
    "path": "vue/src/platforms/weex/compiler/modules/recycle-list/v-for.js",
    "content": "/* @flow */\n\nimport { parseFor } from 'compiler/parser/index'\nimport { getAndRemoveAttr, addRawAttr } from 'compiler/helpers'\n\nexport function preTransformVFor (el: ASTElement, options: WeexCompilerOptions) {\n  const exp = getAndRemoveAttr(el, 'v-for')\n  if (!exp) {\n    return\n  }\n\n  const res = parseFor(exp)\n  if (!res) {\n    if (process.env.NODE_ENV !== 'production' && options.warn) {\n      options.warn(`Invalid v-for expression: ${exp}`)\n    }\n    return\n  }\n\n  const desc: Object = {\n    '@expression': res.for,\n    '@alias': res.alias\n  }\n  if (res.iterator2) {\n    desc['@key'] = res.iterator1\n    desc['@index'] = res.iterator2\n  } else {\n    desc['@index'] = res.iterator1\n  }\n\n  delete el.attrsMap['v-for']\n  addRawAttr(el, '[[repeat]]', desc)\n}\n"
  },
  {
    "path": "vue/src/platforms/weex/compiler/modules/recycle-list/v-if.js",
    "content": "/* @flow */\n\nimport { addIfCondition } from 'compiler/parser/index'\nimport { getAndRemoveAttr, addRawAttr } from 'compiler/helpers'\n\nfunction hasConditionDirective (el: ASTElement): boolean {\n  for (const attr in el.attrsMap) {\n    if (/^v\\-if|v\\-else|v\\-else\\-if$/.test(attr)) {\n      return true\n    }\n  }\n  return false\n}\n\nfunction getPreviousConditions (el: ASTElement): Array<string> {\n  const conditions = []\n  if (el.parent && el.parent.children) {\n    for (let c = 0, n = el.parent.children.length; c < n; ++c) {\n      // $flow-disable-line\n      const ifConditions = el.parent.children[c].ifConditions\n      if (ifConditions) {\n        for (let i = 0, l = ifConditions.length; i < l; ++i) {\n          const condition = ifConditions[i]\n          if (condition && condition.exp) {\n            conditions.push(condition.exp)\n          }\n        }\n      }\n    }\n  }\n  return conditions\n}\n\nexport function preTransformVIf (el: ASTElement, options: WeexCompilerOptions) {\n  if (hasConditionDirective(el)) {\n    let exp\n    const ifExp = getAndRemoveAttr(el, 'v-if', true /* remove from attrsMap */)\n    const elseifExp = getAndRemoveAttr(el, 'v-else-if', true)\n    // don't need the value, but remove it to avoid being generated as a\n    // custom directive\n    getAndRemoveAttr(el, 'v-else', true)\n    if (ifExp) {\n      exp = ifExp\n      addIfCondition(el, { exp: ifExp, block: el })\n    } else {\n      elseifExp && addIfCondition(el, { exp: elseifExp, block: el })\n      const prevConditions = getPreviousConditions(el)\n      if (prevConditions.length) {\n        const prevMatch = prevConditions.join(' || ')\n        exp = elseifExp\n          ? `!(${prevMatch}) && (${elseifExp})` // v-else-if\n          : `!(${prevMatch})` // v-else\n      } else if (process.env.NODE_ENV !== 'production' && options.warn) {\n        options.warn(\n          `v-${elseifExp ? ('else-if=\"' + elseifExp + '\"') : 'else'} ` +\n          `used on element <${el.tag}> without corresponding v-if.`\n        )\n        return\n      }\n    }\n    addRawAttr(el, '[[match]]', exp)\n  }\n}\n"
  },
  {
    "path": "vue/src/platforms/weex/compiler/modules/recycle-list/v-on.js",
    "content": "/* @flow */\n\nconst inlineStatementRE = /^\\s*([A-Za-z_$0-9\\['\\.\"\\]]+)*\\s*\\(\\s*(([A-Za-z_$0-9\\['\\.\"\\]]+)?(\\s*,\\s*([A-Za-z_$0-9\\['\\.\"\\]]+))*)\\s*\\)$/\n\nfunction parseHandlerParams (handler: ASTElementHandler) {\n  const res = inlineStatementRE.exec(handler.value)\n  if (res && res[2]) {\n    handler.params = res[2].split(/\\s*,\\s*/)\n  }\n}\n\nexport function postTransformVOn (el: ASTElement, options: WeexCompilerOptions) {\n  const events: ASTElementHandlers | void = el.events\n  if (!events) {\n    return\n  }\n  for (const name in events) {\n    const handler: ASTElementHandler | Array<ASTElementHandler> = events[name]\n    if (Array.isArray(handler)) {\n      handler.map(fn => parseHandlerParams(fn))\n    } else {\n      parseHandlerParams(handler)\n    }\n  }\n}\n"
  },
  {
    "path": "vue/src/platforms/weex/compiler/modules/recycle-list/v-once.js",
    "content": "/* @flow */\n\nimport { getAndRemoveAttr, addRawAttr } from 'compiler/helpers'\n\nfunction containVOnce (el: ASTElement): boolean {\n  for (const attr in el.attrsMap) {\n    if (/^v\\-once$/i.test(attr)) {\n      return true\n    }\n  }\n  return false\n}\n\nexport function preTransformVOnce (el: ASTElement, options: WeexCompilerOptions) {\n  if (containVOnce(el)) {\n    getAndRemoveAttr(el, 'v-once', true)\n    addRawAttr(el, '[[once]]', true)\n  }\n}\n"
  },
  {
    "path": "vue/src/platforms/weex/compiler/modules/style.js",
    "content": "/* @flow */\n\nimport { cached, camelize, isPlainObject } from 'shared/util'\nimport { parseText } from 'compiler/parser/text-parser'\nimport {\n  getAndRemoveAttr,\n  getBindingAttr,\n  baseWarn\n} from 'compiler/helpers'\n\ntype StaticStyleResult = {\n  dynamic: boolean,\n  styleResult: string | Object | void\n};\n\nconst normalize = cached(camelize)\n\nfunction transformNode (el: ASTElement, options: CompilerOptions) {\n  const warn = options.warn || baseWarn\n  const staticStyle = getAndRemoveAttr(el, 'style')\n  const { dynamic, styleResult } = parseStaticStyle(staticStyle, options)\n  if (process.env.NODE_ENV !== 'production' && dynamic) {\n    warn(\n      `style=\"${String(staticStyle)}\": ` +\n      'Interpolation inside attributes has been deprecated. ' +\n      'Use v-bind or the colon shorthand instead.'\n    )\n  }\n  if (!dynamic && styleResult) {\n    // $flow-disable-line\n    el.staticStyle = styleResult\n  }\n  const styleBinding = getBindingAttr(el, 'style', false /* getStatic */)\n  if (styleBinding) {\n    el.styleBinding = styleBinding\n  } else if (dynamic) {\n    // $flow-disable-line\n    el.styleBinding = styleResult\n  }\n}\n\nfunction genData (el: ASTElement): string {\n  let data = ''\n  if (el.staticStyle) {\n    data += `staticStyle:${el.staticStyle},`\n  }\n  if (el.styleBinding) {\n    data += `style:${el.styleBinding},`\n  }\n  return data\n}\n\nfunction parseStaticStyle (staticStyle: ?string, options: CompilerOptions): StaticStyleResult {\n  // \"width: 200px; height: 200px;\" -> {width: 200, height: 200}\n  // \"width: 200px; height: {{y}}\" -> {width: 200, height: y}\n  let dynamic = false\n  let styleResult = ''\n  if (typeof staticStyle === 'string') {\n    const styleList = staticStyle.trim().split(';').map(style => {\n      const result = style.trim().split(':')\n      if (result.length !== 2) {\n        return\n      }\n      const key = normalize(result[0].trim())\n      const value = result[1].trim()\n      const dynamicValue = parseText(value, options.delimiters)\n      if (dynamicValue) {\n        dynamic = true\n        return key + ':' + dynamicValue.expression\n      }\n      return key + ':' + JSON.stringify(value)\n    }).filter(result => result)\n    if (styleList.length) {\n      styleResult = '{' + styleList.join(',') + '}'\n    }\n  } else if (isPlainObject(staticStyle)) {\n    styleResult = JSON.stringify(staticStyle) || ''\n  }\n  return { dynamic, styleResult }\n}\n\nexport default {\n  staticKeys: ['staticStyle'],\n  transformNode,\n  genData\n}\n"
  },
  {
    "path": "vue/src/platforms/weex/entry-compiler.js",
    "content": "export { compile } from 'weex/compiler/index'\n"
  },
  {
    "path": "vue/src/platforms/weex/entry-framework.js",
    "content": "/* @flow */\n\n// this will be preserved during build\n// $flow-disable-line\nconst VueFactory = require('./factory')\n\nconst instanceOptions: { [key: string]: WeexInstanceOption } = {}\n\n/**\n * Create instance context.\n */\nexport function createInstanceContext (\n  instanceId: string,\n  runtimeContext: WeexRuntimeContext,\n  data: Object = {}\n): WeexInstanceContext {\n  const weex: Weex = runtimeContext.weex\n  const instance: WeexInstanceOption = instanceOptions[instanceId] = {\n    instanceId,\n    config: weex.config,\n    document: weex.document,\n    data\n  }\n\n  // Each instance has a independent `Vue` module instance\n  const Vue = instance.Vue = createVueModuleInstance(instanceId, weex)\n\n  // DEPRECATED\n  const timerAPIs = getInstanceTimer(instanceId, weex.requireModule)\n\n  const instanceContext = Object.assign({ Vue }, timerAPIs)\n  Object.freeze(instanceContext)\n  return instanceContext\n}\n\n/**\n * Destroy an instance with id. It will make sure all memory of\n * this instance released and no more leaks.\n */\nexport function destroyInstance (instanceId: string): void {\n  const instance = instanceOptions[instanceId]\n  if (instance && instance.app instanceof instance.Vue) {\n    try {\n      instance.app.$destroy()\n      instance.document.destroy()\n    } catch (e) {}\n    delete instance.document\n    delete instance.app\n  }\n  delete instanceOptions[instanceId]\n}\n\n/**\n * Refresh an instance with id and new top-level component data.\n * It will use `Vue.set` on all keys of the new data. So it's better\n * define all possible meaningful keys when instance created.\n */\nexport function refreshInstance (\n  instanceId: string,\n  data: Object\n): Error | void {\n  const instance = instanceOptions[instanceId]\n  if (!instance || !(instance.app instanceof instance.Vue)) {\n    return new Error(`refreshInstance: instance ${instanceId} not found!`)\n  }\n  if (instance.Vue && instance.Vue.set) {\n    for (const key in data) {\n      instance.Vue.set(instance.app, key, data[key])\n    }\n  }\n  // Finally `refreshFinish` signal needed.\n  instance.document.taskCenter.send('dom', { action: 'refreshFinish' }, [])\n}\n\n/**\n * Create a fresh instance of Vue for each Weex instance.\n */\nfunction createVueModuleInstance (\n  instanceId: string,\n  weex: Weex\n): GlobalAPI {\n  const exports = {}\n  VueFactory(exports, weex.document)\n  const Vue = exports.Vue\n\n  const instance = instanceOptions[instanceId]\n\n  // patch reserved tag detection to account for dynamically registered\n  // components\n  const weexRegex = /^weex:/i\n  const isReservedTag = Vue.config.isReservedTag || (() => false)\n  const isRuntimeComponent = Vue.config.isRuntimeComponent || (() => false)\n  Vue.config.isReservedTag = name => {\n    return (!isRuntimeComponent(name) && weex.supports(`@component/${name}`)) ||\n      isReservedTag(name) ||\n      weexRegex.test(name)\n  }\n  Vue.config.parsePlatformTagName = name => name.replace(weexRegex, '')\n\n  // expose weex-specific info\n  Vue.prototype.$instanceId = instanceId\n  Vue.prototype.$document = instance.document\n\n  // expose weex native module getter on subVue prototype so that\n  // vdom runtime modules can access native modules via vnode.context\n  Vue.prototype.$requireWeexModule = weex.requireModule\n\n  // Hack `Vue` behavior to handle instance information and data\n  // before root component created.\n  Vue.mixin({\n    beforeCreate () {\n      const options = this.$options\n      // root component (vm)\n      if (options.el) {\n        // set external data of instance\n        const dataOption = options.data\n        const internalData = (typeof dataOption === 'function' ? dataOption() : dataOption) || {}\n        options.data = Object.assign(internalData, instance.data)\n        // record instance by id\n        instance.app = this\n      }\n    },\n    mounted () {\n      const options = this.$options\n      // root component (vm)\n      if (options.el && weex.document && instance.app === this) {\n        try {\n          // Send \"createFinish\" signal to native.\n          weex.document.taskCenter.send('dom', { action: 'createFinish' }, [])\n        } catch (e) {}\n      }\n    }\n  })\n\n  /**\n   * @deprecated Just instance variable `weex.config`\n   * Get instance config.\n   * @return {object}\n   */\n  Vue.prototype.$getConfig = function () {\n    if (instance.app instanceof Vue) {\n      return instance.config\n    }\n  }\n\n  return Vue\n}\n\n/**\n * DEPRECATED\n * Generate HTML5 Timer APIs. An important point is that the callback\n * will be converted into callback id when sent to native. So the\n * framework can make sure no side effect of the callback happened after\n * an instance destroyed.\n */\nfunction getInstanceTimer (\n  instanceId: string,\n  moduleGetter: Function\n): Object {\n  const instance = instanceOptions[instanceId]\n  const timer = moduleGetter('timer')\n  const timerAPIs = {\n    setTimeout: (...args) => {\n      const handler = function () {\n        args[0](...args.slice(2))\n      }\n\n      timer.setTimeout(handler, args[1])\n      return instance.document.taskCenter.callbackManager.lastCallbackId.toString()\n    },\n    setInterval: (...args) => {\n      const handler = function () {\n        args[0](...args.slice(2))\n      }\n\n      timer.setInterval(handler, args[1])\n      return instance.document.taskCenter.callbackManager.lastCallbackId.toString()\n    },\n    clearTimeout: (n) => {\n      timer.clearTimeout(n)\n    },\n    clearInterval: (n) => {\n      timer.clearInterval(n)\n    }\n  }\n  return timerAPIs\n}\n"
  },
  {
    "path": "vue/src/platforms/weex/entry-runtime-factory.js",
    "content": "// this entry is built and wrapped with a factory function\n// used to generate a fresh copy of Vue for every Weex instance.\n\nimport Vue from './runtime/index'\n\nexports.Vue = Vue\n"
  },
  {
    "path": "vue/src/platforms/weex/runtime/components/index.js",
    "content": "import Richtext from './richtext'\nimport Transition from './transition'\nimport TransitionGroup from './transition-group'\n\nexport default {\n  Richtext,\n  Transition,\n  TransitionGroup\n}\n"
  },
  {
    "path": "vue/src/platforms/weex/runtime/components/richtext.js",
    "content": "/* @flow */\n\nfunction getVNodeType (vnode: VNode): string {\n  if (!vnode.tag) {\n    return ''\n  }\n  return vnode.tag.replace(/vue\\-component\\-(\\d+\\-)?/, '')\n}\n\nfunction isSimpleSpan (vnode: VNode): boolean {\n  return vnode.children &&\n    vnode.children.length === 1 &&\n    !vnode.children[0].tag\n}\n\nfunction parseStyle (vnode: VNode): Object | void {\n  if (!vnode || !vnode.data) {\n    return\n  }\n  const { staticStyle, staticClass } = vnode.data\n  if (vnode.data.style || vnode.data.class || staticStyle || staticClass) {\n    const styles = Object.assign({}, staticStyle, vnode.data.style)\n    const cssMap = vnode.context.$options.style || {}\n    const classList = [].concat(staticClass, vnode.data.class)\n    classList.forEach(name => {\n      if (name && cssMap[name]) {\n        Object.assign(styles, cssMap[name])\n      }\n    })\n    return styles\n  }\n}\n\nfunction convertVNodeChildren (children: Array<VNode>): Array<VNode> | void {\n  if (!children.length) {\n    return\n  }\n\n  return children.map(vnode => {\n    const type: string = getVNodeType(vnode)\n    const props: Object = { type }\n\n    // convert raw text node\n    if (!type) {\n      props.type = 'span'\n      props.attr = {\n        value: (vnode.text || '').trim()\n      }\n    } else {\n      props.style = parseStyle(vnode)\n      if (vnode.data) {\n        props.attr = vnode.data.attrs\n        if (vnode.data.on) {\n          props.events = vnode.data.on\n        }\n      }\n      if (type === 'span' && isSimpleSpan(vnode)) {\n        props.attr = props.attr || {}\n        props.attr.value = vnode.children[0].text.trim()\n        return props\n      }\n    }\n\n    if (vnode.children && vnode.children.length) {\n      props.children = convertVNodeChildren(vnode.children)\n    }\n\n    return props\n  })\n}\n\nexport default {\n  name: 'richtext',\n  render (h: Function) {\n    return h('weex:richtext', {\n      on: this._events,\n      attrs: {\n        value: convertVNodeChildren(this.$options._renderChildren || [])\n      }\n    })\n  }\n}\n"
  },
  {
    "path": "vue/src/platforms/weex/runtime/components/transition-group.js",
    "content": "import { warn, extend } from 'core/util/index'\nimport { transitionProps, extractTransitionData } from './transition'\n\nconst props = extend({\n  tag: String,\n  moveClass: String\n}, transitionProps)\n\ndelete props.mode\n\nexport default {\n  props,\n\n  created () {\n    const dom = this.$requireWeexModule('dom')\n    this.getPosition = el => new Promise((resolve, reject) => {\n      dom.getComponentRect(el.ref, res => {\n        if (!res.result) {\n          reject(new Error(`failed to get rect for element: ${el.tag}`))\n        } else {\n          resolve(res.size)\n        }\n      })\n    })\n\n    const animation = this.$requireWeexModule('animation')\n    this.animate = (el, options) => new Promise(resolve => {\n      animation.transition(el.ref, options, resolve)\n    })\n  },\n\n  render (h) {\n    const tag = this.tag || this.$vnode.data.tag || 'span'\n    const map = Object.create(null)\n    const prevChildren = this.prevChildren = this.children\n    const rawChildren = this.$slots.default || []\n    const children = this.children = []\n    const transitionData = extractTransitionData(this)\n\n    for (let i = 0; i < rawChildren.length; i++) {\n      const c = rawChildren[i]\n      if (c.tag) {\n        if (c.key != null && String(c.key).indexOf('__vlist') !== 0) {\n          children.push(c)\n          map[c.key] = c\n          ;(c.data || (c.data = {})).transition = transitionData\n        } else if (process.env.NODE_ENV !== 'production') {\n          const opts = c.componentOptions\n          const name = opts\n            ? (opts.Ctor.options.name || opts.tag)\n            : c.tag\n          warn(`<transition-group> children must be keyed: <${name}>`)\n        }\n      }\n    }\n\n    if (prevChildren) {\n      const kept = []\n      const removed = []\n      prevChildren.forEach(c => {\n        c.data.transition = transitionData\n\n        // TODO: record before patch positions\n\n        if (map[c.key]) {\n          kept.push(c)\n        } else {\n          removed.push(c)\n        }\n      })\n      this.kept = h(tag, null, kept)\n      this.removed = removed\n    }\n\n    return h(tag, null, children)\n  },\n\n  beforeUpdate () {\n    // force removing pass\n    this.__patch__(\n      this._vnode,\n      this.kept,\n      false, // hydrating\n      true // removeOnly (!important, avoids unnecessary moves)\n    )\n    this._vnode = this.kept\n  },\n\n  updated () {\n    const children = this.prevChildren\n    const moveClass = this.moveClass || ((this.name || 'v') + '-move')\n    const moveData = children.length && this.getMoveData(children[0].context, moveClass)\n    if (!moveData) {\n      return\n    }\n\n    // TODO: finish implementing move animations once\n    // we have access to sync getComponentRect()\n\n    // children.forEach(callPendingCbs)\n\n    // Promise.all(children.map(c => {\n    //   const oldPos = c.data.pos\n    //   const newPos = c.data.newPos\n    //   const dx = oldPos.left - newPos.left\n    //   const dy = oldPos.top - newPos.top\n    //   if (dx || dy) {\n    //     c.data.moved = true\n    //     return this.animate(c.elm, {\n    //       styles: {\n    //         transform: `translate(${dx}px,${dy}px)`\n    //       }\n    //     })\n    //   }\n    // })).then(() => {\n    //   children.forEach(c => {\n    //     if (c.data.moved) {\n    //       this.animate(c.elm, {\n    //         styles: {\n    //           transform: ''\n    //         },\n    //         duration: moveData.duration || 0,\n    //         delay: moveData.delay || 0,\n    //         timingFunction: moveData.timingFunction || 'linear'\n    //       })\n    //     }\n    //   })\n    // })\n  },\n\n  methods: {\n    getMoveData (context, moveClass) {\n      const stylesheet = context.$options.style || {}\n      return stylesheet['@TRANSITION'] && stylesheet['@TRANSITION'][moveClass]\n    }\n  }\n}\n\n// function callPendingCbs (c) {\n//   /* istanbul ignore if */\n//   if (c.elm._moveCb) {\n//     c.elm._moveCb()\n//   }\n//   /* istanbul ignore if */\n//   if (c.elm._enterCb) {\n//     c.elm._enterCb()\n//   }\n// }\n"
  },
  {
    "path": "vue/src/platforms/weex/runtime/components/transition.js",
    "content": "// reuse same transition component logic from web\nexport {\n  transitionProps,\n  extractTransitionData\n} from 'web/runtime/components/transition'\n\nimport Transition from 'web/runtime/components/transition'\n\nexport default Transition\n"
  },
  {
    "path": "vue/src/platforms/weex/runtime/directives/index.js",
    "content": "export default {\n}\n"
  },
  {
    "path": "vue/src/platforms/weex/runtime/index.js",
    "content": "/* @flow */\n\nimport Vue from 'core/index'\nimport { patch } from 'weex/runtime/patch'\nimport { mountComponent } from 'core/instance/lifecycle'\nimport platformDirectives from 'weex/runtime/directives/index'\nimport platformComponents from 'weex/runtime/components/index'\n\nimport {\n  query,\n  mustUseProp,\n  isReservedTag,\n  isRuntimeComponent,\n  isUnknownElement\n} from 'weex/util/element'\n\n// install platform specific utils\nVue.config.mustUseProp = mustUseProp\nVue.config.isReservedTag = isReservedTag\nVue.config.isRuntimeComponent = isRuntimeComponent\nVue.config.isUnknownElement = isUnknownElement\n\n// install platform runtime directives and components\nVue.options.directives = platformDirectives\nVue.options.components = platformComponents\n\n// install platform patch function\nVue.prototype.__patch__ = patch\n\n// wrap mount\nVue.prototype.$mount = function (\n  el?: any,\n  hydrating?: boolean\n): Component {\n  return mountComponent(\n    this,\n    el && query(el, this.$document),\n    hydrating\n  )\n}\n\nexport default Vue\n"
  },
  {
    "path": "vue/src/platforms/weex/runtime/modules/attrs.js",
    "content": "/* @flow */\n\nimport { extend } from 'shared/util'\n\nfunction updateAttrs (oldVnode: VNodeWithData, vnode: VNodeWithData) {\n  if (!oldVnode.data.attrs && !vnode.data.attrs) {\n    return\n  }\n  let key, cur, old\n  const elm = vnode.elm\n  const oldAttrs = oldVnode.data.attrs || {}\n  let attrs = vnode.data.attrs || {}\n  // clone observed objects, as the user probably wants to mutate it\n  if (attrs.__ob__) {\n    attrs = vnode.data.attrs = extend({}, attrs)\n  }\n\n  const supportBatchUpdate = typeof elm.setAttrs === 'function'\n  const batchedAttrs = {}\n  for (key in attrs) {\n    cur = attrs[key]\n    old = oldAttrs[key]\n    if (old !== cur) {\n      supportBatchUpdate\n        ? (batchedAttrs[key] = cur)\n        : elm.setAttr(key, cur)\n    }\n  }\n  for (key in oldAttrs) {\n    if (attrs[key] == null) {\n      supportBatchUpdate\n        ? (batchedAttrs[key] = undefined)\n        : elm.setAttr(key)\n    }\n  }\n  if (supportBatchUpdate) {\n    elm.setAttrs(batchedAttrs)\n  }\n}\n\nexport default {\n  create: updateAttrs,\n  update: updateAttrs\n}\n"
  },
  {
    "path": "vue/src/platforms/weex/runtime/modules/class.js",
    "content": "/* @flow */\n\nimport { extend } from 'shared/util'\n\nfunction updateClass (oldVnode: VNodeWithData, vnode: VNodeWithData) {\n  const el = vnode.elm\n  const ctx = vnode.context\n\n  const data: VNodeData = vnode.data\n  const oldData: VNodeData = oldVnode.data\n  if (!data.staticClass &&\n    !data.class &&\n    (!oldData || (!oldData.staticClass && !oldData.class))\n  ) {\n    return\n  }\n\n  const oldClassList = []\n  // unlike web, weex vnode staticClass is an Array\n  const oldStaticClass: any = oldData.staticClass\n  if (oldStaticClass) {\n    oldClassList.push.apply(oldClassList, oldStaticClass)\n  }\n  if (oldData.class) {\n    oldClassList.push.apply(oldClassList, oldData.class)\n  }\n\n  const classList = []\n  // unlike web, weex vnode staticClass is an Array\n  const staticClass: any = data.staticClass\n  if (staticClass) {\n    classList.push.apply(classList, staticClass)\n  }\n  if (data.class) {\n    classList.push.apply(classList, data.class)\n  }\n\n  if (typeof el.setClassList === 'function') {\n    el.setClassList(classList)\n  } else {\n    const style = getStyle(oldClassList, classList, ctx)\n    if (typeof el.setStyles === 'function') {\n      el.setStyles(style)\n    } else {\n      for (const key in style) {\n        el.setStyle(key, style[key])\n      }\n    }\n  }\n}\n\nfunction getStyle (oldClassList: Array<string>, classList: Array<string>, ctx: Component): Object {\n  // style is a weex-only injected object\n  // compiled from <style> tags in weex files\n  const stylesheet: any = ctx.$options.style || {}\n  const result = {}\n  classList.forEach(name => {\n    const style = stylesheet[name]\n    extend(result, style)\n  })\n  oldClassList.forEach(name => {\n    const style = stylesheet[name]\n    for (const key in style) {\n      if (!result.hasOwnProperty(key)) {\n        result[key] = ''\n      }\n    }\n  })\n  return result\n}\n\nexport default {\n  create: updateClass,\n  update: updateClass\n}\n"
  },
  {
    "path": "vue/src/platforms/weex/runtime/modules/events.js",
    "content": "/* @flow */\n\nimport { updateListeners } from 'core/vdom/helpers/update-listeners'\n\nlet target: any\n\nfunction add (\n  event: string,\n  handler: Function,\n  once: boolean,\n  capture: boolean,\n  passive?: boolean,\n  params?: Array<any>\n) {\n  if (capture) {\n    console.log('Weex do not support event in bubble phase.')\n    return\n  }\n  if (once) {\n    const oldHandler = handler\n    const _target = target // save current target element in closure\n    handler = function (ev) {\n      const res = arguments.length === 1\n        ? oldHandler(ev)\n        : oldHandler.apply(null, arguments)\n      if (res !== null) {\n        remove(event, null, null, _target)\n      }\n    }\n  }\n  target.addEvent(event, handler, params)\n}\n\nfunction remove (\n  event: string,\n  handler: any,\n  capture: any,\n  _target?: any\n) {\n  (_target || target).removeEvent(event)\n}\n\nfunction updateDOMListeners (oldVnode: VNodeWithData, vnode: VNodeWithData) {\n  if (!oldVnode.data.on && !vnode.data.on) {\n    return\n  }\n  const on = vnode.data.on || {}\n  const oldOn = oldVnode.data.on || {}\n  target = vnode.elm\n  updateListeners(on, oldOn, add, remove, vnode.context)\n  target = undefined\n}\n\nexport default {\n  create: updateDOMListeners,\n  update: updateDOMListeners\n}\n"
  },
  {
    "path": "vue/src/platforms/weex/runtime/modules/index.js",
    "content": "import attrs from './attrs'\nimport klass from './class'\nimport events from './events'\nimport style from './style'\nimport transition from './transition'\n\nexport default [\n  attrs,\n  klass,\n  events,\n  style,\n  transition\n]\n"
  },
  {
    "path": "vue/src/platforms/weex/runtime/modules/style.js",
    "content": "/* @flow */\n\nimport { extend, cached, camelize } from 'shared/util'\n\nconst normalize = cached(camelize)\n\nfunction createStyle (oldVnode: VNodeWithData, vnode: VNodeWithData) {\n  if (!vnode.data.staticStyle) {\n    updateStyle(oldVnode, vnode)\n    return\n  }\n  const elm = vnode.elm\n  const staticStyle = vnode.data.staticStyle\n  const supportBatchUpdate = typeof elm.setStyles === 'function'\n  const batchedStyles = {}\n  for (const name in staticStyle) {\n    if (staticStyle[name]) {\n      supportBatchUpdate\n        ? (batchedStyles[normalize(name)] = staticStyle[name])\n        : elm.setStyle(normalize(name), staticStyle[name])\n    }\n  }\n  if (supportBatchUpdate) {\n    elm.setStyles(batchedStyles)\n  }\n  updateStyle(oldVnode, vnode)\n}\n\nfunction updateStyle (oldVnode: VNodeWithData, vnode: VNodeWithData) {\n  if (!oldVnode.data.style && !vnode.data.style) {\n    return\n  }\n  let cur, name\n  const elm = vnode.elm\n  const oldStyle: any = oldVnode.data.style || {}\n  let style: any = vnode.data.style || {}\n\n  const needClone = style.__ob__\n\n  // handle array syntax\n  if (Array.isArray(style)) {\n    style = vnode.data.style = toObject(style)\n  }\n\n  // clone the style for future updates,\n  // in case the user mutates the style object in-place.\n  if (needClone) {\n    style = vnode.data.style = extend({}, style)\n  }\n\n  const supportBatchUpdate = typeof elm.setStyles === 'function'\n  const batchedStyles = {}\n  for (name in oldStyle) {\n    if (!style[name]) {\n      supportBatchUpdate\n        ? (batchedStyles[normalize(name)] = '')\n        : elm.setStyle(normalize(name), '')\n    }\n  }\n  for (name in style) {\n    cur = style[name]\n    supportBatchUpdate\n      ? (batchedStyles[normalize(name)] = cur)\n      : elm.setStyle(normalize(name), cur)\n  }\n  if (supportBatchUpdate) {\n    elm.setStyles(batchedStyles)\n  }\n}\n\nfunction toObject (arr) {\n  const res = {}\n  for (var i = 0; i < arr.length; i++) {\n    if (arr[i]) {\n      extend(res, arr[i])\n    }\n  }\n  return res\n}\n\nexport default {\n  create: createStyle,\n  update: updateStyle\n}\n"
  },
  {
    "path": "vue/src/platforms/weex/runtime/modules/transition.js",
    "content": "import { warn } from 'core/util/debug'\nimport { extend, once, noop } from 'shared/util'\nimport { activeInstance } from 'core/instance/lifecycle'\nimport { resolveTransition } from 'web/runtime/transition-util'\n\nexport default {\n  create: enter,\n  activate: enter,\n  remove: leave\n}\n\nfunction enter (_, vnode) {\n  const el = vnode.elm\n\n  // call leave callback now\n  if (el._leaveCb) {\n    el._leaveCb.cancelled = true\n    el._leaveCb()\n  }\n\n  const data = resolveTransition(vnode.data.transition)\n  if (!data) {\n    return\n  }\n\n  /* istanbul ignore if */\n  if (el._enterCb) {\n    return\n  }\n\n  const {\n    enterClass,\n    enterToClass,\n    enterActiveClass,\n    appearClass,\n    appearToClass,\n    appearActiveClass,\n    beforeEnter,\n    enter,\n    afterEnter,\n    enterCancelled,\n    beforeAppear,\n    appear,\n    afterAppear,\n    appearCancelled\n  } = data\n\n  let context = activeInstance\n  let transitionNode = activeInstance.$vnode\n  while (transitionNode && transitionNode.parent) {\n    transitionNode = transitionNode.parent\n    context = transitionNode.context\n  }\n\n  const isAppear = !context._isMounted || !vnode.isRootInsert\n\n  if (isAppear && !appear && appear !== '') {\n    return\n  }\n\n  const startClass = isAppear ? appearClass : enterClass\n  const toClass = isAppear ? appearToClass : enterToClass\n  const activeClass = isAppear ? appearActiveClass : enterActiveClass\n  const beforeEnterHook = isAppear ? (beforeAppear || beforeEnter) : beforeEnter\n  const enterHook = isAppear ? (typeof appear === 'function' ? appear : enter) : enter\n  const afterEnterHook = isAppear ? (afterAppear || afterEnter) : afterEnter\n  const enterCancelledHook = isAppear ? (appearCancelled || enterCancelled) : enterCancelled\n\n  const userWantsControl =\n    enterHook &&\n    // enterHook may be a bound method which exposes\n    // the length of original fn as _length\n    (enterHook._length || enterHook.length) > 1\n\n  const stylesheet = vnode.context.$options.style || {}\n  const startState = stylesheet[startClass]\n  const transitionProperties = (stylesheet['@TRANSITION'] && stylesheet['@TRANSITION'][activeClass]) || {}\n  const endState = getEnterTargetState(el, stylesheet, startClass, toClass, activeClass, vnode.context)\n  const needAnimation = Object.keys(endState).length > 0\n\n  const cb = el._enterCb = once(() => {\n    if (cb.cancelled) {\n      enterCancelledHook && enterCancelledHook(el)\n    } else {\n      afterEnterHook && afterEnterHook(el)\n    }\n    el._enterCb = null\n  })\n\n  // We need to wait until the native element has been inserted, but currently\n  // there's no API to do that. So we have to wait \"one frame\" - not entirely\n  // sure if this is guaranteed to be enough (e.g. on slow devices?)\n  setTimeout(() => {\n    const parent = el.parentNode\n    const pendingNode = parent && parent._pending && parent._pending[vnode.key]\n    if (pendingNode &&\n      pendingNode.context === vnode.context &&\n      pendingNode.tag === vnode.tag &&\n      pendingNode.elm._leaveCb\n    ) {\n      pendingNode.elm._leaveCb()\n    }\n    enterHook && enterHook(el, cb)\n\n    if (needAnimation) {\n      const animation = vnode.context.$requireWeexModule('animation')\n      animation.transition(el.ref, {\n        styles: endState,\n        duration: transitionProperties.duration || 0,\n        delay: transitionProperties.delay || 0,\n        timingFunction: transitionProperties.timingFunction || 'linear'\n      }, userWantsControl ? noop : cb)\n    } else if (!userWantsControl) {\n      cb()\n    }\n  }, 16)\n\n  // start enter transition\n  beforeEnterHook && beforeEnterHook(el)\n\n  if (startState) {\n    if (typeof el.setStyles === 'function') {\n      el.setStyles(startState)\n    } else {\n      for (const key in startState) {\n        el.setStyle(key, startState[key])\n      }\n    }\n  }\n\n  if (!needAnimation && !userWantsControl) {\n    cb()\n  }\n}\n\nfunction leave (vnode, rm) {\n  const el = vnode.elm\n\n  // call enter callback now\n  if (el._enterCb) {\n    el._enterCb.cancelled = true\n    el._enterCb()\n  }\n\n  const data = resolveTransition(vnode.data.transition)\n  if (!data) {\n    return rm()\n  }\n\n  if (el._leaveCb) {\n    return\n  }\n\n  const {\n    leaveClass,\n    leaveToClass,\n    leaveActiveClass,\n    beforeLeave,\n    leave,\n    afterLeave,\n    leaveCancelled,\n    delayLeave\n  } = data\n\n  const userWantsControl =\n    leave &&\n    // leave hook may be a bound method which exposes\n    // the length of original fn as _length\n    (leave._length || leave.length) > 1\n\n  const stylesheet = vnode.context.$options.style || {}\n  const startState = stylesheet[leaveClass]\n  const endState = stylesheet[leaveToClass] || stylesheet[leaveActiveClass]\n  const transitionProperties = (stylesheet['@TRANSITION'] && stylesheet['@TRANSITION'][leaveActiveClass]) || {}\n\n  const cb = el._leaveCb = once(() => {\n    if (el.parentNode && el.parentNode._pending) {\n      el.parentNode._pending[vnode.key] = null\n    }\n    if (cb.cancelled) {\n      leaveCancelled && leaveCancelled(el)\n    } else {\n      rm()\n      afterLeave && afterLeave(el)\n    }\n    el._leaveCb = null\n  })\n\n  if (delayLeave) {\n    delayLeave(performLeave)\n  } else {\n    performLeave()\n  }\n\n  function performLeave () {\n    const animation = vnode.context.$requireWeexModule('animation')\n    // the delayed leave may have already been cancelled\n    if (cb.cancelled) {\n      return\n    }\n    // record leaving element\n    if (!vnode.data.show) {\n      (el.parentNode._pending || (el.parentNode._pending = {}))[vnode.key] = vnode\n    }\n    beforeLeave && beforeLeave(el)\n\n    if (startState) {\n      animation.transition(el.ref, {\n        styles: startState\n      }, next)\n    } else {\n      next()\n    }\n\n    function next () {\n      animation.transition(el.ref, {\n        styles: endState,\n        duration: transitionProperties.duration || 0,\n        delay: transitionProperties.delay || 0,\n        timingFunction: transitionProperties.timingFunction || 'linear'\n      }, userWantsControl ? noop : cb)\n    }\n\n    leave && leave(el, cb)\n    if (!endState && !userWantsControl) {\n      cb()\n    }\n  }\n}\n\n// determine the target animation style for an entering transition.\nfunction getEnterTargetState (el, stylesheet, startClass, endClass, activeClass, vm) {\n  const targetState = {}\n  const startState = stylesheet[startClass]\n  const endState = stylesheet[endClass]\n  const activeState = stylesheet[activeClass]\n  // 1. fallback to element's default styling\n  if (startState) {\n    for (const key in startState) {\n      targetState[key] = el.style[key]\n      if (\n        process.env.NODE_ENV !== 'production' &&\n        targetState[key] == null &&\n        (!activeState || activeState[key] == null) &&\n        (!endState || endState[key] == null)\n      ) {\n        warn(\n          `transition property \"${key}\" is declared in enter starting class (.${startClass}), ` +\n          `but not declared anywhere in enter ending class (.${endClass}), ` +\n          `enter active cass (.${activeClass}) or the element's default styling. ` +\n          `Note in Weex, CSS properties need explicit values to be transitionable.`\n        )\n      }\n    }\n  }\n  // 2. if state is mixed in active state, extract them while excluding\n  //    transition properties\n  if (activeState) {\n    for (const key in activeState) {\n      if (key.indexOf('transition') !== 0) {\n        targetState[key] = activeState[key]\n      }\n    }\n  }\n  // 3. explicit endState has highest priority\n  if (endState) {\n    extend(targetState, endState)\n  }\n  return targetState\n}\n"
  },
  {
    "path": "vue/src/platforms/weex/runtime/node-ops.js",
    "content": "/* @flow */\ndeclare var document: WeexDocument;\n\nimport TextNode from 'weex/runtime/text-node'\n\nexport const namespaceMap = {}\n\nexport function createElement (tagName: string): WeexElement {\n  return document.createElement(tagName)\n}\n\nexport function createElementNS (namespace: string, tagName: string): WeexElement {\n  return document.createElement(namespace + ':' + tagName)\n}\n\nexport function createTextNode (text: string) {\n  return new TextNode(text)\n}\n\nexport function createComment (text: string) {\n  return document.createComment(text)\n}\n\nexport function insertBefore (\n  node: WeexElement,\n  target: WeexElement,\n  before: WeexElement\n) {\n  if (target.nodeType === 3) {\n    if (node.type === 'text') {\n      node.setAttr('value', target.text)\n      target.parentNode = node\n    } else {\n      const text = createElement('text')\n      text.setAttr('value', target.text)\n      node.insertBefore(text, before)\n    }\n    return\n  }\n  node.insertBefore(target, before)\n}\n\nexport function removeChild (node: WeexElement, child: WeexElement) {\n  if (child.nodeType === 3) {\n    node.setAttr('value', '')\n    return\n  }\n  node.removeChild(child)\n}\n\nexport function appendChild (node: WeexElement, child: WeexElement) {\n  if (child.nodeType === 3) {\n    if (node.type === 'text') {\n      node.setAttr('value', child.text)\n      child.parentNode = node\n    } else {\n      const text = createElement('text')\n      text.setAttr('value', child.text)\n      node.appendChild(text)\n    }\n    return\n  }\n\n  node.appendChild(child)\n}\n\nexport function parentNode (node: WeexElement): WeexElement | void {\n  return node.parentNode\n}\n\nexport function nextSibling (node: WeexElement): WeexElement | void {\n  return node.nextSibling\n}\n\nexport function tagName (node: WeexElement): string {\n  return node.type\n}\n\nexport function setTextContent (node: WeexElement, text: string) {\n  if (node.parentNode) {\n    node.parentNode.setAttr('value', text)\n  }\n}\n\nexport function setAttribute (node: WeexElement, key: string, val: any) {\n  node.setAttr(key, val)\n}\n\nexport function setStyleScope (node: WeexElement, scopeId: string) {\n  node.setAttr('@styleScope', scopeId)\n}\n"
  },
  {
    "path": "vue/src/platforms/weex/runtime/patch.js",
    "content": "/* @flow */\n\nimport * as nodeOps from 'weex/runtime/node-ops'\nimport { createPatchFunction } from 'core/vdom/patch'\nimport baseModules from 'core/vdom/modules/index'\nimport platformModules from 'weex/runtime/modules/index'\n\n// the directive module should be applied last, after all\n// built-in modules have been applied.\nconst modules = platformModules.concat(baseModules)\n\nexport const patch: Function = createPatchFunction({\n  nodeOps,\n  modules,\n  LONG_LIST_THRESHOLD: 10\n})\n"
  },
  {
    "path": "vue/src/platforms/weex/runtime/recycle-list/render-component-template.js",
    "content": "/* @flow */\n\nimport { warn } from 'core/util/debug'\nimport { handleError } from 'core/util/error'\nimport { RECYCLE_LIST_MARKER } from 'weex/util/index'\nimport { createComponentInstanceForVnode } from 'core/vdom/create-component'\nimport { resolveVirtualComponent } from './virtual-component'\n\nexport function isRecyclableComponent (vnode: VNodeWithData): boolean {\n  return vnode.data.attrs\n    ? (RECYCLE_LIST_MARKER in vnode.data.attrs)\n    : false\n}\n\nexport function renderRecyclableComponentTemplate (vnode: MountedComponentVNode): VNode {\n  // $flow-disable-line\n  delete vnode.data.attrs[RECYCLE_LIST_MARKER]\n  resolveVirtualComponent(vnode)\n  const vm = createComponentInstanceForVnode(vnode)\n  const render = (vm.$options: any)['@render']\n  if (render) {\n    try {\n      return render.call(vm)\n    } catch (err) {\n      handleError(err, vm, `@render`)\n    }\n  } else {\n    warn(\n      `@render function not defined on component used in <recycle-list>. ` +\n      `Make sure to declare \\`recyclable=\"true\"\\` on the component's template.`,\n      vm\n    )\n  }\n}\n"
  },
  {
    "path": "vue/src/platforms/weex/runtime/recycle-list/virtual-component.js",
    "content": "/* @flow */\n\n// https://github.com/Hanks10100/weex-native-directive/tree/master/component\n\nimport { mergeOptions, isPlainObject, noop } from 'core/util/index'\nimport Watcher from 'core/observer/watcher'\nimport { initProxy } from 'core/instance/proxy'\nimport { initState, getData } from 'core/instance/state'\nimport { initRender } from 'core/instance/render'\nimport { initEvents } from 'core/instance/events'\nimport { initProvide, initInjections } from 'core/instance/inject'\nimport { initLifecycle, callHook } from 'core/instance/lifecycle'\nimport { initInternalComponent, resolveConstructorOptions } from 'core/instance/init'\nimport { registerComponentHook, updateComponentData } from '../../util/index'\n\nlet uid = 0\n\n// override Vue.prototype._init\nfunction initVirtualComponent (options: Object = {}) {\n  const vm: Component = this\n  const componentId = options.componentId\n\n  // virtual component uid\n  vm._uid = `virtual-component-${uid++}`\n\n  // a flag to avoid this being observed\n  vm._isVue = true\n  // merge options\n  if (options && options._isComponent) {\n    // optimize internal component instantiation\n    // since dynamic options merging is pretty slow, and none of the\n    // internal component options needs special treatment.\n    initInternalComponent(vm, options)\n  } else {\n    vm.$options = mergeOptions(\n      resolveConstructorOptions(vm.constructor),\n      options || {},\n      vm\n    )\n  }\n\n  /* istanbul ignore else */\n  if (process.env.NODE_ENV !== 'production') {\n    initProxy(vm)\n  } else {\n    vm._renderProxy = vm\n  }\n\n  vm._self = vm\n  initLifecycle(vm)\n  initEvents(vm)\n  initRender(vm)\n  callHook(vm, 'beforeCreate')\n  initInjections(vm) // resolve injections before data/props\n  initState(vm)\n  initProvide(vm) // resolve provide after data/props\n  callHook(vm, 'created')\n\n  // send initial data to native\n  const data = vm.$options.data\n  const params = typeof data === 'function'\n    ? getData(data, vm)\n    : data || {}\n  if (isPlainObject(params)) {\n    updateComponentData(componentId, params)\n  }\n\n  registerComponentHook(componentId, 'lifecycle', 'attach', () => {\n    callHook(vm, 'beforeMount')\n\n    const updateComponent = () => {\n      vm._update(vm._vnode, false)\n    }\n    new Watcher(vm, updateComponent, noop, null, true)\n\n    vm._isMounted = true\n    callHook(vm, 'mounted')\n  })\n\n  registerComponentHook(componentId, 'lifecycle', 'detach', () => {\n    vm.$destroy()\n  })\n}\n\n// override Vue.prototype._update\nfunction updateVirtualComponent (vnode?: VNode) {\n  const vm: Component = this\n  const componentId = vm.$options.componentId\n  if (vm._isMounted) {\n    callHook(vm, 'beforeUpdate')\n  }\n  vm._vnode = vnode\n  if (vm._isMounted && componentId) {\n    // TODO: data should be filtered and without bindings\n    const data = Object.assign({}, vm._data)\n    updateComponentData(componentId, data, () => {\n      callHook(vm, 'updated')\n    })\n  }\n}\n\n// listening on native callback\nexport function resolveVirtualComponent (vnode: MountedComponentVNode): VNode {\n  const BaseCtor = vnode.componentOptions.Ctor\n  const VirtualComponent = BaseCtor.extend({})\n  const cid = VirtualComponent.cid\n  VirtualComponent.prototype._init = initVirtualComponent\n  VirtualComponent.prototype._update = updateVirtualComponent\n\n  vnode.componentOptions.Ctor = BaseCtor.extend({\n    beforeCreate () {\n      // const vm: Component = this\n\n      // TODO: listen on all events and dispatch them to the\n      // corresponding virtual components according to the componentId.\n      // vm._virtualComponents = {}\n      const createVirtualComponent = (componentId, propsData) => {\n        // create virtual component\n        // const subVm =\n        new VirtualComponent({\n          componentId,\n          propsData\n        })\n        // if (vm._virtualComponents) {\n        //   vm._virtualComponents[componentId] = subVm\n        // }\n      }\n\n      registerComponentHook(cid, 'lifecycle', 'create', createVirtualComponent)\n    },\n    beforeDestroy () {\n      delete this._virtualComponents\n    }\n  })\n}\n\n"
  },
  {
    "path": "vue/src/platforms/weex/runtime/text-node.js",
    "content": "let latestNodeId = 1\n\nexport default function TextNode (text) {\n  this.instanceId = ''\n  this.nodeId = latestNodeId++\n  this.parentNode = null\n  this.nodeType = 3\n  this.text = text\n}\n"
  },
  {
    "path": "vue/src/platforms/weex/util/element.js",
    "content": "/* @flow */\n\n// These util functions are split into its own file because Rollup cannot drop\n// makeMap() due to potential side effects, so these variables end up\n// bloating the web builds.\n\nimport { makeMap } from 'shared/util'\n\nexport const isReservedTag = makeMap(\n  'template,script,style,element,content,slot,link,meta,svg,view,' +\n  'a,div,img,image,text,span,input,switch,textarea,spinner,select,' +\n  'slider,slider-neighbor,indicator,canvas,' +\n  'list,cell,header,loading,loading-indicator,refresh,scrollable,scroller,' +\n  'video,web,embed,tabbar,tabheader,datepicker,timepicker,marquee,countdown',\n  true\n)\n\n// Elements that you can, intentionally, leave open (and which close themselves)\n// more flexible than web\nexport const canBeLeftOpenTag = makeMap(\n  'web,spinner,switch,video,textarea,canvas,' +\n  'indicator,marquee,countdown',\n  true\n)\n\nexport const isRuntimeComponent = makeMap(\n  'richtext,transition,transition-group',\n  true\n)\n\nexport const isUnaryTag = makeMap(\n  'embed,img,image,input,link,meta',\n  true\n)\n\nexport function mustUseProp (tag: string, type: ?string, name: string): boolean {\n  return false\n}\n\nexport function getTagNamespace (tag?: string): string | void { }\n\nexport function isUnknownElement (tag?: string): boolean {\n  return false\n}\n\nexport function query (el: string | Element, document: Object) {\n  // document is injected by weex factory wrapper\n  const placeholder = document.createComment('root')\n  placeholder.hasAttribute = placeholder.removeAttribute = function () {} // hack for patch\n  document.documentElement.appendChild(placeholder)\n  return placeholder\n}\n"
  },
  {
    "path": "vue/src/platforms/weex/util/index.js",
    "content": "/* @flow */\ndeclare var document: WeexDocument;\n\nimport { warn } from 'core/util/index'\n\nexport const RECYCLE_LIST_MARKER = '@inRecycleList'\n\n// Register the component hook to weex native render engine.\n// The hook will be triggered by native, not javascript.\nexport function registerComponentHook (\n  componentId: string,\n  type: string, // hook type, could be \"lifecycle\" or \"instance\"\n  hook: string, // hook name\n  fn: Function\n) {\n  if (!document || !document.taskCenter) {\n    warn(`Can't find available \"document\" or \"taskCenter\".`)\n    return\n  }\n  if (typeof document.taskCenter.registerHook === 'function') {\n    return document.taskCenter.registerHook(componentId, type, hook, fn)\n  }\n  warn(`Failed to register component hook \"${type}@${hook}#${componentId}\".`)\n}\n\n// Updates the state of the component to weex native render engine.\nexport function updateComponentData (\n  componentId: string,\n  newData: Object | void,\n  callback?: Function\n) {\n  if (!document || !document.taskCenter) {\n    warn(`Can't find available \"document\" or \"taskCenter\".`)\n    return\n  }\n  if (typeof document.taskCenter.updateData === 'function') {\n    return document.taskCenter.updateData(componentId, newData, callback)\n  }\n  warn(`Failed to update component data (${componentId}).`)\n}\n"
  },
  {
    "path": "vue/src/platforms/weex/util/parser.js",
    "content": "/* @flow */\n\n// import { warn } from 'core/util/index'\n\n// this will be preserved during build\n// $flow-disable-line\nconst acorn = require('acorn') // $flow-disable-line\nconst walk = require('acorn/dist/walk') // $flow-disable-line\nconst escodegen = require('escodegen')\n\nexport function nodeToBinding (node: Object): any {\n  switch (node.type) {\n    case 'Literal': return node.value\n    case 'Identifier':\n    case 'UnaryExpression':\n    case 'BinaryExpression':\n    case 'LogicalExpression':\n    case 'ConditionalExpression':\n    case 'MemberExpression': return { '@binding': escodegen.generate(node) }\n    case 'ArrayExpression': return node.elements.map(_ => nodeToBinding(_))\n    case 'ObjectExpression': {\n      const object = {}\n      node.properties.forEach(prop => {\n        if (!prop.key || prop.key.type !== 'Identifier') {\n          return\n        }\n        const key = escodegen.generate(prop.key)\n        const value = nodeToBinding(prop.value)\n        if (key && value) {\n          object[key] = value\n        }\n      })\n      return object\n    }\n    default: {\n      // warn(`Not support ${node.type}: \"${escodegen.generate(node)}\"`)\n      return ''\n    }\n  }\n}\n\nexport function generateBinding (exp: ?string): any {\n  if (exp && typeof exp === 'string') {\n    let ast = null\n    try {\n      ast = acorn.parse(`(${exp})`)\n    } catch (e) {\n      // warn(`Failed to parse the expression: \"${exp}\"`)\n      return ''\n    }\n\n    let output = ''\n    walk.simple(ast, {\n      Expression (node) {\n        output = nodeToBinding(node)\n      }\n    })\n    return output\n  }\n}\n"
  },
  {
    "path": "vue/src/server/bundle-renderer/create-bundle-renderer.js",
    "content": "/* @flow */\n\nimport { createPromiseCallback } from '../util'\nimport { createBundleRunner } from './create-bundle-runner'\nimport type { Renderer, RenderOptions } from '../create-renderer'\nimport { createSourceMapConsumers, rewriteErrorTrace } from './source-map-support'\n\nconst fs = require('fs')\nconst path = require('path')\nconst PassThrough = require('stream').PassThrough\n\nconst INVALID_MSG =\n  'Invalid server-rendering bundle format. Should be a string ' +\n  'or a bundle Object of type:\\n\\n' +\n`{\n  entry: string;\n  files: { [filename: string]: string; };\n  maps: { [filename: string]: string; };\n}\\n`\n\n// The render bundle can either be a string (single bundled file)\n// or a bundle manifest object generated by vue-ssr-webpack-plugin.\ntype RenderBundle = {\n  basedir?: string;\n  entry: string;\n  files: { [filename: string]: string; };\n  maps: { [filename: string]: string; };\n  modules?: { [filename: string]: Array<string> };\n};\n\nexport function createBundleRendererCreator (\n  createRenderer: (options?: RenderOptions) => Renderer\n) {\n  return function createBundleRenderer (\n    bundle: string | RenderBundle,\n    rendererOptions?: RenderOptions = {}\n  ) {\n    let files, entry, maps\n    let basedir = rendererOptions.basedir\n\n    // load bundle if given filepath\n    if (\n      typeof bundle === 'string' &&\n      /\\.js(on)?$/.test(bundle) &&\n      path.isAbsolute(bundle)\n    ) {\n      if (fs.existsSync(bundle)) {\n        const isJSON = /\\.json$/.test(bundle)\n        basedir = basedir || path.dirname(bundle)\n        bundle = fs.readFileSync(bundle, 'utf-8')\n        if (isJSON) {\n          try {\n            bundle = JSON.parse(bundle)\n          } catch (e) {\n            throw new Error(`Invalid JSON bundle file: ${bundle}`)\n          }\n        }\n      } else {\n        throw new Error(`Cannot locate bundle file: ${bundle}`)\n      }\n    }\n\n    if (typeof bundle === 'object') {\n      entry = bundle.entry\n      files = bundle.files\n      basedir = basedir || bundle.basedir\n      maps = createSourceMapConsumers(bundle.maps)\n      if (typeof entry !== 'string' || typeof files !== 'object') {\n        throw new Error(INVALID_MSG)\n      }\n    } else if (typeof bundle === 'string') {\n      entry = '__vue_ssr_bundle__'\n      files = { '__vue_ssr_bundle__': bundle }\n      maps = {}\n    } else {\n      throw new Error(INVALID_MSG)\n    }\n\n    const renderer = createRenderer(rendererOptions)\n\n    const run = createBundleRunner(\n      entry,\n      files,\n      basedir,\n      rendererOptions.runInNewContext\n    )\n\n    return {\n      renderToString: (context?: Object, cb: any) => {\n        if (typeof context === 'function') {\n          cb = context\n          context = {}\n        }\n\n        let promise\n        if (!cb) {\n          ({ promise, cb } = createPromiseCallback())\n        }\n\n        run(context).catch(err => {\n          rewriteErrorTrace(err, maps)\n          cb(err)\n        }).then(app => {\n          if (app) {\n            renderer.renderToString(app, context, (err, res) => {\n              rewriteErrorTrace(err, maps)\n              cb(err, res)\n            })\n          }\n        })\n\n        return promise\n      },\n\n      renderToStream: (context?: Object) => {\n        const res = new PassThrough()\n        run(context).catch(err => {\n          rewriteErrorTrace(err, maps)\n          // avoid emitting synchronously before user can\n          // attach error listener\n          process.nextTick(() => {\n            res.emit('error', err)\n          })\n        }).then(app => {\n          if (app) {\n            const renderStream = renderer.renderToStream(app, context)\n\n            renderStream.on('error', err => {\n              rewriteErrorTrace(err, maps)\n              res.emit('error', err)\n            })\n\n            // relay HTMLStream special events\n            if (rendererOptions && rendererOptions.template) {\n              renderStream.on('beforeStart', () => {\n                res.emit('beforeStart')\n              })\n              renderStream.on('beforeEnd', () => {\n                res.emit('beforeEnd')\n              })\n            }\n\n            renderStream.pipe(res)\n          }\n        })\n\n        return res\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "vue/src/server/bundle-renderer/create-bundle-runner.js",
    "content": "import { isPlainObject } from 'shared/util'\n\nconst vm = require('vm')\nconst path = require('path')\nconst resolve = require('resolve')\nconst NativeModule = require('module')\n\nfunction createSandbox (context) {\n  const sandbox = {\n    Buffer,\n    console,\n    process,\n    setTimeout,\n    setInterval,\n    setImmediate,\n    clearTimeout,\n    clearInterval,\n    clearImmediate,\n    __VUE_SSR_CONTEXT__: context\n  }\n  sandbox.global = sandbox\n  return sandbox\n}\n\nfunction compileModule (files, basedir, runInNewContext) {\n  const compiledScripts = {}\n  const resolvedModules = {}\n\n  function getCompiledScript (filename) {\n    if (compiledScripts[filename]) {\n      return compiledScripts[filename]\n    }\n    const code = files[filename]\n    const wrapper = NativeModule.wrap(code)\n    const script = new vm.Script(wrapper, {\n      filename,\n      displayErrors: true\n    })\n    compiledScripts[filename] = script\n    return script\n  }\n\n  function evaluateModule (filename, sandbox, evaluatedFiles = {}) {\n    if (evaluatedFiles[filename]) {\n      return evaluatedFiles[filename]\n    }\n\n    const script = getCompiledScript(filename)\n    const compiledWrapper = runInNewContext === false\n      ? script.runInThisContext()\n      : script.runInNewContext(sandbox)\n    const m = { exports: {}}\n    const r = file => {\n      file = path.posix.join('.', file)\n      if (files[file]) {\n        return evaluateModule(file, sandbox, evaluatedFiles)\n      } else if (basedir) {\n        return require(\n          resolvedModules[file] ||\n          (resolvedModules[file] = resolve.sync(file, { basedir }))\n        )\n      } else {\n        return require(file)\n      }\n    }\n    compiledWrapper.call(m.exports, m.exports, r, m)\n\n    const res = Object.prototype.hasOwnProperty.call(m.exports, 'default')\n      ? m.exports.default\n      : m.exports\n    evaluatedFiles[filename] = res\n    return res\n  }\n  return evaluateModule\n}\n\nfunction deepClone (val) {\n  if (isPlainObject(val)) {\n    const res = {}\n    for (const key in val) {\n      res[key] = deepClone(val[key])\n    }\n    return res\n  } else if (Array.isArray(val)) {\n    return val.slice()\n  } else {\n    return val\n  }\n}\n\nexport function createBundleRunner (entry, files, basedir, runInNewContext) {\n  const evaluate = compileModule(files, basedir, runInNewContext)\n  if (runInNewContext !== false && runInNewContext !== 'once') {\n    // new context mode: creates a fresh context and re-evaluate the bundle\n    // on each render. Ensures entire application state is fresh for each\n    // render, but incurs extra evaluation cost.\n    return (userContext = {}) => new Promise(resolve => {\n      userContext._registeredComponents = new Set()\n      const res = evaluate(entry, createSandbox(userContext))\n      resolve(typeof res === 'function' ? res(userContext) : res)\n    })\n  } else {\n    // direct mode: instead of re-evaluating the whole bundle on\n    // each render, it simply calls the exported function. This avoids the\n    // module evaluation costs but requires the source code to be structured\n    // slightly differently.\n    let runner // lazy creation so that errors can be caught by user\n    let initialContext\n    return (userContext = {}) => new Promise(resolve => {\n      if (!runner) {\n        const sandbox = runInNewContext === 'once'\n          ? createSandbox()\n          : global\n        // the initial context is only used for collecting possible non-component\n        // styles injected by vue-style-loader.\n        initialContext = sandbox.__VUE_SSR_CONTEXT__ = {}\n        runner = evaluate(entry, sandbox)\n        // On subsequent renders, __VUE_SSR_CONTEXT__ will not be available\n        // to prevent cross-request pollution.\n        delete sandbox.__VUE_SSR_CONTEXT__\n        if (typeof runner !== 'function') {\n          throw new Error(\n            'bundle export should be a function when using ' +\n            '{ runInNewContext: false }.'\n          )\n        }\n      }\n      userContext._registeredComponents = new Set()\n\n      // vue-style-loader styles imported outside of component lifecycle hooks\n      if (initialContext._styles) {\n        userContext._styles = deepClone(initialContext._styles)\n        // #6353 ensure \"styles\" is exposed even if no styles are injected\n        // in component lifecycles.\n        // the renderStyles fn is exposed by vue-style-loader >= 3.0.3\n        const renderStyles = initialContext._renderStyles\n        if (renderStyles) {\n          Object.defineProperty(userContext, 'styles', {\n            enumerable: true,\n            get () {\n              return renderStyles(userContext._styles)\n            }\n          })\n        }\n      }\n\n      resolve(runner(userContext))\n    })\n  }\n}\n"
  },
  {
    "path": "vue/src/server/bundle-renderer/source-map-support.js",
    "content": "/* @flow */\n\nconst SourceMapConsumer = require('source-map').SourceMapConsumer\n\nconst filenameRE = /\\(([^)]+\\.js):(\\d+):(\\d+)\\)$/\n\nexport function createSourceMapConsumers (rawMaps: Object) {\n  const maps = {}\n  Object.keys(rawMaps).forEach(file => {\n    maps[file] = new SourceMapConsumer(rawMaps[file])\n  })\n  return maps\n}\n\nexport function rewriteErrorTrace (e: any, mapConsumers: {\n  [key: string]: SourceMapConsumer\n}) {\n  if (e && typeof e.stack === 'string') {\n    e.stack = e.stack.split('\\n').map(line => {\n      return rewriteTraceLine(line, mapConsumers)\n    }).join('\\n')\n  }\n}\n\nfunction rewriteTraceLine (trace: string, mapConsumers: {\n  [key: string]: SourceMapConsumer\n}) {\n  const m = trace.match(filenameRE)\n  const map = m && mapConsumers[m[1]]\n  if (m != null && map) {\n    const originalPosition = map.originalPositionFor({\n      line: Number(m[2]),\n      column: Number(m[3])\n    })\n    if (originalPosition.source != null) {\n      const { source, line, column } = originalPosition\n      const mappedPosition = `(${source.replace(/^webpack:\\/\\/\\//, '')}:${String(line)}:${String(column)})`\n      return trace.replace(filenameRE, mappedPosition)\n    } else {\n      return trace\n    }\n  } else {\n    return trace\n  }\n}\n"
  },
  {
    "path": "vue/src/server/create-basic-renderer.js",
    "content": "/* @flow */\n\nimport { createWriteFunction } from './write'\nimport { createRenderFunction } from './render'\nimport type { RenderOptions } from './create-renderer'\n\nexport function createBasicRenderer ({\n  modules = [],\n  directives = {},\n  isUnaryTag = (() => false),\n  cache\n}: RenderOptions = {}) {\n  const render = createRenderFunction(modules, directives, isUnaryTag, cache)\n\n  return function renderToString (\n    component: Component,\n    context: any,\n    done: any\n  ): void {\n    if (typeof context === 'function') {\n      done = context\n      context = {}\n    }\n    let result = ''\n    const write = createWriteFunction(text => {\n      result += text\n      return false\n    }, done)\n    try {\n      render(component, write, context, () => {\n        done(null, result)\n      })\n    } catch (e) {\n      done(e)\n    }\n  }\n}\n"
  },
  {
    "path": "vue/src/server/create-renderer.js",
    "content": "/* @flow */\n\nimport RenderStream from './render-stream'\nimport { createWriteFunction } from './write'\nimport { createRenderFunction } from './render'\nimport { createPromiseCallback } from './util'\nimport TemplateRenderer from './template-renderer/index'\nimport type { ClientManifest } from './template-renderer/index'\n\nexport type Renderer = {\n  renderToString: (component: Component, context: any, cb: any) => ?Promise<string>;\n  renderToStream: (component: Component, context?: Object) => stream$Readable;\n};\n\ntype RenderCache = {\n  get: (key: string, cb?: Function) => string | void;\n  set: (key: string, val: string) => void;\n  has?: (key: string, cb?: Function) => boolean | void;\n};\n\nexport type RenderOptions = {\n  modules?: Array<(vnode: VNode) => ?string>;\n  directives?: Object;\n  isUnaryTag?: Function;\n  cache?: RenderCache;\n  template?: string;\n  inject?: boolean;\n  basedir?: string;\n  shouldPreload?: Function;\n  shouldPrefetch?: Function;\n  clientManifest?: ClientManifest;\n  runInNewContext?: boolean | 'once';\n};\n\nexport function createRenderer ({\n  modules = [],\n  directives = {},\n  isUnaryTag = (() => false),\n  template,\n  inject,\n  cache,\n  shouldPreload,\n  shouldPrefetch,\n  clientManifest\n}: RenderOptions = {}): Renderer {\n  const render = createRenderFunction(modules, directives, isUnaryTag, cache)\n  const templateRenderer = new TemplateRenderer({\n    template,\n    inject,\n    shouldPreload,\n    shouldPrefetch,\n    clientManifest\n  })\n\n  return {\n    renderToString (\n      component: Component,\n      context: any,\n      cb: any\n    ): ?Promise<string> {\n      if (typeof context === 'function') {\n        cb = context\n        context = {}\n      }\n      if (context) {\n        templateRenderer.bindRenderFns(context)\n      }\n\n      // no callback, return Promise\n      let promise\n      if (!cb) {\n        ({ promise, cb } = createPromiseCallback())\n      }\n\n      let result = ''\n      const write = createWriteFunction(text => {\n        result += text\n        return false\n      }, cb)\n      try {\n        render(component, write, context, err => {\n          if (template) {\n            result = templateRenderer.renderSync(result, context)\n          }\n          if (err) {\n            cb(err)\n          } else {\n            cb(null, result)\n          }\n        })\n      } catch (e) {\n        cb(e)\n      }\n\n      return promise\n    },\n\n    renderToStream (\n      component: Component,\n      context?: Object\n    ): stream$Readable {\n      if (context) {\n        templateRenderer.bindRenderFns(context)\n      }\n      const renderStream = new RenderStream((write, done) => {\n        render(component, write, context, done)\n      })\n      if (!template) {\n        return renderStream\n      } else {\n        const templateStream = templateRenderer.createStream(context)\n        renderStream.on('error', err => {\n          templateStream.emit('error', err)\n        })\n        renderStream.pipe(templateStream)\n        return templateStream\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "vue/src/server/optimizing-compiler/codegen.js",
    "content": "/* @flow */\n\n// The SSR codegen is essentially extending the default codegen to handle\n// SSR-optimizable nodes and turn them into string render fns. In cases where\n// a node is not optimizable it simply falls back to the default codegen.\n\nimport {\n  genIf,\n  genFor,\n  genData,\n  genText,\n  genElement,\n  genChildren,\n  CodegenState\n} from 'compiler/codegen/index'\n\nimport {\n  genAttrSegments,\n  genDOMPropSegments,\n  genClassSegments,\n  genStyleSegments,\n  applyModelTransform\n} from './modules'\n\nimport { escape } from 'web/server/util'\nimport { optimizability } from './optimizer'\nimport type { CodegenResult } from 'compiler/codegen/index'\n\nexport type StringSegment = {\n  type: number;\n  value: string;\n};\n\n// segment types\nexport const RAW = 0\nexport const INTERPOLATION = 1\nexport const EXPRESSION = 2\n\nexport function generate (\n  ast: ASTElement | void,\n  options: CompilerOptions\n): CodegenResult {\n  const state = new CodegenState(options)\n  const code = ast ? genSSRElement(ast, state) : '_c(\"div\")'\n  return {\n    render: `with(this){return ${code}}`,\n    staticRenderFns: state.staticRenderFns\n  }\n}\n\nfunction genSSRElement (el: ASTElement, state: CodegenState): string {\n  if (el.for && !el.forProcessed) {\n    return genFor(el, state, genSSRElement)\n  } else if (el.if && !el.ifProcessed) {\n    return genIf(el, state, genSSRElement)\n  } else if (el.tag === 'template' && !el.slotTarget) {\n    return el.ssrOptimizability === optimizability.FULL\n      ? genChildrenAsStringNode(el, state)\n      : genSSRChildren(el, state) || 'void 0'\n  }\n\n  switch (el.ssrOptimizability) {\n    case optimizability.FULL:\n      // stringify whole tree\n      return genStringElement(el, state)\n    case optimizability.SELF:\n      // stringify self and check children\n      return genStringElementWithChildren(el, state)\n    case optimizability.CHILDREN:\n      // generate self as VNode and stringify children\n      return genNormalElement(el, state, true)\n    case optimizability.PARTIAL:\n      // generate self as VNode and check children\n      return genNormalElement(el, state, false)\n    default:\n      // bail whole tree\n      return genElement(el, state)\n  }\n}\n\nfunction genNormalElement (el, state, stringifyChildren) {\n  const data = el.plain ? undefined : genData(el, state)\n  const children = stringifyChildren\n    ? `[${genChildrenAsStringNode(el, state)}]`\n    : genSSRChildren(el, state, true)\n  return `_c('${el.tag}'${\n    data ? `,${data}` : ''\n  }${\n    children ? `,${children}` : ''\n  })`\n}\n\nfunction genSSRChildren (el, state, checkSkip) {\n  return genChildren(el, state, checkSkip, genSSRElement, genSSRNode)\n}\n\nfunction genSSRNode (el, state) {\n  return el.type === 1\n    ? genSSRElement(el, state)\n    : genText(el)\n}\n\nfunction genChildrenAsStringNode (el, state) {\n  return el.children.length\n    ? `_ssrNode(${flattenSegments(childrenToSegments(el, state))})`\n    : ''\n}\n\nfunction genStringElement (el, state) {\n  return `_ssrNode(${elementToString(el, state)})`\n}\n\nfunction genStringElementWithChildren (el, state) {\n  const children = genSSRChildren(el, state, true)\n  return `_ssrNode(${\n    flattenSegments(elementToOpenTagSegments(el, state))\n  },\"</${el.tag}>\"${\n    children ? `,${children}` : ''\n  })`\n}\n\nfunction elementToString (el, state) {\n  return `(${flattenSegments(elementToSegments(el, state))})`\n}\n\nfunction elementToSegments (el, state): Array<StringSegment> {\n  // v-for / v-if\n  if (el.for && !el.forProcessed) {\n    el.forProcessed = true\n    return [{\n      type: EXPRESSION,\n      value: genFor(el, state, elementToString, '_ssrList')\n    }]\n  } else if (el.if && !el.ifProcessed) {\n    el.ifProcessed = true\n    return [{\n      type: EXPRESSION,\n      value: genIf(el, state, elementToString, '\"<!---->\"')\n    }]\n  } else if (el.tag === 'template') {\n    return childrenToSegments(el, state)\n  }\n\n  const openSegments = elementToOpenTagSegments(el, state)\n  const childrenSegments = childrenToSegments(el, state)\n  const { isUnaryTag } = state.options\n  const close = (isUnaryTag && isUnaryTag(el.tag))\n    ? []\n    : [{ type: RAW, value: `</${el.tag}>` }]\n  return openSegments.concat(childrenSegments, close)\n}\n\nfunction elementToOpenTagSegments (el, state): Array<StringSegment> {\n  applyModelTransform(el, state)\n  let binding\n  const segments = [{ type: RAW, value: `<${el.tag}` }]\n  // attrs\n  if (el.attrs) {\n    segments.push.apply(segments, genAttrSegments(el.attrs))\n  }\n  // domProps\n  if (el.props) {\n    segments.push.apply(segments, genDOMPropSegments(el.props, el.attrs))\n  }\n  // v-bind=\"object\"\n  if ((binding = el.attrsMap['v-bind'])) {\n    segments.push({ type: EXPRESSION, value: `_ssrAttrs(${binding})` })\n  }\n  // v-bind.prop=\"object\"\n  if ((binding = el.attrsMap['v-bind.prop'])) {\n    segments.push({ type: EXPRESSION, value: `_ssrDOMProps(${binding})` })\n  }\n  // class\n  if (el.staticClass || el.classBinding) {\n    segments.push.apply(\n      segments,\n      genClassSegments(el.staticClass, el.classBinding)\n    )\n  }\n  // style & v-show\n  if (el.staticStyle || el.styleBinding || el.attrsMap['v-show']) {\n    segments.push.apply(\n      segments,\n      genStyleSegments(\n        el.attrsMap.style,\n        el.staticStyle,\n        el.styleBinding,\n        el.attrsMap['v-show']\n      )\n    )\n  }\n  // _scopedId\n  if (state.options.scopeId) {\n    segments.push({ type: RAW, value: ` ${state.options.scopeId}` })\n  }\n  segments.push({ type: RAW, value: `>` })\n  return segments\n}\n\nfunction childrenToSegments (el, state): Array<StringSegment> {\n  let binding\n  if ((binding = el.attrsMap['v-html'])) {\n    return [{ type: EXPRESSION, value: `_s(${binding})` }]\n  }\n  if ((binding = el.attrsMap['v-text'])) {\n    return [{ type: INTERPOLATION, value: `_s(${binding})` }]\n  }\n  if (el.tag === 'textarea' && (binding = el.attrsMap['v-model'])) {\n    return [{ type: INTERPOLATION, value: `_s(${binding})` }]\n  }\n  return el.children\n    ? nodesToSegments(el.children, state)\n    : []\n}\n\nfunction nodesToSegments (\n  children: Array<ASTNode>,\n  state: CodegenState\n): Array<StringSegment> {\n  const segments = []\n  for (let i = 0; i < children.length; i++) {\n    const c = children[i]\n    if (c.type === 1) {\n      segments.push.apply(segments, elementToSegments(c, state))\n    } else if (c.type === 2) {\n      segments.push({ type: INTERPOLATION, value: c.expression })\n    } else if (c.type === 3) {\n      segments.push({ type: RAW, value: escape(c.text) })\n    }\n  }\n  return segments\n}\n\nfunction flattenSegments (segments: Array<StringSegment>): string {\n  const mergedSegments = []\n  let textBuffer = ''\n\n  const pushBuffer = () => {\n    if (textBuffer) {\n      mergedSegments.push(JSON.stringify(textBuffer))\n      textBuffer = ''\n    }\n  }\n\n  for (let i = 0; i < segments.length; i++) {\n    const s = segments[i]\n    if (s.type === RAW) {\n      textBuffer += s.value\n    } else if (s.type === INTERPOLATION) {\n      pushBuffer()\n      mergedSegments.push(`_ssrEscape(${s.value})`)\n    } else if (s.type === EXPRESSION) {\n      pushBuffer()\n      mergedSegments.push(`(${s.value})`)\n    }\n  }\n  pushBuffer()\n\n  return mergedSegments.join('+')\n}\n"
  },
  {
    "path": "vue/src/server/optimizing-compiler/index.js",
    "content": "/* @flow */\n\nimport { parse } from 'compiler/parser/index'\nimport { generate } from './codegen'\nimport { optimize } from './optimizer'\nimport { createCompilerCreator } from 'compiler/create-compiler'\n\nexport const createCompiler = createCompilerCreator(function baseCompile (\n  template: string,\n  options: CompilerOptions\n): CompiledResult {\n  const ast = parse(template.trim(), options)\n  optimize(ast, options)\n  const code = generate(ast, options)\n  return {\n    ast,\n    render: code.render,\n    staticRenderFns: code.staticRenderFns\n  }\n})\n"
  },
  {
    "path": "vue/src/server/optimizing-compiler/modules.js",
    "content": "/* @flow */\n\nimport {\n  RAW,\n  // INTERPOLATION,\n  EXPRESSION\n} from './codegen'\n\nimport {\n  propsToAttrMap,\n  isRenderableAttr\n} from 'web/server/util'\n\nimport {\n  isBooleanAttr,\n  isEnumeratedAttr\n} from 'web/util/attrs'\n\nimport type { StringSegment } from './codegen'\nimport type { CodegenState } from 'compiler/codegen/index'\n\ntype Attr = { name: string; value: string };\n\nconst plainStringRE = /^\"(?:[^\"\\\\]|\\\\.)*\"$|^'(?:[^'\\\\]|\\\\.)*'$/\n\n// let the model AST transform translate v-model into appropriate\n// props bindings\nexport function applyModelTransform (el: ASTElement, state: CodegenState) {\n  if (el.directives) {\n    for (let i = 0; i < el.directives.length; i++) {\n      const dir = el.directives[i]\n      if (dir.name === 'model') {\n        state.directives.model(el, dir, state.warn)\n        // remove value for textarea as its converted to text\n        if (el.tag === 'textarea' && el.props) {\n          el.props = el.props.filter(p => p.name !== 'value')\n        }\n        break\n      }\n    }\n  }\n}\n\nexport function genAttrSegments (\n  attrs: Array<Attr>\n): Array<StringSegment> {\n  return attrs.map(({ name, value }) => genAttrSegment(name, value))\n}\n\nexport function genDOMPropSegments (\n  props: Array<Attr>,\n  attrs: ?Array<Attr>\n): Array<StringSegment> {\n  const segments = []\n  props.forEach(({ name, value }) => {\n    name = propsToAttrMap[name] || name.toLowerCase()\n    if (isRenderableAttr(name) &&\n      !(attrs && attrs.some(a => a.name === name))\n    ) {\n      segments.push(genAttrSegment(name, value))\n    }\n  })\n  return segments\n}\n\nfunction genAttrSegment (name: string, value: string): StringSegment {\n  if (plainStringRE.test(value)) {\n    // force double quote\n    value = value.replace(/^'|'$/g, '\"')\n    // force enumerated attr to \"true\"\n    if (isEnumeratedAttr(name) && value !== `\"false\"`) {\n      value = `\"true\"`\n    }\n    return {\n      type: RAW,\n      value: isBooleanAttr(name)\n        ? ` ${name}=\"${name}\"`\n        : value === '\"\"'\n          ? ` ${name}`\n          : ` ${name}=\"${JSON.parse(value)}\"`\n    }\n  } else {\n    return {\n      type: EXPRESSION,\n      value: `_ssrAttr(${JSON.stringify(name)},${value})`\n    }\n  }\n}\n\nexport function genClassSegments (\n  staticClass: ?string,\n  classBinding: ?string\n): Array<StringSegment> {\n  if (staticClass && !classBinding) {\n    return [{ type: RAW, value: ` class=${staticClass}` }]\n  } else {\n    return [{\n      type: EXPRESSION,\n      value: `_ssrClass(${staticClass || 'null'},${classBinding || 'null'})`\n    }]\n  }\n}\n\nexport function genStyleSegments (\n  staticStyle: ?string,\n  parsedStaticStyle: ?string,\n  styleBinding: ?string,\n  vShowExpression: ?string\n): Array<StringSegment> {\n  if (staticStyle && !styleBinding && !vShowExpression) {\n    return [{ type: RAW, value: ` style=${JSON.stringify(staticStyle)}` }]\n  } else {\n    return [{\n      type: EXPRESSION,\n      value: `_ssrStyle(${\n        parsedStaticStyle || 'null'\n      },${\n        styleBinding || 'null'\n      }, ${\n        vShowExpression\n          ? `{ display: (${vShowExpression}) ? '' : 'none' }`\n          : 'null'\n      })`\n    }]\n  }\n}\n"
  },
  {
    "path": "vue/src/server/optimizing-compiler/optimizer.js",
    "content": "/* @flow */\n\n/**\n * In SSR, the vdom tree is generated only once and never patched, so\n * we can optimize most element / trees into plain string render functions.\n * The SSR optimizer walks the AST tree to detect optimizable elements and trees.\n *\n * The criteria for SSR optimizability is quite a bit looser than static tree\n * detection (which is designed for client re-render). In SSR we bail only for\n * components/slots/custom directives.\n */\n\nimport { no, makeMap, isBuiltInTag } from 'shared/util'\n\n// optimizability constants\nexport const optimizability = {\n  FALSE: 0,    // whole sub tree un-optimizable\n  FULL: 1,     // whole sub tree optimizable\n  SELF: 2,     // self optimizable but has some un-optimizable children\n  CHILDREN: 3, // self un-optimizable but have fully optimizable children\n  PARTIAL: 4   // self un-optimizable with some un-optimizable children\n}\n\nlet isPlatformReservedTag\n\nexport function optimize (root: ?ASTElement, options: CompilerOptions) {\n  if (!root) return\n  isPlatformReservedTag = options.isReservedTag || no\n  walk(root, true)\n}\n\nfunction walk (node: ASTNode, isRoot?: boolean) {\n  if (isUnOptimizableTree(node)) {\n    node.ssrOptimizability = optimizability.FALSE\n    return\n  }\n  // root node or nodes with custom directives should always be a VNode\n  const selfUnoptimizable = isRoot || hasCustomDirective(node)\n  const check = child => {\n    if (child.ssrOptimizability !== optimizability.FULL) {\n      node.ssrOptimizability = selfUnoptimizable\n        ? optimizability.PARTIAL\n        : optimizability.SELF\n    }\n  }\n  if (selfUnoptimizable) {\n    node.ssrOptimizability = optimizability.CHILDREN\n  }\n  if (node.type === 1) {\n    for (let i = 0, l = node.children.length; i < l; i++) {\n      const child = node.children[i]\n      walk(child)\n      check(child)\n    }\n    if (node.ifConditions) {\n      for (let i = 1, l = node.ifConditions.length; i < l; i++) {\n        const block = node.ifConditions[i].block\n        walk(block, isRoot)\n        check(block)\n      }\n    }\n    if (node.ssrOptimizability == null ||\n      (!isRoot && (node.attrsMap['v-html'] || node.attrsMap['v-text']))\n    ) {\n      node.ssrOptimizability = optimizability.FULL\n    } else {\n      node.children = optimizeSiblings(node)\n    }\n  } else {\n    node.ssrOptimizability = optimizability.FULL\n  }\n}\n\nfunction optimizeSiblings (el) {\n  const children = el.children\n  const optimizedChildren = []\n\n  let currentOptimizableGroup = []\n  const pushGroup = () => {\n    if (currentOptimizableGroup.length) {\n      optimizedChildren.push({\n        type: 1,\n        parent: el,\n        tag: 'template',\n        attrsList: [],\n        attrsMap: {},\n        children: currentOptimizableGroup,\n        ssrOptimizability: optimizability.FULL\n      })\n    }\n    currentOptimizableGroup = []\n  }\n\n  for (let i = 0; i < children.length; i++) {\n    const c = children[i]\n    if (c.ssrOptimizability === optimizability.FULL) {\n      currentOptimizableGroup.push(c)\n    } else {\n      // wrap fully-optimizable adjacent siblings inside a template tag\n      // so that they can be optimized into a single ssrNode by codegen\n      pushGroup()\n      optimizedChildren.push(c)\n    }\n  }\n  pushGroup()\n  return optimizedChildren\n}\n\nfunction isUnOptimizableTree (node: ASTNode): boolean {\n  if (node.type === 2 || node.type === 3) { // text or expression\n    return false\n  }\n  return (\n    isBuiltInTag(node.tag) || // built-in (slot, component)\n    !isPlatformReservedTag(node.tag) || // custom component\n    !!node.component || // \"is\" component\n    isSelectWithModel(node) // <select v-model> requires runtime inspection\n  )\n}\n\nconst isBuiltInDir = makeMap('text,html,show,on,bind,model,pre,cloak,once')\n\nfunction hasCustomDirective (node: ASTNode): ?boolean {\n  return (\n    node.type === 1 &&\n    node.directives &&\n    node.directives.some(d => !isBuiltInDir(d.name))\n  )\n}\n\n// <select v-model> cannot be optimized because it requires a runtime check\n// to determine proper selected option\nfunction isSelectWithModel (node: ASTNode): boolean {\n  return (\n    node.type === 1 &&\n    node.tag === 'select' &&\n    node.directives != null &&\n    node.directives.some(d => d.name === 'model')\n  )\n}\n"
  },
  {
    "path": "vue/src/server/optimizing-compiler/runtime-helpers.js",
    "content": "/* @flow */\n\nimport { escape } from 'web/server/util'\nimport { isObject, extend } from 'shared/util'\nimport { renderAttr } from 'web/server/modules/attrs'\nimport { renderClass } from 'web/util/class'\nimport { genStyle } from 'web/server/modules/style'\nimport { normalizeStyleBinding } from 'web/util/style'\n\nimport {\n  normalizeChildren,\n  simpleNormalizeChildren\n} from 'core/vdom/helpers/normalize-children'\n\nimport {\n  propsToAttrMap,\n  isRenderableAttr\n} from 'web/server/util'\n\nconst ssrHelpers = {\n  _ssrEscape: escape,\n  _ssrNode: renderStringNode,\n  _ssrList: renderStringList,\n  _ssrAttr: renderAttr,\n  _ssrAttrs: renderAttrs,\n  _ssrDOMProps: renderDOMProps,\n  _ssrClass: renderSSRClass,\n  _ssrStyle: renderSSRStyle\n}\n\nexport function installSSRHelpers (vm: Component) {\n  if (vm._ssrNode) {\n    return\n  }\n  let Vue = vm.constructor\n  while (Vue.super) {\n    Vue = Vue.super\n  }\n  extend(Vue.prototype, ssrHelpers)\n  if (Vue.FunctionalRenderContext) {\n    extend(Vue.FunctionalRenderContext.prototype, ssrHelpers)\n  }\n}\n\nclass StringNode {\n  isString: boolean;\n  open: string;\n  close: ?string;\n  children: ?Array<any>;\n\n  constructor (\n    open: string,\n    close?: string,\n    children?: Array<any>,\n    normalizationType?: number\n  ) {\n    this.isString = true\n    this.open = open\n    this.close = close\n    if (children) {\n      this.children = normalizationType === 1\n        ? simpleNormalizeChildren(children)\n        : normalizationType === 2\n          ? normalizeChildren(children)\n          : children\n    } else {\n      this.children = void 0\n    }\n  }\n}\n\nfunction renderStringNode (\n  open: string,\n  close?: string,\n  children?: Array<any>,\n  normalizationType?: number\n): StringNode {\n  return new StringNode(open, close, children, normalizationType)\n}\n\nfunction renderStringList (\n  val: any,\n  render: (\n    val: any,\n    keyOrIndex: string | number,\n    index?: number\n  ) => string\n): string {\n  let ret = ''\n  let i, l, keys, key\n  if (Array.isArray(val) || typeof val === 'string') {\n    for (i = 0, l = val.length; i < l; i++) {\n      ret += render(val[i], i)\n    }\n  } else if (typeof val === 'number') {\n    for (i = 0; i < val; i++) {\n      ret += render(i + 1, i)\n    }\n  } else if (isObject(val)) {\n    keys = Object.keys(val)\n    for (i = 0, l = keys.length; i < l; i++) {\n      key = keys[i]\n      ret += render(val[key], key, i)\n    }\n  }\n  return ret\n}\n\nfunction renderAttrs (obj: Object): string {\n  let res = ''\n  for (const key in obj) {\n    res += renderAttr(key, obj[key])\n  }\n  return res\n}\n\nfunction renderDOMProps (obj: Object): string {\n  let res = ''\n  for (const key in obj) {\n    const attr = propsToAttrMap[key] || key.toLowerCase()\n    if (isRenderableAttr(attr)) {\n      res += renderAttr(attr, obj[key])\n    }\n  }\n  return res\n}\n\nfunction renderSSRClass (\n  staticClass: ?string,\n  dynamic: any\n): string {\n  const res = renderClass(staticClass, dynamic)\n  return res === '' ? res : ` class=\"${escape(res)}\"`\n}\n\nfunction renderSSRStyle (\n  staticStyle: ?Object,\n  dynamic: any,\n  extra: ?Object\n): string {\n  const style = {}\n  if (staticStyle) extend(style, staticStyle)\n  if (dynamic) extend(style, normalizeStyleBinding(dynamic))\n  if (extra) extend(style, extra)\n  const res = genStyle(style)\n  return res === '' ? res : ` style=${JSON.stringify(escape(res))}`\n}\n"
  },
  {
    "path": "vue/src/server/render-context.js",
    "content": "/* @flow */\n\nimport { isUndef } from 'shared/util'\n\ntype RenderState = {\n  type: 'Element';\n  rendered: number;\n  total: number;\n  children: Array<VNode>;\n  endTag: string;\n} | {\n  type: 'Fragment';\n  rendered: number;\n  total: number;\n  children: Array<VNode>;\n} | {\n  type: 'Component';\n  prevActive: Component;\n} | {\n  type: 'ComponentWithCache';\n  buffer: Array<string>;\n  bufferIndex: number;\n  componentBuffer: Array<Set<Class<Component>>>;\n  key: string;\n};\n\nexport class RenderContext {\n  userContext: ?Object;\n  activeInstance: Component;\n  renderStates: Array<RenderState>;\n  write: (text: string, next: Function) => void;\n  renderNode: (node: VNode, isRoot: boolean, context: RenderContext) => void;\n  next: () => void;\n  done: (err: ?Error) => void;\n\n  modules: Array<(node: VNode) => ?string>;\n  directives: Object;\n  isUnaryTag: (tag: string) => boolean;\n\n  cache: any;\n  get: ?(key: string, cb: Function) => void;\n  has: ?(key: string, cb: Function) => void;\n\n  constructor (options: Object) {\n    this.userContext = options.userContext\n    this.activeInstance = options.activeInstance\n    this.renderStates = []\n\n    this.write = options.write\n    this.done = options.done\n    this.renderNode = options.renderNode\n\n    this.isUnaryTag = options.isUnaryTag\n    this.modules = options.modules\n    this.directives = options.directives\n\n    const cache = options.cache\n    if (cache && (!cache.get || !cache.set)) {\n      throw new Error('renderer cache must implement at least get & set.')\n    }\n    this.cache = cache\n    this.get = cache && normalizeAsync(cache, 'get')\n    this.has = cache && normalizeAsync(cache, 'has')\n\n    this.next = this.next.bind(this)\n  }\n\n  next () {\n    const lastState = this.renderStates[this.renderStates.length - 1]\n    if (isUndef(lastState)) {\n      return this.done()\n    }\n    switch (lastState.type) {\n      case 'Element':\n      case 'Fragment':\n        const { children, total } = lastState\n        const rendered = lastState.rendered++\n        if (rendered < total) {\n          this.renderNode(children[rendered], false, this)\n        } else {\n          this.renderStates.pop()\n          if (lastState.type === 'Element') {\n            this.write(lastState.endTag, this.next)\n          } else {\n            this.next()\n          }\n        }\n        break\n      case 'Component':\n        this.renderStates.pop()\n        this.activeInstance = lastState.prevActive\n        this.next()\n        break\n      case 'ComponentWithCache':\n        this.renderStates.pop()\n        const { buffer, bufferIndex, componentBuffer, key } = lastState\n        const result = {\n          html: buffer[bufferIndex],\n          components: componentBuffer[bufferIndex]\n        }\n        this.cache.set(key, result)\n        if (bufferIndex === 0) {\n          // this is a top-level cached component,\n          // exit caching mode.\n          this.write.caching = false\n        } else {\n          // parent component is also being cached,\n          // merge self into parent's result\n          buffer[bufferIndex - 1] += result.html\n          const prev = componentBuffer[bufferIndex - 1]\n          result.components.forEach(c => prev.add(c))\n        }\n        buffer.length = bufferIndex\n        componentBuffer.length = bufferIndex\n        this.next()\n        break\n    }\n  }\n}\n\nfunction normalizeAsync (cache, method) {\n  const fn = cache[method]\n  if (isUndef(fn)) {\n    return\n  } else if (fn.length > 1) {\n    return (key, cb) => fn.call(cache, key, cb)\n  } else {\n    return (key, cb) => cb(fn.call(cache, key))\n  }\n}\n"
  },
  {
    "path": "vue/src/server/render-stream.js",
    "content": "/* @flow */\n\n/**\n * Original RenderStream implementation by Sasha Aickin (@aickin)\n * Licensed under the Apache License, Version 2.0\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Modified by Evan You (@yyx990803)\n */\n\nconst stream = require('stream')\n\nimport { isTrue, isUndef } from 'shared/util'\nimport { createWriteFunction } from './write'\n\nexport default class RenderStream extends stream.Readable {\n  buffer: string;\n  render: (write: Function, done: Function) => void;\n  expectedSize: number;\n  write: Function;\n  next: Function;\n  end: Function;\n  done: boolean;\n\n  constructor (render: Function) {\n    super()\n    this.buffer = ''\n    this.render = render\n    this.expectedSize = 0\n\n    this.write = createWriteFunction((text, next) => {\n      const n = this.expectedSize\n      this.buffer += text\n      if (this.buffer.length >= n) {\n        this.next = next\n        this.pushBySize(n)\n        return true // we will decide when to call next\n      }\n      return false\n    }, err => {\n      this.emit('error', err)\n    })\n\n    this.end = () => {\n      // the rendering is finished; we should push out the last of the buffer.\n      this.done = true\n      this.push(this.buffer)\n    }\n  }\n\n  pushBySize (n: number) {\n    const bufferToPush = this.buffer.substring(0, n)\n    this.buffer = this.buffer.substring(n)\n    this.push(bufferToPush)\n  }\n\n  tryRender () {\n    try {\n      this.render(this.write, this.end)\n    } catch (e) {\n      this.emit('error', e)\n    }\n  }\n\n  tryNext () {\n    try {\n      this.next()\n    } catch (e) {\n      this.emit('error', e)\n    }\n  }\n\n  _read (n: number) {\n    this.expectedSize = n\n    // it's possible that the last chunk added bumped the buffer up to > 2 * n,\n    // which means we will need to go through multiple read calls to drain it\n    // down to < n.\n    if (isTrue(this.done)) {\n      this.push(null)\n      return\n    }\n    if (this.buffer.length >= n) {\n      this.pushBySize(n)\n      return\n    }\n    if (isUndef(this.next)) {\n      // start the rendering chain.\n      this.tryRender()\n    } else {\n      // continue with the rendering.\n      this.tryNext()\n    }\n  }\n}\n"
  },
  {
    "path": "vue/src/server/render.js",
    "content": "/* @flow */\n\nimport { escape } from 'web/server/util'\nimport { SSR_ATTR } from 'shared/constants'\nimport { RenderContext } from './render-context'\nimport { generateComponentTrace } from 'core/util/debug'\nimport { ssrCompileToFunctions } from 'web/server/compiler'\nimport { installSSRHelpers } from './optimizing-compiler/runtime-helpers'\n\nimport { isDef, isUndef, isTrue } from 'shared/util'\n\nimport {\n  createComponent,\n  createComponentInstanceForVnode\n} from 'core/vdom/create-component'\n\nlet warned = Object.create(null)\nconst warnOnce = msg => {\n  if (!warned[msg]) {\n    warned[msg] = true\n    console.warn(`\\n\\u001b[31m${msg}\\u001b[39m\\n`)\n  }\n}\n\nconst onCompilationError = (err, vm) => {\n  const trace = vm ? generateComponentTrace(vm) : ''\n  throw new Error(`\\n\\u001b[31m${err}${trace}\\u001b[39m\\n`)\n}\n\nconst normalizeRender = vm => {\n  const { render, template, _scopeId } = vm.$options\n  if (isUndef(render)) {\n    if (template) {\n      const compiled = ssrCompileToFunctions(template, {\n        scopeId: _scopeId,\n        warn: onCompilationError\n      }, vm)\n\n      vm.$options.render = compiled.render\n      vm.$options.staticRenderFns = compiled.staticRenderFns\n    } else {\n      throw new Error(\n        `render function or template not defined in component: ${\n          vm.$options.name || vm.$options._componentTag || 'anonymous'\n        }`\n      )\n    }\n  }\n}\n\nfunction renderNode (node, isRoot, context) {\n  if (node.isString) {\n    renderStringNode(node, context)\n  } else if (isDef(node.componentOptions)) {\n    renderComponent(node, isRoot, context)\n  } else if (isDef(node.tag)) {\n    renderElement(node, isRoot, context)\n  } else if (isTrue(node.isComment)) {\n    if (isDef(node.asyncFactory)) {\n      // async component\n      renderAsyncComponent(node, isRoot, context)\n    } else {\n      context.write(`<!--${node.text}-->`, context.next)\n    }\n  } else {\n    context.write(\n      node.raw ? node.text : escape(String(node.text)),\n      context.next\n    )\n  }\n}\n\nfunction registerComponentForCache (options, write) {\n  // exposed by vue-loader, need to call this if cache hit because\n  // component lifecycle hooks will not be called.\n  const register = options._ssrRegister\n  if (write.caching && isDef(register)) {\n    write.componentBuffer[write.componentBuffer.length - 1].add(register)\n  }\n  return register\n}\n\nfunction renderComponent (node, isRoot, context) {\n  const { write, next, userContext } = context\n\n  // check cache hit\n  const Ctor = node.componentOptions.Ctor\n  const getKey = Ctor.options.serverCacheKey\n  const name = Ctor.options.name\n  const cache = context.cache\n  const registerComponent = registerComponentForCache(Ctor.options, write)\n\n  if (isDef(getKey) && isDef(cache) && isDef(name)) {\n    const key = name + '::' + getKey(node.componentOptions.propsData)\n    const { has, get } = context\n    if (isDef(has)) {\n      has(key, hit => {\n        if (hit === true && isDef(get)) {\n          get(key, res => {\n            if (isDef(registerComponent)) {\n              registerComponent(userContext)\n            }\n            res.components.forEach(register => register(userContext))\n            write(res.html, next)\n          })\n        } else {\n          renderComponentWithCache(node, isRoot, key, context)\n        }\n      })\n    } else if (isDef(get)) {\n      get(key, res => {\n        if (isDef(res)) {\n          if (isDef(registerComponent)) {\n            registerComponent(userContext)\n          }\n          res.components.forEach(register => register(userContext))\n          write(res.html, next)\n        } else {\n          renderComponentWithCache(node, isRoot, key, context)\n        }\n      })\n    }\n  } else {\n    if (isDef(getKey) && isUndef(cache)) {\n      warnOnce(\n        `[vue-server-renderer] Component ${\n          Ctor.options.name || '(anonymous)'\n        } implemented serverCacheKey, ` +\n        'but no cache was provided to the renderer.'\n      )\n    }\n    if (isDef(getKey) && isUndef(name)) {\n      warnOnce(\n        `[vue-server-renderer] Components that implement \"serverCacheKey\" ` +\n        `must also define a unique \"name\" option.`\n      )\n    }\n    renderComponentInner(node, isRoot, context)\n  }\n}\n\nfunction renderComponentWithCache (node, isRoot, key, context) {\n  const write = context.write\n  write.caching = true\n  const buffer = write.cacheBuffer\n  const bufferIndex = buffer.push('') - 1\n  const componentBuffer = write.componentBuffer\n  componentBuffer.push(new Set())\n  context.renderStates.push({\n    type: 'ComponentWithCache',\n    key,\n    buffer,\n    bufferIndex,\n    componentBuffer\n  })\n  renderComponentInner(node, isRoot, context)\n}\n\nfunction renderComponentInner (node, isRoot, context) {\n  const prevActive = context.activeInstance\n  // expose userContext on vnode\n  node.ssrContext = context.userContext\n  const child = context.activeInstance = createComponentInstanceForVnode(\n    node,\n    context.activeInstance\n  )\n  normalizeRender(child)\n  const childNode = child._render()\n  childNode.parent = node\n  context.renderStates.push({\n    type: 'Component',\n    prevActive\n  })\n  renderNode(childNode, isRoot, context)\n}\n\nfunction renderAsyncComponent (node, isRoot, context) {\n  const factory = node.asyncFactory\n\n  const resolve = comp => {\n    if (comp.__esModule && comp.default) {\n      comp = comp.default\n    }\n    const { data, children, tag } = node.asyncMeta\n    const nodeContext = node.asyncMeta.context\n    const resolvedNode: any = createComponent(\n      comp,\n      data,\n      nodeContext,\n      children,\n      tag\n    )\n    if (resolvedNode) {\n      if (resolvedNode.componentOptions) {\n        // normal component\n        renderComponent(resolvedNode, isRoot, context)\n      } else if (!Array.isArray(resolvedNode)) {\n        // single return node from functional component\n        renderNode(resolvedNode, isRoot, context)\n      } else {\n        // multiple return nodes from functional component\n        context.renderStates.push({\n          type: 'Fragment',\n          children: resolvedNode,\n          rendered: 0,\n          total: resolvedNode.length\n        })\n        context.next()\n      }\n    } else {\n      // invalid component, but this does not throw on the client\n      // so render empty comment node\n      context.write(`<!---->`, context.next)\n    }\n  }\n\n  if (factory.resolved) {\n    resolve(factory.resolved)\n    return\n  }\n\n  const reject = context.done\n  let res\n  try {\n    res = factory(resolve, reject)\n  } catch (e) {\n    reject(e)\n  }\n  if (res) {\n    if (typeof res.then === 'function') {\n      res.then(resolve, reject).catch(reject)\n    } else {\n      // new syntax in 2.3\n      const comp = res.component\n      if (comp && typeof comp.then === 'function') {\n        comp.then(resolve, reject).catch(reject)\n      }\n    }\n  }\n}\n\nfunction renderStringNode (el, context) {\n  const { write, next } = context\n  if (isUndef(el.children) || el.children.length === 0) {\n    write(el.open + (el.close || ''), next)\n  } else {\n    const children: Array<VNode> = el.children\n    context.renderStates.push({\n      type: 'Element',\n      children,\n      rendered: 0,\n      total: children.length,\n      endTag: el.close\n    })\n    write(el.open, next)\n  }\n}\n\nfunction renderElement (el, isRoot, context) {\n  const { write, next } = context\n\n  if (isTrue(isRoot)) {\n    if (!el.data) el.data = {}\n    if (!el.data.attrs) el.data.attrs = {}\n    el.data.attrs[SSR_ATTR] = 'true'\n  }\n\n  if (el.fnOptions) {\n    registerComponentForCache(el.fnOptions, write)\n  }\n\n  const startTag = renderStartingTag(el, context)\n  const endTag = `</${el.tag}>`\n  if (context.isUnaryTag(el.tag)) {\n    write(startTag, next)\n  } else if (isUndef(el.children) || el.children.length === 0) {\n    write(startTag + endTag, next)\n  } else {\n    const children: Array<VNode> = el.children\n    context.renderStates.push({\n      type: 'Element',\n      children,\n      rendered: 0,\n      total: children.length,\n      endTag\n    })\n    write(startTag, next)\n  }\n}\n\nfunction hasAncestorData (node: VNode) {\n  const parentNode = node.parent\n  return isDef(parentNode) && (isDef(parentNode.data) || hasAncestorData(parentNode))\n}\n\nfunction getVShowDirectiveInfo (node: VNode): ?VNodeDirective {\n  let dir: VNodeDirective\n  let tmp\n\n  while (isDef(node)) {\n    if (node.data && node.data.directives) {\n      tmp = node.data.directives.find(dir => dir.name === 'show')\n      if (tmp) {\n        dir = tmp\n      }\n    }\n    node = node.parent\n  }\n  return dir\n}\n\nfunction renderStartingTag (node: VNode, context) {\n  let markup = `<${node.tag}`\n  const { directives, modules } = context\n\n  // construct synthetic data for module processing\n  // because modules like style also produce code by parent VNode data\n  if (isUndef(node.data) && hasAncestorData(node)) {\n    node.data = {}\n  }\n  if (isDef(node.data)) {\n    // check directives\n    const dirs = node.data.directives\n    if (dirs) {\n      for (let i = 0; i < dirs.length; i++) {\n        const name = dirs[i].name\n        const dirRenderer = directives[name]\n        if (dirRenderer && name !== 'show') {\n          // directives mutate the node's data\n          // which then gets rendered by modules\n          dirRenderer(node, dirs[i])\n        }\n      }\n    }\n\n    // v-show directive needs to be merged from parent to child\n    const vshowDirectiveInfo = getVShowDirectiveInfo(node)\n    if (vshowDirectiveInfo) {\n      directives.show(node, vshowDirectiveInfo)\n    }\n\n    // apply other modules\n    for (let i = 0; i < modules.length; i++) {\n      const res = modules[i](node)\n      if (res) {\n        markup += res\n      }\n    }\n  }\n  // attach scoped CSS ID\n  let scopeId\n  const activeInstance = context.activeInstance\n  if (isDef(activeInstance) &&\n    activeInstance !== node.context &&\n    isDef(scopeId = activeInstance.$options._scopeId)\n  ) {\n    markup += ` ${(scopeId: any)}`\n  }\n  if (isDef(node.fnScopeId)) {\n    markup += ` ${node.fnScopeId}`\n  } else {\n    while (isDef(node)) {\n      if (isDef(scopeId = node.context.$options._scopeId)) {\n        markup += ` ${scopeId}`\n      }\n      node = node.parent\n    }\n  }\n  return markup + '>'\n}\n\nexport function createRenderFunction (\n  modules: Array<(node: VNode) => ?string>,\n  directives: Object,\n  isUnaryTag: Function,\n  cache: any\n) {\n  return function render (\n    component: Component,\n    write: (text: string, next: Function) => void,\n    userContext: ?Object,\n    done: Function\n  ) {\n    warned = Object.create(null)\n    const context = new RenderContext({\n      activeInstance: component,\n      userContext,\n      write, done, renderNode,\n      isUnaryTag, modules, directives,\n      cache\n    })\n    installSSRHelpers(component)\n    normalizeRender(component)\n    renderNode(component._render(), true, context)\n  }\n}\n"
  },
  {
    "path": "vue/src/server/template-renderer/create-async-file-mapper.js",
    "content": "/* @flow */\n\n/**\n * Creates a mapper that maps components used during a server-side render\n * to async chunk files in the client-side build, so that we can inline them\n * directly in the rendered HTML to avoid waterfall requests.\n */\n\nimport type { ClientManifest } from './index'\n\nexport type AsyncFileMapper = (files: Array<string>) => Array<string>;\n\nexport function createMapper (\n  clientManifest: ClientManifest\n): AsyncFileMapper {\n  const map = createMap(clientManifest)\n  // map server-side moduleIds to client-side files\n  return function mapper (moduleIds: Array<string>): Array<string> {\n    const res = new Set()\n    for (let i = 0; i < moduleIds.length; i++) {\n      const mapped = map.get(moduleIds[i])\n      if (mapped) {\n        for (let j = 0; j < mapped.length; j++) {\n          res.add(mapped[j])\n        }\n      }\n    }\n    return Array.from(res)\n  }\n}\n\nfunction createMap (clientManifest) {\n  const map = new Map()\n  Object.keys(clientManifest.modules).forEach(id => {\n    map.set(id, mapIdToFile(id, clientManifest))\n  })\n  return map\n}\n\nfunction mapIdToFile (id, clientManifest) {\n  const files = []\n  const fileIndices = clientManifest.modules[id]\n  if (fileIndices) {\n    fileIndices.forEach(index => {\n      const file = clientManifest.all[index]\n      // only include async files or non-js assets\n      if (clientManifest.async.indexOf(file) > -1 || !(/\\.js($|\\?)/.test(file))) {\n        files.push(file)\n      }\n    })\n  }\n  return files\n}\n"
  },
  {
    "path": "vue/src/server/template-renderer/index.js",
    "content": "/* @flow */\n\nconst path = require('path')\nconst serialize = require('serialize-javascript')\n\nimport { isJS, isCSS } from '../util'\nimport TemplateStream from './template-stream'\nimport { parseTemplate } from './parse-template'\nimport { createMapper } from './create-async-file-mapper'\nimport type { ParsedTemplate } from './parse-template'\nimport type { AsyncFileMapper } from './create-async-file-mapper'\n\ntype TemplateRendererOptions = {\n  template: ?string;\n  inject?: boolean;\n  clientManifest?: ClientManifest;\n  shouldPreload?: (file: string, type: string) => boolean;\n  shouldPrefetch?: (file: string, type: string) => boolean;\n};\n\nexport type ClientManifest = {\n  publicPath: string;\n  all: Array<string>;\n  initial: Array<string>;\n  async: Array<string>;\n  modules: {\n    [id: string]: Array<number>;\n  },\n  hasNoCssVersion?: {\n    [file: string]: boolean;\n  }\n};\n\ntype Resource = {\n  file: string;\n  extension: string;\n  fileWithoutQuery: string;\n  asType: string;\n};\n\nexport default class TemplateRenderer {\n  options: TemplateRendererOptions;\n  inject: boolean;\n  parsedTemplate: ParsedTemplate | null;\n  publicPath: string;\n  clientManifest: ClientManifest;\n  preloadFiles: Array<Resource>;\n  prefetchFiles: Array<Resource>;\n  mapFiles: AsyncFileMapper;\n\n  constructor (options: TemplateRendererOptions) {\n    this.options = options\n    this.inject = options.inject !== false\n    // if no template option is provided, the renderer is created\n    // as a utility object for rendering assets like preload links and scripts.\n    this.parsedTemplate = options.template\n      ? parseTemplate(options.template)\n      : null\n\n    // extra functionality with client manifest\n    if (options.clientManifest) {\n      const clientManifest = this.clientManifest = options.clientManifest\n      this.publicPath = clientManifest.publicPath.replace(/\\/$/, '')\n      // preload/prefetch directives\n      this.preloadFiles = (clientManifest.initial || []).map(normalizeFile)\n      this.prefetchFiles = (clientManifest.async || []).map(normalizeFile)\n      // initial async chunk mapping\n      this.mapFiles = createMapper(clientManifest)\n    }\n  }\n\n  bindRenderFns (context: Object) {\n    const renderer: any = this\n    ;['ResourceHints', 'State', 'Scripts', 'Styles'].forEach(type => {\n      context[`render${type}`] = renderer[`render${type}`].bind(renderer, context)\n    })\n    // also expose getPreloadFiles, useful for HTTP/2 push\n    context.getPreloadFiles = renderer.getPreloadFiles.bind(renderer, context)\n  }\n\n  // render synchronously given rendered app content and render context\n  renderSync (content: string, context: ?Object) {\n    const template = this.parsedTemplate\n    if (!template) {\n      throw new Error('renderSync cannot be called without a template.')\n    }\n    context = context || {}\n    if (this.inject) {\n      return (\n        template.head(context) +\n        (context.head || '') +\n        this.renderResourceHints(context) +\n        this.renderStyles(context) +\n        template.neck(context) +\n        content +\n        this.renderState(context) +\n        this.renderScripts(context) +\n        template.tail(context)\n      )\n    } else {\n      return (\n        template.head(context) +\n        template.neck(context) +\n        content +\n        template.tail(context)\n      )\n    }\n  }\n\n  renderStyles (context: Object): string {\n    const cssFiles = this.clientManifest\n      ? this.clientManifest.all.filter(isCSS)\n      : []\n    return (\n      // render links for css files\n      (cssFiles.length\n        ? cssFiles.map(file => `<link rel=\"stylesheet\" href=\"${this.publicPath}/${file}\">`).join('')\n        : '') +\n      // context.styles is a getter exposed by vue-style-loader which contains\n      // the inline component styles collected during SSR\n      (context.styles || '')\n    )\n  }\n\n  renderResourceHints (context: Object): string {\n    return this.renderPreloadLinks(context) + this.renderPrefetchLinks(context)\n  }\n\n  getPreloadFiles (context: Object): Array<Resource> {\n    const usedAsyncFiles = this.getUsedAsyncFiles(context)\n    if (this.preloadFiles || usedAsyncFiles) {\n      return (this.preloadFiles || []).concat(usedAsyncFiles || [])\n    } else {\n      return []\n    }\n  }\n\n  renderPreloadLinks (context: Object): string {\n    const files = this.getPreloadFiles(context)\n    const shouldPreload = this.options.shouldPreload\n    if (files.length) {\n      return files.map(({ file, extension, fileWithoutQuery, asType }) => {\n        let extra = ''\n        // by default, we only preload scripts or css\n        if (!shouldPreload && asType !== 'script' && asType !== 'style') {\n          return ''\n        }\n        // user wants to explicitly control what to preload\n        if (shouldPreload && !shouldPreload(fileWithoutQuery, asType)) {\n          return ''\n        }\n        if (asType === 'font') {\n          extra = ` type=\"font/${extension}\" crossorigin`\n        }\n        return `<link rel=\"preload\" href=\"${\n          this.publicPath}/${file\n        }\"${\n          asType !== '' ? ` as=\"${asType}\"` : ''\n        }${\n          extra\n        }>`\n      }).join('')\n    } else {\n      return ''\n    }\n  }\n\n  renderPrefetchLinks (context: Object): string {\n    const shouldPrefetch = this.options.shouldPrefetch\n    if (this.prefetchFiles) {\n      const usedAsyncFiles = this.getUsedAsyncFiles(context)\n      const alreadyRendered = file => {\n        return usedAsyncFiles && usedAsyncFiles.some(f => f.file === file)\n      }\n      return this.prefetchFiles.map(({ file, fileWithoutQuery, asType }) => {\n        if (shouldPrefetch && !shouldPrefetch(fileWithoutQuery, asType)) {\n          return ''\n        }\n        if (alreadyRendered(file)) {\n          return ''\n        }\n        return `<link rel=\"prefetch\" href=\"${this.publicPath}/${file}\">`\n      }).join('')\n    } else {\n      return ''\n    }\n  }\n\n  renderState (context: Object, options?: Object): string {\n    const {\n      contextKey = 'state',\n      windowKey = '__INITIAL_STATE__'\n    } = options || {}\n    const state = serialize(context[contextKey], { isJSON: true })\n    const autoRemove = process.env.NODE_ENV === 'production'\n      ? ';(function(){var s;(s=document.currentScript||document.scripts[document.scripts.length-1]).parentNode.removeChild(s);}());'\n      : ''\n    return context[contextKey]\n      ? `<script>window.${windowKey}=${state}${autoRemove}</script>`\n      : ''\n  }\n\n  renderScripts (context: Object): string {\n    if (this.clientManifest) {\n      const initial = this.preloadFiles\n      const async = this.getUsedAsyncFiles(context)\n      const needed = [initial[0]].concat(async || [], initial.slice(1))\n      return needed.filter(({ file }) => isJS(file)).map(({ file }) => {\n        return `<script src=\"${this.publicPath}/${file}\" defer></script>`\n      }).join('')\n    } else {\n      return ''\n    }\n  }\n\n  getUsedAsyncFiles (context: Object): ?Array<Resource> {\n    if (!context._mappedFiles && context._registeredComponents && this.mapFiles) {\n      const registered = Array.from(context._registeredComponents)\n      context._mappedFiles = this.mapFiles(registered).map(normalizeFile)\n    }\n    return context._mappedFiles\n  }\n\n  // create a transform stream\n  createStream (context: ?Object): TemplateStream {\n    if (!this.parsedTemplate) {\n      throw new Error('createStream cannot be called without a template.')\n    }\n    return new TemplateStream(this, this.parsedTemplate, context || {})\n  }\n}\n\nfunction normalizeFile (file: string): Resource {\n  const withoutQuery = file.replace(/\\?.*/, '')\n  const extension = path.extname(withoutQuery).slice(1)\n  return {\n    file,\n    extension,\n    fileWithoutQuery: withoutQuery,\n    asType: getPreloadType(extension)\n  }\n}\n\nfunction getPreloadType (ext: string): string {\n  if (ext === 'js') {\n    return 'script'\n  } else if (ext === 'css') {\n    return 'style'\n  } else if (/jpe?g|png|svg|gif|webp|ico/.test(ext)) {\n    return 'image'\n  } else if (/woff2?|ttf|otf|eot/.test(ext)) {\n    return 'font'\n  } else {\n    // not exhausting all possibilities here, but above covers common cases\n    return ''\n  }\n}\n"
  },
  {
    "path": "vue/src/server/template-renderer/parse-template.js",
    "content": "/* @flow */\n\nconst compile = require('lodash.template')\nconst compileOptions = {\n  escape: /{{([^{][\\s\\S]+?[^}])}}/g,\n  interpolate: /{{{([\\s\\S]+?)}}}/g\n}\n\nexport type ParsedTemplate = {\n  head: (data: any) => string;\n  neck: (data: any) => string;\n  tail: (data: any) => string;\n};\n\nexport function parseTemplate (\n  template: string,\n  contentPlaceholder?: string = '<!--vue-ssr-outlet-->'\n): ParsedTemplate {\n  if (typeof template === 'object') {\n    return template\n  }\n\n  let i = template.indexOf('</head>')\n  const j = template.indexOf(contentPlaceholder)\n\n  if (j < 0) {\n    throw new Error(`Content placeholder not found in template.`)\n  }\n\n  if (i < 0) {\n    i = template.indexOf('<body>')\n    if (i < 0) {\n      i = j\n    }\n  }\n\n  return {\n    head: compile(template.slice(0, i), compileOptions),\n    neck: compile(template.slice(i, j), compileOptions),\n    tail: compile(template.slice(j + contentPlaceholder.length), compileOptions)\n  }\n}\n"
  },
  {
    "path": "vue/src/server/template-renderer/template-stream.js",
    "content": "/* @flow */\n\nconst Transform = require('stream').Transform\nimport type TemplateRenderer from './index'\nimport type { ParsedTemplate } from './parse-template'\n\nexport default class TemplateStream extends Transform {\n  started: boolean;\n  renderer: TemplateRenderer;\n  template: ParsedTemplate;\n  context: Object;\n  inject: boolean;\n\n  constructor (\n    renderer: TemplateRenderer,\n    template: ParsedTemplate,\n    context: Object\n  ) {\n    super()\n    this.started = false\n    this.renderer = renderer\n    this.template = template\n    this.context = context || {}\n    this.inject = renderer.inject\n  }\n\n  _transform (data: Buffer | string, encoding: string, done: Function) {\n    if (!this.started) {\n      this.emit('beforeStart')\n      this.start()\n    }\n    this.push(data)\n    done()\n  }\n\n  start () {\n    this.started = true\n    this.push(this.template.head(this.context))\n\n    if (this.inject) {\n      // inline server-rendered head meta information\n      if (this.context.head) {\n        this.push(this.context.head)\n      }\n\n      // inline preload/prefetch directives for initial/async chunks\n      const links = this.renderer.renderResourceHints(this.context)\n      if (links) {\n        this.push(links)\n      }\n\n      // CSS files and inline server-rendered CSS collected by vue-style-loader\n      const styles = this.renderer.renderStyles(this.context)\n      if (styles) {\n        this.push(styles)\n      }\n    }\n\n    this.push(this.template.neck(this.context))\n  }\n\n  _flush (done: Function) {\n    this.emit('beforeEnd')\n\n    if (this.inject) {\n      // inline initial store state\n      const state = this.renderer.renderState(this.context)\n      if (state) {\n        this.push(state)\n      }\n\n      // embed scripts needed\n      const scripts = this.renderer.renderScripts(this.context)\n      if (scripts) {\n        this.push(scripts)\n      }\n    }\n\n    this.push(this.template.tail(this.context))\n    done()\n  }\n}\n"
  },
  {
    "path": "vue/src/server/util.js",
    "content": "/* @flow */\n\nexport const isJS = (file: string): boolean => /\\.js(\\?[^.]+)?$/.test(file)\n\nexport const isCSS = (file: string): boolean => /\\.css(\\?[^.]+)?$/.test(file)\n\nexport function createPromiseCallback () {\n  let resolve, reject\n  const promise: Promise<string> = new Promise((_resolve, _reject) => {\n    resolve = _resolve\n    reject = _reject\n  })\n  const cb = (err: Error, res?: string) => {\n    if (err) return reject(err)\n    resolve(res || '')\n  }\n  return { promise, cb }\n}\n"
  },
  {
    "path": "vue/src/server/webpack-plugin/client.js",
    "content": "const hash = require('hash-sum')\nconst uniq = require('lodash.uniq')\nimport { isJS } from './util'\n\nexport default class VueSSRClientPlugin {\n  constructor (options = {}) {\n    this.options = Object.assign({\n      filename: 'vue-ssr-client-manifest.json'\n    }, options)\n  }\n\n  apply (compiler) {\n    compiler.plugin('emit', (compilation, cb) => {\n      const stats = compilation.getStats().toJson()\n\n      const allFiles = uniq(stats.assets\n        .map(a => a.name))\n\n      const initialFiles = uniq(Object.keys(stats.entrypoints)\n        .map(name => stats.entrypoints[name].assets)\n        .reduce((assets, all) => all.concat(assets), [])\n        .filter(isJS))\n\n      const asyncFiles = allFiles\n        .filter(isJS)\n        .filter(file => initialFiles.indexOf(file) < 0)\n\n      const manifest = {\n        publicPath: stats.publicPath,\n        all: allFiles,\n        initial: initialFiles,\n        async: asyncFiles,\n        modules: { /* [identifier: string]: Array<index: number> */ }\n      }\n\n      const assetModules = stats.modules.filter(m => m.assets.length)\n      const fileToIndex = file => manifest.all.indexOf(file)\n      stats.modules.forEach(m => {\n        // ignore modules duplicated in multiple chunks\n        if (m.chunks.length === 1) {\n          const cid = m.chunks[0]\n          const chunk = stats.chunks.find(c => c.id === cid)\n          if (!chunk || !chunk.files) {\n            return\n          }\n          const id = m.identifier.replace(/\\s\\w+$/, '') // remove appended hash\n          const files = manifest.modules[hash(id)] = chunk.files.map(fileToIndex)\n          // find all asset modules associated with the same chunk\n          assetModules.forEach(m => {\n            if (m.chunks.some(id => id === cid)) {\n              files.push.apply(files, m.assets.map(fileToIndex))\n            }\n          })\n        }\n      })\n\n      // const debug = (file, obj) => {\n      //   require('fs').writeFileSync(__dirname + '/' + file, JSON.stringify(obj, null, 2))\n      // }\n      // debug('stats.json', stats)\n      // debug('client-manifest.json', manifest)\n\n      const json = JSON.stringify(manifest, null, 2)\n      compilation.assets[this.options.filename] = {\n        source: () => json,\n        size: () => json.length\n      }\n      cb()\n    })\n  }\n}\n"
  },
  {
    "path": "vue/src/server/webpack-plugin/server.js",
    "content": "import { validate, isJS } from './util'\n\nexport default class VueSSRServerPlugin {\n  constructor (options = {}) {\n    this.options = Object.assign({\n      filename: 'vue-ssr-server-bundle.json'\n    }, options)\n  }\n\n  apply (compiler) {\n    validate(compiler)\n\n    compiler.plugin('emit', (compilation, cb) => {\n      const stats = compilation.getStats().toJson()\n      const entryName = Object.keys(stats.entrypoints)[0]\n      const entryInfo = stats.entrypoints[entryName]\n\n      if (!entryInfo) {\n        // #5553\n        return cb()\n      }\n\n      const entryAssets = entryInfo.assets.filter(isJS)\n\n      if (entryAssets.length > 1) {\n        throw new Error(\n          `Server-side bundle should have one single entry file. ` +\n          `Avoid using CommonsChunkPlugin in the server config.`\n        )\n      }\n\n      const entry = entryAssets[0]\n      if (!entry || typeof entry !== 'string') {\n        throw new Error(\n          `Entry \"${entryName}\" not found. Did you specify the correct entry option?`\n        )\n      }\n\n      const bundle = {\n        entry,\n        files: {},\n        maps: {}\n      }\n\n      stats.assets.forEach(asset => {\n        if (asset.name.match(/\\.js$/)) {\n          bundle.files[asset.name] = compilation.assets[asset.name].source()\n        } else if (asset.name.match(/\\.js\\.map$/)) {\n          bundle.maps[asset.name.replace(/\\.map$/, '')] = JSON.parse(compilation.assets[asset.name].source())\n        }\n        // do not emit anything else for server\n        delete compilation.assets[asset.name]\n      })\n\n      const json = JSON.stringify(bundle, null, 2)\n      const filename = this.options.filename\n\n      compilation.assets[filename] = {\n        source: () => json,\n        size: () => json.length\n      }\n\n      cb()\n    })\n  }\n}\n"
  },
  {
    "path": "vue/src/server/webpack-plugin/util.js",
    "content": "const { red, yellow } = require('chalk')\n\nconst prefix = `[vue-server-renderer-webpack-plugin]`\nconst warn = exports.warn = msg => console.error(red(`${prefix} ${msg}\\n`))\nconst tip = exports.tip = msg => console.log(yellow(`${prefix} ${msg}\\n`))\n\nexport const validate = compiler => {\n  if (compiler.options.target !== 'node') {\n    warn('webpack config `target` should be \"node\".')\n  }\n\n  if (compiler.options.output && compiler.options.output.libraryTarget !== 'commonjs2') {\n    warn('webpack config `output.libraryTarget` should be \"commonjs2\".')\n  }\n\n  if (!compiler.options.externals) {\n    tip(\n      'It is recommended to externalize dependencies in the server build for ' +\n      'better build performance.'\n    )\n  }\n}\n\nexport { isJS, isCSS } from '../util'\n"
  },
  {
    "path": "vue/src/server/write.js",
    "content": "/* @flow */\n\nconst MAX_STACK_DEPTH = 1000\nconst noop = _ => _\n\nconst defer = typeof process !== 'undefined' && process.nextTick\n  ? process.nextTick\n  : typeof Promise !== 'undefined'\n    ? fn => Promise.resolve().then(fn)\n    : typeof setTimeout !== 'undefined'\n      ? setTimeout\n      : noop\n\nif (defer === noop) {\n  throw new Error(\n    'Your JavaScript runtime does not support any asynchronous primitives ' +\n    'that are required by vue-server-renderer. Please use a polyfill for ' +\n    'either Promise or setTimeout.'\n  )\n}\n\nexport function createWriteFunction (\n  write: (text: string, next: Function) => boolean,\n  onError: Function\n): Function {\n  let stackDepth = 0\n  const cachedWrite = (text, next) => {\n    if (text && cachedWrite.caching) {\n      cachedWrite.cacheBuffer[cachedWrite.cacheBuffer.length - 1] += text\n    }\n    const waitForNext = write(text, next)\n    if (waitForNext !== true) {\n      if (stackDepth >= MAX_STACK_DEPTH) {\n        defer(() => {\n          try { next() } catch (e) {\n            onError(e)\n          }\n        })\n      } else {\n        stackDepth++\n        next()\n        stackDepth--\n      }\n    }\n  }\n  cachedWrite.caching = false\n  cachedWrite.cacheBuffer = []\n  cachedWrite.componentBuffer = []\n  return cachedWrite\n}\n"
  },
  {
    "path": "vue/src/sfc/parser.js",
    "content": "/* @flow */\n\nimport deindent from 'de-indent'\nimport { parseHTML } from 'compiler/parser/html-parser'\nimport { makeMap } from 'shared/util'\n\nconst splitRE = /\\r?\\n/g\nconst replaceRE = /./g\nconst isSpecialTag = makeMap('script,style,template', true)\n\ntype Attribute = {\n  name: string,\n  value: string\n};\n\n/**\n * Parse a single-file component (*.vue) file into an SFC Descriptor Object.\n */\nexport function parseComponent (\n  content: string,\n  options?: Object = {}\n): SFCDescriptor {\n  const sfc: SFCDescriptor = {\n    template: null,\n    script: null,\n    styles: [],\n    customBlocks: []\n  }\n  let depth = 0\n  let currentBlock: ?SFCBlock = null\n\n  function start (\n    tag: string,\n    attrs: Array<Attribute>,\n    unary: boolean,\n    start: number,\n    end: number\n  ) {\n    if (depth === 0) {\n      currentBlock = {\n        type: tag,\n        content: '',\n        start: end,\n        attrs: attrs.reduce((cumulated, { name, value }) => {\n          cumulated[name] = value || true\n          return cumulated\n        }, {})\n      }\n      if (isSpecialTag(tag)) {\n        checkAttrs(currentBlock, attrs)\n        if (tag === 'style') {\n          sfc.styles.push(currentBlock)\n        } else {\n          sfc[tag] = currentBlock\n        }\n      } else { // custom blocks\n        sfc.customBlocks.push(currentBlock)\n      }\n    }\n    if (!unary) {\n      depth++\n    }\n  }\n\n  function checkAttrs (block: SFCBlock, attrs: Array<Attribute>) {\n    for (let i = 0; i < attrs.length; i++) {\n      const attr = attrs[i]\n      if (attr.name === 'lang') {\n        block.lang = attr.value\n      }\n      if (attr.name === 'scoped') {\n        block.scoped = true\n      }\n      if (attr.name === 'module') {\n        block.module = attr.value || true\n      }\n      if (attr.name === 'src') {\n        block.src = attr.value\n      }\n    }\n  }\n\n  function end (tag: string, start: number, end: number) {\n    if (depth === 1 && currentBlock) {\n      currentBlock.end = start\n      let text = deindent(content.slice(currentBlock.start, currentBlock.end))\n      // pad content so that linters and pre-processors can output correct\n      // line numbers in errors and warnings\n      if (currentBlock.type !== 'template' && options.pad) {\n        text = padContent(currentBlock, options.pad) + text\n      }\n      currentBlock.content = text\n      currentBlock = null\n    }\n    depth--\n  }\n\n  function padContent (block: SFCBlock, pad: true | \"line\" | \"space\") {\n    if (pad === 'space') {\n      return content.slice(0, block.start).replace(replaceRE, ' ')\n    } else {\n      const offset = content.slice(0, block.start).split(splitRE).length\n      const padChar = block.type === 'script' && !block.lang\n        ? '//\\n'\n        : '\\n'\n      return Array(offset).join(padChar)\n    }\n  }\n\n  parseHTML(content, {\n    start,\n    end\n  })\n\n  return sfc\n}\n"
  },
  {
    "path": "vue/src/shared/constants.js",
    "content": "export const SSR_ATTR = 'data-server-rendered'\n\nexport const ASSET_TYPES = [\n  'component',\n  'directive',\n  'filter'\n]\n\nexport const LIFECYCLE_HOOKS = [\n  'beforeCreate',\n  'created',\n  'beforeMount',\n  'mounted',\n  'beforeUpdate',\n  'updated',\n  'beforeDestroy',\n  'destroyed',\n  'activated',\n  'deactivated',\n  'errorCaptured'\n]\n"
  },
  {
    "path": "vue/src/shared/util.js",
    "content": "/* @flow */\n\nexport const emptyObject = Object.freeze({})\n\n// these helpers produces better vm code in JS engines due to their\n// explicitness and function inlining\nexport function isUndef (v: any): boolean %checks {\n  return v === undefined || v === null\n}\n\nexport function isDef (v: any): boolean %checks {\n  return v !== undefined && v !== null\n}\n\nexport function isTrue (v: any): boolean %checks {\n  return v === true\n}\n\nexport function isFalse (v: any): boolean %checks {\n  return v === false\n}\n\n/**\n * Check if value is primitive\n */\nexport function isPrimitive (value: any): boolean %checks {\n  return (\n    typeof value === 'string' ||\n    typeof value === 'number' ||\n    // $flow-disable-line\n    typeof value === 'symbol' ||\n    typeof value === 'boolean'\n  )\n}\n\n/**\n * Quick object check - this is primarily used to tell\n * Objects from primitive values when we know the value\n * is a JSON-compliant type.\n */\nexport function isObject (obj: mixed): boolean %checks {\n  return obj !== null && typeof obj === 'object'\n}\n\n/**\n * Get the raw type string of a value e.g. [object Object]\n */\nconst _toString = Object.prototype.toString\n\nexport function toRawType (value: any): string {\n  return _toString.call(value).slice(8, -1)\n}\n\n/**\n * Strict object type check. Only returns true\n * for plain JavaScript objects.\n */\nexport function isPlainObject (obj: any): boolean {\n  return _toString.call(obj) === '[object Object]'\n}\n\nexport function isRegExp (v: any): boolean {\n  return _toString.call(v) === '[object RegExp]'\n}\n\n/**\n * Check if val is a valid array index.\n */\nexport function isValidArrayIndex (val: any): boolean {\n  const n = parseFloat(String(val))\n  return n >= 0 && Math.floor(n) === n && isFinite(val)\n}\n\n/**\n * Convert a value to a string that is actually rendered.\n */\nexport function toString (val: any): string {\n  return val == null\n    ? ''\n    : typeof val === 'object'\n      ? JSON.stringify(val, null, 2)\n      : String(val)\n}\n\n/**\n * Convert a input value to a number for persistence.\n * If the conversion fails, return original string.\n */\nexport function toNumber (val: string): number | string {\n  const n = parseFloat(val)\n  return isNaN(n) ? val : n\n}\n\n/**\n * Make a map and return a function for checking if a key\n * is in that map.\n */\nexport function makeMap (\n  str: string,\n  expectsLowerCase?: boolean\n): (key: string) => true | void {\n  const map = Object.create(null)\n  const list: Array<string> = str.split(',')\n  for (let i = 0; i < list.length; i++) {\n    map[list[i]] = true\n  }\n  return expectsLowerCase\n    ? val => map[val.toLowerCase()]\n    : val => map[val]\n}\n\n/**\n * Check if a tag is a built-in tag.\n */\nexport const isBuiltInTag = makeMap('slot,component', true)\n\n/**\n * Check if a attribute is a reserved attribute.\n */\nexport const isReservedAttribute = makeMap('key,ref,slot,slot-scope,is')\n\n/**\n * Remove an item from an array\n */\nexport function remove (arr: Array<any>, item: any): Array<any> | void {\n  if (arr.length) {\n    const index = arr.indexOf(item)\n    if (index > -1) {\n      return arr.splice(index, 1)\n    }\n  }\n}\n\n/**\n * Check whether the object has the property.\n */\nconst hasOwnProperty = Object.prototype.hasOwnProperty\nexport function hasOwn (obj: Object | Array<*>, key: string): boolean {\n  return hasOwnProperty.call(obj, key)\n}\n\n/**\n * Create a cached version of a pure function.\n */\nexport function cached<F: Function> (fn: F): F {\n  const cache = Object.create(null)\n  return (function cachedFn (str: string) {\n    const hit = cache[str]\n    return hit || (cache[str] = fn(str))\n  }: any)\n}\n\n/**\n * Camelize a hyphen-delimited string.\n */\nconst camelizeRE = /-(\\w)/g\nexport const camelize = cached((str: string): string => {\n  return str.replace(camelizeRE, (_, c) => c ? c.toUpperCase() : '')\n})\n\n/**\n * Capitalize a string.\n */\nexport const capitalize = cached((str: string): string => {\n  return str.charAt(0).toUpperCase() + str.slice(1)\n})\n\n/**\n * Hyphenate a camelCase string.\n */\nconst hyphenateRE = /\\B([A-Z])/g\nexport const hyphenate = cached((str: string): string => {\n  return str.replace(hyphenateRE, '-$1').toLowerCase()\n})\n\n/**\n * Simple bind polyfill for environments that do not support it... e.g.\n * PhantomJS 1.x. Technically we don't need this anymore since native bind is\n * now more performant in most browsers, but removing it would be breaking for\n * code that was able to run in PhantomJS 1.x, so this must be kept for\n * backwards compatibility.\n */\n\n/* istanbul ignore next */\nfunction polyfillBind (fn: Function, ctx: Object): Function {\n  function boundFn (a) {\n    const l = arguments.length\n    return l\n      ? l > 1\n        ? fn.apply(ctx, arguments)\n        : fn.call(ctx, a)\n      : fn.call(ctx)\n  }\n\n  boundFn._length = fn.length\n  return boundFn\n}\n\nfunction nativeBind (fn: Function, ctx: Object): Function {\n  return fn.bind(ctx)\n}\n\nexport const bind = Function.prototype.bind\n  ? nativeBind\n  : polyfillBind\n\n/**\n * Convert an Array-like object to a real Array.\n */\nexport function toArray (list: any, start?: number): Array<any> {\n  start = start || 0\n  let i = list.length - start\n  const ret: Array<any> = new Array(i)\n  while (i--) {\n    ret[i] = list[i + start]\n  }\n  return ret\n}\n\n/**\n * Mix properties into target object.\n */\nexport function extend (to: Object, _from: ?Object): Object {\n  for (const key in _from) {\n    to[key] = _from[key]\n  }\n  return to\n}\n\n/**\n * Merge an Array of Objects into a single Object.\n */\nexport function toObject (arr: Array<any>): Object {\n  const res = {}\n  for (let i = 0; i < arr.length; i++) {\n    if (arr[i]) {\n      extend(res, arr[i])\n    }\n  }\n  return res\n}\n\n/**\n * Perform no operation.\n * Stubbing args to make Flow happy without leaving useless transpiled code\n * with ...rest (https://flow.org/blog/2017/05/07/Strict-Function-Call-Arity/)\n */\nexport function noop (a?: any, b?: any, c?: any) {}\n\n/**\n * Always return false.\n */\nexport const no = (a?: any, b?: any, c?: any) => false\n\n/**\n * Return same value\n */\nexport const identity = (_: any) => _\n\n/**\n * Generate a static keys string from compiler modules.\n */\nexport function genStaticKeys (modules: Array<ModuleOptions>): string {\n  return modules.reduce((keys, m) => {\n    return keys.concat(m.staticKeys || [])\n  }, []).join(',')\n}\n\n/**\n * Check if two values are loosely equal - that is,\n * if they are plain objects, do they have the same shape?\n */\nexport function looseEqual (a: any, b: any): boolean {\n  if (a === b) return true\n  const isObjectA = isObject(a)\n  const isObjectB = isObject(b)\n  if (isObjectA && isObjectB) {\n    try {\n      const isArrayA = Array.isArray(a)\n      const isArrayB = Array.isArray(b)\n      if (isArrayA && isArrayB) {\n        return a.length === b.length && a.every((e, i) => {\n          return looseEqual(e, b[i])\n        })\n      } else if (!isArrayA && !isArrayB) {\n        const keysA = Object.keys(a)\n        const keysB = Object.keys(b)\n        return keysA.length === keysB.length && keysA.every(key => {\n          return looseEqual(a[key], b[key])\n        })\n      } else {\n        /* istanbul ignore next */\n        return false\n      }\n    } catch (e) {\n      /* istanbul ignore next */\n      return false\n    }\n  } else if (!isObjectA && !isObjectB) {\n    return String(a) === String(b)\n  } else {\n    return false\n  }\n}\n\nexport function looseIndexOf (arr: Array<mixed>, val: mixed): number {\n  for (let i = 0; i < arr.length; i++) {\n    if (looseEqual(arr[i], val)) return i\n  }\n  return -1\n}\n\n/**\n * Ensure a function is called only once.\n */\nexport function once (fn: Function): Function {\n  let called = false\n  return function () {\n    if (!called) {\n      called = true\n      fn.apply(this, arguments)\n    }\n  }\n}\n"
  },
  {
    "path": "vue/test/e2e/.eslintrc",
    "content": "{\n  \"rules\": {\n    \"indent\": 0\n  }\n}\n"
  },
  {
    "path": "vue/test/e2e/nightwatch.config.js",
    "content": "// http://nightwatchjs.org/guide#settings-file\nmodule.exports = {\n  'src_folders': ['test/e2e/specs'],\n  'output_folder': 'test/e2e/reports',\n  'custom_commands_path': ['node_modules/nightwatch-helpers/commands'],\n  'custom_assertions_path': ['node_modules/nightwatch-helpers/assertions'],\n\n  'selenium': {\n    'start_process': true,\n    'server_path': require('selenium-server').path,\n    'host': '127.0.0.1',\n    'port': 4444,\n    'cli_args': {\n      'webdriver.chrome.driver': require('chromedriver').path\n      // , 'webdriver.gecko.driver': require('geckodriver').path\n    }\n  },\n\n  'test_settings': {\n    'default': {\n      'selenium_port': 4444,\n      'selenium_host': 'localhost',\n      'silent': true,\n      'screenshots': {\n        'enabled': true,\n        'on_failure': true,\n        'on_error': false,\n        'path': 'test/e2e/screenshots'\n      }\n    },\n\n    'chrome': {\n      'desiredCapabilities': {\n        'browserName': 'chrome',\n        'javascriptEnabled': true,\n        'acceptSslCerts': true\n      }\n    },\n\n    'firefox': {\n      'desiredCapabilities': {\n        'browserName': 'firefox',\n        'javascriptEnabled': true,\n        'acceptSslCerts': true,\n        'marionette': true\n      }\n    },\n\n    'phantomjs': {\n      'desiredCapabilities': {\n        'browserName': 'phantomjs',\n        'javascriptEnabled': true,\n        'acceptSslCerts': true\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "vue/test/e2e/runner.js",
    "content": "var path = require('path')\nvar spawn = require('cross-spawn')\nvar httpServer = require('http-server')\nvar server = httpServer.createServer({\n  root: path.resolve(__dirname, '../../')\n})\n\nserver.listen(8080)\n\nvar args = process.argv.slice(2)\nif (args.indexOf('--config') === -1) {\n  args = args.concat(['--config', 'test/e2e/nightwatch.config.js'])\n}\nif (args.indexOf('--env') === -1) {\n  args = args.concat(['--env', 'chrome,phantomjs'])\n}\nvar i = args.indexOf('--test')\nif (i > -1) {\n  args[i + 1] = 'test/e2e/specs/' + args[i + 1] + '.js'\n}\n\nvar runner = spawn('./node_modules/.bin/nightwatch', args, {\n  stdio: 'inherit'\n})\n\nrunner.on('exit', function (code) {\n  server.close()\n  process.exit(code)\n})\n\nrunner.on('error', function (err) {\n  server.close()\n  throw err\n})\n"
  },
  {
    "path": "vue/test/e2e/specs/async-edge-cases.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\">\n    <title></title>\n    <script src=\"../../../dist/vue.min.js\"></script>\n  </head>\n  <body>\n\n    <!-- #4510 click and change event on checkbox -->\n    <div id=\"case-1\">\n      <div @click=\"num++\">\n        {{ num }}\n        <input type=\"checkbox\" v-model=\"checked\">\n      </div>\n    </div>\n    <script>\n    var vm1 = new Vue({\n      el: '#case-1',\n      data: {\n        num: 1,\n        checked: false\n      }\n    })\n    </script>\n\n    <!-- #6566 click event bubbling -->\n    <div id=\"case-2\">\n      <div class=\"panel\" v-if=\"expand\">\n        <button @click=\"expand = false, countA++\">Expand is True</button>\n      </div>\n      <div class=\"header\" v-if=\"!expand\" @click=\"expand = true, countB++\">\n        <button>Expand is False</button>\n      </div>\n      <div class=\"count-a\">\n        countA: {{countA}}\n      </div>\n      <div class=\"count-b\">\n        countB: {{countB}}\n      </div>\n    </div>\n    <script>\n    var vm2 = new Vue({\n      el: '#case-2',\n      data: {\n        expand: true,\n        countA: 0,\n        countB: 0,\n      }\n    })\n    </script>\n\n  </body>\n</html>\n"
  },
  {
    "path": "vue/test/e2e/specs/async-edge-cases.js",
    "content": "module.exports = {\n  'async edge cases': function (browser) {\n    browser\n    .url('http://localhost:8080/test/e2e/specs/async-edge-cases.html')\n      // #4510\n      .assert.containsText('#case-1', '1')\n      .assert.checked('#case-1 input', false)\n\n      .click('#case-1 input')\n      .assert.containsText('#case-1', '2')\n      .assert.checked('#case-1 input', true)\n\n      .click('#case-1 input')\n      .assert.containsText('#case-1', '3')\n      .assert.checked('#case-1 input', false)\n\n      // #6566\n      .assert.containsText('#case-2 button', 'Expand is True')\n      .assert.containsText('.count-a', 'countA: 0')\n      .assert.containsText('.count-b', 'countB: 0')\n\n      .click('#case-2 button')\n      .assert.containsText('#case-2 button', 'Expand is False')\n      .assert.containsText('.count-a', 'countA: 1')\n      .assert.containsText('.count-b', 'countB: 0')\n\n      .click('#case-2 button')\n      .assert.containsText('#case-2 button', 'Expand is True')\n      .assert.containsText('.count-a', 'countA: 1')\n      .assert.containsText('.count-b', 'countB: 1')\n\n      .end()\n  }\n}\n"
  },
  {
    "path": "vue/test/e2e/specs/basic-ssr.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\">\n    <title></title>\n  </head>\n  <body>\n    <script src=\"../../../dist/vue.min.js\"></script>\n    <script src=\"../../../packages/vue-server-renderer/basic.js\"></script>\n\n    <div id=\"result\">wtf</div>\n\n    <script>\n    var vm = new Vue({\n      data: { msg: 'foo' },\n      template: '<div>{{ msg }}</div>'\n    })\n\n    renderVueComponentToString(vm, function (err, result) {\n      document.getElementById('result').textContent = err && err.toString() || result\n    })\n    </script>\n  </body>\n</html>\n"
  },
  {
    "path": "vue/test/e2e/specs/basic-ssr.js",
    "content": "module.exports = {\n  'basic SSR': function (browser) {\n    browser\n    .url('http://localhost:8080/test/e2e/specs/basic-ssr.html')\n      .assert.containsText('#result', '<div data-server-rendered=\"true\">foo</div>')\n      .end()\n  }\n}\n"
  },
  {
    "path": "vue/test/e2e/specs/commits.js",
    "content": "module.exports = {\n  'commits': function (browser) {\n    browser\n    .url('http://localhost:8080/examples/commits/')\n      .waitForElementVisible('li', 5000)\n      .assert.count('input', 2)\n      .assert.count('label', 2)\n      .assert.containsText('label[for=\"master\"]', 'master')\n      .assert.containsText('label[for=\"dev\"]', 'dev')\n      .assert.checked('#master')\n      .assert.checked('#dev', false)\n      .assert.containsText('p', 'vuejs/vue@master')\n      .assert.count('li', 3)\n      .assert.count('li .commit', 3)\n      .assert.count('li .message', 3)\n      .click('#dev')\n      .assert.containsText('p', 'vuejs/vue@dev')\n      .assert.count('li', 3)\n      .assert.count('li .commit', 3)\n      .assert.count('li .message', 3)\n      .end()\n  }\n}\n"
  },
  {
    "path": "vue/test/e2e/specs/grid.js",
    "content": "module.exports = {\n  'grid': function (browser) {\n    var columns = ['name', 'power']\n\n    browser\n    .url('http://localhost:8080/examples/grid/')\n      .waitForElementVisible('table', 1000)\n      .assert.count('th', 2)\n      .assert.count('th.active', 0)\n      .assert.containsText('th:nth-child(1)', 'Name')\n      .assert.containsText('th:nth-child(2)', 'Power')\n      assertTable([\n        { name: 'Chuck Norris', power: Infinity },\n        { name: 'Bruce Lee', power: 9000 },\n        { name: 'Jackie Chan', power: 7000 },\n        { name: 'Jet Li', power: 8000 }\n      ])\n\n    browser\n      .click('th:nth-child(1)')\n      .assert.count('th.active:nth-child(1)', 1)\n      .assert.count('th.active:nth-child(2)', 0)\n      .assert.count('th:nth-child(1) .arrow.dsc', 1)\n      .assert.count('th:nth-child(2) .arrow.dsc', 0)\n      assertTable([\n        { name: 'Jet Li', power: 8000 },\n        { name: 'Jackie Chan', power: 7000 },\n        { name: 'Chuck Norris', power: Infinity },\n        { name: 'Bruce Lee', power: 9000 }\n      ])\n\n    browser\n      .click('th:nth-child(2)')\n      .assert.count('th.active:nth-child(1)', 0)\n      .assert.count('th.active:nth-child(2)', 1)\n      .assert.count('th:nth-child(1) .arrow.dsc', 1)\n      .assert.count('th:nth-child(2) .arrow.dsc', 1)\n      assertTable([\n        { name: 'Chuck Norris', power: Infinity },\n        { name: 'Bruce Lee', power: 9000 },\n        { name: 'Jet Li', power: 8000 },\n        { name: 'Jackie Chan', power: 7000 }\n      ])\n\n    browser\n      .click('th:nth-child(2)')\n      .assert.count('th.active:nth-child(1)', 0)\n      .assert.count('th.active:nth-child(2)', 1)\n      .assert.count('th:nth-child(1) .arrow.dsc', 1)\n      .assert.count('th:nth-child(2) .arrow.asc', 1)\n      assertTable([\n        { name: 'Jackie Chan', power: 7000 },\n        { name: 'Jet Li', power: 8000 },\n        { name: 'Bruce Lee', power: 9000 },\n        { name: 'Chuck Norris', power: Infinity }\n      ])\n\n    browser\n      .click('th:nth-child(1)')\n      .assert.count('th.active:nth-child(1)', 1)\n      .assert.count('th.active:nth-child(2)', 0)\n      .assert.count('th:nth-child(1) .arrow.asc', 1)\n      .assert.count('th:nth-child(2) .arrow.asc', 1)\n      assertTable([\n        { name: 'Bruce Lee', power: 9000 },\n        { name: 'Chuck Norris', power: Infinity },\n        { name: 'Jackie Chan', power: 7000 },\n        { name: 'Jet Li', power: 8000 }\n      ])\n\n    browser\n      .setValue('input[name=\"query\"]', 'j')\n      assertTable([\n        { name: 'Jackie Chan', power: 7000 },\n        { name: 'Jet Li', power: 8000 }\n      ])\n\n    browser\n      .clearValue('input[name=\"query\"]')\n      .setValue('input[name=\"query\"]', 'infinity')\n      assertTable([\n        { name: 'Chuck Norris', power: Infinity }\n      ])\n\n    browser\n      .clearValue('input[name=\"query\"]')\n      .assert.count('p', 0)\n      .setValue('input[name=\"query\"]', 'stringthatdoesnotexistanywhere')\n      .assert.count('p', 1)\n\n    browser.end()\n\n    function assertTable (data) {\n      browser.assert.count('td', data.length * columns.length)\n      for (var i = 0; i < data.length; i++) {\n        for (var j = 0; j < columns.length; j++) {\n          browser.assert.containsText(\n            'tr:nth-child(' + (i + 1) + ') td:nth-child(' + (j + 1) + ')',\n            data[i][columns[j]]\n          )\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "vue/test/e2e/specs/markdown.js",
    "content": "module.exports = {\n  'markdown': function (browser) {\n    browser\n    .url('http://localhost:8080/examples/markdown/')\n      .waitForElementVisible('#editor', 1000)\n      .assert.value('textarea', '# hello')\n      .assert.hasHTML('#editor div', '<h1 id=\"hello\">hello</h1>')\n      .setValue('textarea', '\\n## foo\\n\\n- bar\\n- baz')\n      // assert the output is not updated yet because of debounce\n      .assert.hasHTML('#editor div', '<h1 id=\"hello\">hello</h1>')\n      .waitFor(500)\n      .assert.hasHTML('#editor div',\n        '<h1 id=\"hello\">hello</h1>\\n' +\n        '<h2 id=\"foo\">foo</h2>\\n' +\n        '<ul>\\n<li>bar</li>\\n<li>baz</li>\\n</ul>'\n      )\n      .end()\n  }\n}\n"
  },
  {
    "path": "vue/test/e2e/specs/modal.js",
    "content": "module.exports = {\n  'modal': function (browser) {\n    browser\n    .url('http://localhost:8080/examples/modal/')\n      .waitForElementVisible('#app', 1000)\n      .assert.elementNotPresent('.modal-mask')\n      .click('#show-modal')\n      .assert.elementPresent('.modal-mask')\n      .assert.elementPresent('.modal-wrapper')\n      .assert.elementPresent('.modal-container')\n      .waitFor(50)\n      .assert.cssClassPresent('.modal-mask', 'modal-enter-active')\n      .waitFor(300)\n      .assert.cssClassNotPresent('.modal-mask', 'modal-enter-active')\n      .assert.containsText('.modal-header h3', 'custom header')\n      .assert.containsText('.modal-body', 'default body')\n      .assert.containsText('.modal-footer', 'default footer')\n      .click('.modal-default-button')\n      // should have transition\n      .assert.elementPresent('.modal-mask')\n      .waitFor(50)\n      .assert.cssClassPresent('.modal-mask', 'modal-leave-active')\n      .waitFor(300)\n      .assert.elementNotPresent('.modal-mask')\n      .end()\n  }\n}\n"
  },
  {
    "path": "vue/test/e2e/specs/select2.js",
    "content": "/* globals vm */\nmodule.exports = {\n  'select2': function (browser) {\n    browser\n    .url('http://localhost:8080/examples/select2/')\n      .waitForElementVisible('.select2', 1000)\n      .assert.elementPresent('select')\n      .assert.containsText('p', 'Selected: 0')\n      .assert.containsText('span.select2', 'Select one')\n\n      .click('.select2-selection__rendered')\n      .assert.count('.select2-results__option', 3)\n      .assert.containsText('.select2-results__option:nth-child(1)', 'Select one')\n      .assert.containsText('.select2-results__option:nth-child(2)', 'Hello')\n      .assert.containsText('.select2-results__option:nth-child(3)', 'World')\n      .assert.attributePresent('.select2-results__option:nth-child(1)', 'aria-disabled')\n\n      .click('.select2-results__option:nth-child(2)')\n      .assert.count('.select2-results__option', 0)\n      .assert.containsText('p', 'Selected: 1')\n      .assert.containsText('span.select2', 'Hello')\n\n      // test dynamic options\n      .execute(function () {\n        vm.options.push({ id: 3, text: 'Vue' })\n      })\n      .click('.select2-selection__rendered')\n      .assert.count('.select2-results__option', 4)\n      .assert.containsText('.select2-results__option:nth-child(1)', 'Select one')\n      .assert.containsText('.select2-results__option:nth-child(2)', 'Hello')\n      .assert.containsText('.select2-results__option:nth-child(3)', 'World')\n      .assert.containsText('.select2-results__option:nth-child(4)', 'Vue')\n\n      .click('.select2-results__option:nth-child(4)')\n      .assert.count('.select2-results__option', 0)\n      .assert.containsText('p', 'Selected: 3')\n      .assert.containsText('span.select2', 'Vue')\n\n      .execute(function () {\n        vm.selected = 2\n      })\n      .assert.containsText('p', 'Selected: 2')\n      .assert.containsText('span.select2', 'World')\n      .end()\n  }\n}\n"
  },
  {
    "path": "vue/test/e2e/specs/svg.js",
    "content": "/* globals stats, valueToPoint */\nmodule.exports = {\n  'svg': function (browser) {\n    browser\n    .url('http://localhost:8080/examples/svg/')\n      .waitForElementVisible('svg', 1000)\n      .assert.count('g', 1)\n      .assert.count('polygon', 1)\n      .assert.count('circle', 1)\n      .assert.count('text', 6)\n      .assert.count('label', 6)\n      .assert.count('button', 7)\n      .assert.count('input[type=\"range\"]', 6)\n      .assert.evaluate(function () {\n        var points = stats.map(function (stat, i) {\n        var point = valueToPoint(stat.value, i, 6)\n          return point.x + ',' + point.y\n        }).join(' ')\n        return document.querySelector('polygon').attributes[0].value === points\n      })\n      .click('button.remove')\n      .assert.count('text', 5)\n      .assert.count('label', 5)\n      .assert.count('button', 6)\n      .assert.count('input[type=\"range\"]', 5)\n      .assert.evaluate(function () {\n        var points = stats.map(function (stat, i) {\n        var point = valueToPoint(stat.value, i, 5)\n          return point.x + ',' + point.y\n        }).join(' ')\n        return document.querySelector('polygon').attributes[0].value === points\n      })\n      .setValue('input[name=\"newlabel\"]', 'foo')\n      .click('#add > button')\n      .assert.count('text', 6)\n      .assert.count('label', 6)\n      .assert.count('button', 7)\n      .assert.count('input[type=\"range\"]', 6)\n      .assert.evaluate(function () {\n        var points = stats.map(function (stat, i) {\n        var point = valueToPoint(stat.value, i, 6)\n          return point.x + ',' + point.y\n        }).join(' ')\n        return document.querySelector('polygon').attributes[0].value === points\n      })\n      .end()\n  }\n}\n"
  },
  {
    "path": "vue/test/e2e/specs/todomvc.js",
    "content": "module.exports = {\n  'todomvc': function (browser) {\n    browser\n    .url('http://localhost:8080/examples/todomvc/#test')\n      .waitForElementVisible('.todoapp', 1000)\n      .assert.notVisible('.main')\n      .assert.notVisible('.footer')\n      .assert.count('.filters .selected', 1)\n      .assert.evaluate(function () {\n        return document.querySelector('.filters .selected').textContent === 'All'\n      })\n\n    createNewItem('test')\n      .assert.count('.todo', 1)\n      .assert.notVisible('.todo .edit')\n      .assert.containsText('.todo label', 'test')\n      .assert.containsText('.todo-count strong', '1')\n      .assert.checked('.todo .toggle', false)\n      .assert.visible('.main')\n      .assert.visible('.footer')\n      .assert.notVisible('.clear-completed')\n      .assert.value('.new-todo', '')\n\n    createNewItem('test2')\n      .assert.count('.todo', 2)\n      .assert.containsText('.todo:nth-child(2) label', 'test2')\n      .assert.containsText('.todo-count strong', '2')\n\n    // toggle\n    browser\n      .click('.todo .toggle')\n      .assert.count('.todo.completed', 1)\n      .assert.cssClassPresent('.todo:nth-child(1)', 'completed')\n      .assert.containsText('.todo-count strong', '1')\n      .assert.visible('.clear-completed')\n\n    createNewItem('test3')\n      .assert.count('.todo', 3)\n      .assert.containsText('.todo:nth-child(3) label', 'test3')\n      .assert.containsText('.todo-count strong', '2')\n\n    createNewItem('test4')\n    createNewItem('test5')\n      .assert.count('.todo', 5)\n      .assert.containsText('.todo-count strong', '4')\n\n    // toggle more\n    browser\n      .click('.todo:nth-child(4) .toggle')\n      .click('.todo:nth-child(5) .toggle')\n      .assert.count('.todo.completed', 3)\n      .assert.containsText('.todo-count strong', '2')\n\n    // remove\n    removeItemAt(1)\n      .assert.count('.todo', 4)\n      .assert.count('.todo.completed', 2)\n      .assert.containsText('.todo-count strong', '2')\n    removeItemAt(2)\n      .assert.count('.todo', 3)\n      .assert.count('.todo.completed', 2)\n      .assert.containsText('.todo-count strong', '1')\n\n    // remove all\n    browser\n      .click('.clear-completed')\n      .assert.count('.todo', 1)\n      .assert.containsText('.todo label', 'test2')\n      .assert.count('.todo.completed', 0)\n      .assert.containsText('.todo-count strong', '1')\n      .assert.notVisible('.clear-completed')\n\n    // prepare to test filters\n    createNewItem('test')\n    createNewItem('test')\n      .click('.todo:nth-child(2) .toggle')\n      .click('.todo:nth-child(3) .toggle')\n\n    // active filter\n    browser\n      .click('.filters li:nth-child(2) a')\n      .assert.count('.todo', 1)\n      .assert.count('.todo.completed', 0)\n      // add item with filter active\n      createNewItem('test')\n      .assert.count('.todo', 2)\n\n    // completed filter\n    browser.click('.filters li:nth-child(3) a')\n      .assert.count('.todo', 2)\n      .assert.count('.todo.completed', 2)\n\n    // filter on page load\n    browser.url('http://localhost:8080/examples/todomvc/#active')\n      .assert.count('.todo', 2)\n      .assert.count('.todo.completed', 0)\n      .assert.containsText('.todo-count strong', '2')\n\n    // completed on page load\n    browser.url('http://localhost:8080/examples/todomvc/#completed')\n      .assert.count('.todo', 2)\n      .assert.count('.todo.completed', 2)\n      .assert.containsText('.todo-count strong', '2')\n\n    // toggling with filter active\n    browser\n      .click('.todo .toggle')\n      .assert.count('.todo', 1)\n      .click('.filters li:nth-child(2) a')\n      .assert.count('.todo', 3)\n      .click('.todo .toggle')\n      .assert.count('.todo', 2)\n\n    // editing triggered by blur\n    browser\n      .click('.filters li:nth-child(1) a')\n      .dblClick('.todo:nth-child(1) label')\n      .assert.count('.todo.editing', 1)\n      .assert.focused('.todo:nth-child(1) .edit')\n      .clearValue('.todo:nth-child(1) .edit')\n      .setValue('.todo:nth-child(1) .edit', 'edited!')\n      .click('footer') // blur\n      .assert.count('.todo.editing', 0)\n      .assert.containsText('.todo:nth-child(1) label', 'edited!')\n\n    // editing triggered by enter\n    browser\n      .dblClick('.todo label')\n      .enterValue('.todo:nth-child(1) .edit', 'edited again!')\n      .assert.count('.todo.editing', 0)\n      .assert.containsText('.todo:nth-child(1) label', 'edited again!')\n\n    // cancel\n    browser\n      .dblClick('.todo label')\n      .clearValue('.todo:nth-child(1) .edit')\n      .setValue('.todo:nth-child(1) .edit', 'edited!')\n      .trigger('.todo:nth-child(1) .edit', 'keyup', 27)\n      .assert.count('.todo.editing', 0)\n      .assert.containsText('.todo:nth-child(1) label', 'edited again!')\n\n    // empty value should remove\n    browser\n      .dblClick('.todo label')\n      .enterValue('.todo:nth-child(1) .edit', ' ')\n      .assert.count('.todo', 3)\n\n    // toggle all\n    browser\n      .click('.toggle-all')\n      .assert.count('.todo.completed', 3)\n      .click('.toggle-all')\n      .assert.count('.todo:not(.completed)', 3)\n      .end()\n\n    function createNewItem (text) {\n      return browser.enterValue('.new-todo', text)\n    }\n\n    function removeItemAt (n) {\n      return browser\n        .moveToElement('.todo:nth-child(' + n + ')', 10, 10)\n        .click('.todo:nth-child(' + n + ') .destroy')\n    }\n  }\n}\n"
  },
  {
    "path": "vue/test/e2e/specs/tree.js",
    "content": "module.exports = {\n  'tree': function (browser) {\n    browser\n    .url('http://localhost:8080/examples/tree/')\n      .waitForElementVisible('li', 1000)\n      .assert.count('.item', 12)\n      .assert.count('.add', 4)\n      .assert.count('.item > ul', 4)\n      .assert.notVisible('#demo li ul')\n      .assert.containsText('#demo li div span', '[+]')\n\n      // expand root\n      .click('.bold')\n      .assert.visible('#demo ul')\n      .assert.evaluate(function () {\n        return document.querySelector('#demo li ul').children.length === 4\n      })\n      .assert.containsText('#demo li div span', '[-]')\n      .assert.containsText('#demo > .item > ul > .item:nth-child(1)', 'hello')\n      .assert.containsText('#demo > .item > ul > .item:nth-child(2)', 'wat')\n      .assert.containsText('#demo > .item > ul > .item:nth-child(3)', 'child folder')\n      .assert.containsText('#demo > .item > ul > .item:nth-child(3)', '[+]')\n\n      // add items to root\n      .click('#demo > .item > ul > .add')\n      .assert.evaluate(function () {\n        return document.querySelector('#demo li ul').children.length === 5\n      })\n      .assert.containsText('#demo > .item > ul > .item:nth-child(1)', 'hello')\n      .assert.containsText('#demo > .item > ul > .item:nth-child(2)', 'wat')\n      .assert.containsText('#demo > .item > ul > .item:nth-child(3)', 'child folder')\n      .assert.containsText('#demo > .item > ul > .item:nth-child(3)', '[+]')\n      .assert.containsText('#demo > .item > ul > .item:nth-child(4)', 'new stuff')\n\n      // add another item\n      .click('#demo > .item > ul > .add')\n      .assert.evaluate(function () {\n        return document.querySelector('#demo li ul').children.length === 6\n      })\n      .assert.containsText('#demo > .item > ul > .item:nth-child(1)', 'hello')\n      .assert.containsText('#demo > .item > ul > .item:nth-child(2)', 'wat')\n      .assert.containsText('#demo > .item > ul > .item:nth-child(3)', 'child folder')\n      .assert.containsText('#demo > .item > ul > .item:nth-child(3)', '[+]')\n      .assert.containsText('#demo > .item > ul > .item:nth-child(4)', 'new stuff')\n      .assert.containsText('#demo > .item > ul > .item:nth-child(5)', 'new stuff')\n\n      .click('#demo ul .bold')\n      .assert.visible('#demo ul ul')\n      .assert.containsText('#demo ul > .item:nth-child(3)', '[-]')\n      .assert.evaluate(function () {\n        return document.querySelector('#demo ul ul').children.length === 5\n      })\n\n      .click('.bold')\n      .assert.notVisible('#demo ul')\n      .assert.containsText('#demo li div span', '[+]')\n      .click('.bold')\n      .assert.visible('#demo ul')\n      .assert.containsText('#demo li div span', '[-]')\n\n      .dblClick('#demo ul > .item div')\n      .assert.count('.item', 15)\n      .assert.count('.item > ul', 5)\n      .assert.containsText('#demo ul > .item:nth-child(1)', '[-]')\n      .assert.evaluate(function () {\n        var firstItem = document.querySelector('#demo ul > .item:nth-child(1)')\n        var ul = firstItem.querySelector('ul')\n        return ul.children.length === 2\n      })\n      .end()\n  }\n}\n"
  },
  {
    "path": "vue/test/helpers/.eslintrc",
    "content": "{\n  \"env\": {\n    \"jasmine\": true\n  },\n  \"globals\": {\n    \"waitForUpdate\": true\n  }\n}\n"
  },
  {
    "path": "vue/test/helpers/classlist.js",
    "content": "beforeEach(() => {\n  jasmine.addMatchers({\n    // since classList may not be supported in all browsers\n    toHaveClass: () => {\n      return {\n        compare: (el, cls) => {\n          const pass = el.classList\n            ? el.classList.contains(cls)\n            : el.getAttribute('class').split(/\\s+/g).indexOf(cls) > -1\n          return {\n            pass,\n            message: `Expected element${pass ? ' ' : ' not '}to have class ${cls}`\n          }\n        }\n      }\n    }\n  })\n})\n"
  },
  {
    "path": "vue/test/helpers/test-object-option.js",
    "content": "import Vue from 'vue'\n\nexport default function testObjectOption (name) {\n  it(`Options ${name}: should warn non object value`, () => {\n    const options = {}\n    options[name] = () => {}\n    new Vue(options)\n    expect(`Invalid value for option \"${name}\"`).toHaveBeenWarned()\n  })\n\n  it(`Options ${name}: should not warn valid object value`, () => {\n    const options = {}\n    options[name] = {}\n    new Vue(options)\n    expect(`Invalid value for option \"${name}\"`).not.toHaveBeenWarned()\n  })\n}\n"
  },
  {
    "path": "vue/test/helpers/to-equal.js",
    "content": "import { isEqual } from 'lodash'\n\nbeforeEach(() => {\n  jasmine.addMatchers({\n    // override built-in toEqual because it behaves incorrectly\n    // on Vue-observed arrays in Safari\n    toEqual: () => {\n      return {\n        compare: (a, b) => {\n          const pass = isEqual(a, b)\n          return {\n            pass,\n            message: `Expected ${a} to equal ${b}`\n          }\n        }\n      }\n    }\n  })\n})\n"
  },
  {
    "path": "vue/test/helpers/to-have-been-warned.js",
    "content": "function noop () {}\n\nif (typeof console === 'undefined') {\n  window.console = {\n    warn: noop,\n    error: noop\n  }\n}\n\n// avoid info messages during test\nconsole.info = noop\n\nlet asserted\n\nfunction createCompareFn (spy) {\n  const hasWarned = msg => {\n    var count = spy.calls.count()\n    var args\n    while (count--) {\n      args = spy.calls.argsFor(count)\n      if (args.some(containsMsg)) {\n        return true\n      }\n    }\n\n    function containsMsg (arg) {\n      return arg.toString().indexOf(msg) > -1\n    }\n  }\n\n  return {\n    compare: msg => {\n      asserted = asserted.concat(msg)\n      var warned = Array.isArray(msg)\n        ? msg.some(hasWarned)\n        : hasWarned(msg)\n      return {\n        pass: warned,\n        message: warned\n          ? 'Expected message \"' + msg + '\" not to have been warned'\n          : 'Expected message \"' + msg + '\" to have been warned'\n      }\n    }\n  }\n}\n\n// define custom matcher for warnings\nbeforeEach(() => {\n  asserted = []\n  spyOn(console, 'warn')\n  spyOn(console, 'error')\n  jasmine.addMatchers({\n    toHaveBeenWarned: () => createCompareFn(console.error),\n    toHaveBeenTipped: () => createCompareFn(console.warn)\n  })\n})\n\nafterEach(done => {\n  const warned = msg => asserted.some(assertedMsg => msg.toString().indexOf(assertedMsg) > -1)\n  let count = console.error.calls.count()\n  let args\n  while (count--) {\n    args = console.error.calls.argsFor(count)\n    if (!warned(args[0])) {\n      done.fail(`Unexpected console.error message: ${args[0]}`)\n      return\n    }\n  }\n  done()\n})\n"
  },
  {
    "path": "vue/test/helpers/trigger-event.js",
    "content": "window.triggerEvent = function triggerEvent (target, event, process) {\n  var e = document.createEvent('HTMLEvents')\n  e.initEvent(event, true, true)\n  if (process) process(e)\n  target.dispatchEvent(e)\n}\n"
  },
  {
    "path": "vue/test/helpers/vdom.js",
    "content": "import VNode from 'core/vdom/vnode'\n\nwindow.createTextVNode = function (text) {\n  return new VNode(undefined, undefined, undefined, text)\n}\n"
  },
  {
    "path": "vue/test/helpers/wait-for-update.js",
    "content": "import Vue from 'vue'\n\n// helper for async assertions.\n// Use like this:\n//\n// vm.a = 123\n// waitForUpdate(() => {\n//   expect(vm.$el.textContent).toBe('123')\n//   vm.a = 234\n// })\n// .then(() => {\n//   // more assertions...\n// })\n// .then(done)\nwindow.waitForUpdate = initialCb => {\n  let end\n  const queue = initialCb ? [initialCb] : []\n\n  function shift () {\n    const job = queue.shift()\n    if (queue.length) {\n      let hasError = false\n      try {\n        job.wait ? job(shift) : job()\n      } catch (e) {\n        hasError = true\n        const done = queue[queue.length - 1]\n        if (done && done.fail) {\n          done.fail(e)\n        }\n      }\n      if (!hasError && !job.wait) {\n        if (queue.length) {\n          Vue.nextTick(shift)\n        }\n      }\n    } else if (job && (job.fail || job === end)) {\n      job() // done\n    }\n  }\n\n  Vue.nextTick(() => {\n    if (!queue.length || (!end && !queue[queue.length - 1].fail)) {\n      throw new Error('waitForUpdate chain is missing .then(done)')\n    }\n    shift()\n  })\n\n  const chainer = {\n    then: nextCb => {\n      queue.push(nextCb)\n      return chainer\n    },\n    thenWaitFor: (wait) => {\n      if (typeof wait === 'number') {\n        wait = timeout(wait)\n      }\n      wait.wait = true\n      queue.push(wait)\n      return chainer\n    },\n    end: endFn => {\n      queue.push(endFn)\n      end = endFn\n    }\n  }\n\n  return chainer\n}\n\nfunction timeout (n) {\n  return next => setTimeout(next, n)\n}\n"
  },
  {
    "path": "vue/test/ssr/.eslintrc",
    "content": "{\n  \"env\": {\n    \"jasmine\": true\n  },\n  \"plugins\": [\"jasmine\"],\n  \"rules\": {\n    \"jasmine/no-focused-tests\": 2\n  }\n}\n"
  },
  {
    "path": "vue/test/ssr/async-loader.js",
    "content": "const hash = require('hash-sum')\n\nmodule.exports = function (code) {\n  const id = hash(this.request) // simulating vue-loader module id injection\n  return code.replace('__MODULE_ID__', id)\n}\n"
  },
  {
    "path": "vue/test/ssr/compile-with-webpack.js",
    "content": "import path from 'path'\nimport webpack from 'webpack'\nimport MemoryFS from 'memory-fs'\n\nexport function compileWithWebpack (file, extraConfig, cb) {\n  const config = Object.assign({\n    entry: path.resolve(__dirname, 'fixtures', file),\n    module: {\n      rules: [\n        {\n          test: /\\.js$/,\n          loader: 'babel-loader'\n        },\n        {\n          test: /async-.*\\.js$/,\n          loader: require.resolve('./async-loader')\n        },\n        {\n          test: /\\.(png|woff2|css)$/,\n          loader: 'file-loader',\n          options: {\n            name: '[name].[ext]'\n          }\n        }\n      ]\n    }\n  }, extraConfig)\n\n  const compiler = webpack(config)\n  const fs = new MemoryFS()\n  compiler.outputFileSystem = fs\n\n  compiler.run((err, stats) => {\n    expect(err).toBeFalsy()\n    expect(stats.errors).toBeFalsy()\n    cb(fs)\n  })\n}\n"
  },
  {
    "path": "vue/test/ssr/fixtures/app.js",
    "content": "import Vue from '../../../dist/vue.runtime.common.js'\n\nexport default context => {\n  return new Promise(resolve => {\n    context.msg = 'hello'\n    resolve(new Vue({\n      render (h) {\n        return h('div', context.url)\n      }\n    }))\n  })\n}\n"
  },
  {
    "path": "vue/test/ssr/fixtures/async-bar.js",
    "content": "module.exports = {\n  beforeCreate () {\n    this.$vnode.ssrContext._registeredComponents.add('__MODULE_ID__')\n  },\n  render (h) {\n    return h('div', 'async bar')\n  }\n}\n"
  },
  {
    "path": "vue/test/ssr/fixtures/async-foo.js",
    "content": "// import image and font\nimport './test.css'\nimport font from './test.woff2'\nimport image from './test.png'\n\nmodule.exports = {\n  beforeCreate () {\n    this.$vnode.ssrContext._registeredComponents.add('__MODULE_ID__')\n  },\n  render (h) {\n    return h('div', `async ${font} ${image}`)\n  }\n}\n"
  },
  {
    "path": "vue/test/ssr/fixtures/cache.js",
    "content": "import Vue from '../../../dist/vue.runtime.common.js'\n\nconst app = {\n  name: 'app',\n  props: ['id'],\n  serverCacheKey: props => props.id,\n  render (h) {\n    return h('div', '/test')\n  }\n}\n\nexport default () => {\n  return Promise.resolve(new Vue({\n    render: h => h(app, { props: { id: 1 }})\n  }))\n}\n"
  },
  {
    "path": "vue/test/ssr/fixtures/error.js",
    "content": "throw new Error('foo')\n"
  },
  {
    "path": "vue/test/ssr/fixtures/nested-cache.js",
    "content": "import Vue from '../../../dist/vue.runtime.common.js'\n\nfunction createRegisterFn (id) {\n  return function (context) {\n    context = context || this.$vnode.ssrContext\n    context.registered.push(id)\n  }\n}\n\nfunction addHooks (comp) {\n  const hook = createRegisterFn(comp.name)\n  return Object.assign(comp, {\n    _ssrRegister: hook,\n    beforeCreate: hook\n  })\n}\n\nconst grandchild = addHooks({\n  name: 'grandchild',\n  props: ['id'],\n  serverCacheKey: props => props.id,\n  render (h) {\n    return h('div', '/test')\n  }\n})\n\nconst child = addHooks({\n  name: 'child',\n  props: ['id'],\n  serverCacheKey: props => props.id,\n  render (h) {\n    return h(grandchild, { props: { id: this.id }})\n  }\n})\n\nconst app = addHooks({\n  name: 'app',\n  props: ['id'],\n  serverCacheKey: props => props.id,\n  render (h) {\n    return h(child, { props: { id: this.id }})\n  }\n})\n\nexport default () => {\n  return Promise.resolve(new Vue({\n    render: h => h(app, { props: { id: 1 }})\n  }))\n}\n"
  },
  {
    "path": "vue/test/ssr/fixtures/promise-rejection.js",
    "content": "export default () => {\n  return Promise.reject(new Error('foo'))\n}\n"
  },
  {
    "path": "vue/test/ssr/fixtures/split.js",
    "content": "import Vue from '../../../dist/vue.runtime.common.js'\n\n// async component!\nconst Foo = () => import('./async-foo')\nconst Bar = () => import('./async-bar') // eslint-disable-line\n\nexport default context => {\n  return new Promise(resolve => {\n    context.msg = 'hello'\n    const vm = new Vue({\n      render (h) {\n        return h('div', [\n          context.url,\n          h(Foo)\n        ])\n      }\n    })\n\n    // simulate router.onReady\n    Foo().then(comp => {\n      // resolve now to make the render sync\n      Foo.resolved = Vue.extend(comp)\n      resolve(vm)\n    })\n  })\n}\n"
  },
  {
    "path": "vue/test/ssr/fixtures/test.css",
    "content": ""
  },
  {
    "path": "vue/test/ssr/jasmine.json",
    "content": "{\n  \"spec_dir\": \"test/ssr\",\n  \"spec_files\": [\n    \"*.spec.js\"\n  ],\n  \"helpers\": [\n    \"../../node_modules/babel-register/lib/node.js\"\n  ]\n}\n"
  },
  {
    "path": "vue/test/ssr/ssr-basic-renderer.spec.js",
    "content": "import Vue from '../../dist/vue.runtime.common.js'\nimport renderToString from '../../packages/vue-server-renderer/basic'\n\ndescribe('SSR: basicRenderer', () => {\n  it('should work', done => {\n    renderToString(new Vue({\n      template: `\n        <div>\n          <p class=\"hi\">yoyo</p>\n          <div id=\"ho\" :class=\"{ red: isRed }\"></div>\n          <span>{{ test }}</span>\n          <input :value=\"test\">\n          <img :src=\"imageUrl\">\n          <test></test>\n          <test-async></test-async>\n        </div>\n      `,\n      data: {\n        test: 'hi',\n        isRed: true,\n        imageUrl: 'https://vuejs.org/images/logo.png'\n      },\n      components: {\n        test: {\n          render () {\n            return this.$createElement('div', { class: ['a'] }, 'test')\n          }\n        },\n        testAsync (resolve) {\n          resolve({\n            render () {\n              return this.$createElement('span', { class: ['b'] }, 'testAsync')\n            }\n          })\n        }\n      }\n    }), (err, result) => {\n      expect(err).toBeNull()\n      expect(result).toContain(\n        '<div data-server-rendered=\"true\">' +\n          '<p class=\"hi\">yoyo</p> ' +\n          '<div id=\"ho\" class=\"red\"></div> ' +\n          '<span>hi</span> ' +\n          '<input value=\"hi\"> ' +\n          '<img src=\"https://vuejs.org/images/logo.png\"> ' +\n          '<div class=\"a\">test</div> ' +\n          '<span class=\"b\">testAsync</span>' +\n        '</div>'\n      )\n      done()\n    })\n  })\n\n  // #5941\n  it('should work peoperly when accessing $ssrContext in root component', done => {\n    let ssrContext\n    renderToString(new Vue({\n      template: `\n        <div></div>\n      `,\n      created () {\n        ssrContext = this.$ssrContext\n      }\n    }), (err, result) => {\n      expect(err).toBeNull()\n      expect(ssrContext).toBeUndefined()\n      done()\n    })\n  })\n})\n"
  },
  {
    "path": "vue/test/ssr/ssr-bundle-render.spec.js",
    "content": "import LRU from 'lru-cache'\nimport { compileWithWebpack } from './compile-with-webpack'\nimport { createBundleRenderer } from '../../packages/vue-server-renderer'\nimport VueSSRServerPlugin from '../../packages/vue-server-renderer/server-plugin'\n\nexport function createRenderer (file, options, cb) {\n  if (typeof options === 'function') {\n    cb = options\n    options = undefined\n  }\n  const asBundle = !!(options && options.asBundle)\n  if (options) delete options.asBundle\n\n  compileWithWebpack(file, {\n    target: 'node',\n    devtool: asBundle ? '#source-map' : false,\n    output: {\n      path: '/',\n      filename: 'bundle.js',\n      libraryTarget: 'commonjs2'\n    },\n    externals: [require.resolve('../../dist/vue.runtime.common.js')],\n    plugins: asBundle\n      ? [new VueSSRServerPlugin()]\n      : []\n  }, fs => {\n    const bundle = asBundle\n      ? JSON.parse(fs.readFileSync('/vue-ssr-server-bundle.json', 'utf-8'))\n      : fs.readFileSync('/bundle.js', 'utf-8')\n    const renderer = createBundleRenderer(bundle, options)\n    cb(renderer)\n  })\n}\n\ndescribe('SSR: bundle renderer', () => {\n  createAssertions(true)\n  createAssertions(false)\n})\n\nfunction createAssertions (runInNewContext) {\n  it('renderToString', done => {\n    createRenderer('app.js', { runInNewContext }, renderer => {\n      const context = { url: '/test' }\n      renderer.renderToString(context, (err, res) => {\n        expect(err).toBeNull()\n        expect(res).toBe('<div data-server-rendered=\"true\">/test</div>')\n        expect(context.msg).toBe('hello')\n        done()\n      })\n    })\n  })\n\n  it('renderToStream', done => {\n    createRenderer('app.js', { runInNewContext }, renderer => {\n      const context = { url: '/test' }\n      const stream = renderer.renderToStream(context)\n      let res = ''\n      stream.on('data', chunk => {\n        res += chunk.toString()\n      })\n      stream.on('end', () => {\n        expect(res).toBe('<div data-server-rendered=\"true\">/test</div>')\n        expect(context.msg).toBe('hello')\n        done()\n      })\n    })\n  })\n\n  it('renderToString catch error', done => {\n    createRenderer('error.js', { runInNewContext }, renderer => {\n      renderer.renderToString(err => {\n        expect(err.message).toBe('foo')\n        done()\n      })\n    })\n  })\n\n  it('renderToString catch Promise rejection', done => {\n    createRenderer('promise-rejection.js', { runInNewContext }, renderer => {\n      renderer.renderToString(err => {\n        expect(err.message).toBe('foo')\n        done()\n      })\n    })\n  })\n\n  it('renderToStream catch error', done => {\n    createRenderer('error.js', { runInNewContext }, renderer => {\n      const stream = renderer.renderToStream()\n      stream.on('error', err => {\n        expect(err.message).toBe('foo')\n        done()\n      })\n    })\n  })\n\n  it('renderToStream catch Promise rejection', done => {\n    createRenderer('promise-rejection.js', { runInNewContext }, renderer => {\n      const stream = renderer.renderToStream()\n      stream.on('error', err => {\n        expect(err.message).toBe('foo')\n        done()\n      })\n    })\n  })\n\n  it('render with cache (get/set)', done => {\n    const cache = {}\n    const get = jasmine.createSpy('get')\n    const set = jasmine.createSpy('set')\n    const options = {\n      runInNewContext,\n      cache: {\n        // async\n        get: (key, cb) => {\n          setTimeout(() => {\n            get(key)\n            cb(cache[key])\n          }, 0)\n        },\n        set: (key, val) => {\n          set(key, val)\n          cache[key] = val\n        }\n      }\n    }\n    createRenderer('cache.js', options, renderer => {\n      const expected = '<div data-server-rendered=\"true\">/test</div>'\n      const key = 'app::1'\n      renderer.renderToString((err, res) => {\n        expect(err).toBeNull()\n        expect(res).toBe(expected)\n        expect(get).toHaveBeenCalledWith(key)\n        const setArgs = set.calls.argsFor(0)\n        expect(setArgs[0]).toBe(key)\n        expect(setArgs[1].html).toBe(expected)\n        expect(cache[key].html).toBe(expected)\n        renderer.renderToString((err, res) => {\n          expect(err).toBeNull()\n          expect(res).toBe(expected)\n          expect(get.calls.count()).toBe(2)\n          expect(set.calls.count()).toBe(1)\n          done()\n        })\n      })\n    })\n  })\n\n  it('render with cache (get/set/has)', done => {\n    const cache = {}\n    const has = jasmine.createSpy('has')\n    const get = jasmine.createSpy('get')\n    const set = jasmine.createSpy('set')\n    const options = {\n      runInNewContext,\n      cache: {\n        // async\n        has: (key, cb) => {\n          has(key)\n          cb(!!cache[key])\n        },\n        // sync\n        get: key => {\n          get(key)\n          return cache[key]\n        },\n        set: (key, val) => {\n          set(key, val)\n          cache[key] = val\n        }\n      }\n    }\n    createRenderer('cache.js', options, renderer => {\n      const expected = '<div data-server-rendered=\"true\">/test</div>'\n      const key = 'app::1'\n      renderer.renderToString((err, res) => {\n        expect(err).toBeNull()\n        expect(res).toBe(expected)\n        expect(has).toHaveBeenCalledWith(key)\n        expect(get).not.toHaveBeenCalled()\n        const setArgs = set.calls.argsFor(0)\n        expect(setArgs[0]).toBe(key)\n        expect(setArgs[1].html).toBe(expected)\n        expect(cache[key].html).toBe(expected)\n        renderer.renderToString((err, res) => {\n          expect(err).toBeNull()\n          expect(res).toBe(expected)\n          expect(has.calls.count()).toBe(2)\n          expect(get.calls.count()).toBe(1)\n          expect(set.calls.count()).toBe(1)\n          done()\n        })\n      })\n    })\n  })\n\n  it('render with cache (nested)', done => {\n    const cache = LRU({ maxAge: Infinity })\n    spyOn(cache, 'get').and.callThrough()\n    spyOn(cache, 'set').and.callThrough()\n    const options = {\n      cache,\n      runInNewContext\n    }\n    createRenderer('nested-cache.js', options, renderer => {\n      const expected = '<div data-server-rendered=\"true\">/test</div>'\n      const key = 'app::1'\n      const context1 = { registered: [] }\n      const context2 = { registered: [] }\n      renderer.renderToString(context1, (err, res) => {\n        expect(err).toBeNull()\n        expect(res).toBe(expected)\n        expect(cache.set.calls.count()).toBe(3) // 3 nested components cached\n        const cached = cache.get(key)\n        expect(cached.html).toBe(expected)\n        expect(cache.get.calls.count()).toBe(1)\n\n        // assert component usage registration for nested children\n        expect(context1.registered).toEqual(['app', 'child', 'grandchild'])\n\n        renderer.renderToString(context2, (err, res) => {\n          expect(err).toBeNull()\n          expect(res).toBe(expected)\n          expect(cache.set.calls.count()).toBe(3) // no new cache sets\n          expect(cache.get.calls.count()).toBe(2) // 1 get for root\n\n          expect(context2.registered).toEqual(['app', 'child', 'grandchild'])\n          done()\n        })\n      })\n    })\n  })\n\n  it('renderToString (bundle format with code split)', done => {\n    createRenderer('split.js', { runInNewContext, asBundle: true }, renderer => {\n      const context = { url: '/test' }\n      renderer.renderToString(context, (err, res) => {\n        expect(err).toBeNull()\n        expect(res).toBe('<div data-server-rendered=\"true\">/test<div>async test.woff2 test.png</div></div>')\n        done()\n      })\n    })\n  })\n\n  it('renderToStream (bundle format with code split)', done => {\n    createRenderer('split.js', { runInNewContext, asBundle: true }, renderer => {\n      const context = { url: '/test' }\n      const stream = renderer.renderToStream(context)\n      let res = ''\n      stream.on('data', chunk => {\n        res += chunk.toString()\n      })\n      stream.on('end', () => {\n        expect(res).toBe('<div data-server-rendered=\"true\">/test<div>async test.woff2 test.png</div></div>')\n        done()\n      })\n    })\n  })\n\n  it('renderToString catch error (bundle format with source map)', done => {\n    createRenderer('error.js', { runInNewContext, asBundle: true }, renderer => {\n      renderer.renderToString(err => {\n        expect(err.stack).toContain('test/ssr/fixtures/error.js:1:6')\n        expect(err.message).toBe('foo')\n        done()\n      })\n    })\n  })\n\n  it('renderToString catch error (bundle format with source map)', done => {\n    createRenderer('error.js', { runInNewContext, asBundle: true }, renderer => {\n      const stream = renderer.renderToStream()\n      stream.on('error', err => {\n        expect(err.stack).toContain('test/ssr/fixtures/error.js:1:6')\n        expect(err.message).toBe('foo')\n        done()\n      })\n    })\n  })\n\n  it('renderToString return Promise', done => {\n    createRenderer('app.js', { runInNewContext }, renderer => {\n      const context = { url: '/test' }\n      renderer.renderToString(context).then(res => {\n        expect(res).toBe('<div data-server-rendered=\"true\">/test</div>')\n        expect(context.msg).toBe('hello')\n        done()\n      })\n    })\n  })\n\n  it('renderToString return Promise (error)', done => {\n    createRenderer('error.js', { runInNewContext }, renderer => {\n      renderer.renderToString().catch(err => {\n        expect(err.message).toBe('foo')\n        done()\n      })\n    })\n  })\n\n  it('renderToString return Promise (Promise rejection)', done => {\n    createRenderer('promise-rejection.js', { runInNewContext }, renderer => {\n      renderer.renderToString().catch(err => {\n        expect(err.message).toBe('foo')\n        done()\n      })\n    })\n  })\n}\n"
  },
  {
    "path": "vue/test/ssr/ssr-stream.spec.js",
    "content": "import Vue from '../../dist/vue.runtime.common.js'\nimport { createRenderer } from '../../packages/vue-server-renderer'\nconst { renderToStream } = createRenderer()\n\ndescribe('SSR: renderToStream', () => {\n  it('should render to a stream', done => {\n    const stream = renderToStream(new Vue({\n      template: `\n        <div>\n          <p class=\"hi\">yoyo</p>\n          <div id=\"ho\" :class=\"[testClass, { red: isRed }]\"></div>\n          <span>{{ test }}</span>\n          <input :value=\"test\">\n          <b-comp></b-comp>\n          <c-comp></c-comp>\n        </div>\n      `,\n      data: {\n        test: 'hi',\n        isRed: true,\n        testClass: 'a'\n      },\n      components: {\n        bComp (resolve) {\n          return resolve({\n            render (h) {\n              return h('test-async-2')\n            },\n            components: {\n              testAsync2 (resolve) {\n                return resolve({\n                  created () { this.$parent.$parent.testClass = 'b' },\n                  render (h) {\n                    return h('div', { class: [this.$parent.$parent.testClass] }, 'test')\n                  }\n                })\n              }\n            }\n          })\n        },\n        cComp: {\n          render (h) {\n            return h('div', { class: [this.$parent.testClass] }, 'test')\n          }\n        }\n      }\n    }))\n    let res = ''\n    stream.on('data', chunk => {\n      res += chunk\n    })\n    stream.on('end', () => {\n      expect(res).toContain(\n        '<div data-server-rendered=\"true\">' +\n          '<p class=\"hi\">yoyo</p> ' +\n          '<div id=\"ho\" class=\"a red\"></div> ' +\n          '<span>hi</span> ' +\n          '<input value=\"hi\"> ' +\n          '<div class=\"b\">test</div> ' +\n          '<div class=\"b\">test</div>' +\n        '</div>'\n      )\n      done()\n    })\n  })\n\n  it('should catch error', done => {\n    Vue.config.silent = true\n    const stream = renderToStream(new Vue({\n      render () {\n        throw new Error('oops')\n      }\n    }))\n    stream.on('error', err => {\n      expect(err.toString()).toMatch(/oops/)\n      Vue.config.silent = false\n      done()\n    })\n    stream.on('data', _ => _)\n  })\n\n  it('should not mingle two components', done => {\n    const padding = (new Array(20000)).join('x')\n    const component1 = new Vue({\n      template: `<div>${padding}<div></div></div>`,\n      _scopeId: '_component1'\n    })\n    const component2 = new Vue({\n      template: `<div></div>`,\n      _scopeId: '_component2'\n    })\n    var stream1 = renderToStream(component1)\n    var stream2 = renderToStream(component2)\n    var res = ''\n    stream1.on('data', (text) => {\n      res += text.toString('utf-8').replace(/x/g, '')\n    })\n    stream1.on('end', () => {\n      expect(res).not.toContain('_component2')\n      done()\n    })\n    stream1.read(1)\n    stream2.read(1)\n  })\n})\n"
  },
  {
    "path": "vue/test/ssr/ssr-string.spec.js",
    "content": "import Vue from '../../dist/vue.runtime.common.js'\nimport VM from 'vm'\nimport { createRenderer } from '../../packages/vue-server-renderer'\nconst { renderToString } = createRenderer()\n\ndescribe('SSR: renderToString', () => {\n  it('static attributes', done => {\n    renderVmWithOptions({\n      template: '<div id=\"foo\" bar=\"123\"></div>'\n    }, result => {\n      expect(result).toContain('<div id=\"foo\" bar=\"123\" data-server-rendered=\"true\"></div>')\n      done()\n    })\n  })\n\n  it('unary tags', done => {\n    renderVmWithOptions({\n      template: '<input value=\"123\">'\n    }, result => {\n      expect(result).toContain('<input value=\"123\" data-server-rendered=\"true\">')\n      done()\n    })\n  })\n\n  it('dynamic attributes', done => {\n    renderVmWithOptions({\n      template: '<div qux=\"quux\" :id=\"foo\" :bar=\"baz\"></div>',\n      data: {\n        foo: 'hi',\n        baz: 123\n      }\n    }, result => {\n      expect(result).toContain('<div qux=\"quux\" id=\"hi\" bar=\"123\" data-server-rendered=\"true\"></div>')\n      done()\n    })\n  })\n\n  it('static class', done => {\n    renderVmWithOptions({\n      template: '<div class=\"foo bar\"></div>'\n    }, result => {\n      expect(result).toContain('<div data-server-rendered=\"true\" class=\"foo bar\"></div>')\n      done()\n    })\n  })\n\n  it('dynamic class', done => {\n    renderVmWithOptions({\n      template: '<div class=\"foo bar\" :class=\"[a, { qux: hasQux, quux: hasQuux }]\"></div>',\n      data: {\n        a: 'baz',\n        hasQux: true,\n        hasQuux: false\n      }\n    }, result => {\n      expect(result).toContain('<div data-server-rendered=\"true\" class=\"foo bar baz qux\"></div>')\n      done()\n    })\n  })\n\n  it('custom component class', done => {\n    renderVmWithOptions({\n      template: '<div><cmp class=\"cmp\"></cmp></div>',\n      components: {\n        cmp: {\n          render: h => h('div', 'test')\n        }\n      }\n    }, result => {\n      expect(result).toContain('<div data-server-rendered=\"true\"><div class=\"cmp\">test</div></div>')\n      done()\n    })\n  })\n\n  it('nested component class', done => {\n    renderVmWithOptions({\n      template: '<cmp class=\"outer\" :class=\"cls\"></cmp>',\n      data: { cls: { 'success': 1 }},\n      components: {\n        cmp: {\n          render: h => h('div', [h('nested', { staticClass: 'nested', 'class': { 'error': 1 }})]),\n          components: {\n            nested: {\n              render: h => h('div', { staticClass: 'inner' }, 'test')\n            }\n          }\n        }\n      }\n    }, result => {\n      expect(result).toContain('<div data-server-rendered=\"true\" class=\"outer success\">' +\n          '<div class=\"inner nested error\">test</div>' +\n        '</div>')\n      done()\n    })\n  })\n\n  it('dynamic style', done => {\n    renderVmWithOptions({\n      template: '<div style=\"background-color:black\" :style=\"{ fontSize: fontSize + \\'px\\', color: color }\"></div>',\n      data: {\n        fontSize: 14,\n        color: 'red'\n      }\n    }, result => {\n      expect(result).toContain(\n        '<div data-server-rendered=\"true\" style=\"background-color:black;font-size:14px;color:red;\"></div>'\n      )\n      done()\n    })\n  })\n\n  it('dynamic string style', done => {\n    renderVmWithOptions({\n      template: '<div :style=\"style\"></div>',\n      data: {\n        style: 'color:red'\n      }\n    }, result => {\n      expect(result).toContain(\n        '<div data-server-rendered=\"true\" style=\"color:red;\"></div>'\n      )\n      done()\n    })\n  })\n\n  it('auto-prefixed style value as array', done => {\n    renderVmWithOptions({\n      template: '<div :style=\"style\"></div>',\n      data: {\n        style: {\n          display: ['-webkit-box', '-ms-flexbox', 'flex']\n        }\n      }\n    }, result => {\n      expect(result).toContain(\n        '<div data-server-rendered=\"true\" style=\"display:-webkit-box;display:-ms-flexbox;display:flex;\"></div>'\n      )\n      done()\n    })\n  })\n\n  it('custom component style', done => {\n    renderVmWithOptions({\n      template: '<section><comp :style=\"style\"></comp></section>',\n      data: {\n        style: 'color:red'\n      },\n      components: {\n        comp: {\n          template: '<div></div>'\n        }\n      }\n    }, result => {\n      expect(result).toContain(\n        '<section data-server-rendered=\"true\"><div style=\"color:red;\"></div></section>'\n      )\n      done()\n    })\n  })\n\n  it('nested custom component style', done => {\n    renderVmWithOptions({\n      template: '<comp style=\"color: blue\" :style=\"style\"></comp>',\n      data: {\n        style: 'color:red'\n      },\n      components: {\n        comp: {\n          template: '<nested style=\"text-align: left;\" :style=\"{fontSize:\\'520rem\\'}\"></nested>',\n          components: {\n            nested: {\n              template: '<div></div>'\n            }\n          }\n        }\n      }\n    }, result => {\n      expect(result).toContain(\n        '<div data-server-rendered=\"true\" style=\"text-align:left;font-size:520rem;color:red;\"></div>'\n      )\n      done()\n    })\n  })\n\n  it('component style not passed to child', done => {\n    renderVmWithOptions({\n      template: '<comp :style=\"style\"></comp>',\n      data: {\n        style: 'color:red'\n      },\n      components: {\n        comp: {\n          template: '<div><div></div></div>'\n        }\n      }\n    }, result => {\n      expect(result).toContain(\n        '<div data-server-rendered=\"true\" style=\"color:red;\"><div></div></div>'\n      )\n      done()\n    })\n  })\n\n  it('component style not passed to slot', done => {\n    renderVmWithOptions({\n      template: '<comp :style=\"style\"><span style=\"color:black\"></span></comp>',\n      data: {\n        style: 'color:red'\n      },\n      components: {\n        comp: {\n          template: '<div><slot></slot></div>'\n        }\n      }\n    }, result => {\n      expect(result).toContain(\n        '<div data-server-rendered=\"true\" style=\"color:red;\"><span style=\"color:black;\"></span></div>'\n      )\n      done()\n    })\n  })\n\n  it('attrs merging on components', done => {\n    const Test = {\n      render: h => h('div', {\n        attrs: { id: 'a' }\n      })\n    }\n    renderVmWithOptions({\n      render: h => h(Test, {\n        attrs: { id: 'b', name: 'c' }\n      })\n    }, res => {\n      expect(res).toContain(\n        '<div id=\"b\" data-server-rendered=\"true\" name=\"c\"></div>'\n      )\n      done()\n    })\n  })\n\n  it('domProps merging on components', done => {\n    const Test = {\n      render: h => h('div', {\n        domProps: { innerHTML: 'a' }\n      })\n    }\n    renderVmWithOptions({\n      render: h => h(Test, {\n        domProps: { innerHTML: 'b', value: 'c' }\n      })\n    }, res => {\n      expect(res).toContain(\n        '<div data-server-rendered=\"true\" value=\"c\">b</div>'\n      )\n      done()\n    })\n  })\n\n  it('v-show directive render', done => {\n    renderVmWithOptions({\n      template: '<div v-show=\"false\"><span>inner</span></div>'\n    }, res => {\n      expect(res).toContain(\n        '<div data-server-rendered=\"true\" style=\"display:none;\"><span>inner</span></div>'\n      )\n      done()\n    })\n  })\n\n  it('v-show directive merge with style', done => {\n    renderVmWithOptions({\n      template: '<div :style=\"[{lineHeight: 1}]\" v-show=\"false\"><span>inner</span></div>'\n    }, res => {\n      expect(res).toContain(\n        '<div data-server-rendered=\"true\" style=\"line-height:1;display:none;\"><span>inner</span></div>'\n      )\n      done()\n    })\n  })\n\n  it('v-show directive not passed to child', done => {\n    renderVmWithOptions({\n      template: '<foo v-show=\"false\"></foo>',\n      components: {\n        foo: {\n          template: '<div><span>inner</span></div>'\n        }\n      }\n    }, res => {\n      expect(res).toContain(\n        '<div data-server-rendered=\"true\" style=\"display:none;\"><span>inner</span></div>'\n      )\n      done()\n    })\n  })\n\n  it('v-show directive not passed to slot', done => {\n    renderVmWithOptions({\n      template: '<foo v-show=\"false\"><span>inner</span></foo>',\n      components: {\n        foo: {\n          template: '<div><slot></slot></div>'\n        }\n      }\n    }, res => {\n      expect(res).toContain(\n        '<div data-server-rendered=\"true\" style=\"display:none;\"><span>inner</span></div>'\n      )\n      done()\n    })\n  })\n\n  it('v-show directive merging on components', done => {\n    renderVmWithOptions({\n      template: '<foo v-show=\"false\"></foo>',\n      components: {\n        foo: {\n          render: h => h('bar', {\n            directives: [{\n              name: 'show',\n              value: true\n            }]\n          }),\n          components: {\n            bar: {\n              render: h => h('div', 'inner')\n            }\n          }\n        }\n      }\n    }, res => {\n      expect(res).toContain(\n        '<div data-server-rendered=\"true\" style=\"display:none;\">inner</div>'\n      )\n      done()\n    })\n  })\n\n  it('text interpolation', done => {\n    renderVmWithOptions({\n      template: '<div>{{ foo }} side {{ bar }}</div>',\n      data: {\n        foo: 'server',\n        bar: '<span>rendering</span>'\n      }\n    }, result => {\n      expect(result).toContain('<div data-server-rendered=\"true\">server side &lt;span&gt;rendering&lt;/span&gt;</div>')\n      done()\n    })\n  })\n\n  it('v-html on root', done => {\n    renderVmWithOptions({\n      template: '<div v-html=\"text\"></div>',\n      data: {\n        text: '<span>foo</span>'\n      }\n    }, result => {\n      expect(result).toContain('<div data-server-rendered=\"true\"><span>foo</span></div>')\n      done()\n    })\n  })\n\n  it('v-text on root', done => {\n    renderVmWithOptions({\n      template: '<div v-text=\"text\"></div>',\n      data: {\n        text: '<span>foo</span>'\n      }\n    }, result => {\n      expect(result).toContain('<div data-server-rendered=\"true\">&lt;span&gt;foo&lt;/span&gt;</div>')\n      done()\n    })\n  })\n\n  it('v-html', done => {\n    renderVmWithOptions({\n      template: '<div><div v-html=\"text\"></div></div>',\n      data: {\n        text: '<span>foo</span>'\n      }\n    }, result => {\n      expect(result).toContain('<div data-server-rendered=\"true\"><div><span>foo</span></div></div>')\n      done()\n    })\n  })\n\n  it('v-html with null value', done => {\n    renderVmWithOptions({\n      template: '<div><div v-html=\"text\"></div></div>',\n      data: {\n        text: null\n      }\n    }, result => {\n      expect(result).toContain('<div data-server-rendered=\"true\"><div></div></div>')\n      done()\n    })\n  })\n\n  it('v-text', done => {\n    renderVmWithOptions({\n      template: '<div><div v-text=\"text\"></div></div>',\n      data: {\n        text: '<span>foo</span>'\n      }\n    }, result => {\n      expect(result).toContain('<div data-server-rendered=\"true\"><div>&lt;span&gt;foo&lt;/span&gt;</div></div>')\n      done()\n    })\n  })\n\n  it('v-text with null value', done => {\n    renderVmWithOptions({\n      template: '<div><div v-text=\"text\"></div></div>',\n      data: {\n        text: null\n      }\n    }, result => {\n      expect(result).toContain('<div data-server-rendered=\"true\"><div></div></div>')\n      done()\n    })\n  })\n\n  it('child component (hoc)', done => {\n    renderVmWithOptions({\n      template: '<child class=\"foo\" :msg=\"msg\"></child>',\n      data: {\n        msg: 'hello'\n      },\n      components: {\n        child: {\n          props: ['msg'],\n          data () {\n            return { name: 'bar' }\n          },\n          render () {\n            const h = this.$createElement\n            return h('div', { class: ['bar'] }, [`${this.msg} ${this.name}`])\n          }\n        }\n      }\n    }, result => {\n      expect(result).toContain('<div data-server-rendered=\"true\" class=\"foo bar\">hello bar</div>')\n      done()\n    })\n  })\n\n  it('has correct lifecycle during render', done => {\n    let lifecycleCount = 1\n    renderVmWithOptions({\n      template: '<div><span>{{ val }}</span><test></test></div>',\n      data: {\n        val: 'hi'\n      },\n      beforeCreate () {\n        expect(lifecycleCount++).toBe(1)\n      },\n      created () {\n        this.val = 'hello'\n        expect(this.val).toBe('hello')\n        expect(lifecycleCount++).toBe(2)\n      },\n      components: {\n        test: {\n          beforeCreate () {\n            expect(lifecycleCount++).toBe(3)\n          },\n          created () {\n            expect(lifecycleCount++).toBe(4)\n          },\n          render () {\n            expect(lifecycleCount++).toBeGreaterThan(4)\n            return this.$createElement('span', { class: ['b'] }, 'testAsync')\n          }\n        }\n      }\n    }, result => {\n      expect(result).toContain(\n        '<div data-server-rendered=\"true\">' +\n          '<span>hello</span>' +\n          '<span class=\"b\">testAsync</span>' +\n        '</div>'\n      )\n      done()\n    })\n  })\n\n  it('computed properties', done => {\n    renderVmWithOptions({\n      template: '<div>{{ b }}</div>',\n      data: {\n        a: {\n          b: 1\n        }\n      },\n      computed: {\n        b () {\n          return this.a.b + 1\n        }\n      },\n      created () {\n        this.a.b = 2\n        expect(this.b).toBe(3)\n      }\n    }, result => {\n      expect(result).toContain('<div data-server-rendered=\"true\">3</div>')\n      done()\n    })\n  })\n\n  it('renders async component', done => {\n    renderVmWithOptions({\n      template: `\n        <div>\n          <test-async></test-async>\n        </div>\n      `,\n      components: {\n        testAsync (resolve) {\n          setTimeout(() => resolve({\n            render () {\n              return this.$createElement('span', { class: ['b'] }, 'testAsync')\n            }\n          }), 1)\n        }\n      }\n    }, result => {\n      expect(result).toContain('<div data-server-rendered=\"true\"><span class=\"b\">testAsync</span></div>')\n      done()\n    })\n  })\n\n  it('renders async component (Promise, nested)', done => {\n    const Foo = () => Promise.resolve({\n      render: h => h('div', [h('span', 'foo'), h(Bar)])\n    })\n    const Bar = () => ({\n      component: Promise.resolve({\n        render: h => h('span', 'bar')\n      })\n    })\n    renderVmWithOptions({\n      render: h => h(Foo)\n    }, res => {\n      expect(res).toContain(`<div data-server-rendered=\"true\"><span>foo</span><span>bar</span></div>`)\n      done()\n    })\n  })\n\n  it('renders async component (ES module)', done => {\n    const Foo = () => Promise.resolve({\n      __esModule: true,\n      default: {\n        render: h => h('div', [h('span', 'foo'), h(Bar)])\n      }\n    })\n    const Bar = () => ({\n      component: Promise.resolve({\n        __esModule: true,\n        default: {\n          render: h => h('span', 'bar')\n        }\n      })\n    })\n    renderVmWithOptions({\n      render: h => h(Foo)\n    }, res => {\n      expect(res).toContain(`<div data-server-rendered=\"true\"><span>foo</span><span>bar</span></div>`)\n      done()\n    })\n  })\n\n  it('renders async component (hoc)', done => {\n    renderVmWithOptions({\n      template: '<test-async></test-async>',\n      components: {\n        testAsync: () => Promise.resolve({\n          render () {\n            return this.$createElement('span', { class: ['b'] }, 'testAsync')\n          }\n        })\n      }\n    }, result => {\n      expect(result).toContain('<span data-server-rendered=\"true\" class=\"b\">testAsync</span>')\n      done()\n    })\n  })\n\n  it('renders async component (functional, single node)', done => {\n    renderVmWithOptions({\n      template: `\n        <div>\n          <test-async></test-async>\n        </div>\n      `,\n      components: {\n        testAsync (resolve) {\n          setTimeout(() => resolve({\n            functional: true,\n            render (h) {\n              return h('span', { class: ['b'] }, 'testAsync')\n            }\n          }), 1)\n        }\n      }\n    }, result => {\n      expect(result).toContain('<div data-server-rendered=\"true\"><span class=\"b\">testAsync</span></div>')\n      done()\n    })\n  })\n\n  it('renders async component (functional, multiple nodes)', done => {\n    renderVmWithOptions({\n      template: `\n        <div>\n          <test-async></test-async>\n        </div>\n      `,\n      components: {\n        testAsync (resolve) {\n          setTimeout(() => resolve({\n            functional: true,\n            render (h) {\n              return [\n                h('span', { class: ['a'] }, 'foo'),\n                h('span', { class: ['b'] }, 'bar')\n              ]\n            }\n          }), 1)\n        }\n      }\n    }, result => {\n      expect(result).toContain(\n        '<div data-server-rendered=\"true\">' +\n          '<span class=\"a\">foo</span>' +\n          '<span class=\"b\">bar</span>' +\n        '</div>'\n      )\n      done()\n    })\n  })\n\n  it('should catch async component error', done => {\n    Vue.config.silent = true\n    renderToString(new Vue({\n      template: '<test-async></test-async>',\n      components: {\n        testAsync: () => Promise.resolve({\n          render () {\n            throw new Error('foo')\n          }\n        })\n      }\n    }), (err, result) => {\n      Vue.config.silent = false\n      expect(err).toBeTruthy()\n      expect(result).toBeUndefined()\n      done()\n    })\n  })\n\n  it('everything together', done => {\n    renderVmWithOptions({\n      template: `\n        <div>\n          <p class=\"hi\">yoyo</p>\n          <div id=\"ho\" :class=\"{ red: isRed }\"></div>\n          <span>{{ test }}</span>\n          <input :value=\"test\">\n          <img :src=\"imageUrl\">\n          <test></test>\n          <test-async></test-async>\n        </div>\n      `,\n      data: {\n        test: 'hi',\n        isRed: true,\n        imageUrl: 'https://vuejs.org/images/logo.png'\n      },\n      components: {\n        test: {\n          render () {\n            return this.$createElement('div', { class: ['a'] }, 'test')\n          }\n        },\n        testAsync (resolve) {\n          resolve({\n            render () {\n              return this.$createElement('span', { class: ['b'] }, 'testAsync')\n            }\n          })\n        }\n      }\n    }, result => {\n      expect(result).toContain(\n        '<div data-server-rendered=\"true\">' +\n          '<p class=\"hi\">yoyo</p> ' +\n          '<div id=\"ho\" class=\"red\"></div> ' +\n          '<span>hi</span> ' +\n          '<input value=\"hi\"> ' +\n          '<img src=\"https://vuejs.org/images/logo.png\"> ' +\n          '<div class=\"a\">test</div> ' +\n          '<span class=\"b\">testAsync</span>' +\n        '</div>'\n      )\n      done()\n    })\n  })\n\n  it('normal attr', done => {\n    renderVmWithOptions({\n      template: `\n        <div>\n          <span :test=\"'ok'\">hello</span>\n          <span :test=\"null\">hello</span>\n          <span :test=\"false\">hello</span>\n          <span :test=\"true\">hello</span>\n          <span :test=\"0\">hello</span>\n        </div>\n      `\n    }, result => {\n      expect(result).toContain(\n        '<div data-server-rendered=\"true\">' +\n          '<span test=\"ok\">hello</span> ' +\n          '<span>hello</span> ' +\n          '<span>hello</span> ' +\n          '<span test=\"true\">hello</span> ' +\n          '<span test=\"0\">hello</span>' +\n        '</div>'\n      )\n      done()\n    })\n  })\n\n  it('enumerated attr', done => {\n    renderVmWithOptions({\n      template: `\n        <div>\n          <span :draggable=\"true\">hello</span>\n          <span :draggable=\"'ok'\">hello</span>\n          <span :draggable=\"null\">hello</span>\n          <span :draggable=\"false\">hello</span>\n          <span :draggable=\"''\">hello</span>\n          <span :draggable=\"'false'\">hello</span>\n        </div>\n      `\n    }, result => {\n      expect(result).toContain(\n        '<div data-server-rendered=\"true\">' +\n          '<span draggable=\"true\">hello</span> ' +\n          '<span draggable=\"true\">hello</span> ' +\n          '<span draggable=\"false\">hello</span> ' +\n          '<span draggable=\"false\">hello</span> ' +\n          '<span draggable=\"true\">hello</span> ' +\n          '<span draggable=\"false\">hello</span>' +\n        '</div>'\n      )\n      done()\n    })\n  })\n\n  it('boolean attr', done => {\n    renderVmWithOptions({\n      template: `\n        <div>\n          <span :disabled=\"true\">hello</span>\n          <span :disabled=\"'ok'\">hello</span>\n          <span :disabled=\"null\">hello</span>\n          <span :disabled=\"''\">hello</span>\n        </div>\n      `\n    }, result => {\n      expect(result).toContain(\n        '<div data-server-rendered=\"true\">' +\n          '<span disabled=\"disabled\">hello</span> ' +\n          '<span disabled=\"disabled\">hello</span> ' +\n          '<span>hello</span> ' +\n          '<span disabled=\"disabled\">hello</span>' +\n        '</div>'\n      )\n      done()\n    })\n  })\n\n  it('v-bind object', done => {\n    renderVmWithOptions({\n      data: {\n        test: { id: 'a', class: ['a', 'b'], value: 'c' }\n      },\n      template: '<input v-bind=\"test\">'\n    }, result => {\n      expect(result).toContain('<input id=\"a\" data-server-rendered=\"true\" value=\"c\" class=\"a b\">')\n      done()\n    })\n  })\n\n  it('custom directives', done => {\n    const renderer = createRenderer({\n      directives: {\n        'class-prefixer': (node, dir) => {\n          if (node.data.class) {\n            node.data.class = `${dir.value}-${node.data.class}`\n          }\n          if (node.data.staticClass) {\n            node.data.staticClass = `${dir.value}-${node.data.staticClass}`\n          }\n        }\n      }\n    })\n    renderer.renderToString(new Vue({\n      render () {\n        const h = this.$createElement\n        return h('p', {\n          class: 'class1',\n          staticClass: 'class2',\n          directives: [{\n            name: 'class-prefixer',\n            value: 'my'\n          }]\n        }, ['hello world'])\n      }\n    }), (err, result) => {\n      expect(err).toBeNull()\n      expect(result).toContain('<p data-server-rendered=\"true\" class=\"my-class2 my-class1\">hello world</p>')\n      done()\n    })\n  })\n\n  it('_scopeId', done => {\n    renderVmWithOptions({\n      _scopeId: '_v-parent',\n      template: '<div id=\"foo\"><p><child></child></p></div>',\n      components: {\n        child: {\n          _scopeId: '_v-child',\n          render () {\n            const h = this.$createElement\n            return h('div', null, [h('span', null, ['foo'])])\n          }\n        }\n      }\n    }, result => {\n      expect(result).toContain(\n        '<div id=\"foo\" data-server-rendered=\"true\" _v-parent>' +\n          '<p _v-parent>' +\n            '<div _v-child _v-parent><span _v-child>foo</span></div>' +\n          '</p>' +\n        '</div>'\n      )\n      done()\n    })\n  })\n\n  it('_scopeId on slot content', done => {\n    renderVmWithOptions({\n      _scopeId: '_v-parent',\n      template: '<div><child><p>foo</p></child></div>',\n      components: {\n        child: {\n          _scopeId: '_v-child',\n          render () {\n            const h = this.$createElement\n            return h('div', null, this.$slots.default)\n          }\n        }\n      }\n    }, result => {\n      expect(result).toContain(\n        '<div data-server-rendered=\"true\" _v-parent>' +\n          '<div _v-child _v-parent><p _v-child _v-parent>foo</p></div>' +\n        '</div>'\n      )\n      done()\n    })\n  })\n\n  it('comment nodes', done => {\n    renderVmWithOptions({\n      template: '<div><transition><div v-if=\"false\"></div></transition></div>'\n    }, result => {\n      expect(result).toContain(`<div data-server-rendered=\"true\"><!----></div>`)\n      done()\n    })\n  })\n\n  it('should catch error', done => {\n    Vue.config.silent = true\n    renderToString(new Vue({\n      render () {\n        throw new Error('oops')\n      }\n    }), err => {\n      expect(err instanceof Error).toBe(true)\n      Vue.config.silent = false\n      done()\n    })\n  })\n\n  it('default value Foreign Function', () => {\n    const FunctionConstructor = VM.runInNewContext('Function')\n    const func = () => 123\n    const vm = new Vue({\n      props: {\n        a: {\n          type: FunctionConstructor,\n          default: func\n        }\n      },\n      propsData: {\n        a: undefined\n      }\n    })\n    expect(vm.a).toBe(func)\n  })\n\n  it('should prevent xss in attributes', done => {\n    renderVmWithOptions({\n      data: {\n        xss: '\"><script>alert(1)</script>'\n      },\n      template: `\n        <div>\n          <a :title=\"xss\" :style=\"{ color: xss }\" :class=\"[xss]\">foo</a>\n        </div>\n      `\n    }, res => {\n      expect(res).not.toContain(`<script>alert(1)</script>`)\n      done()\n    })\n  })\n\n  it('should prevent script xss with v-bind object syntax + array value', done => {\n    renderVmWithOptions({\n      data: {\n        test: ['\"><script>alert(1)</script><!--\"']\n      },\n      template: `<div v-bind=\"{ test }\"></div>`\n    }, res => {\n      expect(res).not.toContain(`<script>alert(1)</script>`)\n      done()\n    })\n  })\n\n  it('v-if', done => {\n    renderVmWithOptions({\n      template: `\n        <div>\n          <span v-if=\"true\">foo</span>\n          <span v-if=\"false\">bar</span>\n        </div>\n      `\n    }, res => {\n      expect(res).toContain(`<div data-server-rendered=\"true\"><span>foo</span> <!----></div>`)\n      done()\n    })\n  })\n\n  it('v-for', done => {\n    renderVmWithOptions({\n      template: `\n        <div>\n          <span>foo</span>\n          <span v-for=\"i in 2\">{{ i }}</span>\n        </div>\n      `\n    }, res => {\n      expect(res).toContain(`<div data-server-rendered=\"true\"><span>foo</span> <span>1</span><span>2</span></div>`)\n      done()\n    })\n  })\n\n  it('template v-if', done => {\n    renderVmWithOptions({\n      template: `\n        <div>\n          <span>foo</span>\n          <template v-if=\"true\">\n            <span>foo</span> bar <span>baz</span>\n          </template>\n        </div>\n      `\n    }, res => {\n      expect(res).toContain(`<div data-server-rendered=\"true\"><span>foo</span> <span>foo</span> bar <span>baz</span></div>`)\n      done()\n    })\n  })\n\n  it('template v-for', done => {\n    renderVmWithOptions({\n      template: `\n        <div>\n          <span>foo</span>\n          <template v-for=\"i in 2\">\n            <span>{{ i }}</span><span>bar</span>\n          </template>\n        </div>\n      `\n    }, res => {\n      expect(res).toContain(`<div data-server-rendered=\"true\"><span>foo</span> <span>1</span><span>bar</span><span>2</span><span>bar</span></div>`)\n      done()\n    })\n  })\n\n  it('with inheritAttrs: false + $attrs', done => {\n    renderVmWithOptions({\n      template: `<foo id=\"a\"/>`,\n      components: {\n        foo: {\n          inheritAttrs: false,\n          template: `<div><div v-bind=\"$attrs\"></div></div>`\n        }\n      }\n    }, res => {\n      expect(res).toBe(`<div data-server-rendered=\"true\"><div id=\"a\"></div></div>`)\n      done()\n    })\n  })\n\n  it('should escape static strings', done => {\n    renderVmWithOptions({\n      template: `<div>&lt;foo&gt;</div>`\n    }, res => {\n      expect(res).toBe(`<div data-server-rendered=\"true\">&lt;foo&gt;</div>`)\n      done()\n    })\n  })\n\n  it('should not cache computed properties', done => {\n    renderVmWithOptions({\n      template: `<div>{{ foo }}</div>`,\n      data: () => ({ bar: 1 }),\n      computed: {\n        foo () { return this.bar + 1 }\n      },\n      created () {\n        this.foo // access\n        this.bar++ // trigger change\n      }\n    }, res => {\n      expect(res).toBe(`<div data-server-rendered=\"true\">3</div>`)\n      done()\n    })\n  })\n\n  it('return Promise', done => {\n    renderToString(new Vue({\n      template: `<div>{{ foo }}</div>`,\n      data: { foo: 'bar' }\n    })).then(res => {\n      expect(res).toBe(`<div data-server-rendered=\"true\">bar</div>`)\n      done()\n    })\n  })\n\n  it('return Promise (error)', done => {\n    Vue.config.silent = true\n    renderToString(new Vue({\n      render () {\n        throw new Error('foobar')\n      }\n    })).catch(err => {\n      expect(err.toString()).toContain(`foobar`)\n      Vue.config.silent = false\n      done()\n    })\n  })\n\n  it('should catch template compilation error', done => {\n    renderToString(new Vue({\n      template: `<div></div><div></div>`\n    }), (err, res) => {\n      expect(err.toString()).toContain('Component template should contain exactly one root element')\n      done()\n    })\n  })\n\n  // #6907\n  it('should not optimize root if conditions', done => {\n    renderVmWithOptions({\n      data: { foo: 123 },\n      template: `<input :type=\"'text'\" v-model=\"foo\">`\n    }, res => {\n      expect(res).toBe(`<input type=\"text\" data-server-rendered=\"true\" value=\"123\">`)\n      done()\n    })\n  })\n\n  it('render muted properly', done => {\n    renderVmWithOptions({\n      template: '<video muted></video>'\n    }, result => {\n      expect(result).toContain('<video muted=\"muted\" data-server-rendered=\"true\"></video>')\n      done()\n    })\n  })\n\n  it('render v-model with textarea', done => {\n    renderVmWithOptions({\n      data: { foo: 'bar' },\n      template: '<div><textarea v-model=\"foo\"></textarea></div>'\n    }, result => {\n      expect(result).toContain('<textarea>bar</textarea>')\n      done()\n    })\n  })\n\n  it('render v-model with textarea (non-optimized)', done => {\n    renderVmWithOptions({\n      render (h) {\n        return h('textarea', {\n          domProps: {\n            value: 'foo'\n          }\n        })\n      }\n    }, result => {\n      expect(result).toContain('<textarea data-server-rendered=\"true\">foo</textarea>')\n      done()\n    })\n  })\n\n  it('render v-model with <select> (value binding)', done => {\n    renderVmWithOptions({\n      data: {\n        selected: 2,\n        options: [\n          { id: 1, label: 'one' },\n          { id: 2, label: 'two' }\n        ]\n      },\n      template: `\n      <div>\n        <select v-model=\"selected\">\n          <option v-for=\"o in options\" :value=\"o.id\">{{ o.label }}</option>\n        </select>\n      </div>\n      `\n    }, result => {\n      expect(result).toContain(\n        '<select>' +\n          '<option value=\"1\">one</option>' +\n          '<option selected=\"selected\" value=\"2\">two</option>' +\n        '</select>'\n      )\n      done()\n    })\n  })\n\n  it('render v-model with <select> (static value)', done => {\n    renderVmWithOptions({\n      data: {\n        selected: 2\n      },\n      template: `\n      <div>\n        <select v-model=\"selected\">\n          <option value=\"1\">one</option>\n          <option value=\"2\">two</option>\n        </select>\n      </div>\n      `\n    }, result => {\n      expect(result).toContain(\n        '<select>' +\n          '<option value=\"1\">one</option> ' +\n          '<option value=\"2\" selected=\"selected\">two</option>' +\n        '</select>'\n      )\n      done()\n    })\n  })\n\n  it('render v-model with <select> (text as value)', done => {\n    renderVmWithOptions({\n      data: {\n        selected: 2,\n        options: [\n          { id: 1, label: 'one' },\n          { id: 2, label: 'two' }\n        ]\n      },\n      template: `\n      <div>\n        <select v-model=\"selected\">\n          <option v-for=\"o in options\">{{ o.id }}</option>\n        </select>\n      </div>\n      `\n    }, result => {\n      expect(result).toContain(\n        '<select>' +\n          '<option>1</option>' +\n          '<option selected=\"selected\">2</option>' +\n        '</select>'\n      )\n      done()\n    })\n  })\n\n  // #7223\n  it('should not double escape attribute values', done => {\n    renderVmWithOptions({\n      template: `\n      <div>\n        <div id=\"a\\nb\"></div>\n      </div>\n      `\n    }, result => {\n      expect(result).toContain(`<div id=\"a\\nb\"></div>`)\n      done()\n    })\n  })\n\n  it('should expose ssr helpers on functional context', done => {\n    let called = false\n    renderVmWithOptions({\n      template: `<div><foo/></div>`,\n      components: {\n        foo: {\n          functional: true,\n          render (h, ctx) {\n            expect(ctx._ssrNode).toBeTruthy()\n            called = true\n          }\n        }\n      }\n    }, () => {\n      expect(called).toBe(true)\n      done()\n    })\n  })\n})\n\nfunction renderVmWithOptions (options, cb) {\n  renderToString(new Vue(options), (err, res) => {\n    expect(err).toBeNull()\n    cb(res)\n  })\n}\n"
  },
  {
    "path": "vue/test/ssr/ssr-template.spec.js",
    "content": "import webpack from 'webpack'\nimport Vue from '../../dist/vue.runtime.common.js'\nimport { compileWithWebpack } from './compile-with-webpack'\nimport { createRenderer } from '../../packages/vue-server-renderer'\nimport VueSSRClientPlugin from '../../packages/vue-server-renderer/client-plugin'\nimport { createRenderer as createBundleRenderer } from './ssr-bundle-render.spec.js'\n\nconst defaultTemplate = `<html><head></head><body><!--vue-ssr-outlet--></body></html>`\nconst interpolateTemplate = `<html><head><title>{{ title }}</title></head><body><!--vue-ssr-outlet-->{{{ snippet }}}</body></html>`\n\nfunction generateClientManifest (file, cb) {\n  compileWithWebpack(file, {\n    output: {\n      path: '/',\n      filename: '[name].js'\n    },\n    plugins: [\n      new webpack.optimize.CommonsChunkPlugin({\n        name: 'manifest',\n        minChunks: Infinity\n      }),\n      new VueSSRClientPlugin()\n    ]\n  }, fs => {\n    cb(JSON.parse(fs.readFileSync('/vue-ssr-client-manifest.json', 'utf-8')))\n  })\n}\n\nfunction createRendererWithManifest (file, options, cb) {\n  if (typeof options === 'function') {\n    cb = options\n    options = null\n  }\n  generateClientManifest(file, clientManifest => {\n    createBundleRenderer(file, Object.assign({\n      asBundle: true,\n      template: defaultTemplate,\n      clientManifest\n    }, options), cb)\n  })\n}\n\ndescribe('SSR: template option', () => {\n  it('renderToString', done => {\n    const renderer = createRenderer({\n      template: defaultTemplate\n    })\n\n    const context = {\n      head: '<meta name=\"viewport\" content=\"width=device-width\">',\n      styles: '<style>h1 { color: red }</style>',\n      state: { a: 1 }\n    }\n\n    renderer.renderToString(new Vue({\n      template: '<div>hi</div>'\n    }), context, (err, res) => {\n      expect(err).toBeNull()\n      expect(res).toContain(\n        `<html><head>${context.head}${context.styles}</head><body>` +\n        `<div data-server-rendered=\"true\">hi</div>` +\n        `<script>window.__INITIAL_STATE__={\"a\":1}</script>` +\n        `</body></html>`\n      )\n      done()\n    })\n  })\n\n  it('renderToString with interpolation', done => {\n    const renderer = createRenderer({\n      template: interpolateTemplate\n    })\n\n    const context = {\n      title: '<script>hacks</script>',\n      snippet: '<div>foo</div>',\n      head: '<meta name=\"viewport\" content=\"width=device-width\">',\n      styles: '<style>h1 { color: red }</style>',\n      state: { a: 1 }\n    }\n\n    renderer.renderToString(new Vue({\n      template: '<div>hi</div>'\n    }), context, (err, res) => {\n      expect(err).toBeNull()\n      expect(res).toContain(\n        `<html><head>` +\n        // double mustache should be escaped\n        `<title>&lt;script&gt;hacks&lt;/script&gt;</title>` +\n        `${context.head}${context.styles}</head><body>` +\n        `<div data-server-rendered=\"true\">hi</div>` +\n        `<script>window.__INITIAL_STATE__={\"a\":1}</script>` +\n        // triple should be raw\n        `<div>foo</div>` +\n        `</body></html>`\n      )\n      done()\n    })\n  })\n\n  it('renderToStream', done => {\n    const renderer = createRenderer({\n      template: defaultTemplate\n    })\n\n    const context = {\n      head: '<meta name=\"viewport\" content=\"width=device-width\">',\n      styles: '<style>h1 { color: red }</style>',\n      state: { a: 1 }\n    }\n\n    const stream = renderer.renderToStream(new Vue({\n      template: '<div>hi</div>'\n    }), context)\n\n    let res = ''\n    stream.on('data', chunk => {\n      res += chunk\n    })\n    stream.on('end', () => {\n      expect(res).toContain(\n        `<html><head>${context.head}${context.styles}</head><body>` +\n        `<div data-server-rendered=\"true\">hi</div>` +\n        `<script>window.__INITIAL_STATE__={\"a\":1}</script>` +\n        `</body></html>`\n      )\n      done()\n    })\n  })\n\n  it('renderToStream with interpolation', done => {\n    const renderer = createRenderer({\n      template: interpolateTemplate\n    })\n\n    const context = {\n      title: '<script>hacks</script>',\n      snippet: '<div>foo</div>',\n      head: '<meta name=\"viewport\" content=\"width=device-width\">',\n      styles: '<style>h1 { color: red }</style>',\n      state: { a: 1 }\n    }\n\n    const stream = renderer.renderToStream(new Vue({\n      template: '<div>hi</div>'\n    }), context)\n\n    let res = ''\n    stream.on('data', chunk => {\n      res += chunk\n    })\n    stream.on('end', () => {\n      expect(res).toContain(\n        `<html><head>` +\n        // double mustache should be escaped\n        `<title>&lt;script&gt;hacks&lt;/script&gt;</title>` +\n        `${context.head}${context.styles}</head><body>` +\n        `<div data-server-rendered=\"true\">hi</div>` +\n        `<script>window.__INITIAL_STATE__={\"a\":1}</script>` +\n        // triple should be raw\n        `<div>foo</div>` +\n        `</body></html>`\n      )\n      done()\n    })\n  })\n\n  it('bundleRenderer + renderToString', done => {\n    createBundleRenderer('app.js', {\n      asBundle: true,\n      template: defaultTemplate\n    }, renderer => {\n      const context = {\n        head: '<meta name=\"viewport\" content=\"width=device-width\">',\n        styles: '<style>h1 { color: red }</style>',\n        state: { a: 1 },\n        url: '/test'\n      }\n      renderer.renderToString(context, (err, res) => {\n        expect(err).toBeNull()\n        expect(res).toContain(\n          `<html><head>${context.head}${context.styles}</head><body>` +\n          `<div data-server-rendered=\"true\">/test</div>` +\n          `<script>window.__INITIAL_STATE__={\"a\":1}</script>` +\n          `</body></html>`\n        )\n        expect(context.msg).toBe('hello')\n        done()\n      })\n    })\n  })\n\n  it('bundleRenderer + renderToStream', done => {\n    createBundleRenderer('app.js', {\n      asBundle: true,\n      template: defaultTemplate\n    }, renderer => {\n      const context = {\n        head: '<meta name=\"viewport\" content=\"width=device-width\">',\n        styles: '<style>h1 { color: red }</style>',\n        state: { a: 1 },\n        url: '/test'\n      }\n      const stream = renderer.renderToStream(context)\n      let res = ''\n      stream.on('data', chunk => {\n        res += chunk.toString()\n      })\n      stream.on('end', () => {\n        expect(res).toContain(\n          `<html><head>${context.head}${context.styles}</head><body>` +\n          `<div data-server-rendered=\"true\">/test</div>` +\n          `<script>window.__INITIAL_STATE__={\"a\":1}</script>` +\n          `</body></html>`\n        )\n        expect(context.msg).toBe('hello')\n        done()\n      })\n    })\n  })\n\n  const expectedHTMLWithManifest = (options = {}) =>\n    `<html><head>` +\n      // used chunks should have preload\n      `<link rel=\"preload\" href=\"/manifest.js\" as=\"script\">` +\n      `<link rel=\"preload\" href=\"/main.js\" as=\"script\">` +\n      `<link rel=\"preload\" href=\"/0.js\" as=\"script\">` +\n      `<link rel=\"preload\" href=\"/test.css\" as=\"style\">` +\n      // images and fonts are only preloaded when explicitly asked for\n      (options.preloadOtherAssets ? `<link rel=\"preload\" href=\"/test.woff2\" as=\"font\" type=\"font/woff2\" crossorigin>` : ``) +\n      (options.preloadOtherAssets ? `<link rel=\"preload\" href=\"/test.png\" as=\"image\">` : ``) +\n      // unused chunks should have prefetch\n      (options.noPrefetch ? `` : `<link rel=\"prefetch\" href=\"/1.js\">`) +\n      // css assets should be loaded\n      `<link rel=\"stylesheet\" href=\"/test.css\">` +\n    `</head><body>` +\n      `<div data-server-rendered=\"true\"><div>async test.woff2 test.png</div></div>` +\n      // state should be inlined before scripts\n      `<script>window.${options.stateKey || '__INITIAL_STATE__'}={\"a\":1}</script>` +\n      // manifest chunk should be first\n      `<script src=\"/manifest.js\" defer></script>` +\n      // async chunks should be before main chunk\n      `<script src=\"/0.js\" defer></script>` +\n      `<script src=\"/main.js\" defer></script>` +\n    `</body></html>`\n\n  createClientManifestAssertions(true)\n  createClientManifestAssertions(false)\n\n  function createClientManifestAssertions (runInNewContext) {\n    it('bundleRenderer + renderToString + clientManifest ()', done => {\n      createRendererWithManifest('split.js', { runInNewContext }, renderer => {\n        renderer.renderToString({ state: { a: 1 }}, (err, res) => {\n          expect(err).toBeNull()\n          expect(res).toContain(expectedHTMLWithManifest())\n          done()\n        })\n      })\n    })\n\n    it('bundleRenderer + renderToStream + clientManifest + shouldPreload', done => {\n      createRendererWithManifest('split.js', {\n        runInNewContext,\n        shouldPreload: (file, type) => {\n          if (type === 'image' || type === 'script' || type === 'font' || type === 'style') {\n            return true\n          }\n        }\n      }, renderer => {\n        const stream = renderer.renderToStream({ state: { a: 1 }})\n        let res = ''\n        stream.on('data', chunk => {\n          res += chunk.toString()\n        })\n        stream.on('end', () => {\n          expect(res).toContain(expectedHTMLWithManifest({\n            preloadOtherAssets: true\n          }))\n          done()\n        })\n      })\n    })\n\n    it('bundleRenderer + renderToStream + clientManifest + shouldPrefetch', done => {\n      createRendererWithManifest('split.js', {\n        runInNewContext,\n        shouldPrefetch: (file, type) => {\n          if (type === 'script') {\n            return false\n          }\n        }\n      }, renderer => {\n        const stream = renderer.renderToStream({ state: { a: 1 }})\n        let res = ''\n        stream.on('data', chunk => {\n          res += chunk.toString()\n        })\n        stream.on('end', () => {\n          expect(res).toContain(expectedHTMLWithManifest({\n            noPrefetch: true\n          }))\n          done()\n        })\n      })\n    })\n\n    it('bundleRenderer + renderToString + clientManifest + inject: false', done => {\n      createRendererWithManifest('split.js', {\n        runInNewContext,\n        template: `<html>` +\n          `<head>{{{ renderResourceHints() }}}{{{ renderStyles() }}}</head>` +\n          `<body><!--vue-ssr-outlet-->{{{ renderState({ windowKey: '__FOO__', contextKey: 'foo' }) }}}{{{ renderScripts() }}}</body>` +\n        `</html>`,\n        inject: false\n      }, renderer => {\n        const context = { foo: { a: 1 }}\n        renderer.renderToString(context, (err, res) => {\n          expect(err).toBeNull()\n          expect(res).toContain(expectedHTMLWithManifest({\n            stateKey: '__FOO__'\n          }))\n          done()\n        })\n      })\n    })\n\n    it('bundleRenderer + renderToString + clientManifest + no template', done => {\n      createRendererWithManifest('split.js', {\n        runInNewContext,\n        template: null\n      }, renderer => {\n        const context = { foo: { a: 1 }}\n        renderer.renderToString(context, (err, res) => {\n          expect(err).toBeNull()\n\n          const customOutput =\n            `<html><head>${\n              context.renderResourceHints() +\n              context.renderStyles()\n            }</head><body>${\n              res +\n              context.renderState({\n                windowKey: '__FOO__',\n                contextKey: 'foo'\n              }) +\n              context.renderScripts()\n            }</body></html>`\n\n          expect(customOutput).toContain(expectedHTMLWithManifest({\n            stateKey: '__FOO__'\n          }))\n          done()\n        })\n      })\n    })\n\n    it('whitespace insensitive interpolation', done => {\n      const interpolateTemplate = `<html><head><title>{{title}}</title></head><body><!--vue-ssr-outlet-->{{{snippet}}}</body></html>`\n      const renderer = createRenderer({\n        template: interpolateTemplate\n      })\n\n      const context = {\n        title: '<script>hacks</script>',\n        snippet: '<div>foo</div>',\n        head: '<meta name=\"viewport\" content=\"width=device-width\">',\n        styles: '<style>h1 { color: red }</style>',\n        state: { a: 1 }\n      }\n\n      renderer.renderToString(new Vue({\n        template: '<div>hi</div>'\n      }), context, (err, res) => {\n        expect(err).toBeNull()\n        expect(res).toContain(\n          `<html><head>` +\n          // double mustache should be escaped\n          `<title>&lt;script&gt;hacks&lt;/script&gt;</title>` +\n          `${context.head}${context.styles}</head><body>` +\n          `<div data-server-rendered=\"true\">hi</div>` +\n          `<script>window.__INITIAL_STATE__={\"a\":1}</script>` +\n          // triple should be raw\n          `<div>foo</div>` +\n          `</body></html>`\n        )\n        done()\n      })\n    })\n  }\n})\n"
  },
  {
    "path": "vue/test/unit/.eslintrc",
    "content": "{\n  \"env\": {\n    \"jasmine\": true\n  },\n  \"globals\": {\n    \"waitForUpdate\": true,\n    \"triggerEvent\": true,\n    \"createTextVNode\": true\n  },\n  \"plugins\": [\"jasmine\"],\n  \"rules\": {\n    \"jasmine/no-focused-tests\": 2\n  }\n}\n"
  },
  {
    "path": "vue/test/unit/features/component/component-async.spec.js",
    "content": "import Vue from 'vue'\nimport { Promise } from 'es6-promise'\n\ndescribe('Component async', () => {\n  it('normal', done => {\n    const vm = new Vue({\n      template: '<div><test></test></div>',\n      components: {\n        test: (resolve) => {\n          setTimeout(() => {\n            resolve({\n              template: '<div>hi</div>'\n            })\n            // wait for parent update\n            Vue.nextTick(next)\n          }, 0)\n        }\n      }\n    }).$mount()\n    expect(vm.$el.innerHTML).toBe('<!---->')\n    expect(vm.$children.length).toBe(0)\n    function next () {\n      expect(vm.$el.innerHTML).toBe('<div>hi</div>')\n      expect(vm.$children.length).toBe(1)\n      done()\n    }\n  })\n\n  it('resolve ES module default', done => {\n    const vm = new Vue({\n      template: '<div><test></test></div>',\n      components: {\n        test: (resolve) => {\n          setTimeout(() => {\n            resolve({\n              __esModule: true,\n              default: {\n                template: '<div>hi</div>'\n              }\n            })\n            // wait for parent update\n            Vue.nextTick(next)\n          }, 0)\n        }\n      }\n    }).$mount()\n    expect(vm.$el.innerHTML).toBe('<!---->')\n    expect(vm.$children.length).toBe(0)\n    function next () {\n      expect(vm.$el.innerHTML).toBe('<div>hi</div>')\n      expect(vm.$children.length).toBe(1)\n      done()\n    }\n  })\n\n  it('as root', done => {\n    const vm = new Vue({\n      template: '<test></test>',\n      components: {\n        test: resolve => {\n          setTimeout(() => {\n            resolve({\n              template: '<div>hi</div>'\n            })\n            // wait for parent update\n            Vue.nextTick(next)\n          }, 0)\n        }\n      }\n    }).$mount()\n    expect(vm.$el.nodeType).toBe(8)\n    expect(vm.$children.length).toBe(0)\n    function next () {\n      expect(vm.$el.nodeType).toBe(1)\n      expect(vm.$el.outerHTML).toBe('<div>hi</div>')\n      expect(vm.$children.length).toBe(1)\n      done()\n    }\n  })\n\n  it('dynamic', done => {\n    var vm = new Vue({\n      template: '<component :is=\"view\"></component>',\n      data: {\n        view: 'view-a'\n      },\n      components: {\n        'view-a': resolve => {\n          setTimeout(() => {\n            resolve({\n              template: '<div>A</div>'\n            })\n            Vue.nextTick(step1)\n          }, 0)\n        },\n        'view-b': resolve => {\n          setTimeout(() => {\n            resolve({\n              template: '<p>B</p>'\n            })\n            Vue.nextTick(step2)\n          }, 0)\n        }\n      }\n    }).$mount()\n    var aCalled = false\n    function step1 () {\n      // ensure A is resolved only once\n      expect(aCalled).toBe(false)\n      aCalled = true\n      expect(vm.$el.tagName).toBe('DIV')\n      expect(vm.$el.textContent).toBe('A')\n      vm.view = 'view-b'\n    }\n    function step2 () {\n      expect(vm.$el.tagName).toBe('P')\n      expect(vm.$el.textContent).toBe('B')\n      vm.view = 'view-a'\n      waitForUpdate(function () {\n        expect(vm.$el.tagName).toBe('DIV')\n        expect(vm.$el.textContent).toBe('A')\n      }).then(done)\n    }\n  })\n\n  it('warn reject', () => {\n    new Vue({\n      template: '<test></test>',\n      components: {\n        test: (resolve, reject) => {\n          reject('nooooo')\n        }\n      }\n    }).$mount()\n    expect('Reason: nooooo').toHaveBeenWarned()\n  })\n\n  it('with v-for', done => {\n    const vm = new Vue({\n      template: '<div><test v-for=\"n in list\" :key=\"n\" :n=\"n\"></test></div>',\n      data: {\n        list: [1, 2, 3]\n      },\n      components: {\n        test: resolve => {\n          setTimeout(() => {\n            resolve({\n              props: ['n'],\n              template: '<div>{{n}}</div>'\n            })\n            Vue.nextTick(next)\n          }, 0)\n        }\n      }\n    }).$mount()\n    function next () {\n      expect(vm.$el.innerHTML).toBe('<div>1</div><div>2</div><div>3</div>')\n      done()\n    }\n  })\n\n  it('returning Promise', done => {\n    const vm = new Vue({\n      template: '<div><test></test></div>',\n      components: {\n        test: () => {\n          return new Promise(resolve => {\n            setTimeout(() => {\n              resolve({\n                template: '<div>hi</div>'\n              })\n              // wait for promise resolve and then parent update\n              Promise.resolve().then(() => {\n                Vue.nextTick(next)\n              })\n            }, 0)\n          })\n        }\n      }\n    }).$mount()\n    expect(vm.$el.innerHTML).toBe('<!---->')\n    expect(vm.$children.length).toBe(0)\n    function next () {\n      expect(vm.$el.innerHTML).toBe('<div>hi</div>')\n      expect(vm.$children.length).toBe(1)\n      done()\n    }\n  })\n\n  describe('loading/error/timeout', () => {\n    it('with loading component', done => {\n      const vm = new Vue({\n        template: `<div><test/></div>`,\n        components: {\n          test: () => ({\n            component: new Promise(resolve => {\n              setTimeout(() => {\n                resolve({ template: '<div>hi</div>' })\n                // wait for promise resolve and then parent update\n                Promise.resolve().then(() => {\n                  Vue.nextTick(next)\n                })\n              }, 50)\n            }),\n            loading: { template: `<div>loading</div>` },\n            delay: 1\n          })\n        }\n      }).$mount()\n\n      expect(vm.$el.innerHTML).toBe('<!---->')\n\n      let loadingAsserted = false\n      setTimeout(() => {\n        Vue.nextTick(() => {\n          loadingAsserted = true\n          expect(vm.$el.textContent).toBe('loading')\n        })\n      }, 1)\n\n      function next () {\n        expect(loadingAsserted).toBe(true)\n        expect(vm.$el.textContent).toBe('hi')\n        done()\n      }\n    })\n\n    it('with loading component (0 delay)', done => {\n      const vm = new Vue({\n        template: `<div><test/></div>`,\n        components: {\n          test: () => ({\n            component: new Promise(resolve => {\n              setTimeout(() => {\n                resolve({ template: '<div>hi</div>' })\n                // wait for promise resolve and then parent update\n                Promise.resolve().then(() => {\n                  Vue.nextTick(next)\n                })\n              }, 50)\n            }),\n            loading: { template: `<div>loading</div>` },\n            delay: 0\n          })\n        }\n      }).$mount()\n\n      expect(vm.$el.textContent).toBe('loading')\n\n      function next () {\n        expect(vm.$el.textContent).toBe('hi')\n        done()\n      }\n    })\n\n    it('with error component', done => {\n      const vm = new Vue({\n        template: `<div><test/></div>`,\n        components: {\n          test: () => ({\n            component: new Promise((resolve, reject) => {\n              setTimeout(() => {\n                reject()\n                // wait for promise resolve and then parent update\n                Promise.resolve().then(() => {\n                  Vue.nextTick(next)\n                })\n              }, 50)\n            }),\n            loading: { template: `<div>loading</div>` },\n            error: { template: `<div>error</div>` },\n            delay: 0\n          })\n        }\n      }).$mount()\n\n      expect(vm.$el.textContent).toBe('loading')\n\n      function next () {\n        expect(`Failed to resolve async component`).toHaveBeenWarned()\n        expect(vm.$el.textContent).toBe('error')\n        done()\n      }\n    })\n\n    it('with error component + timeout', done => {\n      const vm = new Vue({\n        template: `<div><test/></div>`,\n        components: {\n          test: () => ({\n            component: new Promise((resolve, reject) => {\n              setTimeout(() => {\n                resolve({ template: '<div>hi</div>' })\n                // wait for promise resolve and then parent update\n                Promise.resolve().then(() => {\n                  Vue.nextTick(next)\n                })\n              }, 50)\n            }),\n            loading: { template: `<div>loading</div>` },\n            error: { template: `<div>error</div>` },\n            delay: 0,\n            timeout: 1\n          })\n        }\n      }).$mount()\n\n      expect(vm.$el.textContent).toBe('loading')\n\n      setTimeout(() => {\n        Vue.nextTick(() => {\n          expect(`Failed to resolve async component`).toHaveBeenWarned()\n          expect(vm.$el.textContent).toBe('error')\n        })\n      }, 1)\n\n      function next () {\n        expect(vm.$el.textContent).toBe('error') // late resolve ignored\n        done()\n      }\n    })\n\n    it('should not trigger timeout if resolved', done => {\n      const vm = new Vue({\n        template: `<div><test/></div>`,\n        components: {\n          test: () => ({\n            component: new Promise((resolve, reject) => {\n              setTimeout(() => {\n                resolve({ template: '<div>hi</div>' })\n              }, 10)\n            }),\n            error: { template: `<div>error</div>` },\n            timeout: 20\n          })\n        }\n      }).$mount()\n\n      setTimeout(() => {\n        expect(vm.$el.textContent).toBe('hi')\n        expect(`Failed to resolve async component`).not.toHaveBeenWarned()\n        done()\n      }, 50)\n    })\n\n    // #7107\n    it(`should work when resolving sync in sibling component's mounted hook`, done => {\n      let resolveTwo\n\n      const vm = new Vue({\n        template: `<div><one/> <two/></div>`,\n        components: {\n          one: {\n            template: `<div>one</div>`,\n            mounted () {\n              resolveTwo()\n            }\n          },\n          two: resolve => {\n            resolveTwo = () => {\n              resolve({\n                template: `<div>two</div>`\n              })\n            }\n          }\n        }\n      }).$mount()\n\n      expect(vm.$el.textContent).toBe('one ')\n      waitForUpdate(() => {\n        expect(vm.$el.textContent).toBe('one two')\n      }).then(done)\n    })\n  })\n})\n"
  },
  {
    "path": "vue/test/unit/features/component/component-keep-alive.spec.js",
    "content": "import Vue from 'vue'\nimport injectStyles from '../transition/inject-styles'\nimport { isIE9 } from 'core/util/env'\nimport { nextFrame } from 'web/runtime/transition-util'\n\ndescribe('Component keep-alive', () => {\n  const { duration, buffer } = injectStyles()\n  let components, one, two, el\n  beforeEach(() => {\n    one = {\n      template: '<div>one</div>',\n      created: jasmine.createSpy('one created'),\n      mounted: jasmine.createSpy('one mounted'),\n      activated: jasmine.createSpy('one activated'),\n      deactivated: jasmine.createSpy('one deactivated'),\n      destroyed: jasmine.createSpy('one destroyed')\n    }\n    two = {\n      template: '<div>two</div>',\n      created: jasmine.createSpy('two created'),\n      mounted: jasmine.createSpy('two mounted'),\n      activated: jasmine.createSpy('two activated'),\n      deactivated: jasmine.createSpy('two deactivated'),\n      destroyed: jasmine.createSpy('two destroyed')\n    }\n    components = {\n      one,\n      two\n    }\n    el = document.createElement('div')\n    document.body.appendChild(el)\n  })\n\n  function assertHookCalls (component, callCounts) {\n    expect([\n      component.created.calls.count(),\n      component.mounted.calls.count(),\n      component.activated.calls.count(),\n      component.deactivated.calls.count(),\n      component.destroyed.calls.count()\n    ]).toEqual(callCounts)\n  }\n\n  it('should work', done => {\n    const vm = new Vue({\n      template: `\n        <div v-if=\"ok\">\n          <keep-alive>\n            <component :is=\"view\"></component>\n          </keep-alive>\n        </div>\n      `,\n      data: {\n        view: 'one',\n        ok: true\n      },\n      components\n    }).$mount()\n    expect(vm.$el.textContent).toBe('one')\n    assertHookCalls(one, [1, 1, 1, 0, 0])\n    assertHookCalls(two, [0, 0, 0, 0, 0])\n    vm.view = 'two'\n    waitForUpdate(() => {\n      expect(vm.$el.textContent).toBe('two')\n      assertHookCalls(one, [1, 1, 1, 1, 0])\n      assertHookCalls(two, [1, 1, 1, 0, 0])\n      vm.view = 'one'\n    }).then(() => {\n      expect(vm.$el.textContent).toBe('one')\n      assertHookCalls(one, [1, 1, 2, 1, 0])\n      assertHookCalls(two, [1, 1, 1, 1, 0])\n      vm.view = 'two'\n    }).then(() => {\n      expect(vm.$el.textContent).toBe('two')\n      assertHookCalls(one, [1, 1, 2, 2, 0])\n      assertHookCalls(two, [1, 1, 2, 1, 0])\n      vm.ok = false // teardown\n    }).then(() => {\n      expect(vm.$el.textContent).toBe('')\n      assertHookCalls(one, [1, 1, 2, 2, 1])\n      assertHookCalls(two, [1, 1, 2, 2, 1])\n    }).then(done)\n  })\n\n  it('should invoke hooks on the entire sub tree', done => {\n    one.template = '<two/>'\n    one.components = { two }\n\n    const vm = new Vue({\n      template: `\n        <div>\n          <keep-alive>\n            <one v-if=\"ok\"/>\n          </keep-alive>\n        </div>\n      `,\n      data: {\n        ok: true\n      },\n      components\n    }).$mount()\n\n    expect(vm.$el.textContent).toBe('two')\n    assertHookCalls(one, [1, 1, 1, 0, 0])\n    assertHookCalls(two, [1, 1, 1, 0, 0])\n    vm.ok = false\n    waitForUpdate(() => {\n      expect(vm.$el.textContent).toBe('')\n      assertHookCalls(one, [1, 1, 1, 1, 0])\n      assertHookCalls(two, [1, 1, 1, 1, 0])\n      vm.ok = true\n    }).then(() => {\n      expect(vm.$el.textContent).toBe('two')\n      assertHookCalls(one, [1, 1, 2, 1, 0])\n      assertHookCalls(two, [1, 1, 2, 1, 0])\n      vm.ok = false\n    }).then(() => {\n      expect(vm.$el.textContent).toBe('')\n      assertHookCalls(one, [1, 1, 2, 2, 0])\n      assertHookCalls(two, [1, 1, 2, 2, 0])\n    }).then(done)\n  })\n\n  it('should handle nested keep-alive hooks properly', done => {\n    one.template = '<keep-alive><two v-if=\"ok\" /></keep-alive>'\n    one.data = () => ({ ok: true })\n    one.components = { two }\n\n    const vm = new Vue({\n      template: `\n        <div>\n          <keep-alive>\n            <one v-if=\"ok\" ref=\"one\" />\n          </keep-alive>\n        </div>\n      `,\n      data: {\n        ok: true\n      },\n      components\n    }).$mount()\n\n    var oneInstance = vm.$refs.one\n    expect(vm.$el.textContent).toBe('two')\n    assertHookCalls(one, [1, 1, 1, 0, 0])\n    assertHookCalls(two, [1, 1, 1, 0, 0])\n    vm.ok = false\n    waitForUpdate(() => {\n      expect(vm.$el.textContent).toBe('')\n      assertHookCalls(one, [1, 1, 1, 1, 0])\n      assertHookCalls(two, [1, 1, 1, 1, 0])\n    }).then(() => {\n      vm.ok = true\n    }).then(() => {\n      expect(vm.$el.textContent).toBe('two')\n      assertHookCalls(one, [1, 1, 2, 1, 0])\n      assertHookCalls(two, [1, 1, 2, 1, 0])\n    }).then(() => {\n      // toggle sub component when activated\n      oneInstance.ok = false\n    }).then(() => {\n      expect(vm.$el.textContent).toBe('')\n      assertHookCalls(one, [1, 1, 2, 1, 0])\n      assertHookCalls(two, [1, 1, 2, 2, 0])\n    }).then(() => {\n      oneInstance.ok = true\n    }).then(() => {\n      expect(vm.$el.textContent).toBe('two')\n      assertHookCalls(one, [1, 1, 2, 1, 0])\n      assertHookCalls(two, [1, 1, 3, 2, 0])\n    }).then(() => {\n      vm.ok = false\n    }).then(() => {\n      expect(vm.$el.textContent).toBe('')\n      assertHookCalls(one, [1, 1, 2, 2, 0])\n      assertHookCalls(two, [1, 1, 3, 3, 0])\n    }).then(() => {\n      // toggle sub component when parent is deactivated\n      oneInstance.ok = false\n    }).then(() => {\n      expect(vm.$el.textContent).toBe('')\n      assertHookCalls(one, [1, 1, 2, 2, 0])\n      assertHookCalls(two, [1, 1, 3, 3, 0]) // should not be affected\n    }).then(() => {\n      oneInstance.ok = true\n    }).then(() => {\n      expect(vm.$el.textContent).toBe('')\n      assertHookCalls(one, [1, 1, 2, 2, 0])\n      assertHookCalls(two, [1, 1, 3, 3, 0]) // should not be affected\n    }).then(() => {\n      vm.ok = true\n    }).then(() => {\n      expect(vm.$el.textContent).toBe('two')\n      assertHookCalls(one, [1, 1, 3, 2, 0])\n      assertHookCalls(two, [1, 1, 4, 3, 0])\n    }).then(() => {\n      oneInstance.ok = false\n      vm.ok = false\n    }).then(() => {\n      expect(vm.$el.textContent).toBe('')\n      assertHookCalls(one, [1, 1, 3, 3, 0])\n      assertHookCalls(two, [1, 1, 4, 4, 0])\n    }).then(() => {\n      vm.ok = true\n    }).then(() => {\n      expect(vm.$el.textContent).toBe('')\n      assertHookCalls(one, [1, 1, 4, 3, 0])\n      assertHookCalls(two, [1, 1, 4, 4, 0]) // should remain inactive\n    }).then(done)\n  })\n\n  function sharedAssertions (vm, done) {\n    expect(vm.$el.textContent).toBe('one')\n    assertHookCalls(one, [1, 1, 1, 0, 0])\n    assertHookCalls(two, [0, 0, 0, 0, 0])\n    vm.view = 'two'\n    waitForUpdate(() => {\n      expect(vm.$el.textContent).toBe('two')\n      assertHookCalls(one, [1, 1, 1, 1, 0])\n      assertHookCalls(two, [1, 1, 0, 0, 0])\n      vm.view = 'one'\n    }).then(() => {\n      expect(vm.$el.textContent).toBe('one')\n      assertHookCalls(one, [1, 1, 2, 1, 0])\n      assertHookCalls(two, [1, 1, 0, 0, 1])\n      vm.view = 'two'\n    }).then(() => {\n      expect(vm.$el.textContent).toBe('two')\n      assertHookCalls(one, [1, 1, 2, 2, 0])\n      assertHookCalls(two, [2, 2, 0, 0, 1])\n      vm.ok = false // teardown\n    }).then(() => {\n      expect(vm.$el.textContent).toBe('')\n      assertHookCalls(one, [1, 1, 2, 2, 1])\n      assertHookCalls(two, [2, 2, 0, 0, 2])\n    }).then(done)\n  }\n\n  it('include (string)', done => {\n    const vm = new Vue({\n      template: `\n        <div v-if=\"ok\">\n          <keep-alive include=\"one\">\n            <component :is=\"view\"></component>\n          </keep-alive>\n        </div>\n      `,\n      data: {\n        view: 'one',\n        ok: true\n      },\n      components\n    }).$mount()\n    sharedAssertions(vm, done)\n  })\n\n  it('include (regex)', done => {\n    const vm = new Vue({\n      template: `\n        <div v-if=\"ok\">\n          <keep-alive :include=\"/^one$/\">\n            <component :is=\"view\"></component>\n          </keep-alive>\n        </div>\n      `,\n      data: {\n        view: 'one',\n        ok: true\n      },\n      components\n    }).$mount()\n    sharedAssertions(vm, done)\n  })\n\n  it('include (array)', done => {\n    const vm = new Vue({\n      template: `\n        <div v-if=\"ok\">\n          <keep-alive :include=\"['one']\">\n            <component :is=\"view\"></component>\n          </keep-alive>\n        </div>\n      `,\n      data: {\n        view: 'one',\n        ok: true\n      },\n      components\n    }).$mount()\n    sharedAssertions(vm, done)\n  })\n\n  it('exclude (string)', done => {\n    const vm = new Vue({\n      template: `\n        <div v-if=\"ok\">\n          <keep-alive exclude=\"two\">\n            <component :is=\"view\"></component>\n          </keep-alive>\n        </div>\n      `,\n      data: {\n        view: 'one',\n        ok: true\n      },\n      components\n    }).$mount()\n    sharedAssertions(vm, done)\n  })\n\n  it('exclude (regex)', done => {\n    const vm = new Vue({\n      template: `\n        <div v-if=\"ok\">\n          <keep-alive :exclude=\"/^two$/\">\n            <component :is=\"view\"></component>\n          </keep-alive>\n        </div>\n      `,\n      data: {\n        view: 'one',\n        ok: true\n      },\n      components\n    }).$mount()\n    sharedAssertions(vm, done)\n  })\n\n  it('exclude (array)', done => {\n    const vm = new Vue({\n      template: `\n        <div v-if=\"ok\">\n          <keep-alive :exclude=\"['two']\">\n            <component :is=\"view\"></component>\n          </keep-alive>\n        </div>\n      `,\n      data: {\n        view: 'one',\n        ok: true\n      },\n      components\n    }).$mount()\n    sharedAssertions(vm, done)\n  })\n\n  it('include + exclude', done => {\n    const vm = new Vue({\n      template: `\n        <div v-if=\"ok\">\n          <keep-alive include=\"one,two\" exclude=\"two\">\n            <component :is=\"view\"></component>\n          </keep-alive>\n        </div>\n      `,\n      data: {\n        view: 'one',\n        ok: true\n      },\n      components\n    }).$mount()\n    sharedAssertions(vm, done)\n  })\n\n  it('prune cache on include/exclude change', done => {\n    const vm = new Vue({\n      template: `\n        <div>\n          <keep-alive :include=\"include\">\n            <component :is=\"view\"></component>\n          </keep-alive>\n        </div>\n      `,\n      data: {\n        view: 'one',\n        include: 'one,two'\n      },\n      components\n    }).$mount()\n\n    vm.view = 'two'\n    waitForUpdate(() => {\n      assertHookCalls(one, [1, 1, 1, 1, 0])\n      assertHookCalls(two, [1, 1, 1, 0, 0])\n      vm.include = 'two'\n    }).then(() => {\n      assertHookCalls(one, [1, 1, 1, 1, 1])\n      assertHookCalls(two, [1, 1, 1, 0, 0])\n      vm.view = 'one'\n    }).then(() => {\n      assertHookCalls(one, [2, 2, 1, 1, 1])\n      assertHookCalls(two, [1, 1, 1, 1, 0])\n    }).then(done)\n  })\n\n  it('prune cache on include/exclude change + view switch', done => {\n    const vm = new Vue({\n      template: `\n        <div>\n          <keep-alive :include=\"include\">\n            <component :is=\"view\"></component>\n          </keep-alive>\n        </div>\n      `,\n      data: {\n        view: 'one',\n        include: 'one,two'\n      },\n      components\n    }).$mount()\n\n    vm.view = 'two'\n    waitForUpdate(() => {\n      assertHookCalls(one, [1, 1, 1, 1, 0])\n      assertHookCalls(two, [1, 1, 1, 0, 0])\n      vm.include = 'one'\n      vm.view = 'one'\n    }).then(() => {\n      assertHookCalls(one, [1, 1, 2, 1, 0])\n      // two should be pruned\n      assertHookCalls(two, [1, 1, 1, 1, 1])\n    }).then(done)\n  })\n\n  it('should not prune currently active instance', done => {\n    const vm = new Vue({\n      template: `\n        <div>\n          <keep-alive :include=\"include\">\n            <component :is=\"view\"></component>\n          </keep-alive>\n        </div>\n      `,\n      data: {\n        view: 'one',\n        include: 'one,two'\n      },\n      components\n    }).$mount()\n\n    vm.include = 'two'\n    waitForUpdate(() => {\n      assertHookCalls(one, [1, 1, 1, 0, 0])\n      assertHookCalls(two, [0, 0, 0, 0, 0])\n      vm.view = 'two'\n    }).then(() => {\n      assertHookCalls(one, [1, 1, 1, 0, 1])\n      assertHookCalls(two, [1, 1, 1, 0, 0])\n    }).then(done)\n  })\n\n  // #3882\n  it('deeply nested keep-alive should be destroyed properly', done => {\n    one.template = `<div><keep-alive><two></two></keep-alive></div>`\n    one.components = { two }\n    const vm = new Vue({\n      template: `<div><parent v-if=\"ok\"></parent></div>`,\n      data: { ok: true },\n      components: {\n        parent: {\n          template: `<div><keep-alive><one></one></keep-alive></div>`,\n          components: { one }\n        }\n      }\n    }).$mount()\n\n    assertHookCalls(one, [1, 1, 1, 0, 0])\n    assertHookCalls(two, [1, 1, 1, 0, 0])\n\n    vm.ok = false\n    waitForUpdate(() => {\n      assertHookCalls(one, [1, 1, 1, 1, 1])\n      assertHookCalls(two, [1, 1, 1, 1, 1])\n    }).then(done)\n  })\n\n  // #4237\n  it('should update latest props/listeners for a re-activated component', done => {\n    const one = {\n      props: ['prop'],\n      template: `<div>one {{ prop }}</div>`\n    }\n    const two = {\n      props: ['prop'],\n      template: `<div>two {{ prop }}</div>`\n    }\n    const vm = new Vue({\n      data: { view: 'one', n: 1 },\n      template: `\n        <div>\n          <keep-alive>\n            <component :is=\"view\" :prop=\"n\"></component>\n          </keep-alive>\n        </div>\n      `,\n      components: { one, two }\n    }).$mount()\n\n    expect(vm.$el.textContent).toBe('one 1')\n    vm.n++\n    waitForUpdate(() => {\n      expect(vm.$el.textContent).toBe('one 2')\n      vm.view = 'two'\n    }).then(() => {\n      expect(vm.$el.textContent).toBe('two 2')\n    }).then(done)\n  })\n\n  it('max', done => {\n    const spyA = jasmine.createSpy()\n    const spyB = jasmine.createSpy()\n    const spyC = jasmine.createSpy()\n    const spyAD = jasmine.createSpy()\n    const spyBD = jasmine.createSpy()\n    const spyCD = jasmine.createSpy()\n\n    function assertCount (calls) {\n      expect([\n        spyA.calls.count(),\n        spyAD.calls.count(),\n        spyB.calls.count(),\n        spyBD.calls.count(),\n        spyC.calls.count(),\n        spyCD.calls.count()\n      ]).toEqual(calls)\n    }\n\n    const vm = new Vue({\n      template: `\n        <keep-alive max=\"2\">\n          <component :is=\"n\"></component>\n        </keep-alive>\n      `,\n      data: {\n        n: 'aa'\n      },\n      components: {\n        aa: {\n          template: '<div>a</div>',\n          created: spyA,\n          destroyed: spyAD\n        },\n        bb: {\n          template: '<div>bbb</div>',\n          created: spyB,\n          destroyed: spyBD\n        },\n        cc: {\n          template: '<div>ccc</div>',\n          created: spyC,\n          destroyed: spyCD\n        }\n      }\n    }).$mount()\n\n    assertCount([1, 0, 0, 0, 0, 0])\n    vm.n = 'bb'\n    waitForUpdate(() => {\n      assertCount([1, 0, 1, 0, 0, 0])\n      vm.n = 'cc'\n    }).then(() => {\n      // should prune A because max cache reached\n      assertCount([1, 1, 1, 0, 1, 0])\n      vm.n = 'bb'\n    }).then(() => {\n      // B should be reused, and made latest\n      assertCount([1, 1, 1, 0, 1, 0])\n      vm.n = 'aa'\n    }).then(() => {\n      // C should be pruned because B was used last so C is the oldest cached\n      assertCount([2, 1, 1, 0, 1, 1])\n    }).then(done)\n  })\n\n  it('should warn unknown component inside', () => {\n    new Vue({\n      template: `<keep-alive><foo/></keep-alive>`\n    }).$mount()\n    expect(`Unknown custom element: <foo>`).toHaveBeenWarned()\n  })\n\n  // #6938\n  it('should not cache anonymous component when include is specified', done => {\n    const Foo = {\n      name: 'foo',\n      template: `<div>foo</div>`,\n      created: jasmine.createSpy('foo')\n    }\n\n    const Bar = {\n      template: `<div>bar</div>`,\n      created: jasmine.createSpy('bar')\n    }\n\n    const Child = {\n      functional: true,\n      render (h, ctx) {\n        return h(ctx.props.view ? Foo : Bar)\n      }\n    }\n\n    const vm = new Vue({\n      template: `\n        <keep-alive include=\"foo\">\n          <child :view=\"view\"></child>\n        </keep-alive>\n      `,\n      data: {\n        view: true\n      },\n      components: { Child }\n    }).$mount()\n\n    function assert (foo, bar) {\n      expect(Foo.created.calls.count()).toBe(foo)\n      expect(Bar.created.calls.count()).toBe(bar)\n    }\n\n    expect(vm.$el.textContent).toBe('foo')\n    assert(1, 0)\n    vm.view = false\n    waitForUpdate(() => {\n      expect(vm.$el.textContent).toBe('bar')\n      assert(1, 1)\n      vm.view = true\n    }).then(() => {\n      expect(vm.$el.textContent).toBe('foo')\n      assert(1, 1)\n      vm.view = false\n    }).then(() => {\n      expect(vm.$el.textContent).toBe('bar')\n      assert(1, 2)\n    }).then(done)\n  })\n\n  it('should cache anonymous components if include is not specified', done => {\n    const Foo = {\n      template: `<div>foo</div>`,\n      created: jasmine.createSpy('foo')\n    }\n\n    const Bar = {\n      template: `<div>bar</div>`,\n      created: jasmine.createSpy('bar')\n    }\n\n    const Child = {\n      functional: true,\n      render (h, ctx) {\n        return h(ctx.props.view ? Foo : Bar)\n      }\n    }\n\n    const vm = new Vue({\n      template: `\n        <keep-alive>\n          <child :view=\"view\"></child>\n        </keep-alive>\n      `,\n      data: {\n        view: true\n      },\n      components: { Child }\n    }).$mount()\n\n    function assert (foo, bar) {\n      expect(Foo.created.calls.count()).toBe(foo)\n      expect(Bar.created.calls.count()).toBe(bar)\n    }\n\n    expect(vm.$el.textContent).toBe('foo')\n    assert(1, 0)\n    vm.view = false\n    waitForUpdate(() => {\n      expect(vm.$el.textContent).toBe('bar')\n      assert(1, 1)\n      vm.view = true\n    }).then(() => {\n      expect(vm.$el.textContent).toBe('foo')\n      assert(1, 1)\n      vm.view = false\n    }).then(() => {\n      expect(vm.$el.textContent).toBe('bar')\n      assert(1, 1)\n    }).then(done)\n  })\n\n  // #7105\n  it('should not destroy active instance when pruning cache', done => {\n    const Foo = {\n      template: `<div>foo</div>`,\n      destroyed: jasmine.createSpy('destroyed')\n    }\n    const vm = new Vue({\n      template: `\n        <div>\n          <keep-alive :include=\"include\">\n            <foo/>\n          </keep-alive>\n        </div>\n      `,\n      data: {\n        include: ['foo']\n      },\n      components: { Foo }\n    }).$mount()\n    // condition: a render where a previous component is reused\n    vm.include = ['foo']\n    waitForUpdate(() => {\n      vm.include = ['']\n    }).then(() => {\n      expect(Foo.destroyed).not.toHaveBeenCalled()\n    }).then(done)\n  })\n\n  if (!isIE9) {\n    it('with transition-mode out-in', done => {\n      let next\n      const vm = new Vue({\n        template: `<div>\n          <transition name=\"test\" mode=\"out-in\" @after-leave=\"afterLeave\">\n            <keep-alive>\n              <component :is=\"view\" class=\"test\"></component>\n            </keep-alive>\n          </transition>\n        </div>`,\n        data: {\n          view: 'one'\n        },\n        components,\n        methods: {\n          afterLeave () {\n            next()\n          }\n        }\n      }).$mount(el)\n      expect(vm.$el.textContent).toBe('one')\n      assertHookCalls(one, [1, 1, 1, 0, 0])\n      assertHookCalls(two, [0, 0, 0, 0, 0])\n      vm.view = 'two'\n      waitForUpdate(() => {\n        expect(vm.$el.innerHTML).toBe(\n          '<div class=\"test test-leave test-leave-active\">one</div><!---->'\n        )\n        assertHookCalls(one, [1, 1, 1, 1, 0])\n        assertHookCalls(two, [0, 0, 0, 0, 0])\n      }).thenWaitFor(nextFrame).then(() => {\n        expect(vm.$el.innerHTML).toBe(\n          '<div class=\"test test-leave-active test-leave-to\">one</div><!---->'\n        )\n      }).thenWaitFor(_next => { next = _next }).then(() => {\n        expect(vm.$el.innerHTML).toBe('<!---->')\n      }).thenWaitFor(nextFrame).then(() => {\n        expect(vm.$el.innerHTML).toBe(\n          '<div class=\"test test-enter test-enter-active\">two</div>'\n        )\n        assertHookCalls(one, [1, 1, 1, 1, 0])\n        assertHookCalls(two, [1, 1, 1, 0, 0])\n      }).thenWaitFor(nextFrame).then(() => {\n        expect(vm.$el.innerHTML).toBe(\n          '<div class=\"test test-enter-active test-enter-to\">two</div>'\n        )\n      }).thenWaitFor(duration + buffer).then(() => {\n        expect(vm.$el.innerHTML).toBe(\n          '<div class=\"test\">two</div>'\n        )\n        assertHookCalls(one, [1, 1, 1, 1, 0])\n        assertHookCalls(two, [1, 1, 1, 0, 0])\n      }).then(() => {\n        vm.view = 'one'\n      }).then(() => {\n        expect(vm.$el.innerHTML).toBe(\n          '<div class=\"test test-leave test-leave-active\">two</div><!---->'\n        )\n        assertHookCalls(one, [1, 1, 1, 1, 0])\n        assertHookCalls(two, [1, 1, 1, 1, 0])\n      }).thenWaitFor(nextFrame).then(() => {\n        expect(vm.$el.innerHTML).toBe(\n          '<div class=\"test test-leave-active test-leave-to\">two</div><!---->'\n        )\n      }).thenWaitFor(_next => { next = _next }).then(() => {\n        expect(vm.$el.innerHTML).toBe('<!---->')\n      }).thenWaitFor(nextFrame).then(() => {\n        expect(vm.$el.innerHTML).toBe(\n          '<div class=\"test test-enter test-enter-active\">one</div>'\n        )\n        assertHookCalls(one, [1, 1, 2, 1, 0])\n        assertHookCalls(two, [1, 1, 1, 1, 0])\n      }).thenWaitFor(nextFrame).then(() => {\n        expect(vm.$el.innerHTML).toBe(\n          '<div class=\"test test-enter-active test-enter-to\">one</div>'\n        )\n      }).thenWaitFor(duration + buffer).then(() => {\n        expect(vm.$el.innerHTML).toBe(\n          '<div class=\"test\">one</div>'\n        )\n        assertHookCalls(one, [1, 1, 2, 1, 0])\n        assertHookCalls(two, [1, 1, 1, 1, 0])\n      }).then(done)\n    })\n\n    it('with transition-mode out-in + include', done => {\n      let next\n      const vm = new Vue({\n        template: `<div>\n          <transition name=\"test\" mode=\"out-in\" @after-leave=\"afterLeave\">\n            <keep-alive include=\"one\">\n              <component :is=\"view\" class=\"test\"></component>\n            </keep-alive>\n          </transition>\n        </div>`,\n        data: {\n          view: 'one'\n        },\n        components,\n        methods: {\n          afterLeave () {\n            next()\n          }\n        }\n      }).$mount(el)\n      expect(vm.$el.textContent).toBe('one')\n      assertHookCalls(one, [1, 1, 1, 0, 0])\n      assertHookCalls(two, [0, 0, 0, 0, 0])\n      vm.view = 'two'\n      waitForUpdate(() => {\n        expect(vm.$el.innerHTML).toBe(\n          '<div class=\"test test-leave test-leave-active\">one</div><!---->'\n        )\n        assertHookCalls(one, [1, 1, 1, 1, 0])\n        assertHookCalls(two, [0, 0, 0, 0, 0])\n      }).thenWaitFor(nextFrame).then(() => {\n        expect(vm.$el.innerHTML).toBe(\n          '<div class=\"test test-leave-active test-leave-to\">one</div><!---->'\n        )\n      }).thenWaitFor(_next => { next = _next }).then(() => {\n        expect(vm.$el.innerHTML).toBe('<!---->')\n      }).thenWaitFor(nextFrame).then(() => {\n        expect(vm.$el.innerHTML).toBe(\n          '<div class=\"test test-enter test-enter-active\">two</div>'\n        )\n        assertHookCalls(one, [1, 1, 1, 1, 0])\n        assertHookCalls(two, [1, 1, 0, 0, 0])\n      }).thenWaitFor(nextFrame).then(() => {\n        expect(vm.$el.innerHTML).toBe(\n          '<div class=\"test test-enter-active test-enter-to\">two</div>'\n        )\n      }).thenWaitFor(duration + buffer).then(() => {\n        expect(vm.$el.innerHTML).toBe(\n          '<div class=\"test\">two</div>'\n        )\n        assertHookCalls(one, [1, 1, 1, 1, 0])\n        assertHookCalls(two, [1, 1, 0, 0, 0])\n      }).then(() => {\n        vm.view = 'one'\n      }).then(() => {\n        expect(vm.$el.innerHTML).toBe(\n          '<div class=\"test test-leave test-leave-active\">two</div><!---->'\n        )\n        assertHookCalls(one, [1, 1, 1, 1, 0])\n        assertHookCalls(two, [1, 1, 0, 0, 1])\n      }).thenWaitFor(nextFrame).then(() => {\n        expect(vm.$el.innerHTML).toBe(\n          '<div class=\"test test-leave-active test-leave-to\">two</div><!---->'\n        )\n      }).thenWaitFor(_next => { next = _next }).then(() => {\n        expect(vm.$el.innerHTML).toBe('<!---->')\n      }).thenWaitFor(nextFrame).then(() => {\n        expect(vm.$el.innerHTML).toBe(\n          '<div class=\"test test-enter test-enter-active\">one</div>'\n        )\n        assertHookCalls(one, [1, 1, 2, 1, 0])\n        assertHookCalls(two, [1, 1, 0, 0, 1])\n      }).thenWaitFor(nextFrame).then(() => {\n        expect(vm.$el.innerHTML).toBe(\n          '<div class=\"test test-enter-active test-enter-to\">one</div>'\n        )\n      }).thenWaitFor(duration + buffer).then(() => {\n        expect(vm.$el.innerHTML).toBe(\n          '<div class=\"test\">one</div>'\n        )\n        assertHookCalls(one, [1, 1, 2, 1, 0])\n        assertHookCalls(two, [1, 1, 0, 0, 1])\n      }).then(done)\n    })\n\n    it('with transition-mode in-out', done => {\n      let next\n      const vm = new Vue({\n        template: `<div>\n          <transition name=\"test\" mode=\"in-out\" @after-enter=\"afterEnter\">\n            <keep-alive>\n              <component :is=\"view\" class=\"test\"></component>\n            </keep-alive>\n          </transition>\n        </div>`,\n        data: {\n          view: 'one'\n        },\n        components,\n        methods: {\n          afterEnter () {\n            next()\n          }\n        }\n      }).$mount(el)\n      expect(vm.$el.textContent).toBe('one')\n      assertHookCalls(one, [1, 1, 1, 0, 0])\n      assertHookCalls(two, [0, 0, 0, 0, 0])\n      vm.view = 'two'\n      waitForUpdate(() => {\n        expect(vm.$el.innerHTML).toBe(\n          '<div class=\"test\">one</div>' +\n          '<div class=\"test test-enter test-enter-active\">two</div>'\n        )\n        assertHookCalls(one, [1, 1, 1, 1, 0])\n        assertHookCalls(two, [1, 1, 1, 0, 0])\n      }).thenWaitFor(nextFrame).then(() => {\n        expect(vm.$el.innerHTML).toBe(\n          '<div class=\"test\">one</div>' +\n          '<div class=\"test test-enter-active test-enter-to\">two</div>'\n        )\n      }).thenWaitFor(_next => { next = _next }).then(() => {\n        expect(vm.$el.innerHTML).toBe(\n          '<div class=\"test\">one</div>' +\n          '<div class=\"test\">two</div>'\n        )\n      }).then(() => {\n        expect(vm.$el.innerHTML).toBe(\n          '<div class=\"test test-leave test-leave-active\">one</div>' +\n          '<div class=\"test\">two</div>'\n        )\n      }).thenWaitFor(nextFrame).then(() => {\n        expect(vm.$el.innerHTML).toBe(\n          '<div class=\"test test-leave-active test-leave-to\">one</div>' +\n          '<div class=\"test\">two</div>'\n        )\n      }).thenWaitFor(duration + buffer).then(() => {\n        expect(vm.$el.innerHTML).toBe(\n          '<div class=\"test\">two</div>'\n        )\n        assertHookCalls(one, [1, 1, 1, 1, 0])\n        assertHookCalls(two, [1, 1, 1, 0, 0])\n      }).then(() => {\n        vm.view = 'one'\n      }).then(() => {\n        expect(vm.$el.innerHTML).toBe(\n          '<div class=\"test\">two</div>' +\n          '<div class=\"test test-enter test-enter-active\">one</div>'\n        )\n        assertHookCalls(one, [1, 1, 2, 1, 0])\n        assertHookCalls(two, [1, 1, 1, 1, 0])\n      }).thenWaitFor(nextFrame).then(() => {\n        expect(vm.$el.innerHTML).toBe(\n          '<div class=\"test\">two</div>' +\n          '<div class=\"test test-enter-active test-enter-to\">one</div>'\n        )\n      }).thenWaitFor(_next => { next = _next }).then(() => {\n        expect(vm.$el.innerHTML).toBe(\n          '<div class=\"test\">two</div>' +\n          '<div class=\"test\">one</div>'\n        )\n      }).then(() => {\n        expect(vm.$el.innerHTML).toBe(\n          '<div class=\"test test-leave test-leave-active\">two</div>' +\n          '<div class=\"test\">one</div>'\n        )\n      }).thenWaitFor(nextFrame).then(() => {\n        expect(vm.$el.innerHTML).toBe(\n          '<div class=\"test test-leave-active test-leave-to\">two</div>' +\n          '<div class=\"test\">one</div>'\n        )\n      }).thenWaitFor(duration + buffer).then(() => {\n        expect(vm.$el.innerHTML).toBe(\n          '<div class=\"test\">one</div>'\n        )\n        assertHookCalls(one, [1, 1, 2, 1, 0])\n        assertHookCalls(two, [1, 1, 1, 1, 0])\n      }).then(done)\n    })\n\n    it('dynamic components, in-out with early cancel', done => {\n      let next\n      const vm = new Vue({\n        template: `<div>\n          <transition name=\"test\" mode=\"in-out\" @after-enter=\"afterEnter\">\n            <keep-alive>\n              <component :is=\"view\" class=\"test\"></component>\n            </keep-alive>\n          </transition>\n        </div>`,\n        data: { view: 'one' },\n        components,\n        methods: {\n          afterEnter () {\n            next()\n          }\n        }\n      }).$mount(el)\n      expect(vm.$el.textContent).toBe('one')\n      vm.view = 'two'\n      waitForUpdate(() => {\n        expect(vm.$el.innerHTML).toBe(\n          '<div class=\"test\">one</div>' +\n          '<div class=\"test test-enter test-enter-active\">two</div>'\n        )\n      }).thenWaitFor(nextFrame).then(() => {\n        expect(vm.$el.innerHTML).toBe(\n          '<div class=\"test\">one</div>' +\n          '<div class=\"test test-enter-active test-enter-to\">two</div>'\n        )\n        // switch again before enter finishes,\n        // this cancels both enter and leave.\n        vm.view = 'one'\n      }).then(() => {\n        // 1. the pending leaving \"one\" should be removed instantly.\n        // 2. the entering \"two\" should be placed into its final state instantly.\n        // 3. a new \"one\" is created and entering\n        expect(vm.$el.innerHTML).toBe(\n          '<div class=\"test\">two</div>' +\n          '<div class=\"test test-enter test-enter-active\">one</div>'\n        )\n      }).thenWaitFor(nextFrame).then(() => {\n        expect(vm.$el.innerHTML).toBe(\n          '<div class=\"test\">two</div>' +\n          '<div class=\"test test-enter-active test-enter-to\">one</div>'\n        )\n      }).thenWaitFor(_next => { next = _next }).then(() => {\n        expect(vm.$el.innerHTML).toBe(\n          '<div class=\"test\">two</div>' +\n          '<div class=\"test\">one</div>'\n        )\n      }).then(() => {\n        expect(vm.$el.innerHTML).toBe(\n          '<div class=\"test test-leave test-leave-active\">two</div>' +\n          '<div class=\"test\">one</div>'\n        )\n      }).thenWaitFor(nextFrame).then(() => {\n        expect(vm.$el.innerHTML).toBe(\n          '<div class=\"test test-leave-active test-leave-to\">two</div>' +\n          '<div class=\"test\">one</div>'\n        )\n      }).thenWaitFor(duration + buffer).then(() => {\n        expect(vm.$el.innerHTML).toBe(\n          '<div class=\"test\">one</div>'\n        )\n      }).then(done).then(done)\n    })\n\n    // #4339\n    it('component with inner transition', done => {\n      const vm = new Vue({\n        template: `\n          <div>\n            <keep-alive>\n              <component ref=\"test\" :is=\"view\"></component>\n            </keep-alive>\n          </div>\n        `,\n        data: { view: 'foo' },\n        components: {\n          foo: { template: '<transition><div class=\"test\">foo</div></transition>' },\n          bar: { template: '<transition name=\"test\"><div class=\"test\">bar</div></transition>' }\n        }\n      }).$mount(el)\n\n      // should not apply transition on initial render by default\n      expect(vm.$el.innerHTML).toBe('<div class=\"test\">foo</div>')\n      vm.view = 'bar'\n      waitForUpdate(() => {\n        expect(vm.$el.innerHTML).toBe(\n          '<div class=\"test v-leave v-leave-active\">foo</div>' +\n          '<div class=\"test test-enter test-enter-active\">bar</div>'\n        )\n      }).thenWaitFor(nextFrame).then(() => {\n        expect(vm.$el.innerHTML).toBe(\n          '<div class=\"test v-leave-active v-leave-to\">foo</div>' +\n          '<div class=\"test test-enter-active test-enter-to\">bar</div>'\n        )\n      }).thenWaitFor(duration + buffer).then(() => {\n        expect(vm.$el.innerHTML).toBe(\n          '<div class=\"test\">bar</div>'\n        )\n        vm.view = 'foo'\n      }).then(() => {\n        expect(vm.$el.innerHTML).toBe(\n          '<div class=\"test test-leave test-leave-active\">bar</div>' +\n          '<div class=\"test v-enter v-enter-active\">foo</div>'\n        )\n      }).thenWaitFor(nextFrame).then(() => {\n        expect(vm.$el.innerHTML).toBe(\n          '<div class=\"test test-leave-active test-leave-to\">bar</div>' +\n          '<div class=\"test v-enter-active v-enter-to\">foo</div>'\n        )\n      }).thenWaitFor(duration + buffer).then(() => {\n        expect(vm.$el.innerHTML).toBe(\n          '<div class=\"test\">foo</div>'\n        )\n      }).then(done)\n    })\n\n    it('async components with transition-mode out-in', done => {\n      const barResolve = jasmine.createSpy('bar resolved')\n      let next\n      const foo = (resolve) => {\n        setTimeout(() => {\n          resolve(one)\n          Vue.nextTick(next)\n        }, duration / 2)\n      }\n      const bar = (resolve) => {\n        setTimeout(() => {\n          resolve(two)\n          barResolve()\n        }, duration / 2)\n      }\n      components = {\n        foo,\n        bar\n      }\n      const vm = new Vue({\n        template: `<div>\n          <transition name=\"test\" mode=\"out-in\" @after-enter=\"afterEnter\" @after-leave=\"afterLeave\">\n            <keep-alive>\n              <component :is=\"view\" class=\"test\"></component>\n            </keep-alive>\n          </transition>\n        </div>`,\n        data: {\n          view: 'foo'\n        },\n        components,\n        methods: {\n          afterEnter () {\n            next()\n          },\n          afterLeave () {\n            next()\n          }\n        }\n      }).$mount(el)\n      expect(vm.$el.textContent).toBe('')\n      next = () => {\n        assertHookCalls(one, [1, 1, 1, 0, 0])\n        assertHookCalls(two, [0, 0, 0, 0, 0])\n        waitForUpdate(() => {\n          expect(vm.$el.innerHTML).toBe(\n            '<div class=\"test test-enter test-enter-active\">one</div>'\n          )\n        }).thenWaitFor(nextFrame).then(() => {\n          expect(vm.$el.innerHTML).toBe(\n            '<div class=\"test test-enter-active test-enter-to\">one</div>'\n          )\n        }).thenWaitFor(_next => { next = _next }).then(() => {\n          // foo afterEnter get called\n          expect(vm.$el.innerHTML).toBe('<div class=\"test\">one</div>')\n          vm.view = 'bar'\n        }).thenWaitFor(nextFrame).then(() => {\n          assertHookCalls(one, [1, 1, 1, 1, 0])\n          assertHookCalls(two, [0, 0, 0, 0, 0])\n          expect(vm.$el.innerHTML).toBe(\n            '<div class=\"test test-leave-active test-leave-to\">one</div><!---->'\n          )\n        }).thenWaitFor(_next => { next = _next }).then(() => {\n          // foo afterLeave get called\n          // and bar has already been resolved before afterLeave get called\n          expect(barResolve.calls.count()).toBe(1)\n          expect(vm.$el.innerHTML).toBe('<!---->')\n        }).thenWaitFor(nextFrame).then(() => {\n          expect(vm.$el.innerHTML).toBe(\n            '<div class=\"test test-enter test-enter-active\">two</div>'\n          )\n          assertHookCalls(one, [1, 1, 1, 1, 0])\n          assertHookCalls(two, [1, 1, 1, 0, 0])\n        }).thenWaitFor(nextFrame).then(() => {\n          expect(vm.$el.innerHTML).toBe(\n            '<div class=\"test test-enter-active test-enter-to\">two</div>'\n          )\n        }).thenWaitFor(_next => { next = _next }).then(() => {\n          // bar afterEnter get called\n          expect(vm.$el.innerHTML).toBe('<div class=\"test\">two</div>')\n        }).then(done)\n      }\n    })\n  }\n})\n"
  },
  {
    "path": "vue/test/unit/features/component/component-scoped-slot.spec.js",
    "content": "import Vue from 'vue'\n\ndescribe('Component scoped slot', () => {\n  it('default slot', done => {\n    const vm = new Vue({\n      template: `\n        <test ref=\"test\">\n          <template slot-scope=\"props\">\n            <span>{{ props.msg }}</span>\n          </template>\n        </test>\n      `,\n      components: {\n        test: {\n          data () {\n            return { msg: 'hello' }\n          },\n          template: `\n            <div>\n              <slot :msg=\"msg\"></slot>\n            </div>\n          `\n        }\n      }\n    }).$mount()\n\n    expect(vm.$el.innerHTML).toBe('<span>hello</span>')\n    vm.$refs.test.msg = 'world'\n    waitForUpdate(() => {\n      expect(vm.$el.innerHTML).toBe('<span>world</span>')\n    }).then(done)\n  })\n\n  it('default slot (plain element)', done => {\n    const vm = new Vue({\n      template: `\n        <test ref=\"test\">\n          <span slot-scope=\"props\">{{ props.msg }}</span>\n        </test>\n      `,\n      components: {\n        test: {\n          data () {\n            return { msg: 'hello' }\n          },\n          template: `\n            <div>\n              <slot :msg=\"msg\"></slot>\n            </div>\n          `\n        }\n      }\n    }).$mount()\n\n    expect(vm.$el.innerHTML).toBe('<span>hello</span>')\n    vm.$refs.test.msg = 'world'\n    waitForUpdate(() => {\n      expect(vm.$el.innerHTML).toBe('<span>world</span>')\n    }).then(done)\n  })\n\n  it('with v-bind', done => {\n    const vm = new Vue({\n      template: `\n        <test ref=\"test\">\n          <template slot-scope=\"props\">\n            <span>{{ props.msg }} {{ props.msg2 }} {{ props.msg3 }}</span>\n          </template>\n        </test>\n      `,\n      components: {\n        test: {\n          data () {\n            return {\n              msg: 'hello',\n              obj: { msg2: 'world', msg3: '.' }\n            }\n          },\n          template: `\n            <div>\n              <slot :msg=\"msg\" v-bind=\"obj\" msg3=\"!\"></slot>\n            </div>\n          `\n        }\n      }\n    }).$mount()\n\n    expect(vm.$el.innerHTML).toBe('<span>hello world !</span>')\n    vm.$refs.test.msg = 'bye'\n    vm.$refs.test.obj.msg2 = 'bye'\n    waitForUpdate(() => {\n      expect(vm.$el.innerHTML).toBe('<span>bye bye !</span>')\n    }).then(done)\n  })\n\n  it('should warn when using v-bind with no object', () => {\n    new Vue({\n      template: `\n        <test ref=\"test\">\n          <template scope=\"props\">\n          </template>\n        </test>\n      `,\n      components: {\n        test: {\n          data () {\n            return {\n              text: 'some text'\n            }\n          },\n          template: `\n            <div>\n              <slot v-bind=\"text\"></slot>\n            </div>\n          `\n        }\n      }\n    }).$mount()\n    expect('slot v-bind without argument expects an Object').toHaveBeenWarned()\n  })\n\n  it('should not warn when using v-bind with object', () => {\n    new Vue({\n      template: `\n        <test ref=\"test\">\n          <template scope=\"props\">\n          </template>\n        </test>\n      `,\n      components: {\n        test: {\n          data () {\n            return {\n              foo: {\n                text: 'some text'\n              }\n            }\n          },\n          template: `\n            <div>\n              <slot v-bind=\"foo\"></slot>\n            </div>\n          `\n        }\n      }\n    }).$mount()\n    expect('slot v-bind without argument expects an Object').not.toHaveBeenWarned()\n  })\n\n  it('named scoped slot', done => {\n    const vm = new Vue({\n      template: `\n        <test ref=\"test\">\n          <template slot=\"item\" slot-scope=\"props\">\n            <span>{{ props.foo }}</span><span>{{ props.bar }}</span>\n          </template>\n        </test>\n      `,\n      components: {\n        test: {\n          data () {\n            return { foo: 'FOO', bar: 'BAR' }\n          },\n          template: `\n            <div>\n              <slot name=\"item\" :foo=\"foo\" :bar=\"bar\"></slot>\n            </div>\n          `\n        }\n      }\n    }).$mount()\n\n    expect(vm.$el.innerHTML).toBe('<span>FOO</span><span>BAR</span>')\n    vm.$refs.test.foo = 'BAZ'\n    waitForUpdate(() => {\n      expect(vm.$el.innerHTML).toBe('<span>BAZ</span><span>BAR</span>')\n    }).then(done)\n  })\n\n  it('named scoped slot (plain element)', done => {\n    const vm = new Vue({\n      template: `\n        <test ref=\"test\">\n          <span slot=\"item\" slot-scope=\"props\">{{ props.foo }} {{ props.bar }}</span>\n        </test>\n      `,\n      components: {\n        test: {\n          data () {\n            return { foo: 'FOO', bar: 'BAR' }\n          },\n          template: `\n            <div>\n              <slot name=\"item\" :foo=\"foo\" :bar=\"bar\"></slot>\n            </div>\n          `\n        }\n      }\n    }).$mount()\n\n    expect(vm.$el.innerHTML).toBe('<span>FOO BAR</span>')\n    vm.$refs.test.foo = 'BAZ'\n    waitForUpdate(() => {\n      expect(vm.$el.innerHTML).toBe('<span>BAZ BAR</span>')\n    }).then(done)\n  })\n\n  it('fallback content', () => {\n    const vm = new Vue({\n      template: `<test></test>`,\n      components: {\n        test: {\n          data () {\n            return { msg: 'hello' }\n          },\n          template: `\n            <div>\n              <slot name=\"item\" :text=\"msg\">\n                <span>{{ msg }} fallback</span>\n              </slot>\n            </div>\n          `\n        }\n      }\n    }).$mount()\n    expect(vm.$el.innerHTML).toBe('<span>hello fallback</span>')\n  })\n\n  it('slot with v-for', done => {\n    const vm = new Vue({\n      template: `\n        <test ref=\"test\">\n          <template slot=\"item\" slot-scope=\"props\">\n            <span>{{ props.text }}</span>\n          </template>\n        </test>\n      `,\n      components: {\n        test: {\n          data () {\n            return {\n              items: ['foo', 'bar', 'baz']\n            }\n          },\n          template: `\n            <div>\n              <slot v-for=\"item in items\" name=\"item\" :text=\"item\"></slot>\n            </div>\n          `\n        }\n      }\n    }).$mount()\n\n    function assertOutput () {\n      expect(vm.$el.innerHTML).toBe(vm.$refs.test.items.map(item => {\n        return `<span>${item}</span>`\n      }).join(''))\n    }\n\n    assertOutput()\n    vm.$refs.test.items.reverse()\n    waitForUpdate(assertOutput).then(() => {\n      vm.$refs.test.items.push('qux')\n    }).then(assertOutput).then(done)\n  })\n\n  it('slot inside v-for', done => {\n    const vm = new Vue({\n      template: `\n        <test ref=\"test\">\n          <template slot=\"item\" slot-scope=\"props\">\n            <span>{{ props.text }}</span>\n          </template>\n        </test>\n      `,\n      components: {\n        test: {\n          data () {\n            return {\n              items: ['foo', 'bar', 'baz']\n            }\n          },\n          template: `\n            <ul>\n              <li v-for=\"item in items\">\n                <slot name=\"item\" :text=\"item\"></slot>\n              </li>\n            </ul>\n          `\n        }\n      }\n    }).$mount()\n\n    function assertOutput () {\n      expect(vm.$el.innerHTML).toBe(vm.$refs.test.items.map(item => {\n        return `<li><span>${item}</span></li>`\n      }).join(''))\n    }\n\n    assertOutput()\n    vm.$refs.test.items.reverse()\n    waitForUpdate(assertOutput).then(() => {\n      vm.$refs.test.items.push('qux')\n    }).then(assertOutput).then(done)\n  })\n\n  it('scoped slot without scope alias', () => {\n    const vm = new Vue({\n      template: `\n        <test ref=\"test\">\n          <span slot=\"item\">I am static</span>\n        </test>\n      `,\n      components: {\n        test: {\n          data () {\n            return { msg: 'hello' }\n          },\n          template: `\n            <div>\n              <slot name=\"item\" :text=\"msg\"></slot>\n            </div>\n          `\n        }\n      }\n    }).$mount()\n    expect(vm.$el.innerHTML).toBe('<span>I am static</span>')\n  })\n\n  it('non-scoped slot with scope alias', () => {\n    const vm = new Vue({\n      template: `\n        <test ref=\"test\">\n          <template slot=\"item\" slot-scope=\"props\">\n            <span>{{ props.text || 'meh' }}</span>\n          </template>\n        </test>\n      `,\n      components: {\n        test: {\n          data () {\n            return { msg: 'hello' }\n          },\n          template: `\n            <div>\n              <slot name=\"item\"></slot>\n            </div>\n          `\n        }\n      }\n    }).$mount()\n    expect(vm.$el.innerHTML).toBe('<span>meh</span>')\n  })\n\n  it('warn key on slot', () => {\n    new Vue({\n      template: `\n        <test ref=\"test\">\n          <template slot=\"item\" slot-scope=\"props\">\n            <span>{{ props.text }}</span>\n          </template>\n        </test>\n      `,\n      components: {\n        test: {\n          data () {\n            return {\n              items: ['foo', 'bar', 'baz']\n            }\n          },\n          template: `\n            <div>\n              <slot v-for=\"item in items\" name=\"item\" :text=\"item\" :key=\"item\"></slot>\n            </div>\n          `\n        }\n      }\n    }).$mount()\n    expect(`\\`key\\` does not work on <slot>`).toHaveBeenWarned()\n  })\n\n  it('render function usage (named, via data)', done => {\n    const vm = new Vue({\n      render (h) {\n        return h('test', {\n          ref: 'test',\n          scopedSlots: {\n            item: props => h('span', props.text)\n          }\n        })\n      },\n      components: {\n        test: {\n          data () {\n            return { msg: 'hello' }\n          },\n          render (h) {\n            return h('div', [\n              this.$scopedSlots.item({\n                text: this.msg\n              })\n            ])\n          }\n        }\n      }\n    }).$mount()\n\n    expect(vm.$el.innerHTML).toBe('<span>hello</span>')\n    vm.$refs.test.msg = 'world'\n    waitForUpdate(() => {\n      expect(vm.$el.innerHTML).toBe('<span>world</span>')\n    }).then(done)\n  })\n\n  it('render function usage (default, as children)', () => {\n    const vm = new Vue({\n      render (h) {\n        return h('test', [\n          props => h('span', [props.msg])\n        ])\n      },\n      components: {\n        test: {\n          data () {\n            return { msg: 'hello' }\n          },\n          render (h) {\n            return h('div', [\n              this.$scopedSlots.default({ msg: this.msg })\n            ])\n          }\n        }\n      }\n    }).$mount()\n    expect(vm.$el.innerHTML).toBe('<span>hello</span>')\n  })\n\n  // #4779\n  it('should support dynamic slot target', done => {\n    const Child = {\n      template: `\n        <div>\n          <slot name=\"a\" msg=\"a\" />\n          <slot name=\"b\" msg=\"b\" />\n        </div>\n      `\n    }\n\n    const vm = new Vue({\n      data: {\n        a: 'a',\n        b: 'b'\n      },\n      template: `\n        <child>\n          <template :slot=\"a\" slot-scope=\"props\">A {{ props.msg }}</template>\n          <template :slot=\"b\" slot-scope=\"props\">B {{ props.msg }}</template>\n        </child>\n      `,\n      components: { Child }\n    }).$mount()\n\n    expect(vm.$el.textContent.trim()).toBe('A a B b')\n\n    // switch slots\n    vm.a = 'b'\n    vm.b = 'a'\n    waitForUpdate(() => {\n      expect(vm.$el.textContent.trim()).toBe('B a A b')\n    }).then(done)\n  })\n\n  it('render function usage (JSX)', () => {\n    const vm = new Vue({\n      render (h) {\n        return <test>{\n          props => <span>{props.msg}</span>\n        }</test>\n      },\n      components: {\n        test: {\n          data () {\n            return { msg: 'hello' }\n          },\n          render (h) {\n            return <div>\n              {this.$scopedSlots.default({ msg: this.msg })}\n            </div>\n          }\n        }\n      }\n    }).$mount()\n    expect(vm.$el.innerHTML).toBe('<span>hello</span>')\n  })\n\n  // #5615\n  it('scoped slot with v-for', done => {\n    const vm = new Vue({\n      data: { names: ['foo', 'bar'] },\n      template: `\n        <test ref=\"test\">\n          <template v-for=\"n in names\" :slot=\"n\" slot-scope=\"props\">\n            <span>{{ props.msg }}</span>\n          </template>\n          <template slot=\"abc\" slot-scope=\"props\">\n            <span>{{ props.msg }}</span>\n          </template>\n        </test>\n      `,\n      components: {\n        test: {\n          data: () => ({ msg: 'hello' }),\n          template: `\n            <div>\n              <slot name=\"foo\" :msg=\"msg + ' foo'\"></slot>\n              <slot name=\"bar\" :msg=\"msg + ' bar'\"></slot>\n              <slot name=\"abc\" :msg=\"msg + ' abc'\"></slot>\n            </div>\n          `\n        }\n      }\n    }).$mount()\n\n    expect(vm.$el.innerHTML).toBe('<span>hello foo</span> <span>hello bar</span> <span>hello abc</span>')\n    vm.$refs.test.msg = 'world'\n    waitForUpdate(() => {\n      expect(vm.$el.innerHTML).toBe('<span>world foo</span> <span>world bar</span> <span>world abc</span>')\n    }).then(done)\n  })\n\n  it('scoped slot with v-for (plain elements)', done => {\n    const vm = new Vue({\n      data: { names: ['foo', 'bar'] },\n      template: `\n        <test ref=\"test\">\n          <span v-for=\"n in names\" :slot=\"n\" slot-scope=\"props\">{{ props.msg }}</span>\n          <span slot=\"abc\" slot-scope=\"props\">{{ props.msg }}</span>\n        </test>\n      `,\n      components: {\n        test: {\n          data: () => ({ msg: 'hello' }),\n          template: `\n            <div>\n              <slot name=\"foo\" :msg=\"msg + ' foo'\"></slot>\n              <slot name=\"bar\" :msg=\"msg + ' bar'\"></slot>\n              <slot name=\"abc\" :msg=\"msg + ' abc'\"></slot>\n            </div>\n          `\n        }\n      }\n    }).$mount()\n\n    expect(vm.$el.innerHTML).toBe('<span>hello foo</span> <span>hello bar</span> <span>hello abc</span>')\n    vm.$refs.test.msg = 'world'\n    waitForUpdate(() => {\n      expect(vm.$el.innerHTML).toBe('<span>world foo</span> <span>world bar</span> <span>world abc</span>')\n    }).then(done)\n  })\n\n  // #6725\n  it('scoped slot with v-if', done => {\n    const vm = new Vue({\n      data: {\n        ok: false\n      },\n      template: `\n        <test>\n          <template v-if=\"ok\" slot-scope=\"foo\">\n            <p>{{ foo.text }}</p>\n          </template>\n        </test>\n      `,\n      components: {\n        test: {\n          data () {\n            return { msg: 'hello' }\n          },\n          template: `\n            <div>\n              <slot :text=\"msg\">\n                <span>{{ msg }} fallback</span>\n              </slot>\n            </div>\n          `\n        }\n      }\n    }).$mount()\n    expect(vm.$el.innerHTML).toBe('<span>hello fallback</span>')\n\n    vm.ok = true\n    waitForUpdate(() => {\n      expect(vm.$el.innerHTML).toBe('<p>hello</p>')\n    }).then(done)\n  })\n})\n"
  },
  {
    "path": "vue/test/unit/features/component/component-slot.spec.js",
    "content": "import Vue from 'vue'\n\ndescribe('Component slot', () => {\n  let vm, child\n  function mount (options) {\n    vm = new Vue({\n      data: {\n        msg: 'parent message'\n      },\n      template: `<div><test>${options.parentContent || ''}</test></div>`,\n      components: {\n        test: {\n          template: options.childTemplate,\n          data () {\n            return {\n              msg: 'child message'\n            }\n          }\n        }\n      }\n    }).$mount()\n    child = vm.$children[0]\n  }\n\n  it('no content', () => {\n    mount({\n      childTemplate: '<div><slot></slot></div>'\n    })\n    expect(child.$el.childNodes.length).toBe(0)\n  })\n\n  it('default slot', done => {\n    mount({\n      childTemplate: '<div><slot></slot></div>',\n      parentContent: '<p>{{ msg }}</p>'\n    })\n    expect(child.$el.tagName).toBe('DIV')\n    expect(child.$el.children[0].tagName).toBe('P')\n    expect(child.$el.children[0].textContent).toBe('parent message')\n    vm.msg = 'changed'\n    waitForUpdate(() => {\n      expect(child.$el.children[0].textContent).toBe('changed')\n    }).then(done)\n  })\n\n  it('named slot', done => {\n    mount({\n      childTemplate: '<div><slot name=\"test\"></slot></div>',\n      parentContent: '<p slot=\"test\">{{ msg }}</p>'\n    })\n    expect(child.$el.tagName).toBe('DIV')\n    expect(child.$el.children[0].tagName).toBe('P')\n    expect(child.$el.children[0].textContent).toBe('parent message')\n    vm.msg = 'changed'\n    waitForUpdate(() => {\n      expect(child.$el.children[0].textContent).toBe('changed')\n    }).then(done)\n  })\n\n  it('named slot with 0 as a number', done => {\n    mount({\n      childTemplate: '<div><slot :name=\"0\"></slot></div>',\n      parentContent: '<p :slot=\"0\">{{ msg }}</p>'\n    })\n    expect(child.$el.tagName).toBe('DIV')\n    expect(child.$el.children[0].tagName).toBe('P')\n    expect(child.$el.children[0].textContent).toBe('parent message')\n    vm.msg = 'changed'\n    waitForUpdate(() => {\n      expect(child.$el.children[0].textContent).toBe('changed')\n    }).then(done)\n  })\n\n  it('fallback content', () => {\n    mount({\n      childTemplate: '<div><slot><p>{{msg}}</p></slot></div>'\n    })\n    expect(child.$el.children[0].tagName).toBe('P')\n    expect(child.$el.textContent).toBe('child message')\n  })\n\n  it('fallback content with multiple named slots', () => {\n    mount({\n      childTemplate: `\n        <div>\n          <slot name=\"a\"><p>fallback a</p></slot>\n          <slot name=\"b\">fallback b</slot>\n        </div>\n      `,\n      parentContent: '<p slot=\"b\">slot b</p>'\n    })\n    expect(child.$el.children.length).toBe(2)\n    expect(child.$el.children[0].textContent).toBe('fallback a')\n    expect(child.$el.children[1].textContent).toBe('slot b')\n  })\n\n  it('fallback content with mixed named/unnamed slots', () => {\n    mount({\n      childTemplate: `\n        <div>\n          <slot><p>fallback a</p></slot>\n          <slot name=\"b\">fallback b</slot>\n        </div>\n      `,\n      parentContent: '<p slot=\"b\">slot b</p>'\n    })\n    expect(child.$el.children.length).toBe(2)\n    expect(child.$el.children[0].textContent).toBe('fallback a')\n    expect(child.$el.children[1].textContent).toBe('slot b')\n  })\n\n  it('selector matching multiple elements', () => {\n    mount({\n      childTemplate: '<div><slot name=\"t\"></slot></div>',\n      parentContent: '<p slot=\"t\">1</p><div></div><p slot=\"t\">2</p>'\n    })\n    expect(child.$el.innerHTML).toBe('<p>1</p><p>2</p>')\n  })\n\n  it('default content should only render parts not selected', () => {\n    mount({\n      childTemplate: `\n        <div>\n          <slot name=\"a\"></slot>\n          <slot></slot>\n          <slot name=\"b\"></slot>\n        </div>\n      `,\n      parentContent: '<div>foo</div><p slot=\"a\">1</p><p slot=\"b\">2</p>'\n    })\n    expect(child.$el.innerHTML).toBe('<p>1</p> <div>foo</div> <p>2</p>')\n  })\n\n  it('name should only match children', function () {\n    mount({\n      childTemplate: `\n        <div>\n          <slot name=\"a\"><p>fallback a</p></slot>\n          <slot name=\"b\"><p>fallback b</p></slot>\n          <slot name=\"c\"><p>fallback c</p></slot>\n        </div>\n      `,\n      parentContent: `\n        '<p slot=\"b\">select b</p>\n        '<span><p slot=\"b\">nested b</p></span>\n        '<span><p slot=\"c\">nested c</p></span>\n      `\n    })\n    expect(child.$el.children.length).toBe(3)\n    expect(child.$el.children[0].textContent).toBe('fallback a')\n    expect(child.$el.children[1].textContent).toBe('select b')\n    expect(child.$el.children[2].textContent).toBe('fallback c')\n  })\n\n  it('should accept expressions in slot attribute and slot names', () => {\n    mount({\n      childTemplate: `<div><slot :name=\"'a'\"></slot></div>`,\n      parentContent: `<p>one</p><p :slot=\"'a'\">two</p>`\n    })\n    expect(child.$el.innerHTML).toBe('<p>two</p>')\n  })\n\n  it('slot inside v-if', done => {\n    const vm = new Vue({\n      data: {\n        a: 1,\n        b: 2,\n        show: true\n      },\n      template: '<test :show=\"show\"><p slot=\"b\">{{b}}</p><p>{{a}}</p></test>',\n      components: {\n        test: {\n          props: ['show'],\n          template: '<div v-if=\"show\"><slot></slot><slot name=\"b\"></slot></div>'\n        }\n      }\n    }).$mount()\n    expect(vm.$el.textContent).toBe('12')\n    vm.a = 2\n    waitForUpdate(() => {\n      expect(vm.$el.textContent).toBe('22')\n      vm.show = false\n    }).then(() => {\n      expect(vm.$el.textContent).toBe('')\n      vm.show = true\n      vm.a = 3\n    }).then(() => {\n      expect(vm.$el.textContent).toBe('32')\n    }).then(done)\n  })\n\n  it('slot inside v-for', () => {\n    mount({\n      childTemplate: '<div><slot v-for=\"i in 3\" :name=\"i\"></slot></div>',\n      parentContent: '<p v-for=\"i in 3\" :slot=\"i\">{{ i - 1 }}</p>'\n    })\n    expect(child.$el.innerHTML).toBe('<p>0</p><p>1</p><p>2</p>')\n  })\n\n  it('nested slots', done => {\n    const vm = new Vue({\n      template: '<test><test2><p>{{ msg }}</p></test2></test>',\n      data: {\n        msg: 'foo'\n      },\n      components: {\n        test: {\n          template: '<div><slot></slot></div>'\n        },\n        test2: {\n          template: '<div><slot></slot></div>'\n        }\n      }\n    }).$mount()\n    expect(vm.$el.innerHTML).toBe('<div><p>foo</p></div>')\n    vm.msg = 'bar'\n    waitForUpdate(() => {\n      expect(vm.$el.innerHTML).toBe('<div><p>bar</p></div>')\n    }).then(done)\n  })\n\n  it('v-if on inserted content', done => {\n    const vm = new Vue({\n      template: '<test><p v-if=\"ok\">{{ msg }}</p></test>',\n      data: {\n        ok: true,\n        msg: 'hi'\n      },\n      components: {\n        test: {\n          template: '<div><slot>fallback</slot></div>'\n        }\n      }\n    }).$mount()\n    expect(vm.$el.innerHTML).toBe('<p>hi</p>')\n    vm.ok = false\n    waitForUpdate(() => {\n      expect(vm.$el.innerHTML).toBe('fallback')\n      vm.ok = true\n      vm.msg = 'bye'\n    }).then(() => {\n      expect(vm.$el.innerHTML).toBe('<p>bye</p>')\n    }).then(done)\n  })\n\n  it('template slot', function () {\n    const vm = new Vue({\n      template: '<test><template slot=\"test\">hello</template></test>',\n      components: {\n        test: {\n          template: '<div><slot name=\"test\"></slot> world</div>'\n        }\n      }\n    }).$mount()\n    expect(vm.$el.innerHTML).toBe('hello world')\n  })\n\n  it('combined with v-for', () => {\n    const vm = new Vue({\n      template: '<div><test v-for=\"i in 3\" :key=\"i\">{{ i }}</test></div>',\n      components: {\n        test: {\n          template: '<div><slot></slot></div>'\n        }\n      }\n    }).$mount()\n    expect(vm.$el.innerHTML).toBe('<div>1</div><div>2</div><div>3</div>')\n  })\n\n  it('inside template v-if', () => {\n    mount({\n      childTemplate: `\n        <div>\n          <template v-if=\"true\"><slot></slot></template>\n        </div>\n      `,\n      parentContent: 'foo'\n    })\n    expect(child.$el.innerHTML).toBe('foo')\n  })\n\n  it('default slot should use fallback content if has only whitespace', () => {\n    mount({\n      childTemplate: `\n        <div>\n          <slot name=\"first\"><p>first slot</p></slot>\n          <slot><p>this is the default slot</p></slot>\n          <slot name=\"second\"><p>second named slot</p></slot>\n        </div>\n      `,\n      parentContent: `<div slot=\"first\">1</div> <div slot=\"second\">2</div> <div slot=\"second\">2+</div>`\n    })\n    expect(child.$el.innerHTML).toBe(\n      '<div>1</div> <p>this is the default slot</p> <div>2</div><div>2+</div>'\n    )\n  })\n\n  it('programmatic access to $slots', () => {\n    const vm = new Vue({\n      template: '<test><p slot=\"a\">A</p><div>C</div><p slot=\"b\">B</p></test>',\n      components: {\n        test: {\n          render () {\n            expect(this.$slots.a.length).toBe(1)\n            expect(this.$slots.a[0].tag).toBe('p')\n            expect(this.$slots.a[0].children.length).toBe(1)\n            expect(this.$slots.a[0].children[0].text).toBe('A')\n\n            expect(this.$slots.b.length).toBe(1)\n            expect(this.$slots.b[0].tag).toBe('p')\n            expect(this.$slots.b[0].children.length).toBe(1)\n            expect(this.$slots.b[0].children[0].text).toBe('B')\n\n            expect(this.$slots.default.length).toBe(1)\n            expect(this.$slots.default[0].tag).toBe('div')\n            expect(this.$slots.default[0].children.length).toBe(1)\n            expect(this.$slots.default[0].children[0].text).toBe('C')\n\n            return this.$slots.default[0]\n          }\n        }\n      }\n    }).$mount()\n    expect(vm.$el.tagName).toBe('DIV')\n    expect(vm.$el.textContent).toBe('C')\n  })\n\n  it('warn if user directly returns array', () => {\n    new Vue({\n      template: '<test><div></div></test>',\n      components: {\n        test: {\n          render () {\n            return this.$slots.default\n          }\n        }\n      }\n    }).$mount()\n    expect('Render function should return a single root node').toHaveBeenWarned()\n  })\n\n  // #3254\n  it('should not keep slot name when passed further down', () => {\n    const vm = new Vue({\n      template: '<test><span slot=\"foo\">foo</span></test>',\n      components: {\n        test: {\n          template: '<child><slot name=\"foo\"></slot></child>',\n          components: {\n            child: {\n              template: `\n                <div>\n                  <div class=\"default\"><slot></slot></div>\n                  <div class=\"named\"><slot name=\"foo\"></slot></div>\n                </div>\n              `\n            }\n          }\n        }\n      }\n    }).$mount()\n    expect(vm.$el.querySelector('.default').textContent).toBe('foo')\n    expect(vm.$el.querySelector('.named').textContent).toBe('')\n  })\n\n  it('should not keep slot name when passed further down (nested)', () => {\n    const vm = new Vue({\n      template: '<wrap><test><span slot=\"foo\">foo</span></test></wrap>',\n      components: {\n        wrap: {\n          template: '<div><slot></slot></div>'\n        },\n        test: {\n          template: '<child><slot name=\"foo\"></slot></child>',\n          components: {\n            child: {\n              template: `\n                <div>\n                  <div class=\"default\"><slot></slot></div>\n                  <div class=\"named\"><slot name=\"foo\"></slot></div>\n                </div>\n              `\n            }\n          }\n        }\n      }\n    }).$mount()\n    expect(vm.$el.querySelector('.default').textContent).toBe('foo')\n    expect(vm.$el.querySelector('.named').textContent).toBe('')\n  })\n\n  it('should not keep slot name when passed further down (functional)', () => {\n    const child = {\n      template: `\n        <div>\n          <div class=\"default\"><slot></slot></div>\n          <div class=\"named\"><slot name=\"foo\"></slot></div>\n        </div>\n      `\n    }\n\n    const vm = new Vue({\n      template: '<test><span slot=\"foo\">foo</span></test>',\n      components: {\n        test: {\n          functional: true,\n          render (h, ctx) {\n            const slots = ctx.slots()\n            return h(child, slots.foo)\n          }\n        }\n      }\n    }).$mount()\n    expect(vm.$el.querySelector('.default').textContent).toBe('foo')\n    expect(vm.$el.querySelector('.named').textContent).toBe('')\n  })\n\n  // #3400\n  it('named slots should be consistent across re-renders', done => {\n    const vm = new Vue({\n      template: `\n        <comp>\n          <div slot=\"foo\">foo</div>\n        </comp>\n      `,\n      components: {\n        comp: {\n          data () {\n            return { a: 1 }\n          },\n          template: `<div><slot name=\"foo\"></slot>{{ a }}</div>`\n        }\n      }\n    }).$mount()\n    expect(vm.$el.textContent).toBe('foo1')\n    vm.$children[0].a = 2\n    waitForUpdate(() => {\n      expect(vm.$el.textContent).toBe('foo2')\n    }).then(done)\n  })\n\n  // #3437\n  it('should correctly re-create components in slot', done => {\n    const calls = []\n    const vm = new Vue({\n      template: `\n        <comp ref=\"child\">\n          <div slot=\"foo\">\n            <child></child>\n          </div>\n        </comp>\n      `,\n      components: {\n        comp: {\n          data () {\n            return { ok: true }\n          },\n          template: `<div><slot name=\"foo\" v-if=\"ok\"></slot></div>`\n        },\n        child: {\n          template: '<div>child</div>',\n          created () {\n            calls.push(1)\n          },\n          destroyed () {\n            calls.push(2)\n          }\n        }\n      }\n    }).$mount()\n\n    expect(calls).toEqual([1])\n    vm.$refs.child.ok = false\n    waitForUpdate(() => {\n      expect(calls).toEqual([1, 2])\n      vm.$refs.child.ok = true\n    }).then(() => {\n      expect(calls).toEqual([1, 2, 1])\n      vm.$refs.child.ok = false\n    }).then(() => {\n      expect(calls).toEqual([1, 2, 1, 2])\n    }).then(done)\n  })\n\n  it('warn duplicate slots', () => {\n    new Vue({\n      template: `<div>\n        <test>\n          <div>foo</div>\n          <div slot=\"a\">bar</div>\n        </test>\n      </div>`,\n      components: {\n        test: {\n          template: `<div>\n            <slot></slot><slot></slot>\n            <div v-for=\"i in 3\"><slot name=\"a\"></slot></div>\n          </div>`\n        }\n      }\n    }).$mount()\n    expect('Duplicate presence of slot \"default\"').toHaveBeenWarned()\n    expect('Duplicate presence of slot \"a\"').toHaveBeenWarned()\n  })\n\n  it('should not warn valid conditional slots', () => {\n    new Vue({\n      template: `<div>\n        <test>\n          <div>foo</div>\n        </test>\n      </div>`,\n      components: {\n        test: {\n          template: `<div>\n            <slot v-if=\"true\"></slot>\n            <slot v-else></slot>\n          </div>`\n        }\n      }\n    }).$mount()\n    expect('Duplicate presence of slot \"default\"').not.toHaveBeenWarned()\n  })\n\n  // #3518\n  it('events should not break when slot is toggled by v-if', done => {\n    const spy = jasmine.createSpy()\n    const vm = new Vue({\n      template: `<test><div class=\"click\" @click=\"test\">hi</div></test>`,\n      methods: {\n        test: spy\n      },\n      components: {\n        test: {\n          data: () => ({\n            toggle: true\n          }),\n          template: `<div v-if=\"toggle\"><slot></slot></div>`\n        }\n      }\n    }).$mount()\n\n    expect(vm.$el.textContent).toBe('hi')\n    vm.$children[0].toggle = false\n    waitForUpdate(() => {\n      vm.$children[0].toggle = true\n    }).then(() => {\n      triggerEvent(vm.$el.querySelector('.click'), 'click')\n      expect(spy).toHaveBeenCalled()\n    }).then(done)\n  })\n\n  it('renders static tree with text', () => {\n    const vm = new Vue({\n      template: `<div><test><template><div></div>Hello<div></div></template></test></div>`,\n      components: {\n        test: {\n          template: '<div><slot></slot></div>'\n        }\n      }\n    })\n    vm.$mount()\n    expect('Error when rendering root').not.toHaveBeenWarned()\n  })\n\n  // #3872\n  it('functional component as slot', () => {\n    const vm = new Vue({\n      template: `\n        <parent>\n          <child>one</child>\n          <child slot=\"a\">two</child>\n        </parent>\n      `,\n      components: {\n        parent: {\n          template: `<div><slot name=\"a\"></slot><slot></slot></div>`\n        },\n        child: {\n          functional: true,\n          render (h, { slots }) {\n            return h('div', slots().default)\n          }\n        }\n      }\n    }).$mount()\n    expect(vm.$el.innerHTML.trim()).toBe('<div>two</div><div>one</div>')\n  })\n\n  // #4209\n  it('slot of multiple text nodes should not be infinitely merged', done => {\n    const wrap = {\n      template: `<inner ref=\"inner\">foo<slot></slot></inner>`,\n      components: {\n        inner: {\n          data: () => ({ a: 1 }),\n          template: `<div>{{a}}<slot></slot></div>`\n        }\n      }\n    }\n    const vm = new Vue({\n      template: `<wrap ref=\"wrap\">bar</wrap>`,\n      components: { wrap }\n    }).$mount()\n\n    expect(vm.$el.textContent).toBe('1foobar')\n    vm.$refs.wrap.$refs.inner.a++\n    waitForUpdate(() => {\n      expect(vm.$el.textContent).toBe('2foobar')\n    }).then(done)\n  })\n\n  // #4315\n  it('functional component passing slot content to stateful child component', done => {\n    const ComponentWithSlots = {\n      render (h) {\n        return h('div', this.$slots.slot1)\n      }\n    }\n\n    const FunctionalComp = {\n      functional: true,\n      render (h) {\n        return h(ComponentWithSlots, [h('span', { slot: 'slot1' }, 'foo')])\n      }\n    }\n\n    const vm = new Vue({\n      data: { n: 1 },\n      render (h) {\n        return h('div', [this.n, h(FunctionalComp)])\n      }\n    }).$mount()\n\n    expect(vm.$el.textContent).toBe('1foo')\n    vm.n++\n    waitForUpdate(() => {\n      // should not lose named slot\n      expect(vm.$el.textContent).toBe('2foo')\n    }).then(done)\n  })\n\n  it('the elements of slot should be updated correctly', done => {\n    const vm = new Vue({\n      data: { n: 1 },\n      template: '<div><test><span v-for=\"i in n\" :key=\"i\">{{ i }}</span><input value=\"a\"/></test></div>',\n      components: {\n        test: {\n          template: '<div><slot></slot></div>'\n        }\n      }\n    }).$mount()\n    expect(vm.$el.innerHTML).toBe('<div><span>1</span><input value=\"a\"></div>')\n    const input = vm.$el.querySelector('input')\n    input.value = 'b'\n    vm.n++\n    waitForUpdate(() => {\n      expect(vm.$el.innerHTML).toBe('<div><span>1</span><span>2</span><input value=\"a\"></div>')\n      expect(vm.$el.querySelector('input')).toBe(input)\n      expect(vm.$el.querySelector('input').value).toBe('b')\n    }).then(done)\n  })\n\n  // GitHub issue #5888\n  it('should resolve correctly slot with keep-alive', () => {\n    const vm = new Vue({\n      template: `\n      <div>\n        <container>\n          <keep-alive slot=\"foo\">\n            <child></child>\n          </keep-alive>\n        </container>\n      </div>\n      `,\n      components: {\n        container: {\n          template:\n            '<div><slot>default</slot><slot name=\"foo\">named</slot></div>'\n        },\n        child: {\n          template: '<span>foo</span>'\n        }\n      }\n    }).$mount()\n    expect(vm.$el.innerHTML).toBe('<div>default<span>foo</span></div>')\n  })\n\n  // #6372, #6915\n  it('should handle nested components in slots properly', done => {\n    const TestComponent = {\n      template: `\n        <component :is=\"toggleEl ? 'b' : 'i'\">\n          <slot />\n        </component>\n      `,\n      data () {\n        return {\n          toggleEl: true\n        }\n      }\n    }\n\n    const vm = new Vue({\n      template: `\n        <div>\n          <test-component ref=\"test\">\n            <div>\n              <foo/>\n            </div>\n            <bar>\n              <foo/>\n            </bar>\n          </test-component>\n        </div>\n      `,\n      components: {\n        TestComponent,\n        foo: {\n          template: `<div>foo</div>`\n        },\n        bar: {\n          template: `<div>bar<slot/></div>`\n        }\n      }\n    }).$mount()\n\n    expect(vm.$el.innerHTML).toBe(`<b><div><div>foo</div></div> <div>bar<div>foo</div></div></b>`)\n\n    vm.$refs.test.toggleEl = false\n    waitForUpdate(() => {\n      expect(vm.$el.innerHTML).toBe(`<i><div><div>foo</div></div> <div>bar<div>foo</div></div></i>`)\n    }).then(done)\n  })\n\n  it('should preserve slot attribute if not absorbed by a Vue component', () => {\n    const vm = new Vue({\n      template: `\n        <div>\n          <div slot=\"foo\"></div>\n        </div>\n      `\n    }).$mount()\n    expect(vm.$el.children[0].getAttribute('slot')).toBe('foo')\n  })\n\n  it('passing a slot down as named slot', () => {\n    const Bar = {\n      template: `<div class=\"bar\"><slot name=\"foo\"/></div>`\n    }\n\n    const Foo = {\n      components: { Bar },\n      template: `<div class=\"foo\"><bar><slot slot=\"foo\"/></bar></div>`\n    }\n\n    const vm = new Vue({\n      components: { Foo },\n      template: `<div><foo>hello</foo></div>`\n    }).$mount()\n\n    expect(vm.$el.innerHTML).toBe('<div class=\"foo\"><div class=\"bar\">hello</div></div>')\n  })\n\n  it('fallback content for named template slot', () => {\n    const Bar = {\n      template: `<div class=\"bar\"><slot name=\"foo\">fallback</slot></div>`\n    }\n\n    const Foo = {\n      components: { Bar },\n      template: `<div class=\"foo\"><bar><template slot=\"foo\"/><slot/></template></bar></div>`\n    }\n\n    const vm = new Vue({\n      components: { Foo },\n      template: `<div><foo></foo></div>`\n    }).$mount()\n\n    expect(vm.$el.innerHTML).toBe('<div class=\"foo\"><div class=\"bar\">fallback</div></div>')\n  })\n\n  // #7106\n  it('should not lose functional slot across renders', done => {\n    const One = {\n      data: () => ({\n        foo: true\n      }),\n      render (h) {\n        this.foo\n        return h('div', this.$slots.slot)\n      }\n    }\n\n    const Two = {\n      render (h) {\n        return h('span', this.$slots.slot)\n      }\n    }\n\n    const Three = {\n      functional: true,\n      render: (h, { children }) => h('span', children)\n    }\n\n    const vm = new Vue({\n      template: `\n        <div>\n          <one ref=\"one\">\n            <two slot=\"slot\">\n              <three slot=\"slot\">hello</three>\n            </two>\n          </one>\n        </div>\n      `,\n      components: { One, Two, Three }\n    }).$mount()\n\n    expect(vm.$el.textContent).toBe('hello')\n    // trigger re-render of <one>\n    vm.$refs.one.foo = false\n    waitForUpdate(() => {\n      // should still be there\n      expect(vm.$el.textContent).toBe('hello')\n    }).then(done)\n  })\n\n  it('should allow passing named slots as raw children down multiple layers of functional component', () => {\n    const CompB = {\n      functional: true,\n      render (h, { slots }) {\n        return slots().foo\n      }\n    }\n\n    const CompA = {\n      functional: true,\n      render (h, { children }) {\n        return h(CompB, children)\n      }\n    }\n\n    const vm = new Vue({\n      components: {\n        CompA\n      },\n      template: `\n        <div>\n          <comp-a>\n            <span slot=\"foo\">foo</span>\n          </comp-a>\n        </div>\n      `\n    }).$mount()\n\n    expect(vm.$el.textContent).toBe('foo')\n  })\n\n  // #7817\n  it('should not match wrong named slot in functional component on re-render', done => {\n    const Functional = {\n      functional: true,\n      render: (h, ctx) => ctx.slots().default\n    }\n\n    const Stateful = {\n      data () {\n        return { ok: true }\n      },\n      render (h) {\n        this.ok // register dep\n        return h('div', [\n          h(Functional, this.$slots.named)\n        ])\n      }\n    }\n\n    const vm = new Vue({\n      template: `<stateful ref=\"stateful\"><div slot=\"named\">foo</div></stateful>`,\n      components: { Stateful }\n    }).$mount()\n\n    expect(vm.$el.textContent).toBe('foo')\n    vm.$refs.stateful.ok = false\n    waitForUpdate(() => {\n      expect(vm.$el.textContent).toBe('foo')\n    }).then(done)\n  })\n})\n"
  },
  {
    "path": "vue/test/unit/features/component/component.spec.js",
    "content": "import Vue from 'vue'\n\ndescribe('Component', () => {\n  it('static', () => {\n    const vm = new Vue({\n      template: '<test></test>',\n      components: {\n        test: {\n          data () {\n            return { a: 123 }\n          },\n          template: '<span>{{a}}</span>'\n        }\n      }\n    }).$mount()\n    expect(vm.$el.tagName).toBe('SPAN')\n    expect(vm.$el.innerHTML).toBe('123')\n  })\n\n  it('using component in restricted elements', () => {\n    const vm = new Vue({\n      template: '<div><table><tbody><test></test></tbody></table></div>',\n      components: {\n        test: {\n          data () {\n            return { a: 123 }\n          },\n          template: '<tr><td>{{a}}</td></tr>'\n        }\n      }\n    }).$mount()\n    expect(vm.$el.innerHTML).toBe('<table><tbody><tr><td>123</td></tr></tbody></table>')\n  })\n\n  it('\"is\" attribute', () => {\n    const vm = new Vue({\n      template: '<div><table><tbody><tr is=\"test\"></tr></tbody></table></div>',\n      components: {\n        test: {\n          data () {\n            return { a: 123 }\n          },\n          template: '<tr><td>{{a}}</td></tr>'\n        }\n      }\n    }).$mount()\n    expect(vm.$el.innerHTML).toBe('<table><tbody><tr><td>123</td></tr></tbody></table>')\n  })\n\n  it('inline-template', () => {\n    const vm = new Vue({\n      template: '<div><test inline-template><span>{{a}}</span></test></div>',\n      data: {\n        a: 'parent'\n      },\n      components: {\n        test: {\n          data () {\n            return { a: 'child' }\n          }\n        }\n      }\n    }).$mount()\n    expect(vm.$el.innerHTML).toBe('<span>child</span>')\n  })\n\n  it('fragment instance warning', () => {\n    new Vue({\n      template: '<test></test>',\n      components: {\n        test: {\n          data () {\n            return { a: 123, b: 234 }\n          },\n          template: '<p>{{a}}</p><p>{{b}}</p>'\n        }\n      }\n    }).$mount()\n    expect('Component template should contain exactly one root element').toHaveBeenWarned()\n  })\n\n  it('dynamic', done => {\n    const vm = new Vue({\n      template: '<component :is=\"view\" :view=\"view\"></component>',\n      data: {\n        view: 'view-a'\n      },\n      components: {\n        'view-a': {\n          template: '<div>foo {{view}}</div>',\n          data () {\n            return { view: 'a' }\n          }\n        },\n        'view-b': {\n          template: '<div>bar {{view}}</div>',\n          data () {\n            return { view: 'b' }\n          }\n        }\n      }\n    }).$mount()\n    expect(vm.$el.outerHTML).toBe('<div view=\"view-a\">foo a</div>')\n    vm.view = 'view-b'\n    waitForUpdate(() => {\n      expect(vm.$el.outerHTML).toBe('<div view=\"view-b\">bar b</div>')\n      vm.view = ''\n    })\n      .then(() => {\n        expect(vm.$el.nodeType).toBe(8)\n        expect(vm.$el.data).toBe('')\n      }).then(done)\n  })\n\n  it('dynamic with props', done => {\n    const vm = new Vue({\n      template: '<component :is=\"view\" :view=\"view\"></component>',\n      data: {\n        view: 'view-a'\n      },\n      components: {\n        'view-a': {\n          template: '<div>foo {{view}}</div>',\n          props: ['view']\n        },\n        'view-b': {\n          template: '<div>bar {{view}}</div>',\n          props: ['view']\n        }\n      }\n    }).$mount()\n    expect(vm.$el.outerHTML).toBe('<div>foo view-a</div>')\n    vm.view = 'view-b'\n    waitForUpdate(() => {\n      expect(vm.$el.outerHTML).toBe('<div>bar view-b</div>')\n      vm.view = ''\n    }).then(() => {\n      expect(vm.$el.nodeType).toBe(8)\n      expect(vm.$el.data).toBe('')\n    }).then(done)\n  })\n\n  it(':is using raw component constructor', () => {\n    const vm = new Vue({\n      template:\n        '<div>' +\n          '<component :is=\"$options.components.test\"></component>' +\n          '<component :is=\"$options.components.async\"></component>' +\n        '</div>',\n      components: {\n        test: {\n          template: '<span>foo</span>'\n        },\n        async: function (resolve) {\n          resolve({\n            template: '<span>bar</span>'\n          })\n        }\n      }\n    }).$mount()\n    expect(vm.$el.innerHTML).toBe('<span>foo</span><span>bar</span>')\n  })\n\n  it('dynamic combined with v-for', done => {\n    const vm = new Vue({\n      template:\n        '<div>' +\n          '<component v-for=\"(c, i) in comps\" :key=\"i\" :is=\"c.type\"></component>' +\n        '</div>',\n      data: {\n        comps: [{ type: 'one' }, { type: 'two' }]\n      },\n      components: {\n        one: {\n          template: '<span>one</span>'\n        },\n        two: {\n          template: '<span>two</span>'\n        }\n      }\n    }).$mount()\n    expect(vm.$el.innerHTML).toBe('<span>one</span><span>two</span>')\n    vm.comps[1].type = 'one'\n    waitForUpdate(() => {\n      expect(vm.$el.innerHTML).toBe('<span>one</span><span>one</span>')\n    }).then(done)\n  })\n\n  it('dynamic elements with domProps', done => {\n    const vm = new Vue({\n      template: '<component :is=\"view\" :value.prop=\"val\"></component>',\n      data: {\n        view: 'input',\n        val: 'hello'\n      }\n    }).$mount()\n    expect(vm.$el.tagName).toBe('INPUT')\n    expect(vm.$el.value).toBe('hello')\n    vm.view = 'textarea'\n    vm.val += ' world'\n    waitForUpdate(() => {\n      expect(vm.$el.tagName).toBe('TEXTAREA')\n      expect(vm.$el.value).toBe('hello world')\n      vm.view = ''\n    }).then(done)\n  })\n\n  it('should compile parent template directives & content in parent scope', done => {\n    const vm = new Vue({\n      data: {\n        ok: false,\n        message: 'hello'\n      },\n      template: '<test v-show=\"ok\">{{message}}</test>',\n      components: {\n        test: {\n          template: '<div><slot></slot> {{message}}</div>',\n          data () {\n            return {\n              message: 'world'\n            }\n          }\n        }\n      }\n    }).$mount()\n    expect(vm.$el.style.display).toBe('none')\n    expect(vm.$el.textContent).toBe('hello world')\n    vm.ok = true\n    vm.message = 'bye'\n    waitForUpdate(() => {\n      expect(vm.$el.style.display).toBe('')\n      expect(vm.$el.textContent).toBe('bye world')\n    }).then(done)\n  })\n\n  it('parent content + v-if', done => {\n    const vm = new Vue({\n      data: {\n        ok: false,\n        message: 'hello'\n      },\n      template: '<test v-if=\"ok\">{{message}}</test>',\n      components: {\n        test: {\n          template: '<div><slot></slot> {{message}}</div>',\n          data () {\n            return {\n              message: 'world'\n            }\n          }\n        }\n      }\n    }).$mount()\n    expect(vm.$el.textContent).toBe('')\n    expect(vm.$children.length).toBe(0)\n    vm.ok = true\n    waitForUpdate(() => {\n      expect(vm.$children.length).toBe(1)\n      expect(vm.$el.textContent).toBe('hello world')\n    }).then(done)\n  })\n\n  it('props', () => {\n    const vm = new Vue({\n      data: {\n        list: [{ a: 1 }, { a: 2 }]\n      },\n      template: '<test :collection=\"list\"></test>',\n      components: {\n        test: {\n          template: '<ul><li v-for=\"item in collection\">{{item.a}}</li></ul>',\n          props: ['collection']\n        }\n      }\n    }).$mount()\n    expect(vm.$el.outerHTML).toBe('<ul><li>1</li><li>2</li></ul>')\n  })\n\n  it('should warn when using camelCased props in in-DOM template', () => {\n    new Vue({\n      data: {\n        list: [{ a: 1 }, { a: 2 }]\n      },\n      template: '<test :somecollection=\"list\"></test>', // <-- simulate lowercased template\n      components: {\n        test: {\n          template: '<ul><li v-for=\"item in someCollection\">{{item.a}}</li></ul>',\n          props: ['someCollection']\n        }\n      }\n    }).$mount()\n    expect(\n      'You should probably use \"some-collection\" instead of \"someCollection\".'\n    ).toHaveBeenTipped()\n  })\n\n  it('should warn when using camelCased events in in-DOM template', () => {\n    new Vue({\n      template: '<test @foobar=\"a++\"></test>', // <-- simulate lowercased template\n      components: {\n        test: {\n          template: '<div></div>',\n          created () {\n            this.$emit('fooBar')\n          }\n        }\n      }\n    }).$mount()\n    expect(\n      'You should probably use \"foo-bar\" instead of \"fooBar\".'\n    ).toHaveBeenTipped()\n  })\n\n  it('not found component should not throw', () => {\n    expect(function () {\n      new Vue({\n        template: '<div is=\"non-existent\"></div>'\n      })\n    }).not.toThrow()\n  })\n\n  it('properly update replaced higher-order component root node', done => {\n    const vm = new Vue({\n      data: {\n        color: 'red'\n      },\n      template: '<test id=\"foo\" :class=\"color\"></test>',\n      components: {\n        test: {\n          data () {\n            return { tag: 'div' }\n          },\n          render (h) {\n            return h(this.tag, { class: 'test' }, 'hi')\n          }\n        }\n      }\n    }).$mount()\n\n    expect(vm.$el.tagName).toBe('DIV')\n    expect(vm.$el.id).toBe('foo')\n    expect(vm.$el.className).toBe('test red')\n\n    vm.color = 'green'\n    waitForUpdate(() => {\n      expect(vm.$el.tagName).toBe('DIV')\n      expect(vm.$el.id).toBe('foo')\n      expect(vm.$el.className).toBe('test green')\n      vm.$children[0].tag = 'p'\n    }).then(() => {\n      expect(vm.$el.tagName).toBe('P')\n      expect(vm.$el.id).toBe('foo')\n      expect(vm.$el.className).toBe('test green')\n      vm.color = 'red'\n    }).then(() => {\n      expect(vm.$el.tagName).toBe('P')\n      expect(vm.$el.id).toBe('foo')\n      expect(vm.$el.className).toBe('test red')\n    }).then(done)\n  })\n\n  it('catch component render error and preserve previous vnode', done => {\n    const spy = jasmine.createSpy()\n    Vue.config.errorHandler = spy\n    const vm = new Vue({\n      data: {\n        a: {\n          b: 123\n        }\n      },\n      render (h) {\n        return h('div', [this.a.b])\n      }\n    }).$mount()\n    expect(vm.$el.textContent).toBe('123')\n    expect(spy).not.toHaveBeenCalled()\n    vm.a = null\n    waitForUpdate(() => {\n      expect(spy).toHaveBeenCalled()\n      expect(vm.$el.textContent).toBe('123') // should preserve rendered DOM\n      vm.a = { b: 234 }\n    }).then(() => {\n      expect(vm.$el.textContent).toBe('234') // should be able to recover\n      Vue.config.errorHandler = null\n    }).then(done)\n  })\n\n  it('relocates node without error', done => {\n    const el = document.createElement('div')\n    document.body.appendChild(el)\n    const target = document.createElement('div')\n    document.body.appendChild(target)\n\n    const Test = {\n      render (h) {\n        return h('div', { class: 'test' }, this.$slots.default)\n      },\n      mounted () {\n        target.appendChild(this.$el)\n      },\n      beforeDestroy () {\n        const parent = this.$el.parentNode\n        if (parent) {\n          parent.removeChild(this.$el)\n        }\n      }\n    }\n    const vm = new Vue({\n      data () {\n        return {\n          view: true\n        }\n      },\n      template: `<div><test v-if=\"view\">Test</test></div>`,\n      components: {\n        test: Test\n      }\n    }).$mount(el)\n\n    expect(el.outerHTML).toBe('<div></div>')\n    expect(target.outerHTML).toBe('<div><div class=\"test\">Test</div></div>')\n    vm.view = false\n    waitForUpdate(() => {\n      expect(el.outerHTML).toBe('<div></div>')\n      expect(target.outerHTML).toBe('<div></div>')\n      vm.$destroy()\n    }).then(done)\n  })\n})\n"
  },
  {
    "path": "vue/test/unit/features/debug.spec.js",
    "content": "import Vue from 'vue'\nimport { formatComponentName, warn } from 'core/util/debug'\n\ndescribe('Debug utilities', () => {\n  it('properly format component names', () => {\n    const vm = new Vue()\n    expect(formatComponentName(vm)).toBe('<Root>')\n\n    vm.$root = null\n    vm.$options.name = 'hello-there'\n    expect(formatComponentName(vm)).toBe('<HelloThere>')\n\n    vm.$options.name = null\n    vm.$options._componentTag = 'foo-bar-1'\n    expect(formatComponentName(vm)).toBe('<FooBar1>')\n\n    vm.$options._componentTag = null\n    vm.$options.__file = '/foo/bar/baz/SomeThing.vue'\n    expect(formatComponentName(vm)).toBe(`<SomeThing> at ${vm.$options.__file}`)\n    expect(formatComponentName(vm, false)).toBe('<SomeThing>')\n\n    vm.$options.__file = 'C:\\\\foo\\\\bar\\\\baz\\\\windows_file.vue'\n    expect(formatComponentName(vm)).toBe(`<WindowsFile> at ${vm.$options.__file}`)\n    expect(formatComponentName(vm, false)).toBe('<WindowsFile>')\n  })\n\n  it('generate correct component hierarchy trace', () => {\n    const one = {\n      name: 'one',\n      render: h => h(two)\n    }\n    const two = {\n      name: 'two',\n      render: h => h(three)\n    }\n    const three = {\n      name: 'three'\n    }\n    new Vue({\n      render: h => h(one)\n    }).$mount()\n\n    expect(\n      `Failed to mount component: template or render function not defined.\n\nfound in\n\n---> <Three>\n       <Two>\n         <One>\n           <Root>`\n    ).toHaveBeenWarned()\n  })\n\n  it('generate correct component hierarchy trace (recursive)', () => {\n    let i = 0\n    const one = {\n      name: 'one',\n      render: h => i++ < 5 ? h(one) : h(two)\n    }\n    const two = {\n      name: 'two',\n      render: h => h(three)\n    }\n    const three = {\n      name: 'three'\n    }\n    new Vue({\n      render: h => h(one)\n    }).$mount()\n\n    expect(\n      `Failed to mount component: template or render function not defined.\n\nfound in\n\n---> <Three>\n       <Two>\n         <One>... (5 recursive calls)\n           <Root>`\n    ).toHaveBeenWarned()\n  })\n\n  describe('warn', () => {\n    const msg = 'message'\n    const vm = new Vue()\n\n    it('calls warnHandler if warnHandler is set', () => {\n      Vue.config.warnHandler = jasmine.createSpy()\n\n      warn(msg, vm)\n\n      expect(Vue.config.warnHandler).toHaveBeenCalledWith(msg, vm, jasmine.any(String))\n\n      Vue.config.warnHandler = null\n    })\n\n    it('calls console.error if silent is false', () => {\n      Vue.config.silent = false\n\n      warn(msg, vm)\n\n      expect(msg).toHaveBeenWarned()\n      expect(console.error).toHaveBeenCalled()\n    })\n\n    it('does not call console.error if silent is true', () => {\n      Vue.config.silent = true\n\n      warn(msg, vm)\n\n      expect(console.error).not.toHaveBeenCalled()\n\n      Vue.config.silent = false\n    })\n  })\n})\n"
  },
  {
    "path": "vue/test/unit/features/directives/bind.spec.js",
    "content": "import Vue from 'vue'\n\ndescribe('Directive v-bind', () => {\n  it('normal attr', done => {\n    const vm = new Vue({\n      template: '<div><span :test=\"foo\">hello</span></div>',\n      data: { foo: 'ok' }\n    }).$mount()\n    expect(vm.$el.firstChild.getAttribute('test')).toBe('ok')\n    vm.foo = 'again'\n    waitForUpdate(() => {\n      expect(vm.$el.firstChild.getAttribute('test')).toBe('again')\n      vm.foo = null\n    }).then(() => {\n      expect(vm.$el.firstChild.hasAttribute('test')).toBe(false)\n      vm.foo = false\n    }).then(() => {\n      expect(vm.$el.firstChild.hasAttribute('test')).toBe(false)\n      vm.foo = true\n    }).then(() => {\n      expect(vm.$el.firstChild.getAttribute('test')).toBe('true')\n      vm.foo = 0\n    }).then(() => {\n      expect(vm.$el.firstChild.getAttribute('test')).toBe('0')\n    }).then(done)\n  })\n\n  it('should set property for input value', done => {\n    const vm = new Vue({\n      template: `\n        <div>\n          <input type=\"text\" :value=\"foo\">\n          <input type=\"checkbox\" :checked=\"bar\">\n        </div>\n      `,\n      data: {\n        foo: 'ok',\n        bar: false\n      }\n    }).$mount()\n    expect(vm.$el.firstChild.value).toBe('ok')\n    expect(vm.$el.lastChild.checked).toBe(false)\n    vm.bar = true\n    waitForUpdate(() => {\n      expect(vm.$el.lastChild.checked).toBe(true)\n    }).then(done)\n  })\n\n  it('xlink', done => {\n    const vm = new Vue({\n      template: '<svg><a :xlink:special=\"foo\"></a></svg>',\n      data: {\n        foo: 'ok'\n      }\n    }).$mount()\n    const xlinkNS = 'http://www.w3.org/1999/xlink'\n    expect(vm.$el.firstChild.getAttributeNS(xlinkNS, 'special')).toBe('ok')\n    vm.foo = 'again'\n    waitForUpdate(() => {\n      expect(vm.$el.firstChild.getAttributeNS(xlinkNS, 'special')).toBe('again')\n      vm.foo = null\n    }).then(() => {\n      expect(vm.$el.firstChild.hasAttributeNS(xlinkNS, 'special')).toBe(false)\n      vm.foo = true\n    }).then(() => {\n      expect(vm.$el.firstChild.getAttributeNS(xlinkNS, 'special')).toBe('true')\n    }).then(done)\n  })\n\n  it('enumerated attr', done => {\n    const vm = new Vue({\n      template: '<div><span :draggable=\"foo\">hello</span></div>',\n      data: { foo: true }\n    }).$mount()\n    expect(vm.$el.firstChild.getAttribute('draggable')).toBe('true')\n    vm.foo = 'again'\n    waitForUpdate(() => {\n      expect(vm.$el.firstChild.getAttribute('draggable')).toBe('true')\n      vm.foo = null\n    }).then(() => {\n      expect(vm.$el.firstChild.getAttribute('draggable')).toBe('false')\n      vm.foo = ''\n    }).then(() => {\n      expect(vm.$el.firstChild.getAttribute('draggable')).toBe('true')\n      vm.foo = false\n    }).then(() => {\n      expect(vm.$el.firstChild.getAttribute('draggable')).toBe('false')\n      vm.foo = 'false'\n    }).then(() => {\n      expect(vm.$el.firstChild.getAttribute('draggable')).toBe('false')\n    }).then(done)\n  })\n\n  it('boolean attr', done => {\n    const vm = new Vue({\n      template: '<div><span :disabled=\"foo\">hello</span></div>',\n      data: { foo: true }\n    }).$mount()\n    expect(vm.$el.firstChild.getAttribute('disabled')).toBe('disabled')\n    vm.foo = 'again'\n    waitForUpdate(() => {\n      expect(vm.$el.firstChild.getAttribute('disabled')).toBe('disabled')\n      vm.foo = null\n    }).then(() => {\n      expect(vm.$el.firstChild.hasAttribute('disabled')).toBe(false)\n      vm.foo = ''\n    }).then(() => {\n      expect(vm.$el.firstChild.hasAttribute('disabled')).toBe(true)\n    }).then(done)\n  })\n\n  it('.prop modifier', () => {\n    const vm = new Vue({\n      template: '<div><span v-bind:text-content.prop=\"foo\"></span><span :inner-html.prop=\"bar\"></span></div>',\n      data: {\n        foo: 'hello',\n        bar: '<span>qux</span>'\n      }\n    }).$mount()\n    expect(vm.$el.children[0].textContent).toBe('hello')\n    expect(vm.$el.children[1].innerHTML).toBe('<span>qux</span>')\n  })\n\n  it('.prop modifier with normal attribute binding', () => {\n    const vm = new Vue({\n      template: '<input :some.prop=\"some\" :id=\"id\">',\n      data: {\n        some: 'hello',\n        id: false\n      }\n    }).$mount()\n    expect(vm.$el.some).toBe('hello')\n    expect(vm.$el.getAttribute('id')).toBe(null)\n  })\n\n  it('.camel modifier', () => {\n    const vm = new Vue({\n      template: '<svg :view-box.camel=\"viewBox\"></svg>',\n      data: {\n        viewBox: '0 0 1 1'\n      }\n    }).$mount()\n    expect(vm.$el.getAttribute('viewBox')).toBe('0 0 1 1')\n  })\n\n  it('.sync modifier', done => {\n    const vm = new Vue({\n      template: `<test :foo-bar.sync=\"bar\"/>`,\n      data: {\n        bar: 1\n      },\n      components: {\n        test: {\n          props: ['fooBar'],\n          template: `<div @click=\"$emit('update:fooBar', 2)\">{{ fooBar }}</div>`\n        }\n      }\n    }).$mount()\n\n    expect(vm.$el.textContent).toBe('1')\n    triggerEvent(vm.$el, 'click')\n    waitForUpdate(() => {\n      expect(vm.$el.textContent).toBe('2')\n    }).then(done)\n  })\n\n  it('bind object', done => {\n    const vm = new Vue({\n      template: '<input v-bind=\"test\">',\n      data: {\n        test: {\n          id: 'test',\n          class: 'ok',\n          value: 'hello'\n        }\n      }\n    }).$mount()\n    expect(vm.$el.getAttribute('id')).toBe('test')\n    expect(vm.$el.getAttribute('class')).toBe('ok')\n    expect(vm.$el.value).toBe('hello')\n    vm.test.id = 'hi'\n    vm.test.value = 'bye'\n    waitForUpdate(() => {\n      expect(vm.$el.getAttribute('id')).toBe('hi')\n      expect(vm.$el.getAttribute('class')).toBe('ok')\n      expect(vm.$el.value).toBe('bye')\n    }).then(done)\n  })\n\n  it('.sync modifier with bind object', done => {\n    const vm = new Vue({\n      template: `<test v-bind.sync=\"test\"/>`,\n      data: {\n        test: {\n          fooBar: 1\n        }\n      },\n      components: {\n        test: {\n          props: ['fooBar'],\n          template: `<div @click=\"handleUpdate\">{{ fooBar }}</div>`,\n          methods: {\n            handleUpdate () {\n              this.$emit('update:fooBar', 2)\n            }\n          }\n        }\n      }\n    }).$mount()\n    expect(vm.$el.textContent).toBe('1')\n    triggerEvent(vm.$el, 'click')\n    waitForUpdate(() => {\n      expect(vm.$el.textContent).toBe('2')\n      vm.test.fooBar = 3\n    }).then(() => {\n      expect(vm.$el.textContent).toBe('3')\n    }).then(done)\n  })\n\n  it('bind object with overwrite', done => {\n    const vm = new Vue({\n      template: '<input v-bind=\"test\" id=\"foo\" :class=\"test.value\">',\n      data: {\n        test: {\n          id: 'test',\n          class: 'ok',\n          value: 'hello'\n        }\n      }\n    }).$mount()\n    expect(vm.$el.getAttribute('id')).toBe('foo')\n    expect(vm.$el.getAttribute('class')).toBe('hello')\n    expect(vm.$el.value).toBe('hello')\n    vm.test.id = 'hi'\n    vm.test.value = 'bye'\n    waitForUpdate(() => {\n      expect(vm.$el.getAttribute('id')).toBe('foo')\n      expect(vm.$el.getAttribute('class')).toBe('bye')\n      expect(vm.$el.value).toBe('bye')\n    }).then(done)\n  })\n\n  it('bind object with class/style', done => {\n    const vm = new Vue({\n      template: '<input class=\"a\" style=\"color:red\" v-bind=\"test\">',\n      data: {\n        test: {\n          id: 'test',\n          class: ['b', 'c'],\n          style: { fontSize: '12px' }\n        }\n      }\n    }).$mount()\n    expect(vm.$el.id).toBe('test')\n    expect(vm.$el.className).toBe('a b c')\n    expect(vm.$el.style.color).toBe('red')\n    expect(vm.$el.style.fontSize).toBe('12px')\n    vm.test.id = 'hi'\n    vm.test.class = ['d']\n    vm.test.style = { fontSize: '14px' }\n    waitForUpdate(() => {\n      expect(vm.$el.id).toBe('hi')\n      expect(vm.$el.className).toBe('a d')\n      expect(vm.$el.style.color).toBe('red')\n      expect(vm.$el.style.fontSize).toBe('14px')\n    }).then(done)\n  })\n\n  it('bind object as prop', done => {\n    const vm = new Vue({\n      template: '<input v-bind.prop=\"test\">',\n      data: {\n        test: {\n          id: 'test',\n          className: 'ok',\n          value: 'hello'\n        }\n      }\n    }).$mount()\n    expect(vm.$el.id).toBe('test')\n    expect(vm.$el.className).toBe('ok')\n    expect(vm.$el.value).toBe('hello')\n    vm.test.id = 'hi'\n    vm.test.className = 'okay'\n    vm.test.value = 'bye'\n    waitForUpdate(() => {\n      expect(vm.$el.id).toBe('hi')\n      expect(vm.$el.className).toBe('okay')\n      expect(vm.$el.value).toBe('bye')\n    }).then(done)\n  })\n\n  it('bind array', done => {\n    const vm = new Vue({\n      template: '<input v-bind=\"test\">',\n      data: {\n        test: [\n          { id: 'test', class: 'ok' },\n          { value: 'hello' }\n        ]\n      }\n    }).$mount()\n    expect(vm.$el.getAttribute('id')).toBe('test')\n    expect(vm.$el.getAttribute('class')).toBe('ok')\n    expect(vm.$el.value).toBe('hello')\n    vm.test[0].id = 'hi'\n    vm.test[1].value = 'bye'\n    waitForUpdate(() => {\n      expect(vm.$el.getAttribute('id')).toBe('hi')\n      expect(vm.$el.getAttribute('class')).toBe('ok')\n      expect(vm.$el.value).toBe('bye')\n    }).then(done)\n  })\n\n  it('warn expect object', () => {\n    new Vue({\n      template: '<input v-bind=\"test\">',\n      data: {\n        test: 1\n      }\n    }).$mount()\n    expect('v-bind without argument expects an Object or Array value').toHaveBeenWarned()\n  })\n\n  it('set value for option element', () => {\n    const vm = new Vue({\n      template: '<select><option :value=\"val\">val</option></select>',\n      data: {\n        val: 'val'\n      }\n    }).$mount()\n    // check value attribute\n    expect(vm.$el.options[0].getAttribute('value')).toBe('val')\n  })\n\n  // a vdom patch edge case where the user has several un-keyed elements of the\n  // same tag next to each other, and toggling them.\n  it('properly update for toggling un-keyed children', done => {\n    const vm = new Vue({\n      template: `\n        <div>\n          <div v-if=\"ok\" id=\"a\" data-test=\"1\"></div>\n          <div v-if=\"!ok\" id=\"b\"></div>\n        </div>\n      `,\n      data: {\n        ok: true\n      }\n    }).$mount()\n    expect(vm.$el.children[0].id).toBe('a')\n    expect(vm.$el.children[0].getAttribute('data-test')).toBe('1')\n    vm.ok = false\n    waitForUpdate(() => {\n      expect(vm.$el.children[0].id).toBe('b')\n      expect(vm.$el.children[0].getAttribute('data-test')).toBe(null)\n    }).then(done)\n  })\n\n  describe('bind object with special attribute', () => {\n    function makeInstance (options) {\n      return new Vue({\n        template: `<div>${options.parentTemp}</div>`,\n        data: {\n          attrs: {\n            [options.attr]: options.value\n          }\n        },\n        components: {\n          comp: {\n            template: options.childTemp\n          }\n        }\n      }).$mount()\n    }\n\n    it('key', () => {\n      const vm = makeInstance({\n        attr: 'key',\n        value: 'test',\n        parentTemp: '<div v-bind=\"attrs\"></div>'\n      })\n      expect(vm._vnode.children[0].key).toBe('test')\n    })\n\n    it('ref', () => {\n      const vm = makeInstance({\n        attr: 'ref',\n        value: 'test',\n        parentTemp: '<div v-bind=\"attrs\"></div>'\n      })\n      expect(vm.$refs.test).toBe(vm.$el.firstChild)\n    })\n\n    it('slot', () => {\n      const vm = makeInstance({\n        attr: 'slot',\n        value: 'test',\n        parentTemp: '<comp><span v-bind=\"attrs\">123</span></comp>',\n        childTemp: '<div>slot:<slot name=\"test\"></slot></div>'\n      })\n      expect(vm.$el.innerHTML).toBe('<div>slot:<span>123</span></div>')\n    })\n\n    it('is', () => {\n      const vm = makeInstance({\n        attr: 'is',\n        value: 'comp',\n        parentTemp: '<component v-bind=\"attrs\"></component>',\n        childTemp: '<div>comp</div>'\n      })\n      expect(vm.$el.innerHTML).toBe('<div>comp</div>')\n    })\n  })\n})\n"
  },
  {
    "path": "vue/test/unit/features/directives/class.spec.js",
    "content": "import Vue from 'vue'\n\nfunction assertClass (assertions, done) {\n  const vm = new Vue({\n    template: '<div class=\"foo\" :class=\"value\"></div>',\n    data: { value: '' }\n  }).$mount()\n  var chain = waitForUpdate()\n  assertions.forEach(([value, expected], i) => {\n    chain.then(() => {\n      if (typeof value === 'function') {\n        value(vm.value)\n      } else {\n        vm.value = value\n      }\n    }).then(() => {\n      expect(vm.$el.className).toBe(expected)\n      if (i >= assertions.length - 1) {\n        done()\n      }\n    })\n  })\n  chain.then(done)\n}\n\ndescribe('Directive v-bind:class', () => {\n  it('plain string', done => {\n    assertClass([\n      ['bar', 'foo bar'],\n      ['baz qux', 'foo baz qux'],\n      ['qux', 'foo qux'],\n      [undefined, 'foo']\n    ], done)\n  })\n\n  it('object value', done => {\n    assertClass([\n      [{ bar: true, baz: false }, 'foo bar'],\n      [{ baz: true }, 'foo baz'],\n      [null, 'foo'],\n      [{ 'bar baz': true, qux: false }, 'foo bar baz'],\n      [{ qux: true }, 'foo qux']\n    ], done)\n  })\n\n  it('array value', done => {\n    assertClass([\n      [['bar', 'baz'], 'foo bar baz'],\n      [['qux', 'baz'], 'foo qux baz'],\n      [['w', 'x y z'], 'foo w x y z'],\n      [undefined, 'foo'],\n      [['bar'], 'foo bar'],\n      [val => val.push('baz'), 'foo bar baz']\n    ], done)\n  })\n\n  it('array of mixed values', done => {\n    assertClass([\n      [['x', { y: true, z: true }], 'foo x y z'],\n      [['x', { y: true, z: false }], 'foo x y'],\n      [['f', { z: true }], 'foo f z'],\n      [['l', 'f', { n: true, z: true }], 'foo l f n z'],\n      [['x', {}], 'foo x'],\n      [undefined, 'foo']\n    ], done)\n  })\n\n  it('class merge between parent and child', done => {\n    const vm = new Vue({\n      template: '<child class=\"a\" :class=\"value\"></child>',\n      data: { value: 'b' },\n      components: {\n        child: {\n          template: '<div class=\"c\" :class=\"value\"></div>',\n          data: () => ({ value: 'd' })\n        }\n      }\n    }).$mount()\n    const child = vm.$children[0]\n    expect(vm.$el.className).toBe('c a d b')\n    vm.value = 'e'\n    waitForUpdate(() => {\n      expect(vm.$el.className).toBe('c a d e')\n    }).then(() => {\n      child.value = 'f'\n    }).then(() => {\n      expect(vm.$el.className).toBe('c a f e')\n    }).then(() => {\n      vm.value = { foo: true }\n      child.value = ['bar', 'baz']\n    }).then(() => {\n      expect(vm.$el.className).toBe('c a bar baz foo')\n    }).then(done)\n  })\n\n  it('class merge between multiple nested components sharing same element', done => {\n    const vm = new Vue({\n      template: `\n        <component1 :class=\"componentClass1\">\n          <component2 :class=\"componentClass2\">\n            <component3 :class=\"componentClass3\">\n              some text\n            </component3>\n          </component2>\n        </component1>\n      `,\n      data: {\n        componentClass1: 'componentClass1',\n        componentClass2: 'componentClass2',\n        componentClass3: 'componentClass3'\n      },\n      components: {\n        component1: {\n          render () {\n            return this.$slots.default[0]\n          }\n        },\n        component2: {\n          render () {\n            return this.$slots.default[0]\n          }\n        },\n        component3: {\n          template: '<div class=\"staticClass\"><slot></slot></div>'\n        }\n      }\n    }).$mount()\n    expect(vm.$el.className).toBe('staticClass componentClass3 componentClass2 componentClass1')\n    vm.componentClass1 = 'c1'\n    waitForUpdate(() => {\n      expect(vm.$el.className).toBe('staticClass componentClass3 componentClass2 c1')\n      vm.componentClass2 = 'c2'\n    }).then(() => {\n      expect(vm.$el.className).toBe('staticClass componentClass3 c2 c1')\n      vm.componentClass3 = 'c3'\n    }).then(() => {\n      expect(vm.$el.className).toBe('staticClass c3 c2 c1')\n    }).then(done)\n  })\n\n  it('deep update', done => {\n    const vm = new Vue({\n      template: '<div :class=\"test\"></div>',\n      data: {\n        test: { a: true, b: false }\n      }\n    }).$mount()\n    expect(vm.$el.className).toBe('a')\n    vm.test.b = true\n    waitForUpdate(() => {\n      expect(vm.$el.className).toBe('a b')\n    }).then(done)\n  })\n\n  // a vdom patch edge case where the user has several un-keyed elements of the\n  // same tag next to each other, and toggling them.\n  it('properly remove staticClass for toggling un-keyed children', done => {\n    const vm = new Vue({\n      template: `\n        <div>\n          <div v-if=\"ok\" class=\"a\"></div>\n          <div v-if=\"!ok\"></div>\n        </div>\n      `,\n      data: {\n        ok: true\n      }\n    }).$mount()\n    expect(vm.$el.children[0].className).toBe('a')\n    vm.ok = false\n    waitForUpdate(() => {\n      expect(vm.$el.children[0].className).toBe('')\n    }).then(done)\n  })\n})\n"
  },
  {
    "path": "vue/test/unit/features/directives/cloak.spec.js",
    "content": "import Vue from 'vue'\n\ndescribe('Directive v-cloak', () => {\n  it('should be removed after compile', () => {\n    const el = document.createElement('div')\n    el.setAttribute('v-cloak', '')\n    const vm = new Vue({ el })\n    expect(vm.$el.hasAttribute('v-cloak')).toBe(false)\n  })\n})\n"
  },
  {
    "path": "vue/test/unit/features/directives/for.spec.js",
    "content": "import Vue from 'vue'\n\ndescribe('Directive v-for', () => {\n  it('should render array of primitive values', done => {\n    const vm = new Vue({\n      template: `\n        <div>\n          <span v-for=\"item in list\">{{item}}</span>\n        </div>\n      `,\n      data: {\n        list: ['a', 'b', 'c']\n      }\n    }).$mount()\n    expect(vm.$el.innerHTML).toBe('<span>a</span><span>b</span><span>c</span>')\n    Vue.set(vm.list, 0, 'd')\n    waitForUpdate(() => {\n      expect(vm.$el.innerHTML).toBe('<span>d</span><span>b</span><span>c</span>')\n      vm.list.push('d')\n    }).then(() => {\n      expect(vm.$el.innerHTML).toBe('<span>d</span><span>b</span><span>c</span><span>d</span>')\n      vm.list.splice(1, 2)\n    }).then(() => {\n      expect(vm.$el.innerHTML).toBe('<span>d</span><span>d</span>')\n      vm.list = ['x', 'y']\n    }).then(() => {\n      expect(vm.$el.innerHTML).toBe('<span>x</span><span>y</span>')\n    }).then(done)\n  })\n\n  it('should render array of primitive values with index', done => {\n    const vm = new Vue({\n      template: `\n        <div>\n          <span v-for=\"(item, i) in list\">{{i}}-{{item}}</span>\n        </div>\n      `,\n      data: {\n        list: ['a', 'b', 'c']\n      }\n    }).$mount()\n    expect(vm.$el.innerHTML).toBe('<span>0-a</span><span>1-b</span><span>2-c</span>')\n    Vue.set(vm.list, 0, 'd')\n    waitForUpdate(() => {\n      expect(vm.$el.innerHTML).toBe('<span>0-d</span><span>1-b</span><span>2-c</span>')\n      vm.list.push('d')\n    }).then(() => {\n      expect(vm.$el.innerHTML).toBe('<span>0-d</span><span>1-b</span><span>2-c</span><span>3-d</span>')\n      vm.list.splice(1, 2)\n    }).then(() => {\n      expect(vm.$el.innerHTML).toBe('<span>0-d</span><span>1-d</span>')\n      vm.list = ['x', 'y']\n    }).then(() => {\n      expect(vm.$el.innerHTML).toBe('<span>0-x</span><span>1-y</span>')\n    }).then(done)\n  })\n\n  it('should render array of object values', done => {\n    const vm = new Vue({\n      template: `\n        <div>\n          <span v-for=\"item in list\">{{item.value}}</span>\n        </div>\n      `,\n      data: {\n        list: [\n          { value: 'a' },\n          { value: 'b' },\n          { value: 'c' }\n        ]\n      }\n    }).$mount()\n    expect(vm.$el.innerHTML).toBe('<span>a</span><span>b</span><span>c</span>')\n    Vue.set(vm.list, 0, { value: 'd' })\n    waitForUpdate(() => {\n      expect(vm.$el.innerHTML).toBe('<span>d</span><span>b</span><span>c</span>')\n      vm.list[0].value = 'e'\n    }).then(() => {\n      expect(vm.$el.innerHTML).toBe('<span>e</span><span>b</span><span>c</span>')\n      vm.list.push({})\n    }).then(() => {\n      expect(vm.$el.innerHTML).toBe('<span>e</span><span>b</span><span>c</span><span></span>')\n      vm.list.splice(1, 2)\n    }).then(() => {\n      expect(vm.$el.innerHTML).toBe('<span>e</span><span></span>')\n      vm.list = [{ value: 'x' }, { value: 'y' }]\n    }).then(() => {\n      expect(vm.$el.innerHTML).toBe('<span>x</span><span>y</span>')\n    }).then(done)\n  })\n\n  it('should render array of object values with index', done => {\n    const vm = new Vue({\n      template: `\n        <div>\n          <span v-for=\"(item, i) in list\">{{i}}-{{item.value}}</span>\n        </div>\n      `,\n      data: {\n        list: [\n          { value: 'a' },\n          { value: 'b' },\n          { value: 'c' }\n        ]\n      }\n    }).$mount()\n    expect(vm.$el.innerHTML).toBe('<span>0-a</span><span>1-b</span><span>2-c</span>')\n    Vue.set(vm.list, 0, { value: 'd' })\n    waitForUpdate(() => {\n      expect(vm.$el.innerHTML).toBe('<span>0-d</span><span>1-b</span><span>2-c</span>')\n      vm.list[0].value = 'e'\n    }).then(() => {\n      expect(vm.$el.innerHTML).toBe('<span>0-e</span><span>1-b</span><span>2-c</span>')\n      vm.list.push({})\n    }).then(() => {\n      expect(vm.$el.innerHTML).toBe('<span>0-e</span><span>1-b</span><span>2-c</span><span>3-</span>')\n      vm.list.splice(1, 2)\n    }).then(() => {\n      expect(vm.$el.innerHTML).toBe('<span>0-e</span><span>1-</span>')\n      vm.list = [{ value: 'x' }, { value: 'y' }]\n    }).then(() => {\n      expect(vm.$el.innerHTML).toBe('<span>0-x</span><span>1-y</span>')\n    }).then(done)\n  })\n\n  it('should render an Object', done => {\n    const vm = new Vue({\n      template: `\n        <div>\n          <span v-for=\"val in obj\">{{val}}</span>\n        </div>\n      `,\n      data: {\n        obj: { a: 0, b: 1, c: 2 }\n      }\n    }).$mount()\n    expect(vm.$el.innerHTML).toBe('<span>0</span><span>1</span><span>2</span>')\n    vm.obj.a = 3\n    waitForUpdate(() => {\n      expect(vm.$el.innerHTML).toBe('<span>3</span><span>1</span><span>2</span>')\n      Vue.set(vm.obj, 'd', 4)\n    }).then(() => {\n      expect(vm.$el.innerHTML).toBe('<span>3</span><span>1</span><span>2</span><span>4</span>')\n      Vue.delete(vm.obj, 'a')\n    }).then(() => {\n      expect(vm.$el.innerHTML).toBe('<span>1</span><span>2</span><span>4</span>')\n    }).then(done)\n  })\n\n  it('should render an Object with key', done => {\n    const vm = new Vue({\n      template: `\n        <div>\n          <span v-for=\"(val, key) in obj\">{{val}}-{{key}}</span>\n        </div>\n      `,\n      data: {\n        obj: { a: 0, b: 1, c: 2 }\n      }\n    }).$mount()\n    expect(vm.$el.innerHTML).toBe('<span>0-a</span><span>1-b</span><span>2-c</span>')\n    vm.obj.a = 3\n    waitForUpdate(() => {\n      expect(vm.$el.innerHTML).toBe('<span>3-a</span><span>1-b</span><span>2-c</span>')\n      Vue.set(vm.obj, 'd', 4)\n    }).then(() => {\n      expect(vm.$el.innerHTML).toBe('<span>3-a</span><span>1-b</span><span>2-c</span><span>4-d</span>')\n      Vue.delete(vm.obj, 'a')\n    }).then(() => {\n      expect(vm.$el.innerHTML).toBe('<span>1-b</span><span>2-c</span><span>4-d</span>')\n    }).then(done)\n  })\n\n  it('should render an Object with key and index', done => {\n    const vm = new Vue({\n      template: `\n        <div>\n          <span v-for=\"(val, key, i) in obj\">{{val}}-{{key}}-{{i}}</span>\n        </div>\n      `,\n      data: {\n        obj: { a: 0, b: 1, c: 2 }\n      }\n    }).$mount()\n    expect(vm.$el.innerHTML).toBe('<span>0-a-0</span><span>1-b-1</span><span>2-c-2</span>')\n    vm.obj.a = 3\n    waitForUpdate(() => {\n      expect(vm.$el.innerHTML).toBe('<span>3-a-0</span><span>1-b-1</span><span>2-c-2</span>')\n      Vue.set(vm.obj, 'd', 4)\n    }).then(() => {\n      expect(vm.$el.innerHTML).toBe('<span>3-a-0</span><span>1-b-1</span><span>2-c-2</span><span>4-d-3</span>')\n      Vue.delete(vm.obj, 'a')\n    }).then(() => {\n      expect(vm.$el.innerHTML).toBe('<span>1-b-0</span><span>2-c-1</span><span>4-d-2</span>')\n    }).then(done)\n  })\n\n  it('should render each key of data', done => {\n    const vm = new Vue({\n      template: `\n        <div>\n          <span v-for=\"(val, key) in $data\">{{val}}-{{key}}</span>\n        </div>\n      `,\n      data: { a: 0, b: 1, c: 2 }\n    }).$mount()\n    expect(vm.$el.innerHTML).toBe('<span>0-a</span><span>1-b</span><span>2-c</span>')\n    vm.a = 3\n    waitForUpdate(() => {\n      expect(vm.$el.innerHTML).toBe('<span>3-a</span><span>1-b</span><span>2-c</span>')\n    }).then(done)\n  })\n\n  it('check priorities: v-if before v-for', function () {\n    const vm = new Vue({\n      data: {\n        items: [1, 2, 3]\n      },\n      template: '<div><div v-if=\"item < 3\" v-for=\"item in items\">{{item}}</div></div>'\n    }).$mount()\n    expect(vm.$el.textContent).toBe('12')\n  })\n\n  it('check priorities: v-if after v-for', function () {\n    const vm = new Vue({\n      data: {\n        items: [1, 2, 3]\n      },\n      template: '<div><div v-for=\"item in items\" v-if=\"item < 3\">{{item}}</div></div>'\n    }).$mount()\n    expect(vm.$el.textContent).toBe('12')\n  })\n\n  it('range v-for', () => {\n    const vm = new Vue({\n      template: '<div><div v-for=\"n in 3\">{{n}}</div></div>'\n    }).$mount()\n    expect(vm.$el.textContent).toBe('123')\n  })\n\n  it('without key', done => {\n    const vm = new Vue({\n      data: {\n        items: [\n          { id: 1, msg: 'a' },\n          { id: 2, msg: 'b' },\n          { id: 3, msg: 'c' }\n        ]\n      },\n      template: '<div><div v-for=\"item in items\">{{ item.msg }}</div></div>'\n    }).$mount()\n    expect(vm.$el.textContent).toBe('abc')\n    const first = vm.$el.children[0]\n    vm.items.reverse()\n    waitForUpdate(() => {\n      expect(vm.$el.textContent).toBe('cba')\n      // assert reusing DOM element in place\n      expect(vm.$el.children[0]).toBe(first)\n    }).then(done)\n  })\n\n  it('with key', done => {\n    const vm = new Vue({\n      data: {\n        items: [\n          { id: 1, msg: 'a' },\n          { id: 2, msg: 'b' },\n          { id: 3, msg: 'c' }\n        ]\n      },\n      template: '<div><div v-for=\"item in items\" :key=\"item.id\">{{ item.msg }}</div></div>'\n    }).$mount()\n    expect(vm.$el.textContent).toBe('abc')\n    const first = vm.$el.children[0]\n    vm.items.reverse()\n    waitForUpdate(() => {\n      expect(vm.$el.textContent).toBe('cba')\n      // assert moving DOM element\n      expect(vm.$el.children[0]).not.toBe(first)\n      expect(vm.$el.children[2]).toBe(first)\n    }).then(done)\n  })\n\n  it('nested loops', () => {\n    const vm = new Vue({\n      data: {\n        items: [\n          { items: [{ a: 1 }, { a: 2 }], a: 1 },\n          { items: [{ a: 3 }, { a: 4 }], a: 2 }\n        ]\n      },\n      template:\n        '<div>' +\n          '<div v-for=\"(item, i) in items\">' +\n            '<p v-for=\"(subItem, j) in item.items\">{{j}} {{subItem.a}} {{i}} {{item.a}}</p>' +\n          '</div>' +\n        '</div>'\n    }).$mount()\n    expect(vm.$el.innerHTML).toBe(\n      '<div><p>0 1 0 1</p><p>1 2 0 1</p></div>' +\n      '<div><p>0 3 1 2</p><p>1 4 1 2</p></div>'\n    )\n  })\n\n  it('template v-for', done => {\n    const vm = new Vue({\n      data: {\n        list: [\n          { a: 1 },\n          { a: 2 },\n          { a: 3 }\n        ]\n      },\n      template:\n        '<div>' +\n          '<template v-for=\"item in list\">' +\n            '<p>{{item.a}}</p>' +\n            '<p>{{item.a + 1}}</p>' +\n          '</template>' +\n        '</div>'\n    }).$mount()\n    assertMarkup()\n    vm.list.reverse()\n    waitForUpdate(() => {\n      assertMarkup()\n      vm.list.splice(1, 1)\n    }).then(() => {\n      assertMarkup()\n      vm.list.splice(1, 0, { a: 2 })\n    }).then(done)\n\n    function assertMarkup () {\n      var markup = vm.list.map(function (item) {\n        return '<p>' + item.a + '</p><p>' + (item.a + 1) + '</p>'\n      }).join('')\n      expect(vm.$el.innerHTML).toBe(markup)\n    }\n  })\n\n  it('component v-for', done => {\n    const vm = new Vue({\n      data: {\n        list: [\n          { a: 1 },\n          { a: 2 },\n          { a: 3 }\n        ]\n      },\n      template:\n        '<div>' +\n          '<test v-for=\"item in list\" :msg=\"item.a\" :key=\"item.a\">' +\n            '<span>{{item.a}}</span>' +\n          '</test>' +\n        '</div>',\n      components: {\n        test: {\n          props: ['msg'],\n          template: '<p>{{msg}}<slot></slot></p>'\n        }\n      }\n    }).$mount()\n    assertMarkup()\n    vm.list.reverse()\n    waitForUpdate(() => {\n      assertMarkup()\n      vm.list.splice(1, 1)\n    }).then(() => {\n      assertMarkup()\n      vm.list.splice(1, 0, { a: 2 })\n    }).then(done)\n\n    function assertMarkup () {\n      var markup = vm.list.map(function (item) {\n        return `<p>${item.a}<span>${item.a}</span></p>`\n      }).join('')\n      expect(vm.$el.innerHTML).toBe(markup)\n    }\n  })\n\n  it('dynamic component v-for', done => {\n    const vm = new Vue({\n      data: {\n        list: [\n          { type: 'one' },\n          { type: 'two' }\n        ]\n      },\n      template:\n        '<div>' +\n          '<component v-for=\"item in list\" :key=\"item.type\" :is=\"item.type\"></component>' +\n        '</div>',\n      components: {\n        one: {\n          template: '<p>One!</p>'\n        },\n        two: {\n          template: '<div>Two!</div>'\n        }\n      }\n    }).$mount()\n    expect(vm.$el.innerHTML).toContain('<p>One!</p><div>Two!</div>')\n    vm.list.reverse()\n    waitForUpdate(() => {\n      expect(vm.$el.innerHTML).toContain('<div>Two!</div><p>One!</p>')\n    }).then(done)\n  })\n\n  it('should warn component v-for without keys', () => {\n    const warn = console.warn\n    console.warn = jasmine.createSpy()\n    new Vue({\n      template: `<div><test v-for=\"i in 3\"></test></div>`,\n      components: {\n        test: {\n          render () {}\n        }\n      }\n    }).$mount()\n    expect(console.warn.calls.argsFor(0)[0]).toContain(\n      `<test v-for=\"i in 3\">: component lists rendered with v-for should have explicit keys`\n    )\n    console.warn = warn\n  })\n\n  it('multi nested array reactivity', done => {\n    const vm = new Vue({\n      data: {\n        list: [[['foo']]]\n      },\n      template: `\n        <div>\n          <div v-for=\"i in list\">\n            <div v-for=\"j in i\">\n              <div v-for=\"k in j\">\n                {{ k }}\n              </div>\n            </div>\n          </div>\n        </div>\n      `\n    }).$mount()\n    expect(vm.$el.textContent).toMatch(/\\s+foo\\s+/)\n    vm.list[0][0].push('bar')\n    waitForUpdate(() => {\n      expect(vm.$el.textContent).toMatch(/\\s+foo\\s+bar\\s+/)\n    }).then(done)\n  })\n\n  it('should work with strings', done => {\n    const vm = new Vue({\n      data: {\n        text: 'foo'\n      },\n      template: `\n        <div>\n          <span v-for=\"letter in text\">{{ letter }}.</span>\n        </div>\n      `\n    }).$mount()\n    expect(vm.$el.textContent).toMatch('f.o.o.')\n    vm.text += 'bar'\n    waitForUpdate(() => {\n      expect(vm.$el.textContent).toMatch('f.o.o.b.a.r.')\n    }).then(done)\n  })\n\n  // #7792\n  it('should work with multiline expressions', () => {\n    const vm = new Vue({\n      data: {\n        a: [1],\n        b: [2]\n      },\n      template: `\n        <div>\n          <span v-for=\"n in (\n            a.concat(\n              b\n            )\n          )\">{{ n }}</span>\n        </div>\n      `\n    }).$mount()\n    expect(vm.$el.textContent).toBe('12')\n  })\n\n  const supportsDestructuring = (() => {\n    try {\n      new Function('var { foo } = bar')\n      return true\n    } catch (e) {}\n  })()\n\n  if (supportsDestructuring) {\n    it('should support destructuring syntax in alias position (object)', () => {\n      const vm = new Vue({\n        data: { list: [{ foo: 'hi', bar: 'ho' }] },\n        template: '<div><div v-for=\"({ foo, bar }, i) in list\">{{ foo }} {{ bar }} {{ i }}</div></div>'\n      }).$mount()\n      expect(vm.$el.textContent).toBe('hi ho 0')\n    })\n\n    it('should support destructuring syntax in alias position (array)', () => {\n      const vm = new Vue({\n        data: { list: [[1, 2], [3, 4]] },\n        template: '<div><div v-for=\"([ foo, bar ], i) in list\">{{ foo }} {{ bar }} {{ i }}</div></div>'\n      }).$mount()\n      expect(vm.$el.textContent).toBe('1 2 03 4 1')\n    })\n  }\n})\n"
  },
  {
    "path": "vue/test/unit/features/directives/html.spec.js",
    "content": "import Vue from 'vue'\n\ndescribe('Directive v-html', () => {\n  it('should render html', () => {\n    const vm = new Vue({\n      template: '<div v-html=\"a\"></div>',\n      data: { a: 'hello' }\n    }).$mount()\n    expect(vm.$el.innerHTML).toBe('hello')\n  })\n\n  it('should encode html entities', () => {\n    const vm = new Vue({\n      template: '<div v-html=\"a\"></div>',\n      data: { a: '<span>&lt;</span>' }\n    }).$mount()\n    expect(vm.$el.innerHTML).toBe('<span>&lt;</span>')\n  })\n\n  it('should work inline', () => {\n    const vm = new Vue({\n      template: `<div v-html=\"'<span>&lt;</span>'\"></div>`\n    }).$mount()\n    expect(vm.$el.innerHTML).toBe('<span>&lt;</span>')\n  })\n\n  it('should work inline in DOM', () => {\n    const el = document.createElement('div')\n    el.innerHTML = `<div v-html=\"'<span>&lt;</span>'\"></div>`\n    const vm = new Vue({ el })\n    expect(vm.$el.children[0].innerHTML).toBe('<span>&lt;</span>')\n  })\n\n  it('should support all value types', done => {\n    const vm = new Vue({\n      template: '<div v-html=\"a\"></div>',\n      data: { a: false }\n    }).$mount()\n    waitForUpdate(() => {\n      expect(vm.$el.innerHTML).toBe('false')\n      vm.a = []\n    }).then(() => {\n      expect(vm.$el.innerHTML).toBe('[]')\n      vm.a = {}\n    }).then(() => {\n      expect(vm.$el.innerHTML).toBe('{}')\n      vm.a = 123\n    }).then(() => {\n      expect(vm.$el.innerHTML).toBe('123')\n      vm.a = 0\n    }).then(() => {\n      expect(vm.$el.innerHTML).toBe('0')\n      vm.a = ' '\n    }).then(() => {\n      expect(vm.$el.innerHTML).toBe(' ')\n      vm.a = '    '\n    }).then(() => {\n      expect(vm.$el.innerHTML).toBe('    ')\n      vm.a = null\n    }).then(() => {\n      expect(vm.$el.innerHTML).toBe('')\n      vm.a = undefined\n    }).then(() => {\n      expect(vm.$el.innerHTML).toBe('')\n    }).then(done)\n  })\n})\n"
  },
  {
    "path": "vue/test/unit/features/directives/if.spec.js",
    "content": "import Vue from 'vue'\n\ndescribe('Directive v-if', () => {\n  it('should check if value is truthy', () => {\n    const vm = new Vue({\n      template: '<div><span v-if=\"foo\">hello</span></div>',\n      data: { foo: true }\n    }).$mount()\n    expect(vm.$el.innerHTML).toBe('<span>hello</span>')\n  })\n\n  it('should check if value is falsy', () => {\n    const vm = new Vue({\n      template: '<div><span v-if=\"foo\">hello</span></div>',\n      data: { foo: false }\n    }).$mount()\n    expect(vm.$el.innerHTML).toBe('<!---->')\n  })\n\n  it('should update if value changed', done => {\n    const vm = new Vue({\n      template: '<div><span v-if=\"foo\">hello</span></div>',\n      data: { foo: true }\n    }).$mount()\n    expect(vm.$el.innerHTML).toBe('<span>hello</span>')\n    vm.foo = false\n    waitForUpdate(() => {\n      expect(vm.$el.innerHTML).toBe('<!---->')\n      vm.foo = {}\n    }).then(() => {\n      expect(vm.$el.innerHTML).toBe('<span>hello</span>')\n      vm.foo = 0\n    }).then(() => {\n      expect(vm.$el.innerHTML).toBe('<!---->')\n      vm.foo = []\n    }).then(() => {\n      expect(vm.$el.innerHTML).toBe('<span>hello</span>')\n      vm.foo = null\n    }).then(() => {\n      expect(vm.$el.innerHTML).toBe('<!---->')\n      vm.foo = '0'\n    }).then(() => {\n      expect(vm.$el.innerHTML).toBe('<span>hello</span>')\n      vm.foo = undefined\n    }).then(() => {\n      expect(vm.$el.innerHTML).toBe('<!---->')\n      vm.foo = 1\n    }).then(() => {\n      expect(vm.$el.innerHTML).toBe('<span>hello</span>')\n    }).then(done)\n  })\n\n  it('should work well with v-else', done => {\n    const vm = new Vue({\n      template: `\n        <div>\n          <span v-if=\"foo\">hello</span>\n          <span v-else>bye</span>\n        </div>\n      `,\n      data: { foo: true }\n    }).$mount()\n    expect(vm.$el.innerHTML.trim()).toBe('<span>hello</span>')\n    vm.foo = false\n    waitForUpdate(() => {\n      expect(vm.$el.innerHTML.trim()).toBe('<span>bye</span>')\n      vm.foo = {}\n    }).then(() => {\n      expect(vm.$el.innerHTML.trim()).toBe('<span>hello</span>')\n      vm.foo = 0\n    }).then(() => {\n      expect(vm.$el.innerHTML.trim()).toBe('<span>bye</span>')\n      vm.foo = []\n    }).then(() => {\n      expect(vm.$el.innerHTML.trim()).toBe('<span>hello</span>')\n      vm.foo = null\n    }).then(() => {\n      expect(vm.$el.innerHTML.trim()).toBe('<span>bye</span>')\n      vm.foo = '0'\n    }).then(() => {\n      expect(vm.$el.innerHTML.trim()).toBe('<span>hello</span>')\n      vm.foo = undefined\n    }).then(() => {\n      expect(vm.$el.innerHTML.trim()).toBe('<span>bye</span>')\n      vm.foo = 1\n    }).then(() => {\n      expect(vm.$el.innerHTML.trim()).toBe('<span>hello</span>')\n    }).then(done)\n  })\n\n  it('should work well with v-else-if', done => {\n    const vm = new Vue({\n      template: `\n        <div>\n          <span v-if=\"foo\">hello</span>\n          <span v-else-if=\"bar\">elseif</span>\n          <span v-else>bye</span>\n        </div>\n      `,\n      data: { foo: true, bar: false }\n    }).$mount()\n    expect(vm.$el.innerHTML.trim()).toBe('<span>hello</span>')\n    vm.foo = false\n    waitForUpdate(() => {\n      expect(vm.$el.innerHTML.trim()).toBe('<span>bye</span>')\n      vm.bar = true\n    }).then(() => {\n      expect(vm.$el.innerHTML.trim()).toBe('<span>elseif</span>')\n      vm.bar = false\n    }).then(() => {\n      expect(vm.$el.innerHTML.trim()).toBe('<span>bye</span>')\n      vm.foo = true\n    }).then(() => {\n      expect(vm.$el.innerHTML.trim()).toBe('<span>hello</span>')\n      vm.foo = false\n      vm.bar = {}\n    }).then(() => {\n      expect(vm.$el.innerHTML.trim()).toBe('<span>elseif</span>')\n      vm.bar = 0\n    }).then(() => {\n      expect(vm.$el.innerHTML.trim()).toBe('<span>bye</span>')\n      vm.bar = []\n    }).then(() => {\n      expect(vm.$el.innerHTML.trim()).toBe('<span>elseif</span>')\n      vm.bar = null\n    }).then(() => {\n      expect(vm.$el.innerHTML.trim()).toBe('<span>bye</span>')\n      vm.bar = '0'\n    }).then(() => {\n      expect(vm.$el.innerHTML.trim()).toBe('<span>elseif</span>')\n      vm.bar = undefined\n    }).then(() => {\n      expect(vm.$el.innerHTML.trim()).toBe('<span>bye</span>')\n      vm.bar = 1\n    }).then(() => {\n      expect(vm.$el.innerHTML.trim()).toBe('<span>elseif</span>')\n    }).then(done)\n  })\n\n  it('should work well with v-for', done => {\n    const vm = new Vue({\n      template: `\n        <div>\n          <span v-for=\"(item, i) in list\" v-if=\"item.value\">{{i}}</span>\n        </div>\n      `,\n      data: {\n        list: [\n          { value: true },\n          { value: false },\n          { value: true }\n        ]\n      }\n    }).$mount()\n    expect(vm.$el.innerHTML).toBe('<span>0</span><!----><span>2</span>')\n    vm.list[0].value = false\n    waitForUpdate(() => {\n      expect(vm.$el.innerHTML).toBe('<!----><!----><span>2</span>')\n      vm.list.push({ value: true })\n    }).then(() => {\n      expect(vm.$el.innerHTML).toBe('<!----><!----><span>2</span><span>3</span>')\n      vm.list.splice(1, 2)\n    }).then(() => {\n      expect(vm.$el.innerHTML).toBe('<!----><span>1</span>')\n    }).then(done)\n  })\n\n  it('should work well with v-for and v-else', done => {\n    const vm = new Vue({\n      template: `\n        <div>\n          <span v-for=\"(item, i) in list\" v-if=\"item.value\">hello</span>\n          <span v-else>bye</span>\n        </div>\n      `,\n      data: {\n        list: [\n          { value: true },\n          { value: false },\n          { value: true }\n        ]\n      }\n    }).$mount()\n    expect(vm.$el.innerHTML.trim()).toBe('<span>hello</span><span>bye</span><span>hello</span>')\n    vm.list[0].value = false\n    waitForUpdate(() => {\n      expect(vm.$el.innerHTML.trim()).toBe('<span>bye</span><span>bye</span><span>hello</span>')\n      vm.list.push({ value: true })\n    }).then(() => {\n      expect(vm.$el.innerHTML.trim()).toBe('<span>bye</span><span>bye</span><span>hello</span><span>hello</span>')\n      vm.list.splice(1, 2)\n    }).then(() => {\n      expect(vm.$el.innerHTML.trim()).toBe('<span>bye</span><span>hello</span>')\n    }).then(done)\n  })\n\n  it('should work with v-for on v-else branch', done => {\n    const vm = new Vue({\n      template: `\n        <div>\n          <span v-if=\"false\">hello</span>\n          <span v-else v-for=\"item in list\">{{ item }}</span>\n        </div>\n      `,\n      data: {\n        list: [1, 2, 3]\n      }\n    }).$mount()\n    expect(vm.$el.textContent.trim()).toBe('123')\n    vm.list.reverse()\n    waitForUpdate(() => {\n      expect(vm.$el.textContent.trim()).toBe('321')\n    }).then(done)\n  })\n\n  it('should work properly on component root', done => {\n    const vm = new Vue({\n      template: `\n        <div>\n          <test class=\"test\"></test>\n        </div>\n      `,\n      components: {\n        test: {\n          data () {\n            return { ok: true }\n          },\n          template: '<div v-if=\"ok\" id=\"ok\" class=\"inner\">test</div>'\n        }\n      }\n    }).$mount()\n    expect(vm.$el.children[0].id).toBe('ok')\n    expect(vm.$el.children[0].className).toBe('inner test')\n    vm.$children[0].ok = false\n    waitForUpdate(() => {\n      // attrs / class modules should not attempt to patch the comment node\n      expect(vm.$el.innerHTML).toBe('<!---->')\n      vm.$children[0].ok = true\n    }).then(() => {\n      expect(vm.$el.children[0].id).toBe('ok')\n      expect(vm.$el.children[0].className).toBe('inner test')\n    }).then(done)\n  })\n\n  it('should maintain stable list to avoid unnecessary patches', done => {\n    const created = jasmine.createSpy()\n    const destroyed = jasmine.createSpy()\n    const vm = new Vue({\n      data: {\n        ok: true\n      },\n      // when the first div is toggled, the second div should be reused\n      // instead of re-created/destroyed\n      template: `\n        <div>\n          <div v-if=\"ok\"></div>\n          <div><test></test></div>\n        </div>\n      `,\n      components: {\n        test: {\n          template: '<div></div>',\n          created,\n          destroyed\n        }\n      }\n    }).$mount()\n\n    expect(created.calls.count()).toBe(1)\n    vm.ok = false\n    waitForUpdate(() => {\n      expect(created.calls.count()).toBe(1)\n      expect(destroyed).not.toHaveBeenCalled()\n    }).then(done)\n  })\n})\n"
  },
  {
    "path": "vue/test/unit/features/directives/model-checkbox.spec.js",
    "content": "import Vue from 'vue'\n\ndescribe('Directive v-model checkbox', () => {\n  it('should work', done => {\n    const vm = new Vue({\n      data: {\n        test: true\n      },\n      template: '<input type=\"checkbox\" v-model=\"test\">'\n    }).$mount()\n    document.body.appendChild(vm.$el)\n    expect(vm.$el.checked).toBe(true)\n    vm.test = false\n    waitForUpdate(function () {\n      expect(vm.$el.checked).toBe(false)\n      expect(vm.test).toBe(false)\n      vm.$el.click()\n      expect(vm.$el.checked).toBe(true)\n      expect(vm.test).toBe(true)\n    }).then(() => {\n      document.body.removeChild(vm.$el)\n    }).then(done)\n  })\n\n  it('should respect value bindings', done => {\n    const vm = new Vue({\n      data: {\n        test: 1,\n        a: 1,\n        b: 2\n      },\n      template: '<input type=\"checkbox\" v-model=\"test\" :true-value=\"a\" :false-value=\"b\">'\n    }).$mount()\n    document.body.appendChild(vm.$el)\n    expect(vm.$el.checked).toBe(true)\n    vm.$el.click()\n    expect(vm.$el.checked).toBe(false)\n    expect(vm.test).toBe(2)\n    vm.$el.click()\n    expect(vm.$el.checked).toBe(true)\n    expect(vm.test).toBe(1)\n    vm.test = 2\n    waitForUpdate(() => {\n      expect(vm.$el.checked).toBe(false)\n      vm.test = 1\n    }).then(() => {\n      expect(vm.$el.checked).toBe(true)\n      document.body.removeChild(vm.$el)\n    }).then(done)\n  })\n\n  it('bind to Array value', done => {\n    const vm = new Vue({\n      data: {\n        test: ['1']\n      },\n      template: `\n        <div>\n          <input type=\"checkbox\" v-model=\"test\" value=\"1\">\n          <input type=\"checkbox\" v-model=\"test\" value=\"2\">\n        </div>\n      `\n    }).$mount()\n    document.body.appendChild(vm.$el)\n    expect(vm.$el.children[0].checked).toBe(true)\n    expect(vm.$el.children[1].checked).toBe(false)\n    vm.$el.children[0].click()\n    expect(vm.test.length).toBe(0)\n    vm.$el.children[1].click()\n    expect(vm.test).toEqual(['2'])\n    vm.$el.children[0].click()\n    expect(vm.test).toEqual(['2', '1'])\n    vm.test = ['1']\n    waitForUpdate(() => {\n      expect(vm.$el.children[0].checked).toBe(true)\n      expect(vm.$el.children[1].checked).toBe(false)\n    }).then(done)\n  })\n\n  it('bind to Array value ignores false-value', done => {\n    const vm = new Vue({\n      data: {\n        test: ['1']\n      },\n      template: `\n        <div>\n          <input type=\"checkbox\" v-model=\"test\" value=\"1\" :false-value=\"true\">\n          <input type=\"checkbox\" v-model=\"test\" value=\"2\" :false-value=\"true\">\n        </div>\n      `\n    }).$mount()\n    document.body.appendChild(vm.$el)\n    expect(vm.$el.children[0].checked).toBe(true)\n    expect(vm.$el.children[1].checked).toBe(false)\n    vm.$el.children[0].click()\n    expect(vm.test.length).toBe(0)\n    vm.$el.children[1].click()\n    expect(vm.test).toEqual(['2'])\n    vm.$el.children[0].click()\n    expect(vm.test).toEqual(['2', '1'])\n    vm.test = ['1']\n    waitForUpdate(() => {\n      expect(vm.$el.children[0].checked).toBe(true)\n      expect(vm.$el.children[1].checked).toBe(false)\n    }).then(done)\n  })\n\n  it('bind to Array value with value bindings', done => {\n    const vm = new Vue({\n      data: {\n        test: [1]\n      },\n      template: `\n        <div>\n          <input type=\"checkbox\" v-model=\"test\" :value=\"1\">\n          <input type=\"checkbox\" v-model=\"test\" :value=\"2\">\n        </div>\n      `\n    }).$mount()\n    document.body.appendChild(vm.$el)\n    expect(vm.$el.children[0].checked).toBe(true)\n    expect(vm.$el.children[1].checked).toBe(false)\n    vm.$el.children[0].click()\n    expect(vm.test.length).toBe(0)\n    vm.$el.children[1].click()\n    expect(vm.test).toEqual([2])\n    vm.$el.children[0].click()\n    expect(vm.test).toEqual([2, 1])\n    vm.test = [1]\n    waitForUpdate(() => {\n      expect(vm.$el.children[0].checked).toBe(true)\n      expect(vm.$el.children[1].checked).toBe(false)\n    }).then(done)\n  })\n\n  it('bind to Array value with value bindings (object loose equal)', done => {\n    const vm = new Vue({\n      data: {\n        test: [{ a: 1 }]\n      },\n      template: `\n        <div>\n          <input type=\"checkbox\" v-model=\"test\" :value=\"{ a: 1 }\">\n          <input type=\"checkbox\" v-model=\"test\" :value=\"{ a: 2 }\">\n        </div>\n      `\n    }).$mount()\n    document.body.appendChild(vm.$el)\n    expect(vm.$el.children[0].checked).toBe(true)\n    expect(vm.$el.children[1].checked).toBe(false)\n    vm.$el.children[0].click()\n    expect(vm.test.length).toBe(0)\n    vm.$el.children[1].click()\n    expect(vm.test).toEqual([{ a: 2 }])\n    vm.$el.children[0].click()\n    expect(vm.test).toEqual([{ a: 2 }, { a: 1 }])\n    vm.test = [{ a: 1 }]\n    waitForUpdate(() => {\n      expect(vm.$el.children[0].checked).toBe(true)\n      expect(vm.$el.children[1].checked).toBe(false)\n    }).then(done)\n  })\n\n  it('bind to Array value with array value bindings (object loose equal)', done => {\n    const vm = new Vue({\n      data: {\n        test: [{ a: 1 }]\n      },\n      template: `\n        <div>\n          <input type=\"checkbox\" v-model=\"test\" :value=\"{ a: 1 }\">\n          <input type=\"checkbox\" v-model=\"test\" :value=\"[2]\">\n        </div>\n      `\n    }).$mount()\n    document.body.appendChild(vm.$el)\n    expect(vm.$el.children[0].checked).toBe(true)\n    expect(vm.$el.children[1].checked).toBe(false)\n    vm.$el.children[0].click()\n    expect(vm.test.length).toBe(0)\n    vm.$el.children[1].click()\n    expect(vm.test).toEqual([[2]])\n    vm.$el.children[0].click()\n    expect(vm.test).toEqual([[2], { a: 1 }])\n    vm.test = [{ a: 1 }]\n    waitForUpdate(() => {\n      expect(vm.$el.children[0].checked).toBe(true)\n      expect(vm.$el.children[1].checked).toBe(false)\n    }).then(done)\n  })\n\n  it('.number modifier', () => {\n    const vm = new Vue({\n      data: {\n        test: [],\n        check: true\n      },\n      template: `\n        <div>\n          <input type=\"checkbox\" v-model.number=\"test\" value=\"1\">\n          <input type=\"checkbox\" v-model=\"test\" value=\"2\">\n          <input type=\"checkbox\" v-model.number=\"check\">\n        </div>\n      `\n    }).$mount()\n    document.body.appendChild(vm.$el)\n    const checkboxInputs = vm.$el.getElementsByTagName('input')\n    expect(checkboxInputs[0].checked).toBe(false)\n    expect(checkboxInputs[1].checked).toBe(false)\n    expect(checkboxInputs[2].checked).toBe(true)\n    checkboxInputs[0].click()\n    checkboxInputs[1].click()\n    checkboxInputs[2].click()\n    expect(vm.test).toEqual([1, '2'])\n    expect(vm.check).toEqual(false)\n  })\n\n  it('should respect different primitive type value', (done) => {\n    const vm = new Vue({\n      data: {\n        test: [0]\n      },\n      template:\n        '<div>' +\n          '<input type=\"checkbox\" value=\"\" v-model=\"test\">' +\n          '<input type=\"checkbox\" value=\"0\" v-model=\"test\">' +\n          '<input type=\"checkbox\" value=\"1\" v-model=\"test\">' +\n          '<input type=\"checkbox\" value=\"false\" v-model=\"test\">' +\n          '<input type=\"checkbox\" value=\"true\" v-model=\"test\">' +\n        '</div>'\n    }).$mount()\n    const checkboxInput = vm.$el.children\n    expect(checkboxInput[0].checked).toBe(false)\n    expect(checkboxInput[1].checked).toBe(true)\n    expect(checkboxInput[2].checked).toBe(false)\n    expect(checkboxInput[3].checked).toBe(false)\n    expect(checkboxInput[4].checked).toBe(false)\n    vm.test = [1]\n    waitForUpdate(() => {\n      expect(checkboxInput[0].checked).toBe(false)\n      expect(checkboxInput[1].checked).toBe(false)\n      expect(checkboxInput[2].checked).toBe(true)\n      expect(checkboxInput[3].checked).toBe(false)\n      expect(checkboxInput[4].checked).toBe(false)\n      vm.test = ['']\n    }).then(() => {\n      expect(checkboxInput[0].checked).toBe(true)\n      expect(checkboxInput[1].checked).toBe(false)\n      expect(checkboxInput[2].checked).toBe(false)\n      expect(checkboxInput[3].checked).toBe(false)\n      expect(checkboxInput[4].checked).toBe(false)\n      vm.test = [false]\n    }).then(() => {\n      expect(checkboxInput[0].checked).toBe(false)\n      expect(checkboxInput[1].checked).toBe(false)\n      expect(checkboxInput[2].checked).toBe(false)\n      expect(checkboxInput[3].checked).toBe(true)\n      expect(checkboxInput[4].checked).toBe(false)\n      vm.test = [true]\n    }).then(() => {\n      expect(checkboxInput[0].checked).toBe(false)\n      expect(checkboxInput[1].checked).toBe(false)\n      expect(checkboxInput[2].checked).toBe(false)\n      expect(checkboxInput[3].checked).toBe(false)\n      expect(checkboxInput[4].checked).toBe(true)\n      vm.test = ['', 0, 1, false, true]\n    }).then(() => {\n      expect(checkboxInput[0].checked).toBe(true)\n      expect(checkboxInput[1].checked).toBe(true)\n      expect(checkboxInput[2].checked).toBe(true)\n      expect(checkboxInput[3].checked).toBe(true)\n      expect(checkboxInput[4].checked).toBe(true)\n    }).then(done)\n  })\n\n  // #4521\n  it('should work with click event', (done) => {\n    const vm = new Vue({\n      data: {\n        num: 1,\n        checked: false\n      },\n      template: '<div @click=\"add\">click {{ num }}<input ref=\"checkbox\" type=\"checkbox\" v-model=\"checked\"/></div>',\n      methods: {\n        add: function () {\n          this.num++\n        }\n      }\n    }).$mount()\n    document.body.appendChild(vm.$el)\n    const checkbox = vm.$refs.checkbox\n    checkbox.click()\n    waitForUpdate(() => {\n      expect(checkbox.checked).toBe(true)\n      expect(vm.num).toBe(2)\n    }).then(done)\n  })\n\n  it('should get updated with model when in focus', (done) => {\n    const vm = new Vue({\n      data: {\n        a: 2\n      },\n      template: '<input type=\"checkbox\" v-model=\"a\"/>'\n    }).$mount()\n    document.body.appendChild(vm.$el)\n    vm.$el.click()\n    waitForUpdate(() => {\n      expect(vm.$el.checked).toBe(false)\n      vm.a = 2\n    }).then(() => {\n      expect(vm.$el.checked).toBe(true)\n    }).then(done)\n  })\n\n  it('triggers a watcher when binding to an array value in a checkbox', done => {\n    const vm = new Vue({\n      data: {\n        test: {\n          thing: false,\n          arr: [true]\n        }\n      },\n      template: `\n        <div>\n          <input type=\"checkbox\" v-model=\"test.arr[0]\">\n          <span>{{ test.arr[0] }}</span>\n        </div>\n      `\n    }).$mount()\n    document.body.appendChild(vm.$el)\n    expect(vm.$el.children[0].checked).toBe(true)\n    expect(vm.$el.children[1].textContent).toBe('true')\n    vm.$el.children[0].click()\n    expect(vm.$el.children[0].checked).toBe(false)\n    waitForUpdate(() => {\n      expect(vm.$el.children[1].textContent).toBe('false')\n    }).then(done)\n  })\n\n  // #7811\n  it('type should not be overwritten by v-bind', () => {\n    const vm = new Vue({\n      data: {\n        test: true\n      },\n      template: '<input type=\"checkbox\" v-model=\"test\" v-bind=\"$attrs\">'\n    }).$mount()\n    expect(vm.$el.type).toBe('checkbox')\n  })\n})\n"
  },
  {
    "path": "vue/test/unit/features/directives/model-component.spec.js",
    "content": "import Vue from 'vue'\n\ndescribe('Directive v-model component', () => {\n  it('should work', done => {\n    const vm = new Vue({\n      data: {\n        msg: 'hello'\n      },\n      template: `\n        <div>\n          <p>{{ msg }}</p>\n          <test v-model=\"msg\"></test>\n        </div>\n      `,\n      components: {\n        test: {\n          props: ['value'],\n          template: `<input :value=\"value\" @input=\"$emit('input', $event.target.value)\">`\n        }\n      }\n    }).$mount()\n    document.body.appendChild(vm.$el)\n    waitForUpdate(() => {\n      const input = vm.$el.querySelector('input')\n      input.value = 'world'\n      triggerEvent(input, 'input')\n    }).then(() => {\n      expect(vm.msg).toEqual('world')\n      expect(vm.$el.querySelector('p').textContent).toEqual('world')\n      vm.msg = 'changed'\n    }).then(() => {\n      expect(vm.$el.querySelector('p').textContent).toEqual('changed')\n      expect(vm.$el.querySelector('input').value).toEqual('changed')\n    }).then(() => {\n      document.body.removeChild(vm.$el)\n    }).then(done)\n  })\n\n  it('should work with native tags with \"is\"', done => {\n    const vm = new Vue({\n      data: {\n        msg: 'hello'\n      },\n      template: `\n        <div>\n          <p>{{ msg }}</p>\n          <input is=\"test\" v-model=\"msg\">\n        </div>\n      `,\n      components: {\n        test: {\n          props: ['value'],\n          template: `<input :value=\"value\" @input=\"$emit('input', $event.target.value)\">`\n        }\n      }\n    }).$mount()\n    document.body.appendChild(vm.$el)\n    waitForUpdate(() => {\n      const input = vm.$el.querySelector('input')\n      input.value = 'world'\n      triggerEvent(input, 'input')\n    }).then(() => {\n      expect(vm.msg).toEqual('world')\n      expect(vm.$el.querySelector('p').textContent).toEqual('world')\n      vm.msg = 'changed'\n    }).then(() => {\n      expect(vm.$el.querySelector('p').textContent).toEqual('changed')\n      expect(vm.$el.querySelector('input').value).toEqual('changed')\n    }).then(() => {\n      document.body.removeChild(vm.$el)\n    }).then(done)\n  })\n\n  it('should support customization via model option', done => {\n    const spy = jasmine.createSpy('update')\n    const vm = new Vue({\n      data: {\n        msg: 'hello'\n      },\n      methods: {\n        spy\n      },\n      template: `\n        <div>\n          <p>{{ msg }}</p>\n          <test v-model=\"msg\" @update=\"spy\"></test>\n        </div>\n      `,\n      components: {\n        test: {\n          model: {\n            prop: 'currentValue',\n            event: 'update'\n          },\n          props: ['currentValue'],\n          template: `<input :value=\"currentValue\" @input=\"$emit('update', $event.target.value)\">`\n        }\n      }\n    }).$mount()\n    document.body.appendChild(vm.$el)\n    waitForUpdate(() => {\n      const input = vm.$el.querySelector('input')\n      input.value = 'world'\n      triggerEvent(input, 'input')\n    }).then(() => {\n      expect(vm.msg).toEqual('world')\n      expect(vm.$el.querySelector('p').textContent).toEqual('world')\n      expect(spy).toHaveBeenCalledWith('world')\n      vm.msg = 'changed'\n    }).then(() => {\n      expect(vm.$el.querySelector('p').textContent).toEqual('changed')\n      expect(vm.$el.querySelector('input').value).toEqual('changed')\n    }).then(() => {\n      document.body.removeChild(vm.$el)\n    }).then(done)\n  })\n\n  it('modifier: .number', () => {\n    const vm = new Vue({\n      template: `<div><my-input ref=\"input\" v-model.number=\"text\"></my-input></div>`,\n      data: { text: 'foo' },\n      components: {\n        'my-input': {\n          template: '<input>'\n        }\n      }\n    }).$mount()\n    expect(vm.text).toBe('foo')\n    vm.$refs.input.$emit('input', 'bar')\n    expect(vm.text).toBe('bar')\n    vm.$refs.input.$emit('input', '123')\n    expect(vm.text).toBe(123)\n  })\n\n  it('modifier: .trim', () => {\n    const vm = new Vue({\n      template: `<div><my-input ref=\"input\" v-model.trim=\"text\"></my-input></div>`,\n      data: { text: 'foo' },\n      components: {\n        'my-input': {\n          template: '<input>'\n        }\n      }\n    }).$mount()\n    expect(vm.text).toBe('foo')\n    vm.$refs.input.$emit('input', '  bar  ')\n    expect(vm.text).toBe('bar')\n    vm.$refs.input.$emit('input', '   foo o  ')\n    expect(vm.text).toBe('foo o')\n  })\n})\n"
  },
  {
    "path": "vue/test/unit/features/directives/model-dynamic.spec.js",
    "content": "import Vue from 'vue'\n\ndescribe('Directive v-model dynamic input type', () => {\n  it('should work', done => {\n    const vm = new Vue({\n      data: {\n        inputType: null,\n        test: 'b'\n      },\n      template: `<input :type=\"inputType\" v-model=\"test\">`\n    }).$mount()\n    document.body.appendChild(vm.$el)\n\n    // test text\n    assertInputWorks(vm, 'inputType').then(done)\n  })\n\n  it('with v-if', done => {\n    const vm = new Vue({\n      data: {\n        ok: true,\n        type: null,\n        test: 'b'\n      },\n      template: `<input v-if=\"ok\" :type=\"type\" v-model=\"test\"><div v-else>haha</div>`\n    }).$mount()\n    document.body.appendChild(vm.$el)\n\n    const chain = assertInputWorks(vm).then(() => {\n      vm.ok = false\n    }).then(() => {\n      expect(vm.$el.textContent).toBe('haha')\n    }).then(() => {\n      // reset\n      vm.ok = true\n      vm.type = null\n      vm.test = 'b'\n    })\n\n    assertInputWorks(vm, chain).then(done)\n  })\n\n  it('with v-else', done => {\n    const data = {\n      ok: true,\n      type: null,\n      test: 'b'\n    }\n    const vm = new Vue({\n      data,\n      template: `<div v-if=\"ok\">haha</div><input v-else :type=\"type\" v-model=\"test\">`\n    }).$mount()\n    document.body.appendChild(vm.$el)\n    expect(vm.$el.textContent).toBe('haha')\n\n    vm.ok = false\n    assertInputWorks(vm).then(done)\n  })\n\n  it('with v-else-if', done => {\n    const vm = new Vue({\n      data: {\n        foo: true,\n        bar: false,\n        type: null,\n        test: 'b'\n      },\n      template: `<div v-if=\"foo\">text</div><input v-else-if=\"bar\" :type=\"type\" v-model=\"test\">`\n    }).$mount()\n    document.body.appendChild(vm.$el)\n\n    const chain = waitForUpdate(() => {\n      expect(vm.$el.textContent).toBe('text')\n    }).then(() => {\n      vm.foo = false\n    }).then(() => {\n      expect(vm._vnode.isComment).toBe(true)\n    }).then(() => {\n      vm.bar = true\n    })\n\n    assertInputWorks(vm, chain).then(done)\n  })\n\n  it('with v-for', done => {\n    const vm = new Vue({\n      data: {\n        data: {\n          text: 'foo',\n          checkbox: true\n        },\n        types: ['text', 'checkbox']\n      },\n      template: `<div>\n        <input v-for=\"type in types\" :type=\"type\" v-model=\"data[type]\">\n      </div>`\n    }).$mount()\n    document.body.appendChild(vm.$el)\n\n    let el1 = vm.$el.children[0]\n    expect(el1.type).toBe('text')\n    expect(el1.value).toBe('foo')\n    el1.value = 'bar'\n    triggerEvent(el1, 'input')\n    expect(vm.data.text).toBe('bar')\n\n    let el2 = vm.$el.children[1]\n    expect(el2.type).toBe('checkbox')\n    expect(el2.checked).toBe(true)\n    el2.click()\n    expect(vm.data.checkbox).toBe(false)\n\n    // now in reverse!\n    vm.types.reverse()\n    waitForUpdate(() => {\n      el1 = vm.$el.children[0]\n      expect(el1.type).toBe('checkbox')\n      expect(el1.checked).toBe(false)\n      el1.click()\n      expect(vm.data.checkbox).toBe(true)\n\n      el2 = vm.$el.children[1]\n      expect(el2.type).toBe('text')\n      expect(el2.value).toBe('bar')\n      el2.value = 'foo'\n      triggerEvent(el2, 'input')\n      expect(vm.data.text).toBe('foo')\n    }).then(done)\n  })\n\n  it('with v-bind', done => {\n    const vm = new Vue({\n      data: {\n        data: {\n          text: 'foo',\n          checkbox: true\n        },\n        inputs: [{ id: 'one', type: 'text' }, { id: 'two', type: 'checkbox' }]\n      },\n      template: `<div>\n        <input v-for=\"i in inputs\" v-bind=\"i\" v-model=\"data[i.type]\">\n      </div>`\n    }).$mount()\n    document.body.appendChild(vm.$el)\n\n    let el1 = vm.$el.children[0]\n    expect(el1.id).toBe('one')\n    expect(el1.type).toBe('text')\n    expect(el1.value).toBe('foo')\n    el1.value = 'bar'\n    triggerEvent(el1, 'input')\n    expect(vm.data.text).toBe('bar')\n\n    let el2 = vm.$el.children[1]\n    expect(el2.id).toBe('two')\n    expect(el2.type).toBe('checkbox')\n    expect(el2.checked).toBe(true)\n    el2.click()\n    expect(vm.data.checkbox).toBe(false)\n\n    // now in reverse!\n    vm.inputs.reverse()\n    waitForUpdate(() => {\n      el1 = vm.$el.children[0]\n      expect(el1.id).toBe('two')\n      expect(el1.type).toBe('checkbox')\n      expect(el1.checked).toBe(false)\n      el1.click()\n      expect(vm.data.checkbox).toBe(true)\n\n      el2 = vm.$el.children[1]\n      expect(el2.id).toBe('one')\n      expect(el2.type).toBe('text')\n      expect(el2.value).toBe('bar')\n      el2.value = 'foo'\n      triggerEvent(el2, 'input')\n      expect(vm.data.text).toBe('foo')\n    }).then(done)\n  })\n})\n\nfunction assertInputWorks (vm, type, chain) {\n  if (typeof type !== 'string') {\n    if (!chain) chain = type\n    type = 'type'\n  }\n  if (!chain) chain = waitForUpdate()\n  chain.then(() => {\n    expect(vm.$el.value).toBe('b')\n    vm.test = 'a'\n  }).then(() => {\n    expect(vm.$el.value).toBe('a')\n    vm.$el.value = 'c'\n    triggerEvent(vm.$el, 'input')\n    expect(vm.test).toBe('c')\n  }).then(() => {\n    // change it to password\n    vm[type] = 'password'\n    vm.test = 'b'\n  }).then(() => {\n    expect(vm.$el.type).toBe('password')\n    expect(vm.$el.value).toBe('b')\n    vm.$el.value = 'c'\n    triggerEvent(vm.$el, 'input')\n    expect(vm.test).toBe('c')\n  }).then(() => {\n    // change it to checkbox...\n    vm[type] = 'checkbox'\n  }).then(() => {\n    expect(vm.$el.type).toBe('checkbox')\n    expect(vm.$el.checked).toBe(true)\n  }).then(() => {\n    vm.$el.click()\n    expect(vm.$el.checked).toBe(false)\n    expect(vm.test).toBe(false)\n  })\n  return chain\n}\n"
  },
  {
    "path": "vue/test/unit/features/directives/model-file.spec.js",
    "content": "import Vue from 'vue'\n\ndescribe('Directive v-model file', () => {\n  it('warn to use @change instead', () => {\n    new Vue({\n      data: {\n        file: ''\n      },\n      template: '<input v-model=\"file\" type=\"file\">'\n    }).$mount()\n    expect('Use a v-on:change listener instead').toHaveBeenWarned()\n  })\n})\n"
  },
  {
    "path": "vue/test/unit/features/directives/model-parse.spec.js",
    "content": "import { parseModel } from 'compiler/directives/model'\n\ndescribe('model expression parser', () => {\n  it('parse single path', () => {\n    const res = parseModel('foo')\n    expect(res.exp).toBe('foo')\n    expect(res.key).toBe(null)\n  })\n\n  it('parse object dot notation', () => {\n    const res = parseModel('a.b.c')\n    expect(res.exp).toBe('a.b')\n    expect(res.key).toBe('\"c\"')\n  })\n\n  it('parse string in brackets', () => {\n    const res = parseModel('a[\"b\"][c]')\n    expect(res.exp).toBe('a[\"b\"]')\n    expect(res.key).toBe('c')\n  })\n\n  it('parse brackets with object dot notation', () => {\n    const res = parseModel('a[\"b\"][c].xxx')\n    expect(res.exp).toBe('a[\"b\"][c]')\n    expect(res.key).toBe('\"xxx\"')\n  })\n\n  it('parse nested brackets', () => {\n    const res = parseModel('a[i[c]]')\n    expect(res.exp).toBe('a')\n    expect(res.key).toBe('i[c]')\n  })\n\n  it('combined', () => {\n    const res = parseModel('test.xxx.a[\"asa\"][test1[key]]')\n    expect(res.exp).toBe('test.xxx.a[\"asa\"]')\n    expect(res.key).toBe('test1[key]')\n  })\n})\n"
  },
  {
    "path": "vue/test/unit/features/directives/model-radio.spec.js",
    "content": "import Vue from 'vue'\n\ndescribe('Directive v-model radio', () => {\n  it('should work', done => {\n    const vm = new Vue({\n      data: {\n        test: '1'\n      },\n      template: `\n        <div>\n          <input type=\"radio\" value=\"1\" v-model=\"test\" name=\"test\">\n          <input type=\"radio\" value=\"2\" v-model=\"test\" name=\"test\">\n        </div>\n      `\n    }).$mount()\n    document.body.appendChild(vm.$el)\n    expect(vm.$el.children[0].checked).toBe(true)\n    expect(vm.$el.children[1].checked).toBe(false)\n    vm.test = '2'\n    waitForUpdate(() => {\n      expect(vm.$el.children[0].checked).toBe(false)\n      expect(vm.$el.children[1].checked).toBe(true)\n      vm.$el.children[0].click()\n      expect(vm.$el.children[0].checked).toBe(true)\n      expect(vm.$el.children[1].checked).toBe(false)\n      expect(vm.test).toBe('1')\n    }).then(() => {\n      document.body.removeChild(vm.$el)\n    }).then(done)\n  })\n\n  it('should respect value bindings', done => {\n    const vm = new Vue({\n      data: {\n        test: 1\n      },\n      template: `\n        <div>\n          <input type=\"radio\" :value=\"1\" v-model=\"test\" name=\"test\">\n          <input type=\"radio\" :value=\"2\" v-model=\"test\" name=\"test\">\n        </div>\n      `\n    }).$mount()\n    document.body.appendChild(vm.$el)\n    expect(vm.$el.children[0].checked).toBe(true)\n    expect(vm.$el.children[1].checked).toBe(false)\n    vm.test = 2\n    waitForUpdate(() => {\n      expect(vm.$el.children[0].checked).toBe(false)\n      expect(vm.$el.children[1].checked).toBe(true)\n      vm.$el.children[0].click()\n      expect(vm.$el.children[0].checked).toBe(true)\n      expect(vm.$el.children[1].checked).toBe(false)\n      expect(vm.test).toBe(1)\n    }).then(() => {\n      document.body.removeChild(vm.$el)\n    }).then(done)\n  })\n\n  it('should respect value bindings (object loose equal)', done => {\n    const vm = new Vue({\n      data: {\n        test: { a: 1 }\n      },\n      template: `\n        <div>\n          <input type=\"radio\" :value=\"{ a: 1 }\" v-model=\"test\" name=\"test\">\n          <input type=\"radio\" :value=\"{ a: 2 }\" v-model=\"test\" name=\"test\">\n        </div>\n      `\n    }).$mount()\n    document.body.appendChild(vm.$el)\n    expect(vm.$el.children[0].checked).toBe(true)\n    expect(vm.$el.children[1].checked).toBe(false)\n    vm.test = { a: 2 }\n    waitForUpdate(() => {\n      expect(vm.$el.children[0].checked).toBe(false)\n      expect(vm.$el.children[1].checked).toBe(true)\n      vm.$el.children[0].click()\n      expect(vm.$el.children[0].checked).toBe(true)\n      expect(vm.$el.children[1].checked).toBe(false)\n      expect(vm.test).toEqual({ a: 1 })\n    }).then(() => {\n      document.body.removeChild(vm.$el)\n    }).then(done)\n  })\n\n  it('multiple radios ', (done) => {\n    const spy = jasmine.createSpy()\n    const vm = new Vue({\n      data: {\n        selections: ['a', '1'],\n        radioList: [\n          {\n            name: 'questionA',\n            data: ['a', 'b', 'c']\n          },\n          {\n            name: 'questionB',\n            data: ['1', '2']\n          }\n        ]\n      },\n      watch: {\n        selections: spy\n      },\n      template:\n        '<div>' +\n          '<div v-for=\"(radioGroup, idx) in radioList\">' +\n            '<div>' +\n              '<span v-for=\"(item, index) in radioGroup.data\">' +\n                '<input :name=\"radioGroup.name\" type=\"radio\" :value=\"item\" v-model=\"selections[idx]\" :id=\"idx\"/>' +\n                '<label>{{item}}</label>' +\n              '</span>' +\n            '</div>' +\n          '</div>' +\n        '</div>'\n    }).$mount()\n    document.body.appendChild(vm.$el)\n    var inputs = vm.$el.getElementsByTagName('input')\n    inputs[1].click()\n    waitForUpdate(() => {\n      expect(vm.selections).toEqual(['b', '1'])\n      expect(spy).toHaveBeenCalled()\n    }).then(done)\n  })\n\n  it('.number modifier', () => {\n    const vm = new Vue({\n      data: {\n        test: 1\n      },\n      template: `\n        <div>\n          <input type=\"radio\" value=\"1\" v-model=\"test\" name=\"test\">\n          <input type=\"radio\" value=\"2\" v-model.number=\"test\" name=\"test\">\n        </div>\n      `\n    }).$mount()\n    document.body.appendChild(vm.$el)\n    expect(vm.$el.children[0].checked).toBe(true)\n    expect(vm.$el.children[1].checked).toBe(false)\n    vm.$el.children[1].click()\n    expect(vm.$el.children[0].checked).toBe(false)\n    expect(vm.$el.children[1].checked).toBe(true)\n    expect(vm.test).toBe(2)\n  })\n\n  it('should respect different primitive type value', (done) => {\n    const vm = new Vue({\n      data: {\n        test: 1\n      },\n      template:\n        '<div>' +\n          '<input type=\"radio\" value=\"\" v-model=\"test\" name=\"test\">' +\n          '<input type=\"radio\" value=\"0\" v-model=\"test\" name=\"test\">' +\n          '<input type=\"radio\" value=\"1\" v-model=\"test\" name=\"test\">' +\n          '<input type=\"radio\" value=\"false\" v-model=\"test\" name=\"test\">' +\n          '<input type=\"radio\" value=\"true\" v-model=\"test\" name=\"test\">' +\n        '</div>'\n    }).$mount()\n    var radioboxInput = vm.$el.children\n    expect(radioboxInput[0].checked).toBe(false)\n    expect(radioboxInput[1].checked).toBe(false)\n    expect(radioboxInput[2].checked).toBe(true)\n    expect(radioboxInput[3].checked).toBe(false)\n    expect(radioboxInput[4].checked).toBe(false)\n    vm.test = 0\n    waitForUpdate(() => {\n      expect(radioboxInput[0].checked).toBe(false)\n      expect(radioboxInput[1].checked).toBe(true)\n      expect(radioboxInput[2].checked).toBe(false)\n      expect(radioboxInput[3].checked).toBe(false)\n      expect(radioboxInput[4].checked).toBe(false)\n      vm.test = ''\n    }).then(() => {\n      expect(radioboxInput[0].checked).toBe(true)\n      expect(radioboxInput[1].checked).toBe(false)\n      expect(radioboxInput[2].checked).toBe(false)\n      expect(radioboxInput[3].checked).toBe(false)\n      expect(radioboxInput[4].checked).toBe(false)\n      vm.test = false\n    }).then(() => {\n      expect(radioboxInput[0].checked).toBe(false)\n      expect(radioboxInput[1].checked).toBe(false)\n      expect(radioboxInput[2].checked).toBe(false)\n      expect(radioboxInput[3].checked).toBe(true)\n      expect(radioboxInput[4].checked).toBe(false)\n      vm.test = true\n    }).then(() => {\n      expect(radioboxInput[0].checked).toBe(false)\n      expect(radioboxInput[1].checked).toBe(false)\n      expect(radioboxInput[2].checked).toBe(false)\n      expect(radioboxInput[3].checked).toBe(false)\n      expect(radioboxInput[4].checked).toBe(true)\n    }).then(done)\n  })\n\n  // #4521\n  it('should work with click event', (done) => {\n    const vm = new Vue({\n      data: {\n        num: 1,\n        checked: 1\n      },\n      template:\n        '<div @click=\"add\">' +\n          'click {{ num }}<input name=\"test\" type=\"radio\" value=\"1\" v-model=\"checked\"/>' +\n          '<input name=\"test\" type=\"radio\" value=\"2\" v-model=\"checked\"/>' +\n        '</div>',\n      methods: {\n        add: function () {\n          this.num++\n        }\n      }\n    }).$mount()\n    document.body.appendChild(vm.$el)\n    const radios = vm.$el.getElementsByTagName('input')\n    radios[0].click()\n    waitForUpdate(() => {\n      expect(radios[0].checked).toBe(true)\n      expect(radios[1].checked).toBe(false)\n      expect(vm.num).toBe(2)\n      radios[0].click()\n    }).then(() => {\n      expect(radios[0].checked).toBe(true)\n      expect(radios[1].checked).toBe(false)\n      expect(vm.num).toBe(3)\n      radios[1].click()\n    }).then(() => {\n      expect(radios[0].checked).toBe(false)\n      expect(radios[1].checked).toBe(true)\n      expect(vm.num).toBe(4)\n    }).then(done)\n  })\n\n  it('should get updated with model when in focus', (done) => {\n    const vm = new Vue({\n      data: {\n        a: '2'\n      },\n      template: '<input type=\"radio\" value=\"1\" v-model=\"a\"/>'\n    }).$mount()\n    document.body.appendChild(vm.$el)\n    vm.$el.click()\n    waitForUpdate(() => {\n      expect(vm.$el.checked).toBe(true)\n      vm.a = 2\n    }).then(() => {\n      expect(vm.$el.checked).toBe(false)\n    }).then(done)\n  })\n})\n"
  },
  {
    "path": "vue/test/unit/features/directives/model-select.spec.js",
    "content": "import Vue from 'vue'\nimport { looseEqual } from 'shared/util'\n\n// Android 4.4 Chrome 30 has the bug that a multi-select option cannot be\n// deselected by setting its \"selected\" prop via JavaScript.\nfunction hasMultiSelectBug () {\n  var s = document.createElement('select')\n  s.setAttribute('multiple', '')\n  var o = document.createElement('option')\n  s.appendChild(o)\n  o.selected = true\n  o.selected = false\n  return o.selected !== false\n}\n\n/**\n * setting <select>'s value in IE9 doesn't work\n * we have to manually loop through the options\n */\nfunction updateSelect (el, value) {\n  var options = el.options\n  var i = options.length\n  while (i--) {\n    if (looseEqual(getValue(options[i]), value)) {\n      options[i].selected = true\n      break\n    }\n  }\n}\n\nfunction getValue (option) {\n  return '_value' in option\n    ? option._value\n    : option.value || option.text\n}\n\ndescribe('Directive v-model select', () => {\n  it('should work', done => {\n    const vm = new Vue({\n      data: {\n        test: 'b'\n      },\n      template:\n        '<select v-model=\"test\">' +\n          '<option>a</option>' +\n          '<option>b</option>' +\n          '<option>c</option>' +\n        '</select>'\n    }).$mount()\n    document.body.appendChild(vm.$el)\n    expect(vm.test).toBe('b')\n    expect(vm.$el.value).toBe('b')\n    expect(vm.$el.childNodes[1].selected).toBe(true)\n    vm.test = 'c'\n    waitForUpdate(function () {\n      expect(vm.$el.value).toBe('c')\n      expect(vm.$el.childNodes[2].selected).toBe(true)\n      updateSelect(vm.$el, 'a')\n      triggerEvent(vm.$el, 'change')\n      expect(vm.test).toBe('a')\n    }).then(done)\n  })\n\n  it('should work with value bindings', done => {\n    const vm = new Vue({\n      data: {\n        test: 2\n      },\n      template:\n        '<select v-model=\"test\">' +\n          '<option value=\"1\">a</option>' +\n          '<option :value=\"2\">b</option>' +\n          '<option :value=\"3\">c</option>' +\n        '</select>'\n    }).$mount()\n    document.body.appendChild(vm.$el)\n    expect(vm.$el.value).toBe('2')\n    expect(vm.$el.childNodes[1].selected).toBe(true)\n    vm.test = 3\n    waitForUpdate(function () {\n      expect(vm.$el.value).toBe('3')\n      expect(vm.$el.childNodes[2].selected).toBe(true)\n\n      updateSelect(vm.$el, '1')\n      triggerEvent(vm.$el, 'change')\n      expect(vm.test).toBe('1')\n\n      updateSelect(vm.$el, '2')\n      triggerEvent(vm.$el, 'change')\n      expect(vm.test).toBe(2)\n    }).then(done)\n  })\n\n  it('should work with value bindings (object loose equal)', done => {\n    const vm = new Vue({\n      data: {\n        test: { a: 2 }\n      },\n      template:\n        '<select v-model=\"test\">' +\n          '<option value=\"1\">a</option>' +\n          '<option :value=\"{ a: 2 }\">b</option>' +\n          '<option :value=\"{ a: 3 }\">c</option>' +\n        '</select>'\n    }).$mount()\n    document.body.appendChild(vm.$el)\n    expect(vm.$el.childNodes[1].selected).toBe(true)\n    vm.test = { a: 3 }\n    waitForUpdate(function () {\n      expect(vm.$el.childNodes[2].selected).toBe(true)\n\n      updateSelect(vm.$el, '1')\n      triggerEvent(vm.$el, 'change')\n      expect(vm.test).toBe('1')\n\n      updateSelect(vm.$el, { a: 2 })\n      triggerEvent(vm.$el, 'change')\n      expect(vm.test).toEqual({ a: 2 })\n    }).then(done)\n  })\n\n  it('should work with value bindings (Array loose equal)', done => {\n    const vm = new Vue({\n      data: {\n        test: [{ a: 2 }]\n      },\n      template:\n        '<select v-model=\"test\">' +\n          '<option value=\"1\">a</option>' +\n          '<option :value=\"[{ a: 2 }]\">b</option>' +\n          '<option :value=\"[{ a: 3 }]\">c</option>' +\n        '</select>'\n    }).$mount()\n    document.body.appendChild(vm.$el)\n    expect(vm.$el.childNodes[1].selected).toBe(true)\n    vm.test = [{ a: 3 }]\n    waitForUpdate(function () {\n      expect(vm.$el.childNodes[2].selected).toBe(true)\n\n      updateSelect(vm.$el, '1')\n      triggerEvent(vm.$el, 'change')\n      expect(vm.test).toBe('1')\n\n      updateSelect(vm.$el, [{ a: 2 }])\n      triggerEvent(vm.$el, 'change')\n      expect(vm.test).toEqual([{ a: 2 }])\n    }).then(done)\n  })\n\n  it('should work with v-for', done => {\n    const vm = new Vue({\n      data: {\n        test: 'b',\n        opts: ['a', 'b', 'c']\n      },\n      template:\n        '<select v-model=\"test\">' +\n          '<option v-for=\"o in opts\">{{ o }}</option>' +\n        '</select>'\n    }).$mount()\n    document.body.appendChild(vm.$el)\n    expect(vm.test).toBe('b')\n    expect(vm.$el.value).toBe('b')\n    expect(vm.$el.childNodes[1].selected).toBe(true)\n    vm.test = 'c'\n    waitForUpdate(function () {\n      expect(vm.$el.value).toBe('c')\n      expect(vm.$el.childNodes[2].selected).toBe(true)\n      updateSelect(vm.$el, 'a')\n      triggerEvent(vm.$el, 'change')\n      expect(vm.test).toBe('a')\n      // update v-for opts\n      vm.opts = ['d', 'a']\n    }).then(() => {\n      expect(vm.$el.childNodes[0].selected).toBe(false)\n      expect(vm.$el.childNodes[1].selected).toBe(true)\n    }).then(done)\n  })\n\n  it('should work with v-for & value bindings', done => {\n    const vm = new Vue({\n      data: {\n        test: 2,\n        opts: [1, 2, 3]\n      },\n      template:\n        '<select v-model=\"test\">' +\n          '<option v-for=\"o in opts\" :value=\"o\">option {{ o }}</option>' +\n        '</select>'\n    }).$mount()\n    document.body.appendChild(vm.$el)\n    expect(vm.$el.value).toBe('2')\n    expect(vm.$el.childNodes[1].selected).toBe(true)\n    vm.test = 3\n    waitForUpdate(function () {\n      expect(vm.$el.value).toBe('3')\n      expect(vm.$el.childNodes[2].selected).toBe(true)\n      updateSelect(vm.$el, 1)\n      triggerEvent(vm.$el, 'change')\n      expect(vm.test).toBe(1)\n      // update v-for opts\n      vm.opts = [0, 1]\n    }).then(() => {\n      expect(vm.$el.childNodes[0].selected).toBe(false)\n      expect(vm.$el.childNodes[1].selected).toBe(true)\n    }).then(done)\n  })\n\n  it('should work with select which has no default selected options', (done) => {\n    const spy = jasmine.createSpy()\n    const vm = new Vue({\n      data: {\n        id: 4,\n        list: [1, 2, 3],\n        testChange: 5\n      },\n      template:\n        '<div>' +\n          '<select @change=\"test\" v-model=\"id\">' +\n            '<option v-for=\"item in list\" :value=\"item\">{{item}}</option>' +\n          '</select>' +\n          '{{testChange}}' +\n        '</div>',\n      methods: {\n        test: spy\n      }\n    }).$mount()\n    document.body.appendChild(vm.$el)\n    vm.testChange = 10\n    waitForUpdate(() => {\n      expect(spy.calls.count()).toBe(0)\n    }).then(done)\n  })\n\n  if (!hasMultiSelectBug()) {\n    it('multiple', done => {\n      const vm = new Vue({\n        data: {\n          test: ['b']\n        },\n        template:\n          '<select v-model=\"test\" multiple>' +\n            '<option>a</option>' +\n            '<option>b</option>' +\n            '<option>c</option>' +\n          '</select>'\n      }).$mount()\n      var opts = vm.$el.options\n      expect(opts[0].selected).toBe(false)\n      expect(opts[1].selected).toBe(true)\n      expect(opts[2].selected).toBe(false)\n      vm.test = ['a', 'c']\n      waitForUpdate(() => {\n        expect(opts[0].selected).toBe(true)\n        expect(opts[1].selected).toBe(false)\n        expect(opts[2].selected).toBe(true)\n        opts[0].selected = false\n        opts[1].selected = true\n        triggerEvent(vm.$el, 'change')\n        expect(vm.test).toEqual(['b', 'c'])\n      }).then(done)\n    })\n\n    it('multiple + v-for', done => {\n      const vm = new Vue({\n        data: {\n          test: ['b'],\n          opts: ['a', 'b', 'c']\n        },\n        template:\n          '<select v-model=\"test\" multiple>' +\n            '<option v-for=\"o in opts\">{{ o }}</option>' +\n          '</select>'\n      }).$mount()\n      var opts = vm.$el.options\n      expect(opts[0].selected).toBe(false)\n      expect(opts[1].selected).toBe(true)\n      expect(opts[2].selected).toBe(false)\n      vm.test = ['a', 'c']\n      waitForUpdate(() => {\n        expect(opts[0].selected).toBe(true)\n        expect(opts[1].selected).toBe(false)\n        expect(opts[2].selected).toBe(true)\n        opts[0].selected = false\n        opts[1].selected = true\n        triggerEvent(vm.$el, 'change')\n        expect(vm.test).toEqual(['b', 'c'])\n        // update v-for opts\n        vm.opts = ['c', 'd']\n      }).then(() => {\n        expect(opts[0].selected).toBe(true)\n        expect(opts[1].selected).toBe(false)\n        expect(vm.test).toEqual(['c']) // should remove 'd' which no longer has a matching option\n      }).then(done)\n    })\n  }\n\n  it('should work with multiple binding', (done) => {\n    const spy = jasmine.createSpy()\n    const vm = new Vue({\n      data: {\n        isMultiple: true,\n        selections: ['1']\n      },\n      template:\n        '<select v-model=\"selections\" :multiple=\"isMultiple\">' +\n          '<option value=\"1\">item 1</option>' +\n          '<option value=\"2\">item 2</option>' +\n        '</select>',\n      watch: {\n        selections: spy\n      }\n    }).$mount()\n    document.body.appendChild(vm.$el)\n    vm.$el.options[1].selected = true\n    triggerEvent(vm.$el, 'change')\n    waitForUpdate(() => {\n      expect(spy).toHaveBeenCalled()\n      expect(vm.selections).toEqual(['1', '2'])\n    }).then(done)\n  })\n\n  it('should not have multiple attr with falsy values except \\'\\'', () => {\n    const vm = new Vue({\n      template:\n        '<div>' +\n          '<select id=\"undefined\" :multiple=\"undefined\"></select>' +\n          '<select id=\"null\" :multiple=\"null\"></select>' +\n          '<select id=\"false\" :multiple=\"false\"></select>' +\n          '<select id=\"string\" :multiple=\"\\'\\'\"></select>' +\n        '</div>'\n    }).$mount()\n    expect(vm.$el.querySelector('#undefined').multiple).toEqual(false)\n    expect(vm.$el.querySelector('#null').multiple).toEqual(false)\n    expect(vm.$el.querySelector('#false').multiple).toEqual(false)\n    expect(vm.$el.querySelector('#string').multiple).toEqual(true)\n  })\n\n  it('multiple with static template', () => {\n    const vm = new Vue({\n      template:\n      '<select multiple>' +\n        '<option selected>a</option>' +\n        '<option selected>b</option>' +\n        '<option selected>c</option>' +\n      '</select>'\n    }).$mount()\n    var opts = vm.$el.options\n    expect(opts[0].selected).toBe(true)\n    expect(opts[1].selected).toBe(true)\n    expect(opts[2].selected).toBe(true)\n  })\n\n  it('multiple selects', (done) => {\n    const spy = jasmine.createSpy()\n    const vm = new Vue({\n      data: {\n        selections: ['', ''],\n        selectBoxes: [\n          [\n            { value: 'foo', text: 'foo' },\n            { value: 'bar', text: 'bar' }\n          ],\n          [\n            { value: 'day', text: 'day' },\n            { value: 'night', text: 'night' }\n          ]\n        ]\n      },\n      watch: {\n        selections: spy\n      },\n      template:\n        '<div>' +\n          '<select v-for=\"(item, index) in selectBoxes\" v-model=\"selections[index]\">' +\n            '<option v-for=\"element in item\" v-bind:value=\"element.value\" v-text=\"element.text\"></option>' +\n          '</select>' +\n          '<span ref=\"rs\">{{selections}}</span>' +\n        '</div>'\n    }).$mount()\n    document.body.appendChild(vm.$el)\n    var selects = vm.$el.getElementsByTagName('select')\n    var select0 = selects[0]\n    select0.options[0].selected = true\n    triggerEvent(select0, 'change')\n    waitForUpdate(() => {\n      expect(spy).toHaveBeenCalled()\n      expect(vm.selections).toEqual(['foo', ''])\n    }).then(done)\n  })\n\n  it('.number modifier', () => {\n    const vm = new Vue({\n      data: {\n        test: 2\n      },\n      template:\n        '<select v-model.number=\"test\">' +\n          '<option value=\"1\">a</option>' +\n          '<option :value=\"2\">b</option>' +\n          '<option :value=\"3\">c</option>' +\n        '</select>'\n    }).$mount()\n    document.body.appendChild(vm.$el)\n    updateSelect(vm.$el, '1')\n    triggerEvent(vm.$el, 'change')\n    expect(vm.test).toBe(1)\n  })\n\n  it('should respect different primitive type value', (done) => {\n    const vm = new Vue({\n      data: {\n        test: 0\n      },\n      template:\n        '<select v-model.number=\"test\">' +\n          '<option value=\"\">a</option>' +\n          '<option value=\"0\">b</option>' +\n          '<option value=\"1\">c</option>' +\n          '<option value=\"false\">c</option>' +\n          '<option value=\"true\">c</option>' +\n        '</select>'\n    }).$mount()\n    var opts = vm.$el.options\n    expect(opts[0].selected).toBe(false)\n    expect(opts[1].selected).toBe(true)\n    expect(opts[2].selected).toBe(false)\n    expect(opts[3].selected).toBe(false)\n    expect(opts[4].selected).toBe(false)\n    vm.test = 1\n    waitForUpdate(() => {\n      expect(opts[0].selected).toBe(false)\n      expect(opts[1].selected).toBe(false)\n      expect(opts[2].selected).toBe(true)\n      expect(opts[3].selected).toBe(false)\n      expect(opts[4].selected).toBe(false)\n      vm.test = ''\n    }).then(() => {\n      expect(opts[0].selected).toBe(true)\n      expect(opts[1].selected).toBe(false)\n      expect(opts[2].selected).toBe(false)\n      expect(opts[3].selected).toBe(false)\n      expect(opts[4].selected).toBe(false)\n      vm.test = false\n    }).then(() => {\n      expect(opts[0].selected).toBe(false)\n      expect(opts[1].selected).toBe(false)\n      expect(opts[2].selected).toBe(false)\n      expect(opts[3].selected).toBe(true)\n      expect(opts[4].selected).toBe(false)\n      vm.test = true\n    }).then(() => {\n      expect(opts[0].selected).toBe(false)\n      expect(opts[1].selected).toBe(false)\n      expect(opts[2].selected).toBe(false)\n      expect(opts[3].selected).toBe(false)\n      expect(opts[4].selected).toBe(true)\n    }).then(done)\n  })\n\n  it('should warn multiple with non-Array value', done => {\n    new Vue({\n      data: {\n        test: 'meh'\n      },\n      template:\n        '<select v-model=\"test\" multiple></select>'\n    }).$mount()\n    // IE warns on a setTimeout as well\n    setTimeout(() => {\n      expect('<select multiple v-model=\"test\"> expects an Array value for its binding, but got String')\n        .toHaveBeenWarned()\n      done()\n    }, 0)\n  })\n\n  it('should work with option value that has circular reference', done => {\n    const circular = {}\n    circular.self = circular\n\n    const vm = new Vue({\n      data: {\n        test: 'b',\n        circular\n      },\n      template:\n        '<select v-model=\"test\">' +\n          '<option :value=\"circular\">a</option>' +\n          '<option>b</option>' +\n          '<option>c</option>' +\n        '</select>'\n    }).$mount()\n    document.body.appendChild(vm.$el)\n    expect(vm.test).toBe('b')\n    expect(vm.$el.value).toBe('b')\n    expect(vm.$el.childNodes[1].selected).toBe(true)\n    vm.test = circular\n    waitForUpdate(function () {\n      expect(vm.$el.childNodes[0].selected).toBe(true)\n    }).then(done)\n  })\n\n  // #6112\n  it('should not set non-matching value to undefined if options did not change', done => {\n    const vm = new Vue({\n      data: {\n        test: '1'\n      },\n      template:\n        '<select v-model=\"test\">' +\n          '<option>a</option>' +\n        '</select>'\n    }).$mount()\n\n    vm.test = '2'\n    waitForUpdate(() => {\n      expect(vm.test).toBe('2')\n    }).then(done)\n  })\n\n  // #6193\n  it('should not trigger change event when matching option can be found for each value', done => {\n    const spy = jasmine.createSpy()\n    const vm = new Vue({\n      data: {\n        options: ['1']\n      },\n      computed: {\n        test: {\n          get () {\n            return '1'\n          },\n          set () {\n            spy()\n          }\n        }\n      },\n      template:\n        '<select v-model=\"test\">' +\n          '<option :key=\"opt\" v-for=\"opt in options\" :value=\"opt\">{{ opt }}</option>' +\n        '</select>'\n    }).$mount()\n\n    vm.options = ['1', '2']\n    waitForUpdate(() => {\n      expect(spy).not.toHaveBeenCalled()\n    }).then(done)\n  })\n\n  // #6903\n  describe('should correctly handle v-model when the vnodes are the same', () => {\n    function makeInstance (foo) {\n      return new Vue({\n        data: {\n          foo: foo,\n          options: ['b', 'c', 'd'],\n          value: 'c'\n        },\n        template:\n          '<div>' +\n            '<select v-if=\"foo\" data-attr>' +\n              '<option selected>a</option>' +\n            '</select>' +\n            '<select v-else v-model=\"value\">' +\n              '<option v-for=\"option in options\" :value=\"option\">{{ option }}</option>' +\n            '</select>' +\n          '</div>'\n      }).$mount()\n    }\n\n    it('register v-model', done => {\n      const vm = makeInstance(true)\n\n      expect(vm.$el.firstChild.selectedIndex).toBe(0)\n      vm.foo = false\n      waitForUpdate(() => {\n        expect(vm.$el.firstChild.selectedIndex).toBe(1)\n      }).then(done)\n    })\n\n    it('remove v-model', done => {\n      const vm = makeInstance(false)\n\n      expect(vm.$el.firstChild.selectedIndex).toBe(1)\n      vm.foo = true\n      waitForUpdate(() => {\n        expect(vm.$el.firstChild.selectedIndex).toBe(0)\n      }).then(done)\n    })\n  })\n})\n"
  },
  {
    "path": "vue/test/unit/features/directives/model-text.spec.js",
    "content": "import Vue from 'vue'\nimport { isIE9, isIE, isAndroid } from 'core/util/env'\n\ndescribe('Directive v-model text', () => {\n  it('should update value both ways', done => {\n    const vm = new Vue({\n      data: {\n        test: 'b'\n      },\n      template: '<input v-model=\"test\">'\n    }).$mount()\n    expect(vm.$el.value).toBe('b')\n    vm.test = 'a'\n    waitForUpdate(() => {\n      expect(vm.$el.value).toBe('a')\n      vm.$el.value = 'c'\n      triggerEvent(vm.$el, 'input')\n      expect(vm.test).toBe('c')\n    }).then(done)\n  })\n\n  it('should work with space ended expression in v-model', () => {\n    const vm = new Vue({\n      data: {\n        obj: {\n          test: 'b'\n        }\n      },\n      template: '<input v-model=\"obj.test \">'\n    }).$mount()\n\n    triggerEvent(vm.$el, 'input')\n    expect(vm.obj['test ']).toBe(undefined)\n    expect(vm.obj.test).toBe('b')\n  })\n\n  it('.lazy modifier', () => {\n    const vm = new Vue({\n      data: {\n        test: 'b'\n      },\n      template: '<input v-model.lazy=\"test\">'\n    }).$mount()\n    expect(vm.$el.value).toBe('b')\n    expect(vm.test).toBe('b')\n    vm.$el.value = 'c'\n    triggerEvent(vm.$el, 'input')\n    expect(vm.test).toBe('b')\n    triggerEvent(vm.$el, 'change')\n    expect(vm.test).toBe('c')\n  })\n\n  it('.number modifier', () => {\n    const vm = new Vue({\n      data: {\n        test: 1\n      },\n      template: '<input v-model.number=\"test\">'\n    }).$mount()\n    expect(vm.test).toBe(1)\n    vm.$el.value = '2'\n    triggerEvent(vm.$el, 'input')\n    expect(vm.test).toBe(2)\n    // should let strings pass through\n    vm.$el.value = 'f'\n    triggerEvent(vm.$el, 'input')\n    expect(vm.test).toBe('f')\n  })\n\n  it('.trim modifier', () => {\n    const vm = new Vue({\n      data: {\n        test: 'hi'\n      },\n      template: '<input v-model.trim=\"test\">'\n    }).$mount()\n    expect(vm.test).toBe('hi')\n    vm.$el.value = ' what '\n    triggerEvent(vm.$el, 'input')\n    expect(vm.test).toBe('what')\n  })\n\n  it('.number focus and typing', (done) => {\n    const vm = new Vue({\n      data: {\n        test: 0,\n        update: 0\n      },\n      template:\n        '<div>' +\n          '<input ref=\"input\" v-model.number=\"test\">{{ update }}' +\n          '<input ref=\"blur\">' +\n        '</div>'\n    }).$mount()\n    document.body.appendChild(vm.$el)\n    vm.$refs.input.focus()\n    expect(vm.test).toBe(0)\n    vm.$refs.input.value = '1.0'\n    triggerEvent(vm.$refs.input, 'input')\n    expect(vm.test).toBe(1)\n    vm.update++\n    waitForUpdate(() => {\n      expect(vm.$refs.input.value).toBe('1.0')\n      vm.$refs.blur.focus()\n      vm.update++\n    }).then(() => {\n      expect(vm.$refs.input.value).toBe('1')\n    }).then(done)\n  })\n\n  it('.trim focus and typing', (done) => {\n    const vm = new Vue({\n      data: {\n        test: 'abc',\n        update: 0\n      },\n      template:\n        '<div>' +\n          '<input ref=\"input\" v-model.trim=\"test\" type=\"text\">{{ update }}' +\n          '<input ref=\"blur\"/>' +\n        '</div>'\n    }).$mount()\n    document.body.appendChild(vm.$el)\n    vm.$refs.input.focus()\n    vm.$refs.input.value = ' abc '\n    triggerEvent(vm.$refs.input, 'input')\n    expect(vm.test).toBe('abc')\n    vm.update++\n    waitForUpdate(() => {\n      expect(vm.$refs.input.value).toBe(' abc ')\n      vm.$refs.blur.focus()\n      vm.update++\n    }).then(() => {\n      expect(vm.$refs.input.value).toBe('abc')\n    }).then(done)\n  })\n\n  it('multiple inputs', (done) => {\n    const spy = jasmine.createSpy()\n    const vm = new Vue({\n      data: {\n        selections: [[1, 2, 3], [4, 5]],\n        inputList: [\n          {\n            name: 'questionA',\n            data: ['a', 'b', 'c']\n          },\n          {\n            name: 'questionB',\n            data: ['1', '2']\n          }\n        ]\n      },\n      watch: {\n        selections: spy\n      },\n      template:\n        '<div>' +\n          '<div v-for=\"(inputGroup, idx) in inputList\">' +\n            '<div>' +\n              '<span v-for=\"(item, index) in inputGroup.data\">' +\n                '<input v-bind:name=\"item\" type=\"text\" v-model.number=\"selections[idx][index]\" v-bind:id=\"idx+\\'-\\'+index\"/>' +\n                '<label>{{item}}</label>' +\n              '</span>' +\n            '</div>' +\n          '</div>' +\n          '<span ref=\"rs\">{{selections}}</span>' +\n        '</div>'\n    }).$mount()\n    var inputs = vm.$el.getElementsByTagName('input')\n    inputs[1].value = 'test'\n    triggerEvent(inputs[1], 'input')\n    waitForUpdate(() => {\n      expect(spy).toHaveBeenCalled()\n      expect(vm.selections).toEqual([[1, 'test', 3], [4, 5]])\n    }).then(done)\n  })\n\n  if (isIE9) {\n    it('IE9 selectionchange', done => {\n      const vm = new Vue({\n        data: {\n          test: 'foo'\n        },\n        template: '<input v-model=\"test\">'\n      }).$mount()\n      const input = vm.$el\n      input.value = 'bar'\n      document.body.appendChild(input)\n      input.focus()\n      triggerEvent(input, 'selectionchange')\n      waitForUpdate(() => {\n        expect(vm.test).toBe('bar')\n        input.value = 'a'\n        triggerEvent(input, 'selectionchange')\n        expect(vm.test).toBe('a')\n      }).then(done)\n    })\n  }\n\n  it('compositionevents', function (done) {\n    const vm = new Vue({\n      data: {\n        test: 'foo'\n      },\n      template: '<input v-model=\"test\">'\n    }).$mount()\n    const input = vm.$el\n    triggerEvent(input, 'compositionstart')\n    input.value = 'baz'\n    // input before composition unlock should not call set\n    triggerEvent(input, 'input')\n    expect(vm.test).toBe('foo')\n    // after composition unlock it should work\n    triggerEvent(input, 'compositionend')\n    triggerEvent(input, 'input')\n    expect(vm.test).toBe('baz')\n    done()\n  })\n\n  it('warn invalid tag', () => {\n    new Vue({\n      data: {\n        test: 'foo'\n      },\n      template: '<div v-model=\"test\"></div>'\n    }).$mount()\n    expect('<div v-model=\"test\">: v-model is not supported on this element type').toHaveBeenWarned()\n  })\n\n  // #3468\n  it('should have higher priority than user v-on events', () => {\n    const spy = jasmine.createSpy()\n    const vm = new Vue({\n      data: {\n        a: 'a'\n      },\n      template: '<input v-model=\"a\" @input=\"onInput\">',\n      methods: {\n        onInput (e) {\n          spy(e.target.value)\n        }\n      }\n    }).$mount()\n    vm.$el.value = 'b'\n    triggerEvent(vm.$el, 'input')\n    expect(spy).toHaveBeenCalledWith('b')\n  })\n\n  it('warn binding to v-for alias', () => {\n    new Vue({\n      data: {\n        strings: ['hi']\n      },\n      template: `\n        <div>\n          <div v-for=\"str in strings\">\n            <input v-model=\"str\">\n          </div>\n        </div>\n      `\n    }).$mount()\n    expect('You are binding v-model directly to a v-for iteration alias').toHaveBeenWarned()\n  })\n\n  it('warn if v-model and v-bind:value conflict', () => {\n    new Vue({\n      data: {\n        test: 'foo'\n      },\n      template: '<input type=\"text\" v-model=\"test\" v-bind:value=\"test\">'\n    }).$mount()\n    expect('v-bind:value=\"test\" conflicts with v-model').toHaveBeenWarned()\n  })\n\n  it('warn if v-model and :value conflict', () => {\n    new Vue({\n      data: {\n        test: 'foo'\n      },\n      template: '<input type=\"text\" v-model=\"test\" :value=\"test\">'\n    }).$mount()\n    expect(':value=\"test\" conflicts with v-model').toHaveBeenWarned()\n  })\n\n  it('should not warn on radio, checkbox, or custom component', () => {\n    new Vue({\n      data: { test: '' },\n      components: {\n        foo: {\n          props: ['model', 'value'],\n          model: { prop: 'model', event: 'change' },\n          template: `<div/>`\n        }\n      },\n      template: `\n        <div>\n          <input type=\"checkbox\" v-model=\"test\" :value=\"test\">\n          <input type=\"radio\" v-model=\"test\" :value=\"test\">\n          <foo v-model=\"test\" :value=\"test\"/>\n        </div>\n      `\n    }).$mount()\n    expect('conflicts with v-model').not.toHaveBeenWarned()\n  })\n\n  it('should not warn on input with dynamic type binding', () => {\n    new Vue({\n      data: {\n        type: 'checkbox',\n        test: 'foo'\n      },\n      template: '<input :type=\"type\" v-model=\"test\" :value=\"test\">'\n    }).$mount()\n    expect('conflicts with v-model').not.toHaveBeenWarned()\n  })\n\n  if (!isAndroid) {\n    it('does not trigger extra input events with single compositionend', () => {\n      const spy = jasmine.createSpy()\n      const vm = new Vue({\n        data: {\n          a: 'a'\n        },\n        template: '<input v-model=\"a\" @input=\"onInput\">',\n        methods: {\n          onInput (e) {\n            spy(e.target.value)\n          }\n        }\n      }).$mount()\n      expect(spy.calls.count()).toBe(0)\n      vm.$el.value = 'b'\n      triggerEvent(vm.$el, 'input')\n      expect(spy.calls.count()).toBe(1)\n      triggerEvent(vm.$el, 'compositionend')\n      expect(spy.calls.count()).toBe(1)\n    })\n\n    it('triggers extra input on compositionstart + end', () => {\n      const spy = jasmine.createSpy()\n      const vm = new Vue({\n        data: {\n          a: 'a'\n        },\n        template: '<input v-model=\"a\" @input=\"onInput\">',\n        methods: {\n          onInput (e) {\n            spy(e.target.value)\n          }\n        }\n      }).$mount()\n      expect(spy.calls.count()).toBe(0)\n      vm.$el.value = 'b'\n      triggerEvent(vm.$el, 'input')\n      expect(spy.calls.count()).toBe(1)\n      triggerEvent(vm.$el, 'compositionstart')\n      triggerEvent(vm.$el, 'compositionend')\n      expect(spy.calls.count()).toBe(2)\n    })\n\n    // #4392\n    it('should not update value with modifiers when in focus if post-conversion values are the same', done => {\n      const vm = new Vue({\n        data: {\n          a: 1,\n          foo: false\n        },\n        template: '<div>{{ foo }}<input ref=\"input\" v-model.number=\"a\"></div>'\n      }).$mount()\n\n      document.body.appendChild(vm.$el)\n      vm.$refs.input.focus()\n      vm.$refs.input.value = '1.000'\n      vm.foo = true\n\n      waitForUpdate(() => {\n        expect(vm.$refs.input.value).toBe('1.000')\n      }).then(done)\n    })\n\n    // #6552\n    // This was original introduced due to the microtask between DOM events issue\n    // but fixed after switching to MessageChannel.\n    it('should not block input when another input listener with modifier is used', done => {\n      const vm = new Vue({\n        data: {\n          a: 'a',\n          foo: false\n        },\n        template: `\n          <div>\n            <input ref=\"input\" v-model=\"a\" @input.capture=\"onInput\">{{ a }}\n            <div v-if=\"foo\">foo</div>\n          </div>\n        `,\n        methods: {\n          onInput (e) {\n            this.foo = true\n          }\n        }\n      }).$mount()\n\n      document.body.appendChild(vm.$el)\n      vm.$refs.input.focus()\n      vm.$refs.input.value = 'b'\n      triggerEvent(vm.$refs.input, 'input')\n\n      // not using wait for update here because there will be two update cycles\n      // one caused by onInput in the first listener\n      setTimeout(() => {\n        expect(vm.a).toBe('b')\n        expect(vm.$refs.input.value).toBe('b')\n        done()\n      }, 16)\n    })\n\n    it('should create and make reactive non-existent properties', done => {\n      const vm = new Vue({\n        data: {\n          foo: {}\n        },\n        template: '<input v-model=\"foo.bar\">'\n      }).$mount()\n      expect(vm.$el.value).toBe('')\n\n      vm.$el.value = 'a'\n      triggerEvent(vm.$el, 'input')\n      expect(vm.foo.bar).toBe('a')\n      vm.foo.bar = 'b'\n      waitForUpdate(() => {\n        expect(vm.$el.value).toBe('b')\n        vm.foo = {}\n      }).then(() => {\n        expect(vm.$el.value).toBe('')\n      }).then(done)\n    })\n  }\n\n  // #7138\n  if (isIE && !isIE9) {\n    it('should not fire input on initial render of textarea with placeholder in IE10/11', done => {\n      const el = document.createElement('div')\n      document.body.appendChild(el)\n      const vm = new Vue({\n        el,\n        data: { foo: null },\n        template: `<textarea v-model=\"foo\" placeholder=\"bar\"></textarea>`\n      })\n      setTimeout(() => {\n        expect(vm.foo).toBe(null)\n        done()\n      }, 17)\n    })\n  }\n})\n"
  },
  {
    "path": "vue/test/unit/features/directives/on.spec.js",
    "content": "import Vue from 'vue'\nimport { supportsPassive } from 'core/util/env'\n\ndescribe('Directive v-on', () => {\n  let vm, spy, el\n\n  beforeEach(() => {\n    vm = null\n    spy = jasmine.createSpy()\n    el = document.createElement('div')\n    document.body.appendChild(el)\n  })\n\n  afterEach(() => {\n    if (vm) {\n      document.body.removeChild(vm.$el)\n    }\n  })\n\n  it('should bind event to a method', () => {\n    vm = new Vue({\n      el,\n      template: '<div v-on:click=\"foo\"></div>',\n      methods: { foo: spy }\n    })\n    triggerEvent(vm.$el, 'click')\n    expect(spy.calls.count()).toBe(1)\n\n    const args = spy.calls.allArgs()\n    const event = args[0] && args[0][0] || {}\n    expect(event.type).toBe('click')\n  })\n\n  it('should bind event to a inline statement', () => {\n    vm = new Vue({\n      el,\n      template: '<div v-on:click=\"foo(1,2,3,$event)\"></div>',\n      methods: { foo: spy }\n    })\n    triggerEvent(vm.$el, 'click')\n    expect(spy.calls.count()).toBe(1)\n\n    const args = spy.calls.allArgs()\n    const firstArgs = args[0]\n    expect(firstArgs.length).toBe(4)\n    expect(firstArgs[0]).toBe(1)\n    expect(firstArgs[1]).toBe(2)\n    expect(firstArgs[2]).toBe(3)\n    expect(firstArgs[3].type).toBe('click')\n  })\n\n  it('should support inline function expression', () => {\n    const spy = jasmine.createSpy()\n    vm = new Vue({\n      el,\n      template: `<div class=\"test\" @click=\"function (e) { log(e.target.className) }\"></div>`,\n      methods: {\n        log: spy\n      }\n    }).$mount()\n    triggerEvent(vm.$el, 'click')\n    expect(spy).toHaveBeenCalledWith('test')\n  })\n\n  it('should support shorthand', () => {\n    vm = new Vue({\n      el,\n      template: '<a href=\"#test\" @click.prevent=\"foo\"></a>',\n      methods: { foo: spy }\n    })\n    triggerEvent(vm.$el, 'click')\n    expect(spy.calls.count()).toBe(1)\n  })\n\n  it('should support stop propagation', () => {\n    vm = new Vue({\n      el,\n      template: `\n        <div @click.stop=\"foo\"></div>\n      `,\n      methods: { foo: spy }\n    })\n    const hash = window.location.hash\n    triggerEvent(vm.$el, 'click')\n    expect(window.location.hash).toBe(hash)\n  })\n\n  it('should support prevent default', () => {\n    vm = new Vue({\n      el,\n      template: `\n        <input type=\"checkbox\" ref=\"input\" @click.prevent=\"foo\">\n      `,\n      methods: {\n        foo ($event) {\n          spy($event.defaultPrevented)\n        }\n      }\n    })\n    vm.$refs.input.checked = false\n    triggerEvent(vm.$refs.input, 'click')\n    expect(spy).toHaveBeenCalledWith(true)\n  })\n\n  it('should support capture', () => {\n    const callOrder = []\n    vm = new Vue({\n      el,\n      template: `\n        <div @click.capture=\"foo\">\n          <div @click=\"bar\"></div>\n        </div>\n      `,\n      methods: {\n        foo () { callOrder.push(1) },\n        bar () { callOrder.push(2) }\n      }\n    })\n    triggerEvent(vm.$el.firstChild, 'click')\n    expect(callOrder.toString()).toBe('1,2')\n  })\n\n  it('should support once', () => {\n    vm = new Vue({\n      el,\n      template: `\n        <div @click.once=\"foo\">\n        </div>\n      `,\n      methods: { foo: spy }\n    })\n    triggerEvent(vm.$el, 'click')\n    expect(spy.calls.count()).toBe(1)\n    triggerEvent(vm.$el, 'click')\n    expect(spy.calls.count()).toBe(1) // should no longer trigger\n  })\n\n  // #4655\n  it('should handle .once on multiple elements properly', () => {\n    vm = new Vue({\n      el,\n      template: `\n        <div>\n          <button ref=\"one\" @click.once=\"foo\">one</button>\n          <button ref=\"two\" @click.once=\"foo\">two</button>\n        </div>\n      `,\n      methods: { foo: spy }\n    })\n    triggerEvent(vm.$refs.one, 'click')\n    expect(spy.calls.count()).toBe(1)\n    triggerEvent(vm.$refs.one, 'click')\n    expect(spy.calls.count()).toBe(1)\n    triggerEvent(vm.$refs.two, 'click')\n    expect(spy.calls.count()).toBe(2)\n    triggerEvent(vm.$refs.one, 'click')\n    triggerEvent(vm.$refs.two, 'click')\n    expect(spy.calls.count()).toBe(2)\n  })\n\n  it('should support capture and once', () => {\n    const callOrder = []\n    vm = new Vue({\n      el,\n      template: `\n        <div @click.capture.once=\"foo\">\n          <div @click=\"bar\"></div>\n        </div>\n      `,\n      methods: {\n        foo () { callOrder.push(1) },\n        bar () { callOrder.push(2) }\n      }\n    })\n    triggerEvent(vm.$el.firstChild, 'click')\n    expect(callOrder.toString()).toBe('1,2')\n    triggerEvent(vm.$el.firstChild, 'click')\n    expect(callOrder.toString()).toBe('1,2,2')\n  })\n\n  // #4846\n  it('should support once and other modifiers', () => {\n    vm = new Vue({\n      el,\n      template: `<div @click.once.self=\"foo\"><span/></div>`,\n      methods: { foo: spy }\n    })\n    triggerEvent(vm.$el.firstChild, 'click')\n    expect(spy).not.toHaveBeenCalled()\n    triggerEvent(vm.$el, 'click')\n    expect(spy).toHaveBeenCalled()\n    triggerEvent(vm.$el, 'click')\n    expect(spy.calls.count()).toBe(1)\n  })\n\n  it('should support keyCode', () => {\n    vm = new Vue({\n      el,\n      template: `<input @keyup.enter=\"foo\">`,\n      methods: { foo: spy }\n    })\n    triggerEvent(vm.$el, 'keyup', e => {\n      e.keyCode = 13\n    })\n    expect(spy).toHaveBeenCalled()\n  })\n\n  it('should support automatic key name inference', () => {\n    vm = new Vue({\n      el,\n      template: `<input @keyup.arrow-right=\"foo\">`,\n      methods: { foo: spy }\n    })\n    triggerEvent(vm.$el, 'keyup', e => {\n      e.key = 'ArrowRight'\n    })\n    expect(spy).toHaveBeenCalled()\n  })\n\n  // ctrl, shift, alt, meta\n  it('should support system modifers', () => {\n    vm = new Vue({\n      el,\n      template: `\n        <div>\n          <input ref=\"ctrl\" @keyup.ctrl=\"foo\">\n          <input ref=\"shift\" @keyup.shift=\"foo\">\n          <input ref=\"alt\" @keyup.alt=\"foo\">\n          <input ref=\"meta\" @keyup.meta=\"foo\">\n        </div>\n      `,\n      methods: { foo: spy }\n    })\n\n    triggerEvent(vm.$refs.ctrl, 'keyup')\n    expect(spy.calls.count()).toBe(0)\n    triggerEvent(vm.$refs.ctrl, 'keyup', e => { e.ctrlKey = true })\n    expect(spy.calls.count()).toBe(1)\n\n    triggerEvent(vm.$refs.shift, 'keyup')\n    expect(spy.calls.count()).toBe(1)\n    triggerEvent(vm.$refs.shift, 'keyup', e => { e.shiftKey = true })\n    expect(spy.calls.count()).toBe(2)\n\n    triggerEvent(vm.$refs.alt, 'keyup')\n    expect(spy.calls.count()).toBe(2)\n    triggerEvent(vm.$refs.alt, 'keyup', e => { e.altKey = true })\n    expect(spy.calls.count()).toBe(3)\n\n    triggerEvent(vm.$refs.meta, 'keyup')\n    expect(spy.calls.count()).toBe(3)\n    triggerEvent(vm.$refs.meta, 'keyup', e => { e.metaKey = true })\n    expect(spy.calls.count()).toBe(4)\n  })\n\n  it('should support exact modifier', () => {\n    vm = new Vue({\n      el,\n      template: `\n        <div>\n          <input ref=\"ctrl\" @keyup.exact=\"foo\">\n        </div>\n      `,\n      methods: { foo: spy }\n    })\n\n    triggerEvent(vm.$refs.ctrl, 'keyup')\n    expect(spy.calls.count()).toBe(1)\n\n    triggerEvent(vm.$refs.ctrl, 'keyup', e => {\n      e.ctrlKey = true\n    })\n    expect(spy.calls.count()).toBe(1)\n\n    // should not trigger if has other system modifiers\n    triggerEvent(vm.$refs.ctrl, 'keyup', e => {\n      e.ctrlKey = true\n      e.altKey = true\n    })\n    expect(spy.calls.count()).toBe(1)\n  })\n\n  it('should support system modifers with exact', () => {\n    vm = new Vue({\n      el,\n      template: `\n        <div>\n          <input ref=\"ctrl\" @keyup.ctrl.exact=\"foo\">\n        </div>\n      `,\n      methods: { foo: spy }\n    })\n\n    triggerEvent(vm.$refs.ctrl, 'keyup')\n    expect(spy.calls.count()).toBe(0)\n\n    triggerEvent(vm.$refs.ctrl, 'keyup', e => {\n      e.ctrlKey = true\n    })\n    expect(spy.calls.count()).toBe(1)\n\n    // should not trigger if has other system modifiers\n    triggerEvent(vm.$refs.ctrl, 'keyup', e => {\n      e.ctrlKey = true\n      e.altKey = true\n    })\n    expect(spy.calls.count()).toBe(1)\n  })\n\n  it('should support number keyCode', () => {\n    vm = new Vue({\n      el,\n      template: `<input @keyup.13=\"foo\">`,\n      methods: { foo: spy }\n    })\n    triggerEvent(vm.$el, 'keyup', e => {\n      e.keyCode = 13\n    })\n    expect(spy).toHaveBeenCalled()\n  })\n\n  it('should support mouse modifier', () => {\n    const left = 0\n    const middle = 1\n    const right = 2\n    const spyLeft = jasmine.createSpy()\n    const spyMiddle = jasmine.createSpy()\n    const spyRight = jasmine.createSpy()\n\n    vm = new Vue({\n      el,\n      template: `\n        <div>\n          <div ref=\"left\" @mousedown.left=\"foo\">left</div>\n          <div ref=\"right\" @mousedown.right=\"foo1\">right</div>\n          <div ref=\"middle\" @mousedown.middle=\"foo2\">right</div>\n        </div>\n      `,\n      methods: {\n        foo: spyLeft,\n        foo1: spyRight,\n        foo2: spyMiddle\n      }\n    })\n\n    triggerEvent(vm.$refs.left, 'mousedown', e => { e.button = right })\n    triggerEvent(vm.$refs.left, 'mousedown', e => { e.button = middle })\n    expect(spyLeft).not.toHaveBeenCalled()\n    triggerEvent(vm.$refs.left, 'mousedown', e => { e.button = left })\n    expect(spyLeft).toHaveBeenCalled()\n\n    triggerEvent(vm.$refs.right, 'mousedown', e => { e.button = left })\n    triggerEvent(vm.$refs.right, 'mousedown', e => { e.button = middle })\n    expect(spyRight).not.toHaveBeenCalled()\n    triggerEvent(vm.$refs.right, 'mousedown', e => { e.button = right })\n    expect(spyRight).toHaveBeenCalled()\n\n    triggerEvent(vm.$refs.middle, 'mousedown', e => { e.button = left })\n    triggerEvent(vm.$refs.middle, 'mousedown', e => { e.button = right })\n    expect(spyMiddle).not.toHaveBeenCalled()\n    triggerEvent(vm.$refs.middle, 'mousedown', e => { e.button = middle })\n    expect(spyMiddle).toHaveBeenCalled()\n  })\n\n  it('should support KeyboardEvent.key for built in aliases', () => {\n    vm = new Vue({\n      el,\n      template: `\n        <div>\n          <input ref=\"enter\" @keyup.enter=\"foo\">\n          <input ref=\"space\" @keyup.space=\"foo\">\n          <input ref=\"esc\" @keyup.esc=\"foo\">\n          <input ref=\"left\" @keyup.left=\"foo\">\n          <input ref=\"delete\" @keyup.delete=\"foo\">\n        </div>\n      `,\n      methods: { foo: spy }\n    })\n\n    triggerEvent(vm.$refs.enter, 'keyup', e => { e.key = 'Enter' })\n    expect(spy.calls.count()).toBe(1)\n    triggerEvent(vm.$refs.space, 'keyup', e => { e.key = ' ' })\n    expect(spy.calls.count()).toBe(2)\n    triggerEvent(vm.$refs.esc, 'keyup', e => { e.key = 'Escape' })\n    expect(spy.calls.count()).toBe(3)\n    triggerEvent(vm.$refs.left, 'keyup', e => { e.key = 'ArrowLeft' })\n    expect(spy.calls.count()).toBe(4)\n    triggerEvent(vm.$refs.delete, 'keyup', e => { e.key = 'Backspace' })\n    expect(spy.calls.count()).toBe(5)\n    triggerEvent(vm.$refs.delete, 'keyup', e => { e.key = 'Delete' })\n    expect(spy.calls.count()).toBe(6)\n  })\n\n  it('should support custom keyCode', () => {\n    Vue.config.keyCodes.test = 1\n    vm = new Vue({\n      el,\n      template: `<input @keyup.test=\"foo\">`,\n      methods: { foo: spy }\n    })\n    triggerEvent(vm.$el, 'keyup', e => {\n      e.keyCode = 1\n    })\n    expect(spy).toHaveBeenCalled()\n    Vue.config.keyCodes = Object.create(null)\n  })\n\n  it('should override build-in keyCode', () => {\n    Vue.config.keyCodes.up = [1, 87]\n    vm = new Vue({\n      el,\n      template: `<input @keyup.up=\"foo\" @keyup.down=\"foo\">`,\n      methods: { foo: spy }\n    })\n    triggerEvent(vm.$el, 'keyup', e => {\n      e.keyCode = 87\n    })\n    expect(spy).toHaveBeenCalled()\n    triggerEvent(vm.$el, 'keyup', e => {\n      e.keyCode = 1\n    })\n    expect(spy).toHaveBeenCalledTimes(2)\n    // should not affect build-in down keycode\n    triggerEvent(vm.$el, 'keyup', e => {\n      e.keyCode = 40\n    })\n    expect(spy).toHaveBeenCalledTimes(3)\n    Vue.config.keyCodes = Object.create(null)\n  })\n\n  it('should bind to a child component', () => {\n    vm = new Vue({\n      el,\n      template: '<bar @custom=\"foo\"></bar>',\n      methods: { foo: spy },\n      components: {\n        bar: {\n          template: '<span>Hello</span>'\n        }\n      }\n    })\n    vm.$children[0].$emit('custom', 'foo', 'bar')\n    expect(spy).toHaveBeenCalledWith('foo', 'bar')\n  })\n\n  it('should be able to bind native events for a child component', () => {\n    vm = new Vue({\n      el,\n      template: '<bar @click.native=\"foo\"></bar>',\n      methods: { foo: spy },\n      components: {\n        bar: {\n          template: '<span>Hello</span>'\n        }\n      }\n    })\n    vm.$children[0].$emit('click')\n    expect(spy).not.toHaveBeenCalled()\n    triggerEvent(vm.$children[0].$el, 'click')\n    expect(spy).toHaveBeenCalled()\n  })\n\n  it('.once modifier should work with child components', () => {\n    vm = new Vue({\n      el,\n      template: '<bar @custom.once=\"foo\"></bar>',\n      methods: { foo: spy },\n      components: {\n        bar: {\n          template: '<span>Hello</span>'\n        }\n      }\n    })\n    vm.$children[0].$emit('custom')\n    expect(spy.calls.count()).toBe(1)\n    vm.$children[0].$emit('custom')\n    expect(spy.calls.count()).toBe(1) // should not be called again\n  })\n\n  it('remove listener', done => {\n    const spy2 = jasmine.createSpy('remove listener')\n    vm = new Vue({\n      el,\n      methods: { foo: spy, bar: spy2 },\n      data: {\n        ok: true\n      },\n      render (h) {\n        return this.ok\n          ? h('input', { on: { click: this.foo }})\n          : h('input', { on: { input: this.bar }})\n      }\n    })\n    triggerEvent(vm.$el, 'click')\n    expect(spy.calls.count()).toBe(1)\n    expect(spy2.calls.count()).toBe(0)\n    vm.ok = false\n    waitForUpdate(() => {\n      triggerEvent(vm.$el, 'click')\n      expect(spy.calls.count()).toBe(1) // should no longer trigger\n      triggerEvent(vm.$el, 'input')\n      expect(spy2.calls.count()).toBe(1)\n    }).then(done)\n  })\n\n  it('remove capturing listener', done => {\n    const spy2 = jasmine.createSpy('remove listener')\n    vm = new Vue({\n      el,\n      methods: { foo: spy, bar: spy2, stopped (ev) { ev.stopPropagation() } },\n      data: {\n        ok: true\n      },\n      render (h) {\n        return this.ok\n          ? h('div', { on: { '!click': this.foo }}, [h('div', { on: { click: this.stopped }})])\n          : h('div', { on: { mouseOver: this.bar }}, [h('div')])\n      }\n    })\n    triggerEvent(vm.$el.firstChild, 'click')\n    expect(spy.calls.count()).toBe(1)\n    expect(spy2.calls.count()).toBe(0)\n    vm.ok = false\n    waitForUpdate(() => {\n      triggerEvent(vm.$el.firstChild, 'click')\n      expect(spy.calls.count()).toBe(1) // should no longer trigger\n      triggerEvent(vm.$el, 'mouseOver')\n      expect(spy2.calls.count()).toBe(1)\n    }).then(done)\n  })\n\n  it('remove once listener', done => {\n    const spy2 = jasmine.createSpy('remove listener')\n    vm = new Vue({\n      el,\n      methods: { foo: spy, bar: spy2 },\n      data: {\n        ok: true\n      },\n      render (h) {\n        return this.ok\n          ? h('input', { on: { '~click': this.foo }})\n          : h('input', { on: { input: this.bar }})\n      }\n    })\n    triggerEvent(vm.$el, 'click')\n    expect(spy.calls.count()).toBe(1)\n    triggerEvent(vm.$el, 'click')\n    expect(spy.calls.count()).toBe(1) // should no longer trigger\n    expect(spy2.calls.count()).toBe(0)\n    vm.ok = false\n    waitForUpdate(() => {\n      triggerEvent(vm.$el, 'click')\n      expect(spy.calls.count()).toBe(1) // should no longer trigger\n      triggerEvent(vm.$el, 'input')\n      expect(spy2.calls.count()).toBe(1)\n    }).then(done)\n  })\n\n  it('remove capturing and once listener', done => {\n    const spy2 = jasmine.createSpy('remove listener')\n    vm = new Vue({\n      el,\n      methods: { foo: spy, bar: spy2, stopped (ev) { ev.stopPropagation() } },\n      data: {\n        ok: true\n      },\n      render (h) {\n        return this.ok\n          ? h('div', { on: { '~!click': this.foo }}, [h('div', { on: { click: this.stopped }})])\n          : h('div', { on: { mouseOver: this.bar }}, [h('div')])\n      }\n    })\n    triggerEvent(vm.$el.firstChild, 'click')\n    expect(spy.calls.count()).toBe(1)\n    triggerEvent(vm.$el.firstChild, 'click')\n    expect(spy.calls.count()).toBe(1) // should no longer trigger\n    expect(spy2.calls.count()).toBe(0)\n    vm.ok = false\n    waitForUpdate(() => {\n      triggerEvent(vm.$el.firstChild, 'click')\n      expect(spy.calls.count()).toBe(1) // should no longer trigger\n      triggerEvent(vm.$el, 'mouseOver')\n      expect(spy2.calls.count()).toBe(1)\n    }).then(done)\n  })\n\n  it('remove listener on child component', done => {\n    const spy2 = jasmine.createSpy('remove listener')\n    vm = new Vue({\n      el,\n      methods: { foo: spy, bar: spy2 },\n      data: {\n        ok: true\n      },\n      components: {\n        test: {\n          template: '<div></div>'\n        }\n      },\n      render (h) {\n        return this.ok\n          ? h('test', { on: { foo: this.foo }})\n          : h('test', { on: { bar: this.bar }})\n      }\n    })\n    vm.$children[0].$emit('foo')\n    expect(spy.calls.count()).toBe(1)\n    expect(spy2.calls.count()).toBe(0)\n    vm.ok = false\n    waitForUpdate(() => {\n      vm.$children[0].$emit('foo')\n      expect(spy.calls.count()).toBe(1) // should no longer trigger\n      vm.$children[0].$emit('bar')\n      expect(spy2.calls.count()).toBe(1)\n    }).then(done)\n  })\n\n  it('warn missing handlers', () => {\n    vm = new Vue({\n      el,\n      data: { none: null },\n      template: `<div @click=\"none\"></div>`\n    })\n    expect(`Invalid handler for event \"click\": got null`).toHaveBeenWarned()\n    expect(() => {\n      triggerEvent(vm.$el, 'click')\n    }).not.toThrow()\n  })\n\n  // Github Issue #5046\n  it('should support keyboard modifier for direction keys', () => {\n    const spyLeft = jasmine.createSpy()\n    const spyRight = jasmine.createSpy()\n    const spyUp = jasmine.createSpy()\n    const spyDown = jasmine.createSpy()\n    vm = new Vue({\n      el,\n      template: `\n        <div>\n          <input ref=\"left\" @keydown.left=\"foo\"></input>\n          <input ref=\"right\" @keydown.right=\"foo1\"></input>\n          <input ref=\"up\" @keydown.up=\"foo2\"></input>\n          <input ref=\"down\" @keydown.down=\"foo3\"></input>\n        </div>\n      `,\n      methods: {\n        foo: spyLeft,\n        foo1: spyRight,\n        foo2: spyUp,\n        foo3: spyDown\n      }\n    })\n    triggerEvent(vm.$refs.left, 'keydown', e => { e.keyCode = 37 })\n    triggerEvent(vm.$refs.left, 'keydown', e => { e.keyCode = 39 })\n\n    triggerEvent(vm.$refs.right, 'keydown', e => { e.keyCode = 39 })\n    triggerEvent(vm.$refs.right, 'keydown', e => { e.keyCode = 38 })\n\n    triggerEvent(vm.$refs.up, 'keydown', e => { e.keyCode = 38 })\n    triggerEvent(vm.$refs.up, 'keydown', e => { e.keyCode = 37 })\n\n    triggerEvent(vm.$refs.down, 'keydown', e => { e.keyCode = 40 })\n    triggerEvent(vm.$refs.down, 'keydown', e => { e.keyCode = 39 })\n\n    expect(spyLeft.calls.count()).toBe(1)\n    expect(spyRight.calls.count()).toBe(1)\n    expect(spyUp.calls.count()).toBe(1)\n    expect(spyDown.calls.count()).toBe(1)\n  })\n\n  // This test case should only run when the test browser supports passive.\n  if (supportsPassive) {\n    it('should support passive', () => {\n      vm = new Vue({\n        el,\n        template: `\n          <div>\n            <input type=\"checkbox\" ref=\"normal\" @click=\"foo\"/>\n            <input type=\"checkbox\" ref=\"passive\" @click.passive=\"foo\"/>\n            <input type=\"checkbox\" ref=\"exclusive\" @click.prevent.passive/>\n          </div>\n        `,\n        methods: {\n          foo (e) {\n            e.preventDefault()\n          }\n        }\n      })\n\n      vm.$refs.normal.checked = false\n      vm.$refs.passive.checked = false\n      vm.$refs.exclusive.checked = false\n      vm.$refs.normal.click()\n      vm.$refs.passive.click()\n      vm.$refs.exclusive.click()\n      expect(vm.$refs.normal.checked).toBe(false)\n      expect(vm.$refs.passive.checked).toBe(true)\n      expect(vm.$refs.exclusive.checked).toBe(true)\n      expect('passive and prevent can\\'t be used together. Passive handler can\\'t prevent default event.').toHaveBeenWarned()\n    })\n  }\n\n  // GitHub Issues #5146\n  it('should only prevent when match keycode', () => {\n    let prevented = false\n    vm = new Vue({\n      el,\n      template: `\n        <input ref=\"input\" @keydown.enter.prevent=\"foo\">\n      `,\n      methods: {\n        foo ($event) {\n          prevented = $event.defaultPrevented\n        }\n      }\n    })\n\n    triggerEvent(vm.$refs.input, 'keydown', e => { e.keyCode = 32 })\n    expect(prevented).toBe(false)\n    triggerEvent(vm.$refs.input, 'keydown', e => { e.keyCode = 13 })\n    expect(prevented).toBe(true)\n  })\n\n  it('should transform click.right to contextmenu', () => {\n    const spy = jasmine.createSpy('click.right')\n    const vm = new Vue({\n      template: `<div @click.right=\"foo\"></div>`,\n      methods: { foo: spy }\n    }).$mount()\n\n    triggerEvent(vm.$el, 'contextmenu')\n    expect(spy).toHaveBeenCalled()\n  })\n\n  it('should transform click.middle to mouseup', () => {\n    const spy = jasmine.createSpy('click.middle')\n    const vm = new Vue({\n      template: `<div @click.middle=\"foo\"></div>`,\n      methods: { foo: spy }\n    }).$mount()\n    triggerEvent(vm.$el, 'mouseup', e => { e.button = 0 })\n    expect(spy).not.toHaveBeenCalled()\n    triggerEvent(vm.$el, 'mouseup', e => { e.button = 1 })\n    expect(spy).toHaveBeenCalled()\n  })\n\n  it('object syntax (no argument)', () => {\n    const click = jasmine.createSpy('click')\n    const mouseup = jasmine.createSpy('mouseup')\n    vm = new Vue({\n      el,\n      template: `<button v-on=\"listeners\">foo</button>`,\n      created () {\n        this.listeners = {\n          click,\n          mouseup\n        }\n      }\n    })\n\n    triggerEvent(vm.$el, 'click')\n    expect(click.calls.count()).toBe(1)\n    expect(mouseup.calls.count()).toBe(0)\n\n    triggerEvent(vm.$el, 'mouseup')\n    expect(click.calls.count()).toBe(1)\n    expect(mouseup.calls.count()).toBe(1)\n  })\n\n  it('object syntax (no argument, mixed with normal listeners)', () => {\n    const click1 = jasmine.createSpy('click1')\n    const click2 = jasmine.createSpy('click2')\n    const mouseup = jasmine.createSpy('mouseup')\n    vm = new Vue({\n      el,\n      template: `<button v-on=\"listeners\" @click=\"click2\">foo</button>`,\n      created () {\n        this.listeners = {\n          click: click1,\n          mouseup\n        }\n      },\n      methods: {\n        click2\n      }\n    })\n\n    triggerEvent(vm.$el, 'click')\n    expect(click1.calls.count()).toBe(1)\n    expect(click2.calls.count()).toBe(1)\n    expect(mouseup.calls.count()).toBe(0)\n\n    triggerEvent(vm.$el, 'mouseup')\n    expect(click1.calls.count()).toBe(1)\n    expect(click2.calls.count()).toBe(1)\n    expect(mouseup.calls.count()).toBe(1)\n  })\n\n  it('object syntax (usage in HOC, mixed with native listeners)', () => {\n    const click = jasmine.createSpy('click')\n    const mouseup = jasmine.createSpy('mouseup')\n    const mousedown = jasmine.createSpy('mousedown')\n\n    vm = new Vue({\n      el,\n      template: `\n        <foo-button\n          @click=\"click\"\n          @mousedown=\"mousedown\"\n          @mouseup.native=\"mouseup\">\n        </foo-button>\n      `,\n      methods: {\n        click,\n        mouseup,\n        mousedown\n      },\n      components: {\n        fooButton: {\n          template: `\n            <button v-on=\"$listeners\"></button>\n          `\n        }\n      }\n    })\n\n    triggerEvent(vm.$el, 'click')\n    expect(click.calls.count()).toBe(1)\n    expect(mouseup.calls.count()).toBe(0)\n    expect(mousedown.calls.count()).toBe(0)\n\n    triggerEvent(vm.$el, 'mouseup')\n    expect(click.calls.count()).toBe(1)\n    expect(mouseup.calls.count()).toBe(1)\n    expect(mousedown.calls.count()).toBe(0)\n\n    triggerEvent(vm.$el, 'mousedown')\n    expect(click.calls.count()).toBe(1)\n    expect(mouseup.calls.count()).toBe(1)\n    expect(mousedown.calls.count()).toBe(1)\n  })\n\n  // #6805 (v-on=\"object\" bind order problem)\n  it('object syntax (no argument): should fire after high-priority listeners', done => {\n    const MyCheckbox = {\n      template: '<input type=\"checkbox\" v-model=\"model\" v-on=\"$listeners\">',\n      props: {\n        value: false\n      },\n      computed: {\n        model: {\n          get () {\n            return this.value\n          },\n          set (val) {\n            this.$emit('input', val)\n          }\n        }\n      }\n    }\n\n    vm = new Vue({\n      el,\n      template: `\n        <div>\n          <my-checkbox v-model=\"check\" @change=\"change\"></my-checkbox>\n        </div>\n      `,\n      components: { MyCheckbox },\n      data: {\n        check: false\n      },\n      methods: {\n        change () {\n          expect(this.check).toBe(true)\n          done()\n        }\n      }\n    })\n\n    vm.$el.querySelector('input').click()\n  })\n\n  it('warn object syntax with modifier', () => {\n    new Vue({\n      template: `<button v-on.self=\"{}\"></button>`\n    }).$mount()\n    expect(`v-on without argument does not support modifiers`).toHaveBeenWarned()\n  })\n\n  it('warn object syntax with non-object value', () => {\n    new Vue({\n      template: `<button v-on=\"123\"></button>`\n    }).$mount()\n    expect(`v-on without argument expects an Object value`).toHaveBeenWarned()\n  })\n})\n"
  },
  {
    "path": "vue/test/unit/features/directives/once.spec.js",
    "content": "import Vue from 'vue'\n\ndescribe('Directive v-once', () => {\n  it('should not rerender component', done => {\n    const vm = new Vue({\n      template: '<div v-once>{{ a }}</div>',\n      data: { a: 'hello' }\n    }).$mount()\n    expect(vm.$el.innerHTML).toBe('hello')\n    vm.a = 'world'\n    waitForUpdate(() => {\n      expect(vm.$el.innerHTML).toBe('hello')\n    }).then(done)\n  })\n\n  it('should not rerender self and child component', done => {\n    const vm = new Vue({\n      template: `\n        <div v-once>\n          <span>{{ a }}</span>\n          <item :b=\"a\"></item>\n        </div>`,\n      data: { a: 'hello' },\n      components: {\n        item: {\n          template: '<div>{{ b }}</div>',\n          props: ['b']\n        }\n      }\n    }).$mount()\n    expect(vm.$children.length).toBe(1)\n    expect(vm.$el.innerHTML)\n      .toBe('<span>hello</span> <div>hello</div>')\n    vm.a = 'world'\n    waitForUpdate(() => {\n      expect(vm.$el.innerHTML)\n        .toBe('<span>hello</span> <div>hello</div>')\n    }).then(done)\n  })\n\n  it('should rerender parent but not self', done => {\n    const vm = new Vue({\n      template: `\n        <div>\n          <span>{{ a }}</span>\n          <item v-once :b=\"a\"></item>\n        </div>`,\n      data: { a: 'hello' },\n      components: {\n        item: {\n          template: '<div>{{ b }}</div>',\n          props: ['b']\n        }\n      }\n    }).$mount()\n    expect(vm.$children.length).toBe(1)\n    expect(vm.$el.innerHTML)\n      .toBe('<span>hello</span> <div>hello</div>')\n    vm.a = 'world'\n    waitForUpdate(() => {\n      expect(vm.$el.innerHTML)\n        .toBe('<span>world</span> <div>hello</div>')\n    }).then(done)\n  })\n\n  it('should not rerender static sub nodes', done => {\n    const vm = new Vue({\n      template: `\n        <div>\n          <span v-once>{{ a }}</span>\n          <item :b=\"a\"></item>\n          <span>{{ suffix }}</span>\n        </div>`,\n      data: {\n        a: 'hello',\n        suffix: '?'\n      },\n      components: {\n        item: {\n          template: '<div>{{ b }}</div>',\n          props: ['b']\n        }\n      }\n    }).$mount()\n    expect(vm.$el.innerHTML)\n      .toBe('<span>hello</span> <div>hello</div> <span>?</span>')\n    vm.a = 'world'\n    waitForUpdate(() => {\n      expect(vm.$el.innerHTML)\n        .toBe('<span>hello</span> <div>world</div> <span>?</span>')\n      vm.suffix = '!'\n    }).then(() => {\n      expect(vm.$el.innerHTML)\n        .toBe('<span>hello</span> <div>world</div> <span>!</span>')\n    }).then(done)\n  })\n\n  it('should work with v-if', done => {\n    const vm = new Vue({\n      data: {\n        tester: true,\n        yes: 'y',\n        no: 'n'\n      },\n      template: `\n        <div>\n          <div v-if=\"tester\">{{ yes }}</div>\n          <div v-else>{{ no }}</div>\n          <div v-if=\"tester\" v-once>{{ yes }}</div>\n          <div v-else>{{ no }}</div>\n          <div v-if=\"tester\">{{ yes }}</div>\n          <div v-else v-once>{{ no }}</div>\n          <div v-if=\"tester\" v-once>{{ yes }}</div>\n          <div v-else v-once>{{ no }}</div>\n        </div>\n      `\n    }).$mount()\n    expectTextContent(vm, 'yyyy')\n    vm.yes = 'yes'\n    waitForUpdate(() => {\n      expectTextContent(vm, 'yesyyesy')\n      vm.tester = false\n    }).then(() => {\n      expectTextContent(vm, 'nnnn')\n      vm.no = 'no'\n    }).then(() => {\n      expectTextContent(vm, 'nononn')\n    }).then(done)\n  })\n\n  it('should work with v-for', done => {\n    const vm = new Vue({\n      data: {\n        list: [1, 2, 3]\n      },\n      template: `<div><div v-for=\"i in list\" v-once>{{i}}</div></div>`\n    }).$mount()\n    expect(vm.$el.textContent).toBe('123')\n    vm.list.reverse()\n    waitForUpdate(() => {\n      expect(vm.$el.textContent).toBe('123')\n    }).then(done)\n  })\n\n  it('should work inside v-for', done => {\n    const vm = new Vue({\n      data: {\n        list: [\n          { id: 0, text: 'a' },\n          { id: 1, text: 'b' },\n          { id: 2, text: 'c' }\n        ]\n      },\n      template: `\n        <div>\n          <div v-for=\"i in list\" :key=\"i.id\">\n            <div>\n              <span v-once>{{ i.text }}</span><span>{{ i.text }}</span>\n            </div>\n          </div>\n        </div>\n      `\n    }).$mount()\n\n    expect(vm.$el.textContent).toBe('aabbcc')\n\n    vm.list[0].text = 'd'\n    waitForUpdate(() => {\n      expect(vm.$el.textContent).toBe('adbbcc')\n      vm.list[1].text = 'e'\n    }).then(() => {\n      expect(vm.$el.textContent).toBe('adbecc')\n      vm.list.reverse()\n    }).then(() => {\n      expect(vm.$el.textContent).toBe('ccbead')\n    }).then(done)\n  })\n\n  it('should work inside v-for with v-if', done => {\n    const vm = new Vue({\n      data: {\n        list: [\n          { id: 0, text: 'a', tester: true, truthy: 'y' }\n        ]\n      },\n      template: `\n        <div>\n          <div v-for=\"i in list\" :key=\"i.id\">\n              <span v-if=\"i.tester\" v-once>{{ i.truthy }}</span>\n              <span v-else v-once>{{ i.text }}</span>\n              <span v-if=\"i.tester\" v-once>{{ i.truthy }}</span>\n              <span v-else>{{ i.text }}</span>\n              <span v-if=\"i.tester\">{{ i.truthy }}</span>\n              <span v-else v-once>{{ i.text }}</span>\n              <span v-if=\"i.tester\">{{ i.truthy }}</span>\n              <span v-else>{{ i.text }}</span>\n          </div>\n        </div>\n      `\n    }).$mount()\n\n    expectTextContent(vm, 'yyyy')\n\n    vm.list[0].truthy = 'yy'\n    waitForUpdate(() => {\n      expectTextContent(vm, 'yyyyyy')\n      vm.list[0].tester = false\n    }).then(() => {\n      expectTextContent(vm, 'aaaa')\n      vm.list[0].text = 'nn'\n    }).then(() => {\n      expectTextContent(vm, 'annann')\n    }).then(done)\n  })\n\n  it('should work inside v-for with nested v-else', done => {\n    const vm = new Vue({\n      data: {\n        list: [{ id: 0, text: 'a', tester: true, truthy: 'y' }]\n      },\n      template: `\n        <div v-if=\"0\"></div>\n        <div v-else>\n          <div v-for=\"i in list\" :key=\"i.id\">\n            <span v-if=\"i.tester\" v-once>{{ i.truthy }}</span>\n            <span v-else v-once>{{ i.text }}</span>\n          </div>\n        </div>\n      `\n    }).$mount()\n\n    expectTextContent(vm, 'y')\n    vm.list[0].truthy = 'yy'\n    waitForUpdate(() => {\n      expectTextContent(vm, 'y')\n      vm.list[0].tester = false\n    }).then(() => {\n      expectTextContent(vm, 'a')\n      vm.list[0].text = 'nn'\n    }).then(() => {\n      expectTextContent(vm, 'a')\n    }).then(done)\n  })\n\n  it('should work inside v-for with nested v-else-if and v-else', done => {\n    const vm = new Vue({\n      data: {\n        tester: false,\n        list: [{ id: 0, text: 'a', tester: true, truthy: 'y' }]\n      },\n      template: `\n        <div v-if=\"0\"></div>\n        <div v-else-if=\"tester\">\n          <div v-for=\"i in list\" :key=\"i.id\">\n            <span v-if=\"i.tester\" v-once>{{ i.truthy }}</span>\n            <span v-else-if=\"tester\" v-once>{{ i.text }}elseif</span>\n            <span v-else v-once>{{ i.text }}</span>\n          </div>\n        </div>\n        <div v-else>\n          <div v-for=\"i in list\" :key=\"i.id\">\n            <span v-if=\"i.tester\" v-once>{{ i.truthy }}</span>\n            <span v-else-if=\"tester\">{{ i.text }}elseif</span>\n            <span v-else v-once>{{ i.text }}</span>\n          </div>\n        </div>\n      `\n    }).$mount()\n\n    expectTextContent(vm, 'y')\n    vm.list[0].truthy = 'yy'\n    waitForUpdate(() => {\n      expectTextContent(vm, 'y')\n      vm.list[0].tester = false\n    }).then(() => {\n      expectTextContent(vm, 'a')\n      vm.list[0].text = 'nn'\n    }).then(() => {\n      expectTextContent(vm, 'a')\n      vm.tester = true\n    }).then(() => {\n      expectTextContent(vm, 'nnelseif')\n      vm.list[0].text = 'xx'\n    }).then(() => {\n      expectTextContent(vm, 'nnelseif')\n      vm.list[0].tester = true\n    }).then(() => {\n      expectTextContent(vm, 'yy')\n      vm.list[0].truthy = 'nn'\n    }).then(() => {\n      expectTextContent(vm, 'yy')\n    }).then(done)\n  })\n\n  it('should warn inside non-keyed v-for', () => {\n    const vm = new Vue({\n      data: {\n        list: [\n          { id: 0, text: 'a' },\n          { id: 1, text: 'b' },\n          { id: 2, text: 'c' }\n        ]\n      },\n      template: `\n        <div>\n          <div v-for=\"i in list\">\n            <span v-once>{{ i.text }}</span><span>{{ i.text }}</span>\n          </div>\n        </div>\n      `\n    }).$mount()\n\n    expect(vm.$el.textContent).toBe('aabbcc')\n    expect(`v-once can only be used inside v-for that is keyed.`).toHaveBeenWarned()\n  })\n\n  // #4288\n  it('should inherit child reference for v-once', done => {\n    const vm = new Vue({\n      template: `<div>{{a}}<test v-if=\"ok\" v-once></test></div>`,\n      data: {\n        a: 0,\n        ok: true\n      },\n      components: {\n        test: {\n          template: '<div>foo</div>'\n        }\n      }\n    }).$mount()\n    vm.a++ // first update to force a patch\n    waitForUpdate(() => {\n      expect(vm.$el.textContent).toBe('1foo')\n    }).then(() => {\n      vm.ok = false // teardown component with v-once\n    }).then(done) // should not throw\n  })\n\n  // #6826\n  it('should render different component instances properly', done => {\n    const vm = new Vue({\n      components: {\n        foo: {\n          props: ['name'],\n          template: '<div v-once>{{ name }}</div>'\n        }\n      },\n      template: `\n        <div>\n          <foo name=\"a\" v-once></foo>\n          <foo name=\"b\" v-once></foo>\n        </div>\n      `\n    }).$mount()\n    waitForUpdate(() => {\n      expect(vm.$el.children[0].innerHTML).toBe('a')\n      expect(vm.$el.children[1].innerHTML).toBe('b')\n    }).then(done)\n  })\n})\n\nfunction expectTextContent (vm, text) {\n  expect(vm.$el.textContent.replace(/\\s+/g, '')).toBe(text)\n}\n"
  },
  {
    "path": "vue/test/unit/features/directives/pre.spec.js",
    "content": "import Vue from 'vue'\n\ndescribe('Directive v-pre', function () {\n  it('should not compile inner content', function () {\n    const vm = new Vue({\n      template: `<div>\n        <div v-pre>{{ a }}</div>\n        <div>{{ a }}</div>\n        <div v-pre>\n          <component></component>\n        </div>\n      </div>`,\n      data: {\n        a: 123\n      }\n    })\n    vm.$mount()\n    expect(vm.$el.firstChild.textContent).toBe('{{ a }}')\n    expect(vm.$el.children[1].textContent).toBe('123')\n    expect(vm.$el.lastChild.innerHTML).toBe('<component></component>')\n  })\n\n  it('should not compile on root node', function () {\n    const vm = new Vue({\n      template: '<div v-pre>{{ a }}</div>',\n      replace: true,\n      data: {\n        a: 123\n      }\n    })\n    vm.$mount()\n    expect(vm.$el.firstChild.textContent).toBe('{{ a }}')\n  })\n})\n"
  },
  {
    "path": "vue/test/unit/features/directives/show.spec.js",
    "content": "import Vue from 'vue'\n\ndescribe('Directive v-show', () => {\n  it('should check show value is truthy', () => {\n    const vm = new Vue({\n      template: '<div><span v-show=\"foo\">hello</span></div>',\n      data: { foo: true }\n    }).$mount()\n    expect(vm.$el.firstChild.style.display).toBe('')\n  })\n\n  it('should check show value is falsy', () => {\n    const vm = new Vue({\n      template: '<div><span v-show=\"foo\">hello</span></div>',\n      data: { foo: false }\n    }).$mount()\n    expect(vm.$el.firstChild.style.display).toBe('none')\n  })\n\n  it('should update show value changed', done => {\n    const vm = new Vue({\n      template: '<div><span v-show=\"foo\">hello</span></div>',\n      data: { foo: true }\n    }).$mount()\n    expect(vm.$el.firstChild.style.display).toBe('')\n    vm.foo = false\n    waitForUpdate(() => {\n      expect(vm.$el.firstChild.style.display).toBe('none')\n      vm.foo = {}\n    }).then(() => {\n      expect(vm.$el.firstChild.style.display).toBe('')\n      vm.foo = 0\n    }).then(() => {\n      expect(vm.$el.firstChild.style.display).toBe('none')\n      vm.foo = []\n    }).then(() => {\n      expect(vm.$el.firstChild.style.display).toBe('')\n      vm.foo = null\n    }).then(() => {\n      expect(vm.$el.firstChild.style.display).toBe('none')\n      vm.foo = '0'\n    }).then(() => {\n      expect(vm.$el.firstChild.style.display).toBe('')\n      vm.foo = undefined\n    }).then(() => {\n      expect(vm.$el.firstChild.style.display).toBe('none')\n      vm.foo = 1\n    }).then(() => {\n      expect(vm.$el.firstChild.style.display).toBe('')\n    }).then(done)\n  })\n\n  it('should respect display value in style attribute', done => {\n    const vm = new Vue({\n      template: '<div><span v-show=\"foo\" style=\"display:block\">hello</span></div>',\n      data: { foo: true }\n    }).$mount()\n    expect(vm.$el.firstChild.style.display).toBe('block')\n    vm.foo = false\n    waitForUpdate(() => {\n      expect(vm.$el.firstChild.style.display).toBe('none')\n      vm.foo = true\n    }).then(() => {\n      expect(vm.$el.firstChild.style.display).toBe('block')\n    }).then(done)\n  })\n\n  it('should support unbind when reused', done => {\n    const vm = new Vue({\n      template:\n        '<div v-if=\"tester\"><span v-show=\"false\"></span></div>' +\n        '<div v-else><span @click=\"tester=!tester\">show</span></div>',\n      data: { tester: true }\n    }).$mount()\n    expect(vm.$el.firstChild.style.display).toBe('none')\n    vm.tester = false\n    waitForUpdate(() => {\n      expect(vm.$el.firstChild.style.display).toBe('')\n      vm.tester = true\n    }).then(() => {\n      expect(vm.$el.firstChild.style.display).toBe('none')\n    }).then(done)\n  })\n})\n"
  },
  {
    "path": "vue/test/unit/features/directives/static-style-parser.spec.js",
    "content": "import { parseStyleText } from 'web/util/style'\nconst base64ImgUrl = 'url(\"data:image/webp;base64,UklGRkoAAABXRUJQVlA4WAoAAAAQAAAAAAAAAAAAQUxQSAwAAAARBxAR/Q9ERP8DAABWUDggGAAAABQBAJ0BKgEAAQAAAP4AAA3AAP7mtQAAAA==\")'\nconst logoUrl = 'url(https://vuejs.org/images/logo.png)'\n\nit('should parse normal static style', () => {\n  const staticStyle = `font-size: 12px;background: ${logoUrl};color:red`\n  const res = parseStyleText(staticStyle)\n  expect(res.background).toBe(logoUrl)\n  expect(res.color).toBe('red')\n  expect(res['font-size']).toBe('12px')\n})\n\nit('should parse base64 background', () => {\n  const staticStyle = `background: ${base64ImgUrl}`\n  const res = parseStyleText(staticStyle)\n  expect(res.background).toBe(base64ImgUrl)\n})\n\nit('should parse multiple background images ', () => {\n  let staticStyle = `background: ${logoUrl}, ${logoUrl};`\n  let res = parseStyleText(staticStyle)\n  expect(res.background).toBe(`${logoUrl}, ${logoUrl}`)\n\n  staticStyle = `background: ${base64ImgUrl}, ${base64ImgUrl}`\n  res = parseStyleText(staticStyle)\n  expect(res.background).toBe(`${base64ImgUrl}, ${base64ImgUrl}`)\n})\n\nit('should parse other images ', () => {\n  let staticStyle = `shape-outside: ${logoUrl}`\n  let res = parseStyleText(staticStyle)\n  expect(res['shape-outside']).toBe(logoUrl)\n\n  staticStyle = `list-style-image: ${logoUrl}`\n  res = parseStyleText(staticStyle)\n  expect(res['list-style-image']).toBe(logoUrl)\n\n  staticStyle = `border-image: ${logoUrl} 30 30 repeat`\n  res = parseStyleText(staticStyle)\n  expect(res['border-image']).toBe(`${logoUrl} 30 30 repeat`)\n})\n"
  },
  {
    "path": "vue/test/unit/features/directives/style.spec.js",
    "content": "import Vue from 'vue'\n\nfunction checkPrefixedProp (prop) {\n  var el = document.createElement('div')\n  var upper = prop.charAt(0).toUpperCase() + prop.slice(1)\n  if (!(prop in el.style)) {\n    var prefixes = ['Webkit', 'Moz', 'ms']\n    var i = prefixes.length\n    while (i--) {\n      if ((prefixes[i] + upper) in el.style) {\n        prop = prefixes[i] + upper\n      }\n    }\n  }\n  return prop\n}\n\ndescribe('Directive v-bind:style', () => {\n  let vm\n\n  beforeEach(() => {\n    vm = new Vue({\n      template: '<div :style=\"styles\"></div>',\n      data () {\n        return {\n          styles: {},\n          fontSize: 16\n        }\n      }\n    }).$mount()\n  })\n\n  it('string', done => {\n    vm.styles = 'color:red;'\n    waitForUpdate(() => {\n      expect(vm.$el.style.cssText.replace(/\\s/g, '')).toBe('color:red;')\n    }).then(done)\n  })\n\n  it('falsy number', done => {\n    vm.styles = { opacity: 0 }\n    waitForUpdate(() => {\n      expect(vm.$el.style.opacity).toBe('0')\n    }).then(done)\n  })\n\n  it('plain object', done => {\n    vm.styles = { color: 'red' }\n    waitForUpdate(() => {\n      expect(vm.$el.style.cssText.replace(/\\s/g, '')).toBe('color:red;')\n    }).then(done)\n  })\n\n  it('camelCase', done => {\n    vm.styles = { marginRight: '10px' }\n    waitForUpdate(() => {\n      expect(vm.$el.style.marginRight).toBe('10px')\n    }).then(done)\n  })\n\n  it('remove if falsy value', done => {\n    vm.$el.style.color = 'red'\n    waitForUpdate(() => {\n      vm.styles = { color: null }\n    }).then(() => {\n      expect(vm.$el.style.color).toBe('')\n    }).then(done)\n  })\n\n  it('ignore unsupported property', done => {\n    vm.styles = { foo: 'bar' }\n    waitForUpdate(() => {\n      expect(vm.$el.style.foo).not.toBe('bar')\n    }).then(done)\n  })\n\n  it('auto prefix', done => {\n    const prop = checkPrefixedProp('transform')\n    const val = 'scale(0.5)'\n    vm.styles = { transform: val }\n    waitForUpdate(() => {\n      expect(vm.$el.style[prop]).toBe(val)\n    }).then(done)\n  })\n\n  it('auto-prefixed style value as array', done => {\n    vm.styles = { display: ['-webkit-box', '-ms-flexbox', 'flex'] }\n    const testEl = document.createElement('div')\n    vm.styles.display.forEach(value => {\n      testEl.style.display = value\n    })\n    waitForUpdate(() => {\n      expect(vm.$el.style.display).toBe(testEl.style.display)\n    }).then(done)\n  })\n\n  it('!important', done => {\n    vm.styles = { display: 'block !important' }\n    waitForUpdate(() => {\n      expect(vm.$el.style.getPropertyPriority('display')).toBe('important')\n    }).then(done)\n  })\n\n  it('object with multiple entries', done => {\n    vm.$el.style.color = 'red'\n    vm.styles = {\n      marginLeft: '10px',\n      marginRight: '15px'\n    }\n    waitForUpdate(() => {\n      expect(vm.$el.style.getPropertyValue('color')).toBe('red')\n      expect(vm.$el.style.getPropertyValue('margin-left')).toBe('10px')\n      expect(vm.$el.style.getPropertyValue('margin-right')).toBe('15px')\n      vm.styles = {\n        color: 'blue',\n        padding: null\n      }\n    }).then(() => {\n      expect(vm.$el.style.getPropertyValue('color')).toBe('blue')\n      expect(vm.$el.style.getPropertyValue('padding')).toBeFalsy()\n      expect(vm.$el.style.getPropertyValue('margin-left')).toBeFalsy()\n      expect(vm.$el.style.getPropertyValue('margin-right')).toBeFalsy()\n      // handle falsy value\n      vm.styles = null\n    }).then(() => {\n      expect(vm.$el.style.getPropertyValue('color')).toBeFalsy()\n      expect(vm.$el.style.getPropertyValue('padding')).toBeFalsy()\n      expect(vm.$el.style.getPropertyValue('margin-left')).toBeFalsy()\n      expect(vm.$el.style.getPropertyValue('margin-right')).toBeFalsy()\n    }).then(done)\n  })\n\n  it('array of objects', done => {\n    vm.$el.style.padding = '10px'\n    vm.styles = [{ color: 'red' }, { marginRight: '20px' }]\n\n    waitForUpdate(() => {\n      expect(vm.$el.style.getPropertyValue('color')).toBe('red')\n      expect(vm.$el.style.getPropertyValue('margin-right')).toBe('20px')\n      expect(vm.$el.style.getPropertyValue('padding')).toBe('10px')\n      vm.styles = [{ color: 'blue' }, { padding: null }]\n    }).then(() => {\n      expect(vm.$el.style.getPropertyValue('color')).toBe('blue')\n      expect(vm.$el.style.getPropertyValue('margin-right')).toBeFalsy()\n      expect(vm.$el.style.getPropertyValue('padding')).toBeFalsy()\n    }).then(done)\n  })\n\n  it('updates objects deeply', done => {\n    vm.styles = { display: 'none' }\n    waitForUpdate(() => {\n      expect(vm.$el.style.display).toBe('none')\n      vm.styles.display = 'block'\n    }).then(() => {\n      expect(vm.$el.style.display).toBe('block')\n    }).then(done)\n  })\n\n  it('background size with only one value', done => {\n    vm.styles = { backgroundSize: '100%' }\n    waitForUpdate(() => {\n      expect(vm.$el.style.cssText.replace(/\\s/g, '')).toMatch(/background-size:100%(auto)?;/)\n    }).then(done)\n  })\n\n  it('should work with interpolation', done => {\n    vm.styles = { fontSize: `${vm.fontSize}px` }\n    waitForUpdate(() => {\n      expect(vm.$el.style.fontSize).toBe('16px')\n    }).then(done)\n  })\n\n  const supportCssVariable = () => {\n    const el = document.createElement('div')\n    el.style.setProperty('--color', 'red')\n    return el.style.getPropertyValue('--color') === 'red'\n  }\n\n  if (supportCssVariable()) {\n    it('CSS variables', done => {\n      vm.styles = { '--color': 'red' }\n      waitForUpdate(() => {\n        expect(vm.$el.style.getPropertyValue('--color')).toBe('red')\n      }).then(done)\n    })\n  }\n\n  it('should merge static style with binding style', () => {\n    const vm = new Vue({\n      template: '<div style=\"background: url(https://vuejs.org/images/logo.png);color: blue\" :style=\"test\"></div>',\n      data: {\n        test: { color: 'red', fontSize: '12px' }\n      }\n    }).$mount()\n    const style = vm.$el.style\n    expect(style.getPropertyValue('background-image')).toMatch('https://vuejs.org/images/logo.png')\n    expect(style.getPropertyValue('color')).toBe('red')\n    expect(style.getPropertyValue('font-size')).toBe('12px')\n  })\n\n  it('should merge between parent and child', done => {\n    const vm = new Vue({\n      template: '<child style=\"text-align: left;margin-right:20px\" :style=\"test\"></child>',\n      data: {\n        test: { color: 'red', fontSize: '12px' }\n      },\n      components: {\n        child: {\n          template: '<div style=\"margin-right:10px;\" :style=\"{marginLeft: marginLeft}\"></div>',\n          data: () => ({ marginLeft: '16px' })\n        }\n      }\n    }).$mount()\n    const style = vm.$el.style\n    const child = vm.$children[0]\n    const css = style.cssText.replace(/\\s/g, '')\n    expect(css).toContain('margin-right:20px;')\n    expect(css).toContain('margin-left:16px;')\n    expect(css).toContain('text-align:left;')\n    expect(css).toContain('color:red;')\n    expect(css).toContain('font-size:12px;')\n    expect(style.color).toBe('red')\n    expect(style.marginRight).toBe('20px')\n    vm.test.color = 'blue'\n    waitForUpdate(() => {\n      expect(style.color).toBe('blue')\n      child.marginLeft = '30px'\n    }).then(() => {\n      expect(style.marginLeft).toBe('30px')\n      child.fontSize = '30px'\n    }).then(() => {\n      expect(style.fontSize).toBe('12px')\n    }).then(done)\n  })\n\n  it('should not pass to child root element', () => {\n    const vm = new Vue({\n      template: '<child :style=\"test\"></child>',\n      data: {\n        test: { color: 'red', fontSize: '12px' }\n      },\n      components: {\n        child: {\n          template: '<div><nested ref=\"nested\" style=\"color: blue;text-align:left\"></nested></div>',\n          components: {\n            nested: {\n              template: '<div></div>'\n            }\n          }\n        }\n      }\n    }).$mount()\n    const style = vm.$el.style\n    expect(style.color).toBe('red')\n    expect(style.textAlign).toBe('')\n    expect(style.fontSize).toBe('12px')\n    expect(vm.$children[0].$refs.nested.$el.style.color).toBe('blue')\n  })\n\n  it('should merge between nested components', (done) => {\n    const vm = new Vue({\n      template: '<child :style=\"test\"></child>',\n      data: {\n        test: { color: 'red', fontSize: '12px' }\n      },\n      components: {\n        child: {\n          template: '<nested style=\"color: blue;text-align:left\"></nested>',\n          components: {\n            nested: {\n              template: '<div style=\"margin-left: 12px;\" :style=\"nestedStyle\"></div>',\n              data: () => ({ nestedStyle: { marginLeft: '30px' }})\n            }\n          }\n        }\n      }\n    }).$mount()\n    const style = vm.$el.style\n    const child = vm.$children[0].$children[0]\n    expect(style.color).toBe('red')\n    expect(style.marginLeft).toBe('30px')\n    expect(style.textAlign).toBe('left')\n    expect(style.fontSize).toBe('12px')\n    vm.test.color = 'yellow'\n    waitForUpdate(() => {\n      child.nestedStyle.marginLeft = '60px'\n    }).then(() => {\n      expect(style.marginLeft).toBe('60px')\n      child.nestedStyle = {\n        fontSize: '14px',\n        marginLeft: '40px'\n      }\n    }).then(() => {\n      expect(style.fontSize).toBe('12px')\n      expect(style.marginLeft).toBe('40px')\n    }).then(done)\n  })\n\n  it('should not merge for different adjacent elements', (done) => {\n    const vm = new Vue({\n      template:\n        '<div>' +\n          '<section style=\"color: blue\" :style=\"style\" v-if=\"!bool\"></section>' +\n          '<div></div>' +\n          '<section style=\"margin-top: 12px\" v-if=\"bool\"></section>' +\n        '</div>',\n      data: {\n        bool: false,\n        style: {\n          fontSize: '12px'\n        }\n      }\n    }).$mount()\n    const style = vm.$el.children[0].style\n    expect(style.fontSize).toBe('12px')\n    expect(style.color).toBe('blue')\n    waitForUpdate(() => {\n      vm.bool = true\n    }).then(() => {\n      expect(style.color).toBe('')\n      expect(style.fontSize).toBe('')\n      expect(style.marginTop).toBe('12px')\n    }).then(done)\n  })\n\n  it('should not merge for v-if, v-else-if and v-else elements', (done) => {\n    const vm = new Vue({\n      template:\n        '<div>' +\n          '<section style=\"color: blue\" :style=\"style\" v-if=\"foo\"></section>' +\n          '<section style=\"margin-top: 12px\" v-else-if=\"bar\"></section>' +\n          '<section style=\"margin-bottom: 24px\" v-else></section>' +\n          '<div></div>' +\n        '</div>',\n      data: {\n        foo: true,\n        bar: false,\n        style: {\n          fontSize: '12px'\n        }\n      }\n    }).$mount()\n    const style = vm.$el.children[0].style\n    expect(style.fontSize).toBe('12px')\n    expect(style.color).toBe('blue')\n    waitForUpdate(() => {\n      vm.foo = false\n    }).then(() => {\n      expect(style.color).toBe('')\n      expect(style.fontSize).toBe('')\n      expect(style.marginBottom).toBe('24px')\n      vm.bar = true\n    }).then(() => {\n      expect(style.color).toBe('')\n      expect(style.fontSize).toBe('')\n      expect(style.marginBottom).toBe('')\n      expect(style.marginTop).toBe('12px')\n    }).then(done)\n  })\n\n  // #5318\n  it('should work for elements passed down as a slot', done => {\n    const vm = new Vue({\n      template: `<test><div :style=\"style\"/></test>`,\n      data: {\n        style: { color: 'red' }\n      },\n      components: {\n        test: {\n          template: `<div><slot/></div>`\n        }\n      }\n    }).$mount()\n\n    expect(vm.$el.children[0].style.color).toBe('red')\n    vm.style.color = 'green'\n    waitForUpdate(() => {\n      expect(vm.$el.children[0].style.color).toBe('green')\n    }).then(done)\n  })\n})\n"
  },
  {
    "path": "vue/test/unit/features/directives/text.spec.js",
    "content": "import Vue from 'vue'\n\ndescribe('Directive v-text', () => {\n  it('should render text', () => {\n    const vm = new Vue({\n      template: '<div v-text=\"a\"></div>',\n      data: { a: 'hello' }\n    }).$mount()\n    expect(vm.$el.innerHTML).toBe('hello')\n  })\n\n  it('should encode html entities', () => {\n    const vm = new Vue({\n      template: '<div v-text=\"a\"></div>',\n      data: { a: '<foo>' }\n    }).$mount()\n    expect(vm.$el.innerHTML).toBe('&lt;foo&gt;')\n  })\n\n  it('should support all value types', done => {\n    const vm = new Vue({\n      template: '<div v-text=\"a\"></div>',\n      data: { a: false }\n    }).$mount()\n    waitForUpdate(() => {\n      expect(vm.$el.innerHTML).toBe('false')\n      vm.a = []\n    }).then(() => {\n      expect(vm.$el.innerHTML).toBe('[]')\n      vm.a = {}\n    }).then(() => {\n      expect(vm.$el.innerHTML).toBe('{}')\n      vm.a = 123\n    }).then(() => {\n      expect(vm.$el.innerHTML).toBe('123')\n      vm.a = 0\n    }).then(() => {\n      expect(vm.$el.innerHTML).toBe('0')\n      vm.a = ' '\n    }).then(() => {\n      expect(vm.$el.innerHTML).toBe(' ')\n      vm.a = '    '\n    }).then(() => {\n      expect(vm.$el.innerHTML).toBe('    ')\n      vm.a = null\n    }).then(() => {\n      expect(vm.$el.innerHTML).toBe('')\n      vm.a = undefined\n    }).then(() => {\n      expect(vm.$el.innerHTML).toBe('')\n    }).then(done)\n  })\n})\n"
  },
  {
    "path": "vue/test/unit/features/error-handling.spec.js",
    "content": "import Vue from 'vue'\n\nconst components = createErrorTestComponents()\n\ndescribe('Error handling', () => {\n  // hooks that prevents the component from rendering, but should not\n  // break parent component\n  ;[\n    ['data', 'data()'],\n    ['render', 'render'],\n    ['beforeCreate', 'beforeCreate hook'],\n    ['created', 'created hook'],\n    ['beforeMount', 'beforeMount hook'],\n    ['directive bind', 'directive foo bind hook'],\n    ['event', 'event handler for \"e\"']\n  ].forEach(([type, description]) => {\n    it(`should recover from errors in ${type}`, done => {\n      const vm = createTestInstance(components[type])\n      expect(`Error in ${description}`).toHaveBeenWarned()\n      expect(`Error: ${type}`).toHaveBeenWarned()\n      assertRootInstanceActive(vm).then(done)\n    })\n  })\n\n  // error in mounted hook should affect neither child nor parent\n  it('should recover from errors in mounted hook', done => {\n    const vm = createTestInstance(components.mounted)\n    expect(`Error in mounted hook`).toHaveBeenWarned()\n    expect(`Error: mounted`).toHaveBeenWarned()\n    assertBothInstancesActive(vm).then(done)\n  })\n\n  // error in beforeUpdate/updated should affect neither child nor parent\n  ;[\n    ['beforeUpdate', 'beforeUpdate hook'],\n    ['updated', 'updated hook'],\n    ['directive update', 'directive foo update hook']\n  ].forEach(([type, description]) => {\n    it(`should recover from errors in ${type} hook`, done => {\n      const vm = createTestInstance(components[type])\n      assertBothInstancesActive(vm).then(() => {\n        expect(`Error in ${description}`).toHaveBeenWarned()\n        expect(`Error: ${type}`).toHaveBeenWarned()\n      }).then(done)\n    })\n  })\n\n  ;[\n    ['beforeDestroy', 'beforeDestroy hook'],\n    ['destroyed', 'destroyed hook'],\n    ['directive unbind', 'directive foo unbind hook']\n  ].forEach(([type, description]) => {\n    it(`should recover from errors in ${type} hook`, done => {\n      const vm = createTestInstance(components[type])\n      vm.ok = false\n      waitForUpdate(() => {\n        expect(`Error in ${description}`).toHaveBeenWarned()\n        expect(`Error: ${type}`).toHaveBeenWarned()\n      }).thenWaitFor(next => {\n        assertRootInstanceActive(vm).end(next)\n      }).then(done)\n    })\n  })\n\n  it('should recover from errors in user watcher getter', done => {\n    const vm = createTestInstance(components.userWatcherGetter)\n    vm.n++\n    waitForUpdate(() => {\n      expect(`Error in getter for watcher`).toHaveBeenWarned()\n      function getErrorMsg () {\n        try {\n          this.a.b.c\n        } catch (e) {\n          return e.toString()\n        }\n      }\n      const msg = getErrorMsg.call(vm)\n      expect(msg).toHaveBeenWarned()\n    }).thenWaitFor(next => {\n      assertBothInstancesActive(vm).end(next)\n    }).then(done)\n  })\n\n  it('should recover from errors in user watcher callback', done => {\n    const vm = createTestInstance(components.userWatcherCallback)\n    vm.n++\n    waitForUpdate(() => {\n      expect(`Error in callback for watcher \"n\"`).toHaveBeenWarned()\n      expect(`Error: userWatcherCallback`).toHaveBeenWarned()\n    }).thenWaitFor(next => {\n      assertBothInstancesActive(vm).end(next)\n    }).then(done)\n  })\n\n  it('config.errorHandler should capture render errors', done => {\n    const spy = Vue.config.errorHandler = jasmine.createSpy('errorHandler')\n    const vm = createTestInstance(components.render)\n\n    const args = spy.calls.argsFor(0)\n    expect(args[0].toString()).toContain('Error: render') // error\n    expect(args[1]).toBe(vm.$refs.child) // vm\n    expect(args[2]).toContain('render') // description\n\n    assertRootInstanceActive(vm).then(() => {\n      Vue.config.errorHandler = null\n    }).then(done)\n  })\n\n  it('should capture and recover from nextTick errors', done => {\n    const err1 = new Error('nextTick')\n    const err2 = new Error('nextTick2')\n    const spy = Vue.config.errorHandler = jasmine.createSpy('errorHandler')\n    Vue.nextTick(() => { throw err1 })\n    Vue.nextTick(() => {\n      expect(spy).toHaveBeenCalledWith(err1, undefined, 'nextTick')\n\n      const vm = new Vue()\n      vm.$nextTick(() => { throw err2 })\n      Vue.nextTick(() => {\n        // should be called with correct instance info\n        expect(spy).toHaveBeenCalledWith(err2, vm, 'nextTick')\n        Vue.config.errorHandler = null\n        done()\n      })\n    })\n  })\n\n  it('should recover from errors thrown in errorHandler itself', () => {\n    Vue.config.errorHandler = () => {\n      throw new Error('error in errorHandler ¯\\\\_(ツ)_/¯')\n    }\n    const vm = new Vue({\n      render (h) {\n        throw new Error('error in render')\n      },\n      renderError (h, err) {\n        return h('div', err.toString())\n      }\n    }).$mount()\n    expect('error in errorHandler').toHaveBeenWarned()\n    expect('error in render').toHaveBeenWarned()\n    expect(vm.$el.textContent).toContain('error in render')\n    Vue.config.errorHandler = null\n  })\n})\n\nfunction createErrorTestComponents () {\n  const components = {}\n\n  // data\n  components.data = {\n    data () {\n      throw new Error('data')\n    },\n    render (h) {\n      return h('div')\n    }\n  }\n\n  // render error\n  components.render = {\n    render (h) {\n      throw new Error('render')\n    }\n  }\n\n  // lifecycle errors\n  ;['create', 'mount', 'update', 'destroy'].forEach(hook => {\n    // before\n    const before = 'before' + hook.charAt(0).toUpperCase() + hook.slice(1)\n    const beforeComp = components[before] = {\n      props: ['n'],\n      render (h) {\n        return h('div', this.n)\n      }\n    }\n    beforeComp[before] = function () {\n      throw new Error(before)\n    }\n\n    // after\n    const after = hook.replace(/e?$/, 'ed')\n    const afterComp = components[after] = {\n      props: ['n'],\n      render (h) {\n        return h('div', this.n)\n      }\n    }\n    afterComp[after] = function () {\n      throw new Error(after)\n    }\n  })\n\n  // directive hooks errors\n  ;['bind', 'update', 'unbind'].forEach(hook => {\n    const key = 'directive ' + hook\n    const dirComp = components[key] = {\n      props: ['n'],\n      template: `<div v-foo=\"n\">{{ n }}</div>`\n    }\n    const dirFoo = {}\n    dirFoo[hook] = function () {\n      throw new Error(key)\n    }\n    dirComp.directives = {\n      foo: dirFoo\n    }\n  })\n\n  // user watcher\n  components.userWatcherGetter = {\n    props: ['n'],\n    created () {\n      this.$watch(function () {\n        return this.n + this.a.b.c\n      }, val => {\n        console.log('user watcher fired: ' + val)\n      })\n    },\n    render (h) {\n      return h('div', this.n)\n    }\n  }\n\n  components.userWatcherCallback = {\n    props: ['n'],\n    watch: {\n      n () {\n        throw new Error('userWatcherCallback error')\n      }\n    },\n    render (h) {\n      return h('div', this.n)\n    }\n  }\n\n  // event errors\n  components.event = {\n    beforeCreate () {\n      this.$on('e', () => { throw new Error('event') })\n    },\n    mounted () {\n      this.$emit('e')\n    },\n    render (h) {\n      return h('div')\n    }\n  }\n\n  return components\n}\n\nfunction createTestInstance (Comp) {\n  return new Vue({\n    data: {\n      n: 0,\n      ok: true\n    },\n    render (h) {\n      return h('div', [\n        'n:' + this.n + '\\n',\n        this.ok\n          ? h(Comp, { ref: 'child', props: { n: this.n }})\n          : null\n      ])\n    }\n  }).$mount()\n}\n\nfunction assertRootInstanceActive (vm, chain) {\n  expect(vm.$el.innerHTML).toContain('n:0\\n')\n  vm.n++\n  return waitForUpdate(() => {\n    expect(vm.$el.innerHTML).toContain('n:1\\n')\n  })\n}\n\nfunction assertBothInstancesActive (vm) {\n  vm.n = 0\n  return waitForUpdate(() => {\n    expect(vm.$refs.child.$el.innerHTML).toContain('0')\n  }).thenWaitFor(next => {\n    assertRootInstanceActive(vm).then(() => {\n      expect(vm.$refs.child.$el.innerHTML).toContain('1')\n    }).end(next)\n  })\n}\n"
  },
  {
    "path": "vue/test/unit/features/filter/filter.spec.js",
    "content": "import Vue from 'vue'\nimport { parseFilters } from 'compiler/parser/filter-parser'\n\ndescribe('Filters', () => {\n  it('basic usage', () => {\n    const vm = new Vue({\n      template: '<div>{{ msg | upper }}</div>',\n      data: {\n        msg: 'hi'\n      },\n      filters: {\n        upper: v => v.toUpperCase()\n      }\n    }).$mount()\n    expect(vm.$el.textContent).toBe('HI')\n  })\n\n  it('chained usage', () => {\n    const vm = new Vue({\n      template: '<div>{{ msg | upper | reverse }}</div>',\n      data: {\n        msg: 'hi'\n      },\n      filters: {\n        upper: v => v.toUpperCase(),\n        reverse: v => v.split('').reverse().join('')\n      }\n    }).$mount()\n    expect(vm.$el.textContent).toBe('IH')\n  })\n\n  it('in v-bind', () => {\n    const vm = new Vue({\n      template: `\n        <div\n          v-bind:id=\"id | upper | reverse\"\n          :class=\"cls | reverse\"\n          :ref=\"ref | lower\">\n        </div>\n      `,\n      filters: {\n        upper: v => v.toUpperCase(),\n        reverse: v => v.split('').reverse().join(''),\n        lower: v => v.toLowerCase()\n      },\n      data: {\n        id: 'abc',\n        cls: 'foo',\n        ref: 'BAR'\n      }\n    }).$mount()\n    expect(vm.$el.id).toBe('CBA')\n    expect(vm.$el.className).toBe('oof')\n    expect(vm.$refs.bar).toBe(vm.$el)\n  })\n\n  it('handle regex with pipe', () => {\n    const vm = new Vue({\n      template: `<test ref=\"test\" :pattern=\"/a|b\\\\// | identity\"></test>`,\n      filters: { identity: v => v },\n      components: {\n        test: {\n          props: ['pattern'],\n          template: '<div></div>'\n        }\n      }\n    }).$mount()\n    expect(vm.$refs.test.pattern instanceof RegExp).toBe(true)\n    expect(vm.$refs.test.pattern.toString()).toBe('/a|b\\\\//')\n  })\n\n  it('handle division', () => {\n    const vm = new Vue({\n      data: { a: 2 },\n      template: `<div>{{ 1/a / 4 | double }}</div>`,\n      filters: { double: v => v * 2 }\n    }).$mount()\n    expect(vm.$el.textContent).toBe(String(1 / 4))\n  })\n\n  it('handle division with parenthesis', () => {\n    const vm = new Vue({\n      data: { a: 20 },\n      template: `<div>{{ (a*2) / 5 | double }}</div>`,\n      filters: { double: v => v * 2 }\n    }).$mount()\n    expect(vm.$el.textContent).toBe(String(16))\n  })\n\n  it('handle division with dot', () => {\n    const vm = new Vue({\n      template: `<div>{{ 20. / 5 | double }}</div>`,\n      filters: { double: v => v * 2 }\n    }).$mount()\n    expect(vm.$el.textContent).toBe(String(8))\n  })\n\n  it('handle division with array values', () => {\n    const vm = new Vue({\n      data: { a: [20] },\n      template: `<div>{{ a[0] / 5 | double }}</div>`,\n      filters: { double: v => v * 2 }\n    }).$mount()\n    expect(vm.$el.textContent).toBe(String(8))\n  })\n\n  it('handle division with hash values', () => {\n    const vm = new Vue({\n      data: { a: { n: 20 }},\n      template: `<div>{{ a['n'] / 5 | double }}</div>`,\n      filters: { double: v => v * 2 }\n    }).$mount()\n    expect(vm.$el.textContent).toBe(String(8))\n  })\n\n  it('handle division with variable_', () => {\n    const vm = new Vue({\n      data: { a_: 8 },\n      template: `<div>{{ a_ / 2 | double }}</div>`,\n      filters: { double: v => v * 2 }\n    }).$mount()\n    expect(vm.$el.textContent).toBe(String(8))\n  })\n\n  it('arguments', () => {\n    const vm = new Vue({\n      template: `<div>{{ msg | add(a, 3) }}</div>`,\n      data: {\n        msg: 1,\n        a: 2\n      },\n      filters: {\n        add: (v, arg1, arg2) => v + arg1 + arg2\n      }\n    }).$mount()\n    expect(vm.$el.textContent).toBe('6')\n  })\n\n  it('quotes', () => {\n    const vm = new Vue({\n      template: `<div>{{ msg + \"b | c\" + 'd' | upper }}</div>`,\n      data: {\n        msg: 'a'\n      },\n      filters: {\n        upper: v => v.toUpperCase()\n      }\n    }).$mount()\n    expect(vm.$el.textContent).toBe('AB | CD')\n  })\n\n  it('double pipe', () => {\n    const vm = new Vue({\n      template: `<div>{{ b || msg | upper }}</div>`,\n      data: {\n        b: false,\n        msg: 'a'\n      },\n      filters: {\n        upper: v => v.toUpperCase()\n      }\n    }).$mount()\n    expect(vm.$el.textContent).toBe('A')\n  })\n\n  it('object literal', () => {\n    const vm = new Vue({\n      template: `<div>{{ { a: 123 } | pick('a') }}</div>`,\n      filters: {\n        pick: (v, key) => v[key]\n      }\n    }).$mount()\n    expect(vm.$el.textContent).toBe('123')\n  })\n\n  it('array literal', () => {\n    const vm = new Vue({\n      template: `<div>{{ [1, 2, 3] | reverse }}</div>`,\n      filters: {\n        reverse: arr => arr.reverse().join(',')\n      }\n    }).$mount()\n    expect(vm.$el.textContent).toBe('3,2,1')\n  })\n\n  it('warn non-existent', () => {\n    new Vue({\n      template: '<div>{{ msg | upper }}</div>',\n      data: { msg: 'foo' }\n    }).$mount()\n    expect('Failed to resolve filter: upper').toHaveBeenWarned()\n  })\n\n  it('support template string', () => {\n    expect(parseFilters('`a | ${b}c` | d')).toBe('_f(\"d\")(`a | ${b}c`)')\n  })\n})\n"
  },
  {
    "path": "vue/test/unit/features/global-api/assets.spec.js",
    "content": "import Vue from 'vue'\n\ndescribe('Global API: assets', () => {\n  const Test = Vue.extend()\n\n  it('directive / filters', () => {\n    const assets = ['directive', 'filter']\n    assets.forEach(function (type) {\n      const def = {}\n      Test[type]('test', def)\n      expect(Test.options[type + 's'].test).toBe(def)\n      expect(Test[type]('test')).toBe(def)\n      // extended registration should not pollute global\n      expect(Vue.options[type + 's'].test).toBeUndefined()\n    })\n  })\n\n  describe('Vue.component', () => {\n    it('should register a component', () => {\n      Vue.component('foo', {\n        template: '<span>foo</span>'\n      })\n      Vue.component('bar', {\n        template: '<span>bar</span>'\n      })\n      const vm = new Vue({\n        template: '<div><foo></foo><bar></bar></div>'\n      }).$mount()\n      expect(vm.$el.innerHTML).toBe('<span>foo</span><span>bar</span>')\n      // unregister them\n      delete Vue.options.components.foo\n      delete Vue.options.components.bar\n    })\n  })\n\n  it('component on extended constructor', () => {\n    const def = { a: 1 }\n    Test.component('test', def)\n    const component = Test.options.components.test\n    expect(typeof component).toBe('function')\n    expect(component.super).toBe(Vue)\n    expect(component.options.a).toBe(1)\n    expect(component.options.name).toBe('test')\n    expect(Test.component('test')).toBe(component)\n    // already extended\n    Test.component('test2', component)\n    expect(Test.component('test2')).toBe(component)\n    // extended registration should not pollute global\n    expect(Vue.options.components.test).toBeUndefined()\n  })\n\n  // #4434\n  it('local registration should take priority regardless of naming convention', () => {\n    Vue.component('x-foo', {\n      template: '<span>global</span>'\n    })\n    const vm = new Vue({\n      components: {\n        xFoo: {\n          template: '<span>local</span>'\n        }\n      },\n      template: '<div><x-foo></x-foo></div>'\n    }).$mount()\n    expect(vm.$el.textContent).toBe('local')\n    delete Vue.options.components['x-foo']\n  })\n})\n"
  },
  {
    "path": "vue/test/unit/features/global-api/compile.spec.js",
    "content": "import Vue from 'vue'\n\ndescribe('Global API: compile', () => {\n  it('should compile render functions', () => {\n    const res = Vue.compile('<div><span>{{ msg }}</span></div>')\n    const vm = new Vue({\n      data: {\n        msg: 'hello'\n      },\n      render: res.render,\n      staticRenderFns: res.staticRenderFns\n    }).$mount()\n    expect(vm.$el.innerHTML).toContain('<span>hello</span>')\n  })\n})\n"
  },
  {
    "path": "vue/test/unit/features/global-api/config.spec.js",
    "content": "import Vue from 'vue'\nimport { warn } from 'core/util/debug'\n\ndescribe('Global config', () => {\n  it('should warn replacing config object', () => {\n    const originalConfig = Vue.config\n    Vue.config = {}\n    expect(Vue.config).toBe(originalConfig)\n    expect('Do not replace the Vue.config object').toHaveBeenWarned()\n  })\n\n  describe('silent', () => {\n    it('should be false by default', () => {\n      warn('foo')\n      expect('foo').toHaveBeenWarned()\n    })\n\n    it('should work when set to true', () => {\n      Vue.config.silent = true\n      warn('foo')\n      expect('foo').not.toHaveBeenWarned()\n      Vue.config.silent = false\n    })\n  })\n\n  describe('optionMergeStrategies', () => {\n    it('should allow defining custom option merging strategies', () => {\n      const spy = jasmine.createSpy('option merging')\n      Vue.config.optionMergeStrategies.__test__ = (parent, child, vm) => {\n        spy(parent, child, vm)\n        return child + 1\n      }\n      const Test = Vue.extend({\n        __test__: 1\n      })\n      expect(spy.calls.count()).toBe(1)\n      expect(spy).toHaveBeenCalledWith(undefined, 1, undefined)\n      expect(Test.options.__test__).toBe(2)\n      const test = new Test({\n        __test__: 2\n      })\n      expect(spy.calls.count()).toBe(2)\n      expect(spy).toHaveBeenCalledWith(2, 2, test)\n      expect(test.$options.__test__).toBe(3)\n    })\n  })\n\n  describe('ignoredElements', () => {\n    it('should work', () => {\n      Vue.config.ignoredElements = ['foo', /^ion-/]\n      new Vue({\n        template: `<div><foo/><ion-foo/><ion-bar/></div>`\n      }).$mount()\n      expect('Unknown custom element').not.toHaveBeenWarned()\n      Vue.config.ignoredElements = []\n    })\n  })\n})\n"
  },
  {
    "path": "vue/test/unit/features/global-api/extend.spec.js",
    "content": "import Vue from 'vue'\n\ndescribe('Global API: extend', () => {\n  it('should correctly merge options', () => {\n    const Test = Vue.extend({\n      name: 'test',\n      a: 1,\n      b: 2\n    })\n    expect(Test.options.a).toBe(1)\n    expect(Test.options.b).toBe(2)\n    expect(Test.super).toBe(Vue)\n    const t = new Test({\n      a: 2\n    })\n    expect(t.$options.a).toBe(2)\n    expect(t.$options.b).toBe(2)\n    // inheritance\n    const Test2 = Test.extend({\n      a: 2\n    })\n    expect(Test2.options.a).toBe(2)\n    expect(Test2.options.b).toBe(2)\n    const t2 = new Test2({\n      a: 3\n    })\n    expect(t2.$options.a).toBe(3)\n    expect(t2.$options.b).toBe(2)\n  })\n\n  it('should warn invalid names', () => {\n    Vue.extend({ name: '123' })\n    expect('Invalid component name: \"123\"').toHaveBeenWarned()\n    Vue.extend({ name: '_fesf' })\n    expect('Invalid component name: \"_fesf\"').toHaveBeenWarned()\n    Vue.extend({ name: 'Some App' })\n    expect('Invalid component name: \"Some App\"').toHaveBeenWarned()\n  })\n\n  it('should work when used as components', () => {\n    const foo = Vue.extend({\n      template: '<span>foo</span>'\n    })\n    const bar = Vue.extend({\n      template: '<span>bar</span>'\n    })\n    const vm = new Vue({\n      template: '<div><foo></foo><bar></bar></div>',\n      components: { foo, bar }\n    }).$mount()\n    expect(vm.$el.innerHTML).toBe('<span>foo</span><span>bar</span>')\n  })\n\n  it('should merge lifecycle hooks', () => {\n    const calls = []\n    const A = Vue.extend({\n      created () {\n        calls.push(1)\n      }\n    })\n    const B = A.extend({\n      created () {\n        calls.push(2)\n      }\n    })\n    new B({\n      created () {\n        calls.push(3)\n      }\n    })\n    expect(calls).toEqual([1, 2, 3])\n  })\n\n  it('should merge methods', () => {\n    const A = Vue.extend({\n      methods: {\n        a () { return this.n }\n      }\n    })\n    const B = A.extend({\n      methods: {\n        b () { return this.n + 1 }\n      }\n    })\n    const b = new B({\n      data: { n: 0 },\n      methods: {\n        c () { return this.n + 2 }\n      }\n    })\n    expect(b.a()).toBe(0)\n    expect(b.b()).toBe(1)\n    expect(b.c()).toBe(2)\n  })\n\n  it('should merge assets', () => {\n    const A = Vue.extend({\n      components: {\n        aa: {\n          template: '<div>A</div>'\n        }\n      }\n    })\n    const B = A.extend({\n      components: {\n        bb: {\n          template: '<div>B</div>'\n        }\n      }\n    })\n    const b = new B({\n      template: '<div><aa></aa><bb></bb></div>'\n    }).$mount()\n    expect(b.$el.innerHTML).toBe('<div>A</div><div>B</div>')\n  })\n\n  it('caching', () => {\n    const options = {\n      template: '<div></div>'\n    }\n    const A = Vue.extend(options)\n    const B = Vue.extend(options)\n    expect(A).toBe(B)\n  })\n\n  // #4767\n  it('extended options should use different identify from parent', () => {\n    const A = Vue.extend({ computed: {}})\n    const B = A.extend()\n    B.options.computed.b = () => 'foo'\n    expect(B.options.computed).not.toBe(A.options.computed)\n    expect(A.options.computed.b).toBeUndefined()\n  })\n})\n"
  },
  {
    "path": "vue/test/unit/features/global-api/mixin.spec.js",
    "content": "import Vue from 'vue'\n\ndescribe('Global API: mixin', () => {\n  let options\n  beforeEach(() => { options = Vue.options })\n  afterEach(() => { Vue.options = options })\n\n  it('should work', () => {\n    const spy = jasmine.createSpy('global mixin')\n    Vue.mixin({\n      created () {\n        spy(this.$options.myOption)\n      }\n    })\n    new Vue({\n      myOption: 'hello'\n    })\n    expect(spy).toHaveBeenCalledWith('hello')\n  })\n\n  it('should work for constructors created before mixin is applied', () => {\n    const calls = []\n    const Test = Vue.extend({\n      name: 'test',\n      beforeCreate () {\n        calls.push(this.$options.myOption + ' local')\n      }\n    })\n    Vue.mixin({\n      beforeCreate () {\n        calls.push(this.$options.myOption + ' global')\n      }\n    })\n    expect(Test.options.name).toBe('test')\n    new Test({\n      myOption: 'hello'\n    })\n    expect(calls).toEqual(['hello global', 'hello local'])\n  })\n\n  // #3957\n  it('should work for global props', () => {\n    const Test = Vue.extend({\n      template: `<div>{{ prop }}</div>`\n    })\n\n    Vue.mixin({\n      props: ['prop']\n    })\n\n    // test child component\n    const vm = new Vue({\n      template: '<test prop=\"hi\"></test>',\n      components: { Test }\n    }).$mount()\n\n    expect(vm.$el.textContent).toBe('hi')\n  })\n\n  // vue-loader#433\n  it('should not drop late-set render functions', () => {\n    const Test = Vue.extend({})\n    Test.options.render = h => h('div', 'hello')\n\n    Vue.mixin({})\n\n    const vm = new Vue({\n      render: h => h(Test)\n    }).$mount()\n\n    expect(vm.$el.textContent).toBe('hello')\n  })\n\n  // #4266\n  it('should not drop scopedId', () => {\n    const Test = Vue.extend({})\n    Test.options._scopeId = 'foo'\n\n    Vue.mixin({})\n\n    const vm = new Test({\n      template: '<div><p>hi</p></div>'\n    }).$mount()\n\n    expect(vm.$el.children[0].hasAttribute('foo')).toBe(true)\n  })\n\n  // #4976\n  it('should not drop late-attached custom options on existing constructors', () => {\n    const baseSpy = jasmine.createSpy('base')\n    const Base = Vue.extend({\n      beforeCreate: baseSpy\n    })\n\n    const Test = Base.extend({})\n\n    // Inject options later\n    // vue-loader and vue-hot-reload-api are doing like this\n    Test.options.computed = {\n      $style: () => 123\n    }\n\n    const spy = jasmine.createSpy('late attached')\n    Test.options.beforeCreate = Test.options.beforeCreate.concat(spy)\n\n    // Update super constructor's options\n    const mixinSpy = jasmine.createSpy('mixin')\n    Vue.mixin({\n      beforeCreate: mixinSpy\n    })\n\n    // mount the component\n    const vm = new Test({\n      template: '<div>{{ $style }}</div>'\n    }).$mount()\n\n    expect(spy.calls.count()).toBe(1)\n    expect(baseSpy.calls.count()).toBe(1)\n    expect(mixinSpy.calls.count()).toBe(1)\n    expect(vm.$el.textContent).toBe('123')\n    expect(vm.$style).toBe(123)\n\n    // Should not be dropped\n    expect(Test.options.computed.$style()).toBe(123)\n    expect(Test.options.beforeCreate).toEqual([mixinSpy, baseSpy, spy])\n  })\n\n  // vue-class-component#83\n  it('should work for a constructor mixin', () => {\n    const spy = jasmine.createSpy('global mixin')\n    const Mixin = Vue.extend({\n      created () {\n        spy(this.$options.myOption)\n      }\n    })\n\n    Vue.mixin(Mixin)\n\n    new Vue({\n      myOption: 'hello'\n    })\n    expect(spy).toHaveBeenCalledWith('hello')\n  })\n\n  // vue-class-component#87\n  it('should not drop original lifecycle hooks', () => {\n    const base = jasmine.createSpy('base')\n\n    const Base = Vue.extend({\n      beforeCreate: base\n    })\n\n    const injected = jasmine.createSpy('injected')\n\n    // inject a function\n    Base.options.beforeCreate = Base.options.beforeCreate.concat(injected)\n\n    Vue.mixin({})\n\n    new Base({})\n\n    expect(base).toHaveBeenCalled()\n    expect(injected).toHaveBeenCalled()\n  })\n})\n"
  },
  {
    "path": "vue/test/unit/features/global-api/set-delete.spec.js",
    "content": "import Vue from 'vue'\n\ndescribe('Global API: set/delete', () => {\n  describe('Vue.set', () => {\n    it('should update a vue object', done => {\n      const vm = new Vue({\n        template: '<div>{{x}}</div>',\n        data: { x: 1 }\n      }).$mount()\n      expect(vm.$el.innerHTML).toBe('1')\n      Vue.set(vm, 'x', 2)\n      waitForUpdate(() => {\n        expect(vm.$el.innerHTML).toBe('2')\n      }).then(done)\n    })\n\n    it('should update a observing object', done => {\n      const vm = new Vue({\n        template: '<div>{{foo.x}}</div>',\n        data: { foo: { x: 1 }}\n      }).$mount()\n      expect(vm.$el.innerHTML).toBe('1')\n      Vue.set(vm.foo, 'x', 2)\n      waitForUpdate(() => {\n        expect(vm.$el.innerHTML).toBe('2')\n      }).then(done)\n    })\n\n    it('should update a observing array', done => {\n      const vm = new Vue({\n        template: '<div><div v-for=\"v,k in list\">{{k}}-{{v}}</div></div>',\n        data: { list: ['a', 'b', 'c'] }\n      }).$mount()\n      expect(vm.$el.innerHTML).toBe('<div>0-a</div><div>1-b</div><div>2-c</div>')\n      Vue.set(vm.list, 1, 'd')\n      waitForUpdate(() => {\n        expect(vm.$el.innerHTML).toBe('<div>0-a</div><div>1-d</div><div>2-c</div>')\n        Vue.set(vm.list, '2', 'e')\n      }).then(() => {\n        expect(vm.$el.innerHTML).toBe('<div>0-a</div><div>1-d</div><div>2-e</div>')\n        /* eslint-disable no-new-wrappers */\n        Vue.set(vm.list, new Number(1), 'f')\n      }).then(() => {\n        expect(vm.$el.innerHTML).toBe('<div>0-a</div><div>1-f</div><div>2-e</div>')\n        Vue.set(vm.list, '3g', 'g')\n      }).then(() => {\n        expect(vm.$el.innerHTML).toBe('<div>0-a</div><div>1-f</div><div>2-e</div>')\n      }).then(done)\n    })\n\n    it('should update a vue object with nothing', done => {\n      const vm = new Vue({\n        template: '<div>{{x}}</div>',\n        data: { x: 1 }\n      }).$mount()\n      expect(vm.$el.innerHTML).toBe('1')\n      Vue.set(vm, 'x', null)\n      waitForUpdate(() => {\n        expect(vm.$el.innerHTML).toBe('')\n        Vue.set(vm, 'x')\n      }).then(() => {\n        expect(vm.$el.innerHTML).toBe('')\n      }).then(done)\n    })\n\n    it('be able to use string type index in array', done => {\n      const vm = new Vue({\n        template: '<div><p v-for=\"obj in lists\">{{obj.name}}</p></div>',\n        data: {\n          lists: [\n            { name: 'A' },\n            { name: 'B' },\n            { name: 'C' }\n          ]\n        }\n      }).$mount()\n      expect(vm.$el.innerHTML).toBe('<p>A</p><p>B</p><p>C</p>')\n      Vue.set(vm.lists, '0', { name: 'D' })\n      waitForUpdate(() => {\n        expect(vm.$el.innerHTML).toBe('<p>D</p><p>B</p><p>C</p>')\n      }).then(done)\n    })\n\n    // #6845\n    it('should not overwrite properties on prototype chain', () => {\n      class Model {\n        constructor () {\n          this._bar = null\n        }\n        get bar () {\n          return this._bar\n        }\n        set bar (newvalue) {\n          this._bar = newvalue\n        }\n      }\n\n      const vm = new Vue({\n        data: {\n          data: new Model()\n        }\n      })\n\n      Vue.set(vm.data, 'bar', 123)\n      expect(vm.data.bar).toBe(123)\n      expect(vm.data.hasOwnProperty('bar')).toBe(false)\n      expect(vm.data._bar).toBe(123)\n    })\n  })\n\n  describe('Vue.delete', () => {\n    it('should delete a key', done => {\n      const vm = new Vue({\n        template: '<div>{{obj.x}}</div>',\n        data: { obj: { x: 1 }}\n      }).$mount()\n      expect(vm.$el.innerHTML).toBe('1')\n      vm.obj.x = 2\n      waitForUpdate(() => {\n        expect(vm.$el.innerHTML).toBe('2')\n        Vue.delete(vm.obj, 'x')\n      }).then(() => {\n        expect(vm.$el.innerHTML).toBe('')\n        vm.obj.x = 3\n      }).then(() => {\n        expect(vm.$el.innerHTML).toBe('')\n      }).then(done)\n    })\n\n    it('be able to delete an item in array', done => {\n      const vm = new Vue({\n        template: '<div><p v-for=\"obj in lists\">{{obj.name}}</p></div>',\n        data: {\n          lists: [\n            { name: 'A' },\n            { name: 'B' },\n            { name: 'C' }\n          ]\n        }\n      }).$mount()\n      expect(vm.$el.innerHTML).toBe('<p>A</p><p>B</p><p>C</p>')\n      Vue.delete(vm.lists, 1)\n      waitForUpdate(() => {\n        expect(vm.$el.innerHTML).toBe('<p>A</p><p>C</p>')\n        Vue.delete(vm.lists, NaN)\n      }).then(() => {\n        expect(vm.$el.innerHTML).toBe('<p>A</p><p>C</p>')\n        Vue.delete(vm.lists, -1)\n      }).then(() => {\n        expect(vm.$el.innerHTML).toBe('<p>A</p><p>C</p>')\n        Vue.delete(vm.lists, '1.3')\n      }).then(() => {\n        expect(vm.$el.innerHTML).toBe('<p>A</p><p>C</p>')\n        Vue.delete(vm.lists, true)\n      }).then(() => {\n        expect(vm.$el.innerHTML).toBe('<p>A</p><p>C</p>')\n        Vue.delete(vm.lists, {})\n      }).then(() => {\n        expect(vm.$el.innerHTML).toBe('<p>A</p><p>C</p>')\n        Vue.delete(vm.lists, '1')\n      }).then(() => {\n        expect(vm.$el.innerHTML).toBe('<p>A</p>')\n        /* eslint-disable no-new-wrappers */\n        Vue.delete(vm.lists, new Number(0))\n      }).then(() => {\n        expect(vm.$el.innerHTML).toBe('')\n      }).then(done)\n    })\n  })\n})\n"
  },
  {
    "path": "vue/test/unit/features/global-api/use.spec.js",
    "content": "import Vue from 'vue'\n\ndescribe('Global API: use', () => {\n  const def = {}\n  const options = {}\n  const pluginStub = {\n    install: (Vue, opts) => {\n      Vue.directive('plugin-test', def)\n      expect(opts).toBe(options)\n    }\n  }\n\n  it('should apply Object plugin', () => {\n    Vue.use(pluginStub, options)\n    expect(Vue.options.directives['plugin-test']).toBe(def)\n    delete Vue.options.directives['plugin-test']\n    expect(Vue.options.directives['plugin-test']).toBeUndefined()\n\n    // should not double apply\n    Vue.use(pluginStub, options)\n    expect(Vue.options.directives['plugin-test']).toBeUndefined()\n  })\n\n  it('should apply Function plugin', () => {\n    Vue.use(pluginStub.install, options)\n    expect(Vue.options.directives['plugin-test']).toBe(def)\n    delete Vue.options.directives['plugin-test']\n  })\n\n  it('should work on extended constructors without polluting the base', () => {\n    const Ctor = Vue.extend({})\n    Ctor.use(pluginStub, options)\n    expect(Vue.options.directives['plugin-test']).toBeUndefined()\n    expect(Ctor.options.directives['plugin-test']).toBe(def)\n  })\n\n  // GitHub issue #5970\n  it('should work on multi version', () => {\n    const Ctor1 = Vue.extend({})\n    const Ctor2 = Vue.extend({})\n\n    Ctor1.use(pluginStub, options)\n    expect(Vue.options.directives['plugin-test']).toBeUndefined()\n    expect(Ctor1.options.directives['plugin-test']).toBe(def)\n\n    // multi version Vue Ctor with the same cid\n    Ctor2.cid = Ctor1.cid\n    Ctor2.use(pluginStub, options)\n    expect(Vue.options.directives['plugin-test']).toBeUndefined()\n    expect(Ctor2.options.directives['plugin-test']).toBe(def)\n  })\n})\n"
  },
  {
    "path": "vue/test/unit/features/instance/init.spec.js",
    "content": "import Vue from 'vue'\n\ndescribe('Initialization', () => {\n  it('without new', () => {\n    try { Vue() } catch (e) {}\n    expect('Vue is a constructor and should be called with the `new` keyword').toHaveBeenWarned()\n  })\n\n  it('with new', () => {\n    expect(new Vue() instanceof Vue).toBe(true)\n  })\n})\n"
  },
  {
    "path": "vue/test/unit/features/instance/methods-data.spec.js",
    "content": "import Vue from 'vue'\n\ndescribe('Instance methods data', () => {\n  it('$set/$delete', done => {\n    const vm = new Vue({\n      template: '<div>{{ a.msg }}</div>',\n      data: {\n        a: {}\n      }\n    }).$mount()\n    expect(vm.$el.innerHTML).toBe('')\n    vm.$set(vm.a, 'msg', 'hello')\n    waitForUpdate(() => {\n      expect(vm.$el.innerHTML).toBe('hello')\n      vm.$delete(vm.a, 'msg')\n    }).then(() => {\n      expect(vm.$el.innerHTML).toBe('')\n    }).then(done)\n  })\n\n  describe('$watch', () => {\n    let vm, spy\n    beforeEach(() => {\n      spy = jasmine.createSpy('watch')\n      vm = new Vue({\n        data: {\n          a: {\n            b: 1\n          }\n        },\n        methods: {\n          foo: spy\n        }\n      })\n    })\n\n    it('basic usage', done => {\n      vm.$watch('a.b', spy)\n      vm.a.b = 2\n      waitForUpdate(() => {\n        expect(spy.calls.count()).toBe(1)\n        expect(spy).toHaveBeenCalledWith(2, 1)\n        vm.a = { b: 3 }\n      }).then(() => {\n        expect(spy.calls.count()).toBe(2)\n        expect(spy).toHaveBeenCalledWith(3, 2)\n      }).then(done)\n    })\n\n    it('immediate', () => {\n      vm.$watch('a.b', spy, { immediate: true })\n      expect(spy.calls.count()).toBe(1)\n      expect(spy).toHaveBeenCalledWith(1)\n    })\n\n    it('unwatch', done => {\n      const unwatch = vm.$watch('a.b', spy)\n      unwatch()\n      vm.a.b = 2\n      waitForUpdate(() => {\n        expect(spy.calls.count()).toBe(0)\n      }).then(done)\n    })\n\n    it('function watch', done => {\n      vm.$watch(function () {\n        return this.a.b\n      }, spy)\n      vm.a.b = 2\n      waitForUpdate(() => {\n        expect(spy).toHaveBeenCalledWith(2, 1)\n      }).then(done)\n    })\n\n    it('deep watch', done => {\n      var oldA = vm.a\n      vm.$watch('a', spy, { deep: true })\n      vm.a.b = 2\n      waitForUpdate(() => {\n        expect(spy).toHaveBeenCalledWith(oldA, oldA)\n        vm.a = { b: 3 }\n      }).then(() => {\n        expect(spy).toHaveBeenCalledWith(vm.a, oldA)\n      }).then(done)\n    })\n\n    it('handler option', done => {\n      var oldA = vm.a\n      vm.$watch('a', {\n        handler: spy,\n        deep: true\n      })\n      vm.a.b = 2\n      waitForUpdate(() => {\n        expect(spy).toHaveBeenCalledWith(oldA, oldA)\n        vm.a = { b: 3 }\n      }).then(() => {\n        expect(spy).toHaveBeenCalledWith(vm.a, oldA)\n      }).then(done)\n    })\n\n    it('handler option in string', () => {\n      vm.$watch('a.b', {\n        handler: 'foo',\n        immediate: true\n      })\n      expect(spy.calls.count()).toBe(1)\n      expect(spy).toHaveBeenCalledWith(1)\n    })\n\n    it('warn expression', () => {\n      vm.$watch('a + b', spy)\n      expect('Watcher only accepts simple dot-delimited paths').toHaveBeenWarned()\n    })\n  })\n})\n"
  },
  {
    "path": "vue/test/unit/features/instance/methods-events.spec.js",
    "content": "import Vue from 'vue'\n\ndescribe('Instance methods events', () => {\n  let vm, spy\n  beforeEach(() => {\n    vm = new Vue()\n    spy = jasmine.createSpy('emitter')\n  })\n\n  it('$on', () => {\n    vm.$on('test', function () {\n      // expect correct context\n      expect(this).toBe(vm)\n      spy.apply(this, arguments)\n    })\n    vm.$emit('test', 1, 2, 3, 4)\n    expect(spy.calls.count()).toBe(1)\n    expect(spy).toHaveBeenCalledWith(1, 2, 3, 4)\n  })\n\n  it('$on multi event', () => {\n    vm.$on(['test1', 'test2'], function () {\n      expect(this).toBe(vm)\n      spy.apply(this, arguments)\n    })\n    vm.$emit('test1', 1, 2, 3, 4)\n    expect(spy.calls.count()).toBe(1)\n    expect(spy).toHaveBeenCalledWith(1, 2, 3, 4)\n    vm.$emit('test2', 5, 6, 7, 8)\n    expect(spy.calls.count()).toBe(2)\n    expect(spy).toHaveBeenCalledWith(5, 6, 7, 8)\n  })\n\n  it('$off multi event', () => {\n    vm.$on(['test1', 'test2', 'test3'], spy)\n    vm.$off(['test1', 'test2'], spy)\n    vm.$emit('test1')\n    vm.$emit('test2')\n    expect(spy).not.toHaveBeenCalled()\n    vm.$emit('test3', 1, 2, 3, 4)\n    expect(spy.calls.count()).toBe(1)\n  })\n\n  it('$off multi event without callback', () => {\n    vm.$on(['test1', 'test2'], spy)\n    vm.$off(['test1', 'test2'])\n    vm.$emit('test1')\n    expect(spy).not.toHaveBeenCalled()\n  })\n\n  it('$once', () => {\n    vm.$once('test', spy)\n    vm.$emit('test', 1, 2, 3)\n    vm.$emit('test', 2, 3, 4)\n    expect(spy.calls.count()).toBe(1)\n    expect(spy).toHaveBeenCalledWith(1, 2, 3)\n  })\n\n  it('$off', () => {\n    vm.$on('test1', spy)\n    vm.$on('test2', spy)\n    vm.$off()\n    vm.$emit('test1')\n    vm.$emit('test2')\n    expect(spy).not.toHaveBeenCalled()\n  })\n\n  it('$off event', () => {\n    vm.$on('test1', spy)\n    vm.$on('test2', spy)\n    vm.$off('test1')\n    vm.$off('test1') // test off something that's already off\n    vm.$emit('test1', 1)\n    vm.$emit('test2', 2)\n    expect(spy.calls.count()).toBe(1)\n    expect(spy).toHaveBeenCalledWith(2)\n  })\n\n  it('$off event + fn', () => {\n    var spy2 = jasmine.createSpy('emitter')\n    vm.$on('test', spy)\n    vm.$on('test', spy2)\n    vm.$off('test', spy)\n    vm.$emit('test', 1, 2, 3)\n    expect(spy).not.toHaveBeenCalled()\n    expect(spy2.calls.count()).toBe(1)\n    expect(spy2).toHaveBeenCalledWith(1, 2, 3)\n  })\n})\n"
  },
  {
    "path": "vue/test/unit/features/instance/methods-lifecycle.spec.js",
    "content": "import Vue from 'vue'\n\ndescribe('Instance methods lifecycle', () => {\n  describe('$mount', () => {\n    it('empty mount', () => {\n      const vm = new Vue({\n        data: { msg: 'hi' },\n        template: '<div>{{ msg }}</div>'\n      }).$mount()\n      expect(vm.$el.tagName).toBe('DIV')\n      expect(vm.$el.textContent).toBe('hi')\n    })\n\n    it('mount to existing element', () => {\n      const el = document.createElement('div')\n      el.innerHTML = '{{ msg }}'\n      const vm = new Vue({\n        data: { msg: 'hi' }\n      }).$mount(el)\n      expect(vm.$el.tagName).toBe('DIV')\n      expect(vm.$el.textContent).toBe('hi')\n    })\n\n    it('mount to id', () => {\n      const el = document.createElement('div')\n      el.id = 'mount-test'\n      el.innerHTML = '{{ msg }}'\n      document.body.appendChild(el)\n      const vm = new Vue({\n        data: { msg: 'hi' }\n      }).$mount('#mount-test')\n      expect(vm.$el.tagName).toBe('DIV')\n      expect(vm.$el.textContent).toBe('hi')\n    })\n  })\n\n  describe('$destroy', () => {\n    it('remove self from parent', () => {\n      const vm = new Vue({\n        template: '<test></test>',\n        components: {\n          test: { template: '<div></div>' }\n        }\n      }).$mount()\n      vm.$children[0].$destroy()\n      expect(vm.$children.length).toBe(0)\n    })\n\n    it('teardown watchers', () => {\n      const vm = new Vue({\n        data: { a: 123 },\n        template: '<div></div>'\n      }).$mount()\n      vm.$watch('a', () => {})\n      vm.$destroy()\n      expect(vm._watcher.active).toBe(false)\n      expect(vm._watchers.every(w => !w.active)).toBe(true)\n    })\n\n    it('remove self from data observer', () => {\n      const vm = new Vue({ data: { a: 1 }})\n      vm.$destroy()\n      expect(vm.$data.__ob__.vmCount).toBe(0)\n    })\n\n    it('avoid duplicate calls', () => {\n      const spy = jasmine.createSpy('destroy')\n      const vm = new Vue({\n        beforeDestroy: spy\n      })\n      vm.$destroy()\n      vm.$destroy()\n      expect(spy.calls.count()).toBe(1)\n    })\n  })\n\n  describe('$forceUpdate', () => {\n    it('should force update', done => {\n      const vm = new Vue({\n        data: {\n          a: {}\n        },\n        template: '<div>{{ a.b }}</div>'\n      }).$mount()\n      expect(vm.$el.textContent).toBe('')\n      vm.a.b = 'foo'\n      waitForUpdate(() => {\n        // should not work because adding new property\n        expect(vm.$el.textContent).toBe('')\n        vm.$forceUpdate()\n      }).then(() => {\n        expect(vm.$el.textContent).toBe('foo')\n      }).then(done)\n    })\n  })\n\n  describe('$nextTick', () => {\n    it('should be called after DOM update in correct context', done => {\n      const vm = new Vue({\n        template: '<div>{{ msg }}</div>',\n        data: {\n          msg: 'foo'\n        }\n      }).$mount()\n      vm.msg = 'bar'\n      vm.$nextTick(function () {\n        expect(this).toBe(vm)\n        expect(vm.$el.textContent).toBe('bar')\n        done()\n      })\n    })\n\n    if (typeof Promise !== 'undefined') {\n      it('should be called after DOM update in correct context, when using Promise syntax', done => {\n        const vm = new Vue({\n          template: '<div>{{ msg }}</div>',\n          data: {\n            msg: 'foo'\n          }\n        }).$mount()\n        vm.msg = 'bar'\n        vm.$nextTick().then(ctx => {\n          expect(ctx).toBe(vm)\n          expect(vm.$el.textContent).toBe('bar')\n          done()\n        })\n      })\n    }\n  })\n})\n"
  },
  {
    "path": "vue/test/unit/features/instance/properties.spec.js",
    "content": "import Vue from 'vue'\n\ndescribe('Instance properties', () => {\n  it('$data', () => {\n    const data = { a: 1 }\n    const vm = new Vue({\n      data\n    })\n    expect(vm.a).toBe(1)\n    expect(vm.$data).toBe(data)\n    // vm -> data\n    vm.a = 2\n    expect(data.a).toBe(2)\n    // data -> vm\n    data.a = 3\n    expect(vm.a).toBe(3)\n  })\n\n  it('$options', () => {\n    const A = Vue.extend({\n      methods: {\n        a () {}\n      }\n    })\n    const vm = new A({\n      methods: {\n        b () {}\n      }\n    })\n    expect(typeof vm.$options.methods.a).toBe('function')\n    expect(typeof vm.$options.methods.b).toBe('function')\n  })\n\n  it('$root/$children', done => {\n    const vm = new Vue({\n      template: '<div><test v-if=\"ok\"></test></div>',\n      data: { ok: true },\n      components: {\n        test: {\n          template: '<div></div>'\n        }\n      }\n    }).$mount()\n    expect(vm.$root).toBe(vm)\n    expect(vm.$children.length).toBe(1)\n    expect(vm.$children[0].$root).toBe(vm)\n    vm.ok = false\n    waitForUpdate(() => {\n      expect(vm.$children.length).toBe(0)\n      vm.ok = true\n    }).then(() => {\n      expect(vm.$children.length).toBe(1)\n      expect(vm.$children[0].$root).toBe(vm)\n    }).then(done)\n  })\n\n  it('$parent', () => {\n    const calls = []\n    const makeOption = name => ({\n      name,\n      template: `<div><slot></slot></div>`,\n      created () {\n        calls.push(`${name}:${this.$parent.$options.name}`)\n      }\n    })\n    new Vue({\n      template: `\n        <div>\n          <outer><middle><inner></inner></middle></outer>\n          <next></next>\n        </div>\n      `,\n      components: {\n        outer: makeOption('outer'),\n        middle: makeOption('middle'),\n        inner: makeOption('inner'),\n        next: makeOption('next')\n      }\n    }).$mount()\n    expect(calls).toEqual(['outer:undefined', 'middle:outer', 'inner:middle', 'next:undefined'])\n  })\n\n  it('$props', done => {\n    const Comp = Vue.extend({\n      props: ['msg'],\n      template: '<div>{{ msg }} {{ $props.msg }}</div>'\n    })\n    const vm = new Comp({\n      propsData: {\n        msg: 'foo'\n      }\n    }).$mount()\n    // check render\n    expect(vm.$el.textContent).toContain('foo foo')\n    // warn set\n    vm.$props = {}\n    expect('$props is readonly').toHaveBeenWarned()\n    // check existence\n    expect(vm.$props.msg).toBe('foo')\n    // check change\n    vm.msg = 'bar'\n    expect(vm.$props.msg).toBe('bar')\n    waitForUpdate(() => {\n      expect(vm.$el.textContent).toContain('bar bar')\n    }).then(() => {\n      vm.$props.msg = 'baz'\n      expect(vm.msg).toBe('baz')\n    }).then(() => {\n      expect(vm.$el.textContent).toContain('baz baz')\n    }).then(done)\n  })\n\n  it('warn mutating $props', () => {\n    const Comp = {\n      props: ['msg'],\n      render () {},\n      mounted () {\n        expect(this.$props.msg).toBe('foo')\n        this.$props.msg = 'bar'\n      }\n    }\n    new Vue({\n      template: `<comp ref=\"comp\" msg=\"foo\" />`,\n      components: { Comp }\n    }).$mount()\n    expect(`Avoid mutating a prop`).toHaveBeenWarned()\n  })\n\n  it('$attrs', done => {\n    const vm = new Vue({\n      template: `<foo :id=\"foo\" bar=\"1\"/>`,\n      data: { foo: 'foo' },\n      components: {\n        foo: {\n          props: ['bar'],\n          template: `<div><div v-bind=\"$attrs\"></div></div>`\n        }\n      }\n    }).$mount()\n    expect(vm.$el.children[0].id).toBe('foo')\n    expect(vm.$el.children[0].hasAttribute('bar')).toBe(false)\n    vm.foo = 'bar'\n    waitForUpdate(() => {\n      expect(vm.$el.children[0].id).toBe('bar')\n      expect(vm.$el.children[0].hasAttribute('bar')).toBe(false)\n    }).then(done)\n  })\n\n  // #6263\n  it('$attrs should not be undefined when no props passed in', () => {\n    const vm = new Vue({\n      template: `<foo/>`,\n      data: { foo: 'foo' },\n      components: {\n        foo: {\n          template: `<div>{{ this.foo }}</div>`\n        }\n      }\n    }).$mount()\n    expect(vm.$attrs).toBeDefined()\n  })\n\n  it('warn mutating $attrs', () => {\n    const vm = new Vue()\n    vm.$attrs = {}\n    expect(`$attrs is readonly`).toHaveBeenWarned()\n  })\n\n  it('$listeners', done => {\n    const spyA = jasmine.createSpy('A')\n    const spyB = jasmine.createSpy('B')\n    const vm = new Vue({\n      template: `<foo @click=\"foo\"/>`,\n      data: { foo: spyA },\n      components: {\n        foo: {\n          template: `<div v-on=\"$listeners\"></div>`\n        }\n      }\n    }).$mount()\n\n    // has to be in dom for test to pass in IE\n    document.body.appendChild(vm.$el)\n\n    triggerEvent(vm.$el, 'click')\n    expect(spyA.calls.count()).toBe(1)\n    expect(spyB.calls.count()).toBe(0)\n\n    vm.foo = spyB\n    waitForUpdate(() => {\n      triggerEvent(vm.$el, 'click')\n      expect(spyA.calls.count()).toBe(1)\n      expect(spyB.calls.count()).toBe(1)\n      document.body.removeChild(vm.$el)\n    }).then(done)\n  })\n\n  it('warn mutating $listeners', () => {\n    const vm = new Vue()\n    vm.$listeners = {}\n    expect(`$listeners is readonly`).toHaveBeenWarned()\n  })\n})\n"
  },
  {
    "path": "vue/test/unit/features/instance/render-proxy.spec.js",
    "content": "import Vue from 'vue'\n\nif (typeof Proxy !== 'undefined') {\n  describe('render proxy', () => {\n    it('should warn missing property in render fns with `with`', () => {\n      new Vue({\n        template: `<div>{{ a }}</div>`\n      }).$mount()\n      expect(`Property or method \"a\" is not defined`).toHaveBeenWarned()\n    })\n\n    it('should warn missing property in render fns without `with`', () => {\n      const render = function (h) {\n        return h('div', [this.a])\n      }\n      render._withStripped = true\n      new Vue({\n        render\n      }).$mount()\n      expect(`Property or method \"a\" is not defined`).toHaveBeenWarned()\n    })\n\n    it('should not warn for hand-written render functions', () => {\n      new Vue({\n        render (h) {\n          return h('div', [this.a])\n        }\n      }).$mount()\n      expect(`Property or method \"a\" is not defined`).not.toHaveBeenWarned()\n    })\n\n    it('support symbols using the `in` operator in hand-written render functions', () => {\n      const sym = Symbol()\n\n      const vm = new Vue({\n        created () {\n          this[sym] = 'foo'\n        },\n        render (h) {\n          if (sym in this) {\n            return h('div', [this[sym]])\n          }\n        }\n      }).$mount()\n\n      expect(vm.$el.textContent).toBe('foo')\n    })\n  })\n}\n"
  },
  {
    "path": "vue/test/unit/features/options/_scopeId.spec.js",
    "content": "import Vue from 'vue'\n\ndescribe('Options _scopeId', () => {\n  it('should add scopeId attributes', () => {\n    const vm = new Vue({\n      _scopeId: 'foo',\n      template: '<div><p><span></span></p></div>'\n    }).$mount()\n    expect(vm.$el.hasAttribute('foo')).toBe(true)\n    expect(vm.$el.children[0].hasAttribute('foo')).toBe(true)\n    expect(vm.$el.children[0].children[0].hasAttribute('foo')).toBe(true)\n  })\n\n  it('should add scopedId attributes from both parent and child on child root', () => {\n    const vm = new Vue({\n      _scopeId: 'foo',\n      template: '<div><child></child></div>',\n      components: {\n        child: {\n          _scopeId: 'bar',\n          template: '<div></div>'\n        }\n      }\n    }).$mount()\n    expect(vm.$el.children[0].hasAttribute('foo')).toBe(true)\n    expect(vm.$el.children[0].hasAttribute('bar')).toBe(true)\n  })\n\n  it('should add scopedId attributes from both parent and child on slot contents', () => {\n    const vm = new Vue({\n      _scopeId: 'foo',\n      template: '<div><child><p>hi</p></child></div>',\n      components: {\n        child: {\n          _scopeId: 'bar',\n          template: '<div><slot></slot></div>'\n        }\n      }\n    }).$mount()\n    expect(vm.$el.children[0].children[0].hasAttribute('foo')).toBe(true)\n    expect(vm.$el.children[0].children[0].hasAttribute('bar')).toBe(true)\n  })\n\n  // #4774\n  it('should not discard parent scopeId when component root element is replaced', done => {\n    const vm = new Vue({\n      _scopeId: 'data-1',\n      template: `<div><child ref=\"child\" /></div>`,\n      components: {\n        child: {\n          _scopeId: 'data-2',\n          data: () => ({ show: true }),\n          template: '<div v-if=\"show\"></div>'\n        }\n      }\n    }).$mount()\n\n    const child = vm.$refs.child\n\n    expect(child.$el.hasAttribute('data-1')).toBe(true)\n    expect(child.$el.hasAttribute('data-2')).toBe(true)\n\n    child.show = false\n    waitForUpdate(() => {\n      child.show = true\n    }).then(() => {\n      expect(child.$el.hasAttribute('data-1')).toBe(true)\n      expect(child.$el.hasAttribute('data-2')).toBe(true)\n    }).then(done)\n  })\n\n  it('should work on functional components', () => {\n    const child = {\n      functional: true,\n      _scopeId: 'child',\n      render (h) {\n        return h('div', { class: 'child' }, [\n          h('span', { class: 'child' }, 'child')\n        ])\n      }\n    }\n    const vm = new Vue({\n      _scopeId: 'parent',\n      components: { child },\n      template: '<div><child></child></div>'\n    }).$mount()\n\n    expect(vm.$el.hasAttribute('parent')).toBe(true)\n    const childEls = vm.$el.querySelectorAll('.child')\n    ;[].forEach.call(childEls, el => {\n      expect(el.hasAttribute('child')).toBe(true)\n      // functional component with scopeId will not inherit parent scopeId\n      expect(el.hasAttribute('parent')).toBe(false)\n    })\n  })\n})\n"
  },
  {
    "path": "vue/test/unit/features/options/comments.spec.js",
    "content": "import Vue from 'vue'\n\ndescribe('Comments', () => {\n  it('comments should be kept', () => {\n    const vm = new Vue({\n      comments: true,\n      data () {\n        return {\n          foo: 1\n        }\n      },\n      template: '<div><span>node1</span><!--comment1-->{{foo}}<!--comment2--></div>'\n    }).$mount()\n    expect(vm.$el.innerHTML).toEqual('<span>node1</span><!--comment1-->1<!--comment2-->')\n  })\n})\n"
  },
  {
    "path": "vue/test/unit/features/options/components.spec.js",
    "content": "import Vue from 'vue'\nimport { UA } from 'core/util/env'\nimport testObjectOption from '../../../helpers/test-object-option'\n\ndescribe('Options components', () => {\n  testObjectOption('components')\n\n  it('should accept plain object', () => {\n    const vm = new Vue({\n      template: '<test></test>',\n      components: {\n        test: {\n          template: '<div>hi</div>'\n        }\n      }\n    }).$mount()\n    expect(vm.$el.tagName).toBe('DIV')\n    expect(vm.$el.textContent).toBe('hi')\n  })\n\n  it('should accept extended constructor', () => {\n    const Test = Vue.extend({\n      template: '<div>hi</div>'\n    })\n    const vm = new Vue({\n      template: '<test></test>',\n      components: {\n        test: Test\n      }\n    }).$mount()\n    expect(vm.$el.tagName).toBe('DIV')\n    expect(vm.$el.textContent).toBe('hi')\n  })\n\n  it('should accept camelCase', () => {\n    const myComp = {\n      template: '<div>hi</div>'\n    }\n    const vm = new Vue({\n      template: '<my-comp></my-comp>',\n      components: {\n        myComp\n      }\n    }).$mount()\n    expect(vm.$el.tagName).toBe('DIV')\n    expect(vm.$el.textContent).toBe('hi')\n  })\n\n  it('should accept PascalCase', () => {\n    const MyComp = {\n      template: '<div>hi</div>'\n    }\n    const vm = new Vue({\n      template: '<my-comp></my-comp>',\n      components: {\n        MyComp\n      }\n    }).$mount()\n    expect(vm.$el.tagName).toBe('DIV')\n    expect(vm.$el.textContent).toBe('hi')\n  })\n\n  it('should warn native HTML elements', () => {\n    new Vue({\n      components: {\n        div: { template: '<div></div>' }\n      }\n    })\n    expect('Do not use built-in or reserved HTML elements as component').toHaveBeenWarned()\n  })\n\n  it('should warn built-in elements', () => {\n    new Vue({\n      components: {\n        component: { template: '<div></div>' }\n      }\n    })\n    expect('Do not use built-in or reserved HTML elements as component').toHaveBeenWarned()\n  })\n\n  // the HTMLUnknownElement check doesn't work in Android 4.2\n  // but since it doesn't support custom elements nor will any dev use it\n  // as their primary debugging browser, it doesn't really matter.\n  if (!(UA && /android 4\\.2/.test(UA))) {\n    it('warn non-existent', () => {\n      new Vue({\n        template: '<test></test>'\n      }).$mount()\n      expect('Unknown custom element: <test>').toHaveBeenWarned()\n    })\n  }\n})\n"
  },
  {
    "path": "vue/test/unit/features/options/computed.spec.js",
    "content": "import Vue from 'vue'\nimport testObjectOption from '../../../helpers/test-object-option'\n\ndescribe('Options computed', () => {\n  testObjectOption('computed')\n\n  it('basic usage', done => {\n    const vm = new Vue({\n      template: '<div>{{ b }}</div>',\n      data: {\n        a: 1\n      },\n      computed: {\n        b () {\n          return this.a + 1\n        }\n      }\n    }).$mount()\n    expect(vm.b).toBe(2)\n    expect(vm.$el.textContent).toBe('2')\n    vm.a = 2\n    expect(vm.b).toBe(3)\n    waitForUpdate(() => {\n      expect(vm.$el.textContent).toBe('3')\n    }).then(done)\n  })\n\n  it('with setter', done => {\n    const vm = new Vue({\n      template: '<div>{{ b }}</div>',\n      data: {\n        a: 1\n      },\n      computed: {\n        b: {\n          get () { return this.a + 1 },\n          set (v) { this.a = v - 1 }\n        }\n      }\n    }).$mount()\n    expect(vm.b).toBe(2)\n    expect(vm.$el.textContent).toBe('2')\n    vm.a = 2\n    expect(vm.b).toBe(3)\n    waitForUpdate(() => {\n      expect(vm.$el.textContent).toBe('3')\n      vm.b = 1\n      expect(vm.a).toBe(0)\n    }).then(() => {\n      expect(vm.$el.textContent).toBe('1')\n    }).then(done)\n  })\n\n  it('warn with setter and no getter', () => {\n    const vm = new Vue({\n      template: `\n        <div>\n          <test></test>\n        </div>\n      `,\n      components: {\n        test: {\n          data () {\n            return {\n              a: 1\n            }\n          },\n          computed: {\n            b: {\n              set (v) { this.a = v }\n            }\n          },\n          template: `<div>{{a}}</div>`\n        }\n      }\n    }).$mount()\n    expect(vm.$el.innerHTML).toBe('<div>1</div>')\n    expect('Getter is missing for computed property \"b\".').toHaveBeenWarned()\n  })\n\n  it('warn assigning to computed with no setter', () => {\n    const vm = new Vue({\n      computed: {\n        b () {\n          return 1\n        }\n      }\n    })\n    vm.b = 2\n    expect(`Computed property \"b\" was assigned to but it has no setter.`).toHaveBeenWarned()\n  })\n\n  it('watching computed', done => {\n    const spy = jasmine.createSpy('watch computed')\n    const vm = new Vue({\n      data: {\n        a: 1\n      },\n      computed: {\n        b () { return this.a + 1 }\n      }\n    })\n    vm.$watch('b', spy)\n    vm.a = 2\n    waitForUpdate(() => {\n      expect(spy).toHaveBeenCalledWith(3, 2)\n    }).then(done)\n  })\n\n  it('caching', () => {\n    const spy = jasmine.createSpy('cached computed')\n    const vm = new Vue({\n      data: {\n        a: 1\n      },\n      computed: {\n        b () {\n          spy()\n          return this.a + 1\n        }\n      }\n    })\n    expect(spy.calls.count()).toBe(0)\n    vm.b\n    expect(spy.calls.count()).toBe(1)\n    vm.b\n    expect(spy.calls.count()).toBe(1)\n  })\n\n  it('cache: false', () => {\n    const spy = jasmine.createSpy('cached computed')\n    const vm = new Vue({\n      data: {\n        a: 1\n      },\n      computed: {\n        b: {\n          cache: false,\n          get () {\n            spy()\n            return this.a + 1\n          }\n        }\n      }\n    })\n    expect(spy.calls.count()).toBe(0)\n    vm.b\n    expect(spy.calls.count()).toBe(1)\n    vm.b\n    expect(spy.calls.count()).toBe(2)\n  })\n\n  it('as component', done => {\n    const Comp = Vue.extend({\n      template: `<div>{{ b }} {{ c }}</div>`,\n      data () {\n        return { a: 1 }\n      },\n      computed: {\n        // defined on prototype\n        b () {\n          return this.a + 1\n        }\n      }\n    })\n\n    const vm = new Comp({\n      computed: {\n        // defined at instantiation\n        c () {\n          return this.b + 1\n        }\n      }\n    }).$mount()\n    expect(vm.b).toBe(2)\n    expect(vm.c).toBe(3)\n    expect(vm.$el.textContent).toBe('2 3')\n    vm.a = 2\n    expect(vm.b).toBe(3)\n    expect(vm.c).toBe(4)\n    waitForUpdate(() => {\n      expect(vm.$el.textContent).toBe('3 4')\n    }).then(done)\n  })\n\n  it('warn conflict with data', () => {\n    new Vue({\n      data: {\n        a: 1\n      },\n      computed: {\n        a: () => 2\n      }\n    })\n    expect(`computed property \"a\" is already defined in data`).toHaveBeenWarned()\n  })\n\n  it('warn conflict with props', () => {\n    new Vue({\n      props: ['a'],\n      propsData: { a: 1 },\n      computed: {\n        a: () => 2\n      }\n    })\n    expect(`computed property \"a\" is already defined as a prop`).toHaveBeenWarned()\n  })\n\n  it('rethrow computed error', () => {\n    const vm = new Vue({\n      computed: {\n        a: () => {\n          throw new Error('rethrow')\n        }\n      }\n    })\n    expect(() => vm.a).toThrowError('rethrow')\n  })\n\n  // #7767\n  it('should avoid unnecessary re-renders', done => {\n    const computedSpy = jasmine.createSpy('computed')\n    const updatedSpy = jasmine.createSpy('updated')\n    const vm = new Vue({\n      data: {\n        msg: 'bar'\n      },\n      computed: {\n        a () {\n          computedSpy()\n          return this.msg !== 'foo'\n        }\n      },\n      template: `<div>{{ a }}</div>`,\n      updated: updatedSpy\n    }).$mount()\n\n    expect(vm.$el.textContent).toBe('true')\n    expect(computedSpy.calls.count()).toBe(1)\n    expect(updatedSpy.calls.count()).toBe(0)\n\n    vm.msg = 'baz'\n    waitForUpdate(() => {\n      expect(vm.$el.textContent).toBe('true')\n      expect(computedSpy.calls.count()).toBe(2)\n      expect(updatedSpy.calls.count()).toBe(0)\n    }).then(() => {\n      vm.msg = 'foo'\n    }).then(() => {\n      expect(vm.$el.textContent).toBe('false')\n      expect(computedSpy.calls.count()).toBe(3)\n      expect(updatedSpy.calls.count()).toBe(1)\n    }).then(done)\n  })\n})\n"
  },
  {
    "path": "vue/test/unit/features/options/data.spec.js",
    "content": "import Vue from 'vue'\n\ndescribe('Options data', () => {\n  it('should proxy and be reactive', done => {\n    const data = { msg: 'foo' }\n    const vm = new Vue({\n      data,\n      template: '<div>{{ msg }}</div>'\n    }).$mount()\n    expect(vm.$data).toEqual({ msg: 'foo' })\n    expect(vm.$data).toBe(data)\n    data.msg = 'bar'\n    waitForUpdate(() => {\n      expect(vm.$el.textContent).toBe('bar')\n    }).then(done)\n  })\n\n  it('should merge data properly', () => {\n    const Test = Vue.extend({\n      data () {\n        return { a: 1 }\n      }\n    })\n    let vm = new Test({\n      data: { b: 2 }\n    })\n    expect(vm.a).toBe(1)\n    expect(vm.b).toBe(2)\n    // no instance data\n    vm = new Test()\n    expect(vm.a).toBe(1)\n    // no child-val\n    const Extended = Test.extend({})\n    vm = new Extended()\n    expect(vm.a).toBe(1)\n    // recursively merge objects\n    const WithObject = Vue.extend({\n      data () {\n        return {\n          obj: {\n            a: 1\n          }\n        }\n      }\n    })\n    vm = new WithObject({\n      data: {\n        obj: {\n          b: 2\n        }\n      }\n    })\n    expect(vm.obj.a).toBe(1)\n    expect(vm.obj.b).toBe(2)\n  })\n\n  it('should warn non-function during extend', () => {\n    Vue.extend({\n      data: { msg: 'foo' }\n    })\n    expect('The \"data\" option should be a function').toHaveBeenWarned()\n  })\n\n  it('should warn non object return', () => {\n    new Vue({\n      data () {}\n    })\n    expect('data functions should return an object').toHaveBeenWarned()\n  })\n\n  it('should warn replacing root $data', () => {\n    const vm = new Vue({\n      data: {}\n    })\n    vm.$data = {}\n    expect('Avoid replacing instance root $data').toHaveBeenWarned()\n  })\n\n  it('should have access to props', () => {\n    const Test = {\n      props: ['a'],\n      render () {},\n      data () {\n        return {\n          b: this.a\n        }\n      }\n    }\n    const vm = new Vue({\n      template: `<test ref=\"test\" :a=\"1\"></test>`,\n      components: { Test }\n    }).$mount()\n    expect(vm.$refs.test.b).toBe(1)\n  })\n\n  it('props should not be reactive', done => {\n    let calls = 0\n    const vm = new Vue({\n      template: `<child :msg=\"msg\"></child>`,\n      data: {\n        msg: 'hello'\n      },\n      beforeUpdate () { calls++ },\n      components: {\n        child: {\n          template: `<span>{{ localMsg }}</span>`,\n          props: ['msg'],\n          data () {\n            return { localMsg: this.msg }\n          },\n          computed: {\n            computedMsg () {\n              return this.msg + ' world'\n            }\n          }\n        }\n      }\n    }).$mount()\n    const child = vm.$children[0]\n    vm.msg = 'hi'\n    waitForUpdate(() => {\n      expect(child.localMsg).toBe('hello')\n      expect(child.computedMsg).toBe('hi world')\n      expect(calls).toBe(1)\n    }).then(done)\n  })\n\n  it('should have access to methods', () => {\n    const vm = new Vue({\n      methods: {\n        get () {\n          return { a: 1 }\n        }\n      },\n      data () {\n        return this.get()\n      }\n    })\n    expect(vm.a).toBe(1)\n  })\n\n  it('should be called with this', () => {\n    const vm = new Vue({\n      template: '<div><child></child></div>',\n      provide: { foo: 1 },\n      components: {\n        child: {\n          template: '<span>{{bar}}</span>',\n          inject: ['foo'],\n          data ({ foo }) {\n            return { bar: 'foo:' + foo }\n          }\n        }\n      }\n    }).$mount()\n    expect(vm.$el.innerHTML).toBe('<span>foo:1</span>')\n  })\n\n  it('should be called with vm as first argument when merged', () => {\n    const superComponent = {\n      data: ({ foo }) => ({ ext: 'ext:' + foo })\n    }\n    const mixins = [\n      {\n        data: ({ foo }) => ({ mixin1: 'm1:' + foo })\n      },\n      {\n        data: ({ foo }) => ({ mixin2: 'm2:' + foo })\n      }\n    ]\n    const vm = new Vue({\n      template: '<div><child></child></div>',\n      provide: { foo: 1 },\n      components: {\n        child: {\n          extends: superComponent,\n          mixins,\n          template: '<span>{{bar}}-{{ext}}-{{mixin1}}-{{mixin2}}</span>',\n          inject: ['foo'],\n          data: ({ foo }) => ({ bar: 'foo:' + foo })\n        }\n      }\n    }).$mount()\n    expect(vm.$el.innerHTML).toBe('<span>foo:1-ext:1-m1:1-m2:1</span>')\n  })\n})\n"
  },
  {
    "path": "vue/test/unit/features/options/delimiters.spec.js",
    "content": "import Vue from 'vue'\n\ndescribe('Delimiters', () => {\n  it('default delimiters should work', () => {\n    const vm = new Vue({\n      data: {\n        a: 1\n      },\n      template: '<div>{{ a }}</div>'\n    }).$mount()\n    expect(vm.$el.textContent).toEqual('1')\n  })\n\n  it('custom delimiters should work', () => {\n    const vm = new Vue({\n      delimiters: ['[[', ']]'],\n      template: '<div>[[ a ]]</div>',\n      data: {\n        a: 1\n      }\n    }).$mount()\n\n    expect(vm.$el.textContent).toEqual('1')\n  })\n\n  it('default delimiters should be ignored when custom delimiters defined', () => {\n    const vm = new Vue({\n      delimiters: ['[[', ']]'],\n      template: '<div>{{ a }}</div>',\n      data: {\n        a: 1\n      }\n    }).$mount()\n\n    expect(vm.$el.textContent).toEqual('{{ a }}')\n  })\n\n  it('delimiters should only affect vm', () => {\n    const Component = Vue.extend({\n      data: function () {\n        return {\n          b: 2\n        }\n      },\n      template: '<span>[[ b ]]</span>'\n    })\n\n    const vm = new Vue({\n      delimiters: ['[[', ']]'],\n      template: '<div>[[ a ]] - <test-component></test-component></div>',\n      data: {\n        a: 2\n      },\n      components: {\n        'test-component': Component\n      }\n    }).$mount()\n\n    expect(vm.$el.textContent).toEqual('2 - [[ b ]]')\n  })\n\n  it('delimiters defined globally should work on all vms', () => {\n    Vue.options.delimiters = ['[[', ']]']\n\n    const Component = Vue.extend({\n      template: '<span>[[ a ]]</span>',\n      data: function () {\n        return {\n          a: 2\n        }\n      }\n    })\n\n    const vm = new Vue({\n      data: {\n        b: 1\n      },\n      template: '<div>[[ b ]] - <test-component></test-component></div>',\n      components: {\n        'test-component': Component\n      }\n    }).$mount()\n\n    expect(vm.$el.textContent).toEqual('1 - 2')\n    // restore default options\n    delete Vue.options.delimiters\n  })\n\n  it('component specific delimiters should override global delimiters', () => {\n    Vue.options.delimiters = ['[[', ']]']\n\n    const Component = Vue.extend({\n      delimiters: ['@{{', '}}'],\n      template: '<span>@{{ a }}</span>',\n      data: function () {\n        return {\n          a: 2\n        }\n      }\n    })\n\n    const vm = new Vue({\n      data: {\n        b: 1\n      },\n      template: '<div>[[ b ]] - <test-component></test-component></div>',\n      components: {\n        'test-component': Component\n      }\n    }).$mount()\n\n    expect(vm.$el.textContent).toEqual('1 - 2')\n    // restore default options\n    delete Vue.options.delimiters\n  })\n})\n"
  },
  {
    "path": "vue/test/unit/features/options/directives.spec.js",
    "content": "import Vue from 'vue'\n\ndescribe('Options directives', () => {\n  it('basic usage', done => {\n    const bindSpy = jasmine.createSpy('bind')\n    const insertedSpy = jasmine.createSpy('inserted')\n    const updateSpy = jasmine.createSpy('update')\n    const componentUpdatedSpy = jasmine.createSpy('componentUpdated')\n    const unbindSpy = jasmine.createSpy('unbind')\n\n    const assertContext = (el, binding, vnode) => {\n      expect(vnode.context).toBe(vm)\n      expect(binding.arg).toBe('arg')\n      expect(binding.modifiers).toEqual({ hello: true })\n    }\n\n    const vm = new Vue({\n      template: '<div class=\"hi\"><div v-if=\"ok\" v-test:arg.hello=\"a\">{{ msg }}</div></div>',\n      data: {\n        msg: 'hi',\n        a: 'foo',\n        ok: true\n      },\n      directives: {\n        test: {\n          bind (el, binding, vnode) {\n            bindSpy()\n            assertContext(el, binding, vnode)\n            expect(binding.value).toBe('foo')\n            expect(binding.expression).toBe('a')\n            expect(binding.oldValue).toBeUndefined()\n            expect(el.parentNode).toBeNull()\n          },\n          inserted (el, binding, vnode) {\n            insertedSpy()\n            assertContext(el, binding, vnode)\n            expect(binding.value).toBe('foo')\n            expect(binding.expression).toBe('a')\n            expect(binding.oldValue).toBeUndefined()\n            expect(el.parentNode.className).toBe('hi')\n          },\n          update (el, binding, vnode, oldVnode) {\n            updateSpy()\n            assertContext(el, binding, vnode)\n            expect(el).toBe(vm.$el.children[0])\n            expect(oldVnode).not.toBe(vnode)\n            expect(binding.expression).toBe('a')\n            if (binding.value !== binding.oldValue) {\n              expect(binding.value).toBe('bar')\n              expect(binding.oldValue).toBe('foo')\n            }\n          },\n          componentUpdated (el, binding, vnode) {\n            componentUpdatedSpy()\n            assertContext(el, binding, vnode)\n          },\n          unbind (el, binding, vnode) {\n            unbindSpy()\n            assertContext(el, binding, vnode)\n          }\n        }\n      }\n    })\n\n    vm.$mount()\n    expect(bindSpy).toHaveBeenCalled()\n    expect(insertedSpy).toHaveBeenCalled()\n    expect(updateSpy).not.toHaveBeenCalled()\n    expect(componentUpdatedSpy).not.toHaveBeenCalled()\n    expect(unbindSpy).not.toHaveBeenCalled()\n    vm.a = 'bar'\n    waitForUpdate(() => {\n      expect(updateSpy).toHaveBeenCalled()\n      expect(componentUpdatedSpy).toHaveBeenCalled()\n      expect(unbindSpy).not.toHaveBeenCalled()\n      vm.msg = 'bye'\n    }).then(() => {\n      expect(componentUpdatedSpy.calls.count()).toBe(2)\n      vm.ok = false\n    }).then(() => {\n      expect(unbindSpy).toHaveBeenCalled()\n    }).then(done)\n  })\n\n  it('function shorthand', done => {\n    const spy = jasmine.createSpy('directive')\n    const vm = new Vue({\n      template: '<div v-test:arg.hello=\"a\"></div>',\n      data: { a: 'foo' },\n      directives: {\n        test (el, binding, vnode) {\n          expect(vnode.context).toBe(vm)\n          expect(binding.arg).toBe('arg')\n          expect(binding.modifiers).toEqual({ hello: true })\n          spy(binding.value, binding.oldValue)\n        }\n      }\n    })\n    vm.$mount()\n    expect(spy).toHaveBeenCalledWith('foo', undefined)\n    vm.a = 'bar'\n    waitForUpdate(() => {\n      expect(spy).toHaveBeenCalledWith('bar', 'foo')\n    }).then(done)\n  })\n\n  it('function shorthand (global)', done => {\n    const spy = jasmine.createSpy('directive')\n    Vue.directive('test', function (el, binding, vnode) {\n      expect(vnode.context).toBe(vm)\n      expect(binding.arg).toBe('arg')\n      expect(binding.modifiers).toEqual({ hello: true })\n      spy(binding.value, binding.oldValue)\n    })\n    const vm = new Vue({\n      template: '<div v-test:arg.hello=\"a\"></div>',\n      data: { a: 'foo' }\n    })\n    vm.$mount()\n    expect(spy).toHaveBeenCalledWith('foo', undefined)\n    vm.a = 'bar'\n    waitForUpdate(() => {\n      expect(spy).toHaveBeenCalledWith('bar', 'foo')\n      delete Vue.options.directives.test\n    }).then(done)\n  })\n\n  it('should teardown directives on old vnodes when new vnodes have none', done => {\n    const vm = new Vue({\n      data: {\n        ok: true\n      },\n      template: `\n        <div>\n          <div v-if=\"ok\" v-test>a</div>\n          <div v-else class=\"b\">b</div>\n        </div>\n      `,\n      directives: {\n        test: {\n          bind: el => { el.id = 'a' },\n          unbind: el => { el.id = '' }\n        }\n      }\n    }).$mount()\n    expect(vm.$el.children[0].id).toBe('a')\n    vm.ok = false\n    waitForUpdate(() => {\n      expect(vm.$el.children[0].id).toBe('')\n      expect(vm.$el.children[0].className).toBe('b')\n    }).then(done)\n  })\n\n  it('should properly handle same node with different directive sets', done => {\n    const spies = {}\n    const createSpy = name => (spies[name] = jasmine.createSpy(name))\n    const vm = new Vue({\n      data: {\n        ok: true,\n        val: 123\n      },\n      template: `\n        <div>\n          <div v-if=\"ok\" v-test=\"val\" v-test.hi=\"val\"></div>\n          <div v-if=\"!ok\" v-test.hi=\"val\" v-test2=\"val\"></div>\n        </div>\n      `,\n      directives: {\n        test: {\n          bind: createSpy('bind1'),\n          inserted: createSpy('inserted1'),\n          update: createSpy('update1'),\n          componentUpdated: createSpy('componentUpdated1'),\n          unbind: createSpy('unbind1')\n        },\n        test2: {\n          bind: createSpy('bind2'),\n          inserted: createSpy('inserted2'),\n          update: createSpy('update2'),\n          componentUpdated: createSpy('componentUpdated2'),\n          unbind: createSpy('unbind2')\n        }\n      }\n    }).$mount()\n\n    expect(spies.bind1.calls.count()).toBe(2)\n    expect(spies.inserted1.calls.count()).toBe(2)\n    expect(spies.bind2.calls.count()).toBe(0)\n    expect(spies.inserted2.calls.count()).toBe(0)\n\n    vm.ok = false\n    waitForUpdate(() => {\n      // v-test with modifier should be updated\n      expect(spies.update1.calls.count()).toBe(1)\n      expect(spies.componentUpdated1.calls.count()).toBe(1)\n\n      // v-test without modifier should be unbound\n      expect(spies.unbind1.calls.count()).toBe(1)\n\n      // v-test2 should be bound\n      expect(spies.bind2.calls.count()).toBe(1)\n      expect(spies.inserted2.calls.count()).toBe(1)\n\n      vm.ok = true\n    }).then(() => {\n      // v-test without modifier should be bound again\n      expect(spies.bind1.calls.count()).toBe(3)\n      expect(spies.inserted1.calls.count()).toBe(3)\n\n      // v-test2 should be unbound\n      expect(spies.unbind2.calls.count()).toBe(1)\n\n      // v-test with modifier should be updated again\n      expect(spies.update1.calls.count()).toBe(2)\n      expect(spies.componentUpdated1.calls.count()).toBe(2)\n\n      vm.val = 234\n    }).then(() => {\n      expect(spies.update1.calls.count()).toBe(4)\n      expect(spies.componentUpdated1.calls.count()).toBe(4)\n    }).then(done)\n  })\n\n  it('warn non-existent', () => {\n    new Vue({\n      template: '<div v-test></div>'\n    }).$mount()\n    expect('Failed to resolve directive: test').toHaveBeenWarned()\n  })\n\n  // #6513\n  it('should invoke unbind & inserted on inner component root element change', done => {\n    const dir = {\n      bind: jasmine.createSpy('bind'),\n      inserted: jasmine.createSpy('inserted'),\n      unbind: jasmine.createSpy('unbind')\n    }\n\n    const Child = {\n      template: `<div v-if=\"ok\"/><span v-else/>`,\n      data: () => ({ ok: true })\n    }\n\n    const vm = new Vue({\n      template: `<child ref=\"child\" v-test />`,\n      directives: { test: dir },\n      components: { Child }\n    }).$mount()\n\n    const oldEl = vm.$el\n    expect(dir.bind.calls.count()).toBe(1)\n    expect(dir.bind.calls.argsFor(0)[0]).toBe(oldEl)\n    expect(dir.inserted.calls.count()).toBe(1)\n    expect(dir.inserted.calls.argsFor(0)[0]).toBe(oldEl)\n    expect(dir.unbind).not.toHaveBeenCalled()\n\n    vm.$refs.child.ok = false\n    waitForUpdate(() => {\n      expect(vm.$el.tagName).toBe('SPAN')\n      expect(dir.bind.calls.count()).toBe(2)\n      expect(dir.bind.calls.argsFor(1)[0]).toBe(vm.$el)\n      expect(dir.inserted.calls.count()).toBe(2)\n      expect(dir.inserted.calls.argsFor(1)[0]).toBe(vm.$el)\n      expect(dir.unbind.calls.count()).toBe(1)\n      expect(dir.unbind.calls.argsFor(0)[0]).toBe(oldEl)\n    }).then(done)\n  })\n})\n"
  },
  {
    "path": "vue/test/unit/features/options/el.spec.js",
    "content": "import Vue from 'vue'\n\ndescribe('Options el', () => {\n  it('basic usage', () => {\n    const el = document.createElement('div')\n    el.innerHTML = '<span>{{message}}</span>'\n    const vm = new Vue({\n      el,\n      data: { message: 'hello world' }\n    })\n    expect(vm.$el.tagName).toBe('DIV')\n    expect(vm.$el.textContent).toBe(vm.message)\n  })\n\n  it('should be replaced when use together with `template` option', () => {\n    const el = document.createElement('div')\n    el.innerHTML = '<span>{{message}}</span>'\n    const vm = new Vue({\n      el,\n      template: '<p id=\"app\"><span>{{message}}</span></p>',\n      data: { message: 'hello world' }\n    })\n    expect(vm.$el.tagName).toBe('P')\n    expect(vm.$el.textContent).toBe(vm.message)\n  })\n\n  it('should be replaced when use together with `render` option', () => {\n    const el = document.createElement('div')\n    el.innerHTML = '<span>{{message}}</span>'\n    const vm = new Vue({\n      el,\n      render (h) {\n        return h('p', { staticAttrs: { id: 'app' }}, [\n          h('span', {}, [this.message])\n        ])\n      },\n      data: { message: 'hello world' }\n    })\n    expect(vm.$el.tagName).toBe('P')\n    expect(vm.$el.textContent).toBe(vm.message)\n  })\n\n  it('svg element', () => {\n    const parent = document.createElement('div')\n    parent.innerHTML =\n      '<svg>' +\n        '<text :x=\"x\" :y=\"y\" :fill=\"color\">{{ text }}</text>' +\n        '<g><clipPath><foo></foo></clipPath></g>' +\n      '</svg>'\n    const vm = new Vue({\n      el: parent.childNodes[0],\n      data: {\n        x: 64,\n        y: 128,\n        color: 'red',\n        text: 'svg text'\n      }\n    })\n    expect(vm.$el.tagName).toBe('svg')\n    expect(vm.$el.childNodes[0].getAttribute('x')).toBe(vm.x.toString())\n    expect(vm.$el.childNodes[0].getAttribute('y')).toBe(vm.y.toString())\n    expect(vm.$el.childNodes[0].getAttribute('fill')).toBe(vm.color)\n    expect(vm.$el.childNodes[0].textContent).toBe(vm.text)\n    // nested, non-explicitly listed SVG elements\n    expect(vm.$el.childNodes[1].childNodes[0].namespaceURI).toContain('svg')\n    expect(vm.$el.childNodes[1].childNodes[0].childNodes[0].namespaceURI).toContain('svg')\n  })\n\n  // https://w3c.github.io/DOM-Parsing/#dfn-serializing-an-attribute-value\n  it('properly decode attribute values when parsing templates from DOM', () => {\n    const el = document.createElement('div')\n    el.innerHTML = '<a href=\"/a?foo=bar&baz=qux\" name=\"<abc>\" single=\\'\"hi\"\\'></a>'\n    const vm = new Vue({ el })\n    expect(vm.$el.children[0].getAttribute('href')).toBe('/a?foo=bar&baz=qux')\n    expect(vm.$el.children[0].getAttribute('name')).toBe('<abc>')\n    expect(vm.$el.children[0].getAttribute('single')).toBe('\"hi\"')\n  })\n\n  it('decode attribute value newlines when parsing templates from DOM in IE', () => {\n    const el = document.createElement('div')\n    el.innerHTML = `<a :style=\"{\\ncolor:'red'\\n}\"></a>`\n    const vm = new Vue({ el })\n    expect(vm.$el.children[0].style.color).toBe('red')\n  })\n\n  it('warn cannot find element', () => {\n    new Vue({ el: '#non-existent' })\n    expect('Cannot find element: #non-existent').toHaveBeenWarned()\n  })\n})\n"
  },
  {
    "path": "vue/test/unit/features/options/errorCaptured.spec.js",
    "content": "import Vue from 'vue'\n\ndescribe('Options errorCaptured', () => {\n  let globalSpy\n\n  beforeEach(() => {\n    globalSpy = Vue.config.errorHandler = jasmine.createSpy()\n  })\n\n  afterEach(() => {\n    Vue.config.errorHandler = null\n  })\n\n  it('should capture error from child component', () => {\n    const spy = jasmine.createSpy()\n\n    let child\n    let err\n    const Child = {\n      created () {\n        child = this\n        err = new Error('child')\n        throw err\n      },\n      render () {}\n    }\n\n    new Vue({\n      errorCaptured: spy,\n      render: h => h(Child)\n    }).$mount()\n\n    expect(spy).toHaveBeenCalledWith(err, child, 'created hook')\n    // should propagate by default\n    expect(globalSpy).toHaveBeenCalledWith(err, child, 'created hook')\n  })\n\n  it('should be able to render the error in itself', done => {\n    let child\n    const Child = {\n      created () {\n        child = this\n        throw new Error('error from child')\n      },\n      render () {}\n    }\n\n    const vm = new Vue({\n      data: {\n        error: null\n      },\n      errorCaptured (e, vm, info) {\n        expect(vm).toBe(child)\n        this.error = e.toString() + ' in ' + info\n      },\n      render (h) {\n        if (this.error) {\n          return h('pre', this.error)\n        }\n        return h(Child)\n      }\n    }).$mount()\n\n    waitForUpdate(() => {\n      expect(vm.$el.textContent).toContain('error from child')\n      expect(vm.$el.textContent).toContain('in created hook')\n    }).then(done)\n  })\n\n  it('should not propagate to global handler when returning true', () => {\n    const spy = jasmine.createSpy()\n\n    let child\n    let err\n    const Child = {\n      created () {\n        child = this\n        err = new Error('child')\n        throw err\n      },\n      render () {}\n    }\n\n    new Vue({\n      errorCaptured (err, vm, info) {\n        spy(err, vm, info)\n        return false\n      },\n      render: h => h(Child, {})\n    }).$mount()\n\n    expect(spy).toHaveBeenCalledWith(err, child, 'created hook')\n    // should not propagate\n    expect(globalSpy).not.toHaveBeenCalled()\n  })\n\n  it('should propagate to global handler if itself throws error', () => {\n    let child\n    let err\n    const Child = {\n      created () {\n        child = this\n        err = new Error('child')\n        throw err\n      },\n      render () {}\n    }\n\n    let err2\n    const vm = new Vue({\n      errorCaptured () {\n        err2 = new Error('foo')\n        throw err2\n      },\n      render: h => h(Child, {})\n    }).$mount()\n\n    expect(globalSpy).toHaveBeenCalledWith(err, child, 'created hook')\n    expect(globalSpy).toHaveBeenCalledWith(err2, vm, 'errorCaptured hook')\n  })\n\n  it('should work across multiple parents, mixins and extends', () => {\n    const calls = []\n\n    const Child = {\n      created () {\n        throw new Error('child')\n      },\n      render () {}\n    }\n\n    const ErrorBoundaryBase = {\n      errorCaptured () {\n        calls.push(1)\n      }\n    }\n\n    const mixin = {\n      errorCaptured () {\n        calls.push(2)\n      }\n    }\n\n    const ErrorBoundaryExtended = {\n      extends: ErrorBoundaryBase,\n      mixins: [mixin],\n      errorCaptured () {\n        calls.push(3)\n      },\n      render: h => h(Child)\n    }\n\n    Vue.config.errorHandler = () => {\n      calls.push(5)\n    }\n\n    new Vue({\n      errorCaptured () {\n        calls.push(4)\n      },\n      render: h => h(ErrorBoundaryExtended)\n    }).$mount()\n\n    expect(calls).toEqual([1, 2, 3, 4, 5])\n  })\n\n  it('should work across multiple parents, mixins and extends with return false', () => {\n    const calls = []\n\n    const Child = {\n      created () {\n        throw new Error('child')\n      },\n      render () {}\n    }\n\n    const ErrorBoundaryBase = {\n      errorCaptured () {\n        calls.push(1)\n      }\n    }\n\n    const mixin = {\n      errorCaptured () {\n        calls.push(2)\n      }\n    }\n\n    const ErrorBoundaryExtended = {\n      extends: ErrorBoundaryBase,\n      mixins: [mixin],\n      errorCaptured () {\n        calls.push(3)\n        return false\n      },\n      render: h => h(Child)\n    }\n\n    Vue.config.errorHandler = () => {\n      calls.push(5)\n    }\n\n    new Vue({\n      errorCaptured () {\n        calls.push(4)\n      },\n      render: h => h(ErrorBoundaryExtended)\n    }).$mount()\n\n    expect(calls).toEqual([1, 2, 3])\n  })\n})\n"
  },
  {
    "path": "vue/test/unit/features/options/extends.spec.js",
    "content": "import Vue from 'vue'\nimport { nativeWatch } from 'core/util/env'\n\ndescribe('Options extends', () => {\n  it('should work on objects', () => {\n    const A = {\n      data () {\n        return { a: 1 }\n      }\n    }\n    const B = {\n      extends: A,\n      data () {\n        return { b: 2 }\n      }\n    }\n    const vm = new Vue({\n      extends: B,\n      data: {\n        c: 3\n      }\n    })\n    expect(vm.a).toBe(1)\n    expect(vm.b).toBe(2)\n    expect(vm.c).toBe(3)\n  })\n\n  it('should work on extended constructors', () => {\n    const A = Vue.extend({\n      data () {\n        return { a: 1 }\n      }\n    })\n    const B = Vue.extend({\n      extends: A,\n      data () {\n        return { b: 2 }\n      }\n    })\n    const vm = new Vue({\n      extends: B,\n      data: {\n        c: 3\n      }\n    })\n    expect(vm.a).toBe(1)\n    expect(vm.b).toBe(2)\n    expect(vm.c).toBe(3)\n  })\n\n  if (nativeWatch) {\n    it('should work with global mixins + Object.prototype.watch', done => {\n      Vue.mixin({})\n\n      const spy = jasmine.createSpy('watch')\n      const A = Vue.extend({\n        data: function () {\n          return { a: 1 }\n        },\n        watch: {\n          a: spy\n        },\n        created: function () {\n          this.a = 2\n        }\n      })\n      new Vue({\n        extends: A\n      })\n      waitForUpdate(() => {\n        expect(spy).toHaveBeenCalledWith(2, 1)\n      }).then(done)\n    })\n  }\n})\n"
  },
  {
    "path": "vue/test/unit/features/options/functional.spec.js",
    "content": "import Vue from 'vue'\nimport { createEmptyVNode } from 'core/vdom/vnode'\n\ndescribe('Options functional', () => {\n  it('should work', done => {\n    const vm = new Vue({\n      data: { test: 'foo' },\n      template: '<div><wrap :msg=\"test\">bar</wrap></div>',\n      components: {\n        wrap: {\n          functional: true,\n          props: ['msg'],\n          render (h, { props, children }) {\n            return h('div', null, [props.msg, ' '].concat(children))\n          }\n        }\n      }\n    }).$mount()\n    expect(vm.$el.innerHTML).toBe('<div>foo bar</div>')\n    vm.test = 'qux'\n    waitForUpdate(() => {\n      expect(vm.$el.innerHTML).toBe('<div>qux bar</div>')\n    }).then(done)\n  })\n\n  it('should expose all props when not declared', done => {\n    const fn = {\n      functional: true,\n      render (h, { props }) {\n        return h('div', `${props.msg} ${props.kebabMsg}`)\n      }\n    }\n\n    const vm = new Vue({\n      data: { test: 'foo' },\n      render (h) {\n        return h('div', [\n          h(fn, {\n            props: { msg: this.test },\n            attrs: { 'kebab-msg': 'bar' }\n          })\n        ])\n      }\n    }).$mount()\n\n    expect(vm.$el.innerHTML).toBe('<div>foo bar</div>')\n    vm.test = 'qux'\n    waitForUpdate(() => {\n      expect(vm.$el.innerHTML).toBe('<div>qux bar</div>')\n    }).then(done)\n  })\n\n  it('should expose data.on as listeners', () => {\n    const foo = jasmine.createSpy('foo')\n    const bar = jasmine.createSpy('bar')\n    const vm = new Vue({\n      template: '<div><wrap @click=\"foo\" @test=\"bar\"/></div>',\n      methods: { foo, bar },\n      components: {\n        wrap: {\n          functional: true,\n          render (h, { listeners }) {\n            return h('div', {\n              on: {\n                click: [listeners.click, () => listeners.test('bar')]\n              }\n            })\n          }\n        }\n      }\n    }).$mount()\n\n    triggerEvent(vm.$el.children[0], 'click')\n    expect(foo).toHaveBeenCalled()\n    expect(foo.calls.argsFor(0)[0].type).toBe('click') // should have click event\n    triggerEvent(vm.$el.children[0], 'mousedown')\n    expect(bar).toHaveBeenCalledWith('bar')\n  })\n\n  it('should support returning more than one root node', () => {\n    const vm = new Vue({\n      template: `<div><test></test></div>`,\n      components: {\n        test: {\n          functional: true,\n          render (h) {\n            return [h('span', 'foo'), h('span', 'bar')]\n          }\n        }\n      }\n    }).$mount()\n    expect(vm.$el.innerHTML).toBe('<span>foo</span><span>bar</span>')\n  })\n\n  it('should support slots', () => {\n    const vm = new Vue({\n      data: { test: 'foo' },\n      template: '<div><wrap><div slot=\"a\">foo</div><div slot=\"b\">bar</div></wrap></div>',\n      components: {\n        wrap: {\n          functional: true,\n          props: ['msg'],\n          render (h, { slots }) {\n            slots = slots()\n            return h('div', null, [slots.b, slots.a])\n          }\n        }\n      }\n    }).$mount()\n    expect(vm.$el.innerHTML).toBe('<div><div>bar</div><div>foo</div></div>')\n  })\n\n  it('should let vnode raw data pass through', done => {\n    const onValid = jasmine.createSpy('valid')\n    const vm = new Vue({\n      data: { msg: 'hello' },\n      template: `<div>\n        <validate field=\"field1\" @valid=\"onValid\">\n          <input type=\"text\" v-model=\"msg\">\n        </validate>\n      </div>`,\n      components: {\n        validate: {\n          functional: true,\n          props: ['field'],\n          render (h, { props, children, data: { on }}) {\n            props.child = children[0]\n            return h('validate-control', { props, on })\n          }\n        },\n        'validate-control': {\n          props: ['field', 'child'],\n          render () {\n            return this.child\n          },\n          mounted () {\n            this.$el.addEventListener('input', this.onInput)\n          },\n          destroyed () {\n            this.$el.removeEventListener('input', this.onInput)\n          },\n          methods: {\n            onInput (e) {\n              const value = e.target.value\n              if (this.validate(value)) {\n                this.$emit('valid', this)\n              }\n            },\n            // something validation logic here\n            validate (val) {\n              return val.length > 0\n            }\n          }\n        }\n      },\n      methods: { onValid }\n    }).$mount()\n    document.body.appendChild(vm.$el)\n    const input = vm.$el.querySelector('input')\n    expect(onValid).not.toHaveBeenCalled()\n    waitForUpdate(() => {\n      input.value = 'foo'\n      triggerEvent(input, 'input')\n    }).then(() => {\n      expect(onValid).toHaveBeenCalled()\n    }).then(() => {\n      document.body.removeChild(vm.$el)\n      vm.$destroy()\n    }).then(done)\n  })\n\n  it('create empty vnode when render return null', () => {\n    const child = {\n      functional: true,\n      render () {\n        return null\n      }\n    }\n    const vm = new Vue({\n      components: {\n        child\n      }\n    })\n    const h = vm.$createElement\n    const vnode = h('child')\n    expect(vnode).toEqual(createEmptyVNode())\n  })\n\n  // #7282\n  it('should normalize top-level arrays', () => {\n    const Foo = {\n      functional: true,\n      render (h) {\n        return [h('span', 'hi'), null]\n      }\n    }\n    const vm = new Vue({\n      template: `<div><foo/></div>`,\n      components: { Foo }\n    }).$mount()\n    expect(vm.$el.innerHTML).toBe('<span>hi</span>')\n  })\n\n  it('should work when used as named slot and returning array', () => {\n    const Foo = {\n      template: `<div><slot name=\"test\"/></div>`\n    }\n\n    const Bar = {\n      functional: true,\n      render: h => ([\n        h('div', 'one'),\n        h('div', 'two'),\n        h(Baz)\n      ])\n    }\n\n    const Baz = {\n      functional: true,\n      render: h => h('div', 'three')\n    }\n\n    const vm = new Vue({\n      template: `<foo><bar slot=\"test\"/></foo>`,\n      components: { Foo, Bar }\n    }).$mount()\n\n    expect(vm.$el.innerHTML).toBe('<div>one</div><div>two</div><div>three</div>')\n  })\n\n  it('should apply namespace when returning arrays', () => {\n    const Child = {\n      functional: true,\n      render: h => ([h('foo'), h('bar')])\n    }\n    const vm = new Vue({\n      template: `<svg><child/></svg>`,\n      components: { Child }\n    }).$mount()\n\n    expect(vm.$el.childNodes[0].namespaceURI).toContain('svg')\n    expect(vm.$el.childNodes[1].namespaceURI).toContain('svg')\n  })\n\n  it('should work with render fns compiled from template', done => {\n    // code generated via vue-template-es2015-compiler\n    var render = function (_h, _vm) {\n      var _c = _vm._c\n      return _c(\n        'div',\n        [\n          _c('h2', { staticClass: 'red' }, [_vm._v(_vm._s(_vm.props.msg))]),\n          _vm._t('default'),\n          _vm._t('slot2'),\n          _vm._t('scoped', null, { msg: _vm.props.msg }),\n          _vm._m(0),\n          _c('div', { staticClass: 'clickable', on: { click: _vm.parent.fn }}, [\n            _vm._v('click me')\n          ])\n        ],\n        2\n      )\n    }\n    var staticRenderFns = [\n      function (_h, _vm) {\n        var _c = _vm._c\n        return _c('div', [_vm._v('Some '), _c('span', [_vm._v('text')])])\n      }\n    ]\n\n    const child = {\n      functional: true,\n      _compiled: true,\n      render,\n      staticRenderFns\n    }\n\n    const parent = new Vue({\n      components: {\n        child\n      },\n      data: {\n        msg: 'hello'\n      },\n      template: `\n      <div>\n        <child :msg=\"msg\">\n          <span>{{ msg }}</span>\n          <div slot=\"slot2\">Second slot</div>\n          <template slot=\"scoped\" slot-scope=\"scope\">{{ scope.msg }}</template>\n        </child>\n      </div>\n      `,\n      methods: {\n        fn () {\n          this.msg = 'bye'\n        }\n      }\n    }).$mount()\n\n    function assertMarkup () {\n      expect(parent.$el.innerHTML).toBe(\n        `<div>` +\n          `<h2 class=\"red\">${parent.msg}</h2>` +\n          `<span>${parent.msg}</span> ` +\n          `<div>Second slot</div>` +\n          parent.msg +\n          // static\n          `<div>Some <span>text</span></div>` +\n          `<div class=\"clickable\">click me</div>` +\n        `</div>`\n      )\n    }\n\n    assertMarkup()\n    triggerEvent(parent.$el.querySelector('.clickable'), 'click')\n    waitForUpdate(assertMarkup).then(done)\n  })\n})\n"
  },
  {
    "path": "vue/test/unit/features/options/inheritAttrs.spec.js",
    "content": "import Vue from 'vue'\n\ndescribe('Options inheritAttrs', () => {\n  it('should work', done => {\n    const vm = new Vue({\n      template: `<foo :id=\"foo\"/>`,\n      data: { foo: 'foo' },\n      components: {\n        foo: {\n          inheritAttrs: false,\n          template: `<div>foo</div>`\n        }\n      }\n    }).$mount()\n    expect(vm.$el.id).toBe('')\n    vm.foo = 'bar'\n    waitForUpdate(() => {\n      expect(vm.$el.id).toBe('')\n    }).then(done)\n  })\n\n  it('with inner v-bind', done => {\n    const vm = new Vue({\n      template: `<foo :id=\"foo\"/>`,\n      data: { foo: 'foo' },\n      components: {\n        foo: {\n          inheritAttrs: false,\n          template: `<div><div v-bind=\"$attrs\"></div></div>`\n        }\n      }\n    }).$mount()\n    expect(vm.$el.children[0].id).toBe('foo')\n    vm.foo = 'bar'\n    waitForUpdate(() => {\n      expect(vm.$el.children[0].id).toBe('bar')\n    }).then(done)\n  })\n})\n"
  },
  {
    "path": "vue/test/unit/features/options/inject.spec.js",
    "content": "import Vue from 'vue'\nimport { Observer } from 'core/observer/index'\nimport { isNative, isObject, hasOwn } from 'core/util/index'\nimport testObjectOption from '../../../helpers/test-object-option'\n\ndescribe('Options provide/inject', () => {\n  testObjectOption('inject')\n\n  let injected\n  const injectedComp = {\n    inject: ['foo', 'bar'],\n    render () {},\n    created () {\n      injected = [this.foo, this.bar]\n    }\n  }\n\n  beforeEach(() => {\n    injected = null\n  })\n\n  it('should work', () => {\n    new Vue({\n      template: `<child/>`,\n      provide: {\n        foo: 1,\n        bar: false\n      },\n      components: {\n        child: {\n          template: `<injected-comp/>`,\n          components: {\n            injectedComp\n          }\n        }\n      }\n    }).$mount()\n\n    expect(injected).toEqual([1, false])\n  })\n\n  it('should use closest parent', () => {\n    new Vue({\n      template: `<child/>`,\n      provide: {\n        foo: 1,\n        bar: null\n      },\n      components: {\n        child: {\n          provide: {\n            foo: 3\n          },\n          template: `<injected-comp/>`,\n          components: {\n            injectedComp\n          }\n        }\n      }\n    }).$mount()\n\n    expect(injected).toEqual([3, null])\n  })\n\n  it('provide function', () => {\n    new Vue({\n      template: `<child/>`,\n      data: {\n        a: 1,\n        b: false\n      },\n      provide () {\n        return {\n          foo: this.a,\n          bar: this.b\n        }\n      },\n      components: {\n        child: {\n          template: `<injected-comp/>`,\n          components: {\n            injectedComp\n          }\n        }\n      }\n    }).$mount()\n\n    expect(injected).toEqual([1, false])\n  })\n\n  it('inject with alias', () => {\n    const injectAlias = {\n      inject: {\n        baz: 'foo',\n        qux: 'bar'\n      },\n      render () {},\n      created () {\n        injected = [this.baz, this.qux]\n      }\n    }\n\n    new Vue({\n      template: `<child/>`,\n      provide: {\n        foo: false,\n        bar: 2\n      },\n      components: {\n        child: {\n          template: `<inject-alias/>`,\n          components: {\n            injectAlias\n          }\n        }\n      }\n    }).$mount()\n\n    expect(injected).toEqual([false, 2])\n  })\n\n  it('inject before resolving data/props', () => {\n    const vm = new Vue({\n      provide: {\n        foo: 1\n      }\n    })\n\n    const child = new Vue({\n      parent: vm,\n      inject: ['foo'],\n      data () {\n        return {\n          bar: this.foo + 1\n        }\n      },\n      props: {\n        baz: {\n          default () {\n            return this.foo + 2\n          }\n        }\n      }\n    })\n\n    expect(child.foo).toBe(1)\n    expect(child.bar).toBe(2)\n    expect(child.baz).toBe(3)\n  })\n\n  // GitHub issue #5194\n  it('should work with functional', () => {\n    new Vue({\n      template: `<child/>`,\n      provide: {\n        foo: 1,\n        bar: false\n      },\n      components: {\n        child: {\n          functional: true,\n          inject: ['foo', 'bar'],\n          render (h, context) {\n            const { injections } = context\n            injected = [injections.foo, injections.bar]\n          }\n        }\n      }\n    }).$mount()\n\n    expect(injected).toEqual([1, false])\n  })\n\n  if (typeof Reflect !== 'undefined' && isNative(Reflect.ownKeys)) {\n    it('with Symbol keys', () => {\n      const s = Symbol()\n      const vm = new Vue({\n        template: `<child/>`,\n        provide: {\n          [s]: 123\n        },\n        components: {\n          child: {\n            inject: { s },\n            template: `<div>{{ s }}</div>`\n          }\n        }\n      }).$mount()\n      expect(vm.$el.textContent).toBe('123')\n    })\n  }\n\n  // GitHub issue #5223\n  it('should work with reactive array', done => {\n    const vm = new Vue({\n      template: `<div><child></child></div>`,\n      data () {\n        return {\n          foo: []\n        }\n      },\n      provide () {\n        return {\n          foo: this.foo\n        }\n      },\n      components: {\n        child: {\n          inject: ['foo'],\n          template: `<span>{{foo.length}}</span>`\n        }\n      }\n    }).$mount()\n\n    expect(vm.$el.innerHTML).toEqual(`<span>0</span>`)\n    vm.foo.push(vm.foo.length)\n    vm.$nextTick(() => {\n      expect(vm.$el.innerHTML).toEqual(`<span>1</span>`)\n      vm.foo.pop()\n      vm.$nextTick(() => {\n        expect(vm.$el.innerHTML).toEqual(`<span>0</span>`)\n        done()\n      })\n    })\n  })\n\n  it('should extend properly', () => {\n    const parent = Vue.extend({\n      template: `<span/>`,\n      inject: ['foo']\n    })\n\n    const child = parent.extend({\n      template: `<span/>`,\n      inject: ['bar'],\n      created () {\n        injected = [this.foo, this.bar]\n      }\n    })\n\n    new Vue({\n      template: `<div><parent/><child/></div>`,\n      provide: {\n        foo: 1,\n        bar: false\n      },\n      components: {\n        parent,\n        child\n      }\n    }).$mount()\n\n    expect(injected).toEqual([1, false])\n  })\n\n  it('should merge from mixins properly (objects)', () => {\n    const mixinA = { inject: { foo: 'foo' }}\n    const mixinB = { inject: { bar: 'bar' }}\n    const child = {\n      mixins: [mixinA, mixinB],\n      template: `<span/>`,\n      created () {\n        injected = [this.foo, this.bar]\n      }\n    }\n    new Vue({\n      provide: { foo: 'foo', bar: 'bar', baz: 'baz' },\n      render (h) {\n        return h(child)\n      }\n    }).$mount()\n\n    expect(injected).toEqual(['foo', 'bar'])\n  })\n\n  it('should merge from mixins properly (arrays)', () => {\n    const mixinA = { inject: ['foo'] }\n    const mixinB = { inject: ['bar'] }\n    const child = {\n      mixins: [mixinA, mixinB],\n      inject: ['baz'],\n      template: `<span/>`,\n      created () {\n        injected = [this.foo, this.bar, this.baz]\n      }\n    }\n    new Vue({\n      provide: { foo: 'foo', bar: 'bar', baz: 'baz' },\n      render (h) {\n        return h(child)\n      }\n    }).$mount()\n\n    expect(injected).toEqual(['foo', 'bar', 'baz'])\n  })\n\n  it('should merge from mixins properly (mix of objects and arrays)', () => {\n    const mixinA = { inject: { foo: 'foo' }}\n    const mixinB = { inject: ['bar'] }\n    const child = {\n      mixins: [mixinA, mixinB],\n      inject: { qux: 'baz' },\n      template: `<span/>`,\n      created () {\n        injected = [this.foo, this.bar, this.qux]\n      }\n    }\n    new Vue({\n      provide: { foo: 'foo', bar: 'bar', baz: 'baz' },\n      render (h) {\n        return h(child)\n      }\n    }).$mount()\n\n    expect(injected).toEqual(['foo', 'bar', 'baz'])\n  })\n\n  it('should warn when injections has been modified', () => {\n    const key = 'foo'\n    const vm = new Vue({\n      provide: {\n        foo: 1\n      }\n    })\n\n    const child = new Vue({\n      parent: vm,\n      inject: ['foo']\n    })\n\n    expect(child.foo).toBe(1)\n    child.foo = 2\n    expect(\n      `Avoid mutating an injected value directly since the changes will be ` +\n      `overwritten whenever the provided component re-renders. ` +\n      `injection being mutated: \"${key}\"`).toHaveBeenWarned()\n  })\n\n  it('should warn when injections cannot be found', () => {\n    const vm = new Vue({})\n    new Vue({\n      parent: vm,\n      inject: ['foo', 'bar'],\n      created () {}\n    })\n    expect(`Injection \"foo\" not found`).toHaveBeenWarned()\n    expect(`Injection \"bar\" not found`).toHaveBeenWarned()\n  })\n\n  it('should not warn when injections can be found', () => {\n    const vm = new Vue({\n      provide: {\n        foo: 1,\n        bar: false,\n        baz: undefined\n      }\n    })\n    new Vue({\n      parent: vm,\n      inject: ['foo', 'bar', 'baz'],\n      created () {}\n    })\n    expect(`Injection \"foo\" not found`).not.toHaveBeenWarned()\n    expect(`Injection \"bar\" not found`).not.toHaveBeenWarned()\n    expect(`Injection \"baz\" not found`).not.toHaveBeenWarned()\n  })\n\n  it('should not warn when injection key which is not provided is not enumerable', () => {\n    const parent = new Vue({ provide: { foo: 1 }})\n    const inject = { foo: 'foo' }\n    Object.defineProperty(inject, '__ob__', { enumerable: false, value: '__ob__' })\n    new Vue({ parent, inject })\n    expect(`Injection \"__ob__\" not found`).not.toHaveBeenWarned()\n  })\n\n  // Github issue #6097\n  it('should not warn when injections cannot be found but have default value', () => {\n    const vm = new Vue({})\n    new Vue({\n      parent: vm,\n      inject: {\n        foo: { default: 1 },\n        bar: { default: false },\n        baz: { default: undefined }\n      },\n      created () {\n        injected = [this.foo, this.bar, this.baz]\n      }\n    })\n    expect(injected).toEqual([1, false, undefined])\n  })\n\n  it('should support name alias and default together', () => {\n    const vm = new Vue({\n      provide: {\n        FOO: 2\n      }\n    })\n    new Vue({\n      parent: vm,\n      inject: {\n        foo: { from: 'FOO', default: 1 },\n        bar: { default: false },\n        baz: { default: undefined }\n      },\n      created () {\n        injected = [this.foo, this.bar, this.baz]\n      }\n    })\n    expect(injected).toEqual([2, false, undefined])\n  })\n\n  it('should use provided value even if inject has default', () => {\n    const vm = new Vue({\n      provide: {\n        foo: 1,\n        bar: false,\n        baz: undefined\n      }\n    })\n    new Vue({\n      parent: vm,\n      inject: {\n        foo: { default: 2 },\n        bar: { default: 2 },\n        baz: { default: 2 }\n      },\n      created () {\n        injected = [this.foo, this.bar, this.baz]\n      }\n    })\n    expect(injected).toEqual([1, false, undefined])\n  })\n\n  // Github issue #6008\n  it('should merge provide from mixins (objects)', () => {\n    const mixinA = { provide: { foo: 'foo' }}\n    const mixinB = { provide: { bar: 'bar' }}\n    const child = {\n      inject: ['foo', 'bar'],\n      template: `<span/>`,\n      created () {\n        injected = [this.foo, this.bar]\n      }\n    }\n    new Vue({\n      mixins: [mixinA, mixinB],\n      render (h) {\n        return h(child)\n      }\n    }).$mount()\n\n    expect(injected).toEqual(['foo', 'bar'])\n  })\n\n  it('should merge provide from mixins (functions)', () => {\n    const mixinA = { provide: () => ({ foo: 'foo' }) }\n    const mixinB = { provide: () => ({ bar: 'bar' }) }\n    const child = {\n      inject: ['foo', 'bar'],\n      template: `<span/>`,\n      created () {\n        injected = [this.foo, this.bar]\n      }\n    }\n    new Vue({\n      mixins: [mixinA, mixinB],\n      render (h) {\n        return h(child)\n      }\n    }).$mount()\n\n    expect(injected).toEqual(['foo', 'bar'])\n  })\n\n  it('should merge provide from mixins (mix of objects and functions)', () => {\n    const mixinA = { provide: { foo: 'foo' }}\n    const mixinB = { provide: () => ({ bar: 'bar' }) }\n    const mixinC = { provide: { baz: 'baz' }}\n    const mixinD = { provide: () => ({ bam: 'bam' }) }\n    const child = {\n      inject: ['foo', 'bar', 'baz', 'bam'],\n      template: `<span/>`,\n      created () {\n        injected = [this.foo, this.bar, this.baz, this.bam]\n      }\n    }\n    new Vue({\n      mixins: [mixinA, mixinB, mixinC, mixinD],\n      render (h) {\n        return h(child)\n      }\n    }).$mount()\n\n    expect(injected).toEqual(['foo', 'bar', 'baz', 'bam'])\n  })\n\n  it('should merge provide from mixins and override existing keys', () => {\n    const mixinA = { provide: { foo: 'foo' }}\n    const mixinB = { provide: { foo: 'bar' }}\n    const child = {\n      inject: ['foo'],\n      template: `<span/>`,\n      created () {\n        injected = [this.foo]\n      }\n    }\n    new Vue({\n      mixins: [mixinA, mixinB],\n      render (h) {\n        return h(child)\n      }\n    }).$mount()\n\n    expect(injected).toEqual(['bar'])\n  })\n\n  it('should merge provide when Vue.extend', () => {\n    const mixinA = { provide: () => ({ foo: 'foo' }) }\n    const child = {\n      inject: ['foo', 'bar'],\n      template: `<span/>`,\n      created () {\n        injected = [this.foo, this.bar]\n      }\n    }\n    const Ctor = Vue.extend({\n      mixins: [mixinA],\n      provide: { bar: 'bar' },\n      render (h) {\n        return h(child)\n      }\n    })\n\n    new Ctor().$mount()\n\n    expect(injected).toEqual(['foo', 'bar'])\n  })\n\n  // #5913\n  it('should keep the reactive with provide', () => {\n    function isObserver (obj) {\n      if (isObject(obj)) {\n        return hasOwn(obj, '__ob__') && obj.__ob__ instanceof Observer\n      }\n      return false\n    }\n\n    const vm = new Vue({\n      template: `<div><child ref='child'></child></div>`,\n      data () {\n        return {\n          foo: {},\n          $foo: {},\n          foo1: []\n        }\n      },\n      provide () {\n        return {\n          foo: this.foo,\n          $foo: this.$foo,\n          foo1: this.foo1,\n          bar: {},\n          baz: []\n        }\n      },\n      components: {\n        child: {\n          inject: ['foo', '$foo', 'foo1', 'bar', 'baz'],\n          template: `<span/>`\n        }\n      }\n    }).$mount()\n    const child = vm.$refs.child\n    expect(isObserver(child.foo)).toBe(true)\n    expect(isObserver(child.$foo)).toBe(false)\n    expect(isObserver(child.foo1)).toBe(true)\n    expect(isObserver(child.bar)).toBe(false)\n    expect(isObserver(child.baz)).toBe(false)\n  })\n\n  // #6175\n  it('merge provide properly from mixins', () => {\n    const ProvideFooMixin = {\n      provide: {\n        foo: 'foo injected'\n      }\n    }\n\n    const ProvideBarMixin = {\n      provide: {\n        bar: 'bar injected'\n      }\n    }\n\n    const Child = {\n      inject: ['foo', 'bar'],\n      render (h) {\n        return h('div', [`foo: ${this.foo}, `, `bar: ${this.bar}`])\n      }\n    }\n\n    const Parent = {\n      mixins: [ProvideFooMixin, ProvideBarMixin],\n      render (h) {\n        return h(Child)\n      }\n    }\n\n    const vm = new Vue({\n      render (h) {\n        return h(Parent)\n      }\n    }).$mount()\n\n    expect(vm.$el.textContent).toBe(`foo: foo injected, bar: bar injected`)\n  })\n\n  it('merge provide with object syntax when using Vue.extend', () => {\n    const child = {\n      inject: ['foo'],\n      template: `<span/>`,\n      created () {\n        injected = this.foo\n      }\n    }\n    const Ctor = Vue.extend({\n      provide: { foo: 'foo' },\n      render (h) {\n        return h(child)\n      }\n    })\n\n    new Ctor().$mount()\n\n    expect(injected).toEqual('foo')\n  })\n\n  // #7284\n  it('should not inject prototype properties', () => {\n    const vm = new Vue({\n      provide: {}\n    })\n    new Vue({\n      parent: vm,\n      inject: ['constructor']\n    })\n    expect(`Injection \"constructor\" not found`).toHaveBeenWarned()\n  })\n})\n"
  },
  {
    "path": "vue/test/unit/features/options/lifecycle.spec.js",
    "content": "import Vue from 'vue'\n\ndescribe('Options lifecycle hooks', () => {\n  let spy\n  beforeEach(() => {\n    spy = jasmine.createSpy('hook')\n  })\n\n  describe('beforeCreate', () => {\n    it('should allow modifying options', () => {\n      const vm = new Vue({\n        data: {\n          a: 1\n        },\n        beforeCreate () {\n          spy()\n          expect(this.a).toBeUndefined()\n          this.$options.computed = {\n            b () {\n              return this.a + 1\n            }\n          }\n        }\n      })\n      expect(spy).toHaveBeenCalled()\n      expect(vm.b).toBe(2)\n    })\n  })\n\n  describe('created', () => {\n    it('should have completed observation', () => {\n      new Vue({\n        data: {\n          a: 1\n        },\n        created () {\n          expect(this.a).toBe(1)\n          spy()\n        }\n      })\n      expect(spy).toHaveBeenCalled()\n    })\n  })\n\n  describe('beforeMount', () => {\n    it('should not have mounted', () => {\n      const vm = new Vue({\n        render () {},\n        beforeMount () {\n          spy()\n          expect(this._isMounted).toBe(false)\n          expect(this.$el).toBeUndefined() // due to empty mount\n          expect(this._vnode).toBeNull()\n          expect(this._watcher).toBeNull()\n        }\n      })\n      expect(spy).not.toHaveBeenCalled()\n      vm.$mount()\n      expect(spy).toHaveBeenCalled()\n    })\n  })\n\n  describe('mounted', () => {\n    it('should have mounted', () => {\n      const vm = new Vue({\n        template: '<div></div>',\n        mounted () {\n          spy()\n          expect(this._isMounted).toBe(true)\n          expect(this.$el.tagName).toBe('DIV')\n          expect(this._vnode.tag).toBe('div')\n        }\n      })\n      expect(spy).not.toHaveBeenCalled()\n      vm.$mount()\n      expect(spy).toHaveBeenCalled()\n    })\n\n    // #3898\n    it('should call for manually mounted instance with parent', () => {\n      const parent = new Vue()\n      expect(spy).not.toHaveBeenCalled()\n      new Vue({\n        parent,\n        template: '<div></div>',\n        mounted () {\n          spy()\n        }\n      }).$mount()\n      expect(spy).toHaveBeenCalled()\n    })\n\n    it('should mount child parent in correct order', () => {\n      const calls = []\n      new Vue({\n        template: '<div><test></test></div>',\n        mounted () {\n          calls.push('parent')\n        },\n        components: {\n          test: {\n            template: '<nested></nested>',\n            mounted () {\n              expect(this.$el.parentNode).toBeTruthy()\n              calls.push('child')\n            },\n            components: {\n              nested: {\n                template: '<div></div>',\n                mounted () {\n                  expect(this.$el.parentNode).toBeTruthy()\n                  calls.push('nested')\n                }\n              }\n            }\n          }\n        }\n      }).$mount()\n      expect(calls).toEqual(['nested', 'child', 'parent'])\n    })\n  })\n\n  describe('beforeUpdate', () => {\n    it('should be called before update', done => {\n      const vm = new Vue({\n        template: '<div>{{ msg }}</div>',\n        data: { msg: 'foo' },\n        beforeUpdate () {\n          spy()\n          expect(this.$el.textContent).toBe('foo')\n        }\n      }).$mount()\n      expect(spy).not.toHaveBeenCalled()\n      vm.msg = 'bar'\n      expect(spy).not.toHaveBeenCalled() // should be async\n      waitForUpdate(() => {\n        expect(spy).toHaveBeenCalled()\n      }).then(done)\n    })\n\n    it('should be called before render and allow mutating state', done => {\n      const vm = new Vue({\n        template: '<div>{{ msg }}</div>',\n        data: { msg: 'foo' },\n        beforeUpdate () {\n          this.msg += '!'\n        }\n      }).$mount()\n      expect(vm.$el.textContent).toBe('foo')\n      vm.msg = 'bar'\n      waitForUpdate(() => {\n        expect(vm.$el.textContent).toBe('bar!')\n      }).then(done)\n    })\n  })\n\n  describe('updated', () => {\n    it('should be called after update', done => {\n      const vm = new Vue({\n        template: '<div>{{ msg }}</div>',\n        data: { msg: 'foo' },\n        updated () {\n          spy()\n          expect(this.$el.textContent).toBe('bar')\n        }\n      }).$mount()\n      expect(spy).not.toHaveBeenCalled()\n      vm.msg = 'bar'\n      expect(spy).not.toHaveBeenCalled() // should be async\n      waitForUpdate(() => {\n        expect(spy).toHaveBeenCalled()\n      }).then(done)\n    })\n\n    it('should be called after children are updated', done => {\n      const calls = []\n      const vm = new Vue({\n        template: '<div><test ref=\"child\">{{ msg }}</test></div>',\n        data: { msg: 'foo' },\n        components: {\n          test: {\n            template: `<div><slot></slot></div>`,\n            updated () {\n              expect(this.$el.textContent).toBe('bar')\n              calls.push('child')\n            }\n          }\n        },\n        updated () {\n          expect(this.$el.textContent).toBe('bar')\n          calls.push('parent')\n        }\n      }).$mount()\n\n      expect(calls).toEqual([])\n      vm.msg = 'bar'\n      expect(calls).toEqual([])\n      waitForUpdate(() => {\n        expect(calls).toEqual(['child', 'parent'])\n      }).then(done)\n    })\n  })\n\n  describe('beforeDestroy', () => {\n    it('should be called before destroy', () => {\n      const vm = new Vue({\n        render () {},\n        beforeDestroy () {\n          spy()\n          expect(this._isBeingDestroyed).toBe(false)\n          expect(this._isDestroyed).toBe(false)\n        }\n      }).$mount()\n      expect(spy).not.toHaveBeenCalled()\n      vm.$destroy()\n      vm.$destroy()\n      expect(spy).toHaveBeenCalled()\n      expect(spy.calls.count()).toBe(1)\n    })\n  })\n\n  describe('destroyed', () => {\n    it('should be called after destroy', () => {\n      const vm = new Vue({\n        render () {},\n        destroyed () {\n          spy()\n          expect(this._isBeingDestroyed).toBe(true)\n          expect(this._isDestroyed).toBe(true)\n        }\n      }).$mount()\n      expect(spy).not.toHaveBeenCalled()\n      vm.$destroy()\n      vm.$destroy()\n      expect(spy).toHaveBeenCalled()\n      expect(spy.calls.count()).toBe(1)\n    })\n  })\n\n  it('should emit hook events', () => {\n    const created = jasmine.createSpy()\n    const mounted = jasmine.createSpy()\n    const destroyed = jasmine.createSpy()\n    const vm = new Vue({\n      render () {},\n      beforeCreate () {\n        this.$on('hook:created', created)\n        this.$on('hook:mounted', mounted)\n        this.$on('hook:destroyed', destroyed)\n      }\n    })\n\n    expect(created).toHaveBeenCalled()\n    expect(mounted).not.toHaveBeenCalled()\n    expect(destroyed).not.toHaveBeenCalled()\n\n    vm.$mount()\n    expect(mounted).toHaveBeenCalled()\n    expect(destroyed).not.toHaveBeenCalled()\n\n    vm.$destroy()\n    expect(destroyed).toHaveBeenCalled()\n  })\n})\n"
  },
  {
    "path": "vue/test/unit/features/options/methods.spec.js",
    "content": "import Vue from 'vue'\nimport testObjectOption from '../../../helpers/test-object-option'\n\ndescribe('Options methods', () => {\n  testObjectOption('methods')\n\n  it('should have correct context', () => {\n    const vm = new Vue({\n      data: {\n        a: 1\n      },\n      methods: {\n        plus () {\n          this.a++\n        }\n      }\n    })\n    vm.plus()\n    expect(vm.a).toBe(2)\n  })\n\n  it('should warn undefined methods', () => {\n    new Vue({\n      methods: {\n        hello: undefined\n      }\n    })\n    expect(`Method \"hello\" has an undefined value in the component definition`).toHaveBeenWarned()\n  })\n\n  it('should warn methods conflicting with data', () => {\n    new Vue({\n      data: {\n        foo: 1\n      },\n      methods: {\n        foo () {}\n      }\n    })\n    expect(`Method \"foo\" has already been defined as a data property`).toHaveBeenWarned()\n  })\n\n  it('should warn methods conflicting with internal methods', () => {\n    new Vue({\n      methods: {\n        _update () {}\n      }\n    })\n    expect(`Method \"_update\" conflicts with an existing Vue instance method`).toHaveBeenWarned()\n  })\n})\n"
  },
  {
    "path": "vue/test/unit/features/options/mixins.spec.js",
    "content": "import Vue from 'vue'\nimport { mergeOptions } from 'core/util/index'\n\ndescribe('Options mixins', () => {\n  it('vm should have options from mixin', () => {\n    const mixin = {\n      directives: {\n        c: {}\n      },\n      methods: {\n        a: function () {}\n      }\n    }\n\n    const vm = new Vue({\n      mixins: [mixin],\n      methods: {\n        b: function () {}\n      }\n    })\n\n    expect(vm.a).toBeDefined()\n    expect(vm.b).toBeDefined()\n    expect(vm.$options.directives.c).toBeDefined()\n  })\n\n  it('should call hooks from mixins first', () => {\n    const a = {}\n    const b = {}\n    const c = {}\n    const f1 = function () {}\n    const f2 = function () {}\n    const f3 = function () {}\n    const mixinA = {\n      a: 1,\n      template: 'foo',\n      directives: {\n        a: a\n      },\n      created: f1\n    }\n    const mixinB = {\n      b: 1,\n      directives: {\n        b: b\n      },\n      created: f2\n    }\n    const result = mergeOptions({}, {\n      directives: {\n        c: c\n      },\n      template: 'bar',\n      mixins: [mixinA, mixinB],\n      created: f3\n    })\n    expect(result.a).toBe(1)\n    expect(result.b).toBe(1)\n    expect(result.directives.a).toBe(a)\n    expect(result.directives.b).toBe(b)\n    expect(result.directives.c).toBe(c)\n    expect(result.created[0]).toBe(f1)\n    expect(result.created[1]).toBe(f2)\n    expect(result.created[2]).toBe(f3)\n    expect(result.template).toBe('bar')\n  })\n\n  it('mixin methods should not override defined method', () => {\n    const f1 = function () {}\n    const f2 = function () {}\n    const f3 = function () {}\n    const mixinA = {\n      methods: {\n        xyz: f1\n      }\n    }\n    const mixinB = {\n      methods: {\n        xyz: f2\n      }\n    }\n    const result = mergeOptions({}, {\n      mixins: [mixinA, mixinB],\n      methods: {\n        xyz: f3\n      }\n    })\n    expect(result.methods.xyz).toBe(f3)\n  })\n\n  it('should accept constructors as mixins', () => {\n    const mixin = Vue.extend({\n      directives: {\n        c: {}\n      },\n      methods: {\n        a: function () {}\n      }\n    })\n\n    const vm = new Vue({\n      mixins: [mixin],\n      methods: {\n        b: function () {}\n      }\n    })\n\n    expect(vm.a).toBeDefined()\n    expect(vm.b).toBeDefined()\n    expect(vm.$options.directives.c).toBeDefined()\n  })\n})\n"
  },
  {
    "path": "vue/test/unit/features/options/name.spec.js",
    "content": "import Vue from 'vue'\n\ndescribe('Options name', () => {\n  it('should contain itself in self components', () => {\n    const vm = Vue.extend({\n      name: 'SuperVue'\n    })\n\n    expect(vm.options.components['SuperVue']).toEqual(vm)\n  })\n\n  it('should warn when incorrect name given', () => {\n    Vue.extend({\n      name: 'Hyper*Vue'\n    })\n\n    /* eslint-disable */\n    expect(`Invalid component name: \"Hyper*Vue\". Component names can only contain alphanumeric characters and the hyphen, and must start with a letter.`)\n      .toHaveBeenWarned()\n    /* eslint-enable */\n\n    Vue.extend({\n      name: '2Cool2BValid'\n    })\n\n    /* eslint-disable */\n    expect(`Invalid component name: \"2Cool2BValid\". Component names can only contain alphanumeric characters and the hyphen, and must start with a letter.`)\n      .toHaveBeenWarned()\n    /* eslint-enable */\n  })\n\n  it('id should not override given name when using Vue.component', () => {\n    const SuperComponent = Vue.component('super-component', {\n      name: 'SuperVue'\n    })\n\n    expect(SuperComponent.options.components['SuperVue']).toEqual(SuperComponent)\n    expect(SuperComponent.options.components['super-component']).toEqual(SuperComponent)\n  })\n})\n"
  },
  {
    "path": "vue/test/unit/features/options/parent.spec.js",
    "content": "import Vue from 'vue'\n\ndescribe('Options parent', () => {\n  it('should work', () => {\n    const parent = new Vue({\n      render () {}\n    }).$mount()\n\n    const child = new Vue({\n      parent: parent,\n      render () {}\n    }).$mount()\n\n    // this option is straight-forward\n    // it should register 'parent' as a $parent for 'child'\n    // and push 'child' to $children array on 'parent'\n    expect(child.$options.parent).toBeDefined()\n    expect(child.$options.parent).toEqual(parent)\n    expect(child.$parent).toBeDefined()\n    expect(child.$parent).toEqual(parent)\n    expect(parent.$children).toContain(child)\n\n    // destroy 'child' and check if it was removed from 'parent' $children\n    child.$destroy()\n    expect(parent.$children.length).toEqual(0)\n    parent.$destroy()\n  })\n})\n"
  },
  {
    "path": "vue/test/unit/features/options/props.spec.js",
    "content": "import Vue from 'vue'\nimport { hasSymbol } from 'core/util/env'\nimport testObjectOption from '../../../helpers/test-object-option'\n\ndescribe('Options props', () => {\n  testObjectOption('props')\n\n  it('array syntax', done => {\n    const vm = new Vue({\n      data: {\n        b: 'bar'\n      },\n      template: '<test v-bind:b=\"b\" ref=\"child\"></test>',\n      components: {\n        test: {\n          props: ['b'],\n          template: '<div>{{b}}</div>'\n        }\n      }\n    }).$mount()\n    expect(vm.$el.innerHTML).toBe('bar')\n    vm.b = 'baz'\n    waitForUpdate(() => {\n      expect(vm.$el.innerHTML).toBe('baz')\n      vm.$refs.child.b = 'qux'\n    }).then(() => {\n      expect(vm.$el.innerHTML).toBe('qux')\n      expect('Avoid mutating a prop directly').toHaveBeenWarned()\n    }).then(done)\n  })\n\n  it('object syntax', done => {\n    const vm = new Vue({\n      data: {\n        b: 'bar'\n      },\n      template: '<test v-bind:b=\"b\" ref=\"child\"></test>',\n      components: {\n        test: {\n          props: { b: String },\n          template: '<div>{{b}}</div>'\n        }\n      }\n    }).$mount()\n    expect(vm.$el.innerHTML).toBe('bar')\n    vm.b = 'baz'\n    waitForUpdate(() => {\n      expect(vm.$el.innerHTML).toBe('baz')\n      vm.$refs.child.b = 'qux'\n    }).then(() => {\n      expect(vm.$el.innerHTML).toBe('qux')\n      expect('Avoid mutating a prop directly').toHaveBeenWarned()\n    }).then(done)\n  })\n\n  it('warn mixed syntax', () => {\n    new Vue({\n      props: [{ b: String }]\n    })\n    expect('props must be strings when using array syntax').toHaveBeenWarned()\n  })\n\n  it('default values', () => {\n    const vm = new Vue({\n      data: {\n        b: undefined\n      },\n      template: '<test :b=\"b\"></test>',\n      components: {\n        test: {\n          props: {\n            a: {\n              default: 'A' // absent\n            },\n            b: {\n              default: 'B' // undefined\n            }\n          },\n          template: '<div>{{a}}{{b}}</div>'\n        }\n      }\n    }).$mount()\n    expect(vm.$el.textContent).toBe('AB')\n  })\n\n  it('default value reactivity', done => {\n    const vm = new Vue({\n      props: {\n        a: {\n          default: () => ({ b: 1 })\n        }\n      },\n      propsData: {\n        a: undefined\n      },\n      template: '<div>{{ a.b }}</div>'\n    }).$mount()\n    expect(vm.$el.textContent).toBe('1')\n    vm.a.b = 2\n    waitForUpdate(() => {\n      expect(vm.$el.textContent).toBe('2')\n    }).then(done)\n  })\n\n  it('default value Function', () => {\n    const func = () => 132\n    const vm = new Vue({\n      props: {\n        a: {\n          type: Function,\n          default: func\n        }\n      },\n      propsData: {\n        a: undefined\n      }\n    })\n    expect(vm.a).toBe(func)\n  })\n\n  it('warn object/array default values', () => {\n    new Vue({\n      props: {\n        a: {\n          default: { b: 1 }\n        }\n      },\n      propsData: {\n        a: undefined\n      }\n    })\n    expect('Props with type Object/Array must use a factory function').toHaveBeenWarned()\n  })\n\n  it('warn missing required', () => {\n    new Vue({\n      template: '<test></test>',\n      components: {\n        test: {\n          props: { a: { required: true }},\n          template: '<div>{{a}}</div>'\n        }\n      }\n    }).$mount()\n    expect('Missing required prop: \"a\"').toHaveBeenWarned()\n  })\n\n  describe('assertions', () => {\n    function makeInstance (value, type, validator, required) {\n      return new Vue({\n        template: '<test :test=\"val\"></test>',\n        data: {\n          val: value\n        },\n        components: {\n          test: {\n            template: '<div></div>',\n            props: {\n              test: {\n                type,\n                validator,\n                required\n              }\n            }\n          }\n        }\n      }).$mount()\n    }\n\n    it('string', () => {\n      makeInstance('hello', String)\n      expect(console.error.calls.count()).toBe(0)\n      makeInstance(123, String)\n      expect('Expected String').toHaveBeenWarned()\n    })\n\n    it('number', () => {\n      makeInstance(123, Number)\n      expect(console.error.calls.count()).toBe(0)\n      makeInstance('123', Number)\n      expect('Expected Number').toHaveBeenWarned()\n    })\n\n    it('boolean', () => {\n      makeInstance(true, Boolean)\n      expect(console.error.calls.count()).toBe(0)\n      makeInstance('123', Boolean)\n      expect('Expected Boolean').toHaveBeenWarned()\n    })\n\n    it('function', () => {\n      makeInstance(() => {}, Function)\n      expect(console.error.calls.count()).toBe(0)\n      makeInstance(123, Function)\n      expect('Expected Function').toHaveBeenWarned()\n    })\n\n    it('object', () => {\n      makeInstance({}, Object)\n      expect(console.error.calls.count()).toBe(0)\n      makeInstance([], Object)\n      expect('Expected Object').toHaveBeenWarned()\n    })\n\n    it('array', () => {\n      makeInstance([], Array)\n      expect(console.error.calls.count()).toBe(0)\n      makeInstance({}, Array)\n      expect('Expected Array').toHaveBeenWarned()\n    })\n\n    it('primitive wrapper objects', () => {\n      /* eslint-disable no-new-wrappers */\n      makeInstance(new String('s'), String)\n      expect(console.error.calls.count()).toBe(0)\n      makeInstance(new Number(1), Number)\n      expect(console.error.calls.count()).toBe(0)\n      makeInstance(new Boolean(true), Boolean)\n      expect(console.error.calls.count()).toBe(0)\n      /* eslint-enable no-new-wrappers */\n    })\n\n    if (hasSymbol) {\n      it('symbol', () => {\n        makeInstance(Symbol('foo'), Symbol)\n        expect(console.error.calls.count()).toBe(0)\n        makeInstance({}, Symbol)\n        expect('Expected Symbol').toHaveBeenWarned()\n      })\n    }\n\n    it('custom constructor', () => {\n      function Class () {}\n      makeInstance(new Class(), Class)\n      expect(console.error.calls.count()).toBe(0)\n      makeInstance({}, Class)\n      expect('type check failed').toHaveBeenWarned()\n    })\n\n    it('multiple types', () => {\n      makeInstance([], [Array, Number, Boolean])\n      expect(console.error.calls.count()).toBe(0)\n      makeInstance({}, [Array, Number, Boolean])\n      expect('Expected Array, Number, Boolean, got Object').toHaveBeenWarned()\n    })\n\n    it('custom validator', () => {\n      makeInstance(123, null, v => v === 123)\n      expect(console.error.calls.count()).toBe(0)\n      makeInstance(123, null, v => v === 234)\n      expect('custom validator check failed').toHaveBeenWarned()\n    })\n\n    it('type check + custom validator', () => {\n      makeInstance(123, Number, v => v === 123)\n      expect(console.error.calls.count()).toBe(0)\n      makeInstance(123, Number, v => v === 234)\n      expect('custom validator check failed').toHaveBeenWarned()\n      makeInstance(123, String, v => v === 123)\n      expect('Expected String').toHaveBeenWarned()\n    })\n\n    it('multiple types + custom validator', () => {\n      makeInstance(123, [Number, String, Boolean], v => v === 123)\n      expect(console.error.calls.count()).toBe(0)\n      makeInstance(123, [Number, String, Boolean], v => v === 234)\n      expect('custom validator check failed').toHaveBeenWarned()\n      makeInstance(123, [String, Boolean], v => v === 123)\n      expect('Expected String, Boolean').toHaveBeenWarned()\n    })\n\n    it('optional with type + null/undefined', () => {\n      makeInstance(undefined, String)\n      expect(console.error.calls.count()).toBe(0)\n      makeInstance(null, String)\n      expect(console.error.calls.count()).toBe(0)\n    })\n\n    it('required with type + null/undefined', () => {\n      makeInstance(undefined, String, null, true)\n      expect(console.error.calls.count()).toBe(1)\n      expect('Expected String').toHaveBeenWarned()\n      makeInstance(null, Boolean, null, true)\n      expect(console.error.calls.count()).toBe(2)\n      expect('Expected Boolean').toHaveBeenWarned()\n    })\n\n    it('optional prop of any type (type: true or prop: true)', () => {\n      makeInstance(1, true)\n      expect(console.error.calls.count()).toBe(0)\n      makeInstance('any', true)\n      expect(console.error.calls.count()).toBe(0)\n      makeInstance({}, true)\n      expect(console.error.calls.count()).toBe(0)\n      makeInstance(undefined, true)\n      expect(console.error.calls.count()).toBe(0)\n      makeInstance(null, true)\n      expect(console.error.calls.count()).toBe(0)\n    })\n  })\n\n  it('should work with v-bind', () => {\n    const vm = new Vue({\n      template: `<test v-bind=\"{ a: 1, b: 2 }\"></test>`,\n      components: {\n        test: {\n          props: ['a', 'b'],\n          template: '<div>{{ a }} {{ b }}</div>'\n        }\n      }\n    }).$mount()\n    expect(vm.$el.textContent).toBe('1 2')\n  })\n\n  it('should warn data fields already defined as a prop', () => {\n    new Vue({\n      template: '<test a=\"1\"></test>',\n      components: {\n        test: {\n          template: '<div></div>',\n          data: function () {\n            return { a: 123 }\n          },\n          props: {\n            a: null\n          }\n        }\n      }\n    }).$mount()\n    expect('already declared as a prop').toHaveBeenWarned()\n  })\n\n  it('should warn methods already defined as a prop', () => {\n    new Vue({\n      template: '<test a=\"1\"></test>',\n      components: {\n        test: {\n          template: '<div></div>',\n          props: {\n            a: null\n          },\n          methods: {\n            a () {\n\n            }\n          }\n        }\n      }\n    }).$mount()\n    expect(`Method \"a\" has already been defined as a prop`).toHaveBeenWarned()\n    expect(`Avoid mutating a prop directly`).toHaveBeenWarned()\n  })\n\n  it('treat boolean props properly', () => {\n    const vm = new Vue({\n      template: '<comp ref=\"child\" prop-a prop-b=\"prop-b\"></comp>',\n      components: {\n        comp: {\n          template: '<div></div>',\n          props: {\n            propA: Boolean,\n            propB: Boolean,\n            propC: Boolean\n          }\n        }\n      }\n    }).$mount()\n    expect(vm.$refs.child.propA).toBe(true)\n    expect(vm.$refs.child.propB).toBe(true)\n    expect(vm.$refs.child.propC).toBe(false)\n  })\n\n  it('should respect default value of a Boolean prop', function () {\n    const vm = new Vue({\n      template: '<test></test>',\n      components: {\n        test: {\n          props: {\n            prop: {\n              type: Boolean,\n              default: true\n            }\n          },\n          template: '<div>{{prop}}</div>'\n        }\n      }\n    }).$mount()\n    expect(vm.$el.textContent).toBe('true')\n  })\n\n  it('non reactive values passed down as prop should not be converted', done => {\n    const a = Object.freeze({\n      nested: {\n        msg: 'hello'\n      }\n    })\n    const parent = new Vue({\n      template: '<comp :a=\"a.nested\"></comp>',\n      data: {\n        a: a\n      },\n      components: {\n        comp: {\n          template: '<div></div>',\n          props: ['a']\n        }\n      }\n    }).$mount()\n    const child = parent.$children[0]\n    expect(child.a.msg).toBe('hello')\n    expect(child.a.__ob__).toBeUndefined() // should not be converted\n    parent.a = Object.freeze({\n      nested: {\n        msg: 'yo'\n      }\n    })\n    waitForUpdate(() => {\n      expect(child.a.msg).toBe('yo')\n      expect(child.a.__ob__).toBeUndefined()\n    }).then(done)\n  })\n\n  it('should not warn for non-required, absent prop', function () {\n    new Vue({\n      template: '<test></test>',\n      components: {\n        test: {\n          template: '<div></div>',\n          props: {\n            prop: {\n              type: String\n            }\n          }\n        }\n      }\n    }).$mount()\n    expect(console.error.calls.count()).toBe(0)\n  })\n\n  // #3453\n  it('should not fire watcher on object/array props when parent re-renders', done => {\n    const spy = jasmine.createSpy()\n    const vm = new Vue({\n      data: {\n        arr: []\n      },\n      template: '<test :prop=\"arr\">hi</test>',\n      components: {\n        test: {\n          props: ['prop'],\n          watch: {\n            prop: spy\n          },\n          template: '<div><slot></slot></div>'\n        }\n      }\n    }).$mount()\n    vm.$forceUpdate()\n    waitForUpdate(() => {\n      expect(spy).not.toHaveBeenCalled()\n    }).then(done)\n  })\n\n  // #4090\n  it('should not trigger watcher on default value', done => {\n    const spy = jasmine.createSpy()\n    const vm = new Vue({\n      template: `<test :value=\"a\" :test=\"b\"></test>`,\n      data: {\n        a: 1,\n        b: undefined\n      },\n      components: {\n        test: {\n          template: '<div>{{ value }}</div>',\n          props: {\n            value: { type: Number },\n            test: {\n              type: Object,\n              default: () => ({})\n            }\n          },\n          watch: {\n            test: spy\n          }\n        }\n      }\n    }).$mount()\n\n    vm.a++\n    waitForUpdate(() => {\n      expect(spy).not.toHaveBeenCalled()\n      vm.b = {}\n    }).then(() => {\n      expect(spy.calls.count()).toBe(1)\n    }).then(() => {\n      vm.b = undefined\n    }).then(() => {\n      expect(spy.calls.count()).toBe(2)\n      vm.a++\n    }).then(() => {\n      expect(spy.calls.count()).toBe(2)\n    }).then(done)\n  })\n\n  it('warn reserved props', () => {\n    const specialAttrs = ['key', 'ref', 'slot', 'is', 'slot-scope']\n    new Vue({\n      props: specialAttrs\n    })\n    specialAttrs.forEach(attr => {\n      expect(`\"${attr}\" is a reserved attribute`).toHaveBeenWarned()\n    })\n  })\n\n  it('should consider order when casting [Boolean, String] multi-type props', () => {\n    const vm = new Vue({\n      template: '<test ref=\"test\" booleanOrString stringOrBoolean />',\n      components: {\n        test: {\n          template: '<div></div>',\n          props: {\n            booleanOrString: [Boolean, String],\n            stringOrBoolean: [String, Boolean]\n          }\n        }\n      }\n    }).$mount()\n    expect(vm.$refs.test.$props.booleanOrString).toBe(true)\n    expect(vm.$refs.test.$props.stringOrBoolean).toBe('')\n  })\n})\n"
  },
  {
    "path": "vue/test/unit/features/options/propsData.spec.js",
    "content": "import Vue from 'vue'\n\ndescribe('Options propsData', () => {\n  it('should work', done => {\n    const A = Vue.extend({\n      props: ['a'],\n      template: '<div>{{ a }}</div>'\n    })\n    const vm = new A({\n      propsData: {\n        a: 123\n      }\n    }).$mount()\n    expect(vm.a).toBe(123)\n    expect(vm.$el.textContent).toBe('123')\n    vm.a = 234\n    waitForUpdate(() => {\n      expect(vm.$el.textContent).toBe('234')\n    }).then(done)\n  })\n\n  it('warn non instantiation usage', () => {\n    Vue.extend({\n      propsData: {\n        a: 123\n      }\n    })\n    expect('option \"propsData\" can only be used during instance creation').toHaveBeenWarned()\n  })\n})\n"
  },
  {
    "path": "vue/test/unit/features/options/render.spec.js",
    "content": "import Vue from 'vue'\n\ndescribe('Options render', () => {\n  it('basic usage', () => {\n    const vm = new Vue({\n      render (h) {\n        const children = []\n        for (let i = 0; i < this.items.length; i++) {\n          children.push(h('li', { staticClass: 'task' }, [this.items[i].name]))\n        }\n        return h('ul', { staticClass: 'tasks' }, children)\n      },\n      data: {\n        items: [{ id: 1, name: 'task1' }, { id: 2, name: 'task2' }]\n      }\n    }).$mount()\n    expect(vm.$el.tagName).toBe('UL')\n    for (let i = 0; i < vm.$el.children.length; i++) {\n      const li = vm.$el.children[i]\n      expect(li.tagName).toBe('LI')\n      expect(li.textContent).toBe(vm.items[i].name)\n    }\n  })\n\n  it('allow null data', () => {\n    const vm = new Vue({\n      render (h) {\n        return h('div', null, 'hello' /* string as children*/)\n      }\n    }).$mount()\n    expect(vm.$el.tagName).toBe('DIV')\n    expect(vm.$el.textContent).toBe('hello')\n  })\n\n  it('should warn non `render` option and non `template` option', () => {\n    new Vue().$mount()\n    expect('Failed to mount component: template or render function not defined.').toHaveBeenWarned()\n  })\n})\n"
  },
  {
    "path": "vue/test/unit/features/options/renderError.spec.js",
    "content": "import Vue from 'vue'\n\ndescribe('Options renderError', () => {\n  it('should be used on render errors', done => {\n    Vue.config.errorHandler = () => {}\n    const vm = new Vue({\n      data: {\n        ok: true\n      },\n      render (h) {\n        if (this.ok) {\n          return h('div', 'ok')\n        } else {\n          throw new Error('no')\n        }\n      },\n      renderError (h, err) {\n        return h('div', err.toString())\n      }\n    }).$mount()\n    expect(vm.$el.textContent).toBe('ok')\n    vm.ok = false\n    waitForUpdate(() => {\n      expect(vm.$el.textContent).toBe('Error: no')\n      Vue.config.errorHandler = null\n    }).then(done)\n  })\n\n  it('should pass on errors in renderError to global handler', () => {\n    const spy = Vue.config.errorHandler = jasmine.createSpy()\n    const err = new Error('renderError')\n    const vm = new Vue({\n      render () {\n        throw new Error('render')\n      },\n      renderError () {\n        throw err\n      }\n    }).$mount()\n    expect(spy).toHaveBeenCalledWith(err, vm, 'renderError')\n  })\n})\n"
  },
  {
    "path": "vue/test/unit/features/options/template.spec.js",
    "content": "import Vue from 'vue'\n\ndescribe('Options template', () => {\n  let el\n  beforeEach(() => {\n    el = document.createElement('script')\n    el.type = 'x-template'\n    el.id = 'app'\n    el.innerHTML = '<p>{{message}}</p>'\n    document.body.appendChild(el)\n  })\n\n  afterEach(() => {\n    document.body.removeChild(el)\n  })\n\n  it('basic usage', () => {\n    const vm = new Vue({\n      template: '<div>{{message}}</div>',\n      data: { message: 'hello world' }\n    }).$mount()\n    expect(vm.$el.tagName).toBe('DIV')\n    expect(vm.$el.textContent).toBe(vm.message)\n  })\n\n  it('id reference', () => {\n    const vm = new Vue({\n      template: '#app',\n      data: { message: 'hello world' }\n    }).$mount()\n    expect(vm.$el.tagName).toBe('P')\n    expect(vm.$el.textContent).toBe(vm.message)\n  })\n\n  it('DOM element', () => {\n    const elm = document.createElement('p')\n    elm.innerHTML = '<p>{{message}}</p>'\n    const vm = new Vue({\n      template: elm,\n      data: { message: 'hello world' }\n    }).$mount()\n    expect(vm.$el.tagName).toBe('P')\n    expect(vm.$el.textContent).toBe(vm.message)\n  })\n\n  it('invalid template', () => {\n    new Vue({\n      template: Vue,\n      data: { message: 'hello world' }\n    }).$mount()\n    expect('invalid template option').toHaveBeenWarned()\n  })\n\n  it('warn error in generated function', () => {\n    new Vue({\n      template: '<div v-if=\"!@\"><span>{{ a\"\" }}</span><span>{{ do + 1 }}</span></div>'\n    }).$mount()\n    expect('Error compiling template').toHaveBeenWarned()\n    expect('Raw expression: v-if=\"!@\"').toHaveBeenWarned()\n    expect('Raw expression: {{ a\"\" }}').toHaveBeenWarned()\n    expect('avoid using JavaScript keyword as property name: \"do\"').toHaveBeenWarned()\n  })\n\n  it('should not warn $ prefixed keywords', () => {\n    new Vue({\n      template: `<div @click=\"$delete(foo, 'bar')\"></div>`\n    }).$mount()\n    expect('avoid using JavaScript keyword as property name').not.toHaveBeenWarned()\n  })\n\n  it('warn error in generated function (v-for)', () => {\n    new Vue({\n      template: '<div><div v-for=\"(1, 2) in a----\"></div></div>'\n    }).$mount()\n    expect('Error compiling template').toHaveBeenWarned()\n    expect('invalid v-for alias \"1\"').toHaveBeenWarned()\n    expect('invalid v-for iterator \"2\"').toHaveBeenWarned()\n    expect('Raw expression: v-for=\"(1, 2) in a----\"').toHaveBeenWarned()\n  })\n\n  it('warn error in generated function (v-on)', () => {\n    new Vue({\n      template: `<div @click=\"delete('Delete')\"></div>`,\n      methods: { delete: function () {} }\n    }).$mount()\n    expect('Error compiling template').toHaveBeenWarned()\n    expect(\n      `avoid using JavaScript unary operator as property name: \"delete()\" in expression @click=\"delete('Delete')\"`\n    ).toHaveBeenWarned()\n  })\n})\n"
  },
  {
    "path": "vue/test/unit/features/options/watch.spec.js",
    "content": "import Vue from 'vue'\nimport testObjectOption from '../../../helpers/test-object-option'\n\ndescribe('Options watch', () => {\n  let spy\n  beforeEach(() => {\n    spy = jasmine.createSpy('watch')\n  })\n\n  testObjectOption('watch')\n\n  it('basic usage', done => {\n    const vm = new Vue({\n      data: {\n        a: 1\n      },\n      watch: {\n        a: spy\n      }\n    })\n    expect(spy).not.toHaveBeenCalled()\n    vm.a = 2\n    expect(spy).not.toHaveBeenCalled()\n    waitForUpdate(() => {\n      expect(spy).toHaveBeenCalledWith(2, 1)\n    }).then(done)\n  })\n\n  it('string method name', done => {\n    const vm = new Vue({\n      data: {\n        a: 1\n      },\n      watch: {\n        a: 'onChange'\n      },\n      methods: {\n        onChange: spy\n      }\n    })\n    expect(spy).not.toHaveBeenCalled()\n    vm.a = 2\n    expect(spy).not.toHaveBeenCalled()\n    waitForUpdate(() => {\n      expect(spy).toHaveBeenCalledWith(2, 1)\n    }).then(done)\n  })\n\n  it('multiple cbs (after option merge)', done => {\n    const spy1 = jasmine.createSpy('watch')\n    const Test = Vue.extend({\n      watch: {\n        a: spy1\n      }\n    })\n    const vm = new Test({\n      data: { a: 1 },\n      watch: {\n        a: spy\n      }\n    })\n    vm.a = 2\n    waitForUpdate(() => {\n      expect(spy1).toHaveBeenCalledWith(2, 1)\n      expect(spy).toHaveBeenCalledWith(2, 1)\n    }).then(done)\n  })\n\n  it('with option: immediate', done => {\n    const vm = new Vue({\n      data: { a: 1 },\n      watch: {\n        a: {\n          handler: spy,\n          immediate: true\n        }\n      }\n    })\n    expect(spy).toHaveBeenCalledWith(1)\n    vm.a = 2\n    waitForUpdate(() => {\n      expect(spy).toHaveBeenCalledWith(2, 1)\n    }).then(done)\n  })\n\n  it('with option: deep', done => {\n    const vm = new Vue({\n      data: { a: { b: 1 }},\n      watch: {\n        a: {\n          handler: spy,\n          deep: true\n        }\n      }\n    })\n    const oldA = vm.a\n    expect(spy).not.toHaveBeenCalled()\n    vm.a.b = 2\n    expect(spy).not.toHaveBeenCalled()\n    waitForUpdate(() => {\n      expect(spy).toHaveBeenCalledWith(vm.a, vm.a)\n      vm.a = { b: 3 }\n    }).then(() => {\n      expect(spy).toHaveBeenCalledWith(vm.a, oldA)\n    }).then(done)\n  })\n\n  it('correctly merges multiple extends', done => {\n    var spy2 = jasmine.createSpy('A')\n    var spy3 = jasmine.createSpy('B')\n    var A = Vue.extend({\n      data: function () {\n        return {\n          a: 0,\n          b: 0\n        }\n      },\n      watch: {\n        b: spy\n      }\n    })\n\n    var B = Vue.extend({\n      extends: A,\n      watch: {\n        a: spy2\n      }\n    })\n\n    var C = Vue.extend({\n      extends: B,\n      watch: {\n        a: spy3\n      }\n    })\n\n    var vm = new C()\n    vm.a = 1\n\n    waitForUpdate(() => {\n      expect(spy).not.toHaveBeenCalled()\n      expect(spy2).toHaveBeenCalledWith(1, 0)\n      expect(spy3).toHaveBeenCalledWith(1, 0)\n    }).then(done)\n  })\n})\n"
  },
  {
    "path": "vue/test/unit/features/ref.spec.js",
    "content": "import Vue from 'vue'\n\ndescribe('ref', () => {\n  const components = {\n    test: {\n      id: 'test',\n      template: '<div>test</div>'\n    },\n    test2: {\n      id: 'test2',\n      template: '<div>test2</div>'\n    },\n    test3: {\n      id: 'test3',\n      template: '<div>test3</div>'\n    }\n  }\n\n  it('should work', () => {\n    const vm = new Vue({\n      data: {\n        value: 'bar'\n      },\n      template: `<div>\n        <test ref=\"foo\"></test>\n        <test2 :ref=\"value\"></test2>\n        <test3 :ref=\"0\"></test3>\n      </div>`,\n      components\n    })\n    vm.$mount()\n    expect(vm.$refs.foo).toBeTruthy()\n    expect(vm.$refs.foo.$options.id).toBe('test')\n    expect(vm.$refs.bar).toBeTruthy()\n    expect(vm.$refs.bar.$options.id).toBe('test2')\n    expect(vm.$refs['0']).toBeTruthy()\n    expect(vm.$refs['0'].$options.id).toBe('test3')\n  })\n\n  it('should dynamically update refs', done => {\n    const vm = new Vue({\n      data: {\n        value: 'foo'\n      },\n      template: '<div :ref=\"value\"></div>'\n    }).$mount()\n    expect(vm.$refs.foo).toBe(vm.$el)\n    vm.value = 'bar'\n    waitForUpdate(() => {\n      expect(vm.$refs.foo).toBeUndefined()\n      expect(vm.$refs.bar).toBe(vm.$el)\n    }).then(done)\n  })\n\n  it('should work as a hyperscript prop', () => {\n    const vm = new Vue({\n      components,\n      render (h) {\n        return h('div', null, [\n          h('test', { ref: 'test' })\n        ])\n      }\n    })\n    vm.$mount()\n    expect(vm.$refs.test).toBeTruthy()\n    expect(vm.$refs.test.$options.id).toBe('test')\n  })\n\n  it('should accept HOC component', () => {\n    const vm = new Vue({\n      template: '<test ref=\"test\"></test>',\n      components\n    })\n    vm.$mount()\n    expect(vm.$refs.test).toBeTruthy()\n    expect(vm.$refs.test.$options.id).toBe('test')\n  })\n\n  it('should accept dynamic component', done => {\n    const vm = new Vue({\n      template: `<div>\n        <component :is=\"test\" ref=\"test\"></component>\n      </div>`,\n      components,\n      data: { test: 'test' }\n    })\n    vm.$mount()\n    expect(vm.$refs.test.$options.id).toBe('test')\n    vm.test = 'test2'\n    waitForUpdate(() => {\n      expect(vm.$refs.test.$options.id).toBe('test2')\n      vm.test = ''\n    }).then(() => {\n      expect(vm.$refs.test).toBeUndefined()\n    }).then(done)\n  })\n\n  it('should register as Array when used with v-for', done => {\n    const vm = new Vue({\n      data: {\n        items: [1, 2, 3]\n      },\n      template: `\n        <div>\n          <div v-for=\"n in items\" ref=\"list\">{{n}}</div>\n        </div>\n      `\n    }).$mount()\n    assertRefs()\n    // updating\n    vm.items.push(4)\n    waitForUpdate(assertRefs)\n      .then(() => { vm.items = [] })\n      .then(assertRefs)\n      .then(done)\n\n    function assertRefs () {\n      expect(Array.isArray(vm.$refs.list)).toBe(true)\n      expect(vm.$refs.list.length).toBe(vm.items.length)\n      expect(vm.$refs.list.every((item, i) => item.textContent === String(i + 1))).toBe(true)\n    }\n  })\n\n  it('should register as Array when used with v-for (components)', done => {\n    const vm = new Vue({\n      data: {\n        items: [1, 2, 3]\n      },\n      template: `\n        <div>\n          <test v-for=\"n in items\" ref=\"list\" :key=\"n\" :n=\"n\"></test>\n        </div>\n      `,\n      components: {\n        test: {\n          props: ['n'],\n          template: '<div>{{ n }}</div>'\n        }\n      }\n    }).$mount()\n    assertRefs()\n    // updating\n    vm.items.push(4)\n    waitForUpdate(assertRefs)\n      .then(() => { vm.items = [] })\n      .then(assertRefs)\n      .then(done)\n\n    function assertRefs () {\n      expect(Array.isArray(vm.$refs.list)).toBe(true)\n      expect(vm.$refs.list.length).toBe(vm.items.length)\n      expect(vm.$refs.list.every((comp, i) => comp.$el.textContent === String(i + 1))).toBe(true)\n    }\n  })\n\n  it('should work with v-for on dynamic component', done => {\n    components.test3 = {\n      id: 'test3',\n      template: `<test1 v-if=\"!normal\"></test1><div v-else>test3</div>`,\n      data () {\n        return { normal: false }\n      },\n      components: { test1: components.test }\n    }\n    // a flag that representing whether to test component content or not\n    let testContent = false\n\n    const vm = new Vue({\n      template: `\n        <div>\n          <component\n            v-for=\"(item, index) in items\"\n            :key=\"index\"\n            :is=\"item\"\n            ref=\"children\">\n          </component>\n        </div>\n      `,\n      data: {\n        items: ['test2', 'test3']\n      },\n      components\n    }).$mount()\n    assertRefs()\n    expect(vm.$refs.children[0].$el.textContent).toBe('test2')\n    expect(vm.$refs.children[1].$el.textContent).toBe('test')\n    // updating\n    vm.$refs.children[1].normal = true\n    testContent = true\n    waitForUpdate(assertRefs)\n      .then(() => { vm.items.push('test') })\n      .then(assertRefs)\n      .then(done)\n\n    function assertRefs () {\n      expect(Array.isArray(vm.$refs.children)).toBe(true)\n      expect(vm.$refs.children.length).toBe(vm.items.length)\n      if (testContent) {\n        expect(\n          vm.$refs.children.every((comp, i) => comp.$el.textContent === vm.items[i])\n        ).toBe(true)\n      }\n    }\n  })\n\n  it('should register on component with empty roots', done => {\n    const vm = new Vue({\n      template: '<child ref=\"test\"></child>',\n      components: {\n        child: {\n          template: '<div v-if=\"show\"></div>',\n          data () {\n            return { show: false }\n          }\n        }\n      }\n    }).$mount()\n    expect(vm.$refs.test).toBe(vm.$children[0])\n    vm.$refs.test.show = true\n    waitForUpdate(() => {\n      expect(vm.$refs.test).toBe(vm.$children[0])\n      vm.$refs.test.show = false\n    }).then(() => {\n      expect(vm.$refs.test).toBe(vm.$children[0])\n    }).then(done)\n  })\n})\n"
  },
  {
    "path": "vue/test/unit/features/transition/inject-styles.js",
    "content": "function insertCSS (text) {\n  var cssEl = document.createElement('style')\n  cssEl.textContent = text.trim()\n  document.head.appendChild(cssEl)\n}\n\nconst duration = process.env.TRANSITION_DURATION || 50\nconst buffer = process.env.TRANSITION_BUFFER || 10\nlet injected = false\n\nexport default function injectStyles () {\n  if (injected) return { duration, buffer }\n  injected = true\n  insertCSS(`\n    .test {\n      -webkit-transition: opacity ${duration}ms ease;\n      transition: opacity ${duration}ms ease;\n    }\n    .group-move {\n      -webkit-transition: -webkit-transform ${duration}ms ease;\n      transition: transform ${duration}ms ease;\n    }\n    .v-appear, .v-enter, .v-leave-active,\n    .test-appear, .test-enter, .test-leave-active,\n    .hello, .bye.active,\n    .changed-enter {\n      opacity: 0;\n    }\n    .test-anim-enter-active {\n      animation: test-enter ${duration}ms;\n      -webkit-animation: test-enter ${duration}ms;\n    }\n    .test-anim-leave-active {\n      animation: test-leave ${duration}ms;\n      -webkit-animation: test-leave ${duration}ms;\n    }\n    .test-anim-long-enter-active {\n      animation: test-enter ${duration * 2}ms;\n      -webkit-animation: test-enter ${duration * 2}ms;\n    }\n    .test-anim-long-leave-active {\n      animation: test-leave ${duration * 2}ms;\n      -webkit-animation: test-leave ${duration * 2}ms;\n    }\n    @keyframes test-enter {\n      from { opacity: 0 }\n      to { opacity: 1 }\n    }\n    @-webkit-keyframes test-enter {\n      from { opacity: 0 }\n      to { opacity: 1 }\n    }\n    @keyframes test-leave {\n      from { opacity: 1 }\n      to { opacity: 0 }\n    }\n    @-webkit-keyframes test-leave {\n      from { opacity: 1 }\n      to { opacity: 0 }\n    }\n  `)\n  return { duration, buffer }\n}\n\n"
  },
  {
    "path": "vue/test/unit/features/transition/transition-group.spec.js",
    "content": "import Vue from 'vue'\nimport injectStyles from './inject-styles'\nimport { isIE9 } from 'core/util/env'\nimport { nextFrame } from 'web/runtime/transition-util'\n\nif (!isIE9) {\n  describe('Transition group', () => {\n    const { duration, buffer } = injectStyles()\n\n    let el\n    beforeEach(() => {\n      el = document.createElement('div')\n      document.body.appendChild(el)\n    })\n\n    function createBasicVM (useIs, appear) {\n      const vm = new Vue({\n        template: `\n          <div>\n            ${useIs ? `<span is=\"transition-group\">` : `<transition-group${appear ? ` appear` : ``}>`}\n              <div v-for=\"item in items\" :key=\"item\" class=\"test\">{{ item }}</div>\n            ${useIs ? `</span>` : `</transition-group>`}\n          </div>\n        `,\n        data: {\n          items: ['a', 'b', 'c']\n        }\n      }).$mount(el)\n      if (!appear) {\n        expect(vm.$el.innerHTML).toBe(\n          `<span>` +\n            vm.items.map(i => `<div class=\"test\">${i}</div>`).join('') +\n          `</span>`\n        )\n      }\n      return vm\n    }\n\n    it('enter', done => {\n      const vm = createBasicVM()\n      vm.items.push('d', 'e')\n      waitForUpdate(() => {\n        expect(vm.$el.innerHTML).toBe(\n          `<span>` +\n            ['a', 'b', 'c'].map(i => `<div class=\"test\">${i}</div>`).join('') +\n            `<div class=\"test v-enter v-enter-active\">d</div>` +\n            `<div class=\"test v-enter v-enter-active\">e</div>` +\n          `</span>`\n        )\n      }).thenWaitFor(nextFrame).then(() => {\n        expect(vm.$el.innerHTML).toBe(\n          `<span>` +\n            ['a', 'b', 'c'].map(i => `<div class=\"test\">${i}</div>`).join('') +\n            `<div class=\"test v-enter-active v-enter-to\">d</div>` +\n            `<div class=\"test v-enter-active v-enter-to\">e</div>` +\n          `</span>`\n        )\n      }).thenWaitFor(duration + buffer).then(() => {\n        expect(vm.$el.innerHTML).toBe(\n          `<span>` +\n            vm.items.map(i => `<div class=\"test\">${i}</div>`).join('') +\n          `</span>`\n        )\n      }).then(done)\n    })\n\n    it('leave', done => {\n      const vm = createBasicVM()\n      vm.items = ['b']\n      waitForUpdate(() => {\n        expect(vm.$el.innerHTML).toBe(\n          `<span>` +\n            `<div class=\"test v-leave v-leave-active\">a</div>` +\n            `<div class=\"test\">b</div>` +\n            `<div class=\"test v-leave v-leave-active\">c</div>` +\n          `</span>`\n        )\n      }).thenWaitFor(nextFrame).then(() => {\n        expect(vm.$el.innerHTML).toBe(\n          `<span>` +\n            `<div class=\"test v-leave-active v-leave-to\">a</div>` +\n            `<div class=\"test\">b</div>` +\n            `<div class=\"test v-leave-active v-leave-to\">c</div>` +\n          `</span>`\n        )\n      }).thenWaitFor(duration + buffer).then(() => {\n        expect(vm.$el.innerHTML).toBe(\n          `<span>` +\n            vm.items.map(i => `<div class=\"test\">${i}</div>`).join('') +\n          `</span>`\n        )\n      }).then(done)\n    })\n\n    it('enter + leave', done => {\n      const vm = createBasicVM()\n      vm.items = ['b', 'c', 'd']\n      waitForUpdate(() => {\n        expect(vm.$el.innerHTML).toBe(\n          `<span>` +\n            `<div class=\"test v-leave v-leave-active\">a</div>` +\n            `<div class=\"test\">b</div>` +\n            `<div class=\"test\">c</div>` +\n            `<div class=\"test v-enter v-enter-active\">d</div>` +\n          `</span>`\n        )\n      }).thenWaitFor(nextFrame).then(() => {\n        expect(vm.$el.innerHTML).toBe(\n          `<span>` +\n            `<div class=\"test v-leave-active v-leave-to\">a</div>` +\n            `<div class=\"test\">b</div>` +\n            `<div class=\"test\">c</div>` +\n            `<div class=\"test v-enter-active v-enter-to\">d</div>` +\n          `</span>`\n        )\n      }).thenWaitFor(duration + buffer).then(() => {\n        expect(vm.$el.innerHTML).toBe(\n          `<span>` +\n            vm.items.map(i => `<div class=\"test\">${i}</div>`).join('') +\n          `</span>`\n        )\n      }).then(done)\n    })\n\n    it('use with \"is\" attribute', done => {\n      const vm = createBasicVM(true)\n      vm.items = ['b', 'c', 'd']\n      waitForUpdate(() => {\n        expect(vm.$el.innerHTML).toBe(\n          `<span>` +\n            `<div class=\"test v-leave v-leave-active\">a</div>` +\n            `<div class=\"test\">b</div>` +\n            `<div class=\"test\">c</div>` +\n            `<div class=\"test v-enter v-enter-active\">d</div>` +\n          `</span>`\n        )\n      }).thenWaitFor(nextFrame).then(() => {\n        expect(vm.$el.innerHTML).toBe(\n          `<span>` +\n            `<div class=\"test v-leave-active v-leave-to\">a</div>` +\n            `<div class=\"test\">b</div>` +\n            `<div class=\"test\">c</div>` +\n            `<div class=\"test v-enter-active v-enter-to\">d</div>` +\n          `</span>`\n        )\n      }).thenWaitFor(duration + buffer).then(() => {\n        expect(vm.$el.innerHTML).toBe(\n          `<span>` +\n            vm.items.map(i => `<div class=\"test\">${i}</div>`).join('') +\n          `</span>`\n        )\n      }).then(done)\n    })\n\n    it('appear', done => {\n      const vm = createBasicVM(false, true /* appear */)\n      waitForUpdate(() => {\n        expect(vm.$el.innerHTML).toBe(\n          `<span>` +\n            vm.items.map(i => `<div class=\"test v-enter v-enter-active\">${i}</div>`).join('') +\n          `</span>`\n        )\n      }).thenWaitFor(nextFrame).then(() => {\n        expect(vm.$el.innerHTML).toBe(\n          `<span>` +\n            vm.items.map(i => `<div class=\"test v-enter-active v-enter-to\">${i}</div>`).join('') +\n          `</span>`\n        )\n      }).thenWaitFor(duration + buffer).then(() => {\n        expect(vm.$el.innerHTML).toBe(\n          `<span>` +\n            vm.items.map(i => `<div class=\"test\">${i}</div>`).join('') +\n          `</span>`\n        )\n      }).then(done)\n    })\n\n    it('events', done => {\n      let next\n      const beforeEnterSpy = jasmine.createSpy()\n      const afterEnterSpy = jasmine.createSpy()\n      const afterLeaveSpy = jasmine.createSpy()\n      const vm = new Vue({\n        template: `\n          <div>\n            <transition-group @before-enter=\"beforeEnter\" @after-enter=\"afterEnter\" @after-leave=\"afterLeave\">\n              <div v-for=\"item in items\" :key=\"item\" class=\"test\">{{ item }}</div>\n            </transition-group>\n          </div>\n        `,\n        data: {\n          items: ['a', 'b', 'c']\n        },\n        methods: {\n          beforeEnter (el) {\n            expect(el.textContent).toBe('d')\n            beforeEnterSpy()\n          },\n          afterEnter (el) {\n            expect(el.textContent).toBe('d')\n            afterEnterSpy()\n            next()\n          },\n          afterLeave (el) {\n            expect(el.textContent).toBe('a')\n            afterLeaveSpy()\n            next()\n          }\n        }\n      }).$mount(el)\n\n      vm.items.push('d')\n      waitForUpdate(() => {\n        expect(vm.$el.innerHTML).toBe(\n          `<span>` +\n            `<div class=\"test\">a</div>` +\n            `<div class=\"test\">b</div>` +\n            `<div class=\"test\">c</div>` +\n            `<div class=\"test v-enter v-enter-active\">d</div>` +\n          `</span>`\n        )\n        expect(beforeEnterSpy.calls.count()).toBe(1)\n      }).thenWaitFor(_next => { next = _next }).then(() => {\n        expect(vm.$el.innerHTML).toBe(\n          `<span>` +\n            `<div class=\"test\">a</div>` +\n            `<div class=\"test\">b</div>` +\n            `<div class=\"test\">c</div>` +\n            `<div class=\"test\">d</div>` +\n          `</span>`\n        )\n        expect(afterEnterSpy.calls.count()).toBe(1)\n        vm.items.shift()\n      }).thenWaitFor(_next => { next = _next }).then(() => {\n        expect(vm.$el.innerHTML).toBe(\n          `<span>` +\n            `<div class=\"test\">b</div>` +\n            `<div class=\"test\">c</div>` +\n            `<div class=\"test\">d</div>` +\n          `</span>`\n        )\n        expect(afterLeaveSpy.calls.count()).toBe(1)\n      }).then(done)\n    })\n\n    it('move', done => {\n      const vm = new Vue({\n        template: `\n          <div>\n            <transition-group name=\"group\">\n              <div v-for=\"item in items\" :key=\"item\" class=\"test\">{{ item }}</div>\n            </transition-group>\n          </div>\n        `,\n        data: {\n          items: ['a', 'b', 'c']\n        }\n      }).$mount(el)\n\n      vm.items = ['d', 'b', 'a']\n      waitForUpdate(() => {\n        expect(vm.$el.innerHTML.replace(/\\s?style=\"\"(\\s?)/g, '$1')).toBe(\n          `<span>` +\n            `<div class=\"test group-enter group-enter-active\">d</div>` +\n            `<div class=\"test\">b</div>` +\n            `<div class=\"test group-move\">a</div>` +\n            `<div class=\"test group-leave group-leave-active group-move\">c</div>` +\n          `</span>`\n        )\n      }).thenWaitFor(nextFrame).then(() => {\n        expect(vm.$el.innerHTML.replace(/\\s?style=\"\"(\\s?)/g, '$1')).toBe(\n          `<span>` +\n            `<div class=\"test group-enter-active group-enter-to\">d</div>` +\n            `<div class=\"test\">b</div>` +\n            `<div class=\"test group-move\">a</div>` +\n            `<div class=\"test group-leave-active group-move group-leave-to\">c</div>` +\n          `</span>`\n        )\n      }).thenWaitFor(duration * 2).then(() => {\n        expect(vm.$el.innerHTML.replace(/\\s?style=\"\"(\\s?)/g, '$1')).toBe(\n          `<span>` +\n            `<div class=\"test\">d</div>` +\n            `<div class=\"test\">b</div>` +\n            `<div class=\"test\">a</div>` +\n          `</span>`\n        )\n      }).then(done)\n    })\n\n    it('warn unkeyed children', () => {\n      new Vue({\n        template: `<div><transition-group><div v-for=\"i in 3\"></div></transition-group></div>`\n      }).$mount()\n      expect('<transition-group> children must be keyed: <div>').toHaveBeenWarned()\n    })\n\n    // GitHub issue #6006\n    it('should work with dynamic name', done => {\n      const vm = new Vue({\n        template: `\n          <div>\n            <transition-group :name=\"name\">\n              <div v-for=\"item in items\" :key=\"item\">{{ item }}</div>\n            </transition-group>\n          </div>\n        `,\n        data: {\n          items: ['a', 'b', 'c'],\n          name: 'group'\n        }\n      }).$mount(el)\n\n      vm.name = 'invalid-name'\n      vm.items = ['b', 'c', 'a']\n      waitForUpdate(() => {\n        expect(vm.$el.innerHTML.replace(/\\s?style=\"\"(\\s?)/g, '$1')).toBe(\n          `<span>` +\n            `<div>b</div>` +\n            `<div>c</div>` +\n            `<div>a</div>` +\n          `</span>`\n        )\n        vm.name = 'group'\n        vm.items = ['a', 'b', 'c']\n      }).thenWaitFor(nextFrame).then(() => {\n        expect(vm.$el.innerHTML.replace(/\\s?style=\"\"(\\s?)/g, '$1')).toBe(\n          `<span>` +\n            `<div class=\"group-move\">a</div>` +\n            `<div class=\"group-move\">b</div>` +\n            `<div class=\"group-move\">c</div>` +\n          `</span>`\n        )\n      }).thenWaitFor(duration * 2 + buffer).then(() => {\n        expect(vm.$el.innerHTML.replace(/\\s?style=\"\"(\\s?)/g, '$1')).toBe(\n          `<span>` +\n            `<div>a</div>` +\n            `<div>b</div>` +\n            `<div>c</div>` +\n          `</span>`\n        )\n      }).then(done)\n    })\n  })\n}\n"
  },
  {
    "path": "vue/test/unit/features/transition/transition-mode.spec.js",
    "content": "import Vue from 'vue'\nimport injectStyles from './inject-styles'\nimport { isIE9 } from 'core/util/env'\nimport { nextFrame } from 'web/runtime/transition-util'\n\nif (!isIE9) {\n  describe('Transition mode', () => {\n    const { duration, buffer } = injectStyles()\n    const components = {\n      one: { template: '<div>one</div>' },\n      two: { template: '<div>two</div>' }\n    }\n\n    let el\n    beforeEach(() => {\n      el = document.createElement('div')\n      document.body.appendChild(el)\n    })\n\n    it('dynamic components, simultaneous', done => {\n      const vm = new Vue({\n        template: `<div>\n          <transition>\n            <component :is=\"view\" class=\"test\">\n            </component>\n          </transition>\n        </div>`,\n        data: { view: 'one' },\n        components\n      }).$mount(el)\n      expect(vm.$el.textContent).toBe('one')\n      vm.view = 'two'\n      waitForUpdate(() => {\n        expect(vm.$el.innerHTML).toBe(\n          '<div class=\"test v-leave v-leave-active\">one</div>' +\n          '<div class=\"test v-enter v-enter-active\">two</div>'\n        )\n      }).thenWaitFor(nextFrame).then(() => {\n        expect(vm.$el.innerHTML).toBe(\n          '<div class=\"test v-leave-active v-leave-to\">one</div>' +\n          '<div class=\"test v-enter-active v-enter-to\">two</div>'\n        )\n      }).thenWaitFor(duration + buffer).then(() => {\n        expect(vm.$el.innerHTML).toBe(\n          '<div class=\"test\">two</div>'\n        )\n      }).then(done)\n    })\n\n    it('dynamic components, out-in', done => {\n      let next\n      const vm = new Vue({\n        template: `<div>\n          <transition name=\"test\" mode=\"out-in\" @after-leave=\"afterLeave\">\n            <component :is=\"view\" class=\"test\">\n            </component>\n          </transition>\n        </div>`,\n        data: { view: 'one' },\n        components,\n        methods: {\n          afterLeave () {\n            next()\n          }\n        }\n      }).$mount(el)\n      expect(vm.$el.textContent).toBe('one')\n      vm.view = 'two'\n      waitForUpdate(() => {\n        expect(vm.$el.innerHTML).toBe(\n          '<div class=\"test test-leave test-leave-active\">one</div><!---->'\n        )\n      }).thenWaitFor(nextFrame).then(() => {\n        expect(vm.$el.innerHTML).toBe(\n          '<div class=\"test test-leave-active test-leave-to\">one</div><!---->'\n        )\n      }).thenWaitFor(_next => { next = _next }).then(() => {\n        expect(vm.$el.innerHTML).toBe('<!---->')\n      }).thenWaitFor(nextFrame).then(() => {\n        expect(vm.$el.innerHTML).toBe(\n          '<div class=\"test test-enter test-enter-active\">two</div>'\n        )\n      }).thenWaitFor(nextFrame).then(() => {\n        expect(vm.$el.innerHTML).toBe(\n          '<div class=\"test test-enter-active test-enter-to\">two</div>'\n        )\n      }).thenWaitFor(duration + buffer).then(() => {\n        expect(vm.$el.innerHTML).toBe(\n          '<div class=\"test\">two</div>'\n        )\n      }).then(done)\n    })\n\n    // #3440\n    it('dynamic components, out-in (with extra re-render)', done => {\n      let next\n      const vm = new Vue({\n        template: `<div>\n          <transition name=\"test\" mode=\"out-in\" @after-leave=\"afterLeave\">\n            <component :is=\"view\" class=\"test\">\n            </component>\n          </transition>\n        </div>`,\n        data: { view: 'one' },\n        components,\n        methods: {\n          afterLeave () {\n            next()\n          }\n        }\n      }).$mount(el)\n      expect(vm.$el.textContent).toBe('one')\n      vm.view = 'two'\n      waitForUpdate(() => {\n        expect(vm.$el.innerHTML).toBe(\n          '<div class=\"test test-leave test-leave-active\">one</div><!---->'\n        )\n      }).thenWaitFor(nextFrame).then(() => {\n        expect(vm.$el.innerHTML).toBe(\n          '<div class=\"test test-leave-active test-leave-to\">one</div><!---->'\n        )\n        // Force re-render before the element finishes leaving\n        // this should not cause the incoming element to enter early\n        vm.$forceUpdate()\n      }).thenWaitFor(_next => { next = _next }).then(() => {\n        expect(vm.$el.innerHTML).toBe('<!---->')\n      }).thenWaitFor(nextFrame).then(() => {\n        expect(vm.$el.innerHTML).toBe(\n          '<div class=\"test test-enter test-enter-active\">two</div>'\n        )\n      }).thenWaitFor(nextFrame).then(() => {\n        expect(vm.$el.innerHTML).toBe(\n          '<div class=\"test test-enter-active test-enter-to\">two</div>'\n        )\n      }).thenWaitFor(duration + buffer).then(() => {\n        expect(vm.$el.innerHTML).toBe(\n          '<div class=\"test\">two</div>'\n        )\n      }).then(done)\n    })\n\n    it('dynamic components, in-out', done => {\n      let next\n      const vm = new Vue({\n        template: `<div>\n          <transition name=\"test\" mode=\"in-out\" @after-enter=\"afterEnter\">\n            <component :is=\"view\" class=\"test\">\n            </component>\n          </transition>\n        </div>`,\n        data: { view: 'one' },\n        components,\n        methods: {\n          afterEnter () {\n            next()\n          }\n        }\n      }).$mount(el)\n      expect(vm.$el.textContent).toBe('one')\n      vm.view = 'two'\n      waitForUpdate(() => {\n        expect(vm.$el.innerHTML).toBe(\n          '<div class=\"test\">one</div>' +\n          '<div class=\"test test-enter test-enter-active\">two</div>'\n        )\n      }).thenWaitFor(nextFrame).then(() => {\n        expect(vm.$el.innerHTML).toBe(\n          '<div class=\"test\">one</div>' +\n          '<div class=\"test test-enter-active test-enter-to\">two</div>'\n        )\n      }).thenWaitFor(_next => { next = _next }).then(() => {\n        expect(vm.$el.innerHTML).toBe(\n          '<div class=\"test\">one</div>' +\n          '<div class=\"test\">two</div>'\n        )\n      }).then(() => {\n        expect(vm.$el.innerHTML).toBe(\n          '<div class=\"test test-leave test-leave-active\">one</div>' +\n          '<div class=\"test\">two</div>'\n        )\n      }).thenWaitFor(nextFrame).then(() => {\n        expect(vm.$el.innerHTML).toBe(\n          '<div class=\"test test-leave-active test-leave-to\">one</div>' +\n          '<div class=\"test\">two</div>'\n        )\n      }).thenWaitFor(duration + buffer).then(() => {\n        expect(vm.$el.innerHTML).toBe(\n          '<div class=\"test\">two</div>'\n        )\n      }).then(done)\n    })\n\n    it('dynamic components, in-out with early cancel', done => {\n      let next\n      const vm = new Vue({\n        template: `<div>\n          <transition name=\"test\" mode=\"in-out\" @after-enter=\"afterEnter\">\n            <component :is=\"view\" class=\"test\"></component>\n          </transition>\n        </div>`,\n        data: { view: 'one' },\n        components,\n        methods: {\n          afterEnter () {\n            next()\n          }\n        }\n      }).$mount(el)\n      expect(vm.$el.textContent).toBe('one')\n      vm.view = 'two'\n      waitForUpdate(() => {\n        expect(vm.$el.innerHTML).toBe(\n          '<div class=\"test\">one</div>' +\n          '<div class=\"test test-enter test-enter-active\">two</div>'\n        )\n      }).thenWaitFor(nextFrame).then(() => {\n        expect(vm.$el.innerHTML).toBe(\n          '<div class=\"test\">one</div>' +\n          '<div class=\"test test-enter-active test-enter-to\">two</div>'\n        )\n        // switch again before enter finishes,\n        // this cancels both enter and leave.\n        vm.view = 'one'\n      }).then(() => {\n        // 1. the pending leaving \"one\" should be removed instantly.\n        // 2. the entering \"two\" should be placed into its final state instantly.\n        // 3. a new \"one\" is created and entering\n        expect(vm.$el.innerHTML).toBe(\n          '<div class=\"test\">two</div>' +\n          '<div class=\"test test-enter test-enter-active\">one</div>'\n        )\n      }).thenWaitFor(nextFrame).then(() => {\n        expect(vm.$el.innerHTML).toBe(\n          '<div class=\"test\">two</div>' +\n          '<div class=\"test test-enter-active test-enter-to\">one</div>'\n        )\n      }).thenWaitFor(_next => { next = _next }).then(() => {\n        expect(vm.$el.innerHTML).toBe(\n          '<div class=\"test\">two</div>' +\n          '<div class=\"test\">one</div>'\n        )\n      }).then(() => {\n        expect(vm.$el.innerHTML).toBe(\n          '<div class=\"test test-leave test-leave-active\">two</div>' +\n          '<div class=\"test\">one</div>'\n        )\n      }).thenWaitFor(nextFrame).then(() => {\n        expect(vm.$el.innerHTML).toBe(\n          '<div class=\"test test-leave-active test-leave-to\">two</div>' +\n          '<div class=\"test\">one</div>'\n        )\n      }).thenWaitFor(duration + buffer).then(() => {\n        expect(vm.$el.innerHTML).toBe(\n          '<div class=\"test\">one</div>'\n        )\n      }).then(done).then(done)\n    })\n\n    it('normal elements with different keys, simultaneous', done => {\n      const vm = new Vue({\n        template: `<div>\n          <transition>\n            <div :key=\"view\" class=\"test\">{{view}}</div>\n          </transition>\n        </div>`,\n        data: { view: 'one' },\n        components\n      }).$mount(el)\n      expect(vm.$el.textContent).toBe('one')\n      vm.view = 'two'\n      waitForUpdate(() => {\n        expect(vm.$el.innerHTML).toBe(\n          '<div class=\"test v-leave v-leave-active\">one</div>' +\n          '<div class=\"test v-enter v-enter-active\">two</div>'\n        )\n      }).thenWaitFor(nextFrame).then(() => {\n        expect(vm.$el.innerHTML).toBe(\n          '<div class=\"test v-leave-active v-leave-to\">one</div>' +\n          '<div class=\"test v-enter-active v-enter-to\">two</div>'\n        )\n      }).thenWaitFor(duration + buffer).then(() => {\n        expect(vm.$el.innerHTML).toBe(\n          '<div class=\"test\">two</div>'\n        )\n      }).then(done)\n    })\n\n    it('normal elements with different keys, out-in', done => {\n      let next\n      const vm = new Vue({\n        template: `<div>\n          <transition name=\"test\" mode=\"out-in\" @after-leave=\"afterLeave\">\n            <div :key=\"view\" class=\"test\">{{view}}</div>\n          </transition>\n        </div>`,\n        data: { view: 'one' },\n        components,\n        methods: {\n          afterLeave () {\n            next()\n          }\n        }\n      }).$mount(el)\n      expect(vm.$el.textContent).toBe('one')\n      vm.view = 'two'\n      waitForUpdate(() => {\n        expect(vm.$el.innerHTML).toBe(\n          '<div class=\"test test-leave test-leave-active\">one</div><!---->'\n        )\n      }).thenWaitFor(nextFrame).then(() => {\n        expect(vm.$el.innerHTML).toBe(\n          '<div class=\"test test-leave-active test-leave-to\">one</div><!---->'\n        )\n      }).thenWaitFor(_next => { next = _next }).then(() => {\n        expect(vm.$el.innerHTML).toBe('<!---->')\n      }).thenWaitFor(nextFrame).then(() => {\n        expect(vm.$el.innerHTML).toBe(\n          '<div class=\"test test-enter test-enter-active\">two</div>'\n        )\n      }).thenWaitFor(nextFrame).then(() => {\n        expect(vm.$el.innerHTML).toBe(\n          '<div class=\"test test-enter-active test-enter-to\">two</div>'\n        )\n      }).thenWaitFor(duration + buffer).then(() => {\n        expect(vm.$el.innerHTML).toBe(\n          '<div class=\"test\">two</div>'\n        )\n      }).then(done)\n    })\n\n    it('normal elements with different keys, in-out', done => {\n      let next\n      const vm = new Vue({\n        template: `<div>\n          <transition name=\"test\" mode=\"in-out\" @after-enter=\"afterEnter\">\n            <div :key=\"view\" class=\"test\">{{view}}</div>\n          </transition>\n        </div>`,\n        data: { view: 'one' },\n        components,\n        methods: {\n          afterEnter () {\n            next()\n          }\n        }\n      }).$mount(el)\n      expect(vm.$el.textContent).toBe('one')\n      vm.view = 'two'\n      waitForUpdate(() => {\n        expect(vm.$el.innerHTML).toBe(\n          '<div class=\"test\">one</div>' +\n          '<div class=\"test test-enter test-enter-active\">two</div>'\n        )\n      }).thenWaitFor(nextFrame).then(() => {\n        expect(vm.$el.innerHTML).toBe(\n          '<div class=\"test\">one</div>' +\n          '<div class=\"test test-enter-active test-enter-to\">two</div>'\n        )\n      }).thenWaitFor(_next => { next = _next }).then(() => {\n        expect(vm.$el.innerHTML).toBe(\n          '<div class=\"test\">one</div>' +\n          '<div class=\"test\">two</div>'\n        )\n      }).then(() => {\n        expect(vm.$el.innerHTML).toBe(\n          '<div class=\"test test-leave test-leave-active\">one</div>' +\n          '<div class=\"test\">two</div>'\n        )\n      }).thenWaitFor(nextFrame).then(() => {\n        expect(vm.$el.innerHTML).toBe(\n          '<div class=\"test test-leave-active test-leave-to\">one</div>' +\n          '<div class=\"test\">two</div>'\n        )\n      }).thenWaitFor(duration + buffer).then(() => {\n        expect(vm.$el.innerHTML).toBe(\n          '<div class=\"test\">two</div>'\n        )\n      }).then(done)\n    })\n\n    it('transition out-in on async component (resolve before leave complete)', done => {\n      const vm = new Vue({\n        template: `\n          <div>\n            <transition name=\"test-anim\" mode=\"out-in\">\n              <component-a v-if=\"ok\"></component-a>\n              <component-b v-else></component-b>\n            </transition>\n          </div>\n        `,\n        components: {\n          componentA: resolve => {\n            setTimeout(() => {\n              resolve({ template: '<div><h1>component A</h1></div>' })\n              next1()\n            }, duration / 2)\n          },\n          componentB: resolve => {\n            setTimeout(() => {\n              resolve({ template: '<div><h1>component B</h1></div>' })\n            }, duration / 2)\n          }\n        },\n        data: {\n          ok: true\n        }\n      }).$mount(el)\n\n      expect(vm.$el.innerHTML).toBe('<!---->')\n\n      function next1 () {\n        Vue.nextTick(() => {\n          expect(vm.$el.children.length).toBe(1)\n          expect(vm.$el.textContent).toBe('component A')\n          expect(vm.$el.children[0].className).toBe('test-anim-enter test-anim-enter-active')\n          nextFrame(() => {\n            expect(vm.$el.children[0].className).toBe('test-anim-enter-active test-anim-enter-to')\n            setTimeout(() => {\n              expect(vm.$el.children[0].className).toBe('')\n              vm.ok = false\n              next2()\n            }, duration + buffer)\n          })\n        })\n      }\n\n      function next2 () {\n        waitForUpdate(() => {\n          expect(vm.$el.children.length).toBe(1)\n          expect(vm.$el.textContent).toBe('component A')\n          expect(vm.$el.children[0].className).toBe('test-anim-leave test-anim-leave-active')\n        }).thenWaitFor(nextFrame).then(() => {\n          expect(vm.$el.children[0].className).toBe('test-anim-leave-active test-anim-leave-to')\n        }).thenWaitFor(duration + buffer).then(() => {\n          expect(vm.$el.children.length).toBe(1)\n          expect(vm.$el.textContent).toBe('component B')\n          expect(vm.$el.children[0].className).toMatch('test-anim-enter-active')\n        }).thenWaitFor(duration * 2).then(() => {\n          expect(vm.$el.children[0].className).toBe('')\n        }).then(done)\n      }\n    })\n\n    it('transition out-in on async component (resolve after leave complete)', done => {\n      const vm = new Vue({\n        template: `\n          <div>\n            <transition name=\"test-anim\" mode=\"out-in\">\n              <component-a v-if=\"ok\"></component-a>\n              <component-b v-else></component-b>\n            </transition>\n          </div>\n        `,\n        components: {\n          componentA: { template: '<div><h1>component A</h1></div>' },\n          componentB: resolve => {\n            setTimeout(() => {\n              resolve({ template: '<div><h1>component B</h1></div>' })\n              Vue.nextTick(next)\n            }, (duration + buffer) * 1.5)\n          }\n        },\n        data: {\n          ok: true\n        }\n      }).$mount(el)\n\n      expect(vm.$el.innerHTML).toBe('<div><h1>component A</h1></div>')\n\n      let next\n\n      vm.ok = false\n      waitForUpdate(() => {\n        expect(vm.$el.children.length).toBe(1)\n        expect(vm.$el.textContent).toBe('component A')\n        expect(vm.$el.children[0].className).toBe('test-anim-leave test-anim-leave-active')\n      }).thenWaitFor(nextFrame).then(() => {\n        expect(vm.$el.children[0].className).toBe('test-anim-leave-active test-anim-leave-to')\n      }).thenWaitFor(duration + buffer).then(() => {\n        expect(vm.$el.children.length).toBe(0)\n        expect(vm.$el.innerHTML).toBe('<!---->')\n      }).thenWaitFor(_next => { next = _next }).then(() => {\n        expect(vm.$el.children.length).toBe(1)\n        expect(vm.$el.textContent).toBe('component B')\n        expect(vm.$el.children[0].className).toBe('test-anim-enter test-anim-enter-active')\n      }).thenWaitFor(nextFrame).then(() => {\n        expect(vm.$el.children[0].className).toBe('test-anim-enter-active test-anim-enter-to')\n      }).thenWaitFor(duration + buffer).then(() => {\n        expect(vm.$el.children.length).toBe(1)\n        expect(vm.$el.textContent).toBe('component B')\n        expect(vm.$el.children[0].className).toBe('')\n      }).then(done)\n    })\n\n    it('transition in-out on async component', done => {\n      const vm = new Vue({\n        template: `\n          <div>\n            <transition name=\"test-anim\" mode=\"in-out\">\n              <component-a v-if=\"ok\"></component-a>\n              <component-b v-else></component-b>\n            </transition>\n          </div>\n        `,\n        components: {\n          componentA: resolve => {\n            setTimeout(() => {\n              resolve({ template: '<div><h1>component A</h1></div>' })\n              next1()\n            }, duration / 2)\n          },\n          componentB: resolve => {\n            setTimeout(() => {\n              resolve({ template: '<div><h1>component B</h1></div>' })\n              next2()\n            }, duration / 2)\n          }\n        },\n        data: {\n          ok: true\n        }\n      }).$mount(el)\n\n      expect(vm.$el.innerHTML).toBe('<!---->')\n\n      function next1 () {\n        Vue.nextTick(() => {\n          expect(vm.$el.children.length).toBe(1)\n          expect(vm.$el.textContent).toBe('component A')\n          expect(vm.$el.children[0].className).toBe('test-anim-enter test-anim-enter-active')\n          nextFrame(() => {\n            expect(vm.$el.children[0].className).toBe('test-anim-enter-active test-anim-enter-to')\n            setTimeout(() => {\n              expect(vm.$el.children[0].className).toBe('')\n              vm.ok = false\n            }, duration + buffer)\n          })\n        })\n      }\n\n      function next2 () {\n        waitForUpdate(() => {\n          expect(vm.$el.children.length).toBe(2)\n          expect(vm.$el.textContent).toBe('component Acomponent B')\n          expect(vm.$el.children[0].className).toBe('')\n          expect(vm.$el.children[1].className).toBe('test-anim-enter test-anim-enter-active')\n        }).thenWaitFor(nextFrame).then(() => {\n          expect(vm.$el.children[1].className).toBe('test-anim-enter-active test-anim-enter-to')\n        }).thenWaitFor(duration + buffer).then(() => {\n          expect(vm.$el.children.length).toBe(2)\n          expect(vm.$el.textContent).toBe('component Acomponent B')\n          expect(vm.$el.children[0].className).toMatch('test-anim-leave-active')\n          expect(vm.$el.children[1].className).toBe('')\n        }).thenWaitFor(duration + buffer).then(() => {\n          expect(vm.$el.children.length).toBe(1)\n          expect(vm.$el.textContent).toBe('component B')\n          expect(vm.$el.children[0].className).toBe('')\n        }).then(done)\n      }\n    })\n\n    it('warn invalid mode', () => {\n      new Vue({\n        template: '<transition mode=\"foo\"><div>123</div></transition>'\n      }).$mount()\n      expect('invalid <transition> mode: foo').toHaveBeenWarned()\n    })\n  })\n}\n"
  },
  {
    "path": "vue/test/unit/features/transition/transition.spec.js",
    "content": "import Vue from 'vue'\nimport injectStyles from './inject-styles'\nimport { isIE9 } from 'core/util/env'\nimport { nextFrame } from 'web/runtime/transition-util'\n\nif (!isIE9) {\n  describe('Transition basic', () => {\n    const { duration, buffer } = injectStyles()\n    const explicitDuration = duration * 2\n\n    let el\n    beforeEach(() => {\n      el = document.createElement('div')\n      document.body.appendChild(el)\n    })\n\n    it('basic transition', done => {\n      const vm = new Vue({\n        template: '<div><transition><div v-if=\"ok\" class=\"test\">foo</div></transition></div>',\n        data: { ok: true }\n      }).$mount(el)\n\n      // should not apply transition on initial render by default\n      expect(vm.$el.innerHTML).toBe('<div class=\"test\">foo</div>')\n      vm.ok = false\n      waitForUpdate(() => {\n        expect(vm.$el.children[0].className).toBe('test v-leave v-leave-active')\n      }).thenWaitFor(nextFrame).then(() => {\n        expect(vm.$el.children[0].className).toBe('test v-leave-active v-leave-to')\n      }).thenWaitFor(duration + buffer).then(() => {\n        expect(vm.$el.children.length).toBe(0)\n        vm.ok = true\n      }).then(() => {\n        expect(vm.$el.children[0].className).toBe('test v-enter v-enter-active')\n      }).thenWaitFor(nextFrame).then(() => {\n        expect(vm.$el.children[0].className).toBe('test v-enter-active v-enter-to')\n      }).thenWaitFor(duration + buffer).then(() => {\n        expect(vm.$el.children[0].className).toBe('test')\n      }).then(done)\n    })\n\n    it('named transition', done => {\n      const vm = new Vue({\n        template: '<div><transition name=\"test\"><div v-if=\"ok\" class=\"test\">foo</div></transition></div>',\n        data: { ok: true }\n      }).$mount(el)\n\n      // should not apply transition on initial render by default\n      expect(vm.$el.innerHTML).toBe('<div class=\"test\">foo</div>')\n      vm.ok = false\n      waitForUpdate(() => {\n        expect(vm.$el.children[0].className).toBe('test test-leave test-leave-active')\n      }).thenWaitFor(nextFrame).then(() => {\n        expect(vm.$el.children[0].className).toBe('test test-leave-active test-leave-to')\n      }).thenWaitFor(duration + buffer).then(() => {\n        expect(vm.$el.children.length).toBe(0)\n        vm.ok = true\n      }).then(() => {\n        expect(vm.$el.children[0].className).toBe('test test-enter test-enter-active')\n      }).thenWaitFor(nextFrame).then(() => {\n        expect(vm.$el.children[0].className).toBe('test test-enter-active test-enter-to')\n      }).thenWaitFor(duration + buffer).then(() => {\n        expect(vm.$el.children[0].className).toBe('test')\n      }).then(done)\n    })\n\n    it('custom transition classes', done => {\n      const vm = new Vue({\n        template: `\n          <div>\n            <transition\n              enter-class=\"hello\"\n              enter-active-class=\"hello-active\"\n              enter-to-class=\"hello-to\"\n              leave-class=\"bye\"\n              leave-to-class=\"bye-to\"\n              leave-active-class=\"byebye active more \">\n              <div v-if=\"ok\" class=\"test\">foo</div>\n            </transition>\n          </div>\n        `,\n        data: { ok: true }\n      }).$mount(el)\n\n      // should not apply transition on initial render by default\n      expect(vm.$el.innerHTML).toBe('<div class=\"test\">foo</div>')\n      vm.ok = false\n      waitForUpdate(() => {\n        expect(vm.$el.children[0].className).toBe('test bye byebye active more')\n      }).thenWaitFor(nextFrame).then(() => {\n        expect(vm.$el.children[0].className).toBe('test byebye active more bye-to')\n      }).thenWaitFor(duration + buffer).then(() => {\n        expect(vm.$el.children.length).toBe(0)\n        vm.ok = true\n      }).then(() => {\n        expect(vm.$el.children[0].className).toBe('test hello hello-active')\n      }).thenWaitFor(nextFrame).then(() => {\n        expect(vm.$el.children[0].className).toBe('test hello-active hello-to')\n      }).thenWaitFor(duration + buffer).then(() => {\n        expect(vm.$el.children[0].className).toBe('test')\n      }).then(done)\n    })\n\n    it('dynamic transition', done => {\n      const vm = new Vue({\n        template: `\n          <div>\n            <transition :name=\"trans\">\n              <div v-if=\"ok\" class=\"test\">foo</div>\n            </transition>\n          </div>\n        `,\n        data: {\n          ok: true,\n          trans: 'test'\n        }\n      }).$mount(el)\n\n      // should not apply transition on initial render by default\n      expect(vm.$el.innerHTML).toBe('<div class=\"test\">foo</div>')\n      vm.ok = false\n      waitForUpdate(() => {\n        expect(vm.$el.children[0].className).toBe('test test-leave test-leave-active')\n      }).thenWaitFor(nextFrame).then(() => {\n        expect(vm.$el.children[0].className).toBe('test test-leave-active test-leave-to')\n      }).thenWaitFor(duration + buffer).then(() => {\n        expect(vm.$el.children.length).toBe(0)\n        vm.ok = true\n        vm.trans = 'changed'\n      }).then(() => {\n        expect(vm.$el.children[0].className).toBe('test changed-enter changed-enter-active')\n      }).thenWaitFor(nextFrame).then(() => {\n        expect(vm.$el.children[0].className).toBe('test changed-enter-active changed-enter-to')\n      }).thenWaitFor(duration + buffer).then(() => {\n        expect(vm.$el.children[0].className).toBe('test')\n      }).then(done)\n    })\n\n    it('inline transition object', done => {\n      const enter = jasmine.createSpy('enter')\n      const leave = jasmine.createSpy('leave')\n      const vm = new Vue({\n        render (h) {\n          return h('div', null, [\n            h('transition', {\n              props: {\n                name: 'inline',\n                enterClass: 'hello',\n                enterToClass: 'hello-to',\n                enterActiveClass: 'hello-active',\n                leaveClass: 'bye',\n                leaveToClass: 'bye-to',\n                leaveActiveClass: 'byebye active'\n              },\n              on: {\n                enter,\n                leave\n              }\n            }, this.ok ? [h('div', { class: 'test' }, 'foo')] : undefined)\n          ])\n        },\n        data: { ok: true }\n      }).$mount(el)\n\n      // should not apply transition on initial render by default\n      expect(vm.$el.innerHTML).toBe('<div class=\"test\">foo</div>')\n      vm.ok = false\n      waitForUpdate(() => {\n        expect(vm.$el.children[0].className).toBe('test bye byebye active')\n        expect(leave).toHaveBeenCalled()\n      }).thenWaitFor(nextFrame).then(() => {\n        expect(vm.$el.children[0].className).toBe('test byebye active bye-to')\n      }).thenWaitFor(duration + buffer).then(() => {\n        expect(vm.$el.children.length).toBe(0)\n        vm.ok = true\n      }).then(() => {\n        expect(vm.$el.children[0].className).toBe('test hello hello-active')\n        expect(enter).toHaveBeenCalled()\n      }).thenWaitFor(nextFrame).then(() => {\n        expect(vm.$el.children[0].className).toBe('test hello-active hello-to')\n      }).thenWaitFor(duration + buffer).then(() => {\n        expect(vm.$el.children[0].className).toBe('test')\n      }).then(done)\n    })\n\n    it('transition events', done => {\n      const onLeaveSpy = jasmine.createSpy('leave')\n      const onEnterSpy = jasmine.createSpy('enter')\n      const beforeLeaveSpy = jasmine.createSpy('beforeLeave')\n      const beforeEnterSpy = jasmine.createSpy('beforeEnter')\n      const afterLeaveSpy = jasmine.createSpy('afterLeave')\n      const afterEnterSpy = jasmine.createSpy('afterEnter')\n\n      const vm = new Vue({\n        template: `\n          <div>\n            <transition\n              name=\"test\"\n              @before-enter=\"beforeEnter\"\n              @enter=\"enter\"\n              @after-enter=\"afterEnter\"\n              @before-leave=\"beforeLeave\"\n              @leave=\"leave\"\n              @after-leave=\"afterLeave\">\n              <div v-if=\"ok\" class=\"test\">foo</div>\n            </transition>\n          </div>\n        `,\n        data: { ok: true },\n        methods: {\n          beforeLeave: (el) => {\n            expect(el).toBe(vm.$el.children[0])\n            expect(el.className).toBe('test')\n            beforeLeaveSpy(el)\n          },\n          leave: (el) => onLeaveSpy(el),\n          afterLeave: (el) => afterLeaveSpy(el),\n          beforeEnter: (el) => {\n            expect(vm.$el.contains(el)).toBe(false)\n            expect(el.className).toBe('test')\n            beforeEnterSpy(el)\n          },\n          enter: (el) => {\n            expect(vm.$el.contains(el)).toBe(true)\n            onEnterSpy(el)\n          },\n          afterEnter: (el) => afterEnterSpy(el)\n        }\n      }).$mount(el)\n\n      // should not apply transition on initial render by default\n      expect(vm.$el.innerHTML).toBe('<div class=\"test\">foo</div>')\n\n      let _el = vm.$el.children[0]\n      vm.ok = false\n      waitForUpdate(() => {\n        expect(beforeLeaveSpy).toHaveBeenCalledWith(_el)\n        expect(onLeaveSpy).toHaveBeenCalledWith(_el)\n        expect(vm.$el.children[0].className).toBe('test test-leave test-leave-active')\n      }).thenWaitFor(nextFrame).then(() => {\n        expect(afterLeaveSpy).not.toHaveBeenCalled()\n        expect(vm.$el.children[0].className).toBe('test test-leave-active test-leave-to')\n      }).thenWaitFor(duration + buffer).then(() => {\n        expect(afterLeaveSpy).toHaveBeenCalledWith(_el)\n        expect(vm.$el.children.length).toBe(0)\n        vm.ok = true\n      }).then(() => {\n        _el = vm.$el.children[0]\n        expect(beforeEnterSpy).toHaveBeenCalledWith(_el)\n        expect(onEnterSpy).toHaveBeenCalledWith(_el)\n        expect(vm.$el.children[0].className).toBe('test test-enter test-enter-active')\n      }).thenWaitFor(nextFrame).then(() => {\n        expect(afterEnterSpy).not.toHaveBeenCalled()\n        expect(vm.$el.children[0].className).toBe('test test-enter-active test-enter-to')\n      }).thenWaitFor(duration + buffer).then(() => {\n        expect(afterEnterSpy).toHaveBeenCalledWith(_el)\n        expect(vm.$el.children[0].className).toBe('test')\n      }).then(done)\n    })\n\n    it('transition events (v-show)', done => {\n      const onLeaveSpy = jasmine.createSpy('leave')\n      const onEnterSpy = jasmine.createSpy('enter')\n      const beforeLeaveSpy = jasmine.createSpy('beforeLeave')\n      const beforeEnterSpy = jasmine.createSpy('beforeEnter')\n      const afterLeaveSpy = jasmine.createSpy('afterLeave')\n      const afterEnterSpy = jasmine.createSpy('afterEnter')\n\n      const vm = new Vue({\n        template: `\n          <div>\n            <transition\n              name=\"test\"\n              @before-enter=\"beforeEnter\"\n              @enter=\"enter\"\n              @after-enter=\"afterEnter\"\n              @before-leave=\"beforeLeave\"\n              @leave=\"leave\"\n              @after-leave=\"afterLeave\">\n              <div v-show=\"ok\" class=\"test\">foo</div>\n            </transition>\n          </div>\n        `,\n        data: { ok: true },\n        methods: {\n          beforeLeave: (el) => {\n            expect(el.style.display).toBe('')\n            expect(el).toBe(vm.$el.children[0])\n            expect(el.className).toBe('test')\n            beforeLeaveSpy(el)\n          },\n          leave: (el) => {\n            expect(el.style.display).toBe('')\n            onLeaveSpy(el)\n          },\n          afterLeave: (el) => {\n            expect(el.style.display).toBe('none')\n            afterLeaveSpy(el)\n          },\n          beforeEnter: (el) => {\n            expect(el.className).toBe('test')\n            expect(el.style.display).toBe('none')\n            beforeEnterSpy(el)\n          },\n          enter: (el) => {\n            expect(el.style.display).toBe('')\n            onEnterSpy(el)\n          },\n          afterEnter: (el) => {\n            expect(el.style.display).toBe('')\n            afterEnterSpy(el)\n          }\n        }\n      }).$mount(el)\n\n      // should not apply transition on initial render by default\n      expect(vm.$el.innerHTML).toBe('<div class=\"test\">foo</div>')\n\n      let _el = vm.$el.children[0]\n      vm.ok = false\n      waitForUpdate(() => {\n        expect(beforeLeaveSpy).toHaveBeenCalledWith(_el)\n        expect(onLeaveSpy).toHaveBeenCalledWith(_el)\n        expect(vm.$el.children[0].className).toBe('test test-leave test-leave-active')\n      }).thenWaitFor(nextFrame).then(() => {\n        expect(afterLeaveSpy).not.toHaveBeenCalled()\n        expect(vm.$el.children[0].className).toBe('test test-leave-active test-leave-to')\n      }).thenWaitFor(duration + buffer).then(() => {\n        expect(afterLeaveSpy).toHaveBeenCalledWith(_el)\n        expect(vm.$el.children[0].style.display).toBe('none')\n        vm.ok = true\n      }).then(() => {\n        _el = vm.$el.children[0]\n        expect(beforeEnterSpy).toHaveBeenCalledWith(_el)\n        expect(onEnterSpy).toHaveBeenCalledWith(_el)\n        expect(vm.$el.children[0].className).toBe('test test-enter test-enter-active')\n      }).thenWaitFor(nextFrame).then(() => {\n        expect(afterEnterSpy).not.toHaveBeenCalled()\n        expect(vm.$el.children[0].className).toBe('test test-enter-active test-enter-to')\n      }).thenWaitFor(duration + buffer).then(() => {\n        expect(afterEnterSpy).toHaveBeenCalledWith(_el)\n        expect(vm.$el.children[0].className).toBe('test')\n      }).then(done)\n    })\n\n    it('explicit user callback in JavaScript hooks', done => {\n      let next\n      const vm = new Vue({\n        template: `<div>\n          <transition name=\"test\" @enter=\"enter\" @leave=\"leave\">\n            <div v-if=\"ok\" class=\"test\">foo</div>\n          </transition>\n        </div>`,\n        data: { ok: true },\n        methods: {\n          enter: (el, cb) => {\n            next = cb\n          },\n          leave: (el, cb) => {\n            next = cb\n          }\n        }\n      }).$mount(el)\n      vm.ok = false\n      waitForUpdate(() => {\n        expect(vm.$el.children[0].className).toBe('test test-leave test-leave-active')\n      }).thenWaitFor(nextFrame).then(() => {\n        expect(vm.$el.children[0].className).toBe('test test-leave-active test-leave-to')\n      }).thenWaitFor(duration + buffer).then(() => {\n        expect(vm.$el.children[0].className).toBe('test test-leave-active test-leave-to')\n        expect(next).toBeTruthy()\n        next()\n        expect(vm.$el.children.length).toBe(0)\n      }).then(() => {\n        vm.ok = true\n      }).then(() => {\n        expect(vm.$el.children[0].className).toBe('test test-enter test-enter-active')\n      }).thenWaitFor(nextFrame).then(() => {\n        expect(vm.$el.children[0].className).toBe('test test-enter-active test-enter-to')\n      }).thenWaitFor(duration + buffer).then(() => {\n        expect(vm.$el.children[0].className).toBe('test test-enter-active test-enter-to')\n        expect(next).toBeTruthy()\n        next()\n        expect(vm.$el.children[0].className).toBe('test')\n      }).then(done)\n    })\n\n    it('css: false', done => {\n      const enterSpy = jasmine.createSpy('enter')\n      const leaveSpy = jasmine.createSpy('leave')\n      const vm = new Vue({\n        template: `\n          <div>\n            <transition :css=\"false\" name=\"test\" @enter=\"enter\" @leave=\"leave\">\n              <div v-if=\"ok\" class=\"test\">foo</div>\n            </transition>\n          </div>\n        `,\n        data: { ok: true },\n        methods: {\n          enter: enterSpy,\n          leave: leaveSpy\n        }\n      }).$mount(el)\n\n      vm.ok = false\n      waitForUpdate(() => {\n        expect(leaveSpy).toHaveBeenCalled()\n        expect(vm.$el.innerHTML).toBe('<!---->')\n        vm.ok = true\n      }).then(() => {\n        expect(enterSpy).toHaveBeenCalled()\n        expect(vm.$el.innerHTML).toBe('<div class=\"test\">foo</div>')\n      }).then(done)\n    })\n\n    it('no transition detected', done => {\n      const enterSpy = jasmine.createSpy('enter')\n      const leaveSpy = jasmine.createSpy('leave')\n      const vm = new Vue({\n        template: '<div><transition name=\"nope\" @enter=\"enter\" @leave=\"leave\"><div v-if=\"ok\">foo</div></transition></div>',\n        data: { ok: true },\n        methods: {\n          enter: enterSpy,\n          leave: leaveSpy\n        }\n      }).$mount(el)\n\n      vm.ok = false\n      waitForUpdate(() => {\n        expect(leaveSpy).toHaveBeenCalled()\n        expect(vm.$el.innerHTML).toBe('<div class=\"nope-leave nope-leave-active\">foo</div><!---->')\n      }).thenWaitFor(nextFrame).then(() => {\n        expect(vm.$el.innerHTML).toBe('<!---->')\n        vm.ok = true\n      }).then(() => {\n        expect(enterSpy).toHaveBeenCalled()\n        expect(vm.$el.innerHTML).toBe('<div class=\"nope-enter nope-enter-active\">foo</div>')\n      }).thenWaitFor(nextFrame).then(() => {\n        expect(vm.$el.innerHTML).toBe('<div>foo</div>')\n      }).then(done)\n    })\n\n    it('enterCancelled', done => {\n      const spy = jasmine.createSpy('enterCancelled')\n      const vm = new Vue({\n        template: `\n          <div>\n            <transition name=\"test\" @enter-cancelled=\"enterCancelled\">\n              <div v-if=\"ok\" class=\"test\">foo</div>\n            </transition>\n          </div>\n        `,\n        data: { ok: false },\n        methods: {\n          enterCancelled: spy\n        }\n      }).$mount(el)\n\n      expect(vm.$el.innerHTML).toBe('<!---->')\n      vm.ok = true\n      waitForUpdate(() => {\n        expect(vm.$el.children[0].className).toBe('test test-enter test-enter-active')\n      }).thenWaitFor(nextFrame).then(() => {\n        expect(vm.$el.children[0].className).toBe('test test-enter-active test-enter-to')\n      }).thenWaitFor(duration / 2).then(() => {\n        vm.ok = false\n      }).then(() => {\n        expect(spy).toHaveBeenCalled()\n        expect(vm.$el.children[0].className).toBe('test test-leave test-leave-active')\n      }).thenWaitFor(nextFrame).then(() => {\n        expect(vm.$el.children[0].className).toBe('test test-leave-active test-leave-to')\n      }).thenWaitFor(duration + buffer).then(() => {\n        expect(vm.$el.children.length).toBe(0)\n      }).then(done)\n    })\n\n    it('should remove stale leaving elements', done => {\n      const spy = jasmine.createSpy('afterLeave')\n      const vm = new Vue({\n        template: `\n          <div>\n            <transition name=\"test\" @after-leave=\"afterLeave\">\n              <div v-if=\"ok\" class=\"test\">foo</div>\n            </transition>\n          </div>\n        `,\n        data: { ok: true },\n        methods: {\n          afterLeave: spy\n        }\n      }).$mount(el)\n\n      expect(vm.$el.innerHTML).toBe('<div class=\"test\">foo</div>')\n      vm.ok = false\n      waitForUpdate(() => {\n        expect(vm.$el.children[0].className).toBe('test test-leave test-leave-active')\n      }).thenWaitFor(duration / 2).then(() => {\n        vm.ok = true\n      }).then(() => {\n        expect(spy).toHaveBeenCalled()\n        expect(vm.$el.children.length).toBe(1) // should have removed leaving element\n        expect(vm.$el.children[0].className).toBe('test test-enter test-enter-active')\n      }).thenWaitFor(nextFrame).then(() => {\n        expect(vm.$el.children[0].className).toBe('test test-enter-active test-enter-to')\n      }).thenWaitFor(duration + buffer).then(() => {\n        expect(vm.$el.innerHTML).toBe('<div class=\"test\">foo</div>')\n      }).then(done)\n    })\n\n    it('transition with v-show', done => {\n      const vm = new Vue({\n        template: `\n          <div>\n            <transition name=\"test\">\n              <div v-show=\"ok\" class=\"test\">foo</div>\n            </transition>\n          </div>\n        `,\n        data: { ok: true }\n      }).$mount(el)\n\n      // should not apply transition on initial render by default\n      expect(vm.$el.textContent).toBe('foo')\n      expect(vm.$el.children[0].style.display).toBe('')\n      expect(vm.$el.children[0].className).toBe('test')\n      vm.ok = false\n      waitForUpdate(() => {\n        expect(vm.$el.children[0].className).toBe('test test-leave test-leave-active')\n      }).thenWaitFor(nextFrame).then(() => {\n        expect(vm.$el.children[0].className).toBe('test test-leave-active test-leave-to')\n      }).thenWaitFor(duration + buffer).then(() => {\n        expect(vm.$el.children[0].style.display).toBe('none')\n        vm.ok = true\n      }).then(() => {\n        expect(vm.$el.children[0].style.display).toBe('')\n        expect(vm.$el.children[0].className).toBe('test test-enter test-enter-active')\n      }).thenWaitFor(nextFrame).then(() => {\n        expect(vm.$el.children[0].className).toBe('test test-enter-active test-enter-to')\n      }).thenWaitFor(duration + buffer).then(() => {\n        expect(vm.$el.children[0].className).toBe('test')\n      }).then(done)\n    })\n\n    it('transition with v-show, inside child component', done => {\n      const vm = new Vue({\n        template: `\n          <div>\n            <test v-show=\"ok\"></test>\n          </div>\n        `,\n        data: { ok: true },\n        components: {\n          test: {\n            template: `<transition name=\"test\"><div class=\"test\">foo</div></transition>`\n          }\n        }\n      }).$mount(el)\n\n      // should not apply transition on initial render by default\n      expect(vm.$el.textContent).toBe('foo')\n      expect(vm.$el.children[0].style.display).toBe('')\n      vm.ok = false\n      waitForUpdate(() => {\n        expect(vm.$el.children[0].className).toBe('test test-leave test-leave-active')\n      }).thenWaitFor(nextFrame).then(() => {\n        expect(vm.$el.children[0].className).toBe('test test-leave-active test-leave-to')\n      }).thenWaitFor(duration + buffer).then(() => {\n        expect(vm.$el.children[0].style.display).toBe('none')\n        vm.ok = true\n      }).then(() => {\n        expect(vm.$el.children[0].style.display).toBe('')\n        expect(vm.$el.children[0].className).toBe('test test-enter test-enter-active')\n      }).thenWaitFor(nextFrame).then(() => {\n        expect(vm.$el.children[0].className).toBe('test test-enter-active test-enter-to')\n      }).thenWaitFor(duration + buffer).then(() => {\n        expect(vm.$el.children[0].className).toBe('test')\n      }).then(done)\n    })\n\n    it('leaveCancelled (v-show only)', done => {\n      const spy = jasmine.createSpy('leaveCancelled')\n      const vm = new Vue({\n        template: `\n          <div>\n            <transition name=\"test\" @leave-cancelled=\"leaveCancelled\">\n              <div v-show=\"ok\" class=\"test\">foo</div>\n            </transition>\n          </div>\n        `,\n        data: { ok: true },\n        methods: {\n          leaveCancelled: spy\n        }\n      }).$mount(el)\n\n      expect(vm.$el.children[0].style.display).toBe('')\n      vm.ok = false\n      waitForUpdate(() => {\n        expect(vm.$el.children[0].className).toBe('test test-leave test-leave-active')\n      }).thenWaitFor(nextFrame).then(() => {\n        expect(vm.$el.children[0].className).toBe('test test-leave-active test-leave-to')\n      }).thenWaitFor(10).then(() => {\n        vm.ok = true\n      }).then(() => {\n        expect(spy).toHaveBeenCalled()\n        expect(vm.$el.children[0].className).toBe('test test-enter test-enter-active')\n      }).thenWaitFor(nextFrame).then(() => {\n        expect(vm.$el.children[0].className).toBe('test test-enter-active test-enter-to')\n      }).thenWaitFor(duration + buffer).then(() => {\n        expect(vm.$el.children[0].style.display).toBe('')\n      }).then(done)\n    })\n\n    it('leave transition with v-show: cancelled on next frame', done => {\n      const vm = new Vue({\n        template: `\n          <div>\n            <transition name=\"test\">\n              <div v-show=\"ok\" class=\"test\">foo</div>\n            </transition>\n          </div>\n        `,\n        data: { ok: true }\n      }).$mount(el)\n\n      vm.ok = false\n      waitForUpdate(() => {\n        vm.ok = true\n      }).thenWaitFor(nextFrame).then(() => {\n        expect(vm.$el.children[0].className).toBe('test test-enter-active test-enter-to')\n      }).thenWaitFor(duration + buffer).then(() => {\n        expect(vm.$el.children[0].className).toBe('test')\n      }).then(done)\n    })\n\n    it('enter transition with v-show: cancelled on next frame', done => {\n      const vm = new Vue({\n        template: `\n          <div>\n            <transition name=\"test\">\n              <div v-show=\"ok\" class=\"test\">foo</div>\n            </transition>\n          </div>\n        `,\n        data: { ok: false }\n      }).$mount(el)\n\n      vm.ok = true\n      waitForUpdate(() => {\n        vm.ok = false\n      }).thenWaitFor(nextFrame).then(() => {\n        expect(vm.$el.children[0].className).toBe('test test-leave-active test-leave-to')\n      }).thenWaitFor(duration + buffer).then(() => {\n        expect(vm.$el.children[0].className).toBe('test')\n      }).then(done)\n    })\n\n    it('animations', done => {\n      const vm = new Vue({\n        template: `\n          <div>\n            <transition name=\"test-anim\">\n              <div v-if=\"ok\">foo</div>\n            </transition>\n          </div>\n        `,\n        data: { ok: true }\n      }).$mount(el)\n\n      // should not apply transition on initial render by default\n      expect(vm.$el.innerHTML).toBe('<div>foo</div>')\n      vm.ok = false\n      waitForUpdate(() => {\n        expect(vm.$el.children[0].className).toBe('test-anim-leave test-anim-leave-active')\n      }).thenWaitFor(nextFrame).then(() => {\n        expect(vm.$el.children[0].className).toBe('test-anim-leave-active test-anim-leave-to')\n      }).thenWaitFor(duration + buffer).then(() => {\n        expect(vm.$el.children.length).toBe(0)\n        vm.ok = true\n      }).then(() => {\n        expect(vm.$el.children[0].className).toBe('test-anim-enter test-anim-enter-active')\n      }).thenWaitFor(nextFrame).then(() => {\n        expect(vm.$el.children[0].className).toBe('test-anim-enter-active test-anim-enter-to')\n      }).thenWaitFor(duration + buffer).then(() => {\n        expect(vm.$el.children[0].className).toBe('')\n      }).then(done)\n    })\n\n    it('explicit transition type', done => {\n      const vm = new Vue({\n        template: `\n          <div>\n            <transition name=\"test-anim-long\" type=\"animation\">\n              <div v-if=\"ok\" class=\"test\">foo</div>\n            </transition>\n          </div>\n        `,\n        data: { ok: true }\n      }).$mount(el)\n\n      // should not apply transition on initial render by default\n      expect(vm.$el.innerHTML).toBe('<div class=\"test\">foo</div>')\n      vm.ok = false\n      waitForUpdate(() => {\n        expect(vm.$el.children[0].className).toBe('test test-anim-long-leave test-anim-long-leave-active')\n      }).thenWaitFor(nextFrame).then(() => {\n        expect(vm.$el.children[0].className).toBe('test test-anim-long-leave-active test-anim-long-leave-to')\n      }).thenWaitFor(duration + 5).then(() => {\n        // should not end early due to transition presence\n        expect(vm.$el.children[0].className).toBe('test test-anim-long-leave-active test-anim-long-leave-to')\n      }).thenWaitFor(duration + 5).then(() => {\n        expect(vm.$el.children.length).toBe(0)\n        vm.ok = true\n      }).then(() => {\n        expect(vm.$el.children[0].className).toBe('test test-anim-long-enter test-anim-long-enter-active')\n      }).thenWaitFor(nextFrame).then(() => {\n        expect(vm.$el.children[0].className).toBe('test test-anim-long-enter-active test-anim-long-enter-to')\n      }).thenWaitFor(duration + 5).then(() => {\n        expect(vm.$el.children[0].className).toBe('test test-anim-long-enter-active test-anim-long-enter-to')\n      }).thenWaitFor(duration + 5).then(() => {\n        expect(vm.$el.children[0].className).toBe('test')\n      }).then(done)\n    })\n\n    it('transition on appear', done => {\n      const vm = new Vue({\n        template: `\n          <div>\n            <transition name=\"test\"\n              appear\n              appear-class=\"test-appear\"\n              appear-to-class=\"test-appear-to\"\n              appear-active-class=\"test-appear-active\">\n              <div v-if=\"ok\" class=\"test\">foo</div>\n            </transition>\n          </div>\n        `,\n        data: { ok: true }\n      }).$mount(el)\n\n      waitForUpdate(() => {\n        expect(vm.$el.children[0].className).toBe('test test-appear test-appear-active')\n      }).thenWaitFor(nextFrame).then(() => {\n        expect(vm.$el.children[0].className).toBe('test test-appear-active test-appear-to')\n      }).thenWaitFor(duration + buffer).then(() => {\n        expect(vm.$el.children[0].className).toBe('test')\n      }).then(done)\n    })\n\n    it('transition on appear with v-show', done => {\n      const vm = new Vue({\n        template: `\n          <div>\n            <transition name=\"test\" appear>\n              <div v-show=\"ok\" class=\"test\">foo</div>\n            </transition>\n          </div>\n        `,\n        data: { ok: true }\n      }).$mount(el)\n\n      waitForUpdate(() => {\n        expect(vm.$el.children[0].className).toBe('test test-enter test-enter-active')\n      }).thenWaitFor(nextFrame).then(() => {\n        expect(vm.$el.children[0].className).toBe('test test-enter-active test-enter-to')\n      }).thenWaitFor(duration + buffer).then(() => {\n        expect(vm.$el.children[0].className).toBe('test')\n      }).then(done)\n    })\n\n    it('transition on SVG elements', done => {\n      const vm = new Vue({\n        template: `\n          <svg>\n            <transition>\n              <circle cx=\"0\" cy=\"0\" r=\"10\" v-if=\"ok\" class=\"test\"></circle>\n            </transition>\n          </svg>\n        `,\n        data: { ok: true }\n      }).$mount(el)\n\n      // should not apply transition on initial render by default\n      expect(vm.$el.childNodes[0].getAttribute('class')).toBe('test')\n      vm.ok = false\n      waitForUpdate(() => {\n        expect(vm.$el.childNodes[0].getAttribute('class')).toBe('test v-leave v-leave-active')\n      }).thenWaitFor(nextFrame).then(() => {\n        expect(vm.$el.childNodes[0].getAttribute('class')).toBe('test v-leave-active v-leave-to')\n      }).thenWaitFor(duration + buffer).then(() => {\n        expect(vm.$el.childNodes.length).toBe(1)\n        expect(vm.$el.childNodes[0].nodeType).toBe(8) // should be an empty comment node\n        expect(vm.$el.childNodes[0].textContent).toBe('')\n        vm.ok = true\n      }).then(() => {\n        expect(vm.$el.childNodes[0].getAttribute('class')).toBe('test v-enter v-enter-active')\n      }).thenWaitFor(nextFrame).then(() => {\n        expect(vm.$el.childNodes[0].getAttribute('class')).toBe('test v-enter-active v-enter-to')\n      }).thenWaitFor(duration + buffer).then(() => {\n        expect(vm.$el.childNodes[0].getAttribute('class')).toBe('test')\n      }).then(done)\n    })\n\n    it('transition on child components', done => {\n      const vm = new Vue({\n        template: `\n          <div>\n            <transition>\n              <test v-if=\"ok\" class=\"test\"></test>\n            </transition>\n          </div>\n        `,\n        data: { ok: true },\n        components: {\n          test: {\n            template: `\n              <transition name=\"test\">\n                <div>foo</div>\n              </transition>\n            ` // test transition override from parent\n          }\n        }\n      }).$mount(el)\n\n      // should not apply transition on initial render by default\n      expect(vm.$el.innerHTML).toBe('<div class=\"test\">foo</div>')\n      vm.ok = false\n      waitForUpdate(() => {\n        expect(vm.$el.children[0].className).toBe('test v-leave v-leave-active')\n      }).thenWaitFor(nextFrame).then(() => {\n        expect(vm.$el.children[0].className).toBe('test v-leave-active v-leave-to')\n      }).thenWaitFor(duration + buffer).then(() => {\n        expect(vm.$el.children.length).toBe(0)\n        vm.ok = true\n      }).then(() => {\n        expect(vm.$el.children[0].className).toBe('test v-enter v-enter-active')\n      }).thenWaitFor(nextFrame).then(() => {\n        expect(vm.$el.children[0].className).toBe('test v-enter-active v-enter-to')\n      }).thenWaitFor(duration + buffer).then(() => {\n        expect(vm.$el.children[0].className).toBe('test')\n      }).then(done)\n    })\n\n    it('transition inside child component', done => {\n      const vm = new Vue({\n        template: `\n          <div>\n            <test v-if=\"ok\" class=\"test\"></test>\n          </div>\n        `,\n        data: { ok: true },\n        components: {\n          test: {\n            template: `\n              <transition>\n                <div>foo</div>\n              </transition>\n            `\n          }\n        }\n      }).$mount(el)\n\n      // should not apply transition on initial render by default\n      expect(vm.$el.innerHTML).toBe('<div class=\"test\">foo</div>')\n      vm.ok = false\n      waitForUpdate(() => {\n        expect(vm.$el.children[0].className).toBe('test v-leave v-leave-active')\n      }).thenWaitFor(nextFrame).then(() => {\n        expect(vm.$el.children[0].className).toBe('test v-leave-active v-leave-to')\n      }).thenWaitFor(duration + buffer).then(() => {\n        expect(vm.$el.children.length).toBe(0)\n        vm.ok = true\n      }).then(() => {\n        expect(vm.$el.children[0].className).toBe('test v-enter v-enter-active')\n      }).thenWaitFor(nextFrame).then(() => {\n        expect(vm.$el.children[0].className).toBe('test v-enter-active v-enter-to')\n      }).thenWaitFor(duration + buffer).then(() => {\n        expect(vm.$el.children[0].className).toBe('test')\n      }).then(done)\n    })\n\n    it('custom transition higher-order component', done => {\n      const vm = new Vue({\n        template: '<div><my-transition><div v-if=\"ok\" class=\"test\">foo</div></my-transition></div>',\n        data: { ok: true },\n        components: {\n          'my-transition': {\n            functional: true,\n            render (h, { data, children }) {\n              (data.props || (data.props = {})).name = 'test'\n              return h('transition', data, children)\n            }\n          }\n        }\n      }).$mount(el)\n\n      // should not apply transition on initial render by default\n      expect(vm.$el.innerHTML).toBe('<div class=\"test\">foo</div>')\n      vm.ok = false\n      waitForUpdate(() => {\n        expect(vm.$el.children[0].className).toBe('test test-leave test-leave-active')\n      }).thenWaitFor(nextFrame).then(() => {\n        expect(vm.$el.children[0].className).toBe('test test-leave-active test-leave-to')\n      }).thenWaitFor(duration + buffer).then(() => {\n        expect(vm.$el.children.length).toBe(0)\n        vm.ok = true\n      }).then(() => {\n        expect(vm.$el.children[0].className).toBe('test test-enter test-enter-active')\n      }).thenWaitFor(nextFrame).then(() => {\n        expect(vm.$el.children[0].className).toBe('test test-enter-active test-enter-to')\n      }).thenWaitFor(duration + buffer).then(() => {\n        expect(vm.$el.children[0].className).toBe('test')\n      }).then(done)\n    })\n\n    it('warn when used on multiple elements', () => {\n      new Vue({\n        template: `<transition><p>1</p><p>2</p></transition>`\n      }).$mount()\n      expect(`<transition> can only be used on a single element`).toHaveBeenWarned()\n    })\n\n    describe('explicit durations -', () => {\n      it('single value', done => {\n        const vm = new Vue({\n          template: `\n            <div>\n              <transition duration=\"${explicitDuration}\">\n                <div v-if=\"ok\" class=\"test\">foo</div>\n              </transition>\n            </div>\n          `,\n          data: { ok: true }\n        }).$mount(el)\n\n        vm.ok = false\n\n        waitForUpdate(() => {\n          expect(vm.$el.children[0].className).toBe('test v-leave v-leave-active')\n        }).thenWaitFor(nextFrame).then(() => {\n          expect(vm.$el.children[0].className).toBe('test v-leave-active v-leave-to')\n        }).thenWaitFor(explicitDuration + buffer).then(() => {\n          expect(vm.$el.children.length).toBe(0)\n          vm.ok = true\n        }).then(() => {\n          expect(vm.$el.children[0].className).toBe('test v-enter v-enter-active')\n        }).thenWaitFor(nextFrame).then(() => {\n          expect(vm.$el.children[0].className).toBe('test v-enter-active v-enter-to')\n        }).thenWaitFor(explicitDuration + buffer).then(() => {\n          expect(vm.$el.children[0].className).toBe('test')\n        }).then(done)\n      })\n\n      it('enter and auto leave', done => {\n        const vm = new Vue({\n          template: `\n            <div>\n              <transition :duration=\"{ enter: ${explicitDuration} }\">\n                <div v-if=\"ok\" class=\"test\">foo</div>\n              </transition>\n            </div>\n          `,\n          data: { ok: true }\n        }).$mount(el)\n\n        vm.ok = false\n\n        waitForUpdate(() => {\n          expect(vm.$el.children[0].className).toBe('test v-leave v-leave-active')\n        }).thenWaitFor(nextFrame).then(() => {\n          expect(vm.$el.children[0].className).toBe('test v-leave-active v-leave-to')\n        }).thenWaitFor(duration + buffer).then(() => {\n          expect(vm.$el.children.length).toBe(0)\n          vm.ok = true\n        }).then(() => {\n          expect(vm.$el.children[0].className).toBe('test v-enter v-enter-active')\n        }).thenWaitFor(nextFrame).then(() => {\n          expect(vm.$el.children[0].className).toBe('test v-enter-active v-enter-to')\n        }).thenWaitFor(explicitDuration + buffer).then(() => {\n          expect(vm.$el.children[0].className).toBe('test')\n        }).then(done)\n      })\n\n      it('leave and auto enter', done => {\n        const vm = new Vue({\n          template: `\n            <div>\n              <transition :duration=\"{ leave: ${explicitDuration} }\">\n                <div v-if=\"ok\" class=\"test\">foo</div>\n              </transition>\n            </div>\n          `,\n          data: { ok: true }\n        }).$mount(el)\n\n        vm.ok = false\n\n        waitForUpdate(() => {\n          expect(vm.$el.children[0].className).toBe('test v-leave v-leave-active')\n        }).thenWaitFor(nextFrame).then(() => {\n          expect(vm.$el.children[0].className).toBe('test v-leave-active v-leave-to')\n        }).thenWaitFor(explicitDuration + buffer).then(() => {\n          expect(vm.$el.children.length).toBe(0)\n          vm.ok = true\n        }).then(() => {\n          expect(vm.$el.children[0].className).toBe('test v-enter v-enter-active')\n        }).thenWaitFor(nextFrame).then(() => {\n          expect(vm.$el.children[0].className).toBe('test v-enter-active v-enter-to')\n        }).thenWaitFor(duration + buffer).then(() => {\n          expect(vm.$el.children[0].className).toBe('test')\n        }).then(done)\n      })\n\n      it('separate enter and leave', done => {\n        const enter = explicitDuration\n        const leave = explicitDuration * 2\n\n        const vm = new Vue({\n          template: `\n            <div>\n              <transition :duration=\"{ enter: ${enter}, leave: ${leave} }\">\n                <div v-if=\"ok\" class=\"test\">foo</div>\n              </transition>\n            </div>\n          `,\n          data: { ok: true }\n        }).$mount(el)\n\n        vm.ok = false\n\n        waitForUpdate(() => {\n          expect(vm.$el.children[0].className).toBe('test v-leave v-leave-active')\n        }).thenWaitFor(nextFrame).then(() => {\n          expect(vm.$el.children[0].className).toBe('test v-leave-active v-leave-to')\n        }).thenWaitFor(leave + buffer).then(() => {\n          expect(vm.$el.children.length).toBe(0)\n          vm.ok = true\n        }).then(() => {\n          expect(vm.$el.children[0].className).toBe('test v-enter v-enter-active')\n        }).thenWaitFor(nextFrame).then(() => {\n          expect(vm.$el.children[0].className).toBe('test v-enter-active v-enter-to')\n        }).thenWaitFor(enter + buffer).then(() => {\n          expect(vm.$el.children[0].className).toBe('test')\n        }).then(done)\n      })\n\n      it('enter and leave + duration change', done => {\n        const enter1 = explicitDuration * 2\n        const enter2 = explicitDuration\n        const leave1 = explicitDuration * 0.5\n        const leave2 = explicitDuration * 3\n\n        const vm = new Vue({\n          template: `\n            <div>\n              <transition :duration=\"{ enter: enter, leave: leave }\">\n                <div v-if=\"ok\" class=\"test\">foo</div>\n              </transition>\n            </div>\n          `,\n          data: {\n            ok: true,\n            enter: enter1,\n            leave: leave1\n          }\n        }).$mount(el)\n\n        vm.ok = false\n\n        waitForUpdate(() => {\n          expect(vm.$el.children[0].className).toBe('test v-leave v-leave-active')\n        }).thenWaitFor(nextFrame).then(() => {\n          expect(vm.$el.children[0].className).toBe('test v-leave-active v-leave-to')\n        }).thenWaitFor(leave1 + buffer).then(() => {\n          expect(vm.$el.children.length).toBe(0)\n          vm.ok = true\n        }).then(() => {\n          expect(vm.$el.children[0].className).toBe('test v-enter v-enter-active')\n        }).thenWaitFor(nextFrame).then(() => {\n          expect(vm.$el.children[0].className).toBe('test v-enter-active v-enter-to')\n        }).thenWaitFor(enter1 + buffer).then(() => {\n          expect(vm.$el.children[0].className).toBe('test')\n          vm.enter = enter2\n          vm.leave = leave2\n        }).then(() => {\n          vm.ok = false\n        }).then(() => {\n          expect(vm.$el.children[0].className).toBe('test v-leave v-leave-active')\n        }).thenWaitFor(nextFrame).then(() => {\n          expect(vm.$el.children[0].className).toBe('test v-leave-active v-leave-to')\n        }).thenWaitFor(leave2 + buffer).then(() => {\n          expect(vm.$el.children.length).toBe(0)\n          vm.ok = true\n        }).then(() => {\n          expect(vm.$el.children[0].className).toBe('test v-enter v-enter-active')\n        }).thenWaitFor(nextFrame).then(() => {\n          expect(vm.$el.children[0].className).toBe('test v-enter-active v-enter-to')\n        }).thenWaitFor(enter2 + buffer).then(() => {\n          expect(vm.$el.children[0].className).toBe('test')\n        }).then(done)\n      }, 10000)\n\n      it('warn invalid durations', done => {\n        const vm = new Vue({\n          template: `\n            <div>\n              <transition :duration=\"{ enter: NaN, leave: 'foo' }\">\n                <div v-if=\"ok\" class=\"test\">foo</div>\n              </transition>\n            </div>\n          `,\n          data: {\n            ok: true\n          }\n        }).$mount(el)\n\n        vm.ok = false\n        waitForUpdate(() => {\n          expect(`<transition> explicit leave duration is not a valid number - got \"foo\"`).toHaveBeenWarned()\n        }).thenWaitFor(duration + buffer).then(() => {\n          vm.ok = true\n        }).then(() => {\n          expect(`<transition> explicit enter duration is NaN`).toHaveBeenWarned()\n        }).then(done)\n      })\n    })\n\n    // #6687\n    it('transition on child components with empty root node', done => {\n      const vm = new Vue({\n        template: `\n          <div>\n            <transition mode=\"out-in\">\n              <component class=\"test\" :is=\"view\"></component>\n            </transition>\n          </div>\n        `,\n        data: { view: 'one' },\n        components: {\n          'one': {\n            template: '<div v-if=\"false\">one</div>'\n          },\n          'two': {\n            template: '<div>two</div>'\n          }\n        }\n      }).$mount(el)\n\n      // should not apply transition on initial render by default\n      expect(vm.$el.innerHTML).toBe('<!---->')\n      vm.view = 'two'\n      waitForUpdate(() => {\n        expect(vm.$el.innerHTML).toBe('<div class=\"test v-enter v-enter-active\">two</div>')\n      }).thenWaitFor(nextFrame).then(() => {\n        expect(vm.$el.children[0].className).toBe('test v-enter-active v-enter-to')\n      }).thenWaitFor(duration + buffer).then(() => {\n        expect(vm.$el.children[0].className).toBe('test')\n        vm.view = 'one'\n      }).then(() => {\n        // incoming comment node is appended instantly because it doesn't have\n        // data and therefore doesn't go through the transition module.\n        expect(vm.$el.innerHTML).toBe('<div class=\"test v-leave v-leave-active\">two</div><!---->')\n      }).thenWaitFor(nextFrame).then(() => {\n        expect(vm.$el.children[0].className).toBe('test v-leave-active v-leave-to')\n      }).thenWaitFor(duration + buffer).then(() => {\n        expect(vm.$el.innerHTML).toBe('<!---->')\n      }).then(done)\n    })\n  })\n}\n"
  },
  {
    "path": "vue/test/unit/index.js",
    "content": "require('es6-promise/auto')\n\n// import all helpers\nconst helpersContext = require.context('../helpers', true)\nhelpersContext.keys().forEach(helpersContext)\n\n// require all test files\nconst testsContext = require.context('./', true, /\\.spec$/)\ntestsContext.keys().forEach(testsContext)\n"
  },
  {
    "path": "vue/test/unit/karma.base.config.js",
    "content": "var alias = require('../../scripts/alias')\nvar webpack = require('webpack')\n\nvar webpackConfig = {\n  resolve: {\n    alias: alias\n  },\n  module: {\n    rules: [\n      {\n        test: /\\.js$/,\n        loader: 'babel-loader',\n        exclude: /node_modules/\n      }\n    ]\n  },\n  plugins: [\n    new webpack.DefinePlugin({\n      __WEEX__: false,\n      'process.env': {\n        NODE_ENV: '\"development\"',\n        TRANSITION_DURATION: process.env.CI ? 100 : 50,\n        TRANSITION_BUFFER: 10\n      }\n    })\n  ],\n  devtool: '#inline-source-map'\n}\n\n// shared config for all unit tests\nmodule.exports = {\n  frameworks: ['jasmine'],\n  files: [\n    './index.js'\n  ],\n  preprocessors: {\n    './index.js': ['webpack', 'sourcemap']\n  },\n  webpack: webpackConfig,\n  webpackMiddleware: {\n    noInfo: true\n  },\n  plugins: [\n    'karma-jasmine',\n    'karma-mocha-reporter',\n    'karma-sourcemap-loader',\n    'karma-webpack'\n  ]\n}\n"
  },
  {
    "path": "vue/test/unit/karma.cover.config.js",
    "content": "var base = require('./karma.base.config.js')\n\nmodule.exports = function (config) {\n  var options = Object.assign(base, {\n    browsers: ['PhantomJS'],\n    reporters: ['mocha', 'coverage'],\n    coverageReporter: {\n      reporters: [\n        { type: 'lcov', dir: '../../coverage', subdir: '.' },\n        { type: 'text-summary', dir: '../../coverage', subdir: '.' }\n      ]\n    },\n    singleRun: true,\n    plugins: base.plugins.concat([\n      'karma-coverage',\n      'karma-phantomjs-launcher'\n    ])\n  })\n\n  // add babel-plugin-istanbul for code instrumentation\n  options.webpack.module.rules[0].options = {\n    plugins: [['istanbul', {\n      exclude: [\n        'test/',\n        'src/compiler/parser/html-parser.js',\n        'src/core/instance/proxy.js',\n        'src/sfc/deindent.js',\n        'src/platforms/weex/'\n      ]\n    }]]\n  }\n\n  config.set(options)\n}\n"
  },
  {
    "path": "vue/test/unit/karma.dev.config.js",
    "content": "var base = require('./karma.base.config.js')\n\nmodule.exports = function (config) {\n  config.set(Object.assign(base, {\n    browsers: ['PhantomJS'],\n    reporters: ['progress'],\n    plugins: base.plugins.concat([\n      'karma-phantomjs-launcher'\n    ])\n  }))\n}\n"
  },
  {
    "path": "vue/test/unit/karma.sauce.config.js",
    "content": "var webpack = require('webpack')\nvar base = require('./karma.base.config.js')\n\nbase.webpack.plugins = [\n  new webpack.DefinePlugin({\n    __WEEX__: false,\n    'process.env': {\n      NODE_ENV: '\"development\"',\n      // sauce lab vms are slow!\n      TRANSITION_DURATION: 500,\n      TRANSITION_BUFFER: 50\n    }\n  })\n]\n\n/**\n * Having too many tests running concurrently on saucelabs\n * causes timeouts and errors, so we have to run them in\n * smaller batches.\n */\n\nvar batches = [\n  // the cool kids\n  {\n    sl_chrome: {\n      base: 'SauceLabs',\n      browserName: 'chrome',\n      platform: 'Windows 7'\n    },\n    sl_firefox: {\n      base: 'SauceLabs',\n      browserName: 'firefox'\n    },\n    sl_mac_safari: {\n      base: 'SauceLabs',\n      browserName: 'safari',\n      platform: 'OS X 10.10'\n    }\n  },\n  // ie family\n  {\n    sl_ie_9: {\n      base: 'SauceLabs',\n      browserName: 'internet explorer',\n      platform: 'Windows 7',\n      version: '9'\n    },\n    sl_ie_10: {\n      base: 'SauceLabs',\n      browserName: 'internet explorer',\n      platform: 'Windows 8',\n      version: '10'\n    },\n    sl_ie_11: {\n      base: 'SauceLabs',\n      browserName: 'internet explorer',\n      platform: 'Windows 8.1',\n      version: '11'\n    },\n    sl_edge: {\n      base: 'SauceLabs',\n      browserName: 'MicrosoftEdge',\n      platform: 'Windows 10'\n    }\n  },\n  // mobile\n  {\n    sl_ios_safari_9: {\n      base: 'SauceLabs',\n      browserName: 'iphone',\n      version: '10.3'\n    },\n    sl_android_6_0: {\n      base: 'SauceLabs',\n      browserName: 'android',\n      version: '6.0'\n    }\n  }\n]\n\nmodule.exports = function (config) {\n  var batch = batches[process.argv[4] || 0]\n\n  config.set(Object.assign(base, {\n    singleRun: true,\n    browsers: Object.keys(batch),\n    customLaunchers: batch,\n    reporters: process.env.CI\n      ? ['dots', 'saucelabs'] // avoid spamming CI output\n      : ['progress', 'saucelabs'],\n    sauceLabs: {\n      testName: 'Vue.js unit tests',\n      recordScreenshots: false,\n      connectOptions: {\n        'no-ssl-bump-domains': 'all' // Ignore SSL error on Android emulator\n      },\n      build: process.env.CIRCLE_BUILD_NUM || process.env.SAUCE_BUILD_ID || Date.now()\n    },\n    // mobile emulators are really slow\n    captureTimeout: 300000,\n    browserNoActivityTimeout: 300000,\n    plugins: base.plugins.concat([\n      'karma-sauce-launcher'\n    ])\n  }))\n}\n"
  },
  {
    "path": "vue/test/unit/karma.unit.config.js",
    "content": "var base = require('./karma.base.config.js')\n\nmodule.exports = function (config) {\n  config.set(Object.assign(base, {\n    browsers: ['Chrome', 'Firefox', 'Safari'],\n    reporters: ['progress'],\n    singleRun: true,\n    plugins: base.plugins.concat([\n      'karma-chrome-launcher',\n      'karma-firefox-launcher',\n      'karma-safari-launcher'\n    ])\n  }))\n}\n"
  },
  {
    "path": "vue/test/unit/modules/compiler/codegen.spec.js",
    "content": "import { parse } from 'compiler/parser/index'\nimport { optimize } from 'compiler/optimizer'\nimport { generate } from 'compiler/codegen'\nimport { isObject, extend } from 'shared/util'\nimport { isReservedTag } from 'web/util/index'\nimport { baseOptions } from 'web/compiler/options'\n\nfunction assertCodegen (template, generatedCode, ...args) {\n  let staticRenderFnCodes = []\n  let generateOptions = baseOptions\n  let proc = null\n  let len = args.length\n  while (len--) {\n    const arg = args[len]\n    if (Array.isArray(arg)) {\n      staticRenderFnCodes = arg\n    } else if (isObject(arg)) {\n      generateOptions = arg\n    } else if (typeof arg === 'function') {\n      proc = arg\n    }\n  }\n  const ast = parse(template, baseOptions)\n  optimize(ast, baseOptions)\n  proc && proc(ast)\n  const res = generate(ast, generateOptions)\n  expect(res.render).toBe(generatedCode)\n  expect(res.staticRenderFns).toEqual(staticRenderFnCodes)\n}\n\n/* eslint-disable quotes */\ndescribe('codegen', () => {\n  it('generate directive', () => {\n    assertCodegen(\n      '<p v-custom1:arg1.modifier=\"value1\" v-custom2></p>',\n      `with(this){return _c('p',{directives:[{name:\"custom1\",rawName:\"v-custom1:arg1.modifier\",value:(value1),expression:\"value1\",arg:\"arg1\",modifiers:{\"modifier\":true}},{name:\"custom2\",rawName:\"v-custom2\"}]})}`\n    )\n  })\n\n  it('generate filters', () => {\n    assertCodegen(\n      '<div :id=\"a | b | c\">{{ d | e | f }}</div>',\n      `with(this){return _c('div',{attrs:{\"id\":_f(\"c\")(_f(\"b\")(a))}},[_v(_s(_f(\"f\")(_f(\"e\")(d))))])}`\n    )\n  })\n\n  it('generate filters with no arguments', () => {\n    assertCodegen(\n      '<div>{{ d | e() }}</div>',\n      `with(this){return _c('div',[_v(_s(_f(\"e\")(d)))])}`\n    )\n  })\n\n  it('generate v-for directive', () => {\n    assertCodegen(\n      '<div><li v-for=\"item in items\" :key=\"item.uid\"></li></div>',\n      `with(this){return _c('div',_l((items),function(item){return _c('li',{key:item.uid})}))}`\n    )\n    // iterator syntax\n    assertCodegen(\n      '<div><li v-for=\"(item, i) in items\"></li></div>',\n      `with(this){return _c('div',_l((items),function(item,i){return _c('li')}))}`\n    )\n    assertCodegen(\n      '<div><li v-for=\"(item, key, index) in items\"></li></div>',\n      `with(this){return _c('div',_l((items),function(item,key,index){return _c('li')}))}`\n    )\n    // destructuring\n    assertCodegen(\n      '<div><li v-for=\"{ a, b } in items\"></li></div>',\n      `with(this){return _c('div',_l((items),function({ a, b }){return _c('li')}))}`\n    )\n    assertCodegen(\n      '<div><li v-for=\"({ a, b }, key, index) in items\"></li></div>',\n      `with(this){return _c('div',_l((items),function({ a, b },key,index){return _c('li')}))}`\n    )\n    // v-for with extra element\n    assertCodegen(\n      '<div><p></p><li v-for=\"item in items\"></li></div>',\n      `with(this){return _c('div',[_c('p'),_l((items),function(item){return _c('li')})],2)}`\n    )\n  })\n\n  it('generate v-if directive', () => {\n    assertCodegen(\n      '<p v-if=\"show\">hello</p>',\n      `with(this){return (show)?_c('p',[_v(\"hello\")]):_e()}`\n    )\n  })\n\n  it('generate v-else directive', () => {\n    assertCodegen(\n      '<div><p v-if=\"show\">hello</p><p v-else>world</p></div>',\n      `with(this){return _c('div',[(show)?_c('p',[_v(\"hello\")]):_c('p',[_v(\"world\")])])}`\n    )\n  })\n\n  it('generate v-else-if directive', () => {\n    assertCodegen(\n      '<div><p v-if=\"show\">hello</p><p v-else-if=\"hide\">world</p></div>',\n      `with(this){return _c('div',[(show)?_c('p',[_v(\"hello\")]):(hide)?_c('p',[_v(\"world\")]):_e()])}`\n    )\n  })\n\n  it('generate v-else-if with v-else directive', () => {\n    assertCodegen(\n      '<div><p v-if=\"show\">hello</p><p v-else-if=\"hide\">world</p><p v-else>bye</p></div>',\n      `with(this){return _c('div',[(show)?_c('p',[_v(\"hello\")]):(hide)?_c('p',[_v(\"world\")]):_c('p',[_v(\"bye\")])])}`\n    )\n  })\n\n  it('generate multi v-else-if with v-else directive', () => {\n    assertCodegen(\n      '<div><p v-if=\"show\">hello</p><p v-else-if=\"hide\">world</p><p v-else-if=\"3\">elseif</p><p v-else>bye</p></div>',\n      `with(this){return _c('div',[(show)?_c('p',[_v(\"hello\")]):(hide)?_c('p',[_v(\"world\")]):(3)?_c('p',[_v(\"elseif\")]):_c('p',[_v(\"bye\")])])}`\n    )\n  })\n\n  it('generate ref', () => {\n    assertCodegen(\n      '<p ref=\"component1\"></p>',\n      `with(this){return _c('p',{ref:\"component1\"})}`\n    )\n  })\n\n  it('generate ref on v-for', () => {\n    assertCodegen(\n      '<ul><li v-for=\"item in items\" ref=\"component1\"></li></ul>',\n      `with(this){return _c('ul',_l((items),function(item){return _c('li',{ref:\"component1\",refInFor:true})}))}`\n    )\n  })\n\n  it('generate v-bind directive', () => {\n    assertCodegen(\n      '<p v-bind=\"test\"></p>',\n      `with(this){return _c('p',_b({},'p',test,false))}`\n    )\n  })\n\n  it('generate v-bind with prop directive', () => {\n    assertCodegen(\n      '<p v-bind.prop=\"test\"></p>',\n      `with(this){return _c('p',_b({},'p',test,true))}`\n    )\n  })\n\n  it('generate v-bind directive with sync modifier', () => {\n    assertCodegen(\n      '<p v-bind.sync=\"test\"></p>',\n      `with(this){return _c('p',_b({},'p',test,false,true))}`\n    )\n  })\n\n  it('generate template tag', () => {\n    assertCodegen(\n      '<div><template><p>{{hello}}</p></template></div>',\n      `with(this){return _c('div',[[_c('p',[_v(_s(hello))])]],2)}`\n    )\n  })\n\n  it('generate single slot', () => {\n    assertCodegen(\n      '<div><slot></slot></div>',\n      `with(this){return _c('div',[_t(\"default\")],2)}`\n    )\n  })\n\n  it('generate named slot', () => {\n    assertCodegen(\n      '<div><slot name=\"one\"></slot></div>',\n      `with(this){return _c('div',[_t(\"one\")],2)}`\n    )\n  })\n\n  it('generate slot fallback content', () => {\n    assertCodegen(\n      '<div><slot><div>hi</div></slot></div>',\n      `with(this){return _c('div',[_t(\"default\",[_c('div',[_v(\"hi\")])])],2)}`\n    )\n  })\n\n  it('generate slot target', () => {\n    assertCodegen(\n      '<p slot=\"one\">hello world</p>',\n      `with(this){return _c('p',{attrs:{\"slot\":\"one\"},slot:\"one\"},[_v(\"hello world\")])}`\n    )\n  })\n\n  it('generate scoped slot', () => {\n    assertCodegen(\n      '<foo><template slot-scope=\"bar\">{{ bar }}</template></foo>',\n      `with(this){return _c('foo',{scopedSlots:_u([{key:\"default\",fn:function(bar){return [_v(_s(bar))]}}])})}`\n    )\n    assertCodegen(\n      '<foo><div slot-scope=\"bar\">{{ bar }}</div></foo>',\n      `with(this){return _c('foo',{scopedSlots:_u([{key:\"default\",fn:function(bar){return _c('div',{},[_v(_s(bar))])}}])})}`\n    )\n  })\n\n  it('generate named scoped slot', () => {\n    assertCodegen(\n      '<foo><template slot=\"foo\" slot-scope=\"bar\">{{ bar }}</template></foo>',\n      `with(this){return _c('foo',{scopedSlots:_u([{key:\"foo\",fn:function(bar){return [_v(_s(bar))]}}])})}`\n    )\n    assertCodegen(\n      '<foo><div slot=\"foo\" slot-scope=\"bar\">{{ bar }}</div></foo>',\n      `with(this){return _c('foo',{scopedSlots:_u([{key:\"foo\",fn:function(bar){return _c('div',{},[_v(_s(bar))])}}])})}`\n    )\n  })\n\n  it('generate class binding', () => {\n    // static\n    assertCodegen(\n      '<p class=\"class1\">hello world</p>',\n      `with(this){return _c('p',{staticClass:\"class1\"},[_v(\"hello world\")])}`,\n    )\n    // dynamic\n    assertCodegen(\n      '<p :class=\"class1\">hello world</p>',\n      `with(this){return _c('p',{class:class1},[_v(\"hello world\")])}`\n    )\n  })\n\n  it('generate style binding', () => {\n    assertCodegen(\n      '<p :style=\"error\">hello world</p>',\n      `with(this){return _c('p',{style:(error)},[_v(\"hello world\")])}`\n    )\n  })\n\n  it('generate v-show directive', () => {\n    assertCodegen(\n      '<p v-show=\"shown\">hello world</p>',\n      `with(this){return _c('p',{directives:[{name:\"show\",rawName:\"v-show\",value:(shown),expression:\"shown\"}]},[_v(\"hello world\")])}`\n    )\n  })\n\n  it('generate DOM props with v-bind directive', () => {\n    // input + value\n    assertCodegen(\n      '<input :value=\"msg\">',\n      `with(this){return _c('input',{domProps:{\"value\":msg}})}`\n    )\n    // non input\n    assertCodegen(\n      '<p :value=\"msg\"/>',\n      `with(this){return _c('p',{attrs:{\"value\":msg}})}`\n    )\n  })\n\n  it('generate attrs with v-bind directive', () => {\n    assertCodegen(\n      '<input :name=\"field1\">',\n      `with(this){return _c('input',{attrs:{\"name\":field1}})}`\n    )\n  })\n\n  it('generate static attrs', () => {\n    assertCodegen(\n      '<input name=\"field1\">',\n      `with(this){return _c('input',{attrs:{\"name\":\"field1\"}})}`\n    )\n  })\n\n  it('generate events with v-on directive', () => {\n    assertCodegen(\n      '<input @input=\"onInput\">',\n      `with(this){return _c('input',{on:{\"input\":onInput}})}`\n    )\n  })\n\n  it('generate events with method call', () => {\n    assertCodegen(\n      '<input @input=\"onInput($event);\">',\n      `with(this){return _c('input',{on:{\"input\":function($event){onInput($event);}}})}`\n    )\n    // empty arguments\n    assertCodegen(\n      '<input @input=\"onInput();\">',\n      `with(this){return _c('input',{on:{\"input\":function($event){onInput();}}})}`\n    )\n    // without semicolon\n    assertCodegen(\n      '<input @input=\"onInput($event)\">',\n      `with(this){return _c('input',{on:{\"input\":function($event){onInput($event)}}})}`\n    )\n    // multiple args\n    assertCodegen(\n      '<input @input=\"onInput($event, \\'abc\\', 5);\">',\n      `with(this){return _c('input',{on:{\"input\":function($event){onInput($event, 'abc', 5);}}})}`\n    )\n    // expression in args\n    assertCodegen(\n      '<input @input=\"onInput($event, 2+2);\">',\n      `with(this){return _c('input',{on:{\"input\":function($event){onInput($event, 2+2);}}})}`\n    )\n    // tricky symbols in args\n    assertCodegen(\n      '<input @input=\"onInput(\\');[\\'());\\');\">',\n      `with(this){return _c('input',{on:{\"input\":function($event){onInput(');[\\'());');}}})}`\n    )\n  })\n\n  it('generate events with multiple statements', () => {\n    // normal function\n    assertCodegen(\n      '<input @input=\"onInput1();onInput2()\">',\n      `with(this){return _c('input',{on:{\"input\":function($event){onInput1();onInput2()}}})}`\n    )\n    // function with multiple args\n    assertCodegen(\n      '<input @input=\"onInput1($event, \\'text\\');onInput2(\\'text2\\', $event)\">',\n      `with(this){return _c('input',{on:{\"input\":function($event){onInput1($event, 'text');onInput2('text2', $event)}}})}`\n    )\n  })\n\n  it('generate events with keycode', () => {\n    assertCodegen(\n      '<input @input.enter=\"onInput\">',\n      `with(this){return _c('input',{on:{\"input\":function($event){if(!('button' in $event)&&_k($event.keyCode,\"enter\",13,$event.key,\"Enter\"))return null;return onInput($event)}}})}`\n    )\n    // multiple keycodes (delete)\n    assertCodegen(\n      '<input @input.delete=\"onInput\">',\n      `with(this){return _c('input',{on:{\"input\":function($event){if(!('button' in $event)&&_k($event.keyCode,\"delete\",[8,46],$event.key,[\"Backspace\",\"Delete\"]))return null;return onInput($event)}}})}`\n    )\n    // multiple keycodes (chained)\n    assertCodegen(\n      '<input @keydown.enter.delete=\"onInput\">',\n      `with(this){return _c('input',{on:{\"keydown\":function($event){if(!('button' in $event)&&_k($event.keyCode,\"enter\",13,$event.key,\"Enter\")&&_k($event.keyCode,\"delete\",[8,46],$event.key,[\"Backspace\",\"Delete\"]))return null;return onInput($event)}}})}`\n    )\n    // number keycode\n    assertCodegen(\n      '<input @input.13=\"onInput\">',\n      `with(this){return _c('input',{on:{\"input\":function($event){if(!('button' in $event)&&$event.keyCode!==13)return null;return onInput($event)}}})}`\n    )\n    // custom keycode\n    assertCodegen(\n      '<input @input.custom=\"onInput\">',\n      `with(this){return _c('input',{on:{\"input\":function($event){if(!('button' in $event)&&_k($event.keyCode,\"custom\",undefined,$event.key,undefined))return null;return onInput($event)}}})}`\n    )\n  })\n\n  it('generate events with generic modifiers', () => {\n    assertCodegen(\n      '<input @input.stop=\"onInput\">',\n      `with(this){return _c('input',{on:{\"input\":function($event){$event.stopPropagation();return onInput($event)}}})}`\n    )\n    assertCodegen(\n      '<input @input.prevent=\"onInput\">',\n      `with(this){return _c('input',{on:{\"input\":function($event){$event.preventDefault();return onInput($event)}}})}`\n    )\n    assertCodegen(\n      '<input @input.self=\"onInput\">',\n      `with(this){return _c('input',{on:{\"input\":function($event){if($event.target !== $event.currentTarget)return null;return onInput($event)}}})}`\n    )\n  })\n\n  // GitHub Issues #5146\n  it('generate events with generic modifiers and keycode correct order', () => {\n    assertCodegen(\n      '<input @keydown.enter.prevent=\"onInput\">',\n      `with(this){return _c('input',{on:{\"keydown\":function($event){if(!('button' in $event)&&_k($event.keyCode,\"enter\",13,$event.key,\"Enter\"))return null;$event.preventDefault();return onInput($event)}}})}`\n    )\n\n    assertCodegen(\n      '<input @keydown.enter.stop=\"onInput\">',\n      `with(this){return _c('input',{on:{\"keydown\":function($event){if(!('button' in $event)&&_k($event.keyCode,\"enter\",13,$event.key,\"Enter\"))return null;$event.stopPropagation();return onInput($event)}}})}`\n    )\n  })\n\n  it('generate events with mouse event modifiers', () => {\n    assertCodegen(\n      '<input @click.ctrl=\"onClick\">',\n      `with(this){return _c('input',{on:{\"click\":function($event){if(!$event.ctrlKey)return null;return onClick($event)}}})}`\n    )\n    assertCodegen(\n      '<input @click.shift=\"onClick\">',\n      `with(this){return _c('input',{on:{\"click\":function($event){if(!$event.shiftKey)return null;return onClick($event)}}})}`\n    )\n    assertCodegen(\n      '<input @click.alt=\"onClick\">',\n      `with(this){return _c('input',{on:{\"click\":function($event){if(!$event.altKey)return null;return onClick($event)}}})}`\n    )\n    assertCodegen(\n      '<input @click.meta=\"onClick\">',\n      `with(this){return _c('input',{on:{\"click\":function($event){if(!$event.metaKey)return null;return onClick($event)}}})}`\n    )\n    assertCodegen(\n      '<input @click.exact=\"onClick\">',\n      `with(this){return _c('input',{on:{\"click\":function($event){if($event.ctrlKey||$event.shiftKey||$event.altKey||$event.metaKey)return null;return onClick($event)}}})}`\n    )\n    assertCodegen(\n      '<input @click.ctrl.exact=\"onClick\">',\n      `with(this){return _c('input',{on:{\"click\":function($event){if(!$event.ctrlKey)return null;if($event.shiftKey||$event.altKey||$event.metaKey)return null;return onClick($event)}}})}`\n    )\n  })\n\n  it('generate events with multiple modifiers', () => {\n    assertCodegen(\n      '<input @input.stop.prevent.self=\"onInput\">',\n      `with(this){return _c('input',{on:{\"input\":function($event){$event.stopPropagation();$event.preventDefault();if($event.target !== $event.currentTarget)return null;return onInput($event)}}})}`\n    )\n  })\n\n  it('generate events with capture modifier', () => {\n    assertCodegen(\n      '<input @input.capture=\"onInput\">',\n      `with(this){return _c('input',{on:{\"!input\":function($event){return onInput($event)}}})}`\n    )\n  })\n\n  it('generate events with once modifier', () => {\n    assertCodegen(\n      '<input @input.once=\"onInput\">',\n      `with(this){return _c('input',{on:{\"~input\":function($event){return onInput($event)}}})}`\n    )\n  })\n\n  it('generate events with capture and once modifier', () => {\n    assertCodegen(\n      '<input @input.capture.once=\"onInput\">',\n      `with(this){return _c('input',{on:{\"~!input\":function($event){return onInput($event)}}})}`\n    )\n  })\n\n  it('generate events with once and capture modifier', () => {\n    assertCodegen(\n      '<input @input.once.capture=\"onInput\">',\n      `with(this){return _c('input',{on:{\"~!input\":function($event){return onInput($event)}}})}`\n    )\n  })\n\n  it('generate events with inline statement', () => {\n    assertCodegen(\n      '<input @input=\"current++\">',\n      `with(this){return _c('input',{on:{\"input\":function($event){current++}}})}`\n    )\n  })\n\n  it('generate events with inline function expression', () => {\n    // normal function\n    assertCodegen(\n      '<input @input=\"function () { current++ }\">',\n      `with(this){return _c('input',{on:{\"input\":function () { current++ }}})}`\n    )\n    // arrow with no args\n    assertCodegen(\n      '<input @input=\"()=>current++\">',\n      `with(this){return _c('input',{on:{\"input\":()=>current++}})}`\n    )\n    // arrow with parens, single arg\n    assertCodegen(\n      '<input @input=\"(e) => current++\">',\n      `with(this){return _c('input',{on:{\"input\":(e) => current++}})}`\n    )\n    // arrow with parens, multi args\n    assertCodegen(\n      '<input @input=\"(a, b, c) => current++\">',\n      `with(this){return _c('input',{on:{\"input\":(a, b, c) => current++}})}`\n    )\n    // arrow with destructuring\n    assertCodegen(\n      '<input @input=\"({ a, b }) => current++\">',\n      `with(this){return _c('input',{on:{\"input\":({ a, b }) => current++}})}`\n    )\n    // arrow single arg no parens\n    assertCodegen(\n      '<input @input=\"e=>current++\">',\n      `with(this){return _c('input',{on:{\"input\":e=>current++}})}`\n    )\n    // with modifiers\n    assertCodegen(\n      `<input @keyup.enter=\"e=>current++\">`,\n      `with(this){return _c('input',{on:{\"keyup\":function($event){if(!('button' in $event)&&_k($event.keyCode,\"enter\",13,$event.key,\"Enter\"))return null;return (e=>current++)($event)}}})}`\n    )\n  })\n\n  // #3893\n  it('should not treat handler with unexpected whitespace as inline statement', () => {\n    assertCodegen(\n      '<input @input=\" onInput \">',\n      `with(this){return _c('input',{on:{\"input\":onInput}})}`\n    )\n  })\n\n  it('generate unhandled events', () => {\n    assertCodegen(\n      '<input @input=\"current++\">',\n      `with(this){return _c('input',{on:{\"input\":function(){}}})}`,\n      ast => {\n        ast.events.input = undefined\n      }\n    )\n  })\n\n  it('generate multiple event handlers', () => {\n    assertCodegen(\n      '<input @input=\"current++\" @input.stop=\"onInput\">',\n      `with(this){return _c('input',{on:{\"input\":[function($event){current++},function($event){$event.stopPropagation();return onInput($event)}]}})}`\n    )\n  })\n\n  it('generate component', () => {\n    assertCodegen(\n      '<my-component name=\"mycomponent1\" :msg=\"msg\" @notify=\"onNotify\"><div>hi</div></my-component>',\n      `with(this){return _c('my-component',{attrs:{\"name\":\"mycomponent1\",\"msg\":msg},on:{\"notify\":onNotify}},[_c('div',[_v(\"hi\")])])}`\n    )\n  })\n\n  it('generate svg component with children', () => {\n    assertCodegen(\n      '<svg><my-comp><circle :r=\"10\"></circle></my-comp></svg>',\n      `with(this){return _c('svg',[_c('my-comp',[_c('circle',{attrs:{\"r\":10}})])],1)}`\n    )\n  })\n\n  it('generate is attribute', () => {\n    assertCodegen(\n      '<div is=\"component1\"></div>',\n      `with(this){return _c(\"component1\",{tag:\"div\"})}`\n    )\n    assertCodegen(\n      '<div :is=\"component1\"></div>',\n      `with(this){return _c(component1,{tag:\"div\"})}`\n    )\n  })\n\n  it('generate component with inline-template', () => {\n    // have \"inline-template'\"\n    assertCodegen(\n      '<my-component inline-template><p><span>hello world</span></p></my-component>',\n      `with(this){return _c('my-component',{inlineTemplate:{render:function(){with(this){return _m(0)}},staticRenderFns:[function(){with(this){return _c('p',[_c('span',[_v(\"hello world\")])])}}]}})}`\n    )\n    // \"have inline-template attrs, but not having exactly one child element\n    assertCodegen(\n      '<my-component inline-template><hr><hr></my-component>',\n      `with(this){return _c('my-component',{inlineTemplate:{render:function(){with(this){return _c('hr')}},staticRenderFns:[]}})}`\n    )\n    try {\n      assertCodegen(\n        '<my-component inline-template></my-component>',\n        ''\n      )\n    } catch (e) {}\n    expect('Inline-template components must have exactly one child element.').toHaveBeenWarned()\n    expect(console.error.calls.count()).toBe(2)\n  })\n\n  it('generate static trees inside v-for', () => {\n    assertCodegen(\n      `<div><div v-for=\"i in 10\"><p><span></span></p></div></div>`,\n      `with(this){return _c('div',_l((10),function(i){return _c('div',[_m(0,true)])}))}`,\n      [`with(this){return _c('p',[_c('span')])}`]\n    )\n  })\n\n  it('generate component with v-for', () => {\n    // normalize type: 2\n    assertCodegen(\n      '<div><child></child><template v-for=\"item in list\">{{ item }}</template></div>',\n      `with(this){return _c('div',[_c('child'),_l((list),function(item){return [_v(_s(item))]})],2)}`\n    )\n  })\n\n  it('generate component with comment', () => {\n    const options = extend({\n      comments: true\n    }, baseOptions)\n    const template = '<div><!--comment--></div>'\n    const generatedCode = `with(this){return _c('div',[_e(\"comment\")])}`\n\n    const ast = parse(template, options)\n    optimize(ast, options)\n    const res = generate(ast, options)\n    expect(res.render).toBe(generatedCode)\n  })\n\n  // #6150\n  it('generate comments with special characters', () => {\n    const options = extend({\n      comments: true\n    }, baseOptions)\n    const template = '<div><!--\\n\\'comment\\'\\n--></div>'\n    const generatedCode = `with(this){return _c('div',[_e(\"\\\\n'comment'\\\\n\")])}`\n\n    const ast = parse(template, options)\n    optimize(ast, options)\n    const res = generate(ast, options)\n    expect(res.render).toBe(generatedCode)\n  })\n\n  it('not specified ast type', () => {\n    const res = generate(null, baseOptions)\n    expect(res.render).toBe(`with(this){return _c(\"div\")}`)\n    expect(res.staticRenderFns).toEqual([])\n  })\n\n  it('not specified directives option', () => {\n    assertCodegen(\n      '<p v-if=\"show\">hello world</p>',\n      `with(this){return (show)?_c('p',[_v(\"hello world\")]):_e()}`,\n      { isReservedTag }\n    )\n  })\n})\n/* eslint-enable quotes */\n"
  },
  {
    "path": "vue/test/unit/modules/compiler/compiler-options.spec.js",
    "content": "import Vue from 'vue'\nimport { compile } from 'web/compiler'\nimport { getAndRemoveAttr } from 'compiler/helpers'\n\ndescribe('compile options', () => {\n  it('should be compiled', () => {\n    const { render, staticRenderFns, errors } = compile(`\n      <div>\n        <input type=\"text\" v-model=\"msg\" required max=\"8\" v-validate:field1.group1.group2>\n      </div>\n    `, {\n      directives: {\n        validate (el, dir) {\n          if (dir.name === 'validate' && dir.arg) {\n            el.validate = {\n              field: dir.arg,\n              groups: dir.modifiers ? Object.keys(dir.modifiers) : []\n            }\n          }\n        }\n      },\n      modules: [\n        {\n          transformNode (el) {\n            el.validators = el.validators || []\n            const validators = ['required', 'min', 'max', 'pattern', 'maxlength', 'minlength']\n            validators.forEach(name => {\n              const rule = getAndRemoveAttr(el, name)\n              if (rule !== undefined) {\n                el.validators.push({ name, rule })\n              }\n            })\n          },\n          genData (el) {\n            let data = ''\n            if (el.validate) {\n              data += `validate:${JSON.stringify(el.validate)},`\n            }\n            if (el.validators) {\n              data += `validators:${JSON.stringify(el.validators)},`\n            }\n            return data\n          },\n          transformCode (el, code) {\n            // check\n            if (!el.validate || !el.validators) {\n              return code\n            }\n            // setup validation result props\n            const result = { dirty: false } // define something other prop\n            el.validators.forEach(validator => {\n              result[validator.name] = null\n            })\n            // generate code\n            return `_c('validate',{props:{\n              field:${JSON.stringify(el.validate.field)},\n              groups:${JSON.stringify(el.validate.groups)},\n              validators:${JSON.stringify(el.validators)},\n              result:${JSON.stringify(result)},\n              child:${code}}\n            })`\n          }\n        }\n      ]\n    })\n    expect(render).not.toBeUndefined()\n    expect(staticRenderFns).toEqual([])\n    expect(errors).toEqual([])\n\n    const renderFn = new Function(render)\n    const vm = new Vue({\n      data: {\n        msg: 'hello'\n      },\n      components: {\n        validate: {\n          props: ['field', 'groups', 'validators', 'result', 'child'],\n          render (h) {\n            return this.child\n          },\n          computed: {\n            valid () {\n              let ret = true\n              for (let i = 0; i > this.validators.length; i++) {\n                const { name } = this.validators[i]\n                if (!this.result[name]) {\n                  ret = false\n                  break\n                }\n              }\n              return ret\n            }\n          },\n          mounted () {\n            // initialize validation\n            const value = this.$el.value\n            this.validators.forEach(validator => {\n              const ret = this[validator.name](value, validator.rule)\n              this.result[validator.name] = ret\n            })\n          },\n          methods: {\n            // something validators logic\n            required (val) {\n              return val.length > 0\n            },\n            max (val, rule) {\n              return !(parseInt(val, 10) > parseInt(rule, 10))\n            }\n          }\n        }\n      },\n      render: renderFn,\n      staticRenderFns\n    }).$mount()\n    expect(vm.$el.innerHTML).toBe('<input type=\"text\">')\n    expect(vm.$children[0].valid).toBe(true)\n  })\n\n  it('should collect errors', () => {\n    let compiled = compile('hello')\n    expect(compiled.errors.length).toBe(1)\n    expect(compiled.errors[0]).toContain('root element')\n\n    compiled = compile('<div v-if=\"a----\">{{ b++++ }}</div>')\n    expect(compiled.errors.length).toBe(2)\n    expect(compiled.errors[0]).toContain('Raw expression: v-if=\"a----\"')\n    expect(compiled.errors[1]).toContain('Raw expression: {{ b++++ }}')\n  })\n})\n"
  },
  {
    "path": "vue/test/unit/modules/compiler/optimizer.spec.js",
    "content": "import { parse } from 'compiler/parser/index'\nimport { extend } from 'shared/util'\nimport { optimize } from 'compiler/optimizer'\nimport { baseOptions } from 'web/compiler/options'\n\ndescribe('optimizer', () => {\n  it('simple', () => {\n    const ast = parse('<h1 id=\"section1\"><span>hello world</span></h1>', baseOptions)\n    optimize(ast, baseOptions)\n    expect(ast.static).toBe(true) // h1\n    expect(ast.staticRoot).toBe(true)\n    expect(ast.children[0].static).toBe(true) // span\n  })\n\n  it('simple with comment', () => {\n    const options = extend({\n      comments: true\n    }, baseOptions)\n    const ast = parse('<h1 id=\"section1\"><span>hello world</span><!--comment--></h1>', options)\n    optimize(ast, options)\n    expect(ast.static).toBe(true) // h1\n    expect(ast.staticRoot).toBe(true)\n    expect(ast.children.length).toBe(2)\n    expect(ast.children[0].static).toBe(true) // span\n    expect(ast.children[1].static).toBe(true) // comment\n  })\n\n  it('skip simple nodes', () => {\n    const ast = parse('<h1 id=\"section1\">hello</h1>', baseOptions)\n    optimize(ast, baseOptions)\n    expect(ast.static).toBe(true)\n    expect(ast.staticRoot).toBe(false) // this is too simple to warrant a static tree\n  })\n\n  it('interpolation', () => {\n    const ast = parse('<h1>{{msg}}</h1>', baseOptions)\n    optimize(ast, baseOptions)\n    expect(ast.static).toBe(false) // h1\n    expect(ast.children[0].static).toBe(false) // text node with interpolation\n  })\n\n  it('nested elements', () => {\n    const ast = parse('<ul><li>hello</li><li>world</li></ul>', baseOptions)\n    optimize(ast, baseOptions)\n    // ul\n    expect(ast.static).toBe(true)\n    expect(ast.staticRoot).toBe(true)\n    // li\n    expect(ast.children[0].static).toBe(true) // first\n    expect(ast.children[1].static).toBe(true) // second\n    // text node inside li\n    expect(ast.children[0].children[0].static).toBe(true) // first\n    expect(ast.children[1].children[0].static).toBe(true) // second\n  })\n\n  it('nested complex elements', () => {\n    const ast = parse('<ul><li>{{msg1}}</li><li>---</li><li>{{msg2}}</li></ul>', baseOptions)\n    optimize(ast, baseOptions)\n    // ul\n    expect(ast.static).toBe(false) // ul\n    // li\n    expect(ast.children[0].static).toBe(false) // first\n    expect(ast.children[1].static).toBe(true) // second\n    expect(ast.children[2].static).toBe(false) // third\n    // text node inside li\n    expect(ast.children[0].children[0].static).toBe(false) // first\n    expect(ast.children[1].children[0].static).toBe(true) // second\n    expect(ast.children[2].children[0].static).toBe(false) // third\n  })\n\n  it('v-if directive', () => {\n    const ast = parse('<div id=\"section1\" v-if=\"show\"><p><span>hello world</span></p></div>', baseOptions)\n    optimize(ast, baseOptions)\n    expect(ast.static).toBe(false)\n    expect(ast.children[0].static).toBe(true)\n  })\n\n  it('v-else directive', () => {\n    const ast = parse('<div><p v-if=\"show\">hello world</p><div v-else><p><span>foo bar</span></p></div></div>', baseOptions)\n    optimize(ast, baseOptions)\n    expect(ast.static).toBe(false)\n    expect(ast.children[0].static).toBe(false)\n    expect(ast.children[0].ifConditions[0].block.static).toBe(false)\n    expect(ast.children[0].ifConditions[1].block.static).toBe(false)\n    expect(ast.children[0].ifConditions[0].block.children[0].static).toBe(true)\n    expect(ast.children[0].ifConditions[1].block.children[0].static).toBe(true)\n  })\n\n  it('v-pre directive', () => {\n    const ast = parse('<ul v-pre><li>{{msg}}</li><li>world</li></ul>', baseOptions)\n    optimize(ast, baseOptions)\n    expect(ast.static).toBe(true)\n    expect(ast.staticRoot).toBe(true)\n    expect(ast.children[0].static).toBe(true)\n    expect(ast.children[1].static).toBe(true)\n    expect(ast.children[0].children[0].static).toBe(true)\n    expect(ast.children[1].children[0].static).toBe(true)\n  })\n\n  it('v-for directive', () => {\n    const ast = parse('<ul><li v-for=\"item in items\">hello world {{$index}}</li></ul>', baseOptions)\n    optimize(ast, baseOptions)\n    // ul\n    expect(ast.static).toBe(false)\n    // li with v-for\n    expect(ast.children[0].static).toBe(false)\n    expect(ast.children[0].children[0].static).toBe(false)\n  })\n\n  it('v-once directive', () => {\n    const ast = parse('<p v-once>{{msg}}</p>', baseOptions)\n    optimize(ast, baseOptions)\n    expect(ast.static).toBe(false) // p\n    expect(ast.children[0].static).toBe(false) // text node\n  })\n\n  it('single slot', () => {\n    const ast = parse('<div><slot>hello</slot></div>', baseOptions)\n    optimize(ast, baseOptions)\n    expect(ast.children[0].static).toBe(false) // slot\n    expect(ast.children[0].children[0].static).toBe(true) // text node\n  })\n\n  it('named slot', () => {\n    const ast = parse('<div><slot name=\"one\">hello world</slot></div>', baseOptions)\n    optimize(ast, baseOptions)\n    expect(ast.children[0].static).toBe(false) // slot\n    expect(ast.children[0].children[0].static).toBe(true) // text node\n  })\n\n  it('slot target', () => {\n    const ast = parse('<p slot=\"one\">hello world</p>', baseOptions)\n    optimize(ast, baseOptions)\n    expect(ast.static).toBe(false) // slot\n    expect(ast.children[0].static).toBe(true) // text node\n  })\n\n  it('component', () => {\n    const ast = parse('<my-component></my-component>', baseOptions)\n    optimize(ast, baseOptions)\n    expect(ast.static).toBe(false) // component\n  })\n\n  it('component for inline-template', () => {\n    const ast = parse('<my-component inline-template><p>hello world</p><p>{{msg}}</p></my-component>', baseOptions)\n    optimize(ast, baseOptions)\n    // component\n    expect(ast.static).toBe(false) // component\n    // p\n    expect(ast.children[0].static).toBe(true) // first\n    expect(ast.children[1].static).toBe(false) // second\n    // text node inside p\n    expect(ast.children[0].children[0].static).toBe(true) // first\n    expect(ast.children[1].children[0].static).toBe(false) // second\n  })\n\n  it('class binding', () => {\n    const ast = parse('<p :class=\"class1\">hello world</p>', baseOptions)\n    optimize(ast, baseOptions)\n    expect(ast.static).toBe(false)\n    expect(ast.children[0].static).toBe(true)\n  })\n\n  it('style binding', () => {\n    const ast = parse('<p :style=\"error\">{{msg}}</p>', baseOptions)\n    optimize(ast, baseOptions)\n    expect(ast.static).toBe(false)\n    expect(ast.children[0].static).toBe(false)\n  })\n\n  it('key', () => {\n    const ast = parse('<p key=\"foo\">hello world</p>', baseOptions)\n    optimize(ast, baseOptions)\n    expect(ast.static).toBe(false)\n    expect(ast.children[0].static).toBe(true)\n  })\n\n  it('ref', () => {\n    const ast = parse('<p ref=\"foo\">hello world</p>', baseOptions)\n    optimize(ast, baseOptions)\n    expect(ast.static).toBe(false)\n    expect(ast.children[0].static).toBe(true)\n  })\n\n  it('transition', () => {\n    const ast = parse('<p v-if=\"show\" transition=\"expand\">hello world</p>', baseOptions)\n    optimize(ast, baseOptions)\n    expect(ast.static).toBe(false)\n    expect(ast.children[0].static).toBe(true)\n  })\n\n  it('v-bind directive', () => {\n    const ast = parse('<input type=\"text\" name=\"field1\" :value=\"msg\">', baseOptions)\n    optimize(ast, baseOptions)\n    expect(ast.static).toBe(false)\n  })\n\n  it('v-on directive', () => {\n    const ast = parse('<input type=\"text\" name=\"field1\" :value=\"msg\" @input=\"onInput\">', baseOptions)\n    optimize(ast, baseOptions)\n    expect(ast.static).toBe(false)\n  })\n\n  it('custom directive', () => {\n    const ast = parse('<form><input type=\"text\" name=\"field1\" :value=\"msg\" v-validate:field1=\"required\"></form>', baseOptions)\n    optimize(ast, baseOptions)\n    expect(ast.static).toBe(false)\n    expect(ast.children[0].static).toBe(false)\n  })\n\n  it('not root ast', () => {\n    const ast = null\n    optimize(ast, baseOptions)\n    expect(ast).toBe(null)\n  })\n\n  it('not specified isReservedTag option', () => {\n    const ast = parse('<h1 id=\"section1\">hello world</h1>', baseOptions)\n    optimize(ast, {})\n    expect(ast.static).toBe(false)\n  })\n\n  it('mark static trees inside v-for', () => {\n    const ast = parse(`<div><div v-for=\"i in 10\"><p><span>hi</span></p></div></div>`, baseOptions)\n    optimize(ast, baseOptions)\n    expect(ast.children[0].children[0].staticRoot).toBe(true)\n    expect(ast.children[0].children[0].staticInFor).toBe(true)\n  })\n\n  it('mark static trees inside v-for with nested v-else and v-once', () => {\n    const ast = parse(`\n      <div v-if=\"1\"></div>\n      <div v-else-if=\"2\">\n        <div v-for=\"i in 10\" :key=\"i\">\n          <div v-if=\"1\">{{ i }}</div>\n          <div v-else-if=\"2\" v-once>{{ i }}</div>\n          <div v-else v-once>{{ i }}</div>\n        </div>\n      </div>\n      <div v-else>\n        <div v-for=\"i in 10\" :key=\"i\">\n          <div v-if=\"1\">{{ i }}</div>\n          <div v-else v-once>{{ i }}</div>\n        </div>\n      </div>\n      `, baseOptions)\n    optimize(ast, baseOptions)\n    expect(ast.ifConditions[1].block.children[0].children[0].ifConditions[1].block.staticRoot).toBe(false)\n    expect(ast.ifConditions[1].block.children[0].children[0].ifConditions[1].block.staticInFor).toBe(true)\n\n    expect(ast.ifConditions[1].block.children[0].children[0].ifConditions[2].block.staticRoot).toBe(false)\n    expect(ast.ifConditions[1].block.children[0].children[0].ifConditions[2].block.staticInFor).toBe(true)\n\n    expect(ast.ifConditions[2].block.children[0].children[0].ifConditions[1].block.staticRoot).toBe(false)\n    expect(ast.ifConditions[2].block.children[0].children[0].ifConditions[1].block.staticInFor).toBe(true)\n  })\n})\n"
  },
  {
    "path": "vue/test/unit/modules/compiler/parser.spec.js",
    "content": "import { parse } from 'compiler/parser/index'\nimport { extend } from 'shared/util'\nimport { baseOptions } from 'web/compiler/options'\nimport { isIE, isEdge } from 'core/util/env'\n\ndescribe('parser', () => {\n  it('simple element', () => {\n    const ast = parse('<h1>hello world</h1>', baseOptions)\n    expect(ast.tag).toBe('h1')\n    expect(ast.plain).toBe(true)\n    expect(ast.children[0].text).toBe('hello world')\n  })\n\n  it('interpolation in element', () => {\n    const ast = parse('<h1>{{msg}}</h1>', baseOptions)\n    expect(ast.tag).toBe('h1')\n    expect(ast.plain).toBe(true)\n    expect(ast.children[0].expression).toBe('_s(msg)')\n  })\n\n  it('child elements', () => {\n    const ast = parse('<ul><li>hello world</li></ul>', baseOptions)\n    expect(ast.tag).toBe('ul')\n    expect(ast.plain).toBe(true)\n    expect(ast.children[0].tag).toBe('li')\n    expect(ast.children[0].plain).toBe(true)\n    expect(ast.children[0].children[0].text).toBe('hello world')\n    expect(ast.children[0].parent).toBe(ast)\n  })\n\n  it('unary element', () => {\n    const ast = parse('<hr>', baseOptions)\n    expect(ast.tag).toBe('hr')\n    expect(ast.plain).toBe(true)\n    expect(ast.children.length).toBe(0)\n  })\n\n  it('svg element', () => {\n    const ast = parse('<svg><text>hello world</text></svg>', baseOptions)\n    expect(ast.tag).toBe('svg')\n    expect(ast.ns).toBe('svg')\n    expect(ast.plain).toBe(true)\n    expect(ast.children[0].tag).toBe('text')\n    expect(ast.children[0].children[0].text).toBe('hello world')\n    expect(ast.children[0].parent).toBe(ast)\n  })\n\n  it('camelCase element', () => {\n    const ast = parse('<MyComponent><p>hello world</p></MyComponent>', baseOptions)\n    expect(ast.tag).toBe('MyComponent')\n    expect(ast.plain).toBe(true)\n    expect(ast.children[0].tag).toBe('p')\n    expect(ast.children[0].plain).toBe(true)\n    expect(ast.children[0].children[0].text).toBe('hello world')\n    expect(ast.children[0].parent).toBe(ast)\n  })\n\n  it('forbidden element', () => {\n    // style\n    const styleAst = parse('<style>error { color: red; }</style>', baseOptions)\n    expect(styleAst.tag).toBe('style')\n    expect(styleAst.plain).toBe(true)\n    expect(styleAst.forbidden).toBe(true)\n    expect(styleAst.children[0].text).toBe('error { color: red; }')\n    expect('Templates should only be responsible for mapping the state').toHaveBeenWarned()\n    // script\n    const scriptAst = parse('<script type=\"text/javascript\">alert(\"hello world!\")</script>', baseOptions)\n    expect(scriptAst.tag).toBe('script')\n    expect(scriptAst.plain).toBe(false)\n    expect(scriptAst.forbidden).toBe(true)\n    expect(scriptAst.children[0].text).toBe('alert(\"hello world!\")')\n    expect('Templates should only be responsible for mapping the state').toHaveBeenWarned()\n  })\n\n  it('not contain root element', () => {\n    parse('hello world', baseOptions)\n    expect('Component template requires a root element, rather than just text').toHaveBeenWarned()\n  })\n\n  it('warn text before root element', () => {\n    parse('before root {{ interpolation }}<div></div>', baseOptions)\n    expect('text \"before root {{ interpolation }}\" outside root element will be ignored.').toHaveBeenWarned()\n  })\n\n  it('warn text after root element', () => {\n    parse('<div></div>after root {{ interpolation }}', baseOptions)\n    expect('text \"after root {{ interpolation }}\" outside root element will be ignored.').toHaveBeenWarned()\n  })\n\n  it('warn multiple root elements', () => {\n    parse('<div></div><div></div>', baseOptions)\n    expect('Component template should contain exactly one root element').toHaveBeenWarned()\n  })\n\n  it('remove duplicate whitespace text nodes caused by comments', () => {\n    const ast = parse(`<div><a></a> <!----> <a></a></div>`, baseOptions)\n    expect(ast.children.length).toBe(3)\n    expect(ast.children[0].tag).toBe('a')\n    expect(ast.children[1].text).toBe(' ')\n    expect(ast.children[2].tag).toBe('a')\n  })\n\n  it('remove text nodes between v-if conditions', () => {\n    const ast = parse(`<div><div v-if=\"1\"></div> <div v-else-if=\"2\"></div> <div v-else></div> <span></span></div>`, baseOptions)\n    expect(ast.children.length).toBe(3)\n    expect(ast.children[0].tag).toBe('div')\n    expect(ast.children[0].ifConditions.length).toBe(3)\n    expect(ast.children[1].text).toBe(' ') // text\n    expect(ast.children[2].tag).toBe('span')\n  })\n\n  it('warn non whitespace text between v-if conditions', () => {\n    parse(`<div><div v-if=\"1\"></div> foo <div v-else></div></div>`, baseOptions)\n    expect(`text \"foo\" between v-if and v-else(-if) will be ignored`).toHaveBeenWarned()\n  })\n\n  it('not warn 2 root elements with v-if and v-else', () => {\n    parse('<div v-if=\"1\"></div><div v-else></div>', baseOptions)\n    expect('Component template should contain exactly one root element')\n      .not.toHaveBeenWarned()\n  })\n\n  it('not warn 3 root elements with v-if, v-else-if and v-else', () => {\n    parse('<div v-if=\"1\"></div><div v-else-if=\"2\"></div><div v-else></div>', baseOptions)\n    expect('Component template should contain exactly one root element')\n      .not.toHaveBeenWarned()\n  })\n\n  it('not warn 2 root elements with v-if and v-else on separate lines', () => {\n    parse(`\n      <div v-if=\"1\"></div>\n      <div v-else></div>\n    `, baseOptions)\n    expect('Component template should contain exactly one root element')\n      .not.toHaveBeenWarned()\n  })\n\n  it('not warn 3 or more root elements with v-if, v-else-if and v-else on separate lines', () => {\n    parse(`\n      <div v-if=\"1\"></div>\n      <div v-else-if=\"2\"></div>\n      <div v-else></div>\n    `, baseOptions)\n    expect('Component template should contain exactly one root element')\n      .not.toHaveBeenWarned()\n\n    parse(`\n      <div v-if=\"1\"></div>\n      <div v-else-if=\"2\"></div>\n      <div v-else-if=\"3\"></div>\n      <div v-else-if=\"4\"></div>\n      <div v-else></div>\n    `, baseOptions)\n    expect('Component template should contain exactly one root element')\n      .not.toHaveBeenWarned()\n  })\n\n  it('generate correct ast for 2 root elements with v-if and v-else on separate lines', () => {\n    const ast = parse(`\n      <div v-if=\"1\"></div>\n      <p v-else></p>\n    `, baseOptions)\n    expect(ast.tag).toBe('div')\n    expect(ast.ifConditions[1].block.tag).toBe('p')\n  })\n\n  it('generate correct ast for 3 or more root elements with v-if and v-else on separate lines', () => {\n    const ast = parse(`\n      <div v-if=\"1\"></div>\n      <span v-else-if=\"2\"></span>\n      <p v-else></p>\n    `, baseOptions)\n    expect(ast.tag).toBe('div')\n    expect(ast.ifConditions[0].block.tag).toBe('div')\n    expect(ast.ifConditions[1].block.tag).toBe('span')\n    expect(ast.ifConditions[2].block.tag).toBe('p')\n\n    const astMore = parse(`\n      <div v-if=\"1\"></div>\n      <span v-else-if=\"2\"></span>\n      <div v-else-if=\"3\"></div>\n      <span v-else-if=\"4\"></span>\n      <p v-else></p>\n    `, baseOptions)\n    expect(astMore.tag).toBe('div')\n    expect(astMore.ifConditions[0].block.tag).toBe('div')\n    expect(astMore.ifConditions[1].block.tag).toBe('span')\n    expect(astMore.ifConditions[2].block.tag).toBe('div')\n    expect(astMore.ifConditions[3].block.tag).toBe('span')\n    expect(astMore.ifConditions[4].block.tag).toBe('p')\n  })\n\n  it('warn 2 root elements with v-if', () => {\n    parse('<div v-if=\"1\"></div><div v-if=\"2\"></div>', baseOptions)\n    expect('Component template should contain exactly one root element').toHaveBeenWarned()\n  })\n\n  it('warn 3 root elements with v-if and v-else on first 2', () => {\n    parse('<div v-if=\"1\"></div><div v-else></div><div></div>', baseOptions)\n    expect('Component template should contain exactly one root element').toHaveBeenWarned()\n  })\n\n  it('warn 3 root elements with v-if and v-else-if on first 2', () => {\n    parse('<div v-if=\"1\"></div><div v-else-if></div><div></div>', baseOptions)\n    expect('Component template should contain exactly one root element').toHaveBeenWarned()\n  })\n\n  it('warn 4 root elements with v-if, v-else-if and v-else on first 2', () => {\n    parse('<div v-if=\"1\"></div><div v-else-if></div><div v-else></div><div></div>', baseOptions)\n    expect('Component template should contain exactly one root element').toHaveBeenWarned()\n  })\n\n  it('warn 2 root elements with v-if and v-else with v-for on 2nd', () => {\n    parse('<div v-if=\"1\"></div><div v-else v-for=\"i in [1]\"></div>', baseOptions)\n    expect('Cannot use v-for on stateful component root element because it renders multiple elements')\n      .toHaveBeenWarned()\n  })\n\n  it('warn 2 root elements with v-if and v-else-if with v-for on 2nd', () => {\n    parse('<div v-if=\"1\"></div><div v-else-if=\"2\" v-for=\"i in [1]\"></div>', baseOptions)\n    expect('Cannot use v-for on stateful component root element because it renders multiple elements')\n      .toHaveBeenWarned()\n  })\n\n  it('warn <template> as root element', () => {\n    parse('<template></template>', baseOptions)\n    expect('Cannot use <template> as component root element').toHaveBeenWarned()\n  })\n\n  it('warn <slot> as root element', () => {\n    parse('<slot></slot>', baseOptions)\n    expect('Cannot use <slot> as component root element').toHaveBeenWarned()\n  })\n\n  it('warn v-for on root element', () => {\n    parse('<div v-for=\"item in items\"></div>', baseOptions)\n    expect('Cannot use v-for on stateful component root element').toHaveBeenWarned()\n  })\n\n  it('warn <template> key', () => {\n    parse('<div><template v-for=\"i in 10\" :key=\"i\"></template></div>', baseOptions)\n    expect('<template> cannot be keyed').toHaveBeenWarned()\n  })\n\n  it('v-pre directive', () => {\n    const ast = parse('<div v-pre id=\"message1\"><p>{{msg}}</p></div>', baseOptions)\n    expect(ast.pre).toBe(true)\n    expect(ast.attrs[0].name).toBe('id')\n    expect(ast.attrs[0].value).toBe('\"message1\"')\n    expect(ast.children[0].children[0].text).toBe('{{msg}}')\n  })\n\n  it('v-for directive basic syntax', () => {\n    const ast = parse('<ul><li v-for=\"item in items\"></li></ul>', baseOptions)\n    const liAst = ast.children[0]\n    expect(liAst.for).toBe('items')\n    expect(liAst.alias).toBe('item')\n  })\n\n  it('v-for directive iteration syntax', () => {\n    const ast = parse('<ul><li v-for=\"(item, index) in items\"></li></ul>', baseOptions)\n    const liAst = ast.children[0]\n    expect(liAst.for).toBe('items')\n    expect(liAst.alias).toBe('item')\n    expect(liAst.iterator1).toBe('index')\n    expect(liAst.iterator2).toBeUndefined()\n  })\n\n  it('v-for directive iteration syntax (multiple)', () => {\n    const ast = parse('<ul><li v-for=\"(item, key, index) in items\"></li></ul>', baseOptions)\n    const liAst = ast.children[0]\n    expect(liAst.for).toBe('items')\n    expect(liAst.alias).toBe('item')\n    expect(liAst.iterator1).toBe('key')\n    expect(liAst.iterator2).toBe('index')\n  })\n\n  it('v-for directive key', () => {\n    const ast = parse('<ul><li v-for=\"item in items\" :key=\"item.uid\"></li></ul>', baseOptions)\n    const liAst = ast.children[0]\n    expect(liAst.for).toBe('items')\n    expect(liAst.alias).toBe('item')\n    expect(liAst.key).toBe('item.uid')\n  })\n\n  it('v-for directive destructuring', () => {\n    let ast = parse('<ul><li v-for=\"{ foo } in items\"></li></ul>', baseOptions)\n    let liAst = ast.children[0]\n    expect(liAst.for).toBe('items')\n    expect(liAst.alias).toBe('{ foo }')\n\n    // with paren\n    ast = parse('<ul><li v-for=\"({ foo }) in items\"></li></ul>', baseOptions)\n    liAst = ast.children[0]\n    expect(liAst.for).toBe('items')\n    expect(liAst.alias).toBe('{ foo }')\n\n    // multi-var destructuring\n    ast = parse('<ul><li v-for=\"{ foo, bar, baz } in items\"></li></ul>', baseOptions)\n    liAst = ast.children[0]\n    expect(liAst.for).toBe('items')\n    expect(liAst.alias).toBe('{ foo, bar, baz }')\n\n    // multi-var destructuring with paren\n    ast = parse('<ul><li v-for=\"({ foo, bar, baz }) in items\"></li></ul>', baseOptions)\n    liAst = ast.children[0]\n    expect(liAst.for).toBe('items')\n    expect(liAst.alias).toBe('{ foo, bar, baz }')\n\n    // with index\n    ast = parse('<ul><li v-for=\"({ foo }, i) in items\"></li></ul>', baseOptions)\n    liAst = ast.children[0]\n    expect(liAst.for).toBe('items')\n    expect(liAst.alias).toBe('{ foo }')\n    expect(liAst.iterator1).toBe('i')\n\n    // with key + index\n    ast = parse('<ul><li v-for=\"({ foo }, i, j) in items\"></li></ul>', baseOptions)\n    liAst = ast.children[0]\n    expect(liAst.for).toBe('items')\n    expect(liAst.alias).toBe('{ foo }')\n    expect(liAst.iterator1).toBe('i')\n    expect(liAst.iterator2).toBe('j')\n\n    // multi-var destructuring with index\n    ast = parse('<ul><li v-for=\"({ foo, bar, baz }, i) in items\"></li></ul>', baseOptions)\n    liAst = ast.children[0]\n    expect(liAst.for).toBe('items')\n    expect(liAst.alias).toBe('{ foo, bar, baz }')\n    expect(liAst.iterator1).toBe('i')\n\n    // array\n    ast = parse('<ul><li v-for=\"[ foo ] in items\"></li></ul>', baseOptions)\n    liAst = ast.children[0]\n    expect(liAst.for).toBe('items')\n    expect(liAst.alias).toBe('[ foo ]')\n\n    // multi-array\n    ast = parse('<ul><li v-for=\"[ foo, bar, baz ] in items\"></li></ul>', baseOptions)\n    liAst = ast.children[0]\n    expect(liAst.for).toBe('items')\n    expect(liAst.alias).toBe('[ foo, bar, baz ]')\n\n    // array with paren\n    ast = parse('<ul><li v-for=\"([ foo ]) in items\"></li></ul>', baseOptions)\n    liAst = ast.children[0]\n    expect(liAst.for).toBe('items')\n    expect(liAst.alias).toBe('[ foo ]')\n\n    // multi-array with paren\n    ast = parse('<ul><li v-for=\"([ foo, bar, baz ]) in items\"></li></ul>', baseOptions)\n    liAst = ast.children[0]\n    expect(liAst.for).toBe('items')\n    expect(liAst.alias).toBe('[ foo, bar, baz ]')\n\n    // array with index\n    ast = parse('<ul><li v-for=\"([ foo ], i) in items\"></li></ul>', baseOptions)\n    liAst = ast.children[0]\n    expect(liAst.for).toBe('items')\n    expect(liAst.alias).toBe('[ foo ]')\n    expect(liAst.iterator1).toBe('i')\n\n    // array with key + index\n    ast = parse('<ul><li v-for=\"([ foo ], i, j) in items\"></li></ul>', baseOptions)\n    liAst = ast.children[0]\n    expect(liAst.for).toBe('items')\n    expect(liAst.alias).toBe('[ foo ]')\n    expect(liAst.iterator1).toBe('i')\n    expect(liAst.iterator2).toBe('j')\n\n    // multi-array with paren\n    ast = parse('<ul><li v-for=\"([ foo, bar, baz ]) in items\"></li></ul>', baseOptions)\n    liAst = ast.children[0]\n    expect(liAst.for).toBe('items')\n    expect(liAst.alias).toBe('[ foo, bar, baz ]')\n\n    // multi-array with index\n    ast = parse('<ul><li v-for=\"([ foo, bar, baz ], i) in items\"></li></ul>', baseOptions)\n    liAst = ast.children[0]\n    expect(liAst.for).toBe('items')\n    expect(liAst.alias).toBe('[ foo, bar, baz ]')\n    expect(liAst.iterator1).toBe('i')\n\n    // nested\n    ast = parse('<ul><li v-for=\"({ foo, bar: { baz }, qux: [ n ] }, i, j) in items\"></li></ul>', baseOptions)\n    liAst = ast.children[0]\n    expect(liAst.for).toBe('items')\n    expect(liAst.alias).toBe('{ foo, bar: { baz }, qux: [ n ] }')\n    expect(liAst.iterator1).toBe('i')\n    expect(liAst.iterator2).toBe('j')\n\n    // array nested\n    ast = parse('<ul><li v-for=\"([ foo, { bar }, baz ], i, j) in items\"></li></ul>', baseOptions)\n    liAst = ast.children[0]\n    expect(liAst.for).toBe('items')\n    expect(liAst.alias).toBe('[ foo, { bar }, baz ]')\n    expect(liAst.iterator1).toBe('i')\n    expect(liAst.iterator2).toBe('j')\n  })\n\n  it('v-for directive invalid syntax', () => {\n    parse('<ul><li v-for=\"item into items\"></li></ul>', baseOptions)\n    expect('Invalid v-for expression').toHaveBeenWarned()\n  })\n\n  it('v-if directive syntax', () => {\n    const ast = parse('<p v-if=\"show\">hello world</p>', baseOptions)\n    expect(ast.if).toBe('show')\n    expect(ast.ifConditions[0].exp).toBe('show')\n  })\n\n  it('v-else-if directive syntax', () => {\n    const ast = parse('<div><p v-if=\"show\">hello</p><span v-else-if=\"2\">elseif</span><p v-else>world</p></div>', baseOptions)\n    const ifAst = ast.children[0]\n    const conditionsAst = ifAst.ifConditions\n    expect(conditionsAst.length).toBe(3)\n    expect(conditionsAst[1].block.children[0].text).toBe('elseif')\n    expect(conditionsAst[1].block.parent).toBe(ast)\n    expect(conditionsAst[2].block.children[0].text).toBe('world')\n    expect(conditionsAst[2].block.parent).toBe(ast)\n  })\n\n  it('v-else directive syntax', () => {\n    const ast = parse('<div><p v-if=\"show\">hello</p><p v-else>world</p></div>', baseOptions)\n    const ifAst = ast.children[0]\n    const conditionsAst = ifAst.ifConditions\n    expect(conditionsAst.length).toBe(2)\n    expect(conditionsAst[1].block.children[0].text).toBe('world')\n    expect(conditionsAst[1].block.parent).toBe(ast)\n  })\n\n  it('v-else-if directive invalid syntax', () => {\n    parse('<div><p v-else-if=\"1\">world</p></div>', baseOptions)\n    expect('v-else-if=\"1\" used on element').toHaveBeenWarned()\n  })\n\n  it('v-else directive invalid syntax', () => {\n    parse('<div><p v-else>world</p></div>', baseOptions)\n    expect('v-else used on element').toHaveBeenWarned()\n  })\n\n  it('v-once directive syntax', () => {\n    const ast = parse('<p v-once>world</p>', baseOptions)\n    expect(ast.once).toBe(true)\n  })\n\n  it('slot tag single syntax', () => {\n    const ast = parse('<div><slot></slot></div>', baseOptions)\n    expect(ast.children[0].tag).toBe('slot')\n    expect(ast.children[0].slotName).toBeUndefined()\n  })\n\n  it('slot tag named syntax', () => {\n    const ast = parse('<div><slot name=\"one\">hello world</slot></div>', baseOptions)\n    expect(ast.children[0].tag).toBe('slot')\n    expect(ast.children[0].slotName).toBe('\"one\"')\n  })\n\n  it('slot target', () => {\n    const ast = parse('<p slot=\"one\">hello world</p>', baseOptions)\n    expect(ast.slotTarget).toBe('\"one\"')\n  })\n\n  it('component properties', () => {\n    const ast = parse('<my-component :msg=\"hello\"></my-component>', baseOptions)\n    expect(ast.attrs[0].name).toBe('msg')\n    expect(ast.attrs[0].value).toBe('hello')\n  })\n\n  it('component \"is\" attribute', () => {\n    const ast = parse('<my-component is=\"component1\"></my-component>', baseOptions)\n    expect(ast.component).toBe('\"component1\"')\n  })\n\n  it('component \"inline-template\" attribute', () => {\n    const ast = parse('<my-component inline-template>hello world</my-component>', baseOptions)\n    expect(ast.inlineTemplate).toBe(true)\n  })\n\n  it('class binding', () => {\n    // static\n    const ast1 = parse('<p class=\"class1\">hello world</p>', baseOptions)\n    expect(ast1.staticClass).toBe('\"class1\"')\n    // dynamic\n    const ast2 = parse('<p :class=\"class1\">hello world</p>', baseOptions)\n    expect(ast2.classBinding).toBe('class1')\n    // interpolation warning\n    parse('<p class=\"{{error}}\">hello world</p>', baseOptions)\n    expect('Interpolation inside attributes has been removed').toHaveBeenWarned()\n  })\n\n  it('style binding', () => {\n    const ast = parse('<p :style=\"error\">hello world</p>', baseOptions)\n    expect(ast.styleBinding).toBe('error')\n  })\n\n  it('attribute with v-bind', () => {\n    const ast = parse('<input type=\"text\" name=\"field1\" :value=\"msg\">', baseOptions)\n    expect(ast.attrsList[0].name).toBe('type')\n    expect(ast.attrsList[0].value).toBe('text')\n    expect(ast.attrsList[1].name).toBe('name')\n    expect(ast.attrsList[1].value).toBe('field1')\n    expect(ast.attrsMap['type']).toBe('text')\n    expect(ast.attrsMap['name']).toBe('field1')\n    expect(ast.attrs[0].name).toBe('type')\n    expect(ast.attrs[0].value).toBe('\"text\"')\n    expect(ast.attrs[1].name).toBe('name')\n    expect(ast.attrs[1].value).toBe('\"field1\"')\n    expect(ast.props[0].name).toBe('value')\n    expect(ast.props[0].value).toBe('msg')\n  })\n\n  // #6887\n  it('special case static attribute that must be props', () => {\n    const ast = parse('<video muted></video>', baseOptions)\n    expect(ast.attrs[0].name).toBe('muted')\n    expect(ast.attrs[0].value).toBe('\"\"')\n    expect(ast.props[0].name).toBe('muted')\n    expect(ast.props[0].value).toBe('true')\n  })\n\n  it('attribute with v-on', () => {\n    const ast = parse('<input type=\"text\" name=\"field1\" :value=\"msg\" @input=\"onInput\">', baseOptions)\n    expect(ast.events.input.value).toBe('onInput')\n  })\n\n  it('attribute with directive', () => {\n    const ast = parse('<input type=\"text\" name=\"field1\" :value=\"msg\" v-validate:field1=\"required\">', baseOptions)\n    expect(ast.directives[0].name).toBe('validate')\n    expect(ast.directives[0].value).toBe('required')\n    expect(ast.directives[0].arg).toBe('field1')\n  })\n\n  it('attribute with modifiered directive', () => {\n    const ast = parse('<input type=\"text\" name=\"field1\" :value=\"msg\" v-validate.on.off>', baseOptions)\n    expect(ast.directives[0].modifiers.on).toBe(true)\n    expect(ast.directives[0].modifiers.off).toBe(true)\n  })\n\n  it('literal attribute', () => {\n    // basic\n    const ast1 = parse('<input type=\"text\" name=\"field1\" value=\"hello world\">', baseOptions)\n    expect(ast1.attrsList[0].name).toBe('type')\n    expect(ast1.attrsList[0].value).toBe('text')\n    expect(ast1.attrsList[1].name).toBe('name')\n    expect(ast1.attrsList[1].value).toBe('field1')\n    expect(ast1.attrsList[2].name).toBe('value')\n    expect(ast1.attrsList[2].value).toBe('hello world')\n    expect(ast1.attrsMap['type']).toBe('text')\n    expect(ast1.attrsMap['name']).toBe('field1')\n    expect(ast1.attrsMap['value']).toBe('hello world')\n    expect(ast1.attrs[0].name).toBe('type')\n    expect(ast1.attrs[0].value).toBe('\"text\"')\n    expect(ast1.attrs[1].name).toBe('name')\n    expect(ast1.attrs[1].value).toBe('\"field1\"')\n    expect(ast1.attrs[2].name).toBe('value')\n    expect(ast1.attrs[2].value).toBe('\"hello world\"')\n    // interpolation warning\n    parse('<input type=\"text\" name=\"field1\" value=\"{{msg}}\">', baseOptions)\n    expect('Interpolation inside attributes has been removed').toHaveBeenWarned()\n  })\n\n  if (!isIE && !isEdge) {\n    it('duplicate attribute', () => {\n      parse('<p class=\"class1\" class=\"class1\">hello world</p>', baseOptions)\n      expect('duplicate attribute').toHaveBeenWarned()\n    })\n  }\n\n  it('custom delimiter', () => {\n    const ast = parse('<p>{msg}</p>', extend({ delimiters: ['{', '}'] }, baseOptions))\n    expect(ast.children[0].expression).toBe('_s(msg)')\n  })\n\n  it('not specified getTagNamespace option', () => {\n    const options = extend({}, baseOptions)\n    delete options.getTagNamespace\n    const ast = parse('<svg><text>hello world</text></svg>', options)\n    expect(ast.tag).toBe('svg')\n    expect(ast.ns).toBeUndefined()\n  })\n\n  it('not specified mustUseProp', () => {\n    const options = extend({}, baseOptions)\n    delete options.mustUseProp\n    const ast = parse('<input type=\"text\" name=\"field1\" :value=\"msg\">', options)\n    expect(ast.props).toBeUndefined()\n  })\n\n  it('use prop when prop modifier was explicitly declared', () => {\n    const ast = parse('<component is=\"textarea\" :value.prop=\"val\" />', baseOptions)\n    expect(ast.attrs).toBeUndefined()\n    expect(ast.props.length).toBe(1)\n    expect(ast.props[0].name).toBe('value')\n    expect(ast.props[0].value).toBe('val')\n  })\n\n  it('pre/post transforms', () => {\n    const options = extend({}, baseOptions)\n    const spy1 = jasmine.createSpy('preTransform')\n    const spy2 = jasmine.createSpy('postTransform')\n    options.modules = options.modules.concat([{\n      preTransformNode (el) {\n        spy1(el.tag)\n      },\n      postTransformNode (el) {\n        expect(el.attrs.length).toBe(1)\n        spy2(el.tag)\n      }\n    }])\n    parse('<img v-pre src=\"hi\">', options)\n    expect(spy1).toHaveBeenCalledWith('img')\n    expect(spy2).toHaveBeenCalledWith('img')\n  })\n\n  it('preserve whitespace in <pre> tag', function () {\n    const options = extend({}, baseOptions)\n    const ast = parse('<pre><code>  \\n<span>hi</span>\\n  </code><span> </span></pre>', options)\n    const code = ast.children[0]\n    expect(code.children[0].type).toBe(3)\n    expect(code.children[0].text).toBe('  \\n')\n    expect(code.children[2].type).toBe(3)\n    expect(code.children[2].text).toBe('\\n  ')\n\n    const span = ast.children[1]\n    expect(span.children[0].type).toBe(3)\n    expect(span.children[0].text).toBe(' ')\n  })\n\n  // #5992\n  it('ignore the first newline in <pre> tag', function () {\n    const options = extend({}, baseOptions)\n    const ast = parse('<div><pre>\\nabc</pre>\\ndef<pre>\\n\\nabc</pre></div>', options)\n    const pre = ast.children[0]\n    expect(pre.children[0].type).toBe(3)\n    expect(pre.children[0].text).toBe('abc')\n    const text = ast.children[1]\n    expect(text.type).toBe(3)\n    expect(text.text).toBe('\\ndef')\n    const pre2 = ast.children[2]\n    expect(pre2.children[0].type).toBe(3)\n    expect(pre2.children[0].text).toBe('\\nabc')\n  })\n\n  it('forgivingly handle < in plain text', () => {\n    const options = extend({}, baseOptions)\n    const ast = parse('<p>1 < 2 < 3</p>', options)\n    expect(ast.tag).toBe('p')\n    expect(ast.children.length).toBe(1)\n    expect(ast.children[0].type).toBe(3)\n    expect(ast.children[0].text).toBe('1 < 2 < 3')\n  })\n\n  it('IE conditional comments', () => {\n    const options = extend({}, baseOptions)\n    const ast = parse(`\n      <div>\n        <!--[if lte IE 8]>\n          <p>Test 1</p>\n        <![endif]-->\n      </div>\n    `, options)\n    expect(ast.tag).toBe('div')\n    expect(ast.children.length).toBe(0)\n  })\n\n  it('parse content in textarea as text', () => {\n    const options = extend({}, baseOptions)\n\n    const whitespace = parse(`\n      <textarea>\n        <p>Test 1</p>\n        test2\n      </textarea>\n    `, options)\n    expect(whitespace.tag).toBe('textarea')\n    expect(whitespace.children.length).toBe(1)\n    expect(whitespace.children[0].type).toBe(3)\n    // textarea is whitespace sensitive\n    expect(whitespace.children[0].text).toBe(`        <p>Test 1</p>\n        test2\n      `)\n\n    const comment = parse('<textarea><!--comment--></textarea>', options)\n    expect(comment.tag).toBe('textarea')\n    expect(comment.children.length).toBe(1)\n    expect(comment.children[0].type).toBe(3)\n    expect(comment.children[0].text).toBe('<!--comment-->')\n  })\n\n  // #5526\n  it('should not decode text in script tags', () => {\n    const options = extend({}, baseOptions)\n    const ast = parse(`<script type=\"x/template\">&gt;<foo>&lt;</script>`, options)\n    expect(ast.children[0].text).toBe(`&gt;<foo>&lt;`)\n  })\n\n  it('should ignore comments', () => {\n    const options = extend({}, baseOptions)\n    const ast = parse(`<div>123<!--comment here--></div>`, options)\n    expect(ast.tag).toBe('div')\n    expect(ast.children.length).toBe(1)\n    expect(ast.children[0].type).toBe(3)\n    expect(ast.children[0].text).toBe('123')\n  })\n\n  it('should kept comments', () => {\n    const options = extend({\n      comments: true\n    }, baseOptions)\n    const ast = parse(`<div>123<!--comment here--></div>`, options)\n    expect(ast.tag).toBe('div')\n    expect(ast.children.length).toBe(2)\n    expect(ast.children[0].type).toBe(3)\n    expect(ast.children[0].text).toBe('123')\n    expect(ast.children[1].type).toBe(3) // parse comment with ASTText\n    expect(ast.children[1].isComment).toBe(true) // parse comment with ASTText\n    expect(ast.children[1].text).toBe('comment here')\n  })\n})\n"
  },
  {
    "path": "vue/test/unit/modules/observer/dep.spec.js",
    "content": "import Dep from 'core/observer/dep'\n\ndescribe('Dep', () => {\n  let dep\n\n  beforeEach(() => {\n    dep = new Dep()\n  })\n\n  describe('instance', () => {\n    it('should be created with correct properties', () => {\n      expect(dep.subs.length).toBe(0)\n      expect(new Dep().id).toBe(dep.id + 1)\n    })\n  })\n\n  describe('addSub()', () => {\n    it('should add sub', () => {\n      dep.addSub(null)\n      expect(dep.subs.length).toBe(1)\n      expect(dep.subs[0]).toBe(null)\n    })\n  })\n\n  describe('removeSub()', () => {\n    it('should remove sub', () => {\n      dep.subs.push(null)\n      dep.removeSub(null)\n      expect(dep.subs.length).toBe(0)\n    })\n  })\n\n  describe('depend()', () => {\n    let _target\n\n    beforeAll(() => {\n      _target = Dep.target\n    })\n\n    afterAll(() => {\n      Dep.target = _target\n    })\n\n    it('should do nothing if no target', () => {\n      Dep.target = null\n      dep.depend()\n    })\n\n    it('should add itself to target', () => {\n      Dep.target = jasmine.createSpyObj('TARGET', ['addDep'])\n      dep.depend()\n      expect(Dep.target.addDep).toHaveBeenCalledWith(dep)\n    })\n  })\n\n  describe('notify()', () => {\n    it('should notify subs', () => {\n      dep.subs.push(jasmine.createSpyObj('SUB', ['update']))\n      dep.notify()\n      expect(dep.subs[0].update).toHaveBeenCalled()\n    })\n  })\n})\n"
  },
  {
    "path": "vue/test/unit/modules/observer/observer.spec.js",
    "content": "import Vue from 'vue'\nimport {\n  Observer,\n  observe,\n  set as setProp,\n  del as delProp\n} from 'core/observer/index'\nimport Dep from 'core/observer/dep'\nimport { hasOwn } from 'core/util/index'\n\ndescribe('Observer', () => {\n  it('create on non-observables', () => {\n    // skip primitive value\n    const ob1 = observe(1)\n    expect(ob1).toBeUndefined()\n    // avoid vue instance\n    const ob2 = observe(new Vue())\n    expect(ob2).toBeUndefined()\n    // avoid frozen objects\n    const ob3 = observe(Object.freeze({}))\n    expect(ob3).toBeUndefined()\n  })\n\n  it('create on object', () => {\n    // on object\n    const obj = {\n      a: {},\n      b: {}\n    }\n    const ob1 = observe(obj)\n    expect(ob1 instanceof Observer).toBe(true)\n    expect(ob1.value).toBe(obj)\n    expect(obj.__ob__).toBe(ob1)\n    // should've walked children\n    expect(obj.a.__ob__ instanceof Observer).toBe(true)\n    expect(obj.b.__ob__ instanceof Observer).toBe(true)\n    // should return existing ob on already observed objects\n    const ob2 = observe(obj)\n    expect(ob2).toBe(ob1)\n  })\n\n  it('create on null', () => {\n    // on null\n    const obj = Object.create(null)\n    obj.a = {}\n    obj.b = {}\n    const ob1 = observe(obj)\n    expect(ob1 instanceof Observer).toBe(true)\n    expect(ob1.value).toBe(obj)\n    expect(obj.__ob__).toBe(ob1)\n    // should've walked children\n    expect(obj.a.__ob__ instanceof Observer).toBe(true)\n    expect(obj.b.__ob__ instanceof Observer).toBe(true)\n    // should return existing ob on already observed objects\n    const ob2 = observe(obj)\n    expect(ob2).toBe(ob1)\n  })\n\n  it('create on already observed object', () => {\n    // on object\n    const obj = {}\n    let val = 0\n    let getCount = 0\n    Object.defineProperty(obj, 'a', {\n      configurable: true,\n      enumerable: true,\n      get () {\n        getCount++\n        return val\n      },\n      set (v) { val = v }\n    })\n\n    const ob1 = observe(obj)\n    expect(ob1 instanceof Observer).toBe(true)\n    expect(ob1.value).toBe(obj)\n    expect(obj.__ob__).toBe(ob1)\n\n    getCount = 0\n    // Each read of 'a' should result in only one get underlying get call\n    obj.a\n    expect(getCount).toBe(1)\n    obj.a\n    expect(getCount).toBe(2)\n\n    // should return existing ob on already observed objects\n    const ob2 = observe(obj)\n    expect(ob2).toBe(ob1)\n\n    // should call underlying setter\n    obj.a = 10\n    expect(val).toBe(10)\n  })\n\n  it('create on property with only getter', () => {\n    // on object\n    const obj = {}\n    Object.defineProperty(obj, 'a', {\n      configurable: true,\n      enumerable: true,\n      get () { return 123 }\n    })\n\n    const ob1 = observe(obj)\n    expect(ob1 instanceof Observer).toBe(true)\n    expect(ob1.value).toBe(obj)\n    expect(obj.__ob__).toBe(ob1)\n\n    // should be able to read\n    expect(obj.a).toBe(123)\n\n    // should return existing ob on already observed objects\n    const ob2 = observe(obj)\n    expect(ob2).toBe(ob1)\n\n    // since there is no setter, you shouldn't be able to write to it\n    // PhantomJS throws when a property with no setter is set\n    // but other real browsers don't\n    try {\n      obj.a = 101\n    } catch (e) {}\n    expect(obj.a).toBe(123)\n  })\n\n  it('create on property with only setter', () => {\n    // on object\n    const obj = {}\n    let val = 10\n    Object.defineProperty(obj, 'a', { // eslint-disable-line accessor-pairs\n      configurable: true,\n      enumerable: true,\n      set (v) { val = v }\n    })\n\n    const ob1 = observe(obj)\n    expect(ob1 instanceof Observer).toBe(true)\n    expect(ob1.value).toBe(obj)\n    expect(obj.__ob__).toBe(ob1)\n\n    // reads should return undefined\n    expect(obj.a).toBe(undefined)\n\n    // should return existing ob on already observed objects\n    const ob2 = observe(obj)\n    expect(ob2).toBe(ob1)\n\n    // writes should call the set function\n    obj.a = 100\n    expect(val).toBe(100)\n  })\n\n  it('create on property which is marked not configurable', () => {\n    // on object\n    const obj = {}\n    Object.defineProperty(obj, 'a', {\n      configurable: false,\n      enumerable: true,\n      val: 10\n    })\n\n    const ob1 = observe(obj)\n    expect(ob1 instanceof Observer).toBe(true)\n    expect(ob1.value).toBe(obj)\n    expect(obj.__ob__).toBe(ob1)\n  })\n\n  it('create on array', () => {\n    // on object\n    const arr = [{}, {}]\n    const ob1 = observe(arr)\n    expect(ob1 instanceof Observer).toBe(true)\n    expect(ob1.value).toBe(arr)\n    expect(arr.__ob__).toBe(ob1)\n    // should've walked children\n    expect(arr[0].__ob__ instanceof Observer).toBe(true)\n    expect(arr[1].__ob__ instanceof Observer).toBe(true)\n  })\n\n  it('observing object prop change', () => {\n    const obj = { a: { b: 2 }, c: NaN }\n    observe(obj)\n    // mock a watcher!\n    const watcher = {\n      deps: [],\n      addDep (dep) {\n        this.deps.push(dep)\n        dep.addSub(this)\n      },\n      update: jasmine.createSpy()\n    }\n    // collect dep\n    Dep.target = watcher\n    obj.a.b\n    Dep.target = null\n    expect(watcher.deps.length).toBe(3) // obj.a + a + a.b\n    obj.a.b = 3\n    expect(watcher.update.calls.count()).toBe(1)\n    // swap object\n    obj.a = { b: 4 }\n    expect(watcher.update.calls.count()).toBe(2)\n    watcher.deps = []\n\n    Dep.target = watcher\n    obj.a.b\n    obj.c\n    Dep.target = null\n    expect(watcher.deps.length).toBe(4)\n    // set on the swapped object\n    obj.a.b = 5\n    expect(watcher.update.calls.count()).toBe(3)\n    // should not trigger on NaN -> NaN set\n    obj.c = NaN\n    expect(watcher.update.calls.count()).toBe(3)\n  })\n\n  it('observing object prop change on defined property', () => {\n    const obj = { val: 2 }\n    Object.defineProperty(obj, 'a', {\n      configurable: true,\n      enumerable: true,\n      get () { return this.val },\n      set (v) {\n        this.val = v\n        return this.val\n      }\n    })\n\n    observe(obj)\n    // mock a watcher!\n    const watcher = {\n      deps: [],\n      addDep: function (dep) {\n        this.deps.push(dep)\n        dep.addSub(this)\n      },\n      update: jasmine.createSpy()\n    }\n    // collect dep\n    Dep.target = watcher\n    expect(obj.a).toBe(2) // Make sure 'this' is preserved\n    Dep.target = null\n    obj.a = 3\n    expect(obj.val).toBe(3) // make sure 'setter' was called\n    obj.val = 5\n    expect(obj.a).toBe(5) // make sure 'getter' was called\n  })\n\n  it('observing set/delete', () => {\n    const obj1 = { a: 1 }\n    const ob1 = observe(obj1)\n    const dep1 = ob1.dep\n    spyOn(dep1, 'notify')\n    setProp(obj1, 'b', 2)\n    expect(obj1.b).toBe(2)\n    expect(dep1.notify.calls.count()).toBe(1)\n    delProp(obj1, 'a')\n    expect(hasOwn(obj1, 'a')).toBe(false)\n    expect(dep1.notify.calls.count()).toBe(2)\n    // set existing key, should be a plain set and not\n    // trigger own ob's notify\n    setProp(obj1, 'b', 3)\n    expect(obj1.b).toBe(3)\n    expect(dep1.notify.calls.count()).toBe(2)\n    // set non-existing key\n    setProp(obj1, 'c', 1)\n    expect(obj1.c).toBe(1)\n    expect(dep1.notify.calls.count()).toBe(3)\n    // should ignore deleting non-existing key\n    delProp(obj1, 'a')\n    expect(dep1.notify.calls.count()).toBe(3)\n    // should work on non-observed objects\n    const obj2 = { a: 1 }\n    delProp(obj2, 'a')\n    expect(hasOwn(obj2, 'a')).toBe(false)\n    // should work on Object.create(null)\n    const obj3 = Object.create(null)\n    obj3.a = 1\n    const ob3 = observe(obj3)\n    const dep3 = ob3.dep\n    spyOn(dep3, 'notify')\n    setProp(obj3, 'b', 2)\n    expect(obj3.b).toBe(2)\n    expect(dep3.notify.calls.count()).toBe(1)\n    delProp(obj3, 'a')\n    expect(hasOwn(obj3, 'a')).toBe(false)\n    expect(dep3.notify.calls.count()).toBe(2)\n    // set and delete non-numeric key on array\n    const arr2 = ['a']\n    const ob2 = observe(arr2)\n    const dep2 = ob2.dep\n    spyOn(dep2, 'notify')\n    setProp(arr2, 'b', 2)\n    expect(arr2.b).toBe(2)\n    expect(dep2.notify.calls.count()).toBe(1)\n    delProp(arr2, 'b')\n    expect(hasOwn(arr2, 'b')).toBe(false)\n    expect(dep2.notify.calls.count()).toBe(2)\n  })\n\n  it('warning set/delete on a Vue instance', done => {\n    const vm = new Vue({\n      template: '<div>{{a}}</div>',\n      data: { a: 1 }\n    }).$mount()\n    expect(vm.$el.outerHTML).toBe('<div>1</div>')\n    Vue.set(vm, 'a', 2)\n    waitForUpdate(() => {\n      expect(vm.$el.outerHTML).toBe('<div>2</div>')\n      expect('Avoid adding reactive properties to a Vue instance').not.toHaveBeenWarned()\n      Vue.delete(vm, 'a')\n    }).then(() => {\n      expect('Avoid deleting properties on a Vue instance').toHaveBeenWarned()\n      expect(vm.$el.outerHTML).toBe('<div>2</div>')\n      Vue.set(vm, 'b', 123)\n      expect('Avoid adding reactive properties to a Vue instance').toHaveBeenWarned()\n    }).then(done)\n  })\n\n  it('warning set/delete on Vue instance root $data', done => {\n    const data = { a: 1 }\n    const vm = new Vue({\n      template: '<div>{{a}}</div>',\n      data\n    }).$mount()\n    expect(vm.$el.outerHTML).toBe('<div>1</div>')\n    expect(Vue.set(data, 'a', 2)).toBe(2)\n    waitForUpdate(() => {\n      expect(vm.$el.outerHTML).toBe('<div>2</div>')\n      expect('Avoid adding reactive properties to a Vue instance').not.toHaveBeenWarned()\n      Vue.delete(data, 'a')\n    }).then(() => {\n      expect('Avoid deleting properties on a Vue instance').toHaveBeenWarned()\n      expect(vm.$el.outerHTML).toBe('<div>2</div>')\n      expect(Vue.set(data, 'b', 123)).toBe(123)\n      expect('Avoid adding reactive properties to a Vue instance').toHaveBeenWarned()\n    }).then(done)\n  })\n\n  it('observing array mutation', () => {\n    const arr = []\n    const ob = observe(arr)\n    const dep = ob.dep\n    spyOn(dep, 'notify')\n    const objs = [{}, {}, {}]\n    arr.push(objs[0])\n    arr.pop()\n    arr.unshift(objs[1])\n    arr.shift()\n    arr.splice(0, 0, objs[2])\n    arr.sort()\n    arr.reverse()\n    expect(dep.notify.calls.count()).toBe(7)\n    // inserted elements should be observed\n    objs.forEach(obj => {\n      expect(obj.__ob__ instanceof Observer).toBe(true)\n    })\n  })\n\n  it('warn set/delete on non valid values', () => {\n    try {\n      setProp(null, 'foo', 1)\n    } catch (e) {}\n    expect(`Cannot set reactive property on undefined, null, or primitive value`).toHaveBeenWarned()\n\n    try {\n      delProp(null, 'foo')\n    } catch (e) {}\n    expect(`Cannot delete reactive property on undefined, null, or primitive value`).toHaveBeenWarned()\n  })\n\n  it('should lazy invoke existing getters', () => {\n    const obj = {}\n    let called = false\n    Object.defineProperty(obj, 'getterProp', {\n      enumerable: true,\n      get: () => {\n        called = true\n        return 'some value'\n      }\n    })\n    observe(obj)\n    expect(called).toBe(false)\n  })\n})\n"
  },
  {
    "path": "vue/test/unit/modules/observer/scheduler.spec.js",
    "content": "import Vue from 'vue'\nimport {\n  MAX_UPDATE_COUNT,\n  queueWatcher as _queueWatcher\n} from 'core/observer/scheduler'\n\nfunction queueWatcher (watcher) {\n  watcher.vm = {} // mock vm\n  _queueWatcher(watcher)\n}\n\ndescribe('Scheduler', () => {\n  let spy\n  beforeEach(() => {\n    spy = jasmine.createSpy('scheduler')\n  })\n\n  it('queueWatcher', done => {\n    queueWatcher({\n      run: spy\n    })\n    waitForUpdate(() => {\n      expect(spy.calls.count()).toBe(1)\n    }).then(done)\n  })\n\n  it('dedup', done => {\n    queueWatcher({\n      id: 1,\n      run: spy\n    })\n    queueWatcher({\n      id: 1,\n      run: spy\n    })\n    waitForUpdate(() => {\n      expect(spy.calls.count()).toBe(1)\n    }).then(done)\n  })\n\n  it('allow duplicate when flushing', done => {\n    const job = {\n      id: 1,\n      run: spy\n    }\n    queueWatcher(job)\n    queueWatcher({\n      id: 2,\n      run () { queueWatcher(job) }\n    })\n    waitForUpdate(() => {\n      expect(spy.calls.count()).toBe(2)\n    }).then(done)\n  })\n\n  it('call user watchers before component re-render', done => {\n    const calls = []\n    const vm = new Vue({\n      data: {\n        a: 1\n      },\n      template: '<div>{{ a }}</div>',\n      watch: {\n        a () { calls.push(1) }\n      },\n      beforeUpdate () {\n        calls.push(2)\n      }\n    }).$mount()\n    vm.a = 2\n    waitForUpdate(() => {\n      expect(calls).toEqual([1, 2])\n    }).then(done)\n  })\n\n  it('call user watcher triggered by component re-render immediately', done => {\n    // this happens when a component re-render updates the props of a child\n    const calls = []\n    const vm = new Vue({\n      data: {\n        a: 1\n      },\n      watch: {\n        a () {\n          calls.push(1)\n        }\n      },\n      beforeUpdate () {\n        calls.push(2)\n      },\n      template: '<div><test :a=\"a\"></test></div>',\n      components: {\n        test: {\n          props: ['a'],\n          template: '<div>{{ a }}</div>',\n          watch: {\n            a () {\n              calls.push(3)\n            }\n          },\n          beforeUpdate () {\n            calls.push(4)\n          }\n        }\n      }\n    }).$mount()\n    vm.a = 2\n    waitForUpdate(() => {\n      expect(calls).toEqual([1, 2, 3, 4])\n    }).then(done)\n  })\n\n  it('warn against infinite update loops', function (done) {\n    let count = 0\n    const job = {\n      id: 1,\n      run () {\n        count++\n        queueWatcher(job)\n      }\n    }\n    queueWatcher(job)\n    waitForUpdate(() => {\n      expect(count).toBe(MAX_UPDATE_COUNT + 1)\n      expect('infinite update loop').toHaveBeenWarned()\n    }).then(done)\n  })\n\n  it('should call newly pushed watcher after current watcher is done', done => {\n    const callOrder = []\n    queueWatcher({\n      id: 1,\n      user: true,\n      run () {\n        callOrder.push(1)\n        queueWatcher({\n          id: 2,\n          run () {\n            callOrder.push(3)\n          }\n        })\n        callOrder.push(2)\n      }\n    })\n    waitForUpdate(() => {\n      expect(callOrder).toEqual([1, 2, 3])\n    }).then(done)\n  })\n\n  // GitHub issue #5191\n  it('emit should work when updated hook called', done => {\n    const el = document.createElement('div')\n    const vm = new Vue({\n      template: `<div><child @change=\"bar\" :foo=\"foo\"></child></div>`,\n      data: {\n        foo: 0\n      },\n      methods: {\n        bar: spy\n      },\n      components: {\n        child: {\n          template: `<div>{{foo}}</div>`,\n          props: ['foo'],\n          updated () {\n            this.$emit('change')\n          }\n        }\n      }\n    }).$mount(el)\n    vm.$nextTick(() => {\n      vm.foo = 1\n      vm.$nextTick(() => {\n        expect(vm.$el.innerHTML).toBe('<div>1</div>')\n        expect(spy).toHaveBeenCalled()\n        done()\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "vue/test/unit/modules/observer/watcher.spec.js",
    "content": "import Vue from 'vue'\nimport Watcher from 'core/observer/watcher'\n\ndescribe('Watcher', () => {\n  let vm, spy\n  beforeEach(() => {\n    vm = new Vue({\n      template: '<div></div>',\n      data: {\n        a: 1,\n        b: {\n          c: 2,\n          d: 4\n        },\n        c: 'c',\n        msg: 'yo'\n      }\n    }).$mount()\n    spy = jasmine.createSpy('watcher')\n  })\n\n  it('path', done => {\n    const watcher = new Watcher(vm, 'b.c', spy)\n    expect(watcher.value).toBe(2)\n    vm.b.c = 3\n    waitForUpdate(() => {\n      expect(watcher.value).toBe(3)\n      expect(spy).toHaveBeenCalledWith(3, 2)\n      vm.b = { c: 4 } // swapping the object\n    }).then(() => {\n      expect(watcher.value).toBe(4)\n      expect(spy).toHaveBeenCalledWith(4, 3)\n    }).then(done)\n  })\n\n  it('non-existent path, set later', done => {\n    const watcher1 = new Watcher(vm, 'b.e', spy)\n    expect(watcher1.value).toBeUndefined()\n    // check $add should not affect isolated children\n    const child2 = new Vue({ parent: vm })\n    const watcher2 = new Watcher(child2, 'b.e', spy)\n    expect(watcher2.value).toBeUndefined()\n    Vue.set(vm.b, 'e', 123)\n    waitForUpdate(() => {\n      expect(watcher1.value).toBe(123)\n      expect(watcher2.value).toBeUndefined()\n      expect(spy.calls.count()).toBe(1)\n      expect(spy).toHaveBeenCalledWith(123, undefined)\n    }).then(done)\n  })\n\n  it('delete', done => {\n    const watcher = new Watcher(vm, 'b.c', spy)\n    expect(watcher.value).toBe(2)\n    Vue.delete(vm.b, 'c')\n    waitForUpdate(() => {\n      expect(watcher.value).toBeUndefined()\n      expect(spy).toHaveBeenCalledWith(undefined, 2)\n    }).then(done)\n  })\n\n  it('path containing $data', done => {\n    const watcher = new Watcher(vm, '$data.b.c', spy)\n    expect(watcher.value).toBe(2)\n    vm.b = { c: 3 }\n    waitForUpdate(() => {\n      expect(watcher.value).toBe(3)\n      expect(spy).toHaveBeenCalledWith(3, 2)\n      vm.$data.b.c = 4\n    }).then(() => {\n      expect(watcher.value).toBe(4)\n      expect(spy).toHaveBeenCalledWith(4, 3)\n    }).then(done)\n  })\n\n  it('deep watch', done => {\n    let oldB\n    new Watcher(vm, 'b', spy, {\n      deep: true\n    })\n    vm.b.c = { d: 4 }\n    waitForUpdate(() => {\n      expect(spy).toHaveBeenCalledWith(vm.b, vm.b)\n      oldB = vm.b\n      vm.b = { c: [{ a: 1 }] }\n    }).then(() => {\n      expect(spy).toHaveBeenCalledWith(vm.b, oldB)\n      expect(spy.calls.count()).toBe(2)\n      vm.b.c[0].a = 2\n    }).then(() => {\n      expect(spy).toHaveBeenCalledWith(vm.b, vm.b)\n      expect(spy.calls.count()).toBe(3)\n    }).then(done)\n  })\n\n  it('deep watch $data', done => {\n    new Watcher(vm, '$data', spy, {\n      deep: true\n    })\n    vm.b.c = 3\n    waitForUpdate(() => {\n      expect(spy).toHaveBeenCalledWith(vm.$data, vm.$data)\n    }).then(done)\n  })\n\n  it('deep watch with circular references', done => {\n    new Watcher(vm, 'b', spy, {\n      deep: true\n    })\n    Vue.set(vm.b, '_', vm.b)\n    waitForUpdate(() => {\n      expect(spy).toHaveBeenCalledWith(vm.b, vm.b)\n      expect(spy.calls.count()).toBe(1)\n      vm.b._.c = 1\n    }).then(() => {\n      expect(spy).toHaveBeenCalledWith(vm.b, vm.b)\n      expect(spy.calls.count()).toBe(2)\n    }).then(done)\n  })\n\n  it('fire change for prop addition/deletion in non-deep mode', done => {\n    new Watcher(vm, 'b', spy)\n    Vue.set(vm.b, 'e', 123)\n    waitForUpdate(() => {\n      expect(spy).toHaveBeenCalledWith(vm.b, vm.b)\n      expect(spy.calls.count()).toBe(1)\n      Vue.delete(vm.b, 'e')\n    }).then(() => {\n      expect(spy.calls.count()).toBe(2)\n    }).then(done)\n  })\n\n  it('watch function', done => {\n    const watcher = new Watcher(vm, function () {\n      return this.a + this.b.d\n    }, spy)\n    expect(watcher.value).toBe(5)\n    vm.a = 2\n    waitForUpdate(() => {\n      expect(spy).toHaveBeenCalledWith(6, 5)\n      vm.b = { d: 2 }\n    }).then(() => {\n      expect(spy).toHaveBeenCalledWith(4, 6)\n    }).then(done)\n  })\n\n  it('computed mode, lazy', done => {\n    let getterCallCount = 0\n    const watcher = new Watcher(vm, function () {\n      getterCallCount++\n      return this.a + this.b.d\n    }, null, { computed: true })\n\n    expect(getterCallCount).toBe(0)\n    expect(watcher.computed).toBe(true)\n    expect(watcher.value).toBeUndefined()\n    expect(watcher.dirty).toBe(true)\n    expect(watcher.dep).toBeTruthy()\n\n    const value = watcher.evaluate()\n    expect(getterCallCount).toBe(1)\n    expect(value).toBe(5)\n    expect(watcher.value).toBe(5)\n    expect(watcher.dirty).toBe(false)\n\n    // should not get again if not dirty\n    watcher.evaluate()\n    expect(getterCallCount).toBe(1)\n\n    vm.a = 2\n    waitForUpdate(() => {\n      expect(getterCallCount).toBe(1)\n      expect(watcher.value).toBe(5)\n      expect(watcher.dirty).toBe(true)\n\n      const value = watcher.evaluate()\n      expect(getterCallCount).toBe(2)\n      expect(value).toBe(6)\n      expect(watcher.value).toBe(6)\n      expect(watcher.dirty).toBe(false)\n    }).then(done)\n  })\n\n  it('computed mode, activated', done => {\n    let getterCallCount = 0\n    const watcher = new Watcher(vm, function () {\n      getterCallCount++\n      return this.a + this.b.d\n    }, null, { computed: true })\n\n    // activate by mocking a subscriber\n    const subMock = jasmine.createSpyObj('sub', ['update'])\n    watcher.dep.addSub(subMock)\n\n    const value = watcher.evaluate()\n    expect(getterCallCount).toBe(1)\n    expect(value).toBe(5)\n\n    vm.a = 2\n    waitForUpdate(() => {\n      expect(getterCallCount).toBe(2)\n      expect(subMock.update).toHaveBeenCalled()\n\n      // since already computed, calling evaluate again should not trigger\n      // getter\n      watcher.evaluate()\n      expect(getterCallCount).toBe(2)\n    }).then(done)\n  })\n\n  it('teardown', done => {\n    const watcher = new Watcher(vm, 'b.c', spy)\n    watcher.teardown()\n    vm.b.c = 3\n    waitForUpdate(() => {\n      expect(watcher.active).toBe(false)\n      expect(spy).not.toHaveBeenCalled()\n    }).then(done)\n  })\n\n  it('warn not support path', () => {\n    new Watcher(vm, 'd.e + c', spy)\n    expect('Failed watching path:').toHaveBeenWarned()\n  })\n})\n"
  },
  {
    "path": "vue/test/unit/modules/server-compiler/optimizer.spec.js",
    "content": ""
  },
  {
    "path": "vue/test/unit/modules/sfc/sfc-parser.spec.js",
    "content": "import { parseComponent } from 'sfc/parser'\n\ndescribe('Single File Component parser', () => {\n  it('should parse', () => {\n    const res = parseComponent(`\n      <template>\n        <div>hi</div>\n      </template>\n      <style src=\"./test.css\"></style>\n      <style lang=\"stylus\" scoped>\n        h1\n          color red\n        h2\n          color green\n      </style>\n      <style module>\n        h1 { font-weight: bold }\n      </style>\n      <style bool-attr val-attr=\"test\"></style>\n      <script>\n        export default {}\n      </script>\n      <div>\n        <style>nested should be ignored</style>\n      </div>\n    `)\n    expect(res.template.content.trim()).toBe('<div>hi</div>')\n    expect(res.styles.length).toBe(4)\n    expect(res.styles[0].src).toBe('./test.css')\n    expect(res.styles[1].lang).toBe('stylus')\n    expect(res.styles[1].scoped).toBe(true)\n    expect(res.styles[1].content.trim()).toBe('h1\\n  color red\\nh2\\n  color green')\n    expect(res.styles[2].module).toBe(true)\n    expect(res.styles[3].attrs['bool-attr']).toBe(true)\n    expect(res.styles[3].attrs['val-attr']).toBe('test')\n    expect(res.script.content.trim()).toBe('export default {}')\n  })\n\n  it('should parse template with closed input', () => {\n    const res = parseComponent(`\n      <template>\n        <input type=\"text\"/>\n      </template>\n    `)\n\n    expect(res.template.content.trim()).toBe('<input type=\"text\"/>')\n  })\n\n  it('should handle nested template', () => {\n    const res = parseComponent(`\n      <template>\n        <div><template v-if=\"ok\">hi</template></div>\n      </template>\n    `)\n    expect(res.template.content.trim()).toBe('<div><template v-if=\"ok\">hi</template></div>')\n  })\n\n  it('pad content', () => {\n    const content = `\n      <template>\n        <div></div>\n      </template>\n      <script>\n        export default {}\n      </script>\n      <style>\n        h1 { color: red }\n      </style>\n`\n    const padDefault = parseComponent(content.trim(), { pad: true })\n    const padLine = parseComponent(content.trim(), { pad: 'line' })\n    const padSpace = parseComponent(content.trim(), { pad: 'space' })\n\n    expect(padDefault.script.content).toBe(Array(3 + 1).join('//\\n') + '\\nexport default {}\\n')\n    expect(padDefault.styles[0].content).toBe(Array(6 + 1).join('\\n') + '\\nh1 { color: red }\\n')\n    expect(padLine.script.content).toBe(Array(3 + 1).join('//\\n') + '\\nexport default {}\\n')\n    expect(padLine.styles[0].content).toBe(Array(6 + 1).join('\\n') + '\\nh1 { color: red }\\n')\n    expect(padSpace.script.content).toBe(`<template>\n        <div></div>\n      </template>\n      <script>`.replace(/./g, ' ') + '\\nexport default {}\\n')\n    expect(padSpace.styles[0].content).toBe(`<template>\n        <div></div>\n      </template>\n      <script>\n        export default {}\n      </script>\n      <style>`.replace(/./g, ' ') + '\\nh1 { color: red }\\n')\n  })\n\n  it('should handle template blocks with lang as special text', () => {\n    const res = parseComponent(`\n      <template lang=\"pug\">\n        div\n          h1(v-if='1 < 2') hello\n      </template>\n    `)\n    expect(res.template.content.trim()).toBe(`div\\n  h1(v-if='1 < 2') hello`)\n  })\n\n  it('should handle component contains \"<\" only', () => {\n    const res = parseComponent(`\n      <template>\n        <span><</span>\n      </template>\n    `)\n    expect(res.template.content.trim()).toBe(`<span><</span>`)\n  })\n\n  it('should handle custom blocks without parsing them', () => {\n    const res = parseComponent(`\n      <template>\n        <div></div>\n      </template>\n      <example name=\"simple\">\n        <my-button ref=\"button\">Hello</my-button>\n      </example>\n      <example name=\"with props\">\n        <my-button color=\"red\">Hello</my-button>\n      </example>\n      <test name=\"simple\" foo=\"bar\">\n      export default function simple (vm) {\n        describe('Hello', () => {\n          it('should display Hello', () => {\n            this.vm.$refs.button.$el.innerText.should.equal('Hello')\n          }))\n        }))\n      }\n      </test>\n    `)\n    expect(res.customBlocks.length).toBe(3)\n\n    const simpleExample = res.customBlocks[0]\n    expect(simpleExample.type).toBe('example')\n    expect(simpleExample.content.trim()).toBe('<my-button ref=\"button\">Hello</my-button>')\n    expect(simpleExample.attrs.name).toBe('simple')\n\n    const withProps = res.customBlocks[1]\n    expect(withProps.type).toBe('example')\n    expect(withProps.content.trim()).toBe('<my-button color=\"red\">Hello</my-button>')\n    expect(withProps.attrs.name).toBe('with props')\n\n    const simpleTest = res.customBlocks[2]\n    expect(simpleTest.type).toBe('test')\n    expect(simpleTest.content.trim()).toBe(`export default function simple (vm) {\n  describe('Hello', () => {\n    it('should display Hello', () => {\n      this.vm.$refs.button.$el.innerText.should.equal('Hello')\n    }))\n  }))\n}`)\n    expect(simpleTest.attrs.name).toBe('simple')\n    expect(simpleTest.attrs.foo).toBe('bar')\n  })\n\n  // Regression #4289\n  it('accepts nested template tag', () => {\n    const raw = `<div>\n      <template v-if=\"true === true\">\n        <section class=\"section\">\n          <div class=\"container\">\n            Should be shown\n          </div>\n        </section>\n      </template>\n      <template v-else>\n        <p>Should not be shown</p>\n      </template>\n    </div>`\n    const res = parseComponent(`<template>${raw}</template>`)\n    expect(res.template.content.trim()).toBe(raw)\n  })\n\n  it('should not hang on trailing text', () => {\n    const res = parseComponent(`<template>hi</`)\n    expect(res.template.content).toBe('hi')\n  })\n})\n"
  },
  {
    "path": "vue/test/unit/modules/util/next-tick.spec.js",
    "content": "import { nextTick } from 'core/util/next-tick'\n\ndescribe('nextTick', () => {\n  it('accepts a callback', done => {\n    nextTick(done)\n  })\n\n  it('returns undefined when passed a callback', () => {\n    expect(nextTick(() => {})).toBeUndefined()\n  })\n\n  if (typeof Promise !== 'undefined') {\n    it('returns a Promise when provided no callback', done => {\n      nextTick().then(done)\n    })\n\n    it('returns a Promise with a context argument when provided a falsy callback and an object', done => {\n      const obj = {}\n      nextTick(undefined, obj).then(ctx => {\n        expect(ctx).toBe(obj)\n        done()\n      })\n    })\n\n    it('returned Promise should resolve correctly vs callback', done => {\n      const spy = jasmine.createSpy()\n      nextTick(spy)\n      nextTick().then(() => {\n        expect(spy).toHaveBeenCalled()\n        done()\n      })\n    })\n  }\n})\n"
  },
  {
    "path": "vue/test/unit/modules/vdom/create-component.spec.js",
    "content": "import Vue from 'vue'\nimport { createComponent } from 'core/vdom/create-component'\n\ndescribe('create-component', () => {\n  let vm\n  beforeEach(done => {\n    vm = new Vue({\n      template: '<p>{{msg}}</p>',\n      data () {\n        return { msg: 'hello, my children' }\n      }\n    }).$mount()\n    Vue.nextTick(done)\n  })\n\n  it('create a component basically', () => {\n    const child = {\n      name: 'child',\n      props: ['msg'],\n      render () {}\n    }\n    const data = {\n      props: { msg: 'hello world' },\n      attrs: { id: 1 },\n      staticAttrs: { class: 'foo' },\n      on: { notify: 'onNotify' }\n    }\n    const vnode = createComponent(child, data, vm, vm)\n    expect(vnode.tag).toMatch(/vue-component-[0-9]+-child/)\n    expect(vnode.data.attrs).toEqual({ id: 1 })\n    expect(vnode.data.staticAttrs).toEqual({ class: 'foo' })\n    expect(vnode.componentOptions.propsData).toEqual({ msg: 'hello world' })\n    expect(vnode.componentOptions.listeners).toEqual({ notify: 'onNotify' })\n    expect(vnode.children).toBeUndefined()\n    expect(vnode.text).toBeUndefined()\n    expect(vnode.elm).toBeUndefined()\n    expect(vnode.ns).toBeUndefined()\n    expect(vnode.context).toEqual(vm)\n  })\n\n  it('create a component when resolved with async loading', done => {\n    let vnode = null\n    const data = {\n      props: {},\n      staticAttrs: { class: 'foo' }\n    }\n    spyOn(vm, '$forceUpdate')\n    function async (resolve, reject) {\n      setTimeout(() => {\n        resolve({\n          name: 'child',\n          props: ['msg']\n        })\n        Vue.nextTick(loaded)\n      }, 0)\n    }\n    function go () {\n      vnode = createComponent(async, data, vm, vm)\n      expect(vnode.isComment).toBe(true) // not to be loaded yet.\n      expect(vnode.asyncFactory).toBe(async)\n    }\n    function loaded () {\n      vnode = createComponent(async, data, vm, vm)\n      expect(vnode.tag).toMatch(/vue-component-[0-9]+-child/)\n      expect(vnode.data.staticAttrs).toEqual({ class: 'foo' })\n      expect(vnode.children).toBeUndefined()\n      expect(vnode.text).toBeUndefined()\n      expect(vnode.elm).toBeUndefined()\n      expect(vnode.ns).toBeUndefined()\n      expect(vnode.context).toEqual(vm)\n      expect(vm.$forceUpdate).toHaveBeenCalled()\n      done()\n    }\n    go()\n  })\n\n  it('not create a component when rejected with async loading', done => {\n    let vnode = null\n    const data = {\n      props: { msg: 'hello world' },\n      attrs: { id: 1 }\n    }\n    const reason = 'failed!!'\n    function async (resolve, reject) {\n      setTimeout(() => {\n        reject(reason)\n        Vue.nextTick(failed)\n      }, 0)\n    }\n    function go () {\n      vnode = createComponent(async, data, vm, vm)\n      expect(vnode.isComment).toBe(true) // not to be loaded yet.\n    }\n    function failed () {\n      vnode = createComponent(async, data, vm, vm)\n      expect(vnode.isComment).toBe(true) // failed, still a comment node\n      expect(`Failed to resolve async component: ${async}\\nReason: ${reason}`).toHaveBeenWarned()\n      done()\n    }\n    go()\n  })\n\n  it('not create a component when specified with falsy', () => {\n    const vnode = createComponent(null, {}, vm, vm)\n    expect(vnode).toBeUndefined()\n  })\n\n  it('warn component definition type', () => {\n    const Ctor = 'child'\n    const vnode = createComponent(Ctor, {}, vm, vm)\n    expect(vnode).toBeUndefined()\n    expect(`Invalid Component definition: ${Ctor}`).toHaveBeenWarned()\n  })\n})\n"
  },
  {
    "path": "vue/test/unit/modules/vdom/create-element.spec.js",
    "content": "import Vue from 'vue'\nimport { createEmptyVNode } from 'core/vdom/vnode'\n\ndescribe('create-element', () => {\n  it('render vnode with basic reserved tag using createElement', () => {\n    const vm = new Vue({\n      data: { msg: 'hello world' }\n    })\n    const h = vm.$createElement\n    const vnode = h('p', {})\n    expect(vnode.tag).toBe('p')\n    expect(vnode.data).toEqual({})\n    expect(vnode.children).toBeUndefined()\n    expect(vnode.text).toBeUndefined()\n    expect(vnode.elm).toBeUndefined()\n    expect(vnode.ns).toBeUndefined()\n    expect(vnode.context).toEqual(vm)\n  })\n\n  it('render vnode with component using createElement', () => {\n    const vm = new Vue({\n      data: { message: 'hello world' },\n      components: {\n        'my-component': {\n          props: ['msg']\n        }\n      }\n    })\n    const h = vm.$createElement\n    const vnode = h('my-component', { props: { msg: vm.message }})\n    expect(vnode.tag).toMatch(/vue-component-[0-9]+/)\n    expect(vnode.componentOptions.propsData).toEqual({ msg: vm.message })\n    expect(vnode.children).toBeUndefined()\n    expect(vnode.text).toBeUndefined()\n    expect(vnode.elm).toBeUndefined()\n    expect(vnode.ns).toBeUndefined()\n    expect(vnode.context).toEqual(vm)\n  })\n\n  it('render vnode with custom tag using createElement', () => {\n    const vm = new Vue({\n      data: { msg: 'hello world' }\n    })\n    const h = vm.$createElement\n    const tag = 'custom-tag'\n    const vnode = h(tag, {})\n    expect(vnode.tag).toBe('custom-tag')\n    expect(vnode.data).toEqual({})\n    expect(vnode.children).toBeUndefined()\n    expect(vnode.text).toBeUndefined()\n    expect(vnode.elm).toBeUndefined()\n    expect(vnode.ns).toBeUndefined()\n    expect(vnode.context).toEqual(vm)\n    expect(vnode.componentOptions).toBeUndefined()\n  })\n\n  it('render empty vnode with falsy tag using createElement', () => {\n    const vm = new Vue({\n      data: { msg: 'hello world' }\n    })\n    const h = vm.$createElement\n    const vnode = h(null, {})\n    expect(vnode).toEqual(createEmptyVNode())\n  })\n\n  it('render vnode with not string tag using createElement', () => {\n    const vm = new Vue({\n      data: { msg: 'hello world' }\n    })\n    const h = vm.$createElement\n    const vnode = h(Vue.extend({ // Component class\n      props: ['msg']\n    }), { props: { msg: vm.message }})\n    expect(vnode.tag).toMatch(/vue-component-[0-9]+/)\n    expect(vnode.componentOptions.propsData).toEqual({ msg: vm.message })\n    expect(vnode.children).toBeUndefined()\n    expect(vnode.text).toBeUndefined()\n    expect(vnode.elm).toBeUndefined()\n    expect(vnode.ns).toBeUndefined()\n    expect(vnode.context).toEqual(vm)\n  })\n\n  it('render vnode with createElement with children', () => {\n    const vm = new Vue({})\n    const h = vm.$createElement\n    const vnode = h('p', void 0, [h('br'), 'hello world', h('br')])\n    expect(vnode.children[0].tag).toBe('br')\n    expect(vnode.children[1].text).toBe('hello world')\n    expect(vnode.children[2].tag).toBe('br')\n  })\n\n  it('render vnode with children, omitting data', () => {\n    const vm = new Vue({})\n    const h = vm.$createElement\n    const vnode = h('p', [h('br'), 'hello world', h('br')])\n    expect(vnode.children[0].tag).toBe('br')\n    expect(vnode.children[1].text).toBe('hello world')\n    expect(vnode.children[2].tag).toBe('br')\n  })\n\n  it('render vnode with children, including boolean and null type', () => {\n    const vm = new Vue({})\n    const h = vm.$createElement\n    const vnode = h('p', [h('br'), true, 123, h('br'), 'abc', null])\n    expect(vnode.children.length).toBe(4)\n    expect(vnode.children[0].tag).toBe('br')\n    expect(vnode.children[1].text).toBe('123')\n    expect(vnode.children[2].tag).toBe('br')\n    expect(vnode.children[3].text).toBe('abc')\n  })\n\n  it('render svg elements with correct namespace', () => {\n    const vm = new Vue({})\n    const h = vm.$createElement\n    const vnode = h('svg', [h('a', [h('foo', [h('bar')])])])\n    expect(vnode.ns).toBe('svg')\n    // should apply ns to children recursively\n    expect(vnode.children[0].ns).toBe('svg')\n    expect(vnode.children[0].children[0].ns).toBe('svg')\n    expect(vnode.children[0].children[0].children[0].ns).toBe('svg')\n  })\n\n  it('render MathML elements with correct namespace', () => {\n    const vm = new Vue({})\n    const h = vm.$createElement\n    const vnode = h('math', [h('matrix')])\n    expect(vnode.ns).toBe('math')\n    // should apply ns to children\n    expect(vnode.children[0].ns).toBe('math')\n    // although not explicitly listed, elements nested under <math>\n    // should not be treated as component\n    expect(vnode.children[0].componentOptions).toBeUndefined()\n  })\n\n  it('render svg foreignObject with correct namespace', () => {\n    const vm = new Vue({})\n    const h = vm.$createElement\n    const vnode = h('svg', [h('foreignObject', [h('p'), h('svg')])])\n    expect(vnode.ns).toBe('svg')\n    expect(vnode.children[0].ns).toBe('svg')\n    expect(vnode.children[0].children[0].ns).toBeUndefined()\n    // #7330\n    expect(vnode.children[0].children[1].ns).toBe('svg')\n  })\n\n  // #6642\n  it('render svg foreignObject component with correct namespace', () => {\n    const vm = new Vue({\n      template: `\n        <svg>\n          <test></test>\n        </svg>\n      `,\n      components: {\n        test: {\n          template: `\n          <foreignObject>\n            <p xmlns=\"http://www.w3.org/1999/xhtml\"></p>\n          </foreignObject>\n          `\n        }\n      }\n    }).$mount()\n    const testComp = vm.$children[0]\n    expect(testComp.$vnode.ns).toBe('svg')\n    expect(testComp._vnode.tag).toBe('foreignObject')\n    expect(testComp._vnode.ns).toBe('svg')\n    expect(testComp._vnode.children[0].tag).toBe('p')\n    expect(testComp._vnode.children[0].ns).toBeUndefined()\n  })\n\n  // #6506\n  it('render SVGAElement in a component correctly', () => {\n    const vm = new Vue({\n      template: `\n        <svg>\n          <test></test>\n        </svg>\n      `,\n      components: {\n        test: { render: h => h('a') }\n      }\n    }).$mount()\n    const testComp = vm.$children[0]\n    expect(testComp.$vnode.ns).toBe('svg')\n    expect(testComp._vnode.tag).toBe('a')\n    expect(testComp._vnode.ns).toBe('svg')\n  })\n\n  it('warn observed data objects', () => {\n    new Vue({\n      data: {\n        data: {}\n      },\n      render (h) {\n        return h('div', this.data)\n      }\n    }).$mount()\n    expect('Avoid using observed data object as vnode data').toHaveBeenWarned()\n  })\n\n  it('warn non-primitive key', () => {\n    new Vue({\n      render (h) {\n        return h('div', { key: {}})\n      }\n    }).$mount()\n    expect('Avoid using non-primitive value as key').toHaveBeenWarned()\n  })\n\n  it('doesn\\'t warn boolean key', () => {\n    new Vue({\n      render (h) {\n        return h('div', { key: true })\n      }\n    }).$mount()\n    expect('Avoid using non-primitive value as key').not.toHaveBeenWarned()\n  })\n\n  it('doesn\\'t warn symbol key', () => {\n    new Vue({\n      render (h) {\n        return h('div', { key: Symbol('symbol') })\n      }\n    }).$mount()\n    expect('Avoid using non-primitive value as key').not.toHaveBeenWarned()\n  })\n\n  it('nested child elements should be updated correctly', done => {\n    const vm = new Vue({\n      data: { n: 1 },\n      render (h) {\n        const list = []\n        for (let i = 0; i < this.n; i++) {\n          list.push(h('span', i))\n        }\n        const input = h('input', {\n          attrs: {\n            value: 'a',\n            type: 'text'\n          }\n        })\n        return h('div', [[...list, input]])\n      }\n    }).$mount()\n    expect(vm.$el.innerHTML).toContain('<span>0</span><input')\n    const el = vm.$el.querySelector('input')\n    el.value = 'b'\n    vm.n++\n    waitForUpdate(() => {\n      expect(vm.$el.innerHTML).toContain('<span>0</span><span>1</span><input')\n      expect(vm.$el.querySelector('input')).toBe(el)\n      expect(vm.$el.querySelector('input').value).toBe('b')\n    }).then(done)\n  })\n\n  // #7786\n  it('creates element with vnode reference in :class or :style', () => {\n    const vm = new Vue({\n      components: {\n        foo: {\n          render (h) {\n            return h('div', {\n              class: {\n                'has-vnode': this.$vnode\n              }\n            }, 'foo')\n          }\n        }\n      },\n      render: h => h('foo')\n    }).$mount()\n    expect(vm.$el.innerHTML).toContain('foo')\n    expect(vm.$el.classList.contains('has-vnode')).toBe(true)\n  })\n})\n"
  },
  {
    "path": "vue/test/unit/modules/vdom/modules/attrs.spec.js",
    "content": "import Vue from 'vue'\nimport { patch } from 'web/runtime/patch'\nimport VNode from 'core/vdom/vnode'\nimport { xlinkNS } from 'web/util/index'\n\ndescribe('vdom attrs module', () => {\n  it('should create an element with attrs', () => {\n    const vnode = new VNode('p', { attrs: { id: 1, class: 'class1' }})\n    const elm = patch(null, vnode)\n    expect(elm.id).toBe('1')\n    expect(elm).toHaveClass('class1')\n  })\n\n  it('should change the elements attrs', () => {\n    const vnode1 = new VNode('i', { attrs: { id: '1', class: 'i am vdom' }})\n    const vnode2 = new VNode('i', { attrs: { id: '2', class: 'i am' }})\n    patch(null, vnode1)\n    const elm = patch(vnode1, vnode2)\n    expect(elm.id).toBe('2')\n    expect(elm).toHaveClass('i')\n    expect(elm).toHaveClass('am')\n    expect(elm).not.toHaveClass('vdom')\n  })\n\n  it('should remove the elements attrs', () => {\n    const vnode1 = new VNode('i', { attrs: { id: '1', class: 'i am vdom' }})\n    const vnode2 = new VNode('i', { attrs: { id: '1' }})\n    patch(null, vnode1)\n    const elm = patch(vnode1, vnode2)\n    expect(elm.id).toBe('1')\n    expect(elm.className).toBe('')\n  })\n\n  it('should remove the elements attrs for new nodes without attrs data', () => {\n    const vnode1 = new VNode('i', { attrs: { id: '1', class: 'i am vdom' }})\n    const vnode2 = new VNode('i', {})\n    patch(null, vnode1)\n    const elm = patch(vnode1, vnode2)\n    expect(elm.id).toBe('')\n    expect(elm.className).toBe('')\n  })\n\n  it('should remove the falsy value from boolean attr', () => {\n    const vnode = new VNode('option', { attrs: { disabled: null }})\n    const elm = patch(null, vnode)\n    expect(elm.getAttribute('disabled')).toBe(null)\n  })\n\n  it('should set the attr name to boolean attr', () => {\n    const vnode = new VNode('option', { attrs: { disabled: true }})\n    const elm = patch(null, vnode)\n    expect(elm.getAttribute('disabled')).toBe('disabled')\n  })\n\n  it('should set the falsy value to enumerated attr', () => {\n    const vnode = new VNode('div', { attrs: { contenteditable: null }})\n    const elm = patch(null, vnode)\n    expect(elm.getAttribute('contenteditable')).toBe('false')\n  })\n\n  it('should set the boolean string value to enumerated attr', () => {\n    const vnode = new VNode('div', { attrs: { contenteditable: 'true' }})\n    const elm = patch(null, vnode)\n    expect(elm.getAttribute('contenteditable')).toBe('true')\n  })\n\n  it('should set the xlink value to attr', () => {\n    const vnode = new VNode('a', { attrs: { 'xlink:href': '#id1' }})\n    const elm = patch(null, vnode)\n    expect(elm.getAttributeNS(xlinkNS, 'href')).toBe('#id1')\n  })\n\n  it('should set the xlink boolean string value to attr', () => {\n    const vnode = new VNode('option', { attrs: { 'xlink:disabled': true }})\n    const elm = patch(null, vnode)\n    expect(elm.getAttributeNS(xlinkNS, 'disabled')).toBe('true')\n  })\n\n  it('should handle mutating observed attrs object', done => {\n    const vm = new Vue({\n      data: {\n        attrs: {\n          id: 'foo'\n        }\n      },\n      render (h) {\n        return h('div', {\n          attrs: this.attrs\n        })\n      }\n    }).$mount()\n\n    expect(vm.$el.id).toBe('foo')\n    vm.attrs.id = 'bar'\n    waitForUpdate(() => {\n      expect(vm.$el.id).toBe('bar')\n      vm.attrs = { id: 'baz' }\n    }).then(() => {\n      expect(vm.$el.id).toBe('baz')\n    }).then(done)\n  })\n})\n"
  },
  {
    "path": "vue/test/unit/modules/vdom/modules/class.spec.js",
    "content": "import { patch } from 'web/runtime/patch'\nimport VNode from 'core/vdom/vnode'\n\ndescribe('vdom class module', () => {\n  it('should create an element with staticClass', () => {\n    const vnode = new VNode('p', { staticClass: 'class1' })\n    const elm = patch(null, vnode)\n    expect(elm).toHaveClass('class1')\n  })\n\n  it('should create an element with class', () => {\n    const vnode = new VNode('p', { class: 'class1' })\n    const elm = patch(null, vnode)\n    expect(elm).toHaveClass('class1')\n  })\n\n  it('should create an element with array class', () => {\n    const vnode = new VNode('p', { class: ['class1', 'class2'] })\n    const elm = patch(null, vnode)\n    expect(elm).toHaveClass('class1')\n    expect(elm).toHaveClass('class2')\n  })\n\n  it('should create an element with object class', () => {\n    const vnode = new VNode('p', {\n      class: { class1: true, class2: false, class3: true }\n    })\n    const elm = patch(null, vnode)\n    expect(elm).toHaveClass('class1')\n    expect(elm).not.toHaveClass('class2')\n    expect(elm).toHaveClass('class3')\n  })\n\n  it('should create an element with mixed class', () => {\n    const vnode = new VNode('p', {\n      class: [{ class1: false, class2: true, class3: false }, 'class4', ['class5', 'class6']]\n    })\n    const elm = patch(null, vnode)\n    expect(elm).not.toHaveClass('class1')\n    expect(elm).toHaveClass('class2')\n    expect(elm).not.toHaveClass('class3')\n    expect(elm).toHaveClass('class4')\n    expect(elm).toHaveClass('class5')\n    expect(elm).toHaveClass('class6')\n  })\n\n  it('should create an element with staticClass and class', () => {\n    const vnode = new VNode('p', { staticClass: 'class1', class: 'class2' })\n    const elm = patch(null, vnode)\n    expect(elm).toHaveClass('class1')\n    expect(elm).toHaveClass('class2')\n  })\n\n  it('should handle transition class', () => {\n    const vnode1 = new VNode('p', {\n      class: { class1: true, class2: false, class3: true }\n    })\n    let elm = patch(null, vnode1)\n    elm._transitionClasses = ['class4']\n    const vnode2 = new VNode('p', {\n      class: { class1: true, class2: true, class3: true }\n    })\n    elm = patch(vnode1, vnode2)\n    expect(elm).toHaveClass('class1')\n    expect(elm).toHaveClass('class2')\n    expect(elm).toHaveClass('class3')\n    expect(elm).toHaveClass('class4')\n  })\n\n  it('should change the elements class', () => {\n    const vnode1 = new VNode('p', {\n      class: { class1: true, class2: false, class3: true }\n    })\n    const vnode2 = new VNode('p', { staticClass: 'foo bar' })\n    let elm = patch(null, vnode1)\n    elm = patch(vnode1, vnode2)\n    expect(elm).not.toHaveClass('class1')\n    expect(elm).not.toHaveClass('class2')\n    expect(elm).not.toHaveClass('class3')\n    expect(elm).toHaveClass('foo')\n    expect(elm).toHaveClass('bar')\n  })\n\n  it('should remove the elements class', () => {\n    const vnode1 = new VNode('p', {\n      class: { class1: true, class2: false, class3: true }\n    })\n    const vnode2 = new VNode('p', { class: {}})\n    let elm = patch(null, vnode1)\n    elm = patch(vnode1, vnode2)\n    expect(elm).not.toHaveClass('class1')\n    expect(elm).not.toHaveClass('class2')\n    expect(elm).not.toHaveClass('class3')\n  })\n\n  it('should remove class for new nodes without class data', () => {\n    const vnode1 = new VNode('p', {\n      class: { class1: true, class2: false, class3: true }\n    })\n    const vnode2 = new VNode('p', {})\n    let elm = patch(null, vnode1)\n    elm = patch(vnode1, vnode2)\n    expect(elm).not.toHaveClass('class1')\n    expect(elm).not.toHaveClass('class2')\n    expect(elm).not.toHaveClass('class3')\n  })\n})\n"
  },
  {
    "path": "vue/test/unit/modules/vdom/modules/directive.spec.js",
    "content": "import Vue from 'vue'\nimport { patch } from 'web/runtime/patch'\nimport VNode from 'core/vdom/vnode'\n\ndescribe('vdom directive module', () => {\n  it('should work', () => {\n    const directive1 = {\n      bind: jasmine.createSpy('bind'),\n      update: jasmine.createSpy('update'),\n      unbind: jasmine.createSpy('unbind')\n    }\n    const vm = new Vue({ directives: { directive1 }})\n    // create\n    const vnode1 = new VNode('div', {}, [\n      new VNode('p', {\n        directives: [{\n          name: 'directive1', value: 'hello', arg: 'arg1', modifiers: { modifier1: true }\n        }]\n      }, undefined, 'hello world', undefined, vm)\n    ])\n    patch(null, vnode1)\n    expect(directive1.bind).toHaveBeenCalled()\n    // update\n    const vnode2 = new VNode('div', {}, [\n      new VNode('p', {\n        directives: [{\n          name: 'directive1', value: 'world', arg: 'arg1', modifiers: { modifier1: true }\n        }]\n      }, undefined, 'hello world', undefined, vm)\n    ])\n    patch(vnode1, vnode2)\n    expect(directive1.update).toHaveBeenCalled()\n    // destroy\n    const vnode3 = new VNode('div')\n    patch(vnode2, vnode3)\n    expect(directive1.unbind).toHaveBeenCalled()\n  })\n})\n"
  },
  {
    "path": "vue/test/unit/modules/vdom/modules/dom-props.spec.js",
    "content": "import Vue from 'vue'\nimport { patch } from 'web/runtime/patch'\nimport VNode from 'core/vdom/vnode'\n\ndescribe('vdom domProps module', () => {\n  it('should create an element with domProps', () => {\n    const vnode = new VNode('a', { domProps: { src: 'http://localhost/' }})\n    const elm = patch(null, vnode)\n    expect(elm.src).toBe('http://localhost/')\n  })\n\n  it('should change the elements domProps', () => {\n    const vnode1 = new VNode('a', { domProps: { src: 'http://localhost/' }})\n    const vnode2 = new VNode('a', { domProps: { src: 'https://vuejs.org/' }})\n    patch(null, vnode1)\n    const elm = patch(vnode1, vnode2)\n    expect(elm.src).toBe('https://vuejs.org/')\n  })\n\n  it('should remove the elements domProps', () => {\n    const vnode1 = new VNode('a', { domProps: { src: 'http://localhost/' }})\n    const vnode2 = new VNode('a', { domProps: {}})\n    patch(null, vnode1)\n    const elm = patch(vnode1, vnode2)\n    expect(elm.src).toBe('')\n  })\n\n  it('should initialize the elements value to zero', () => {\n    const vnode = new VNode('input', { domProps: { value: 0 }})\n    const elm = patch(null, vnode)\n    expect(elm.value).toBe('0')\n  })\n\n  it('should save raw value on element', () => {\n    const value = {}\n    const vnode = new VNode('input', { domProps: { value }})\n    const elm = patch(null, vnode)\n    expect(elm._value).toBe(value)\n  })\n\n  it('should discard vnode children if the node has innerHTML or textContent as a prop', () => {\n    const vnode = new VNode('div', { domProps: { innerHTML: 'hi' }}, [\n      new VNode('span'), new VNode('span')\n    ])\n    const elm = patch(null, vnode)\n    expect(elm.innerHTML).toBe('hi')\n    expect(vnode.children.length).toBe(0)\n\n    const vnode2 = new VNode('div', { domProps: { textContent: 'hi' }}, [\n      new VNode('span'), new VNode('span')\n    ])\n    const elm2 = patch(null, vnode2)\n    expect(elm2.textContent).toBe('hi')\n    expect(vnode2.children.length).toBe(0)\n\n    const vnode3 = new VNode('div', undefined, undefined, '123')\n    patch(null, vnode3)\n    const elm3 = patch(vnode3, vnode2)\n    expect(elm3.textContent).toBe('hi')\n\n    const vnode4 = new VNode('div', undefined, undefined, new VNode('span'))\n    patch(null, vnode4)\n    const elm4 = patch(vnode4, vnode)\n    expect(elm4.textContent).toBe('hi')\n  })\n\n  it('should handle mutating observed props object', done => {\n    const vm = new Vue({\n      data: {\n        props: {\n          id: 'foo'\n        }\n      },\n      render (h) {\n        return h('div', {\n          domProps: this.props\n        })\n      }\n    }).$mount()\n\n    expect(vm.$el.id).toBe('foo')\n    vm.props.id = 'bar'\n    waitForUpdate(() => {\n      expect(vm.$el.id).toBe('bar')\n      vm.props = { id: 'baz' }\n    }).then(() => {\n      expect(vm.$el.id).toBe('baz')\n    }).then(done)\n  })\n})\n"
  },
  {
    "path": "vue/test/unit/modules/vdom/modules/events.spec.js",
    "content": "import { patch } from 'web/runtime/patch'\nimport VNode from 'core/vdom/vnode'\n\ndescribe('vdom events module', () => {\n  it('should attach event handler to element', () => {\n    const click = jasmine.createSpy()\n    const vnode = new VNode('a', { on: { click }})\n\n    const elm = patch(null, vnode)\n    document.body.appendChild(elm)\n    triggerEvent(elm, 'click')\n    expect(click.calls.count()).toBe(1)\n  })\n\n  it('should not duplicate the same listener', () => {\n    const click = jasmine.createSpy()\n    const vnode1 = new VNode('a', { on: { click }})\n    const vnode2 = new VNode('a', { on: { click }})\n\n    const elm = patch(null, vnode1)\n    patch(vnode1, vnode2)\n    document.body.appendChild(elm)\n    triggerEvent(elm, 'click')\n    expect(click.calls.count()).toBe(1)\n  })\n\n  it('should update different listener', () => {\n    const click = jasmine.createSpy()\n    const click2 = jasmine.createSpy()\n    const vnode1 = new VNode('a', { on: { click }})\n    const vnode2 = new VNode('a', { on: { click: click2 }})\n\n    const elm = patch(null, vnode1)\n    document.body.appendChild(elm)\n    triggerEvent(elm, 'click')\n    expect(click.calls.count()).toBe(1)\n    expect(click2.calls.count()).toBe(0)\n\n    patch(vnode1, vnode2)\n    triggerEvent(elm, 'click')\n    expect(click.calls.count()).toBe(1)\n    expect(click2.calls.count()).toBe(1)\n  })\n\n  it('should attach Array of multiple handlers', () => {\n    const click = jasmine.createSpy()\n    const vnode = new VNode('a', { on: { click: [click, click] }})\n\n    const elm = patch(null, vnode)\n    document.body.appendChild(elm)\n    triggerEvent(elm, 'click')\n    expect(click.calls.count()).toBe(2)\n  })\n\n  it('should update Array of multiple handlers', () => {\n    const click = jasmine.createSpy()\n    const click2 = jasmine.createSpy()\n    const vnode1 = new VNode('a', { on: { click: [click, click2] }})\n    const vnode2 = new VNode('a', { on: { click: [click] }})\n\n    const elm = patch(null, vnode1)\n    document.body.appendChild(elm)\n    triggerEvent(elm, 'click')\n    expect(click.calls.count()).toBe(1)\n    expect(click2.calls.count()).toBe(1)\n\n    patch(vnode1, vnode2)\n    triggerEvent(elm, 'click')\n    expect(click.calls.count()).toBe(2)\n    expect(click2.calls.count()).toBe(1)\n  })\n\n  it('should remove handlers that are no longer present', () => {\n    const click = jasmine.createSpy()\n    const vnode1 = new VNode('a', { on: { click }})\n    const vnode2 = new VNode('a', {})\n\n    const elm = patch(null, vnode1)\n    document.body.appendChild(elm)\n    triggerEvent(elm, 'click')\n    expect(click.calls.count()).toBe(1)\n\n    patch(vnode1, vnode2)\n    triggerEvent(elm, 'click')\n    expect(click.calls.count()).toBe(1)\n  })\n\n  it('should remove Array handlers that are no longer present', () => {\n    const click = jasmine.createSpy()\n    const vnode1 = new VNode('a', { on: { click: [click, click] }})\n    const vnode2 = new VNode('a', {})\n\n    const elm = patch(null, vnode1)\n    document.body.appendChild(elm)\n    triggerEvent(elm, 'click')\n    expect(click.calls.count()).toBe(2)\n\n    patch(vnode1, vnode2)\n    triggerEvent(elm, 'click')\n    expect(click.calls.count()).toBe(2)\n  })\n\n  // #4650\n  it('should handle single -> array or array -> single handler changes', () => {\n    const click = jasmine.createSpy()\n    const click2 = jasmine.createSpy()\n    const click3 = jasmine.createSpy()\n    const vnode0 = new VNode('a', { on: { click: click }})\n    const vnode1 = new VNode('a', { on: { click: [click, click2] }})\n    const vnode2 = new VNode('a', { on: { click: click }})\n    const vnode3 = new VNode('a', { on: { click: [click2, click3] }})\n\n    const elm = patch(null, vnode0)\n    document.body.appendChild(elm)\n    triggerEvent(elm, 'click')\n    expect(click.calls.count()).toBe(1)\n    expect(click2.calls.count()).toBe(0)\n\n    patch(vnode0, vnode1)\n    triggerEvent(elm, 'click')\n    expect(click.calls.count()).toBe(2)\n    expect(click2.calls.count()).toBe(1)\n\n    patch(vnode1, vnode2)\n    triggerEvent(elm, 'click')\n    expect(click.calls.count()).toBe(3)\n    expect(click2.calls.count()).toBe(1)\n\n    patch(vnode2, vnode3)\n    triggerEvent(elm, 'click')\n    expect(click.calls.count()).toBe(3)\n    expect(click2.calls.count()).toBe(2)\n    expect(click3.calls.count()).toBe(1)\n  })\n})\n"
  },
  {
    "path": "vue/test/unit/modules/vdom/modules/style.spec.js",
    "content": "import { patch } from 'web/runtime/patch'\nimport VNode from 'core/vdom/vnode'\n\ndescribe('vdom style module', () => {\n  it('should create an element with style', () => {\n    const vnode = new VNode('p', { style: { fontSize: '12px' }})\n    const elm = patch(null, vnode)\n    expect(elm.style.fontSize).toBe('12px')\n  })\n\n  it('should create an element with array style', () => {\n    const vnode = new VNode('p', { style: [{ fontSize: '12px' }, { color: 'red' }] })\n    const elm = patch(null, vnode)\n    expect(elm.style.fontSize).toBe('12px')\n    expect(elm.style.color).toBe('red')\n  })\n\n  it('should change elements style', () => {\n    const vnode1 = new VNode('p', { style: { fontSize: '12px' }})\n    const vnode2 = new VNode('p', { style: { fontSize: '10px', display: 'block' }})\n    patch(null, vnode1)\n    const elm = patch(vnode1, vnode2)\n    expect(elm.style.fontSize).toBe('10px')\n    expect(elm.style.display).toBe('block')\n  })\n\n  it('should remove elements attrs', () => {\n    const vnode1 = new VNode('p', { style: { fontSize: '12px' }})\n    const vnode2 = new VNode('p', { style: { display: 'block' }})\n    patch(null, vnode1)\n    const elm = patch(vnode1, vnode2)\n    expect(elm.style.fontSize).toBe('')\n    expect(elm.style.display).toBe('block')\n  })\n})\n"
  },
  {
    "path": "vue/test/unit/modules/vdom/patch/children.spec.js",
    "content": "import { patch } from 'web/runtime/patch'\nimport VNode, { createEmptyVNode } from 'core/vdom/vnode'\n\nfunction prop (name) {\n  return obj => { return obj[name] }\n}\n\nfunction map (fn, list) {\n  const ret = []\n  for (let i = 0; i < list.length; i++) {\n    ret[i] = fn(list[i])\n  }\n  return ret\n}\n\nfunction spanNum (n) {\n  if (typeof n === 'string') {\n    return new VNode('span', {}, undefined, n)\n  } else {\n    return new VNode('span', { key: n }, undefined, n.toString())\n  }\n}\n\nfunction shuffle (array) {\n  let currentIndex = array.length\n  let temporaryValue\n  let randomIndex\n\n  // while there remain elements to shuffle...\n  while (currentIndex !== 0) {\n    // pick a remaining element...\n    randomIndex = Math.floor(Math.random() * currentIndex)\n    currentIndex -= 1\n    // and swap it with the current element.\n    temporaryValue = array[currentIndex]\n    array[currentIndex] = array[randomIndex]\n    array[randomIndex] = temporaryValue\n  }\n  return array\n}\n\nconst inner = prop('innerHTML')\nconst tag = prop('tagName')\n\ndescribe('vdom patch: children', () => {\n  let vnode0\n  beforeEach(() => {\n    vnode0 = new VNode('p', { attrs: { id: '1' }}, [createTextVNode('hello world')])\n    patch(null, vnode0)\n  })\n\n  it('should appends elements', () => {\n    const vnode1 = new VNode('p', {}, [1].map(spanNum))\n    const vnode2 = new VNode('p', {}, [1, 2, 3].map(spanNum))\n    let elm = patch(vnode0, vnode1)\n    expect(elm.children.length).toBe(1)\n    elm = patch(vnode1, vnode2)\n    expect(elm.children.length).toBe(3)\n    expect(elm.children[1].innerHTML).toBe('2')\n    expect(elm.children[2].innerHTML).toBe('3')\n  })\n\n  it('should prepends elements', () => {\n    const vnode1 = new VNode('p', {}, [4, 5].map(spanNum))\n    const vnode2 = new VNode('p', {}, [1, 2, 3, 4, 5].map(spanNum))\n    let elm = patch(vnode0, vnode1)\n    expect(elm.children.length).toBe(2)\n    elm = patch(vnode1, vnode2)\n    expect(map(inner, elm.children)).toEqual(['1', '2', '3', '4', '5'])\n  })\n\n  it('should add elements in the middle', () => {\n    const vnode1 = new VNode('p', {}, [1, 2, 4, 5].map(spanNum))\n    const vnode2 = new VNode('p', {}, [1, 2, 3, 4, 5].map(spanNum))\n    let elm = patch(vnode0, vnode1)\n    expect(elm.children.length).toBe(4)\n    elm = patch(vnode1, vnode2)\n    expect(map(inner, elm.children)).toEqual(['1', '2', '3', '4', '5'])\n  })\n\n  it('should add elements at begin and end', () => {\n    const vnode1 = new VNode('p', {}, [2, 3, 4].map(spanNum))\n    const vnode2 = new VNode('p', {}, [1, 2, 3, 4, 5].map(spanNum))\n    let elm = patch(vnode0, vnode1)\n    expect(elm.children.length).toBe(3)\n    elm = patch(vnode1, vnode2)\n    expect(map(inner, elm.children)).toEqual(['1', '2', '3', '4', '5'])\n  })\n\n  it('should add children to parent with no children', () => {\n    const vnode1 = new VNode('p', { key: 'p' })\n    const vnode2 = new VNode('p', { key: 'p' }, [1, 2, 3].map(spanNum))\n    let elm = patch(vnode0, vnode1)\n    expect(elm.children.length).toBe(0)\n    elm = patch(vnode1, vnode2)\n    expect(map(inner, elm.children)).toEqual(['1', '2', '3'])\n  })\n\n  it('should remove all children from parent', () => {\n    const vnode1 = new VNode('p', { key: 'p' }, [1, 2, 3].map(spanNum))\n    const vnode2 = new VNode('p', { key: 'p' })\n    let elm = patch(vnode0, vnode1)\n    expect(map(inner, elm.children)).toEqual(['1', '2', '3'])\n    elm = patch(vnode1, vnode2)\n    expect(elm.children.length).toBe(0)\n  })\n\n  it('should remove elements from the beginning', () => {\n    const vnode1 = new VNode('p', {}, [1, 2, 3, 4, 5].map(spanNum))\n    const vnode2 = new VNode('p', {}, [3, 4, 5].map(spanNum))\n    let elm = patch(vnode0, vnode1)\n    expect(elm.children.length).toBe(5)\n    elm = patch(vnode1, vnode2)\n    expect(map(inner, elm.children)).toEqual(['3', '4', '5'])\n  })\n\n  it('should removes elements from end', () => {\n    const vnode1 = new VNode('p', {}, [1, 2, 3, 4, 5].map(spanNum))\n    const vnode2 = new VNode('p', {}, [1, 2, 3].map(spanNum))\n    let elm = patch(vnode0, vnode1)\n    expect(elm.children.length).toBe(5)\n    elm = patch(vnode1, vnode2)\n    expect(elm.children.length).toBe(3)\n    expect(map(inner, elm.children)).toEqual(['1', '2', '3'])\n  })\n\n  it('should remove elements from the middle', () => {\n    const vnode1 = new VNode('p', {}, [1, 2, 3, 4, 5].map(spanNum))\n    const vnode2 = new VNode('p', {}, [1, 2, 4, 5].map(spanNum))\n    let elm = patch(vnode0, vnode1)\n    expect(elm.children.length).toBe(5)\n    elm = patch(vnode1, vnode2)\n    expect(elm.children.length).toBe(4)\n    expect(map(inner, elm.children)).toEqual(['1', '2', '4', '5'])\n  })\n\n  it('should moves element forward', () => {\n    const vnode1 = new VNode('p', {}, [1, 2, 3, 4].map(spanNum))\n    const vnode2 = new VNode('p', {}, [2, 3, 1, 4].map(spanNum))\n    let elm = patch(vnode0, vnode1)\n    expect(elm.children.length).toBe(4)\n    elm = patch(vnode1, vnode2)\n    expect(elm.children.length).toBe(4)\n    expect(map(inner, elm.children)).toEqual(['2', '3', '1', '4'])\n  })\n\n  it('should move elements to end', () => {\n    const vnode1 = new VNode('p', {}, [1, 2, 3].map(spanNum))\n    const vnode2 = new VNode('p', {}, [2, 3, 1].map(spanNum))\n    let elm = patch(vnode0, vnode1)\n    expect(elm.children.length).toBe(3)\n    elm = patch(vnode1, vnode2)\n    expect(elm.children.length).toBe(3)\n    expect(map(inner, elm.children)).toEqual(['2', '3', '1'])\n  })\n\n  it('should move element backwards', () => {\n    const vnode1 = new VNode('p', {}, [1, 2, 3, 4].map(spanNum))\n    const vnode2 = new VNode('p', {}, [1, 4, 2, 3].map(spanNum))\n    let elm = patch(vnode0, vnode1)\n    expect(elm.children.length).toBe(4)\n    elm = patch(vnode1, vnode2)\n    expect(elm.children.length).toBe(4)\n    expect(map(inner, elm.children)).toEqual(['1', '4', '2', '3'])\n  })\n\n  it('should swap first and last', () => {\n    const vnode1 = new VNode('p', {}, [1, 2, 3, 4].map(spanNum))\n    const vnode2 = new VNode('p', {}, [4, 2, 3, 1].map(spanNum))\n    let elm = patch(vnode0, vnode1)\n    expect(elm.children.length).toBe(4)\n    elm = patch(vnode1, vnode2)\n    expect(elm.children.length).toBe(4)\n    expect(map(inner, elm.children)).toEqual(['4', '2', '3', '1'])\n  })\n\n  it('should move to left and replace', () => {\n    const vnode1 = new VNode('p', {}, [1, 2, 3, 4, 5].map(spanNum))\n    const vnode2 = new VNode('p', {}, [4, 1, 2, 3, 6].map(spanNum))\n    let elm = patch(vnode0, vnode1)\n    expect(elm.children.length).toBe(5)\n    elm = patch(vnode1, vnode2)\n    expect(elm.children.length).toBe(5)\n    expect(map(inner, elm.children)).toEqual(['4', '1', '2', '3', '6'])\n  })\n\n  it('should move to left and leaves hold', () => {\n    const vnode1 = new VNode('p', {}, [1, 4, 5].map(spanNum))\n    const vnode2 = new VNode('p', {}, [4, 6].map(spanNum))\n    let elm = patch(vnode0, vnode1)\n    expect(elm.children.length).toBe(3)\n    elm = patch(vnode1, vnode2)\n    expect(map(inner, elm.children)).toEqual(['4', '6'])\n  })\n\n  it('should handle moved and set to undefined element ending at the end', () => {\n    const vnode1 = new VNode('p', {}, [2, 4, 5].map(spanNum))\n    const vnode2 = new VNode('p', {}, [4, 5, 3].map(spanNum))\n    let elm = patch(vnode0, vnode1)\n    expect(elm.children.length).toBe(3)\n    elm = patch(vnode1, vnode2)\n    expect(elm.children.length).toBe(3)\n    expect(map(inner, elm.children)).toEqual(['4', '5', '3'])\n  })\n\n  it('should move a key in non-keyed nodes with a size up', () => {\n    const vnode1 = new VNode('p', {}, [1, 'a', 'b', 'c'].map(spanNum))\n    const vnode2 = new VNode('p', {}, ['d', 'a', 'b', 'c', 1, 'e'].map(spanNum))\n    let elm = patch(vnode0, vnode1)\n    expect(elm.children.length).toBe(4)\n    expect(elm.textContent, '1abc')\n    elm = patch(vnode1, vnode2)\n    expect(elm.children.length).toBe(6)\n    expect(elm.textContent, 'dabc1e')\n  })\n\n  it('should reverse element', () => {\n    const vnode1 = new VNode('p', {}, [1, 2, 3, 4, 5, 6, 7, 8].map(spanNum))\n    const vnode2 = new VNode('p', {}, [8, 7, 6, 5, 4, 3, 2, 1].map(spanNum))\n    let elm = patch(vnode0, vnode1)\n    expect(elm.children.length).toBe(8)\n    elm = patch(vnode1, vnode2)\n    expect(map(inner, elm.children)).toEqual(['8', '7', '6', '5', '4', '3', '2', '1'])\n  })\n\n  it('something', () => {\n    const vnode1 = new VNode('p', {}, [0, 1, 2, 3, 4, 5].map(spanNum))\n    const vnode2 = new VNode('p', {}, [4, 3, 2, 1, 5, 0].map(spanNum))\n    let elm = patch(vnode0, vnode1)\n    expect(elm.children.length).toBe(6)\n    elm = patch(vnode1, vnode2)\n    expect(map(inner, elm.children)).toEqual(['4', '3', '2', '1', '5', '0'])\n  })\n\n  it('should handle random shuffle', () => {\n    let n\n    let i\n    const arr = []\n    const opacities = []\n    const elms = 14\n    const samples = 5\n    function spanNumWithOpacity (n, o) {\n      return new VNode('span', { key: n, style: { opacity: o }}, undefined, n.toString())\n    }\n\n    for (n = 0; n < elms; ++n) { arr[n] = n }\n    for (n = 0; n < samples; ++n) {\n      const vnode1 = new VNode('span', {}, arr.map(n => {\n        return spanNumWithOpacity(n, '1')\n      }))\n      const shufArr = shuffle(arr.slice(0))\n      let elm = patch(vnode0, vnode1)\n      for (i = 0; i < elms; ++i) {\n        expect(elm.children[i].innerHTML).toBe(i.toString())\n        opacities[i] = Math.random().toFixed(5).toString()\n      }\n      const vnode2 = new VNode('span', {}, arr.map(n => {\n        return spanNumWithOpacity(shufArr[n], opacities[n])\n      }))\n      elm = patch(vnode1, vnode2)\n      for (i = 0; i < elms; ++i) {\n        expect(elm.children[i].innerHTML).toBe(shufArr[i].toString())\n        expect(opacities[i].indexOf(elm.children[i].style.opacity)).toBe(0)\n      }\n    }\n  })\n\n  it('should append elements with updating children without keys', () => {\n    const vnode1 = new VNode('div', {}, [\n      new VNode('span', {}, undefined, 'hello')\n    ])\n    const vnode2 = new VNode('div', {}, [\n      new VNode('span', {}, undefined, 'hello'),\n      new VNode('span', {}, undefined, 'world')\n    ])\n    let elm = patch(vnode0, vnode1)\n    expect(map(inner, elm.children)).toEqual(['hello'])\n    elm = patch(vnode1, vnode2)\n    expect(map(inner, elm.children)).toEqual(['hello', 'world'])\n  })\n\n  it('should handle unmoved text nodes with updating children without keys', () => {\n    const vnode1 = new VNode('div', {}, [\n      createTextVNode('text'),\n      new VNode('span', {}, undefined, 'hello')\n    ])\n    const vnode2 = new VNode('div', {}, [\n      createTextVNode('text'),\n      new VNode('span', {}, undefined, 'hello')\n    ])\n    let elm = patch(vnode0, vnode1)\n    expect(elm.childNodes[0].textContent).toBe('text')\n    elm = patch(vnode1, vnode2)\n    expect(elm.childNodes[0].textContent).toBe('text')\n  })\n\n  it('should handle changing text children with updating children without keys', () => {\n    const vnode1 = new VNode('div', {}, [\n      createTextVNode('text'),\n      new VNode('span', {}, undefined, 'hello')\n    ])\n    const vnode2 = new VNode('div', {}, [\n      createTextVNode('text2'),\n      new VNode('span', {}, undefined, 'hello')\n    ])\n    let elm = patch(vnode0, vnode1)\n    expect(elm.childNodes[0].textContent).toBe('text')\n    elm = patch(vnode1, vnode2)\n    expect(elm.childNodes[0].textContent).toBe('text2')\n  })\n\n  it('should prepend element with updating children without keys', () => {\n    const vnode1 = new VNode('div', {}, [\n      new VNode('span', {}, undefined, 'world')\n    ])\n    const vnode2 = new VNode('div', {}, [\n      new VNode('span', {}, undefined, 'hello'),\n      new VNode('span', {}, undefined, 'world')\n    ])\n    let elm = patch(vnode0, vnode1)\n    expect(map(inner, elm.children)).toEqual(['world'])\n    elm = patch(vnode1, vnode2)\n    expect(map(inner, elm.children)).toEqual(['hello', 'world'])\n  })\n\n  it('should prepend element of different tag type with updating children without keys', () => {\n    const vnode1 = new VNode('div', {}, [\n      new VNode('span', {}, undefined, 'world')\n    ])\n    const vnode2 = new VNode('div', {}, [\n      new VNode('div', {}, undefined, 'hello'),\n      new VNode('span', {}, undefined, 'world')\n    ])\n    let elm = patch(vnode0, vnode1)\n    expect(map(inner, elm.children)).toEqual(['world'])\n    elm = patch(vnode1, vnode2)\n    expect(map(prop('tagName'), elm.children)).toEqual(['DIV', 'SPAN'])\n    expect(map(inner, elm.children)).toEqual(['hello', 'world'])\n  })\n\n  it('should remove elements with updating children without keys', () => {\n    const vnode1 = new VNode('div', {}, [\n      new VNode('span', {}, undefined, 'one'),\n      new VNode('span', {}, undefined, 'two'),\n      new VNode('span', {}, undefined, 'three')\n    ])\n    const vnode2 = new VNode('div', {}, [\n      new VNode('span', {}, undefined, 'one'),\n      new VNode('span', {}, undefined, 'three')\n    ])\n    let elm = patch(vnode0, vnode1)\n    expect(map(inner, elm.children)).toEqual(['one', 'two', 'three'])\n    elm = patch(vnode1, vnode2)\n    expect(map(inner, elm.children)).toEqual(['one', 'three'])\n  })\n\n  it('should remove a single text node with updating children without keys', () => {\n    const vnode1 = new VNode('div', {}, undefined, 'one')\n    const vnode2 = new VNode('div', {})\n    let elm = patch(vnode0, vnode1)\n    expect(elm.textContent).toBe('one')\n    elm = patch(vnode1, vnode2)\n    expect(elm.textContent).toBe('')\n  })\n\n  it('should remove a single text node when children are updated', () => {\n    const vnode1 = new VNode('div', {}, undefined, 'one')\n    const vnode2 = new VNode('div', {}, [\n      new VNode('div', {}, undefined, 'two'),\n      new VNode('span', {}, undefined, 'three')\n    ])\n    let elm = patch(vnode0, vnode1)\n    expect(elm.textContent).toBe('one')\n    elm = patch(vnode1, vnode2)\n    expect(map(prop('textContent'), elm.childNodes)).toEqual(['two', 'three'])\n  })\n\n  it('should remove a text node among other elements', () => {\n    const vnode1 = new VNode('div', {}, [\n      createTextVNode('one'),\n      new VNode('span', {}, undefined, 'two')\n    ])\n    const vnode2 = new VNode('div', {}, [\n      new VNode('div', {}, undefined, 'three')\n    ])\n    let elm = patch(vnode0, vnode1)\n    expect(map(prop('textContent'), elm.childNodes)).toEqual(['one', 'two'])\n    elm = patch(vnode1, vnode2)\n    expect(elm.childNodes.length).toBe(1)\n    expect(elm.childNodes[0].tagName).toBe('DIV')\n    expect(elm.childNodes[0].textContent).toBe('three')\n  })\n\n  it('should reorder elements', () => {\n    const vnode1 = new VNode('div', {}, [\n      new VNode('span', {}, undefined, 'one'),\n      new VNode('div', {}, undefined, 'two'),\n      new VNode('b', {}, undefined, 'three')\n    ])\n    const vnode2 = new VNode('div', {}, [\n      new VNode('b', {}, undefined, 'three'),\n      new VNode('span', {}, undefined, 'two'),\n      new VNode('div', {}, undefined, 'one')\n    ])\n    let elm = patch(vnode0, vnode1)\n    expect(map(inner, elm.children)).toEqual(['one', 'two', 'three'])\n    elm = patch(vnode1, vnode2)\n    expect(map(inner, elm.children)).toEqual(['three', 'two', 'one'])\n  })\n\n  it('should handle children with the same key but with different tag', () => {\n    const vnode1 = new VNode('div', {}, [\n      new VNode('div', { key: 1 }, undefined, 'one'),\n      new VNode('div', { key: 2 }, undefined, 'two'),\n      new VNode('div', { key: 3 }, undefined, 'three'),\n      new VNode('div', { key: 4 }, undefined, 'four')\n    ])\n    const vnode2 = new VNode('div', {}, [\n      new VNode('div', { key: 4 }, undefined, 'four'),\n      new VNode('span', { key: 3 }, undefined, 'three'),\n      new VNode('span', { key: 2 }, undefined, 'two'),\n      new VNode('div', { key: 1 }, undefined, 'one')\n    ])\n    let elm = patch(vnode0, vnode1)\n    expect(map(tag, elm.children)).toEqual(['DIV', 'DIV', 'DIV', 'DIV'])\n    expect(map(inner, elm.children)).toEqual(['one', 'two', 'three', 'four'])\n    elm = patch(vnode1, vnode2)\n    expect(map(tag, elm.children)).toEqual(['DIV', 'SPAN', 'SPAN', 'DIV'])\n    expect(map(inner, elm.children)).toEqual(['four', 'three', 'two', 'one'])\n  })\n\n  it('should handle children with the same tag, same key, but one with data and one without data', () => {\n    const vnode1 = new VNode('div', {}, [\n      new VNode('div', { class: 'hi' }, undefined, 'one')\n    ])\n    const vnode2 = new VNode('div', {}, [\n      new VNode('div', undefined, undefined, 'four')\n    ])\n    let elm = patch(vnode0, vnode1)\n    const child1 = elm.children[0]\n    expect(child1.className).toBe('hi')\n    elm = patch(vnode1, vnode2)\n    const child2 = elm.children[0]\n    expect(child1).not.toBe(child2)\n    expect(child2.className).toBe('')\n  })\n\n  it('should handle static vnodes properly', () => {\n    function makeNode (text) {\n      return new VNode('div', undefined, [\n        new VNode(undefined, undefined, undefined, text)\n      ])\n    }\n    const b = makeNode('B')\n    b.isStatic = true\n    b.key = `__static__1`\n    const vnode1 = new VNode('div', {}, [makeNode('A'), b, makeNode('C')])\n    const vnode2 = new VNode('div', {}, [b])\n    const vnode3 = new VNode('div', {}, [makeNode('A'), b, makeNode('C')])\n\n    let elm = patch(vnode0, vnode1)\n    expect(elm.textContent).toBe('ABC')\n    elm = patch(vnode1, vnode2)\n    expect(elm.textContent).toBe('B')\n    elm = patch(vnode2, vnode3)\n    expect(elm.textContent).toBe('ABC')\n  })\n\n  it('should handle static vnodes inside ', () => {\n    function makeNode (text) {\n      return new VNode('div', undefined, [\n        new VNode(undefined, undefined, undefined, text)\n      ])\n    }\n    const b = makeNode('B')\n    b.isStatic = true\n    b.key = `__static__1`\n    const vnode1 = new VNode('div', {}, [makeNode('A'), b, makeNode('C')])\n    const vnode2 = new VNode('div', {}, [b])\n    const vnode3 = new VNode('div', {}, [makeNode('A'), b, makeNode('C')])\n\n    let elm = patch(vnode0, vnode1)\n    expect(elm.textContent).toBe('ABC')\n    elm = patch(vnode1, vnode2)\n    expect(elm.textContent).toBe('B')\n    elm = patch(vnode2, vnode3)\n    expect(elm.textContent).toBe('ABC')\n  })\n\n  // #6502\n  it('should not de-opt when both head and tail are changed', () => {\n    const vnode1 = new VNode('div', {}, [\n      createEmptyVNode(),\n      new VNode('div'),\n      createEmptyVNode()\n    ])\n    const vnode2 = new VNode('div', {}, [\n      new VNode('p'),\n      new VNode('div'),\n      new VNode('p')\n    ])\n    let root = patch(null, vnode1)\n    const original = root.childNodes[1]\n\n    root = patch(vnode1, vnode2)\n    const postPatch = root.childNodes[1]\n\n    expect(postPatch).toBe(original)\n  })\n\n  it('should warn with duplicate keys: createChildren', () => {\n    function makeNode (key) {\n      return new VNode('div', { key: key })\n    }\n\n    const vnode = new VNode('p', {}, ['b', 'a', 'c', 'b'].map(makeNode))\n    patch(null, vnode)\n    expect(`Duplicate keys detected: 'b'`).toHaveBeenWarned()\n  })\n\n  it('should warn with duplicate keys: updateChildren', () => {\n    function makeNode (key) {\n      return new VNode('div', { key: key })\n    }\n\n    const vnode2 = new VNode('p', {}, ['b', 'a', 'c', 'b'].map(makeNode))\n    const vnode3 = new VNode('p', {}, ['b', 'x', 'd', 'b'].map(makeNode))\n    patch(vnode0, vnode2)\n    expect(`Duplicate keys detected: 'b'`).toHaveBeenWarned()\n    patch(vnode2, vnode3)\n    expect(`Duplicate keys detected: 'b'`).toHaveBeenWarned()\n  })\n})\n"
  },
  {
    "path": "vue/test/unit/modules/vdom/patch/edge-cases.spec.js",
    "content": "import Vue from 'vue'\n\ndescribe('vdom patch: edge cases', () => {\n  // exposed by #3406\n  // When a static vnode is inside v-for, it's possible for the same vnode\n  // to be used in multiple places, and its element will be replaced. This\n  // causes patch errors when node ops depend on the vnode's element position.\n  it('should handle static vnodes by key', done => {\n    const vm = new Vue({\n      data: {\n        ok: true\n      },\n      template: `\n        <div>\n          <div v-for=\"i in 2\">\n            <div v-if=\"ok\">a</div><div>b</div><div v-if=\"!ok\">c</div><div>d</div>\n          </div>\n        </div>\n      `\n    }).$mount()\n    expect(vm.$el.textContent).toBe('abdabd')\n    vm.ok = false\n    waitForUpdate(() => {\n      expect(vm.$el.textContent).toBe('bcdbcd')\n    }).then(done)\n  })\n\n  // exposed by #7705\n  // methods and function expressions with modifiers should return result instead of undefined\n  // skipped odd children[1,3, ...] because they are rendered as text nodes with undefined value\n  it('should return listener\\'s result for method name and function expression with and w/o modifiers', done => {\n    const dummyEvt = { preventDefault: () => {} }\n    new Vue({\n      template: `\n        <div v-test>\n          <div @click=\"addFive\"></div>\n          <div @click.prevent=\"addFive\"></div>\n          <div @click=\"addFive($event, 5)\"></div>\n          <div @click.prevent=\"addFive($event, 5)\"></div>\n        </div>\n      `,\n      methods: {\n        addFive ($event, toAdd = 0) {\n          return toAdd + 5\n        }\n      },\n      directives: {\n        test: {\n          bind (el, binding, vnode) {\n            waitForUpdate(() => {\n              expect(vnode.children[0].data.on.click()).toBe(5)\n            }).then(() => {\n              expect(vnode.children[2].data.on.click(dummyEvt)).toBe(5)\n            }).then(() => {\n              expect(vnode.children[4].data.on.click()).not.toBeDefined()\n            }).then(() => {\n              expect(vnode.children[6].data.on.click(dummyEvt)).not.toBeDefined()\n            }).then(done)\n          }\n        }\n      }\n    }).$mount()\n  })\n\n  // #3533\n  // a static node is reused in createElm, which changes its elm reference\n  // and is inserted into a different parent.\n  // later when patching the next element a DOM insertion uses it as the\n  // reference node, causing a parent mismatch.\n  it('should handle static node edge case when it\\'s reused AND used as a reference node for insertion', done => {\n    const vm = new Vue({\n      data: {\n        ok: true\n      },\n      template: `\n        <div>\n          <button @click=\"ok = !ok\">toggle</button>\n          <div class=\"b\" v-if=\"ok\">123</div>\n          <div class=\"c\">\n            <div><span/></div><p>{{ 1 }}</p>\n          </div>\n          <div class=\"d\">\n            <label>{{ 2 }}</label>\n          </div>\n          <div class=\"b\" v-if=\"ok\">123</div>\n        </div>\n      `\n    }).$mount()\n\n    expect(vm.$el.querySelector('.c').textContent).toBe('1')\n    expect(vm.$el.querySelector('.d').textContent).toBe('2')\n    vm.ok = false\n    waitForUpdate(() => {\n      expect(vm.$el.querySelector('.c').textContent).toBe('1')\n      expect(vm.$el.querySelector('.d').textContent).toBe('2')\n    }).then(done)\n  })\n\n  it('should handle slot nodes being reused across render', done => {\n    const vm = new Vue({\n      template: `\n        <foo ref=\"foo\">\n          <div>slot</div>\n        </foo>\n      `,\n      components: {\n        foo: {\n          data () {\n            return { ok: true }\n          },\n          render (h) {\n            const children = [\n              this.ok ? h('div', 'toggler ') : null,\n              h('div', [this.$slots.default, h('span', ' 1')]),\n              h('div', [h('label', ' 2')])\n            ]\n            return h('div', children)\n          }\n        }\n      }\n    }).$mount()\n    expect(vm.$el.textContent).toContain('toggler slot 1 2')\n    vm.$refs.foo.ok = false\n    waitForUpdate(() => {\n      expect(vm.$el.textContent).toContain('slot 1 2')\n      vm.$refs.foo.ok = true\n    }).then(() => {\n      expect(vm.$el.textContent).toContain('toggler slot 1 2')\n      vm.$refs.foo.ok = false\n    }).then(() => {\n      expect(vm.$el.textContent).toContain('slot 1 2')\n      vm.$refs.foo.ok = true\n    }).then(done)\n  })\n\n  it('should synchronize vm\\' vnode', done => {\n    const comp = {\n      data: () => ({ swap: true }),\n      render (h) {\n        return this.swap\n          ? h('a', 'atag')\n          : h('span', 'span')\n      }\n    }\n\n    const wrapper = {\n      render: h => h('comp'),\n      components: { comp }\n    }\n\n    const vm = new Vue({\n      render (h) {\n        const children = [\n          h('wrapper'),\n          h('div', 'row')\n        ]\n        if (this.swap) {\n          children.reverse()\n        }\n        return h('div', children)\n      },\n      data: () => ({ swap: false }),\n      components: { wrapper }\n    }).$mount()\n\n    expect(vm.$el.innerHTML).toBe('<a>atag</a><div>row</div>')\n    const wrapperVm = vm.$children[0]\n    const compVm = wrapperVm.$children[0]\n    vm.swap = true\n    waitForUpdate(() => {\n      expect(compVm.$vnode.parent).toBe(wrapperVm.$vnode)\n      expect(vm.$el.innerHTML).toBe('<div>row</div><a>atag</a>')\n      vm.swap = false\n    }).then(() => {\n      expect(compVm.$vnode.parent).toBe(wrapperVm.$vnode)\n      expect(vm.$el.innerHTML).toBe('<a>atag</a><div>row</div>')\n      compVm.swap = false\n    }).then(() => {\n      expect(vm.$el.innerHTML).toBe('<span>span</span><div>row</div>')\n      expect(compVm.$vnode.parent).toBe(wrapperVm.$vnode)\n      vm.swap = true\n    }).then(() => {\n      expect(vm.$el.innerHTML).toBe('<div>row</div><span>span</span>')\n      expect(compVm.$vnode.parent).toBe(wrapperVm.$vnode)\n      vm.swap = true\n    }).then(done)\n  })\n\n  // #4530\n  it('should not reset value when patching between dynamic/static bindings', done => {\n    const vm = new Vue({\n      data: { ok: true },\n      template: `\n        <div>\n          <input type=\"button\" v-if=\"ok\" value=\"a\">\n          <input type=\"button\" :value=\"'b'\">\n        </div>\n      `\n    }).$mount()\n    expect(vm.$el.children[0].value).toBe('a')\n    vm.ok = false\n    waitForUpdate(() => {\n      expect(vm.$el.children[0].value).toBe('b')\n      vm.ok = true\n    }).then(() => {\n      expect(vm.$el.children[0].value).toBe('a')\n    }).then(done)\n  })\n\n  // #6313\n  it('should not replace node when switching between text-like inputs', done => {\n    const vm = new Vue({\n      data: { show: false },\n      template: `\n        <div>\n          <input :type=\"show ? 'text' : 'password'\">\n        </div>\n      `\n    }).$mount()\n    const node = vm.$el.children[0]\n    expect(vm.$el.children[0].type).toBe('password')\n    vm.$el.children[0].value = 'test'\n    vm.show = true\n    waitForUpdate(() => {\n      expect(vm.$el.children[0]).toBe(node)\n      expect(vm.$el.children[0].value).toBe('test')\n      expect(vm.$el.children[0].type).toBe('text')\n      vm.show = false\n    }).then(() => {\n      expect(vm.$el.children[0]).toBe(node)\n      expect(vm.$el.children[0].value).toBe('test')\n      expect(vm.$el.children[0].type).toBe('password')\n    }).then(done)\n  })\n\n  it('should properly patch nested HOC when root element is replaced', done => {\n    const vm = new Vue({\n      template: `<foo class=\"hello\" ref=\"foo\" />`,\n      components: {\n        foo: {\n          template: `<bar ref=\"bar\" />`,\n          components: {\n            bar: {\n              template: `<div v-if=\"ok\"></div><span v-else></span>`,\n              data () {\n                return { ok: true }\n              }\n            }\n          }\n        }\n      }\n    }).$mount()\n\n    expect(vm.$refs.foo.$refs.bar.$el.tagName).toBe('DIV')\n    expect(vm.$refs.foo.$refs.bar.$el.className).toBe(`hello`)\n\n    vm.$refs.foo.$refs.bar.ok = false\n    waitForUpdate(() => {\n      expect(vm.$refs.foo.$refs.bar.$el.tagName).toBe('SPAN')\n      expect(vm.$refs.foo.$refs.bar.$el.className).toBe(`hello`)\n    }).then(done)\n  })\n\n  // #6790\n  it('should not render undefined for empty nested arrays', () => {\n    const vm = new Vue({\n      template: `<div><template v-for=\"i in emptyArr\"></template></div>`,\n      data: { emptyArr: [] }\n    }).$mount()\n    expect(vm.$el.textContent).toBe('')\n  })\n\n  // #6803\n  it('backwards compat with checkbox code generated before 2.4', () => {\n    const spy = jasmine.createSpy()\n    const vm = new Vue({\n      data: {\n        label: 'foobar',\n        name: 'foobar'\n      },\n      computed: {\n        value: {\n          get () {\n            return 1\n          },\n          set: spy\n        }\n      },\n      render (h) {\n        const _vm = this\n        return h('div', {},\n          [h('input', {\n            directives: [{\n              name: 'model',\n              rawName: 'v-model',\n              value: (_vm.value),\n              expression: 'value'\n            }],\n            attrs: {\n              'type': 'radio',\n              'name': _vm.name\n            },\n            domProps: {\n              'value': _vm.label,\n              'checked': _vm._q(_vm.value, _vm.label)\n            },\n            on: {\n              '__c': function ($event) {\n                _vm.value = _vm.label\n              }\n            }\n          })])\n      }\n    }).$mount()\n\n    document.body.appendChild(vm.$el)\n    vm.$el.children[0].click()\n    expect(spy).toHaveBeenCalled()\n  })\n\n  // #7041\n  it('transition children with only deep bindings should be patched on update', done => {\n    const vm = new Vue({\n      template: `\n      <div>\n        <transition>\n          <div :style=\"style\"></div>\n        </transition>\n      </div>\n      `,\n      data: () => ({\n        style: { color: 'red' }\n      })\n    }).$mount()\n    expect(vm.$el.children[0].style.color).toBe('red')\n    vm.style.color = 'green'\n    waitForUpdate(() => {\n      expect(vm.$el.children[0].style.color).toBe('green')\n    }).then(done)\n  })\n\n  // #7294\n  it('should cleanup component inline events on patch when no events are present', done => {\n    const log = jasmine.createSpy()\n    const vm = new Vue({\n      data: { ok: true },\n      template: `\n        <div>\n          <foo v-if=\"ok\" @custom=\"log\"/>\n          <foo v-else/>\n        </div>\n      `,\n      components: {\n        foo: {\n          render () {}\n        }\n      },\n      methods: { log }\n    }).$mount()\n\n    vm.ok = false\n    waitForUpdate(() => {\n      vm.$children[0].$emit('custom')\n      expect(log).not.toHaveBeenCalled()\n    }).then(done)\n  })\n\n  // #6864\n  it('should not special-case boolean attributes for custom elements', () => {\n    Vue.config.ignoredElements = [/^custom-/]\n    const vm = new Vue({\n      template: `<div><custom-foo selected=\"1\"/></div>`\n    }).$mount()\n    expect(vm.$el.querySelector('custom-foo').getAttribute('selected')).toBe('1')\n    Vue.config.ignoredElements = []\n  })\n\n  // #7805\n  it('should not cause duplicate init when components share data object', () => {\n    const Base = {\n      render (h) {\n        return h('div', this.$options.name)\n      }\n    }\n\n    const Foo = {\n      name: 'Foo',\n      extends: Base\n    }\n\n    const Bar = {\n      name: 'Bar',\n      extends: Base\n    }\n\n    // sometimes we do need to tap into these internal hooks (e.g. in vue-router)\n    // so make sure it does work\n    const inlineHookSpy = jasmine.createSpy('inlineInit')\n\n    const vm = new Vue({\n      render (h) {\n        const data = { staticClass: 'text-red', hook: {\n          init: inlineHookSpy\n        }}\n\n        return h('div', [\n          h(Foo, data),\n          h(Bar, data)\n        ])\n      }\n    }).$mount()\n\n    expect(vm.$el.textContent).toBe('FooBar')\n    expect(inlineHookSpy.calls.count()).toBe(2)\n  })\n})\n"
  },
  {
    "path": "vue/test/unit/modules/vdom/patch/element.spec.js",
    "content": "import Vue from 'vue'\nimport { patch } from 'web/runtime/patch'\nimport VNode from 'core/vdom/vnode'\n\ndescribe('vdom patch: element', () => {\n  it('should create an element', () => {\n    const vnode = new VNode('p', { attrs: { id: '1' }}, [createTextVNode('hello world')])\n    const elm = patch(null, vnode)\n    expect(elm.tagName).toBe('P')\n    expect(elm.outerHTML).toBe('<p id=\"1\">hello world</p>')\n  })\n\n  it('should create an element which having the namespace', () => {\n    const vnode = new VNode('svg', {})\n    vnode.ns = 'svg'\n    const elm = patch(null, vnode)\n    expect(elm.namespaceURI).toBe('http://www.w3.org/2000/svg')\n  })\n\n  const el = document.createElement('unknown')\n  // Android Browser <= 4.2 doesn't use correct class name,\n  // but it doesn't matter because no one's gonna use it as their primary\n  // development browser.\n  if (/HTMLUnknownElement/.test(el.toString())) {\n    it('should warn unknown element', () => {\n      const vnode = new VNode('unknown')\n      patch(null, vnode)\n      expect(`Unknown custom element: <unknown>`).toHaveBeenWarned()\n    })\n  }\n\n  it('should warn unknown element with hyphen', () => {\n    const vnode = new VNode('unknown-foo')\n    patch(null, vnode)\n    expect(`Unknown custom element: <unknown-foo>`).toHaveBeenWarned()\n  })\n\n  it('should create an elements which having text content', () => {\n    const vnode = new VNode('div', {}, [createTextVNode('hello world')])\n    const elm = patch(null, vnode)\n    expect(elm.innerHTML).toBe('hello world')\n  })\n\n  it('should create create an elements which having span and text content', () => {\n    const vnode = new VNode('div', {}, [\n      new VNode('span'),\n      createTextVNode('hello world')\n    ])\n    const elm = patch(null, vnode)\n    expect(elm.childNodes[0].tagName).toBe('SPAN')\n    expect(elm.childNodes[1].textContent).toBe('hello world')\n  })\n\n  it('should create element with scope attribute', () => {\n    const vnode = new VNode('div')\n    vnode.context = new Vue({ _scopeId: 'foo' })\n    const elm = patch(null, vnode)\n    expect(elm.hasAttribute('foo')).toBe(true)\n  })\n})\n"
  },
  {
    "path": "vue/test/unit/modules/vdom/patch/hooks.spec.js",
    "content": "import { patch } from 'web/runtime/patch'\nimport { createPatchFunction } from 'core/vdom/patch'\nimport baseModules from 'core/vdom/modules/index'\nimport * as nodeOps from 'web/runtime/node-ops'\nimport platformModules from 'web/runtime/modules/index'\nimport VNode from 'core/vdom/vnode'\n\nconst modules = baseModules.concat(platformModules)\n\ndescribe('vdom patch: hooks', () => {\n  let vnode0\n  beforeEach(() => {\n    vnode0 = new VNode('p', { attrs: { id: '1' }}, [createTextVNode('hello world')])\n    patch(null, vnode0)\n  })\n\n  it('should call `insert` listener after both parents, siblings and children have been inserted', () => {\n    const result = []\n    function insert (vnode) {\n      expect(vnode.elm.children.length).toBe(2)\n      expect(vnode.elm.parentNode.children.length).toBe(3)\n      result.push(vnode)\n    }\n    const vnode1 = new VNode('div', {}, [\n      new VNode('span', {}, undefined, 'first sibling'),\n      new VNode('div', { hook: { insert }}, [\n        new VNode('span', {}, undefined, 'child 1'),\n        new VNode('span', {}, undefined, 'child 2')\n      ]),\n      new VNode('span', {}, undefined, 'can touch me')\n    ])\n    patch(vnode0, vnode1)\n    expect(result.length).toBe(1)\n  })\n\n  it('should call `prepatch` listener', () => {\n    const result = []\n    function prepatch (oldVnode, newVnode) {\n      expect(oldVnode).toEqual(vnode1.children[1])\n      expect(newVnode).toEqual(vnode2.children[1])\n      result.push(newVnode)\n    }\n    const vnode1 = new VNode('div', {}, [\n      new VNode('span', {}, undefined, 'first sibling'),\n      new VNode('div', { hook: { prepatch }}, [\n        new VNode('span', {}, undefined, 'child 1'),\n        new VNode('span', {}, undefined, 'child 2')\n      ])\n    ])\n    const vnode2 = new VNode('div', {}, [\n      new VNode('span', {}, undefined, 'first sibling'),\n      new VNode('div', { hook: { prepatch }}, [\n        new VNode('span', {}, undefined, 'child 1'),\n        new VNode('span', {}, undefined, 'child 2')\n      ])\n    ])\n    patch(vnode0, vnode1)\n    patch(vnode1, vnode2)\n    expect(result.length).toBe(1)\n  })\n\n  it('should call `postpatch` after `prepatch` listener', () => {\n    const pre = []\n    const post = []\n    function prepatch (oldVnode, newVnode) {\n      pre.push(pre)\n    }\n    function postpatch (oldVnode, newVnode) {\n      expect(pre.length).toBe(post.length + 1)\n      post.push(post)\n    }\n    const vnode1 = new VNode('div', {}, [\n      new VNode('span', {}, undefined, 'first sibling'),\n      new VNode('div', { hook: { prepatch, postpatch }}, [\n        new VNode('span', {}, undefined, 'child 1'),\n        new VNode('span', {}, undefined, 'child 2')\n      ])\n    ])\n    const vnode2 = new VNode('div', {}, [\n      new VNode('span', {}, undefined, 'first sibling'),\n      new VNode('div', { hook: { prepatch, postpatch }}, [\n        new VNode('span', {}, undefined, 'child 1'),\n        new VNode('span', {}, undefined, 'child 2')\n      ])\n    ])\n    patch(vnode0, vnode1)\n    patch(vnode1, vnode2)\n    expect(pre.length).toBe(1)\n    expect(post.length).toBe(1)\n  })\n\n  it('should call `update` listener', () => {\n    const result1 = []\n    const result2 = []\n    function cb (result, oldVnode, newVnode) {\n      if (result.length > 1) {\n        expect(result[result.length - 1]).toEqual(oldVnode)\n      }\n      result.push(newVnode)\n    }\n    const vnode1 = new VNode('div', {}, [\n      new VNode('span', {}, undefined, 'first sibling'),\n      new VNode('div', { hook: { update: cb.bind(null, result1) }}, [\n        new VNode('span', {}, undefined, 'child 1'),\n        new VNode('span', { hook: { update: cb.bind(null, result2) }}, undefined, 'child 2')\n      ])\n    ])\n    const vnode2 = new VNode('div', {}, [\n      new VNode('span', {}, undefined, 'first sibling'),\n      new VNode('div', { hook: { update: cb.bind(null, result1) }}, [\n        new VNode('span', {}, undefined, 'child 1'),\n        new VNode('span', { hook: { update: cb.bind(null, result2) }}, undefined, 'child 2')\n      ])\n    ])\n    patch(vnode0, vnode1)\n    patch(vnode1, vnode2)\n    expect(result1.length).toBe(1)\n    expect(result2.length).toBe(1)\n  })\n\n  it('should call `remove` listener', () => {\n    const result = []\n    function remove (vnode, rm) {\n      const parent = vnode.elm.parentNode\n      expect(vnode.elm.children.length).toBe(2)\n      expect(vnode.elm.children.length).toBe(2)\n      result.push(vnode)\n      rm()\n      expect(parent.children.length).toBe(1)\n    }\n    const vnode1 = new VNode('div', {}, [\n      new VNode('span', {}, undefined, 'first sibling'),\n      new VNode('div', { hook: { remove }}, [\n        new VNode('span', {}, undefined, 'child 1'),\n        new VNode('span', {}, undefined, 'child 2')\n      ])\n    ])\n    const vnode2 = new VNode('div', {}, [\n      new VNode('span', {}, undefined, 'first sibling')\n    ])\n    patch(vnode0, vnode1)\n    patch(vnode1, vnode2)\n    expect(result.length).toBe(1)\n  })\n\n  it('should call `init` and `prepatch` listeners on root', () => {\n    let count = 0\n    function init (vnode) { count++ }\n    function prepatch (oldVnode, newVnode) { count++ }\n    const vnode1 = new VNode('div', { hook: { init, prepatch }})\n    patch(vnode0, vnode1)\n    expect(count).toBe(1)\n    const vnode2 = new VNode('span', { hook: { init, prepatch }})\n    patch(vnode1, vnode2)\n    expect(count).toBe(2)\n  })\n\n  it('should remove element when all remove listeners are done', () => {\n    let rm1, rm2, rm3\n    const patch1 = createPatchFunction({\n      nodeOps,\n      modules: modules.concat([\n        { remove (_, rm) { rm1 = rm } },\n        { remove (_, rm) { rm2 = rm } }\n      ])\n    })\n    const vnode1 = new VNode('div', {}, [\n      new VNode('a', { hook: { remove (_, rm) { rm3 = rm } }})\n    ])\n    const vnode2 = new VNode('div', {}, [])\n    let elm = patch1(vnode0, vnode1)\n    expect(elm.children.length).toBe(1)\n    elm = patch1(vnode1, vnode2)\n    expect(elm.children.length).toBe(1)\n    rm1()\n    expect(elm.children.length).toBe(1)\n    rm3()\n    expect(elm.children.length).toBe(1)\n    rm2()\n    expect(elm.children.length).toBe(0)\n  })\n\n  it('should invoke the remove hook on replaced root', () => {\n    const result = []\n    const parent = nodeOps.createElement('div')\n    vnode0 = nodeOps.createElement('div')\n    parent.appendChild(vnode0)\n    function remove (vnode, rm) {\n      result.push(vnode)\n      rm()\n    }\n    const vnode1 = new VNode('div', { hook: { remove }}, [\n      new VNode('b', {}, undefined, 'child 1'),\n      new VNode('i', {}, undefined, 'child 2')\n    ])\n    const vnode2 = new VNode('span', {}, [\n      new VNode('b', {}, undefined, 'child 1'),\n      new VNode('i', {}, undefined, 'child 2')\n    ])\n    patch(vnode0, vnode1)\n    patch(vnode1, vnode2)\n    expect(result.length).toBe(1)\n  })\n\n  it('should invoke global `destroy` hook for all removed children', () => {\n    const result = []\n    function destroy (vnode) { result.push(vnode) }\n    const vnode1 = new VNode('div', {}, [\n      new VNode('span', {}, undefined, 'first sibling'),\n      new VNode('div', {}, [\n        new VNode('span', { hook: { destroy }}, undefined, 'child 1'),\n        new VNode('span', {}, undefined, 'child 2')\n      ])\n    ])\n    const vnode2 = new VNode('div')\n    patch(vnode0, vnode1)\n    patch(vnode1, vnode2)\n    expect(result.length).toBe(1)\n  })\n\n  it('should handle text vnodes with `undefined` `data` property', () => {\n    const vnode1 = new VNode('div', {}, [createTextVNode(' ')])\n    const vnode2 = new VNode('div', {}, [])\n    patch(vnode0, vnode1)\n    patch(vnode1, vnode2)\n  })\n\n  it('should invoke `destroy` module hook for all removed children', () => {\n    let created = 0\n    let destroyed = 0\n    const patch1 = createPatchFunction({\n      nodeOps,\n      modules: modules.concat([\n        { create () { created++ } },\n        { destroy () { destroyed++ } }\n      ])\n    })\n    const vnode1 = new VNode('div', {}, [\n      new VNode('span', {}, undefined, 'first sibling'),\n      new VNode('div', {}, [\n        new VNode('span', {}, undefined, 'child 1'),\n        new VNode('span', {}, undefined, 'child 2')\n      ])\n    ])\n    const vnode2 = new VNode('div', {})\n    patch1(vnode0, vnode1)\n    expect(destroyed).toBe(1) // should invoke for replaced root nodes too\n    patch1(vnode1, vnode2)\n    expect(created).toBe(5)\n    expect(destroyed).toBe(5)\n  })\n\n  it('should not invoke `create` and `remove` module hook for text nodes', () => {\n    let created = 0\n    let removed = 0\n    const patch1 = createPatchFunction({\n      nodeOps,\n      modules: modules.concat([\n        { create () { created++ } },\n        { remove () { removed++ } }\n      ])\n    })\n    const vnode1 = new VNode('div', {}, [\n      new VNode('span', {}, undefined, 'first child'),\n      createTextVNode(''),\n      new VNode('span', {}, undefined, 'third child')\n    ])\n    const vnode2 = new VNode('div', {})\n    patch1(vnode0, vnode1)\n    patch1(vnode1, vnode2)\n    expect(created).toBe(3)\n    expect(removed).toBe(2)\n  })\n\n  it('should not invoke `destroy` module hook for text nodes', () => {\n    let created = 0\n    let destroyed = 0\n    const patch1 = createPatchFunction({\n      nodeOps,\n      modules: modules.concat([\n        { create () { created++ } },\n        { destroy () { destroyed++ } }\n      ])\n    })\n    const vnode1 = new VNode('div', {}, [\n      new VNode('span', {}, undefined, 'first sibling'),\n      new VNode('div', {}, [\n        new VNode('span', {}, undefined, 'child 1'),\n        new VNode('span', {}, [\n          createTextVNode('text1'),\n          createTextVNode('text2')\n        ])\n      ])\n    ])\n    const vnode2 = new VNode('div', {})\n    patch1(vnode0, vnode1)\n    expect(destroyed).toBe(1) // should invoke for replaced root nodes too\n    patch1(vnode1, vnode2)\n    expect(created).toBe(5)\n    expect(destroyed).toBe(5)\n  })\n\n  it('should call `create` listener before inserted into parent but after children', () => {\n    const result = []\n    function create (empty, vnode) {\n      expect(vnode.elm.children.length).toBe(2)\n      expect(vnode.elm.parentNode).toBe(null)\n      result.push(vnode)\n    }\n    const vnode1 = new VNode('div', {}, [\n      new VNode('span', {}, undefined, 'first sibling'),\n      new VNode('div', { hook: { create }}, [\n        new VNode('span', {}, undefined, 'child 1'),\n        new VNode('span', {}, undefined, 'child 2')\n      ]),\n      new VNode('span', {}, undefined, 'can\\'t touch me')\n    ])\n    patch(vnode0, vnode1)\n    expect(result.length).toBe(1)\n  })\n})\n"
  },
  {
    "path": "vue/test/unit/modules/vdom/patch/hydration.spec.js",
    "content": "import Vue from 'vue'\nimport VNode from 'core/vdom/vnode'\nimport { patch } from 'web/runtime/patch'\nimport { SSR_ATTR } from 'shared/constants'\n\nfunction createMockSSRDOM (innerHTML) {\n  const dom = document.createElement('div')\n  dom.setAttribute(SSR_ATTR, 'true')\n  dom.innerHTML = innerHTML\n  return dom\n}\n\ndescribe('vdom patch: hydration', () => {\n  let vnode0\n  beforeEach(() => {\n    vnode0 = new VNode('p', { attrs: { id: '1' }}, [createTextVNode('hello world')])\n    patch(null, vnode0)\n  })\n\n  it('should hydrate elements when server-rendered DOM tree is same as virtual DOM tree', () => {\n    const result = []\n    function init (vnode) { result.push(vnode) }\n    function createServerRenderedDOM () {\n      const root = document.createElement('div')\n      root.setAttribute(SSR_ATTR, 'true')\n      const span = document.createElement('span')\n      root.appendChild(span)\n      const div = document.createElement('div')\n      const child1 = document.createElement('span')\n      const child2 = document.createElement('span')\n      child1.textContent = 'hi'\n      child2.textContent = 'ho'\n      div.appendChild(child1)\n      div.appendChild(child2)\n      root.appendChild(div)\n      return root\n    }\n    const node0 = createServerRenderedDOM()\n    const vnode1 = new VNode('div', {}, [\n      new VNode('span', {}),\n      new VNode('div', { hook: { init }}, [\n        new VNode('span', {}, [new VNode(undefined, undefined, undefined, 'hi')]),\n        new VNode('span', {}, [new VNode(undefined, undefined, undefined, 'ho')])\n      ])\n    ])\n    patch(node0, vnode1)\n    expect(result.length).toBe(1)\n\n    function traverseAndAssert (vnode, element) {\n      expect(vnode.elm).toBe(element)\n      if (vnode.children) {\n        vnode.children.forEach((node, i) => {\n          traverseAndAssert(node, element.childNodes[i])\n        })\n      }\n    }\n    // ensure vnodes are correctly associated with actual DOM\n    traverseAndAssert(vnode1, node0)\n\n    // check update\n    const vnode2 = new VNode('div', { attrs: { id: 'foo' }}, [\n      new VNode('span', { attrs: { id: 'bar' }}),\n      new VNode('div', { hook: { init }}, [\n        new VNode('span', {}),\n        new VNode('span', {})\n      ])\n    ])\n    patch(vnode1, vnode2)\n    expect(node0.id).toBe('foo')\n    expect(node0.children[0].id).toBe('bar')\n  })\n\n  it('should warn message that virtual DOM tree is not matching when hydrate element', () => {\n    function createServerRenderedDOM () {\n      const root = document.createElement('div')\n      root.setAttribute(SSR_ATTR, 'true')\n      const span = document.createElement('span')\n      root.appendChild(span)\n      const div = document.createElement('div')\n      const child1 = document.createElement('span')\n      div.appendChild(child1)\n      root.appendChild(div)\n      return root\n    }\n    const node0 = createServerRenderedDOM()\n    const vnode1 = new VNode('div', {}, [\n      new VNode('span', {}),\n      new VNode('div', {}, [\n        new VNode('span', {}),\n        new VNode('span', {})\n      ])\n    ])\n    patch(node0, vnode1)\n    expect('The client-side rendered virtual DOM tree is not matching').toHaveBeenWarned()\n  })\n\n  // component hydration is better off with a more e2e approach\n  it('should hydrate components when server-rendered DOM tree is same as virtual DOM tree', done => {\n    const dom = createMockSSRDOM('<span>foo</span><div class=\"b a\"><span>foo qux</span></div><!---->')\n    const originalNode1 = dom.children[0]\n    const originalNode2 = dom.children[1]\n\n    const vm = new Vue({\n      template: '<div><span>{{msg}}</span><test class=\"a\" :msg=\"msg\"></test><p v-if=\"ok\"></p></div>',\n      data: {\n        msg: 'foo',\n        ok: false\n      },\n      components: {\n        test: {\n          props: ['msg'],\n          data () {\n            return { a: 'qux' }\n          },\n          template: '<div class=\"b\"><span>{{msg}} {{a}}</span></div>'\n        }\n      }\n    })\n\n    expect(() => { vm.$mount(dom) }).not.toThrow()\n    expect('not matching server-rendered content').not.toHaveBeenWarned()\n    expect(vm.$el).toBe(dom)\n    expect(vm.$children[0].$el).toBe(originalNode2)\n    expect(vm.$el.children[0]).toBe(originalNode1)\n    expect(vm.$el.children[1]).toBe(originalNode2)\n    vm.msg = 'bar'\n    waitForUpdate(() => {\n      expect(vm.$el.innerHTML).toBe('<span>bar</span><div class=\"b a\"><span>bar qux</span></div><!---->')\n      vm.$children[0].a = 'ququx'\n    }).then(() => {\n      expect(vm.$el.innerHTML).toBe('<span>bar</span><div class=\"b a\"><span>bar ququx</span></div><!---->')\n      vm.ok = true\n    }).then(() => {\n      expect(vm.$el.innerHTML).toBe('<span>bar</span><div class=\"b a\"><span>bar ququx</span></div><p></p>')\n    }).then(done)\n  })\n\n  it('should warn failed hydration for non-matching DOM in child component', () => {\n    const dom = createMockSSRDOM('<div><span></span></div>')\n\n    new Vue({\n      template: '<div><test></test></div>',\n      components: {\n        test: {\n          template: '<div><a></a></div>'\n        }\n      }\n    }).$mount(dom)\n\n    expect('not matching server-rendered content').toHaveBeenWarned()\n  })\n\n  it('should warn failed hydration when component is not properly registered', () => {\n    const dom = createMockSSRDOM('<div><foo></foo></div>')\n\n    new Vue({\n      template: '<div><foo></foo></div>'\n    }).$mount(dom)\n\n    expect('not matching server-rendered content').toHaveBeenWarned()\n    expect('Unknown custom element: <foo>').toHaveBeenWarned()\n  })\n\n  it('should overwrite textNodes in the correct position but with mismatching text without warning', () => {\n    const dom = createMockSSRDOM('<div><span>foo</span></div>')\n\n    new Vue({\n      template: '<div><test></test></div>',\n      components: {\n        test: {\n          data () {\n            return { a: 'qux' }\n          },\n          template: '<div><span>{{a}}</span></div>'\n        }\n      }\n    }).$mount(dom)\n\n    expect('not matching server-rendered content').not.toHaveBeenWarned()\n    expect(dom.querySelector('span').textContent).toBe('qux')\n  })\n\n  it('should pick up elements with no children and populate without warning', done => {\n    const dom = createMockSSRDOM('<div><span></span></div>')\n    const span = dom.querySelector('span')\n\n    const vm = new Vue({\n      template: '<div><test></test></div>',\n      components: {\n        test: {\n          data () {\n            return { a: 'qux' }\n          },\n          template: '<div><span>{{a}}</span></div>'\n        }\n      }\n    }).$mount(dom)\n\n    expect('not matching server-rendered content').not.toHaveBeenWarned()\n    expect(span).toBe(vm.$el.querySelector('span'))\n    expect(vm.$el.innerHTML).toBe('<div><span>qux</span></div>')\n\n    vm.$children[0].a = 'foo'\n    waitForUpdate(() => {\n      expect(vm.$el.innerHTML).toBe('<div><span>foo</span></div>')\n    }).then(done)\n  })\n\n  it('should hydrate async component', done => {\n    const dom = createMockSSRDOM('<span>foo</span>')\n    const span = dom.querySelector('span')\n\n    const Foo = resolve => setTimeout(() => {\n      resolve({\n        data: () => ({ msg: 'foo' }),\n        template: `<span>{{ msg }}</span>`\n      })\n    }, 0)\n\n    const vm = new Vue({\n      template: '<div><foo ref=\"foo\" /></div>',\n      components: { Foo }\n    }).$mount(dom)\n\n    expect('not matching server-rendered content').not.toHaveBeenWarned()\n    expect(dom.innerHTML).toBe('<span>foo</span>')\n    expect(vm.$refs.foo).toBeUndefined()\n\n    setTimeout(() => {\n      expect(dom.innerHTML).toBe('<span>foo</span>')\n      expect(vm.$refs.foo).not.toBeUndefined()\n      vm.$refs.foo.msg = 'bar'\n      waitForUpdate(() => {\n        expect(dom.innerHTML).toBe('<span>bar</span>')\n        expect(dom.querySelector('span')).toBe(span)\n      }).then(done)\n    }, 50)\n  })\n\n  it('should hydrate async component without showing loading', done => {\n    const dom = createMockSSRDOM('<span>foo</span>')\n    const span = dom.querySelector('span')\n\n    const Foo = () => ({\n      component: new Promise(resolve => {\n        setTimeout(() => {\n          resolve({\n            data: () => ({ msg: 'foo' }),\n            template: `<span>{{ msg }}</span>`\n          })\n        }, 10)\n      }),\n      delay: 1,\n      loading: {\n        render: h => h('span', 'loading')\n      }\n    })\n\n    const vm = new Vue({\n      template: '<div><foo ref=\"foo\" /></div>',\n      components: { Foo }\n    }).$mount(dom)\n\n    expect('not matching server-rendered content').not.toHaveBeenWarned()\n    expect(dom.innerHTML).toBe('<span>foo</span>')\n    expect(vm.$refs.foo).toBeUndefined()\n\n    setTimeout(() => {\n      expect(dom.innerHTML).toBe('<span>foo</span>')\n    }, 2)\n\n    setTimeout(() => {\n      expect(dom.innerHTML).toBe('<span>foo</span>')\n      expect(vm.$refs.foo).not.toBeUndefined()\n      vm.$refs.foo.msg = 'bar'\n      waitForUpdate(() => {\n        expect(dom.innerHTML).toBe('<span>bar</span>')\n        expect(dom.querySelector('span')).toBe(span)\n      }).then(done)\n    }, 50)\n  })\n\n  it('should hydrate async component by replacing DOM if error occurs', done => {\n    const dom = createMockSSRDOM('<span>foo</span>')\n\n    const Foo = () => ({\n      component: new Promise((resolve, reject) => {\n        setTimeout(() => {\n          reject('something went wrong')\n        }, 10)\n      }),\n      error: {\n        render: h => h('span', 'error')\n      }\n    })\n\n    new Vue({\n      template: '<div><foo ref=\"foo\" /></div>',\n      components: { Foo }\n    }).$mount(dom)\n\n    expect('not matching server-rendered content').not.toHaveBeenWarned()\n    expect(dom.innerHTML).toBe('<span>foo</span>')\n\n    setTimeout(() => {\n      expect('Failed to resolve async').toHaveBeenWarned()\n      expect(dom.innerHTML).toBe('<span>error</span>')\n      done()\n    }, 50)\n  })\n\n  it('should hydrate v-html with children', () => {\n    const dom = createMockSSRDOM('<span>foo</span>')\n\n    new Vue({\n      data: {\n        html: `<span>foo</span>`\n      },\n      template: `<div v-html=\"html\">hello</div>`\n    }).$mount(dom)\n\n    expect('not matching server-rendered content').not.toHaveBeenWarned()\n  })\n\n  it('should warn mismatching v-html', () => {\n    const dom = createMockSSRDOM('<span>bar</span>')\n\n    new Vue({\n      data: {\n        html: `<span>foo</span>`\n      },\n      template: `<div v-html=\"html\">hello</div>`\n    }).$mount(dom)\n\n    expect('not matching server-rendered content').toHaveBeenWarned()\n  })\n\n  it('should hydrate with adjacent text nodes from array children (e.g. slots)', () => {\n    const dom = createMockSSRDOM('<div>foo</div> hello')\n\n    new Vue({\n      template: `<test>hello</test>`,\n      components: {\n        test: {\n          template: `\n            <div>\n              <div>foo</div>\n              <slot/>\n            </div>\n          `\n        }\n      }\n    }).$mount(dom)\n    expect('not matching server-rendered content').not.toHaveBeenWarned()\n  })\n\n  // #7063\n  it('should properly initialize dynamic style bindings for future updates', done => {\n    const dom = createMockSSRDOM('<div style=\"padding-left:0px\"></div>')\n\n    const vm = new Vue({\n      data: {\n        style: { paddingLeft: '0px' }\n      },\n      template: `<div><div :style=\"style\"></div></div>`\n    }).$mount(dom)\n\n    // should update\n    vm.style.paddingLeft = '100px'\n    waitForUpdate(() => {\n      expect(dom.children[0].style.paddingLeft).toBe('100px')\n    }).then(done)\n  })\n\n  it('should properly initialize dynamic class bindings for future updates', done => {\n    const dom = createMockSSRDOM('<div class=\"foo bar\"></div>')\n\n    const vm = new Vue({\n      data: {\n        cls: [{ foo: true }, 'bar']\n      },\n      template: `<div><div :class=\"cls\"></div></div>`\n    }).$mount(dom)\n\n    // should update\n    vm.cls[0].foo = false\n    waitForUpdate(() => {\n      expect(dom.children[0].className).toBe('bar')\n    }).then(done)\n  })\n})\n"
  },
  {
    "path": "vue/test/weex/.eslintrc",
    "content": "{\n  \"env\": {\n    \"jasmine\": true\n  },\n  \"plugins\": [\"jasmine\"],\n  \"rules\": {\n    \"jasmine/no-focused-tests\": 2\n  }\n}\n"
  },
  {
    "path": "vue/test/weex/cases/cases.spec.js",
    "content": "import {\n  readFile,\n  readObject,\n  compileVue,\n  compileWithDeps,\n  createInstance,\n  addTaskHook,\n  resetTaskHook,\n  getRoot,\n  getEvents,\n  fireEvent\n} from '../helpers'\n\n// Create one-off render test case\nfunction createRenderTestCase (name) {\n  const source = readFile(`${name}.vue`)\n  const target = readObject(`${name}.vdom.js`)\n  return done => {\n    compileVue(source).then(code => {\n      const id = String(Date.now() * Math.random())\n      const instance = createInstance(id, code)\n      setTimeout(() => {\n        expect(getRoot(instance)).toEqual(target)\n        instance.$destroy()\n        done()\n      }, 50)\n    }).catch(done.fail)\n  }\n}\n\n// Create event test case, will trigger the first bind event\nfunction createEventTestCase (name) {\n  const source = readFile(`${name}.vue`)\n  const before = readObject(`${name}.before.vdom.js`)\n  const after = readObject(`${name}.after.vdom.js`)\n  return done => {\n    compileVue(source).then(code => {\n      const id = String(Date.now() * Math.random())\n      const instance = createInstance(id, code)\n      setTimeout(() => {\n        expect(getRoot(instance)).toEqual(before)\n        const event = getEvents(instance)[0]\n        fireEvent(instance, event.ref, event.type, {})\n        setTimeout(() => {\n          expect(getRoot(instance)).toEqual(after)\n          instance.$destroy()\n          done()\n        }, 50)\n      }, 50)\n    }).catch(done.fail)\n  }\n}\n\ndescribe('Usage', () => {\n  describe('render', () => {\n    it('sample', createRenderTestCase('render/sample'))\n  })\n\n  describe('event', () => {\n    it('click', createEventTestCase('event/click'))\n  })\n\n  describe('recycle-list', () => {\n    it('text node', createRenderTestCase('recycle-list/text-node'))\n    it('attributes', createRenderTestCase('recycle-list/attrs'))\n    // it('class name', createRenderTestCase('recycle-list/classname'))\n    it('inline style', createRenderTestCase('recycle-list/inline-style'))\n    it('v-if', createRenderTestCase('recycle-list/v-if'))\n    it('v-else', createRenderTestCase('recycle-list/v-else'))\n    it('v-else-if', createRenderTestCase('recycle-list/v-else-if'))\n    it('v-for', createRenderTestCase('recycle-list/v-for'))\n    it('v-for-iterator', createRenderTestCase('recycle-list/v-for-iterator'))\n    it('v-on', createRenderTestCase('recycle-list/v-on'))\n    it('v-on-inline', createRenderTestCase('recycle-list/v-on-inline'))\n    it('v-once', createRenderTestCase('recycle-list/v-once'))\n\n    it('stateless component', done => {\n      compileWithDeps('recycle-list/components/stateless.vue', [{\n        name: 'banner',\n        path: 'recycle-list/components/banner.vue'\n      }]).then(code => {\n        const id = String(Date.now() * Math.random())\n        const instance = createInstance(id, code)\n        setTimeout(() => {\n          const target = readObject('recycle-list/components/stateless.vdom.js')\n          expect(getRoot(instance)).toEqual(target)\n          instance.$destroy()\n          done()\n        }, 50)\n      }).catch(done.fail)\n    })\n\n    it('stateless component with props', done => {\n      compileWithDeps('recycle-list/components/stateless-with-props.vue', [{\n        name: 'poster',\n        path: 'recycle-list/components/poster.vue'\n      }]).then(code => {\n        const id = String(Date.now() * Math.random())\n        const instance = createInstance(id, code)\n        setTimeout(() => {\n          const target = readObject('recycle-list/components/stateless-with-props.vdom.js')\n          expect(getRoot(instance)).toEqual(target)\n          instance.$destroy()\n          done()\n        }, 50)\n      }).catch(done.fail)\n    })\n\n    it('multi stateless components', done => {\n      compileWithDeps('recycle-list/components/stateless-multi-components.vue', [{\n        name: 'banner',\n        path: 'recycle-list/components/banner.vue'\n      }, {\n        name: 'poster',\n        path: 'recycle-list/components/poster.vue'\n      }, {\n        name: 'footer',\n        path: 'recycle-list/components/footer.vue'\n      }]).then(code => {\n        const id = String(Date.now() * Math.random())\n        const instance = createInstance(id, code)\n        setTimeout(() => {\n          const target = readObject('recycle-list/components/stateless-multi-components.vdom.js')\n          expect(getRoot(instance)).toEqual(target)\n          instance.$destroy()\n          done()\n        }, 50)\n      }).catch(done.fail)\n    })\n\n    it('stateful component', done => {\n      const tasks = []\n      addTaskHook((_, task) => tasks.push(task))\n      compileWithDeps('recycle-list/components/stateful.vue', [{\n        name: 'counter',\n        path: 'recycle-list/components/counter.vue'\n      }]).then(code => {\n        const id = String(Date.now() * Math.random())\n        const instance = createInstance(id, code)\n        // expect(tasks.length).toEqual(3)\n        setTimeout(() => {\n          // check the render results\n          const target = readObject('recycle-list/components/stateful.vdom.js')\n          expect(getRoot(instance)).toEqual(target)\n          tasks.length = 0\n\n          // // trigger component hooks\n          // instance.$triggerHook(\n          //   2, // cid of the virtual component template\n          //   'create', // lifecycle hook name\n\n          //   // arguments for the callback\n          //   [\n          //     'x-1', // componentId of the virtual component\n          //     { start: 3 } // propsData of the virtual component\n          //   ]\n          // )\n          // instance.$triggerHook(2, 'create', ['x-2', { start: 11 }])\n\n          // // the state (_data) of the virtual component should be sent to native\n          // expect(tasks.length).toEqual(2)\n          // expect(tasks[0].method).toEqual('updateComponentData')\n          // expect(tasks[0].args).toEqual(['x-1', { count: 6 }, ''])\n          // expect(tasks[1].method).toEqual('updateComponentData')\n          // expect(tasks[1].args).toEqual(['x-2', { count: 22 }, ''])\n\n          // instance.$triggerHook('x-1', 'attach')\n          // instance.$triggerHook('x-2', 'attach')\n          // tasks.length = 0\n\n          // // simulate a click event\n          // // the event will be caught by the virtual component template and\n          // // should be dispatched to virtual component according to the componentId\n          // const event = getEvents(instance)[0]\n          // fireEvent(instance, event.ref, 'click', { componentId: 'x-1' })\n          setTimeout(() => {\n            // expect(tasks.length).toEqual(1)\n            // expect(tasks[0].method).toEqual('updateComponentData')\n            // expect(tasks[0].args).toEqual([{ count: 7 }])\n            instance.$destroy()\n            resetTaskHook()\n            done()\n          })\n        }, 50)\n      }).catch(done.fail)\n    })\n\n    // it('component lifecycle', done => {\n    //   global.__lifecycles = []\n    //   compileWithDeps('recycle-list/components/stateful-lifecycle.vue', [{\n    //     name: 'lifecycle',\n    //     path: 'recycle-list/components/lifecycle.vue'\n    //   }]).then(code => {\n    //     const id = String(Date.now() * Math.random())\n    //     const instance = createInstance(id, code)\n    //     setTimeout(() => {\n    //       const target = readObject('recycle-list/components/stateful-lifecycle.vdom.js')\n    //       expect(getRoot(instance)).toEqual(target)\n\n    //       instance.$triggerHook(2, 'create', ['y-1'])\n    //       instance.$triggerHook('y-1', 'attach')\n    //       instance.$triggerHook('y-1', 'detach')\n    //       expect(global.__lifecycles).toEqual([\n    //         'beforeCreate undefined',\n    //         'created 0',\n    //         'beforeMount 1',\n    //         'mounted 1',\n    //         'beforeUpdate 2',\n    //         'updated 2',\n    //         'beforeDestroy 2',\n    //         'destroyed 2'\n    //       ])\n\n    //       delete global.__lifecycles\n    //       instance.$destroy()\n    //       done()\n    //     }, 50)\n    //   }).catch(done.fail)\n    // })\n\n    it('stateful component with v-model', done => {\n      compileWithDeps('recycle-list/components/stateful-v-model.vue', [{\n        name: 'editor',\n        path: 'recycle-list/components/editor.vue'\n      }]).then(code => {\n        const id = String(Date.now() * Math.random())\n        const instance = createInstance(id, code)\n        setTimeout(() => {\n          const target = readObject('recycle-list/components/stateful-v-model.vdom.js')\n          expect(getRoot(instance)).toEqual(target)\n          instance.$destroy()\n          done()\n        }, 50)\n      }).catch(done.fail)\n    })\n  })\n})\n"
  },
  {
    "path": "vue/test/weex/cases/event/click.after.vdom.js",
    "content": "({\n  type: 'div',\n  event: ['click'],\n  children: [{\n    type: 'text',\n    attr: {\n      value: '43'\n    }\n  }]\n})\n"
  },
  {
    "path": "vue/test/weex/cases/event/click.before.vdom.js",
    "content": "({\n  type: 'div',\n  event: ['click'],\n  children: [{\n    type: 'text',\n    attr: {\n      value: '42'\n    }\n  }]\n})\n"
  },
  {
    "path": "vue/test/weex/cases/event/click.vue",
    "content": "<template>\n  <div @click=\"inc\">\n    <text>{{count}}</text>\n  </div>\n</template>\n\n<script>\n  module.exports = {\n    data () {\n      return {\n        count: 42\n      }\n    },\n    methods: {\n      inc () {\n        this.count++\n      }\n    }\n  }\n</script>\n"
  },
  {
    "path": "vue/test/weex/cases/recycle-list/attrs.vdom.js",
    "content": "({\n  type: 'recycle-list',\n  attr: {\n    append: 'tree',\n    listData: [\n      { type: 'A', count: 1, source: 'http://whatever.com/x.png' },\n      { type: 'A', count: 2, source: 'http://whatever.com/y.png' },\n      { type: 'A', count: 3, source: 'http://whatever.com/z.png' }\n    ],\n    switch: 'type',\n    alias: 'item'\n  },\n  children: [{\n    type: 'cell-slot',\n    attr: { append: 'tree', case: 'A' },\n    children: [{\n      type: 'image',\n      attr: {\n        resize: 'cover',\n        src: {\n          '@binding': 'item.source'\n        }\n      }\n    }, {\n      type: 'text',\n      attr: {\n        lines: '3',\n        count: {\n          '@binding': 'item.count'\n        }\n      }\n    }]\n  }]\n})\n"
  },
  {
    "path": "vue/test/weex/cases/recycle-list/attrs.vue",
    "content": "<template>\n  <recycle-list for=\"item in longList\" switch=\"type\">\n    <cell-slot case=\"A\">\n      <image resize=\"cover\" :src=\"item.source\">\n      <text lines=\"3\" v-bind:count=\"item.count\"></text>\n    </cell-slot>\n  </recycle-list>\n</template>\n\n<script>\n  module.exports = {\n    data () {\n      return {\n        longList: [\n          { type: 'A', count: 1, source: 'http://whatever.com/x.png' },\n          { type: 'A', count: 2, source: 'http://whatever.com/y.png' },\n          { type: 'A', count: 3, source: 'http://whatever.com/z.png' }\n        ]\n      }\n    }\n  }\n</script>\n\n"
  },
  {
    "path": "vue/test/weex/cases/recycle-list/classname.vdom.js",
    "content": "({\n  type: 'recycle-list',\n  attr: {\n    append: 'tree',\n    listData: [\n      { type: 'A', color: 'red' },\n      { type: 'A', color: 'blue' }\n    ],\n    switch: 'type',\n    alias: 'item'\n  },\n  children: [{\n    type: 'cell-slot',\n    attr: { append: 'tree', case: 'A' },\n    style: {\n      backgroundColor: '#FF6600'\n    },\n    children: [{\n      type: 'text',\n      attr: {\n        // not supported yet\n        // classList: ['text', { '@binding': 'item.color' }],\n        value: 'content'\n      }\n    }]\n  }]\n})\n"
  },
  {
    "path": "vue/test/weex/cases/recycle-list/classname.vue",
    "content": "<template>\n  <recycle-list for=\"item in longList\" switch=\"type\">\n    <cell-slot case=\"A\" class=\"cell\">\n      <text :class=\"['text', item.color]\">content</text>\n    </cell-slot>\n  </recycle-list>\n</template>\n\n<style scoped>\n  .cell {\n    background-color: #FF6600;\n  }\n  .text {\n    font-size: 100px;\n    text-align: center;\n  }\n  .red {\n    color: #FF0000;\n  }\n  .blue {\n    color: #0000FF;\n  }\n</style>\n\n<script>\n  module.exports = {\n    data () {\n      return {\n        longList: [\n          { type: 'A', color: 'red' },\n          { type: 'A', color: 'blue' }\n        ]\n      }\n    }\n  }\n</script>\n\n"
  },
  {
    "path": "vue/test/weex/cases/recycle-list/components/banner.vue",
    "content": "<template recyclable=\"true\">\n  <div class=\"banner\">\n    <text class=\"title\">BANNER</text>\n  </div>\n</template>\n\n<style scoped>\n  .banner {\n    height: 120px;\n    justify-content: center;\n    align-items: center;\n    background-color: rgb(162, 217, 192);\n  }\n  .title {\n    font-weight: bold;\n    color: #41B883;\n    font-size: 60px;\n  }\n</style>\n"
  },
  {
    "path": "vue/test/weex/cases/recycle-list/components/counter.vue",
    "content": "<template recyclable=\"true\">\n  <div>\n    <text class=\"output\">{{count}}</text>\n    <text class=\"button\" @click=\"inc\">+</text>\n  </div>\n</template>\n\n<script>\n  module.exports = {\n    props: ['start'],\n    data () {\n      return {\n        count: parseInt(this.start, 10) * 2 || 42\n      }\n    },\n    methods: {\n      inc () {\n        this.count++\n      }\n    }\n  }\n</script>\n\n<style scoped>\n  .output {\n    font-size: 150px;\n    text-align: center;\n  }\n  .button {\n    font-size: 100px;\n    text-align: center;\n    border-width: 2px;\n    border-color: #DDD;\n    background-color: #F5F5F5;\n  }\n</style>\n"
  },
  {
    "path": "vue/test/weex/cases/recycle-list/components/editor.vue",
    "content": "<template recyclable=\"true\">\n  <div>\n    <text class=\"output\">{{output}}</text>\n    <input class=\"input\" type=\"text\" v-model=\"output\" />\n  </div>\n</template>\n\n<script>\n  module.exports = {\n    props: ['message'],\n    data () {\n      return {\n        output: this.message || ''\n      }\n    }\n  }\n</script>\n\n<style scoped>\n  .output {\n    height: 80px;\n    font-size: 60px;\n    color: #41B883;\n  }\n  .input {\n    font-size: 50px;\n    color: #666666;\n    border-width: 2px;\n    border-color: #41B883;\n  }\n</style>\n"
  },
  {
    "path": "vue/test/weex/cases/recycle-list/components/footer.vue",
    "content": "<template recyclable=\"true\">\n  <div class=\"footer\">\n    <text class=\"copyright\">All rights reserved.</text>\n  </div>\n</template>\n\n<style scoped>\n  .footer {\n    height: 80px;\n    justify-content: center;\n    background-color: #EEEEEE;\n  }\n  .copyright {\n    color: #AAAAAA;\n    font-size: 32px;\n    text-align: center;\n  }\n</style>\n"
  },
  {
    "path": "vue/test/weex/cases/recycle-list/components/lifecycle.vue",
    "content": "<template recyclable=\"true\">\n  <div>\n    <text>{{number}}</text>\n  </div>\n</template>\n\n<script>\n  module.exports = {\n    data () {\n      return { number: 0 }\n    },\n    beforeCreate () {\n      try { __lifecycles.push('beforeCreate ' + this.number) } catch (e) {}\n    },\n    created () {\n      try { __lifecycles.push('created ' + this.number) } catch (e) {}\n      this.number++\n    },\n    beforeMount () {\n      try { __lifecycles.push('beforeMount ' + this.number) } catch (e) {}\n    },\n    mounted () {\n      try { __lifecycles.push('mounted ' + this.number) } catch (e) {}\n      this.number++\n    },\n    beforeUpdate () {\n      try { __lifecycles.push('beforeUpdate ' + this.number) } catch (e) {}\n    },\n    updated () {\n      try { __lifecycles.push('updated ' + this.number) } catch (e) {}\n    },\n    beforeDestroy () {\n      try { __lifecycles.push('beforeDestroy ' + this.number) } catch (e) {}\n    },\n    destroyed () {\n      try { __lifecycles.push('destroyed ' + this.number) } catch (e) {}\n    }\n  }\n</script>\n"
  },
  {
    "path": "vue/test/weex/cases/recycle-list/components/poster.vue",
    "content": "<template recyclable=\"true\">\n  <div>\n    <image class=\"image\" :src=\"imageUrl\"></image>\n    <text class=\"title\">{{title}}</text>\n  </div>\n</template>\n\n<script>\n  module.exports = {\n    props: {\n      imageUrl: {\n        type: String,\n        default: 'https://gw.alicdn.com/tfs/TB1KF_ybRTH8KJjy0FiXXcRsXXa-890-1186.png'\n      },\n      title: {\n        type: String,\n        default: 'I WANT YOU!'\n      }\n    }\n  }\n</script>\n\n<style scoped>\n  .image {\n    width: 750px;\n    height: 1000px;\n  }\n  .title {\n    font-size: 80px;\n    text-align: center;\n    color: #E95659;\n  }\n</style>\n"
  },
  {
    "path": "vue/test/weex/cases/recycle-list/components/stateful-lifecycle.vdom.js",
    "content": "({\n  type: 'recycle-list',\n  attr: {\n    append: 'tree',\n    listData: [\n      { type: 'X' },\n      { type: 'X' }\n    ],\n    switch: 'type',\n    alias: 'item'\n  },\n  children: [{\n    type: 'cell-slot',\n    attr: { append: 'tree', case: 'X' },\n    children: [{\n      type: 'div',\n      attr: {\n        '@isComponentRoot': true,\n        '@componentProps': {}\n      },\n      children: [{\n        type: 'text',\n        attr: {\n          value: { '@binding': 'number' }\n        }\n      }]\n    }]\n  }]\n})\n"
  },
  {
    "path": "vue/test/weex/cases/recycle-list/components/stateful-lifecycle.vue",
    "content": "<template>\n  <recycle-list for=\"item in longList\" switch=\"type\">\n    <cell-slot case=\"X\">\n      <lifecycle></lifecycle>\n    </cell-slot>\n  </recycle-list>\n</template>\n\n<script>\n  // require('./lifecycle.vue')\n  module.exports = {\n    data () {\n      return {\n        longList: [\n          { type: 'X' },\n          { type: 'X' }\n        ]\n      }\n    }\n  }\n</script>\n"
  },
  {
    "path": "vue/test/weex/cases/recycle-list/components/stateful-v-model.vdom.js",
    "content": "({\n  type: 'recycle-list',\n  attr: {\n    append: 'tree',\n    listData: [\n      { type: 'A' },\n      { type: 'A' }\n    ],\n    switch: 'type',\n    alias: 'item'\n  },\n  children: [{\n    type: 'cell-slot',\n    attr: { append: 'tree', case: 'A' },\n    children: [{\n      type: 'div',\n      attr: {\n        '@isComponentRoot': true,\n        '@componentProps': {\n          message: 'No binding'\n        }\n      },\n      children: [{\n        type: 'text',\n        classList: ['output'],\n        attr: {\n          value: { '@binding': 'output' }\n        }\n      }, {\n        type: 'input',\n        event: ['input'],\n        classList: ['input'],\n        attr: {\n          type: 'text',\n          value: 'No binding'\n        }\n      }]\n    }]\n  }]\n})\n"
  },
  {
    "path": "vue/test/weex/cases/recycle-list/components/stateful-v-model.vue",
    "content": "<template>\n  <recycle-list for=\"item in longList\" switch=\"type\">\n    <cell-slot case=\"A\">\n      <editor message=\"No binding\"></editor>\n    </cell-slot>\n  </recycle-list>\n</template>\n\n<script>\n  // require('./editor.vue')\n  module.exports = {\n    data () {\n      return {\n        longList: [\n          { type: 'A' },\n          { type: 'A' }\n        ]\n      }\n    }\n  }\n</script>\n"
  },
  {
    "path": "vue/test/weex/cases/recycle-list/components/stateful.vdom.js",
    "content": "({\n  type: 'recycle-list',\n  attr: {\n    append: 'tree',\n    listData: [\n      { type: 'A', number: 24 },\n      { type: 'A', number: 42 }\n    ],\n    switch: 'type',\n    alias: 'item'\n  },\n  children: [{\n    type: 'cell-slot',\n    attr: { append: 'tree', case: 'A' },\n    children: [{\n      type: 'div',\n      attr: {\n        '@isComponentRoot': true,\n        '@componentProps': {\n          start: { '@binding': 'item.number' }\n        }\n      },\n      children: [{\n        type: 'text',\n        classList: ['output'],\n        attr: {\n          value: { '@binding': 'count' } // need confirm\n        }\n      }, {\n        type: 'text',\n        event: ['click'],\n        classList: ['button'],\n        attr: { value: '+' }\n      }]\n    }, {\n      type: 'text',\n      attr: { value: 'other' }\n    }]\n  }]\n})\n"
  },
  {
    "path": "vue/test/weex/cases/recycle-list/components/stateful.vue",
    "content": "<template>\n  <recycle-list for=\"item in longList\" switch=\"type\">\n    <cell-slot case=\"A\">\n      <counter :start=\"item.number\"></counter>\n      <text>other</text>\n    </cell-slot>\n  </recycle-list>\n</template>\n\n<script>\n  // require('./counter.vue')\n  module.exports = {\n    data () {\n      return {\n        longList: [\n          { type: 'A', number: 24 },\n          { type: 'A', number: 42 }\n        ]\n      }\n    }\n  }\n</script>\n"
  },
  {
    "path": "vue/test/weex/cases/recycle-list/components/stateless-multi-components.vdom.js",
    "content": "({\n  type: 'recycle-list',\n  attr: {\n    append: 'tree',\n    listData: [\n      { type: 'A' },\n      { type: 'B', poster: 'yy', title: 'y' },\n      { type: 'A' }\n    ],\n    switch: 'type',\n    alias: 'item'\n  },\n  children: [{\n    type: 'cell-slot',\n    attr: { append: 'tree', case: 'A' },\n    children: [{\n      type: 'div',\n      attr: {\n        '@isComponentRoot': true,\n        '@componentProps': {}\n      },\n      classList: ['banner'],\n      children: [{\n        type: 'text',\n        classList: ['title'],\n        attr: { value: 'BANNER' }\n      }]\n    }, {\n      type: 'text',\n      attr: { value: '----' }\n    }, {\n      type: 'div',\n      attr: {\n        '@isComponentRoot': true,\n        '@componentProps': {}\n      },\n      classList: ['footer'],\n      children: [{\n        type: 'text',\n        classList: ['copyright'],\n        attr: { value: 'All rights reserved.' }\n      }]\n    }]\n  }, {\n    type: 'cell-slot',\n    attr: { append: 'tree', case: 'B' },\n    children: [{\n      type: 'div',\n      attr: {\n        '@isComponentRoot': true,\n        '@componentProps': {}\n      },\n      classList: ['banner'],\n      children: [{\n        type: 'text',\n        classList: ['title'],\n        attr: { value: 'BANNER' }\n      }]\n    }, {\n      type: 'div',\n      attr: {\n        '@isComponentRoot': true,\n        '@componentProps': {\n          imageUrl: { '@binding': 'item.poster' },\n          title: { '@binding': 'item.title' }\n        }\n      },\n      children: [{\n        type: 'image',\n        classList: ['image'],\n        attr: {\n          src: { '@binding': 'imageUrl' }\n        }\n      }, {\n        type: 'text',\n        classList: ['title'],\n        attr: {\n          value: { '@binding': 'title' }\n        }\n      }]\n    }]\n  }]\n})\n"
  },
  {
    "path": "vue/test/weex/cases/recycle-list/components/stateless-multi-components.vue",
    "content": "<template>\n  <recycle-list for=\"item in longList\" switch=\"type\">\n    <cell-slot case=\"A\">\n      <banner></banner>\n      <text>----</text>\n      <footer></footer>\n    </cell-slot>\n    <cell-slot case=\"B\">\n      <banner></banner>\n      <poster :image-url=\"item.poster\" :title=\"item.title\"></poster>\n    </cell-slot>\n  </recycle-list>\n</template>\n\n<script>\n  // require('./banner.vue')\n  // require('./footer.vue')\n  // require('./poster.vue')\n  module.exports = {\n    data () {\n      return {\n        longList: [\n          { type: 'A' },\n          { type: 'B', poster: 'yy', title: 'y' },\n          { type: 'A' }\n        ]\n      }\n    }\n  }\n</script>\n"
  },
  {
    "path": "vue/test/weex/cases/recycle-list/components/stateless-with-props.vdom.js",
    "content": "({\n  type: 'recycle-list',\n  attr: {\n    append: 'tree',\n    listData: [\n      { type: 'A', poster: 'xx', title: 'x' },\n      { type: 'A', poster: 'yy', title: 'y' }\n    ],\n    switch: 'type',\n    alias: 'item'\n  },\n  children: [{\n    type: 'cell-slot',\n    attr: { append: 'tree', case: 'A' },\n    children: [{\n      type: 'div',\n      attr: {\n        '@isComponentRoot': true,\n        '@componentProps': {\n          imageUrl: { '@binding': 'item.poster' },\n          title: { '@binding': 'item.title' }\n        }\n      },\n      children: [{\n        type: 'image',\n        classList: ['image'],\n        attr: {\n          src: { '@binding': 'imageUrl' }\n        }\n      }, {\n        type: 'text',\n        classList: ['title'],\n        attr: {\n          value: { '@binding': 'title' }\n        }\n      }]\n    }, {\n      type: 'text',\n      attr: {\n        value: 'content'\n      }\n    }]\n  }]\n})\n"
  },
  {
    "path": "vue/test/weex/cases/recycle-list/components/stateless-with-props.vue",
    "content": "<template>\n  <recycle-list for=\"item in longList\" switch=\"type\">\n    <cell-slot case=\"A\">\n      <poster :image-url=\"item.poster\" :title=\"item.title\"></poster>\n      <text>content</text>\n    </cell-slot>\n  </recycle-list>\n</template>\n\n<script>\n  // require('./poster.vue')\n  module.exports = {\n    data () {\n      return {\n        longList: [\n          { type: 'A', poster: 'xx', title: 'x' },\n          { type: 'A', poster: 'yy', title: 'y' }\n        ]\n      }\n    }\n  }\n</script>\n"
  },
  {
    "path": "vue/test/weex/cases/recycle-list/components/stateless.vdom.js",
    "content": "({\n  type: 'recycle-list',\n  attr: {\n    append: 'tree',\n    listData: [\n      { type: 'A' },\n      { type: 'A' }\n    ],\n    switch: 'type',\n    alias: 'item'\n  },\n  children: [{\n    type: 'cell-slot',\n    attr: { append: 'tree', case: 'A' },\n    children: [{\n      type: 'div',\n      attr: {\n        '@isComponentRoot': true,\n        '@componentProps': {}\n      },\n      classList: ['banner'],\n      children: [{\n        type: 'text',\n        classList: ['title'],\n        attr: {\n          value: 'BANNER'\n        }\n      }]\n    }, {\n      type: 'text',\n      attr: {\n        value: 'content'\n      }\n    }]\n  }]\n})\n"
  },
  {
    "path": "vue/test/weex/cases/recycle-list/components/stateless.vue",
    "content": "<template>\n  <recycle-list for=\"item in longList\" switch=\"type\">\n    <cell-slot case=\"A\">\n      <banner></banner>\n      <text>content</text>\n    </cell-slot>\n  </recycle-list>\n</template>\n\n<script>\n  // require('./banner.vue')\n  module.exports = {\n    data () {\n      return {\n        longList: [\n          { type: 'A' },\n          { type: 'A' }\n        ]\n      }\n    }\n  }\n</script>\n"
  },
  {
    "path": "vue/test/weex/cases/recycle-list/inline-style.vdom.js",
    "content": "({\n  type: 'recycle-list',\n  attr: {\n    append: 'tree',\n    listData: [\n      { type: 'A', color: '#606060' },\n      { type: 'A', color: '#E5E5E5' }\n    ],\n    switch: 'type',\n    alias: 'item'\n  },\n  children: [{\n    type: 'cell-slot',\n    attr: { append: 'tree', case: 'A' },\n    style: {\n      backgroundColor: '#FF6600'\n    },\n    children: [{\n      type: 'text',\n      style: {\n        fontSize: '100px',\n        color: { '@binding': 'item.color' }\n      },\n      attr: {\n        value: 'content'\n      }\n    }]\n  }]\n})\n"
  },
  {
    "path": "vue/test/weex/cases/recycle-list/inline-style.vue",
    "content": "<template>\n  <recycle-list for=\"item in longList\" switch=\"type\">\n    <cell-slot case=\"A\" style=\"background-color:#FF6600\">\n      <text :style=\"{ fontSize: '100px', color: item.color }\">content</text>\n    </cell-slot>\n  </recycle-list>\n</template>\n\n<script>\n  module.exports = {\n    data () {\n      return {\n        longList: [\n          { type: 'A', color: '#606060' },\n          { type: 'A', color: '#E5E5E5' }\n        ]\n      }\n    }\n  }\n</script>\n\n"
  },
  {
    "path": "vue/test/weex/cases/recycle-list/text-node.vdom.js",
    "content": "({\n  type: 'recycle-list',\n  attr: {\n    append: 'tree',\n    listData: [\n      { type: 'A', dynamic: 'decimal', two: '2', four: '4' },\n      { type: 'A', dynamic: 'binary', two: '10', four: '100' }\n    ],\n    switch: 'type',\n    alias: 'item'\n  },\n  children: [{\n    type: 'cell-slot',\n    attr: { append: 'tree', case: 'A' },\n    children: [{\n      type: 'text',\n      attr: {\n        value: 'static'\n      }\n    }, {\n      type: 'text',\n      attr: {\n        value: { '@binding': 'item.dynamic' }\n      }\n    }, {\n      type: 'text',\n      attr: {\n        value: [\n          'one ',\n          { '@binding': 'item.two' },\n          ' three ',\n          { '@binding': 'item.four' },\n          ' five'\n        ]\n      }\n    }]\n  }]\n})\n"
  },
  {
    "path": "vue/test/weex/cases/recycle-list/text-node.vue",
    "content": "<template>\n  <recycle-list for=\"item in longList\" switch=\"type\">\n    <cell-slot case=\"A\">\n      <text>static</text>\n      <text>{{item.dynamic}}</text>\n      <text>one {{item.two}} three {{ item.four }} five</text>\n    </cell-slot>\n  </recycle-list>\n</template>\n\n<script>\n  module.exports = {\n    data () {\n      return {\n        longList: [\n          { type: 'A', dynamic: 'decimal', two: '2', four: '4' },\n          { type: 'A', dynamic: 'binary', two: '10', four: '100' }\n        ]\n      }\n    }\n  }\n</script>\n\n"
  },
  {
    "path": "vue/test/weex/cases/recycle-list/v-else-if.vdom.js",
    "content": "({\n  type: 'recycle-list',\n  attr: {\n    append: 'tree',\n    listData: [\n      { type: 'A' },\n      { type: 'A' }\n    ],\n    switch: 'type',\n    alias: 'item'\n  },\n  children: [{\n    type: 'cell-slot',\n    attr: { append: 'tree', case: 'A' },\n    children: [{\n      type: 'image',\n      attr: {\n        '[[match]]': 'item.sourceA',\n        src: { '@binding': 'item.sourceA' }\n      }\n    }, {\n      type: 'image',\n      attr: {\n        '[[match]]': '!(item.sourceA) && (item.sourceB)',\n        src: { '@binding': 'item.sourceB' }\n      }\n    }, {\n      type: 'image',\n      attr: {\n        '[[match]]': '!(item.sourceA || item.sourceB)',\n        src: { '@binding': 'item.placeholder' }\n      }\n    }]\n  }]\n})\n"
  },
  {
    "path": "vue/test/weex/cases/recycle-list/v-else-if.vue",
    "content": "<template>\n  <recycle-list for=\"item in longList\" switch=\"type\">\n    <cell-slot case=\"A\">\n      <image v-if=\"item.sourceA\" :src=\"item.sourceA\"></image>\n      <image v-else-if=\"item.sourceB\" :src=\"item.sourceB\"></image>\n      <image v-else :src=\"item.placeholder\"></image>\n    </cell-slot>\n  </recycle-list>\n</template>\n\n<script>\n  module.exports = {\n    data () {\n      return {\n        longList: [\n          { type: 'A' },\n          { type: 'A' }\n        ]\n      }\n    }\n  }\n</script>\n\n"
  },
  {
    "path": "vue/test/weex/cases/recycle-list/v-else.vdom.js",
    "content": "({\n  type: 'recycle-list',\n  attr: {\n    append: 'tree',\n    listData: [\n      { type: 'A' },\n      { type: 'A' }\n    ],\n    switch: 'type',\n    alias: 'item'\n  },\n  children: [{\n    type: 'cell-slot',\n    attr: { append: 'tree', case: 'A' },\n    children: [{\n      type: 'image',\n      attr: {\n        '[[match]]': 'item.source',\n        src: { '@binding': 'item.source' }\n      }\n    }, {\n      type: 'image',\n      attr: {\n        '[[match]]': '!(item.source)',\n        src: { '@binding': 'item.placeholder' }\n      }\n    }]\n  }]\n})\n"
  },
  {
    "path": "vue/test/weex/cases/recycle-list/v-else.vue",
    "content": "<template>\n  <recycle-list for=\"item in longList\" switch=\"type\">\n    <cell-slot case=\"A\">\n      <image v-if=\"item.source\" :src=\"item.source\"></image>\n      <image v-else v-bind:src=\"item.placeholder\"></image>\n    </cell-slot>\n  </recycle-list>\n</template>\n\n<script>\n  module.exports = {\n    data () {\n      return {\n        longList: [\n          { type: 'A' },\n          { type: 'A' }\n        ]\n      }\n    }\n  }\n</script>\n\n"
  },
  {
    "path": "vue/test/weex/cases/recycle-list/v-for-iterator.vdom.js",
    "content": "({\n  type: 'recycle-list',\n  attr: {\n    append: 'tree',\n    listData: [\n      { type: 'A' },\n      { type: 'A' }\n    ],\n    switch: 'type',\n    alias: 'item'\n  },\n  children: [{\n    type: 'cell-slot',\n    attr: { append: 'tree', case: 'A' },\n    children: [{\n      type: 'div',\n      attr: {\n        '[[repeat]]': {\n          '@expression': 'item.list',\n          '@index': 'index',\n          '@alias': 'object'\n        }\n      },\n      children: [{\n        type: 'text',\n        attr: {\n          value: {\n            '@binding': 'object.name'\n          }\n        }\n      }, {\n        type: 'text',\n        attr: {\n          '[[repeat]]': {\n            '@expression': 'object',\n            '@alias': 'v',\n            '@key': 'k',\n            '@index': 'i'\n          },\n          value: {\n            '@binding': 'v'\n          }\n        }\n      }]\n    }]\n  }]\n})\n"
  },
  {
    "path": "vue/test/weex/cases/recycle-list/v-for-iterator.vue",
    "content": "<template>\n  <recycle-list for=\"item in longList\" switch=\"type\">\n    <cell-slot case=\"A\">\n      <div v-for=\"(object, index) in item.list\" :key=\"index\">\n        <text>{{object.name}}</text>\n        <text v-for=\"(v, k, i) in object\" :key=\"k\">{{v}}</text>\n      </div>\n    </cell-slot>\n  </recycle-list>\n</template>\n\n<script>\n  module.exports = {\n    data () {\n      return {\n        longList: [\n          { type: 'A' },\n          { type: 'A' }\n        ]\n      }\n    }\n  }\n</script>\n\n"
  },
  {
    "path": "vue/test/weex/cases/recycle-list/v-for.vdom.js",
    "content": "({\n  type: 'recycle-list',\n  attr: {\n    append: 'tree',\n    listData: [\n      { type: 'A' },\n      { type: 'A' }\n    ],\n    switch: 'type',\n    alias: 'item'\n  },\n  children: [{\n    type: 'cell-slot',\n    attr: { append: 'tree', case: 'A' },\n    children: [{\n      type: 'div',\n      attr: {\n        '[[repeat]]': {\n          '@expression': 'item.list',\n          '@alias': 'panel'\n        }\n      },\n      children: [{\n        type: 'text',\n        attr: {\n          value: {\n            '@binding': 'panel.label'\n          }\n        }\n      }]\n    }]\n  }]\n})\n"
  },
  {
    "path": "vue/test/weex/cases/recycle-list/v-for.vue",
    "content": "<template>\n  <recycle-list for=\"item in longList\" switch=\"type\">\n    <cell-slot case=\"A\">\n      <div v-for=\"panel in item.list\" :key=\"panel.id\">\n        <text>{{panel.label}}</text>\n      </div>\n    </cell-slot>\n  </recycle-list>\n</template>\n\n<script>\n  module.exports = {\n    data () {\n      return {\n        longList: [\n          { type: 'A' },\n          { type: 'A' }\n        ]\n      }\n    }\n  }\n</script>\n\n"
  },
  {
    "path": "vue/test/weex/cases/recycle-list/v-if.vdom.js",
    "content": "({\n  type: 'recycle-list',\n  attr: {\n    append: 'tree',\n    listData: [\n      { type: 'A' },\n      { type: 'A' }\n    ],\n    switch: 'type',\n    alias: 'item'\n  },\n  children: [{\n    type: 'cell-slot',\n    attr: { append: 'tree', case: 'A' },\n    children: [{\n      type: 'image',\n      attr: {\n        '[[match]]': 'item.source',\n        src: { '@binding': 'item.source' }\n      }\n    }, {\n      type: 'text',\n      attr: {\n        '[[match]]': '!item.source',\n        value: 'Title'\n      }\n    }]\n  }]\n})\n"
  },
  {
    "path": "vue/test/weex/cases/recycle-list/v-if.vue",
    "content": "<template>\n  <recycle-list for=\"item in longList\" switch=\"type\">\n    <cell-slot case=\"A\">\n      <image v-if=\"item.source\" :src=\"item.source\"></image>\n      <text v-if=\"!item.source\">Title</text>\n    </cell-slot>\n  </recycle-list>\n</template>\n\n<script>\n  module.exports = {\n    data () {\n      return {\n        longList: [\n          { type: 'A' },\n          { type: 'A' }\n        ]\n      }\n    }\n  }\n</script>\n\n"
  },
  {
    "path": "vue/test/weex/cases/recycle-list/v-on-inline.vdom.js",
    "content": "({\n  type: 'recycle-list',\n  attr: {\n    append: 'tree',\n    listData: [\n      { type: 'A' },\n      { type: 'A' }\n    ],\n    switch: 'type',\n    alias: 'item'\n  },\n  children: [{\n    type: 'cell-slot',\n    attr: { append: 'tree', case: 'A' },\n    children: [{\n      type: 'text',\n      event: ['click', {\n        type: 'longpress',\n        params: [{ '@binding': 'item.key' }]\n      }]\n    }, {\n      type: 'text',\n      event: [{\n        type: 'appear',\n        params: [\n          { '@binding': 'item.index' },\n          { '@binding': 'item.type' }\n        ]\n      }],\n      attr: { value: 'Button' }\n    }, {\n      type: 'text',\n      event: [{ type: 'disappear' }],\n      attr: { value: 'Tips' }\n    }]\n  }]\n})\n"
  },
  {
    "path": "vue/test/weex/cases/recycle-list/v-on-inline.vue",
    "content": "<template>\n  <recycle-list for=\"item in longList\" switch=\"type\">\n    <cell-slot case=\"A\">\n      <text v-on:click=\"toggle()\" @longpress=\"toggle(item.key)\"></text>\n      <text @appear=\"onappear(item.index, 'static', item.type, $event)\">Button</text>\n      <text @disappear=\"onappear(25, 'static')\">Tips</text>\n    </cell-slot>\n  </recycle-list>\n</template>\n\n<script>\n  module.exports = {\n    data () {\n      return {\n        longList: [\n          { type: 'A' },\n          { type: 'A' }\n        ]\n      }\n    },\n    methods: {\n      hide () {},\n      toggle () {},\n      onappear () {}\n    }\n  }\n</script>\n\n"
  },
  {
    "path": "vue/test/weex/cases/recycle-list/v-on.vdom.js",
    "content": "({\n  type: 'recycle-list',\n  attr: {\n    append: 'tree',\n    listData: [\n      { type: 'A' },\n      { type: 'A' }\n    ],\n    switch: 'type',\n    alias: 'item'\n  },\n  children: [{\n    type: 'cell-slot',\n    attr: { append: 'tree', case: 'A' },\n    children: [{\n      type: 'text',\n      event: ['click', 'longpress'],\n      attr: { value: 'A' }\n    }, {\n      type: 'text',\n      event: ['touchend'],\n      attr: { value: 'B' }\n    }]\n  }]\n})\n"
  },
  {
    "path": "vue/test/weex/cases/recycle-list/v-on.vue",
    "content": "<template>\n  <recycle-list for=\"item in longList\" switch=\"type\">\n    <cell-slot case=\"A\">\n      <text v-on:click=\"handler\" @longpress=\"move\">A</text>\n      <text @touchend=\"move\">B</text>\n    </cell-slot>\n  </recycle-list>\n</template>\n\n<script>\n  module.exports = {\n    data () {\n      return {\n        longList: [\n          { type: 'A' },\n          { type: 'A' }\n        ]\n      }\n    },\n    methods: {\n      handler () {},\n      move () {}\n    }\n  }\n</script>\n\n"
  },
  {
    "path": "vue/test/weex/cases/recycle-list/v-once.vdom.js",
    "content": "({\n  type: 'recycle-list',\n  attr: {\n    append: 'tree',\n    listData: [\n      { type: 'A' },\n      { type: 'A' }\n    ],\n    alias: 'item'\n  },\n  children: [{\n    type: 'cell-slot',\n    attr: { append: 'tree' },\n    children: [{\n      type: 'text',\n      attr: {\n        '[[once]]': true,\n        value: { '@binding': 'item.type' }\n      }\n    }]\n  }]\n})\n"
  },
  {
    "path": "vue/test/weex/cases/recycle-list/v-once.vue",
    "content": "<template>\n  <recycle-list for=\"item in list\">\n    <cell-slot>\n      <text v-once>{{item.type}}</text>\n    </cell-slot>\n  </recycle-list>\n</template>\n\n<script>\n  module.exports = {\n    data () {\n      return {\n        list: [\n          { type: 'A' },\n          { type: 'A' }\n        ]\n      }\n    }\n  }\n</script>\n\n"
  },
  {
    "path": "vue/test/weex/cases/render/sample.vdom.js",
    "content": "({\n  type: 'div',\n  style: {\n    justifyContent: 'center'\n  },\n  children: [{\n    type: 'text',\n    attr: {\n      value: 'Yo'\n    },\n    classList: ['freestyle']\n  }]\n})\n"
  },
  {
    "path": "vue/test/weex/cases/render/sample.vue",
    "content": "<template>\n  <div style=\"justify-content:center\">\n    <text class=\"freestyle\">{{string}}</text>\n  </div>\n</template>\n\n<style scoped>\n  .freestyle {\n    color: #41B883;\n    font-size: 233px;\n    text-align: center;\n  }\n</style>\n\n<script>\n  module.exports = {\n    data () {\n      return {\n        string: 'Yo'\n      }\n    }\n  }\n</script>\n"
  },
  {
    "path": "vue/test/weex/compiler/append.spec.js",
    "content": "import { compile } from '../../../packages/weex-template-compiler'\nimport { strToRegExp } from '../helpers/index'\n\ndescribe('append props', () => {\n  it('add append=\"tree\" on <cell>', () => {\n    const { render, staticRenderFns, errors } = compile(`<list><cell></cell></list>`)\n    expect(render).not.toBeUndefined()\n    expect(staticRenderFns).not.toBeUndefined()\n    expect(staticRenderFns.length).toEqual(1)\n    expect(staticRenderFns).toMatch(strToRegExp(`appendAsTree:true`))\n    expect(staticRenderFns).toMatch(strToRegExp(`attrs:{\"append\":\"tree\"}`))\n    expect(errors).toEqual([])\n  })\n\n  it('override append=\"node\" on <cell>', () => {\n    const { render, staticRenderFns, errors } = compile(`<list><cell append=\"node\"></cell></list>`)\n    expect(render + staticRenderFns).toMatch(strToRegExp(`attrs:{\"append\":\"node\"}`))\n    expect(errors).toEqual([])\n  })\n\n  it('add append=\"tree\" on <header>', () => {\n    const { render, staticRenderFns, errors } = compile(`<list><header></header></list>`)\n    expect(render + staticRenderFns).toMatch(strToRegExp(`appendAsTree:true`))\n    expect(render + staticRenderFns).toMatch(strToRegExp(`attrs:{\"append\":\"tree\"}`))\n    expect(errors).toEqual([])\n  })\n\n  it('add append=\"tree\" on <recycle-list>', () => {\n    const { render, staticRenderFns, errors } = compile(`<recycle-list for=\"item in list\"><div></div></recycle-list>`)\n    expect(render + staticRenderFns).toMatch(strToRegExp(`appendAsTree:true`))\n    expect(render + staticRenderFns).toMatch(strToRegExp(`\"append\":\"tree\"`))\n    expect(errors).toEqual([])\n  })\n\n  it('add append=\"tree\" on <cell-slot>', () => {\n    const { render, staticRenderFns, errors } = compile(`<list><cell-slot></cell-slot></list>`)\n    expect(render + staticRenderFns).toMatch(strToRegExp(`appendAsTree:true`))\n    expect(render + staticRenderFns).toMatch(strToRegExp(`attrs:{\"append\":\"tree\"}`))\n    expect(errors).toEqual([])\n  })\n\n  it('override append=\"node\" on <cell-slot>', () => {\n    const { render, staticRenderFns, errors } = compile(`<list><cell-slot append=\"node\"></cell-slot></list>`)\n    expect(render + staticRenderFns).toMatch(strToRegExp(`attrs:{\"append\":\"node\"}`))\n    expect(errors).toEqual([])\n  })\n})\n"
  },
  {
    "path": "vue/test/weex/compiler/class.spec.js",
    "content": "import { compile } from '../../../packages/weex-template-compiler'\nimport { strToRegExp } from '../helpers/index'\n\ndescribe('compile class', () => {\n  it('should be compiled', () => {\n    const { render, staticRenderFns, errors } = compile(`<div class=\"a b c\"></div>`)\n    expect(render).not.toBeUndefined()\n    expect(staticRenderFns).not.toBeUndefined()\n    expect(staticRenderFns.length).toEqual(0)\n    expect(render).toMatch(strToRegExp(`staticClass:[\"a\",\"b\",\"c\"]`))\n    expect(errors).toEqual([])\n  })\n\n  it('should compile dynamic class', () => {\n    const { render, staticRenderFns, errors } = compile(`<div class=\"a {{b}} c\"></div>`)\n    expect(render).not.toBeUndefined()\n    expect(staticRenderFns).toEqual([])\n    expect(render).toMatch(strToRegExp(`class:[\"a\",_s(b),\"c\"]`))\n    expect(errors).not.toBeUndefined()\n    expect(errors.length).toEqual(1)\n    expect(errors[0]).toMatch(strToRegExp(`a {{b}} c`))\n    expect(errors[0]).toMatch(strToRegExp(`v-bind`))\n  })\n\n  it('should compile class binding of array', () => {\n    const { render, staticRenderFns, errors } = compile(`<div v-bind:class=\"['a', 'b', c]\"></div>`)\n    expect(render).not.toBeUndefined()\n    expect(staticRenderFns).toEqual([])\n    expect(render).toMatch(strToRegExp(`class:['a', 'b', c]`))\n    expect(errors).toEqual([])\n  })\n\n  it('should compile class binding of map', () => {\n    const { render, staticRenderFns, errors } = compile(`<div v-bind:class=\"{ a: true, b: x }\"></div>`)\n    expect(render).not.toBeUndefined()\n    expect(staticRenderFns).toEqual([])\n    expect(render).toMatch(strToRegExp(`class:{ a: true, b: x }`))\n    expect(errors).toEqual([])\n  })\n\n  it('should compile class binding of a variable', () => {\n    const { render, staticRenderFns, errors } = compile(`<div v-bind:class=\"x\"></div>`)\n    expect(render).not.toBeUndefined()\n    expect(staticRenderFns).toEqual([])\n    expect(render).toMatch(strToRegExp(`class:x`))\n    expect(errors).toEqual([])\n  })\n\n  it('should compile class binding by shorthand', () => {\n    const { render, staticRenderFns, errors } = compile(`<div :class=\"['a', 'b', c]\"></div>`)\n    expect(render).not.toBeUndefined()\n    expect(staticRenderFns).toEqual([])\n    expect(render).toMatch(strToRegExp(`class:['a', 'b', c]`))\n    expect(errors).toEqual([])\n  })\n})\n"
  },
  {
    "path": "vue/test/weex/compiler/compile.spec.js",
    "content": "import { compile } from '../../../packages/weex-template-compiler'\nimport { strToRegExp } from '../helpers/index'\n\ndescribe('compile basic', () => {\n  it('should be compiled', () => {\n    const { render, staticRenderFns, errors } = compile(`<div>{{hi}}</div>`)\n    expect(render).toEqual(`with(this){return _c('div',[_v(_s(hi))])}`)\n    expect(staticRenderFns.length).toBe(0)\n    expect(errors).toEqual([])\n  })\n\n  it('should compile data bindings', () => {\n    const { render, staticRenderFns, errors } = compile(`<div :a=\"b\"></div>`)\n    expect(render).toEqual(`with(this){return _c('div',{attrs:{\"a\":b}})}`)\n    expect(staticRenderFns).toEqual([])\n    expect(errors).toEqual([])\n  })\n\n  it('should compile event bindings', () => {\n    const { render, staticRenderFns, errors } = compile(`<div @click=\"x\"></div>`)\n    expect(render).toEqual(`with(this){return _c('div',{on:{\"click\":x}})}`)\n    expect(staticRenderFns).toEqual([])\n    expect(errors).toEqual([])\n  })\n\n  it('should compile data bindings with children', () => {\n    const { render, staticRenderFns, errors } = compile(`<foo :a=\"b\"><text>Hello</text></foo>`)\n    expect(render).toEqual(`with(this){return _c('foo',{attrs:{\"a\":b}},[_c('text',[_v(\"Hello\")])])}`)\n    expect(staticRenderFns).toEqual([])\n    expect(errors).toEqual([])\n  })\n\n  it('should compile unary tag', () => {\n    const inputCase = compile(`<div><input><text>abc</text></div>`)\n    expect(inputCase.render).toMatch(strToRegExp(`return _m(0)`))\n    expect(inputCase.staticRenderFns).toMatch(strToRegExp(`_c('div',[_c('input'),_c('text',[_v(\"abc\")])])`))\n    expect(inputCase.errors).toEqual([])\n\n    const imageCase = compile(`<div><image src=\"path\"><text>abc</text></div>`)\n    expect(imageCase.render).toMatch(strToRegExp(`return _m(0)`))\n    expect(imageCase.staticRenderFns).toMatch(strToRegExp(`_c('div',[_c('image',{attrs:{\"src\":\"path\"}}),_c('text',[_v(\"abc\")])])`))\n    expect(imageCase.errors).toEqual([])\n\n    const complexCase = compile(`\n      <div>\n        <image src=\"path\">\n        <image></image>\n        <div>\n          <embed>\n          <text>start</text>\n          <input type=\"text\">\n          <input type=\"url\" />\n          <text>end</text>\n        </div>\n      </div>\n    `)\n    expect(complexCase.render).toMatch(strToRegExp(`return _m(0)`))\n    expect(complexCase.staticRenderFns).toMatch(strToRegExp(`_c('image',{attrs:{\"src\":\"path\"}}),_c('image'),_c('div'`))\n    expect(complexCase.staticRenderFns).toMatch(strToRegExp(`_c('div',[_c('embed'),_c('text',[_v(\"start\")]),_c('input',{attrs:{\"type\":\"text\"}}),_c('input',{attrs:{\"type\":\"url\"}}),_c('text',[_v(\"end\")])]`))\n    expect(complexCase.errors).toEqual([])\n  })\n\n  it('should compile more complex situation', () => {\n    // from examples of https://github.com/alibaba/weex\n    const { render, staticRenderFns, errors } = compile(`\n      <refresh class=\"refresh\" @refresh=\"handleRefresh\" :display=\"displayRefresh\"\n        style=\"flex-direction:row;\">\n        <loading-indicator></loading-indicator>\n        <text style=\"margin-left:36px;color:#eee;\">Load more...</text>\n      </refresh>\n    `)\n    expect(render).toEqual(`with(this){return _c('refresh',{staticClass:[\"refresh\"],staticStyle:{flexDirection:\"row\"},attrs:{\"display\":displayRefresh},on:{\"refresh\":handleRefresh}},[_c('loading-indicator'),_c('text',{staticStyle:{marginLeft:\"36px\",color:\"#eee\"}},[_v(\"Load more...\")])])}`)\n    expect(staticRenderFns).toEqual([])\n    expect(errors).toEqual([])\n  })\n})\n"
  },
  {
    "path": "vue/test/weex/compiler/parser.spec.js",
    "content": "import { generateBinding } from '../../../src/platforms/weex/util/parser'\n\ndescribe('expression parser', () => {\n  describe('generateBinding', () => {\n    it('primitive literal', () => {\n      expect(generateBinding('15')).toEqual(15)\n      expect(generateBinding('\"xxx\"')).toEqual('xxx')\n    })\n\n    it('identifiers', () => {\n      expect(generateBinding('x')).toEqual({ '@binding': 'x' })\n      expect(generateBinding('x.y')).toEqual({ '@binding': 'x.y' })\n      expect(generateBinding(`x.y['z']`)).toEqual({ '@binding': `x.y['z']` })\n    })\n\n    it('object literal', () => {\n      expect(generateBinding('{}')).toEqual({})\n      expect(generateBinding('{ abc: 25 }')).toEqual({ abc: 25 })\n      expect(generateBinding('{ abc: 25, def: \"xxx\" }')).toEqual({ abc: 25, def: 'xxx' })\n      expect(generateBinding('{ a: 3, b: { bb: \"bb\", bbb: { bbc: \"BBC\" } } }'))\n        .toEqual({ a: 3, b: { bb: 'bb', bbb: { bbc: 'BBC' }}})\n    })\n\n    it('array literal', () => {\n      expect(generateBinding('[]')).toEqual([])\n      expect(generateBinding('[{ abc: 25 }]')).toEqual([{ abc: 25 }])\n      expect(generateBinding('[{ abc: 25, def: [\"xxx\"] }]')).toEqual([{ abc: 25, def: ['xxx'] }])\n      expect(generateBinding('{ a: [3,16], b: [{ bb: [\"aa\",\"bb\"], bbb: [{bbc:\"BBC\"}] }] }'))\n        .toEqual({ a: [3, 16], b: [{ bb: ['aa', 'bb'], bbb: [{ bbc: 'BBC' }] }] })\n    })\n\n    it('expressions', () => {\n      expect(generateBinding(`3 + 5`)).toEqual({ '@binding': `3 + 5` })\n      expect(generateBinding(`'x' + 2`)).toEqual({ '@binding': `'x' + 2` })\n      expect(generateBinding(`\\`xx\\` + 2`)).toEqual({ '@binding': `\\`xx\\` + 2` })\n      expect(generateBinding(`item.size * 23 + 'px'`)).toEqual({ '@binding': `item.size * 23 + 'px'` })\n    })\n\n    it('object bindings', () => {\n      expect(generateBinding(`{ color: textColor }`)).toEqual({\n        color: { '@binding': 'textColor' }\n      })\n      expect(generateBinding(`{ color: '#FF' + 66 * 100, fontSize: item.size }`)).toEqual({\n        color: { '@binding': `'#FF' + 66 * 100` },\n        fontSize: { '@binding': 'item.size' }\n      })\n      expect(generateBinding(`{\n        x: { xx: obj, xy: -2 + 5 },\n        y: {\n          yy: { yyy: obj.y || yy },\n          yz: typeof object.yz === 'string' ? object.yz : ''\n        }\n      }`)).toEqual({\n        x: { xx: { '@binding': 'obj' }, xy: { '@binding': '-2 + 5' }},\n        y: {\n          yy: { yyy: { '@binding': 'obj.y || yy' }},\n          yz: { '@binding': `typeof object.yz === 'string' ? object.yz : ''` }\n        }\n      })\n    })\n\n    it('array bindings', () => {\n      expect(generateBinding(`[textColor, 3 + 5, 'string']`)).toEqual([\n        { '@binding': 'textColor' },\n        { '@binding': '3 + 5' },\n        'string'\n      ])\n      expect(generateBinding(`[\n        { color: '#FF' + 66 * -100 },\n        item && item.style,\n        { fontSize: item.size | 0 }\n      ]`)).toEqual([\n        { color: { '@binding': `'#FF' + 66 * -100` }},\n        { '@binding': 'item && item.style' },\n        { fontSize: { '@binding': 'item.size | 0' }}\n      ])\n      expect(generateBinding(`[{\n        x: [{ xx: [fn instanceof Function ? 'function' : '' , 25] }],\n        y: {\n          yy: [{ yyy: [obj.yy.y, obj.y.yy] }],\n          yz: [object.yz, void 0]\n        }\n      }]`)).toEqual([{\n        x: [{ xx: [{ '@binding': `fn instanceof Function ? 'function' : ''` }, 25] }],\n        y: {\n          yy: [{ yyy: [{ '@binding': 'obj.yy.y' }, { '@binding': 'obj.y.yy' }] }],\n          yz: [{ '@binding': 'object.yz' }, { '@binding': 'void 0' }]\n        }\n      }])\n    })\n\n    it('unsupported bindings', () => {\n      expect(generateBinding('() => {}')).toEqual('')\n      expect(generateBinding('function(){}')).toEqual('')\n      expect(generateBinding('(function(){})()')).toEqual('')\n      expect(generateBinding('var abc = 35')).toEqual('')\n      expect(generateBinding('abc++')).toEqual('')\n      expect(generateBinding('x.y(0)')).toEqual('')\n      expect(generateBinding('class X {}')).toEqual('')\n      expect(generateBinding('if (typeof x == null) { 35 }')).toEqual('')\n      expect(generateBinding('while (x == null)')).toEqual('')\n      expect(generateBinding('new Function()')).toEqual('')\n    })\n  })\n})\n"
  },
  {
    "path": "vue/test/weex/compiler/props.spec.js",
    "content": "import { compile } from '../../../packages/weex-template-compiler'\nimport { strToRegExp } from '../helpers/index'\n\ndescribe('compile props', () => {\n  it('custom props', () => {\n    const { render, staticRenderFns, errors } = compile(`<div custom=\"whatever\"></div>`)\n    expect(render).not.toBeUndefined()\n    expect(staticRenderFns).not.toBeUndefined()\n    expect(staticRenderFns.length).toEqual(0)\n    expect(render).toMatch(strToRegExp(`attrs:{\"custom\":\"whatever\"}`))\n    expect(errors).toEqual([])\n  })\n\n  it('camelize props', () => {\n    const { render, staticRenderFns, errors } = compile(`<div kebab-case=\"whatever\"></div>`)\n    expect(render).not.toBeUndefined()\n    expect(staticRenderFns).not.toBeUndefined()\n    expect(staticRenderFns.length).toEqual(0)\n    expect(render).toMatch(strToRegExp(`attrs:{\"kebabCase\":\"whatever\"}`))\n    expect(errors).toEqual([])\n  })\n})\n"
  },
  {
    "path": "vue/test/weex/compiler/style.spec.js",
    "content": "import { compile } from '../../../packages/weex-template-compiler'\nimport { strToRegExp } from '../helpers/index'\n\ndescribe('compile style', () => {\n  it('should be compiled', () => {\n    const { render, staticRenderFns, errors } = compile(`<div style=\"a: x; b: y\"></div>`)\n    expect(render).not.toBeUndefined()\n    expect(staticRenderFns).not.toBeUndefined()\n    expect(staticRenderFns.length).toEqual(0)\n    expect(render).toMatch(strToRegExp(`staticStyle:{a:\"x\",b:\"y\"}`))\n    expect(errors).toEqual([])\n  })\n\n  it('should compile empty style value', () => {\n    const { render, staticRenderFns, errors } = compile(`<div style=\"\"></div>`)\n    expect(render).not.toBeUndefined()\n    expect(staticRenderFns).not.toBeUndefined()\n    expect(staticRenderFns.length).toEqual(0)\n    expect(render).toMatch(/[(^style|^staticStyle)]/)\n    expect(errors).toEqual([])\n  })\n\n  it('should compile style value with trailing semicolon', () => {\n    const { render, staticRenderFns, errors } = compile(`<div style=\"a: x; b: y;\"></div>`)\n    expect(render).not.toBeUndefined()\n    expect(staticRenderFns).not.toBeUndefined()\n    expect(staticRenderFns.length).toEqual(0)\n    expect(render).toMatch(strToRegExp(`staticStyle:{a:\"x\",b:\"y\"}`))\n    expect(errors).toEqual([])\n  })\n\n  it('should compile hyphenated style name & value', () => {\n    const { render, staticRenderFns, errors } = compile(`<div style=\"-abc-def: x-y; abc-def: x-y\"></div>`)\n    expect(render).not.toBeUndefined()\n    expect(staticRenderFns).not.toBeUndefined()\n    expect(staticRenderFns.length).toEqual(0)\n    expect(render).toMatch(strToRegExp(`staticStyle:{AbcDef:\"x-y\",abcDef:\"x-y\"}`))\n    expect(errors).toEqual([])\n  })\n\n  it('should compile dynamic style', () => {\n    const { render, staticRenderFns, errors } = compile(`<div style=\"a: x; b: {{y}}\"></div>`)\n    expect(render).not.toBeUndefined()\n    expect(staticRenderFns).toEqual([])\n    expect(render).toMatch(strToRegExp(`style:{a:\"x\",b:_s(y)}`))\n    expect(errors).not.toBeUndefined()\n    expect(errors.length).toEqual(1)\n    expect(errors[0]).toMatch(strToRegExp(`b: {{y}}`))\n    expect(errors[0]).toMatch(strToRegExp(`v-bind`))\n  })\n\n  it('should compile style binding of array', () => {\n    const { render, staticRenderFns, errors } = compile(`<div v-bind:style=\"[a, b, c]\"></div>`)\n    expect(render).not.toBeUndefined()\n    expect(staticRenderFns).toEqual([])\n    expect(render).toMatch(strToRegExp(`style:[a, b, c]`))\n    expect(errors).toEqual([])\n  })\n\n  it('should compile style binding of map', () => {\n    const { render, staticRenderFns, errors } = compile(`<div v-bind:style=\"{ a: x, b: 'y' + z }\"></div>`)\n    expect(render).not.toBeUndefined()\n    expect(staticRenderFns).toEqual([])\n    expect(render).toMatch(strToRegExp(`style:{ a: x, b: 'y' + z }`))\n    expect(errors).toEqual([])\n  })\n\n  it('should compile style binding of a variable', () => {\n    const { render, staticRenderFns, errors } = compile(`<div v-bind:style=\"x\"></div>`)\n    expect(render).not.toBeUndefined()\n    expect(staticRenderFns).toEqual([])\n    expect(render).toMatch(strToRegExp(`style:x`))\n    expect(errors).toEqual([])\n  })\n\n  it('should compile style binding by shorthand', () => {\n    const { render, staticRenderFns, errors } = compile(`<div :style=\"[a, b, c]\"></div>`)\n    expect(render).not.toBeUndefined()\n    expect(staticRenderFns).toEqual([])\n    expect(render).toMatch(strToRegExp(`style:[a, b, c]`))\n    expect(errors).toEqual([])\n  })\n})\n"
  },
  {
    "path": "vue/test/weex/compiler/v-model.spec.js",
    "content": "import { compile } from '../../../packages/weex-template-compiler'\nimport { strToRegExp } from '../helpers/index'\n\ndescribe('compile v-model', () => {\n  it('should compile modelable native component', () => {\n    const { render, staticRenderFns, errors } = compile(`<div><input v-model=\"x\" /></div>`)\n    expect(render).not.toBeUndefined()\n    expect(render).toMatch(strToRegExp(`attrs:{\"value\":(x)}`))\n    expect(render).toMatch(strToRegExp(`on:{\"input\":function($event){x=$event.target.attr.value}}`))\n    expect(staticRenderFns).toEqual([])\n    expect(errors).toEqual([])\n  })\n\n  it('should compile other component with whole $event as the value', () => {\n    const { render, staticRenderFns, errors } = compile(`<div><foo v-model=\"x\" /></div>`)\n    expect(render).not.toBeUndefined()\n    expect(render).toMatch(strToRegExp(`model:{value:(x),callback:function ($$v) {x=$$v},expression:\"x\"}`))\n    expect(staticRenderFns).toEqual([])\n    expect(errors).toEqual([])\n  })\n\n  it('should compile with trim modifier for modelable native component', () => {\n    const { render, staticRenderFns, errors } = compile(`<div><input v-model.trim=\"x\" /></div>`)\n    expect(render).not.toBeUndefined()\n    expect(render).toMatch(strToRegExp(`attrs:{\"value\":(x)}`))\n    expect(render).toMatch(strToRegExp(`on:{\"input\":function($event){x=$event.target.attr.value.trim()}}`))\n    expect(staticRenderFns).toEqual([])\n    expect(errors).toEqual([])\n  })\n\n  it('should compile with trim & lazy modifier', () => {\n    const { render, staticRenderFns, errors } = compile(`<div><input v-model.trim.lazy=\"x\" /><input v-model.lazy.trim=\"y\" /></div>`)\n    expect(render).not.toBeUndefined()\n    expect(render).toMatch(strToRegExp(`attrs:{\"value\":(x)}`))\n    expect(render).toMatch(strToRegExp(`attrs:{\"value\":(y)}`))\n    expect(render).toMatch(strToRegExp(`on:{\"change\":function($event){x=$event.target.attr.value.trim()}}`))\n    expect(render).toMatch(strToRegExp(`on:{\"change\":function($event){y=$event.target.attr.value.trim()}}`))\n    expect(staticRenderFns).toEqual([])\n    expect(errors).toEqual([])\n  })\n})\n"
  },
  {
    "path": "vue/test/weex/helpers/index.js",
    "content": "import fs from 'fs'\nimport path from 'path'\nimport * as Vue from '../../../packages/weex-vue-framework'\nimport { compile } from '../../../packages/weex-template-compiler'\nimport WeexRuntime from 'weex-js-runtime'\nimport styler from 'weex-styler'\n\nconst styleRE = /<\\s*style\\s*\\w*>([^(<\\/)]*)<\\/\\s*style\\s*>/g\nconst scriptRE = /<\\s*script.*>([^]*)<\\/\\s*script\\s*>/\nconst templateRE = /<\\s*template\\s*([^>]*)>([^]*)<\\/\\s*template\\s*>/\n\nexport function readFile (filename) {\n  return fs.readFileSync(path.resolve(__dirname, '../cases/', filename), 'utf8')\n}\n\nexport function readObject (filename) {\n  return (new Function(`return ${readFile(filename)}`))()\n}\n\nconsole.debug = () => {}\n\n// http://stackoverflow.com/a/35478115\nconst matchOperatorsRe = /[|\\\\{}()[\\]^$+*?.]/g\nexport function strToRegExp (str) {\n  return new RegExp(str.replace(matchOperatorsRe, '\\\\$&'))\n}\n\nfunction parseStatic (fns) {\n  return '[' + fns.map(fn => `function () { ${fn} }`).join(',') + ']'\n}\n\nexport function compileAndStringify (template) {\n  const { render, staticRenderFns } = compile(template)\n  return {\n    render: `function () { ${render} }`,\n    staticRenderFns: parseStatic(staticRenderFns)\n  }\n}\n\n/**\n * Compile *.vue file into js code\n * @param {string} source raw text of *.vue file\n * @param {string} componentName whether compile to a component\n */\nexport function compileVue (source, componentName) {\n  return new Promise((resolve, reject) => {\n    if (!templateRE.test(source)) {\n      return reject('No Template!')\n    }\n    const scriptMatch = scriptRE.exec(source)\n    const script = scriptMatch ? scriptMatch[1] : ''\n    const templateMatch = templateRE.exec(source)\n    const compileOptions = {}\n    if (/\\s*recyclable\\=?/i.test(templateMatch[1])) {\n      compileOptions.recyclable = true\n    }\n    const res = compile(templateMatch[2], compileOptions)\n\n    const name = 'test_case_' + (Math.random() * 99999999).toFixed(0)\n    const generateCode = styles => (`\n      try { weex.document.registerStyleSheets(\"${name}\", [${JSON.stringify(styles)}]) } catch(e) {};\n      var ${name} = Object.assign({\n        _scopeId: \"${name}\",\n        style: ${JSON.stringify(styles)},\n        render: function () { ${res.render} },\n        ${res['@render'] ? ('\"@render\": function () {' + res['@render'] + '},') : ''}\n        staticRenderFns: ${parseStatic(res.staticRenderFns)},\n      }, (function(){\n        var module = { exports: {} };\n        ${script};\n        return module.exports;\n      })());\n    ` + (componentName\n        ? `Vue.component('${componentName}', ${name});\\n`\n        : `${name}.el = 'body';new Vue(${name});`)\n    )\n\n    let cssText = ''\n    let styleMatch = null\n    while ((styleMatch = styleRE.exec(source))) {\n      cssText += `\\n${styleMatch[1]}\\n`\n    }\n    styler.parse(cssText, (error, result) => {\n      if (error) {\n        return reject(error)\n      }\n      resolve(generateCode(result.jsonStyle))\n    })\n    resolve(generateCode({}))\n  })\n}\n\nexport function compileWithDeps (entryPath, deps) {\n  return new Promise((resolve, reject) => {\n    if (Array.isArray(deps)) {\n      Promise.all(deps.map(dep => {\n        return compileVue(readFile(dep.path), dep.name).catch(reject)\n      })).then(depCodes => {\n        compileVue(readFile(entryPath)).then(entryCode => {\n          resolve(depCodes.join('\\n') + entryCode)\n        }).catch(reject)\n      }).catch(reject)\n    }\n  })\n}\n\nfunction isObject (object) {\n  return object !== null && typeof object === 'object'\n}\n\nfunction isEmptyObject (object) {\n  return isObject(object) && Object.keys(object).length < 1\n}\n\nfunction omitUseless (object) {\n  if (isObject(object)) {\n    delete object.ref\n    for (const key in object) {\n      omitUseless(object[key])\n      if (key === '@styleScope' ||\n        key === '@templateId' ||\n        key === 'bindingExpression') {\n        delete object[key]\n      }\n      if (key.charAt(0) !== '@' &&\n        (isEmptyObject(object[key]) || object[key] === undefined)) {\n        delete object[key]\n      }\n    }\n  }\n  return object\n}\n\nexport function getRoot (instance) {\n  return omitUseless(instance.$getRoot())\n}\n\n// Get all binding events in the instance\nexport function getEvents (instance) {\n  const events = []\n  const recordEvent = node => {\n    if (!node) { return }\n    if (Array.isArray(node.event)) {\n      node.event.forEach(type => {\n        events.push({ ref: node.ref, type })\n      })\n    }\n    if (Array.isArray(node.children)) {\n      node.children.forEach(recordEvent)\n    }\n  }\n  recordEvent(instance.$getRoot())\n  return events\n}\n\nexport function fireEvent (instance, ref, type, event = {}) {\n  const el = instance.document.getRef(ref)\n  if (el) {\n    instance.document.fireEvent(el, type, event)\n  }\n}\n\nexport function createInstance (id, code, ...args) {\n  WeexRuntime.config.frameworks = { Vue }\n  const context = WeexRuntime.init(WeexRuntime.config)\n  context.registerModules({\n    timer: ['setTimeout', 'setInterval']\n  })\n  const instance = context.createInstance(id, `// { \"framework\": \"Vue\" }\\n${code}`, ...args) || {}\n  instance.document = context.getDocument(id)\n  instance.$getRoot = () => context.getRoot(id)\n  instance.$refresh = (data) => context.refreshInstance(id, data)\n  instance.$destroy = () => {\n    delete instance.document\n    context.destroyInstance(id)\n  }\n  instance.$triggerHook = (id, hook, args) => {\n    instance.document.taskCenter.triggerHook(id, 'lifecycle', hook, { args })\n  }\n  return instance\n}\n\nexport function compileAndExecute (template, additional = '') {\n  return new Promise(resolve => {\n    const id = String(Date.now() * Math.random())\n    const { render, staticRenderFns } = compile(template)\n    const instance = createInstance(id, `\n      new Vue({\n        el: '#whatever',\n        render: function () { ${render} },\n        staticRenderFns: ${parseStatic(staticRenderFns)},\n        ${additional}\n      })\n    `)\n    setTimeout(() => resolve(instance), 10)\n  })\n}\n\nexport function syncPromise (arr) {\n  let p = Promise.resolve()\n  arr.forEach(item => {\n    p = p.then(item)\n  })\n  return p\n}\n\nexport function checkRefresh (instance, data, checker) {\n  return () => new Promise(res => {\n    instance.$refresh(data)\n    setTimeout(() => {\n      checker(getRoot(instance))\n      res()\n    })\n  })\n}\n\nexport function addTaskHook (hook) {\n  global.callNative = function callNative (id, tasks) {\n    if (Array.isArray(tasks) && typeof hook === 'function') {\n      tasks.forEach(task => {\n        hook(id, {\n          module: task.module,\n          method: task.method,\n          args: Array.from(task.args)\n        })\n      })\n    }\n  }\n}\n\nexport function resetTaskHook () {\n  delete global.callNative\n}\n"
  },
  {
    "path": "vue/test/weex/jasmine.json",
    "content": "{\n  \"spec_dir\": \"test/weex\",\n  \"spec_files\": [\n    \"**/*[sS]pec.js\"\n  ],\n  \"helpers\": [\n    \"../../node_modules/babel-register/lib/node.js\"\n  ]\n}\n"
  },
  {
    "path": "vue/test/weex/runtime/attrs.spec.js",
    "content": "import { getRoot, fireEvent, compileAndExecute } from '../helpers/index'\n\ndescribe('generate attribute', () => {\n  it('should be generated', (done) => {\n    compileAndExecute(`\n      <div>\n        <text value=\"Hello World\" style=\"font-size: 100\"></text>\n      </div>\n    `).then(instance => {\n      expect(getRoot(instance)).toEqual({\n        type: 'div',\n        children: [{\n          type: 'text',\n          style: { fontSize: '100' },\n          attr: { value: 'Hello World' }\n        }]\n      })\n      done()\n    }).catch(e => done.fail(e))\n  })\n\n  it('should be updated', (done) => {\n    compileAndExecute(`\n      <div @click=\"foo\">\n        <text :value=\"x\"></text>\n      </div>\n    `, `\n      data: { x: 'Hello World' },\n      methods: {\n        foo: function () {\n          this.x = 'Hello Vue'\n        }\n      }\n    `).then(instance => {\n      expect(getRoot(instance)).toEqual({\n        type: 'div',\n        event: ['click'],\n        children: [\n          { type: 'text', attr: { value: 'Hello World' }}\n        ]\n      })\n      fireEvent(instance, '_root', 'click')\n      return instance\n    }).then(instance => {\n      expect(getRoot(instance)).toEqual({\n        type: 'div',\n        event: ['click'],\n        children: [\n          { type: 'text', attr: { value: 'Hello Vue' }}\n        ]\n      })\n      done()\n    }).catch(e => done.fail(e))\n  })\n\n  it('should be cleared', (done) => {\n    compileAndExecute(`\n      <div @click=\"foo\">\n        <text :value=\"x\"></text>\n      </div>\n    `, `\n      data: { x: 'Hello World' },\n      methods: {\n        foo: function () {\n          this.x = ''\n        }\n      }\n    `).then(instance => {\n      expect(getRoot(instance)).toEqual({\n        type: 'div',\n        event: ['click'],\n        children: [\n          { type: 'text', attr: { value: 'Hello World' }}\n        ]\n      })\n      fireEvent(instance, '_root', 'click')\n      return instance\n    }).then(instance => {\n      expect(getRoot(instance)).toEqual({\n        type: 'div',\n        event: ['click'],\n        children: [\n          { type: 'text', attr: { value: '' }}\n        ]\n      })\n      done()\n    })\n  })\n})\n"
  },
  {
    "path": "vue/test/weex/runtime/class.spec.js",
    "content": "import { getRoot, fireEvent, compileAndExecute } from '../helpers/index'\n\ndescribe('generate class', () => {\n  it('should be generated', () => {\n    compileAndExecute(`\n      <div>\n        <text class=\"a b c\">Hello World</text>\n      </div>\n    `, `\n      style: {\n        a: { fontSize: '100' },\n        b: { color: '#ff0000' },\n        c: { fontWeight: 'bold' }\n      }\n    `).then(instance => {\n      expect(getRoot(instance)).toEqual({\n        type: 'div',\n        children: [{\n          type: 'text',\n          classList: ['a', 'b', 'c'],\n          attr: { value: 'Hello World' }\n        }]\n      })\n    })\n  })\n\n  it('should be updated', (done) => {\n    compileAndExecute(`\n      <div @click=\"foo\">\n        <text :class=\"['a', x]\">Hello World</text>\n      </div>\n    `, `\n      data: { x: 'b' },\n      style: {\n        a: { fontSize: '100' },\n        b: { color: '#ff0000' },\n        c: { fontWeight: 'bold' },\n        d: {\n          color: '#0000ff',\n          fontWeight: 'bold'\n        }\n      },\n      methods: {\n        foo: function () {\n          this.x = 'd'\n        }\n      }\n    `).then(instance => {\n      expect(getRoot(instance)).toEqual({\n        type: 'div',\n        event: ['click'],\n        children: [{\n          type: 'text',\n          classList: ['a', 'b'],\n          attr: { value: 'Hello World' }\n        }]\n      })\n      fireEvent(instance, '_root', 'click')\n      return instance\n    }).then(instance => {\n      expect(getRoot(instance)).toEqual({\n        type: 'div',\n        event: ['click'],\n        children: [{\n          type: 'text',\n          classList: ['a', 'd'],\n          attr: { value: 'Hello World' }\n        }]\n      })\n      done()\n    })\n  })\n\n  it('should be applied in order', (done) => {\n    compileAndExecute(`\n      <div @click=\"foo\">\n        <text :class=\"arr\">Hello World</text>\n      </div>\n    `, `\n      data: {\n        arr: ['b', 'a']\n      },\n      style: {\n        a: { color: '#ff0000' },\n        b: { color: '#00ff00' },\n        c: { color: '#0000ff' }\n      },\n      methods: {\n        foo: function () {\n          this.arr.push('c')\n        }\n      }\n    `).then(instance => {\n      expect(getRoot(instance)).toEqual({\n        type: 'div',\n        event: ['click'],\n        children: [{\n          type: 'text',\n          classList: ['b', 'a'],\n          attr: { value: 'Hello World' }\n        }]\n      })\n      fireEvent(instance, '_root', 'click')\n      return instance\n    }).then(instance => {\n      expect(getRoot(instance)).toEqual({\n        type: 'div',\n        event: ['click'],\n        children: [{\n          type: 'text',\n          classList: ['b', 'a', 'c'],\n          attr: { value: 'Hello World' }\n        }]\n      })\n      done()\n    })\n  })\n\n  it('should be cleared', (done) => {\n    compileAndExecute(`\n      <div @click=\"foo\">\n        <text :class=\"['a', x]\">Hello World</text>\n      </div>\n    `, `\n      data: { x: 'b' },\n      style: {\n        a: { fontSize: '100' },\n        b: { color: '#ff0000' },\n        c: { fontWeight: 'bold' }\n      },\n      methods: {\n        foo: function () {\n          this.x = 'c'\n        }\n      }\n    `).then(instance => {\n      expect(getRoot(instance)).toEqual({\n        type: 'div',\n        event: ['click'],\n        children: [{\n          type: 'text',\n          classList: ['a', 'b'],\n          attr: { value: 'Hello World' }\n        }]\n      })\n      fireEvent(instance, '_root', 'click')\n      return instance\n    }).then(instance => {\n      expect(getRoot(instance)).toEqual({\n        type: 'div',\n        event: ['click'],\n        children: [{\n          type: 'text',\n          classList: ['a', 'c'],\n          attr: { value: 'Hello World' }\n        }]\n      })\n      done()\n    })\n  })\n})\n"
  },
  {
    "path": "vue/test/weex/runtime/components/richtext.spec.js",
    "content": "import {\n  compileAndStringify,\n  getRoot,\n  fireEvent,\n  createInstance\n} from '../../helpers/index'\n\nfunction compileSnippet (snippet, additional) {\n  const { render, staticRenderFns } = compileAndStringify(`<div>${snippet}</div>`)\n  const id = String(Date.now() * Math.random())\n  const instance = createInstance(id, `\n    new Vue({\n      el: 'body',\n      render: ${render},\n      staticRenderFns: ${staticRenderFns},\n      ${additional}\n    })\n  `)\n  return getRoot(instance).children[0]\n}\n\ndescribe('richtext component', () => {\n  it('with no child', () => {\n    expect(compileSnippet(`\n      <richtext></richtext>\n    `)).toEqual({\n      type: 'richtext'\n    })\n  })\n\n  it('with single text node', () => {\n    expect(compileSnippet(`\n      <richtext>single</richtext>\n    `)).toEqual({\n      type: 'richtext',\n      attr: {\n        value: [{\n          type: 'span',\n          attr: {\n            value: 'single'\n          }\n        }]\n      }\n    })\n  })\n\n  describe('span', () => {\n    it('single node', () => {\n      expect(compileSnippet(`\n        <richtext>\n          <span>single</span>\n        </richtext>\n      `)).toEqual({\n        type: 'richtext',\n        attr: {\n          value: [{\n            type: 'span',\n            attr: {\n              value: 'single'\n            }\n          }]\n        }\n      })\n    })\n\n    it('multiple node', () => {\n      expect(compileSnippet(`\n        <richtext>\n          <span>AAA</span>\n          <span>BBB</span>\n        </richtext>\n      `)).toEqual({\n        type: 'richtext',\n        attr: {\n          value: [{\n            type: 'span',\n            attr: { value: 'AAA' }\n          }, {\n            type: 'span',\n            attr: { value: 'BBB' }\n          }]\n        }\n      })\n    })\n\n    it('with raw text', () => {\n      expect(compileSnippet(`\n        <richtext>\n          AAA\n          <span>BBB</span>CCC\n          <span>DDD</span>\n        </richtext>\n      `)).toEqual({\n        type: 'richtext',\n        attr: {\n          value: [{\n            type: 'span',\n            attr: { value: 'AAA' }\n          }, {\n            type: 'span',\n            attr: { value: 'BBB' }\n          }, {\n            type: 'span',\n            attr: { value: 'CCC' }\n          }, {\n            type: 'span',\n            attr: { value: 'DDD' }\n          }]\n        }\n      })\n    })\n  })\n\n  describe('a', () => {\n    it('single node', () => {\n      expect(compileSnippet(`\n        <richtext>\n          <a href=\"http://whatever.com\"></a>\n        </richtext>\n      `)).toEqual({\n        type: 'richtext',\n        attr: {\n          value: [{\n            type: 'a',\n            attr: { href: 'http://whatever.com' }\n          }]\n        }\n      })\n    })\n\n    it('multiple node', () => {\n      expect(compileSnippet(`\n        <richtext>\n          <a href=\"http://a.whatever.com\"></a>\n          <a href=\"http://b.whatever.com\"></a>\n        </richtext>\n      `)).toEqual({\n        type: 'richtext',\n        attr: {\n          value: [{\n            type: 'a',\n            attr: { href: 'http://a.whatever.com' }\n          }, {\n            type: 'a',\n            attr: { href: 'http://b.whatever.com' }\n          }]\n        }\n      })\n    })\n  })\n\n  describe('image', () => {\n    it('single node', () => {\n      expect(compileSnippet(`\n        <richtext>\n          <image src=\"path/to/profile.png\"></image>\n        </richtext>\n      `)).toEqual({\n        type: 'richtext',\n        attr: {\n          value: [{\n            type: 'image',\n            attr: { src: 'path/to/profile.png' }\n          }]\n        }\n      })\n    })\n\n    it('multiple node', () => {\n      expect(compileSnippet(`\n        <richtext>\n          <image src=\"path/to/A.png\"></image>\n          <image src=\"path/to/B.png\"></image>\n        </richtext>\n      `)).toEqual({\n        type: 'richtext',\n        attr: {\n          value: [{\n            type: 'image',\n            attr: { src: 'path/to/A.png' }\n          }, {\n            type: 'image',\n            attr: { src: 'path/to/B.png' }\n          }]\n        }\n      })\n    })\n\n    it('with width and height', () => {\n      expect(compileSnippet(`\n        <richtext>\n          <image\n            style=\"width:150px;height:150px;\"\n            src=\"path/to/profile.png\">\n          </image>\n        </richtext>\n      `)).toEqual({\n        type: 'richtext',\n        attr: {\n          value: [{\n            type: 'image',\n            style: { width: '150px', height: '150px' },\n            attr: { src: 'path/to/profile.png' }\n          }]\n        }\n      })\n    })\n  })\n\n  describe('nested', () => {\n    it('span', () => {\n      expect(compileSnippet(`\n        <richtext>\n          <span>AAA\n            <span>\n              <span>BBB</span>\n              <span><span>CCC</span>DDD</span>\n            </span>\n          </span>\n        </richtext>\n      `)).toEqual({\n        type: 'richtext',\n        attr: {\n          value: [{\n            type: 'span',\n            children: [{\n              type: 'span',\n              attr: { value: 'AAA' }\n            }, {\n              type: 'span',\n              children: [{\n                type: 'span',\n                attr: { value: 'BBB' }\n              }, {\n                type: 'span',\n                children: [{\n                  type: 'span',\n                  attr: { value: 'CCC' }\n                }, {\n                  type: 'span',\n                  attr: { value: 'DDD' }\n                }]\n              }]\n            }]\n          }]\n        }\n      })\n    })\n\n    it('image and a', () => {\n      expect(compileSnippet(`\n        <richtext>\n          <span>title</span>\n          <a href=\"http://remote.com/xx.js\">\n            <span><span>name</span></span>\n            <image src=\"path/to/yy.gif\"></image>\n          </a>\n        </richtext>\n      `)).toEqual({\n        type: 'richtext',\n        attr: {\n          value: [{\n            type: 'span',\n            attr: { value: 'title' }\n          }, {\n            type: 'a',\n            attr: { href: 'http://remote.com/xx.js' },\n            children: [{\n              type: 'span',\n              children: [{\n                type: 'span',\n                attr: { value: 'name' }\n              }]\n            }, {\n              type: 'image',\n              attr: { src: 'path/to/yy.gif' }\n            }]\n          }]\n        }\n      })\n    })\n  })\n\n  describe('with styles', () => {\n    it('inline', () => {\n      expect(compileSnippet(`\n        <richtext>\n          <span style=\"font-size:16px;color:#FF6600;\">ABCD</span>\n          <image style=\"width:33.33px;height:66.67px\" src=\"path/to/A.png\"></image>\n        </richtext>\n      `)).toEqual({\n        type: 'richtext',\n        attr: {\n          value: [{\n            type: 'span',\n            style: { fontSize: '16px', color: '#FF6600' },\n            attr: { value: 'ABCD' }\n          }, {\n            type: 'image',\n            style: { width: '33.33px', height: '66.67px' },\n            attr: { src: 'path/to/A.png' }\n          }]\n        }\n      })\n    })\n\n    it('class list', () => {\n      expect(compileSnippet(`\n        <richtext>\n          <image class=\"icon\" src=\"path/to/A.png\"></image>\n          <span class=\"title large\">ABCD</span>\n        </richtext>\n      `, `\n        style: {\n          title: { color: '#FF6600' },\n          large: { fontSize: 24 },\n          icon: { width: 40, height: 60 }\n        }\n      `)).toEqual({\n        type: 'richtext',\n        attr: {\n          value: [{\n            type: 'image',\n            style: { width: 40, height: 60 },\n            attr: { src: 'path/to/A.png' }\n          }, {\n            type: 'span',\n            style: { fontSize: 24, color: '#FF6600' },\n            attr: { value: 'ABCD' }\n          }]\n        }\n      })\n    })\n  })\n\n  describe('data binding', () => {\n    it('simple', () => {\n      expect(compileSnippet(`\n        <richtext>\n          <span>{{name}}</span>\n        </richtext>\n      `, `data: { name: 'ABCDEFG' }`)).toEqual({\n        type: 'richtext',\n        attr: {\n          value: [{\n            type: 'span',\n            attr: { value: 'ABCDEFG' }\n          }]\n        }\n      })\n    })\n\n    it('nested', () => {\n      expect(compileSnippet(`\n        <richtext>\n          <span>{{a}}</span>\n          <span>{{b}}<span>{{c.d}}</span></span>\n          <span>{{e}}</span>\n        </richtext>\n      `, `\n        data: { a: 'A', b: 'B', c: { d: 'CD' }, e: 'E' }\n      `)).toEqual({\n        type: 'richtext',\n        attr: {\n          value: [{\n            type: 'span',\n            attr: { value: 'A' }\n          }, {\n            type: 'span',\n            children: [{\n              type: 'span',\n              attr: { value: 'B' }\n            }, {\n              type: 'span',\n              attr: { value: 'CD' }\n            }]\n          }, {\n            type: 'span',\n            attr: { value: 'E' }\n          }]\n        }\n      })\n    })\n\n    it('update', () => {\n      expect(compileSnippet(`\n        <richtext>\n          <span>{{name}}</span>\n        </richtext>\n      `, `\n        data: { name: 'default' },\n        created: function () {\n          this.name = 'updated'\n        }\n      `)).toEqual({\n        type: 'richtext',\n        attr: {\n          value: [{\n            type: 'span',\n            attr: { value: 'updated' }\n          }]\n        }\n      })\n    })\n\n    it('attribute', () => {\n      expect(compileSnippet(`\n        <richtext>\n          <span :label=\"label\">{{name}}</span>\n        </richtext>\n      `, `\n        data: {\n          label: 'uid',\n          name: '10100'\n        }\n      `)).toEqual({\n        type: 'richtext',\n        attr: {\n          value: [{\n            type: 'span',\n            attr: {\n              label: 'uid',\n              value: '10100'\n            }\n          }]\n        }\n      })\n    })\n\n    it('update attribute', () => {\n      expect(compileSnippet(`\n        <richtext>\n          <span :label=\"label\">{{name}}</span>\n        </richtext>\n      `, `\n        data: {\n          label: 'name',\n          name: 'Hanks'\n        },\n        created: function () {\n          this.label = 'uid';\n          this.name = '10100';\n        }\n      `)).toEqual({\n        type: 'richtext',\n        attr: {\n          value: [{\n            type: 'span',\n            attr: {\n              label: 'uid',\n              value: '10100'\n            }\n          }]\n        }\n      })\n    })\n\n    it('inline style', () => {\n      expect(compileSnippet(`\n        <richtext>\n          <span :style=\"styleObject\">ABCD</span>\n          <span :style=\"{ textAlign: align, color: 'red' }\">EFGH</span>\n        </richtext>\n      `, `\n        data: {\n          styleObject: { fontSize: '32px', color: '#F6F660' },\n          align: 'center'\n        }\n      `)).toEqual({\n        type: 'richtext',\n        attr: {\n          value: [{\n            type: 'span',\n            style: { fontSize: '32px', color: '#F6F660' },\n            attr: { value: 'ABCD' }\n          }, {\n            type: 'span',\n            style: { textAlign: 'center', color: 'red' },\n            attr: { value: 'EFGH' }\n          }]\n        }\n      })\n    })\n\n    it('class list', () => {\n      expect(compileSnippet(`\n        <richtext>\n          <image :class=\"classList\" src=\"path/to/A.png\"></image>\n          <span :class=\"['title', size]\">ABCD</span>\n          <span class=\"large\" style=\"color:#F6F0F4\">EFGH</span>\n        </richtext>\n      `, `\n        style: {\n          title: { color: '#FF6600' },\n          large: { fontSize: 24 },\n          icon: { width: 40, height: 60 }\n        },\n        data: {\n          classList: ['unknown'],\n          size: 'small'\n        },\n        created: function () {\n          this.classList = ['icon'];\n          this.size = 'large';\n        }\n      `)).toEqual({\n        type: 'richtext',\n        attr: {\n          value: [{\n            type: 'image',\n            style: { width: 40, height: 60 },\n            attr: { src: 'path/to/A.png' }\n          }, {\n            type: 'span',\n            style: { fontSize: 24, color: '#FF6600' },\n            attr: { value: 'ABCD' }\n          }, {\n            type: 'span',\n            style: { fontSize: 24, color: '#F6F0F4' },\n            attr: { value: 'EFGH' }\n          }]\n        }\n      })\n    })\n\n    it('update inline style', () => {\n      expect(compileSnippet(`\n        <richtext>\n          <span :style=\"styleObject\">ABCD</span>\n          <span :style=\"{ textAlign: align, color: 'red' }\">EFGH</span>\n        </richtext>\n      `, `\n        data: {\n          styleObject: { fontSize: '32px', color: '#F6F660' }\n        },\n        created: function () {\n          this.styleObject = { fontSize: '24px', color: 'blue' }\n          this.styleObject.color = '#ABCDEF'\n          this.align = 'left'\n        }\n      `)).toEqual({\n        type: 'richtext',\n        attr: {\n          value: [{\n            type: 'span',\n            style: { fontSize: '24px', color: '#ABCDEF' },\n            attr: { value: 'ABCD' }\n          }, {\n            type: 'span',\n            style: { textAlign: 'left', color: 'red' },\n            attr: { value: 'EFGH' }\n          }]\n        }\n      })\n    })\n  })\n\n  describe('itself', () => {\n    it('inline styles', () => {\n      expect(compileSnippet(`\n        <richtext style=\"background-color:red\">\n          <span>empty</span>\n        </richtext>\n      `)).toEqual({\n        type: 'richtext',\n        style: { backgroundColor: 'red' },\n        attr: {\n          value: [{\n            type: 'span',\n            attr: { value: 'empty' }\n          }]\n        }\n      })\n    })\n\n    it('class list', () => {\n      expect(compileSnippet(`\n        <richtext class=\"title\">\n          <span class=\"large\">ABCD</span>\n        </richtext>\n      `, `\n        style: {\n          title: { backgroundColor: '#FF6600', height: 200 },\n          large: { fontSize: 24 }\n        }\n      `)).toEqual({\n        type: 'richtext',\n        classList: ['title'],\n        attr: {\n          value: [{\n            type: 'span',\n            style: { fontSize: 24 },\n            attr: { value: 'ABCD' }\n          }]\n        }\n      })\n    })\n\n    it('update styles', () => {\n      expect(compileSnippet(`\n        <richtext :class=\"classList\" :style=\"{ backgroundColor: color }\">\n          <span class=\"large\">ABCD</span>\n        </richtext>\n      `, `\n        data: { classList: ['unknow'], color: '#FF6600' },\n        style: {\n          title: { height: 200 },\n          large: { fontSize: 24 }\n        },\n        created: function () {\n          this.classList = ['title']\n        }\n      `)).toEqual({\n        type: 'richtext',\n        classList: ['title'],\n        style: { backgroundColor: '#FF6600' },\n        attr: {\n          value: [{\n            type: 'span',\n            style: { fontSize: 24 },\n            attr: { value: 'ABCD' }\n          }]\n        }\n      })\n    })\n\n    it('bind events', (done) => {\n      const { render, staticRenderFns } = compileAndStringify(`\n        <div>\n          <richtext @click=\"handler\">\n            <span>Label: {{label}}</span>\n          </richtext>\n        </div>\n      `)\n      const id = String(Date.now() * Math.random())\n      const instance = createInstance(id, `\n        new Vue({\n          el: 'body',\n          render: ${render},\n          staticRenderFns: ${staticRenderFns},\n          data: { label: 'AAA' },\n          methods: {\n            handler: function () {\n              this.label = 'BBB'\n            }\n          }\n        })\n      `)\n      const richtext = instance.document.body.children[0]\n      fireEvent(instance, richtext.ref, 'click')\n      setTimeout(() => {\n        expect(getRoot(instance).children[0]).toEqual({\n          type: 'richtext',\n          event: ['click'],\n          attr: {\n            value: [{\n              type: 'span',\n              attr: { value: 'Label: BBB' }\n            }]\n          }\n        })\n        done()\n      }, 0)\n    })\n\n    it('v-for', () => {\n      expect(compileSnippet(`\n        <div>\n          <richtext v-for=\"k in labels\">\n            <span>{{k}}</span>\n          </richtext>\n        </div>\n      `, `\n        data: {\n          labels: ['A', 'B', 'C']\n        }\n      `)).toEqual({\n        type: 'div',\n        children: [{\n          type: 'richtext',\n          attr: { value: [{ type: 'span', attr: { value: 'A' }}] }\n        }, {\n          type: 'richtext',\n          attr: { value: [{ type: 'span', attr: { value: 'B' }}] }\n        }, {\n          type: 'richtext',\n          attr: { value: [{ type: 'span', attr: { value: 'C' }}] }\n        }]\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "vue/test/weex/runtime/events.spec.js",
    "content": "import { getRoot, fireEvent, compileAndStringify, compileAndExecute } from '../helpers/index'\n\ndescribe('generate events', () => {\n  it('should be bound and fired for native component', (done) => {\n    compileAndExecute(`\n      <div @click=\"foo\">\n        <text>Hello {{x}}</text>\n      </div>\n    `, `\n      data: { x: 'World' },\n      methods: {\n        foo: function () {\n          this.x = 'Weex'\n        }\n      }\n    `).then(instance => {\n      expect(getRoot(instance)).toEqual({\n        type: 'div',\n        event: ['click'],\n        children: [{\n          type: 'text',\n          attr: { value: 'Hello World' }\n        }]\n      })\n      fireEvent(instance, '_root', 'click')\n      return instance\n    }).then(instance => {\n      expect(getRoot(instance)).toEqual({\n        type: 'div',\n        event: ['click'],\n        children: [{\n          type: 'text',\n          attr: { value: 'Hello Weex' }\n        }]\n      })\n      done()\n    })\n  })\n\n  it('should be bound and fired by custom component', (done) => {\n    const { render, staticRenderFns } = compileAndStringify(`<text>Hello {{x}}</text>`)\n    compileAndExecute(`\n      <div>\n        <text>Hello {{x}}</text>\n        <sub @click=\"foo\" @click.native=\"bar\"></sub>\n      </div>\n    `, `\n      data: { x: 'World' },\n      components: {\n        sub: {\n          data: function () {\n            return { x: 'Sub' }\n          },\n          render: ${render},\n          staticRenderFns: ${staticRenderFns},\n          created: function () {\n            this.$emit('click')\n          }\n        }\n      },\n      methods: {\n        foo: function () {\n          this.x = 'Foo'\n        },\n        bar: function () {\n          this.x = 'Bar'\n        }\n      }\n    `).then(instance => {\n      expect(getRoot(instance)).toEqual({\n        type: 'div',\n        children: [{\n          type: 'text',\n          attr: { value: 'Hello Foo' }\n        }, {\n          type: 'text',\n          event: ['click'],\n          attr: { value: 'Hello Sub' }\n        }]\n      })\n      fireEvent(instance, instance.document.body.children[1].ref, 'click')\n      return instance\n    }).then(instance => {\n      expect(getRoot(instance)).toEqual({\n        type: 'div',\n        children: [{\n          type: 'text',\n          attr: { value: 'Hello Bar' }\n        }, {\n          type: 'text',\n          event: ['click'],\n          attr: { value: 'Hello Sub' }\n        }]\n      })\n      done()\n    })\n  })\n})\n"
  },
  {
    "path": "vue/test/weex/runtime/framework.spec.js",
    "content": "import { getRoot, createInstance } from '../helpers/index'\n\ndescribe('framework APIs', () => {\n  it('createInstance', () => {\n    const id = String(Date.now() * Math.random())\n    const instance = createInstance(id, `\n      new Vue({\n        render: function (createElement) {\n          return createElement('div', {}, [\n            createElement('text', { attrs: { value: 'Hello' }}, [])\n          ])\n        },\n        el: \"body\"\n      })\n    `)\n    expect(getRoot(instance)).toEqual({\n      type: 'div',\n      children: [{ type: 'text', attr: { value: 'Hello' }}]\n    })\n  })\n\n  it('createInstance with config', () => {\n    const id = String(Date.now() * Math.random())\n    const instance = createInstance(id, `\n      new Vue({\n        render: function (createElement) {\n          return createElement('div', {}, [\n            createElement('text', { attrs: { value: JSON.stringify(weex.config) }}, [])\n          ])\n        },\n        el: \"body\"\n      })\n    `, { bundleType: 'Vue', bundleUrl: 'http://example.com/', a: 1, b: 2 })\n    expect(getRoot(instance)).toEqual({\n      type: 'div',\n      children: [{\n        type: 'text',\n        attr: { value: '{\"bundleType\":\"Vue\",\"bundleUrl\":\"http://example.com/\",\"a\":1,\"b\":2,\"env\":{}}' }\n      }]\n    })\n  })\n\n  it('createInstance with external data', () => {\n    const id = String(Date.now() * Math.random())\n    const instance = createInstance(id, `\n      new Vue({\n        data: {\n          a: 1,\n          b: 2\n        },\n        render: function (createElement) {\n          return createElement('div', {}, [\n            createElement('text', { attrs: { value: this.a + '-' + this.b }}, [])\n          ])\n        },\n        el: \"body\"\n      })\n    `, undefined, { a: 111 })\n    expect(getRoot(instance)).toEqual({\n      type: 'div',\n      children: [{ type: 'text', attr: { value: '111-2' }}]\n    })\n  })\n\n  it('destroyInstance', (done) => {\n    const id = String(Date.now() * Math.random())\n    const instance = createInstance(id, `\n      new Vue({\n        data: {\n          x: 'Hello'\n        },\n        render: function (createElement) {\n          return createElement('div', {}, [\n            createElement('text', { attrs: { value: this.x }}, [])\n          ])\n        },\n        el: \"body\"\n      })\n    `)\n    expect(getRoot(instance)).toEqual({\n      type: 'div',\n      children: [{ type: 'text', attr: { value: 'Hello' }}]\n    })\n    instance.$destroy()\n    setTimeout(() => {\n      expect(instance.document).toBeUndefined()\n      expect(instance.app).toBeUndefined()\n      done()\n    }, 0)\n  })\n\n  it('refreshInstance', (done) => {\n    const id = String(Date.now() * Math.random())\n    const instance = createInstance(id, `\n      new Vue({\n        data: {\n          x: 'Hello'\n        },\n        render: function (createElement) {\n          return createElement('div', {}, [\n            createElement('text', { attrs: { value: this.x }}, [])\n          ])\n        },\n        el: \"body\"\n      })\n    `)\n    expect(getRoot(instance)).toEqual({\n      type: 'div',\n      children: [{ type: 'text', attr: { value: 'Hello' }}]\n    })\n    instance.$refresh({ x: 'World' })\n    setTimeout(() => {\n      expect(getRoot(instance)).toEqual({\n        type: 'div',\n        children: [{ type: 'text', attr: { value: 'World' }}]\n      })\n      instance.$destroy()\n      const result = instance.$refresh({ x: 'World' })\n      expect(result instanceof Error).toBe(true)\n      done()\n    })\n  })\n\n  it('registering global assets', () => {\n    const id = String(Date.now() * Math.random())\n    const instance = createInstance(id, `\n      Vue.component('test', {\n        render (h) {\n          return h('div', 'Hello')\n        }\n      })\n      new Vue({\n        render (h) {\n          return h('test')\n        },\n        el: 'body'\n      })\n    `)\n    expect(getRoot(instance)).toEqual({\n      type: 'div',\n      children: [{ type: 'text', attr: { value: 'Hello' }}]\n    })\n  })\n\n  it('adding prototype methods', () => {\n    const id = String(Date.now() * Math.random())\n    const instance = createInstance(id, `\n      Vue.prototype.$test = () => 'Hello'\n      const Test = {\n        render (h) {\n          return h('div', this.$test())\n        }\n      }\n      new Vue({\n        render (h) {\n          return h(Test)\n        },\n        el: 'body'\n      })\n    `)\n    expect(getRoot(instance)).toEqual({\n      type: 'div',\n      children: [{ type: 'text', attr: { value: 'Hello' }}]\n    })\n  })\n\n  it('using global mixins', () => {\n    const id = String(Date.now() * Math.random())\n    const instance = createInstance(id, `\n      Vue.mixin({\n        created () {\n          this.test = true\n        }\n      })\n      const Test = {\n        data: () => ({ test: false }),\n        render (h) {\n          return h('div', this.test ? 'Hello' : 'nope')\n        }\n      }\n      new Vue({\n        data: { test: false },\n        render (h) {\n          return this.test ? h(Test) : h('p')\n        },\n        el: 'body'\n      })\n    `)\n    expect(getRoot(instance)).toEqual({\n      type: 'div',\n      children: [{ type: 'text', attr: { value: 'Hello' }}]\n    })\n  })\n})\n"
  },
  {
    "path": "vue/test/weex/runtime/node.spec.js",
    "content": "import {\n  compileAndStringify,\n  createInstance,\n  getRoot,\n  syncPromise,\n  checkRefresh\n} from '../helpers/index'\n\ndescribe('node in render function', () => {\n  it('should be generated', () => {\n    const id = String(Date.now() * Math.random())\n    const instance = createInstance(id, `\n      new Vue({\n        render: function (createElement) {\n          return createElement('div', {}, [\n            createElement('text', { attrs: { value: 'Hello' }}, [])\n          ])\n        },\n        el: \"body\"\n      })\n    `)\n    expect(getRoot(instance)).toEqual({\n      type: 'div',\n      children: [\n        { type: 'text', attr: { value: 'Hello' }}\n      ]\n    })\n  })\n\n  it('should be generated with all types of text', () => {\n    const id = String(Date.now() * Math.random())\n    const instance = createInstance(id, `\n      new Vue({\n        render: function (createElement) {\n          return createElement('div', {}, [\n            createElement('text', { attrs: { value: 'Hello' }}, []),\n            'World',\n            createElement('text', {}, ['Weex'])\n          ])\n        },\n        el: \"body\"\n      })\n    `)\n    expect(getRoot(instance)).toEqual({\n      type: 'div',\n      children: [\n        { type: 'text', attr: { value: 'Hello' }},\n        { type: 'text', attr: { value: 'World' }},\n        { type: 'text', attr: { value: 'Weex' }}\n      ]\n    })\n  })\n\n  it('should be generated with comments', () => {\n    // todo\n  })\n\n  it('should be generated with module diff', (done) => {\n    const id = String(Date.now() * Math.random())\n    const instance = createInstance(id, `\n      new Vue({\n        data: {\n          counter: 0\n        },\n        methods: {\n          foo: function () {}\n        },\n        render: function (createElement) {\n          switch (this.counter) {\n            case 1:\n            return createElement('div', {}, [\n              createElement('text', { attrs: { value: 'World' }}, [])\n            ])\n\n            case 2:\n            return createElement('div', {}, [\n              createElement('text', { attrs: { value: 'World' }, style: { fontSize: 100 }}, [])\n            ])\n\n            case 3:\n            return createElement('div', {}, [\n              createElement('text', {\n                attrs: { value: 'World' },\n                style: { fontSize: 100 },\n                on: { click: this.foo }\n              }, [])\n            ])\n\n            case 4:\n            return createElement('div', {}, [\n              createElement('text', {\n                attrs: { value: 'Weex' },\n                style: { color: '#ff0000' }\n              }, [])\n            ])\n\n            default:\n            return createElement('div', {}, [\n              createElement('text', { attrs: { value: 'Hello' }}, [])\n            ])\n          }\n        },\n        el: \"body\"\n      })\n    `)\n    expect(getRoot(instance)).toEqual({\n      type: 'div',\n      children: [\n        { type: 'text', attr: { value: 'Hello' }}\n      ]\n    })\n\n    syncPromise([\n      checkRefresh(instance, { counter: 1 }, result => {\n        expect(result).toEqual({\n          type: 'div',\n          children: [\n            { type: 'text', attr: { value: 'World' }}\n          ]\n        })\n      }),\n      checkRefresh(instance, { counter: 2 }, result => {\n        expect(result).toEqual({\n          type: 'div',\n          children: [\n            { type: 'text', attr: { value: 'World' }, style: { fontSize: 100 }}\n          ]\n        })\n      }),\n      checkRefresh(instance, { counter: 3 }, result => {\n        expect(result).toEqual({\n          type: 'div',\n          children: [\n            { type: 'text', attr: { value: 'World' }, style: { fontSize: 100 }, event: ['click'] }\n          ]\n        })\n      }),\n      checkRefresh(instance, { counter: 4 }, result => {\n        expect(result).toEqual({\n          type: 'div',\n          children: [\n            { type: 'text', attr: { value: 'Weex' }, style: { fontSize: '', color: '#ff0000' }}\n          ]\n        })\n        done()\n      })\n    ])\n  })\n\n  it('should be generated with sub components', () => {\n    const id = String(Date.now() * Math.random())\n    const instance = createInstance(id, `\n      new Vue({\n        render: function (createElement) {\n          return createElement('div', {}, [\n            createElement('text', { attrs: { value: 'Hello' }}, []),\n            createElement('foo', { props: { x: 'Weex' }})\n          ])\n        },\n        components: {\n          foo: {\n            props: {\n              x: { default: 'World' }\n            },\n            render: function (createElement) {\n              return createElement('text', { attrs: { value: this.x }}, [])\n            }\n          }\n        },\n        el: \"body\"\n      })\n    `)\n    expect(getRoot(instance)).toEqual({\n      type: 'div',\n      children: [\n        { type: 'text', attr: { value: 'Hello' }},\n        { type: 'text', attr: { value: 'Weex' }}\n      ]\n    })\n  })\n\n  it('should be generated with if/for diff', (done) => {\n    const { render, staticRenderFns } = compileAndStringify(`\n      <div>\n        <text v-for=\"item in list\" v-if=\"item.x\">{{item.v}}</text>\n      </div>\n    `)\n    const id = String(Date.now() * Math.random())\n    const instance = createInstance(id, `\n      new Vue({\n        data: {\n          list: [\n            { v: 'Hello', x: true },\n            { v: 'World', x: false },\n            { v: 'Weex', x: true }\n          ]\n        },\n        computed: {\n          x: {\n            get: function () { return 0 },\n            set: function (v) {\n              switch (v) {\n                case 1:\n                this.list[1].x = true\n                break\n                case 2:\n                this.list.push({ v: 'v-if' })\n                break\n                case 3:\n                this.list.push({ v: 'v-for', x: true })\n                break\n                case 4:\n                this.list.splice(1, 2)\n                break\n              }\n            }\n          }\n        },\n        render: ${render},\n        staticRenderFns: ${staticRenderFns},\n        el: \"body\"\n      })\n    `)\n    expect(getRoot(instance)).toEqual({\n      type: 'div',\n      children: [\n        { type: 'text', attr: { value: 'Hello' }},\n        { type: 'text', attr: { value: 'Weex' }}\n      ]\n    })\n\n    syncPromise([\n      checkRefresh(instance, { x: 1 }, result => {\n        expect(result).toEqual({\n          type: 'div',\n          children: [\n            { type: 'text', attr: { value: 'Hello' }},\n            { type: 'text', attr: { value: 'World' }},\n            { type: 'text', attr: { value: 'Weex' }}\n          ]\n        })\n      }),\n      checkRefresh(instance, { x: 2 }, result => {\n        expect(result).toEqual({\n          type: 'div',\n          children: [\n            { type: 'text', attr: { value: 'Hello' }},\n            { type: 'text', attr: { value: 'World' }},\n            { type: 'text', attr: { value: 'Weex' }}\n          ]\n        })\n      }),\n      checkRefresh(instance, { x: 3 }, result => {\n        expect(result).toEqual({\n          type: 'div',\n          children: [\n            { type: 'text', attr: { value: 'Hello' }},\n            { type: 'text', attr: { value: 'World' }},\n            { type: 'text', attr: { value: 'Weex' }},\n            { type: 'text', attr: { value: 'v-for' }}\n          ]\n        })\n      }),\n      checkRefresh(instance, { x: 4 }, result => {\n        expect(result).toEqual({\n          type: 'div',\n          children: [\n            { type: 'text', attr: { value: 'Hello' }},\n            { type: 'text', attr: { value: 'v-for' }}\n          ]\n        })\n        done()\n      })\n    ])\n  })\n\n  it('should be generated with node structure diff', (done) => {\n    const id = String(Date.now() * Math.random())\n    const instance = createInstance(id, `\n      new Vue({\n        data: {\n          counter: 0\n        },\n        render: function (createElement) {\n          switch (this.counter) {\n            case 1:\n            return createElement('div', {}, [\n              createElement('text', { attrs: { value: 'Hello' }}, []),\n              createElement('text', { attrs: { value: 'World' }}, [])\n            ])\n\n            case 2:\n            return createElement('div', {}, [\n              createElement('text', { attrs: { value: 'Hello' }}, []),\n              createElement('text', { attrs: { value: 'World' }}, []),\n              createElement('text', { attrs: { value: 'Weex' }}, [])\n            ])\n\n            case 3:\n            return createElement('div', {}, [\n              createElement('text', { attrs: { value: 'Hello' }}, []),\n              createElement('text', { attrs: { value: 'Weex' }}, [])\n            ])\n\n            case 4:\n            return createElement('div', {}, [\n              createElement('text', { attrs: { value: 'Weex' }}, [])\n            ])\n\n            case 5:\n            return createElement('div', {}, [\n              createElement('text', { attrs: { value: 'Hello' }}, []),\n              createElement('text', { attrs: { value: 'Weex' }}, [])\n            ])\n\n            case 6:\n            return createElement('div', {}, [\n              createElement('input', { attrs: { value: 'Hello' }}, []),\n              createElement('text', { attrs: { value: 'Weex' }}, [])\n            ])\n\n            default:\n            return createElement('div', {}, [\n              createElement('text', { attrs: { value: 'Hello' }}, []),\n            ])\n          }\n        },\n        el: \"body\"\n      })\n    `)\n    expect(getRoot(instance)).toEqual({\n      type: 'div',\n      children: [\n        { type: 'text', attr: { value: 'Hello' }}\n      ]\n    })\n\n    syncPromise([\n      checkRefresh(instance, { counter: 1 }, result => {\n        expect(result).toEqual({\n          type: 'div',\n          children: [\n            { type: 'text', attr: { value: 'Hello' }},\n            { type: 'text', attr: { value: 'World' }}\n          ]\n        })\n      }),\n      checkRefresh(instance, { counter: 2 }, result => {\n        expect(result).toEqual({\n          type: 'div',\n          children: [\n            { type: 'text', attr: { value: 'Hello' }},\n            { type: 'text', attr: { value: 'World' }},\n            { type: 'text', attr: { value: 'Weex' }}\n          ]\n        })\n      }),\n      checkRefresh(instance, { counter: 3 }, result => {\n        expect(result).toEqual({\n          type: 'div',\n          children: [\n            { type: 'text', attr: { value: 'Hello' }},\n            { type: 'text', attr: { value: 'Weex' }}\n          ]\n        })\n      }),\n      checkRefresh(instance, { counter: 4 }, result => {\n        expect(result).toEqual({\n          type: 'div',\n          children: [\n            { type: 'text', attr: { value: 'Weex' }}\n          ]\n        })\n      }),\n      checkRefresh(instance, { counter: 5 }, result => {\n        expect(result).toEqual({\n          type: 'div',\n          children: [\n            { type: 'text', attr: { value: 'Hello' }},\n            { type: 'text', attr: { value: 'Weex' }}\n          ]\n        })\n      }),\n      checkRefresh(instance, { counter: 6 }, result => {\n        expect(result).toEqual({\n          type: 'div',\n          children: [\n            { type: 'input', attr: { value: 'Hello' }},\n            { type: 'text', attr: { value: 'Weex' }}\n          ]\n        })\n        done()\n      })\n    ])\n  })\n\n  it('should be generated with component diff', (done) => {\n    const id = String(Date.now() * Math.random())\n    const instance = createInstance(id, `\n      new Vue({\n        data: {\n          counter: 0\n        },\n        components: {\n          foo: {\n            props: { a: { default: '1' }, b: { default: '2' }},\n            render: function (createElement) {\n              return createElement('text', { attrs: { value: this.a + '-' + this.b }}, [])\n            }\n          },\n          bar: {\n            render: function (createElement) {\n              return createElement('text', { attrs: { value: 'Bar' }, style: { fontSize: 100 }})\n            }\n          },\n          baz: {\n            render: function (createElement) {\n              return createElement('image', { attrs: { src: 'http://example.com/favicon.ico' }})\n            }\n          }\n        },\n        render: function (createElement) {\n          switch (this.counter) {\n            case 1:\n            return createElement('div', {}, [\n              createElement('foo', { props: { a: '111', b: '222' }}, [])\n            ])\n\n            case 2:\n            return createElement('div', {}, [\n              createElement('foo', {}, [])\n            ])\n\n            case 3:\n            return createElement('div', {}, [\n              createElement('bar', {}, [])\n            ])\n\n            case 4:\n            return createElement('div', {}, [\n              createElement('baz', {}, [])\n            ])\n\n            case 5:\n            return createElement('div', {}, [\n              createElement('foo', {}, []),\n              createElement('bar', {}, []),\n              createElement('baz', {}, [])\n            ])\n\n            default:\n            return createElement('div', {}, [\n              createElement('foo', { props: { a: '111' }}, [])\n            ])\n          }\n        },\n        el: \"body\"\n      })\n    `)\n    expect(getRoot(instance)).toEqual({\n      type: 'div',\n      children: [\n        { type: 'text', attr: { value: '111-2' }}\n      ]\n    })\n\n    syncPromise([\n      checkRefresh(instance, { counter: 1 }, result => {\n        expect(result).toEqual({\n          type: 'div',\n          children: [\n            { type: 'text', attr: { value: '111-222' }}\n          ]\n        })\n      }),\n      checkRefresh(instance, { counter: 2 }, result => {\n        expect(result).toEqual({\n          type: 'div',\n          children: [\n            { type: 'text', attr: { value: '1-2' }}\n          ]\n        })\n      }),\n      checkRefresh(instance, { counter: 3 }, result => {\n        expect(result).toEqual({\n          type: 'div',\n          children: [\n            { type: 'text', attr: { value: 'Bar' }, style: { fontSize: 100 }}\n          ]\n        })\n      }),\n      checkRefresh(instance, { counter: 4 }, result => {\n        expect(result).toEqual({\n          type: 'div',\n          children: [\n            { type: 'image', attr: { src: 'http://example.com/favicon.ico' }}\n          ]\n        })\n      }),\n      checkRefresh(instance, { counter: 5 }, result => {\n        expect(result).toEqual({\n          type: 'div',\n          children: [\n            { type: 'text', attr: { value: '1-2' }},\n            { type: 'text', attr: { value: 'Bar' }, style: { fontSize: 100 }},\n            { type: 'image', attr: { src: 'http://example.com/favicon.ico' }}\n          ]\n        })\n        done()\n      })\n    ])\n  })\n})\n"
  },
  {
    "path": "vue/test/weex/runtime/style.spec.js",
    "content": "import { getRoot, fireEvent, compileAndExecute } from '../helpers/index'\n\ndescribe('generate style', () => {\n  it('should be generated', () => {\n    compileAndExecute(`\n      <div>\n        <text style=\"font-size: 100\">Hello World</text>\n      </div>\n    `).then(instance => {\n      expect(getRoot(instance)).toEqual({\n        type: 'div',\n        children: [{\n          type: 'text',\n          style: { fontSize: '100' },\n          attr: { value: 'Hello World' }\n        }]\n      })\n    })\n  })\n\n  it('should be generated by array binding', (done) => {\n    compileAndExecute(`\n      <div>\n        <text :style=\"[x, y]\" @click=\"foo\">Hello {{z}}</text>\n      </div>\n    `, `\n      data: {\n        x: { fontSize: 100, color: '#00ff00' },\n        y: { color: '#ff0000', fontWeight: 'bold' },\n        z: 'World'\n      },\n      methods: {\n        foo: function () {\n          this.x.fontSize = 200\n          this.x.color = '#0000ff'\n          Vue.delete(this.y, 'fontWeight')\n          this.z = 'Weex'\n        }\n      }\n    `).then(instance => {\n      expect(getRoot(instance)).toEqual({\n        type: 'div',\n        children: [\n          {\n            type: 'text',\n            event: ['click'],\n            style: { fontSize: 100, color: '#ff0000', fontWeight: 'bold' },\n            attr: { value: 'Hello World' }\n          }\n        ]\n      })\n      fireEvent(instance, instance.document.body.children[0].ref, 'click')\n      return instance\n    }).then(instance => {\n      expect(getRoot(instance)).toEqual({\n        type: 'div',\n        children: [\n          {\n            type: 'text',\n            event: ['click'],\n            style: { fontSize: 200, color: '#ff0000', fontWeight: '' },\n            attr: { value: 'Hello Weex' }\n          }\n        ]\n      })\n      done()\n    })\n  })\n\n  it('should be generated by map binding', (done) => {\n    compileAndExecute(`\n      <div>\n        <text :style=\"{ fontSize: x, color: '#00ff00' }\" @click=\"foo\">Hello</text>\n        <text :style=\"y\">{{z}}</text>\n      </div>\n    `, `\n      data: {\n        x: 100,\n        y: { color: '#ff0000', fontWeight: 'bold' },\n        z: 'World'\n      },\n      methods: {\n        foo: function () {\n          this.x = 200\n          this.y.color = '#0000ff'\n          Vue.delete(this.y, 'fontWeight')\n          this.z = 'Weex'\n        }\n      }\n    `).then(instance => {\n      expect(getRoot(instance)).toEqual({\n        type: 'div',\n        children: [\n          {\n            type: 'text',\n            event: ['click'],\n            style: { fontSize: 100, color: '#00ff00' },\n            attr: { value: 'Hello' }\n          },\n          {\n            type: 'text',\n            style: { color: '#ff0000', fontWeight: 'bold' },\n            attr: { value: 'World' }\n          }\n        ]\n      })\n      fireEvent(instance, instance.document.body.children[0].ref, 'click')\n      return instance\n    }).then(instance => {\n      expect(getRoot(instance)).toEqual({\n        type: 'div',\n        children: [\n          {\n            type: 'text',\n            event: ['click'],\n            style: { fontSize: 200, color: '#00ff00' },\n            attr: { value: 'Hello' }\n          },\n          {\n            type: 'text',\n            style: { color: '#0000ff', fontWeight: '' },\n            attr: { value: 'Weex' }\n          }\n        ]\n      })\n      done()\n    })\n  })\n})\n"
  },
  {
    "path": "vue-router/build/build.js",
    "content": "const fs = require('fs')\nconst path = require('path')\nconst zlib = require('zlib')\nconst uglify = require('uglify-js')\nconst rollup = require('rollup')\nconst configs = require('./configs')\n\nif (!fs.existsSync('dist')) {\n  fs.mkdirSync('dist')\n}\n\nbuild(Object.keys(configs).map(key => configs[key]))\n\nfunction build (builds) {\n  let built = 0\n  const total = builds.length\n  const next = () => {\n    buildEntry(builds[built]).then(() => {\n      built++\n      if (built < total) {\n        next()\n      }\n    }).catch(logError)\n  }\n\n  next()\n}\n\nfunction buildEntry ({ input, output }) {\n  const isProd = /min\\.js$/.test(output.file)\n  return rollup.rollup(input)\n    .then(bundle => bundle.generate(output))\n    .then(({ code }) => {\n      if (isProd) {\n        var minified = uglify.minify(code, {\n          output: {\n            preamble: output.banner,\n            /* eslint-disable camelcase */\n            ascii_only: true\n            /* eslint-enable camelcase */\n          }\n        }).code\n        return write(output.file, minified, true)\n      } else {\n        return write(output.file, code)\n      }\n    })\n}\n\nfunction write (dest, code, zip) {\n  return new Promise((resolve, reject) => {\n    function report (extra) {\n      console.log(blue(path.relative(process.cwd(), dest)) + ' ' + getSize(code) + (extra || ''))\n      resolve()\n    }\n\n    fs.writeFile(dest, code, err => {\n      if (err) return reject(err)\n      if (zip) {\n        zlib.gzip(code, (err, zipped) => {\n          if (err) return reject(err)\n          report(' (gzipped: ' + getSize(zipped) + ')')\n        })\n      } else {\n        report()\n      }\n    })\n  })\n}\n\nfunction getSize (code) {\n  return (code.length / 1024).toFixed(2) + 'kb'\n}\n\nfunction logError (e) {\n  console.log(e)\n}\n\nfunction blue (str) {\n  return '\\x1b[1m\\x1b[34m' + str + '\\x1b[39m\\x1b[22m'\n}\n"
  },
  {
    "path": "vue-router/build/configs.js",
    "content": "const path = require('path')\nconst buble = require('rollup-plugin-buble')\nconst flow = require('rollup-plugin-flow-no-whitespace')\nconst cjs = require('rollup-plugin-commonjs')\nconst node = require('rollup-plugin-node-resolve')\nconst replace = require('rollup-plugin-replace')\nconst version = process.env.VERSION || require('../package.json').version\nconst banner =\n`/*!\n  * vue-router v${version}\n  * (c) ${new Date().getFullYear()} Evan You\n  * @license MIT\n  */`\n\nconst resolve = _path => path.resolve(__dirname, '../', _path)\n\nmodule.exports = [\n  // browser dev\n  {\n    file: resolve('dist/vue-router.js'),\n    format: 'umd',\n    env: 'development'\n  },\n  {\n    file: resolve('dist/vue-router.min.js'),\n    format: 'umd',\n    env: 'production'\n  },\n  {\n    file: resolve('dist/vue-router.common.js'),\n    format: 'cjs'\n  },\n  {\n    file: resolve('dist/vue-router.esm.js'),\n    format: 'es'\n  }\n].map(genConfig)\n\nfunction genConfig (opts) {\n  const config = {\n    input: {\n      input: resolve('src/index.js'),\n      plugins: [\n        flow(),\n        node(),\n        cjs(),\n        replace({\n          __VERSION__: version\n        }),\n        buble()\n      ]\n    },\n    output: {\n      file: opts.file,\n      format: opts.format,\n      banner,\n      name: 'VueRouter'\n    }\n  }\n\n  if (opts.env) {\n    config.input.plugins.unshift(replace({\n      'process.env.NODE_ENV': JSON.stringify(opts.env)\n    }))\n  }\n\n  return config\n}\n"
  },
  {
    "path": "vue-router/build/release.sh",
    "content": "set -e\necho \"Enter release version: \"\nread VERSION\n\nread -p \"Releasing $VERSION - are you sure? (y/n)\" -n 1 -r\necho    # (optional) move to a new line\nif [[ $REPLY =~ ^[Yy]$ ]]\nthen\n  echo \"Releasing $VERSION ...\"\n  npm test\n  VERSION=$VERSION npm run build\n\n  # commit\n  git add -A\n  git commit -m \"[build] $VERSION\"\n  npm version $VERSION --message \"[release] $VERSION\"\n\n  # publish\n  git push origin refs/tags/v$VERSION\n  git push\n  npm publish\nfi\n"
  },
  {
    "path": "vue-router/build/rollup.dev.config.js",
    "content": "const { input, output } = require('./configs')[0]\n\nmodule.exports = Object.assign({}, input, { output })\n"
  },
  {
    "path": "vue-router/build/update-docs.sh",
    "content": "cd docs\nrm -rf _book\ngitbook install\ngitbook build\ncp assets/circle.yml _book/circle.yml\ncp assets/CNAME _book/CNAME\ncd _book\ngit init\ngit add -A\ngit commit -m 'update book'\ngit push -f git@github.com:vuejs/vue-router.git master:gh-pages\n"
  },
  {
    "path": "vue-router/flow/declarations.js",
    "content": "declare var document: Document;\n\ndeclare class RouteRegExp extends RegExp {\n  keys: Array<{ name: string, optional: boolean }>;\n}\n\ndeclare type PathToRegexpOptions = {\n  sensitive?: boolean,\n  strict?: boolean,\n  end?: boolean\n}\n\ndeclare module 'path-to-regexp' {\n  declare var exports: {\n    (path: string, keys?: Array<?{ name: string }>, options?: PathToRegexpOptions): RouteRegExp;\n    compile: (path: string) => (params: Object) => string;\n  }\n}\n\ndeclare type Dictionary<T> = { [key: string]: T }\n\ndeclare type NavigationGuard = (\n  to: Route,\n  from: Route,\n  next: (to?: RawLocation | false | Function | void) => void\n) => any\n\ndeclare type AfterNavigationHook = (to: Route, from: Route) => any\n\ntype Position = { x: number, y: number };\ntype PositionResult = Position | { selector: string, offset?: Position } | void;\n\ndeclare type RouterOptions = {\n  routes?: Array<RouteConfig>;\n  mode?: string;\n  fallback?: boolean;\n  base?: string;\n  linkActiveClass?: string;\n  parseQuery?: (query: string) => Object;\n  stringifyQuery?: (query: Object) => string;\n  scrollBehavior?: (\n    to: Route,\n    from: Route,\n    savedPosition: ?Position\n  ) => PositionResult | Promise<PositionResult>;\n}\n\ndeclare type RedirectOption = RawLocation | ((to: Route) => RawLocation)\n\ndeclare type RouteConfig = {\n  path: string;\n  name?: string;\n  component?: any;\n  components?: Dictionary<any>;\n  redirect?: RedirectOption;\n  alias?: string | Array<string>;\n  children?: Array<RouteConfig>;\n  beforeEnter?: NavigationGuard;\n  meta?: any;\n  props?: boolean | Object | Function;\n  caseSensitive?: boolean;\n  pathToRegexpOptions?: PathToRegexpOptions;\n}\n\ndeclare type RouteRecord = {\n  path: string;\n  regex: RouteRegExp;\n  components: Dictionary<any>;\n  instances: Dictionary<any>;\n  name: ?string;\n  parent: ?RouteRecord;\n  redirect: ?RedirectOption;\n  matchAs: ?string;\n  beforeEnter: ?NavigationGuard;\n  meta: any;\n  props: boolean | Object | Function | Dictionary<boolean | Object | Function>;\n}\n\ndeclare type Location = {\n  _normalized?: boolean;\n  name?: string;\n  path?: string;\n  hash?: string;\n  query?: Dictionary<string>;\n  params?: Dictionary<string>;\n  append?: boolean;\n  replace?: boolean;\n}\n\ndeclare type RawLocation = string | Location\n\ndeclare type Route = {\n  path: string;\n  name: ?string;\n  hash: string;\n  query: Dictionary<string>;\n  params: Dictionary<string>;\n  fullPath: string;\n  matched: Array<RouteRecord>;\n  redirectedFrom?: string;\n  meta?: any;\n}\n"
  },
  {
    "path": "vue-router/src/components/link.js",
    "content": "/* @flow */\n\nimport { createRoute, isSameRoute, isIncludedRoute } from '../util/route'\nimport { _Vue } from '../install'\n\n// work around weird flow bug\nconst toTypes: Array<Function> = [String, Object]\nconst eventTypes: Array<Function> = [String, Array]\n\nexport default {\n  name: 'RouterLink',\n  props: {\n    to: {\n      type: toTypes,\n      required: true\n    },\n    tag: {\n      type: String,\n      default: 'a'\n    },\n    exact: Boolean,\n    append: Boolean,\n    replace: Boolean,\n    activeClass: String,\n    exactActiveClass: String,\n    event: {\n      type: eventTypes,\n      default: 'click'\n    }\n  },\n  render (h: Function) {\n    const router = this.$router\n    const current = this.$route\n    const { location, route, href } = router.resolve(this.to, current, this.append)\n\n    const classes = {}\n    const globalActiveClass = router.options.linkActiveClass\n    const globalExactActiveClass = router.options.linkExactActiveClass\n    // Support global empty active class\n    const activeClassFallback = globalActiveClass == null\n            ? 'router-link-active'\n            : globalActiveClass\n    const exactActiveClassFallback = globalExactActiveClass == null\n            ? 'router-link-exact-active'\n            : globalExactActiveClass\n    const activeClass = this.activeClass == null\n            ? activeClassFallback\n            : this.activeClass\n    const exactActiveClass = this.exactActiveClass == null\n            ? exactActiveClassFallback\n            : this.exactActiveClass\n    const compareTarget = location.path\n      ? createRoute(null, location, null, router)\n      : route\n\n    classes[exactActiveClass] = isSameRoute(current, compareTarget)\n    classes[activeClass] = this.exact\n      ? classes[exactActiveClass]\n      : isIncludedRoute(current, compareTarget)\n\n    const handler = e => {\n      if (guardEvent(e)) {\n        if (this.replace) {\n          router.replace(location)\n        } else {\n          router.push(location)\n        }\n      }\n    }\n\n    const on = { click: guardEvent }\n    if (Array.isArray(this.event)) {\n      this.event.forEach(e => { on[e] = handler })\n    } else {\n      on[this.event] = handler\n    }\n\n    const data: any = {\n      class: classes\n    }\n\n    if (this.tag === 'a') {\n      data.on = on\n      data.attrs = { href }\n    } else {\n      // find the first <a> child and apply listener and href\n      const a = findAnchor(this.$slots.default)\n      if (a) {\n        // in case the <a> is a static node\n        a.isStatic = false\n        const extend = _Vue.util.extend\n        const aData = a.data = extend({}, a.data)\n        aData.on = on\n        const aAttrs = a.data.attrs = extend({}, a.data.attrs)\n        aAttrs.href = href\n      } else {\n        // doesn't have <a> child, apply listener to self\n        data.on = on\n      }\n    }\n\n    return h(this.tag, data, this.$slots.default)\n  }\n}\n\nfunction guardEvent (e) {\n  // don't redirect with control keys\n  if (e.metaKey || e.altKey || e.ctrlKey || e.shiftKey) return\n  // don't redirect when preventDefault called\n  if (e.defaultPrevented) return\n  // don't redirect on right click\n  if (e.button !== undefined && e.button !== 0) return\n  // don't redirect if `target=\"_blank\"`\n  if (e.currentTarget && e.currentTarget.getAttribute) {\n    const target = e.currentTarget.getAttribute('target')\n    if (/\\b_blank\\b/i.test(target)) return\n  }\n  // this may be a Weex event which doesn't have this method\n  if (e.preventDefault) {\n    e.preventDefault()\n  }\n  return true\n}\n\nfunction findAnchor (children) {\n  if (children) {\n    let child\n    for (let i = 0; i < children.length; i++) {\n      child = children[i]\n      if (child.tag === 'a') {\n        return child\n      }\n      if (child.children && (child = findAnchor(child.children))) {\n        return child\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "vue-router/src/components/view.js",
    "content": "import { warn } from '../util/warn'\n\nexport default {\n  name: 'RouterView',\n  functional: true,\n  props: {\n    name: {\n      type: String,\n      default: 'default'\n    }\n  },\n  render (_, { props, children, parent, data }) {\n    data.routerView = true\n\n    // directly use parent context's createElement() function\n    // so that components rendered by router-view can resolve named slots\n    const h = parent.$createElement\n    const name = props.name\n    const route = parent.$route\n    const cache = parent._routerViewCache || (parent._routerViewCache = {})\n\n    // determine current view depth, also check to see if the tree\n    // has been toggled inactive but kept-alive.\n    let depth = 0\n    let inactive = false\n    while (parent && parent._routerRoot !== parent) {\n      if (parent.$vnode && parent.$vnode.data.routerView) {\n        depth++\n      }\n      if (parent._inactive) {\n        inactive = true\n      }\n      parent = parent.$parent\n    }\n    data.routerViewDepth = depth\n\n    // render previous view if the tree is inactive and kept-alive\n    if (inactive) {\n      return h(cache[name], data, children)\n    }\n\n    const matched = route.matched[depth]\n    // render empty node if no matched route\n    if (!matched) {\n      cache[name] = null\n      return h()\n    }\n\n    const component = cache[name] = matched.components[name]\n\n    // attach instance registration hook\n    // this will be called in the instance's injected lifecycle hooks\n    data.registerRouteInstance = (vm, val) => {\n      // val could be undefined for unregistration\n      const current = matched.instances[name]\n      if (\n        (val && current !== vm) ||\n        (!val && current === vm)\n      ) {\n        matched.instances[name] = val\n      }\n    }\n\n    // also register instance in prepatch hook\n    // in case the same component instance is reused across different routes\n    ;(data.hook || (data.hook = {})).prepatch = (_, vnode) => {\n      matched.instances[name] = vnode.componentInstance\n    }\n\n    // resolve props\n    let propsToPass = data.props = resolveProps(route, matched.props && matched.props[name])\n    if (propsToPass) {\n      // clone to prevent mutation\n      propsToPass = data.props = extend({}, propsToPass)\n      // pass non-declared props as attrs\n      const attrs = data.attrs = data.attrs || {}\n      for (const key in propsToPass) {\n        if (!component.props || !(key in component.props)) {\n          attrs[key] = propsToPass[key]\n          delete propsToPass[key]\n        }\n      }\n    }\n\n    return h(component, data, children)\n  }\n}\n\nfunction resolveProps (route, config) {\n  switch (typeof config) {\n    case 'undefined':\n      return\n    case 'object':\n      return config\n    case 'function':\n      return config(route)\n    case 'boolean':\n      return config ? route.params : undefined\n    default:\n      if (process.env.NODE_ENV !== 'production') {\n        warn(\n          false,\n          `props in \"${route.path}\" is a ${typeof config}, ` +\n          `expecting an object, function or boolean.`\n        )\n      }\n  }\n}\n\nfunction extend (to, from) {\n  for (const key in from) {\n    to[key] = from[key]\n  }\n  return to\n}\n"
  },
  {
    "path": "vue-router/src/create-matcher.js",
    "content": "/* @flow */\n\nimport type VueRouter from './index'\nimport { resolvePath } from './util/path'\nimport { assert, warn } from './util/warn'\nimport { createRoute } from './util/route'\nimport { fillParams } from './util/params'\nimport { createRouteMap } from './create-route-map'\nimport { normalizeLocation } from './util/location'\n\nexport type Matcher = {\n  match: (raw: RawLocation, current?: Route, redirectedFrom?: Location) => Route;\n  addRoutes: (routes: Array<RouteConfig>) => void;\n};\n\nexport function createMatcher (\n  routes: Array<RouteConfig>,\n  router: VueRouter\n): Matcher {\n  const { pathList, pathMap, nameMap } = createRouteMap(routes)\n\n  function addRoutes (routes) {\n    createRouteMap(routes, pathList, pathMap, nameMap)\n  }\n\n  function match (\n    raw: RawLocation,\n    currentRoute?: Route,\n    redirectedFrom?: Location\n  ): Route {\n    const location = normalizeLocation(raw, currentRoute, false, router)\n    const { name } = location\n\n    if (name) {\n      const record = nameMap[name]\n      if (process.env.NODE_ENV !== 'production') {\n        warn(record, `Route with name '${name}' does not exist`)\n      }\n      if (!record) return _createRoute(null, location)\n      const paramNames = record.regex.keys\n        .filter(key => !key.optional)\n        .map(key => key.name)\n\n      if (typeof location.params !== 'object') {\n        location.params = {}\n      }\n\n      if (currentRoute && typeof currentRoute.params === 'object') {\n        for (const key in currentRoute.params) {\n          if (!(key in location.params) && paramNames.indexOf(key) > -1) {\n            location.params[key] = currentRoute.params[key]\n          }\n        }\n      }\n\n      if (record) {\n        location.path = fillParams(record.path, location.params, `named route \"${name}\"`)\n        return _createRoute(record, location, redirectedFrom)\n      }\n    } else if (location.path) {\n      location.params = {}\n      for (let i = 0; i < pathList.length; i++) {\n        const path = pathList[i]\n        const record = pathMap[path]\n        if (matchRoute(record.regex, location.path, location.params)) {\n          return _createRoute(record, location, redirectedFrom)\n        }\n      }\n    }\n    // no match\n    return _createRoute(null, location)\n  }\n\n  function redirect (\n    record: RouteRecord,\n    location: Location\n  ): Route {\n    const originalRedirect = record.redirect\n    let redirect = typeof originalRedirect === 'function'\n        ? originalRedirect(createRoute(record, location, null, router))\n        : originalRedirect\n\n    if (typeof redirect === 'string') {\n      redirect = { path: redirect }\n    }\n\n    if (!redirect || typeof redirect !== 'object') {\n      if (process.env.NODE_ENV !== 'production') {\n        warn(\n          false, `invalid redirect option: ${JSON.stringify(redirect)}`\n        )\n      }\n      return _createRoute(null, location)\n    }\n\n    const re: Object = redirect\n    const { name, path } = re\n    let { query, hash, params } = location\n    query = re.hasOwnProperty('query') ? re.query : query\n    hash = re.hasOwnProperty('hash') ? re.hash : hash\n    params = re.hasOwnProperty('params') ? re.params : params\n\n    if (name) {\n      // resolved named direct\n      const targetRecord = nameMap[name]\n      if (process.env.NODE_ENV !== 'production') {\n        assert(targetRecord, `redirect failed: named route \"${name}\" not found.`)\n      }\n      return match({\n        _normalized: true,\n        name,\n        query,\n        hash,\n        params\n      }, undefined, location)\n    } else if (path) {\n      // 1. resolve relative redirect\n      const rawPath = resolveRecordPath(path, record)\n      // 2. resolve params\n      const resolvedPath = fillParams(rawPath, params, `redirect route with path \"${rawPath}\"`)\n      // 3. rematch with existing query and hash\n      return match({\n        _normalized: true,\n        path: resolvedPath,\n        query,\n        hash\n      }, undefined, location)\n    } else {\n      if (process.env.NODE_ENV !== 'production') {\n        warn(false, `invalid redirect option: ${JSON.stringify(redirect)}`)\n      }\n      return _createRoute(null, location)\n    }\n  }\n\n  function alias (\n    record: RouteRecord,\n    location: Location,\n    matchAs: string\n  ): Route {\n    const aliasedPath = fillParams(matchAs, location.params, `aliased route with path \"${matchAs}\"`)\n    const aliasedMatch = match({\n      _normalized: true,\n      path: aliasedPath\n    })\n    if (aliasedMatch) {\n      const matched = aliasedMatch.matched\n      const aliasedRecord = matched[matched.length - 1]\n      location.params = aliasedMatch.params\n      return _createRoute(aliasedRecord, location)\n    }\n    return _createRoute(null, location)\n  }\n\n  function _createRoute (\n    record: ?RouteRecord,\n    location: Location,\n    redirectedFrom?: Location\n  ): Route {\n    if (record && record.redirect) {\n      return redirect(record, redirectedFrom || location)\n    }\n    if (record && record.matchAs) {\n      return alias(record, location, record.matchAs)\n    }\n    return createRoute(record, location, redirectedFrom, router)\n  }\n\n  return {\n    match,\n    addRoutes\n  }\n}\n\nfunction matchRoute (\n  regex: RouteRegExp,\n  path: string,\n  params: Object\n): boolean {\n  const m = path.match(regex)\n\n  if (!m) {\n    return false\n  } else if (!params) {\n    return true\n  }\n\n  for (let i = 1, len = m.length; i < len; ++i) {\n    const key = regex.keys[i - 1]\n    const val = typeof m[i] === 'string' ? decodeURIComponent(m[i]) : m[i]\n    if (key) {\n      params[key.name] = val\n    }\n  }\n\n  return true\n}\n\nfunction resolveRecordPath (path: string, record: RouteRecord): string {\n  return resolvePath(path, record.parent ? record.parent.path : '/', true)\n}\n"
  },
  {
    "path": "vue-router/src/create-route-map.js",
    "content": "/* @flow */\n\nimport Regexp from 'path-to-regexp'\nimport { cleanPath } from './util/path'\nimport { assert, warn } from './util/warn'\n\nexport function createRouteMap (\n  routes: Array<RouteConfig>,\n  oldPathList?: Array<string>,\n  oldPathMap?: Dictionary<RouteRecord>,\n  oldNameMap?: Dictionary<RouteRecord>\n): {\n  pathList: Array<string>;\n  pathMap: Dictionary<RouteRecord>;\n  nameMap: Dictionary<RouteRecord>;\n} {\n  // the path list is used to control path matching priority\n  const pathList: Array<string> = oldPathList || []\n  // $flow-disable-line\n  const pathMap: Dictionary<RouteRecord> = oldPathMap || Object.create(null)\n  // $flow-disable-line\n  const nameMap: Dictionary<RouteRecord> = oldNameMap || Object.create(null)\n\n  routes.forEach(route => {\n    addRouteRecord(pathList, pathMap, nameMap, route)\n  })\n\n  // ensure wildcard routes are always at the end\n  for (let i = 0, l = pathList.length; i < l; i++) {\n    if (pathList[i] === '*') {\n      pathList.push(pathList.splice(i, 1)[0])\n      l--\n      i--\n    }\n  }\n\n  return {\n    pathList,\n    pathMap,\n    nameMap\n  }\n}\n\nfunction addRouteRecord (\n  pathList: Array<string>,\n  pathMap: Dictionary<RouteRecord>,\n  nameMap: Dictionary<RouteRecord>,\n  route: RouteConfig,\n  parent?: RouteRecord,\n  matchAs?: string\n) {\n  const { path, name } = route\n  if (process.env.NODE_ENV !== 'production') {\n    assert(path != null, `\"path\" is required in a route configuration.`)\n    assert(\n      typeof route.component !== 'string',\n      `route config \"component\" for path: ${String(path || name)} cannot be a ` +\n      `string id. Use an actual component instead.`\n    )\n  }\n\n  const pathToRegexpOptions: PathToRegexpOptions = route.pathToRegexpOptions || {}\n  const normalizedPath = normalizePath(\n    path,\n    parent,\n    pathToRegexpOptions.strict\n  )\n\n  if (typeof route.caseSensitive === 'boolean') {\n    pathToRegexpOptions.sensitive = route.caseSensitive\n  }\n\n  const record: RouteRecord = {\n    path: normalizedPath,\n    regex: compileRouteRegex(normalizedPath, pathToRegexpOptions),\n    components: route.components || { default: route.component },\n    instances: {},\n    name,\n    parent,\n    matchAs,\n    redirect: route.redirect,\n    beforeEnter: route.beforeEnter,\n    meta: route.meta || {},\n    props: route.props == null\n      ? {}\n      : route.components\n        ? route.props\n        : { default: route.props }\n  }\n\n  if (route.children) {\n    // Warn if route is named, does not redirect and has a default child route.\n    // If users navigate to this route by name, the default child will\n    // not be rendered (GH Issue #629)\n    if (process.env.NODE_ENV !== 'production') {\n      if (route.name && !route.redirect && route.children.some(child => /^\\/?$/.test(child.path))) {\n        warn(\n          false,\n          `Named Route '${route.name}' has a default child route. ` +\n          `When navigating to this named route (:to=\"{name: '${route.name}'\"), ` +\n          `the default child route will not be rendered. Remove the name from ` +\n          `this route and use the name of the default child route for named ` +\n          `links instead.`\n        )\n      }\n    }\n    route.children.forEach(child => {\n      const childMatchAs = matchAs\n        ? cleanPath(`${matchAs}/${child.path}`)\n        : undefined\n      addRouteRecord(pathList, pathMap, nameMap, child, record, childMatchAs)\n    })\n  }\n\n  if (route.alias !== undefined) {\n    const aliases = Array.isArray(route.alias)\n      ? route.alias\n      : [route.alias]\n\n    aliases.forEach(alias => {\n      const aliasRoute = {\n        path: alias,\n        children: route.children\n      }\n      addRouteRecord(\n        pathList,\n        pathMap,\n        nameMap,\n        aliasRoute,\n        parent,\n        record.path || '/' // matchAs\n      )\n    })\n  }\n\n  if (!pathMap[record.path]) {\n    pathList.push(record.path)\n    pathMap[record.path] = record\n  }\n\n  if (name) {\n    if (!nameMap[name]) {\n      nameMap[name] = record\n    } else if (process.env.NODE_ENV !== 'production' && !matchAs) {\n      warn(\n        false,\n        `Duplicate named routes definition: ` +\n        `{ name: \"${name}\", path: \"${record.path}\" }`\n      )\n    }\n  }\n}\n\nfunction compileRouteRegex (path: string, pathToRegexpOptions: PathToRegexpOptions): RouteRegExp {\n  const regex = Regexp(path, [], pathToRegexpOptions)\n  if (process.env.NODE_ENV !== 'production') {\n    const keys: any = Object.create(null)\n    regex.keys.forEach(key => {\n      warn(!keys[key.name], `Duplicate param keys in route with path: \"${path}\"`)\n      keys[key.name] = true\n    })\n  }\n  return regex\n}\n\nfunction normalizePath (path: string, parent?: RouteRecord, strict?: boolean): string {\n  if (!strict) path = path.replace(/\\/$/, '')\n  if (path[0] === '/') return path\n  if (parent == null) return path\n  return cleanPath(`${parent.path}/${path}`)\n}\n"
  },
  {
    "path": "vue-router/src/history/abstract.js",
    "content": "/* @flow */\n\nimport type Router from '../index'\nimport { History } from './base'\n\nexport class AbstractHistory extends History {\n  index: number;\n  stack: Array<Route>;\n\n  constructor (router: Router, base: ?string) {\n    super(router, base)\n    this.stack = []\n    this.index = -1\n  }\n\n  push (location: RawLocation, onComplete?: Function, onAbort?: Function) {\n    this.transitionTo(location, route => {\n      this.stack = this.stack.slice(0, this.index + 1).concat(route)\n      this.index++\n      onComplete && onComplete(route)\n    }, onAbort)\n  }\n\n  replace (location: RawLocation, onComplete?: Function, onAbort?: Function) {\n    this.transitionTo(location, route => {\n      this.stack = this.stack.slice(0, this.index).concat(route)\n      onComplete && onComplete(route)\n    }, onAbort)\n  }\n\n  go (n: number) {\n    const targetIndex = this.index + n\n    if (targetIndex < 0 || targetIndex >= this.stack.length) {\n      return\n    }\n    const route = this.stack[targetIndex]\n    this.confirmTransition(route, () => {\n      this.index = targetIndex\n      this.updateRoute(route)\n    })\n  }\n\n  getCurrentLocation () {\n    const current = this.stack[this.stack.length - 1]\n    return current ? current.fullPath : '/'\n  }\n\n  ensureURL () {\n    // noop\n  }\n}\n"
  },
  {
    "path": "vue-router/src/history/base.js",
    "content": "/* @flow */\n\nimport { _Vue } from '../install'\nimport type Router from '../index'\nimport { inBrowser } from '../util/dom'\nimport { runQueue } from '../util/async'\nimport { warn, isError } from '../util/warn'\nimport { START, isSameRoute } from '../util/route'\nimport {\n  flatten,\n  flatMapComponents,\n  resolveAsyncComponents\n} from '../util/resolve-components'\n\nexport class History {\n  router: Router;\n  base: string;\n  current: Route;\n  pending: ?Route;\n  cb: (r: Route) => void;\n  ready: boolean;\n  readyCbs: Array<Function>;\n  readyErrorCbs: Array<Function>;\n  errorCbs: Array<Function>;\n\n  // implemented by sub-classes\n  +go: (n: number) => void;\n  +push: (loc: RawLocation) => void;\n  +replace: (loc: RawLocation) => void;\n  +ensureURL: (push?: boolean) => void;\n  +getCurrentLocation: () => string;\n\n  constructor (router: Router, base: ?string) {\n    this.router = router\n    this.base = normalizeBase(base)\n    // start with a route object that stands for \"nowhere\"\n    this.current = START\n    this.pending = null\n    this.ready = false\n    this.readyCbs = []\n    this.readyErrorCbs = []\n    this.errorCbs = []\n  }\n\n  listen (cb: Function) {\n    this.cb = cb\n  }\n\n  onReady (cb: Function, errorCb: ?Function) {\n    if (this.ready) {\n      cb()\n    } else {\n      this.readyCbs.push(cb)\n      if (errorCb) {\n        this.readyErrorCbs.push(errorCb)\n      }\n    }\n  }\n\n  onError (errorCb: Function) {\n    this.errorCbs.push(errorCb)\n  }\n\n  transitionTo (location: RawLocation, onComplete?: Function, onAbort?: Function) {\n    const route = this.router.match(location, this.current)\n    this.confirmTransition(route, () => {\n      this.updateRoute(route)\n      onComplete && onComplete(route)\n      this.ensureURL()\n\n      // fire ready cbs once\n      if (!this.ready) {\n        this.ready = true\n        this.readyCbs.forEach(cb => { cb(route) })\n      }\n    }, err => {\n      if (onAbort) {\n        onAbort(err)\n      }\n      if (err && !this.ready) {\n        this.ready = true\n        this.readyErrorCbs.forEach(cb => { cb(err) })\n      }\n    })\n  }\n\n  confirmTransition (route: Route, onComplete: Function, onAbort?: Function) {\n    const current = this.current\n    const abort = err => {\n      if (isError(err)) {\n        if (this.errorCbs.length) {\n          this.errorCbs.forEach(cb => { cb(err) })\n        } else {\n          warn(false, 'uncaught error during route navigation:')\n          console.error(err)\n        }\n      }\n      onAbort && onAbort(err)\n    }\n    if (\n      isSameRoute(route, current) &&\n      // in the case the route map has been dynamically appended to\n      route.matched.length === current.matched.length\n    ) {\n      this.ensureURL()\n      return abort()\n    }\n\n    const {\n      updated,\n      deactivated,\n      activated\n    } = resolveQueue(this.current.matched, route.matched)\n\n    const queue: Array<?NavigationGuard> = [].concat(\n      // in-component leave guards\n      extractLeaveGuards(deactivated),\n      // global before hooks\n      this.router.beforeHooks,\n      // in-component update hooks\n      extractUpdateHooks(updated),\n      // in-config enter guards\n      activated.map(m => m.beforeEnter),\n      // async components\n      resolveAsyncComponents(activated)\n    )\n\n    this.pending = route\n    const iterator = (hook: NavigationGuard, next) => {\n      if (this.pending !== route) {\n        return abort()\n      }\n      try {\n        hook(route, current, (to: any) => {\n          if (to === false || isError(to)) {\n            // next(false) -> abort navigation, ensure current URL\n            this.ensureURL(true)\n            abort(to)\n          } else if (\n            typeof to === 'string' ||\n            (typeof to === 'object' && (\n              typeof to.path === 'string' ||\n              typeof to.name === 'string'\n            ))\n          ) {\n            // next('/') or next({ path: '/' }) -> redirect\n            abort()\n            if (typeof to === 'object' && to.replace) {\n              this.replace(to)\n            } else {\n              this.push(to)\n            }\n          } else {\n            // confirm transition and pass on the value\n            next(to)\n          }\n        })\n      } catch (e) {\n        abort(e)\n      }\n    }\n\n    runQueue(queue, iterator, () => {\n      const postEnterCbs = []\n      const isValid = () => this.current === route\n      // wait until async components are resolved before\n      // extracting in-component enter guards\n      const enterGuards = extractEnterGuards(activated, postEnterCbs, isValid)\n      const queue = enterGuards.concat(this.router.resolveHooks)\n      runQueue(queue, iterator, () => {\n        if (this.pending !== route) {\n          return abort()\n        }\n        this.pending = null\n        onComplete(route)\n        if (this.router.app) {\n          this.router.app.$nextTick(() => {\n            postEnterCbs.forEach(cb => { cb() })\n          })\n        }\n      })\n    })\n  }\n\n  updateRoute (route: Route) {\n    const prev = this.current\n    this.current = route\n    this.cb && this.cb(route)\n    this.router.afterHooks.forEach(hook => {\n      hook && hook(route, prev)\n    })\n  }\n}\n\nfunction normalizeBase (base: ?string): string {\n  if (!base) {\n    if (inBrowser) {\n      // respect <base> tag\n      const baseEl = document.querySelector('base')\n      base = (baseEl && baseEl.getAttribute('href')) || '/'\n      // strip full URL origin\n      base = base.replace(/^https?:\\/\\/[^\\/]+/, '')\n    } else {\n      base = '/'\n    }\n  }\n  // make sure there's the starting slash\n  if (base.charAt(0) !== '/') {\n    base = '/' + base\n  }\n  // remove trailing slash\n  return base.replace(/\\/$/, '')\n}\n\nfunction resolveQueue (\n  current: Array<RouteRecord>,\n  next: Array<RouteRecord>\n): {\n  updated: Array<RouteRecord>,\n  activated: Array<RouteRecord>,\n  deactivated: Array<RouteRecord>\n} {\n  let i\n  const max = Math.max(current.length, next.length)\n  for (i = 0; i < max; i++) {\n    if (current[i] !== next[i]) {\n      break\n    }\n  }\n  return {\n    updated: next.slice(0, i),\n    activated: next.slice(i),\n    deactivated: current.slice(i)\n  }\n}\n\nfunction extractGuards (\n  records: Array<RouteRecord>,\n  name: string,\n  bind: Function,\n  reverse?: boolean\n): Array<?Function> {\n  const guards = flatMapComponents(records, (def, instance, match, key) => {\n    const guard = extractGuard(def, name)\n    if (guard) {\n      return Array.isArray(guard)\n        ? guard.map(guard => bind(guard, instance, match, key))\n        : bind(guard, instance, match, key)\n    }\n  })\n  return flatten(reverse ? guards.reverse() : guards)\n}\n\nfunction extractGuard (\n  def: Object | Function,\n  key: string\n): NavigationGuard | Array<NavigationGuard> {\n  if (typeof def !== 'function') {\n    // extend now so that global mixins are applied.\n    def = _Vue.extend(def)\n  }\n  return def.options[key]\n}\n\nfunction extractLeaveGuards (deactivated: Array<RouteRecord>): Array<?Function> {\n  return extractGuards(deactivated, 'beforeRouteLeave', bindGuard, true)\n}\n\nfunction extractUpdateHooks (updated: Array<RouteRecord>): Array<?Function> {\n  return extractGuards(updated, 'beforeRouteUpdate', bindGuard)\n}\n\nfunction bindGuard (guard: NavigationGuard, instance: ?_Vue): ?NavigationGuard {\n  if (instance) {\n    return function boundRouteGuard () {\n      return guard.apply(instance, arguments)\n    }\n  }\n}\n\nfunction extractEnterGuards (\n  activated: Array<RouteRecord>,\n  cbs: Array<Function>,\n  isValid: () => boolean\n): Array<?Function> {\n  return extractGuards(activated, 'beforeRouteEnter', (guard, _, match, key) => {\n    return bindEnterGuard(guard, match, key, cbs, isValid)\n  })\n}\n\nfunction bindEnterGuard (\n  guard: NavigationGuard,\n  match: RouteRecord,\n  key: string,\n  cbs: Array<Function>,\n  isValid: () => boolean\n): NavigationGuard {\n  return function routeEnterGuard (to, from, next) {\n    return guard(to, from, cb => {\n      next(cb)\n      if (typeof cb === 'function') {\n        cbs.push(() => {\n          // #750\n          // if a router-view is wrapped with an out-in transition,\n          // the instance may not have been registered at this time.\n          // we will need to poll for registration until current route\n          // is no longer valid.\n          poll(cb, match.instances, key, isValid)\n        })\n      }\n    })\n  }\n}\n\nfunction poll (\n  cb: any, // somehow flow cannot infer this is a function\n  instances: Object,\n  key: string,\n  isValid: () => boolean\n) {\n  if (instances[key]) {\n    cb(instances[key])\n  } else if (isValid()) {\n    setTimeout(() => {\n      poll(cb, instances, key, isValid)\n    }, 16)\n  }\n}\n"
  },
  {
    "path": "vue-router/src/history/hash.js",
    "content": "/* @flow */\n\nimport type Router from '../index'\nimport { History } from './base'\nimport { cleanPath } from '../util/path'\nimport { getLocation } from './html5'\nimport { setupScroll, handleScroll } from '../util/scroll'\nimport { pushState, replaceState, supportsPushState } from '../util/push-state'\n\nexport class HashHistory extends History {\n  constructor (router: Router, base: ?string, fallback: boolean) {\n    super(router, base)\n    // check history fallback deeplinking\n    if (fallback && checkFallback(this.base)) {\n      return\n    }\n    ensureSlash()\n  }\n\n  // this is delayed until the app mounts\n  // to avoid the hashchange listener being fired too early\n  setupListeners () {\n    const router = this.router\n    const expectScroll = router.options.scrollBehavior\n    const supportsScroll = supportsPushState && expectScroll\n\n    if (supportsScroll) {\n      setupScroll()\n    }\n\n    window.addEventListener(supportsPushState ? 'popstate' : 'hashchange', () => {\n      const current = this.current\n      if (!ensureSlash()) {\n        return\n      }\n      this.transitionTo(getHash(), route => {\n        if (supportsScroll) {\n          handleScroll(this.router, route, current, true)\n        }\n        if (!supportsPushState) {\n          replaceHash(route.fullPath)\n        }\n      })\n    })\n  }\n\n  push (location: RawLocation, onComplete?: Function, onAbort?: Function) {\n    const { current: fromRoute } = this\n    this.transitionTo(location, route => {\n      pushHash(route.fullPath)\n      handleScroll(this.router, route, fromRoute, false)\n      onComplete && onComplete(route)\n    }, onAbort)\n  }\n\n  replace (location: RawLocation, onComplete?: Function, onAbort?: Function) {\n    const { current: fromRoute } = this\n    this.transitionTo(location, route => {\n      replaceHash(route.fullPath)\n      handleScroll(this.router, route, fromRoute, false)\n      onComplete && onComplete(route)\n    }, onAbort)\n  }\n\n  go (n: number) {\n    window.history.go(n)\n  }\n\n  ensureURL (push?: boolean) {\n    const current = this.current.fullPath\n    if (getHash() !== current) {\n      push ? pushHash(current) : replaceHash(current)\n    }\n  }\n\n  getCurrentLocation () {\n    return getHash()\n  }\n}\n\nfunction checkFallback (base) {\n  const location = getLocation(base)\n  if (!/^\\/#/.test(location)) {\n    window.location.replace(\n      cleanPath(base + '/#' + location)\n    )\n    return true\n  }\n}\n\nfunction ensureSlash (): boolean {\n  const path = getHash()\n  if (path.charAt(0) === '/') {\n    return true\n  }\n  replaceHash('/' + path)\n  return false\n}\n\nexport function getHash (): string {\n  // We can't use window.location.hash here because it's not\n  // consistent across browsers - Firefox will pre-decode it!\n  const href = window.location.href\n  const index = href.indexOf('#')\n  return index === -1 ? '' : href.slice(index + 1)\n}\n\nfunction getUrl (path) {\n  const href = window.location.href\n  const i = href.indexOf('#')\n  const base = i >= 0 ? href.slice(0, i) : href\n  return `${base}#${path}`\n}\n\nfunction pushHash (path) {\n  if (supportsPushState) {\n    pushState(getUrl(path))\n  } else {\n    window.location.hash = path\n  }\n}\n\nfunction replaceHash (path) {\n  if (supportsPushState) {\n    replaceState(getUrl(path))\n  } else {\n    window.location.replace(getUrl(path))\n  }\n}\n"
  },
  {
    "path": "vue-router/src/history/html5.js",
    "content": "/* @flow */\n\nimport type Router from '../index'\nimport { History } from './base'\nimport { cleanPath } from '../util/path'\nimport { START } from '../util/route'\nimport { setupScroll, handleScroll } from '../util/scroll'\nimport { pushState, replaceState, supportsPushState } from '../util/push-state'\n\nexport class HTML5History extends History {\n  constructor (router: Router, base: ?string) {\n    super(router, base)\n\n    const expectScroll = router.options.scrollBehavior\n    const supportsScroll = supportsPushState && expectScroll\n\n    if (supportsScroll) {\n      setupScroll()\n    }\n\n    const initLocation = getLocation(this.base)\n    window.addEventListener('popstate', e => {\n      const current = this.current\n\n      // Avoiding first `popstate` event dispatched in some browsers but first\n      // history route not updated since async guard at the same time.\n      const location = getLocation(this.base)\n      if (this.current === START && location === initLocation) {\n        return\n      }\n\n      this.transitionTo(location, route => {\n        if (supportsScroll) {\n          handleScroll(router, route, current, true)\n        }\n      })\n    })\n  }\n\n  go (n: number) {\n    window.history.go(n)\n  }\n\n  push (location: RawLocation, onComplete?: Function, onAbort?: Function) {\n    const { current: fromRoute } = this\n    this.transitionTo(location, route => {\n      pushState(cleanPath(this.base + route.fullPath))\n      handleScroll(this.router, route, fromRoute, false)\n      onComplete && onComplete(route)\n    }, onAbort)\n  }\n\n  replace (location: RawLocation, onComplete?: Function, onAbort?: Function) {\n    const { current: fromRoute } = this\n    this.transitionTo(location, route => {\n      replaceState(cleanPath(this.base + route.fullPath))\n      handleScroll(this.router, route, fromRoute, false)\n      onComplete && onComplete(route)\n    }, onAbort)\n  }\n\n  ensureURL (push?: boolean) {\n    if (getLocation(this.base) !== this.current.fullPath) {\n      const current = cleanPath(this.base + this.current.fullPath)\n      push ? pushState(current) : replaceState(current)\n    }\n  }\n\n  getCurrentLocation (): string {\n    return getLocation(this.base)\n  }\n}\n\nexport function getLocation (base: string): string {\n  let path = window.location.pathname\n  if (base && path.indexOf(base) === 0) {\n    path = path.slice(base.length)\n  }\n  return (path || '/') + window.location.search + window.location.hash\n}\n"
  },
  {
    "path": "vue-router/src/index.js",
    "content": "/* @flow */\n\nimport { install } from './install'\nimport { START } from './util/route'\nimport { assert } from './util/warn'\nimport { inBrowser } from './util/dom'\nimport { cleanPath } from './util/path'\nimport { createMatcher } from './create-matcher'\nimport { normalizeLocation } from './util/location'\nimport { supportsPushState } from './util/push-state'\n\nimport { HashHistory } from './history/hash'\nimport { HTML5History } from './history/html5'\nimport { AbstractHistory } from './history/abstract'\n\nimport type { Matcher } from './create-matcher'\n\nexport default class VueRouter {\n  static install: () => void;\n  static version: string;\n\n  app: any;\n  apps: Array<any>;\n  ready: boolean;\n  readyCbs: Array<Function>;\n  options: RouterOptions;\n  mode: string;\n  history: HashHistory | HTML5History | AbstractHistory;\n  matcher: Matcher;\n  fallback: boolean;\n  beforeHooks: Array<?NavigationGuard>;\n  resolveHooks: Array<?NavigationGuard>;\n  afterHooks: Array<?AfterNavigationHook>;\n\n  constructor (options: RouterOptions = {}) {\n    this.app = null\n    this.apps = []\n    this.options = options\n    this.beforeHooks = []\n    this.resolveHooks = []\n    this.afterHooks = []\n    this.matcher = createMatcher(options.routes || [], this)\n\n    let mode = options.mode || 'hash'\n    this.fallback = mode === 'history' && !supportsPushState && options.fallback !== false\n    if (this.fallback) {\n      mode = 'hash'\n    }\n    if (!inBrowser) {\n      mode = 'abstract'\n    }\n    this.mode = mode\n\n    switch (mode) {\n      case 'history':\n        this.history = new HTML5History(this, options.base)\n        break\n      case 'hash':\n        this.history = new HashHistory(this, options.base, this.fallback)\n        break\n      case 'abstract':\n        this.history = new AbstractHistory(this, options.base)\n        break\n      default:\n        if (process.env.NODE_ENV !== 'production') {\n          assert(false, `invalid mode: ${mode}`)\n        }\n    }\n  }\n\n  match (\n    raw: RawLocation,\n    current?: Route,\n    redirectedFrom?: Location\n  ): Route {\n    return this.matcher.match(raw, current, redirectedFrom)\n  }\n\n  get currentRoute (): ?Route {\n    return this.history && this.history.current\n  }\n\n  init (app: any /* Vue component instance */) {\n    process.env.NODE_ENV !== 'production' && assert(\n      install.installed,\n      `not installed. Make sure to call \\`Vue.use(VueRouter)\\` ` +\n      `before creating root instance.`\n    )\n\n    this.apps.push(app)\n\n    // main app already initialized.\n    if (this.app) {\n      return\n    }\n\n    this.app = app\n\n    const history = this.history\n\n    if (history instanceof HTML5History) {\n      history.transitionTo(history.getCurrentLocation())\n    } else if (history instanceof HashHistory) {\n      const setupHashListener = () => {\n        history.setupListeners()\n      }\n      history.transitionTo(\n        history.getCurrentLocation(),\n        setupHashListener,\n        setupHashListener\n      )\n    }\n\n    history.listen(route => {\n      this.apps.forEach((app) => {\n        app._route = route\n      })\n    })\n  }\n\n  beforeEach (fn: Function): Function {\n    return registerHook(this.beforeHooks, fn)\n  }\n\n  beforeResolve (fn: Function): Function {\n    return registerHook(this.resolveHooks, fn)\n  }\n\n  afterEach (fn: Function): Function {\n    return registerHook(this.afterHooks, fn)\n  }\n\n  onReady (cb: Function, errorCb?: Function) {\n    this.history.onReady(cb, errorCb)\n  }\n\n  onError (errorCb: Function) {\n    this.history.onError(errorCb)\n  }\n\n  push (location: RawLocation, onComplete?: Function, onAbort?: Function) {\n    this.history.push(location, onComplete, onAbort)\n  }\n\n  replace (location: RawLocation, onComplete?: Function, onAbort?: Function) {\n    this.history.replace(location, onComplete, onAbort)\n  }\n\n  go (n: number) {\n    this.history.go(n)\n  }\n\n  back () {\n    this.go(-1)\n  }\n\n  forward () {\n    this.go(1)\n  }\n\n  getMatchedComponents (to?: RawLocation | Route): Array<any> {\n    const route: any = to\n      ? to.matched\n        ? to\n        : this.resolve(to).route\n      : this.currentRoute\n    if (!route) {\n      return []\n    }\n    return [].concat.apply([], route.matched.map(m => {\n      return Object.keys(m.components).map(key => {\n        return m.components[key]\n      })\n    }))\n  }\n\n  resolve (\n    to: RawLocation,\n    current?: Route,\n    append?: boolean\n  ): {\n    location: Location,\n    route: Route,\n    href: string,\n    // for backwards compat\n    normalizedTo: Location,\n    resolved: Route\n  } {\n    const location = normalizeLocation(\n      to,\n      current || this.history.current,\n      append,\n      this\n    )\n    const route = this.match(location, current)\n    const fullPath = route.redirectedFrom || route.fullPath\n    const base = this.history.base\n    const href = createHref(base, fullPath, this.mode)\n    return {\n      location,\n      route,\n      href,\n      // for backwards compat\n      normalizedTo: location,\n      resolved: route\n    }\n  }\n\n  addRoutes (routes: Array<RouteConfig>) {\n    this.matcher.addRoutes(routes)\n    if (this.history.current !== START) {\n      this.history.transitionTo(this.history.getCurrentLocation())\n    }\n  }\n}\n\nfunction registerHook (list: Array<any>, fn: Function): Function {\n  list.push(fn)\n  return () => {\n    const i = list.indexOf(fn)\n    if (i > -1) list.splice(i, 1)\n  }\n}\n\nfunction createHref (base: string, fullPath: string, mode) {\n  var path = mode === 'hash' ? '#' + fullPath : fullPath\n  return base ? cleanPath(base + '/' + path) : path\n}\n\nVueRouter.install = install\nVueRouter.version = '__VERSION__'\n\nif (inBrowser && window.Vue) {\n  window.Vue.use(VueRouter)\n}\n"
  },
  {
    "path": "vue-router/src/install.js",
    "content": "import View from './components/view'\nimport Link from './components/link'\n\nexport let _Vue\n\nexport function install (Vue) {\n  if (install.installed && _Vue === Vue) return\n  install.installed = true\n\n  _Vue = Vue\n\n  const isDef = v => v !== undefined\n\n  const registerInstance = (vm, callVal) => {\n    let i = vm.$options._parentVnode\n    if (isDef(i) && isDef(i = i.data) && isDef(i = i.registerRouteInstance)) {\n      i(vm, callVal)\n    }\n  }\n\n  Vue.mixin({\n    beforeCreate () {\n      if (isDef(this.$options.router)) {\n        this._routerRoot = this\n        this._router = this.$options.router\n        this._router.init(this)\n        Vue.util.defineReactive(this, '_route', this._router.history.current)\n      } else {\n        this._routerRoot = (this.$parent && this.$parent._routerRoot) || this\n      }\n      registerInstance(this, this)\n    },\n    destroyed () {\n      registerInstance(this)\n    }\n  })\n\n  Object.defineProperty(Vue.prototype, '$router', {\n    get () { return this._routerRoot._router }\n  })\n\n  Object.defineProperty(Vue.prototype, '$route', {\n    get () { return this._routerRoot._route }\n  })\n\n  Vue.component('RouterView', View)\n  Vue.component('RouterLink', Link)\n\n  const strats = Vue.config.optionMergeStrategies\n  // use the same hook merging strategy for route hooks\n  strats.beforeRouteEnter = strats.beforeRouteLeave = strats.beforeRouteUpdate = strats.created\n}\n"
  },
  {
    "path": "vue-router/src/util/async.js",
    "content": "/* @flow */\n\nexport function runQueue (queue: Array<?NavigationGuard>, fn: Function, cb: Function) {\n  const step = index => {\n    if (index >= queue.length) {\n      cb()\n    } else {\n      if (queue[index]) {\n        fn(queue[index], () => {\n          step(index + 1)\n        })\n      } else {\n        step(index + 1)\n      }\n    }\n  }\n  step(0)\n}\n"
  },
  {
    "path": "vue-router/src/util/dom.js",
    "content": "/* @flow */\n\nexport const inBrowser = typeof window !== 'undefined'\n"
  },
  {
    "path": "vue-router/src/util/location.js",
    "content": "/* @flow */\n\nimport type VueRouter from '../index'\nimport { parsePath, resolvePath } from './path'\nimport { resolveQuery } from './query'\nimport { fillParams } from './params'\nimport { warn } from './warn'\n\nexport function normalizeLocation (\n  raw: RawLocation,\n  current: ?Route,\n  append: ?boolean,\n  router: ?VueRouter\n): Location {\n  let next: Location = typeof raw === 'string' ? { path: raw } : raw\n  // named target\n  if (next.name || next._normalized) {\n    return next\n  }\n\n  // relative params\n  if (!next.path && next.params && current) {\n    next = assign({}, next)\n    next._normalized = true\n    const params: any = assign(assign({}, current.params), next.params)\n    if (current.name) {\n      next.name = current.name\n      next.params = params\n    } else if (current.matched.length) {\n      const rawPath = current.matched[current.matched.length - 1].path\n      next.path = fillParams(rawPath, params, `path ${current.path}`)\n    } else if (process.env.NODE_ENV !== 'production') {\n      warn(false, `relative params navigation requires a current route.`)\n    }\n    return next\n  }\n\n  const parsedPath = parsePath(next.path || '')\n  const basePath = (current && current.path) || '/'\n  const path = parsedPath.path\n    ? resolvePath(parsedPath.path, basePath, append || next.append)\n    : basePath\n\n  const query = resolveQuery(\n    parsedPath.query,\n    next.query,\n    router && router.options.parseQuery\n  )\n\n  let hash = next.hash || parsedPath.hash\n  if (hash && hash.charAt(0) !== '#') {\n    hash = `#${hash}`\n  }\n\n  return {\n    _normalized: true,\n    path,\n    query,\n    hash\n  }\n}\n\nfunction assign (a, b) {\n  for (const key in b) {\n    a[key] = b[key]\n  }\n  return a\n}\n"
  },
  {
    "path": "vue-router/src/util/params.js",
    "content": "/* @flow */\n\nimport { warn } from './warn'\nimport Regexp from 'path-to-regexp'\n\n// $flow-disable-line\nconst regexpCompileCache: {\n  [key: string]: Function\n} = Object.create(null)\n\nexport function fillParams (\n  path: string,\n  params: ?Object,\n  routeMsg: string\n): string {\n  try {\n    const filler =\n      regexpCompileCache[path] ||\n      (regexpCompileCache[path] = Regexp.compile(path))\n    return filler(params || {}, { pretty: true })\n  } catch (e) {\n    if (process.env.NODE_ENV !== 'production') {\n      warn(false, `missing param for ${routeMsg}: ${e.message}`)\n    }\n    return ''\n  }\n}\n"
  },
  {
    "path": "vue-router/src/util/path.js",
    "content": "/* @flow */\n\nexport function resolvePath (\n  relative: string,\n  base: string,\n  append?: boolean\n): string {\n  const firstChar = relative.charAt(0)\n  if (firstChar === '/') {\n    return relative\n  }\n\n  if (firstChar === '?' || firstChar === '#') {\n    return base + relative\n  }\n\n  const stack = base.split('/')\n\n  // remove trailing segment if:\n  // - not appending\n  // - appending to trailing slash (last segment is empty)\n  if (!append || !stack[stack.length - 1]) {\n    stack.pop()\n  }\n\n  // resolve relative path\n  const segments = relative.replace(/^\\//, '').split('/')\n  for (let i = 0; i < segments.length; i++) {\n    const segment = segments[i]\n    if (segment === '..') {\n      stack.pop()\n    } else if (segment !== '.') {\n      stack.push(segment)\n    }\n  }\n\n  // ensure leading slash\n  if (stack[0] !== '') {\n    stack.unshift('')\n  }\n\n  return stack.join('/')\n}\n\nexport function parsePath (path: string): {\n  path: string;\n  query: string;\n  hash: string;\n} {\n  let hash = ''\n  let query = ''\n\n  const hashIndex = path.indexOf('#')\n  if (hashIndex >= 0) {\n    hash = path.slice(hashIndex)\n    path = path.slice(0, hashIndex)\n  }\n\n  const queryIndex = path.indexOf('?')\n  if (queryIndex >= 0) {\n    query = path.slice(queryIndex + 1)\n    path = path.slice(0, queryIndex)\n  }\n\n  return {\n    path,\n    query,\n    hash\n  }\n}\n\nexport function cleanPath (path: string): string {\n  return path.replace(/\\/\\//g, '/')\n}\n"
  },
  {
    "path": "vue-router/src/util/push-state.js",
    "content": "/* @flow */\n\nimport { inBrowser } from './dom'\nimport { saveScrollPosition } from './scroll'\n\nexport const supportsPushState = inBrowser && (function () {\n  const ua = window.navigator.userAgent\n\n  if (\n    (ua.indexOf('Android 2.') !== -1 || ua.indexOf('Android 4.0') !== -1) &&\n    ua.indexOf('Mobile Safari') !== -1 &&\n    ua.indexOf('Chrome') === -1 &&\n    ua.indexOf('Windows Phone') === -1\n  ) {\n    return false\n  }\n\n  return window.history && 'pushState' in window.history\n})()\n\n// use User Timing api (if present) for more accurate key precision\nconst Time = inBrowser && window.performance && window.performance.now\n  ? window.performance\n  : Date\n\nlet _key: string = genKey()\n\nfunction genKey (): string {\n  return Time.now().toFixed(3)\n}\n\nexport function getStateKey () {\n  return _key\n}\n\nexport function setStateKey (key: string) {\n  _key = key\n}\n\nexport function pushState (url?: string, replace?: boolean) {\n  saveScrollPosition()\n  // try...catch the pushState call to get around Safari\n  // DOM Exception 18 where it limits to 100 pushState calls\n  const history = window.history\n  try {\n    if (replace) {\n      history.replaceState({ key: _key }, '', url)\n    } else {\n      _key = genKey()\n      history.pushState({ key: _key }, '', url)\n    }\n  } catch (e) {\n    window.location[replace ? 'replace' : 'assign'](url)\n  }\n}\n\nexport function replaceState (url?: string) {\n  pushState(url, true)\n}\n"
  },
  {
    "path": "vue-router/src/util/query.js",
    "content": "/* @flow */\n\nimport { warn } from './warn'\n\nconst encodeReserveRE = /[!'()*]/g\nconst encodeReserveReplacer = c => '%' + c.charCodeAt(0).toString(16)\nconst commaRE = /%2C/g\n\n// fixed encodeURIComponent which is more conformant to RFC3986:\n// - escapes [!'()*]\n// - preserve commas\nconst encode = str => encodeURIComponent(str)\n  .replace(encodeReserveRE, encodeReserveReplacer)\n  .replace(commaRE, ',')\n\nconst decode = decodeURIComponent\n\nexport function resolveQuery (\n  query: ?string,\n  extraQuery: Dictionary<string> = {},\n  _parseQuery: ?Function\n): Dictionary<string> {\n  const parse = _parseQuery || parseQuery\n  let parsedQuery\n  try {\n    parsedQuery = parse(query || '')\n  } catch (e) {\n    process.env.NODE_ENV !== 'production' && warn(false, e.message)\n    parsedQuery = {}\n  }\n  for (const key in extraQuery) {\n    parsedQuery[key] = extraQuery[key]\n  }\n  return parsedQuery\n}\n\nfunction parseQuery (query: string): Dictionary<string> {\n  const res = {}\n\n  query = query.trim().replace(/^(\\?|#|&)/, '')\n\n  if (!query) {\n    return res\n  }\n\n  query.split('&').forEach(param => {\n    const parts = param.replace(/\\+/g, ' ').split('=')\n    const key = decode(parts.shift())\n    const val = parts.length > 0\n      ? decode(parts.join('='))\n      : null\n\n    if (res[key] === undefined) {\n      res[key] = val\n    } else if (Array.isArray(res[key])) {\n      res[key].push(val)\n    } else {\n      res[key] = [res[key], val]\n    }\n  })\n\n  return res\n}\n\nexport function stringifyQuery (obj: Dictionary<string>): string {\n  const res = obj ? Object.keys(obj).map(key => {\n    const val = obj[key]\n\n    if (val === undefined) {\n      return ''\n    }\n\n    if (val === null) {\n      return encode(key)\n    }\n\n    if (Array.isArray(val)) {\n      const result = []\n      val.forEach(val2 => {\n        if (val2 === undefined) {\n          return\n        }\n        if (val2 === null) {\n          result.push(encode(key))\n        } else {\n          result.push(encode(key) + '=' + encode(val2))\n        }\n      })\n      return result.join('&')\n    }\n\n    return encode(key) + '=' + encode(val)\n  }).filter(x => x.length > 0).join('&') : null\n  return res ? `?${res}` : ''\n}\n"
  },
  {
    "path": "vue-router/src/util/resolve-components.js",
    "content": "/* @flow */\n\nimport { _Vue } from '../install'\nimport { warn, isError } from './warn'\n\nexport function resolveAsyncComponents (matched: Array<RouteRecord>): Function {\n  return (to, from, next) => {\n    let hasAsync = false\n    let pending = 0\n    let error = null\n\n    flatMapComponents(matched, (def, _, match, key) => {\n      // if it's a function and doesn't have cid attached,\n      // assume it's an async component resolve function.\n      // we are not using Vue's default async resolving mechanism because\n      // we want to halt the navigation until the incoming component has been\n      // resolved.\n      if (typeof def === 'function' && def.cid === undefined) {\n        hasAsync = true\n        pending++\n\n        const resolve = once(resolvedDef => {\n          if (isESModule(resolvedDef)) {\n            resolvedDef = resolvedDef.default\n          }\n          // save resolved on async factory in case it's used elsewhere\n          def.resolved = typeof resolvedDef === 'function'\n            ? resolvedDef\n            : _Vue.extend(resolvedDef)\n          match.components[key] = resolvedDef\n          pending--\n          if (pending <= 0) {\n            next()\n          }\n        })\n\n        const reject = once(reason => {\n          const msg = `Failed to resolve async component ${key}: ${reason}`\n          process.env.NODE_ENV !== 'production' && warn(false, msg)\n          if (!error) {\n            error = isError(reason)\n              ? reason\n              : new Error(msg)\n            next(error)\n          }\n        })\n\n        let res\n        try {\n          res = def(resolve, reject)\n        } catch (e) {\n          reject(e)\n        }\n        if (res) {\n          if (typeof res.then === 'function') {\n            res.then(resolve, reject)\n          } else {\n            // new syntax in Vue 2.3\n            const comp = res.component\n            if (comp && typeof comp.then === 'function') {\n              comp.then(resolve, reject)\n            }\n          }\n        }\n      }\n    })\n\n    if (!hasAsync) next()\n  }\n}\n\nexport function flatMapComponents (\n  matched: Array<RouteRecord>,\n  fn: Function\n): Array<?Function> {\n  return flatten(matched.map(m => {\n    return Object.keys(m.components).map(key => fn(\n      m.components[key],\n      m.instances[key],\n      m, key\n    ))\n  }))\n}\n\nexport function flatten (arr: Array<any>): Array<any> {\n  return Array.prototype.concat.apply([], arr)\n}\n\nconst hasSymbol =\n  typeof Symbol === 'function' &&\n  typeof Symbol.toStringTag === 'symbol'\n\nfunction isESModule (obj) {\n  return obj.__esModule || (hasSymbol && obj[Symbol.toStringTag] === 'Module')\n}\n\n// in Webpack 2, require.ensure now also returns a Promise\n// so the resolve/reject functions may get called an extra time\n// if the user uses an arrow function shorthand that happens to\n// return that Promise.\nfunction once (fn) {\n  let called = false\n  return function (...args) {\n    if (called) return\n    called = true\n    return fn.apply(this, args)\n  }\n}\n"
  },
  {
    "path": "vue-router/src/util/route.js",
    "content": "/* @flow */\n\nimport type VueRouter from '../index'\nimport { stringifyQuery } from './query'\n\nconst trailingSlashRE = /\\/?$/\n\nexport function createRoute (\n  record: ?RouteRecord,\n  location: Location,\n  redirectedFrom?: ?Location,\n  router?: VueRouter\n): Route {\n  const stringifyQuery = router && router.options.stringifyQuery\n\n  let query: any = location.query || {}\n  try {\n    query = clone(query)\n  } catch (e) {}\n\n  const route: Route = {\n    name: location.name || (record && record.name),\n    meta: (record && record.meta) || {},\n    path: location.path || '/',\n    hash: location.hash || '',\n    query,\n    params: location.params || {},\n    fullPath: getFullPath(location, stringifyQuery),\n    matched: record ? formatMatch(record) : []\n  }\n  if (redirectedFrom) {\n    route.redirectedFrom = getFullPath(redirectedFrom, stringifyQuery)\n  }\n  return Object.freeze(route)\n}\n\nfunction clone (value) {\n  if (Array.isArray(value)) {\n    return value.map(clone)\n  } else if (value && typeof value === 'object') {\n    const res = {}\n    for (const key in value) {\n      res[key] = clone(value[key])\n    }\n    return res\n  } else {\n    return value\n  }\n}\n\n// the starting route that represents the initial state\nexport const START = createRoute(null, {\n  path: '/'\n})\n\nfunction formatMatch (record: ?RouteRecord): Array<RouteRecord> {\n  const res = []\n  while (record) {\n    res.unshift(record)\n    record = record.parent\n  }\n  return res\n}\n\nfunction getFullPath (\n  { path, query = {}, hash = '' },\n  _stringifyQuery\n): string {\n  const stringify = _stringifyQuery || stringifyQuery\n  return (path || '/') + stringify(query) + hash\n}\n\nexport function isSameRoute (a: Route, b: ?Route): boolean {\n  if (b === START) {\n    return a === b\n  } else if (!b) {\n    return false\n  } else if (a.path && b.path) {\n    return (\n      a.path.replace(trailingSlashRE, '') === b.path.replace(trailingSlashRE, '') &&\n      a.hash === b.hash &&\n      isObjectEqual(a.query, b.query)\n    )\n  } else if (a.name && b.name) {\n    return (\n      a.name === b.name &&\n      a.hash === b.hash &&\n      isObjectEqual(a.query, b.query) &&\n      isObjectEqual(a.params, b.params)\n    )\n  } else {\n    return false\n  }\n}\n\nfunction isObjectEqual (a = {}, b = {}): boolean {\n  // handle null value #1566\n  if (!a || !b) return a === b\n  const aKeys = Object.keys(a)\n  const bKeys = Object.keys(b)\n  if (aKeys.length !== bKeys.length) {\n    return false\n  }\n  return aKeys.every(key => {\n    const aVal = a[key]\n    const bVal = b[key]\n    // check nested equality\n    if (typeof aVal === 'object' && typeof bVal === 'object') {\n      return isObjectEqual(aVal, bVal)\n    }\n    return String(aVal) === String(bVal)\n  })\n}\n\nexport function isIncludedRoute (current: Route, target: Route): boolean {\n  return (\n    current.path.replace(trailingSlashRE, '/').indexOf(\n      target.path.replace(trailingSlashRE, '/')\n    ) === 0 &&\n    (!target.hash || current.hash === target.hash) &&\n    queryIncludes(current.query, target.query)\n  )\n}\n\nfunction queryIncludes (current: Dictionary<string>, target: Dictionary<string>): boolean {\n  for (const key in target) {\n    if (!(key in current)) {\n      return false\n    }\n  }\n  return true\n}\n"
  },
  {
    "path": "vue-router/src/util/scroll.js",
    "content": "/* @flow */\n\nimport type Router from '../index'\nimport { assert } from './warn'\nimport { getStateKey, setStateKey } from './push-state'\n\nconst positionStore = Object.create(null)\n\nexport function setupScroll () {\n  // Fix for #1585 for Firefox\n  window.history.replaceState({ key: getStateKey() }, '')\n  window.addEventListener('popstate', e => {\n    saveScrollPosition()\n    if (e.state && e.state.key) {\n      setStateKey(e.state.key)\n    }\n  })\n}\n\nexport function handleScroll (\n  router: Router,\n  to: Route,\n  from: Route,\n  isPop: boolean\n) {\n  if (!router.app) {\n    return\n  }\n\n  const behavior = router.options.scrollBehavior\n  if (!behavior) {\n    return\n  }\n\n  if (process.env.NODE_ENV !== 'production') {\n    assert(typeof behavior === 'function', `scrollBehavior must be a function`)\n  }\n\n  // wait until re-render finishes before scrolling\n  router.app.$nextTick(() => {\n    const position = getScrollPosition()\n    const shouldScroll = behavior.call(router, to, from, isPop ? position : null)\n\n    if (!shouldScroll) {\n      return\n    }\n\n    if (typeof shouldScroll.then === 'function') {\n      shouldScroll.then(shouldScroll => {\n        scrollToPosition((shouldScroll: any), position)\n      }).catch(err => {\n        if (process.env.NODE_ENV !== 'production') {\n          assert(false, err.toString())\n        }\n      })\n    } else {\n      scrollToPosition(shouldScroll, position)\n    }\n  })\n}\n\nexport function saveScrollPosition () {\n  const key = getStateKey()\n  if (key) {\n    positionStore[key] = {\n      x: window.pageXOffset,\n      y: window.pageYOffset\n    }\n  }\n}\n\nfunction getScrollPosition (): ?Object {\n  const key = getStateKey()\n  if (key) {\n    return positionStore[key]\n  }\n}\n\nfunction getElementPosition (el: Element, offset: Object): Object {\n  const docEl: any = document.documentElement\n  const docRect = docEl.getBoundingClientRect()\n  const elRect = el.getBoundingClientRect()\n  return {\n    x: elRect.left - docRect.left - offset.x,\n    y: elRect.top - docRect.top - offset.y\n  }\n}\n\nfunction isValidPosition (obj: Object): boolean {\n  return isNumber(obj.x) || isNumber(obj.y)\n}\n\nfunction normalizePosition (obj: Object): Object {\n  return {\n    x: isNumber(obj.x) ? obj.x : window.pageXOffset,\n    y: isNumber(obj.y) ? obj.y : window.pageYOffset\n  }\n}\n\nfunction normalizeOffset (obj: Object): Object {\n  return {\n    x: isNumber(obj.x) ? obj.x : 0,\n    y: isNumber(obj.y) ? obj.y : 0\n  }\n}\n\nfunction isNumber (v: any): boolean {\n  return typeof v === 'number'\n}\n\nfunction scrollToPosition (shouldScroll, position) {\n  const isObject = typeof shouldScroll === 'object'\n  if (isObject && typeof shouldScroll.selector === 'string') {\n    const el = document.querySelector(shouldScroll.selector)\n    if (el) {\n      let offset = shouldScroll.offset && typeof shouldScroll.offset === 'object' ? shouldScroll.offset : {}\n      offset = normalizeOffset(offset)\n      position = getElementPosition(el, offset)\n    } else if (isValidPosition(shouldScroll)) {\n      position = normalizePosition(shouldScroll)\n    }\n  } else if (isObject && isValidPosition(shouldScroll)) {\n    position = normalizePosition(shouldScroll)\n  }\n\n  if (position) {\n    window.scrollTo(position.x, position.y)\n  }\n}\n"
  },
  {
    "path": "vue-router/src/util/warn.js",
    "content": "/* @flow */\n\nexport function assert (condition: any, message: string) {\n  if (!condition) {\n    throw new Error(`[vue-router] ${message}`)\n  }\n}\n\nexport function warn (condition: any, message: string) {\n  if (process.env.NODE_ENV !== 'production' && !condition) {\n    typeof console !== 'undefined' && console.warn(`[vue-router] ${message}`)\n  }\n}\n\nexport function isError (err: any): boolean {\n  return Object.prototype.toString.call(err).indexOf('Error') > -1\n}\n"
  },
  {
    "path": "vue-router/test/e2e/nightwatch.config.js",
    "content": "// http://nightwatchjs.org/guide#settings-file\n\nmodule.exports = {\n  'src_folders': ['test/e2e/specs'],\n  'output_folder': 'test/e2e/reports',\n  'custom_commands_path': ['node_modules/nightwatch-helpers/commands'],\n  'custom_assertions_path': ['node_modules/nightwatch-helpers/assertions'],\n\n  'selenium': {\n    'start_process': true,\n    'server_path': require('selenium-server').path,\n    'host': '127.0.0.1',\n    'port': 4444,\n    'cli_args': {\n      'webdriver.chrome.driver': require('chromedriver').path\n    }\n  },\n\n  'test_settings': {\n    'default': {\n      'selenium_port': 4444,\n      'selenium_host': 'localhost',\n      'silent': true,\n      'screenshots': {\n        'enabled': true,\n        'on_failure': true,\n        'on_error': false,\n        'path': 'test/e2e/screenshots'\n      }\n    },\n\n    'chrome': {\n      'desiredCapabilities': {\n        'browserName': 'chrome',\n        'javascriptEnabled': true,\n        'acceptSslCerts': true,\n        'chromeOptions': {\n          'args': [\n            'window-size=1280,800'\n          ]\n        }\n      }\n    },\n\n    'phantomjs': {\n      'desiredCapabilities': {\n        'browserName': 'phantomjs',\n        'javascriptEnabled': true,\n        'acceptSslCerts': true\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "vue-router/test/e2e/runner.js",
    "content": "var path = require('path')\nvar spawn = require('cross-spawn')\nvar args = process.argv.slice(2)\n\nvar server = args.indexOf('--dev') > -1\n  ? null\n  : require('../../examples/server')\n\nif (args.indexOf('--config') === -1) {\n  args = args.concat(['--config', 'test/e2e/nightwatch.config.js'])\n}\nif (args.indexOf('--env') === -1) {\n  args = args.concat(['--env', 'phantomjs'])\n}\nvar i = args.indexOf('--test')\nif (i > -1) {\n  args[i + 1] = 'test/e2e/specs/' + args[i + 1].replace(/\\.js$/, '') + '.js'\n}\nif (args.indexOf('phantomjs') > -1) {\n  process.env.PHANTOMJS = true\n}\n\nvar runner = spawn('./node_modules/.bin/nightwatch', args, {\n  stdio: 'inherit'\n})\n\nrunner.on('exit', function (code) {\n  server && server.close()\n  process.exit(code)\n})\n\nrunner.on('error', function (err) {\n  server && server.close()\n  throw err\n})\n"
  },
  {
    "path": "vue-router/test/e2e/specs/active-links.js",
    "content": "\nmodule.exports = {\n  'active links': function (browser) {\n    browser\n    .url('http://localhost:8080/active-links/')\n      .waitForElementVisible('#app', 1000)\n      .assert.count('li a', 11)\n      // assert correct href with base\n      .assert.attributeContains('li:nth-child(1) a', 'href', '/active-links/')\n      .assert.attributeContains('li:nth-child(2) a', 'href', '/active-links/')\n      .assert.attributeContains('li:nth-child(3) a', 'href', '/active-links/users')\n      .assert.attributeContains('li:nth-child(4) a', 'href', '/active-links/users')\n      .assert.attributeContains('li:nth-child(5) a', 'href', '/active-links/users/evan')\n      .assert.attributeContains('li:nth-child(6) a', 'href', '/active-links/users/evan#foo')\n      .assert.attributeContains('li:nth-child(7) a', 'href', '/active-links/users/evan?foo=bar')\n      .assert.attributeContains('li:nth-child(8) a', 'href', '/active-links/users/evan?foo=bar')\n      .assert.attributeContains('li:nth-child(9) a', 'href', '/active-links/users/evan?foo=bar&baz=qux')\n      .assert.attributeContains('li:nth-child(10) a', 'href', '/active-links/about')\n      .assert.attributeContains('li:nth-child(11) a', 'href', '/active-links/about')\n      .assert.containsText('.view', 'Home')\n\n    assertActiveLinks(1, [1, 2], null, [1, 2])\n    assertActiveLinks(2, [1, 2], null, [1, 2])\n    assertActiveLinks(3, [1, 3, 4], null, [3, 4])\n    assertActiveLinks(4, [1, 3, 4], null, [3, 4])\n    assertActiveLinks(5, [1, 3, 5], null, [5])\n    assertActiveLinks(6, [1, 3, 5, 6], null, [6])\n    assertActiveLinks(7, [1, 3, 5, 7, 8], null, [7, 8])\n    assertActiveLinks(8, [1, 3, 5, 7, 8], null, [7, 8])\n    assertActiveLinks(9, [1, 3, 5, 7, 9], null, [9])\n    assertActiveLinks(10, [1, 10], [11], [10], [11])\n    assertActiveLinks(11, [1, 10], [11], [10], [11])\n\n    browser.end()\n\n    function assertActiveLinks (n, activeA, activeLI, exactActiveA, exactActiveLI) {\n      browser.click(`li:nth-child(${n}) a`)\n      activeA.forEach(i => {\n        browser.assert.cssClassPresent(`li:nth-child(${i}) a`, 'router-link-active')\n      })\n      activeLI && activeLI.forEach(i => {\n        browser.assert.cssClassPresent(`li:nth-child(${i})`, 'router-link-active')\n      })\n      exactActiveA.forEach(i => {\n        browser.assert.cssClassPresent(`li:nth-child(${i}) a`, 'router-link-exact-active')\n          .assert.cssClassPresent(`li:nth-child(${i}) a`, 'router-link-active')\n      })\n      exactActiveLI && exactActiveLI.forEach(i => {\n        browser.assert.cssClassPresent(`li:nth-child(${i})`, 'router-link-exact-active')\n          .assert.cssClassPresent(`li:nth-child(${i})`, 'router-link-active')\n      })\n    }\n  }\n}\n"
  },
  {
    "path": "vue-router/test/e2e/specs/auth-flow.js",
    "content": "module.exports = {\n  'auth flow': function (browser) {\n    browser\n    .url('http://localhost:8080/auth-flow/')\n      .waitForElementVisible('#app', 1000)\n      .assert.containsText('#app p', 'You are logged out')\n\n      .click('li:nth-child(3) a')\n      .assert.urlEquals('http://localhost:8080/auth-flow/login?redirect=%2Fdashboard')\n      .assert.containsText('#app h2', 'Login')\n      .assert.containsText('#app p', 'You need to login first.')\n\n      .click('button')\n      .assert.urlEquals('http://localhost:8080/auth-flow/login?redirect=%2Fdashboard')\n      .assert.elementPresent('.error')\n\n      .setValue('input[type=password]', 'password1')\n      .click('button')\n      .assert.urlEquals('http://localhost:8080/auth-flow/dashboard')\n      .assert.containsText('#app h2', 'Dashboard')\n      .assert.containsText('#app p', 'Yay you made it!')\n\n    // reload\n    .url('http://localhost:8080/auth-flow/')\n      .waitForElementVisible('#app', 1000)\n      .assert.containsText('#app p', 'You are logged in')\n\n      // navigate when logged in\n      .click('li:nth-child(3) a')\n      .assert.urlEquals('http://localhost:8080/auth-flow/dashboard')\n      .assert.containsText('#app h2', 'Dashboard')\n      .assert.containsText('#app p', 'Yay you made it!')\n\n    // directly visit dashboard when logged in\n    .url('http://localhost:8080/auth-flow/dashboard')\n      .waitForElementVisible('#app', 1000)\n      .assert.urlEquals('http://localhost:8080/auth-flow/dashboard')\n      .assert.containsText('#app h2', 'Dashboard')\n      .assert.containsText('#app p', 'Yay you made it!')\n\n      // log out\n      .click('li:nth-child(1) a')\n      .assert.urlEquals('http://localhost:8080/auth-flow/')\n      .assert.containsText('#app p', 'You are logged out')\n\n    // directly visit dashboard when logged out\n    .url('http://localhost:8080/auth-flow/dashboard')\n      .waitForElementVisible('#app', 1000)\n      .assert.urlEquals('http://localhost:8080/auth-flow/login?redirect=%2Fdashboard')\n      .assert.containsText('#app h2', 'Login')\n      .assert.containsText('#app p', 'You need to login first.')\n      .end()\n  }\n}\n"
  },
  {
    "path": "vue-router/test/e2e/specs/basic.js",
    "content": "module.exports = {\n  'basic': function (browser) {\n    browser\n    .url('http://localhost:8080/basic/')\n      .waitForElementVisible('#app', 1000)\n      .assert.count('li', 4)\n      .assert.count('li a', 4)\n      // assert correct href with base\n      .assert.attributeContains('li:nth-child(1) a', 'href', '/basic/')\n      .assert.attributeContains('li:nth-child(2) a', 'href', '/basic/foo')\n      .assert.attributeContains('li:nth-child(3) a', 'href', '/basic/bar')\n      .assert.attributeContains('li:nth-child(4) a', 'href', '/basic/bar')\n      .assert.containsText('.view', 'home')\n\n      .click('li:nth-child(2) a')\n      .assert.urlEquals('http://localhost:8080/basic/foo')\n      .assert.containsText('.view', 'foo')\n\n      .click('li:nth-child(3) a')\n      .assert.urlEquals('http://localhost:8080/basic/bar')\n      .assert.containsText('.view', 'bar')\n\n      .click('li:nth-child(1) a')\n      .assert.urlEquals('http://localhost:8080/basic/')\n      .assert.containsText('.view', 'home')\n\n      .click('li:nth-child(4) a')\n      .assert.urlEquals('http://localhost:8080/basic/bar')\n      .assert.containsText('.view', 'bar')\n\n    // check initial visit\n    .url('http://localhost:8080/basic/foo')\n      .waitForElementVisible('#app', 1000)\n      .assert.containsText('.view', 'foo')\n      .end()\n  }\n}\n"
  },
  {
    "path": "vue-router/test/e2e/specs/data-fetching.js",
    "content": "module.exports = {\n  'data fetching': function (browser) {\n    browser\n    .url('http://localhost:8080/data-fetching/')\n      .waitForElementVisible('#app', 1000)\n      .assert.count('li a', 4)\n      .assert.containsText('.view', 'home')\n\n      .click('li:nth-child(2) a')\n      .waitForElementNotPresent('.loading', 500)\n      .assert.containsText('.post h2', 'sunt aut facere')\n      .assert.containsText('.post p', 'quia et suscipit')\n\n      .click('li:nth-child(3) a')\n      .waitForElementNotPresent('.loading', 500)\n      .assert.containsText('.post h2', 'qui est esse')\n      .assert.containsText('.post p', 'est rerum tempore')\n\n      .click('li:nth-child(4) a')\n      .waitForElementNotPresent('.loading', 500)\n      .assert.elementNotPresent('.content')\n      .assert.containsText('.error', 'Post not found')\n\n      .click('li:nth-child(1) a')\n      .assert.elementNotPresent('.post')\n      .assert.containsText('.view', 'home')\n      .end()\n  }\n}\n"
  },
  {
    "path": "vue-router/test/e2e/specs/hash-mode.js",
    "content": "module.exports = {\n  'Hash mode': function (browser) {\n    browser\n    .url('http://localhost:8080/hash-mode/')\n      .waitForElementVisible('#app', 1000)\n      .assert.count('li', 4)\n      .assert.count('li a', 3)\n      .assert.attributeContains('li:nth-child(1) a', 'href', '/hash-mode/#/')\n      .assert.attributeContains('li:nth-child(2) a', 'href', '/hash-mode/#/foo')\n      .assert.attributeContains('li:nth-child(3) a', 'href', '/hash-mode/#/bar')\n      .assert.containsText('.view', 'home')\n\n      .click('li:nth-child(2) a')\n      .assert.urlEquals('http://localhost:8080/hash-mode/#/foo')\n      .assert.containsText('.view', 'foo')\n\n      .click('li:nth-child(3) a')\n      .assert.urlEquals('http://localhost:8080/hash-mode/#/bar')\n      .assert.containsText('.view', 'bar')\n\n      .click('li:nth-child(1) a')\n      .assert.urlEquals('http://localhost:8080/hash-mode/#/')\n      .assert.containsText('.view', 'home')\n\n      .click('li:nth-child(4)')\n      .assert.urlEquals('http://localhost:8080/hash-mode/#/bar')\n      .assert.containsText('.view', 'bar')\n\n    // check initial visit\n    .url('http://localhost:8080/hash-mode/#/foo')\n      .waitForElementVisible('#app', 1000)\n      .assert.containsText('.view', 'foo')\n      .end()\n  }\n}\n"
  },
  {
    "path": "vue-router/test/e2e/specs/hash-scroll-behavior.js",
    "content": "module.exports = {\n  'scroll behavior': function (browser) {\n    browser\n    .url('http://localhost:8080/hash-scroll-behavior/')\n      .waitForElementVisible('#app', 1000)\n      .assert.count('li a', 5)\n      .assert.containsText('.view', 'home')\n\n      .execute(function () {\n        window.scrollTo(0, 100)\n      })\n      .click('li:nth-child(2) a')\n      .assert.containsText('.view', 'foo')\n      .execute(function () {\n        window.scrollTo(0, 200)\n        window.history.back()\n      })\n      .assert.containsText('.view', 'home')\n      .assert.evaluate(function () {\n        return window.pageYOffset === 100\n      }, null, 'restore scroll position on back')\n\n      // scroll on a popped entry\n      .execute(function () {\n        window.scrollTo(0, 50)\n        window.history.forward()\n      })\n      .assert.containsText('.view', 'foo')\n      .assert.evaluate(function () {\n        return window.pageYOffset === 200\n      }, null, 'restore scroll position on forward')\n\n      .execute(function () {\n        window.history.back()\n      })\n      .assert.containsText('.view', 'home')\n      .assert.evaluate(function () {\n        return window.pageYOffset === 50\n      }, null, 'restore scroll position on back again')\n\n      .click('li:nth-child(3) a')\n      .assert.evaluate(function () {\n        return window.pageYOffset === 0\n      }, null, 'scroll to top on new entry')\n\n      .click('li:nth-child(4) a')\n      .assert.evaluate(function () {\n        return document.getElementById('anchor').getBoundingClientRect().top < 1\n      }, null, 'scroll to anchor')\n\n      // scroll back to top so we can click the butotn\n      .execute(function () {\n        window.scrollTo(0, 0)\n      })\n      .click('li:nth-child(5) a')\n      .assert.evaluate(function () {\n        return document.getElementById('anchor2').getBoundingClientRect().top < 101\n      }, null, 'scroll to anchor with offset')\n      .end()\n  }\n}\n"
  },
  {
    "path": "vue-router/test/e2e/specs/lazy-loading.js",
    "content": "module.exports = {\n  'lazy loading': function (browser) {\n    browser\n    .url('http://localhost:8080/lazy-loading/')\n      .waitForElementVisible('#app', 1000)\n      .assert.count('li a', 4)\n      .assert.containsText('.view', 'home')\n\n      .click('li:nth-child(2) a')\n      .assert.containsText('.view', 'This is Foo!')\n\n      .click('li:nth-child(3) a')\n      .assert.containsText('.view', 'This is Bar!')\n\n      .click('li:nth-child(1) a')\n      .assert.containsText('.view', 'home')\n\n      .click('li:nth-child(4) a')\n      .assert.containsText('.view', 'This is Bar!')\n      .assert.containsText('.view h3', 'Baz')\n\n      // test initial visit\n    .url('http://localhost:8080/lazy-loading/foo')\n      .waitForElementVisible('#app', 1000)\n      .assert.containsText('.view', 'This is Foo!')\n\n    .url('http://localhost:8080/lazy-loading/bar/baz')\n      .waitForElementVisible('#app', 1000)\n      .assert.containsText('.view', 'This is Bar!')\n      .assert.containsText('.view h3', 'Baz')\n      .end()\n  }\n}\n"
  },
  {
    "path": "vue-router/test/e2e/specs/named-routes.js",
    "content": "module.exports = {\n  'named routes': function (browser) {\n    browser\n    .url('http://localhost:8080/named-routes/')\n      .waitForElementVisible('#app', 1000)\n      .assert.count('li a', 3)\n      // assert correct href with base\n      .assert.attributeContains('li:nth-child(1) a', 'href', '/named-routes/')\n      .assert.attributeContains('li:nth-child(2) a', 'href', '/named-routes/foo')\n      .assert.attributeContains('li:nth-child(3) a', 'href', '/named-routes/bar')\n      .assert.containsText('p', 'Current route name: home')\n      .assert.containsText('.view', 'Home')\n\n      .click('li:nth-child(2) a')\n      .assert.urlEquals('http://localhost:8080/named-routes/foo')\n      .assert.containsText('p', 'Current route name: foo')\n      .assert.containsText('.view', 'Foo')\n\n      .click('li:nth-child(3) a')\n      .assert.urlEquals('http://localhost:8080/named-routes/bar/123')\n      .assert.containsText('p', 'Current route name: bar')\n      .assert.containsText('.view', 'Bar 123')\n\n      .click('li:nth-child(1) a')\n      .assert.urlEquals('http://localhost:8080/named-routes/')\n      .assert.containsText('p', 'Current route name: home')\n      .assert.containsText('.view', 'Home')\n\n    // check initial visit\n    .url('http://localhost:8080/named-routes/foo')\n      .waitForElementVisible('#app', 1000)\n      .assert.containsText('p', 'Current route name: foo')\n      .assert.containsText('.view', 'Foo')\n      .end()\n  }\n}\n"
  },
  {
    "path": "vue-router/test/e2e/specs/named-views.js",
    "content": "module.exports = {\n  'named views': function (browser) {\n    browser\n    .url('http://localhost:8080/named-views/')\n      .waitForElementVisible('#app', 1000)\n      .assert.count('li a', 2)\n      // assert correct href with base\n      .assert.attributeContains('li:nth-child(1) a', 'href', '/named-views/')\n      .assert.attributeContains('li:nth-child(2) a', 'href', '/named-views/other')\n\n      .assert.containsText('.view.one', 'foo')\n      .assert.containsText('.view.two', 'bar')\n      .assert.containsText('.view.three', 'baz')\n\n      .click('li:nth-child(2) a')\n      .assert.urlEquals('http://localhost:8080/named-views/other')\n      .assert.containsText('.view.one', 'baz')\n      .assert.containsText('.view.two', 'bar')\n      .assert.containsText('.view.three', 'foo')\n\n      .click('li:nth-child(1) a')\n      .assert.urlEquals('http://localhost:8080/named-views/')\n      .assert.containsText('.view.one', 'foo')\n      .assert.containsText('.view.two', 'bar')\n      .assert.containsText('.view.three', 'baz')\n\n    // check initial visit\n    .url('http://localhost:8080/named-views/other')\n      .waitForElementVisible('#app', 1000)\n      .assert.containsText('.view.one', 'baz')\n      .assert.containsText('.view.two', 'bar')\n      .assert.containsText('.view.three', 'foo')\n      .end()\n  }\n}\n"
  },
  {
    "path": "vue-router/test/e2e/specs/navigation-guards.js",
    "content": "module.exports = {\n  'navigation guards': function (browser) {\n    // alert commands not available in phantom\n    if (process.env.PHANTOMJS) {\n      return\n    }\n\n    browser\n    .url('http://localhost:8080/navigation-guards/')\n      .waitForElementVisible('#app', 1000)\n      .assert.count('li a', 8)\n      .assert.containsText('.view', 'home')\n\n      .click('li:nth-child(2) a')\n      .dismissAlert()\n      .waitFor(100)\n      .dismissAlert()\n      .assert.urlEquals('http://localhost:8080/navigation-guards/')\n      .assert.containsText('.view', 'home')\n\n      .click('li:nth-child(2) a')\n      .acceptAlert()\n      .assert.urlEquals('http://localhost:8080/navigation-guards/foo')\n      .assert.containsText('.view', 'foo')\n\n      .click('li:nth-child(3) a')\n      .dismissAlert()\n      .waitFor(100)\n      .dismissAlert()\n      .assert.urlEquals('http://localhost:8080/navigation-guards/foo')\n      .assert.containsText('.view', 'foo')\n\n      .click('li:nth-child(3) a')\n      .acceptAlert()\n      .assert.urlEquals('http://localhost:8080/navigation-guards/bar')\n      .assert.containsText('.view', 'bar')\n\n      .click('li:nth-child(2) a')\n      .dismissAlert()\n      .waitFor(100)\n      .acceptAlert() // redirect to baz\n      .assert.urlEquals('http://localhost:8080/navigation-guards/baz')\n      .assert.containsText('.view', 'baz (not saved)')\n\n      .click('li:nth-child(2) a')\n      .dismissAlert() // not saved\n      .assert.urlEquals('http://localhost:8080/navigation-guards/baz')\n      .assert.containsText('.view', 'baz (not saved)')\n\n      .click('li:nth-child(2) a')\n      .acceptAlert() // not saved, force leave\n      .waitFor(100)\n      .dismissAlert() // should trigger foo's guard\n      .waitFor(100)\n      .dismissAlert()\n      .assert.urlEquals('http://localhost:8080/navigation-guards/baz')\n      .assert.containsText('.view', 'baz')\n\n      .click('li:nth-child(2) a')\n      .acceptAlert()\n      .waitFor(100)\n      .acceptAlert()\n      .assert.urlEquals('http://localhost:8080/navigation-guards/foo')\n      .assert.containsText('.view', 'foo')\n\n      .click('li:nth-child(4) a')\n      .assert.urlEquals('http://localhost:8080/navigation-guards/baz')\n      .assert.containsText('.view', 'baz (not saved)')\n      .click('button')\n      .assert.containsText('.view', 'baz (saved)')\n      .click('li:nth-child(1) a')\n      .assert.urlEquals('http://localhost:8080/navigation-guards/')\n      .assert.containsText('.view', 'home')\n\n      // test initial visit\n    .url('http://localhost:8080/navigation-guards/foo')\n      .dismissAlert()\n      .waitFor(100)\n      .dismissAlert()\n      // should redirect to root\n      .assert.urlEquals('http://localhost:8080/navigation-guards/')\n      // and should not render anything\n      .assert.elementNotPresent('.view')\n\n    .url('http://localhost:8080/navigation-guards/foo')\n      .acceptAlert()\n      .assert.urlEquals('http://localhost:8080/navigation-guards/foo')\n      .assert.containsText('.view', 'foo')\n\n    .url('http://localhost:8080/navigation-guards/bar')\n      .dismissAlert()\n      .waitFor(100)\n      .dismissAlert()\n      // should redirect to root\n      .assert.urlEquals('http://localhost:8080/navigation-guards/')\n      // and should not render anything\n      .assert.elementNotPresent('.view')\n\n    .url('http://localhost:8080/navigation-guards/bar')\n      .acceptAlert()\n      .assert.urlEquals('http://localhost:8080/navigation-guards/bar')\n      .assert.containsText('.view', 'bar')\n\n    // in-component guard\n    .click('li:nth-child(5) a')\n      .assert.urlEquals('http://localhost:8080/navigation-guards/bar')\n      .assert.containsText('.view', 'bar')\n      .waitFor(300)\n      .assert.urlEquals('http://localhost:8080/navigation-guards/qux')\n      .assert.containsText('.view', 'Qux')\n\n    // async component + in-component guard\n    .click('li:nth-child(1) a')\n      .assert.urlEquals('http://localhost:8080/navigation-guards/')\n      .assert.containsText('.view', 'home')\n    .click('li:nth-child(6) a')\n      .assert.urlEquals('http://localhost:8080/navigation-guards/')\n      .assert.containsText('.view', 'home')\n      .waitFor(300)\n      .assert.urlEquals('http://localhost:8080/navigation-guards/qux-async')\n      .assert.containsText('.view', 'Qux')\n\n    // beforeRouteUpdate\n    .click('li:nth-child(7) a')\n      .assert.urlEquals('http://localhost:8080/navigation-guards/quux/1')\n      .assert.containsText('.view', 'id:1 prevId:0')\n    .click('li:nth-child(8) a')\n      .assert.urlEquals('http://localhost:8080/navigation-guards/quux/2')\n      .assert.containsText('.view', 'id:2 prevId:1')\n    .click('li:nth-child(7) a')\n      .assert.urlEquals('http://localhost:8080/navigation-guards/quux/1')\n      .assert.containsText('.view', 'id:1 prevId:2')\n      .end()\n  }\n}\n"
  },
  {
    "path": "vue-router/test/e2e/specs/nested-router.js",
    "content": "module.exports = {\n  'basic': function (browser) {\n    browser\n    .url('http://localhost:8080/nested-router/')\n      .waitForElementVisible('#app', 1000)\n      .assert.count('li a', 3)\n\n      .click('li:nth-child(1) a')\n      .assert.urlEquals('http://localhost:8080/nested-router/nested-router')\n      .assert.containsText('.child', 'Child router path: /')\n      .assert.count('li a', 5)\n\n      .click('.child li:nth-child(1) a')\n      .assert.containsText('.child', 'Child router path: /foo')\n      .assert.containsText('.child .foo', 'foo')\n\n      .click('.child li:nth-child(2) a')\n      .assert.containsText('.child', 'Child router path: /bar')\n      .assert.containsText('.child .bar', 'bar')\n\n      .click('li:nth-child(2) a')\n      .assert.urlEquals('http://localhost:8080/nested-router/foo')\n      .assert.elementNotPresent('.child')\n      .assert.containsText('#app', 'foo')\n      .assert.count('li a', 3)\n      .end()\n  }\n}\n"
  },
  {
    "path": "vue-router/test/e2e/specs/nested-routes.js",
    "content": "module.exports = {\n  'nested routes': function (browser) {\n    browser\n    .url('http://localhost:8080/nested-routes/')\n      .waitForElementVisible('#app', 1000)\n      .assert.count('li a', 9)\n      .assert.urlEquals('http://localhost:8080/nested-routes/parent')\n      .assert.containsText('.view', 'Parent')\n      .assert.containsText('.view', 'default')\n\n      .click('li:nth-child(2) a')\n      .assert.urlEquals('http://localhost:8080/nested-routes/parent/foo')\n      .assert.containsText('.view', 'Parent')\n      .assert.containsText('.view', 'foo')\n\n      .click('li:nth-child(3) a')\n      .assert.urlEquals('http://localhost:8080/nested-routes/parent/bar')\n      .assert.containsText('.view', 'Parent')\n      .assert.containsText('.view', 'bar')\n\n      .click('li:nth-child(4) a')\n      .assert.urlEquals('http://localhost:8080/nested-routes/baz')\n      .assert.containsText('.view', 'Parent')\n      .assert.containsText('.view', 'baz')\n\n      .click('li:nth-child(5) a')\n      .assert.urlEquals('http://localhost:8080/nested-routes/parent/qux/123')\n      .assert.containsText('.view', 'Parent')\n      .assert.containsText('.view', 'qux')\n\n      .click('.nested-parent a')\n      .assert.urlEquals('http://localhost:8080/nested-routes/parent/qux/123/quux')\n      .assert.containsText('.view', 'Parent')\n      .assert.containsText('.view', 'qux')\n      .assert.containsText('.view', 'quux')\n\n      .click('li:nth-child(6) a')\n      .assert.urlEquals('http://localhost:8080/nested-routes/parent/quy/123')\n      .assert.containsText('.view', 'Parent')\n      .assert.containsText('.view', 'quy')\n      .assert.evaluate(function () {\n        var params = JSON.parse(document.querySelector('pre').textContent)\n        return (\n          JSON.stringify(params) === JSON.stringify(['quyId'])\n        )\n      }, null, 'quyId')\n\n      .click('li:nth-child(8) a')\n      .assert.urlEquals('http://localhost:8080/nested-routes/parent/zap/1')\n      .assert.containsText('.view', 'Parent')\n      .assert.containsText('.view', 'zap')\n      .assert.evaluate(function () {\n        var zapId = document.querySelector('pre').textContent\n        return (zapId === '1')\n      }, null, 'zapId')\n\n      .click('li:nth-child(7) a')\n      .assert.urlEquals('http://localhost:8080/nested-routes/parent/zap')\n      .assert.containsText('.view', 'Parent')\n      .assert.containsText('.view', 'zap')\n      .assert.evaluate(function () {\n        var zapId = document.querySelector('pre').textContent\n        return (zapId === '')\n      }, null, 'optional zapId')\n\n      // test relative params\n      .click('li:nth-child(9) a')\n      .assert.evaluate(function () {\n        var zapId = document.querySelector('pre').textContent\n        return (zapId === '2')\n      }, null, 'relative params')\n\n    // check initial visit\n    .url('http://localhost:8080/nested-routes/parent/foo')\n      .waitForElementVisible('#app', 1000)\n      .assert.containsText('.view', 'Parent')\n      .assert.containsText('.view', 'foo')\n    .url('http://localhost:8080/nested-routes/baz')\n      .waitForElementVisible('#app', 1000)\n      .assert.containsText('.view', 'Parent')\n      .assert.containsText('.view', 'baz')\n      .end()\n  }\n}\n"
  },
  {
    "path": "vue-router/test/e2e/specs/redirect.js",
    "content": "module.exports = {\n  'redirect': function (browser) {\n    browser\n    .url('http://localhost:8080/redirect/')\n      .waitForElementVisible('#app', 1000)\n      .assert.count('li a', 12)\n      // assert correct href with base\n      .assert.attributeContains('li:nth-child(1) a', 'href', '/redirect/relative-redirect')\n      .assert.attributeContains('li:nth-child(2) a', 'href', '/redirect/relative-redirect?foo=bar')\n      .assert.attributeContains('li:nth-child(3) a', 'href', '/redirect/absolute-redirect')\n      .assert.attributeContains('li:nth-child(4) a', 'href', '/redirect/dynamic-redirect')\n      .assert.attributeContains('li:nth-child(5) a', 'href', '/redirect/dynamic-redirect/123')\n      .assert.attributeContains('li:nth-child(6) a', 'href', '/redirect/dynamic-redirect?to=foo')\n      .assert.attributeContains('li:nth-child(7) a', 'href', '/redirect/dynamic-redirect#baz')\n      .assert.attributeContains('li:nth-child(8) a', 'href', '/redirect/named-redirect')\n      .assert.attributeContains('li:nth-child(9) a', 'href', '/redirect/redirect-with-params/123')\n      .assert.attributeContains('li:nth-child(10) a', 'href', '/redirect/foobar')\n      .assert.attributeContains('li:nth-child(11) a', 'href', '/redirect/FooBar')\n      .assert.attributeContains('li:nth-child(12) a', 'href', '/not-found')\n\n      .assert.containsText('.view', 'default')\n\n      .click('li:nth-child(1) a')\n      .assert.urlEquals('http://localhost:8080/redirect/foo')\n      .assert.containsText('.view', 'foo')\n\n      .click('li:nth-child(2) a')\n      .assert.urlEquals('http://localhost:8080/redirect/foo?foo=bar')\n      .assert.containsText('.view', 'foo')\n\n      .click('li:nth-child(3) a')\n      .assert.urlEquals('http://localhost:8080/redirect/bar')\n      .assert.containsText('.view', 'bar')\n\n      .click('li:nth-child(4) a')\n      .assert.urlEquals('http://localhost:8080/redirect/bar')\n      .assert.containsText('.view', 'bar')\n\n      .click('li:nth-child(5) a')\n      .assert.urlEquals('http://localhost:8080/redirect/with-params/123')\n      .assert.containsText('.view', '123')\n\n      .click('li:nth-child(6) a')\n      .assert.urlEquals('http://localhost:8080/redirect/foo')\n      .assert.containsText('.view', 'foo')\n\n      .click('li:nth-child(7) a')\n      .assert.urlEquals('http://localhost:8080/redirect/baz')\n      .assert.containsText('.view', 'baz')\n\n      .click('li:nth-child(8) a')\n      .assert.urlEquals('http://localhost:8080/redirect/baz')\n      .assert.containsText('.view', 'baz')\n\n      .click('li:nth-child(9) a')\n      .assert.urlEquals('http://localhost:8080/redirect/with-params/123')\n      .assert.containsText('.view', '123')\n\n      .click('li:nth-child(10) a')\n      .assert.urlEquals('http://localhost:8080/redirect/foobar')\n      .assert.containsText('.view', 'foobar')\n\n      .click('li:nth-child(11) a')\n      .assert.urlEquals('http://localhost:8080/redirect/FooBar')\n      .assert.containsText('.view', 'FooBar')\n\n      .click('li:nth-child(12) a')\n      .assert.urlEquals('http://localhost:8080/redirect/')\n      .assert.containsText('.view', 'default')\n\n    // check initial visit\n    .url('http://localhost:8080/redirect/relative-redirect')\n      .waitForElementVisible('#app', 1000)\n      .assert.urlEquals('http://localhost:8080/redirect/foo')\n      .assert.containsText('.view', 'foo')\n\n    .url('http://localhost:8080/redirect/relative-redirect?foo=bar')\n      .waitForElementVisible('#app', 1000)\n      .assert.urlEquals('http://localhost:8080/redirect/foo?foo=bar')\n      .assert.containsText('.view', 'foo')\n\n    .url('http://localhost:8080/redirect/absolute-redirect')\n      .waitForElementVisible('#app', 1000)\n      .assert.urlEquals('http://localhost:8080/redirect/bar')\n      .assert.containsText('.view', 'bar')\n\n    .url('http://localhost:8080/redirect/dynamic-redirect')\n      .waitForElementVisible('#app', 1000)\n      .assert.urlEquals('http://localhost:8080/redirect/bar')\n      .assert.containsText('.view', 'bar')\n\n    .url('http://localhost:8080/redirect/dynamic-redirect/123')\n      .waitForElementVisible('#app', 1000)\n      .assert.urlEquals('http://localhost:8080/redirect/with-params/123')\n      .assert.containsText('.view', '123')\n\n    .url('http://localhost:8080/redirect/dynamic-redirect?to=foo')\n      .waitForElementVisible('#app', 1000)\n      .assert.urlEquals('http://localhost:8080/redirect/foo')\n      .assert.containsText('.view', 'foo')\n\n    .url('http://localhost:8080/redirect/dynamic-redirect#baz')\n      .waitForElementVisible('#app', 1000)\n      .assert.urlEquals('http://localhost:8080/redirect/baz')\n      .assert.containsText('.view', 'baz')\n\n    .url('http://localhost:8080/redirect/named-redirect')\n      .waitForElementVisible('#app', 1000)\n      .assert.urlEquals('http://localhost:8080/redirect/baz')\n      .assert.containsText('.view', 'baz')\n\n    .url('http://localhost:8080/redirect/redirect-with-params/123')\n      .waitForElementVisible('#app', 1000)\n      .assert.urlEquals('http://localhost:8080/redirect/with-params/123')\n      .assert.containsText('.view', '123')\n\n    .url('http://localhost:8080/redirect/foobar')\n      .waitForElementVisible('#app', 1000)\n      .assert.urlEquals('http://localhost:8080/redirect/foobar')\n      .assert.containsText('.view', 'foobar')\n\n    .url('http://localhost:8080/redirect/FooBar')\n      .waitForElementVisible('#app', 1000)\n      .assert.urlEquals('http://localhost:8080/redirect/FooBar')\n      .assert.containsText('.view', 'FooBar')\n\n    .url('http://localhost:8080/redirect/not-found')\n      .waitForElementVisible('#app', 1000)\n      .assert.urlEquals('http://localhost:8080/redirect/')\n      .assert.containsText('.view', 'default')\n      .end()\n  }\n}\n"
  },
  {
    "path": "vue-router/test/e2e/specs/route-alias.js",
    "content": "module.exports = {\n  'route alias': function (browser) {\n    browser\n    .url('http://localhost:8080/route-alias/')\n      .waitForElementVisible('#app', 1000)\n      .assert.count('li a', 7)\n      // assert correct href with base\n      .assert.attributeContains('li:nth-child(1) a', 'href', '/root-alias')\n      .assert.attributeContains('li:nth-child(2) a', 'href', '/route-alias/foo')\n      .assert.attributeContains('li:nth-child(3) a', 'href', '/route-alias/home/bar-alias')\n      .assert.attributeContains('li:nth-child(4) a', 'href', '/route-alias/baz')\n      .assert.attributeContains('li:nth-child(5) a', 'href', '/route-alias/home/baz-alias')\n      .assert.attributeEquals('li:nth-child(6) a', 'href', 'http://localhost:8080/route-alias/home')\n      .assert.attributeContains('li:nth-child(7) a', 'href', '/route-alias/home/nested-alias/foo')\n\n      .click('li:nth-child(1) a')\n      .assert.urlEquals('http://localhost:8080/route-alias/root-alias')\n      .assert.containsText('.view', 'root')\n\n      .click('li:nth-child(2) a')\n      .assert.urlEquals('http://localhost:8080/route-alias/foo')\n      .assert.containsText('.view', 'Home')\n      .assert.containsText('.view', 'foo')\n\n      .click('li:nth-child(3) a')\n      .assert.urlEquals('http://localhost:8080/route-alias/home/bar-alias')\n      .assert.containsText('.view', 'Home')\n      .assert.containsText('.view', 'bar')\n\n      .click('li:nth-child(4) a')\n      .assert.urlEquals('http://localhost:8080/route-alias/baz')\n      .assert.containsText('.view', 'Home')\n      .assert.containsText('.view', 'baz')\n\n      .click('li:nth-child(5) a')\n      .assert.urlEquals('http://localhost:8080/route-alias/home/baz-alias')\n      .assert.containsText('.view', 'Home')\n      .assert.containsText('.view', 'baz')\n\n      .click('li:nth-child(6) a')\n      .assert.urlEquals('http://localhost:8080/route-alias/home')\n      .assert.containsText('.view', 'Home')\n      .assert.containsText('.view', 'default')\n\n      .click('li:nth-child(7) a')\n      .assert.urlEquals('http://localhost:8080/route-alias/home/nested-alias/foo')\n      .assert.containsText('.view', 'Home')\n      .assert.containsText('.view', 'nested foo')\n\n    // check initial visit\n    .url('http://localhost:8080/route-alias/foo')\n      .waitForElementVisible('#app', 1000)\n      .assert.urlEquals('http://localhost:8080/route-alias/foo')\n      .assert.containsText('.view', 'Home')\n      .assert.containsText('.view', 'foo')\n\n    .url('http://localhost:8080/route-alias/home/bar-alias')\n      .waitForElementVisible('#app', 1000)\n      .assert.urlEquals('http://localhost:8080/route-alias/home/bar-alias')\n      .assert.containsText('.view', 'Home')\n      .assert.containsText('.view', 'bar')\n\n    .url('http://localhost:8080/route-alias/baz')\n      .waitForElementVisible('#app', 1000)\n      .assert.urlEquals('http://localhost:8080/route-alias/baz')\n      .assert.containsText('.view', 'Home')\n      .assert.containsText('.view', 'baz')\n\n    .url('http://localhost:8080/route-alias/home/baz-alias')\n      .waitForElementVisible('#app', 1000)\n      .assert.urlEquals('http://localhost:8080/route-alias/home/baz-alias')\n      .assert.containsText('.view', 'Home')\n      .assert.containsText('.view', 'baz')\n\n    .url('http://localhost:8080/route-alias/home')\n      .waitForElementVisible('#app', 1000)\n      .assert.urlEquals('http://localhost:8080/route-alias/home')\n      .assert.containsText('.view', 'Home')\n      .assert.containsText('.view', 'default')\n\n    .url('http://localhost:8080/route-alias/home/nested-alias/foo')\n      .waitForElementVisible('#app', 1000)\n      .assert.urlEquals('http://localhost:8080/route-alias/home/nested-alias/foo')\n      .assert.containsText('.view', 'Home')\n      .assert.containsText('.view', 'nested foo')\n      .end()\n  }\n}\n"
  },
  {
    "path": "vue-router/test/e2e/specs/route-matching.js",
    "content": "module.exports = {\n  'route-matching': function (browser) {\n    browser\n    .url('http://localhost:8080/route-matching/')\n      .waitForElementVisible('#app', 1000)\n      .assert.count('li a', 10)\n      .assert.evaluate(function () {\n        var route = JSON.parse(document.querySelector('pre').textContent)\n        return (\n          route.matched.length === 1 &&\n          route.matched[0].path === '' &&\n          route.fullPath === '/' &&\n          JSON.stringify(route.params) === JSON.stringify({})\n        )\n      }, null, '/')\n\n      .click('li:nth-child(2) a')\n      .assert.evaluate(function () {\n        var route = JSON.parse(document.querySelector('pre').textContent)\n        return (\n          route.matched.length === 1 &&\n          route.matched[0].path === '/params/:foo/:bar' &&\n          route.fullPath === '/params/foo/bar' &&\n          JSON.stringify(route.params) === JSON.stringify({\n            foo: 'foo',\n            bar: 'bar'\n          })\n        )\n      }, null, '/params/foo/bar')\n\n      .click('li:nth-child(3) a')\n      .assert.evaluate(function () {\n        var route = JSON.parse(document.querySelector('pre').textContent)\n        return (\n          route.matched.length === 1 &&\n          route.matched[0].path === '/optional-params/:foo?' &&\n          route.fullPath === '/optional-params' &&\n          JSON.stringify(route.params) === JSON.stringify({})\n        )\n      }, null, '/optional-params')\n\n      .click('li:nth-child(4) a')\n      .assert.evaluate(function () {\n        var route = JSON.parse(document.querySelector('pre').textContent)\n        return (\n          route.matched.length === 1 &&\n          route.matched[0].path === '/optional-params/:foo?' &&\n          route.fullPath === '/optional-params/foo' &&\n          JSON.stringify(route.params) === JSON.stringify({\n            foo: 'foo'\n          })\n        )\n      }, null, '/optional-params/foo')\n\n      .click('li:nth-child(5) a')\n      .assert.evaluate(function () {\n        var route = JSON.parse(document.querySelector('pre').textContent)\n        return (\n          route.matched.length === 1 &&\n          route.matched[0].path === '/params-with-regex/:id(\\\\d+)' &&\n          route.fullPath === '/params-with-regex/123' &&\n          JSON.stringify(route.params) === JSON.stringify({\n            id: '123'\n          })\n        )\n      }, null, '/params-with-regex/123')\n\n      .click('li:nth-child(6) a')\n      .assert.evaluate(function () {\n        var route = JSON.parse(document.querySelector('pre').textContent)\n        return (\n          route.matched.length === 0 &&\n          route.fullPath === '/params-with-regex/abc' &&\n          JSON.stringify(route.params) === JSON.stringify({})\n        )\n      }, null, '/params-with-regex/abc')\n\n      .click('li:nth-child(7) a')\n      .assert.evaluate(function () {\n        var route = JSON.parse(document.querySelector('pre').textContent)\n        return (\n          route.matched.length === 1 &&\n          route.matched[0].path === '/asterisk/*' &&\n          route.fullPath === '/asterisk/foo' &&\n          JSON.stringify(route.params) === JSON.stringify({\n            0: 'foo'\n          })\n        )\n      }, null, '/asterisk/foo')\n\n      .click('li:nth-child(8) a')\n      .assert.evaluate(function () {\n        var route = JSON.parse(document.querySelector('pre').textContent)\n        return (\n          route.matched.length === 1 &&\n          route.matched[0].path === '/asterisk/*' &&\n          route.fullPath === '/asterisk/foo/bar' &&\n          JSON.stringify(route.params) === JSON.stringify({\n            0: 'foo/bar'\n          })\n        )\n      }, null, '/asterisk/foo/bar')\n\n      .click('li:nth-child(9) a')\n      .assert.evaluate(function () {\n        var route = JSON.parse(document.querySelector('pre').textContent)\n        return (\n          route.matched.length === 1 &&\n          route.matched[0].path === '/optional-group/(foo/)?bar' &&\n          route.fullPath === '/optional-group/bar' &&\n          JSON.stringify(route.params) === JSON.stringify({})\n        )\n      }, null, '/optional-group/bar')\n\n      .click('li:nth-child(10) a')\n      .assert.evaluate(function () {\n        var route = JSON.parse(document.querySelector('pre').textContent)\n        return (\n          route.matched.length === 1 &&\n          route.matched[0].path === '/optional-group/(foo/)?bar' &&\n          route.fullPath === '/optional-group/foo/bar' &&\n          JSON.stringify(route.params) === JSON.stringify({\n            0: 'foo/'\n          })\n        )\n      }, null, '/optional-group/foo/bar')\n      .end()\n  }\n}\n"
  },
  {
    "path": "vue-router/test/e2e/specs/route-props.js",
    "content": "const $attrs = ' { \"foo\": \"123\" }'\n\nmodule.exports = {\n  'route-props': function (browser) {\n    browser\n    .url('http://localhost:8080/route-props/')\n      .waitForElementVisible('#app', 1000)\n      .assert.count('li a', 5)\n\n      .assert.urlEquals('http://localhost:8080/route-props/')\n      .assert.containsText('.hello', 'Hello Vue!' + $attrs)\n\n      .click('li:nth-child(2) a')\n      .assert.urlEquals('http://localhost:8080/route-props/hello/you')\n      .assert.containsText('.hello', 'Hello you' + $attrs)\n\n      .click('li:nth-child(3) a')\n      .assert.urlEquals('http://localhost:8080/route-props/static')\n      .assert.containsText('.hello', 'Hello world' + $attrs)\n\n      .click('li:nth-child(4) a')\n      .assert.urlEquals('http://localhost:8080/route-props/dynamic/1')\n      .assert.containsText('.hello', 'Hello ' + ((new Date()).getFullYear() + 1)+ '!' + $attrs)\n\n      .click('li:nth-child(5) a')\n      .assert.urlEquals('http://localhost:8080/route-props/attrs')\n      .assert.containsText('.hello', 'Hello attrs' + $attrs)\n\n      // should be consistent\n      .click('li:nth-child(4) a')\n      .click('li:nth-child(5) a')\n      .assert.urlEquals('http://localhost:8080/route-props/attrs')\n      .assert.containsText('.hello', 'Hello attrs' + $attrs)\n\n      .end()\n  }\n}\n"
  },
  {
    "path": "vue-router/test/e2e/specs/scroll-behavior.js",
    "content": "module.exports = {\n  'scroll behavior': function (browser) {\n    const TIMEOUT = 2000\n\n    browser\n    .url('http://localhost:8080/scroll-behavior/')\n      .waitForElementVisible('#app', 1000)\n      .assert.count('li a', 5)\n      .assert.containsText('.view', 'home')\n\n      .execute(function () {\n        window.scrollTo(0, 100)\n      })\n      .click('li:nth-child(2) a')\n      .waitForElementPresent('.view.foo', TIMEOUT)\n      .assert.containsText('.view', 'foo')\n      .execute(function () {\n        window.scrollTo(0, 200)\n        window.history.back()\n      })\n      .waitForElementPresent('.view.home', TIMEOUT)\n      .assert.containsText('.view', 'home')\n      .assert.evaluate(function () {\n        return window.pageYOffset === 100\n      }, null, 'restore scroll position on back')\n\n      // with manual scroll restoration\n      // https://developers.google.com/web/updates/2015/09/history-api-scroll-restoration\n      .execute(function () {\n        window.scrollTo(0, 100)\n        history.scrollRestoration = 'manual'\n      })\n      .click('li:nth-child(2) a')\n      .waitForElementPresent('.view.foo', TIMEOUT)\n      .assert.containsText('.view', 'foo')\n      .execute(function () {\n        window.scrollTo(0, 200)\n        window.history.back()\n      })\n      .waitForElementPresent('.view.home', TIMEOUT)\n      .assert.containsText('.view', 'home')\n      .assert.evaluate(function () {\n        return window.pageYOffset === 100\n      }, null, 'restore scroll position on back with manual restoration')\n      .execute(function () {\n        history.scrollRestoration = 'auto'\n      })\n\n      // scroll on a popped entry\n      .execute(function () {\n        window.scrollTo(0, 50)\n        window.history.forward()\n      })\n      .waitForElementPresent('.view.foo', TIMEOUT)\n      .assert.containsText('.view', 'foo')\n      .assert.evaluate(function () {\n        return window.pageYOffset === 200\n      }, null, 'restore scroll position on forward')\n\n      .execute(function () {\n        window.history.back()\n      })\n      .waitForElementPresent('.view.home', TIMEOUT)\n      .assert.containsText('.view', 'home')\n      .assert.evaluate(function () {\n        return window.pageYOffset === 50\n      }, null, 'restore scroll position on back again')\n\n      .click('li:nth-child(3) a')\n      .waitForElementPresent('.view.bar', TIMEOUT)\n      .assert.evaluate(function () {\n        return window.pageYOffset === 0\n      }, null, 'scroll to top on new entry')\n\n      .click('li:nth-child(4) a')\n      .assert.evaluate(function () {\n        return document.getElementById('anchor').getBoundingClientRect().top < 1\n      }, null, 'scroll to anchor')\n\n      .execute(function () {\n        document.querySelector('li:nth-child(5) a').click()\n      })\n      .assert.evaluate(function () {\n        return document.getElementById('anchor2').getBoundingClientRect().top < 101\n      }, null, 'scroll to anchor with offset')\n      .end()\n  }\n}\n"
  },
  {
    "path": "vue-router/test/e2e/specs/transitions.js",
    "content": "module.exports = {\n  'transitions': function (browser) {\n    const TIMEOUT = 2000\n\n    browser\n    .url('http://localhost:8080/transitions/')\n      .waitForElementVisible('#app', 1000)\n      .assert.count('li a', 4)\n\n      .click('li:nth-child(2) a')\n      .assert.cssClassPresent('.view.home', 'fade-leave-active')\n      .waitForElementPresent('.view.parent', TIMEOUT)\n      .assert.cssClassPresent('.view.parent', 'fade-enter-active')\n      .assert.cssClassNotPresent('.child-view.default', 'slide-left-enter-active')\n      .waitForElementNotPresent('.view.parent.fade-enter-active', TIMEOUT)\n\n      .click('li:nth-child(3) a')\n      .assert.cssClassPresent('.child-view.default', 'slide-left-leave-active')\n      .assert.cssClassPresent('.child-view.foo', 'slide-left-enter-active')\n      .waitForElementNotPresent('.child-view.default', TIMEOUT)\n\n      .click('li:nth-child(4) a')\n      .assert.cssClassPresent('.child-view.foo', 'slide-left-leave-active')\n      .assert.cssClassPresent('.child-view.bar', 'slide-left-enter-active')\n      .waitForElementNotPresent('.child-view.foo', TIMEOUT)\n\n      .click('li:nth-child(2) a')\n      .assert.cssClassPresent('.child-view.bar', 'slide-right-leave-active')\n      .assert.cssClassPresent('.child-view.default', 'slide-right-enter-active')\n      .waitForElementNotPresent('.child-view.bar', TIMEOUT)\n\n      .click('li:nth-child(1) a')\n      .assert.cssClassPresent('.view.parent', 'fade-leave-active')\n      .waitForElementPresent('.view.home', TIMEOUT)\n      .assert.cssClassPresent('.view.home', 'fade-enter-active')\n      .waitForElementNotPresent('.view.home.fade-enter-active', TIMEOUT)\n\n      .end()\n  }\n}\n"
  },
  {
    "path": "vue-router/test/unit/jasmine.json",
    "content": "{\n  \"spec_dir\": \"test/unit/specs\",\n  \"spec_files\": [\n    \"*.spec.js\"\n  ],\n  \"helpers\": [\n    \"../../../node_modules/babel-register/lib/node.js\"\n  ]\n}\n"
  },
  {
    "path": "vue-router/test/unit/specs/api.spec.js",
    "content": "import Router from '../../../src/index'\n\ndescribe('router.onReady', () => {\n  it('should work', done => {\n    const calls = []\n\n    const router = new Router({\n      mode: 'abstract',\n      routes: [\n        {\n          path: '/a',\n          component: {\n            name: 'A',\n            beforeRouteEnter: (to, from, next) => {\n              setTimeout(() => {\n                calls.push(2)\n                next()\n              }, 1)\n            }\n          }\n        }\n      ]\n    })\n\n    router.beforeEach((to, from, next) => {\n      setTimeout(() => {\n        calls.push(1)\n        next()\n      }, 1)\n    })\n\n    router.onReady(() => {\n      expect(calls).toEqual([1, 2])\n      // sync call when already ready\n      router.onReady(() => {\n        calls.push(3)\n      })\n      expect(calls).toEqual([1, 2, 3])\n      done()\n    })\n\n    router.push('/a')\n    expect(calls).toEqual([])\n  })\n})\n\ndescribe('router.addRoutes', () => {\n  it('should work', () => {\n    const router = new Router({\n      mode: 'abstract',\n      routes: [\n        { path: '/a', component: { name: 'A' }}\n      ]\n    })\n\n    router.push('/a')\n    let components = router.getMatchedComponents()\n    expect(components.length).toBe(1)\n    expect(components[0].name).toBe('A')\n\n    router.push('/b')\n    components = router.getMatchedComponents()\n    expect(components.length).toBe(0)\n\n    router.addRoutes([\n      { path: '/b', component: { name: 'B' }}\n    ])\n    components = router.getMatchedComponents()\n    expect(components.length).toBe(1)\n    expect(components[0].name).toBe('B')\n\n    // make sure it preserves previous routes\n    router.push('/a')\n    components = router.getMatchedComponents()\n    expect(components.length).toBe(1)\n    expect(components[0].name).toBe('A')\n  })\n})\n\ndescribe('router.push/replace callbacks', () => {\n  let calls = []\n  let router, spy1, spy2\n\n  const Foo = {\n    beforeRouteEnter (to, from, next) {\n      calls.push(3)\n      setTimeout(() => {\n        calls.push(4)\n        next()\n      }, 1)\n    }\n  }\n\n  beforeEach(() => {\n    calls = []\n    spy1 = jasmine.createSpy('complete')\n    spy2 = jasmine.createSpy('abort')\n\n    router = new Router({\n      routes: [\n        { path: '/foo', component: Foo }\n      ]\n    })\n\n    router.beforeEach((to, from, next) => {\n      calls.push(1)\n      setTimeout(() => {\n        calls.push(2)\n        next()\n      }, 1)\n    })\n  })\n\n  it('push complete', done => {\n    router.push('/foo', () => {\n      expect(calls).toEqual([1, 2, 3, 4])\n      done()\n    })\n  })\n\n  it('push abort', done => {\n    router.push('/foo', spy1, spy2)\n    router.push('/bar', () => {\n      expect(calls).toEqual([1, 1, 2, 2])\n      expect(spy1).not.toHaveBeenCalled()\n      expect(spy2).toHaveBeenCalled()\n      done()\n    })\n  })\n\n  it('replace complete', done => {\n    router.replace('/foo', () => {\n      expect(calls).toEqual([1, 2, 3, 4])\n      done()\n    })\n  })\n\n  it('replace abort', done => {\n    router.replace('/foo', spy1, spy2)\n    router.replace('/bar', () => {\n      expect(calls).toEqual([1, 1, 2, 2])\n      expect(spy1).not.toHaveBeenCalled()\n      expect(spy2).toHaveBeenCalled()\n      done()\n    })\n  })\n})\n"
  },
  {
    "path": "vue-router/test/unit/specs/async.spec.js",
    "content": "import { runQueue } from '../../../src/util/async'\n\ndescribe('Async utils', () => {\n  describe('runQueue', () => {\n    it('should work', done => {\n      const calls = []\n      const queue = [1, 2, 3, 4, 5].map(i => next => {\n        calls.push(i)\n        setTimeout(next, 0)\n      })\n      runQueue(queue, (fn, next) => fn(next), () => {\n        expect(calls).toEqual([1, 2, 3, 4, 5])\n        done()\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "vue-router/test/unit/specs/create-map.spec.js",
    "content": "/*eslint-disable no-undef*/\nimport { createRouteMap } from '../../../src/create-route-map'\n\nconst Home = { template: '<div>This is Home</div>' }\nconst Foo = { template: '<div>This is Foo</div>' }\nconst FooBar = { template: '<div>This is FooBar</div>' }\nconst Foobar = { template: '<div>This is foobar</div>' }\nconst Bar = { template: '<div>This is Bar <router-view></router-view></div>' }\nconst Baz = { template: '<div>This is Baz</div>' }\n\nconst routes = [\n  { path: '/', name: 'home', component: Home },\n  { path: '/foo', name: 'foo', component: Foo },\n  { path: '*', name: 'wildcard', component: Baz },\n  {\n    path: '/bar',\n    name: 'bar',\n    component: Bar,\n    children: [\n      {\n        path: '',\n        component: Baz,\n        name: 'bar.baz'\n      }\n    ]\n  },\n  {\n    path: '/bar-redirect',\n    name: 'bar-redirect',\n    redirect: { name: 'bar-redirect.baz' },\n    component: Bar,\n    children: [\n      {\n        path: '',\n        component: Baz,\n        name: 'bar-redirect.baz'\n      }\n    ]\n  }\n]\n\ndescribe('Creating Route Map', function () {\n  let maps\n\n  beforeAll(function () {\n    spyOn(console, 'warn')\n    maps = createRouteMap(routes)\n  })\n\n  beforeEach(function () {\n    console.warn.calls.reset()\n    process.env.NODE_ENV = 'production'\n  })\n\n  it('has a pathMap object for default subroute at /bar/', function () {\n    expect(maps.pathMap['/bar/']).not.toBeUndefined()\n  })\n\n  it('has a pathList which places wildcards at the end', () => {\n    expect(maps.pathList).toEqual(['', '/foo', '/bar/', '/bar', '/bar-redirect/', '/bar-redirect', '*'])\n  })\n\n  it('has a nameMap object for default subroute at \\'bar.baz\\'', function () {\n    expect(maps.nameMap['bar.baz']).not.toBeUndefined()\n  })\n\n  it('in development, has logged a warning concerning named route of parent and default subroute', function () {\n    process.env.NODE_ENV = 'development'\n    maps = createRouteMap(routes)\n    expect(console.warn).toHaveBeenCalledTimes(1)\n    expect(console.warn.calls.argsFor(0)[0]).toMatch('vue-router] Named Route \\'bar\\'')\n  })\n\n  it('in development, throws if path is missing', function () {\n    process.env.NODE_ENV = 'development'\n    expect(() => {\n      maps = createRouteMap([{ component: Bar }])\n    }).toThrowError(/\"path\" is required/)\n  })\n\n  it('in production, it has not logged this warning', function () {\n    maps = createRouteMap(routes)\n    expect(console.warn).not.toHaveBeenCalled()\n  })\n\n  it('in development, warn duplicate param keys', () => {\n    process.env.NODE_ENV = 'development'\n    maps = createRouteMap([\n      {\n        path: '/foo/:id', component: Foo,\n        children: [\n          { path: 'bar/:id', component: Bar }\n        ]\n      }\n    ])\n    expect(console.warn).toHaveBeenCalled()\n    expect(console.warn.calls.argsFor(0)[0]).toMatch('vue-router] Duplicate param keys in route with path: \"/foo/:id/bar/:id\"')\n  })\n\n  describe('path-to-regexp options', function () {\n    const routes = [\n      { path: '/foo', name: 'foo', component: Foo },\n      { path: '/bar', name: 'bar', component: Bar, caseSensitive: false },\n      { path: '/FooBar', name: 'FooBar', component: FooBar, caseSensitive: true },\n      { path: '/foobar', name: 'foobar', component: Foobar, caseSensitive: true }\n    ]\n\n    it('caseSensitive option in route', function () {\n      const { nameMap } = createRouteMap(routes)\n\n      expect(nameMap.FooBar.regex.ignoreCase).toBe(false)\n      expect(nameMap.bar.regex.ignoreCase).toBe(true)\n      expect(nameMap.foo.regex.ignoreCase).toBe(true)\n    })\n\n    it('pathToRegexpOptions option in route', function () {\n      const { nameMap } = createRouteMap([\n        {\n          name: 'foo',\n          path: '/foo',\n          component: Foo,\n          pathToRegexpOptions: {\n            sensitive: true\n          }\n        },\n        {\n          name: 'bar',\n          path: '/bar',\n          component: Bar,\n          pathToRegexpOptions: {\n            sensitive: false\n          }\n        }\n      ])\n\n      expect(nameMap.foo.regex.ignoreCase).toBe(false)\n      expect(nameMap.bar.regex.ignoreCase).toBe(true)\n    })\n\n    it('caseSensitive over pathToRegexpOptions in route', function () {\n      const { nameMap } = createRouteMap([\n        {\n          name: 'foo',\n          path: '/foo',\n          component: Foo,\n          caseSensitive: true,\n          pathToRegexpOptions: {\n            sensitive: false\n          }\n        }\n      ])\n\n      expect(nameMap.foo.regex.ignoreCase).toBe(false)\n    })\n\n    it('keeps trailing slashes with strict mode', function () {\n      const { pathList } = createRouteMap([\n        {\n          path: '/foo/',\n          component: Foo,\n          pathToRegexpOptions: {\n            strict: true\n          }\n        },\n        {\n          path: '/bar/',\n          component: Foo\n        }\n      ])\n\n      expect(pathList).toEqual(['/foo/', '/bar'])\n    })\n  })\n})\n"
  },
  {
    "path": "vue-router/test/unit/specs/create-matcher.spec.js",
    "content": "/*eslint-disable no-undef*/\nimport { createMatcher } from '../../../src/create-matcher'\n\nconst routes = [\n  { path: '/', name: 'home', component: { name: 'home' }},\n  { path: '/foo', name: 'foo', component: { name: 'foo' }},\n]\n\ndescribe('Creating Matcher', function () {\n  let match\n\n  beforeAll(function () {\n    spyOn(console, 'warn')\n    match = createMatcher(routes).match\n  })\n\n  beforeEach(function () {\n    console.warn.calls.reset()\n    process.env.NODE_ENV = 'production'\n  })\n\n  it('in development, has logged a warning if a named route does not exist', function () {\n    process.env.NODE_ENV = 'development'\n    const { name, matched } = match({ name: 'bar' }, routes[0])\n    expect(matched.length).toBe(0)\n    expect(name).toBe('bar')\n    expect(console.warn).toHaveBeenCalled()\n    expect(console.warn.calls.argsFor(0)[0]).toMatch('Route with name \\'bar\\' does not exist')\n  })\n\n  it('in production, it has not logged this warning', function () {\n    match({ name: 'foo' }, routes[0])\n    expect(console.warn).not.toHaveBeenCalled()\n  })\n})\n"
  },
  {
    "path": "vue-router/test/unit/specs/custom-query.spec.js",
    "content": "import Vue from 'vue'\nimport VueRouter from '../../../src/index'\n\nVue.use(VueRouter)\n\ndescribe('custom query parse/stringify', () => {\n  it('should work', () => {\n    const router = new VueRouter({\n      parseQuery: () => ({ foo: 1 }),\n      stringifyQuery: () => '?foo=1'\n    })\n\n    router.push('/?bar=2')\n\n    expect(router.currentRoute.query).toEqual({ foo: 1 })\n    expect(router.currentRoute.fullPath).toEqual('/?foo=1')\n  })\n})\n"
  },
  {
    "path": "vue-router/test/unit/specs/discrete-components.spec.js",
    "content": "import Vue from 'vue'\nimport VueRouter from '../../../src/index'\n\ndescribe('[Vue Instance].$route bindings', () => {\n  describe('boundToSingleVueInstance', () => {\n    it('updates $route on all instances', () => {\n      const router = new VueRouter({\n        routes: [\n          { path: '/', component: { name: 'foo' }},\n          { path: '/bar', component: { name: 'bar' }}\n        ]\n      })\n      const app1 = new Vue({ router })\n      const app2 = new Vue({ router })\n      expect(app1.$route.path).toBe('/')\n      expect(app2.$route.path).toBe('/')\n      router.push('/bar')\n      expect(app1.$route.path).toBe('/bar')\n      expect(app2.$route.path).toBe('/bar')\n    })\n  })\n})\n"
  },
  {
    "path": "vue-router/test/unit/specs/error-handling.spec.js",
    "content": "import Vue from 'vue'\nimport VueRouter from '../../../src/index'\n\nVue.use(VueRouter)\n\ndescribe('error handling', () => {\n  it('onReady errors', () => {\n    const router = new VueRouter()\n    const err = new Error('foo')\n    router.beforeEach(() => { throw err })\n    router.onError(() => {})\n\n    const onReady = jasmine.createSpy('ready')\n    const onError = jasmine.createSpy('error')\n    router.onReady(onReady, onError)\n\n    router.push('/')\n\n    expect(onReady).not.toHaveBeenCalled()\n    expect(onError).toHaveBeenCalledWith(err)\n  })\n\n  it('navigation errors', () => {\n    const router = new VueRouter()\n    const err = new Error('foo')\n    const spy = jasmine.createSpy('error')\n    router.onError(spy)\n\n    router.push('/')\n    router.beforeEach(() => { throw err })\n\n    router.push('/foo')\n    expect(spy).toHaveBeenCalledWith(err)\n  })\n\n  it('async component errors', () => {\n    const err = new Error('foo')\n    const spy1 = jasmine.createSpy('error')\n    const spy2 = jasmine.createSpy('errpr')\n    const Comp = () => { throw err }\n    const router = new VueRouter({\n      routes: [\n        { path: '/', component: Comp }\n      ]\n    })\n\n    router.onError(spy1)\n    router.onReady(() => {}, spy2)\n\n    router.push('/')\n\n    expect(spy1).toHaveBeenCalledWith(err)\n    expect(spy2).toHaveBeenCalledWith(err)\n  })\n})\n"
  },
  {
    "path": "vue-router/test/unit/specs/location.spec.js",
    "content": "import { normalizeLocation } from '../../../src/util/location'\n\ndescribe('Location utils', () => {\n  describe('normalizeLocation', () => {\n    it('string', () => {\n      const loc = normalizeLocation('/abc?foo=bar&baz=qux#hello')\n      expect(loc._normalized).toBe(true)\n      expect(loc.path).toBe('/abc')\n      expect(loc.hash).toBe('#hello')\n      expect(JSON.stringify(loc.query)).toBe(JSON.stringify({\n        foo: 'bar',\n        baz: 'qux'\n      }))\n    })\n\n    it('empty string', function () {\n      const loc = normalizeLocation('', { path: '/abc' })\n      expect(loc._normalized).toBe(true)\n      expect(loc.path).toBe('/abc')\n      expect(loc.hash).toBe('')\n      expect(JSON.stringify(loc.query)).toBe(JSON.stringify({}))\n    })\n\n    it('undefined', function () {\n      const loc = normalizeLocation({}, { path: '/abc' })\n      expect(loc._normalized).toBe(true)\n      expect(loc.path).toBe('/abc')\n      expect(loc.hash).toBe('')\n      expect(JSON.stringify(loc.query)).toBe(JSON.stringify({}))\n    })\n\n    it('relative', () => {\n      const loc = normalizeLocation('abc?foo=bar&baz=qux#hello', {\n        path: '/root/next'\n      })\n      expect(loc._normalized).toBe(true)\n      expect(loc.path).toBe('/root/abc')\n      expect(loc.hash).toBe('#hello')\n      expect(JSON.stringify(loc.query)).toBe(JSON.stringify({\n        foo: 'bar',\n        baz: 'qux'\n      }))\n    })\n\n    it('relative append', () => {\n      const loc = normalizeLocation('abc?foo=bar&baz=qux#hello', {\n        path: '/root/next'\n      }, true)\n      expect(loc._normalized).toBe(true)\n      expect(loc.path).toBe('/root/next/abc')\n      expect(loc.hash).toBe('#hello')\n      expect(JSON.stringify(loc.query)).toBe(JSON.stringify({\n        foo: 'bar',\n        baz: 'qux'\n      }))\n    })\n\n    it('relative query & hash', () => {\n      const loc = normalizeLocation('?foo=bar&baz=qux#hello', {\n        path: '/root/next'\n      })\n      expect(loc._normalized).toBe(true)\n      expect(loc.path).toBe('/root/next')\n      expect(loc.hash).toBe('#hello')\n      expect(JSON.stringify(loc.query)).toBe(JSON.stringify({\n        foo: 'bar',\n        baz: 'qux'\n      }))\n    })\n\n    it('relative params (named)', () => {\n      const loc = normalizeLocation({ params: { lang: 'fr' }}, {\n        name: 'hello',\n        params: { lang: 'en', id: 'foo' }\n      })\n      expect(loc._normalized).toBe(true)\n      expect(loc.name).toBe('hello')\n      expect(loc.params).toEqual({ lang: 'fr', id: 'foo' })\n    })\n\n    it('relative params (non-named)', () => {\n      const loc = normalizeLocation({ params: { lang: 'fr' }}, {\n        path: '/en/foo',\n        params: { lang: 'en', id: 'foo' },\n        matched: [{ path: '/:lang/:id' }]\n      })\n      expect(loc._normalized).toBe(true)\n      expect(loc.path).toBe('/fr/foo')\n    })\n\n    it('relative append', () => {\n      const loc = normalizeLocation({ path: 'a' }, { path: '/b' }, true)\n      expect(loc.path).toBe('/b/a')\n      const loc2 = normalizeLocation({ path: 'a', append: true }, { path: '/b' })\n      expect(loc2.path).toBe('/b/a')\n    })\n\n    it('object', () => {\n      const loc = normalizeLocation({\n        path: '/abc?foo=bar#hello',\n        query: { baz: 'qux' },\n        hash: 'lol'\n      })\n      expect(loc._normalized).toBe(true)\n      expect(loc.path).toBe('/abc')\n      expect(loc.hash).toBe('#lol')\n      expect(JSON.stringify(loc.query)).toBe(JSON.stringify({\n        foo: 'bar',\n        baz: 'qux'\n      }))\n    })\n\n    it('skip normalized', () => {\n      const loc1 = {\n        _normalized: true,\n        path: '/abc?foo=bar#hello',\n        query: { baz: 'qux' },\n        hash: 'lol'\n      }\n      const loc2 = normalizeLocation(loc1)\n      expect(loc1).toBe(loc2)\n    })\n  })\n})\n"
  },
  {
    "path": "vue-router/test/unit/specs/node.spec.js",
    "content": "import Vue from 'vue'\nimport VueRouter from '../../../src/index'\n\nVue.use(VueRouter)\n\ndescribe('Usage in Node', () => {\n  it('should be in abstract mode', () => {\n    const router = new VueRouter()\n    expect(router.mode).toBe('abstract')\n  })\n\n  it('should be able to navigate without app instance', () => {\n    const router = new VueRouter({\n      routes: [\n        { path: '/', component: { name: 'foo' }},\n        { path: '/bar', component: { name: 'bar' }}\n      ]\n    })\n    router.push('/bar')\n    expect(router.history.current.path).toBe('/bar')\n  })\n\n  it('getMatchedComponents', () => {\n    const Foo = { name: 'foo' }\n    const Bar = { name: 'bar' }\n    const Baz = { name: 'baz' }\n    const router = new VueRouter({\n      routes: [\n        { path: '/', component: Foo },\n        { path: '/bar', component: Bar, children: [\n          { path: 'baz', component: Baz }\n        ]}\n      ]\n    })\n    expect(router.getMatchedComponents('/')).toEqual([Foo])\n    expect(router.getMatchedComponents('/bar/baz')).toEqual([Bar, Baz])\n  })\n})\n"
  },
  {
    "path": "vue-router/test/unit/specs/path.spec.js",
    "content": "import { resolvePath, parsePath, cleanPath } from '../../../src/util/path'\n\ndescribe('Path utils', () => {\n  describe('resolvePath', () => {\n    it('absolute', () => {\n      const path = resolvePath('/a', '/b')\n      expect(path).toBe('/a')\n    })\n\n    it('relative', () => {\n      const path = resolvePath('c/d', '/b')\n      expect(path).toBe('/c/d')\n    })\n\n    it('relative with append', () => {\n      const path = resolvePath('c/d', '/b', true)\n      expect(path).toBe('/b/c/d')\n    })\n\n    it('relative parent', () => {\n      const path = resolvePath('../d', '/a/b/c')\n      expect(path).toBe('/a/d')\n    })\n\n    it('relative parent with append', () => {\n      const path = resolvePath('../d', '/a/b/c', true)\n      expect(path).toBe('/a/b/d')\n    })\n\n    it('relative query', () => {\n      const path = resolvePath('?foo=bar', '/a/b')\n      expect(path).toBe('/a/b?foo=bar')\n    })\n\n    it('relative hash', () => {\n      const path = resolvePath('#hi', '/a/b')\n      expect(path).toBe('/a/b#hi')\n    })\n  })\n\n  describe('parsePath', () => {\n    it('plain', () => {\n      const res = parsePath('/a')\n      expect(res.path).toBe('/a')\n      expect(res.hash).toBe('')\n      expect(res.query).toBe('')\n    })\n\n    it('query', () => {\n      const res = parsePath('/a?foo=bar???')\n      expect(res.path).toBe('/a')\n      expect(res.hash).toBe('')\n      expect(res.query).toBe('foo=bar???')\n    })\n\n    it('hash', () => {\n      const res = parsePath('/a#haha#hoho')\n      expect(res.path).toBe('/a')\n      expect(res.hash).toBe('#haha#hoho')\n      expect(res.query).toBe('')\n    })\n\n    it('both', () => {\n      const res = parsePath('/a?foo=bar#ok?baz=qux')\n      expect(res.path).toBe('/a')\n      expect(res.hash).toBe('#ok?baz=qux')\n      expect(res.query).toBe('foo=bar')\n    })\n  })\n\n  describe('cleanPath', () => {\n    it('should work', () => {\n      const path = cleanPath('//a//b//d/')\n      expect(path).toBe('/a/b/d/')\n    })\n  })\n})\n"
  },
  {
    "path": "vue-router/test/unit/specs/query.spec.js",
    "content": "import { resolveQuery, stringifyQuery } from '../../../src/util/query'\n\ndescribe('Query utils', () => {\n  describe('resolveQuery', () => {\n    it('should work', () => {\n      const query = resolveQuery('foo=bar&foo=k', { baz: 'qux' })\n      expect(JSON.stringify(query)).toBe(JSON.stringify({\n        foo: ['bar', 'k'],\n        baz: 'qux'\n      }))\n    })\n  })\n\n  describe('stringifyQuery', () => {\n    it('should work', () => {\n      expect(stringifyQuery({\n        foo: 'bar',\n        baz: 'qux',\n        arr: [1, 2]\n      })).toBe('?foo=bar&baz=qux&arr=1&arr=2')\n    })\n\n    it('should escape reserved chars', () => {\n      expect(stringifyQuery({\n        a: '*()!'\n      })).toBe('?a=%2a%28%29%21')\n    })\n\n    it('should preserve commas', () => {\n      expect(stringifyQuery({\n        list: '1,2,3'\n      })).toBe('?list=1,2,3')\n    })\n  })\n})\n"
  },
  {
    "path": "vue-router/test/unit/specs/route.spec.js",
    "content": "import { isSameRoute, isIncludedRoute } from '../../../src/util/route'\n\ndescribe('Route utils', () => {\n  describe('isSameRoute', () => {\n    it('path', () => {\n      const a = {\n        path: '/a',\n        hash: '#hi',\n        query: { foo: 'bar', arr: [1, 2] }\n      }\n      const b = {\n        path: '/a/',  // Allow trailing slash\n        hash: '#hi',\n        query: { arr: ['1', '2'], foo: 'bar' }\n      }\n      expect(isSameRoute(a, b)).toBe(true)\n    })\n\n    it('name', () => {\n      const a = {\n        path: '/abc',\n        name: 'a',\n        hash: '#hi',\n        query: { foo: 'bar', arr: [1, 2] }\n      }\n      const b = {\n        name: 'a',\n        hash: '#hi',\n        query: { arr: ['1', '2'], foo: 'bar' }\n      }\n      expect(isSameRoute(a, b)).toBe(true)\n    })\n\n    it('nested query', () => {\n      const a = {\n        path: '/abc',\n        query: { foo: { bar: 'bar' }, arr: [1, 2] }\n      }\n      const b = {\n        path: '/abc',\n        query: { arr: [1, 2], foo: { bar: 'bar' } }\n      }\n      const c = {\n        path: '/abc',\n        query: { arr: [1, 2], foo: { bar: 'not bar' } }\n      }\n      expect(isSameRoute(a, b)).toBe(true)\n      expect(isSameRoute(a, c)).toBe(false)\n    })\n\n    it('queries with null values', () => {\n      const a = {\n        path: '/abc',\n        query: { foo: null }\n      }\n      const b = {\n        path: '/abc',\n        query: { foo: null }\n      }\n      const c = {\n        path: '/abc',\n        query: { foo: 5 }\n      }\n      expect(() => isSameRoute(a, b)).not.toThrow()\n      expect(() => isSameRoute(a, c)).not.toThrow()\n      expect(isSameRoute(a, b)).toBe(true)\n      expect(isSameRoute(a, c)).toBe(false)\n    })\n  })\n\n  describe('isIncludedRoute', () => {\n    it('path', () => {\n      const a = { path: '/a/b' }\n      const b = { path: '/a' }\n      const c = { path: '/a/b/c' }\n      const d = { path: '/a/b/' }\n      expect(isIncludedRoute(a, b)).toBe(true)\n      expect(isIncludedRoute(a, c)).toBe(false)\n      expect(isIncludedRoute(a, d)).toBe(true)\n    })\n\n    it('with hash', () => {\n      const a = { path: '/a/b', hash: '#a' }\n      const b = { path: '/a' }\n      const c = { path: '/a', hash: '#a' }\n      const d = { path: '/a', hash: '#b' }\n      expect(isIncludedRoute(a, b)).toBe(true)\n      expect(isIncludedRoute(a, c)).toBe(true)\n      expect(isIncludedRoute(a, d)).toBe(false)\n    })\n\n    it('with query', () => {\n      const a = { path: '/a/b', query: { foo: 'bar', baz: 'qux' }}\n      const b = { path: '/a', query: {}}\n      const c = { path: '/a', query: { foo: 'bar' }}\n      const d = { path: '/a', query: { foo: 'bar', a: 'b' }}\n      expect(isIncludedRoute(a, b)).toBe(true)\n      expect(isIncludedRoute(a, c)).toBe(true)\n      expect(isIncludedRoute(a, d)).toBe(false)\n    })\n\n    it('with both', () => {\n      const a = { path: '/a/b', query: { foo: 'bar', baz: 'qux' }, hash: '#a' }\n      const b = { path: '/a', query: {}}\n      const c = { path: '/a', query: { foo: 'bar' }}\n      const d = { path: '/a', query: { foo: 'bar' }, hash: '#b' }\n      const e = { path: '/a', query: { a: 'b' }, hash: '#a' }\n      expect(isIncludedRoute(a, b)).toBe(true)\n      expect(isIncludedRoute(a, c)).toBe(true)\n      expect(isIncludedRoute(a, d)).toBe(false)\n      expect(isIncludedRoute(a, e)).toBe(false)\n    })\n\n    it('trailing slash', () => {\n      const a = { path: '/users' }\n      const b = { path: '/user' }\n      const c = { path: '/users/' }\n      expect(isIncludedRoute(a, b)).toBe(false)\n      expect(isIncludedRoute(a, c)).toBe(true)\n\n      const d = { path: '/users/hello/world' }\n      const e = { path: '/users/hello' }\n      const f = { path: '/users/hello-world' }\n      expect(isIncludedRoute(d, e)).toBe(true)\n      expect(isIncludedRoute(d, f)).toBe(false)\n    })\n  })\n})\n"
  },
  {
    "path": "vuex/build/build.main.js",
    "content": "const fs = require('fs')\nconst path = require('path')\nconst zlib = require('zlib')\nconst uglify = require('uglify-js')\nconst rollup = require('rollup')\nconst configs = require('./configs')\n\nif (!fs.existsSync('dist')) {\n  fs.mkdirSync('dist')\n}\n\nbuild(Object.keys(configs).map(key => configs[key]))\n\nfunction build (builds) {\n  let built = 0\n  const total = builds.length\n  const next = () => {\n    buildEntry(builds[built]).then(() => {\n      built++\n      if (built < total) {\n        next()\n      }\n    }).catch(logError)\n  }\n\n  next()\n}\n\nfunction buildEntry ({ input, output }) {\n  const isProd = /min\\.js$/.test(output.file)\n  return rollup.rollup(input)\n    .then(bundle => bundle.generate(output))\n    .then(({ code }) => {\n      if (isProd) {\n        var minified = (output.banner ? output.banner + '\\n' : '') + uglify.minify(code, {\n          output: {\n            /* eslint-disable camelcase */\n            ascii_only: true\n            /* eslint-enable camelcase */\n          }\n        }).code\n        return write(output.file, minified, true)\n      } else {\n        return write(output.file, code)\n      }\n    })\n}\n\nfunction write (dest, code, zip) {\n  return new Promise((resolve, reject) => {\n    function report (extra) {\n      console.log(blue(path.relative(process.cwd(), dest)) + ' ' + getSize(code) + (extra || ''))\n      resolve()\n    }\n\n    fs.writeFile(dest, code, err => {\n      if (err) return reject(err)\n      if (zip) {\n        zlib.gzip(code, (err, zipped) => {\n          if (err) return reject(err)\n          report(' (gzipped: ' + getSize(zipped) + ')')\n        })\n      } else {\n        report()\n      }\n    })\n  })\n}\n\nfunction getSize (code) {\n  return (code.length / 1024).toFixed(2) + 'kb'\n}\n\nfunction logError (e) {\n  console.log(e)\n}\n\nfunction blue (str) {\n  return '\\x1b[1m\\x1b[34m' + str + '\\x1b[39m\\x1b[22m'\n}\n"
  },
  {
    "path": "vuex/build/configs.js",
    "content": "const path = require('path')\nconst buble = require('rollup-plugin-buble')\nconst replace = require('rollup-plugin-replace')\nconst version = process.env.VERSION || require('../package.json').version\nconst banner =\n`/**\n * vuex v${version}\n * (c) ${new Date().getFullYear()} Evan You\n * @license MIT\n */`\n\nconst resolve = _path => path.resolve(__dirname, '../', _path)\n\nconst configs = {\n  umdDev: {\n    input: resolve('src/index.js'),\n    file: resolve('dist/vuex.js'),\n    format: 'umd',\n    env: 'development'\n  },\n  umdProd: {\n    input: resolve('src/index.js'),\n    file: resolve('dist/vuex.min.js'),\n    format: 'umd',\n    env: 'production'\n  },\n  commonjs: {\n    input: resolve('src/index.js'),\n    file: resolve('dist/vuex.common.js'),\n    format: 'cjs'\n  },\n  esm: {\n    input: resolve('src/index.esm.js'),\n    file: resolve('dist/vuex.esm.js'),\n    format: 'es'\n  }\n}\n\nfunction genConfig (opts) {\n  const config = {\n    input: {\n      input: opts.input,\n      plugins: [\n        replace({\n          __VERSION__: version\n        }),\n        buble()\n      ]\n    },\n    output: {\n      banner,\n      file: opts.file,\n      format: opts.format,\n      name: 'Vuex'\n    }\n  }\n\n  if (opts.env) {\n    config.input.plugins.unshift(replace({\n      'process.env.NODE_ENV': JSON.stringify(opts.env)\n    }))\n  }\n\n  return config\n}\n\nfunction mapValues (obj, fn) {\n  const res = {}\n  Object.keys(obj).forEach(key => {\n    res[key] = fn(obj[key], key)\n  })\n  return res\n}\n\nmodule.exports = mapValues(configs, genConfig)\n"
  },
  {
    "path": "vuex/build/release.sh",
    "content": "set -e\necho \"Enter release version: \"\nread VERSION\n\nread -p \"Releasing $VERSION - are you sure? (y/n)\" -n 1 -r\necho    # (optional) move to a new line\nif [[ $REPLY =~ ^[Yy]$ ]]\nthen\n  echo \"Releasing $VERSION ...\"\n\n  # run tests\n  npm test 2>/dev/null\n\n  # build\n  VERSION=$VERSION npm run build\n\n  # commit\n  git add -A\n  git commit -m \"[build] $VERSION\"\n  npm version $VERSION --message \"[release] $VERSION\"\n\n  # publish\n  git push origin refs/tags/v$VERSION\n  git push\n  npm publish\nfi\n"
  },
  {
    "path": "vuex/build/rollup.dev.config.js",
    "content": "const { input, output } = require('./configs').commonjs\n\nmodule.exports = Object.assign({}, input, { output })\n"
  },
  {
    "path": "vuex/build/rollup.logger.config.js",
    "content": "const buble = require('rollup-plugin-buble')\n\nmodule.exports = {\n  input: 'src/plugins/logger.js',\n  output: {\n    file: 'dist/logger.js',\n    format: 'umd',\n    name: 'createVuexLogger',\n  },\n  plugins: [buble()]\n}\n"
  },
  {
    "path": "vuex/src/helpers.js",
    "content": "/**\n * Reduce the code which written in Vue.js for getting the state.\n * @param {String} [namespace] - Module's namespace\n * @param {Object|Array} states # Object's item can be a function which accept state and getters for param, you can do something for state and getters in it.\n * @param {Object}\n */\nexport const mapState = normalizeNamespace((namespace, states) => {\n  const res = {}\n  normalizeMap(states).forEach(({ key, val }) => {\n    res[key] = function mappedState () {\n      let state = this.$store.state\n      let getters = this.$store.getters\n      if (namespace) {\n        const module = getModuleByNamespace(this.$store, 'mapState', namespace)\n        if (!module) {\n          return\n        }\n        state = module.context.state\n        getters = module.context.getters\n      }\n      return typeof val === 'function'\n        ? val.call(this, state, getters)\n        : state[val]\n    }\n    // mark vuex getter for devtools\n    res[key].vuex = true\n  })\n  return res\n})\n\n/**\n * Reduce the code which written in Vue.js for committing the mutation\n * @param {String} [namespace] - Module's namespace\n * @param {Object|Array} mutations # Object's item can be a function which accept `commit` function as the first param, it can accept anthor params. You can commit mutation and do any other things in this function. specially, You need to pass anthor params from the mapped function.\n * @return {Object}\n */\nexport const mapMutations = normalizeNamespace((namespace, mutations) => {\n  const res = {}\n  normalizeMap(mutations).forEach(({ key, val }) => {\n    res[key] = function mappedMutation (...args) {\n      // Get the commit method from store\n      let commit = this.$store.commit\n      if (namespace) {\n        const module = getModuleByNamespace(this.$store, 'mapMutations', namespace)\n        if (!module) {\n          return\n        }\n        commit = module.context.commit\n      }\n      return typeof val === 'function'\n        ? val.apply(this, [commit].concat(args))\n        : commit.apply(this.$store, [val].concat(args))\n    }\n  })\n  return res\n})\n\n/**\n * Reduce the code which written in Vue.js for getting the getters\n * @param {String} [namespace] - Module's namespace\n * @param {Object|Array} getters\n * @return {Object}\n */\nexport const mapGetters = normalizeNamespace((namespace, getters) => {\n  const res = {}\n  normalizeMap(getters).forEach(({ key, val }) => {\n    // thie namespace has been mutate by normalizeNamespace\n    val = namespace + val\n    res[key] = function mappedGetter () {\n      if (namespace && !getModuleByNamespace(this.$store, 'mapGetters', namespace)) {\n        return\n      }\n      if (process.env.NODE_ENV !== 'production' && !(val in this.$store.getters)) {\n        console.error(`[vuex] unknown getter: ${val}`)\n        return\n      }\n      return this.$store.getters[val]\n    }\n    // mark vuex getter for devtools\n    res[key].vuex = true\n  })\n  return res\n})\n\n/**\n * Reduce the code which written in Vue.js for dispatch the action\n * @param {String} [namespace] - Module's namespace\n * @param {Object|Array} actions # Object's item can be a function which accept `dispatch` function as the first param, it can accept anthor params. You can dispatch action and do any other things in this function. specially, You need to pass anthor params from the mapped function.\n * @return {Object}\n */\nexport const mapActions = normalizeNamespace((namespace, actions) => {\n  const res = {}\n  normalizeMap(actions).forEach(({ key, val }) => {\n    res[key] = function mappedAction (...args) {\n      // get dispatch function from store\n      let dispatch = this.$store.dispatch\n      if (namespace) {\n        const module = getModuleByNamespace(this.$store, 'mapActions', namespace)\n        if (!module) {\n          return\n        }\n        dispatch = module.context.dispatch\n      }\n      return typeof val === 'function'\n        ? val.apply(this, [dispatch].concat(args))\n        : dispatch.apply(this.$store, [val].concat(args))\n    }\n  })\n  return res\n})\n\n/**\n * Rebinding namespace param for mapXXX function in special scoped, and return them by simple object\n * @param {String} namespace\n * @return {Object}\n */\nexport const createNamespacedHelpers = (namespace) => ({\n  mapState: mapState.bind(null, namespace),\n  mapGetters: mapGetters.bind(null, namespace),\n  mapMutations: mapMutations.bind(null, namespace),\n  mapActions: mapActions.bind(null, namespace)\n})\n\n/**\n * Normalize the map\n * normalizeMap([1, 2, 3]) => [ { key: 1, val: 1 }, { key: 2, val: 2 }, { key: 3, val: 3 } ]\n * normalizeMap({a: 1, b: 2, c: 3}) => [ { key: 'a', val: 1 }, { key: 'b', val: 2 }, { key: 'c', val: 3 } ]\n * @param {Array|Object} map\n * @return {Object}\n */\nfunction normalizeMap (map) {\n  return Array.isArray(map)\n    ? map.map(key => ({ key, val: key }))\n    : Object.keys(map).map(key => ({ key, val: map[key] }))\n}\n\n/**\n * Return a function expect two param contains namespace and map. it will normalize the namespace and then the param's function will handle the new namespace and the map.\n * @param {Function} fn\n * @return {Function}\n */\nfunction normalizeNamespace (fn) {\n  return (namespace, map) => {\n    if (typeof namespace !== 'string') {\n      map = namespace\n      namespace = ''\n    } else if (namespace.charAt(namespace.length - 1) !== '/') {\n      namespace += '/'\n    }\n    return fn(namespace, map)\n  }\n}\n\n/**\n * Search a special module from store by namespace. if module not exist, print error message.\n * @param {Object} store\n * @param {String} helper\n * @param {String} namespace\n * @return {Object}\n */\nfunction getModuleByNamespace (store, helper, namespace) {\n  const module = store._modulesNamespaceMap[namespace]\n  if (process.env.NODE_ENV !== 'production' && !module) {\n    console.error(`[vuex] module namespace not found in ${helper}(): ${namespace}`)\n  }\n  return module\n}\n"
  },
  {
    "path": "vuex/src/index.esm.js",
    "content": "import { Store, install } from './store'\nimport { mapState, mapMutations, mapGetters, mapActions, createNamespacedHelpers } from './helpers'\n\nexport default {\n  Store,\n  install,\n  version: '__VERSION__',\n  mapState,\n  mapMutations,\n  mapGetters,\n  mapActions,\n  createNamespacedHelpers\n}\n\nexport {\n  Store,\n  install,\n  mapState,\n  mapMutations,\n  mapGetters,\n  mapActions,\n  createNamespacedHelpers\n}\n"
  },
  {
    "path": "vuex/src/index.js",
    "content": "import { Store, install } from './store'\nimport { mapState, mapMutations, mapGetters, mapActions, createNamespacedHelpers } from './helpers'\n\nexport default {\n  Store,\n  install,\n  version: '__VERSION__',\n  mapState,\n  mapMutations,\n  mapGetters,\n  mapActions,\n  createNamespacedHelpers\n}\n"
  },
  {
    "path": "vuex/src/mixin.js",
    "content": "export default function (Vue) {\n  const version = Number(Vue.version.split('.')[0])\n\n  if (version >= 2) {\n    Vue.mixin({ beforeCreate: vuexInit })\n  } else {\n    // override init and inject vuex init procedure\n    // for 1.x backwards compatibility.\n    const _init = Vue.prototype._init\n    Vue.prototype._init = function (options = {}) {\n      options.init = options.init\n        ? [vuexInit].concat(options.init)\n        : vuexInit\n      _init.call(this, options)\n    }\n  }\n\n  /**\n   * Vuex init hook, injected into each instances init hooks list.\n   */\n\n  function vuexInit () {\n    const options = this.$options\n    // store injection\n    if (options.store) {\n      this.$store = typeof options.store === 'function'\n        ? options.store()\n        : options.store\n    } else if (options.parent && options.parent.$store) {\n      this.$store = options.parent.$store\n    }\n  }\n}\n"
  },
  {
    "path": "vuex/src/module/module-collection.js",
    "content": "import Module from './module'\nimport { assert, forEachValue } from '../util'\n\nexport default class ModuleCollection {\n  constructor (rawRootModule) {\n    // register root module (Vuex.Store options)\n    this.register([], rawRootModule, false)\n  }\n\n  get (path) {\n    return path.reduce((module, key) => {\n      return module.getChild(key)\n    }, this.root)\n  }\n\n  getNamespace (path) {\n    let module = this.root\n    return path.reduce((namespace, key) => {\n      module = module.getChild(key)\n      return namespace + (module.namespaced ? key + '/' : '')\n    }, '')\n  }\n\n  update (rawRootModule) {\n    update([], this.root, rawRootModule)\n  }\n\n  register (path, rawModule, runtime = true) {\n    if (process.env.NODE_ENV !== 'production') {\n      assertRawModule(path, rawModule)\n    }\n\n    const newModule = new Module(rawModule, runtime)\n    if (path.length === 0) {\n      this.root = newModule\n    } else {\n      const parent = this.get(path.slice(0, -1))\n      parent.addChild(path[path.length - 1], newModule)\n    }\n\n    // register nested modules\n    if (rawModule.modules) {\n      forEachValue(rawModule.modules, (rawChildModule, key) => {\n        this.register(path.concat(key), rawChildModule, runtime)\n      })\n    }\n  }\n\n  unregister (path) {\n    const parent = this.get(path.slice(0, -1))\n    const key = path[path.length - 1]\n    if (!parent.getChild(key).runtime) return\n\n    parent.removeChild(key)\n  }\n}\n\nfunction update (path, targetModule, newModule) {\n  if (process.env.NODE_ENV !== 'production') {\n    assertRawModule(path, newModule)\n  }\n\n  // update target module\n  targetModule.update(newModule)\n\n  // update nested modules\n  if (newModule.modules) {\n    for (const key in newModule.modules) {\n      if (!targetModule.getChild(key)) {\n        if (process.env.NODE_ENV !== 'production') {\n          console.warn(\n            `[vuex] trying to add a new module '${key}' on hot reloading, ` +\n            'manual reload is needed'\n          )\n        }\n        return\n      }\n      update(\n        path.concat(key),\n        targetModule.getChild(key),\n        newModule.modules[key]\n      )\n    }\n  }\n}\n\nconst functionAssert = {\n  assert: value => typeof value === 'function',\n  expected: 'function'\n}\n\nconst objectAssert = {\n  assert: value => typeof value === 'function' ||\n    (typeof value === 'object' && typeof value.handler === 'function'),\n  expected: 'function or object with \"handler\" function'\n}\n\nconst assertTypes = {\n  getters: functionAssert,\n  mutations: functionAssert,\n  actions: objectAssert\n}\n\nfunction assertRawModule (path, rawModule) {\n  Object.keys(assertTypes).forEach(key => {\n    if (!rawModule[key]) return\n\n    const assertOptions = assertTypes[key]\n\n    forEachValue(rawModule[key], (value, type) => {\n      assert(\n        assertOptions.assert(value),\n        makeAssertionMessage(path, key, type, value, assertOptions.expected)\n      )\n    })\n  })\n}\n\nfunction makeAssertionMessage (path, key, type, value, expected) {\n  let buf = `${key} should be ${expected} but \"${key}.${type}\"`\n  if (path.length > 0) {\n    buf += ` in module \"${path.join('.')}\"`\n  }\n  buf += ` is ${JSON.stringify(value)}.`\n  return buf\n}\n"
  },
  {
    "path": "vuex/src/module/module.js",
    "content": "import { forEachValue } from '../util'\n\n// Base data struct for store's module, package with some attribute and method\nexport default class Module {\n  constructor (rawModule, runtime) {\n    this.runtime = runtime\n    // Store some children item\n    this._children = Object.create(null)\n    // Store the origin module object which passed by programmer\n    this._rawModule = rawModule\n    const rawState = rawModule.state\n\n    // Store the origin module's state\n    this.state = (typeof rawState === 'function' ? rawState() : rawState) || {}\n  }\n\n  get namespaced () {\n    return !!this._rawModule.namespaced\n  }\n\n  addChild (key, module) {\n    this._children[key] = module\n  }\n\n  removeChild (key) {\n    delete this._children[key]\n  }\n\n  getChild (key) {\n    return this._children[key]\n  }\n\n  update (rawModule) {\n    this._rawModule.namespaced = rawModule.namespaced\n    if (rawModule.actions) {\n      this._rawModule.actions = rawModule.actions\n    }\n    if (rawModule.mutations) {\n      this._rawModule.mutations = rawModule.mutations\n    }\n    if (rawModule.getters) {\n      this._rawModule.getters = rawModule.getters\n    }\n  }\n\n  forEachChild (fn) {\n    forEachValue(this._children, fn)\n  }\n\n  forEachGetter (fn) {\n    if (this._rawModule.getters) {\n      forEachValue(this._rawModule.getters, fn)\n    }\n  }\n\n  forEachAction (fn) {\n    if (this._rawModule.actions) {\n      forEachValue(this._rawModule.actions, fn)\n    }\n  }\n\n  forEachMutation (fn) {\n    if (this._rawModule.mutations) {\n      forEachValue(this._rawModule.mutations, fn)\n    }\n  }\n}\n"
  },
  {
    "path": "vuex/src/plugins/devtool.js",
    "content": "const devtoolHook =\n  typeof window !== 'undefined' &&\n  window.__VUE_DEVTOOLS_GLOBAL_HOOK__\n\nexport default function devtoolPlugin (store) {\n  if (!devtoolHook) return\n\n  store._devtoolHook = devtoolHook\n\n  devtoolHook.emit('vuex:init', store)\n\n  devtoolHook.on('vuex:travel-to-state', targetState => {\n    store.replaceState(targetState)\n  })\n\n  store.subscribe((mutation, state) => {\n    devtoolHook.emit('vuex:mutation', mutation, state)\n  })\n}\n"
  },
  {
    "path": "vuex/src/plugins/logger.js",
    "content": "// Credits: borrowed code from fcomb/redux-logger\n\nimport { deepCopy } from '../util'\n\nexport default function createLogger ({\n  collapsed = true,\n  filter = (mutation, stateBefore, stateAfter) => true,\n  transformer = state => state,\n  mutationTransformer = mut => mut,\n  logger = console\n} = {}) {\n  return store => {\n    let prevState = deepCopy(store.state)\n\n    store.subscribe((mutation, state) => {\n      if (typeof logger === 'undefined') {\n        return\n      }\n      const nextState = deepCopy(state)\n\n      if (filter(mutation, prevState, nextState)) {\n        const time = new Date()\n        const formattedTime = ` @ ${pad(time.getHours(), 2)}:${pad(time.getMinutes(), 2)}:${pad(time.getSeconds(), 2)}.${pad(time.getMilliseconds(), 3)}`\n        const formattedMutation = mutationTransformer(mutation)\n        const message = `mutation ${mutation.type}${formattedTime}`\n        const startMessage = collapsed\n          ? logger.groupCollapsed\n          : logger.group\n\n        // render\n        try {\n          startMessage.call(logger, message)\n        } catch (e) {\n          console.log(message)\n        }\n\n        logger.log('%c prev state', 'color: #9E9E9E; font-weight: bold', transformer(prevState))\n        logger.log('%c mutation', 'color: #03A9F4; font-weight: bold', formattedMutation)\n        logger.log('%c next state', 'color: #4CAF50; font-weight: bold', transformer(nextState))\n\n        try {\n          logger.groupEnd()\n        } catch (e) {\n          logger.log('—— log end ——')\n        }\n      }\n\n      prevState = nextState\n    })\n  }\n}\n\nfunction repeat (str, times) {\n  return (new Array(times + 1)).join(str)\n}\n\nfunction pad (num, maxLength) {\n  return repeat('0', maxLength - num.toString().length) + num\n}\n"
  },
  {
    "path": "vuex/src/store.js",
    "content": "import applyMixin from './mixin'\nimport devtoolPlugin from './plugins/devtool'\nimport ModuleCollection from './module/module-collection'\nimport { forEachValue, isObject, isPromise, assert } from './util'\n\nlet Vue // bind on install\n\nexport class Store {\n  constructor (options = {}) {\n    // Auto install if it is not done yet and `window` has `Vue`.\n    // To allow users to avoid auto-installation in some cases,\n    // this code should be placed here. See #731\n    if (!Vue && typeof window !== 'undefined' && window.Vue) {\n      install(window.Vue)\n    }\n\n    if (process.env.NODE_ENV !== 'production') {\n      assert(Vue, `must call Vue.use(Vuex) before creating a store instance.`)\n      assert(typeof Promise !== 'undefined', `vuex requires a Promise polyfill in this browser.`)\n      assert(this instanceof Store, `Store must be called with the new operator.`)\n    }\n\n    const {\n      plugins = [],\n      strict = false\n    } = options\n\n    // store internal state\n    this._committing = false\n    this._actions = Object.create(null)\n    this._actionSubscribers = []\n    this._mutations = Object.create(null)\n    this._wrappedGetters = Object.create(null)\n    this._modules = new ModuleCollection(options)\n    this._modulesNamespaceMap = Object.create(null)\n    this._subscribers = []\n    this._watcherVM = new Vue()\n\n    // bind commit and dispatch to self\n    const store = this\n    const { dispatch, commit } = this\n    this.dispatch = function boundDispatch (type, payload) {\n      return dispatch.call(store, type, payload)\n    }\n    this.commit = function boundCommit (type, payload, options) {\n      return commit.call(store, type, payload, options)\n    }\n\n    // strict mode\n    this.strict = strict\n\n    const state = this._modules.root.state\n\n    // init root module.\n    // this also recursively registers all sub-modules\n    // and collects all module getters inside this._wrappedGetters\n    installModule(this, state, [], this._modules.root)\n\n    // initialize the store vm, which is responsible for the reactivity\n    // (also registers _wrappedGetters as computed properties)\n    resetStoreVM(this, state)\n\n    // apply plugins\n    plugins.forEach(plugin => plugin(this))\n\n    if (Vue.config.devtools) {\n      devtoolPlugin(this)\n    }\n  }\n\n  get state () {\n    return this._vm._data.$$state\n  }\n\n  set state (v) {\n    if (process.env.NODE_ENV !== 'production') {\n      assert(false, `Use store.replaceState() to explicit replace store state.`)\n    }\n  }\n\n  commit (_type, _payload, _options) {\n    // check object-style commit\n    const {\n      type,\n      payload,\n      options\n    } = unifyObjectStyle(_type, _payload, _options)\n\n    const mutation = { type, payload }\n    const entry = this._mutations[type]\n    if (!entry) {\n      if (process.env.NODE_ENV !== 'production') {\n        console.error(`[vuex] unknown mutation type: ${type}`)\n      }\n      return\n    }\n    this._withCommit(() => {\n      entry.forEach(function commitIterator (handler) {\n        handler(payload)\n      })\n    })\n    this._subscribers.forEach(sub => sub(mutation, this.state))\n\n    if (\n      process.env.NODE_ENV !== 'production' &&\n      options && options.silent\n    ) {\n      console.warn(\n        `[vuex] mutation type: ${type}. Silent option has been removed. ` +\n        'Use the filter functionality in the vue-devtools'\n      )\n    }\n  }\n\n  dispatch (_type, _payload) {\n    // check object-style dispatch\n    const {\n      type,\n      payload\n    } = unifyObjectStyle(_type, _payload)\n\n    const action = { type, payload }\n    const entry = this._actions[type]\n    if (!entry) {\n      if (process.env.NODE_ENV !== 'production') {\n        console.error(`[vuex] unknown action type: ${type}`)\n      }\n      return\n    }\n\n    this._actionSubscribers.forEach(sub => sub(action, this.state))\n\n    return entry.length > 1\n      ? Promise.all(entry.map(handler => handler(payload)))\n      : entry[0](payload)\n  }\n\n  subscribe (fn) {\n    return genericSubscribe(fn, this._subscribers)\n  }\n\n  subscribeAction (fn) {\n    return genericSubscribe(fn, this._actionSubscribers)\n  }\n\n  watch (getter, cb, options) {\n    if (process.env.NODE_ENV !== 'production') {\n      assert(typeof getter === 'function', `store.watch only accepts a function.`)\n    }\n    return this._watcherVM.$watch(() => getter(this.state, this.getters), cb, options)\n  }\n\n  replaceState (state) {\n    this._withCommit(() => {\n      this._vm._data.$$state = state\n    })\n  }\n\n  registerModule (path, rawModule, options = {}) {\n    if (typeof path === 'string') path = [path]\n\n    if (process.env.NODE_ENV !== 'production') {\n      assert(Array.isArray(path), `module path must be a string or an Array.`)\n      assert(path.length > 0, 'cannot register the root module by using registerModule.')\n    }\n\n    this._modules.register(path, rawModule)\n    installModule(this, this.state, path, this._modules.get(path), options.preserveState)\n    // reset store to update getters...\n    resetStoreVM(this, this.state)\n  }\n\n  unregisterModule (path) {\n    if (typeof path === 'string') path = [path]\n\n    if (process.env.NODE_ENV !== 'production') {\n      assert(Array.isArray(path), `module path must be a string or an Array.`)\n    }\n\n    this._modules.unregister(path)\n    this._withCommit(() => {\n      const parentState = getNestedState(this.state, path.slice(0, -1))\n      Vue.delete(parentState, path[path.length - 1])\n    })\n    resetStore(this)\n  }\n\n  hotUpdate (newOptions) {\n    this._modules.update(newOptions)\n    resetStore(this, true)\n  }\n\n  _withCommit (fn) {\n    const committing = this._committing\n    this._committing = true\n    fn()\n    this._committing = committing\n  }\n}\n\nfunction genericSubscribe (fn, subs) {\n  if (subs.indexOf(fn) < 0) {\n    subs.push(fn)\n  }\n  return () => {\n    const i = subs.indexOf(fn)\n    if (i > -1) {\n      subs.splice(i, 1)\n    }\n  }\n}\n\nfunction resetStore (store, hot) {\n  store._actions = Object.create(null)\n  store._mutations = Object.create(null)\n  store._wrappedGetters = Object.create(null)\n  store._modulesNamespaceMap = Object.create(null)\n  const state = store.state\n  // init all modules\n  installModule(store, state, [], store._modules.root, true)\n  // reset vm\n  resetStoreVM(store, state, hot)\n}\n\nfunction resetStoreVM (store, state, hot) {\n  const oldVm = store._vm\n\n  // bind store public getters\n  store.getters = {}\n  const wrappedGetters = store._wrappedGetters\n  const computed = {}\n  forEachValue(wrappedGetters, (fn, key) => {\n    // use computed to leverage its lazy-caching mechanism\n    computed[key] = () => fn(store)\n    Object.defineProperty(store.getters, key, {\n      get: () => store._vm[key],\n      enumerable: true // for local getters\n    })\n  })\n\n  // use a Vue instance to store the state tree\n  // suppress warnings just in case the user has added\n  // some funky global mixins\n  const silent = Vue.config.silent\n  Vue.config.silent = true\n  store._vm = new Vue({\n    data: {\n      $$state: state\n    },\n    computed\n  })\n  Vue.config.silent = silent\n\n  // enable strict mode for new vm\n  if (store.strict) {\n    enableStrictMode(store)\n  }\n\n  if (oldVm) {\n    if (hot) {\n      // dispatch changes in all subscribed watchers\n      // to force getter re-evaluation for hot reloading.\n      store._withCommit(() => {\n        oldVm._data.$$state = null\n      })\n    }\n    Vue.nextTick(() => oldVm.$destroy())\n  }\n}\n\nfunction installModule (store, rootState, path, module, hot) {\n  const isRoot = !path.length\n  const namespace = store._modules.getNamespace(path)\n\n  // register in namespace map\n  if (module.namespaced) {\n    store._modulesNamespaceMap[namespace] = module\n  }\n\n  // set state\n  if (!isRoot && !hot) {\n    const parentState = getNestedState(rootState, path.slice(0, -1))\n    const moduleName = path[path.length - 1]\n    store._withCommit(() => {\n      Vue.set(parentState, moduleName, module.state)\n    })\n  }\n\n  const local = module.context = makeLocalContext(store, namespace, path)\n\n  module.forEachMutation((mutation, key) => {\n    const namespacedType = namespace + key\n    registerMutation(store, namespacedType, mutation, local)\n  })\n\n  module.forEachAction((action, key) => {\n    const type = action.root ? key : namespace + key\n    const handler = action.handler || action\n    registerAction(store, type, handler, local)\n  })\n\n  module.forEachGetter((getter, key) => {\n    const namespacedType = namespace + key\n    registerGetter(store, namespacedType, getter, local)\n  })\n\n  module.forEachChild((child, key) => {\n    installModule(store, rootState, path.concat(key), child, hot)\n  })\n}\n\n/**\n * make localized dispatch, commit, getters and state\n * if there is no namespace, just use root ones\n */\nfunction makeLocalContext (store, namespace, path) {\n  const noNamespace = namespace === ''\n\n  const local = {\n    dispatch: noNamespace ? store.dispatch : (_type, _payload, _options) => {\n      const args = unifyObjectStyle(_type, _payload, _options)\n      const { payload, options } = args\n      let { type } = args\n\n      if (!options || !options.root) {\n        type = namespace + type\n        if (process.env.NODE_ENV !== 'production' && !store._actions[type]) {\n          console.error(`[vuex] unknown local action type: ${args.type}, global type: ${type}`)\n          return\n        }\n      }\n\n      return store.dispatch(type, payload)\n    },\n\n    commit: noNamespace ? store.commit : (_type, _payload, _options) => {\n      const args = unifyObjectStyle(_type, _payload, _options)\n      const { payload, options } = args\n      let { type } = args\n\n      if (!options || !options.root) {\n        type = namespace + type\n        if (process.env.NODE_ENV !== 'production' && !store._mutations[type]) {\n          console.error(`[vuex] unknown local mutation type: ${args.type}, global type: ${type}`)\n          return\n        }\n      }\n\n      store.commit(type, payload, options)\n    }\n  }\n\n  // getters and state object must be gotten lazily\n  // because they will be changed by vm update\n  Object.defineProperties(local, {\n    getters: {\n      get: noNamespace\n        ? () => store.getters\n        : () => makeLocalGetters(store, namespace)\n    },\n    state: {\n      get: () => getNestedState(store.state, path)\n    }\n  })\n\n  return local\n}\n\nfunction makeLocalGetters (store, namespace) {\n  const gettersProxy = {}\n\n  const splitPos = namespace.length\n  Object.keys(store.getters).forEach(type => {\n    // skip if the target getter is not match this namespace\n    if (type.slice(0, splitPos) !== namespace) return\n\n    // extract local getter type\n    const localType = type.slice(splitPos)\n\n    // Add a port to the getters proxy.\n    // Define as getter property because\n    // we do not want to evaluate the getters in this time.\n    Object.defineProperty(gettersProxy, localType, {\n      get: () => store.getters[type],\n      enumerable: true\n    })\n  })\n\n  return gettersProxy\n}\n\nfunction registerMutation (store, type, handler, local) {\n  const entry = store._mutations[type] || (store._mutations[type] = [])\n  entry.push(function wrappedMutationHandler (payload) {\n    handler.call(store, local.state, payload)\n  })\n}\n\nfunction registerAction (store, type, handler, local) {\n  const entry = store._actions[type] || (store._actions[type] = [])\n  entry.push(function wrappedActionHandler (payload, cb) {\n    let res = handler.call(store, {\n      dispatch: local.dispatch,\n      commit: local.commit,\n      getters: local.getters,\n      state: local.state,\n      rootGetters: store.getters,\n      rootState: store.state\n    }, payload, cb)\n    if (!isPromise(res)) {\n      res = Promise.resolve(res)\n    }\n    if (store._devtoolHook) {\n      return res.catch(err => {\n        store._devtoolHook.emit('vuex:error', err)\n        throw err\n      })\n    } else {\n      return res\n    }\n  })\n}\n\nfunction registerGetter (store, type, rawGetter, local) {\n  if (store._wrappedGetters[type]) {\n    if (process.env.NODE_ENV !== 'production') {\n      console.error(`[vuex] duplicate getter key: ${type}`)\n    }\n    return\n  }\n  store._wrappedGetters[type] = function wrappedGetter (store) {\n    return rawGetter(\n      local.state, // local state\n      local.getters, // local getters\n      store.state, // root state\n      store.getters // root getters\n    )\n  }\n}\n\nfunction enableStrictMode (store) {\n  store._vm.$watch(function () { return this._data.$$state }, () => {\n    if (process.env.NODE_ENV !== 'production') {\n      assert(store._committing, `Do not mutate vuex store state outside mutation handlers.`)\n    }\n  }, { deep: true, sync: true })\n}\n\nfunction getNestedState (state, path) {\n  return path.length\n    ? path.reduce((state, key) => state[key], state)\n    : state\n}\n\nfunction unifyObjectStyle (type, payload, options) {\n  if (isObject(type) && type.type) {\n    options = payload\n    payload = type\n    type = type.type\n  }\n\n  if (process.env.NODE_ENV !== 'production') {\n    assert(typeof type === 'string', `Expects string as the type, but found ${typeof type}.`)\n  }\n\n  return { type, payload, options }\n}\n\nexport function install (_Vue) {\n  if (Vue && _Vue === Vue) {\n    if (process.env.NODE_ENV !== 'production') {\n      console.error(\n        '[vuex] already installed. Vue.use(Vuex) should be called only once.'\n      )\n    }\n    return\n  }\n  Vue = _Vue\n  applyMixin(Vue)\n}\n"
  },
  {
    "path": "vuex/src/util.js",
    "content": "/**\n * Get the first item that pass the test\n * by second argument function\n *\n * @param {Array} list\n * @param {Function} f\n * @return {*}\n */\nexport function find (list, f) {\n  return list.filter(f)[0]\n}\n\n/**\n * Deep copy the given object considering circular structure.\n * This function caches all nested objects and its copies.\n * If it detects circular structure, use cached copy to avoid infinite loop.\n *\n * @param {*} obj\n * @param {Array<Object>} cache\n * @return {*}\n */\nexport function deepCopy (obj, cache = []) {\n  // just return if obj is immutable value\n  if (obj === null || typeof obj !== 'object') {\n    return obj\n  }\n\n  // if obj is hit, it is in circular structure\n  const hit = find(cache, c => c.original === obj)\n  if (hit) {\n    return hit.copy\n  }\n\n  const copy = Array.isArray(obj) ? [] : {}\n  // put the copy into cache at first\n  // because we want to refer it in recursive deepCopy\n  cache.push({\n    original: obj,\n    copy\n  })\n\n  Object.keys(obj).forEach(key => {\n    copy[key] = deepCopy(obj[key], cache)\n  })\n\n  return copy\n}\n\n/**\n * forEach for object\n */\nexport function forEachValue (obj, fn) {\n  Object.keys(obj).forEach(key => fn(obj[key], key))\n}\n\nexport function isObject (obj) {\n  return obj !== null && typeof obj === 'object'\n}\n\nexport function isPromise (val) {\n  return val && typeof val.then === 'function'\n}\n\nexport function assert (condition, msg) {\n  if (!condition) throw new Error(`[vuex] ${msg}`)\n}\n"
  },
  {
    "path": "vuex/test/e2e/nightwatch.config.js",
    "content": "// http://nightwatchjs.org/guide#settings-file\nmodule.exports = {\n  'src_folders': ['test/e2e/specs'],\n  'output_folder': 'test/e2e/reports',\n  'custom_commands_path': ['node_modules/nightwatch-helpers/commands'],\n  'custom_assertions_path': ['node_modules/nightwatch-helpers/assertions'],\n\n  'selenium': {\n    'start_process': true,\n    'server_path': require('selenium-server').path,\n    'host': '127.0.0.1',\n    'port': 4444,\n    'cli_args': {\n      'webdriver.chrome.driver': require('chromedriver').path\n    }\n  },\n\n  'test_settings': {\n    'default': {\n      'selenium_port': 4444,\n      'selenium_host': 'localhost',\n      'silent': true,\n      'screenshots': {\n        'enabled': true,\n        'on_failure': true,\n        'on_error': false,\n        'path': 'test/e2e/screenshots'\n      }\n    },\n\n    'chrome': {\n      'desiredCapabilities': {\n        'browserName': 'chrome',\n        'javascriptEnabled': true,\n        'acceptSslCerts': true\n      }\n    },\n\n    'phantomjs': {\n      'desiredCapabilities': {\n        'browserName': 'phantomjs',\n        'javascriptEnabled': true,\n        'acceptSslCerts': true\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "vuex/test/e2e/runner.js",
    "content": "var spawn = require('cross-spawn')\nvar args = process.argv.slice(2)\n\nvar server = args.indexOf('--dev') > -1\n  ? null\n  : require('../../examples/server')\n\nif (args.indexOf('--config') === -1) {\n  args = args.concat(['--config', 'test/e2e/nightwatch.config.js'])\n}\nif (args.indexOf('--env') === -1) {\n  args = args.concat(['--env', 'phantomjs'])\n}\nvar i = args.indexOf('--test')\nif (i > -1) {\n  args[i + 1] = 'test/e2e/specs/' + args[i + 1]\n}\nif (args.indexOf('phantomjs') > -1) {\n  process.env.PHANTOMJS = true\n}\n\nvar runner = spawn('./node_modules/.bin/nightwatch', args, {\n  stdio: 'inherit'\n})\n\nrunner.on('exit', function (code) {\n  server && server.close()\n  process.exit(code)\n})\n\nrunner.on('error', function (err) {\n  server && server.close()\n  throw err\n})\n"
  },
  {
    "path": "vuex/test/e2e/specs/cart.js",
    "content": "module.exports = {\n  'shopping cart': function (browser) {\n    browser\n    .url('http://localhost:8080/shopping-cart/')\n      .waitForElementVisible('#app', 1000)\n      .waitFor(120) // api simulation\n      .assert.count('li', 3)\n      .assert.count('.cart button[disabled]', 1)\n      .assert.containsText('li:nth-child(1)', 'iPad 4 Mini')\n      .assert.containsText('.cart', 'Please add some products to cart')\n      .assert.containsText('.cart', 'Total: $0.00')\n      .click('li:nth-child(1) button')\n      .assert.containsText('.cart', 'iPad 4 Mini - $500.01 x 1')\n      .assert.containsText('.cart', 'Total: $500.01')\n      .click('li:nth-child(1) button')\n      .assert.containsText('.cart', 'iPad 4 Mini - $500.01 x 2')\n      .assert.containsText('.cart', 'Total: $1,000.02')\n      .assert.count('li:nth-child(1) button[disabled]', 1)\n      .click('li:nth-child(2) button')\n      .assert.containsText('.cart', 'H&M T-Shirt White - $10.99 x 1')\n      .assert.containsText('.cart', 'Total: $1,011.01')\n      .click('.cart button')\n      .waitFor(120)\n      .assert.containsText('.cart', 'Please add some products to cart')\n      .assert.containsText('.cart', 'Total: $0.00')\n      .assert.containsText('.cart', 'Checkout successful')\n      .assert.count('.cart button[disabled]', 1)\n      .end()\n  }\n}\n"
  },
  {
    "path": "vuex/test/e2e/specs/chat.js",
    "content": "module.exports = {\n  'chat': function (browser) {\n    browser\n      .url('http://localhost:8080/chat/')\n      .waitForElementVisible('.chatapp', 1000)\n      .assert.containsText('.thread-count', 'Unread threads: 2')\n      .assert.count('.thread-list-item', 3)\n      .assert.containsText('.thread-list-item.active', 'Functional Heads')\n      .assert.containsText('.message-thread-heading', 'Functional Heads')\n      .assert.count('.message-list-item', 2)\n      .assert.containsText('.message-list-item:nth-child(1) .message-author-name', 'Bill')\n      .assert.containsText('.message-list-item:nth-child(1) .message-text', 'Hey Brian')\n      .enterValue('.message-composer', 'hi')\n      .waitFor(50) // fake api\n      .assert.count('.message-list-item', 3)\n      .assert.containsText('.message-list-item:nth-child(3)', 'hi')\n      .click('.thread-list-item:nth-child(2)')\n      .assert.containsText('.thread-list-item.active', 'Dave and Bill')\n      .assert.containsText('.message-thread-heading', 'Dave and Bill')\n      .assert.count('.message-list-item', 2)\n      .assert.containsText('.message-list-item:nth-child(1) .message-author-name', 'Bill')\n      .assert.containsText('.message-list-item:nth-child(1) .message-text', 'Hey Dave')\n      .enterValue('.message-composer', 'hi')\n      .waitFor(50) // fake api\n      .assert.count('.message-list-item', 3)\n      .assert.containsText('.message-list-item:nth-child(3)', 'hi')\n  }\n}\n"
  },
  {
    "path": "vuex/test/e2e/specs/counter.js",
    "content": "module.exports = {\n  'counter': function (browser) {\n    browser\n    .url('http://localhost:8080/counter/')\n      .waitForElementVisible('#app', 1000)\n      .assert.containsText('div', 'Clicked: 0 times')\n      .click('button:nth-child(1)')\n      .assert.containsText('div', 'Clicked: 1 times')\n      .click('button:nth-child(2)')\n      .assert.containsText('div', 'Clicked: 0 times')\n      .click('button:nth-child(3)')\n      .assert.containsText('div', 'Clicked: 0 times')\n      .click('button:nth-child(1)')\n      .assert.containsText('div', 'Clicked: 1 times')\n      .click('button:nth-child(3)')\n      .assert.containsText('div', 'Clicked: 2 times')\n      .click('button:nth-child(4)')\n      .assert.containsText('div', 'Clicked: 2 times')\n      .waitFor(1000)\n      .assert.containsText('div', 'Clicked: 3 times')\n      .end()\n  }\n}\n"
  },
  {
    "path": "vuex/test/e2e/specs/todomvc.js",
    "content": "module.exports = {\n  'todomvc': function (browser) {\n    browser\n    .url('http://localhost:8080/todomvc/')\n      .waitForElementVisible('.todoapp', 1000)\n      .assert.notVisible('.main')\n      .assert.notVisible('.footer')\n      .assert.count('.filters .selected', 1)\n      .assert.evaluate(function () {\n        return document.querySelector('.filters .selected').textContent === 'All'\n      }, null, 'filter should be \"All\"')\n\n    createNewItem('test')\n      .assert.count('.todo', 1)\n      .assert.notVisible('.todo .edit')\n      .assert.containsText('.todo label', 'test')\n      .assert.containsText('.todo-count strong', '1')\n      .assert.checked('.todo .toggle', false)\n      .assert.visible('.main')\n      .assert.visible('.footer')\n      .assert.notVisible('.clear-completed')\n      .assert.value('.new-todo', '')\n\n    createNewItem('test2')\n      .assert.count('.todo', 2)\n      .assert.containsText('.todo:nth-child(2) label', 'test2')\n      .assert.containsText('.todo-count strong', '2')\n\n    // toggle\n    browser\n      .click('.todo .toggle')\n      .assert.count('.todo.completed', 1)\n      .assert.cssClassPresent('.todo:nth-child(1)', 'completed')\n      .assert.containsText('.todo-count strong', '1')\n      .assert.visible('.clear-completed')\n\n    createNewItem('test3')\n      .assert.count('.todo', 3)\n      .assert.containsText('.todo:nth-child(3) label', 'test3')\n      .assert.containsText('.todo-count strong', '2')\n\n    createNewItem('test4')\n    createNewItem('test5')\n      .assert.count('.todo', 5)\n      .assert.containsText('.todo-count strong', '4')\n\n    // toggle more\n    browser\n      .click('.todo:nth-child(4) .toggle')\n      .click('.todo:nth-child(5) .toggle')\n      .assert.count('.todo.completed', 3)\n      .assert.containsText('.todo-count strong', '2')\n\n    // remove\n    removeItemAt(1)\n      .assert.count('.todo', 4)\n      .assert.count('.todo.completed', 2)\n      .assert.containsText('.todo-count strong', '2')\n    removeItemAt(2)\n      .assert.count('.todo', 3)\n      .assert.count('.todo.completed', 2)\n      .assert.containsText('.todo-count strong', '1')\n\n    // remove all\n    browser\n      .click('.clear-completed')\n      .assert.count('.todo', 1)\n      .assert.containsText('.todo label', 'test2')\n      .assert.count('.todo.completed', 0)\n      .assert.containsText('.todo-count strong', '1')\n      .assert.notVisible('.clear-completed')\n\n    // prepare to test filters\n    createNewItem('test')\n    createNewItem('test')\n      .click('.todo:nth-child(2) .toggle')\n      .click('.todo:nth-child(3) .toggle')\n\n    // active filter\n    browser\n      .click('.filters li:nth-child(2) a')\n      .assert.count('.todo', 1)\n      .assert.count('.todo.completed', 0)\n      // add item with filter active\n    createNewItem('test')\n      .assert.count('.todo', 2)\n\n    // complted filter\n    browser.click('.filters li:nth-child(3) a')\n      .assert.count('.todo', 2)\n      .assert.count('.todo.completed', 2)\n\n    // toggling with filter active\n    browser\n      .click('.todo .toggle')\n      .assert.count('.todo', 1)\n      .click('.filters li:nth-child(2) a')\n      .assert.count('.todo', 3)\n      .click('.todo .toggle')\n      .assert.count('.todo', 2)\n\n    // editing triggered by blur\n    browser\n      .click('.filters li:nth-child(1) a')\n      .dblClick('.todo:nth-child(1) label')\n      .assert.count('.todo.editing', 1)\n      .assert.focused('.todo:nth-child(1) .edit')\n      .clearValue('.todo:nth-child(1) .edit')\n      .setValue('.todo:nth-child(1) .edit', 'edited!')\n      .click('footer') // blur\n      .assert.count('.todo.editing', 0)\n      .assert.containsText('.todo:nth-child(1) label', 'edited!')\n\n    // editing triggered by enter\n    browser\n      .dblClick('.todo label')\n      .enterValue('.todo:nth-child(1) .edit', 'edited again!')\n      .assert.count('.todo.editing', 0)\n      .assert.containsText('.todo:nth-child(1) label', 'edited again!')\n\n    // cancel\n    browser\n      .dblClick('.todo label')\n      .clearValue('.todo:nth-child(1) .edit')\n      .setValue('.todo:nth-child(1) .edit', 'edited!')\n      .trigger('.todo:nth-child(1) .edit', 'keyup', 27)\n      .assert.count('.todo.editing', 0)\n      .assert.containsText('.todo:nth-child(1) label', 'edited again!')\n\n    // empty value should remove\n    browser\n      .dblClick('.todo label')\n      .enterValue('.todo:nth-child(1) .edit', ' ')\n      .assert.count('.todo', 3)\n\n    // toggle all\n    browser\n      .click('.toggle-all')\n      .assert.count('.todo.completed', 3)\n      .click('.toggle-all')\n      .assert.count('.todo:not(.completed)', 3)\n      .end()\n\n    function createNewItem (text) {\n      return browser.enterValue('.new-todo', text)\n    }\n\n    function removeItemAt (n) {\n      return browser\n        .moveToElement('.todo:nth-child(' + n + ')', 10, 10)\n        .click('.todo:nth-child(' + n + ') .destroy')\n    }\n  }\n}\n"
  },
  {
    "path": "vuex/test/unit/.eslintrc",
    "content": "{\n  \"env\": {\n    \"jasmine\": true\n  }\n}\n"
  },
  {
    "path": "vuex/test/unit/helpers.spec.js",
    "content": "import Vue from 'vue/dist/vue.common.js'\nimport Vuex, { mapState, mapMutations, mapGetters, mapActions, createNamespacedHelpers } from '../../dist/vuex.common.js'\n\ndescribe('Helpers', () => {\n  it('mapState (array)', () => {\n    const store = new Vuex.Store({\n      state: {\n        a: 1\n      }\n    })\n    const vm = new Vue({\n      store,\n      computed: mapState(['a'])\n    })\n    expect(vm.a).toBe(1)\n    store.state.a++\n    expect(vm.a).toBe(2)\n  })\n\n  it('mapState (object)', () => {\n    const store = new Vuex.Store({\n      state: {\n        a: 1\n      },\n      getters: {\n        b: () => 2\n      }\n    })\n    const vm = new Vue({\n      store,\n      computed: mapState({\n        a: (state, getters) => {\n          return state.a + getters.b\n        }\n      })\n    })\n    expect(vm.a).toBe(3)\n    store.state.a++\n    expect(vm.a).toBe(4)\n  })\n\n  it('mapState (with namespace)', () => {\n    const store = new Vuex.Store({\n      modules: {\n        foo: {\n          namespaced: true,\n          state: { a: 1 },\n          getters: {\n            b: state => state.a + 1\n          }\n        }\n      }\n    })\n    const vm = new Vue({\n      store,\n      computed: mapState('foo', {\n        a: (state, getters) => {\n          return state.a + getters.b\n        }\n      })\n    })\n    expect(vm.a).toBe(3)\n    store.state.foo.a++\n    expect(vm.a).toBe(5)\n    store.replaceState({\n      foo: { a: 3 }\n    })\n    expect(vm.a).toBe(7)\n  })\n\n  // #708\n  it('mapState (with namespace and a nested module)', () => {\n    const store = new Vuex.Store({\n      modules: {\n        foo: {\n          namespaced: true,\n          state: { a: 1 },\n          modules: {\n            bar: {\n              state: { b: 2 }\n            }\n          }\n        }\n      }\n    })\n    const vm = new Vue({\n      store,\n      computed: mapState('foo', {\n        value: state => state\n      })\n    })\n    expect(vm.value.a).toBe(1)\n    expect(vm.value.bar.b).toBe(2)\n    expect(vm.value.b).toBeUndefined()\n  })\n\n  it('mapMutations (array)', () => {\n    const store = new Vuex.Store({\n      state: { count: 0 },\n      mutations: {\n        inc: state => state.count++,\n        dec: state => state.count--\n      }\n    })\n    const vm = new Vue({\n      store,\n      methods: mapMutations(['inc', 'dec'])\n    })\n    vm.inc()\n    expect(store.state.count).toBe(1)\n    vm.dec()\n    expect(store.state.count).toBe(0)\n  })\n\n  it('mapMutations (object)', () => {\n    const store = new Vuex.Store({\n      state: { count: 0 },\n      mutations: {\n        inc: state => state.count++,\n        dec: state => state.count--\n      }\n    })\n    const vm = new Vue({\n      store,\n      methods: mapMutations({\n        plus: 'inc',\n        minus: 'dec'\n      })\n    })\n    vm.plus()\n    expect(store.state.count).toBe(1)\n    vm.minus()\n    expect(store.state.count).toBe(0)\n  })\n\n  it('mapMutations (function)', () => {\n    const store = new Vuex.Store({\n      state: { count: 0 },\n      mutations: {\n        inc (state, amount) {\n          state.count += amount\n        }\n      }\n    })\n    const vm = new Vue({\n      store,\n      methods: mapMutations({\n        plus (commit, amount) {\n          commit('inc', amount + 1)\n        }\n      })\n    })\n    vm.plus(42)\n    expect(store.state.count).toBe(43)\n  })\n\n  it('mapMutations (with namespace)', () => {\n    const store = new Vuex.Store({\n      modules: {\n        foo: {\n          namespaced: true,\n          state: { count: 0 },\n          mutations: {\n            inc: state => state.count++,\n            dec: state => state.count--\n          }\n        }\n      }\n    })\n    const vm = new Vue({\n      store,\n      methods: mapMutations('foo', {\n        plus: 'inc',\n        minus: 'dec'\n      })\n    })\n    vm.plus()\n    expect(store.state.foo.count).toBe(1)\n    vm.minus()\n    expect(store.state.foo.count).toBe(0)\n  })\n\n  it('mapMutations (function with namepsace)', () => {\n    const store = new Vuex.Store({\n      modules: {\n        foo: {\n          namespaced: true,\n          state: { count: 0 },\n          mutations: {\n            inc (state, amount) {\n              state.count += amount\n            }\n          }\n        }\n      }\n    })\n    const vm = new Vue({\n      store,\n      methods: mapMutations('foo', {\n        plus (commit, amount) {\n          commit('inc', amount + 1)\n        }\n      })\n    })\n    vm.plus(42)\n    expect(store.state.foo.count).toBe(43)\n  })\n\n  it('mapGetters (array)', () => {\n    const store = new Vuex.Store({\n      state: { count: 0 },\n      mutations: {\n        inc: state => state.count++,\n        dec: state => state.count--\n      },\n      getters: {\n        hasAny: ({ count }) => count > 0,\n        negative: ({ count }) => count < 0\n      }\n    })\n    const vm = new Vue({\n      store,\n      computed: mapGetters(['hasAny', 'negative'])\n    })\n    expect(vm.hasAny).toBe(false)\n    expect(vm.negative).toBe(false)\n    store.commit('inc')\n    expect(vm.hasAny).toBe(true)\n    expect(vm.negative).toBe(false)\n    store.commit('dec')\n    store.commit('dec')\n    expect(vm.hasAny).toBe(false)\n    expect(vm.negative).toBe(true)\n  })\n\n  it('mapGetters (object)', () => {\n    const store = new Vuex.Store({\n      state: { count: 0 },\n      mutations: {\n        inc: state => state.count++,\n        dec: state => state.count--\n      },\n      getters: {\n        hasAny: ({ count }) => count > 0,\n        negative: ({ count }) => count < 0\n      }\n    })\n    const vm = new Vue({\n      store,\n      computed: mapGetters({\n        a: 'hasAny',\n        b: 'negative'\n      })\n    })\n    expect(vm.a).toBe(false)\n    expect(vm.b).toBe(false)\n    store.commit('inc')\n    expect(vm.a).toBe(true)\n    expect(vm.b).toBe(false)\n    store.commit('dec')\n    store.commit('dec')\n    expect(vm.a).toBe(false)\n    expect(vm.b).toBe(true)\n  })\n\n  it('mapGetters (with namespace)', () => {\n    const store = new Vuex.Store({\n      modules: {\n        foo: {\n          namespaced: true,\n          state: { count: 0 },\n          mutations: {\n            inc: state => state.count++,\n            dec: state => state.count--\n          },\n          getters: {\n            hasAny: ({ count }) => count > 0,\n            negative: ({ count }) => count < 0\n          }\n        }\n      }\n    })\n    const vm = new Vue({\n      store,\n      computed: mapGetters('foo', {\n        a: 'hasAny',\n        b: 'negative'\n      })\n    })\n    expect(vm.a).toBe(false)\n    expect(vm.b).toBe(false)\n    store.commit('foo/inc')\n    expect(vm.a).toBe(true)\n    expect(vm.b).toBe(false)\n    store.commit('foo/dec')\n    store.commit('foo/dec')\n    expect(vm.a).toBe(false)\n    expect(vm.b).toBe(true)\n  })\n\n  it('mapGetters (with namespace and nested module)', () => {\n    const store = new Vuex.Store({\n      modules: {\n        foo: {\n          namespaced: true,\n          modules: {\n            bar: {\n              namespaced: true,\n              state: { count: 0 },\n              mutations: {\n                inc: state => state.count++,\n                dec: state => state.count--\n              },\n              getters: {\n                hasAny: ({ count }) => count > 0,\n                negative: ({ count }) => count < 0\n              }\n            },\n            cat: {\n              state: { count: 9 },\n              getters: {\n                count: ({ count }) => count\n              }\n            }\n          }\n        }\n      }\n    })\n    const vm = new Vue({\n      store,\n      computed: {\n        ...mapGetters('foo/bar', [\n          'hasAny',\n          'negative'\n        ]),\n        ...mapGetters('foo', [\n          'count'\n        ])\n      }\n    })\n    expect(vm.hasAny).toBe(false)\n    expect(vm.negative).toBe(false)\n    store.commit('foo/bar/inc')\n    expect(vm.hasAny).toBe(true)\n    expect(vm.negative).toBe(false)\n    store.commit('foo/bar/dec')\n    store.commit('foo/bar/dec')\n    expect(vm.hasAny).toBe(false)\n    expect(vm.negative).toBe(true)\n\n    expect(vm.count).toBe(9)\n  })\n\n  it('mapActions (array)', () => {\n    const a = jasmine.createSpy()\n    const b = jasmine.createSpy()\n    const store = new Vuex.Store({\n      actions: {\n        a,\n        b\n      }\n    })\n    const vm = new Vue({\n      store,\n      methods: mapActions(['a', 'b'])\n    })\n    vm.a()\n    expect(a).toHaveBeenCalled()\n    expect(b).not.toHaveBeenCalled()\n    vm.b()\n    expect(b).toHaveBeenCalled()\n  })\n\n  it('mapActions (object)', () => {\n    const a = jasmine.createSpy()\n    const b = jasmine.createSpy()\n    const store = new Vuex.Store({\n      actions: {\n        a,\n        b\n      }\n    })\n    const vm = new Vue({\n      store,\n      methods: mapActions({\n        foo: 'a',\n        bar: 'b'\n      })\n    })\n    vm.foo()\n    expect(a).toHaveBeenCalled()\n    expect(b).not.toHaveBeenCalled()\n    vm.bar()\n    expect(b).toHaveBeenCalled()\n  })\n\n  it('mapActions (function)', () => {\n    const a = jasmine.createSpy()\n    const store = new Vuex.Store({\n      actions: { a }\n    })\n    const vm = new Vue({\n      store,\n      methods: mapActions({\n        foo (dispatch, arg) {\n          dispatch('a', arg + 'bar')\n        }\n      })\n    })\n    vm.foo('foo')\n    expect(a.calls.argsFor(0)[1]).toBe('foobar')\n  })\n\n  it('mapActions (with namespace)', () => {\n    const a = jasmine.createSpy()\n    const b = jasmine.createSpy()\n    const store = new Vuex.Store({\n      modules: {\n        foo: {\n          namespaced: true,\n          actions: {\n            a,\n            b\n          }\n        }\n      }\n    })\n    const vm = new Vue({\n      store,\n      methods: mapActions('foo/', {\n        foo: 'a',\n        bar: 'b'\n      })\n    })\n    vm.foo()\n    expect(a).toHaveBeenCalled()\n    expect(b).not.toHaveBeenCalled()\n    vm.bar()\n    expect(b).toHaveBeenCalled()\n  })\n\n  it('mapActions (function with namespace)', () => {\n    const a = jasmine.createSpy()\n    const store = new Vuex.Store({\n      modules: {\n        foo: {\n          namespaced: true,\n          actions: { a }\n        }\n      }\n    })\n    const vm = new Vue({\n      store,\n      methods: mapActions('foo/', {\n        foo (dispatch, arg) {\n          dispatch('a', arg + 'bar')\n        }\n      })\n    })\n    vm.foo('foo')\n    expect(a.calls.argsFor(0)[1]).toBe('foobar')\n  })\n\n  it('createNamespacedHelpers', () => {\n    const actionA = jasmine.createSpy()\n    const actionB = jasmine.createSpy()\n    const store = new Vuex.Store({\n      modules: {\n        foo: {\n          namespaced: true,\n          state: { count: 0 },\n          getters: {\n            isEven: state => state.count % 2 === 0\n          },\n          mutations: {\n            inc: state => state.count++,\n            dec: state => state.count--\n          },\n          actions: {\n            actionA,\n            actionB\n          }\n        }\n      }\n    })\n    const {\n      mapState,\n      mapGetters,\n      mapMutations,\n      mapActions\n    } = createNamespacedHelpers('foo/')\n    const vm = new Vue({\n      store,\n      computed: {\n        ...mapState(['count']),\n        ...mapGetters(['isEven'])\n      },\n      methods: {\n        ...mapMutations(['inc', 'dec']),\n        ...mapActions(['actionA', 'actionB'])\n      }\n    })\n    expect(vm.count).toBe(0)\n    expect(vm.isEven).toBe(true)\n    store.state.foo.count++\n    expect(vm.count).toBe(1)\n    expect(vm.isEven).toBe(false)\n    vm.inc()\n    expect(store.state.foo.count).toBe(2)\n    expect(store.getters['foo/isEven']).toBe(true)\n    vm.dec()\n    expect(store.state.foo.count).toBe(1)\n    expect(store.getters['foo/isEven']).toBe(false)\n    vm.actionA()\n    expect(actionA).toHaveBeenCalled()\n    expect(actionB).not.toHaveBeenCalled()\n    vm.actionB()\n    expect(actionB).toHaveBeenCalled()\n  })\n})\n"
  },
  {
    "path": "vuex/test/unit/hot-reload.spec.js",
    "content": "import Vue from 'vue/dist/vue.common.js'\nimport Vuex from '../../dist/vuex.common.js'\n\nconst TEST = 'TEST'\nconst isSSR = process.env.VUE_ENV === 'server'\n\ndescribe('Hot Reload', () => {\n  it('mutations', function () {\n    const mutations = {\n      [TEST] (state, n) {\n        state.a += n\n      }\n    }\n    const store = new Vuex.Store({\n      state: {\n        a: 1\n      },\n      mutations,\n      modules: {\n        nested: {\n          state: { a: 2 },\n          mutations,\n          modules: {\n            one: {\n              state: { a: 3 },\n              mutations\n            },\n            nested: {\n              modules: {\n                two: {\n                  state: { a: 4 },\n                  mutations\n                },\n                three: {\n                  state: { a: 5 },\n                  mutations\n                }\n              }\n            }\n          }\n        },\n        four: {\n          state: { a: 6 },\n          mutations\n        }\n      }\n    })\n    store.commit(TEST, 1)\n    expect(store.state.a).toBe(2)\n    expect(store.state.nested.a).toBe(3)\n    expect(store.state.nested.one.a).toBe(4)\n    expect(store.state.nested.nested.two.a).toBe(5)\n    expect(store.state.nested.nested.three.a).toBe(6)\n    expect(store.state.four.a).toBe(7)\n\n    // hot reload only root mutations\n    store.hotUpdate({\n      mutations: {\n        [TEST] (state, n) {\n          state.a = n\n        }\n      }\n    })\n    store.commit(TEST, 1)\n    expect(store.state.a).toBe(1) // only root mutation updated\n    expect(store.state.nested.a).toBe(4)\n    expect(store.state.nested.one.a).toBe(5)\n    expect(store.state.nested.nested.two.a).toBe(6)\n    expect(store.state.nested.nested.three.a).toBe(7)\n    expect(store.state.four.a).toBe(8)\n\n    // hot reload modules\n    store.hotUpdate({\n      modules: {\n        nested: {\n          state: { a: 234 },\n          mutations,\n          modules: {\n            one: {\n              state: { a: 345 },\n              mutations\n            },\n            nested: {\n              modules: {\n                two: {\n                  state: { a: 456 },\n                  mutations\n                },\n                three: {\n                  state: { a: 567 },\n                  mutations\n                }\n              }\n            }\n          }\n        },\n        four: {\n          state: { a: 678 },\n          mutations\n        }\n      }\n    })\n    store.commit(TEST, 2)\n    expect(store.state.a).toBe(2)\n    expect(store.state.nested.a).toBe(6) // should not reload initial state\n    expect(store.state.nested.one.a).toBe(7) // should not reload initial state\n    expect(store.state.nested.nested.two.a).toBe(8) // should not reload initial state\n    expect(store.state.nested.nested.three.a).toBe(9) // should not reload initial state\n    expect(store.state.four.a).toBe(10) // should not reload initial state\n\n    // hot reload all\n    store.hotUpdate({\n      mutations: {\n        [TEST] (state, n) {\n          state.a -= n\n        }\n      },\n      modules: {\n        nested: {\n          state: { a: 234 },\n          mutations: {\n            [TEST] (state, n) {\n              state.a += n\n            }\n          },\n          modules: {\n            one: {\n              state: { a: 345 },\n              mutations: {\n                [TEST] (state, n) {\n                  state.a += n\n                }\n              }\n            },\n            nested: {\n              modules: {\n                two: {\n                  state: { a: 456 },\n                  mutations: {\n                    [TEST] (state, n) {\n                      state.a += n\n                    }\n                  }\n                },\n                three: {\n                  state: { a: 567 },\n                  mutations: {\n                    [TEST] (state, n) {\n                      state.a -= n\n                    }\n                  }\n                }\n              }\n            }\n          }\n        },\n        four: {\n          state: { a: 678 },\n          mutations: {\n            [TEST] (state, n) {\n              state.a -= n\n            }\n          }\n        }\n      }\n    })\n    store.commit(TEST, 3)\n    expect(store.state.a).toBe(-1)\n    expect(store.state.nested.a).toBe(9)\n    expect(store.state.nested.one.a).toBe(10)\n    expect(store.state.nested.nested.two.a).toBe(11)\n    expect(store.state.nested.nested.three.a).toBe(6)\n    expect(store.state.four.a).toBe(7)\n  })\n\n  it('actions', () => {\n    const store = new Vuex.Store({\n      state: {\n        list: []\n      },\n      mutations: {\n        [TEST] (state, n) {\n          state.list.push(n)\n        }\n      },\n      actions: {\n        [TEST] ({ commit }) {\n          commit(TEST, 1)\n        }\n      },\n      modules: {\n        a: {\n          actions: {\n            [TEST] ({ commit }) {\n              commit(TEST, 2)\n            }\n          }\n        }\n      }\n    })\n    store.dispatch(TEST)\n    expect(store.state.list.join()).toBe('1,2')\n\n    // update root\n    store.hotUpdate({\n      actions: {\n        [TEST] ({ commit }) {\n          commit(TEST, 3)\n        }\n      }\n    })\n    store.dispatch(TEST)\n    expect(store.state.list.join()).toBe('1,2,3,2')\n\n    // update modules\n    store.hotUpdate({\n      actions: {\n        [TEST] ({ commit }) {\n          commit(TEST, 4)\n        }\n      },\n      modules: {\n        a: {\n          actions: {\n            [TEST] ({ commit }) {\n              commit(TEST, 5)\n            }\n          }\n        }\n      }\n    })\n    store.dispatch(TEST)\n    expect(store.state.list.join()).toBe('1,2,3,2,4,5')\n  })\n\n  it('getters', done => {\n    const store = new Vuex.Store({\n      state: {\n        count: 0\n      },\n      mutations: {\n        inc: state => state.count++\n      },\n      getters: {\n        count: state => state.count\n      },\n      actions: {\n        check ({ getters }, value) {\n          expect(getters.count).toBe(value)\n        }\n      }\n    })\n\n    const spy = jasmine.createSpy()\n    const vm = new Vue({\n      computed: {\n        a: () => store.getters.count\n      },\n      watch: {\n        a: spy\n      }\n    })\n\n    expect(vm.a).toBe(0)\n    store.dispatch('check', 0)\n\n    store.commit('inc')\n\n    expect(vm.a).toBe(1)\n    store.dispatch('check', 1)\n\n    // update getters\n    store.hotUpdate({\n      getters: {\n        count: state => state.count * 10\n      }\n    })\n\n    expect(vm.a).toBe(10)\n    store.dispatch('check', 10)\n\n    if (isSSR) {\n      done()\n    } else {\n      Vue.nextTick(() => {\n        expect(spy).toHaveBeenCalled()\n        done()\n      })\n    }\n  })\n\n  it('provide warning if a new module is given', () => {\n    const store = new Vuex.Store({})\n\n    spyOn(console, 'warn')\n\n    store.hotUpdate({\n      modules: {\n        test: {\n          state: {\n            count: 0\n          }\n        }\n      }\n    })\n\n    expect(console.warn).toHaveBeenCalledWith(\n      '[vuex] trying to add a new module \\'test\\' on hot reloading, ' +\n      'manual reload is needed'\n    )\n  })\n\n  it('update namespace', () => {\n    // prevent to print notification of unknown action/mutation\n    spyOn(console, 'error')\n\n    const actionSpy = jasmine.createSpy()\n    const mutationSpy = jasmine.createSpy()\n\n    const store = new Vuex.Store({\n      modules: {\n        a: {\n          namespaced: true,\n          state: { value: 1 },\n          getters: { foo: state => state.value },\n          actions: { foo: actionSpy },\n          mutations: { foo: mutationSpy }\n        }\n      }\n    })\n\n    expect(store.state.a.value).toBe(1)\n    expect(store.getters['a/foo']).toBe(1)\n    store.dispatch('a/foo')\n    expect(actionSpy.calls.count()).toBe(1)\n    store.commit('a/foo')\n    expect(actionSpy.calls.count()).toBe(1)\n\n    store.hotUpdate({\n      modules: {\n        a: {\n          namespaced: false\n        }\n      }\n    })\n\n    expect(store.state.a.value).toBe(1)\n    expect(store.getters['a/foo']).toBe(undefined) // removed\n    expect(store.getters['foo']).toBe(1) // renamed\n\n    // should not be called\n    store.dispatch('a/foo')\n    expect(actionSpy.calls.count()).toBe(1)\n\n    // should be called\n    store.dispatch('foo')\n    expect(actionSpy.calls.count()).toBe(2)\n\n    // should not be called\n    store.commit('a/foo')\n    expect(mutationSpy.calls.count()).toBe(1)\n\n    // should be called\n    store.commit('foo')\n    expect(mutationSpy.calls.count()).toBe(2)\n  })\n})\n"
  },
  {
    "path": "vuex/test/unit/jasmine.json",
    "content": "{\n  \"spec_dir\": \"test/unit\",\n  \"spec_files\": [\n    \"**/*.spec.js\"\n  ],\n  \"helpers\": [\n    \"../../node_modules/babel-register/lib/node.js\",\n    \"setup.js\"\n  ]\n}\n"
  },
  {
    "path": "vuex/test/unit/module/module-collection.spec.js",
    "content": "import ModuleCollection from '../../../src/module/module-collection'\n\ndescribe('ModuleCollection', () => {\n  it('get', () => {\n    const collection = new ModuleCollection({\n      state: { value: 1 },\n      modules: {\n        a: {\n          state: { value: 2 }\n        },\n        b: {\n          state: { value: 3 },\n          modules: {\n            c: {\n              state: { value: 4 }\n            }\n          }\n        }\n      }\n    })\n    expect(collection.get([]).state.value).toBe(1)\n    expect(collection.get(['a']).state.value).toBe(2)\n    expect(collection.get(['b']).state.value).toBe(3)\n    expect(collection.get(['b', 'c']).state.value).toBe(4)\n  })\n\n  it('getNamespace', () => {\n    const module = (namespaced, children) => {\n      return {\n        namespaced,\n        modules: children\n      }\n    }\n    const collection = new ModuleCollection({\n      namespace: 'ignore/', // root module namespace should be ignored\n      modules: {\n        a: module(true, {\n          b: module(false, {\n            c: module(true)\n          }),\n          d: module(true)\n        })\n      }\n    })\n    const check = (path, expected) => {\n      const type = 'test'\n      const namespace = collection.getNamespace(path)\n      expect(namespace + type).toBe(expected)\n    }\n    check(['a'], 'a/test')\n    check(['a', 'b'], 'a/test')\n    check(['a', 'b', 'c'], 'a/c/test')\n    check(['a', 'd'], 'a/d/test')\n  })\n\n  it('register', () => {\n    const collection = new ModuleCollection({})\n    collection.register(['a'], {\n      state: { value: 1 }\n    })\n    collection.register(['b'], {\n      state: { value: 2 }\n    })\n    collection.register(['a', 'b'], {\n      state: { value: 3 }\n    })\n\n    expect(collection.get(['a']).state.value).toBe(1)\n    expect(collection.get(['b']).state.value).toBe(2)\n    expect(collection.get(['a', 'b']).state.value).toBe(3)\n  })\n\n  it('unregister', () => {\n    const collection = new ModuleCollection({})\n    collection.register(['a'], {\n      state: { value: true }\n    })\n    expect(collection.get(['a']).state.value).toBe(true)\n\n    collection.unregister(['a'])\n    expect(collection.get(['a'])).toBe(undefined)\n  })\n\n  it('does not unregister initial modules', () => {\n    const collection = new ModuleCollection({\n      modules: {\n        a: {\n          state: { value: true }\n        }\n      }\n    })\n    collection.unregister(['a'])\n    expect(collection.get(['a']).state.value).toBe(true)\n  })\n})\n"
  },
  {
    "path": "vuex/test/unit/module/module.spec.js",
    "content": "import Module from '../../../src/module/module'\n\ndescribe('Module', () => {\n  it('get state', () => {\n    const module = new Module({\n      state: {\n        value: true\n      }\n    })\n    expect(module.state).toEqual({ value: true })\n  })\n\n  it('get state: should return object if state option is empty', () => {\n    const module = new Module({})\n    expect(module.state).toEqual({})\n  })\n\n  it('get namespacer: no namespace option', () => {\n    const module = new Module({})\n    expect(module.namespaced).toBe(false)\n  })\n\n  it('get namespacer: namespace option is true', () => {\n    let module = new Module({\n      namespaced: true\n    })\n    expect(module.namespaced).toBe(true)\n\n    module = new Module({\n      namespaced: 100\n    })\n    expect(module.namespaced).toBe(true)\n  })\n\n  it('add child method', () => {\n    const module = new Module({})\n\n    module.addChild('v1', new Module({}))\n    module.addChild('v2', new Module({}))\n    expect(Object.keys(module._children)).toEqual(['v1', 'v2'])\n  })\n\n  it('remove child method', () => {\n    const module = new Module({})\n\n    module.addChild('v1', new Module({}))\n    module.addChild('v2', new Module({}))\n    expect(Object.keys(module._children)).toEqual(['v1', 'v2'])\n    module.removeChild('v2')\n    module.removeChild('abc')\n    expect(Object.keys(module._children)).toEqual(['v1'])\n  })\n\n  it('get child method', () => {\n    const module = new Module({})\n\n    const subModule1 = new Module({ state: { name: 'v1' }})\n    const subModule2 = new Module({ state: { name: 'v2' }})\n    module.addChild('v1', subModule1)\n    module.addChild('v2', subModule2)\n    expect(module.getChild('v2')).toEqual(subModule2)\n    expect(module.getChild('v1')).toEqual(subModule1)\n  })\n\n  it('update method', () => {\n    const originObject = {\n      state: {\n        name: 'vuex',\n        version: '2.x.x'\n      },\n      namespaced: true,\n      actions: {\n        a1: () => {},\n        a2: () => {}\n      },\n      mutations: {\n        m1: () => {},\n        m2: () => {}\n      },\n      getters: {\n        g1: () => {},\n        g2: () => {}\n      }\n    }\n    const newObject = {\n      actions: {\n        a3: () => {},\n        a4: () => {}\n      },\n      mutations: {\n        m3: () => {},\n        m2: () => {}\n      },\n      getters: {\n        g1: () => {}\n      },\n      namespaced: false,\n      state: {\n        name: 'vuex',\n        version: '3.x.x'\n      }\n    }\n    const module = new Module(originObject)\n\n    expect(module._rawModule).toEqual(originObject)\n\n    module.update(newObject)\n    expect(module._rawModule.actions).toEqual(newObject.actions)\n    expect(module._rawModule.mutations).toEqual(newObject.mutations)\n    expect(module._rawModule.getters).toEqual(newObject.getters)\n    expect(module._rawModule.namespaced).toEqual(newObject.namespaced)\n    expect(module._rawModule.state).toEqual(originObject.state)\n  })\n\n  it('forEachChild method', () => {\n    const module = new Module({})\n    const module1 = new Module({})\n    const module2 = new Module({})\n\n    module.addChild('v1', module1)\n    module.addChild('v2', module2)\n\n    const collections = []\n    module.forEachChild((item) => { collections.push(item) })\n    expect(collections.length).toEqual(2)\n    expect(collections).toEqual([module2, module1])\n  })\n\n  it('forEachAction method', () => {\n    const action1 = () => {}\n    const action2 = () => {}\n\n    const module = new Module({\n      actions: {\n        action1, action2\n      }\n    })\n\n    const collections = []\n    module.forEachAction((item) => { collections.push(item) })\n    expect(collections.length).toEqual(2)\n    expect(collections).toEqual([action1, action2])\n  })\n\n  it('forEachGetter method', () => {\n    const getter1 = () => {}\n    const getter2 = () => {}\n\n    const module = new Module({\n      getters: {\n        getter1, getter2\n      }\n    })\n\n    const collections = []\n    module.forEachGetter((item) => { collections.push(item) })\n    expect(collections.length).toEqual(2)\n    expect(collections).toEqual([getter1, getter2])\n  })\n\n  it('forEachMutation method', () => {\n    const mutation1 = () => {}\n    const mutation2 = () => {}\n\n    const module = new Module({\n      mutations: {\n        mutation1, mutation2\n      }\n    })\n\n    const collections = []\n    module.forEachMutation((item) => { collections.push(item) })\n    expect(collections.length).toEqual(2)\n    expect(collections).toEqual([mutation1, mutation2])\n  })\n})\n"
  },
  {
    "path": "vuex/test/unit/modules.spec.js",
    "content": "import Vue from 'vue'\nimport Vuex from '../../dist/vuex.common.js'\n\nconst TEST = 'TEST'\n\ndescribe('Modules', () => {\n  describe('module registration', () => {\n    it('dynamic module registration', () => {\n      const store = new Vuex.Store({\n        strict: true,\n        modules: {\n          foo: {\n            state: { bar: 1 },\n            mutations: { inc: state => state.bar++ },\n            actions: { incFoo: ({ commit }) => commit('inc') },\n            getters: { bar: state => state.bar }\n          }\n        }\n      })\n      expect(() => {\n        store.registerModule('hi', {\n          state: { a: 1 },\n          mutations: { inc: state => state.a++ },\n          actions: { inc: ({ commit }) => commit('inc') },\n          getters: { a: state => state.a }\n        })\n      }).not.toThrow()\n\n      expect(store._mutations.inc.length).toBe(2)\n      expect(store.state.hi.a).toBe(1)\n      expect(store.getters.a).toBe(1)\n\n      // assert initial modules work as expected after dynamic registration\n      expect(store.state.foo.bar).toBe(1)\n      expect(store.getters.bar).toBe(1)\n\n      // test dispatching actions defined in dynamic module\n      store.dispatch('inc')\n      expect(store.state.hi.a).toBe(2)\n      expect(store.getters.a).toBe(2)\n      expect(store.state.foo.bar).toBe(2)\n      expect(store.getters.bar).toBe(2)\n\n      // unregister\n      store.unregisterModule('hi')\n      expect(store.state.hi).toBeUndefined()\n      expect(store.getters.a).toBeUndefined()\n      expect(store._mutations.inc.length).toBe(1)\n      expect(store._actions.inc).toBeUndefined()\n\n      // assert initial modules still work as expected after unregister\n      store.dispatch('incFoo')\n      expect(store.state.foo.bar).toBe(3)\n      expect(store.getters.bar).toBe(3)\n    })\n\n    it('dynamic module registration with namespace inheritance', () => {\n      const store = new Vuex.Store({\n        modules: {\n          a: {\n            namespaced: true\n          }\n        }\n      })\n      const actionSpy = jasmine.createSpy()\n      const mutationSpy = jasmine.createSpy()\n      store.registerModule(['a', 'b'], {\n        state: { value: 1 },\n        getters: { foo: state => state.value },\n        actions: { foo: actionSpy },\n        mutations: { foo: mutationSpy }\n      })\n\n      expect(store.state.a.b.value).toBe(1)\n      expect(store.getters['a/foo']).toBe(1)\n\n      store.dispatch('a/foo')\n      expect(actionSpy).toHaveBeenCalled()\n\n      store.commit('a/foo')\n      expect(mutationSpy).toHaveBeenCalled()\n    })\n\n    it('dynamic module registration preserving hydration', () => {\n      const store = new Vuex.Store({})\n      store.replaceState({ a: { foo: 'state' }})\n      const actionSpy = jasmine.createSpy()\n      const mutationSpy = jasmine.createSpy()\n      store.registerModule('a', {\n        namespaced: true,\n        getters: { foo: state => state.foo },\n        actions: { foo: actionSpy },\n        mutations: { foo: mutationSpy }\n      }, { preserveState: true })\n\n      expect(store.state.a.foo).toBe('state')\n      expect(store.getters['a/foo']).toBe('state')\n\n      store.dispatch('a/foo')\n      expect(actionSpy).toHaveBeenCalled()\n\n      store.commit('a/foo')\n      expect(mutationSpy).toHaveBeenCalled()\n    })\n  })\n\n  // #524\n  it('should not fire an unrelated watcher', done => {\n    const spy = jasmine.createSpy()\n    const store = new Vuex.Store({\n      modules: {\n        a: {\n          state: { value: 1 }\n        },\n        b: {}\n      }\n    })\n\n    store.watch(state => state.a, spy)\n    store.registerModule(['b', 'c'], {\n      state: { value: 2 }\n    })\n    Vue.nextTick(() => {\n      expect(spy).not.toHaveBeenCalled()\n      done()\n    })\n  })\n\n  describe('modules usage', () => {\n    it('state as function (multiple module in same store)', () => {\n      const module = {\n        state () {\n          return { a: 0 }\n        },\n        mutations: {\n          [TEST] (state, n) {\n            state.a += n\n          }\n        }\n      }\n\n      const store = new Vuex.Store({\n        modules: {\n          one: module,\n          two: module\n        }\n      })\n\n      expect(store.state.one.a).toBe(0)\n      expect(store.state.two.a).toBe(0)\n\n      store.commit(TEST, 1)\n      expect(store.state.one.a).toBe(1)\n      expect(store.state.two.a).toBe(1)\n    })\n\n    it('state as function (same module in multiple stores)', () => {\n      const module = {\n        state () {\n          return { a: 0 }\n        },\n        mutations: {\n          [TEST] (state, n) {\n            state.a += n\n          }\n        }\n      }\n\n      const storeA = new Vuex.Store({\n        modules: {\n          foo: module\n        }\n      })\n\n      const storeB = new Vuex.Store({\n        modules: {\n          bar: module\n        }\n      })\n\n      expect(storeA.state.foo.a).toBe(0)\n      expect(storeB.state.bar.a).toBe(0)\n\n      storeA.commit(TEST, 1)\n      expect(storeA.state.foo.a).toBe(1)\n      expect(storeB.state.bar.a).toBe(0)\n\n      storeB.commit(TEST, 2)\n      expect(storeA.state.foo.a).toBe(1)\n      expect(storeB.state.bar.a).toBe(2)\n    })\n\n    it('module: mutation', function () {\n      const mutations = {\n        [TEST] (state, n) {\n          state.a += n\n        }\n      }\n      const store = new Vuex.Store({\n        state: {\n          a: 1\n        },\n        mutations,\n        modules: {\n          nested: {\n            state: { a: 2 },\n            mutations,\n            modules: {\n              one: {\n                state: { a: 3 },\n                mutations\n              },\n              nested: {\n                modules: {\n                  two: {\n                    state: { a: 4 },\n                    mutations\n                  },\n                  three: {\n                    state: { a: 5 },\n                    mutations\n                  }\n                }\n              }\n            }\n          },\n          four: {\n            state: { a: 6 },\n            mutations\n          }\n        }\n      })\n      store.commit(TEST, 1)\n      expect(store.state.a).toBe(2)\n      expect(store.state.nested.a).toBe(3)\n      expect(store.state.nested.one.a).toBe(4)\n      expect(store.state.nested.nested.two.a).toBe(5)\n      expect(store.state.nested.nested.three.a).toBe(6)\n      expect(store.state.four.a).toBe(7)\n    })\n\n    it('module: action', function () {\n      let calls = 0\n      const makeAction = n => {\n        return {\n          [TEST] ({ state, rootState }) {\n            calls++\n            expect(state.a).toBe(n)\n            expect(rootState).toBe(store.state)\n          }\n        }\n      }\n      const store = new Vuex.Store({\n        state: {\n          a: 1\n        },\n        actions: makeAction(1),\n        modules: {\n          nested: {\n            state: { a: 2 },\n            actions: makeAction(2),\n            modules: {\n              one: {\n                state: { a: 3 },\n                actions: makeAction(3)\n              },\n              nested: {\n                modules: {\n                  two: {\n                    state: { a: 4 },\n                    actions: makeAction(4)\n                  },\n                  three: {\n                    state: { a: 5 },\n                    actions: makeAction(5)\n                  }\n                }\n              }\n            }\n          },\n          four: {\n            state: { a: 6 },\n            actions: makeAction(6)\n          }\n        }\n      })\n      store.dispatch(TEST)\n      expect(calls).toBe(6)\n    })\n\n    it('module: getters', function () {\n      const makeGetter = n => ({\n        [`getter${n}`]: (state, getters, rootState) => {\n          expect(getters.constant).toBe(0)\n          expect(rootState).toBe(store.state)\n          return state.a\n        }\n      })\n      const store = new Vuex.Store({\n        state: {\n          a: 1\n        },\n        getters: {\n          constant: () => 0,\n          ...makeGetter(1)\n        },\n        modules: {\n          nested: {\n            state: { a: 2 },\n            getters: makeGetter(2),\n            modules: {\n              one: {\n                state: { a: 3 },\n                getters: makeGetter(3)\n              },\n              nested: {\n                modules: {\n                  two: {\n                    state: { a: 4 },\n                    getters: makeGetter(4)\n                  },\n                  three: {\n                    state: { a: 5 },\n                    getters: makeGetter(5)\n                  }\n                }\n              }\n            }\n          },\n          four: {\n            state: { a: 6 },\n            getters: makeGetter(6)\n          }\n        }\n      })\n      ;[1, 2, 3, 4, 5, 6].forEach(n => {\n        expect(store.getters[`getter${n}`]).toBe(n)\n      })\n    })\n\n    it('module: namespace', () => {\n      const actionSpy = jasmine.createSpy()\n      const mutationSpy = jasmine.createSpy()\n\n      const store = new Vuex.Store({\n        modules: {\n          a: {\n            namespaced: true,\n            state: {\n              a: 1\n            },\n            getters: {\n              b: () => 2\n            },\n            actions: {\n              [TEST]: actionSpy\n            },\n            mutations: {\n              [TEST]: mutationSpy\n            }\n          }\n        }\n      })\n\n      expect(store.state.a.a).toBe(1)\n      expect(store.getters['a/b']).toBe(2)\n      store.dispatch('a/' + TEST)\n      expect(actionSpy).toHaveBeenCalled()\n      store.commit('a/' + TEST)\n      expect(mutationSpy).toHaveBeenCalled()\n    })\n\n    it('module: nested namespace', () => {\n      // mock module generator\n      const actionSpys = []\n      const mutationSpys = []\n      const createModule = (name, namespaced, children) => {\n        const actionSpy = jasmine.createSpy()\n        const mutationSpy = jasmine.createSpy()\n\n        actionSpys.push(actionSpy)\n        mutationSpys.push(mutationSpy)\n\n        return {\n          namespaced,\n          state: {\n            [name]: true\n          },\n          getters: {\n            [name]: state => state[name]\n          },\n          actions: {\n            [name]: actionSpy\n          },\n          mutations: {\n            [name]: mutationSpy\n          },\n          modules: children\n        }\n      }\n\n      // mock module\n      const modules = {\n        a: createModule('a', true, { // a/a\n          b: createModule('b', false, { // a/b - does not add namespace\n            c: createModule('c', true) // a/c/c\n          }),\n          d: createModule('d', true) // a/d/d\n        })\n      }\n\n      const store = new Vuex.Store({ modules })\n\n      const expectedTypes = [\n        'a/a', 'a/b', 'a/c/c', 'a/d/d'\n      ]\n\n      // getters\n      expectedTypes.forEach(type => {\n        expect(store.getters[type]).toBe(true)\n      })\n\n      // actions\n      expectedTypes.forEach(type => {\n        store.dispatch(type)\n      })\n      actionSpys.forEach(spy => {\n        expect(spy.calls.count()).toBe(1)\n      })\n\n      // mutations\n      expectedTypes.forEach(type => {\n        store.commit(type)\n      })\n      mutationSpys.forEach(spy => {\n        expect(spy.calls.count()).toBe(1)\n      })\n    })\n\n    it('module: getters are namespaced in namespaced module', () => {\n      const store = new Vuex.Store({\n        state: { value: 'root' },\n        getters: {\n          foo: state => state.value\n        },\n        modules: {\n          a: {\n            namespaced: true,\n            state: { value: 'module' },\n            getters: {\n              foo: state => state.value,\n              bar: (state, getters) => getters.foo,\n              baz: (state, getters, rootState, rootGetters) => rootGetters.foo\n            }\n          }\n        }\n      })\n\n      expect(store.getters['a/foo']).toBe('module')\n      expect(store.getters['a/bar']).toBe('module')\n      expect(store.getters['a/baz']).toBe('root')\n    })\n\n    it('module: action context is namespaced in namespaced module', done => {\n      const rootActionSpy = jasmine.createSpy()\n      const rootMutationSpy = jasmine.createSpy()\n      const moduleActionSpy = jasmine.createSpy()\n      const moduleMutationSpy = jasmine.createSpy()\n\n      const store = new Vuex.Store({\n        state: { value: 'root' },\n        getters: { foo: state => state.value },\n        actions: { foo: rootActionSpy },\n        mutations: { foo: rootMutationSpy },\n        modules: {\n          a: {\n            namespaced: true,\n            state: { value: 'module' },\n            getters: { foo: state => state.value },\n            actions: {\n              foo: moduleActionSpy,\n              test ({ dispatch, commit, getters, rootGetters }) {\n                expect(getters.foo).toBe('module')\n                expect(rootGetters.foo).toBe('root')\n\n                dispatch('foo')\n                expect(moduleActionSpy.calls.count()).toBe(1)\n                dispatch('foo', null, { root: true })\n                expect(rootActionSpy.calls.count()).toBe(1)\n\n                commit('foo')\n                expect(moduleMutationSpy.calls.count()).toBe(1)\n                commit('foo', null, { root: true })\n                expect(rootMutationSpy.calls.count()).toBe(1)\n\n                done()\n              }\n            },\n            mutations: { foo: moduleMutationSpy }\n          }\n        }\n      })\n\n      store.dispatch('a/test')\n    })\n\n    it('module: use other module that has same namespace', done => {\n      const actionSpy = jasmine.createSpy()\n      const mutationSpy = jasmine.createSpy()\n\n      const store = new Vuex.Store({\n        modules: {\n          parent: {\n            namespaced: true,\n\n            modules: {\n              a: {\n                state: { value: 'a' },\n                getters: { foo: state => state.value },\n                actions: { foo: actionSpy },\n                mutations: { foo: mutationSpy }\n              },\n\n              b: {\n                state: { value: 'b' },\n                getters: { bar: (state, getters) => getters.foo },\n                actions: {\n                  test ({ dispatch, commit, getters }) {\n                    expect(getters.foo).toBe('a')\n                    expect(getters.bar).toBe('a')\n\n                    dispatch('foo')\n                    expect(actionSpy).toHaveBeenCalled()\n\n                    commit('foo')\n                    expect(mutationSpy).toHaveBeenCalled()\n\n                    done()\n                  }\n                }\n              }\n            }\n          }\n        }\n      })\n\n      store.dispatch('parent/test')\n    })\n\n    it('dispatching multiple actions in different modules', done => {\n      const store = new Vuex.Store({\n        modules: {\n          a: {\n            actions: {\n              [TEST] () {\n                return 1\n              }\n            }\n          },\n          b: {\n            actions: {\n              [TEST] () {\n                return new Promise(r => r(2))\n              }\n            }\n          }\n        }\n      })\n      store.dispatch(TEST).then(res => {\n        expect(res[0]).toBe(1)\n        expect(res[1]).toBe(2)\n        done()\n      })\n    })\n\n    it('root actions dispatched in namespaced modules', done => {\n      const store = new Vuex.Store({\n        modules: {\n          a: {\n            namespaced: true,\n            actions: {\n              [TEST]: {\n                root: true,\n                handler () {\n                  return 1\n                }\n              }\n            }\n          },\n          b: {\n            namespaced: true,\n            actions: {\n              [TEST]: {\n                root: true,\n                handler () {\n                  return new Promise(r => r(2))\n                }\n              }\n            }\n          },\n          c: {\n            namespaced: true,\n            actions: {\n              [TEST]: {\n                handler () {\n                  // Should not be called\n                  return 3\n                }\n              }\n            }\n          },\n          d: {\n            namespaced: true,\n            actions: {\n              [TEST] () {\n                // Should not be called\n                return 4\n              }\n            }\n          }\n        }\n      })\n      store.dispatch(TEST).then(res => {\n        expect(res.length).toBe(2)\n        expect(res[0]).toBe(1)\n        expect(res[1]).toBe(2)\n        done()\n      })\n    })\n\n    it('plugins', function () {\n      let initState\n      const actionSpy = jasmine.createSpy()\n      const mutations = []\n      const subscribeActionSpy = jasmine.createSpy()\n      const store = new Vuex.Store({\n        state: {\n          a: 1\n        },\n        mutations: {\n          [TEST] (state, n) {\n            state.a += n\n          }\n        },\n        actions: {\n          [TEST]: actionSpy\n        },\n        plugins: [\n          store => {\n            initState = store.state\n            store.subscribe((mut, state) => {\n              expect(state).toBe(state)\n              mutations.push(mut)\n            })\n            store.subscribeAction(subscribeActionSpy)\n          }\n        ]\n      })\n      expect(initState).toBe(store.state)\n      store.commit(TEST, 2)\n      store.dispatch(TEST, 2)\n      expect(mutations.length).toBe(1)\n      expect(mutations[0].type).toBe(TEST)\n      expect(mutations[0].payload).toBe(2)\n      expect(actionSpy).toHaveBeenCalled()\n      expect(subscribeActionSpy).toHaveBeenCalledWith(\n        { type: TEST, payload: 2 },\n        store.state\n      )\n    })\n  })\n\n  it('asserts a mutation should be a function', () => {\n    expect(() => {\n      new Vuex.Store({\n        mutations: {\n          test: null\n        }\n      })\n    }).toThrowError(\n      /mutations should be function but \"mutations\\.test\" is null/\n    )\n\n    expect(() => {\n      new Vuex.Store({\n        modules: {\n          foo: {\n            modules: {\n              bar: {\n                mutations: {\n                  test: 123\n                }\n              }\n            }\n          }\n        }\n      })\n    }).toThrowError(\n      /mutations should be function but \"mutations\\.test\" in module \"foo\\.bar\" is 123/\n    )\n  })\n\n  it('asserts an action should be a function', () => {\n    expect(() => {\n      new Vuex.Store({\n        actions: {\n          test: 'test'\n        }\n      })\n    }).toThrowError(\n      /actions should be function or object with \"handler\" function but \"actions\\.test\" is \"test\"/\n    )\n\n    expect(() => {\n      new Vuex.Store({\n        modules: {\n          foo: {\n            modules: {\n              bar: {\n                actions: {\n                  test: 'error'\n                }\n              }\n            }\n          }\n        }\n      })\n    }).toThrowError(\n      /actions should be function or object with \"handler\" function but \"actions\\.test\" in module \"foo\\.bar\" is \"error\"/\n    )\n  })\n\n  it('asserts a getter should be a function', () => {\n    expect(() => {\n      new Vuex.Store({\n        getters: {\n          test: undefined\n        }\n      })\n    }).toThrowError(\n      /getters should be function but \"getters\\.test\" is undefined/\n    )\n\n    expect(() => {\n      new Vuex.Store({\n        modules: {\n          foo: {\n            modules: {\n              bar: {\n                getters: {\n                  test: true\n                }\n              }\n            }\n          }\n        }\n      })\n    }).toThrowError(\n      /getters should be function but \"getters\\.test\" in module \"foo\\.bar\" is true/\n    )\n  })\n})\n"
  },
  {
    "path": "vuex/test/unit/setup.js",
    "content": "import 'babel-polyfill'\nimport Vue from 'vue/dist/vue.common.js'\nimport Vuex from '../../dist/vuex.common.js'\n\nVue.use(Vuex)\n"
  },
  {
    "path": "vuex/test/unit/store.spec.js",
    "content": "import Vue from 'vue/dist/vue.common.js'\nimport Vuex from '../../dist/vuex.common.js'\n\nconst TEST = 'TEST'\nconst isSSR = process.env.VUE_ENV === 'server'\n\ndescribe('Store', () => {\n  it('committing mutations', () => {\n    const store = new Vuex.Store({\n      state: {\n        a: 1\n      },\n      mutations: {\n        [TEST] (state, n) {\n          state.a += n\n        }\n      }\n    })\n    store.commit(TEST, 2)\n    expect(store.state.a).toBe(3)\n  })\n\n  it('committing with object style', () => {\n    const store = new Vuex.Store({\n      state: {\n        a: 1\n      },\n      mutations: {\n        [TEST] (state, payload) {\n          state.a += payload.amount\n        }\n      }\n    })\n    store.commit({\n      type: TEST,\n      amount: 2\n    })\n    expect(store.state.a).toBe(3)\n  })\n\n  it('asserts committed type', () => {\n    const store = new Vuex.Store({\n      state: {\n        a: 1\n      },\n      mutations: {\n        // Maybe registered with undefined type accidentally\n        // if the user has typo in a constant type\n        undefined (state, n) {\n          state.a += n\n        }\n      }\n    })\n    expect(() => {\n      store.commit(undefined, 2)\n    }).toThrowError(/Expects string as the type, but found undefined/)\n    expect(store.state.a).toBe(1)\n  })\n\n  it('dispatching actions, sync', () => {\n    const store = new Vuex.Store({\n      state: {\n        a: 1\n      },\n      mutations: {\n        [TEST] (state, n) {\n          state.a += n\n        }\n      },\n      actions: {\n        [TEST] ({ commit }, n) {\n          commit(TEST, n)\n        }\n      }\n    })\n    store.dispatch(TEST, 2)\n    expect(store.state.a).toBe(3)\n  })\n\n  it('dispatching with object style', () => {\n    const store = new Vuex.Store({\n      state: {\n        a: 1\n      },\n      mutations: {\n        [TEST] (state, n) {\n          state.a += n\n        }\n      },\n      actions: {\n        [TEST] ({ commit }, payload) {\n          commit(TEST, payload.amount)\n        }\n      }\n    })\n    store.dispatch({\n      type: TEST,\n      amount: 2\n    })\n    expect(store.state.a).toBe(3)\n  })\n\n  it('dispatching actions, with returned Promise', done => {\n    const store = new Vuex.Store({\n      state: {\n        a: 1\n      },\n      mutations: {\n        [TEST] (state, n) {\n          state.a += n\n        }\n      },\n      actions: {\n        [TEST] ({ commit }, n) {\n          return new Promise(resolve => {\n            setTimeout(() => {\n              commit(TEST, n)\n              resolve()\n            }, 0)\n          })\n        }\n      }\n    })\n    expect(store.state.a).toBe(1)\n    store.dispatch(TEST, 2).then(() => {\n      expect(store.state.a).toBe(3)\n      done()\n    })\n  })\n\n  it('composing actions with async/await', done => {\n    const store = new Vuex.Store({\n      state: {\n        a: 1\n      },\n      mutations: {\n        [TEST] (state, n) {\n          state.a += n\n        }\n      },\n      actions: {\n        [TEST] ({ commit }, n) {\n          return new Promise(resolve => {\n            setTimeout(() => {\n              commit(TEST, n)\n              resolve()\n            }, 0)\n          })\n        },\n        two: async ({ commit, dispatch }, n) => {\n          await dispatch(TEST, 1)\n          expect(store.state.a).toBe(2)\n          commit(TEST, n)\n        }\n      }\n    })\n    expect(store.state.a).toBe(1)\n    store.dispatch('two', 3).then(() => {\n      expect(store.state.a).toBe(5)\n      done()\n    })\n  })\n\n  it('detecting action Promise errors', done => {\n    const store = new Vuex.Store({\n      actions: {\n        [TEST] () {\n          return new Promise((resolve, reject) => {\n            reject('no')\n          })\n        }\n      }\n    })\n    const spy = jasmine.createSpy()\n    store._devtoolHook = {\n      emit: spy\n    }\n    const thenSpy = jasmine.createSpy()\n    store.dispatch(TEST)\n      .then(thenSpy)\n      .catch(err => {\n        expect(thenSpy).not.toHaveBeenCalled()\n        expect(err).toBe('no')\n        expect(spy).toHaveBeenCalledWith('vuex:error', 'no')\n        done()\n      })\n  })\n\n  it('asserts dispatched type', () => {\n    const store = new Vuex.Store({\n      state: {\n        a: 1\n      },\n      mutations: {\n        [TEST] (state, n) {\n          state.a += n\n        }\n      },\n      actions: {\n        // Maybe registered with undefined type accidentally\n        // if the user has typo in a constant type\n        undefined ({ commit }, n) {\n          commit(TEST, n)\n        }\n      }\n    })\n    expect(() => {\n      store.dispatch(undefined, 2)\n    }).toThrowError(/Expects string as the type, but found undefined/)\n    expect(store.state.a).toBe(1)\n  })\n\n  it('getters', () => {\n    const store = new Vuex.Store({\n      state: {\n        a: 0\n      },\n      getters: {\n        state: state => state.a > 0 ? 'hasAny' : 'none'\n      },\n      mutations: {\n        [TEST] (state, n) {\n          state.a += n\n        }\n      },\n      actions: {\n        check ({ getters }, value) {\n          // check for exposing getters into actions\n          expect(getters.state).toBe(value)\n        }\n      }\n    })\n    expect(store.getters.state).toBe('none')\n    store.dispatch('check', 'none')\n\n    store.commit(TEST, 1)\n\n    expect(store.getters.state).toBe('hasAny')\n    store.dispatch('check', 'hasAny')\n  })\n\n  it('store injection', () => {\n    const store = new Vuex.Store()\n    const vm = new Vue({\n      store\n    })\n    const child = new Vue({ parent: vm })\n    expect(child.$store).toBe(store)\n  })\n\n  it('should warn silent option depreciation', () => {\n    spyOn(console, 'warn')\n\n    const store = new Vuex.Store({\n      mutations: {\n        [TEST] () {}\n      }\n    })\n    store.commit(TEST, {}, { silent: true })\n\n    expect(console.warn).toHaveBeenCalledWith(\n      `[vuex] mutation type: ${TEST}. Silent option has been removed. ` +\n      'Use the filter functionality in the vue-devtools'\n    )\n  })\n\n  it('asserts the call with the new operator', () => {\n    expect(() => {\n      Vuex.Store({})\n    }).toThrowError(/Store must be called with the new operator/)\n  })\n\n  it('should accept state as function', () => {\n    const store = new Vuex.Store({\n      state: () => ({\n        a: 1\n      }),\n      mutations: {\n        [TEST] (state, n) {\n          state.a += n\n        }\n      }\n    })\n    expect(store.state.a).toBe(1)\n    store.commit(TEST, 2)\n    expect(store.state.a).toBe(3)\n  })\n\n  it('should not call root state function twice', () => {\n    const spy = jasmine.createSpy().and.returnValue(1)\n    new Vuex.Store({\n      state: spy\n    })\n    expect(spy).toHaveBeenCalledTimes(1)\n  })\n\n  it('subscribe: should handle subscriptions / unsubscriptions', () => {\n    const subscribeSpy = jasmine.createSpy()\n    const secondSubscribeSpy = jasmine.createSpy()\n    const testPayload = 2\n    const store = new Vuex.Store({\n      state: {},\n      mutations: {\n        [TEST]: () => {}\n      }\n    })\n\n    const unsubscribe = store.subscribe(subscribeSpy)\n    store.subscribe(secondSubscribeSpy)\n    store.commit(TEST, testPayload)\n    unsubscribe()\n    store.commit(TEST, testPayload)\n\n    expect(subscribeSpy).toHaveBeenCalledWith(\n      { type: TEST, payload: testPayload },\n      store.state\n    )\n    expect(secondSubscribeSpy).toHaveBeenCalled()\n    expect(subscribeSpy.calls.count()).toBe(1)\n    expect(secondSubscribeSpy.calls.count()).toBe(2)\n  })\n\n  // store.watch should only be asserted in non-SSR environment\n  if (!isSSR) {\n    it('strict mode: warn mutations outside of handlers', () => {\n      const store = new Vuex.Store({\n        state: {\n          a: 1\n        },\n        strict: true\n      })\n      Vue.config.silent = true\n      expect(() => { store.state.a++ }).toThrow()\n      Vue.config.silent = false\n    })\n\n    it('watch: with resetting vm', done => {\n      const store = new Vuex.Store({\n        state: {\n          count: 0\n        },\n        mutations: {\n          [TEST]: state => state.count++\n        }\n      })\n\n      const spy = jasmine.createSpy()\n      store.watch(state => state.count, spy)\n\n      // reset store vm\n      store.registerModule('test', {})\n\n      Vue.nextTick(() => {\n        store.commit(TEST)\n        expect(store.state.count).toBe(1)\n\n        Vue.nextTick(() => {\n          expect(spy).toHaveBeenCalled()\n          done()\n        })\n      })\n    })\n\n    it('watch: getter function has access to store\\'s getters object', done => {\n      const store = new Vuex.Store({\n        state: {\n          count: 0\n        },\n        mutations: {\n          [TEST]: state => state.count++\n        },\n        getters: {\n          getCount: state => state.count\n        }\n      })\n\n      const getter = function getter (state, getters) {\n        return state.count\n      }\n      const spy = spyOn({ getter }, 'getter').and.callThrough()\n      const spyCb = jasmine.createSpy()\n\n      store.watch(spy, spyCb)\n\n      Vue.nextTick(() => {\n        store.commit(TEST)\n        expect(store.state.count).toBe(1)\n\n        Vue.nextTick(() => {\n          expect(spy).toHaveBeenCalledWith(store.state, store.getters)\n          done()\n        })\n      })\n    })\n  }\n})\n"
  },
  {
    "path": "vuex/test/unit/util.spec.js",
    "content": "import { find, deepCopy, forEachValue, isObject, isPromise, assert } from '../../src/util'\n\ndescribe('util', () => {\n  it('find', () => {\n    const list = [33, 22, 112, 222, 43]\n    expect(find(list, function (a) { return a % 2 === 0 })).toEqual(22)\n  })\n\n  it('deepCopy: nornal structure', () => {\n    const original = {\n      a: 1,\n      b: 'string',\n      c: true,\n      d: null,\n      e: undefined\n    }\n    const copy = deepCopy(original)\n\n    expect(copy).toEqual(original)\n  })\n\n  it('deepCopy: nested structure', () => {\n    const original = {\n      a: {\n        b: 1,\n        c: [2, 3, {\n          d: 4\n        }]\n      }\n    }\n    const copy = deepCopy(original)\n\n    expect(copy).toEqual(original)\n  })\n\n  it('deepCopy: circular structure', () => {\n    const original = {\n      a: 1\n    }\n    original.circular = original\n\n    const copy = deepCopy(original)\n\n    expect(copy).toEqual(original)\n  })\n\n  it('forEachValue', () => {\n    let number = 1\n\n    function plus (value, key) {\n      number += value\n    }\n    const origin = {\n      a: 1,\n      b: 3\n    }\n\n    forEachValue(origin, plus)\n    expect(number).toEqual(5)\n  })\n\n  it('isObject', () => {\n    expect(isObject(1)).toBe(false)\n    expect(isObject('String')).toBe(false)\n    expect(isObject(undefined)).toBe(false)\n    expect(isObject({})).toBe(true)\n    expect(isObject(null)).toBe(false)\n    expect(isObject([])).toBe(true)\n    expect(isObject(new Function())).toBe(false)\n  })\n\n  it('isPromise', () => {\n    const promise = new Promise(() => {}, () => {})\n    expect(isPromise(1)).toBe(false)\n    expect(isPromise(promise)).toBe(true)\n    expect(isPromise(new Function())).toBe(false)\n  })\n\n  it('assert', () => {\n    expect(assert.bind(null, false, 'Hello')).toThrowError('[vuex] Hello')\n  })\n})\n"
  }
]