;
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 |
--------------------------------------------------------------------------------
/vuex/src/plugins/devtool.js:
--------------------------------------------------------------------------------
1 | const devtoolHook =
2 | typeof window !== 'undefined' &&
3 | window.__VUE_DEVTOOLS_GLOBAL_HOOK__
4 |
5 | export default function devtoolPlugin (store) {
6 | if (!devtoolHook) return
7 |
8 | store._devtoolHook = devtoolHook
9 |
10 | devtoolHook.emit('vuex:init', store)
11 |
12 | devtoolHook.on('vuex:travel-to-state', targetState => {
13 | store.replaceState(targetState)
14 | })
15 |
16 | store.subscribe((mutation, state) => {
17 | devtoolHook.emit('vuex:mutation', mutation, state)
18 | })
19 | }
20 |
--------------------------------------------------------------------------------
/vue-router/src/util/warn.js:
--------------------------------------------------------------------------------
1 | /* @flow */
2 |
3 | export function assert (condition: any, message: string) {
4 | if (!condition) {
5 | throw new Error(`[vue-router] ${message}`)
6 | }
7 | }
8 |
9 | export function warn (condition: any, message: string) {
10 | if (process.env.NODE_ENV !== 'production' && !condition) {
11 | typeof console !== 'undefined' && console.warn(`[vue-router] ${message}`)
12 | }
13 | }
14 |
15 | export function isError (err: any): boolean {
16 | return Object.prototype.toString.call(err).indexOf('Error') > -1
17 | }
18 |
--------------------------------------------------------------------------------
/vue/test/weex/cases/recycle-list/v-else.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
22 |
23 |
--------------------------------------------------------------------------------
/vue/scripts/git-hooks/commit-msg:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | # Validate commit log
4 | commit_regex='^Merge.+|(feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert|types)(\(.+\))?: .{1,50}'
5 |
6 | if ! grep -iqE "$commit_regex" "$1"; then
7 | echo
8 | echo " Error: proper commit message format is required for automated changelog generation."
9 | echo
10 | echo " - Use \`npm run commit\` to interactively generate a commit message."
11 | echo " - See .github/COMMIT_CONVENTION.md for more details."
12 | echo
13 | exit 1
14 | fi
15 |
--------------------------------------------------------------------------------
/vuex/dist/logger.d.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Types for the logger plugin.
3 | * This file must be put alongside the JavaScript file of the logger.
4 | */
5 |
6 | import { Payload, Plugin } from "../types/index";
7 |
8 | export interface LoggerOption {
9 | collapsed?: boolean;
10 | filter?: (mutation: P, stateBefore: S, stateAfter: S) => boolean;
11 | transformer?: (state: S) => any;
12 | mutationTransformer?:
(mutation: P) => any;
13 | }
14 |
15 | export default function createLogger(option: LoggerOption): Plugin;
16 |
--------------------------------------------------------------------------------
/vue-router/build/release.sh:
--------------------------------------------------------------------------------
1 | set -e
2 | echo "Enter release version: "
3 | read VERSION
4 |
5 | read -p "Releasing $VERSION - are you sure? (y/n)" -n 1 -r
6 | echo # (optional) move to a new line
7 | if [[ $REPLY =~ ^[Yy]$ ]]
8 | then
9 | echo "Releasing $VERSION ..."
10 | npm test
11 | VERSION=$VERSION npm run build
12 |
13 | # commit
14 | git add -A
15 | git commit -m "[build] $VERSION"
16 | npm version $VERSION --message "[release] $VERSION"
17 |
18 | # publish
19 | git push origin refs/tags/v$VERSION
20 | git push
21 | npm publish
22 | fi
23 |
--------------------------------------------------------------------------------
/vue/src/platforms/weex/compiler/modules/recycle-list/v-once.js:
--------------------------------------------------------------------------------
1 | /* @flow */
2 |
3 | import { getAndRemoveAttr, addRawAttr } from 'compiler/helpers'
4 |
5 | function containVOnce (el: ASTElement): boolean {
6 | for (const attr in el.attrsMap) {
7 | if (/^v\-once$/i.test(attr)) {
8 | return true
9 | }
10 | }
11 | return false
12 | }
13 |
14 | export function preTransformVOnce (el: ASTElement, options: WeexCompilerOptions) {
15 | if (containVOnce(el)) {
16 | getAndRemoveAttr(el, 'v-once', true)
17 | addRawAttr(el, '[[once]]', true)
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/vue/src/platforms/weex/runtime/patch.js:
--------------------------------------------------------------------------------
1 | /* @flow */
2 |
3 | import * as nodeOps from 'weex/runtime/node-ops'
4 | import { createPatchFunction } from 'core/vdom/patch'
5 | import baseModules from 'core/vdom/modules/index'
6 | import platformModules from 'weex/runtime/modules/index'
7 |
8 | // the directive module should be applied last, after all
9 | // built-in modules have been applied.
10 | const modules = platformModules.concat(baseModules)
11 |
12 | export const patch: Function = createPatchFunction({
13 | nodeOps,
14 | modules,
15 | LONG_LIST_THRESHOLD: 10
16 | })
17 |
--------------------------------------------------------------------------------
/vue/test/weex/cases/recycle-list/components/stateful.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | other
6 |
7 |
8 |
9 |
10 |
23 |
--------------------------------------------------------------------------------
/vue/src/server/util.js:
--------------------------------------------------------------------------------
1 | /* @flow */
2 |
3 | export const isJS = (file: string): boolean => /\.js(\?[^.]+)?$/.test(file)
4 |
5 | export const isCSS = (file: string): boolean => /\.css(\?[^.]+)?$/.test(file)
6 |
7 | export function createPromiseCallback () {
8 | let resolve, reject
9 | const promise: Promise = new Promise((_resolve, _reject) => {
10 | resolve = _resolve
11 | reject = _reject
12 | })
13 | const cb = (err: Error, res?: string) => {
14 | if (err) return reject(err)
15 | resolve(res || '')
16 | }
17 | return { promise, cb }
18 | }
19 |
--------------------------------------------------------------------------------
/vue/test/helpers/classlist.js:
--------------------------------------------------------------------------------
1 | beforeEach(() => {
2 | jasmine.addMatchers({
3 | // since classList may not be supported in all browsers
4 | toHaveClass: () => {
5 | return {
6 | compare: (el, cls) => {
7 | const pass = el.classList
8 | ? el.classList.contains(cls)
9 | : el.getAttribute('class').split(/\s+/g).indexOf(cls) > -1
10 | return {
11 | pass,
12 | message: `Expected element${pass ? ' ' : ' not '}to have class ${cls}`
13 | }
14 | }
15 | }
16 | }
17 | })
18 | })
19 |
--------------------------------------------------------------------------------
/vue/test/weex/cases/recycle-list/inline-style.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | content
5 |
6 |
7 |
8 |
9 |
21 |
22 |
--------------------------------------------------------------------------------
/vue/test/weex/cases/recycle-list/v-on.vdom.js:
--------------------------------------------------------------------------------
1 | ({
2 | type: 'recycle-list',
3 | attr: {
4 | append: 'tree',
5 | listData: [
6 | { type: 'A' },
7 | { type: 'A' }
8 | ],
9 | switch: 'type',
10 | alias: 'item'
11 | },
12 | children: [{
13 | type: 'cell-slot',
14 | attr: { append: 'tree', case: 'A' },
15 | children: [{
16 | type: 'text',
17 | event: ['click', 'longpress'],
18 | attr: { value: 'A' }
19 | }, {
20 | type: 'text',
21 | event: ['touchend'],
22 | attr: { value: 'B' }
23 | }]
24 | }]
25 | })
26 |
--------------------------------------------------------------------------------
/vue/test/helpers/test-object-option.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 |
3 | export default function testObjectOption (name) {
4 | it(`Options ${name}: should warn non object value`, () => {
5 | const options = {}
6 | options[name] = () => {}
7 | new Vue(options)
8 | expect(`Invalid value for option "${name}"`).toHaveBeenWarned()
9 | })
10 |
11 | it(`Options ${name}: should not warn valid object value`, () => {
12 | const options = {}
13 | options[name] = {}
14 | new Vue(options)
15 | expect(`Invalid value for option "${name}"`).not.toHaveBeenWarned()
16 | })
17 | }
18 |
--------------------------------------------------------------------------------
/vue/test/weex/cases/recycle-list/v-on.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | A
5 | B
6 |
7 |
8 |
9 |
10 |
26 |
27 |
--------------------------------------------------------------------------------
/vuex/build/release.sh:
--------------------------------------------------------------------------------
1 | set -e
2 | echo "Enter release version: "
3 | read VERSION
4 |
5 | read -p "Releasing $VERSION - are you sure? (y/n)" -n 1 -r
6 | echo # (optional) move to a new line
7 | if [[ $REPLY =~ ^[Yy]$ ]]
8 | then
9 | echo "Releasing $VERSION ..."
10 |
11 | # run tests
12 | npm test 2>/dev/null
13 |
14 | # build
15 | VERSION=$VERSION npm run build
16 |
17 | # commit
18 | git add -A
19 | git commit -m "[build] $VERSION"
20 | npm version $VERSION --message "[release] $VERSION"
21 |
22 | # publish
23 | git push origin refs/tags/v$VERSION
24 | git push
25 | npm publish
26 | fi
27 |
--------------------------------------------------------------------------------
/vue/test/weex/cases/recycle-list/v-else-if.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
23 |
24 |
--------------------------------------------------------------------------------
/vue/test/weex/cases/recycle-list/v-for-iterator.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{object.name}}
6 | {{v}}
7 |
8 |
9 |
10 |
11 |
12 |
24 |
25 |
--------------------------------------------------------------------------------
/vue/test/weex/cases/recycle-list/components/stateless-with-props.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | content
6 |
7 |
8 |
9 |
10 |
23 |
--------------------------------------------------------------------------------
/vue/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 |
--------------------------------------------------------------------------------
/vue/src/platforms/weex/compiler/modules/recycle-list/component.js:
--------------------------------------------------------------------------------
1 | /* @flow */
2 |
3 | import { addAttr } from 'compiler/helpers'
4 | import { RECYCLE_LIST_MARKER } from 'weex/util/index'
5 |
6 | // mark components as inside recycle-list so that we know we need to invoke
7 | // their special @render function instead of render in create-component.js
8 | export function postTransformComponent (
9 | el: ASTElement,
10 | options: WeexCompilerOptions
11 | ) {
12 | // $flow-disable-line (we know isReservedTag is there)
13 | if (!options.isReservedTag(el.tag) && el.tag !== 'cell-slot') {
14 | addAttr(el, RECYCLE_LIST_MARKER, 'true')
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/vue/test/weex/cases/recycle-list/text-node.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | static
5 | {{item.dynamic}}
6 | one {{item.two}} three {{ item.four }} five
7 |
8 |
9 |
10 |
11 |
23 |
24 |
--------------------------------------------------------------------------------
/vue/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 |
--------------------------------------------------------------------------------
/vue/src/server/optimizing-compiler/index.js:
--------------------------------------------------------------------------------
1 | /* @flow */
2 |
3 | import { parse } from 'compiler/parser/index'
4 | import { generate } from './codegen'
5 | import { optimize } from './optimizer'
6 | import { createCompilerCreator } from 'compiler/create-compiler'
7 |
8 | export const createCompiler = createCompilerCreator(function baseCompile (
9 | template: string,
10 | options: CompilerOptions
11 | ): CompiledResult {
12 | const ast = parse(template.trim(), options)
13 | optimize(ast, options)
14 | const code = generate(ast, options)
15 | return {
16 | ast,
17 | render: code.render,
18 | staticRenderFns: code.staticRenderFns
19 | }
20 | })
21 |
--------------------------------------------------------------------------------
/vue/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 |
--------------------------------------------------------------------------------
/docs/v2/data-driven/index.md:
--------------------------------------------------------------------------------
1 | # 数据驱动
2 |
3 | Vue.js 一个核心思想是数据驱动。所谓数据驱动,是指视图是由数据驱动生成的,我们对视图的修改,不会直接操作 DOM,而是通过修改数据。它相比我们传统的前端开发,如使用 jQuery 等前端库直接修改 DOM,大大简化了代码量。特别是当交互复杂的时候,只关心数据的修改会让代码的逻辑变的非常清晰,因为 DOM 变成了数据的映射,我们所有的逻辑都是对数据的修改,而不用碰触 DOM,这样的代码非常利于维护。
4 |
5 | 在 Vue.js 中我们可以采用简洁的模板语法来声明式的将数据渲染为 DOM:
6 |
7 | ```html
8 |
9 | {{ message }}
10 |
11 | ```
12 |
13 | ```js
14 | var app = new Vue({
15 | el: '#app',
16 | data: {
17 | message: 'Hello Vue!'
18 | }
19 | })
20 | ```
21 |
22 | 最终它会在页面上渲染出 `Hello Vue`。接下来,我们会从源码角度来分析 Vue 是如何实现的,分析过程会以主线代码为主,重要的分支逻辑会放在之后单独分析。数据驱动还有一部分是数据更新驱动视图变化,这一块内容我们也会在之后的章节分析,这一章我们的目标是弄清楚模板和数据如何渲染成最终的 DOM。
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/vue/test/weex/cases/recycle-list/classname.vdom.js:
--------------------------------------------------------------------------------
1 | ({
2 | type: 'recycle-list',
3 | attr: {
4 | append: 'tree',
5 | listData: [
6 | { type: 'A', color: 'red' },
7 | { type: 'A', color: 'blue' }
8 | ],
9 | switch: 'type',
10 | alias: 'item'
11 | },
12 | children: [{
13 | type: 'cell-slot',
14 | attr: { append: 'tree', case: 'A' },
15 | style: {
16 | backgroundColor: '#FF6600'
17 | },
18 | children: [{
19 | type: 'text',
20 | attr: {
21 | // not supported yet
22 | // classList: ['text', { '@binding': 'item.color' }],
23 | value: 'content'
24 | }
25 | }]
26 | }]
27 | })
28 |
--------------------------------------------------------------------------------
/vue/test/weex/cases/recycle-list/v-if.vdom.js:
--------------------------------------------------------------------------------
1 | ({
2 | type: 'recycle-list',
3 | attr: {
4 | append: 'tree',
5 | listData: [
6 | { type: 'A' },
7 | { type: 'A' }
8 | ],
9 | switch: 'type',
10 | alias: 'item'
11 | },
12 | children: [{
13 | type: 'cell-slot',
14 | attr: { append: 'tree', case: 'A' },
15 | children: [{
16 | type: 'image',
17 | attr: {
18 | '[[match]]': 'item.source',
19 | src: { '@binding': 'item.source' }
20 | }
21 | }, {
22 | type: 'text',
23 | attr: {
24 | '[[match]]': '!item.source',
25 | value: 'Title'
26 | }
27 | }]
28 | }]
29 | })
30 |
--------------------------------------------------------------------------------
/vue/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 |
--------------------------------------------------------------------------------
/vue/test/weex/cases/recycle-list/components/stateful-lifecycle.vdom.js:
--------------------------------------------------------------------------------
1 | ({
2 | type: 'recycle-list',
3 | attr: {
4 | append: 'tree',
5 | listData: [
6 | { type: 'X' },
7 | { type: 'X' }
8 | ],
9 | switch: 'type',
10 | alias: 'item'
11 | },
12 | children: [{
13 | type: 'cell-slot',
14 | attr: { append: 'tree', case: 'X' },
15 | children: [{
16 | type: 'div',
17 | attr: {
18 | '@isComponentRoot': true,
19 | '@componentProps': {}
20 | },
21 | children: [{
22 | type: 'text',
23 | attr: {
24 | value: { '@binding': 'number' }
25 | }
26 | }]
27 | }]
28 | }]
29 | })
30 |
--------------------------------------------------------------------------------
/vue/test/e2e/specs/basic-ssr.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | wtf
12 |
13 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/vue/test/weex/cases/recycle-list/components/editor.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{output}}
4 |
5 |
6 |
7 |
8 |
18 |
19 |
32 |
--------------------------------------------------------------------------------
/vue/test/weex/cases/recycle-list/inline-style.vdom.js:
--------------------------------------------------------------------------------
1 | ({
2 | type: 'recycle-list',
3 | attr: {
4 | append: 'tree',
5 | listData: [
6 | { type: 'A', color: '#606060' },
7 | { type: 'A', color: '#E5E5E5' }
8 | ],
9 | switch: 'type',
10 | alias: 'item'
11 | },
12 | children: [{
13 | type: 'cell-slot',
14 | attr: { append: 'tree', case: 'A' },
15 | style: {
16 | backgroundColor: '#FF6600'
17 | },
18 | children: [{
19 | type: 'text',
20 | style: {
21 | fontSize: '100px',
22 | color: { '@binding': 'item.color' }
23 | },
24 | attr: {
25 | value: 'content'
26 | }
27 | }]
28 | }]
29 | })
30 |
--------------------------------------------------------------------------------
/docs/v2/components/index.md:
--------------------------------------------------------------------------------
1 | # 组件化
2 |
3 | Vue.js 另一个核心思想是组件化。所谓组件化,就是把页面拆分成多个组件 (component),每个组件依赖的 CSS、JavaScript、模板、图片等资源放在一起开发和维护。组件是资源独立的,组件在系统内部可复用,组件和组件之间可以嵌套。
4 |
5 | 我们在用 Vue.js 开发实际项目的时候,就是像搭积木一样,编写一堆组件拼装生成页面。在 Vue.js 的官网中,也是花了大篇幅来介绍什么是组件,如何编写组件以及组件拥有的属性和特性。
6 |
7 | 那么在这一章节,我们将从源码的角度来分析 Vue 的组件内部是如何工作的,只有了解了内部的工作原理,才能让我们使用它的时候更加得心应手。
8 |
9 | 接下来我们会用 Vue-cli 初始化的代码为例,来分析一下 Vue 组件初始化的一个过程。
10 |
11 | ```js
12 | import Vue from 'vue'
13 | import App from './App.vue'
14 |
15 | var app = new Vue({
16 | el: '#app',
17 | // 这里的 h 是 createElement 方法
18 | render: h => h(App)
19 | })
20 | ```
21 | 这段代码相信很多同学都很熟悉,它和我们上一章相同的点也是通过 `render` 函数去渲染的,不同的这次通过 `createElement` 传的参数是一个组件而不是一个原生的标签,那么接下来我们就开始分析这一过程。
--------------------------------------------------------------------------------
/vue/test/weex/cases/recycle-list/attrs.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
23 |
24 |
--------------------------------------------------------------------------------
/vue/test/weex/cases/recycle-list/v-else.vdom.js:
--------------------------------------------------------------------------------
1 | ({
2 | type: 'recycle-list',
3 | attr: {
4 | append: 'tree',
5 | listData: [
6 | { type: 'A' },
7 | { type: 'A' }
8 | ],
9 | switch: 'type',
10 | alias: 'item'
11 | },
12 | children: [{
13 | type: 'cell-slot',
14 | attr: { append: 'tree', case: 'A' },
15 | children: [{
16 | type: 'image',
17 | attr: {
18 | '[[match]]': 'item.source',
19 | src: { '@binding': 'item.source' }
20 | }
21 | }, {
22 | type: 'image',
23 | attr: {
24 | '[[match]]': '!(item.source)',
25 | src: { '@binding': 'item.placeholder' }
26 | }
27 | }]
28 | }]
29 | })
30 |
--------------------------------------------------------------------------------
/vue/test/ssr/fixtures/split.js:
--------------------------------------------------------------------------------
1 | import Vue from '../../../dist/vue.runtime.common.js'
2 |
3 | // async component!
4 | const Foo = () => import('./async-foo')
5 | const Bar = () => import('./async-bar') // eslint-disable-line
6 |
7 | export default context => {
8 | return new Promise(resolve => {
9 | context.msg = 'hello'
10 | const vm = new Vue({
11 | render (h) {
12 | return h('div', [
13 | context.url,
14 | h(Foo)
15 | ])
16 | }
17 | })
18 |
19 | // simulate router.onReady
20 | Foo().then(comp => {
21 | // resolve now to make the render sync
22 | Foo.resolved = Vue.extend(comp)
23 | resolve(vm)
24 | })
25 | })
26 | }
27 |
--------------------------------------------------------------------------------
/vue/src/platforms/weex/compiler/modules/recycle-list/text.js:
--------------------------------------------------------------------------------
1 | /* @flow */
2 |
3 | import { addAttr } from 'compiler/helpers'
4 |
5 | function genText (node: ASTNode) {
6 | const value = node.type === 3
7 | ? node.text
8 | : node.type === 2
9 | ? node.tokens.length === 1
10 | ? node.tokens[0]
11 | : node.tokens
12 | : ''
13 | return JSON.stringify(value)
14 | }
15 |
16 | export function postTransformText (el: ASTElement, options: WeexCompilerOptions) {
17 | // weex can only contain text, so the parser
18 | // always generates a single child.
19 | if (el.children.length) {
20 | addAttr(el, 'value', genText(el.children[0]))
21 | el.children = []
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/vue/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 | let div
7 | function getShouldDecode (href: boolean): boolean {
8 | div = div || document.createElement('div')
9 | div.innerHTML = href ? `` : ``
10 | return div.innerHTML.indexOf('
') > 0
11 | }
12 |
13 | // #3663: IE encodes newlines inside attribute values while other browsers don't
14 | export const shouldDecodeNewlines = inBrowser ? getShouldDecode(false) : false
15 | // #6828: chrome encodes content in a[href]
16 | export const shouldDecodeNewlinesForHref = inBrowser ? getShouldDecode(true) : false
17 |
--------------------------------------------------------------------------------
/vue-router/src/util/params.js:
--------------------------------------------------------------------------------
1 | /* @flow */
2 |
3 | import { warn } from './warn'
4 | import Regexp from 'path-to-regexp'
5 |
6 | // $flow-disable-line
7 | const regexpCompileCache: {
8 | [key: string]: Function
9 | } = Object.create(null)
10 |
11 | export function fillParams (
12 | path: string,
13 | params: ?Object,
14 | routeMsg: string
15 | ): string {
16 | try {
17 | const filler =
18 | regexpCompileCache[path] ||
19 | (regexpCompileCache[path] = Regexp.compile(path))
20 | return filler(params || {}, { pretty: true })
21 | } catch (e) {
22 | if (process.env.NODE_ENV !== 'production') {
23 | warn(false, `missing param for ${routeMsg}: ${e.message}`)
24 | }
25 | return ''
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/vue/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(existing, ours) : ours
18 | }
19 | }
20 | }
21 | return data
22 | }
23 |
--------------------------------------------------------------------------------
/vue/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 |
--------------------------------------------------------------------------------
/vue/test/weex/cases/recycle-list/v-for.vdom.js:
--------------------------------------------------------------------------------
1 | ({
2 | type: 'recycle-list',
3 | attr: {
4 | append: 'tree',
5 | listData: [
6 | { type: 'A' },
7 | { type: 'A' }
8 | ],
9 | switch: 'type',
10 | alias: 'item'
11 | },
12 | children: [{
13 | type: 'cell-slot',
14 | attr: { append: 'tree', case: 'A' },
15 | children: [{
16 | type: 'div',
17 | attr: {
18 | '[[repeat]]': {
19 | '@expression': 'item.list',
20 | '@alias': 'panel'
21 | }
22 | },
23 | children: [{
24 | type: 'text',
25 | attr: {
26 | value: {
27 | '@binding': 'panel.label'
28 | }
29 | }
30 | }]
31 | }]
32 | }]
33 | })
34 |
--------------------------------------------------------------------------------
/vue/test/weex/cases/recycle-list/v-on-inline.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Button
6 | Tips
7 |
8 |
9 |
10 |
11 |
28 |
29 |
--------------------------------------------------------------------------------
/vue/test/e2e/specs/markdown.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | 'markdown': function (browser) {
3 | browser
4 | .url('http://localhost:8080/examples/markdown/')
5 | .waitForElementVisible('#editor', 1000)
6 | .assert.value('textarea', '# hello')
7 | .assert.hasHTML('#editor div', 'hello
')
8 | .setValue('textarea', '\n## foo\n\n- bar\n- baz')
9 | // assert the output is not updated yet because of debounce
10 | .assert.hasHTML('#editor div', 'hello
')
11 | .waitFor(500)
12 | .assert.hasHTML('#editor div',
13 | 'hello
\n' +
14 | 'foo
\n' +
15 | ''
16 | )
17 | .end()
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/vue/test/weex/cases/recycle-list/components/poster.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{title}}
5 |
6 |
7 |
8 |
22 |
23 |
34 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vue-analysis",
3 | "version": "1.0.0",
4 | "description": "analysis vue.js deeply",
5 | "directories": {
6 | "doc": "doc"
7 | },
8 | "scripts": {
9 | "dev": "vuepress dev docs",
10 | "build": "vuepress build docs",
11 | "deploy": "sh build.sh"
12 | },
13 | "repository": {
14 | "type": "git",
15 | "url": "git+https://github.com/ustbhuangyi/vue-analysis.git"
16 | },
17 | "keywords": [
18 | "vue",
19 | "analysis"
20 | ],
21 | "author": "ustbuhuangyi",
22 | "license": "MIT",
23 | "bugs": {
24 | "url": "https://github.com/ustbhuangyi/vue-analysis/issues"
25 | },
26 | "homepage": "https://github.com/ustbhuangyi/vue-analysis#readme",
27 | "devDependencies": {
28 | "vuepress": "^1.2.0"
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/vue/scripts/get-weex-version.js:
--------------------------------------------------------------------------------
1 | var coreVersion = require('../package.json').version
2 | var weexVersion = require('../packages/weex-vue-framework/package.json').version
3 | var weexBaseVersion = weexVersion.match(/^[\d.]+/)[0]
4 | var weexSubVersion = Number(weexVersion.match(/-weex\.(\d+)$/)[1])
5 |
6 | if (weexBaseVersion === coreVersion) {
7 | // same core version, increment sub version
8 | weexSubVersion++
9 | } else {
10 | // new core version, reset sub version
11 | weexBaseVersion = coreVersion
12 | weexSubVersion = 1
13 | }
14 |
15 | if (process.argv[2] === '-c') {
16 | console.log(weexVersion)
17 | } else {
18 | console.log(weexBaseVersion + '-weex.' + weexSubVersion)
19 | }
20 |
21 | module.exports = {
22 | base: weexBaseVersion,
23 | sub: weexSubVersion
24 | }
25 |
--------------------------------------------------------------------------------
/vue/src/platforms/weex/compiler/modules/recycle-list/v-bind.js:
--------------------------------------------------------------------------------
1 | /* @flow */
2 |
3 | import { camelize } from 'shared/util'
4 | import { generateBinding } from 'weex/util/parser'
5 | import { bindRE } from 'compiler/parser/index'
6 | import { getAndRemoveAttr, addRawAttr } from 'compiler/helpers'
7 |
8 | function parseAttrName (name: string): string {
9 | return camelize(name.replace(bindRE, ''))
10 | }
11 |
12 | export function preTransformVBind (el: ASTElement, options: WeexCompilerOptions) {
13 | for (const attr in el.attrsMap) {
14 | if (bindRE.test(attr)) {
15 | const name: string = parseAttrName(attr)
16 | const value = generateBinding(getAndRemoveAttr(el, attr))
17 | delete el.attrsMap[attr]
18 | addRawAttr(el, name, value)
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/vue/test/weex/cases/recycle-list/classname.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | content
5 |
6 |
7 |
8 |
9 |
24 |
25 |
37 |
38 |
--------------------------------------------------------------------------------
/vue/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 | import { FunctionalRenderContext } from 'core/vdom/create-functional-component'
5 |
6 | initGlobalAPI(Vue)
7 |
8 | Object.defineProperty(Vue.prototype, '$isServer', {
9 | get: isServerRendering
10 | })
11 |
12 | Object.defineProperty(Vue.prototype, '$ssrContext', {
13 | get () {
14 | /* istanbul ignore next */
15 | return this.$vnode && this.$vnode.ssrContext
16 | }
17 | })
18 |
19 | // expose FunctionalRenderContext for ssr runtime helper installation
20 | Object.defineProperty(Vue, 'FunctionalRenderContext', {
21 | value: FunctionalRenderContext
22 | })
23 |
24 | Vue.version = '__VERSION__'
25 |
26 | export default Vue
27 |
--------------------------------------------------------------------------------
/vue/test/weex/cases/recycle-list/components/counter.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{count}}
4 | +
5 |
6 |
7 |
8 |
23 |
24 |
37 |
--------------------------------------------------------------------------------
/vue-router/test/unit/specs/discrete-components.spec.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import VueRouter from '../../../src/index'
3 |
4 | describe('[Vue Instance].$route bindings', () => {
5 | describe('boundToSingleVueInstance', () => {
6 | it('updates $route on all instances', () => {
7 | const router = new VueRouter({
8 | routes: [
9 | { path: '/', component: { name: 'foo' }},
10 | { path: '/bar', component: { name: 'bar' }}
11 | ]
12 | })
13 | const app1 = new Vue({ router })
14 | const app2 = new Vue({ router })
15 | expect(app1.$route.path).toBe('/')
16 | expect(app2.$route.path).toBe('/')
17 | router.push('/bar')
18 | expect(app1.$route.path).toBe('/bar')
19 | expect(app2.$route.path).toBe('/bar')
20 | })
21 | })
22 | })
23 |
--------------------------------------------------------------------------------
/vue/test/unit/features/options/propsData.spec.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 |
3 | describe('Options propsData', () => {
4 | it('should work', done => {
5 | const A = Vue.extend({
6 | props: ['a'],
7 | template: '{{ a }}
'
8 | })
9 | const vm = new A({
10 | propsData: {
11 | a: 123
12 | }
13 | }).$mount()
14 | expect(vm.a).toBe(123)
15 | expect(vm.$el.textContent).toBe('123')
16 | vm.a = 234
17 | waitForUpdate(() => {
18 | expect(vm.$el.textContent).toBe('234')
19 | }).then(done)
20 | })
21 |
22 | it('warn non instantiation usage', () => {
23 | Vue.extend({
24 | propsData: {
25 | a: 123
26 | }
27 | })
28 | expect('option "propsData" can only be used during instance creation').toHaveBeenWarned()
29 | })
30 | })
31 |
--------------------------------------------------------------------------------
/vue/test/weex/cases/recycle-list/components/stateless.vdom.js:
--------------------------------------------------------------------------------
1 | ({
2 | type: 'recycle-list',
3 | attr: {
4 | append: 'tree',
5 | listData: [
6 | { type: 'A' },
7 | { type: 'A' }
8 | ],
9 | switch: 'type',
10 | alias: 'item'
11 | },
12 | children: [{
13 | type: 'cell-slot',
14 | attr: { append: 'tree', case: 'A' },
15 | children: [{
16 | type: 'div',
17 | attr: {
18 | '@isComponentRoot': true,
19 | '@componentProps': {}
20 | },
21 | classList: ['banner'],
22 | children: [{
23 | type: 'text',
24 | classList: ['title'],
25 | attr: {
26 | value: 'BANNER'
27 | }
28 | }]
29 | }, {
30 | type: 'text',
31 | attr: {
32 | value: 'content'
33 | }
34 | }]
35 | }]
36 | })
37 |
--------------------------------------------------------------------------------
/vue/test/weex/cases/recycle-list/components/stateless-multi-components.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | ----
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
31 |
--------------------------------------------------------------------------------
/vue/test/e2e/specs/commits.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | 'commits': function (browser) {
3 | browser
4 | .url('http://localhost:8080/examples/commits/')
5 | .waitForElementVisible('li', 5000)
6 | .assert.count('input', 2)
7 | .assert.count('label', 2)
8 | .assert.containsText('label[for="master"]', 'master')
9 | .assert.containsText('label[for="dev"]', 'dev')
10 | .assert.checked('#master')
11 | .assert.checked('#dev', false)
12 | .assert.containsText('p', 'vuejs/vue@master')
13 | .assert.count('li', 3)
14 | .assert.count('li .commit', 3)
15 | .assert.count('li .message', 3)
16 | .click('#dev')
17 | .assert.containsText('p', 'vuejs/vue@dev')
18 | .assert.count('li', 3)
19 | .assert.count('li .commit', 3)
20 | .assert.count('li .message', 3)
21 | .end()
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/.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='^weex/\(.*\)$' -> '/src/platforms/weex/\1'
20 | module.name_mapper='^server/\(.*\)$' -> '/src/server/\1'
21 | module.name_mapper='^entries/\(.*\)$' -> '/src/entries/\1'
22 | module.name_mapper='^sfc/\(.*\)$' -> '/src/sfc/\1'
23 | suppress_comment= \\(.\\|\n\\)*\\$flow-disable-line
24 |
--------------------------------------------------------------------------------
/vue/src/platforms/weex/compiler/modules/append.js:
--------------------------------------------------------------------------------
1 | /* @flow */
2 |
3 | import { makeMap } from 'shared/util'
4 |
5 | // The "unitary tag" means that the tag node and its children
6 | // must be sent to the native together.
7 | const isUnitaryTag = makeMap('cell,header,cell-slot,recycle-list', true)
8 |
9 | function preTransformNode (el: ASTElement, options: CompilerOptions) {
10 | if (isUnitaryTag(el.tag) && !el.attrsList.some(item => item.name === 'append')) {
11 | el.attrsMap.append = 'tree'
12 | el.attrsList.push({ name: 'append', value: 'tree' })
13 | }
14 | if (el.attrsMap.append === 'tree') {
15 | el.appendAsTree = true
16 | }
17 | }
18 |
19 | function genData (el: ASTElement): string {
20 | return el.appendAsTree ? `appendAsTree:true,` : ''
21 | }
22 |
23 | export default {
24 | staticKeys: ['appendAsTree'],
25 | preTransformNode,
26 | genData
27 | }
28 |
--------------------------------------------------------------------------------
/vue/test/weex/cases/recycle-list/attrs.vdom.js:
--------------------------------------------------------------------------------
1 | ({
2 | type: 'recycle-list',
3 | attr: {
4 | append: 'tree',
5 | listData: [
6 | { type: 'A', count: 1, source: 'http://whatever.com/x.png' },
7 | { type: 'A', count: 2, source: 'http://whatever.com/y.png' },
8 | { type: 'A', count: 3, source: 'http://whatever.com/z.png' }
9 | ],
10 | switch: 'type',
11 | alias: 'item'
12 | },
13 | children: [{
14 | type: 'cell-slot',
15 | attr: { append: 'tree', case: 'A' },
16 | children: [{
17 | type: 'image',
18 | attr: {
19 | resize: 'cover',
20 | src: {
21 | '@binding': 'item.source'
22 | }
23 | }
24 | }, {
25 | type: 'text',
26 | attr: {
27 | lines: '3',
28 | count: {
29 | '@binding': 'item.count'
30 | }
31 | }
32 | }]
33 | }]
34 | })
35 |
--------------------------------------------------------------------------------
/vue/src/server/webpack-plugin/util.js:
--------------------------------------------------------------------------------
1 | const { red, yellow } = require('chalk')
2 |
3 | const prefix = `[vue-server-renderer-webpack-plugin]`
4 | const warn = exports.warn = msg => console.error(red(`${prefix} ${msg}\n`))
5 | const tip = exports.tip = msg => console.log(yellow(`${prefix} ${msg}\n`))
6 |
7 | export const validate = compiler => {
8 | if (compiler.options.target !== 'node') {
9 | warn('webpack config `target` should be "node".')
10 | }
11 |
12 | if (compiler.options.output && compiler.options.output.libraryTarget !== 'commonjs2') {
13 | warn('webpack config `output.libraryTarget` should be "commonjs2".')
14 | }
15 |
16 | if (!compiler.options.externals) {
17 | tip(
18 | 'It is recommended to externalize dependencies in the server build for ' +
19 | 'better build performance.'
20 | )
21 | }
22 | }
23 |
24 | export { isJS, isCSS } from '../util'
25 |
--------------------------------------------------------------------------------
/vue/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 |
--------------------------------------------------------------------------------
/vue/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 { createCompilerCreator } from './create-compiler'
7 |
8 | // `createCompilerCreator` allows creating compilers that use alternative
9 | // parser/optimizer/codegen, e.g the SSR optimizing compiler.
10 | // Here we just export a default compiler using the default parts.
11 | export const createCompiler = createCompilerCreator(function baseCompile (
12 | template: string,
13 | options: CompilerOptions
14 | ): CompiledResult {
15 | const ast = parse(template.trim(), options)
16 | if (options.optimize !== false) {
17 | optimize(ast, options)
18 | }
19 | const code = generate(ast, options)
20 | return {
21 | ast,
22 | render: code.render,
23 | staticRenderFns: code.staticRenderFns
24 | }
25 | })
26 |
--------------------------------------------------------------------------------
/vue/src/platforms/weex/compiler/modules/recycle-list/v-on.js:
--------------------------------------------------------------------------------
1 | /* @flow */
2 |
3 | const inlineStatementRE = /^\s*([A-Za-z_$0-9\['\."\]]+)*\s*\(\s*(([A-Za-z_$0-9\['\."\]]+)?(\s*,\s*([A-Za-z_$0-9\['\."\]]+))*)\s*\)$/
4 |
5 | function parseHandlerParams (handler: ASTElementHandler) {
6 | const res = inlineStatementRE.exec(handler.value)
7 | if (res && res[2]) {
8 | handler.params = res[2].split(/\s*,\s*/)
9 | }
10 | }
11 |
12 | export function postTransformVOn (el: ASTElement, options: WeexCompilerOptions) {
13 | const events: ASTElementHandlers | void = el.events
14 | if (!events) {
15 | return
16 | }
17 | for (const name in events) {
18 | const handler: ASTElementHandler | Array = events[name]
19 | if (Array.isArray(handler)) {
20 | handler.map(fn => parseHandlerParams(fn))
21 | } else {
22 | parseHandlerParams(handler)
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/vue/test/weex/cases/recycle-list/v-else-if.vdom.js:
--------------------------------------------------------------------------------
1 | ({
2 | type: 'recycle-list',
3 | attr: {
4 | append: 'tree',
5 | listData: [
6 | { type: 'A' },
7 | { type: 'A' }
8 | ],
9 | switch: 'type',
10 | alias: 'item'
11 | },
12 | children: [{
13 | type: 'cell-slot',
14 | attr: { append: 'tree', case: 'A' },
15 | children: [{
16 | type: 'image',
17 | attr: {
18 | '[[match]]': 'item.sourceA',
19 | src: { '@binding': 'item.sourceA' }
20 | }
21 | }, {
22 | type: 'image',
23 | attr: {
24 | '[[match]]': '!(item.sourceA) && (item.sourceB)',
25 | src: { '@binding': 'item.sourceB' }
26 | }
27 | }, {
28 | type: 'image',
29 | attr: {
30 | '[[match]]': '!(item.sourceA || item.sourceB)',
31 | src: { '@binding': 'item.placeholder' }
32 | }
33 | }]
34 | }]
35 | })
36 |
--------------------------------------------------------------------------------
/vue/src/platforms/web/server/modules/style.js:
--------------------------------------------------------------------------------
1 | /* @flow */
2 |
3 | import { escape } from '../util'
4 | import { hyphenate } from 'shared/util'
5 | import { getStyle } from 'web/util/style'
6 |
7 | export function genStyle (style: Object): string {
8 | let styleText = ''
9 | for (const key in style) {
10 | const value = style[key]
11 | const hyphenatedKey = hyphenate(key)
12 | if (Array.isArray(value)) {
13 | for (let i = 0, len = value.length; i < len; i++) {
14 | styleText += `${hyphenatedKey}:${value[i]};`
15 | }
16 | } else {
17 | styleText += `${hyphenatedKey}:${value};`
18 | }
19 | }
20 | return styleText
21 | }
22 |
23 | export default function renderStyle (vnode: VNodeWithData): ?string {
24 | const styleText = genStyle(getStyle(vnode, false))
25 | if (styleText !== '') {
26 | return ` style=${JSON.stringify(escape(styleText))}`
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/vue/test/weex/cases/recycle-list/v-on-inline.vdom.js:
--------------------------------------------------------------------------------
1 | ({
2 | type: 'recycle-list',
3 | attr: {
4 | append: 'tree',
5 | listData: [
6 | { type: 'A' },
7 | { type: 'A' }
8 | ],
9 | switch: 'type',
10 | alias: 'item'
11 | },
12 | children: [{
13 | type: 'cell-slot',
14 | attr: { append: 'tree', case: 'A' },
15 | children: [{
16 | type: 'text',
17 | event: ['click', {
18 | type: 'longpress',
19 | params: [{ '@binding': 'item.key' }]
20 | }]
21 | }, {
22 | type: 'text',
23 | event: [{
24 | type: 'appear',
25 | params: [
26 | { '@binding': 'item.index' },
27 | { '@binding': 'item.type' }
28 | ]
29 | }],
30 | attr: { value: 'Button' }
31 | }, {
32 | type: 'text',
33 | event: [{ type: 'disappear' }],
34 | attr: { value: 'Tips' }
35 | }]
36 | }]
37 | })
38 |
--------------------------------------------------------------------------------
/vuex/test/e2e/runner.js:
--------------------------------------------------------------------------------
1 | var spawn = require('cross-spawn')
2 | var args = process.argv.slice(2)
3 |
4 | var server = args.indexOf('--dev') > -1
5 | ? null
6 | : require('../../examples/server')
7 |
8 | if (args.indexOf('--config') === -1) {
9 | args = args.concat(['--config', 'test/e2e/nightwatch.config.js'])
10 | }
11 | if (args.indexOf('--env') === -1) {
12 | args = args.concat(['--env', 'phantomjs'])
13 | }
14 | var i = args.indexOf('--test')
15 | if (i > -1) {
16 | args[i + 1] = 'test/e2e/specs/' + args[i + 1]
17 | }
18 | if (args.indexOf('phantomjs') > -1) {
19 | process.env.PHANTOMJS = true
20 | }
21 |
22 | var runner = spawn('./node_modules/.bin/nightwatch', args, {
23 | stdio: 'inherit'
24 | })
25 |
26 | runner.on('exit', function (code) {
27 | server && server.close()
28 | process.exit(code)
29 | })
30 |
31 | runner.on('error', function (err) {
32 | server && server.close()
33 | throw err
34 | })
35 |
--------------------------------------------------------------------------------
/vue/test/e2e/runner.js:
--------------------------------------------------------------------------------
1 | var path = require('path')
2 | var spawn = require('cross-spawn')
3 | var httpServer = require('http-server')
4 | var server = httpServer.createServer({
5 | root: path.resolve(__dirname, '../../')
6 | })
7 |
8 | server.listen(8080)
9 |
10 | var args = process.argv.slice(2)
11 | if (args.indexOf('--config') === -1) {
12 | args = args.concat(['--config', 'test/e2e/nightwatch.config.js'])
13 | }
14 | if (args.indexOf('--env') === -1) {
15 | args = args.concat(['--env', 'chrome,phantomjs'])
16 | }
17 | var i = args.indexOf('--test')
18 | if (i > -1) {
19 | args[i + 1] = 'test/e2e/specs/' + args[i + 1] + '.js'
20 | }
21 |
22 | var runner = spawn('./node_modules/.bin/nightwatch', args, {
23 | stdio: 'inherit'
24 | })
25 |
26 | runner.on('exit', function (code) {
27 | server.close()
28 | process.exit(code)
29 | })
30 |
31 | runner.on('error', function (err) {
32 | server.close()
33 | throw err
34 | })
35 |
--------------------------------------------------------------------------------
/vue/test/unit/features/options/parent.spec.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 |
3 | describe('Options parent', () => {
4 | it('should work', () => {
5 | const parent = new Vue({
6 | render () {}
7 | }).$mount()
8 |
9 | const child = new Vue({
10 | parent: parent,
11 | render () {}
12 | }).$mount()
13 |
14 | // this option is straight-forward
15 | // it should register 'parent' as a $parent for 'child'
16 | // and push 'child' to $children array on 'parent'
17 | expect(child.$options.parent).toBeDefined()
18 | expect(child.$options.parent).toEqual(parent)
19 | expect(child.$parent).toBeDefined()
20 | expect(child.$parent).toEqual(parent)
21 | expect(parent.$children).toContain(child)
22 |
23 | // destroy 'child' and check if it was removed from 'parent' $children
24 | child.$destroy()
25 | expect(parent.$children.length).toEqual(0)
26 | parent.$destroy()
27 | })
28 | })
29 |
--------------------------------------------------------------------------------
/vue/test/weex/cases/recycle-list/text-node.vdom.js:
--------------------------------------------------------------------------------
1 | ({
2 | type: 'recycle-list',
3 | attr: {
4 | append: 'tree',
5 | listData: [
6 | { type: 'A', dynamic: 'decimal', two: '2', four: '4' },
7 | { type: 'A', dynamic: 'binary', two: '10', four: '100' }
8 | ],
9 | switch: 'type',
10 | alias: 'item'
11 | },
12 | children: [{
13 | type: 'cell-slot',
14 | attr: { append: 'tree', case: 'A' },
15 | children: [{
16 | type: 'text',
17 | attr: {
18 | value: 'static'
19 | }
20 | }, {
21 | type: 'text',
22 | attr: {
23 | value: { '@binding': 'item.dynamic' }
24 | }
25 | }, {
26 | type: 'text',
27 | attr: {
28 | value: [
29 | 'one ',
30 | { '@binding': 'item.two' },
31 | ' three ',
32 | { '@binding': 'item.four' },
33 | ' five'
34 | ]
35 | }
36 | }]
37 | }]
38 | })
39 |
--------------------------------------------------------------------------------
/vue/src/core/util/lang.js:
--------------------------------------------------------------------------------
1 | /* @flow */
2 |
3 | /**
4 | * Check if a string starts with $ or _
5 | */
6 | export function isReserved (str: string): boolean {
7 | const c = (str + '').charCodeAt(0)
8 | return c === 0x24 || c === 0x5F
9 | }
10 |
11 | /**
12 | * Define a property.
13 | */
14 | export function def (obj: Object, key: string, val: any, enumerable?: boolean) {
15 | Object.defineProperty(obj, key, {
16 | value: val,
17 | enumerable: !!enumerable,
18 | writable: true,
19 | configurable: true
20 | })
21 | }
22 |
23 | /**
24 | * Parse simple path.
25 | */
26 | const bailRE = /[^\w.$]/
27 | export function parsePath (path: string): any {
28 | if (bailRE.test(path)) {
29 | return
30 | }
31 | const segments = path.split('.')
32 | return function (obj) {
33 | for (let i = 0; i < segments.length; i++) {
34 | if (!obj) return
35 | obj = obj[segments[i]]
36 | }
37 | return obj
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/vue/src/platforms/weex/compiler/modules/recycle-list/v-for.js:
--------------------------------------------------------------------------------
1 | /* @flow */
2 |
3 | import { parseFor } from 'compiler/parser/index'
4 | import { getAndRemoveAttr, addRawAttr } from 'compiler/helpers'
5 |
6 | export function preTransformVFor (el: ASTElement, options: WeexCompilerOptions) {
7 | const exp = getAndRemoveAttr(el, 'v-for')
8 | if (!exp) {
9 | return
10 | }
11 |
12 | const res = parseFor(exp)
13 | if (!res) {
14 | if (process.env.NODE_ENV !== 'production' && options.warn) {
15 | options.warn(`Invalid v-for expression: ${exp}`)
16 | }
17 | return
18 | }
19 |
20 | const desc: Object = {
21 | '@expression': res.for,
22 | '@alias': res.alias
23 | }
24 | if (res.iterator2) {
25 | desc['@key'] = res.iterator1
26 | desc['@index'] = res.iterator2
27 | } else {
28 | desc['@index'] = res.iterator1
29 | }
30 |
31 | delete el.attrsMap['v-for']
32 | addRawAttr(el, '[[repeat]]', desc)
33 | }
34 |
--------------------------------------------------------------------------------
/vuex/test/e2e/specs/counter.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | 'counter': function (browser) {
3 | browser
4 | .url('http://localhost:8080/counter/')
5 | .waitForElementVisible('#app', 1000)
6 | .assert.containsText('div', 'Clicked: 0 times')
7 | .click('button:nth-child(1)')
8 | .assert.containsText('div', 'Clicked: 1 times')
9 | .click('button:nth-child(2)')
10 | .assert.containsText('div', 'Clicked: 0 times')
11 | .click('button:nth-child(3)')
12 | .assert.containsText('div', 'Clicked: 0 times')
13 | .click('button:nth-child(1)')
14 | .assert.containsText('div', 'Clicked: 1 times')
15 | .click('button:nth-child(3)')
16 | .assert.containsText('div', 'Clicked: 2 times')
17 | .click('button:nth-child(4)')
18 | .assert.containsText('div', 'Clicked: 2 times')
19 | .waitFor(1000)
20 | .assert.containsText('div', 'Clicked: 3 times')
21 | .end()
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/vue/src/platforms/weex/compiler/modules/props.js:
--------------------------------------------------------------------------------
1 | /* @flow */
2 |
3 | import { cached, camelize } from 'shared/util'
4 |
5 | const normalize = cached(camelize)
6 |
7 | function normalizeKeyName (str: string): string {
8 | if (str.match(/^v\-/)) {
9 | return str.replace(/(v-[a-z\-]+\:)([a-z\-]+)$/i, ($, directive, prop) => {
10 | return directive + normalize(prop)
11 | })
12 | }
13 | return normalize(str)
14 | }
15 |
16 | function transformNode (el: ASTElement, options: CompilerOptions) {
17 | if (Array.isArray(el.attrsList)) {
18 | el.attrsList.forEach(attr => {
19 | if (attr.name && attr.name.match(/\-/)) {
20 | const realName = normalizeKeyName(attr.name)
21 | if (el.attrsMap) {
22 | el.attrsMap[realName] = el.attrsMap[attr.name]
23 | delete el.attrsMap[attr.name]
24 | }
25 | attr.name = realName
26 | }
27 | })
28 | }
29 | }
30 | export default {
31 | transformNode
32 | }
33 |
--------------------------------------------------------------------------------
/vue/test/weex/cases/recycle-list/components/stateful-v-model.vdom.js:
--------------------------------------------------------------------------------
1 | ({
2 | type: 'recycle-list',
3 | attr: {
4 | append: 'tree',
5 | listData: [
6 | { type: 'A' },
7 | { type: 'A' }
8 | ],
9 | switch: 'type',
10 | alias: 'item'
11 | },
12 | children: [{
13 | type: 'cell-slot',
14 | attr: { append: 'tree', case: 'A' },
15 | children: [{
16 | type: 'div',
17 | attr: {
18 | '@isComponentRoot': true,
19 | '@componentProps': {
20 | message: 'No binding'
21 | }
22 | },
23 | children: [{
24 | type: 'text',
25 | classList: ['output'],
26 | attr: {
27 | value: { '@binding': 'output' }
28 | }
29 | }, {
30 | type: 'input',
31 | event: ['input'],
32 | classList: ['input'],
33 | attr: {
34 | type: 'text',
35 | value: 'No binding'
36 | }
37 | }]
38 | }]
39 | }]
40 | })
41 |
--------------------------------------------------------------------------------
/vue/scripts/verify-commit-msg.js:
--------------------------------------------------------------------------------
1 | const chalk = require('chalk')
2 | const msgPath = process.env.GIT_PARAMS
3 | const msg = require('fs').readFileSync(msgPath, 'utf-8').trim()
4 |
5 | const commitRE = /^(revert: )?(feat|fix|polish|docs|style|refactor|perf|test|workflow|ci|chore|types|build)(\(.+\))?: .{1,50}/
6 |
7 | if (!commitRE.test(msg)) {
8 | console.log()
9 | console.error(
10 | ` ${chalk.bgRed.white(' ERROR ')} ${chalk.red(`invalid commit message format.`)}\n\n` +
11 | chalk.red(` Proper commit message format is required for automated changelog generation. Examples:\n\n`) +
12 | ` ${chalk.green(`feat(compiler): add 'comments' option`)}\n` +
13 | ` ${chalk.green(`fix(v-model): handle events on blur (close #28)`)}\n\n` +
14 | chalk.red(` See .github/COMMIT_CONVENTION.md for more details.\n`) +
15 | chalk.red(` You can also use ${chalk.cyan(`npm run commit`)} to interactively generate a commit message.\n`)
16 | )
17 | process.exit(1)
18 | }
19 |
--------------------------------------------------------------------------------
/vue-router/test/e2e/runner.js:
--------------------------------------------------------------------------------
1 | var path = require('path')
2 | var spawn = require('cross-spawn')
3 | var args = process.argv.slice(2)
4 |
5 | var server = args.indexOf('--dev') > -1
6 | ? null
7 | : require('../../examples/server')
8 |
9 | if (args.indexOf('--config') === -1) {
10 | args = args.concat(['--config', 'test/e2e/nightwatch.config.js'])
11 | }
12 | if (args.indexOf('--env') === -1) {
13 | args = args.concat(['--env', 'phantomjs'])
14 | }
15 | var i = args.indexOf('--test')
16 | if (i > -1) {
17 | args[i + 1] = 'test/e2e/specs/' + args[i + 1].replace(/\.js$/, '') + '.js'
18 | }
19 | if (args.indexOf('phantomjs') > -1) {
20 | process.env.PHANTOMJS = true
21 | }
22 |
23 | var runner = spawn('./node_modules/.bin/nightwatch', args, {
24 | stdio: 'inherit'
25 | })
26 |
27 | runner.on('exit', function (code) {
28 | server && server.close()
29 | process.exit(code)
30 | })
31 |
32 | runner.on('error', function (err) {
33 | server && server.close()
34 | throw err
35 | })
36 |
--------------------------------------------------------------------------------
/vue/test/weex/compiler/props.spec.js:
--------------------------------------------------------------------------------
1 | import { compile } from '../../../packages/weex-template-compiler'
2 | import { strToRegExp } from '../helpers/index'
3 |
4 | describe('compile props', () => {
5 | it('custom props', () => {
6 | const { render, staticRenderFns, errors } = compile(``)
7 | expect(render).not.toBeUndefined()
8 | expect(staticRenderFns).not.toBeUndefined()
9 | expect(staticRenderFns.length).toEqual(0)
10 | expect(render).toMatch(strToRegExp(`attrs:{"custom":"whatever"}`))
11 | expect(errors).toEqual([])
12 | })
13 |
14 | it('camelize props', () => {
15 | const { render, staticRenderFns, errors } = compile(``)
16 | expect(render).not.toBeUndefined()
17 | expect(staticRenderFns).not.toBeUndefined()
18 | expect(staticRenderFns.length).toEqual(0)
19 | expect(render).toMatch(strToRegExp(`attrs:{"kebabCase":"whatever"}`))
20 | expect(errors).toEqual([])
21 | })
22 | })
23 |
--------------------------------------------------------------------------------
/vue/test/unit/features/directives/pre.spec.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 |
3 | describe('Directive v-pre', function () {
4 | it('should not compile inner content', function () {
5 | const vm = new Vue({
6 | template: `
7 |
{{ a }}
8 |
{{ a }}
9 |
10 |
11 |
12 |
`,
13 | data: {
14 | a: 123
15 | }
16 | })
17 | vm.$mount()
18 | expect(vm.$el.firstChild.textContent).toBe('{{ a }}')
19 | expect(vm.$el.children[1].textContent).toBe('123')
20 | expect(vm.$el.lastChild.innerHTML).toBe('')
21 | })
22 |
23 | it('should not compile on root node', function () {
24 | const vm = new Vue({
25 | template: '{{ a }}
',
26 | replace: true,
27 | data: {
28 | a: 123
29 | }
30 | })
31 | vm.$mount()
32 | expect(vm.$el.firstChild.textContent).toBe('{{ a }}')
33 | })
34 | })
35 |
--------------------------------------------------------------------------------
/vue/src/server/create-basic-renderer.js:
--------------------------------------------------------------------------------
1 | /* @flow */
2 |
3 | import { createWriteFunction } from './write'
4 | import { createRenderFunction } from './render'
5 | import type { RenderOptions } from './create-renderer'
6 |
7 | export function createBasicRenderer ({
8 | modules = [],
9 | directives = {},
10 | isUnaryTag = (() => false),
11 | cache
12 | }: RenderOptions = {}) {
13 | const render = createRenderFunction(modules, directives, isUnaryTag, cache)
14 |
15 | return function renderToString (
16 | component: Component,
17 | context: any,
18 | done: any
19 | ): void {
20 | if (typeof context === 'function') {
21 | done = context
22 | context = {}
23 | }
24 | let result = ''
25 | const write = createWriteFunction(text => {
26 | result += text
27 | return false
28 | }, done)
29 | try {
30 | render(component, write, context, () => {
31 | done(null, result)
32 | })
33 | } catch (e) {
34 | done(e)
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/vue/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'
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 |
--------------------------------------------------------------------------------
/vue-router/test/unit/specs/query.spec.js:
--------------------------------------------------------------------------------
1 | import { resolveQuery, stringifyQuery } from '../../../src/util/query'
2 |
3 | describe('Query utils', () => {
4 | describe('resolveQuery', () => {
5 | it('should work', () => {
6 | const query = resolveQuery('foo=bar&foo=k', { baz: 'qux' })
7 | expect(JSON.stringify(query)).toBe(JSON.stringify({
8 | foo: ['bar', 'k'],
9 | baz: 'qux'
10 | }))
11 | })
12 | })
13 |
14 | describe('stringifyQuery', () => {
15 | it('should work', () => {
16 | expect(stringifyQuery({
17 | foo: 'bar',
18 | baz: 'qux',
19 | arr: [1, 2]
20 | })).toBe('?foo=bar&baz=qux&arr=1&arr=2')
21 | })
22 |
23 | it('should escape reserved chars', () => {
24 | expect(stringifyQuery({
25 | a: '*()!'
26 | })).toBe('?a=%2a%28%29%21')
27 | })
28 |
29 | it('should preserve commas', () => {
30 | expect(stringifyQuery({
31 | list: '1,2,3'
32 | })).toBe('?list=1,2,3')
33 | })
34 | })
35 | })
36 |
--------------------------------------------------------------------------------
/vue/test/unit/karma.cover.config.js:
--------------------------------------------------------------------------------
1 | var base = require('./karma.base.config.js')
2 |
3 | module.exports = function (config) {
4 | var options = Object.assign(base, {
5 | browsers: ['PhantomJS'],
6 | reporters: ['mocha', 'coverage'],
7 | coverageReporter: {
8 | reporters: [
9 | { type: 'lcov', dir: '../../coverage', subdir: '.' },
10 | { type: 'text-summary', dir: '../../coverage', subdir: '.' }
11 | ]
12 | },
13 | singleRun: true,
14 | plugins: base.plugins.concat([
15 | 'karma-coverage',
16 | 'karma-phantomjs-launcher'
17 | ])
18 | })
19 |
20 | // add babel-plugin-istanbul for code instrumentation
21 | options.webpack.module.rules[0].options = {
22 | plugins: [['istanbul', {
23 | exclude: [
24 | 'test/',
25 | 'src/compiler/parser/html-parser.js',
26 | 'src/core/instance/proxy.js',
27 | 'src/sfc/deindent.js',
28 | 'src/platforms/weex/'
29 | ]
30 | }]]
31 | }
32 |
33 | config.set(options)
34 | }
35 |
--------------------------------------------------------------------------------
/vue/test/weex/cases/recycle-list/components/stateful.vdom.js:
--------------------------------------------------------------------------------
1 | ({
2 | type: 'recycle-list',
3 | attr: {
4 | append: 'tree',
5 | listData: [
6 | { type: 'A', number: 24 },
7 | { type: 'A', number: 42 }
8 | ],
9 | switch: 'type',
10 | alias: 'item'
11 | },
12 | children: [{
13 | type: 'cell-slot',
14 | attr: { append: 'tree', case: 'A' },
15 | children: [{
16 | type: 'div',
17 | attr: {
18 | '@isComponentRoot': true,
19 | '@componentProps': {
20 | start: { '@binding': 'item.number' }
21 | }
22 | },
23 | children: [{
24 | type: 'text',
25 | classList: ['output'],
26 | attr: {
27 | value: { '@binding': 'count' } // need confirm
28 | }
29 | }, {
30 | type: 'text',
31 | event: ['click'],
32 | classList: ['button'],
33 | attr: { value: '+' }
34 | }]
35 | }, {
36 | type: 'text',
37 | attr: { value: 'other' }
38 | }]
39 | }]
40 | })
41 |
--------------------------------------------------------------------------------
/vuex/src/mixin.js:
--------------------------------------------------------------------------------
1 | export default function (Vue) {
2 | const version = Number(Vue.version.split('.')[0])
3 |
4 | if (version >= 2) {
5 | Vue.mixin({ beforeCreate: vuexInit })
6 | } else {
7 | // override init and inject vuex init procedure
8 | // for 1.x backwards compatibility.
9 | const _init = Vue.prototype._init
10 | Vue.prototype._init = function (options = {}) {
11 | options.init = options.init
12 | ? [vuexInit].concat(options.init)
13 | : vuexInit
14 | _init.call(this, options)
15 | }
16 | }
17 |
18 | /**
19 | * Vuex init hook, injected into each instances init hooks list.
20 | */
21 |
22 | function vuexInit () {
23 | const options = this.$options
24 | // store injection
25 | if (options.store) {
26 | this.$store = typeof options.store === 'function'
27 | ? options.store()
28 | : options.store
29 | } else if (options.parent && options.parent.$store) {
30 | this.$store = options.parent.$store
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/vue/src/platforms/web/entry-server-renderer.js:
--------------------------------------------------------------------------------
1 | /* @flow */
2 |
3 | process.env.VUE_ENV = 'server'
4 |
5 | import { extend } from 'shared/util'
6 | import modules from './server/modules/index'
7 | import baseDirectives from './server/directives/index'
8 | import { isUnaryTag, canBeLeftOpenTag } from './compiler/util'
9 |
10 | import { createRenderer as _createRenderer } from 'server/create-renderer'
11 | import { createBundleRendererCreator } from 'server/bundle-renderer/create-bundle-renderer'
12 |
13 | export function createRenderer (options?: Object = {}): {
14 | renderToString: Function,
15 | renderToStream: Function
16 | } {
17 | return _createRenderer(extend(extend({}, options), {
18 | isUnaryTag,
19 | canBeLeftOpenTag,
20 | modules,
21 | // user can provide server-side implementations for custom directives
22 | // when creating the renderer.
23 | directives: extend(baseDirectives, options.directives)
24 | }))
25 | }
26 |
27 | export const createBundleRenderer = createBundleRendererCreator(createRenderer)
28 |
--------------------------------------------------------------------------------
/vue/test/unit/modules/util/next-tick.spec.js:
--------------------------------------------------------------------------------
1 | import { nextTick } from 'core/util/next-tick'
2 |
3 | describe('nextTick', () => {
4 | it('accepts a callback', done => {
5 | nextTick(done)
6 | })
7 |
8 | it('returns undefined when passed a callback', () => {
9 | expect(nextTick(() => {})).toBeUndefined()
10 | })
11 |
12 | if (typeof Promise !== 'undefined') {
13 | it('returns a Promise when provided no callback', done => {
14 | nextTick().then(done)
15 | })
16 |
17 | it('returns a Promise with a context argument when provided a falsy callback and an object', done => {
18 | const obj = {}
19 | nextTick(undefined, obj).then(ctx => {
20 | expect(ctx).toBe(obj)
21 | done()
22 | })
23 | })
24 |
25 | it('returned Promise should resolve correctly vs callback', done => {
26 | const spy = jasmine.createSpy()
27 | nextTick(spy)
28 | nextTick().then(() => {
29 | expect(spy).toHaveBeenCalled()
30 | done()
31 | })
32 | })
33 | }
34 | })
35 |
--------------------------------------------------------------------------------
/vue/test/ssr/compile-with-webpack.js:
--------------------------------------------------------------------------------
1 | import path from 'path'
2 | import webpack from 'webpack'
3 | import MemoryFS from 'memory-fs'
4 |
5 | export function compileWithWebpack (file, extraConfig, cb) {
6 | const config = Object.assign({
7 | entry: path.resolve(__dirname, 'fixtures', file),
8 | module: {
9 | rules: [
10 | {
11 | test: /\.js$/,
12 | loader: 'babel-loader'
13 | },
14 | {
15 | test: /async-.*\.js$/,
16 | loader: require.resolve('./async-loader')
17 | },
18 | {
19 | test: /\.(png|woff2|css)$/,
20 | loader: 'file-loader',
21 | options: {
22 | name: '[name].[ext]'
23 | }
24 | }
25 | ]
26 | }
27 | }, extraConfig)
28 |
29 | const compiler = webpack(config)
30 | const fs = new MemoryFS()
31 | compiler.outputFileSystem = fs
32 |
33 | compiler.run((err, stats) => {
34 | expect(err).toBeFalsy()
35 | expect(stats.errors).toBeFalsy()
36 | cb(fs)
37 | })
38 | }
39 |
--------------------------------------------------------------------------------
/vue/scripts/release-weex.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 | CUR_VERSION=$(node build/get-weex-version.js -c)
4 | NEXT_VERSION=$(node build/get-weex-version.js)
5 |
6 | echo "Current: $CUR_VERSION"
7 | read -p "Enter new version ($NEXT_VERSION): " -n 1 -r
8 | if ! [[ -z $REPLY ]]; then
9 | NEXT_VERSION=$REPLY
10 | fi
11 |
12 | read -p "Releasing weex-vue-framework@$NEXT_VERSION - are you sure? (y/n) " -n 1 -r
13 | echo
14 | if [[ $REPLY =~ ^[Yy]$ ]]; then
15 | echo "Releasing weex-vue-framework@$NEXT_VERSION ..."
16 | npm run lint
17 | npm run flow
18 | npm run test:weex
19 |
20 | # build
21 | WEEX_VERSION=$NEXT_VERSION npm run build:weex
22 |
23 | # update package
24 | # using subshells to avoid having to cd back
25 | ( cd packages/weex-vue-framework
26 | npm version "$NEXT_VERSION"
27 | npm publish
28 | )
29 |
30 | ( cd packages/weex-template-compiler
31 | npm version "$NEXT_VERSION"
32 | npm publish
33 | )
34 |
35 | # commit
36 | git add packages/weex*
37 | git commit -m "[release] weex-vue-framework@$NEXT_VERSION"
38 | fi
39 |
--------------------------------------------------------------------------------
/docs/v2/reactive/index.md:
--------------------------------------------------------------------------------
1 | # 深入响应式原理
2 |
3 | 前面 2 章介绍的都是 Vue 怎么实现数据渲染和组件化的,主要讲的是初始化的过程,把原始的数据最终映射到 DOM 中,但并没有涉及到数据变化到 DOM 变化的部分。而 Vue 的数据驱动除了数据渲染 DOM 之外,还有一个很重要的体现就是数据的变更会触发 DOM 的变化。
4 |
5 | 其实前端开发最重要的 2 个工作,一个是把数据渲染到页面,另一个是处理用户交互。Vue 把数据渲染到页面的能力我们已经通过源码分析出其中的原理了,但是由于一些用户交互或者是其它方面导致数据发生变化重新对页面渲染的原理我们还未分析。
6 |
7 | 考虑如下示例:
8 |
9 | ```html
10 |
11 | {{ message }}
12 |
13 | ```
14 |
15 | ```js
16 | var app = new Vue({
17 | el: '#app',
18 | data: {
19 | message: 'Hello Vue!'
20 | },
21 | methods: {
22 | changeMsg() {
23 | this.message = 'Hello World!'
24 | }
25 | }
26 | })
27 | ```
28 | 当我们去修改 `this.message` 的时候,模板对应的插值也会渲染成新的数据,那么这一切是怎么做到的呢?
29 |
30 | 在分析前,我们先直观的想一下,如果不用 Vue 的话,我们会通过最简单的方法实现这个需求:监听点击事件,修改数据,手动操作 DOM 重新渲染。这个过程和使用 Vue 的最大区别就是多了一步“手动操作 DOM 重新渲染”。这一步看上去并不多,但它背后又潜在的几个要处理的问题:
31 |
32 | 1. 我需要修改哪块的 DOM?
33 |
34 | 2. 我的修改效率和性能是不是最优的?
35 |
36 | 3. 我需要对数据每一次的修改都去操作 DOM 吗?
37 |
38 | 4. 我需要 case by case 去写修改 DOM 的逻辑吗?
39 |
40 | 如果我们使用了 Vue,那么上面几个问题 Vue 内部就帮你做了,那么 Vue 是如何在我们对数据修改后自动做这些事情呢,接下来我们将进入一些 Vue 响应式系统的底层的细节。
--------------------------------------------------------------------------------
/vue-router/test/e2e/specs/nested-router.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | 'basic': function (browser) {
3 | browser
4 | .url('http://localhost:8080/nested-router/')
5 | .waitForElementVisible('#app', 1000)
6 | .assert.count('li a', 3)
7 |
8 | .click('li:nth-child(1) a')
9 | .assert.urlEquals('http://localhost:8080/nested-router/nested-router')
10 | .assert.containsText('.child', 'Child router path: /')
11 | .assert.count('li a', 5)
12 |
13 | .click('.child li:nth-child(1) a')
14 | .assert.containsText('.child', 'Child router path: /foo')
15 | .assert.containsText('.child .foo', 'foo')
16 |
17 | .click('.child li:nth-child(2) a')
18 | .assert.containsText('.child', 'Child router path: /bar')
19 | .assert.containsText('.child .bar', 'bar')
20 |
21 | .click('li:nth-child(2) a')
22 | .assert.urlEquals('http://localhost:8080/nested-router/foo')
23 | .assert.elementNotPresent('.child')
24 | .assert.containsText('#app', 'foo')
25 | .assert.count('li a', 3)
26 | .end()
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/vue/src/platforms/weex/compiler/directives/model.js:
--------------------------------------------------------------------------------
1 | /* @flow */
2 |
3 | import { addHandler, addAttr } from 'compiler/helpers'
4 | import { genComponentModel, genAssignmentCode } from 'compiler/directives/model'
5 |
6 | export default function model (
7 | el: ASTElement,
8 | dir: ASTDirective,
9 | _warn: Function
10 | ): ?boolean {
11 | if (el.tag === 'input' || el.tag === 'textarea') {
12 | genDefaultModel(el, dir.value, dir.modifiers)
13 | } else {
14 | genComponentModel(el, dir.value, dir.modifiers)
15 | }
16 | }
17 |
18 | function genDefaultModel (
19 | el: ASTElement,
20 | value: string,
21 | modifiers: ?ASTModifiers
22 | ): ?boolean {
23 | const { lazy, trim, number } = modifiers || {}
24 | const event = lazy ? 'change' : 'input'
25 |
26 | let valueExpression = `$event.target.attr.value${trim ? '.trim()' : ''}`
27 | if (number) {
28 | valueExpression = `_n(${valueExpression})`
29 | }
30 |
31 | const code = genAssignmentCode(value, valueExpression)
32 | addAttr(el, 'value', `(${value})`)
33 | addHandler(el, event, code, null, true)
34 | }
35 |
--------------------------------------------------------------------------------
/vue/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 |
--------------------------------------------------------------------------------
/vue-router/test/e2e/specs/data-fetching.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | 'data fetching': function (browser) {
3 | browser
4 | .url('http://localhost:8080/data-fetching/')
5 | .waitForElementVisible('#app', 1000)
6 | .assert.count('li a', 4)
7 | .assert.containsText('.view', 'home')
8 |
9 | .click('li:nth-child(2) a')
10 | .waitForElementNotPresent('.loading', 500)
11 | .assert.containsText('.post h2', 'sunt aut facere')
12 | .assert.containsText('.post p', 'quia et suscipit')
13 |
14 | .click('li:nth-child(3) a')
15 | .waitForElementNotPresent('.loading', 500)
16 | .assert.containsText('.post h2', 'qui est esse')
17 | .assert.containsText('.post p', 'est rerum tempore')
18 |
19 | .click('li:nth-child(4) a')
20 | .waitForElementNotPresent('.loading', 500)
21 | .assert.elementNotPresent('.content')
22 | .assert.containsText('.error', 'Post not found')
23 |
24 | .click('li:nth-child(1) a')
25 | .assert.elementNotPresent('.post')
26 | .assert.containsText('.view', 'home')
27 | .end()
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/vue/test/unit/features/options/inheritAttrs.spec.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 |
3 | describe('Options inheritAttrs', () => {
4 | it('should work', done => {
5 | const vm = new Vue({
6 | template: ``,
7 | data: { foo: 'foo' },
8 | components: {
9 | foo: {
10 | inheritAttrs: false,
11 | template: `foo
`
12 | }
13 | }
14 | }).$mount()
15 | expect(vm.$el.id).toBe('')
16 | vm.foo = 'bar'
17 | waitForUpdate(() => {
18 | expect(vm.$el.id).toBe('')
19 | }).then(done)
20 | })
21 |
22 | it('with inner v-bind', done => {
23 | const vm = new Vue({
24 | template: ``,
25 | data: { foo: 'foo' },
26 | components: {
27 | foo: {
28 | inheritAttrs: false,
29 | template: ``
30 | }
31 | }
32 | }).$mount()
33 | expect(vm.$el.children[0].id).toBe('foo')
34 | vm.foo = 'bar'
35 | waitForUpdate(() => {
36 | expect(vm.$el.children[0].id).toBe('bar')
37 | }).then(done)
38 | })
39 | })
40 |
--------------------------------------------------------------------------------
/vue/test/weex/cases/recycle-list/v-for-iterator.vdom.js:
--------------------------------------------------------------------------------
1 | ({
2 | type: 'recycle-list',
3 | attr: {
4 | append: 'tree',
5 | listData: [
6 | { type: 'A' },
7 | { type: 'A' }
8 | ],
9 | switch: 'type',
10 | alias: 'item'
11 | },
12 | children: [{
13 | type: 'cell-slot',
14 | attr: { append: 'tree', case: 'A' },
15 | children: [{
16 | type: 'div',
17 | attr: {
18 | '[[repeat]]': {
19 | '@expression': 'item.list',
20 | '@index': 'index',
21 | '@alias': 'object'
22 | }
23 | },
24 | children: [{
25 | type: 'text',
26 | attr: {
27 | value: {
28 | '@binding': 'object.name'
29 | }
30 | }
31 | }, {
32 | type: 'text',
33 | attr: {
34 | '[[repeat]]': {
35 | '@expression': 'object',
36 | '@alias': 'v',
37 | '@key': 'k',
38 | '@index': 'i'
39 | },
40 | value: {
41 | '@binding': 'v'
42 | }
43 | }
44 | }]
45 | }]
46 | }]
47 | })
48 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 HuangYi
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/vue/src/core/observer/traverse.js:
--------------------------------------------------------------------------------
1 | /* @flow */
2 |
3 | import { _Set as Set, isObject } from '../util/index'
4 | import type { SimpleSet } from '../util/index'
5 | import VNode from '../vdom/vnode'
6 |
7 | const seenObjects = new Set()
8 |
9 | /**
10 | * Recursively traverse an object to evoke all converted
11 | * getters, so that every nested property inside the object
12 | * is collected as a "deep" dependency.
13 | */
14 | export function traverse (val: any) {
15 | _traverse(val, seenObjects)
16 | seenObjects.clear()
17 | }
18 |
19 | function _traverse (val: any, seen: SimpleSet) {
20 | let i, keys
21 | const isA = Array.isArray(val)
22 | if ((!isA && !isObject(val)) || Object.isFrozen(val) || val instanceof VNode) {
23 | return
24 | }
25 | if (val.__ob__) {
26 | const depId = val.__ob__.dep.id
27 | if (seen.has(depId)) {
28 | return
29 | }
30 | seen.add(depId)
31 | }
32 | if (isA) {
33 | i = val.length
34 | while (i--) _traverse(val[i], seen)
35 | } else {
36 | keys = Object.keys(val)
37 | i = keys.length
38 | while (i--) _traverse(val[keys[i]], seen)
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/vue/src/platforms/web/runtime/modules/class.js:
--------------------------------------------------------------------------------
1 | /* @flow */
2 |
3 | import {
4 | isDef,
5 | isUndef
6 | } from 'shared/util'
7 |
8 | import {
9 | concat,
10 | stringifyClass,
11 | genClassForVnode
12 | } from 'web/util/index'
13 |
14 | function updateClass (oldVnode: any, vnode: any) {
15 | const el = vnode.elm
16 | const data: VNodeData = vnode.data
17 | const oldData: VNodeData = oldVnode.data
18 | if (
19 | isUndef(data.staticClass) &&
20 | isUndef(data.class) && (
21 | isUndef(oldData) || (
22 | isUndef(oldData.staticClass) &&
23 | isUndef(oldData.class)
24 | )
25 | )
26 | ) {
27 | return
28 | }
29 |
30 | let cls = genClassForVnode(vnode)
31 |
32 | // handle transition classes
33 | const transitionClass = el._transitionClasses
34 | if (isDef(transitionClass)) {
35 | cls = concat(cls, stringifyClass(transitionClass))
36 | }
37 |
38 | // set the class
39 | if (cls !== el._prevClass) {
40 | el.setAttribute('class', cls)
41 | el._prevClass = cls
42 | }
43 | }
44 |
45 | export default {
46 | create: updateClass,
47 | update: updateClass
48 | }
49 |
--------------------------------------------------------------------------------
/vue/test/e2e/specs/modal.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | 'modal': function (browser) {
3 | browser
4 | .url('http://localhost:8080/examples/modal/')
5 | .waitForElementVisible('#app', 1000)
6 | .assert.elementNotPresent('.modal-mask')
7 | .click('#show-modal')
8 | .assert.elementPresent('.modal-mask')
9 | .assert.elementPresent('.modal-wrapper')
10 | .assert.elementPresent('.modal-container')
11 | .waitFor(50)
12 | .assert.cssClassPresent('.modal-mask', 'modal-enter-active')
13 | .waitFor(300)
14 | .assert.cssClassNotPresent('.modal-mask', 'modal-enter-active')
15 | .assert.containsText('.modal-header h3', 'custom header')
16 | .assert.containsText('.modal-body', 'default body')
17 | .assert.containsText('.modal-footer', 'default footer')
18 | .click('.modal-default-button')
19 | // should have transition
20 | .assert.elementPresent('.modal-mask')
21 | .waitFor(50)
22 | .assert.cssClassPresent('.modal-mask', 'modal-leave-active')
23 | .waitFor(300)
24 | .assert.elementNotPresent('.modal-mask')
25 | .end()
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/vue/test/weex/cases/recycle-list/components/stateless-with-props.vdom.js:
--------------------------------------------------------------------------------
1 | ({
2 | type: 'recycle-list',
3 | attr: {
4 | append: 'tree',
5 | listData: [
6 | { type: 'A', poster: 'xx', title: 'x' },
7 | { type: 'A', poster: 'yy', title: 'y' }
8 | ],
9 | switch: 'type',
10 | alias: 'item'
11 | },
12 | children: [{
13 | type: 'cell-slot',
14 | attr: { append: 'tree', case: 'A' },
15 | children: [{
16 | type: 'div',
17 | attr: {
18 | '@isComponentRoot': true,
19 | '@componentProps': {
20 | imageUrl: { '@binding': 'item.poster' },
21 | title: { '@binding': 'item.title' }
22 | }
23 | },
24 | children: [{
25 | type: 'image',
26 | classList: ['image'],
27 | attr: {
28 | src: { '@binding': 'imageUrl' }
29 | }
30 | }, {
31 | type: 'text',
32 | classList: ['title'],
33 | attr: {
34 | value: { '@binding': 'title' }
35 | }
36 | }]
37 | }, {
38 | type: 'text',
39 | attr: {
40 | value: 'content'
41 | }
42 | }]
43 | }]
44 | })
45 |
--------------------------------------------------------------------------------
/vue/test/unit/karma.base.config.js:
--------------------------------------------------------------------------------
1 | var alias = require('../../scripts/alias')
2 | var webpack = require('webpack')
3 |
4 | var webpackConfig = {
5 | resolve: {
6 | alias: alias
7 | },
8 | module: {
9 | rules: [
10 | {
11 | test: /\.js$/,
12 | loader: 'babel-loader',
13 | exclude: /node_modules/
14 | }
15 | ]
16 | },
17 | plugins: [
18 | new webpack.DefinePlugin({
19 | __WEEX__: false,
20 | 'process.env': {
21 | NODE_ENV: '"development"',
22 | TRANSITION_DURATION: process.env.CI ? 100 : 50,
23 | TRANSITION_BUFFER: 10
24 | }
25 | })
26 | ],
27 | devtool: '#inline-source-map'
28 | }
29 |
30 | // shared config for all unit tests
31 | module.exports = {
32 | frameworks: ['jasmine'],
33 | files: [
34 | './index.js'
35 | ],
36 | preprocessors: {
37 | './index.js': ['webpack', 'sourcemap']
38 | },
39 | webpack: webpackConfig,
40 | webpackMiddleware: {
41 | noInfo: true
42 | },
43 | plugins: [
44 | 'karma-jasmine',
45 | 'karma-mocha-reporter',
46 | 'karma-sourcemap-loader',
47 | 'karma-webpack'
48 | ]
49 | }
50 |
--------------------------------------------------------------------------------
/vue/src/core/instance/render-helpers/check-keycodes.js:
--------------------------------------------------------------------------------
1 | /* @flow */
2 |
3 | import config from 'core/config'
4 | import { hyphenate } from 'shared/util'
5 |
6 | function isKeyNotMatch (expect: T | Array, actual: T): boolean {
7 | if (Array.isArray(expect)) {
8 | return expect.indexOf(actual) === -1
9 | } else {
10 | return expect !== actual
11 | }
12 | }
13 |
14 | /**
15 | * Runtime helper for checking keyCodes from config.
16 | * exposed as Vue.prototype._k
17 | * passing in eventKeyName as last argument separately for backwards compat
18 | */
19 | export function checkKeyCodes (
20 | eventKeyCode: number,
21 | key: string,
22 | builtInKeyCode?: number | Array,
23 | eventKeyName?: string,
24 | builtInKeyName?: string | Array
25 | ): ?boolean {
26 | const mappedKeyCode = config.keyCodes[key] || builtInKeyCode
27 | if (builtInKeyName && eventKeyName && !config.keyCodes[key]) {
28 | return isKeyNotMatch(builtInKeyName, eventKeyName)
29 | } else if (mappedKeyCode) {
30 | return isKeyNotMatch(mappedKeyCode, eventKeyCode)
31 | } else if (eventKeyName) {
32 | return hyphenate(eventKeyName) !== key
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/vue/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 | const methodsToPatch = [
12 | 'push',
13 | 'pop',
14 | 'shift',
15 | 'unshift',
16 | 'splice',
17 | 'sort',
18 | 'reverse'
19 | ]
20 |
21 | /**
22 | * Intercept mutating methods and emit events
23 | */
24 | methodsToPatch.forEach(function (method) {
25 | // cache original method
26 | const original = arrayProto[method]
27 | def(arrayMethods, method, function mutator (...args) {
28 | const result = original.apply(this, args)
29 | const ob = this.__ob__
30 | let inserted
31 | switch (method) {
32 | case 'push':
33 | case 'unshift':
34 | inserted = args
35 | break
36 | case 'splice':
37 | inserted = args.slice(2)
38 | break
39 | }
40 | if (inserted) ob.observeArray(inserted)
41 | // notify change
42 | ob.dep.notify()
43 | return result
44 | })
45 | })
46 |
--------------------------------------------------------------------------------
/vue/src/server/template-renderer/parse-template.js:
--------------------------------------------------------------------------------
1 | /* @flow */
2 |
3 | const compile = require('lodash.template')
4 | const compileOptions = {
5 | escape: /{{([^{][\s\S]+?[^}])}}/g,
6 | interpolate: /{{{([\s\S]+?)}}}/g
7 | }
8 |
9 | export type ParsedTemplate = {
10 | head: (data: any) => string;
11 | neck: (data: any) => string;
12 | tail: (data: any) => string;
13 | };
14 |
15 | export function parseTemplate (
16 | template: string,
17 | contentPlaceholder?: string = ''
18 | ): ParsedTemplate {
19 | if (typeof template === 'object') {
20 | return template
21 | }
22 |
23 | let i = template.indexOf('')
24 | const j = template.indexOf(contentPlaceholder)
25 |
26 | if (j < 0) {
27 | throw new Error(`Content placeholder not found in template.`)
28 | }
29 |
30 | if (i < 0) {
31 | i = template.indexOf('')
32 | if (i < 0) {
33 | i = j
34 | }
35 | }
36 |
37 | return {
38 | head: compile(template.slice(0, i), compileOptions),
39 | neck: compile(template.slice(i, j), compileOptions),
40 | tail: compile(template.slice(j + contentPlaceholder.length), compileOptions)
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/vue-router/test/e2e/specs/lazy-loading.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | 'lazy loading': function (browser) {
3 | browser
4 | .url('http://localhost:8080/lazy-loading/')
5 | .waitForElementVisible('#app', 1000)
6 | .assert.count('li a', 4)
7 | .assert.containsText('.view', 'home')
8 |
9 | .click('li:nth-child(2) a')
10 | .assert.containsText('.view', 'This is Foo!')
11 |
12 | .click('li:nth-child(3) a')
13 | .assert.containsText('.view', 'This is Bar!')
14 |
15 | .click('li:nth-child(1) a')
16 | .assert.containsText('.view', 'home')
17 |
18 | .click('li:nth-child(4) a')
19 | .assert.containsText('.view', 'This is Bar!')
20 | .assert.containsText('.view h3', 'Baz')
21 |
22 | // test initial visit
23 | .url('http://localhost:8080/lazy-loading/foo')
24 | .waitForElementVisible('#app', 1000)
25 | .assert.containsText('.view', 'This is Foo!')
26 |
27 | .url('http://localhost:8080/lazy-loading/bar/baz')
28 | .waitForElementVisible('#app', 1000)
29 | .assert.containsText('.view', 'This is Bar!')
30 | .assert.containsText('.view h3', 'Baz')
31 | .end()
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/vue/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 |
--------------------------------------------------------------------------------
/vue/src/core/instance/render-helpers/index.js:
--------------------------------------------------------------------------------
1 | /* @flow */
2 |
3 | import { toNumber, toString, looseEqual, looseIndexOf } from 'shared/util'
4 | import { createTextVNode, createEmptyVNode } from 'core/vdom/vnode'
5 | import { renderList } from './render-list'
6 | import { renderSlot } from './render-slot'
7 | import { resolveFilter } from './resolve-filter'
8 | import { checkKeyCodes } from './check-keycodes'
9 | import { bindObjectProps } from './bind-object-props'
10 | import { renderStatic, markOnce } from './render-static'
11 | import { bindObjectListeners } from './bind-object-listeners'
12 | import { resolveScopedSlots } from './resolve-slots'
13 |
14 | export function installRenderHelpers (target: any) {
15 | target._o = markOnce
16 | target._n = toNumber
17 | target._s = toString
18 | target._l = renderList
19 | target._t = renderSlot
20 | target._q = looseEqual
21 | target._i = looseIndexOf
22 | target._m = renderStatic
23 | target._f = resolveFilter
24 | target._k = checkKeyCodes
25 | target._b = bindObjectProps
26 | target._v = createTextVNode
27 | target._e = createEmptyVNode
28 | target._u = resolveScopedSlots
29 | target._g = bindObjectListeners
30 | }
31 |
--------------------------------------------------------------------------------
/vue/src/core/vdom/helpers/merge-hook.js:
--------------------------------------------------------------------------------
1 | /* @flow */
2 |
3 | import VNode from '../vnode'
4 | import { createFnInvoker } from './update-listeners'
5 | import { remove, isDef, isUndef, isTrue } from 'shared/util'
6 |
7 | export function mergeVNodeHook (def: Object, hookKey: string, hook: Function) {
8 | if (def instanceof VNode) {
9 | def = def.data.hook || (def.data.hook = {})
10 | }
11 | let invoker
12 | const oldHook = def[hookKey]
13 |
14 | function wrappedHook () {
15 | hook.apply(this, arguments)
16 | // important: remove merged hook to ensure it's called only once
17 | // and prevent memory leak
18 | remove(invoker.fns, wrappedHook)
19 | }
20 |
21 | if (isUndef(oldHook)) {
22 | // no existing hook
23 | invoker = createFnInvoker([wrappedHook])
24 | } else {
25 | /* istanbul ignore if */
26 | if (isDef(oldHook.fns) && isTrue(oldHook.merged)) {
27 | // already a merged invoker
28 | invoker = oldHook
29 | invoker.fns.push(wrappedHook)
30 | } else {
31 | // existing plain hook
32 | invoker = createFnInvoker([oldHook, wrappedHook])
33 | }
34 | }
35 |
36 | invoker.merged = true
37 | def[hookKey] = invoker
38 | }
39 |
--------------------------------------------------------------------------------
/vue/test/ssr/fixtures/nested-cache.js:
--------------------------------------------------------------------------------
1 | import Vue from '../../../dist/vue.runtime.common.js'
2 |
3 | function createRegisterFn (id) {
4 | return function (context) {
5 | context = context || this.$vnode.ssrContext
6 | context.registered.push(id)
7 | }
8 | }
9 |
10 | function addHooks (comp) {
11 | const hook = createRegisterFn(comp.name)
12 | return Object.assign(comp, {
13 | _ssrRegister: hook,
14 | beforeCreate: hook
15 | })
16 | }
17 |
18 | const grandchild = addHooks({
19 | name: 'grandchild',
20 | props: ['id'],
21 | serverCacheKey: props => props.id,
22 | render (h) {
23 | return h('div', '/test')
24 | }
25 | })
26 |
27 | const child = addHooks({
28 | name: 'child',
29 | props: ['id'],
30 | serverCacheKey: props => props.id,
31 | render (h) {
32 | return h(grandchild, { props: { id: this.id }})
33 | }
34 | })
35 |
36 | const app = addHooks({
37 | name: 'app',
38 | props: ['id'],
39 | serverCacheKey: props => props.id,
40 | render (h) {
41 | return h(child, { props: { id: this.id }})
42 | }
43 | })
44 |
45 | export default () => {
46 | return Promise.resolve(new Vue({
47 | render: h => h(app, { props: { id: 1 }})
48 | }))
49 | }
50 |
--------------------------------------------------------------------------------
/vue/src/core/global-api/assets.js:
--------------------------------------------------------------------------------
1 | /* @flow */
2 |
3 | import { ASSET_TYPES } from 'shared/constants'
4 | import { isPlainObject, validateComponentName } from '../util/index'
5 |
6 | export function initAssetRegisters (Vue: GlobalAPI) {
7 | /**
8 | * Create asset registration methods.
9 | */
10 | ASSET_TYPES.forEach(type => {
11 | Vue[type] = function (
12 | id: string,
13 | definition: Function | Object
14 | ): Function | Object | void {
15 | if (!definition) {
16 | return this.options[type + 's'][id]
17 | } else {
18 | /* istanbul ignore if */
19 | if (process.env.NODE_ENV !== 'production' && type === 'component') {
20 | validateComponentName(id)
21 | }
22 | if (type === 'component' && isPlainObject(definition)) {
23 | definition.name = definition.name || id
24 | definition = this.options._base.extend(definition)
25 | }
26 | if (type === 'directive' && typeof definition === 'function') {
27 | definition = { bind: definition, update: definition }
28 | }
29 | this.options[type + 's'][id] = definition
30 | return definition
31 | }
32 | }
33 | })
34 | }
35 |
--------------------------------------------------------------------------------
/vue-router/test/unit/specs/node.spec.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import VueRouter from '../../../src/index'
3 |
4 | Vue.use(VueRouter)
5 |
6 | describe('Usage in Node', () => {
7 | it('should be in abstract mode', () => {
8 | const router = new VueRouter()
9 | expect(router.mode).toBe('abstract')
10 | })
11 |
12 | it('should be able to navigate without app instance', () => {
13 | const router = new VueRouter({
14 | routes: [
15 | { path: '/', component: { name: 'foo' }},
16 | { path: '/bar', component: { name: 'bar' }}
17 | ]
18 | })
19 | router.push('/bar')
20 | expect(router.history.current.path).toBe('/bar')
21 | })
22 |
23 | it('getMatchedComponents', () => {
24 | const Foo = { name: 'foo' }
25 | const Bar = { name: 'bar' }
26 | const Baz = { name: 'baz' }
27 | const router = new VueRouter({
28 | routes: [
29 | { path: '/', component: Foo },
30 | { path: '/bar', component: Bar, children: [
31 | { path: 'baz', component: Baz }
32 | ]}
33 | ]
34 | })
35 | expect(router.getMatchedComponents('/')).toEqual([Foo])
36 | expect(router.getMatchedComponents('/bar/baz')).toEqual([Bar, Baz])
37 | })
38 | })
39 |
--------------------------------------------------------------------------------
/vue/src/platforms/weex/runtime/index.js:
--------------------------------------------------------------------------------
1 | /* @flow */
2 |
3 | import Vue from 'core/index'
4 | import { patch } from 'weex/runtime/patch'
5 | import { mountComponent } from 'core/instance/lifecycle'
6 | import platformDirectives from 'weex/runtime/directives/index'
7 | import platformComponents from 'weex/runtime/components/index'
8 |
9 | import {
10 | query,
11 | mustUseProp,
12 | isReservedTag,
13 | isRuntimeComponent,
14 | isUnknownElement
15 | } from 'weex/util/element'
16 |
17 | // install platform specific utils
18 | Vue.config.mustUseProp = mustUseProp
19 | Vue.config.isReservedTag = isReservedTag
20 | Vue.config.isRuntimeComponent = isRuntimeComponent
21 | Vue.config.isUnknownElement = isUnknownElement
22 |
23 | // install platform runtime directives and components
24 | Vue.options.directives = platformDirectives
25 | Vue.options.components = platformComponents
26 |
27 | // install platform patch function
28 | Vue.prototype.__patch__ = patch
29 |
30 | // wrap mount
31 | Vue.prototype.$mount = function (
32 | el?: any,
33 | hydrating?: boolean
34 | ): Component {
35 | return mountComponent(
36 | this,
37 | el && query(el, this.$document),
38 | hydrating
39 | )
40 | }
41 |
42 | export default Vue
43 |
--------------------------------------------------------------------------------
/vue/test/weex/cases/recycle-list/components/lifecycle.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{number}}
4 |
5 |
6 |
7 |
40 |
--------------------------------------------------------------------------------
/vue-router/test/unit/specs/create-matcher.spec.js:
--------------------------------------------------------------------------------
1 | /*eslint-disable no-undef*/
2 | import { createMatcher } from '../../../src/create-matcher'
3 |
4 | const routes = [
5 | { path: '/', name: 'home', component: { name: 'home' }},
6 | { path: '/foo', name: 'foo', component: { name: 'foo' }},
7 | ]
8 |
9 | describe('Creating Matcher', function () {
10 | let match
11 |
12 | beforeAll(function () {
13 | spyOn(console, 'warn')
14 | match = createMatcher(routes).match
15 | })
16 |
17 | beforeEach(function () {
18 | console.warn.calls.reset()
19 | process.env.NODE_ENV = 'production'
20 | })
21 |
22 | it('in development, has logged a warning if a named route does not exist', function () {
23 | process.env.NODE_ENV = 'development'
24 | const { name, matched } = match({ name: 'bar' }, routes[0])
25 | expect(matched.length).toBe(0)
26 | expect(name).toBe('bar')
27 | expect(console.warn).toHaveBeenCalled()
28 | expect(console.warn.calls.argsFor(0)[0]).toMatch('Route with name \'bar\' does not exist')
29 | })
30 |
31 | it('in production, it has not logged this warning', function () {
32 | match({ name: 'foo' }, routes[0])
33 | expect(console.warn).not.toHaveBeenCalled()
34 | })
35 | })
36 |
--------------------------------------------------------------------------------
/vue/test/unit/features/directives/model-parse.spec.js:
--------------------------------------------------------------------------------
1 | import { parseModel } from 'compiler/directives/model'
2 |
3 | describe('model expression parser', () => {
4 | it('parse single path', () => {
5 | const res = parseModel('foo')
6 | expect(res.exp).toBe('foo')
7 | expect(res.key).toBe(null)
8 | })
9 |
10 | it('parse object dot notation', () => {
11 | const res = parseModel('a.b.c')
12 | expect(res.exp).toBe('a.b')
13 | expect(res.key).toBe('"c"')
14 | })
15 |
16 | it('parse string in brackets', () => {
17 | const res = parseModel('a["b"][c]')
18 | expect(res.exp).toBe('a["b"]')
19 | expect(res.key).toBe('c')
20 | })
21 |
22 | it('parse brackets with object dot notation', () => {
23 | const res = parseModel('a["b"][c].xxx')
24 | expect(res.exp).toBe('a["b"][c]')
25 | expect(res.key).toBe('"xxx"')
26 | })
27 |
28 | it('parse nested brackets', () => {
29 | const res = parseModel('a[i[c]]')
30 | expect(res.exp).toBe('a')
31 | expect(res.key).toBe('i[c]')
32 | })
33 |
34 | it('combined', () => {
35 | const res = parseModel('test.xxx.a["asa"][test1[key]]')
36 | expect(res.exp).toBe('test.xxx.a["asa"]')
37 | expect(res.key).toBe('test1[key]')
38 | })
39 | })
40 |
--------------------------------------------------------------------------------
/vue/src/platforms/weex/runtime/modules/attrs.js:
--------------------------------------------------------------------------------
1 | /* @flow */
2 |
3 | import { extend } from 'shared/util'
4 |
5 | function updateAttrs (oldVnode: VNodeWithData, vnode: VNodeWithData) {
6 | if (!oldVnode.data.attrs && !vnode.data.attrs) {
7 | return
8 | }
9 | let key, cur, old
10 | const elm = vnode.elm
11 | const oldAttrs = oldVnode.data.attrs || {}
12 | let attrs = vnode.data.attrs || {}
13 | // clone observed objects, as the user probably wants to mutate it
14 | if (attrs.__ob__) {
15 | attrs = vnode.data.attrs = extend({}, attrs)
16 | }
17 |
18 | const supportBatchUpdate = typeof elm.setAttrs === 'function'
19 | const batchedAttrs = {}
20 | for (key in attrs) {
21 | cur = attrs[key]
22 | old = oldAttrs[key]
23 | if (old !== cur) {
24 | supportBatchUpdate
25 | ? (batchedAttrs[key] = cur)
26 | : elm.setAttr(key, cur)
27 | }
28 | }
29 | for (key in oldAttrs) {
30 | if (attrs[key] == null) {
31 | supportBatchUpdate
32 | ? (batchedAttrs[key] = undefined)
33 | : elm.setAttr(key)
34 | }
35 | }
36 | if (supportBatchUpdate) {
37 | elm.setAttrs(batchedAttrs)
38 | }
39 | }
40 |
41 | export default {
42 | create: updateAttrs,
43 | update: updateAttrs
44 | }
45 |
--------------------------------------------------------------------------------
/vue/test/e2e/specs/async-edge-cases.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | 'async edge cases': function (browser) {
3 | browser
4 | .url('http://localhost:8080/test/e2e/specs/async-edge-cases.html')
5 | // #4510
6 | .assert.containsText('#case-1', '1')
7 | .assert.checked('#case-1 input', false)
8 |
9 | .click('#case-1 input')
10 | .assert.containsText('#case-1', '2')
11 | .assert.checked('#case-1 input', true)
12 |
13 | .click('#case-1 input')
14 | .assert.containsText('#case-1', '3')
15 | .assert.checked('#case-1 input', false)
16 |
17 | // #6566
18 | .assert.containsText('#case-2 button', 'Expand is True')
19 | .assert.containsText('.count-a', 'countA: 0')
20 | .assert.containsText('.count-b', 'countB: 0')
21 |
22 | .click('#case-2 button')
23 | .assert.containsText('#case-2 button', 'Expand is False')
24 | .assert.containsText('.count-a', 'countA: 1')
25 | .assert.containsText('.count-b', 'countB: 0')
26 |
27 | .click('#case-2 button')
28 | .assert.containsText('#case-2 button', 'Expand is True')
29 | .assert.containsText('.count-a', 'countA: 1')
30 | .assert.containsText('.count-b', 'countB: 1')
31 |
32 | .end()
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/vue/src/core/vdom/modules/ref.js:
--------------------------------------------------------------------------------
1 | /* @flow */
2 |
3 | import { remove, isDef } 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 (!isDef(key)) return
23 |
24 | const vm = vnode.context
25 | const ref = vnode.componentInstance || vnode.elm
26 | const refs = vm.$refs
27 | if (isRemoval) {
28 | if (Array.isArray(refs[key])) {
29 | remove(refs[key], ref)
30 | } else if (refs[key] === ref) {
31 | refs[key] = undefined
32 | }
33 | } else {
34 | if (vnode.data.refInFor) {
35 | if (!Array.isArray(refs[key])) {
36 | refs[key] = [ref]
37 | } else if (refs[key].indexOf(ref) < 0) {
38 | // $flow-disable-line
39 | refs[key].push(ref)
40 | }
41 | } else {
42 | refs[key] = ref
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/vue/src/platforms/weex/runtime/recycle-list/render-component-template.js:
--------------------------------------------------------------------------------
1 | /* @flow */
2 |
3 | import { warn } from 'core/util/debug'
4 | import { handleError } from 'core/util/error'
5 | import { RECYCLE_LIST_MARKER } from 'weex/util/index'
6 | import { createComponentInstanceForVnode } from 'core/vdom/create-component'
7 | import { resolveVirtualComponent } from './virtual-component'
8 |
9 | export function isRecyclableComponent (vnode: VNodeWithData): boolean {
10 | return vnode.data.attrs
11 | ? (RECYCLE_LIST_MARKER in vnode.data.attrs)
12 | : false
13 | }
14 |
15 | export function renderRecyclableComponentTemplate (vnode: MountedComponentVNode): VNode {
16 | // $flow-disable-line
17 | delete vnode.data.attrs[RECYCLE_LIST_MARKER]
18 | resolveVirtualComponent(vnode)
19 | const vm = createComponentInstanceForVnode(vnode)
20 | const render = (vm.$options: any)['@render']
21 | if (render) {
22 | try {
23 | return render.call(vm)
24 | } catch (err) {
25 | handleError(err, vm, `@render`)
26 | }
27 | } else {
28 | warn(
29 | `@render function not defined on component used in . ` +
30 | `Make sure to declare \`recyclable="true"\` on the component's template.`,
31 | vm
32 | )
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/vue/test/unit/features/options/renderError.spec.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 |
3 | describe('Options renderError', () => {
4 | it('should be used on render errors', done => {
5 | Vue.config.errorHandler = () => {}
6 | const vm = new Vue({
7 | data: {
8 | ok: true
9 | },
10 | render (h) {
11 | if (this.ok) {
12 | return h('div', 'ok')
13 | } else {
14 | throw new Error('no')
15 | }
16 | },
17 | renderError (h, err) {
18 | return h('div', err.toString())
19 | }
20 | }).$mount()
21 | expect(vm.$el.textContent).toBe('ok')
22 | vm.ok = false
23 | waitForUpdate(() => {
24 | expect(vm.$el.textContent).toBe('Error: no')
25 | Vue.config.errorHandler = null
26 | }).then(done)
27 | })
28 |
29 | it('should pass on errors in renderError to global handler', () => {
30 | const spy = Vue.config.errorHandler = jasmine.createSpy()
31 | const err = new Error('renderError')
32 | const vm = new Vue({
33 | render () {
34 | throw new Error('render')
35 | },
36 | renderError () {
37 | throw err
38 | }
39 | }).$mount()
40 | expect(spy).toHaveBeenCalledWith(err, vm, 'renderError')
41 | })
42 | })
43 |
--------------------------------------------------------------------------------
/docs/README.md:
--------------------------------------------------------------------------------
1 | ## 前言
2 |
3 | 目前社区有很多 Vue.js 的源码解析文章,但是质量层次不齐,不够系统和全面,这本电子书的目标是全方位细致深度解析 Vue.js 的实现原理,让同学们可以彻底掌握 Vue.js。目前分析的版本是 Vue.js 的最新版本 Vue.js 2.5.17-beta.0,并且之后会随着版本升级而做相应的更新,充分发挥电子书的优势。
4 |
5 | 这本电子书是作为 [《Vue.js 源码揭秘》](http://coding.imooc.com/class/228.html)视频课程的辅助教材。电子书是开源的,同学们可以免费阅读,视频是收费的,25+小时纯干货课程,如果有需要的同学可以购买来学习,**但请务必支持正版,请尊重作者的劳动成果**。
6 |
7 | ## 章节目录
8 |
9 | 为了把 Vue.js 的源码讲明白,课程设计成由浅入深,分为核心、编译、扩展、生态四个方面去讲,并拆成了八个章节,如下图:
10 |
11 |
12 |
13 | **第一章:准备工作**
14 |
15 | 介绍了 Flow、Vue.js 的源码目录设计、Vue.js 的源码构建方式,以及从入口开始分析了 Vue.js 的初始化过程。
16 |
17 | **第二章:数据驱动**
18 |
19 | 详细讲解了模板数据到 DOM 渲染的过程,从 `new Vue` 开始,分析了 `mount`、`render`、`update`、`patch` 等流程。
20 |
21 | **第三章:组件化**
22 |
23 | 分析了组件化的实现原理,并且分析了组件周边的原理实现,包括合并配置、生命周期、组件注册、异步组件。
24 |
25 | **第四章:深入响应式原理**
26 |
27 | 详细讲解了数据的变化如何驱动视图的变化,分析了响应式对象的创建,依赖收集、派发更新的实现过程,一些特殊情况的处理,并对比了计算属性和侦听属性的实现,最后分析了组件更新的过程。
28 |
29 | **第五章:编译**
30 |
31 | 从编译的入口函数开始,分析了编译的三个核心流程的实现:`parse` -> `optimize` -> `codegen`。
32 |
33 | **第六章:扩展**
34 |
35 | 详细讲解了 `event`、`v-model`、`slot`、`keep-alive`、`transition`、`transition-group` 等常用功能的原理实现,该章节作为一个可扩展章节,未来会分析更多 Vue 提供的特性。
36 |
37 | **第七章:Vue-Router**
38 |
39 | 分析了 Vue-Router 的实现原理,从路由注册开始,分析了路由对象、`matcher`,并深入分析了整个路径切换的实现过程和细节。
40 |
41 | **第八章:Vuex**
42 |
43 | 分析了 Vuex 的实现原理,深入分析了它的初始化过程,常用 API 以及插件部分的实现。
44 |
45 |
46 |
--------------------------------------------------------------------------------
/vue/test/unit/features/options/render.spec.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 |
3 | describe('Options render', () => {
4 | it('basic usage', () => {
5 | const vm = new Vue({
6 | render (h) {
7 | const children = []
8 | for (let i = 0; i < this.items.length; i++) {
9 | children.push(h('li', { staticClass: 'task' }, [this.items[i].name]))
10 | }
11 | return h('ul', { staticClass: 'tasks' }, children)
12 | },
13 | data: {
14 | items: [{ id: 1, name: 'task1' }, { id: 2, name: 'task2' }]
15 | }
16 | }).$mount()
17 | expect(vm.$el.tagName).toBe('UL')
18 | for (let i = 0; i < vm.$el.children.length; i++) {
19 | const li = vm.$el.children[i]
20 | expect(li.tagName).toBe('LI')
21 | expect(li.textContent).toBe(vm.items[i].name)
22 | }
23 | })
24 |
25 | it('allow null data', () => {
26 | const vm = new Vue({
27 | render (h) {
28 | return h('div', null, 'hello' /* string as children*/)
29 | }
30 | }).$mount()
31 | expect(vm.$el.tagName).toBe('DIV')
32 | expect(vm.$el.textContent).toBe('hello')
33 | })
34 |
35 | it('should warn non `render` option and non `template` option', () => {
36 | new Vue().$mount()
37 | expect('Failed to mount component: template or render function not defined.').toHaveBeenWarned()
38 | })
39 | })
40 |
--------------------------------------------------------------------------------
/vuex/test/e2e/nightwatch.config.js:
--------------------------------------------------------------------------------
1 | // http://nightwatchjs.org/guide#settings-file
2 | module.exports = {
3 | 'src_folders': ['test/e2e/specs'],
4 | 'output_folder': 'test/e2e/reports',
5 | 'custom_commands_path': ['node_modules/nightwatch-helpers/commands'],
6 | 'custom_assertions_path': ['node_modules/nightwatch-helpers/assertions'],
7 |
8 | 'selenium': {
9 | 'start_process': true,
10 | 'server_path': require('selenium-server').path,
11 | 'host': '127.0.0.1',
12 | 'port': 4444,
13 | 'cli_args': {
14 | 'webdriver.chrome.driver': require('chromedriver').path
15 | }
16 | },
17 |
18 | 'test_settings': {
19 | 'default': {
20 | 'selenium_port': 4444,
21 | 'selenium_host': 'localhost',
22 | 'silent': true,
23 | 'screenshots': {
24 | 'enabled': true,
25 | 'on_failure': true,
26 | 'on_error': false,
27 | 'path': 'test/e2e/screenshots'
28 | }
29 | },
30 |
31 | 'chrome': {
32 | 'desiredCapabilities': {
33 | 'browserName': 'chrome',
34 | 'javascriptEnabled': true,
35 | 'acceptSslCerts': true
36 | }
37 | },
38 |
39 | 'phantomjs': {
40 | 'desiredCapabilities': {
41 | 'browserName': 'phantomjs',
42 | 'javascriptEnabled': true,
43 | 'acceptSslCerts': true
44 | }
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/vue/test/unit/features/options/methods.spec.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import testObjectOption from '../../../helpers/test-object-option'
3 |
4 | describe('Options methods', () => {
5 | testObjectOption('methods')
6 |
7 | it('should have correct context', () => {
8 | const vm = new Vue({
9 | data: {
10 | a: 1
11 | },
12 | methods: {
13 | plus () {
14 | this.a++
15 | }
16 | }
17 | })
18 | vm.plus()
19 | expect(vm.a).toBe(2)
20 | })
21 |
22 | it('should warn undefined methods', () => {
23 | new Vue({
24 | methods: {
25 | hello: undefined
26 | }
27 | })
28 | expect(`Method "hello" has an undefined value in the component definition`).toHaveBeenWarned()
29 | })
30 |
31 | it('should warn methods conflicting with data', () => {
32 | new Vue({
33 | data: {
34 | foo: 1
35 | },
36 | methods: {
37 | foo () {}
38 | }
39 | })
40 | expect(`Method "foo" has already been defined as a data property`).toHaveBeenWarned()
41 | })
42 |
43 | it('should warn methods conflicting with internal methods', () => {
44 | new Vue({
45 | methods: {
46 | _update () {}
47 | }
48 | })
49 | expect(`Method "_update" conflicts with an existing Vue instance method`).toHaveBeenWarned()
50 | })
51 | })
52 |
--------------------------------------------------------------------------------
/vue/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 |
--------------------------------------------------------------------------------
/vue/test/unit/modules/vdom/modules/directive.spec.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import { patch } from 'web/runtime/patch'
3 | import VNode from 'core/vdom/vnode'
4 |
5 | describe('vdom directive module', () => {
6 | it('should work', () => {
7 | const directive1 = {
8 | bind: jasmine.createSpy('bind'),
9 | update: jasmine.createSpy('update'),
10 | unbind: jasmine.createSpy('unbind')
11 | }
12 | const vm = new Vue({ directives: { directive1 }})
13 | // create
14 | const vnode1 = new VNode('div', {}, [
15 | new VNode('p', {
16 | directives: [{
17 | name: 'directive1', value: 'hello', arg: 'arg1', modifiers: { modifier1: true }
18 | }]
19 | }, undefined, 'hello world', undefined, vm)
20 | ])
21 | patch(null, vnode1)
22 | expect(directive1.bind).toHaveBeenCalled()
23 | // update
24 | const vnode2 = new VNode('div', {}, [
25 | new VNode('p', {
26 | directives: [{
27 | name: 'directive1', value: 'world', arg: 'arg1', modifiers: { modifier1: true }
28 | }]
29 | }, undefined, 'hello world', undefined, vm)
30 | ])
31 | patch(vnode1, vnode2)
32 | expect(directive1.update).toHaveBeenCalled()
33 | // destroy
34 | const vnode3 = new VNode('div')
35 | patch(vnode2, vnode3)
36 | expect(directive1.unbind).toHaveBeenCalled()
37 | })
38 | })
39 |
--------------------------------------------------------------------------------
/vue/src/core/util/error.js:
--------------------------------------------------------------------------------
1 | /* @flow */
2 |
3 | import config from '../config'
4 | import { warn } from './debug'
5 | import { inBrowser, inWeex } from './env'
6 |
7 | export function handleError (err: Error, vm: any, info: string) {
8 | if (vm) {
9 | let cur = vm
10 | while ((cur = cur.$parent)) {
11 | const hooks = cur.$options.errorCaptured
12 | if (hooks) {
13 | for (let i = 0; i < hooks.length; i++) {
14 | try {
15 | const capture = hooks[i].call(cur, err, vm, info) === false
16 | if (capture) return
17 | } catch (e) {
18 | globalHandleError(e, cur, 'errorCaptured hook')
19 | }
20 | }
21 | }
22 | }
23 | }
24 | globalHandleError(err, vm, info)
25 | }
26 |
27 | function globalHandleError (err, vm, info) {
28 | if (config.errorHandler) {
29 | try {
30 | return config.errorHandler.call(null, err, vm, info)
31 | } catch (e) {
32 | logError(e, null, 'config.errorHandler')
33 | }
34 | }
35 | logError(err, vm, info)
36 | }
37 |
38 | function logError (err, vm, info) {
39 | if (process.env.NODE_ENV !== 'production') {
40 | warn(`Error in ${info}: "${err.toString()}"`, vm)
41 | }
42 | /* istanbul ignore else */
43 | if ((inBrowser || inWeex) && typeof console !== 'undefined') {
44 | console.error(err)
45 | } else {
46 | throw err
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/vue/test/unit/features/options/name.spec.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 |
3 | describe('Options name', () => {
4 | it('should contain itself in self components', () => {
5 | const vm = Vue.extend({
6 | name: 'SuperVue'
7 | })
8 |
9 | expect(vm.options.components['SuperVue']).toEqual(vm)
10 | })
11 |
12 | it('should warn when incorrect name given', () => {
13 | Vue.extend({
14 | name: 'Hyper*Vue'
15 | })
16 |
17 | /* eslint-disable */
18 | expect(`Invalid component name: "Hyper*Vue". Component names can only contain alphanumeric characters and the hyphen, and must start with a letter.`)
19 | .toHaveBeenWarned()
20 | /* eslint-enable */
21 |
22 | Vue.extend({
23 | name: '2Cool2BValid'
24 | })
25 |
26 | /* eslint-disable */
27 | expect(`Invalid component name: "2Cool2BValid". Component names can only contain alphanumeric characters and the hyphen, and must start with a letter.`)
28 | .toHaveBeenWarned()
29 | /* eslint-enable */
30 | })
31 |
32 | it('id should not override given name when using Vue.component', () => {
33 | const SuperComponent = Vue.component('super-component', {
34 | name: 'SuperVue'
35 | })
36 |
37 | expect(SuperComponent.options.components['SuperVue']).toEqual(SuperComponent)
38 | expect(SuperComponent.options.components['super-component']).toEqual(SuperComponent)
39 | })
40 | })
41 |
--------------------------------------------------------------------------------
/vue/test/e2e/specs/async-edge-cases.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | {{ num }}
14 |
15 |
16 |
17 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
35 |
36 | countA: {{countA}}
37 |
38 |
39 | countB: {{countB}}
40 |
41 |
42 |
52 |
53 |
54 |
55 |
--------------------------------------------------------------------------------
/vue-router/test/e2e/specs/hash-mode.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | 'Hash mode': function (browser) {
3 | browser
4 | .url('http://localhost:8080/hash-mode/')
5 | .waitForElementVisible('#app', 1000)
6 | .assert.count('li', 4)
7 | .assert.count('li a', 3)
8 | .assert.attributeContains('li:nth-child(1) a', 'href', '/hash-mode/#/')
9 | .assert.attributeContains('li:nth-child(2) a', 'href', '/hash-mode/#/foo')
10 | .assert.attributeContains('li:nth-child(3) a', 'href', '/hash-mode/#/bar')
11 | .assert.containsText('.view', 'home')
12 |
13 | .click('li:nth-child(2) a')
14 | .assert.urlEquals('http://localhost:8080/hash-mode/#/foo')
15 | .assert.containsText('.view', 'foo')
16 |
17 | .click('li:nth-child(3) a')
18 | .assert.urlEquals('http://localhost:8080/hash-mode/#/bar')
19 | .assert.containsText('.view', 'bar')
20 |
21 | .click('li:nth-child(1) a')
22 | .assert.urlEquals('http://localhost:8080/hash-mode/#/')
23 | .assert.containsText('.view', 'home')
24 |
25 | .click('li:nth-child(4)')
26 | .assert.urlEquals('http://localhost:8080/hash-mode/#/bar')
27 | .assert.containsText('.view', 'bar')
28 |
29 | // check initial visit
30 | .url('http://localhost:8080/hash-mode/#/foo')
31 | .waitForElementVisible('#app', 1000)
32 | .assert.containsText('.view', 'foo')
33 | .end()
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Vue.js 技术揭秘
2 |
3 | [电子书](https://ustbhuangyi.github.io/vue-analysis/)
4 |
5 | 目前社区有很多 Vue.js 的源码解析文章,但是质量层次不齐,不够系统和全面,这本电子书的目标是全方位细致深度解析 Vue.js 的实现原理,让同学们可以彻底掌握 Vue.js。目前分析的版本是 Vue.js 的最新版本 Vue.js 2.5.17-beta.0,并且之后会随着版本升级而做相应的更新,充分发挥电子书的优势。
6 |
7 | 这本电子书是作为 [《Vue.js 源码揭秘》](http://coding.imooc.com/class/228.html)视频课程的辅助教材。电子书是开源的,同学们可以免费阅读,视频是收费的,25+小时纯干货课程,如果有需要的同学可以购买来学习,**但请务必支持正版,请尊重作者的劳动成果**。
8 |
9 | ## 章节目录
10 |
11 | 为了把 Vue.js 的源码讲明白,课程设计成由浅入深,分为核心、编译、扩展、生态四个方面去讲,并拆成了八个章节,如下图:
12 |
13 |
14 |
15 | **第一章:准备工作**
16 |
17 | 介绍了 Flow、Vue.js 的源码目录设计、Vue.js 的源码构建方式,以及从入口开始分析了 Vue.js 的初始化过程。
18 |
19 | **第二章:数据驱动**
20 |
21 | 详细讲解了模板数据到 DOM 渲染的过程,从 `new Vue` 开始,分析了 `mount`、`render`、`update`、`patch` 等流程。
22 |
23 | **第三章:组件化**
24 |
25 | 分析了组件化的实现原理,并且分析了组件周边的原理实现,包括合并配置、生命周期、组件注册、异步组件。
26 |
27 | **第四章:深入响应式原理**
28 |
29 | 详细讲解了数据的变化如何驱动视图的变化,分析了响应式对象的创建,依赖收集、派发更新的实现过程,一些特殊情况的处理,并对比了计算属性和侦听属性的实现,最后分析了组件更新的过程。
30 |
31 | **第五章:编译**
32 |
33 | 从编译的入口函数开始,分析了编译的三个核心流程的实现:`parse` -> `optimize` -> `codegen`。
34 |
35 | **第六章:扩展**
36 |
37 | 详细讲解了 `event`、`v-model`、`slot`、`keep-alive`、`transition`、`transition-group` 等常用功能的原理实现,该章节作为一个可扩展章节,未来会分析更多 Vue 提供的特性。
38 |
39 | **第七章:Vue-Router**
40 |
41 | 分析了 Vue-Router 的实现原理,从路由注册开始,分析了路由对象、`matcher`,并深入分析了整个路径切换的实现过程和细节。
42 |
43 | **第八章:Vuex**
44 |
45 | 分析了 Vuex 的实现原理,深入分析了它的初始化过程,常用 API 以及插件部分的实现。
46 |
47 |
48 |
--------------------------------------------------------------------------------
/vuex/test/e2e/specs/cart.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | 'shopping cart': function (browser) {
3 | browser
4 | .url('http://localhost:8080/shopping-cart/')
5 | .waitForElementVisible('#app', 1000)
6 | .waitFor(120) // api simulation
7 | .assert.count('li', 3)
8 | .assert.count('.cart button[disabled]', 1)
9 | .assert.containsText('li:nth-child(1)', 'iPad 4 Mini')
10 | .assert.containsText('.cart', 'Please add some products to cart')
11 | .assert.containsText('.cart', 'Total: $0.00')
12 | .click('li:nth-child(1) button')
13 | .assert.containsText('.cart', 'iPad 4 Mini - $500.01 x 1')
14 | .assert.containsText('.cart', 'Total: $500.01')
15 | .click('li:nth-child(1) button')
16 | .assert.containsText('.cart', 'iPad 4 Mini - $500.01 x 2')
17 | .assert.containsText('.cart', 'Total: $1,000.02')
18 | .assert.count('li:nth-child(1) button[disabled]', 1)
19 | .click('li:nth-child(2) button')
20 | .assert.containsText('.cart', 'H&M T-Shirt White - $10.99 x 1')
21 | .assert.containsText('.cart', 'Total: $1,011.01')
22 | .click('.cart button')
23 | .waitFor(120)
24 | .assert.containsText('.cart', 'Please add some products to cart')
25 | .assert.containsText('.cart', 'Total: $0.00')
26 | .assert.containsText('.cart', 'Checkout successful')
27 | .assert.count('.cart button[disabled]', 1)
28 | .end()
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/vue/test/unit/modules/vdom/modules/style.spec.js:
--------------------------------------------------------------------------------
1 | import { patch } from 'web/runtime/patch'
2 | import VNode from 'core/vdom/vnode'
3 |
4 | describe('vdom style module', () => {
5 | it('should create an element with style', () => {
6 | const vnode = new VNode('p', { style: { fontSize: '12px' }})
7 | const elm = patch(null, vnode)
8 | expect(elm.style.fontSize).toBe('12px')
9 | })
10 |
11 | it('should create an element with array style', () => {
12 | const vnode = new VNode('p', { style: [{ fontSize: '12px' }, { color: 'red' }] })
13 | const elm = patch(null, vnode)
14 | expect(elm.style.fontSize).toBe('12px')
15 | expect(elm.style.color).toBe('red')
16 | })
17 |
18 | it('should change elements style', () => {
19 | const vnode1 = new VNode('p', { style: { fontSize: '12px' }})
20 | const vnode2 = new VNode('p', { style: { fontSize: '10px', display: 'block' }})
21 | patch(null, vnode1)
22 | const elm = patch(vnode1, vnode2)
23 | expect(elm.style.fontSize).toBe('10px')
24 | expect(elm.style.display).toBe('block')
25 | })
26 |
27 | it('should remove elements attrs', () => {
28 | const vnode1 = new VNode('p', { style: { fontSize: '12px' }})
29 | const vnode2 = new VNode('p', { style: { display: 'block' }})
30 | patch(null, vnode1)
31 | const elm = patch(vnode1, vnode2)
32 | expect(elm.style.fontSize).toBe('')
33 | expect(elm.style.display).toBe('block')
34 | })
35 | })
36 |
--------------------------------------------------------------------------------
/vue/src/platforms/weex/compiler/index.js:
--------------------------------------------------------------------------------
1 | /* @flow */
2 |
3 | import { genStaticKeys } from 'shared/util'
4 | import { createCompiler } from 'compiler/index'
5 |
6 | import modules from './modules/index'
7 | import directives from './directives/index'
8 |
9 | import {
10 | isUnaryTag,
11 | mustUseProp,
12 | isReservedTag,
13 | canBeLeftOpenTag,
14 | getTagNamespace
15 | } from '../util/element'
16 |
17 | export const baseOptions: WeexCompilerOptions = {
18 | modules,
19 | directives,
20 | isUnaryTag,
21 | mustUseProp,
22 | canBeLeftOpenTag,
23 | isReservedTag,
24 | getTagNamespace,
25 | preserveWhitespace: false,
26 | recyclable: false,
27 | staticKeys: genStaticKeys(modules)
28 | }
29 |
30 | const compiler = createCompiler(baseOptions)
31 |
32 | export function compile (
33 | template: string,
34 | options?: WeexCompilerOptions
35 | ): WeexCompiledResult {
36 | let generateAltRender = false
37 | if (options && options.recyclable === true) {
38 | generateAltRender = true
39 | options.recyclable = false
40 | }
41 | const result = compiler.compile(template, options)
42 |
43 | // generate @render function for
44 | if (options && generateAltRender) {
45 | options.recyclable = true
46 | // disable static optimizations
47 | options.optimize = false
48 | const { render } = compiler.compile(template, options)
49 | result['@render'] = render
50 | }
51 | return result
52 | }
53 |
--------------------------------------------------------------------------------
/docs/v2/prepare/directory.md:
--------------------------------------------------------------------------------
1 | # Vue.js 源码目录设计
2 |
3 | Vue.js 的源码都在 src 目录下,其目录结构如下。
4 |
5 | ```
6 | src
7 | ├── compiler # 编译相关
8 | ├── core # 核心代码
9 | ├── platforms # 不同平台的支持
10 | ├── server # 服务端渲染
11 | ├── sfc # .vue 文件解析
12 | ├── shared # 共享代码
13 | ```
14 |
15 | ## compiler
16 |
17 | compiler 目录包含 Vue.js 所有编译相关的代码。它包括把模板解析成 ast 语法树,ast 语法树优化,代码生成等功能。
18 |
19 | 编译的工作可以在构建时做(借助 webpack、vue-loader 等辅助插件);也可以在运行时做,使用包含构建功能的 Vue.js。显然,编译是一项耗性能的工作,所以更推荐前者——离线编译。
20 |
21 | ## core
22 |
23 | core 目录包含了 Vue.js 的核心代码,包括内置组件、全局 API 封装,Vue 实例化、观察者、虚拟 DOM、工具函数等等。
24 |
25 | 这里的代码可谓是 Vue.js 的灵魂,也是我们之后需要重点分析的地方。
26 |
27 | ## platform
28 |
29 | Vue.js 是一个跨平台的 MVVM 框架,它可以跑在 web 上,也可以配合 weex 跑在 native 客户端上。platform 是 Vue.js 的入口,2 个目录代表 2 个主要入口,分别打包成运行在 web 上和 weex 上的 Vue.js。
30 |
31 | 我们会重点分析 web 入口打包后的 Vue.js,对于 weex 入口打包的 Vue.js,感兴趣的同学可以自行研究。
32 |
33 | ## server
34 |
35 | Vue.js 2.0 支持了服务端渲染,所有服务端渲染相关的逻辑都在这个目录下。注意:这部分代码是跑在服务端的 Node.js,不要和跑在浏览器端的 Vue.js 混为一谈。
36 |
37 | 服务端渲染主要的工作是把组件渲染为服务器端的 HTML 字符串,将它们直接发送到浏览器,最后将静态标记"混合"为客户端上完全交互的应用程序。
38 |
39 | ## sfc
40 |
41 | 通常我们开发 Vue.js 都会借助 webpack 构建, 然后通过 .vue 单文件来编写组件。
42 |
43 | 这个目录下的代码逻辑会把 .vue 文件内容解析成一个 JavaScript 的对象。
44 |
45 | ## shared
46 |
47 | Vue.js 会定义一些工具方法,这里定义的工具方法都是会被浏览器端的 Vue.js 和服务端的 Vue.js 所共享的。
48 |
49 | ## 总结
50 |
51 | 从 Vue.js 的目录设计可以看到,作者把功能模块拆分的非常清楚,相关的逻辑放在一个独立的目录下维护,并且把复用的代码也抽成一个独立目录。
52 |
53 | 这样的目录设计让代码的阅读性和可维护性都变强,是非常值得学习和推敲的。
54 |
55 |
--------------------------------------------------------------------------------
/vue-router/test/e2e/specs/route-props.js:
--------------------------------------------------------------------------------
1 | const $attrs = ' { "foo": "123" }'
2 |
3 | module.exports = {
4 | 'route-props': function (browser) {
5 | browser
6 | .url('http://localhost:8080/route-props/')
7 | .waitForElementVisible('#app', 1000)
8 | .assert.count('li a', 5)
9 |
10 | .assert.urlEquals('http://localhost:8080/route-props/')
11 | .assert.containsText('.hello', 'Hello Vue!' + $attrs)
12 |
13 | .click('li:nth-child(2) a')
14 | .assert.urlEquals('http://localhost:8080/route-props/hello/you')
15 | .assert.containsText('.hello', 'Hello you' + $attrs)
16 |
17 | .click('li:nth-child(3) a')
18 | .assert.urlEquals('http://localhost:8080/route-props/static')
19 | .assert.containsText('.hello', 'Hello world' + $attrs)
20 |
21 | .click('li:nth-child(4) a')
22 | .assert.urlEquals('http://localhost:8080/route-props/dynamic/1')
23 | .assert.containsText('.hello', 'Hello ' + ((new Date()).getFullYear() + 1)+ '!' + $attrs)
24 |
25 | .click('li:nth-child(5) a')
26 | .assert.urlEquals('http://localhost:8080/route-props/attrs')
27 | .assert.containsText('.hello', 'Hello attrs' + $attrs)
28 |
29 | // should be consistent
30 | .click('li:nth-child(4) a')
31 | .click('li:nth-child(5) a')
32 | .assert.urlEquals('http://localhost:8080/route-props/attrs')
33 | .assert.containsText('.hello', 'Hello attrs' + $attrs)
34 |
35 | .end()
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/vue/src/platforms/web/compiler/modules/class.js:
--------------------------------------------------------------------------------
1 | /* @flow */
2 |
3 | import { parseText } from 'compiler/parser/text-parser'
4 | import {
5 | getAndRemoveAttr,
6 | getBindingAttr,
7 | baseWarn
8 | } from 'compiler/helpers'
9 |
10 | function transformNode (el: ASTElement, options: CompilerOptions) {
11 | const warn = options.warn || baseWarn
12 | const staticClass = getAndRemoveAttr(el, 'class')
13 | if (process.env.NODE_ENV !== 'production' && staticClass) {
14 | const res = parseText(staticClass, options.delimiters)
15 | if (res) {
16 | warn(
17 | `class="${staticClass}": ` +
18 | 'Interpolation inside attributes has been removed. ' +
19 | 'Use v-bind or the colon shorthand instead. For example, ' +
20 | 'instead of , use
.'
21 | )
22 | }
23 | }
24 | if (staticClass) {
25 | el.staticClass = JSON.stringify(staticClass)
26 | }
27 | const classBinding = getBindingAttr(el, 'class', false /* getStatic */)
28 | if (classBinding) {
29 | el.classBinding = classBinding
30 | }
31 | }
32 |
33 | function genData (el: ASTElement): string {
34 | let data = ''
35 | if (el.staticClass) {
36 | data += `staticClass:${el.staticClass},`
37 | }
38 | if (el.classBinding) {
39 | data += `class:${el.classBinding},`
40 | }
41 | return data
42 | }
43 |
44 | export default {
45 | staticKeys: ['staticClass'],
46 | transformNode,
47 | genData
48 | }
49 |
--------------------------------------------------------------------------------
/vue/src/platforms/weex/util/index.js:
--------------------------------------------------------------------------------
1 | /* @flow */
2 | declare var document: WeexDocument;
3 |
4 | import { warn } from 'core/util/index'
5 |
6 | export const RECYCLE_LIST_MARKER = '@inRecycleList'
7 |
8 | // Register the component hook to weex native render engine.
9 | // The hook will be triggered by native, not javascript.
10 | export function registerComponentHook (
11 | componentId: string,
12 | type: string, // hook type, could be "lifecycle" or "instance"
13 | hook: string, // hook name
14 | fn: Function
15 | ) {
16 | if (!document || !document.taskCenter) {
17 | warn(`Can't find available "document" or "taskCenter".`)
18 | return
19 | }
20 | if (typeof document.taskCenter.registerHook === 'function') {
21 | return document.taskCenter.registerHook(componentId, type, hook, fn)
22 | }
23 | warn(`Failed to register component hook "${type}@${hook}#${componentId}".`)
24 | }
25 |
26 | // Updates the state of the component to weex native render engine.
27 | export function updateComponentData (
28 | componentId: string,
29 | newData: Object | void,
30 | callback?: Function
31 | ) {
32 | if (!document || !document.taskCenter) {
33 | warn(`Can't find available "document" or "taskCenter".`)
34 | return
35 | }
36 | if (typeof document.taskCenter.updateData === 'function') {
37 | return document.taskCenter.updateData(componentId, newData, callback)
38 | }
39 | warn(`Failed to update component data (${componentId}).`)
40 | }
41 |
--------------------------------------------------------------------------------
/vue/src/server/bundle-renderer/source-map-support.js:
--------------------------------------------------------------------------------
1 | /* @flow */
2 |
3 | const SourceMapConsumer = require('source-map').SourceMapConsumer
4 |
5 | const filenameRE = /\(([^)]+\.js):(\d+):(\d+)\)$/
6 |
7 | export function createSourceMapConsumers (rawMaps: Object) {
8 | const maps = {}
9 | Object.keys(rawMaps).forEach(file => {
10 | maps[file] = new SourceMapConsumer(rawMaps[file])
11 | })
12 | return maps
13 | }
14 |
15 | export function rewriteErrorTrace (e: any, mapConsumers: {
16 | [key: string]: SourceMapConsumer
17 | }) {
18 | if (e && typeof e.stack === 'string') {
19 | e.stack = e.stack.split('\n').map(line => {
20 | return rewriteTraceLine(line, mapConsumers)
21 | }).join('\n')
22 | }
23 | }
24 |
25 | function rewriteTraceLine (trace: string, mapConsumers: {
26 | [key: string]: SourceMapConsumer
27 | }) {
28 | const m = trace.match(filenameRE)
29 | const map = m && mapConsumers[m[1]]
30 | if (m != null && map) {
31 | const originalPosition = map.originalPositionFor({
32 | line: Number(m[2]),
33 | column: Number(m[3])
34 | })
35 | if (originalPosition.source != null) {
36 | const { source, line, column } = originalPosition
37 | const mappedPosition = `(${source.replace(/^webpack:\/\/\//, '')}:${String(line)}:${String(column)})`
38 | return trace.replace(filenameRE, mappedPosition)
39 | } else {
40 | return trace
41 | }
42 | } else {
43 | return trace
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/vue-router/test/e2e/specs/named-views.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | 'named views': function (browser) {
3 | browser
4 | .url('http://localhost:8080/named-views/')
5 | .waitForElementVisible('#app', 1000)
6 | .assert.count('li a', 2)
7 | // assert correct href with base
8 | .assert.attributeContains('li:nth-child(1) a', 'href', '/named-views/')
9 | .assert.attributeContains('li:nth-child(2) a', 'href', '/named-views/other')
10 |
11 | .assert.containsText('.view.one', 'foo')
12 | .assert.containsText('.view.two', 'bar')
13 | .assert.containsText('.view.three', 'baz')
14 |
15 | .click('li:nth-child(2) a')
16 | .assert.urlEquals('http://localhost:8080/named-views/other')
17 | .assert.containsText('.view.one', 'baz')
18 | .assert.containsText('.view.two', 'bar')
19 | .assert.containsText('.view.three', 'foo')
20 |
21 | .click('li:nth-child(1) a')
22 | .assert.urlEquals('http://localhost:8080/named-views/')
23 | .assert.containsText('.view.one', 'foo')
24 | .assert.containsText('.view.two', 'bar')
25 | .assert.containsText('.view.three', 'baz')
26 |
27 | // check initial visit
28 | .url('http://localhost:8080/named-views/other')
29 | .waitForElementVisible('#app', 1000)
30 | .assert.containsText('.view.one', 'baz')
31 | .assert.containsText('.view.two', 'bar')
32 | .assert.containsText('.view.three', 'foo')
33 | .end()
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/vue-router/test/e2e/specs/basic.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | 'basic': function (browser) {
3 | browser
4 | .url('http://localhost:8080/basic/')
5 | .waitForElementVisible('#app', 1000)
6 | .assert.count('li', 4)
7 | .assert.count('li a', 4)
8 | // assert correct href with base
9 | .assert.attributeContains('li:nth-child(1) a', 'href', '/basic/')
10 | .assert.attributeContains('li:nth-child(2) a', 'href', '/basic/foo')
11 | .assert.attributeContains('li:nth-child(3) a', 'href', '/basic/bar')
12 | .assert.attributeContains('li:nth-child(4) a', 'href', '/basic/bar')
13 | .assert.containsText('.view', 'home')
14 |
15 | .click('li:nth-child(2) a')
16 | .assert.urlEquals('http://localhost:8080/basic/foo')
17 | .assert.containsText('.view', 'foo')
18 |
19 | .click('li:nth-child(3) a')
20 | .assert.urlEquals('http://localhost:8080/basic/bar')
21 | .assert.containsText('.view', 'bar')
22 |
23 | .click('li:nth-child(1) a')
24 | .assert.urlEquals('http://localhost:8080/basic/')
25 | .assert.containsText('.view', 'home')
26 |
27 | .click('li:nth-child(4) a')
28 | .assert.urlEquals('http://localhost:8080/basic/bar')
29 | .assert.containsText('.view', 'bar')
30 |
31 | // check initial visit
32 | .url('http://localhost:8080/basic/foo')
33 | .waitForElementVisible('#app', 1000)
34 | .assert.containsText('.view', 'foo')
35 | .end()
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/vue/src/platforms/web/server/directives/model.js:
--------------------------------------------------------------------------------
1 | /* @flow */
2 |
3 | import { looseEqual, looseIndexOf } from 'shared/util'
4 |
5 | // this is only applied for