├── .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 | [](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 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
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. , , v-for, or when the children is provided by user
27 | // with hand-written render functions / JSX. In such cases a full normalization
28 | // is needed to cater to all possible types of children values.
29 | export function normalizeChildren (children) {
30 | return isPrimitive(children)
31 | ? [createTextVNode(children)]
32 | : Array.isArray(children)
33 | ? normalizeArrayChildren(children)
34 | : undefined
35 | }
36 |
37 | function isTextNode (node) {
38 | return isDef(node) && isDef(node.text) && isFalse(node.isComment)
39 | }
40 |
41 | function normalizeArrayChildren (children, nestedIndex) {
42 | const res = []
43 | let i, c, lastIndex, last
44 | for (i = 0; i < children.length; i++) {
45 | c = children[i]
46 | if (isUndef(c) || typeof c === 'boolean') continue
47 | lastIndex = res.length - 1
48 | last = res[lastIndex]
49 | // nested
50 | if (Array.isArray(c)) {
51 | if (c.length > 0) {
52 | c = normalizeArrayChildren(c, `${nestedIndex || ''}_${i}`)
53 | // merge adjacent text nodes
54 | if (isTextNode(c[0]) && isTextNode(last)) {
55 | res[lastIndex] = createTextVNode(last.text + (c[0]).text)
56 | c.shift()
57 | }
58 | res.push.apply(res, c)
59 | }
60 | } else if (isPrimitive(c)) {
61 | if (isTextNode(last)) {
62 | // merge adjacent text nodes
63 | // this is necessary for SSR hydration because text nodes are
64 | // essentially merged when rendered to HTML strings
65 | res[lastIndex] = createTextVNode(last.text + c)
66 | } else if (c !== '') {
67 | // convert primitive to vnode
68 | res.push(createTextVNode(c))
69 | }
70 | } else {
71 | if (isTextNode(c) && isTextNode(last)) {
72 | // merge adjacent text nodes
73 | res[lastIndex] = createTextVNode(last.text + c.text)
74 | } else {
75 | // default key for nested array children (likely generated by v-for)
76 | if (isTrue(children._isVList) &&
77 | isDef(c.tag) &&
78 | isUndef(c.key) &&
79 | isDef(nestedIndex)) {
80 | c.key = `__vlist${nestedIndex}_${i}__`
81 | }
82 | res.push(c)
83 | }
84 | }
85 | }
86 | return res
87 | }
--------------------------------------------------------------------------------
/src/core/gdom/helpers/normalize-scoped-slots.js:
--------------------------------------------------------------------------------
1 | import { def, emptyObject } from '../../utils/index.js'
2 | import { normalizeChildren } from './normalize-children.js'
3 | export function normalizeScopedSlots (
4 | slots,
5 | normalSlots,
6 | prevSlots
7 | ) {
8 | let res
9 | const hasNormalSlots = Object.keys(normalSlots).length > 0
10 | const isStable = slots ? !!slots.$stable : !hasNormalSlots
11 | const key = slots && slots.$key
12 | if (!slots) {
13 | res = {}
14 | } else if (slots._normalized) {
15 | // fast path 1: child component re-render only, parent did not change
16 | return slots._normalized
17 | } else if (
18 | isStable &&
19 | prevSlots &&
20 | prevSlots !== emptyObject &&
21 | key === prevSlots.$key &&
22 | !hasNormalSlots &&
23 | !prevSlots.$hasNormal
24 | ) {
25 | // fast path 2: stable scoped slots w/ no normal slots to proxy,
26 | // only need to normalize once
27 | return prevSlots
28 | } else {
29 | res = {}
30 | for (const key in slots) {
31 | if (slots[key] && key[0] !== '$') {
32 | res[key] = normalizeScopedSlot(normalSlots, key, slots[key])
33 | }
34 | }
35 | }
36 | // expose normal slots on scopedSlots
37 | for (const key in normalSlots) {
38 | if (!(key in res)) {
39 | res[key] = proxyNormalSlot(normalSlots, key)
40 | }
41 | }
42 | // avoriaz seems to mock a non-extensible $scopedSlots object
43 | // and when that is passed down this would cause an error
44 | if (slots && Object.isExtensible(slots)) {
45 | (slots)._normalized = res
46 | }
47 | def(res, '$stable', isStable)
48 | def(res, '$key', key)
49 | def(res, '$hasNormal', hasNormalSlots)
50 | return res
51 | }
52 |
53 | function normalizeScopedSlot(normalSlots, key, fn) {
54 | const normalized = function () {
55 | let res = arguments.length ? fn.apply(null, arguments) : fn({})
56 | res = res && typeof res === 'object' && !Array.isArray(res)
57 | ? [res] // single vnode
58 | : normalizeChildren(res)
59 | return res && (
60 | res.length === 0 ||
61 | (res.length === 1 && res[0].isComment) // #9658
62 | ) ? undefined
63 | : res
64 | }
65 | // this is a slot using the new v-slot syntax without scope. although it is
66 | // compiled as a scoped slot, render fn users would expect it to be present
67 | // on this.$slots because the usage is semantically a normal slot.
68 | if (fn.proxy) {
69 | Object.defineProperty(normalSlots, key, {
70 | get: normalized,
71 | enumerable: true,
72 | configurable: true
73 | })
74 | }
75 | return normalized
76 | }
77 |
78 | function proxyNormalSlot(slots, key) {
79 | return () => slots[key]
80 | }
--------------------------------------------------------------------------------
/src/core/gdom/helpers/resolve-async-component.js:
--------------------------------------------------------------------------------
1 | import {
2 | once,
3 | isDef,
4 | isUndef,
5 | isTrue,
6 | isObject,
7 | hasSymbol,
8 | isPromise,
9 | remove
10 | } from '../../utils/index.js'
11 | import { createEmptyVNode } from '../vnode.js'
12 | import { currentRenderingInstance } from '../../base/render-instance.js'
13 | function ensureCtor (comp, base) {
14 | if (
15 | comp.__esModule ||
16 | (hasSymbol && comp[Symbol.toStringTag] === 'Module')
17 | ) {
18 | comp = comp.default
19 | }
20 | return isObject(comp)
21 | ? base.extend(comp)
22 | : comp
23 | }
24 |
25 | export function createAsyncPlaceholder (
26 | factory,
27 | data,
28 | context,
29 | children,
30 | tag
31 | ) {
32 | const node = createEmptyVNode()
33 | node.asyncFactory = factory
34 | node.asyncMeta = { data, context, children, tag }
35 | return node
36 | }
37 |
38 | export function resolveAsyncComponent (
39 | factory,
40 | baseCtor
41 | ) {
42 | if (isTrue(factory.error) && isDef(factory.errorComp)) {
43 | return factory.errorComp
44 | }
45 |
46 | if (isDef(factory.resolved)) {
47 | return factory.resolved
48 | }
49 |
50 | const owner = currentRenderingInstance
51 | if (owner && isDef(factory.owners) && factory.owners.indexOf(owner) === -1) {
52 | // already pending
53 | factory.owners.push(owner)
54 | }
55 |
56 | if (isTrue(factory.loading) && isDef(factory.loadingComp)) {
57 | return factory.loadingComp
58 | }
59 |
60 | if (owner && !isDef(factory.owners)) {
61 | const owners = factory.owners = [owner]
62 | let sync = true
63 | let timerLoading = null
64 | let timerTimeout = null
65 |
66 | ;(owner).$on('hook:destroyed', () => remove(owners, owner))
67 |
68 | const forceRender = (renderCompleted) => {
69 | for (let i = 0, l = owners.length; i < l; i++) {
70 | (owners[i]).$forceUpdate()
71 | }
72 |
73 | if (renderCompleted) {
74 | owners.length = 0
75 | if (timerLoading !== null) {
76 | clearTimeout(timerLoading)
77 | timerLoading = null
78 | }
79 | if (timerTimeout !== null) {
80 | clearTimeout(timerTimeout)
81 | timerTimeout = null
82 | }
83 | }
84 | }
85 |
86 | const resolve = once((res) => {
87 | // cache resolved
88 | factory.resolved = ensureCtor(res, baseCtor)
89 | // invoke callbacks only if this is not a synchronous resolve
90 | // (async resolves are shimmed as synchronous during SSR)
91 | if (!sync) {
92 | forceRender(true)
93 | } else {
94 | owners.length = 0
95 | }
96 | })
97 |
98 | const reject = once((reason) => {
99 | if (isDef(factory.errorComp)) {
100 | factory.error = true
101 | forceRender(true)
102 | }
103 | })
104 |
105 | const res = factory(resolve, reject)
106 |
107 | if (isObject(res)) {
108 | if (isPromise(res)) {
109 | // () => Promise
110 | if (isUndef(factory.resolved)) {
111 | res.then(resolve, reject)
112 | }
113 | } else if (isPromise(res.component)) {
114 | res.component.then(resolve, reject)
115 |
116 | if (isDef(res.error)) {
117 | factory.errorComp = ensureCtor(res.error, baseCtor)
118 | }
119 |
120 | if (isDef(res.loading)) {
121 | factory.loadingComp = ensureCtor(res.loading, baseCtor)
122 | if (res.delay === 0) {
123 | factory.loading = true
124 | } else {
125 | timerLoading = setTimeout(() => {
126 | timerLoading = null
127 | if (isUndef(factory.resolved) && isUndef(factory.error)) {
128 | factory.loading = true
129 | forceRender(false)
130 | }
131 | }, res.delay || 200)
132 | }
133 | }
134 |
135 | if (isDef(res.timeout)) {
136 | timerTimeout = setTimeout(() => {
137 | timerTimeout = null
138 | if (isUndef(factory.resolved)) {
139 | reject(
140 | null
141 | )
142 | }
143 | }, res.timeout)
144 | }
145 | }
146 | }
147 | sync = false
148 | // return in case resolved synchronously
149 | return factory.loading
150 | ? factory.loadingComp
151 | : factory.resolved
152 | }
153 | }
154 |
--------------------------------------------------------------------------------
/src/core/gdom/helpers/update-listeners.js:
--------------------------------------------------------------------------------
1 | import {
2 | cached,
3 | isUndef,
4 | isTrue,
5 | isPlainObject
6 | } from '../../utils/tools.js'
7 | import { invokeWithErrorHandling } from '../../utils/error.js'
8 | const normalizeEvent = cached((name) => {
9 | const passive = name.charAt(0) === '&'
10 | name = passive ? name.slice(1) : name
11 | const once = name.charAt(0) === '~' // Prefixed last, checked first
12 | name = once ? name.slice(1) : name
13 | const capture = name.charAt(0) === '!'
14 | name = capture ? name.slice(1) : name
15 | return {
16 | name,
17 | once,
18 | capture,
19 | passive
20 | }
21 | })
22 |
23 | export function createFnInvoker (fns, gm) {
24 | function invoker () {
25 | const fns = invoker.fns
26 | if (Array.isArray(fns)) {
27 | const cloned = fns.slice()
28 | for (let i = 0; i < cloned.length; i++) {
29 | invokeWithErrorHandling(cloned[i], null, arguments, gm, `v-on handler`)
30 | }
31 | } else {
32 | // return handler return value for single handlers
33 | return invokeWithErrorHandling(fns, null, arguments, gm, `v-on handler`)
34 | }
35 | }
36 | invoker.fns = fns
37 | return invoker
38 | }
39 |
40 | export function updateListeners (
41 | on,
42 | oldOn,
43 | add,
44 | remove,
45 | createOnceHandler,
46 | gm
47 | ) {
48 | let name, def, cur, old, event
49 | for (name in on) {
50 | def = cur = on[name]
51 | old = oldOn[name]
52 | event = normalizeEvent(name)
53 | /* istanbul ignore if __WEEX__*/
54 | if (false && isPlainObject(def)) {
55 | cur = def.handler
56 | event.params = def.params
57 | }
58 | if (isUndef(cur)) {
59 | } else if (isUndef(old)) {
60 | if (isUndef(cur.fns)) {
61 | cur = on[name] = createFnInvoker(cur, gm)
62 | }
63 | if (isTrue(event.once)) {
64 | cur = on[name] = createOnceHandler(event.name, cur, event.capture)
65 | }
66 | add(event.name, cur, event.capture, event.passive, event.params)
67 | } else if (cur !== old) {
68 | old.fns = cur
69 | on[name] = old
70 | }
71 | }
72 | for (name in oldOn) {
73 | if (isUndef(on[name])) {
74 | event = normalizeEvent(name)
75 | remove(event.name, oldOn[name], event.capture)
76 | }
77 | }
78 | }
--------------------------------------------------------------------------------
/src/core/gdom/index.js:
--------------------------------------------------------------------------------
1 | export * from './create.js'
2 | export * from './vnode.js'
3 | export * from './helpers/index.js'
4 |
--------------------------------------------------------------------------------
/src/core/gdom/modules/directives.js:
--------------------------------------------------------------------------------
1 | /* @flow */
2 |
3 | import { emptyNode } from '../patch.js'
4 | import { resolveAsset } from '../../utils/options.js'
5 | import { handleError } from '../../utils/error.js'
6 | import { mergeVNodeHook } from '../helpers/merge-hook.js'
7 |
8 | export default {
9 | create: updateDirectives,
10 | update: updateDirectives,
11 | destroy: function unbindDirectives (vnode) {
12 | updateDirectives(vnode, emptyNode)
13 | }
14 | }
15 |
16 | function updateDirectives (oldVnode, vnode) {
17 | if (oldVnode.data.directives || vnode.data.directives) {
18 | _update(oldVnode, vnode)
19 | }
20 | }
21 |
22 | function _update (oldVnode, vnode) {
23 | const isCreate = oldVnode === emptyNode
24 | const isDestroy = vnode === emptyNode
25 | const oldDirs = normalizeDirectives(oldVnode.data.directives, oldVnode.context)
26 | const newDirs = normalizeDirectives(vnode.data.directives, vnode.context)
27 |
28 | const dirsWithInsert = []
29 | const dirsWithPostpatch = []
30 |
31 | let key, oldDir, dir
32 | for (key in newDirs) {
33 | oldDir = oldDirs[key]
34 | dir = newDirs[key]
35 | if (!oldDir) {
36 | // new directive, bind
37 | callHook(dir, 'bind', vnode, oldVnode)
38 | if (dir.def && dir.def.inserted) {
39 | dirsWithInsert.push(dir)
40 | }
41 | } else {
42 | // existing directive, update
43 | dir.oldValue = oldDir.value
44 | dir.oldArg = oldDir.arg
45 | callHook(dir, 'update', vnode, oldVnode)
46 | if (dir.def && dir.def.componentUpdated) {
47 | dirsWithPostpatch.push(dir)
48 | }
49 | }
50 | }
51 |
52 | if (dirsWithInsert.length) {
53 | const callInsert = () => {
54 | for (let i = 0; i < dirsWithInsert.length; i++) {
55 | callHook(dirsWithInsert[i], 'inserted', vnode, oldVnode)
56 | }
57 | }
58 | if (isCreate) {
59 | mergeVNodeHook(vnode, 'insert', callInsert)
60 | } else {
61 | callInsert()
62 | }
63 | }
64 |
65 | if (dirsWithPostpatch.length) {
66 | mergeVNodeHook(vnode, 'postpatch', () => {
67 | for (let i = 0; i < dirsWithPostpatch.length; i++) {
68 | callHook(dirsWithPostpatch[i], 'componentUpdated', vnode, oldVnode)
69 | }
70 | })
71 | }
72 |
73 | if (!isCreate) {
74 | for (key in oldDirs) {
75 | if (!newDirs[key]) {
76 | // no longer present, unbind
77 | callHook(oldDirs[key], 'unbind', oldVnode, oldVnode, isDestroy)
78 | }
79 | }
80 | }
81 | }
82 |
83 | const emptyModifiers = Object.create(null)
84 |
85 | function normalizeDirectives (
86 | dirs,
87 | vm
88 | ) {
89 | const res = Object.create(null)
90 | if (!dirs) {
91 | // $flow-disable-line
92 | return res
93 | }
94 | let i, dir
95 | for (i = 0; i < dirs.length; i++) {
96 | dir = dirs[i]
97 | if (!dir.modifiers) {
98 | // $flow-disable-line
99 | dir.modifiers = emptyModifiers
100 | }
101 | res[getRawDirName(dir)] = dir
102 | dir.def = resolveAsset(vm.$options, 'directives', dir.name, true)
103 | }
104 | // $flow-disable-line
105 | return res
106 | }
107 |
108 | function getRawDirName (dir) {
109 | return dir.rawName || `${dir.name}.${Object.keys(dir.modifiers || {}).join('.')}`
110 | }
111 |
112 | function callHook (dir, hook, vnode, oldVnode, isDestroy) {
113 | const fn = dir.def && dir.def[hook]
114 | if (fn) {
115 | try {
116 | fn(vnode.elm, dir, vnode, oldVnode, isDestroy)
117 | } catch (e) {
118 | handleError(e, vnode.context, `directive ${dir.name} ${hook} hook`)
119 | }
120 | }
121 | }
122 |
--------------------------------------------------------------------------------
/src/core/gdom/modules/index.js:
--------------------------------------------------------------------------------
1 | import directives from './directives'
2 | import ref from './ref'
3 |
4 | export default [
5 | ref,
6 | directives
7 | ]
8 |
--------------------------------------------------------------------------------
/src/core/gdom/modules/ref.js:
--------------------------------------------------------------------------------
1 | /* @flow */
2 |
3 | import { remove, isDef } from '../../utils/tools.js'
4 |
5 | export default {
6 | create (_, vnode) {
7 | registerRef(vnode)
8 | },
9 | update (oldVnode, vnode) {
10 | if (oldVnode.data.ref !== vnode.data.ref) {
11 | registerRef(oldVnode, true)
12 | registerRef(vnode)
13 | }
14 | },
15 | destroy (vnode) {
16 | registerRef(vnode, true)
17 | }
18 | }
19 |
20 | export function registerRef (vnode, isRemoval) {
21 | const key = vnode.data.ref
22 | if (!isDef(key)) return
23 |
24 | const vm = vnode.context
25 | const ref = vnode.componentInstance || vnode.elm
26 | const refs = vm.$refs
27 | if (isRemoval) {
28 | if (Array.isArray(refs[key])) {
29 | remove(refs[key], ref)
30 | } else if (refs[key] === ref) {
31 | refs[key] = undefined
32 | }
33 | } else {
34 | if (vnode.data.refInFor) {
35 | if (!Array.isArray(refs[key])) {
36 | refs[key] = [ref]
37 | } else if (refs[key].indexOf(ref) < 0) {
38 | // $flow-disable-line
39 | refs[key].push(ref)
40 | }
41 | } else {
42 | refs[key] = ref
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/core/gdom/vnode.js:
--------------------------------------------------------------------------------
1 | /* @flow */
2 | export class VNode {
3 | constructor (
4 | tag,
5 | data,
6 | children,
7 | text,
8 | elm,
9 | context,
10 | componentOptions,
11 | asyncFactory
12 | ) {
13 | this.tag = tag
14 | this.data = data
15 | this.children = children
16 | this.text = text
17 | this.elm = elm
18 | this.ns = undefined
19 | this.context = context
20 | this.fnContext = undefined
21 | this.fnOptions = undefined
22 | this.fnScopeId = undefined
23 | this.key = data && data.key
24 | this.componentOptions = componentOptions
25 | this.componentInstance = undefined
26 | this.parent = undefined
27 | this.raw = false
28 | this.isStatic = false
29 | this.isRootInsert = true
30 | this.isComment = false
31 | this.isCloned = false
32 | this.isOnce = false
33 | this.asyncFactory = asyncFactory
34 | this.asyncMeta = undefined
35 | this.isAsyncPlaceholder = false
36 | }
37 |
38 | // DEPRECATED: alias for componentInstance for backwards compat.
39 | /* istanbul ignore next */
40 | get child () {
41 | return this.componentInstance
42 | }
43 | }
44 | export const createEmptyVNode = (text) => {
45 | const node = new VNode()
46 | node.text = text
47 | node.isComment = true
48 | return node
49 | }
50 |
51 | export function createTextVNode (val) {
52 | return new VNode(undefined, undefined, undefined, String(val))
53 | }
54 |
55 | // optimized shallow clone
56 | // used for static nodes and slot nodes because they may be reused across
57 | // multiple renders, cloning them avoids errors when DOM manipulations rely
58 | // on their elm reference.
59 | export function cloneVNode (vnode) {
60 | const cloned = new VNode(
61 | vnode.tag,
62 | vnode.data,
63 | // #7975
64 | // clone children array to avoid mutating original in case of cloning
65 | // a child.
66 | vnode.children && vnode.children.slice(),
67 | vnode.text,
68 | vnode.elm,
69 | vnode.context,
70 | vnode.componentOptions,
71 | vnode.asyncFactory
72 | )
73 | cloned.ns = vnode.ns
74 | cloned.isStatic = vnode.isStatic
75 | cloned.key = vnode.key
76 | cloned.isComment = vnode.isComment
77 | cloned.fnContext = vnode.fnContext
78 | cloned.fnOptions = vnode.fnOptions
79 | cloned.fnScopeId = vnode.fnScopeId
80 | cloned.asyncMeta = vnode.asyncMeta
81 | cloned.isCloned = true
82 | return cloned
83 | }
84 |
--------------------------------------------------------------------------------
/src/core/gengine/index.js:
--------------------------------------------------------------------------------
1 | import Gengine from '../base/index.js'
2 | export default Gengine
3 |
--------------------------------------------------------------------------------
/src/core/index.js:
--------------------------------------------------------------------------------
1 | import Gengine from './gengine/index.js'
2 | export default Gengine
3 |
--------------------------------------------------------------------------------
/src/core/observer/array.js:
--------------------------------------------------------------------------------
1 |
2 | import { def } from '../utils/lang.js'
3 | const arrayProto = Array.prototype
4 | export const arrayMethods = Object.create(arrayProto)
5 |
6 | const methodsToPatch = [
7 | 'push',
8 | 'pop',
9 | 'shift',
10 | 'unshift',
11 | 'splice',
12 | 'sort',
13 | 'reverse'
14 | ]
15 |
16 | /**
17 | * Intercept mutating methods and emit events
18 | */
19 | methodsToPatch.forEach(function (method) {
20 | // cache original method
21 | const original = arrayProto[method]
22 | def(arrayMethods, method, function mutator (...args) {
23 | const result = original.apply(this, args)
24 | const ob = this.__ob__
25 | let inserted
26 | switch (method) {
27 | case 'push':
28 | case 'unshift':
29 | inserted = args
30 | break
31 | case 'splice':
32 | inserted = args.slice(2)
33 | break
34 | }
35 | if (inserted) ob.observeArray(inserted)
36 | // notify change
37 | ob.dep.notify()
38 | return result
39 | })
40 | })
--------------------------------------------------------------------------------
/src/core/observer/augement.js:
--------------------------------------------------------------------------------
1 | import { def } from '../utils/lang.js'
2 | /**
3 | * Augment a target Object or Array by intercepting
4 | * the prototype chain using __proto__
5 | */
6 | export function protoAugment (target, src) {
7 | /* eslint-disable no-proto */
8 | target.__proto__ = src
9 | /* eslint-enable no-proto */
10 | }
11 |
12 | /**
13 | * Augment a target Object or Array by defining
14 | * hidden properties.
15 | */
16 | /* istanbul ignore next */
17 | export function copyAugment (target, src, keys) {
18 | for (let i = 0, l = keys.length; i < l; i++) {
19 | const key = keys[i]
20 | def(target, key, src[key])
21 | }
22 | }
--------------------------------------------------------------------------------
/src/core/observer/dep.js:
--------------------------------------------------------------------------------
1 | import { remove } from '../utils/tools.js'
2 | import { config } from '../config/config.js'
3 | let uid = 0
4 | export class Dep {
5 | constructor() {
6 | this.id = uid++
7 | this.subs = []
8 | }
9 | addSub(sub) {
10 | this.subs.push(sub)
11 | }
12 | depend() {
13 | if (Dep.target) {
14 | Dep.target.addDep(this)
15 | }
16 | }
17 | removeSub(sub) {
18 | remove(this.subs, sub)
19 | }
20 | notify() {
21 | const subs = this.subs.slice()
22 | if (!config.async) {
23 | subs.sort((a, b) => a.id - b.id)
24 | }
25 | subs.forEach(function(sub) {
26 | sub.update()
27 | })
28 | }
29 | }
30 | Dep.target = null
31 | const targetStack = []
32 |
33 | export function pushTarget (target) {
34 | targetStack.push(target)
35 | Dep.target = target
36 | }
37 |
38 | export function popTarget () {
39 | targetStack.pop()
40 | Dep.target = targetStack[targetStack.length - 1]
41 | }
--------------------------------------------------------------------------------
/src/core/observer/index.js:
--------------------------------------------------------------------------------
1 | export * from './array.js'
2 | export * from './augement.js'
3 | export * from './dep.js'
4 | export * from './observer.js'
5 | export * from './scheduler.js'
6 | export * from './traverse.js'
7 |
--------------------------------------------------------------------------------
/src/core/observer/scheduler.js:
--------------------------------------------------------------------------------
1 | import { config } from '../config/config.js'
2 | import { callHook, activateChildComponent } from '../base/active.js'
3 | import { devtools } from '../utils/env.js'
4 | import { nextTick } from '../utils/next-tick.js'
5 | let flushing = false
6 | const queue = []
7 | let waiting = false
8 | let index = 0
9 | let has = {}
10 | const activatedChildren = []
11 | export let currentFlushTimestamp = 0
12 | // Async edge case fix requires storing an event listener's attach timestamp.
13 | let getNow = () => Date.now
14 |
15 | /**
16 | * Reset the scheduler's state.
17 | */
18 | function resetSchedulerState () {
19 | index = queue.length = activatedChildren.length = 0
20 | has = {}
21 | waiting = flushing = false
22 | }
23 |
24 | function callActivatedHooks (queue) {
25 | for (let i = 0; i < queue.length; i++) {
26 | queue[i]._inactive = true
27 | activateChildComponent(queue[i], true /* true */)
28 | }
29 | }
30 |
31 | function callUpdatedHooks (queue) {
32 | let i = queue.length
33 | while (i--) {
34 | const watcher = queue[i]
35 | const gm = watcher.gm
36 | if (gm._watcher === watcher && gm._isMounted && !gm._isDestroyed) {
37 | callHook(gm, 'updated')
38 | }
39 | }
40 | }
41 | /**
42 | * Flush both queues and run the watchers.
43 | */
44 | function flushSchedulerQueue () {
45 | currentFlushTimestamp = getNow()
46 | flushing = true
47 | let watcher, id
48 | // Sort queue before flush.
49 | // This ensures that:
50 | // 1. Components are updated from parent to child. (because parent is always
51 | // created before the child)
52 | // 2. A component's user watchers are run before its render watcher (because
53 | // user watchers are created before the render watcher)
54 | // 3. If a component is destroyed during a parent component's watcher run,
55 | // its watchers can be skipped.
56 | queue.sort((a, b) => a.id - b.id)
57 | // do not cache length because more watchers might be pushed
58 | // as we run existing watchers
59 | for (index = 0; index < queue.length; index++) {
60 | watcher = queue[index]
61 | if (watcher.before) {
62 | watcher.before()
63 | }
64 | id = watcher.id
65 | has[id] = null
66 | watcher.run()
67 | }
68 | // keep copies of post queues before resetting state
69 | const activatedQueue = activatedChildren.slice()
70 | const updatedQueue = queue.slice()
71 |
72 | resetSchedulerState()
73 |
74 | // call component updated and activated hooks
75 | callActivatedHooks(activatedQueue)
76 | callUpdatedHooks(updatedQueue)
77 |
78 | // devtool hook
79 | /* istanbul ignore if */
80 | if (devtools && config.devtools) {
81 | devtools.emit('flush')
82 | }
83 | }
84 | /**
85 | * Queue a kept-alive component that was activated during patch.
86 | * The queue will be processed after the entire tree has been patched.
87 | */
88 | export function queueActivatedComponent (gm) {
89 | // setting _inactive to false here so that a render function can
90 | // rely on checking whether it's in an inactive tree (e.g. router-view)
91 | gm._inactive = false
92 | activatedChildren.push(gm)
93 | }
94 | /**
95 | * Push a watcher into the watcher queue.
96 | * Jobs with duplicate IDs will be skipped unless it's
97 | * pushed when the queue is being flushed.
98 | */
99 | export function queueWatcher (watcher) {
100 | const id = watcher.id
101 | if (has[id] == null) {
102 | has[id] = true
103 | if (!flushing) {
104 | queue.push(watcher)
105 | } else {
106 | // if already flushing, splice the watcher based on its id
107 | // if already past its id, it will be run next immediately.
108 | let i = queue.length - 1
109 | while (i > index && queue[i].id > watcher.id) {
110 | i--
111 | }
112 | queue.splice(i + 1, 0, watcher)
113 | }
114 | // queue the flush
115 | if (!waiting) {
116 | waiting = true
117 | nextTick(flushSchedulerQueue)
118 | }
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/src/core/observer/traverse.js:
--------------------------------------------------------------------------------
1 | import { isObject } from '../utils/tools.js'
2 | import { _Set } from '../utils/env.js'
3 | import { VNode } from '../gdom/vnode.js'
4 | const seenObjects = new _Set()
5 | /**
6 | * Recursively traverse an object to evoke all converted
7 | * getters, so that every nested property inside the object
8 | * is collected as a "deep" dependency.
9 | */
10 | export function traverse (val) {
11 | _traverse(val, seenObjects)
12 | seenObjects.clear()
13 | }
14 |
15 | function _traverse (val, seen) {
16 | let i, keys
17 | const isA = Array.isArray(val)
18 | if ((!isA && !isObject(val)) || Object.isFrozen(val) || val instanceof VNode) {
19 | return
20 | }
21 | if (val.__ob__) {
22 | const depId = val.__ob__.dep.id
23 | if (seen.has(depId)) {
24 | return
25 | }
26 | seen.add(depId)
27 | }
28 | if (isA) {
29 | i = val.length
30 | while (i--) _traverse(val[i], seen)
31 | } else {
32 | keys = Object.keys(val)
33 | i = keys.length
34 | while (i--) _traverse(val[keys[i]], seen)
35 | }
36 | }
--------------------------------------------------------------------------------
/src/core/utils/env.js:
--------------------------------------------------------------------------------
1 | export const inBrowser = typeof window !== 'undefined'
2 | export const inWeex = typeof WXEnvironment !== 'undefined' && !!WXEnvironment.platform
3 | export const weexPlatform = inWeex && WXEnvironment.platform.toLowerCase()
4 | export const UA = inBrowser && window.navigator.userAgent.toLowerCase()
5 | export const isIE = UA && /msie|trident/.test(UA)
6 | export const isIE9 = UA && UA.indexOf('msie 9.0') > 0
7 | export const isEdge = UA && UA.indexOf('edge/') > 0
8 | export const isAndroid = (UA && UA.indexOf('android') > 0) || (weexPlatform === 'android')
9 | export const isIOS = (UA && /iphone|ipad|ipod|ios/.test(UA)) || (weexPlatform === 'ios')
10 | export const isChrome = UA && /chrome\/\d+/.test(UA) && !isEdge
11 | export const isPhantomJS = UA && /phantomjs/.test(UA)
12 | export const isFF = UA && UA.match(/firefox\/(\d+)/)
13 |
14 | export const hasProto = '__proto__' in {}
15 | export const nativeWatch = ({}).watch
16 | export const isServerRendering = () => false
17 | export let supportsPassive = false
18 | /* istanbul ignore next */
19 | export function isNative (Ctor) {
20 | return typeof Ctor === 'function' && /native code/.test(Ctor.toString())
21 | }
22 | export const devtools = typeof window !== 'undefined' && window.__VUE_DEVTOOLS_GLOBAL_HOOK__
23 | export const hasSymbol =
24 | typeof Symbol !== 'undefined' && isNative(Symbol) &&
25 | typeof Reflect !== 'undefined' && isNative(Reflect.ownKeys)
26 | let _Set
27 | /* istanbul ignore if */ // $flow-disable-line
28 | if (typeof Set !== 'undefined' && isNative(Set)) {
29 | // use native Set when available.
30 | _Set = Set
31 | } else {
32 | // a non-standard Set polyfill that only works with primitive keys.
33 | _Set = class Set extends SimpleSet {
34 | constructor () {
35 | super(...args)
36 | this.set = Object.create(null)
37 | }
38 | has (key) {
39 | return this.set[key] === true
40 | }
41 | add (key) {
42 | this.set[key] = true
43 | }
44 | clear () {
45 | this.set = Object.create(null)
46 | }
47 | }
48 | }
49 | export class SimpleSet {
50 | has(key) {}
51 | add(key) {}
52 | clear() {}
53 | }
54 | export { _Set }
55 |
--------------------------------------------------------------------------------
/src/core/utils/error.js:
--------------------------------------------------------------------------------
1 | import { config } from '../config/config.js'
2 | import { isPromise } from './tools.js'
3 | import { pushTarget, popTarget } from '../observer/dep.js'
4 | export function handleError (err, vm, info) {
5 | // Deactivate deps tracking while processing error handler to avoid possible infinite rendering.
6 | // See: https://github.com/vuejs/vuex/issues/1505
7 | pushTarget()
8 | try {
9 | if (vm) {
10 | let cur = vm
11 | while ((cur = cur.$parent)) {
12 | const hooks = cur.$options.errorCaptured
13 | if (hooks) {
14 | for (let i = 0; i < hooks.length; i++) {
15 | try {
16 | const capture = hooks[i].call(cur, err, vm, info) === false
17 | if (capture) return
18 | } catch (e) {
19 | globalHandleError(e, cur, 'errorCaptured hook')
20 | }
21 | }
22 | }
23 | }
24 | }
25 | globalHandleError(err, vm, info)
26 | } finally {
27 | popTarget()
28 | }
29 | }
30 | export function invokeWithErrorHandling (
31 | handler,
32 | context,
33 | args,
34 | gm,
35 | info
36 | ) {
37 | let res
38 | try {
39 | res = args ? handler.apply(context, args) : handler.call(context)
40 | if (res && !res._isGengine && isPromise(res) && !res._handled) {
41 | res.catch(e => handleError(e, gm, info + ` (Promise/async)`))
42 | // issue #9511
43 | // avoid catch triggering multiple times when nested calls
44 | res._handled = true
45 | }
46 | } catch (e) {
47 | handleError(e, gm, info)
48 | }
49 | return res
50 | }
51 | function globalHandleError (err, vm, info) {
52 | if (config.errorHandler) {
53 | try {
54 | return config.errorHandler.call(null, err, vm, info)
55 | } catch (e) {
56 | // if the user intentionally throws the original error in the handler,
57 | // do not log it twice
58 | if (e !== err) {
59 | logError(e, null, 'config.errorHandler')
60 | }
61 | }
62 | }
63 | logError(err, vm, info)
64 | }
65 | function logError (err, gm, info) {
66 | /* istanbul ignore else */
67 | if (typeof console !== 'undefined') {
68 | console.error(err)
69 | } else {
70 | throw err
71 | }
72 | }
--------------------------------------------------------------------------------
/src/core/utils/index.js:
--------------------------------------------------------------------------------
1 | export * from './tools.js'
2 | export * from './lang.js'
3 | export * from './env.js'
4 | export * from './error.js'
5 | export * from './options.js'
6 | export * from './props.js'
7 | export * from './next-tick'
8 | export { defineReactive } from '../observer/observer.js'
--------------------------------------------------------------------------------
/src/core/utils/lang.js:
--------------------------------------------------------------------------------
1 | export const unicodeRegExp = /a-zA-Z\u00B7\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u037D\u037F-\u1FFF\u200C-\u200D\u203F-\u2040\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD/
2 | /**
3 | * Check if a string starts with $ or _
4 | */
5 | export function isReserved (str) {
6 | const c = (str + '').charCodeAt(0)
7 | return c === 0x24 || c === 0x5F
8 | }
9 | /**
10 | * Define a property.
11 | */
12 | export function def (obj, key, val, enumerable) {
13 | Object.defineProperty(obj, key, {
14 | value: val,
15 | enumerable: !!enumerable,
16 | writable: true,
17 | configurable: true
18 | })
19 | }
20 | /**
21 | * Parse simple path.
22 | */
23 | const bailRE = new RegExp(`[^${unicodeRegExp.source}.$_\\d]`)
24 | export function parsePath (path) {
25 | if (bailRE.test(path)) {
26 | return
27 | }
28 | const segments = path.split('.')
29 | return function (obj) {
30 | for (let i = 0; i < segments.length; i++) {
31 | if (!obj) return
32 | obj = obj[segments[i]]
33 | }
34 | return obj
35 | }
36 | }
--------------------------------------------------------------------------------
/src/core/utils/next-tick.js:
--------------------------------------------------------------------------------
1 | import { handleError } from './error.js'
2 | import { isNative } from './env.js'
3 | /* @flow */
4 | /* globals MutationObserver */
5 | export let isUsingMicroTask = false
6 |
7 | const callbacks = []
8 | let pending = false
9 |
10 | function flushCallbacks () {
11 | pending = false
12 | const copies = callbacks.slice(0)
13 | callbacks.length = 0
14 | for (let i = 0; i < copies.length; i++) {
15 | copies[i]()
16 | }
17 | }
18 |
19 | // Here we have async deferring wrappers using microtasks.
20 | // In 2.5 we used (macro) tasks (in combination with microtasks).
21 | // However, it has subtle problems when state is changed right before repaint
22 | // (e.g. #6813, out-in transitions).
23 | // Also, using (macro) tasks in event handler would cause some weird behaviors
24 | // that cannot be circumvented (e.g. #7109, #7153, #7546, #7834, #8109).
25 | // So we now use microtasks everywhere, again.
26 | // A major drawback of this tradeoff is that there are some scenarios
27 | // where microtasks have too high a priority and fire in between supposedly
28 | // sequential events (e.g. #4521, #6690, which have workarounds)
29 | // or even between bubbling of the same event (#6566).
30 | let timerFunc
31 |
32 | // The nextTick behavior leverages the microtask queue, which can be accessed
33 | // via either native Promise.then or MutationObserver.
34 | // MutationObserver has wider support, however it is seriously bugged in
35 | // UIWebView in iOS >= 9.3.3 when triggered in touch event handlers. It
36 | // completely stops working after triggering a few times... so, if native
37 | // Promise is available, we will use it:
38 | /* istanbul ignore next, $flow-disable-line */
39 | // if (typeof Promise !== 'undefined' && isNative(Promise)) {
40 | // const p = Promise.resolve()
41 | // timerFunc = () => {
42 | // p.then(flushCallbacks)
43 | // // In problematic UIWebViews, Promise.then doesn't completely break, but
44 | // // it can get stuck in a weird state where callbacks are pushed into the
45 | // // microtask queue but the queue isn't being flushed, until the browser
46 | // // needs to do some other work, e.g. handle a timer. Therefore we can
47 | // // "force" the microtask queue to be flushed by adding an empty timer.
48 | // // if (isIOS) setTimeout(noop)
49 | // }
50 | // isUsingMicroTask = true
51 | // } else if (!isIE && typeof MutationObserver !== 'undefined' && (
52 | // isNative(MutationObserver) ||
53 | // // PhantomJS and iOS 7.x
54 | // MutationObserver.toString() === '[object MutationObserverConstructor]'
55 | // )) {
56 | // // Use MutationObserver where native Promise is not available,
57 | // // e.g. PhantomJS, iOS7, Android 4.4
58 | // // (#6466 MutationObserver is unreliable in IE11)
59 | // let counter = 1
60 | // const observer = new MutationObserver(flushCallbacks)
61 | // const textNode = document.createTextNode(String(counter))
62 | // observer.observe(textNode, {
63 | // characterData: true
64 | // })
65 | // timerFunc = () => {
66 | // counter = (counter + 1) % 2
67 | // textNode.data = String(counter)
68 | // }
69 | // isUsingMicroTask = true
70 | // } else
71 | if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
72 | // Fallback to setImmediate.
73 | // Techinically it leverages the (macro) task queue,
74 | // but it is still a better choice than setTimeout.
75 | timerFunc = () => {
76 | setImmediate(flushCallbacks)
77 | }
78 | } else {
79 | // Fallback to setTimeout.
80 | timerFunc = () => {
81 | setTimeout(flushCallbacks, 0)
82 | }
83 | }
84 |
85 | export function nextTick (cb, ctx) {
86 | let _resolve
87 | callbacks.push(() => {
88 | if (cb) {
89 | try {
90 | cb.call(ctx)
91 | } catch (e) {
92 | handleError(e, ctx, 'nextTick')
93 | }
94 | } else if (_resolve) {
95 | _resolve(ctx)
96 | }
97 | })
98 | if (!pending) {
99 | pending = true
100 | timerFunc()
101 | }
102 | // $flow-disable-line
103 | if (!cb && typeof Promise !== 'undefined') {
104 | return new Promise((resolve) => {
105 | _resolve = resolve
106 | })
107 | }
108 | }
--------------------------------------------------------------------------------
/src/core/utils/props.js:
--------------------------------------------------------------------------------
1 | import { observe, toggleObserving, shouldObserve } from '../observer/observer.js'
2 | import {
3 | hasOwn,
4 | toRawType,
5 | hyphenate,
6 | capitalize
7 | } from './tools.js'
8 | export function validateProp (
9 | key,
10 | propOptions,
11 | propsData,
12 | gm
13 | ) {
14 | const prop = propOptions[key]
15 | const absent = !hasOwn(propsData, key)
16 | let value = propsData[key]
17 | // boolean casting
18 | const booleanIndex = getTypeIndex(Boolean, prop.type)
19 | if (booleanIndex > -1) {
20 | if (absent && !hasOwn(prop, 'default')) {
21 | value = false
22 | } else if (value === '' || value === hyphenate(key)) {
23 | // only cast empty string / same name to boolean if
24 | // boolean has higher priority
25 | const stringIndex = getTypeIndex(String, prop.type)
26 | if (stringIndex < 0 || booleanIndex < stringIndex) {
27 | value = true
28 | }
29 | }
30 | }
31 | // check default value
32 | if (value === undefined) {
33 | value = getPropDefaultValue(gm, prop, key)
34 | // since the default value is a fresh copy,
35 | // make sure to observe it.
36 | const prevShouldObserve = shouldObserve
37 | toggleObserving(true)
38 | observe(value)
39 | toggleObserving(prevShouldObserve)
40 | }
41 | return value
42 | }
43 | /**
44 | * Get the default value of a prop.
45 | */
46 | function getPropDefaultValue (vm, prop, key) {
47 | // no default, return undefined
48 | if (!hasOwn(prop, 'default')) {
49 | return undefined
50 | }
51 | const def = prop.default
52 | // the raw prop value was also undefined from previous render,
53 | // return previous default value to avoid unnecessary watcher trigger
54 | if (vm && vm.$options.propsData &&
55 | vm.$options.propsData[key] === undefined &&
56 | vm._props[key] !== undefined
57 | ) {
58 | return vm._props[key]
59 | }
60 | // call factory function for non-Function types
61 | // a value is Function if its prototype is function even across different execution context
62 | return typeof def === 'function' && getType(prop.type) !== 'Function'
63 | ? def.call(vm)
64 | : def
65 | }
66 | /**
67 | * Use function string name to check built-in types,
68 | * because a simple equality check will fail when running
69 | * across different vms / iframes.
70 | */
71 | function getType (fn) {
72 | const match = fn && fn.toString().match(/^\s*function (\w+)/)
73 | return match ? match[1] : ''
74 | }
75 | function isSameType (a, b) {
76 | return getType(a) === getType(b)
77 | }
78 | function getTypeIndex (type, expectedTypes) {
79 | if (!Array.isArray(expectedTypes)) {
80 | return isSameType(expectedTypes, type) ? 0 : -1
81 | }
82 | for (let i = 0, len = expectedTypes.length; i < len; i++) {
83 | if (isSameType(expectedTypes[i], type)) {
84 | return i
85 | }
86 | }
87 | return -1
88 | }
89 | function getInvalidTypeMessage (name, value, expectedTypes) {
90 | let message = `Invalid prop: type check failed for prop "${name}".` +
91 | ` Expected ${expectedTypes.map(capitalize).join(', ')}`
92 | const expectedType = expectedTypes[0]
93 | const receivedType = toRawType(value)
94 | const expectedValue = styleValue(value, expectedType)
95 | const receivedValue = styleValue(value, receivedType)
96 | // check if we need to specify expected value
97 | if (expectedTypes.length === 1 &&
98 | isExplicable(expectedType) &&
99 | !isBoolean(expectedType, receivedType)) {
100 | message += ` with value ${expectedValue}`
101 | }
102 | message += `, got ${receivedType} `
103 | // check if we need to specify received value
104 | if (isExplicable(receivedType)) {
105 | message += `with value ${receivedValue}.`
106 | }
107 | return message
108 | }
109 |
110 | function styleValue (value, type) {
111 | if (type === 'String') {
112 | return `"${value}"`
113 | } else if (type === 'Number') {
114 | return `${Number(value)}`
115 | } else {
116 | return `${value}`
117 | }
118 | }
119 |
120 | function isExplicable (value) {
121 | const explicitTypes = ['string', 'number', 'boolean']
122 | return explicitTypes.some(elem => value.toLowerCase() === elem)
123 | }
124 |
125 | function isBoolean (...args) {
126 | return args.some(elem => elem.toLowerCase() === 'boolean')
127 | }
128 |
--------------------------------------------------------------------------------
/src/core/watcher/index.js:
--------------------------------------------------------------------------------
1 | import { pushTarget, popTarget } from '../observer/dep.js'
2 | import { queueWatcher } from '../observer/scheduler.js'
3 | import { traverse } from '../observer/traverse.js'
4 | import { _Set as Set } from '../utils/env.js'
5 | import { parsePath } from '../utils/lang.js'
6 | import { handleError } from '../utils/error.js'
7 | import { isObject, remove } from '../utils/tools.js'
8 | let uid = 0
9 | class Watcher {
10 | constructor(gm, expOrFn, cb, options, isRenderWatcher) {
11 | this.gm = gm
12 | if (isRenderWatcher) {
13 | gm._watcher = this
14 | }
15 | gm._watchers.push(this)
16 | if (options) {
17 | this.deep = !!options.deep
18 | this.user = !!options.user
19 | this.lazy = !!options.lazy
20 | this.sync = !!options.sync
21 | this.before = options.before
22 | } else {
23 | this.deep = this.user = this.lazy = this.sync = false
24 | }
25 | this.cb = cb
26 | this.id = ++uid
27 | this.active = true
28 | this.dirty = this.lazy
29 | this.deps = []
30 | this.newDeps = []
31 | this.depIds = new Set()
32 | this.newDepIds = new Set()
33 | this.expOrFn = expOrFn
34 | if (typeof expOrFn === 'function') {
35 | this.getter = expOrFn
36 | } else {
37 | this.getter = this.parseGetter(expOrFn.trim())
38 | }
39 | this.value = this.lazy
40 | ? undefined
41 | : this.get()
42 | }
43 | update() {
44 | /* istanbul ignore else */
45 | if (this.lazy) {
46 | this.dirty = true
47 | } else if (this.sync) {
48 | this.run()
49 | } else {
50 | queueWatcher(this)
51 | }
52 | }
53 | run() {
54 | if (this.active) {
55 | const value = this.get()
56 | if (
57 | value !== this.value ||
58 | // Deep watchers and watchers on Object/Arrays should fire even
59 | // when the value is the same, because the value may
60 | // have mutated.
61 | isObject(value) ||
62 | this.deep
63 | ) {
64 | // set new value
65 | const oldValue = this.value
66 | this.value = value
67 | if (this.user) {
68 | try {
69 | this.cb.call(this.gm, value, oldValue)
70 | } catch (e) {
71 | handleError(e, this.gm, `callback for watcher "${this.expression}"`)
72 | }
73 | } else {
74 | this.cb.call(this.gm, value, oldValue)
75 | }
76 | }
77 | }
78 | }
79 | /**
80 | * Evaluate the value of the watcher.
81 | * This only gets called for lazy watchers.
82 | */
83 | evaluate () {
84 | this.value = this.get()
85 | this.dirty = false
86 | }
87 | /**
88 | * Depend on all deps collected by this watcher.
89 | */
90 | depend () {
91 | let i = this.deps.length
92 | while (i--) {
93 | this.deps[i].depend()
94 | }
95 | }
96 | addDep(dep) {
97 | const id = dep.id
98 | if (!this.newDepIds.has(id)) {
99 | this.newDepIds.add(id)
100 | this.newDeps.push(dep)
101 | if (!this.depIds.has(id)) {
102 | dep.addSub(this)
103 | }
104 | }
105 | }
106 | get() {
107 | pushTarget(this)
108 | let value
109 | const gm = this.gm
110 | try {
111 | value = this.getter.call(gm, gm)
112 | } catch (e) {
113 | if (this.user) {
114 | handleError(e, gm, `getter for watcher "${this.expression}"`)
115 | } else {
116 | throw e
117 | }
118 | } finally {
119 | // "touch" every property so they are all tracked as
120 | // dependencies for deep watching
121 | if (this.deep) {
122 | traverse(value)
123 | }
124 | popTarget()
125 | this.cleanupDeps()
126 | }
127 | return value
128 | }
129 |
130 | /**
131 | * Clean up for dependency collection.
132 | */
133 | cleanupDeps () {
134 | let i = this.deps.length
135 | while (i--) {
136 | const dep = this.deps[i]
137 | if (!this.newDepIds.has(dep.id)) {
138 | dep.removeSub(this)
139 | }
140 | }
141 | let tmp = this.depIds
142 | this.depIds = this.newDepIds
143 | this.newDepIds = tmp
144 | this.newDepIds.clear()
145 | tmp = this.deps
146 | this.deps = this.newDeps
147 | this.newDeps = tmp
148 | this.newDeps.length = 0
149 | }
150 |
151 | parseGetter(exp) {
152 | return parsePath(exp)
153 | }
154 | /**
155 | * Remove self from all dependencies' subscriber list.
156 | */
157 | teardown () {
158 | if (this.active) {
159 | // remove self from gm's watcher list
160 | // this is a somewhat expensive operation so we skip it
161 | // if the gm is being destroyed.
162 | if (!this.gm._isBeingDestroyed) {
163 | remove(this.gm._watchers, this)
164 | }
165 | let i = this.deps.length
166 | while (i--) {
167 | this.deps[i].removeSub(this)
168 | }
169 | this.active = false
170 | }
171 | }
172 | }
173 | export default Watcher
174 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import Gengine from './platforms/web/entry-runtime-with-compiler.js'// './platforms/web/entry-runtime.js'
2 | let Geg = Gengine
3 | export default Geg
4 |
--------------------------------------------------------------------------------
/src/platforms/gxml/compiler/directives/html.js:
--------------------------------------------------------------------------------
1 | /* @flow */
2 |
3 | import { addProp } from '../../../../compiler/helpers.js'
4 |
5 | export default function html (el, dir) {
6 | if (dir.value) {
7 | addProp(el, 'innerHTML', `_s(${dir.value})`, dir)
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/src/platforms/gxml/compiler/directives/index.js:
--------------------------------------------------------------------------------
1 | import model from './model.js'
2 | import text from './text.js'
3 | import html from './html.js'
4 |
5 | export default {
6 | model,
7 | text,
8 | html
9 | }
10 |
--------------------------------------------------------------------------------
/src/platforms/gxml/compiler/directives/model.js:
--------------------------------------------------------------------------------
1 | /* @flow */
2 |
3 | import { config } from '../../../../core/config/config.js'
4 | import { addHandler, addProp, getBindingAttr } from '../../../../compiler/helpers.js'
5 | import { genComponentModel, genAssignmentCode } from '../../../../compiler/directives/model.js'
6 |
7 | let warn
8 |
9 | // in some cases, the event used has to be determined at runtime
10 | // so we used some reserved tokens during compile.
11 | export const RANGE_TOKEN = '__r'
12 | export const CHECKBOX_RADIO_TOKEN = '__c'
13 |
14 | export default function model (
15 | el,
16 | dir,
17 | _warn
18 | ) {
19 | warn = _warn
20 | const value = dir.value
21 | const modifiers = dir.modifiers
22 | const tag = el.tag
23 | const type = el.attrsMap.type
24 |
25 | if (el.component) {
26 | genComponentModel(el, value, modifiers)
27 | // component v-model doesn't need extra runtime
28 | return false
29 | } else if (tag === 'select') {
30 | genSelect(el, value, modifiers)
31 | } else if (tag === 'input' && type === 'checkbox') {
32 | genCheckboxModel(el, value, modifiers)
33 | } else if (tag === 'input' && type === 'radio') {
34 | genRadioModel(el, value, modifiers)
35 | } else if (tag === 'input' || tag === 'textarea') {
36 | genDefaultModel(el, value, modifiers)
37 | } else if (!config.isReservedTag(tag)) {
38 | genComponentModel(el, value, modifiers)
39 | // component v-model doesn't need extra runtime
40 | return false
41 | }
42 |
43 | // ensure runtime directive metadata
44 | return true
45 | }
46 |
47 | function genCheckboxModel (
48 | el,
49 | value,
50 | modifiers
51 | ) {
52 | const number = modifiers && modifiers.number
53 | const valueBinding = getBindingAttr(el, 'value') || 'null'
54 | const trueValueBinding = getBindingAttr(el, 'true-value') || 'true'
55 | const falseValueBinding = getBindingAttr(el, 'false-value') || 'false'
56 | addProp(el, 'checked',
57 | `Array.isArray(${value})` +
58 | `?_i(${value},${valueBinding})>-1` + (
59 | trueValueBinding === 'true'
60 | ? `:(${value})`
61 | : `:_q(${value},${trueValueBinding})`
62 | )
63 | )
64 | addHandler(el, 'change',
65 | `var $$a=${value},` +
66 | '$$el=$event.target,' +
67 | `$$c=$$el.checked?(${trueValueBinding}):(${falseValueBinding});` +
68 | 'if(Array.isArray($$a)){' +
69 | `var $$v=${number ? '_n(' + valueBinding + ')' : valueBinding},` +
70 | '$$i=_i($$a,$$v);' +
71 | `if($$el.checked){$$i<0&&(${genAssignmentCode(value, '$$a.concat([$$v])')})}` +
72 | `else{$$i>-1&&(${genAssignmentCode(value, '$$a.slice(0,$$i).concat($$a.slice($$i+1))')})}` +
73 | `}else{${genAssignmentCode(value, '$$c')}}`,
74 | null, true
75 | )
76 | }
77 |
78 | function genRadioModel (
79 | el,
80 | value,
81 | modifiers
82 | ) {
83 | const number = modifiers && modifiers.number
84 | let valueBinding = getBindingAttr(el, 'value') || 'null'
85 | valueBinding = number ? `_n(${valueBinding})` : valueBinding
86 | addProp(el, 'checked', `_q(${value},${valueBinding})`)
87 | addHandler(el, 'change', genAssignmentCode(value, valueBinding), null, true)
88 | }
89 |
90 | function genSelect (
91 | el,
92 | value,
93 | modifiers
94 | ) {
95 | const number = modifiers && modifiers.number
96 | const selectedVal = `Array.prototype.filter` +
97 | `.call($event.target.options,function(o){return o.selected})` +
98 | `.map(function(o){var val = "_value" in o ? o._value : o.value;` +
99 | `return ${number ? '_n(val)' : 'val'}})`
100 |
101 | const assignment = '$event.target.multiple ? $$selectedVal : $$selectedVal[0]'
102 | let code = `var $$selectedVal = ${selectedVal};`
103 | code = `${code} ${genAssignmentCode(value, assignment)}`
104 | addHandler(el, 'change', code, null, true)
105 | }
106 |
107 | function genDefaultModel (
108 | el,
109 | value,
110 | modifiers
111 | ) {
112 | const type = el.attrsMap.type
113 |
114 | const { lazy, number, trim } = modifiers || {}
115 | const needCompositionGuard = !lazy && type !== 'range'
116 | const event = lazy
117 | ? 'change'
118 | : type === 'range'
119 | ? RANGE_TOKEN
120 | : 'input'
121 |
122 | let valueExpression = '$event.target.value'
123 | if (trim) {
124 | valueExpression = `$event.target.value.trim()`
125 | }
126 | if (number) {
127 | valueExpression = `_n(${valueExpression})`
128 | }
129 |
130 | let code = genAssignmentCode(value, valueExpression)
131 | if (needCompositionGuard) {
132 | code = `if($event.target.composing)return;${code}`
133 | }
134 |
135 | addProp(el, 'value', `(${value})`)
136 | addHandler(el, event, code, null, true)
137 | if (trim || number) {
138 | addHandler(el, 'blur', '$forceUpdate()')
139 | }
140 | }
141 |
--------------------------------------------------------------------------------
/src/platforms/gxml/compiler/directives/text.js:
--------------------------------------------------------------------------------
1 | /* @flow */
2 |
3 | import { addProp } from '../../../../compiler/helpers.js'
4 |
5 | export default function text (el, dir) {
6 | if (dir.value) {
7 | addProp(el, 'textContent', `_s(${dir.value})`, dir)
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/src/platforms/gxml/compiler/index.js:
--------------------------------------------------------------------------------
1 | /* @flow */
2 |
3 | import { baseOptions } from './options.js'
4 | import { createCompiler } from '../../../compiler/index.js'
5 |
6 | const { compile, compileToFunctions } = createCompiler(baseOptions)
7 |
8 | export { compile, compileToFunctions }
9 |
--------------------------------------------------------------------------------
/src/platforms/gxml/compiler/modules/class.js:
--------------------------------------------------------------------------------
1 | import {
2 | getAndRemoveAttr,
3 | getBindingAttr,
4 | baseWarn
5 | } from '../../../../compiler/helpers.js'
6 | import { convertAttrVal } from '../../../../compiler/util/index.js'
7 | function transformNode (el, options) {
8 | const warn = options.warn || baseWarn
9 | const staticClass = getAndRemoveAttr(el, 'class')
10 | if (staticClass) {
11 | el.staticClass = JSON.stringify(staticClass)
12 | }
13 | const classBinding = getBindingAttr(el, 'class', false /* getStatic */)
14 | if (classBinding) {
15 | el.classBinding = classBinding
16 | }
17 | }
18 |
19 | function genData (el) {
20 | let data = {}
21 | if (el.staticClass) {
22 | // data += `staticClass:${el.staticClass},`
23 | data['staticClass'] = `${el.staticClass}`
24 | }
25 | if (el.classBinding) {
26 | // data += `class:${el.classBinding},`
27 | data['class'] = convertAttrVal(this, el.classBinding)
28 | }
29 | return data
30 | }
31 |
32 | export default {
33 | staticKeys: ['staticClass'],
34 | transformNode,
35 | genData
36 | }
37 |
--------------------------------------------------------------------------------
/src/platforms/gxml/compiler/modules/index.js:
--------------------------------------------------------------------------------
1 | import klass from './class.js'
2 | import style from './style.js'
3 | import model from './model.js'
4 |
5 | export default [
6 | klass,
7 | style,
8 | model
9 | ]
10 |
--------------------------------------------------------------------------------
/src/platforms/gxml/compiler/modules/model.js:
--------------------------------------------------------------------------------
1 | /* @flow */
2 |
3 | /**
4 | * Expand input[v-model] with dyanmic type bindings into v-if-else chains
5 | * Turn this:
6 | *
7 | * into this:
8 | *
9 | *
10 | *
11 | */
12 |
13 | import {
14 | addRawAttr,
15 | getBindingAttr,
16 | getAndRemoveAttr
17 | } from '../../../../compiler/helpers.js'
18 |
19 | import {
20 | processFor,
21 | processElement,
22 | addIfCondition,
23 | createASTElement
24 | } from '../../../../compiler/parser/index.js'
25 |
26 | function preTransformNode (el, options) {
27 | if (el.tag === 'input') {
28 | const map = el.attrsMap
29 | if (!map['v-model']) {
30 | return
31 | }
32 |
33 | let typeBinding
34 | if (map[':type'] || map['v-bind:type']) {
35 | typeBinding = getBindingAttr(el, 'type')
36 | }
37 | if (!map.type && !typeBinding && map['v-bind']) {
38 | typeBinding = `(${map['v-bind']}).type`
39 | }
40 |
41 | if (typeBinding) {
42 | const ifCondition = getAndRemoveAttr(el, 'v-if', true)
43 | const ifConditionExtra = ifCondition ? `&&(${ifCondition})` : ``
44 | const hasElse = getAndRemoveAttr(el, 'v-else', true) != null
45 | const elseIfCondition = getAndRemoveAttr(el, 'v-else-if', true)
46 | // 1. checkbox
47 | const branch0 = cloneASTElement(el)
48 | // process for on the main node
49 | processFor(branch0)
50 | addRawAttr(branch0, 'type', 'checkbox')
51 | processElement(branch0, options)
52 | branch0.processed = true // prevent it from double-processed
53 | branch0.if = `(${typeBinding})==='checkbox'` + ifConditionExtra
54 | addIfCondition(branch0, {
55 | exp: branch0.if,
56 | block: branch0
57 | })
58 | // 2. add radio else-if condition
59 | const branch1 = cloneASTElement(el)
60 | getAndRemoveAttr(branch1, 'v-for', true)
61 | addRawAttr(branch1, 'type', 'radio')
62 | processElement(branch1, options)
63 | addIfCondition(branch0, {
64 | exp: `(${typeBinding})==='radio'` + ifConditionExtra,
65 | block: branch1
66 | })
67 | // 3. other
68 | const branch2 = cloneASTElement(el)
69 | getAndRemoveAttr(branch2, 'v-for', true)
70 | addRawAttr(branch2, ':type', typeBinding)
71 | processElement(branch2, options)
72 | addIfCondition(branch0, {
73 | exp: ifCondition,
74 | block: branch2
75 | })
76 |
77 | if (hasElse) {
78 | branch0.else = true
79 | } else if (elseIfCondition) {
80 | branch0.elseif = elseIfCondition
81 | }
82 |
83 | return branch0
84 | }
85 | }
86 | }
87 |
88 | function cloneASTElement (el) {
89 | return createASTElement(el.tag, el.attrsList.slice(), el.parent)
90 | }
91 |
92 | export default {
93 | preTransformNode
94 | }
95 |
--------------------------------------------------------------------------------
/src/platforms/gxml/compiler/modules/style.js:
--------------------------------------------------------------------------------
1 | /* @flow */
2 | import { parseStyleText } from '../../util/style.js'
3 | import {
4 | getAndRemoveAttr,
5 | getBindingAttr
6 | } from '../../../../compiler/helpers.js'
7 | import { convertAttrVal } from '../../../../compiler/util/index.js'
8 | function transformNode (el, options) {
9 | const staticStyle = getAndRemoveAttr(el, 'style')
10 | if (staticStyle) {
11 | el.staticStyle = JSON.stringify(parseStyleText(staticStyle))
12 | }
13 |
14 | const styleBinding = getBindingAttr(el, 'style', false /* getStatic */)
15 | if (styleBinding) {
16 | el.styleBinding = styleBinding
17 | }
18 | }
19 |
20 | function genData (el) {
21 | let data = {}
22 | if (el.staticStyle) {
23 | // data += `staticStyle:${el.staticStyle},`
24 | data['staticStyle'] = `${el.staticStyle}`
25 | }
26 | if (el.styleBinding) {
27 | // data += `style:(${el.styleBinding}),`
28 | data['style'] = convertAttrVal(this, el.styleBinding)
29 | }
30 | return data
31 | }
32 |
33 | export default {
34 | staticKeys: ['staticStyle'],
35 | transformNode,
36 | genData
37 | }
38 |
--------------------------------------------------------------------------------
/src/platforms/gxml/compiler/options.js:
--------------------------------------------------------------------------------
1 | /* @flow */
2 |
3 | import {
4 | isPreTag,
5 | mustUseProp,
6 | isReservedTag,
7 | getTagNamespace
8 | } from '../util/index.js'
9 |
10 | import modules from './modules/index.js'
11 | import directives from './directives/index.js'
12 | import { genStaticKeys } from '../../../core/utils/tools.js'
13 | import { isUnaryTag, canBeLeftOpenTag } from './util.js'
14 |
15 | export const baseOptions = {
16 | expectHTML: true,
17 | modules,
18 | directives,
19 | isPreTag,
20 | isUnaryTag,
21 | mustUseProp,
22 | canBeLeftOpenTag,
23 | isReservedTag,
24 | getTagNamespace,
25 | staticKeys: genStaticKeys(modules)
26 | }
27 |
--------------------------------------------------------------------------------
/src/platforms/gxml/compiler/util.js:
--------------------------------------------------------------------------------
1 | /* @flow */
2 |
3 | import { makeMap } from '../../../core/utils/tools.js'
4 |
5 | export const isUnaryTag = makeMap(
6 | 'area,base,br,col,embed,frame,hr,img,input,isindex,keygen,' +
7 | 'link,meta,param,source,track,wbr'
8 | )
9 |
10 | // Elements that you can, intentionally, leave open
11 | // (and which close themselves)
12 | export const canBeLeftOpenTag = makeMap(
13 | 'colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr,source'
14 | )
15 |
16 | // HTML5 tags https://html.spec.whatwg.org/multipage/indices.html#elements-3
17 | // Phrasing Content https://html.spec.whatwg.org/multipage/dom.html#phrasing-content
18 | export const isNonPhrasingTag = makeMap(
19 | 'address,article,aside,base,blockquote,body,caption,col,colgroup,dd,' +
20 | 'details,dialog,div,dl,dt,fieldset,figcaption,figure,footer,form,' +
21 | 'h1,h2,h3,h4,h5,h6,head,header,hgroup,hr,html,legend,li,menuitem,meta,' +
22 | 'optgroup,option,param,rp,rt,source,style,summary,tbody,td,tfoot,th,thead,' +
23 | 'title,tr,track'
24 | )
25 |
--------------------------------------------------------------------------------
/src/platforms/gxml/entry-compiler.js:
--------------------------------------------------------------------------------
1 | /* @flow */
2 |
3 | export { parseComponent } from '../../sfc/parser.js'
4 | export { compile, compileToFunctions } from './compiler/index.js'
5 | export { generateCodeFrame } from '../../compiler/codeframe.js'
6 |
--------------------------------------------------------------------------------
/src/platforms/gxml/entry-runtime-with-compiler.js:
--------------------------------------------------------------------------------
1 | /* @flow */
2 | import Gengine from './runtime/index.js'
3 | import { query } from './util/index.js'
4 | import { compileToFunctions } from './compiler/index.js'
5 | import { shouldDecodeNewlines, shouldDecodeNewlinesForHref } from './util/compat.js'
6 | import { DOMParser } from './xml/dom-parser.js'
7 | // let gm = null
8 | // const infoToTemplate = cached(info => {
9 | // const el = query.call(gm, info)
10 | // return el
11 | // })
12 | const mount = Gengine.prototype.$mount
13 | Gengine.prototype.$mount = function (
14 | el,
15 | hydrating
16 | ) {
17 | // GXML does not require an element
18 | el = undefined// this.document.childNodes[0]
19 | if (!this.document) {
20 | let domParser = new DOMParser()
21 | this.document = domParser.parseFromString('')
22 | }
23 | const options = this.$options
24 | let compileFunc = (template) => {
25 | if (template) {
26 | const { render, staticRenderFns } = compileToFunctions(template, {
27 | outputSourceRange: false,
28 | shouldDecodeNewlines,
29 | shouldDecodeNewlinesForHref,
30 | delimiters: options.delimiters,
31 | comments: options.comments
32 | }, this)
33 | options.render = render
34 | options.staticRenderFns = staticRenderFns
35 | }
36 | }
37 | // gm = this
38 | if (!options.render) {
39 | query.call(this, options.el, (code) => {
40 | let xmlTemplate = code
41 | /* istanbul ignore if */
42 | // if (el === document.body || el === document.documentElement) {
43 | // return this
44 | // }
45 | // resolve template/el and convert to render function
46 | let template = options.template
47 | if (template) {
48 | if (typeof template === 'string') {
49 | if (template.trim().charAt(0) === '<' &&
50 | template.trim().charAt(template.length - 1) === '>') {
51 | query.call(this, template, (code) => {
52 | template = code
53 | compileFunc(template)
54 | mount.call(this, el, hydrating)
55 | }) // infoToTemplate(template)
56 | return this
57 | }
58 | } else {
59 | return this
60 | }
61 | } else if (xmlTemplate) {
62 | template = xmlTemplate
63 | compileFunc(template)
64 | mount.call(this, el, hydrating)
65 | }
66 | })
67 | } else {
68 | mount.call(this, el, hydrating)
69 | }
70 | return this
71 | }
72 |
73 | /**
74 | * Get outerHTML of elements, taking care
75 | * of SVG elements in IE as well.
76 | */
77 | // function getOuterHTML (el) {
78 | // if (el.outerHTML) {
79 | // return el.outerHTML
80 | // } else {
81 | // const container = document.createElement('div')
82 | // container.appendChild(el.cloneNode(true))
83 | // return container.innerHTML
84 | // }
85 | // }
86 |
87 | Gengine.compile = compileToFunctions
88 |
89 | export default Gengine
90 |
--------------------------------------------------------------------------------
/src/platforms/gxml/entry-runtime.js:
--------------------------------------------------------------------------------
1 | /* @flow */
2 |
3 | import Gengine from './runtime/index.js'
4 |
5 | export default Gengine
6 |
--------------------------------------------------------------------------------
/src/platforms/gxml/gxml/code.js:
--------------------------------------------------------------------------------
1 | export default class Code {
2 | constructor(gm, docInfo) {
3 | this.docInfo = docInfo
4 | let pattern =/