├── .eslintignore ├── packages └── wxml-transpiler │ ├── index.js │ ├── README.md │ └── package.json ├── test ├── lib │ ├── wcc │ └── wcc-new ├── pages │ ├── index │ │ └── index.wxml │ ├── logs │ │ └── logs.wxml │ └── full │ │ └── index.wxml └── test.js ├── publish.sh ├── circle.yml ├── src ├── core │ ├── components │ │ ├── index.js │ │ └── keep-alive.js │ ├── vdom │ │ ├── modules │ │ │ ├── index.js │ │ │ ├── ref.js │ │ │ └── directives.js │ │ ├── helpers │ │ │ ├── index.js │ │ │ ├── get-first-component-child.js │ │ │ ├── merge-hook.js │ │ │ ├── update-listeners.js │ │ │ ├── extract-props.js │ │ │ ├── normalize-children.js │ │ │ └── resolve-async-component.js │ │ ├── create-functional-component.js │ │ ├── vnode.js │ │ └── create-element.js │ ├── global-api │ │ ├── mixin.js │ │ ├── use.js │ │ ├── assets.js │ │ ├── index.js │ │ └── extend.js │ ├── util │ │ ├── index.js │ │ ├── error.js │ │ ├── perf.js │ │ ├── lang.js │ │ ├── debug.js │ │ ├── props.js │ │ └── env.js │ ├── instance │ │ ├── render-helpers │ │ │ ├── resolve-filter.js │ │ │ ├── check-keycodes.js │ │ │ ├── bind-object-listeners.js │ │ │ ├── render-slot.js │ │ │ ├── render-list.js │ │ │ ├── bind-object-props.js │ │ │ ├── resolve-slots.js │ │ │ └── render-static.js │ │ ├── index.js │ │ ├── inject.js │ │ ├── proxy.js │ │ ├── events.js │ │ ├── init.js │ │ └── render.js │ ├── index.js │ ├── observer │ │ ├── array.js │ │ ├── dep.js │ │ ├── scheduler.js │ │ ├── watcher.js │ │ └── index.js │ └── config.js ├── platforms │ └── web │ │ ├── compiler │ │ ├── modules │ │ │ ├── index.js │ │ │ ├── class.js │ │ │ └── style.js │ │ ├── directives │ │ │ ├── index.js │ │ │ ├── html.js │ │ │ ├── text.js │ │ │ └── model.js │ │ ├── index.js │ │ ├── options.js │ │ └── util.js │ │ ├── util │ │ ├── compat.js │ │ ├── index.js │ │ ├── attrs.js │ │ ├── style.js │ │ ├── class.js │ │ └── element.js │ │ └── entry-compiler.js ├── compiler │ ├── directives │ │ ├── index.js │ │ ├── bind.js │ │ ├── on.js │ │ └── model.js │ ├── parser │ │ ├── entity-decoder.js │ │ └── filter-parser.js │ ├── create-compiler.js │ ├── index.js │ ├── to-function.js │ ├── error-detector.js │ ├── helpers.js │ ├── codegen │ │ └── events.js │ └── optimizer.js ├── shared │ └── constants.js └── sfc │ └── parser.js ├── docs ├── resources │ ├── C2DA06C0F8A4D40C55ADF4BB8D3312B4.jpg │ └── D26E6CDB0A8FD3838D3BB09ECF22727C.jpg └── implementation.md ├── .babelrc ├── .gitignore ├── .eslintrc ├── .editorconfig ├── flow ├── ssr.js ├── global-api.js ├── modules.js ├── options.js ├── vnode.js ├── component.js └── compiler.js ├── .flowconfig ├── README.md ├── LICENSE └── package.json /.eslintignore: -------------------------------------------------------------------------------- 1 | flow 2 | dist 3 | packages 4 | -------------------------------------------------------------------------------- /packages/wxml-transpiler/index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./build') 2 | -------------------------------------------------------------------------------- /test/lib/wcc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IOriens/wxml-transpiler/HEAD/test/lib/wcc -------------------------------------------------------------------------------- /publish.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | npm run build 3 | cd packages/wxml-transpiler 4 | npm publish -------------------------------------------------------------------------------- /test/lib/wcc-new: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IOriens/wxml-transpiler/HEAD/test/lib/wcc-new -------------------------------------------------------------------------------- /circle.yml: -------------------------------------------------------------------------------- 1 | machine: 2 | node: 3 | version: 6 4 | 5 | test: 6 | override: 7 | - bash build/ci.sh 8 | -------------------------------------------------------------------------------- /src/core/components/index.js: -------------------------------------------------------------------------------- 1 | import KeepAlive from './keep-alive' 2 | 3 | export default { 4 | KeepAlive 5 | } 6 | -------------------------------------------------------------------------------- /docs/resources/C2DA06C0F8A4D40C55ADF4BB8D3312B4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IOriens/wxml-transpiler/HEAD/docs/resources/C2DA06C0F8A4D40C55ADF4BB8D3312B4.jpg -------------------------------------------------------------------------------- /docs/resources/D26E6CDB0A8FD3838D3BB09ECF22727C.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IOriens/wxml-transpiler/HEAD/docs/resources/D26E6CDB0A8FD3838D3BB09ECF22727C.jpg -------------------------------------------------------------------------------- /src/core/vdom/modules/index.js: -------------------------------------------------------------------------------- 1 | import directives from './directives' 2 | import ref from './ref' 3 | 4 | export default [ 5 | ref, 6 | directives 7 | ] 8 | -------------------------------------------------------------------------------- /src/platforms/web/compiler/modules/index.js: -------------------------------------------------------------------------------- 1 | import klass from './class' 2 | import style from './style' 3 | 4 | export default [ 5 | klass, 6 | style 7 | ] 8 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "flow-vue"], 3 | "plugins": ["transform-vue-jsx", "syntax-dynamic-import"], 4 | "ignore": [ 5 | "dist/*.js", 6 | "packages/**/*.js" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | *.log 4 | *.diff 5 | explorations 6 | TODOs.md 7 | coverage 8 | RELEASE_NOTE*.md 9 | dist/*.js 10 | packages/wxml-transpiler/build.js 11 | test/dist 12 | -------------------------------------------------------------------------------- /src/platforms/web/compiler/directives/index.js: -------------------------------------------------------------------------------- 1 | import model from './model' 2 | import text from './text' 3 | import html from './html' 4 | 5 | export default { 6 | model, 7 | text, 8 | html 9 | } 10 | -------------------------------------------------------------------------------- /src/compiler/directives/index.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import on from './on' 4 | import bind from './bind' 5 | import { noop } from 'shared/util' 6 | 7 | export default { 8 | on, 9 | bind, 10 | cloak: noop 11 | } 12 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "plugins": [ 4 | "flowtype" 5 | ], 6 | "extends": [ 7 | "plugin:vue-libs/recommended", 8 | "plugin:flowtype/recommended" 9 | ], 10 | "globals": { 11 | "__WEEX__": true 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/platforms/web/compiler/index.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { baseOptions } from './options' 4 | import { createCompiler } from 'compiler/index' 5 | 6 | const { compile, compileToFunctions } = createCompiler(baseOptions) 7 | 8 | export { compile, compileToFunctions } 9 | -------------------------------------------------------------------------------- /src/compiler/parser/entity-decoder.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | let decoder 4 | 5 | export default { 6 | decode (html: string): string { 7 | decoder = decoder || document.createElement('div') 8 | decoder.innerHTML = html 9 | return decoder.textContent 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/platforms/web/compiler/directives/html.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { addProp } from 'compiler/helpers' 4 | 5 | export default function html (el: ASTElement, dir: ASTDirective) { 6 | if (dir.value) { 7 | addProp(el, 'innerHTML', `_s(${dir.value})`) 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/platforms/web/compiler/directives/text.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { addProp } from 'compiler/helpers' 4 | 5 | export default function text (el: ASTElement, dir: ASTDirective) { 6 | if (dir.value) { 7 | addProp(el, 'textContent', `_s(${dir.value})`) 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/core/vdom/helpers/index.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | export * from './merge-hook' 4 | export * from './extract-props' 5 | export * from './update-listeners' 6 | export * from './normalize-children' 7 | export * from './resolve-async-component' 8 | export * from './get-first-component-child' 9 | -------------------------------------------------------------------------------- /src/core/global-api/mixin.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { mergeOptions } from '../util/index' 4 | 5 | export function initMixin (Vue: GlobalAPI) { 6 | Vue.mixin = function (mixin: Object) { 7 | this.options = mergeOptions(this.options, mixin) 8 | return this 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/core/util/index.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | export * from 'shared/util' 4 | export * from './lang' 5 | export * from './env' 6 | export * from './options' 7 | export * from './debug' 8 | export * from './props' 9 | export * from './error' 10 | export { defineReactive } from '../observer/index' 11 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | charset = utf-8 7 | indent_style = space 8 | indent_size = 2 9 | end_of_line = lf 10 | insert_final_newline = true 11 | trim_trailing_whitespace = true 12 | 13 | [*.md] 14 | insert_final_newline = false 15 | trim_trailing_whitespace = false 16 | -------------------------------------------------------------------------------- /src/core/instance/render-helpers/resolve-filter.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { identity, resolveAsset } from 'core/util/index' 4 | 5 | /** 6 | * Runtime helper for resolving filters 7 | */ 8 | export function resolveFilter (id: string): Function { 9 | return resolveAsset(this.$options, 'filters', id, true) || identity 10 | } 11 | -------------------------------------------------------------------------------- /src/compiler/directives/bind.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | export default function bind (el: ASTElement, dir: ASTDirective) { 4 | el.wrapData = (code: string) => { 5 | return `_b(${code},'${el.tag}',${dir.value},${ 6 | dir.modifiers && dir.modifiers.prop ? 'true' : 'false' 7 | }${ 8 | dir.modifiers && dir.modifiers.sync ? ',true' : '' 9 | })` 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/compiler/directives/on.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { warn } from 'core/util/index' 4 | 5 | export default function on (el: ASTElement, dir: ASTDirective) { 6 | if (process.env.NODE_ENV !== 'production' && dir.modifiers) { 7 | warn(`v-on without argument does not support modifiers.`) 8 | } 9 | el.wrapListeners = (code: string) => `_g(${code},${dir.value})` 10 | } 11 | -------------------------------------------------------------------------------- /test/pages/index/index.wxml: -------------------------------------------------------------------------------- 1 | 参考价格:¥ 2 | {{price}} 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/shared/constants.js: -------------------------------------------------------------------------------- 1 | export const SSR_ATTR = 'data-server-rendered' 2 | 3 | export const ASSET_TYPES = [ 4 | 'component', 5 | 'directive', 6 | 'filter' 7 | ] 8 | 9 | export const LIFECYCLE_HOOKS = [ 10 | 'beforeCreate', 11 | 'created', 12 | 'beforeMount', 13 | 'mounted', 14 | 'beforeUpdate', 15 | 'updated', 16 | 'beforeDestroy', 17 | 'destroyed', 18 | 'activated', 19 | 'deactivated' 20 | ] 21 | -------------------------------------------------------------------------------- /src/core/vdom/helpers/get-first-component-child.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { isDef } from 'shared/util' 4 | 5 | export function getFirstComponentChild (children: ?Array): ?VNode { 6 | if (Array.isArray(children)) { 7 | for (let i = 0; i < children.length; i++) { 8 | const c = children[i] 9 | if (isDef(c) && isDef(c.componentOptions)) { 10 | return c 11 | } 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /test/pages/logs/logs.wxml: -------------------------------------------------------------------------------- 1 |
a
2 |
b
3 |
c
4 |
not a/b/c
5 | 6 | 13 | 16 | -------------------------------------------------------------------------------- /src/core/index.js: -------------------------------------------------------------------------------- 1 | import Vue from './instance/index' 2 | import { initGlobalAPI } from './global-api/index' 3 | import { isServerRendering } from 'core/util/env' 4 | 5 | initGlobalAPI(Vue) 6 | 7 | Object.defineProperty(Vue.prototype, '$isServer', { 8 | get: isServerRendering 9 | }) 10 | 11 | Object.defineProperty(Vue.prototype, '$ssrContext', { 12 | get () { 13 | /* istanbul ignore next */ 14 | return this.$vnode && this.$vnode.ssrContext 15 | } 16 | }) 17 | 18 | Vue.version = '__VERSION__' 19 | 20 | export default Vue 21 | -------------------------------------------------------------------------------- /flow/ssr.js: -------------------------------------------------------------------------------- 1 | declare type ComponentWithCacheContext = { 2 | type: 'ComponentWithCache'; 3 | bufferIndex: number; 4 | buffer: Array; 5 | key: string; 6 | }; 7 | 8 | declare type ElementContext = { 9 | type: 'Element'; 10 | children: Array; 11 | rendered: number; 12 | endTag: string; 13 | total: number; 14 | }; 15 | 16 | declare type ComponentContext = { 17 | type: 'Component'; 18 | prevActive: Component; 19 | }; 20 | 21 | declare type RenderState = ComponentContext | ComponentWithCacheContext | ElementContext; 22 | -------------------------------------------------------------------------------- /src/core/instance/render-helpers/check-keycodes.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import config from 'core/config' 4 | 5 | /** 6 | * Runtime helper for checking keyCodes from config. 7 | */ 8 | export function checkKeyCodes ( 9 | eventKeyCode: number, 10 | key: string, 11 | builtInAlias: number | Array | void 12 | ): boolean { 13 | const keyCodes = config.keyCodes[key] || builtInAlias 14 | if (Array.isArray(keyCodes)) { 15 | return keyCodes.indexOf(eventKeyCode) === -1 16 | } else { 17 | return keyCodes !== eventKeyCode 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/wxml-transpiler/README.md: -------------------------------------------------------------------------------- 1 | # wxml-transpiler 2 | 3 | This package can be used to compile wxml templates.([view source code here](https://github.com/IOriens/wxml-transpiler)). It works just like wcc.exe, wcc. 4 | 5 | ## Usage 6 | 7 | ```sh 8 | npm i wxml-transpiler 9 | ``` 10 | 11 | ``` js 12 | const compiler = require('wxml-transpiler') 13 | 14 | const fileList = [ 15 | './pages/index/index.wxml', 16 | './common/head.wxml', 17 | './common/foot.wxml' 18 | ] 19 | 20 | const res = compiler.wxmlCompile(fileList) 21 | ``` 22 | 23 | ## Liscense 24 | 25 | MIT -------------------------------------------------------------------------------- /src/platforms/web/util/compat.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { inBrowser } from 'core/util/index' 4 | 5 | // check whether current browser encodes a char inside attribute values 6 | function shouldDecode (content: string, encoded: string): boolean { 7 | const div = document.createElement('div') 8 | div.innerHTML = `
` 9 | return div.innerHTML.indexOf(encoded) > 0 10 | } 11 | 12 | // #3663 13 | // IE encodes newlines inside attribute values while other browsers don't 14 | export const shouldDecodeNewlines = inBrowser ? shouldDecode('\n', ' ') : false 15 | -------------------------------------------------------------------------------- /src/core/util/error.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import config from '../config' 4 | import { warn } from './debug' 5 | import { inBrowser } from './env' 6 | 7 | export function handleError (err: Error, vm: any, info: string) { 8 | if (config.errorHandler) { 9 | config.errorHandler.call(null, err, vm, info) 10 | } else { 11 | if (process.env.NODE_ENV !== 'production') { 12 | warn(`Error in ${info}: "${err.toString()}"`, vm) 13 | } 14 | /* istanbul ignore else */ 15 | if (inBrowser && typeof console !== 'undefined') { 16 | console.error(err) 17 | } else { 18 | throw err 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/core/util/perf.js: -------------------------------------------------------------------------------- 1 | import { inBrowser } from './env' 2 | 3 | export let mark 4 | export let measure 5 | 6 | if (process.env.NODE_ENV !== 'production') { 7 | const perf = inBrowser && window.performance 8 | /* istanbul ignore if */ 9 | if ( 10 | perf && 11 | perf.mark && 12 | perf.measure && 13 | perf.clearMarks && 14 | perf.clearMeasures 15 | ) { 16 | mark = tag => perf.mark(tag) 17 | measure = (name, startTag, endTag) => { 18 | perf.measure(name, startTag, endTag) 19 | perf.clearMarks(startTag) 20 | perf.clearMarks(endTag) 21 | perf.clearMeasures(name) 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/platforms/web/entry-compiler.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | // export { parseComponent } from 'sfc/parser' 4 | import {compile} from './compiler/index' 5 | 6 | 7 | const fs = require('fs') 8 | const resolve = require('path').resolve 9 | 10 | export function wxmlCompile (fileList: Array) { 11 | const srcFiles = fileList.reverse() 12 | var files = srcFiles.map(path => ({ 13 | path, 14 | template: fs.readFileSync(path, 'utf-8') 15 | })) 16 | return compile(files) 17 | } 18 | 19 | // export { ssrCompile, ssrCompileToFunctions } from './server/compiler' 20 | 21 | export { compile, compileToFunctions } from './compiler/index' 22 | -------------------------------------------------------------------------------- /src/platforms/web/compiler/options.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { 4 | isPreTag, 5 | mustUseProp, 6 | isReservedTag, 7 | getTagNamespace 8 | } from '../util/index' 9 | 10 | import modules from './modules/index' 11 | import directives from './directives/index' 12 | import { genStaticKeys } from 'shared/util' 13 | import { isUnaryTag, canBeLeftOpenTag } from './util' 14 | 15 | export const baseOptions: CompilerOptions = { 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/core/instance/index.js: -------------------------------------------------------------------------------- 1 | import { initMixin } from './init' 2 | import { stateMixin } from './state' 3 | import { renderMixin } from './render' 4 | import { eventsMixin } from './events' 5 | import { lifecycleMixin } from './lifecycle' 6 | import { warn } from '../util/index' 7 | 8 | function Vue (options) { 9 | if (process.env.NODE_ENV !== 'production' && 10 | !(this instanceof Vue) 11 | ) { 12 | warn('Vue is a constructor and should be called with the `new` keyword') 13 | } 14 | this._init(options) 15 | } 16 | 17 | initMixin(Vue) 18 | stateMixin(Vue) 19 | eventsMixin(Vue) 20 | lifecycleMixin(Vue) 21 | renderMixin(Vue) 22 | 23 | export default Vue 24 | -------------------------------------------------------------------------------- /src/platforms/web/util/index.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { warn } from 'core/util/index' 4 | 5 | export * from './attrs' 6 | export * from './class' 7 | export * from './element' 8 | 9 | /** 10 | * Query an element selector if it's not an element already. 11 | */ 12 | export function query (el: string | Element): Element { 13 | if (typeof el === 'string') { 14 | const selected = document.querySelector(el) 15 | if (!selected) { 16 | process.env.NODE_ENV !== 'production' && warn( 17 | 'Cannot find element: ' + el 18 | ) 19 | return document.createElement('div') 20 | } 21 | return selected 22 | } else { 23 | return el 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /packages/wxml-transpiler/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wxml-transpiler", 3 | "version": "1.0.5", 4 | "description": "template compiler for wxml", 5 | "main": "index.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "git+https://github.com/IOriens/wxml-transpiler.git" 9 | }, 10 | "keywords": [ 11 | "wxml", 12 | "wcc", 13 | "transpiler", 14 | "compiler" 15 | ], 16 | "author": "Junjie Ding", 17 | "license": "MIT", 18 | "bugs": { 19 | "url": "https://github.com/IOriens/wxml-transpiler/issues" 20 | }, 21 | "homepage": "https://github.com/IOriens/wxml-transpiler/tree/master/packages/wxml-transpiler", 22 | "dependencies": { 23 | "babylon": "^6.18.0" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | .*/node_modules/.* 3 | .*/test/.* 4 | .*/build/.* 5 | .*/examples/.* 6 | .*/benchmarks/.* 7 | 8 | [include] 9 | 10 | [libs] 11 | flow 12 | 13 | [options] 14 | unsafe.enable_getters_and_setters=true 15 | module.name_mapper='^compiler/\(.*\)$' -> '/src/compiler/\1' 16 | module.name_mapper='^core/\(.*\)$' -> '/src/core/\1' 17 | module.name_mapper='^shared/\(.*\)$' -> '/src/shared/\1' 18 | module.name_mapper='^web/\(.*\)$' -> '/src/platforms/web/\1' 19 | module.name_mapper='^entries/\(.*\)$' -> '/src/entries/\1' 20 | module.name_mapper='^sfc/\(.*\)$' -> '/src/sfc/\1' 21 | suppress_comment= \\(.\\|\n\\)*\\$flow-disable-line 22 | -------------------------------------------------------------------------------- /src/core/instance/render-helpers/bind-object-listeners.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { warn, extend, isPlainObject } from 'core/util/index' 4 | 5 | export function bindObjectListeners (data: any, value: any): VNodeData { 6 | if (value) { 7 | if (!isPlainObject(value)) { 8 | process.env.NODE_ENV !== 'production' && warn( 9 | 'v-on without argument expects an Object value', 10 | this 11 | ) 12 | } else { 13 | const on = data.on = data.on ? extend({}, data.on) : {} 14 | for (const key in value) { 15 | const existing = on[key] 16 | const ours = value[key] 17 | on[key] = existing ? [].concat(ours, existing) : ours 18 | } 19 | } 20 | } 21 | return data 22 | } 23 | -------------------------------------------------------------------------------- /src/core/global-api/use.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { toArray } from '../util/index' 4 | 5 | export function initUse (Vue: GlobalAPI) { 6 | Vue.use = function (plugin: Function | Object) { 7 | const installedPlugins = (this._installedPlugins || (this._installedPlugins = [])) 8 | if (installedPlugins.indexOf(plugin) > -1) { 9 | return this 10 | } 11 | 12 | // additional parameters 13 | const args = toArray(arguments, 1) 14 | args.unshift(this) 15 | if (typeof plugin.install === 'function') { 16 | plugin.install.apply(plugin, args) 17 | } else if (typeof plugin === 'function') { 18 | plugin.apply(null, args) 19 | } 20 | installedPlugins.push(plugin) 21 | return this 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # wxml-transpiler 2 | 3 | ## Intro 4 | 5 | Port of wcc.cpp/wcc/wcc.exe to JavaScript: use compiler carried with Vue.js to transpile wxml ([Grammers to Support](https://mp.weixin.qq.com/debug/wxadoc/dev/framework/view/wxml/)). 6 | 7 | ## Give it a Try 8 | 9 | > Get Started 10 | 11 | ```sh 12 | # install deps 13 | npm i 14 | 15 | # build dep 16 | npm run build 17 | 18 | # run 19 | node test/test 20 | ``` 21 | 22 | > Dev Opts 23 | 24 | ```sh 25 | # auto rebuild 26 | npm run dev 27 | 28 | # autorestart type check system 29 | ## brew install watch 30 | watch -t npm run flow 31 | 32 | # autorestart test 33 | npm run autotest 34 | ``` 35 | 36 | ## Todo 37 | 38 | - error position feedback 39 | - `propStore` should better not be global 40 | - push props in parseText to reuse pushed props 41 | 42 | ## License 43 | 44 | [MIT](http://opensource.org/licenses/MIT) 45 | -------------------------------------------------------------------------------- /flow/global-api.js: -------------------------------------------------------------------------------- 1 | declare interface GlobalAPI { 2 | cid: number; 3 | options: Object; 4 | config: Config; 5 | util: Object; 6 | 7 | extend: (options: Object) => Function; 8 | set: (target: Object | Array, key: string | number, value: T) => T; 9 | delete: (target: Object| Array, key: string | number) => void; 10 | nextTick: (fn: Function, context?: Object) => void | Promise<*>; 11 | use: (plugin: Function | Object) => void; 12 | mixin: (mixin: Object) => void; 13 | compile: (template: string) => { render: Function, staticRenderFns: Array }; 14 | 15 | directive: (id: string, def?: Function | Object) => Function | Object | void; 16 | component: (id: string, def?: Class | Object) => Class; 17 | filter: (id: string, def?: Function) => Function | void; 18 | 19 | // allow dynamic method registration 20 | [key: string]: any 21 | }; 22 | -------------------------------------------------------------------------------- /src/core/util/lang.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | export const emptyObject = Object.freeze({}) 4 | 5 | /** 6 | * Check if a string starts with $ or _ 7 | */ 8 | export function isReserved (str: string): boolean { 9 | const c = (str + '').charCodeAt(0) 10 | return c === 0x24 || c === 0x5F 11 | } 12 | 13 | /** 14 | * Define a property. 15 | */ 16 | export function def (obj: Object, key: string, val: any, enumerable?: boolean) { 17 | Object.defineProperty(obj, key, { 18 | value: val, 19 | enumerable: !!enumerable, 20 | writable: true, 21 | configurable: true 22 | }) 23 | } 24 | 25 | /** 26 | * Parse simple path. 27 | */ 28 | const bailRE = /[^\w.$]/ 29 | export function parsePath (path: string): any { 30 | if (bailRE.test(path)) { 31 | return 32 | } 33 | const segments = path.split('.') 34 | return function (obj) { 35 | for (let i = 0; i < segments.length; i++) { 36 | if (!obj) return 37 | obj = obj[segments[i]] 38 | } 39 | return obj 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/platforms/web/compiler/util.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { makeMap } from 'shared/util' 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,import' 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/core/instance/render-helpers/render-slot.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { extend, warn } from 'core/util/index' 4 | 5 | /** 6 | * Runtime helper for rendering 7 | */ 8 | export function renderSlot ( 9 | name: string, 10 | fallback: ?Array, 11 | props: ?Object, 12 | bindObject: ?Object 13 | ): ?Array { 14 | const scopedSlotFn = this.$scopedSlots[name] 15 | if (scopedSlotFn) { // scoped slot 16 | props = props || {} 17 | if (bindObject) { 18 | props = extend(extend({}, bindObject), props) 19 | } 20 | return scopedSlotFn(props) || fallback 21 | } else { 22 | const slotNodes = this.$slots[name] 23 | // warn duplicate slot usage 24 | if (slotNodes && process.env.NODE_ENV !== 'production') { 25 | slotNodes._rendered && warn( 26 | `Duplicate presence of slot "${name}" found in the same render tree ` + 27 | `- this will likely cause render errors.`, 28 | this 29 | ) 30 | slotNodes._rendered = true 31 | } 32 | return slotNodes || fallback 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/core/vdom/helpers/merge-hook.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { createFnInvoker } from './update-listeners' 4 | import { remove, isDef, isUndef, isTrue } from 'shared/util' 5 | 6 | export function mergeVNodeHook (def: Object, hookKey: string, hook: Function) { 7 | let invoker 8 | const oldHook = def[hookKey] 9 | 10 | function wrappedHook () { 11 | hook.apply(this, arguments) 12 | // important: remove merged hook to ensure it's called only once 13 | // and prevent memory leak 14 | remove(invoker.fns, wrappedHook) 15 | } 16 | 17 | if (isUndef(oldHook)) { 18 | // no existing hook 19 | invoker = createFnInvoker([wrappedHook]) 20 | } else { 21 | /* istanbul ignore if */ 22 | if (isDef(oldHook.fns) && isTrue(oldHook.merged)) { 23 | // already a merged invoker 24 | invoker = oldHook 25 | invoker.fns.push(wrappedHook) 26 | } else { 27 | // existing plain hook 28 | invoker = createFnInvoker([oldHook, wrappedHook]) 29 | } 30 | } 31 | 32 | invoker.merged = true 33 | def[hookKey] = invoker 34 | } 35 | -------------------------------------------------------------------------------- /src/core/instance/render-helpers/render-list.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { isObject, isDef } from 'core/util/index' 4 | 5 | /** 6 | * Runtime helper for rendering v-for lists. 7 | */ 8 | export function renderList ( 9 | val: any, 10 | render: ( 11 | val: any, 12 | keyOrIndex: string | number, 13 | index?: number 14 | ) => VNode 15 | ): ?Array { 16 | let ret: ?Array, i, l, keys, key 17 | if (Array.isArray(val) || typeof val === 'string') { 18 | ret = new Array(val.length) 19 | for (i = 0, l = val.length; i < l; i++) { 20 | ret[i] = render(val[i], i) 21 | } 22 | } else if (typeof val === 'number') { 23 | ret = new Array(val) 24 | for (i = 0; i < val; i++) { 25 | ret[i] = render(i + 1, i) 26 | } 27 | } else if (isObject(val)) { 28 | keys = Object.keys(val) 29 | ret = new Array(keys.length) 30 | for (i = 0, l = keys.length; i < l; i++) { 31 | key = keys[i] 32 | ret[i] = render(val[key], key, i) 33 | } 34 | } 35 | if (isDef(ret)) { 36 | (ret: any)._isVList = true 37 | } 38 | return ret 39 | } 40 | -------------------------------------------------------------------------------- /src/core/observer/array.js: -------------------------------------------------------------------------------- 1 | /* 2 | * not type checking this file because flow doesn't play well with 3 | * dynamically accessing methods on Array prototype 4 | */ 5 | 6 | import { def } from '../util/index' 7 | 8 | const arrayProto = Array.prototype 9 | export const arrayMethods = Object.create(arrayProto) 10 | 11 | /** 12 | * Intercept mutating methods and emit events 13 | */ 14 | ;[ 15 | 'push', 16 | 'pop', 17 | 'shift', 18 | 'unshift', 19 | 'splice', 20 | 'sort', 21 | 'reverse' 22 | ] 23 | .forEach(function (method) { 24 | // cache original method 25 | const original = arrayProto[method] 26 | def(arrayMethods, method, function mutator (...args) { 27 | const result = original.apply(this, args) 28 | const ob = this.__ob__ 29 | let inserted 30 | switch (method) { 31 | case 'push': 32 | case 'unshift': 33 | inserted = args 34 | break 35 | case 'splice': 36 | inserted = args.slice(2) 37 | break 38 | } 39 | if (inserted) ob.observeArray(inserted) 40 | // notify change 41 | ob.dep.notify() 42 | return result 43 | }) 44 | }) 45 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013-present, Yuxi (Evan) You 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/core/vdom/modules/ref.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { remove } from 'shared/util' 4 | 5 | export default { 6 | create (_: any, vnode: VNodeWithData) { 7 | registerRef(vnode) 8 | }, 9 | update (oldVnode: VNodeWithData, vnode: VNodeWithData) { 10 | if (oldVnode.data.ref !== vnode.data.ref) { 11 | registerRef(oldVnode, true) 12 | registerRef(vnode) 13 | } 14 | }, 15 | destroy (vnode: VNodeWithData) { 16 | registerRef(vnode, true) 17 | } 18 | } 19 | 20 | export function registerRef (vnode: VNodeWithData, isRemoval: ?boolean) { 21 | const key = vnode.data.ref 22 | if (!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 | -------------------------------------------------------------------------------- /flow/modules.js: -------------------------------------------------------------------------------- 1 | // declare module 'he' { 2 | // declare function escape(html: string): string; 3 | // declare function decode(html: string): string; 4 | // } 5 | 6 | declare module 'source-map' { 7 | declare class SourceMapGenerator { 8 | setSourceContent(filename: string, content: string): void, 9 | addMapping(mapping: Object): void, 10 | toString(): string, 11 | } 12 | declare class SourceMapConsumer { 13 | constructor(map: Object): void, 14 | originalPositionFor(position: { line: number, column: number }): { 15 | source: ?string, 16 | line: ?number, 17 | column: ?number, 18 | }, 19 | } 20 | } 21 | 22 | declare module 'lru-cache' { 23 | declare var exports: { 24 | (): any, 25 | } 26 | } 27 | 28 | declare module 'de-indent' { 29 | declare var exports: { 30 | (input: string): string, 31 | } 32 | } 33 | 34 | declare module 'serialize-javascript' { 35 | declare var exports: { 36 | (input: string, options: { isJSON: boolean }): string, 37 | } 38 | } 39 | 40 | declare module 'lodash.template' { 41 | declare var exports: { 42 | (input: string, options: { interpolate: RegExp, escape: RegExp }): Function, 43 | } 44 | } 45 | 46 | declare module 'babylon' { 47 | declare function parse(input: string, options?: Object): BabylonNode 48 | } 49 | -------------------------------------------------------------------------------- /src/core/observer/dep.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import type Watcher from './watcher' 4 | import { remove } from '../util/index' 5 | 6 | let uid = 0 7 | 8 | /** 9 | * A dep is an observable that can have multiple 10 | * directives subscribing to it. 11 | */ 12 | export default class Dep { 13 | static target: ?Watcher; 14 | id: number; 15 | subs: Array; 16 | 17 | constructor () { 18 | this.id = uid++ 19 | this.subs = [] 20 | } 21 | 22 | addSub (sub: Watcher) { 23 | this.subs.push(sub) 24 | } 25 | 26 | removeSub (sub: Watcher) { 27 | remove(this.subs, sub) 28 | } 29 | 30 | depend () { 31 | if (Dep.target) { 32 | Dep.target.addDep(this) 33 | } 34 | } 35 | 36 | notify () { 37 | // stabilize the subscriber list first 38 | const subs = this.subs.slice() 39 | for (let i = 0, l = subs.length; i < l; i++) { 40 | subs[i].update() 41 | } 42 | } 43 | } 44 | 45 | // the current target watcher being evaluated. 46 | // this is globally unique because there could be only one 47 | // watcher being evaluated at any time. 48 | Dep.target = null 49 | const targetStack = [] 50 | 51 | export function pushTarget (_target: Watcher) { 52 | if (Dep.target) targetStack.push(Dep.target) 53 | Dep.target = _target 54 | } 55 | 56 | export function popTarget () { 57 | Dep.target = targetStack.pop() 58 | } 59 | -------------------------------------------------------------------------------- /src/core/global-api/assets.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import config from '../config' 4 | import { ASSET_TYPES } from 'shared/constants' 5 | import { warn, isPlainObject } from '../util/index' 6 | 7 | export function initAssetRegisters (Vue: GlobalAPI) { 8 | /** 9 | * Create asset registration methods. 10 | */ 11 | ASSET_TYPES.forEach(type => { 12 | Vue[type] = function ( 13 | id: string, 14 | definition: Function | Object 15 | ): Function | Object | void { 16 | if (!definition) { 17 | return this.options[type + 's'][id] 18 | } else { 19 | /* istanbul ignore if */ 20 | if (process.env.NODE_ENV !== 'production') { 21 | if (type === 'component' && config.isReservedTag(id)) { 22 | warn( 23 | 'Do not use built-in or reserved HTML elements as component ' + 24 | 'id: ' + id 25 | ) 26 | } 27 | } 28 | if (type === 'component' && isPlainObject(definition)) { 29 | definition.name = definition.name || id 30 | definition = this.options._base.extend(definition) 31 | } 32 | if (type === 'directive' && typeof definition === 'function') { 33 | definition = { bind: definition, update: definition } 34 | } 35 | this.options[type + 's'][id] = definition 36 | return definition 37 | } 38 | } 39 | }) 40 | } 41 | -------------------------------------------------------------------------------- /src/platforms/web/compiler/modules/class.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { parseText } from 'compiler/parser/text-parser' 4 | import { getAndRemoveAttr, getBindingAttr, baseWarn } from 'compiler/helpers' 5 | 6 | function transformNode (el: ASTElement, options: CompilerOptions) { 7 | const warn = options.warn || baseWarn 8 | const staticClass = getAndRemoveAttr(el, 'class') 9 | if (process.env.NODE_ENV !== 'production' && staticClass) { 10 | const expression = parseText(staticClass, { 11 | delimiters: options.delimiters 12 | }) 13 | if (expression) { 14 | warn( 15 | `class="${staticClass}": ` + 16 | 'Interpolation inside attributes has been removed. ' + 17 | 'Use v-bind or the colon shorthand instead. For example, ' + 18 | 'instead of
, use
.' 19 | ) 20 | } 21 | } 22 | if (staticClass) { 23 | el.staticClass = JSON.stringify(staticClass) 24 | } 25 | const classBinding = getBindingAttr(el, 'class', false /* getStatic */) 26 | if (classBinding) { 27 | el.classBinding = classBinding 28 | } 29 | } 30 | 31 | function genData (el: ASTElement): string { 32 | let data = '' 33 | if (el.staticClass) { 34 | data += `staticClass:${el.staticClass},` 35 | } 36 | if (el.classBinding) { 37 | data += `class:${el.classBinding},` 38 | } 39 | return data 40 | } 41 | 42 | export default { 43 | staticKeys: ['staticClass'], 44 | transformNode, 45 | genData 46 | } 47 | -------------------------------------------------------------------------------- /src/platforms/web/compiler/modules/style.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { parseText } from 'compiler/parser/text-parser' 4 | import { parseStyleText } from 'web/util/style' 5 | import { getAndRemoveAttr, getBindingAttr, baseWarn } from 'compiler/helpers' 6 | 7 | function transformNode (el: ASTElement, options: CompilerOptions) { 8 | const warn = options.warn || baseWarn 9 | const staticStyle = getAndRemoveAttr(el, 'style') 10 | if (staticStyle) { 11 | /* istanbul ignore if */ 12 | if (process.env.NODE_ENV !== 'production') { 13 | const expression = parseText(staticStyle, { 14 | delimiters: options.delimiters 15 | }) 16 | if (expression) { 17 | warn( 18 | `style="${staticStyle}": ` + 19 | 'Interpolation inside attributes has been removed. ' + 20 | 'Use v-bind or the colon shorthand instead. For example, ' + 21 | 'instead of
, use
.' 22 | ) 23 | } 24 | } 25 | el.staticStyle = JSON.stringify(parseStyleText(staticStyle)) 26 | } 27 | 28 | const styleBinding = getBindingAttr(el, 'style', false /* getStatic */) 29 | if (styleBinding) { 30 | el.styleBinding = styleBinding 31 | } 32 | } 33 | 34 | function genData (el: ASTElement): string { 35 | let data = '' 36 | if (el.staticStyle) { 37 | data += `staticStyle:${el.staticStyle},` 38 | } 39 | if (el.styleBinding) { 40 | data += `style:(${el.styleBinding}),` 41 | } 42 | return data 43 | } 44 | 45 | export default { 46 | staticKeys: ['staticStyle'], 47 | transformNode, 48 | genData 49 | } 50 | -------------------------------------------------------------------------------- /src/core/instance/render-helpers/bind-object-props.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import config from 'core/config' 4 | 5 | import { 6 | warn, 7 | isObject, 8 | toObject, 9 | isReservedAttribute 10 | } from 'core/util/index' 11 | 12 | /** 13 | * Runtime helper for merging v-bind="object" into a VNode's data. 14 | */ 15 | export function bindObjectProps ( 16 | data: any, 17 | tag: string, 18 | value: any, 19 | asProp: boolean, 20 | isSync?: boolean 21 | ): VNodeData { 22 | if (value) { 23 | if (!isObject(value)) { 24 | process.env.NODE_ENV !== 'production' && warn( 25 | 'v-bind without argument expects an Object or Array value', 26 | this 27 | ) 28 | } else { 29 | if (Array.isArray(value)) { 30 | value = toObject(value) 31 | } 32 | let hash 33 | for (const key in value) { 34 | if ( 35 | key === 'class' || 36 | key === 'style' || 37 | isReservedAttribute(key) 38 | ) { 39 | hash = data 40 | } else { 41 | const type = data.attrs && data.attrs.type 42 | hash = asProp || config.mustUseProp(tag, type, key) 43 | ? data.domProps || (data.domProps = {}) 44 | : data.attrs || (data.attrs = {}) 45 | } 46 | if (!(key in hash)) { 47 | hash[key] = value[key] 48 | 49 | if (isSync) { 50 | const on = data.on || (data.on = {}) 51 | on[`update:${key}`] = function ($event) { 52 | value[key] = $event 53 | } 54 | } 55 | } 56 | } 57 | } 58 | } 59 | return data 60 | } 61 | -------------------------------------------------------------------------------- /src/core/instance/render-helpers/resolve-slots.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | /** 4 | * Runtime helper for resolving raw children VNodes into a slot object. 5 | */ 6 | export function resolveSlots ( 7 | children: ?Array, 8 | context: ?Component 9 | ): { [key: string]: Array } { 10 | const slots = {} 11 | if (!children) { 12 | return slots 13 | } 14 | const defaultSlot = [] 15 | for (let i = 0, l = children.length; i < l; i++) { 16 | const child = children[i] 17 | // named slots should only be respected if the vnode was rendered in the 18 | // same context. 19 | if ((child.context === context || child.functionalContext === context) && 20 | child.data && child.data.slot != null 21 | ) { 22 | const name = child.data.slot 23 | const slot = (slots[name] || (slots[name] = [])) 24 | if (child.tag === 'template') { 25 | slot.push.apply(slot, child.children) 26 | } else { 27 | slot.push(child) 28 | } 29 | } else { 30 | defaultSlot.push(child) 31 | } 32 | } 33 | // ignore whitespace 34 | if (!defaultSlot.every(isWhitespace)) { 35 | slots.default = defaultSlot 36 | } 37 | return slots 38 | } 39 | 40 | function isWhitespace (node: VNode): boolean { 41 | return node.isComment || node.text === ' ' 42 | } 43 | 44 | export function resolveScopedSlots ( 45 | fns: ScopedSlotsData, // see flow/vnode 46 | res?: Object 47 | ): { [key: string]: Function } { 48 | res = res || {} 49 | for (let i = 0; i < fns.length; i++) { 50 | if (Array.isArray(fns[i])) { 51 | resolveScopedSlots(fns[i], res) 52 | } else { 53 | res[fns[i].key] = fns[i].fn 54 | } 55 | } 56 | return res 57 | } 58 | -------------------------------------------------------------------------------- /src/core/instance/render-helpers/render-static.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { cloneVNode, cloneVNodes } from 'core/vdom/vnode' 4 | 5 | /** 6 | * Runtime helper for rendering static trees. 7 | */ 8 | export function renderStatic ( 9 | index: number, 10 | isInFor?: boolean 11 | ): VNode | Array { 12 | let tree = this._staticTrees[index] 13 | // if has already-rendered static tree and not inside v-for, 14 | // we can reuse the same tree by doing a shallow clone. 15 | if (tree && !isInFor) { 16 | return Array.isArray(tree) 17 | ? cloneVNodes(tree) 18 | : cloneVNode(tree) 19 | } 20 | // otherwise, render a fresh tree. 21 | tree = this._staticTrees[index] = 22 | this.$options.staticRenderFns[index].call(this._renderProxy) 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: VNode | Array, 33 | index: number, 34 | key: string 35 | ) { 36 | markStatic(tree, `__once__${index}${key ? `_${key}` : ``}`, true) 37 | return tree 38 | } 39 | 40 | function markStatic ( 41 | tree: VNode | Array, 42 | key: string, 43 | isOnce: boolean 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/platforms/web/util/attrs.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { makeMap } from 'shared/util' 4 | 5 | // these are reserved for web because they are directly compiled away 6 | // during template compilation 7 | export const isReservedAttr = makeMap('style,class') 8 | 9 | // attributes that should be using props for binding 10 | const acceptValue = makeMap('input,textarea,option,select') 11 | export const mustUseProp = (tag: string, type: ?string, attr: string): boolean => { 12 | return ( 13 | (attr === 'value' && acceptValue(tag)) && type !== 'button' || 14 | (attr === 'selected' && tag === 'option') || 15 | (attr === 'checked' && tag === 'input') || 16 | (attr === 'muted' && tag === 'video') 17 | ) 18 | } 19 | 20 | 21 | export const isEnumeratedAttr = makeMap('contenteditable,draggable,spellcheck') 22 | 23 | export const isBooleanAttr = makeMap( 24 | 'allowfullscreen,async,autofocus,autoplay,checked,compact,controls,declare,' + 25 | 'default,defaultchecked,defaultmuted,defaultselected,defer,disabled,' + 26 | 'enabled,formnovalidate,hidden,indeterminate,inert,ismap,itemscope,loop,multiple,' + 27 | 'muted,nohref,noresize,noshade,novalidate,nowrap,open,pauseonexit,readonly,' + 28 | 'required,reversed,scoped,seamless,selected,sortable,translate,' + 29 | 'truespeed,typemustmatch,visible' 30 | ) 31 | 32 | export const xlinkNS = 'http://www.w3.org/1999/xlink' 33 | 34 | export const isXlink = (name: string): boolean => { 35 | return name.charAt(5) === ':' && name.slice(0, 5) === 'xlink' 36 | } 37 | 38 | export const getXlinkProp = (name: string): string => { 39 | return isXlink(name) ? name.slice(6, name.length) : '' 40 | } 41 | 42 | export const isFalsyAttrValue = (val: any): boolean => { 43 | return val == null || val === false 44 | } 45 | -------------------------------------------------------------------------------- /src/core/global-api/index.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import config from '../config' 4 | import { initUse } from './use' 5 | import { initMixin } from './mixin' 6 | import { initExtend } from './extend' 7 | import { initAssetRegisters } from './assets' 8 | import { set, del } from '../observer/index' 9 | import { ASSET_TYPES } from 'shared/constants' 10 | import builtInComponents from '../components/index' 11 | 12 | import { 13 | warn, 14 | extend, 15 | nextTick, 16 | mergeOptions, 17 | defineReactive 18 | } from '../util/index' 19 | 20 | export function initGlobalAPI (Vue: GlobalAPI) { 21 | // config 22 | const configDef = {} 23 | configDef.get = () => config 24 | if (process.env.NODE_ENV !== 'production') { 25 | configDef.set = () => { 26 | warn( 27 | 'Do not replace the Vue.config object, set individual fields instead.' 28 | ) 29 | } 30 | } 31 | Object.defineProperty(Vue, 'config', configDef) 32 | 33 | // exposed util methods. 34 | // NOTE: these are not considered part of the public API - avoid relying on 35 | // them unless you are aware of the risk. 36 | Vue.util = { 37 | warn, 38 | extend, 39 | mergeOptions, 40 | defineReactive 41 | } 42 | 43 | Vue.set = set 44 | Vue.delete = del 45 | Vue.nextTick = nextTick 46 | 47 | Vue.options = Object.create(null) 48 | ASSET_TYPES.forEach(type => { 49 | Vue.options[type + 's'] = Object.create(null) 50 | }) 51 | 52 | // this is used to identify the "base" constructor to extend all plain-object 53 | // components with in Weex's multi-instance scenarios. 54 | Vue.options._base = Vue 55 | 56 | extend(Vue.options.components, builtInComponents) 57 | 58 | initUse(Vue) 59 | initMixin(Vue) 60 | initExtend(Vue) 61 | initAssetRegisters(Vue) 62 | } 63 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const resolve = require('path').resolve 3 | const exec = require('child_process').exec 4 | const prettier = require('prettier') 5 | const compiler = require('../packages/wxml-transpiler/build.js') 6 | 7 | const fileList = ['./pages/index/index.wxml'] 8 | 9 | const srcFiles = fileList.reverse() 10 | const distDir = resolve(__dirname, './dist') 11 | const vueDist = resolve(distDir, 'test.vue.dist.js') 12 | const vueOriDist = resolve(distDir, 'test.vue.ori.dist.js') 13 | const wccDist = resolve(distDir, 'test.wcc.dist.js') 14 | const wccOriDist = resolve(distDir, 'test.wcc.ori.dist.js') 15 | const diffDist = resolve(distDir, 'vue-wcc.diff') 16 | const formatRule = { 17 | tabWidth: 2, 18 | useTabs: false, 19 | semi: false, 20 | singleQuote: true 21 | } 22 | 23 | var files = srcFiles.map(path => ({ 24 | path, 25 | template: fs.readFileSync(resolve(__dirname, path), 'utf-8') 26 | })) 27 | 28 | if (!fs.existsSync(distDir)) { 29 | fs.mkdirSync(distDir) 30 | } 31 | 32 | exec( 33 | `cd ${__dirname} && ${resolve(__dirname, './lib/wcc')} -b ${srcFiles.join(' ')}`, 34 | (err, wccRes) => { 35 | if (err) throw err 36 | fs.writeFileSync(wccOriDist, wccRes, 'utf8') 37 | fs.writeFileSync(wccDist, prettier.format(wccRes, formatRule), 'utf8') 38 | const vueRes = compiler.compile(files) 39 | console.log(vueRes.tags) 40 | fs.writeFileSync(vueOriDist, vueRes.render, 'utf8') 41 | fs.writeFileSync( 42 | vueDist, 43 | prettier.format(vueRes.render, formatRule), 44 | 'utf8' 45 | ) 46 | exec(`diff -rp ${vueDist} ${wccDist}`, (err, diffRes) => { 47 | // console.log(res) 48 | fs.writeFileSync(diffDist, diffRes, 'utf8') 49 | }) 50 | } 51 | ) 52 | 53 | console.log('See Result in test/dist dir.') 54 | -------------------------------------------------------------------------------- /src/compiler/create-compiler.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { extend } from 'shared/util' 4 | import { detectErrors } from './error-detector' 5 | import { createCompileToFunctionFn } from './to-function' 6 | 7 | export function createCompilerCreator (baseCompile: Function): Function { 8 | return function createCompiler (baseOptions: CompilerOptions) { 9 | function compile ( 10 | template: string, 11 | options?: CompilerOptions 12 | ): CompiledResult { 13 | const finalOptions = Object.create(baseOptions) 14 | const errors = [] 15 | const tips = [] 16 | finalOptions.warn = (msg, 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), 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 | const compiled = baseCompile(template, finalOptions) 42 | if (process.env.NODE_ENV !== 'production') { 43 | errors.push.apply(errors, detectErrors(compiled.ast)) 44 | } 45 | compiled.errors = errors 46 | compiled.tips = tips 47 | return compiled 48 | } 49 | 50 | return { 51 | compile, 52 | compileToFunctions: createCompileToFunctionFn(compile) 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/core/vdom/create-functional-component.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import VNode from './vnode' 4 | import { createElement } from './create-element' 5 | import { resolveInject } from '../instance/inject' 6 | import { resolveSlots } from '../instance/render-helpers/resolve-slots' 7 | 8 | import { 9 | isDef, 10 | camelize, 11 | validateProp 12 | } from '../util/index' 13 | 14 | export function createFunctionalComponent ( 15 | Ctor: Class, 16 | propsData: ?Object, 17 | data: VNodeData, 18 | context: Component, 19 | children: ?Array 20 | ): VNode | void { 21 | const props = {} 22 | const propOptions = Ctor.options.props 23 | if (isDef(propOptions)) { 24 | for (const key in propOptions) { 25 | props[key] = validateProp(key, propOptions, propsData || {}) 26 | } 27 | } else { 28 | if (isDef(data.attrs)) mergeProps(props, data.attrs) 29 | if (isDef(data.props)) mergeProps(props, data.props) 30 | } 31 | // ensure the createElement function in functional components 32 | // gets a unique context - this is necessary for correct named slot check 33 | const _context = Object.create(context) 34 | const h = (a, b, c, d) => createElement(_context, a, b, c, d, true) 35 | const vnode = Ctor.options.render.call(null, h, { 36 | data, 37 | props, 38 | children, 39 | parent: context, 40 | listeners: data.on || {}, 41 | injections: resolveInject(Ctor.options.inject, context), 42 | slots: () => resolveSlots(children, context) 43 | }) 44 | if (vnode instanceof VNode) { 45 | vnode.functionalContext = context 46 | vnode.functionalOptions = Ctor.options 47 | if (data.slot) { 48 | (vnode.data || (vnode.data = {})).slot = data.slot 49 | } 50 | } 51 | return vnode 52 | } 53 | 54 | function mergeProps (to, from) { 55 | for (const key in from) { 56 | to[camelize(key)] = from[key] 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/core/instance/inject.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { warn } from '../util/index' 4 | import { hasSymbol } from 'core/util/env' 5 | import { defineReactive, observerState } from '../observer/index' 6 | 7 | export function initProvide (vm: Component) { 8 | const provide = vm.$options.provide 9 | if (provide) { 10 | vm._provided = typeof provide === 'function' 11 | ? provide.call(vm) 12 | : provide 13 | } 14 | } 15 | 16 | export function initInjections (vm: Component) { 17 | const result = resolveInject(vm.$options.inject, vm) 18 | if (result) { 19 | observerState.shouldConvert = false 20 | Object.keys(result).forEach(key => { 21 | /* istanbul ignore else */ 22 | if (process.env.NODE_ENV !== 'production') { 23 | defineReactive(vm, key, result[key], () => { 24 | warn( 25 | `Avoid mutating an injected value directly since the changes will be ` + 26 | `overwritten whenever the provided component re-renders. ` + 27 | `injection being mutated: "${key}"`, 28 | vm 29 | ) 30 | }) 31 | } else { 32 | defineReactive(vm, key, result[key]) 33 | } 34 | }) 35 | observerState.shouldConvert = true 36 | } 37 | } 38 | 39 | export function resolveInject (inject: any, vm: Component): ?Object { 40 | if (inject) { 41 | // inject is :any because flow is not smart enough to figure out cached 42 | const result = Object.create(null) 43 | const keys = hasSymbol 44 | ? Reflect.ownKeys(inject) 45 | : Object.keys(inject) 46 | 47 | for (let i = 0; i < keys.length; i++) { 48 | const key = keys[i] 49 | const provideKey = inject[key] 50 | let source = vm 51 | while (source) { 52 | if (source._provided && provideKey in source._provided) { 53 | result[key] = source._provided[provideKey] 54 | break 55 | } 56 | source = source.$parent 57 | } 58 | if (process.env.NODE_ENV !== 'production' && !source) { 59 | warn(`Injection "${key}" not found`, vm) 60 | } 61 | } 62 | return result 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/core/vdom/helpers/update-listeners.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { warn } from 'core/util/index' 4 | import { cached, isUndef } from 'shared/util' 5 | 6 | const normalizeEvent = cached((name: string): { 7 | name: string, 8 | once: boolean, 9 | capture: boolean, 10 | passive: boolean 11 | } => { 12 | const passive = name.charAt(0) === '&' 13 | name = passive ? name.slice(1) : name 14 | const once = name.charAt(0) === '~' // Prefixed last, checked first 15 | name = once ? name.slice(1) : name 16 | const capture = name.charAt(0) === '!' 17 | name = capture ? name.slice(1) : name 18 | return { 19 | name, 20 | once, 21 | capture, 22 | passive 23 | } 24 | }) 25 | 26 | export function createFnInvoker (fns: Function | Array): Function { 27 | function invoker () { 28 | const fns = invoker.fns 29 | if (Array.isArray(fns)) { 30 | const cloned = fns.slice() 31 | for (let i = 0; i < cloned.length; i++) { 32 | cloned[i].apply(null, arguments) 33 | } 34 | } else { 35 | // return handler return value for single handlers 36 | return fns.apply(null, arguments) 37 | } 38 | } 39 | invoker.fns = fns 40 | return invoker 41 | } 42 | 43 | export function updateListeners ( 44 | on: Object, 45 | oldOn: Object, 46 | add: Function, 47 | remove: Function, 48 | vm: Component 49 | ) { 50 | let name, cur, old, event 51 | for (name in on) { 52 | cur = on[name] 53 | old = oldOn[name] 54 | event = normalizeEvent(name) 55 | if (isUndef(cur)) { 56 | process.env.NODE_ENV !== 'production' && warn( 57 | `Invalid handler for event "${event.name}": got ` + String(cur), 58 | vm 59 | ) 60 | } else if (isUndef(old)) { 61 | if (isUndef(cur.fns)) { 62 | cur = on[name] = createFnInvoker(cur) 63 | } 64 | add(event.name, cur, event.once, event.capture, event.passive) 65 | } else if (cur !== old) { 66 | old.fns = cur 67 | on[name] = old 68 | } 69 | } 70 | for (name in oldOn) { 71 | if (isUndef(on[name])) { 72 | event = normalizeEvent(name) 73 | remove(event.name, oldOn[name], event.capture) 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/core/vdom/helpers/extract-props.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { 4 | tip, 5 | hasOwn, 6 | isDef, 7 | isUndef, 8 | hyphenate, 9 | formatComponentName 10 | } from 'core/util/index' 11 | 12 | export function extractPropsFromVNodeData ( 13 | data: VNodeData, 14 | Ctor: Class, 15 | tag?: string 16 | ): ?Object { 17 | // we are only extracting raw values here. 18 | // validation and default values are handled in the child 19 | // component itself. 20 | const propOptions = Ctor.options.props 21 | if (isUndef(propOptions)) { 22 | return 23 | } 24 | const res = {} 25 | const { attrs, props } = data 26 | if (isDef(attrs) || isDef(props)) { 27 | for (const key in propOptions) { 28 | const altKey = hyphenate(key) 29 | if (process.env.NODE_ENV !== 'production') { 30 | const keyInLowerCase = key.toLowerCase() 31 | if ( 32 | key !== keyInLowerCase && 33 | attrs && hasOwn(attrs, keyInLowerCase) 34 | ) { 35 | tip( 36 | `Prop "${keyInLowerCase}" is passed to component ` + 37 | `${formatComponentName(tag || Ctor)}, but the declared prop name is` + 38 | ` "${key}". ` + 39 | `Note that HTML attributes are case-insensitive and camelCased ` + 40 | `props need to use their kebab-case equivalents when using in-DOM ` + 41 | `templates. You should probably use "${altKey}" instead of "${key}".` 42 | ) 43 | } 44 | } 45 | checkProp(res, props, key, altKey, true) || 46 | checkProp(res, attrs, key, altKey, false) 47 | } 48 | } 49 | return res 50 | } 51 | 52 | function checkProp ( 53 | res: Object, 54 | hash: ?Object, 55 | key: string, 56 | altKey: string, 57 | preserve: boolean 58 | ): boolean { 59 | if (isDef(hash)) { 60 | if (hasOwn(hash, key)) { 61 | res[key] = hash[key] 62 | if (!preserve) { 63 | delete hash[key] 64 | } 65 | return true 66 | } else if (hasOwn(hash, altKey)) { 67 | res[key] = hash[altKey] 68 | if (!preserve) { 69 | delete hash[altKey] 70 | } 71 | return true 72 | } 73 | } 74 | return false 75 | } 76 | -------------------------------------------------------------------------------- /src/platforms/web/util/style.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { cached, extend, toObject } from 'shared/util' 4 | 5 | export const parseStyleText = cached(function (cssText) { 6 | const res = {} 7 | const listDelimiter = /;(?![^(]*\))/g 8 | const propertyDelimiter = /:(.+)/ 9 | cssText.split(listDelimiter).forEach(function (item) { 10 | if (item) { 11 | var tmp = item.split(propertyDelimiter) 12 | tmp.length > 1 && (res[tmp[0].trim()] = tmp[1].trim()) 13 | } 14 | }) 15 | return res 16 | }) 17 | 18 | // merge static and dynamic style data on the same vnode 19 | function normalizeStyleData (data: VNodeData): ?Object { 20 | const style = normalizeStyleBinding(data.style) 21 | // static style is pre-processed into an object during compilation 22 | // and is always a fresh object, so it's safe to merge into it 23 | return data.staticStyle 24 | ? extend(data.staticStyle, style) 25 | : style 26 | } 27 | 28 | // normalize possible array / string values into Object 29 | export function normalizeStyleBinding (bindingStyle: any): ?Object { 30 | if (Array.isArray(bindingStyle)) { 31 | return toObject(bindingStyle) 32 | } 33 | if (typeof bindingStyle === 'string') { 34 | return parseStyleText(bindingStyle) 35 | } 36 | return bindingStyle 37 | } 38 | 39 | /** 40 | * parent component style should be after child's 41 | * so that parent component's style could override it 42 | */ 43 | export function getStyle (vnode: VNode, checkChild: boolean): Object { 44 | const res = {} 45 | let styleData 46 | 47 | if (checkChild) { 48 | let childNode = vnode 49 | while (childNode.componentInstance) { 50 | childNode = childNode.componentInstance._vnode 51 | if (childNode.data && (styleData = normalizeStyleData(childNode.data))) { 52 | extend(res, styleData) 53 | } 54 | } 55 | } 56 | 57 | if ((styleData = normalizeStyleData(vnode.data))) { 58 | extend(res, styleData) 59 | } 60 | 61 | let parentNode = vnode 62 | while ((parentNode = parentNode.parent)) { 63 | if (parentNode.data && (styleData = normalizeStyleData(parentNode.data))) { 64 | extend(res, styleData) 65 | } 66 | } 67 | return res 68 | } 69 | 70 | -------------------------------------------------------------------------------- /src/platforms/web/util/class.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { isDef, isObject } from 'shared/util' 4 | 5 | export function genClassForVnode (vnode: VNode): string { 6 | let data = vnode.data 7 | let parentNode = vnode 8 | let childNode = vnode 9 | while (isDef(childNode.componentInstance)) { 10 | childNode = childNode.componentInstance._vnode 11 | if (childNode.data) { 12 | data = mergeClassData(childNode.data, data) 13 | } 14 | } 15 | while (isDef(parentNode = parentNode.parent)) { 16 | if (parentNode.data) { 17 | data = mergeClassData(data, parentNode.data) 18 | } 19 | } 20 | return renderClass(data.staticClass, data.class) 21 | } 22 | 23 | function mergeClassData (child: VNodeData, parent: VNodeData): { 24 | staticClass: string, 25 | class: any 26 | } { 27 | return { 28 | staticClass: concat(child.staticClass, parent.staticClass), 29 | class: isDef(child.class) 30 | ? [child.class, parent.class] 31 | : parent.class 32 | } 33 | } 34 | 35 | export function renderClass ( 36 | staticClass: ?string, 37 | dynamicClass: any 38 | ): string { 39 | if (isDef(staticClass) || isDef(dynamicClass)) { 40 | return concat(staticClass, stringifyClass(dynamicClass)) 41 | } 42 | /* istanbul ignore next */ 43 | return '' 44 | } 45 | 46 | export function concat (a: ?string, b: ?string): string { 47 | return a ? b ? (a + ' ' + b) : a : (b || '') 48 | } 49 | 50 | export function stringifyClass (value: any): string { 51 | if (Array.isArray(value)) { 52 | return stringifyArray(value) 53 | } 54 | if (isObject(value)) { 55 | return stringifyObject(value) 56 | } 57 | if (typeof value === 'string') { 58 | return value 59 | } 60 | /* istanbul ignore next */ 61 | return '' 62 | } 63 | 64 | function stringifyArray (value: Array): string { 65 | let res = '' 66 | let stringified 67 | for (let i = 0, l = value.length; i < l; i++) { 68 | if (isDef(stringified = stringifyClass(value[i])) && stringified !== '') { 69 | if (res) res += ' ' 70 | res += stringified 71 | } 72 | } 73 | return res 74 | } 75 | 76 | function stringifyObject (value: Object): string { 77 | let res = '' 78 | for (const key in value) { 79 | if (value[key]) { 80 | if (res) res += ' ' 81 | res += key 82 | } 83 | } 84 | return res 85 | } 86 | -------------------------------------------------------------------------------- /src/core/instance/proxy.js: -------------------------------------------------------------------------------- 1 | /* not type checking this file because flow doesn't play well with Proxy */ 2 | 3 | import config from 'core/config' 4 | import { warn, makeMap } from '../util/index' 5 | 6 | let initProxy 7 | 8 | if (process.env.NODE_ENV !== 'production') { 9 | const allowedGlobals = makeMap( 10 | 'Infinity,undefined,NaN,isFinite,isNaN,' + 11 | 'parseFloat,parseInt,decodeURI,decodeURIComponent,encodeURI,encodeURIComponent,' + 12 | 'Math,Number,Date,Array,Object,Boolean,String,RegExp,Map,Set,JSON,Intl,' + 13 | 'require' // for Webpack/Browserify 14 | ) 15 | 16 | const warnNonPresent = (target, key) => { 17 | warn( 18 | `Property or method "${key}" is not defined on the instance but ` + 19 | `referenced during render. Make sure to declare reactive data ` + 20 | `properties in the data option.`, 21 | target 22 | ) 23 | } 24 | 25 | const hasProxy = 26 | typeof Proxy !== 'undefined' && 27 | Proxy.toString().match(/native code/) 28 | 29 | if (hasProxy) { 30 | const isBuiltInModifier = makeMap('stop,prevent,self,ctrl,shift,alt,meta') 31 | config.keyCodes = new Proxy(config.keyCodes, { 32 | set (target, key, value) { 33 | if (isBuiltInModifier(key)) { 34 | warn(`Avoid overwriting built-in modifier in config.keyCodes: .${key}`) 35 | return false 36 | } else { 37 | target[key] = value 38 | return true 39 | } 40 | } 41 | }) 42 | } 43 | 44 | const hasHandler = { 45 | has (target, key) { 46 | const has = key in target 47 | const isAllowed = allowedGlobals(key) || key.charAt(0) === '_' 48 | if (!has && !isAllowed) { 49 | warnNonPresent(target, key) 50 | } 51 | return has || !isAllowed 52 | } 53 | } 54 | 55 | const getHandler = { 56 | get (target, key) { 57 | if (typeof key === 'string' && !(key in target)) { 58 | warnNonPresent(target, key) 59 | } 60 | return target[key] 61 | } 62 | } 63 | 64 | initProxy = function initProxy (vm) { 65 | if (hasProxy) { 66 | // determine which proxy handler to use 67 | const options = vm.$options 68 | const handlers = options.render && options.render._withStripped 69 | ? getHandler 70 | : hasHandler 71 | vm._renderProxy = new Proxy(vm, handlers) 72 | } else { 73 | vm._renderProxy = vm 74 | } 75 | } 76 | } 77 | 78 | export { initProxy } 79 | -------------------------------------------------------------------------------- /flow/options.js: -------------------------------------------------------------------------------- 1 | declare type InternalComponentOptions = { 2 | _isComponent: true; 3 | parent: Component; 4 | propsData: ?Object; 5 | _parentVnode: VNode; 6 | _parentListeners: ?Object; 7 | _renderChildren: ?Array; 8 | _componentTag: ?string; 9 | _parentElm: ?Node; 10 | _refElm: ?Node; 11 | render?: Function; 12 | staticRenderFns?: Array 13 | }; 14 | 15 | declare type ComponentOptions = { 16 | // data 17 | data: Object | Function | void; 18 | props?: { [key: string]: PropOptions }; 19 | propsData?: ?Object; 20 | computed?: { 21 | [key: string]: Function | { 22 | get?: Function; 23 | set?: Function; 24 | cache?: boolean 25 | } 26 | }; 27 | methods?: { [key: string]: Function }; 28 | watch?: { [key: string]: Function | string }; 29 | 30 | // DOM 31 | el?: string | Element; 32 | template?: string; 33 | render: (h: () => VNode) => VNode; 34 | renderError?: (h: () => VNode, err: Error) => VNode; 35 | staticRenderFns?: Array<() => VNode>; 36 | 37 | // lifecycle 38 | beforeCreate?: Function; 39 | created?: Function; 40 | beforeMount?: Function; 41 | mounted?: Function; 42 | beforeUpdate?: Function; 43 | updated?: Function; 44 | activated?: Function; 45 | deactivated?: Function; 46 | beforeDestroy?: Function; 47 | destroyed?: Function; 48 | 49 | // assets 50 | directives?: { [key: string]: Object }; 51 | components?: { [key: string]: Class }; 52 | transitions?: { [key: string]: Object }; 53 | filters?: { [key: string]: Function }; 54 | 55 | // context 56 | provide?: { [key: string | Symbol]: any } | () => { [key: string | Symbol]: any }; 57 | inject?: { [key: string]: string | Symbol } | Array; 58 | 59 | // component v-model customization 60 | model?: { 61 | prop?: string; 62 | event?: string; 63 | }; 64 | 65 | // misc 66 | parent?: Component; 67 | mixins?: Array; 68 | name?: string; 69 | extends?: Class | Object; 70 | delimiters?: [string, string]; 71 | comments?: boolean; 72 | inheritAttrs?: boolean; 73 | 74 | // private 75 | _isComponent?: true; 76 | _propKeys?: Array; 77 | _parentVnode?: VNode; 78 | _parentListeners?: ?Object; 79 | _renderChildren?: ?Array; 80 | _componentTag: ?string; 81 | _scopeId: ?string; 82 | _base: Class; 83 | _parentElm: ?Node; 84 | _refElm: ?Node; 85 | }; 86 | 87 | declare type PropOptions = { 88 | type: Function | Array | null; 89 | default: any; 90 | required: ?boolean; 91 | validator: ?Function; 92 | } 93 | -------------------------------------------------------------------------------- /src/platforms/web/util/element.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { inBrowser } from 'core/util/env' 4 | import { makeMap } from 'shared/util' 5 | 6 | export const namespaceMap = { 7 | svg: 'http://www.w3.org/2000/svg', 8 | math: 'http://www.w3.org/1998/Math/MathML' 9 | } 10 | 11 | export const isHTMLTag = makeMap( 12 | 'html,body,base,head,link,meta,style,title,' + 13 | 'address,article,aside,footer,header,h1,h2,h3,h4,h5,h6,hgroup,nav,section,' + 14 | 'div,dd,dl,dt,figcaption,figure,picture,hr,img,li,main,ol,p,pre,ul,' + 15 | 'a,b,abbr,bdi,bdo,br,cite,code,data,dfn,em,i,kbd,mark,q,rp,rt,rtc,ruby,' + 16 | 's,samp,small,span,strong,sub,sup,time,u,var,wbr,area,audio,map,track,video,' + 17 | 'embed,object,param,source,canvas,script,noscript,del,ins,' + 18 | 'caption,col,colgroup,table,thead,tbody,td,th,tr,' + 19 | 'button,datalist,fieldset,form,input,label,legend,meter,optgroup,option,' + 20 | 'output,progress,select,textarea,' + 21 | 'details,dialog,menu,menuitem,summary,' + 22 | 'content,element,shadow,template,blockquote,iframe,tfoot' 23 | ) 24 | 25 | // this map is intentionally selective, only covering SVG elements that may 26 | // contain child elements. 27 | export const isSVG = makeMap( 28 | 'svg,animate,circle,clippath,cursor,defs,desc,ellipse,filter,font-face,' + 29 | 'foreignObject,g,glyph,image,line,marker,mask,missing-glyph,path,pattern,' + 30 | 'polygon,polyline,rect,switch,symbol,text,textpath,tspan,use,view', 31 | true 32 | ) 33 | 34 | export const isPreTag = (tag: ?string): boolean => tag === 'pre' 35 | 36 | export const isReservedTag = (tag: string): ?boolean => { 37 | return isHTMLTag(tag) || isSVG(tag) 38 | } 39 | 40 | export function getTagNamespace (tag: string): ?string { 41 | if (isSVG(tag)) { 42 | // return 'svg' 43 | } 44 | // basic support for MathML 45 | // note it doesn't support other MathML elements being component roots 46 | if (tag === 'math') { 47 | return 'math' 48 | } 49 | } 50 | 51 | const unknownElementCache = Object.create(null) 52 | export function isUnknownElement (tag: string): boolean { 53 | /* istanbul ignore if */ 54 | if (!inBrowser) { 55 | return true 56 | } 57 | if (isReservedTag(tag)) { 58 | return false 59 | } 60 | tag = tag.toLowerCase() 61 | /* istanbul ignore if */ 62 | if (unknownElementCache[tag] != null) { 63 | return unknownElementCache[tag] 64 | } 65 | const el = document.createElement(tag) 66 | if (tag.indexOf('-') > -1) { 67 | // http://stackoverflow.com/a/28210364/1070244 68 | return (unknownElementCache[tag] = ( 69 | el.constructor === window.HTMLUnknownElement || 70 | el.constructor === window.HTMLElement 71 | )) 72 | } else { 73 | return (unknownElementCache[tag] = /HTMLUnknownElement/.test(el.toString())) 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/compiler/parser/filter-parser.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | const validDivisionCharRE = /[\w).+\-_$\]]/ 4 | 5 | export function parseFilters (exp: string): string { 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: string, filter: string): string { 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}` 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/core/config.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { 4 | no, 5 | noop, 6 | identity 7 | } from 'shared/util' 8 | 9 | import { LIFECYCLE_HOOKS } from 'shared/constants' 10 | 11 | export type Config = { 12 | // user 13 | optionMergeStrategies: { [key: string]: Function }; 14 | silent: boolean; 15 | productionTip: boolean; 16 | performance: boolean; 17 | devtools: boolean; 18 | errorHandler: ?(err: Error, vm: Component, info: string) => void; 19 | warnHandler: ?(msg: string, vm: Component, trace: string) => void; 20 | ignoredElements: Array; 21 | keyCodes: { [key: string]: number | Array }; 22 | 23 | // platform 24 | isReservedTag: (x?: string) => boolean; 25 | isReservedAttr: (x?: string) => boolean; 26 | parsePlatformTagName: (x: string) => string; 27 | isUnknownElement: (x?: string) => boolean; 28 | getTagNamespace: (x?: string) => string | void; 29 | mustUseProp: (tag: string, type: ?string, name: string) => boolean; 30 | 31 | // legacy 32 | _lifecycleHooks: Array; 33 | }; 34 | 35 | export default ({ 36 | /** 37 | * Option merge strategies (used in core/util/options) 38 | */ 39 | optionMergeStrategies: Object.create(null), 40 | 41 | /** 42 | * Whether to suppress warnings. 43 | */ 44 | silent: false, 45 | 46 | /** 47 | * Show production mode tip message on boot? 48 | */ 49 | productionTip: process.env.NODE_ENV !== 'production', 50 | 51 | /** 52 | * Whether to enable devtools 53 | */ 54 | devtools: process.env.NODE_ENV !== 'production', 55 | 56 | /** 57 | * Whether to record perf 58 | */ 59 | performance: false, 60 | 61 | /** 62 | * Error handler for watcher errors 63 | */ 64 | errorHandler: null, 65 | 66 | /** 67 | * Warn handler for watcher warns 68 | */ 69 | warnHandler: null, 70 | 71 | /** 72 | * Ignore certain custom elements 73 | */ 74 | ignoredElements: [], 75 | 76 | /** 77 | * Custom user key aliases for v-on 78 | */ 79 | keyCodes: Object.create(null), 80 | 81 | /** 82 | * Check if a tag is reserved so that it cannot be registered as a 83 | * component. This is platform-dependent and may be overwritten. 84 | */ 85 | isReservedTag: no, 86 | 87 | /** 88 | * Check if an attribute is reserved so that it cannot be used as a component 89 | * prop. This is platform-dependent and may be overwritten. 90 | */ 91 | isReservedAttr: no, 92 | 93 | /** 94 | * Check if a tag is an unknown element. 95 | * Platform-dependent. 96 | */ 97 | isUnknownElement: no, 98 | 99 | /** 100 | * Get the namespace of an element 101 | */ 102 | getTagNamespace: noop, 103 | 104 | /** 105 | * Parse the real tag name for the specific platform. 106 | */ 107 | parsePlatformTagName: identity, 108 | 109 | /** 110 | * Check if an attribute must be bound using property, e.g. value 111 | * Platform-dependent. 112 | */ 113 | mustUseProp: no, 114 | 115 | /** 116 | * Exposed for legacy reasons 117 | */ 118 | _lifecycleHooks: LIFECYCLE_HOOKS 119 | }: Config) 120 | -------------------------------------------------------------------------------- /src/compiler/index.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { parse } from './parser/index' 4 | import { optimize } from './optimizer' 5 | import { generate } from './codegen/index' 6 | import { genTemplate } from './codegen/template' 7 | import { createCompilerCreator } from './create-compiler' 8 | 9 | // `createCompilerCreator` allows creating compilers that use alternative 10 | // parser/optimizer/codegen, e.g the SSR optimizing compiler. 11 | // Here we just export a default compiler using the default parts. 12 | export const createCompiler = createCompilerCreator(function baseCompile ( 13 | templates: Array<{ path: string, template: string }>, 14 | options: CompilerOptions 15 | ): CompiledResult | any { 16 | const initStore = { 17 | map: Object.create(null), 18 | codeInfoMap: [], 19 | props: [], 20 | tags: [] 21 | } 22 | 23 | const program: { 24 | asts: Array<{ path: string, ast: ASTElement }>, 25 | store: Store, 26 | } = templates.reduce( 27 | (codeInfos, template) => { 28 | codeInfos.store.codeInfoMap.push({ 29 | path: template.path, 30 | ti: [], 31 | ic: [], 32 | templates: [] 33 | }) 34 | 35 | try { 36 | const ast = parse( 37 | `
${template.template}
`, 38 | codeInfos.store, 39 | options 40 | ) 41 | codeInfos.asts.push({ 42 | ast, 43 | path: template.path 44 | }) 45 | } catch (e) { 46 | console.error(e.name, e.message) 47 | console.log(e) 48 | console.error(`When Parsing: ${template.path}`) 49 | process.exit(1) 50 | } 51 | return { 52 | asts: codeInfos.asts, 53 | store: codeInfos.store 54 | } 55 | }, 56 | { 57 | store: initStore, 58 | asts: [] 59 | } 60 | ) 61 | 62 | const propsCode = `var z = []; 63 | (function(z){ 64 | var a = 11; 65 | function Z(ops){z.push(ops)}; 66 | ${program.store.props.map(prop => `Z(${prop});`).join('')} 67 | })(z);` 68 | 69 | program.asts.map(ast => optimize(ast.ast, options)) 70 | 71 | const code = program.asts 72 | .map((ast, idx) => { 73 | let programBody = '' 74 | try { 75 | programBody = generate(ast.ast, program.store, idx, options).render 76 | } catch (e) { 77 | console.error(e) 78 | console.error(`When Generating: ${ast.path}`) 79 | process.exit(1) 80 | } 81 | const templateImportInfo = program.store.codeInfoMap[idx].ti 82 | .map(ti => `"${ti}"`) 83 | .join(',') 84 | const templateIncludeInfo = program.store.codeInfoMap[idx].ic 85 | .map(ic => `"${ic}"`) 86 | .join(',') 87 | 88 | return `${programBody} 89 | e_["${ast.path}"]={f:m${idx},j:[],i:[],ti:[${templateImportInfo}],ic:[${templateIncludeInfo}]};` 90 | }) 91 | .join('') 92 | 93 | return { 94 | program, 95 | render: genTemplate(propsCode + code), 96 | tags: Array.from(new Set(initStore.tags)) 97 | // staticRenderFns: code.staticRenderFns 98 | } 99 | }) 100 | -------------------------------------------------------------------------------- /src/core/util/debug.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import config from '../config' 4 | import { noop } from 'shared/util' 5 | 6 | export let warn = noop 7 | export let tip = noop 8 | export let formatComponentName: Function = (null: any) // work around flow check 9 | 10 | if (process.env.NODE_ENV !== 'production') { 11 | const hasConsole = typeof console !== 'undefined' 12 | const classifyRE = /(?:^|[-_])(\w)/g 13 | const classify = str => str 14 | .replace(classifyRE, c => c.toUpperCase()) 15 | .replace(/[-_]/g, '') 16 | 17 | warn = (msg, vm) => { 18 | const trace = vm ? generateComponentTrace(vm) : '' 19 | 20 | if (config.warnHandler) { 21 | config.warnHandler.call(null, msg, vm, trace) 22 | } else if (hasConsole && (!config.silent)) { 23 | console.error(`[Vue warn]: ${msg}${trace}`) 24 | } 25 | } 26 | 27 | tip = (msg, vm) => { 28 | if (hasConsole && (!config.silent)) { 29 | console.warn(`[Vue tip]: ${msg}` + ( 30 | vm ? generateComponentTrace(vm) : '' 31 | )) 32 | } 33 | } 34 | 35 | formatComponentName = (vm, includeFile) => { 36 | if (vm.$root === vm) { 37 | return '' 38 | } 39 | let name = typeof vm === 'string' 40 | ? vm 41 | : typeof vm === 'function' && vm.options 42 | ? vm.options.name 43 | : vm._isVue 44 | ? vm.$options.name || vm.$options._componentTag 45 | : vm.name 46 | 47 | const file = vm._isVue && vm.$options.__file 48 | if (!name && file) { 49 | const match = file.match(/([^/\\]+)\.vue$/) 50 | name = match && match[1] 51 | } 52 | 53 | return ( 54 | (name ? `<${classify(name)}>` : ``) + 55 | (file && includeFile !== false ? ` at ${file}` : '') 56 | ) 57 | } 58 | 59 | const repeat = (str, n) => { 60 | let res = '' 61 | while (n) { 62 | if (n % 2 === 1) res += str 63 | if (n > 1) str += str 64 | n >>= 1 65 | } 66 | return res 67 | } 68 | 69 | const generateComponentTrace = vm => { 70 | if (vm._isVue && vm.$parent) { 71 | const tree = [] 72 | let currentRecursiveSequence = 0 73 | while (vm) { 74 | if (tree.length > 0) { 75 | const last = tree[tree.length - 1] 76 | if (last.constructor === vm.constructor) { 77 | currentRecursiveSequence++ 78 | vm = vm.$parent 79 | continue 80 | } else if (currentRecursiveSequence > 0) { 81 | tree[tree.length - 1] = [last, currentRecursiveSequence] 82 | currentRecursiveSequence = 0 83 | } 84 | } 85 | tree.push(vm) 86 | vm = vm.$parent 87 | } 88 | return '\n\nfound in\n\n' + tree 89 | .map((vm, i) => `${ 90 | i === 0 ? '---> ' : repeat(' ', 5 + i * 2) 91 | }${ 92 | Array.isArray(vm) 93 | ? `${formatComponentName(vm[0])}... (${vm[1]} recursive calls)` 94 | : formatComponentName(vm) 95 | }`) 96 | .join('\n') 97 | } else { 98 | return `\n\n(found in ${formatComponentName(vm)})` 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/core/components/keep-alive.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { isRegExp } from 'shared/util' 4 | import { getFirstComponentChild } from 'core/vdom/helpers/index' 5 | 6 | type VNodeCache = { [key: string]: ?VNode }; 7 | 8 | const patternTypes: Array = [String, RegExp, Array] 9 | 10 | function getComponentName (opts: ?VNodeComponentOptions): ?string { 11 | return opts && (opts.Ctor.options.name || opts.tag) 12 | } 13 | 14 | function matches (pattern: string | RegExp | Array, name: string): boolean { 15 | if (Array.isArray(pattern)) { 16 | return pattern.indexOf(name) > -1 17 | } else if (typeof pattern === 'string') { 18 | return pattern.split(',').indexOf(name) > -1 19 | } else if (isRegExp(pattern)) { 20 | return pattern.test(name) 21 | } 22 | /* istanbul ignore next */ 23 | return false 24 | } 25 | 26 | function pruneCache (cache: VNodeCache, current: VNode, filter: Function) { 27 | for (const key in cache) { 28 | const cachedNode: ?VNode = cache[key] 29 | if (cachedNode) { 30 | const name: ?string = getComponentName(cachedNode.componentOptions) 31 | if (name && !filter(name)) { 32 | if (cachedNode !== current) { 33 | pruneCacheEntry(cachedNode) 34 | } 35 | cache[key] = null 36 | } 37 | } 38 | } 39 | } 40 | 41 | function pruneCacheEntry (vnode: ?VNode) { 42 | if (vnode) { 43 | vnode.componentInstance.$destroy() 44 | } 45 | } 46 | 47 | export default { 48 | name: 'keep-alive', 49 | abstract: true, 50 | 51 | props: { 52 | include: patternTypes, 53 | exclude: patternTypes 54 | }, 55 | 56 | created () { 57 | this.cache = Object.create(null) 58 | }, 59 | 60 | destroyed () { 61 | for (const key in this.cache) { 62 | pruneCacheEntry(this.cache[key]) 63 | } 64 | }, 65 | 66 | watch: { 67 | include (val: string | RegExp | Array) { 68 | pruneCache(this.cache, this._vnode, name => matches(val, name)) 69 | }, 70 | exclude (val: string | RegExp | Array) { 71 | pruneCache(this.cache, this._vnode, name => !matches(val, name)) 72 | } 73 | }, 74 | 75 | render () { 76 | const vnode: VNode = getFirstComponentChild(this.$slots.default) 77 | const componentOptions: ?VNodeComponentOptions = vnode && vnode.componentOptions 78 | if (componentOptions) { 79 | // check pattern 80 | const name: ?string = getComponentName(componentOptions) 81 | if (name && ( 82 | (this.include && !matches(this.include, name)) || 83 | (this.exclude && matches(this.exclude, name)) 84 | )) { 85 | return vnode 86 | } 87 | const key: ?string = vnode.key == null 88 | // same constructor may get registered as different local components 89 | // so cid alone is not enough (#3269) 90 | ? componentOptions.Ctor.cid + (componentOptions.tag ? `::${componentOptions.tag}` : '') 91 | : vnode.key 92 | if (this.cache[key]) { 93 | vnode.componentInstance = this.cache[key].componentInstance 94 | } else { 95 | this.cache[key] = vnode 96 | } 97 | vnode.data.keepAlive = true 98 | } 99 | return vnode 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/compiler/to-function.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { noop } from 'shared/util' 4 | import { warn, tip } from 'core/util/debug' 5 | 6 | type CompiledFunctionResult = { 7 | render: Function; 8 | staticRenderFns: Array; 9 | }; 10 | 11 | function createFunction (code, errors) { 12 | try { 13 | return new Function(code) 14 | } catch (err) { 15 | errors.push({ err, code }) 16 | return noop 17 | } 18 | } 19 | 20 | export function createCompileToFunctionFn (compile: Function): Function { 21 | const cache: { 22 | [key: string]: CompiledFunctionResult; 23 | } = Object.create(null) 24 | 25 | return function compileToFunctions ( 26 | template: string, 27 | options?: CompilerOptions, 28 | vm?: Component 29 | ): CompiledFunctionResult { 30 | options = options || {} 31 | 32 | /* istanbul ignore if */ 33 | if (process.env.NODE_ENV !== 'production') { 34 | // detect possible CSP restriction 35 | try { 36 | new Function('return 1') 37 | } catch (e) { 38 | if (e.toString().match(/unsafe-eval|CSP/)) { 39 | warn( 40 | 'It seems you are using the standalone build of Vue.js in an ' + 41 | 'environment with Content Security Policy that prohibits unsafe-eval. ' + 42 | 'The template compiler cannot work in this environment. Consider ' + 43 | 'relaxing the policy to allow unsafe-eval or pre-compiling your ' + 44 | 'templates into render functions.' 45 | ) 46 | } 47 | } 48 | } 49 | 50 | // check cache 51 | const key = options.delimiters 52 | ? String(options.delimiters) + template 53 | : template 54 | if (cache[key]) { 55 | return cache[key] 56 | } 57 | 58 | // compile 59 | const compiled = compile(template, options) 60 | 61 | // check compilation errors/tips 62 | if (process.env.NODE_ENV !== 'production') { 63 | if (compiled.errors && compiled.errors.length) { 64 | warn( 65 | `Error compiling template:\n\n${template}\n\n` + 66 | compiled.errors.map(e => `- ${e}`).join('\n') + '\n', 67 | vm 68 | ) 69 | } 70 | if (compiled.tips && compiled.tips.length) { 71 | compiled.tips.forEach(msg => tip(msg, vm)) 72 | } 73 | } 74 | 75 | // turn code into functions 76 | const res = {} 77 | const fnGenErrors = [] 78 | res.render = createFunction(compiled.render, fnGenErrors) 79 | res.staticRenderFns = compiled.staticRenderFns.map(code => { 80 | return createFunction(code, fnGenErrors) 81 | }) 82 | 83 | // check function generation errors. 84 | // this should only happen if there is a bug in the compiler itself. 85 | // mostly for codegen development use 86 | /* istanbul ignore if */ 87 | if (process.env.NODE_ENV !== 'production') { 88 | if ((!compiled.errors || !compiled.errors.length) && fnGenErrors.length) { 89 | warn( 90 | `Failed to generate render function:\n\n` + 91 | fnGenErrors.map(({ err, code }) => `${err.toString()} in\n\n${code}\n`).join('\n'), 92 | vm 93 | ) 94 | } 95 | } 96 | 97 | return (cache[key] = res) 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/core/vdom/helpers/normalize-children.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import VNode, { createTextVNode } from 'core/vdom/vnode' 4 | import { isFalse, isTrue, isDef, isUndef, isPrimitive } from 'shared/util' 5 | 6 | // The template compiler attempts to minimize the need for normalization by 7 | // statically analyzing the template at compile time. 8 | // 9 | // For plain HTML markup, normalization can be completely skipped because the 10 | // generated render function is guaranteed to return Array. There are 11 | // two cases where extra normalization is needed: 12 | 13 | // 1. When the children contains components - because a functional component 14 | // may return an Array instead of a single root. In this case, just a simple 15 | // normalization is needed - if any child is an Array, we flatten the whole 16 | // thing with Array.prototype.concat. It is guaranteed to be only 1-level deep 17 | // because functional components already normalize their own children. 18 | export function simpleNormalizeChildren (children: any) { 19 | for (let i = 0; i < children.length; i++) { 20 | if (Array.isArray(children[i])) { 21 | return Array.prototype.concat.apply([], children) 22 | } 23 | } 24 | return children 25 | } 26 | 27 | // 2. When the children contains constructs that always generated nested Arrays, 28 | // e.g.