├── .babelrc.js ├── .gitignore ├── .idea ├── gengine.iml ├── modules.xml ├── vcs.xml └── workspace.xml ├── LICENSE ├── README.md ├── config.js ├── dist ├── bundle.js ├── geg.js └── geg.min.js ├── index.html ├── package.json ├── src ├── compiler │ ├── codeframe.js │ ├── codegen │ │ ├── events.js │ │ └── index.js │ ├── create-compiler.js │ ├── directives │ │ ├── bind.js │ │ ├── index.js │ │ ├── model.js │ │ └── on.js │ ├── error-detector.js │ ├── helpers.js │ ├── index.js │ ├── optimizer.js │ ├── parser │ │ ├── entity-decoder.js │ │ ├── filter-parser.js │ │ ├── html-parser.js │ │ ├── index.js │ │ └── text-parser.js │ ├── to-function.js │ └── util │ │ └── index.js ├── core │ ├── base │ │ ├── active.js │ │ ├── events.js │ │ ├── index.js │ │ ├── init.js │ │ ├── inject.js │ │ ├── lifecycle.js │ │ ├── proxy.js │ │ ├── render-helpers │ │ │ ├── bind-dynamic-keys.js │ │ │ ├── bind-object-listeners.js │ │ │ ├── bind-object-props.js │ │ │ ├── check-keycodes.js │ │ │ ├── index.js │ │ │ ├── render-list.js │ │ │ ├── render-slot.js │ │ │ ├── render-static.js │ │ │ ├── resolve-filter.js │ │ │ ├── resolve-scoped-slots.js │ │ │ └── resolve-slots.js │ │ ├── render-instance.js │ │ ├── render.js │ │ ├── resove-options.js │ │ └── state.js │ ├── compile │ │ ├── html │ │ │ └── index.js │ │ └── xml │ │ │ └── index.js │ ├── components │ │ ├── index.js │ │ └── keep-alive.js │ ├── config │ │ ├── assets.js │ │ ├── config.js │ │ ├── constants.js │ │ ├── extend.js │ │ ├── index.js │ │ ├── mixin.js │ │ └── use.js │ ├── gdom │ │ ├── create.js │ │ ├── helpers │ │ │ ├── extract-props.js │ │ │ ├── get-first-component-child.js │ │ │ ├── index.js │ │ │ ├── is-async-placeholder.js │ │ │ ├── merge-hook.js │ │ │ ├── normalize-children.js │ │ │ ├── normalize-scoped-slots.js │ │ │ ├── resolve-async-component.js │ │ │ └── update-listeners.js │ │ ├── index.js │ │ ├── modules │ │ │ ├── directives.js │ │ │ ├── index.js │ │ │ └── ref.js │ │ ├── patch.js │ │ └── vnode.js │ ├── gengine │ │ └── index.js │ ├── index.js │ ├── observer │ │ ├── array.js │ │ ├── augement.js │ │ ├── dep.js │ │ ├── index.js │ │ ├── observer.js │ │ ├── scheduler.js │ │ └── traverse.js │ ├── utils │ │ ├── env.js │ │ ├── error.js │ │ ├── index.js │ │ ├── lang.js │ │ ├── next-tick.js │ │ ├── options.js │ │ ├── props.js │ │ └── tools.js │ └── watcher │ │ └── index.js ├── index.js ├── platforms │ └── gxml │ │ ├── compiler │ │ ├── directives │ │ │ ├── html.js │ │ │ ├── index.js │ │ │ ├── model.js │ │ │ └── text.js │ │ ├── index.js │ │ ├── modules │ │ │ ├── class.js │ │ │ ├── index.js │ │ │ ├── model.js │ │ │ └── style.js │ │ ├── options.js │ │ └── util.js │ │ ├── entry-compiler.js │ │ ├── entry-runtime-with-compiler.js │ │ ├── entry-runtime.js │ │ ├── gxml │ │ ├── code.js │ │ ├── index.js │ │ ├── style.js │ │ └── template.js │ │ ├── runtime │ │ ├── class-util.js │ │ ├── components │ │ │ ├── index.js │ │ │ ├── transition-group.js │ │ │ └── transition.js │ │ ├── directives │ │ │ ├── index.js │ │ │ ├── model.js │ │ │ └── show.js │ │ ├── index.js │ │ ├── modules │ │ │ ├── attrs.js │ │ │ ├── class.js │ │ │ ├── dom-props.js │ │ │ ├── events.js │ │ │ ├── index.js │ │ │ ├── style.js │ │ │ └── transition.js │ │ ├── node-ops.js │ │ ├── patch.js │ │ └── transition-util.js │ │ ├── util │ │ ├── attrs.js │ │ ├── class.js │ │ ├── compat.js │ │ ├── element.js │ │ ├── index.js │ │ ├── style.js │ │ └── tools.js │ │ └── xml │ │ ├── dom-parser.js │ │ ├── dom.js │ │ └── sax.js └── sfc │ └── parser.js ├── test.xml ├── yarn-error.log └── yarn.lock /.babelrc.js: -------------------------------------------------------------------------------- 1 | const babelPresetFlowVue = { 2 | plugins: [ 3 | require('@babel/plugin-proposal-class-properties'), 4 | // require('@babel/plugin-syntax-flow'), // not needed, included in transform-flow-strip-types 5 | require('@babel/plugin-transform-flow-strip-types') 6 | ] 7 | } 8 | 9 | module.exports = { 10 | presets: [ 11 | require('@babel/preset-env'), 12 | // require('babel-preset-flow-vue') 13 | babelPresetFlowVue 14 | ], 15 | plugins: [ 16 | require('babel-plugin-transform-vue-jsx'), 17 | require('@babel/plugin-syntax-dynamic-import') 18 | ], 19 | ignore: [ 20 | 'dist/*.js', 21 | 'packages/**/*.js' 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules -------------------------------------------------------------------------------- /.idea/gengine.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Gengine 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 介绍 2 | 3 | [![vue](https://img.shields.io/badge/vue-2.6.10-brightgreen.svg)](https://github.com/vuejs/vue) 4 | 5 | 6 | 7 | [Gengine](https://github.com/GengineJS/geg.git) 是一个基于 Vue.js 核心开发的前端框架,与Vue采用Typescript不同,Geg采用的是Es6开发,并修改了 Vue.js 的底层实现,包括了compile与platforms,并新增gxml部分。框架通过xml组织视图层级,所以它与底层视图渲染的方式,如dom的渲染等并没有直接关系,也因为这个特点,虽然Gengine开发之初是为了使用Vue兼容微信小游戏而设计,但是理论上可以使用Geg.js开发任意特定平台的视图项目。 8 | 9 | **我们可以通过以下关系来描述Geg.js与MVVM的关系。** 10 | ``` 11 | VIEW MODEL 12 | ┌----------┐ ┌---------------┐ ┌----------┐ 13 | | | ---------------| XMLNodeListen ├------------------------> | | 14 | | | | | | | 15 | | View | | | | Model | 16 | | | | | | | 17 | | | <------------- | Data Bindings ├<------------------------ | | 18 | └----------┘ └---------------┘ └----------┘ 19 | | 20 | | 21 | | ┌--------------------------┐ 22 | └---->| Geg.js Implementation | 23 | └--------------------------┘ 24 | ``` 25 | 上面的关系图表明View视图的渲染逻辑需要用Geg.js针对特定平台实现 26 |
27 | 28 | ## 安装 29 | 30 | ``` 31 | # 克隆项目 32 | git clone https://github.com/GengineJS/geg.git 33 | 34 | # 进入项目目录 35 | cd geg 36 | 37 | # 安装依赖 38 | yarn 39 | 40 | # 本地开发 启动项目 41 | yarn min 42 | ``` 43 | 44 |
45 | 46 | ## 目录结构 47 | 48 | 本项目已经为你生成了一个完整的开发框架,提供了Geg.js开发过程中的源码结构,下面是整个项目的目录层级。 49 | 50 | ```bash 51 | ├── dist # rollup源码构建输出 52 | ├── src # 源代码 53 | │   ├── compiler # 编译相关 54 | │   ├── core # 源码核心 55 | │   ├── platforms # 平台相关 56 | │   ├── sfc # parser相关 57 | │ └── index.js # 源码入口 58 | ├── .babelrc # babel-loader 配置 59 | ├── config.js # rollup配置 60 | ├── index.html # 测试入口html 61 | ├── package.json # package.json 62 | ├── test.xml # 测试xml模版相关 63 | ├── yarn-error.log # 构建错误log 64 | └── yarn.lock # 构建依赖相关 65 | ``` 66 |
67 | 68 | ## 初始化 69 | Geg.js是以微信小游戏平台为开发初衷,那么我们以微信小游戏平台Geg-Babylonjs为例,可分为三种初始化方式 70 | 71 | **1. 加载xml文件。** 72 | 73 | 由于Gengine不对dom进行直接操作,所以这里的el传递的是xml路径 74 | 75 | ```js 76 | import GegBabylon from './gegbabylon/index.js' 77 | import Geg from './libs/geg.js' 78 | Geg.use(GegBabylon) 79 | let geg = new Geg({ 80 | el: 'src/template.xml' 81 | }) 82 | ``` 83 | 84 | xml格式如下,外层必须通过template进行定义,说明其内部元素为解析模版 85 | 86 | 87 | ```xml 88 | 100 | ``` 101 | 102 | **2. Geg对象内部定义template字符串。** 103 | 104 | ```js 105 | import GegBabylon from './gegbabylon/index.js' 106 | import Geg from './libs/geg.js' 107 | Geg.use(GegBabylon) 108 | let geg = new Geg({ 109 | el: 'src/template.xml', 110 | template: "" 111 | }) 112 | ``` 113 | 114 | **3. 通过render与$mount方式。** 115 | 116 | ```js 117 | import GegBabylon from './gegbabylon/index.js' 118 | import Geg from './libs/geg.js' 119 | Geg.use(GegBabylon) 120 | let geg = new Geg({ 121 | components: { App }, 122 | render: (h) => h('App') 123 | }).$mount() 124 | ``` 125 | 126 |
127 | 128 | ## 其他语法 129 | 想了解更多语法指南,可参考[Vuejs](https://cn.vuejs.org/v2/guide/) 130 | 131 | ## 生态圈 132 | 133 | **除了Vuejs对应插件外,还可以通过以下插件进行Gengine开发** 134 | 135 | 1. [Geg-Babylonjs](https://github.com/GengineJS/geg-babylonjs.git) 通过Gengine实现对Babylonjs 3D引擎的操作。 136 | 137 | 2. [Geg-Threejs](https://github.com/GengineJS/geg-threejs.git) 通过Gengine实现对Threejs 3D引擎的操作。 138 | 139 | -------------------------------------------------------------------------------- /config.js: -------------------------------------------------------------------------------- 1 | const liveServer = require('rollup-plugin-live-server') 2 | const { terser } = require('rollup-plugin-terser') 3 | const alias = require('rollup-plugin-alias') 4 | import babel from 'rollup-plugin-babel' 5 | let builds = { 6 | // Runtime+compiler ES modules build (for bundlers) 7 | 'max': { 8 | // alias: { he: './entity-decoder.js' }, 9 | entry: 'src/platforms/gxml/entry-runtime-with-compiler.js', 10 | dest: 'dist/geg.js', 11 | format: 'es' 12 | }, 13 | 'min': { 14 | // alias: { he: './entity-decoder.js' }, 15 | entry: 'src/platforms/gxml/entry-runtime-with-compiler.js', 16 | dest: 'dist/geg.min.js', 17 | format: 'umd' 18 | } 19 | } 20 | function genConfig (name) { 21 | const opts = builds[name] 22 | const config = { 23 | input: opts.entry, 24 | external: opts.external, 25 | plugins: [ 26 | // flow(), 27 | liveServer({ 28 | port: 8101, 29 | host: "0.0.0.0", 30 | root: "./", 31 | file: "index.html", 32 | mount: [['/dist', './dist'], ['/src', './src'], ['/node_modules', './node_modules']], 33 | open: false, 34 | wait: 500 35 | }), 36 | babel({ 37 | include: 'src/**', 38 | exclude: 'node_modules/**', 39 | }), 40 | alias({ 41 | resolve: ['.js'], 42 | entries:[ 43 | {find:'he', replacement: './entity-decoder'} 44 | ] 45 | }) 46 | ], 47 | output: { 48 | file: opts.dest, 49 | format: opts.format, 50 | name: opts.moduleName || 'Geg' 51 | }, 52 | onwarn: (msg, warn) => { 53 | if (!/Circular/.test(msg)) { 54 | warn(msg) 55 | } 56 | } 57 | } 58 | if (name === 'min') { 59 | config.plugins.push( 60 | terser({ 61 | toplevel: true, 62 | output: { 63 | ascii_only: true 64 | }, 65 | compress: { 66 | pure_funcs: ['makeMap'] 67 | } 68 | }) 69 | ) 70 | } 71 | return config 72 | } 73 | module.exports = genConfig(process.env.TARGET) -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Gengine 6 | 7 | 8 | 9 | 20 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gengine", 3 | "version": "1.0.0", 4 | "description": "web game or wechat game generate engine", 5 | "main": "src/index.js", 6 | "repository": "https://github.com/GengineJS/geg.git", 7 | "author": "架构组-webgl-郑祥奎 ", 8 | "license": "MIT", 9 | "scripts": { 10 | "max": "rollup -w -c config.js --environment TARGET:max", 11 | "min": "rollup -w -c config.js --environment TARGET:min" 12 | }, 13 | "dependencies": { 14 | "@babel/core": "^7.5.5", 15 | "@babel/plugin-proposal-class-properties": "^7.5.5", 16 | "@babel/plugin-transform-flow-strip-types": "^7.4.4", 17 | "@babel/preset-env": "^7.5.5", 18 | "babel-plugin-syntax-jsx": "^6.18.0", 19 | "babel-plugin-transform-vue-jsx": "^3.7.0", 20 | "de-indent": "^1.0.2", 21 | "he": "^1.2.0", 22 | "rollup-plugin-alias": "^2.0.0", 23 | "rollup-plugin-babel": "^4.3.3", 24 | "rollup-plugin-live-server": "^1.0.3", 25 | "terser": "^4.2.0", 26 | "uglify-es": "^3.3.9", 27 | "xmldom": "^0.6.0" 28 | }, 29 | "devDependencies": { 30 | "jest-worker": "^24.9.0", 31 | "rollup-plugin-terser": "^5.1.1", 32 | "serialize-javascript": "^1.8.0" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/compiler/codeframe.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | const range = 2 4 | 5 | export function generateCodeFrame ( 6 | source, 7 | start = 0, 8 | end = source.length 9 | ) { 10 | const lines = source.split(/\r?\n/) 11 | let count = 0 12 | const res = [] 13 | for (let i = 0; i < lines.length; i++) { 14 | count += lines[i].length + 1 15 | if (count >= start) { 16 | for (let j = i - range; j <= i + range || end > count; j++) { 17 | if (j < 0 || j >= lines.length) continue 18 | res.push(`${j + 1}${repeat(` `, 3 - String(j + 1).length)}| ${lines[j]}`) 19 | const lineLength = lines[j].length 20 | if (j === i) { 21 | // push underline 22 | const pad = start - (count - lineLength) + 1 23 | const length = end > count ? lineLength - pad : end - start 24 | res.push(` | ` + repeat(` `, pad) + repeat(`^`, length)) 25 | } else if (j > i) { 26 | if (end > count) { 27 | const length = Math.min(end - count, lineLength) 28 | res.push(` | ` + repeat(`^`, length)) 29 | } 30 | count += lineLength + 1 31 | } 32 | } 33 | break 34 | } 35 | } 36 | return res.join('\n') 37 | } 38 | 39 | function repeat (str, n) { 40 | let result = '' 41 | if (n > 0) { 42 | while (true) { // eslint-disable-line 43 | if (n & 1) result += str 44 | n >>>= 1 45 | if (n <= 0) break 46 | str += str 47 | } 48 | } 49 | return result 50 | } 51 | -------------------------------------------------------------------------------- /src/compiler/create-compiler.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { extend } from '../core/utils/tools.js' 4 | import { createCompileToFunctionFn } from './to-function.js' 5 | 6 | export function createCompilerCreator (baseCompile) { 7 | return function createCompiler (baseOptions) { 8 | function compile ( 9 | template, 10 | options 11 | ) { 12 | const finalOptions = Object.create(baseOptions) 13 | const errors = [] 14 | const tips = [] 15 | 16 | let warn = (msg, range, tip) => { 17 | (tip ? tips : errors).push(msg) 18 | } 19 | 20 | if (options) { 21 | // merge custom modules 22 | if (options.modules) { 23 | finalOptions.modules = 24 | (baseOptions.modules || []).concat(options.modules) 25 | } 26 | // merge custom directives 27 | if (options.directives) { 28 | finalOptions.directives = extend( 29 | Object.create(baseOptions.directives || null), 30 | options.directives 31 | ) 32 | } 33 | // copy other options 34 | for (const key in options) { 35 | if (key !== 'modules' && key !== 'directives') { 36 | finalOptions[key] = options[key] 37 | } 38 | } 39 | } 40 | 41 | finalOptions.warn = warn 42 | 43 | const compiled = baseCompile(template.trim(), finalOptions) 44 | compiled.errors = errors 45 | compiled.tips = tips 46 | return compiled 47 | } 48 | 49 | return { 50 | compile, 51 | compileToFunctions: createCompileToFunctionFn(compile) 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/compiler/directives/bind.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import { convertAttrVal } from '../util/index.js' 3 | export default function bind (el, dir) { 4 | el.wrapData = (code) => { 5 | let codeRes = function() { 6 | let resArr = [] 7 | resArr.push(code) 8 | resArr.push(`${el.tag}`) 9 | resArr.push(convertAttrVal(this, dir.value)) 10 | if (dir.modifiers && dir.modifiers.prop) { 11 | resArr.push(true) 12 | } else { 13 | resArr.push(false) 14 | } 15 | if (dir.modifiers && dir.modifiers.sync) { 16 | resArr.push(true) 17 | } 18 | return this._d(...resArr) 19 | } 20 | return codeRes.call(this) 21 | // return `_b(${code},'${el.tag}',${dir.value},${ 22 | // dir.modifiers && dir.modifiers.prop ? 'true' : 'false' 23 | // }${ 24 | // dir.modifiers && dir.modifiers.sync ? ',true' : '' 25 | // })` 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/compiler/directives/index.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import on from './on.js' 4 | import bind from './bind.js' 5 | import { noop } from '../../core/utils/tools.js' 6 | 7 | export default { 8 | on, 9 | bind, 10 | cloak: noop 11 | } 12 | -------------------------------------------------------------------------------- /src/compiler/directives/model.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | /** 4 | * Cross-platform code generation for component v-model 5 | */ 6 | export function genComponentModel ( 7 | el, 8 | value, 9 | modifiers 10 | ) { 11 | const { number, trim } = modifiers || {} 12 | 13 | const baseValueExpression = '$$v' 14 | let valueExpression = baseValueExpression 15 | if (trim) { 16 | valueExpression = 17 | `(typeof ${baseValueExpression} === 'string'` + 18 | `? ${baseValueExpression}.trim()` + 19 | `: ${baseValueExpression})` 20 | } 21 | if (number) { 22 | valueExpression = `_n(${valueExpression})` 23 | } 24 | const assignment = genAssignmentCode(value, valueExpression) 25 | 26 | el.model = { 27 | value: `(${value})`, 28 | expression: JSON.stringify(value), 29 | callback: `function (${baseValueExpression}) {${assignment}}` 30 | } 31 | } 32 | 33 | /** 34 | * Cross-platform codegen helper for generating v-model value assignment code. 35 | */ 36 | export function genAssignmentCode ( 37 | value, 38 | assignment 39 | ) { 40 | const res = parseModel(value) 41 | if (res.key === null) { 42 | return `${value}=${assignment}` 43 | } else { 44 | return `$set(${res.exp}, ${res.key}, ${assignment})` 45 | } 46 | } 47 | 48 | /** 49 | * Parse a v-model expression into a base path and a final key segment. 50 | * Handles both dot-path and possible square brackets. 51 | * 52 | * Possible cases: 53 | * 54 | * - test 55 | * - test[key] 56 | * - test[test1[key]] 57 | * - test["a"][key] 58 | * - xxx.test[a[a].test1[key]] 59 | * - test.xxx.a["asa"][test1[key]] 60 | * 61 | */ 62 | 63 | let len, str, chr, index, expressionPos, expressionEndPos 64 | 65 | export function parseModel (val) { 66 | // Fix https://github.com/vuejs/vue/pull/7730 67 | // allow v-model="obj.val " (trailing whitespace) 68 | val = val.trim() 69 | len = val.length 70 | 71 | if (val.indexOf('[') < 0 || val.lastIndexOf(']') < len - 1) { 72 | index = val.lastIndexOf('.') 73 | if (index > -1) { 74 | return { 75 | exp: val.slice(0, index), 76 | key: '"' + val.slice(index + 1) + '"' 77 | } 78 | } else { 79 | return { 80 | exp: val, 81 | key: null 82 | } 83 | } 84 | } 85 | 86 | str = val 87 | index = expressionPos = expressionEndPos = 0 88 | 89 | while (!eof()) { 90 | chr = next() 91 | /* istanbul ignore if */ 92 | if (isStringStart(chr)) { 93 | parseString(chr) 94 | } else if (chr === 0x5B) { 95 | parseBracket(chr) 96 | } 97 | } 98 | 99 | return { 100 | exp: val.slice(0, expressionPos), 101 | key: val.slice(expressionPos + 1, expressionEndPos) 102 | } 103 | } 104 | 105 | function next () { 106 | return str.charCodeAt(++index) 107 | } 108 | 109 | function eof () { 110 | return index >= len 111 | } 112 | 113 | function isStringStart (chr) { 114 | return chr === 0x22 || chr === 0x27 115 | } 116 | 117 | function parseBracket (chr) { 118 | let inBracket = 1 119 | expressionPos = index 120 | while (!eof()) { 121 | chr = next() 122 | if (isStringStart(chr)) { 123 | parseString(chr) 124 | continue 125 | } 126 | if (chr === 0x5B) inBracket++ 127 | if (chr === 0x5D) inBracket-- 128 | if (inBracket === 0) { 129 | expressionEndPos = index 130 | break 131 | } 132 | } 133 | } 134 | 135 | function parseString (chr) { 136 | const stringQuote = chr 137 | while (!eof()) { 138 | chr = next() 139 | if (chr === stringQuote) { 140 | break 141 | } 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/compiler/directives/on.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import { convertAttrVal } from '../util/index.js' 3 | export default function on (el, dir) { 4 | el.wrapListeners = (code) => { 5 | let codeRes = function() { 6 | return this._g(code, convertAttrVal(this, dir.value)) 7 | } 8 | return codeRes.call(this) 9 | // `_g(${code},${dir.value})` 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/compiler/error-detector.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { dirRE, onRE } from './parser/index.js' 4 | 5 | // these keywords should not appear inside expressions, but operators like 6 | // typeof, instanceof and in are allowed 7 | const prohibitedKeywordRE = new RegExp('\\b' + ( 8 | 'do,if,for,let,new,try,var,case,else,with,await,break,catch,class,const,' + 9 | 'super,throw,while,yield,delete,export,import,return,switch,default,' + 10 | 'extends,finally,continue,debugger,function,arguments' 11 | ).split(',').join('\\b|\\b') + '\\b') 12 | 13 | // these unary operators should not be used as property/method names 14 | const unaryOperatorsRE = new RegExp('\\b' + ( 15 | 'delete,typeof,void' 16 | ).split(',').join('\\s*\\([^\\)]*\\)|\\b') + '\\s*\\([^\\)]*\\)') 17 | 18 | // strip strings in expressions 19 | const stripStringRE = /'(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*"|`(?:[^`\\]|\\.)*\$\{|\}(?:[^`\\]|\\.)*`|`(?:[^`\\]|\\.)*`/g 20 | 21 | // detect problematic expressions in a template 22 | export function detectErrors (ast, warn) { 23 | if (ast) { 24 | checkNode(ast, warn) 25 | } 26 | } 27 | 28 | function checkNode (node, warn) { 29 | if (node.type === 1) { 30 | for (const name in node.attrsMap) { 31 | if (dirRE.test(name)) { 32 | const value = node.attrsMap[name] 33 | if (value) { 34 | const range = node.rawAttrsMap[name] 35 | if (name === 'v-for') { 36 | checkFor(node, `v-for="${value}"`, warn, range) 37 | } else if (name === 'v-slot' || name[0] === '#') { 38 | checkFunctionParameterExpression(value, `${name}="${value}"`, warn, range) 39 | } else if (onRE.test(name)) { 40 | checkEvent(value, `${name}="${value}"`, warn, range) 41 | } else { 42 | checkExpression(value, `${name}="${value}"`, warn, range) 43 | } 44 | } 45 | } 46 | } 47 | if (node.children) { 48 | for (let i = 0; i < node.children.length; i++) { 49 | checkNode(node.children[i], warn) 50 | } 51 | } 52 | } else if (node.type === 2) { 53 | checkExpression(node.expression, node.text, warn, node) 54 | } 55 | } 56 | 57 | function checkEvent (exp, text, warn, range) { 58 | const stipped = exp.replace(stripStringRE, '') 59 | const keywordMatch = stipped.match(unaryOperatorsRE) 60 | if (keywordMatch && stipped.charAt(keywordMatch.index - 1) !== '$') { 61 | warn( 62 | `avoid using JavaScript unary operator as property name: ` + 63 | `"${keywordMatch[0]}" in expression ${text.trim()}`, 64 | range 65 | ) 66 | } 67 | checkExpression(exp, text, warn, range) 68 | } 69 | 70 | function checkFor (node, text, warn, range) { 71 | checkExpression(node.for || '', text, warn, range) 72 | checkIdentifier(node.alias, 'v-for alias', text, warn, range) 73 | checkIdentifier(node.iterator1, 'v-for iterator', text, warn, range) 74 | checkIdentifier(node.iterator2, 'v-for iterator', text, warn, range) 75 | } 76 | 77 | function checkIdentifier ( 78 | ident, 79 | type, 80 | text, 81 | warn, 82 | range 83 | ) { 84 | if (typeof ident === 'string') { 85 | try { 86 | new Function(`var ${ident}=_`) 87 | } catch (e) { 88 | warn(`invalid ${type} "${ident}" in expression: ${text.trim()}`, range) 89 | } 90 | } 91 | } 92 | 93 | function checkExpression (exp, text, warn, range) { 94 | try { 95 | new Function(`return ${exp}`) 96 | } catch (e) { 97 | const keywordMatch = exp.replace(stripStringRE, '').match(prohibitedKeywordRE) 98 | if (keywordMatch) { 99 | warn( 100 | `avoid using JavaScript keyword as property name: ` + 101 | `"${keywordMatch[0]}"\n Raw expression: ${text.trim()}`, 102 | range 103 | ) 104 | } else { 105 | warn( 106 | `invalid expression: ${e.message} in\n\n` + 107 | ` ${exp}\n\n` + 108 | ` Raw expression: ${text.trim()}\n`, 109 | range 110 | ) 111 | } 112 | } 113 | } 114 | 115 | function checkFunctionParameterExpression (exp, text, warn, range) { 116 | try { 117 | new Function(exp, '') 118 | } catch (e) { 119 | warn( 120 | `invalid function parameter expression: ${e.message} in\n\n` + 121 | ` ${exp}\n\n` + 122 | ` Raw expression: ${text.trim()}\n`, 123 | range 124 | ) 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/compiler/index.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { parse } from './parser/index.js' 4 | import { optimize } from './optimizer.js' 5 | import { generate } from './codegen/index.js' 6 | import { createCompilerCreator } from './create-compiler.js' 7 | 8 | // `createCompilerCreator` allows creating compilers that use alternative 9 | // parser/optimizer/codegen, e.g the SSR optimizing compiler. 10 | // Here we just export a default compiler using the default parts. 11 | export const createCompiler = createCompilerCreator(function baseCompile ( 12 | template, 13 | options 14 | ) { 15 | const ast = parse(template.trim(), options) 16 | if (options.optimize !== false) { 17 | optimize(ast, options) 18 | } 19 | const code = generate(ast, options) 20 | return { 21 | ast, 22 | render: code.render, 23 | staticRenderFns: code.staticRenderFns 24 | } 25 | }) 26 | -------------------------------------------------------------------------------- /src/compiler/optimizer.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { makeMap, isBuiltInTag, cached, no } from '../core/utils/tools.js' 4 | 5 | let isStaticKey 6 | let isPlatformReservedTag 7 | 8 | const genStaticKeysCached = cached(genStaticKeys) 9 | 10 | /** 11 | * Goal of the optimizer: walk the generated template AST tree 12 | * and detect sub-trees that are purely static, i.e. parts of 13 | * the DOM that never needs to change. 14 | * 15 | * Once we detect these sub-trees, we can: 16 | * 17 | * 1. Hoist them into constants, so that we no longer need to 18 | * create fresh nodes for them on each re-render; 19 | * 2. Completely skip them in the patching process. 20 | */ 21 | export function optimize (root, options) { 22 | if (!root) return 23 | isStaticKey = genStaticKeysCached(options.staticKeys || '') 24 | isPlatformReservedTag = options.isReservedTag || no 25 | // first pass: mark all non-static nodes. 26 | markStatic(root) 27 | // second pass: mark static roots. 28 | markStaticRoots(root, false) 29 | } 30 | 31 | function genStaticKeys (keys) { 32 | return makeMap( 33 | 'type,tag,attrsList,attrsMap,plain,parent,children,attrs,start,end,rawAttrsMap' + 34 | (keys ? ',' + keys : '') 35 | ) 36 | } 37 | 38 | function markStatic (node) { 39 | node.static = isStatic(node) 40 | if (node.type === 1) { 41 | // do not make component slot content static. this avoids 42 | // 1. components not able to mutate slot nodes 43 | // 2. static slot content fails for hot-reloading 44 | if ( 45 | !isPlatformReservedTag(node.tag) && 46 | node.tag !== 'slot' && 47 | node.attrsMap['inline-template'] == null 48 | ) { 49 | return 50 | } 51 | for (let i = 0, l = node.children.length; i < l; i++) { 52 | const child = node.children[i] 53 | markStatic(child) 54 | if (!child.static) { 55 | node.static = false 56 | } 57 | } 58 | if (node.ifConditions) { 59 | for (let i = 1, l = node.ifConditions.length; i < l; i++) { 60 | const block = node.ifConditions[i].block 61 | markStatic(block) 62 | if (!block.static) { 63 | node.static = false 64 | } 65 | } 66 | } 67 | } 68 | } 69 | 70 | function markStaticRoots (node, isInFor) { 71 | if (node.type === 1) { 72 | if (node.static || node.once) { 73 | node.staticInFor = isInFor 74 | } 75 | // For a node to qualify as a static root, it should have children that 76 | // are not just static text. Otherwise the cost of hoisting out will 77 | // outweigh the benefits and it's better off to just always render it fresh. 78 | if (node.static && node.children.length && !( 79 | node.children.length === 1 && 80 | node.children[0].type === 3 81 | )) { 82 | node.staticRoot = true 83 | return 84 | } else { 85 | node.staticRoot = false 86 | } 87 | if (node.children) { 88 | for (let i = 0, l = node.children.length; i < l; i++) { 89 | markStaticRoots(node.children[i], isInFor || !!node.for) 90 | } 91 | } 92 | if (node.ifConditions) { 93 | for (let i = 1, l = node.ifConditions.length; i < l; i++) { 94 | markStaticRoots(node.ifConditions[i].block, isInFor) 95 | } 96 | } 97 | } 98 | } 99 | 100 | function isStatic (node) { 101 | if (node.type === 2) { // expression 102 | return false 103 | } 104 | if (node.type === 3) { // text 105 | return true 106 | } 107 | return !!(node.pre || ( 108 | !node.hasBindings && // no dynamic bindings 109 | !node.if && !node.for && // not v-if or v-for or v-else 110 | !isBuiltInTag(node.tag) && // not a built-in 111 | isPlatformReservedTag(node.tag) && // not a component 112 | !isDirectChildOfTemplateFor(node) && 113 | Object.keys(node).every(isStaticKey) 114 | )) 115 | } 116 | 117 | function isDirectChildOfTemplateFor (node) { 118 | while (node.parent) { 119 | node = node.parent 120 | if (node.tag !== 'template') { 121 | return false 122 | } 123 | if (node.for) { 124 | return true 125 | } 126 | } 127 | return false 128 | } 129 | -------------------------------------------------------------------------------- /src/compiler/parser/entity-decoder.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import { document } from '../../platforms/gxml/xml/dom.js'// '../xml/dom.js' 3 | let decoder 4 | export default { 5 | decode (html) { 6 | decoder = decoder || document.createElement('div') 7 | decoder.innerHTML = html 8 | return decoder.textContent 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/compiler/parser/filter-parser.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | const validDivisionCharRE = /[\w).+\-_$\]]/ 4 | 5 | export function parseFilters (exp) { 6 | let inSingle = false 7 | let inDouble = false 8 | let inTemplateString = false 9 | let inRegex = false 10 | let curly = 0 11 | let square = 0 12 | let paren = 0 13 | let lastFilterIndex = 0 14 | let c, prev, i, expression, filters 15 | 16 | for (i = 0; i < exp.length; i++) { 17 | prev = c 18 | c = exp.charCodeAt(i) 19 | if (inSingle) { 20 | if (c === 0x27 && prev !== 0x5C) inSingle = false 21 | } else if (inDouble) { 22 | if (c === 0x22 && prev !== 0x5C) inDouble = false 23 | } else if (inTemplateString) { 24 | if (c === 0x60 && prev !== 0x5C) inTemplateString = false 25 | } else if (inRegex) { 26 | if (c === 0x2f && prev !== 0x5C) inRegex = false 27 | } else if ( 28 | c === 0x7C && // pipe 29 | exp.charCodeAt(i + 1) !== 0x7C && 30 | exp.charCodeAt(i - 1) !== 0x7C && 31 | !curly && !square && !paren 32 | ) { 33 | if (expression === undefined) { 34 | // first filter, end of expression 35 | lastFilterIndex = i + 1 36 | expression = exp.slice(0, i).trim() 37 | } else { 38 | pushFilter() 39 | } 40 | } else { 41 | switch (c) { 42 | case 0x22: inDouble = true; break // " 43 | case 0x27: inSingle = true; break // ' 44 | case 0x60: inTemplateString = true; break // ` 45 | case 0x28: paren++; break // ( 46 | case 0x29: paren--; break // ) 47 | case 0x5B: square++; break // [ 48 | case 0x5D: square--; break // ] 49 | case 0x7B: curly++; break // { 50 | case 0x7D: curly--; break // } 51 | } 52 | if (c === 0x2f) { // / 53 | let j = i - 1 54 | let p 55 | // find first non-whitespace prev char 56 | for (; j >= 0; j--) { 57 | p = exp.charAt(j) 58 | if (p !== ' ') break 59 | } 60 | if (!p || !validDivisionCharRE.test(p)) { 61 | inRegex = true 62 | } 63 | } 64 | } 65 | } 66 | 67 | if (expression === undefined) { 68 | expression = exp.slice(0, i).trim() 69 | } else if (lastFilterIndex !== 0) { 70 | pushFilter() 71 | } 72 | 73 | function pushFilter () { 74 | (filters || (filters = [])).push(exp.slice(lastFilterIndex, i).trim()) 75 | lastFilterIndex = i + 1 76 | } 77 | 78 | if (filters) { 79 | for (i = 0; i < filters.length; i++) { 80 | expression = wrapFilter(expression, filters[i]) 81 | } 82 | } 83 | 84 | return expression 85 | } 86 | 87 | function wrapFilter (exp, filter) { 88 | const i = filter.indexOf('(') 89 | if (i < 0) { 90 | // _f: resolveFilter 91 | return `_f("${filter}")(${exp})` 92 | } else { 93 | const name = filter.slice(0, i) 94 | const args = filter.slice(i + 1) 95 | return `_f("${name}")(${exp}${args !== ')' ? ',' + args : args}` 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/compiler/parser/text-parser.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { cached } from '../../core/utils/tools.js' 4 | import { parseFilters } from './filter-parser.js' 5 | 6 | const defaultTagRE = /\{\{((?:.|\r?\n)+?)\}\}/g 7 | const regexEscapeRE = /[-.*+?^${}()|[\]\/\\]/g 8 | 9 | const buildRegex = cached(delimiters => { 10 | const open = delimiters[0].replace(regexEscapeRE, '\\$&') 11 | const close = delimiters[1].replace(regexEscapeRE, '\\$&') 12 | return new RegExp(open + '((?:.|\\n)+?)' + close, 'g') 13 | }) 14 | 15 | export function parseText ( 16 | text, 17 | delimiters 18 | ) { 19 | const tagRE = delimiters ? buildRegex(delimiters) : defaultTagRE 20 | if (!tagRE.test(text)) { 21 | return 22 | } 23 | const tokens = [] 24 | const rawTokens = [] 25 | let lastIndex = tagRE.lastIndex = 0 26 | let match, index, tokenValue 27 | while ((match = tagRE.exec(text))) { 28 | index = match.index 29 | // push text token 30 | if (index > lastIndex) { 31 | rawTokens.push(tokenValue = text.slice(lastIndex, index)) 32 | tokens.push(JSON.stringify(tokenValue)) 33 | } 34 | // tag token 35 | const exp = parseFilters(match[1].trim()) 36 | tokens.push(`_s(${exp})`) 37 | rawTokens.push({ '@binding': exp }) 38 | lastIndex = index + match[0].length 39 | } 40 | if (lastIndex < text.length) { 41 | rawTokens.push(tokenValue = text.slice(lastIndex)) 42 | tokens.push(JSON.stringify(tokenValue)) 43 | } 44 | return { 45 | expression: tokens.join('+'), 46 | tokens: rawTokens 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/compiler/to-function.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { noop, extend } from '../core/utils/tools.js' 4 | function createFunction (code, errors) { 5 | try { 6 | return new Function(code) 7 | } catch (err) { 8 | errors.push({ err, code }) 9 | return noop 10 | } 11 | } 12 | 13 | export function createCompileToFunctionFn (compile) { 14 | const cache = Object.create(null) 15 | 16 | return function compileToFunctions ( 17 | template, 18 | options, 19 | gm 20 | ) { 21 | options = extend({}, options) 22 | const warn = options.warn || console.warn 23 | delete options.warn 24 | 25 | // check cache 26 | const key = options.delimiters 27 | ? String(options.delimiters) + template 28 | : template 29 | if (cache[key]) { 30 | return cache[key] 31 | } 32 | 33 | // compile 34 | const compiled = compile(template, options) 35 | 36 | // turn code into functions 37 | const res = {} 38 | const fnGenErrors = [] 39 | res.render =typeof compiled.render === 'function' ? compiled.render : createFunction(compiled.render, fnGenErrors) 40 | res.staticRenderFns = compiled.staticRenderFns.map(code => { 41 | return createFunction(code, fnGenErrors) 42 | }) 43 | 44 | return (cache[key] = res) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/compiler/util/index.js: -------------------------------------------------------------------------------- 1 | function calKeyword (gm, keyword, isconvToStr = false) { 2 | let keywordTrim = keyword.trim() 3 | let objSymbol = /^{([\s|\S]*)}$/ 4 | let arrSymbol = /^\[([\s|\S]*)\]$/ 5 | if (keywordTrim === '') { 6 | return keyword 7 | } else { 8 | let result = null 9 | if (keywordTrim === 'false') { 10 | result = false 11 | } else if (keywordTrim === 'true') { 12 | result = true 13 | } else if (objSymbol.test(keywordTrim)) { 14 | result = JSON.parse(objSymConvert(gm, keywordTrim.match(objSymbol)[1])) 15 | } else if (arrSymbol.test(keywordTrim)) { 16 | result = JSON.parse(keywordTrim) 17 | } else { 18 | let keywords = keywordTrim.split(/[\[\].]/g).filter((x) => x) 19 | keywords.forEach((ival, index) => { 20 | ival = ival.trim() 21 | let num = Number(ival) 22 | !isNaN(num) && (ival = num) 23 | if (index === 0) { 24 | result = window[ival] || gm[ival] 25 | } else { 26 | result = result[ival] 27 | } 28 | }) 29 | result = isconvToStr ? `${result}` : result 30 | } 31 | return result 32 | } 33 | } 34 | 35 | export let convertAttrVal = (gm, val, isconvToStr = false) => { 36 | let singleQuotes = /'([\s|\S]*)'/ 37 | let doubleQuotes = /"([\s|\S]*)"/ 38 | if (doubleQuotes.test(val)) { 39 | return val.match(doubleQuotes)[1] 40 | } else if (singleQuotes.test(val)) { 41 | return val.match(singleQuotes)[1] 42 | } else { 43 | let num = Number(val) 44 | if (!isNaN(num)) { 45 | return num 46 | } else { 47 | // isconvToStr ? `"${gm[val]}"` : gm[val] 48 | return calKeyword(gm, val, isconvToStr) 49 | } 50 | } 51 | } 52 | // if argVals length === 0 ? search strFunc args : apply argVals value to function 53 | export let execStrFunc = (gm, strFunc, argVals = []) => { 54 | // _s(someStr) 55 | let parentheses = /\(([\s|\S]*)\)/ 56 | if (argVals.length === 0 && parentheses.test(strFunc)) { 57 | let args = strFunc.match(parentheses)[1].split(',') 58 | args.forEach((val) => { 59 | argVals.push(convertAttrVal(gm, val)) 60 | }) 61 | } 62 | return gm[`${strFunc.substring(0, strFunc.indexOf('(')).trim()}`](...argVals) 63 | } 64 | export let objSymConvert = (gm, symbolKv) => { 65 | let startSymbol = '{' 66 | let attrKvs = symbolKv.split(',') 67 | attrKvs.forEach((ele, index) => { 68 | let currAttrKV = ele.split(':') 69 | startSymbol += `"${currAttrKV[0].replace(/'|"/g, '')}"` 70 | startSymbol += `:${convertAttrVal(gm, currAttrKV[1], true)}` 71 | if (index < attrKvs.length - 1) { 72 | startSymbol += ',' 73 | } 74 | }) 75 | startSymbol += '}' 76 | return startSymbol 77 | } 78 | export let attrConvert = (gm, attr) => { 79 | // {"attrs":{"align":'center',"text":someStr}} 80 | let patternAttr = /{([\s|\S]*)}/ 81 | if (patternAttr.test(attr)) { 82 | let attrsKV = attr.match(patternAttr)[1] 83 | let attrsV = attrsKV.match(patternAttr)[1] 84 | return `{"attrs":${objSymConvert(gm, attrsV)}}` 85 | } 86 | } -------------------------------------------------------------------------------- /src/core/base/active.js: -------------------------------------------------------------------------------- 1 | import { pushTarget, popTarget } from '../observer/dep.js' 2 | import { invokeWithErrorHandling } from '../utils/error.js' 3 | function isInInactiveTree (gm) { 4 | while (gm && (gm = gm.$parent)) { 5 | if (gm._inactive) return true 6 | } 7 | return false 8 | } 9 | 10 | export function activateChildComponent (gm, direct) { 11 | if (direct) { 12 | gm._directInactive = false 13 | if (isInInactiveTree(gm)) { 14 | return 15 | } 16 | } else if (gm._directInactive) { 17 | return 18 | } 19 | if (gm._inactive || gm._inactive === null) { 20 | gm._inactive = false 21 | for (let i = 0; i < gm.$children.length; i++) { 22 | activateChildComponent(gm.$children[i]) 23 | } 24 | callHook(gm, 'activated') 25 | } 26 | } 27 | 28 | export function deactivateChildComponent (gm, direct) { 29 | if (direct) { 30 | gm._directInactive = true 31 | if (isInInactiveTree(gm)) { 32 | return 33 | } 34 | } 35 | if (!gm._inactive) { 36 | gm._inactive = true 37 | for (let i = 0; i < gm.$children.length; i++) { 38 | deactivateChildComponent(gm.$children[i]) 39 | } 40 | callHook(gm, 'deactivated') 41 | } 42 | } 43 | 44 | export function callHook (gm, hook) { 45 | // #7573 disable dep collection when invoking lifecycle hooks 46 | pushTarget() 47 | const handlers = gm.$options[hook] 48 | const info = `${hook} hook` 49 | if (handlers) { 50 | for (let i = 0, j = handlers.length; i < j; i++) { 51 | invokeWithErrorHandling(handlers[i], gm, null, gm, info) 52 | } 53 | } 54 | if (gm._hasHookEvent) { 55 | gm.$emit('hook:' + hook) 56 | } 57 | popTarget() 58 | } 59 | -------------------------------------------------------------------------------- /src/core/base/events.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { 4 | toArray 5 | } from '../utils/tools.js' 6 | import { invokeWithErrorHandling } from '../utils/error.js' 7 | import { updateListeners } from '../gdom/helpers/update-listeners.js' 8 | 9 | export function initEvents (gm) { 10 | gm._events = Object.create(null) 11 | gm._hasHookEvent = false 12 | // init parent attached events 13 | const listeners = gm.$options._parentListeners 14 | if (listeners) { 15 | updateComponentListeners(gm, listeners) 16 | } 17 | } 18 | 19 | let target 20 | function add (event, fn) { 21 | target.$on(event, fn) 22 | } 23 | 24 | function remove (event, fn) { 25 | target.$off(event, fn) 26 | } 27 | 28 | function createOnceHandler (event, fn) { 29 | const _target = target 30 | return function onceHandler () { 31 | const res = fn.apply(null, arguments) 32 | if (res !== null) { 33 | _target.$off(event, onceHandler) 34 | } 35 | } 36 | } 37 | 38 | export function updateComponentListeners ( 39 | gm, 40 | listeners, 41 | oldListeners 42 | ) { 43 | target = gm 44 | updateListeners(listeners, oldListeners || {}, add, remove, createOnceHandler, gm) 45 | target = undefined 46 | } 47 | 48 | export function eventsMixin (Gengine) { 49 | const hookRE = /^hook:/ 50 | Gengine.prototype.$on = function (event, fn) { 51 | const gm = this 52 | if (Array.isArray(event)) { 53 | for (let i = 0, l = event.length; i < l; i++) { 54 | gm.$on(event[i], fn) 55 | } 56 | } else { 57 | (gm._events[event] || (gm._events[event] = [])).push(fn) 58 | // optimize hook:event cost by using a boolean flag marked at registration 59 | // instead of a hash lookup 60 | if (hookRE.test(event)) { 61 | gm._hasHookEvent = true 62 | } 63 | } 64 | return gm 65 | } 66 | 67 | Gengine.prototype.$once = function (event, fn) { 68 | const gm = this 69 | function on () { 70 | gm.$off(event, on) 71 | fn.apply(gm, arguments) 72 | } 73 | on.fn = fn 74 | gm.$on(event, on) 75 | return gm 76 | } 77 | 78 | Gengine.prototype.$off = function (event, fn) { 79 | const gm = this 80 | // all 81 | if (!arguments.length) { 82 | gm._events = Object.create(null) 83 | return gm 84 | } 85 | // array of events 86 | if (Array.isArray(event)) { 87 | for (let i = 0, l = event.length; i < l; i++) { 88 | gm.$off(event[i], fn) 89 | } 90 | return gm 91 | } 92 | // specific event 93 | const cbs = gm._events[event] 94 | if (!cbs) { 95 | return gm 96 | } 97 | if (!fn) { 98 | gm._events[event] = null 99 | return gm 100 | } 101 | // specific handler 102 | let cb 103 | let i = cbs.length 104 | while (i--) { 105 | cb = cbs[i] 106 | if (cb === fn || cb.fn === fn) { 107 | cbs.splice(i, 1) 108 | break 109 | } 110 | } 111 | return gm 112 | } 113 | 114 | Gengine.prototype.$emit = function (event) { 115 | const gm = this 116 | let cbs = gm._events[event] 117 | if (cbs) { 118 | cbs = cbs.length > 1 ? toArray(cbs) : cbs 119 | const args = toArray(arguments, 1) 120 | const info = `event handler for "${event}"` 121 | for (let i = 0, l = cbs.length; i < l; i++) { 122 | invokeWithErrorHandling(cbs[i], gm, args, gm, info) 123 | } 124 | } 125 | return gm 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/core/base/index.js: -------------------------------------------------------------------------------- 1 | import Compile from '../compile/html/index.js' 2 | import { observe } from '../observer/index.js' 3 | import Watcher from '../watcher/index.js' 4 | import configurable from '../config/index.js' 5 | import { initMixin } from './init.js' 6 | import { renderMixin } from './render.js' 7 | import { stateMixin } from './state.js' 8 | import { eventsMixin } from './events.js' 9 | import { lifecycleMixin } from './lifecycle.js' 10 | export class BaseInterface { 11 | constructor (options) { 12 | this._init(options) 13 | } 14 | _init(options) { 15 | this.$options = options || {} 16 | let data = this._data = this.$options.data 17 | let me = this 18 | // 数据代理 19 | // 实现 vm.xxx -> vm._data.xxx 20 | Object.keys(data).forEach(function(key) { 21 | me._proxyData(key) 22 | }) 23 | 24 | this._initComputed() 25 | 26 | observe(data, this) 27 | 28 | this.$compile = new Compile(options.el || document.body, this) 29 | } 30 | $watch(key, cb, options) { 31 | new Watcher(this, key, cb) 32 | } 33 | 34 | _proxyData(key, setter, getter) { 35 | let me = this 36 | setter = setter || 37 | Object.defineProperty(me, key, { 38 | configurable: false, 39 | enumerable: true, 40 | get: function proxyGetter() { 41 | return me._data[key] 42 | }, 43 | set: function proxySetter(newVal) { 44 | me._data[key] = newVal 45 | } 46 | }) 47 | } 48 | 49 | _initComputed() { 50 | let me = this 51 | let computed = this.$options.computed 52 | if (typeof computed === 'object') { 53 | Object.keys(computed).forEach(function(key) { 54 | Object.defineProperty(me, key, { 55 | get: typeof computed[key] === 'function' 56 | ? computed[key] 57 | : computed[key].get, 58 | set: function() {} 59 | }) 60 | }) 61 | } 62 | } 63 | } 64 | export default class Gengine extends BaseInterface { 65 | constructor(options) { 66 | super(options) 67 | } 68 | } 69 | configurable(Gengine) 70 | initMixin(Gengine) 71 | stateMixin(Gengine) 72 | eventsMixin(Gengine) 73 | lifecycleMixin(Gengine) 74 | renderMixin(Gengine) 75 | -------------------------------------------------------------------------------- /src/core/base/init.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import { initState } from './state.js' 3 | import { initRender } from './render.js' 4 | import { initEvents } from './events.js' 5 | import { initLifecycle, callHook } from './lifecycle.js' 6 | import { initProvide, initInjections } from './inject.js' 7 | import { mergeOptions } from '../utils/index.js' 8 | import { resolveConstructorOptions } from './resove-options.js' 9 | // import Compile from '../compile/html/index.js' 10 | let uid = 0 11 | export function initMixin (Gengine) { 12 | Gengine.prototype._init = function (options, isSub) { 13 | const gm = this 14 | // a uid 15 | gm._uid = uid++ 16 | // a flag to avoid this being observed 17 | gm._isGengine = true 18 | // merge options 19 | if (options && options._isComponent) { 20 | // optimize internal component instantiation 21 | // since dynamic options merging is pretty slow, and none of the 22 | // internal component options needs special treatment. 23 | initInternalComponent(gm, options) 24 | } else { 25 | gm.$options = mergeOptions( 26 | resolveConstructorOptions(gm.constructor), 27 | options || {}, 28 | gm 29 | ) 30 | } 31 | gm._renderProxy = gm 32 | // expose real self 33 | gm._self = gm 34 | initLifecycle(gm) 35 | initEvents(gm) 36 | initRender(gm) 37 | callHook(gm, 'beforeCreate') 38 | initInjections(gm) // resolve injections before data/props 39 | initState(gm) 40 | initProvide(gm) // resolve provide after data/props 41 | callHook(gm, 'created') 42 | if ((gm.$options.el || gm.$options.template) && !isSub) { 43 | gm.$mount(gm.$options.el) 44 | // this.$compile = new Compile(options.el || document.body, this) 45 | } 46 | } 47 | } 48 | 49 | export function initInternalComponent (gm, options) { 50 | const opts = gm.$options = Object.create(gm.constructor.options) 51 | // doing this because it's faster than dynamic enumeration. 52 | const parentVnode = options._parentVnode 53 | opts.parent = options.parent 54 | opts._parentVnode = parentVnode 55 | 56 | const vnodeComponentOptions = parentVnode.componentOptions 57 | opts.propsData = vnodeComponentOptions.propsData 58 | opts._parentListeners = vnodeComponentOptions.listeners 59 | opts._renderChildren = vnodeComponentOptions.children 60 | opts._componentTag = vnodeComponentOptions.tag 61 | 62 | if (options.render) { 63 | opts.render = options.render 64 | opts.staticRenderFns = options.staticRenderFns 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/core/base/inject.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import { hasOwn, hasSymbol } from '../utils/index.js' 3 | import { defineReactive, toggleObserving } from '../observer/index.js' 4 | 5 | export function initProvide (gm) { 6 | const provide = gm.$options.provide 7 | if (provide) { 8 | gm._provided = typeof provide === 'function' 9 | ? provide.call(gm) 10 | : provide 11 | } 12 | } 13 | 14 | export function initInjections (gm) { 15 | const result = resolveInject(gm.$options.inject, gm) 16 | if (result) { 17 | toggleObserving(false) 18 | Object.keys(result).forEach((key) => { 19 | defineReactive(gm, key, result[key]) 20 | }) 21 | toggleObserving(true) 22 | } 23 | } 24 | 25 | export function resolveInject (inject, gm) { 26 | if (inject) { 27 | // inject is :any because flow is not smart enough to figure out cached 28 | const result = Object.create(null) 29 | const keys = hasSymbol 30 | ? Reflect.ownKeys(inject) 31 | : Object.keys(inject) 32 | 33 | for (let i = 0; i < keys.length; i++) { 34 | const key = keys[i] 35 | // #6574 in case the inject object is observed... 36 | if (key === '__ob__') continue 37 | const provideKey = inject[key].from 38 | let source = gm 39 | while (source) { 40 | if (source._provided && hasOwn(source._provided, provideKey)) { 41 | result[key] = source._provided[provideKey] 42 | break 43 | } 44 | source = source.$parent 45 | } 46 | if (!source) { 47 | if ('default' in inject[key]) { 48 | const provideDefault = inject[key].default 49 | result[key] = typeof provideDefault === 'function' 50 | ? provideDefault.call(gm) 51 | : provideDefault 52 | } 53 | } 54 | } 55 | return result 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/core/base/proxy.js: -------------------------------------------------------------------------------- 1 | export let initProxy = () => { 2 | 3 | } 4 | -------------------------------------------------------------------------------- /src/core/base/render-helpers/bind-dynamic-keys.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | // helper to process dynamic keys for dynamic arguments in v-bind and v-on. 4 | // For example, the following template: 5 | // 6 | //
7 | // 8 | // compiles to the following: 9 | // 10 | // _c('div', { attrs: bindDynamicKeys({ "id": "app" }, [key, value]) }) 11 | 12 | export function bindDynamicKeys (baseObj, values) { 13 | for (let i = 0; i < values.length; i += 2) { 14 | const key = values[i] 15 | if (typeof key === 'string' && key) { 16 | baseObj[values[i]] = values[i + 1] 17 | } 18 | } 19 | return baseObj 20 | } 21 | 22 | // helper to dynamically append modifier runtime markers to event names. 23 | // ensure only append when value is already string, otherwise it will be cast 24 | // to string and cause the type check to miss. 25 | export function prependModifier (value, symbol) { 26 | return typeof value === 'string' ? symbol + value : value 27 | } 28 | -------------------------------------------------------------------------------- /src/core/base/render-helpers/bind-object-listeners.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { extend, isPlainObject } from '../../utils/index.js' 4 | 5 | export function bindObjectListeners (data, value) { 6 | if (value) { 7 | if (!isPlainObject(value)) { 8 | } else { 9 | const on = data.on = data.on ? extend({}, data.on) : {} 10 | for (const key in value) { 11 | const existing = on[key] 12 | const ours = value[key] 13 | on[key] = existing ? [].concat(existing, ours) : ours 14 | } 15 | } 16 | } 17 | return data 18 | } 19 | -------------------------------------------------------------------------------- /src/core/base/render-helpers/bind-object-props.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { config } from '../../config/config.js' 4 | 5 | import { 6 | isObject, 7 | toObject, 8 | isReservedAttribute, 9 | camelize, 10 | hyphenate 11 | } from '../../utils/index.js' 12 | 13 | /** 14 | * Runtime helper for merging v-bind="object" into a VNode's data. 15 | */ 16 | export function bindObjectProps ( 17 | data, 18 | tag, 19 | value, 20 | asProp, 21 | isSync 22 | ) { 23 | if (value) { 24 | if (!isObject(value)) { 25 | } else { 26 | if (Array.isArray(value)) { 27 | value = toObject(value) 28 | } 29 | let hash 30 | for (const key in value) { 31 | if ( 32 | key === 'class' || 33 | key === 'style' || 34 | isReservedAttribute(key) 35 | ) { 36 | hash = data 37 | } else { 38 | const type = data.attrs && data.attrs.type 39 | hash = asProp || config.mustUseProp(tag, type, key) 40 | ? data.domProps || (data.domProps = {}) 41 | : data.attrs || (data.attrs = {}) 42 | } 43 | const camelizedKey = camelize(key) 44 | const hyphenatedKey = hyphenate(key) 45 | if (!(camelizedKey in hash) && !(hyphenatedKey in hash)) { 46 | hash[key] = value[key] 47 | 48 | if (isSync) { 49 | const on = data.on || (data.on = {}) 50 | on[`update:${key}`] = function ($event) { 51 | value[key] = $event 52 | } 53 | } 54 | } 55 | } 56 | } 57 | } 58 | return data 59 | } 60 | -------------------------------------------------------------------------------- /src/core/base/render-helpers/check-keycodes.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { config } from '../../config/config.js' 4 | import { hyphenate } from '../../utils/index.js' 5 | 6 | function isKeyNotMatch (expect, actual) { 7 | if (Array.isArray(expect)) { 8 | return expect.indexOf(actual) === -1 9 | } else { 10 | return expect !== actual 11 | } 12 | } 13 | 14 | /** 15 | * Runtime helper for checking keyCodes from config. 16 | * exposed as Vue.prototype._k 17 | * passing in eventKeyName as last argument separately for backwards compat 18 | */ 19 | export function checkKeyCodes ( 20 | eventKeyCode, 21 | key, 22 | builtInKeyCode, 23 | eventKeyName, 24 | builtInKeyName 25 | ) { 26 | const mappedKeyCode = config.keyCodes[key] || builtInKeyCode 27 | if (builtInKeyName && eventKeyName && !config.keyCodes[key]) { 28 | return isKeyNotMatch(builtInKeyName, eventKeyName) 29 | } else if (mappedKeyCode) { 30 | return isKeyNotMatch(mappedKeyCode, eventKeyCode) 31 | } else if (eventKeyName) { 32 | return hyphenate(eventKeyName) !== key 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/core/base/render-helpers/index.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { toNumber, toString, looseEqual, looseIndexOf } from '../../utils/index.js' 4 | import { createTextVNode, createEmptyVNode } from '../../gdom/vnode.js' 5 | import { renderList } from './render-list.js' 6 | import { renderSlot } from './render-slot.js' 7 | import { resolveFilter } from './resolve-filter.js' 8 | import { checkKeyCodes } from './check-keycodes.js' 9 | import { bindObjectProps } from './bind-object-props.js' 10 | import { renderStatic, markOnce } from './render-static.js' 11 | import { bindObjectListeners } from './bind-object-listeners.js' 12 | import { resolveScopedSlots } from './resolve-scoped-slots.js' 13 | import { bindDynamicKeys, prependModifier } from './bind-dynamic-keys.js' 14 | 15 | export function installRenderHelpers (target) { 16 | target._o = markOnce 17 | target._n = toNumber 18 | target._s = toString 19 | target._l = renderList 20 | target._t = renderSlot 21 | target._q = looseEqual 22 | target._i = looseIndexOf 23 | target._m = renderStatic 24 | target._f = resolveFilter 25 | target._k = checkKeyCodes 26 | target._b = bindObjectProps 27 | target._v = createTextVNode 28 | target._e = createEmptyVNode 29 | target._u = resolveScopedSlots 30 | target._g = bindObjectListeners 31 | target._d = bindDynamicKeys 32 | target._p = prependModifier 33 | } 34 | -------------------------------------------------------------------------------- /src/core/base/render-helpers/render-list.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { isObject, isDef, hasSymbol } from '../../utils/index.js' 4 | 5 | /** 6 | * Runtime helper for rendering v-for lists. 7 | */ 8 | export function renderList ( 9 | val, 10 | render 11 | ) { 12 | val = (typeof val === 'string' && this[val]) || val 13 | let ret, i, l, keys, key 14 | if (Array.isArray(val) || typeof val === 'string') { 15 | ret = new Array(val.length) 16 | for (i = 0, l = val.length; i < l; i++) { 17 | ret[i] = render(val[i], i) 18 | } 19 | } else if (typeof val === 'number') { 20 | ret = new Array(val) 21 | for (i = 0; i < val; i++) { 22 | ret[i] = render(i + 1, i) 23 | } 24 | } else if (isObject(val)) { 25 | if (hasSymbol && val[Symbol.iterator]) { 26 | ret = [] 27 | const iterator = val[Symbol.iterator]() 28 | let result = iterator.next() 29 | while (!result.done) { 30 | ret.push(render(result.value, ret.length)) 31 | result = iterator.next() 32 | } 33 | } else { 34 | keys = Object.keys(val) 35 | ret = new Array(keys.length) 36 | for (i = 0, l = keys.length; i < l; i++) { 37 | key = keys[i] 38 | ret[i] = render(val[key], key, i) 39 | } 40 | } 41 | } 42 | if (!isDef(ret)) { 43 | ret = [] 44 | } 45 | (ret)._isVList = true 46 | return ret 47 | } 48 | -------------------------------------------------------------------------------- /src/core/base/render-helpers/render-slot.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { extend } from '../../utils/index.js' 4 | 5 | /** 6 | * Runtime helper for rendering 7 | */ 8 | export function renderSlot ( 9 | name, 10 | fallback, 11 | props, 12 | bindObject 13 | ) { 14 | const scopedSlotFn = this.$scopedSlots[name] 15 | let nodes 16 | if (scopedSlotFn) { // scoped slot 17 | props = props || {} 18 | if (bindObject) { 19 | props = extend(extend({}, bindObject), props) 20 | } 21 | nodes = scopedSlotFn(props) || fallback 22 | } else { 23 | nodes = this.$slots[name] || fallback 24 | } 25 | 26 | const target = props && props.slot 27 | if (target) { 28 | return this.$createElement('template', { slot: target }, nodes) 29 | } else { 30 | return nodes 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/core/base/render-helpers/render-static.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | /** 4 | * Runtime helper for rendering static trees. 5 | */ 6 | export function renderStatic ( 7 | index, 8 | isInFor 9 | ) { 10 | const cached = this._staticTrees || (this._staticTrees = []) 11 | let tree = cached[index] 12 | // if has already-rendered static tree and not inside v-for, 13 | // we can reuse the same tree. 14 | if (tree && !isInFor) { 15 | return tree 16 | } 17 | // otherwise, render a fresh tree. 18 | tree = cached[index] = this.$options.staticRenderFns[index].call( 19 | this._renderProxy, 20 | null, 21 | this // for render fns generated for functional component templates 22 | ) 23 | markStatic(tree, `__static__${index}`, false) 24 | return tree 25 | } 26 | 27 | /** 28 | * Runtime helper for v-once. 29 | * Effectively it means marking the node as static with a unique key. 30 | */ 31 | export function markOnce ( 32 | tree, 33 | index, 34 | key 35 | ) { 36 | markStatic(tree, `__once__${index}${key ? `_${key}` : ``}`, true) 37 | return tree 38 | } 39 | 40 | function markStatic ( 41 | tree, 42 | key, 43 | isOnce 44 | ) { 45 | if (Array.isArray(tree)) { 46 | for (let i = 0; i < tree.length; i++) { 47 | if (tree[i] && typeof tree[i] !== 'string') { 48 | markStaticNode(tree[i], `${key}_${i}`, isOnce) 49 | } 50 | } 51 | } else { 52 | markStaticNode(tree, key, isOnce) 53 | } 54 | } 55 | 56 | function markStaticNode (node, key, isOnce) { 57 | node.isStatic = true 58 | node.key = key 59 | node.isOnce = isOnce 60 | } 61 | -------------------------------------------------------------------------------- /src/core/base/render-helpers/resolve-filter.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { identity, resolveAsset } from '../../utils/index.js' 4 | 5 | /** 6 | * Runtime helper for resolving filters 7 | */ 8 | export function resolveFilter (id) { 9 | return resolveAsset(this.$options, 'filters', id, true) || identity 10 | } 11 | -------------------------------------------------------------------------------- /src/core/base/render-helpers/resolve-scoped-slots.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | export function resolveScopedSlots ( 4 | fns, // see flow/vnode 5 | res, 6 | // the following are added in 2.6 7 | hasDynamicKeys, 8 | contentHashKey 9 | ) { 10 | res = res || { $stable: !hasDynamicKeys } 11 | for (let i = 0; i < fns.length; i++) { 12 | const slot = fns[i] 13 | if (Array.isArray(slot)) { 14 | resolveScopedSlots(slot, res, hasDynamicKeys) 15 | } else if (slot) { 16 | // marker for reverse proxying v-slot without scope on this.$slots 17 | if (slot.proxy) { 18 | slot.fn.proxy = true 19 | } 20 | res[slot.key] = slot.fn 21 | } 22 | } 23 | if (contentHashKey) { 24 | (res).$key = contentHashKey 25 | } 26 | return res 27 | } 28 | -------------------------------------------------------------------------------- /src/core/base/render-helpers/resolve-slots.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | /** 3 | * Runtime helper for resolving raw children VNodes into a slot object. 4 | */ 5 | export function resolveSlots ( 6 | children, 7 | context 8 | ) { 9 | if (!children || !children.length) { 10 | return {} 11 | } 12 | const slots = {} 13 | for (let i = 0, l = children.length; i < l; i++) { 14 | const child = children[i] 15 | const data = child.data 16 | // remove slot attribute if the node is resolved as a Vue slot node 17 | if (data && data.attrs && data.attrs.slot) { 18 | delete data.attrs.slot 19 | } 20 | // named slots should only be respected if the vnode was rendered in the 21 | // same context. 22 | if ((child.context === context || child.fnContext === context) && 23 | data && data.slot != null 24 | ) { 25 | const name = data.slot 26 | const slot = (slots[name] || (slots[name] = [])) 27 | if (child.tag === 'template') { 28 | slot.push.apply(slot, child.children || []) 29 | } else { 30 | slot.push(child) 31 | } 32 | } else { 33 | (slots.default || (slots.default = [])).push(child) 34 | } 35 | } 36 | // ignore slots that contains only whitespace 37 | for (const name in slots) { 38 | if (slots[name].every(isWhitespace)) { 39 | delete slots[name] 40 | } 41 | } 42 | return slots 43 | } 44 | 45 | function isWhitespace (node) { 46 | return (node.isComment && !node.asyncFactory) || node.text === ' ' 47 | } 48 | -------------------------------------------------------------------------------- /src/core/base/render-instance.js: -------------------------------------------------------------------------------- 1 | export let currentRenderingInstance = null 2 | export function applyCurrentRenderingInstance (gm) { 3 | currentRenderingInstance = gm 4 | } -------------------------------------------------------------------------------- /src/core/base/render.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import { 3 | emptyObject, 4 | handleError, 5 | nextTick 6 | } from '../utils/index.js' 7 | import { defineReactive } from '../observer/index.js' 8 | import { createElement } from '../gdom/create.js' 9 | import { VNode, createEmptyVNode } from '../gdom/vnode.js' 10 | import { normalizeScopedSlots } from '../gdom/helpers/normalize-scoped-slots.js' 11 | import { installRenderHelpers } from './render-helpers/index.js' 12 | import { resolveSlots } from './render-helpers/resolve-slots.js' 13 | import { applyCurrentRenderingInstance } from './render-instance.js' 14 | export function initRender (gm) { 15 | gm._vnode = null // the root of the child tree 16 | gm._staticTrees = null // v-once cached trees 17 | const options = gm.$options 18 | const parentVnode = gm.$vnode = options._parentVnode // the placeholder node in parent tree 19 | const renderContext = parentVnode && parentVnode.context 20 | gm.$slots = resolveSlots(options._renderChildren, renderContext) 21 | gm.$scopedSlots = emptyObject 22 | // bind the createElement fn to this instance 23 | // so that we get proper render context inside it. 24 | // args order: tag, data, children, normalizationType, alwaysNormalize 25 | // internal version is used by render functions compiled from templates 26 | gm._c = (a, b, c, d) => createElement(gm, a, b, c, d, false) 27 | // normalization is always applied for the public version, used in 28 | // user-written render functions. 29 | gm.$createElement = (a, b, c, d) => createElement(gm, a, b, c, d, true) 30 | 31 | // $attrs & $listeners are exposed for easier HOC creation. 32 | // they need to be reactive so that HOCs using them are always updated 33 | const parentData = parentVnode && parentVnode.data 34 | defineReactive(gm, '$attrs', parentData && parentData.attrs || emptyObject, null, true) 35 | defineReactive(gm, '$listeners', options._parentListeners || emptyObject, null, true) 36 | } 37 | // for testing only 38 | export function setCurrentRenderingInstance (gm) { 39 | applyCurrentRenderingInstance(gm) 40 | } 41 | 42 | export function renderMixin (Gengine) { 43 | // install runtime convenience helpers 44 | installRenderHelpers(Gengine.prototype) 45 | 46 | Gengine.prototype.$nextTick = function (fn) { 47 | return nextTick(fn, this) 48 | } 49 | 50 | Gengine.prototype._render = function () { 51 | const gm = this 52 | const { render, _parentVnode } = gm.$options 53 | if (_parentVnode) { 54 | gm.$scopedSlots = normalizeScopedSlots( 55 | _parentVnode.data.scopedSlots, 56 | gm.$slots, 57 | gm.$scopedSlots 58 | ) 59 | } 60 | 61 | // set parent vnode. this allows render functions to have access 62 | // to the data on the placeholder node. 63 | gm.$vnode = _parentVnode 64 | // render self 65 | let vnode 66 | try { 67 | // There's no need to maintain a stack because all render fns are called 68 | // separately from one another. Nested component's render fns are called 69 | // when parent component is patched. 70 | applyCurrentRenderingInstance(gm) 71 | vnode = render.call(gm._renderProxy, gm.$createElement) 72 | } catch (e) { 73 | handleError(e, gm, `render`) 74 | // return error render result, 75 | // or previous vnode to prevent render error causing blank component 76 | vnode = gm._vnode 77 | } finally { 78 | applyCurrentRenderingInstance(null) 79 | } 80 | // if the returned array contains only a single node, allow it 81 | if (Array.isArray(vnode) && vnode.length === 1) { 82 | vnode = vnode[0] 83 | } 84 | // return empty vnode in case the render function errored out 85 | if (!(vnode instanceof VNode)) { 86 | vnode = createEmptyVNode() 87 | } 88 | // set parent 89 | vnode.parent = _parentVnode 90 | return vnode 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/core/base/resove-options.js: -------------------------------------------------------------------------------- 1 | import { extend, mergeOptions } from '../utils/index.js' 2 | export function resolveConstructorOptions (Ctor) { 3 | let options = Ctor.options 4 | if (Ctor.super) { 5 | const superOptions = resolveConstructorOptions(Ctor.super) 6 | const cachedSuperOptions = Ctor.superOptions 7 | if (superOptions !== cachedSuperOptions) { 8 | // super option changed, 9 | // need to resolve new options. 10 | Ctor.superOptions = superOptions 11 | // check if there are any late-modified/attached options (#4976) 12 | const modifiedOptions = resolveModifiedOptions(Ctor) 13 | // update base extend options 14 | if (modifiedOptions) { 15 | extend(Ctor.extendOptions, modifiedOptions) 16 | } 17 | options = Ctor.options = mergeOptions(superOptions, Ctor.extendOptions) 18 | if (options.name) { 19 | options.components[options.name] = Ctor 20 | } 21 | } 22 | } 23 | return options 24 | } 25 | 26 | function resolveModifiedOptions (Ctor) { 27 | let modified 28 | const latest = Ctor.options 29 | const sealed = Ctor.sealedOptions 30 | for (const key in latest) { 31 | if (latest[key] !== sealed[key]) { 32 | if (!modified) modified = {} 33 | modified[key] = latest[key] 34 | } 35 | } 36 | return modified 37 | } -------------------------------------------------------------------------------- /src/core/compile/html/index.js: -------------------------------------------------------------------------------- 1 | import Watcher from '../../watcher/index.js' 2 | function Compile(el, vm) { 3 | this.$vm = vm; 4 | this.$el = this.isElementNode(el) ? el : document.querySelector(el); 5 | 6 | if (this.$el) { 7 | this.$fragment = this.node2Fragment(this.$el); 8 | this.init(); 9 | this.$el.appendChild(this.$fragment); 10 | } 11 | } 12 | 13 | Compile.prototype = { 14 | constructor: Compile, 15 | node2Fragment: function(el) { 16 | var fragment = document.createDocumentFragment(), 17 | child 18 | 19 | // 将原生节点拷贝到fragment 20 | while (child = el.firstChild) { 21 | fragment.appendChild(child); 22 | } 23 | 24 | return fragment; 25 | }, 26 | 27 | init: function() { 28 | this.compileElement(this.$fragment); 29 | }, 30 | 31 | compileElement: function(el) { 32 | var childNodes = el.childNodes, 33 | me = this; 34 | 35 | [].slice.call(childNodes).forEach(function(node) { 36 | var text = node.textContent; 37 | var reg = /\{\{(.*)\}\}/; 38 | 39 | if (me.isElementNode(node)) { 40 | me.compile(node); 41 | 42 | } else if (me.isTextNode(node) && reg.test(text)) { 43 | me.compileText(node, RegExp.$1.trim()); 44 | } 45 | 46 | if (node.childNodes && node.childNodes.length) { 47 | me.compileElement(node); 48 | } 49 | }); 50 | }, 51 | 52 | compile: function(node) { 53 | var nodeAttrs = node.attributes, 54 | me = this; 55 | 56 | [].slice.call(nodeAttrs).forEach(function(attr) { 57 | var attrName = attr.name; 58 | if (me.isDirective(attrName)) { 59 | var exp = attr.value; 60 | var dir = attrName.substring(2); 61 | // 事件指令 62 | if (me.isEventDirective(dir)) { 63 | compileUtil.eventHandler(node, me.$vm, exp, dir); 64 | // 普通指令 65 | } else { 66 | compileUtil[dir] && compileUtil[dir](node, me.$vm, exp); 67 | } 68 | 69 | node.removeAttribute(attrName); 70 | } 71 | }); 72 | }, 73 | 74 | compileText: function(node, exp) { 75 | compileUtil.text(node, this.$vm, exp); 76 | }, 77 | 78 | isDirective: function(attr) { 79 | return attr.indexOf('v-') == 0; 80 | }, 81 | 82 | isEventDirective: function(dir) { 83 | return dir.indexOf('on') === 0; 84 | }, 85 | 86 | isElementNode: function(node) { 87 | return node.nodeType == 1; 88 | }, 89 | 90 | isTextNode: function(node) { 91 | return node.nodeType == 3; 92 | } 93 | }; 94 | 95 | // 指令处理集合 96 | var compileUtil = { 97 | text: function(node, vm, exp) { 98 | this.bind(node, vm, exp, 'text'); 99 | }, 100 | 101 | html: function(node, vm, exp) { 102 | this.bind(node, vm, exp, 'html'); 103 | }, 104 | 105 | model: function(node, vm, exp) { 106 | this.bind(node, vm, exp, 'model'); 107 | 108 | var me = this, 109 | val = this._getVMVal(vm, exp); 110 | node.addEventListener('input', function(e) { 111 | var newValue = e.target.value; 112 | if (val === newValue) { 113 | return; 114 | } 115 | 116 | me._setVMVal(vm, exp, newValue); 117 | val = newValue; 118 | }); 119 | }, 120 | 121 | class: function(node, vm, exp) { 122 | this.bind(node, vm, exp, 'class'); 123 | }, 124 | 125 | bind: function(node, vm, exp, dir) { 126 | var updaterFn = updater[dir + 'Updater']; 127 | 128 | updaterFn && updaterFn(node, this._getVMVal(vm, exp)); 129 | 130 | new Watcher(vm, exp, function(value, oldValue) { 131 | updaterFn && updaterFn(node, value, oldValue); 132 | }); 133 | }, 134 | 135 | // 事件处理 136 | eventHandler: function(node, vm, exp, dir) { 137 | var eventType = dir.split(':')[1], 138 | fn = vm.$options.methods && vm.$options.methods[exp]; 139 | 140 | if (eventType && fn) { 141 | node.addEventListener(eventType, fn.bind(vm), false); 142 | } 143 | }, 144 | 145 | _getVMVal: function(vm, exp) { 146 | var val = vm; 147 | exp = exp.split('.'); 148 | exp.forEach(function(k) { 149 | val = val[k]; 150 | }); 151 | return val; 152 | }, 153 | 154 | _setVMVal: function(vm, exp, value) { 155 | var val = vm; 156 | exp = exp.split('.'); 157 | exp.forEach(function(k, i) { 158 | // 非最后一个key,更新val的值 159 | if (i < exp.length - 1) { 160 | val = val[k]; 161 | } else { 162 | val[k] = value; 163 | } 164 | }); 165 | } 166 | }; 167 | 168 | 169 | var updater = { 170 | textUpdater: function(node, value) { 171 | node.textContent = typeof value == 'undefined' ? '' : value; 172 | }, 173 | 174 | htmlUpdater: function(node, value) { 175 | node.innerHTML = typeof value == 'undefined' ? '' : value; 176 | }, 177 | 178 | classUpdater: function(node, value, oldValue) { 179 | var className = node.className; 180 | className = className.replace(oldValue, '').replace(/\s$/, ''); 181 | 182 | var space = className && String(value) ? ' ' : ''; 183 | 184 | node.className = className + space + value; 185 | }, 186 | 187 | modelUpdater: function(node, value, oldValue) { 188 | node.value = typeof value == 'undefined' ? '' : value; 189 | } 190 | }; 191 | export default Compile -------------------------------------------------------------------------------- /src/core/compile/xml/index.js: -------------------------------------------------------------------------------- 1 | function Compile(el, vm) { 2 | this.$vm = vm; 3 | this.$el = this.isElementNode(el) ? el : document.querySelector(el); 4 | 5 | if (this.$el) { 6 | this.$fragment = this.node2Fragment(this.$el); 7 | this.init(); 8 | this.$el.appendChild(this.$fragment); 9 | } 10 | } 11 | 12 | Compile.prototype = { 13 | constructor: Compile, 14 | node2Fragment: function(el) { 15 | var fragment = document.createDocumentFragment(), 16 | child 17 | 18 | // 将原生节点拷贝到fragment 19 | while (child = el.firstChild) { 20 | fragment.appendChild(child); 21 | } 22 | 23 | return fragment; 24 | }, 25 | 26 | init: function() { 27 | this.compileElement(this.$fragment); 28 | }, 29 | 30 | compileElement: function(el) { 31 | var childNodes = el.childNodes, 32 | me = this; 33 | 34 | [].slice.call(childNodes).forEach(function(node) { 35 | var text = node.textContent; 36 | var reg = /\{\{(.*)\}\}/; 37 | 38 | if (me.isElementNode(node)) { 39 | me.compile(node); 40 | 41 | } else if (me.isTextNode(node) && reg.test(text)) { 42 | me.compileText(node, RegExp.$1.trim()); 43 | } 44 | 45 | if (node.childNodes && node.childNodes.length) { 46 | me.compileElement(node); 47 | } 48 | }); 49 | }, 50 | 51 | compile: function(node) { 52 | var nodeAttrs = node.attributes, 53 | me = this; 54 | 55 | [].slice.call(nodeAttrs).forEach(function(attr) { 56 | var attrName = attr.name; 57 | if (me.isDirective(attrName)) { 58 | var exp = attr.value; 59 | var dir = attrName.substring(2); 60 | // 事件指令 61 | if (me.isEventDirective(dir)) { 62 | compileUtil.eventHandler(node, me.$vm, exp, dir); 63 | // 普通指令 64 | } else { 65 | compileUtil[dir] && compileUtil[dir](node, me.$vm, exp); 66 | } 67 | 68 | node.removeAttribute(attrName); 69 | } 70 | }); 71 | }, 72 | 73 | compileText: function(node, exp) { 74 | compileUtil.text(node, this.$vm, exp); 75 | }, 76 | 77 | isDirective: function(attr) { 78 | return attr.indexOf('v-') == 0; 79 | }, 80 | 81 | isEventDirective: function(dir) { 82 | return dir.indexOf('on') === 0; 83 | }, 84 | 85 | isElementNode: function(node) { 86 | return node.nodeType == 1; 87 | }, 88 | 89 | isTextNode: function(node) { 90 | return node.nodeType == 3; 91 | } 92 | }; 93 | 94 | // 指令处理集合 95 | var compileUtil = { 96 | text: function(node, vm, exp) { 97 | this.bind(node, vm, exp, 'text'); 98 | }, 99 | 100 | html: function(node, vm, exp) { 101 | this.bind(node, vm, exp, 'html'); 102 | }, 103 | 104 | model: function(node, vm, exp) { 105 | this.bind(node, vm, exp, 'model'); 106 | 107 | var me = this, 108 | val = this._getVMVal(vm, exp); 109 | node.addEventListener('input', function(e) { 110 | var newValue = e.target.value; 111 | if (val === newValue) { 112 | return; 113 | } 114 | 115 | me._setVMVal(vm, exp, newValue); 116 | val = newValue; 117 | }); 118 | }, 119 | 120 | class: function(node, vm, exp) { 121 | this.bind(node, vm, exp, 'class'); 122 | }, 123 | 124 | bind: function(node, vm, exp, dir) { 125 | var updaterFn = updater[dir + 'Updater']; 126 | 127 | updaterFn && updaterFn(node, this._getVMVal(vm, exp)); 128 | 129 | new Watcher(vm, exp, function(value, oldValue) { 130 | updaterFn && updaterFn(node, value, oldValue); 131 | }); 132 | }, 133 | 134 | // 事件处理 135 | eventHandler: function(node, vm, exp, dir) { 136 | var eventType = dir.split(':')[1], 137 | fn = vm.$options.methods && vm.$options.methods[exp]; 138 | 139 | if (eventType && fn) { 140 | node.addEventListener(eventType, fn.bind(vm), false); 141 | } 142 | }, 143 | 144 | _getVMVal: function(vm, exp) { 145 | var val = vm; 146 | exp = exp.split('.'); 147 | exp.forEach(function(k) { 148 | val = val[k]; 149 | }); 150 | return val; 151 | }, 152 | 153 | _setVMVal: function(vm, exp, value) { 154 | var val = vm; 155 | exp = exp.split('.'); 156 | exp.forEach(function(k, i) { 157 | // 非最后一个key,更新val的值 158 | if (i < exp.length - 1) { 159 | val = val[k]; 160 | } else { 161 | val[k] = value; 162 | } 163 | }); 164 | } 165 | }; 166 | 167 | 168 | var updater = { 169 | textUpdater: function(node, value) { 170 | node.textContent = typeof value == 'undefined' ? '' : value; 171 | }, 172 | 173 | htmlUpdater: function(node, value) { 174 | node.innerHTML = typeof value == 'undefined' ? '' : value; 175 | }, 176 | 177 | classUpdater: function(node, value, oldValue) { 178 | var className = node.className; 179 | className = className.replace(oldValue, '').replace(/\s$/, ''); 180 | 181 | var space = className && String(value) ? ' ' : ''; 182 | 183 | node.className = className + space + value; 184 | }, 185 | 186 | modelUpdater: function(node, value, oldValue) { 187 | node.value = typeof value == 'undefined' ? '' : value; 188 | } 189 | }; 190 | export default Compile -------------------------------------------------------------------------------- /src/core/components/index.js: -------------------------------------------------------------------------------- 1 | import KeepAlive from './keep-alive.js' 2 | 3 | export default { 4 | KeepAlive 5 | } 6 | -------------------------------------------------------------------------------- /src/core/components/keep-alive.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { isRegExp, remove } from '../utils/index.js' 4 | import { getFirstComponentChild } from '../gdom/helpers/index.js' 5 | 6 | function getComponentName (opts) { 7 | return opts && (opts.Ctor.options.name || opts.tag) 8 | } 9 | 10 | function matches (pattern, name) { 11 | if (Array.isArray(pattern)) { 12 | return pattern.indexOf(name) > -1 13 | } else if (typeof pattern === 'string') { 14 | return pattern.split(',').indexOf(name) > -1 15 | } else if (isRegExp(pattern)) { 16 | return pattern.test(name) 17 | } 18 | /* istanbul ignore next */ 19 | return false 20 | } 21 | 22 | function pruneCache (keepAliveInstance, filter) { 23 | const { cache, keys, _vnode } = keepAliveInstance 24 | for (const key in cache) { 25 | const cachedNode = cache[key] 26 | if (cachedNode) { 27 | const name = getComponentName(cachedNode.componentOptions) 28 | if (name && !filter(name)) { 29 | pruneCacheEntry(cache, key, keys, _vnode) 30 | } 31 | } 32 | } 33 | } 34 | 35 | function pruneCacheEntry ( 36 | cache, 37 | key, 38 | keys, 39 | current 40 | ) { 41 | const cached = cache[key] 42 | if (cached && (!current || cached.tag !== current.tag)) { 43 | cached.componentInstance.$destroy() 44 | } 45 | cache[key] = null 46 | remove(keys, key) 47 | } 48 | 49 | const patternTypes = [String, RegExp, Array] 50 | 51 | export default { 52 | name: 'keep-alive', 53 | abstract: true, 54 | 55 | props: { 56 | include: patternTypes, 57 | exclude: patternTypes, 58 | max: [String, Number] 59 | }, 60 | 61 | created () { 62 | this.cache = Object.create(null) 63 | this.keys = [] 64 | }, 65 | 66 | destroyed () { 67 | for (const key in this.cache) { 68 | pruneCacheEntry(this.cache, key, this.keys) 69 | } 70 | }, 71 | 72 | mounted () { 73 | this.$watch('include', (val) => { 74 | pruneCache(this, name => matches(val, name)) 75 | }) 76 | this.$watch('exclude', (val) => { 77 | pruneCache(this, name => !matches(val, name)) 78 | }) 79 | }, 80 | 81 | render () { 82 | const slot = this.$slots.default 83 | const vnode = getFirstComponentChild(slot) 84 | const componentOptions = vnode && vnode.componentOptions 85 | if (componentOptions) { 86 | // check pattern 87 | const name = getComponentName(componentOptions) 88 | const { include, exclude } = this 89 | if ( 90 | // not included 91 | (include && (!name || !matches(include, name))) || 92 | // excluded 93 | (exclude && name && matches(exclude, name)) 94 | ) { 95 | return vnode 96 | } 97 | 98 | const { cache, keys } = this 99 | const key = vnode.key == null 100 | // same constructor may get registered as different local components 101 | // so cid alone is not enough (#3269) 102 | ? componentOptions.Ctor.cid + (componentOptions.tag ? `::${componentOptions.tag}` : '') 103 | : vnode.key 104 | if (cache[key]) { 105 | vnode.componentInstance = cache[key].componentInstance 106 | // make current key freshest 107 | remove(keys, key) 108 | keys.push(key) 109 | } else { 110 | cache[key] = vnode 111 | keys.push(key) 112 | // prune oldest entry 113 | if (this.max && keys.length > parseInt(this.max)) { 114 | pruneCacheEntry(cache, keys[0], keys, this._vnode) 115 | } 116 | } 117 | 118 | vnode.data.keepAlive = true 119 | } 120 | return vnode || (slot && slot[0]) 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/core/config/assets.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { ASSET_TYPES } from './constants.js' 4 | import { isPlainObject } from '../utils/index.js' 5 | 6 | export function initAssetRegisters (Gengine) { 7 | /** 8 | * Create asset registration methods. 9 | */ 10 | ASSET_TYPES.forEach((type) => { 11 | Gengine[type] = function ( 12 | id, 13 | definition 14 | ) { 15 | if (!definition) { 16 | return this.options[type + 's'][id] 17 | } else { 18 | if (type === 'component' && isPlainObject(definition)) { 19 | definition.name = definition.name || id 20 | definition = this.options._base.extend(definition) 21 | } 22 | if (type === 'directive' && typeof definition === 'function') { 23 | definition = { bind: definition, update: definition } 24 | } 25 | this.options[type + 's'][id] = definition 26 | return definition 27 | } 28 | } 29 | }) 30 | } 31 | -------------------------------------------------------------------------------- /src/core/config/config.js: -------------------------------------------------------------------------------- 1 | import { LIFECYCLE_HOOKS } from "./constants.js"; 2 | export var config = { 3 | /** 4 | * Option merge strategies (used in core/util/options) 5 | */ 6 | // $flow-disable-line 7 | optionMergeStrategies: Object.create(null), 8 | 9 | /** 10 | * Whether to suppress warnings. 11 | */ 12 | silent: false, 13 | 14 | /** 15 | * Show production mode tip message on boot? 16 | */ 17 | productionTip: 'development', 18 | 19 | /** 20 | * Whether to enable devtools 21 | */ 22 | devtools: 'development', 23 | 24 | /** 25 | * Whether to record perf 26 | */ 27 | performance: false, 28 | 29 | /** 30 | * Error handler for watcher errors 31 | */ 32 | errorHandler: null, 33 | 34 | /** 35 | * Warn handler for watcher warns 36 | */ 37 | warnHandler: null, 38 | 39 | /** 40 | * Ignore certain custom elements 41 | */ 42 | ignoredElements: [], 43 | 44 | /** 45 | * Custom user key aliases for v-on 46 | */ 47 | // $flow-disable-line 48 | keyCodes: Object.create(null), 49 | 50 | /** 51 | * Check if a tag is reserved so that it cannot be registered as a 52 | * component. This is platform-dependent and may be overwritten. 53 | */ 54 | isReservedTag: false, 55 | 56 | /** 57 | * Check if an attribute is reserved so that it cannot be used as a component 58 | * prop. This is platform-dependent and may be overwritten. 59 | */ 60 | isReservedAttr: false, 61 | 62 | /** 63 | * Check if a tag is an unknown element. 64 | * Platform-dependent. 65 | */ 66 | isUnknownElement: false, 67 | 68 | /** 69 | * Get the namespace of an element 70 | */ 71 | getTagNamespace: () => {}, 72 | 73 | /** 74 | * Parse the real tag name for the specific platform. 75 | */ 76 | parsePlatformTagName: (_) => _, 77 | 78 | /** 79 | * Check if an attribute must be bound using property, e.g. value 80 | * Platform-dependent. 81 | */ 82 | mustUseProp: false, 83 | 84 | /** 85 | * Perform updates asynchronously. Intended to be used by Vue Test Utils 86 | * This will significantly reduce performance if set to false. 87 | */ 88 | async: true, 89 | 90 | /** 91 | * Exposed for legacy reasons 92 | */ 93 | _lifecycleHooks: LIFECYCLE_HOOKS 94 | } -------------------------------------------------------------------------------- /src/core/config/constants.js: -------------------------------------------------------------------------------- 1 | export const SSR_ATTR = 'data-server-rendered' 2 | export const LIFECYCLE_HOOKS = [ 3 | 'beforeCreate', 4 | 'created', 5 | 'beforeMount', 6 | 'mounted', 7 | 'beforeUpdate', 8 | 'updated', 9 | 'beforeDestroy', 10 | 'destroyed', 11 | 'activated', 12 | 'deactivated', 13 | 'errorCaptured', 14 | 'serverPrefetch' 15 | ] 16 | export const ASSET_TYPES = [ 17 | 'component', 18 | 'directive', 19 | 'filter' 20 | ] 21 | -------------------------------------------------------------------------------- /src/core/config/extend.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { extend, mergeOptions } from '../utils/index.js' 4 | import { ASSET_TYPES } from './constants.js' 5 | import { defineComputed, proxy } from '../base/state.js' 6 | 7 | export function initExtend (Gengine) { 8 | /** 9 | * Each instance constructor, including Gengine, has a unique 10 | * cid. This enables us to create wrapped "child 11 | * constructors" for prototypal inheritance and cache them. 12 | */ 13 | Gengine.cid = 0 14 | let cid = 1 15 | 16 | /** 17 | * Class inheritance 18 | */ 19 | Gengine.extend = function (extendOptions) { 20 | extendOptions = extendOptions || {} 21 | const Super = this 22 | const SuperId = Super.cid 23 | const cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {}) 24 | if (cachedCtors[SuperId]) { 25 | return cachedCtors[SuperId] 26 | } 27 | 28 | const name = extendOptions.name || Super.options.name 29 | 30 | const Sub = function VueComponent (options) { 31 | this._init(options, true) 32 | } 33 | Sub.prototype = Object.create(Super.prototype) 34 | Sub.prototype.constructor = Sub 35 | Sub.cid = cid++ 36 | Sub.options = mergeOptions( 37 | Super.options, 38 | extendOptions 39 | ) 40 | Sub['super'] = Super 41 | 42 | // For props and computed properties, we define the proxy getters on 43 | // the Vue instances at extension time, on the extended prototype. This 44 | // avoids Object.defineProperty calls for each instance created. 45 | if (Sub.options.props) { 46 | initProps(Sub) 47 | } 48 | if (Sub.options.computed) { 49 | initComputed(Sub) 50 | } 51 | 52 | // allow further extension/mixin/plugin usage 53 | Sub.extend = Super.extend 54 | Sub.mixin = Super.mixin 55 | Sub.use = Super.use 56 | 57 | // create asset registers, so extended classes 58 | // can have their private assets too. 59 | ASSET_TYPES.forEach(function (type) { 60 | Sub[type] = Super[type] 61 | }) 62 | // enable recursive self-lookup 63 | if (name) { 64 | Sub.options.components[name] = Sub 65 | } 66 | 67 | // keep a reference to the super options at extension time. 68 | // later at instantiation we can check if Super's options have 69 | // been updated. 70 | Sub.superOptions = Super.options 71 | Sub.extendOptions = extendOptions 72 | Sub.sealedOptions = extend({}, Sub.options) 73 | 74 | // cache constructor 75 | cachedCtors[SuperId] = Sub 76 | return Sub 77 | } 78 | } 79 | 80 | function initProps (Comp) { 81 | const props = Comp.options.props 82 | for (const key in props) { 83 | proxy(Comp.prototype, `_props`, key) 84 | } 85 | } 86 | 87 | function initComputed (Comp) { 88 | const computed = Comp.options.computed 89 | for (const key in computed) { 90 | defineComputed(Comp.prototype, key, computed[key]) 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/core/config/index.js: -------------------------------------------------------------------------------- 1 | import { observe, set, del, defineReactive } from '../observer/index.js' 2 | import { mergeOptions, extend, nextTick } from '../utils/index.js' 3 | import builtInComponents from '../components/index.js' 4 | import { initUse } from './use.js' 5 | import { initMixin } from './mixin.js' 6 | import { initExtend } from './extend.js' 7 | import { initAssetRegisters } from './assets.js' 8 | import { ASSET_TYPES} from './constants.js' 9 | import { config } from './config.js' 10 | export * from './constants.js' 11 | 12 | function configurable(Gengine) { 13 | // config 14 | const configDef = {} 15 | configDef.get = () => config 16 | Object.defineProperty(Gengine, 'config', configDef) 17 | 18 | // exposed util methods. 19 | // NOTE: these are not considered part of the public API - avoid relying on 20 | // them unless you are aware of the risk. 21 | Gengine.util = { 22 | extend, 23 | mergeOptions, 24 | defineReactive 25 | } 26 | 27 | Gengine.set = set 28 | Gengine.delete = del 29 | Gengine.nextTick = nextTick 30 | 31 | // 2.6 explicit observable API 32 | Gengine.observable = (obj) => { 33 | observe(obj) 34 | return obj 35 | } 36 | 37 | Gengine.options = Object.create(null) 38 | ASSET_TYPES.forEach((type) => { 39 | Gengine.options[type + 's'] = Object.create(null) 40 | }) 41 | 42 | // this is used to identify the "base" constructor to extend all plain-object 43 | // components with in Weex's multi-instance scenarios. 44 | Gengine.options._base = Gengine 45 | 46 | extend(Gengine.options.components, builtInComponents) 47 | initUse(Gengine) 48 | initMixin(Gengine) 49 | initExtend(Gengine) 50 | initAssetRegisters(Gengine) 51 | } 52 | export default configurable 53 | -------------------------------------------------------------------------------- /src/core/config/mixin.js: -------------------------------------------------------------------------------- 1 | import { mergeOptions } from '../utils/index.js' 2 | 3 | export function initMixin (Gengine) { 4 | Gengine.mixin = function (mixin) { 5 | this.options = mergeOptions(this.options, mixin) 6 | return this 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/core/config/use.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import { toArray } from '../utils/index.js' 3 | 4 | export function initUse (Gengine) { 5 | Gengine.use = function (plugin) { 6 | const installedPlugins = (this._installedPlugins || (this._installedPlugins = [])) 7 | if (installedPlugins.indexOf(plugin) > -1) { 8 | return this 9 | } 10 | // additional parameters 11 | const args = toArray(arguments, 1) 12 | args.unshift(this) 13 | if (typeof plugin.install === 'function') { 14 | plugin.install.apply(plugin, args) 15 | } else if (typeof plugin === 'function') { 16 | plugin.apply(null, args) 17 | } 18 | installedPlugins.push(plugin) 19 | return this 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/core/gdom/helpers/extract-props.js: -------------------------------------------------------------------------------- 1 | import { 2 | isDef, 3 | isUndef, 4 | hasOwn, 5 | hyphenate 6 | } from '../../utils/index.js' 7 | export function extractPropsFromVNodeData ( 8 | data, 9 | Ctor 10 | ) { 11 | // we are only extracting raw values here. 12 | // validation and default values are handled in the child 13 | // component itself. 14 | const propOptions = Ctor.options.props 15 | if (isUndef(propOptions)) { 16 | return 17 | } 18 | const res = {} 19 | const { attrs, props } = data 20 | if (isDef(attrs) || isDef(props)) { 21 | for (const key in propOptions) { 22 | const altKey = hyphenate(key) 23 | checkProp(res, props, key, altKey, true) || 24 | checkProp(res, attrs, key, altKey, false) 25 | } 26 | } 27 | return res 28 | } 29 | 30 | function checkProp ( 31 | res, 32 | hash, 33 | key, 34 | altKey, 35 | preserve 36 | ) { 37 | if (isDef(hash)) { 38 | if (hasOwn(hash, key)) { 39 | res[key] = hash[key] 40 | if (!preserve) { 41 | delete hash[key] 42 | } 43 | return true 44 | } else if (hasOwn(hash, altKey)) { 45 | res[key] = hash[altKey] 46 | if (!preserve) { 47 | delete hash[altKey] 48 | } 49 | return true 50 | } 51 | } 52 | return false 53 | } -------------------------------------------------------------------------------- /src/core/gdom/helpers/get-first-component-child.js: -------------------------------------------------------------------------------- 1 | import { 2 | isDef 3 | } from '../../utils/index.js' 4 | export function getFirstComponentChild (children) { 5 | if (Array.isArray(children)) { 6 | for (let i = 0; i < children.length; i++) { 7 | const c = children[i] 8 | if (isDef(c) && (isDef(c.componentOptions) || isAsyncPlaceholder(c))) { 9 | return c 10 | } 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /src/core/gdom/helpers/index.js: -------------------------------------------------------------------------------- 1 | export * from './merge-hook.js' 2 | export * from './normalize-children.js' 3 | export * from './normalize-scoped-slots.js' 4 | export * from './extract-props.js' 5 | export * from './update-listeners.js' 6 | export * from './is-async-placeholder.js' 7 | export * from './get-first-component-child.js' 8 | -------------------------------------------------------------------------------- /src/core/gdom/helpers/is-async-placeholder.js: -------------------------------------------------------------------------------- 1 | export function isAsyncPlaceholder (node) { 2 | return node.isComment && node.asyncFactory 3 | } -------------------------------------------------------------------------------- /src/core/gdom/helpers/merge-hook.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { VNode } from '../vnode.js' 4 | import { createFnInvoker } from './update-listeners.js' 5 | import { remove, isDef, isUndef, isTrue } from '../../utils/tools.js' 6 | 7 | export function mergeVNodeHook (def, hookKey, hook) { 8 | if (def instanceof VNode) { 9 | def = def.data.hook || (def.data.hook = {}) 10 | } 11 | let invoker 12 | const oldHook = def[hookKey] 13 | 14 | function wrappedHook () { 15 | hook.apply(this, arguments) 16 | // important: remove merged hook to ensure it's called only once 17 | // and prevent memory leak 18 | remove(invoker.fns, wrappedHook) 19 | } 20 | 21 | if (isUndef(oldHook)) { 22 | // no existing hook 23 | invoker = createFnInvoker([wrappedHook]) 24 | } else { 25 | /* istanbul ignore if */ 26 | if (isDef(oldHook.fns) && isTrue(oldHook.merged)) { 27 | // already a merged invoker 28 | invoker = oldHook 29 | invoker.fns.push(wrappedHook) 30 | } else { 31 | // existing plain hook 32 | invoker = createFnInvoker([oldHook, wrappedHook]) 33 | } 34 | } 35 | 36 | invoker.merged = true 37 | def[hookKey] = invoker 38 | } 39 | -------------------------------------------------------------------------------- /src/core/gdom/helpers/normalize-children.js: -------------------------------------------------------------------------------- 1 | import { createTextVNode } from '../vnode.js' 2 | import { isFalse, isTrue, isDef, isUndef, isPrimitive } from '../../utils/index.js' 3 | 4 | // The template compiler attempts to minimize the need for normalization by 5 | // statically analyzing the template at compile time. 6 | // 7 | // For plain HTML markup, normalization can be completely skipped because the 8 | // generated render function is guaranteed to return Array. There are 9 | // two cases where extra normalization is needed: 10 | 11 | // 1. When the children contains components - because a functional component 12 | // may return an Array instead of a single root. In this case, just a simple 13 | // normalization is needed - if any child is an Array, we flatten the whole 14 | // thing with Array.prototype.concat. It is guaranteed to be only 1-level deep 15 | // because functional components already normalize their own children. 16 | export function simpleNormalizeChildren (children) { 17 | for (let i = 0; i < children.length; i++) { 18 | if (Array.isArray(children[i])) { 19 | return Array.prototype.concat.apply([], children) 20 | } 21 | } 22 | return children 23 | } 24 | 25 | // 2. When the children contains constructs that always generated nested Arrays, 26 | // e.g.