vue 快速入门 系列 —— Vue(自身) 项目结构

vue

其他章节请看:

vue 快速入门 系列

Vue(自身) 项目结构

前面我们已经陆续研究了 vue 的核心原理:数据侦测、模板和虚拟 DOM,都是偏底层的。本篇将和大家一起来看一下 vue 自身这个项目,了解它的目录结构,以及构建过程。

vue 的目录结构

将 vue 项目 下载到本地 git clone git@github.com:vuejs/vue.git vuev2.5.20

- vuev2.5.20

- dist // 构建后的文件

- examples // 有几个用 vue 写的示例,直接是通过 <script> 方式。例如有经典的 todo,还有 markdown

- flow // flow 相关。flow 是 JAVASCRIPT 的静态类型检查器

- packages // 这 4 个包在 npm 中都能搜索到

- vue-server-renderer

- vue-template-compiler

- weex-template-compiler

- weex-vue-framework

- scripts // 构建相关的脚本和配置文件。还有 gitHooks

- src

- compiler // 编译器。与模板编译相关的代码,例如解析器、优化器、生成器等。从 core 中分离出来或许是因为有的版本不需要它。

- core // vue 的核心代码

- components // 有 keep-alive 组件

- global-api // 全局 api 的代码。例如 Vue.set

- instance // vue 的构造函数和实例方法。例如 Vue.prototype.$set

- observer // 侦测数据变化相关代码

- util // 工具相关。例如 env.js、error.js、next-tick.js

- vdom // 虚拟 dom

- platforms // 平台相关

- web

- weex // 阿里巴巴发起的跨平台用户界面开发框架

- sfc // 将单文件组件 (*.vue) 文件解析为 SFC 描述符对象

- parser.js

- shared // 公用的工具代码。在 vscode 中搜索 `shared/`,可发现有 76 个文件引用了它

- util.js // 工具模块

- test // 测试相关

- types // TypeScript 相关

Tip: flow 和 githooks 就不节外生枝了

构建版本

dist 目录下有很多版本的 vue,我们需要了解一下它们的差异。

完整版:有 vue.jsvue.esm.jsvue.common.js等。

运行时版本:包含 runtime 的,例如 vue.runtime.jsvue.runtime.esm.jsvue.runtime.common.js

完整版包括运行时和编译器,而运行时基本上就是完整版除去编译器的其它一切。

Tip:编译器,用来将模板字符串编译成为 JavaScript 渲染函数的代码。在 模板 一文中已介绍。

// 需要编译器

new Vue({

template: '<div>{{ hi }}</div>'

})

// 不需要编译器

new Vue({

render (h) {

return h('div', this.hi)

}

})

UMD版本:umd 版本的文件通过 <script> 标签直接在浏览器中使用。有vue.jsvue.runtime.jsvue.min.jsvue.runtime.min.js

CommonJS 版本:包含 common 的,例如 vue.common.jsvue.runtime.common.js。主要给的打包工具使用,入 webpack 1。

ES Module 版本:包含 esm 的,例如 vue.esm.jsvue.runtime.esm.js。主要配合(或现代)的打包工具,比如 webpack 2 或 Rollup。

Tip:有关构建版本更详细的介绍请看 官网

使用 vue 的哪个版本(import 'vue')

现代打包工具,通过 importrequire 引入 vue,使用的都是 vue.runtime.esm.js

为什么是这样?请看实验。

准备一个项目,有 webpack,通过 npm 安装 vue,最后能打包就好了。

Tip:webpack 的简单使用可以看 初步认识 webpack

index.js 中就写一行代码:

import 'vue'

然后构建生成 main.js:

test-project> npx webpack --mode development

Hash: e9412c758fa785a2fd70

Version: webpack 4.46.0

Time: 289ms

Built at: 2022/01/16 上午9:55:41

Asset Size Chunks Chunk Names

main.js 250 KiB main [emitted] main

Entrypoint main = main.js

[./node_modules/webpack/buildin/global.js] (webpack)/buildin/global.js 472 bytes {main} [built]

[./src/index.js] 12 bytes {main} [built]

+ 4 hidden modules

// main.js

...

"use strict";

eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var vue__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! vue */ \"./node_modules/vue/dist/vue.runtime.esm.js\");\n\n\n//# sourceURL=webpack:///./src/index.js?");

我们在main.js 中发现 vue.runtime.esm.js

如果改为 require('vue'),仍然是 vue.runtime.esm.js

eval("__webpack_require__(/*! vue */ \"./node_modules/vue/dist/vue.runtime.esm.js\")\n\n//# sourceURL=webpack:///./src/index.js?");

如果我们删除 vuepackage.json 的一行代码,再次打包:

// node_modules/vue/package.json

{

"name": "vue",

"version": "2.6.14",

"description": "Reactive, component-oriented view layer for modern web interfaces.",

"main": "dist/vue.runtime.common.js",

- "module": "dist/vue.runtime.esm.js",

会发现 main.js 中引入的变成 vue.runtime.common.js

eval("__webpack_require__(/*! vue */ \"./node_modules/vue/dist/vue.runtime.common.js\")\n\n//# sourceURL=webpack:///./src/index.js?");

:在 vue-cli 的项目中,即使只删除 "module": "dist/vue.runtime.esm.js",,使用 vue 版本也不会变成 vue.runtime.common.js

构建分析

dist 目录下有很多版本的 vue。每次运行 npm run build 就会重新生成一遍:

vuev2.5.20> npm run build

> vue@2.6.14 build

> node scripts/build.js

dist\vue.runtime.common.dev.js 227.52kb

dist\vue.runtime.common.prod.js 63.60kb (gzipped: 22.98kb)

dist\vue.common.dev.js 326.08kb

dist\vue.common.prod.js 91.81kb (gzipped: 33.41kb)

dist\vue.runtime.esm.js 231.45kb

dist\vue.esm.js 331.88kb

dist\vue.esm.browser.js 321.26kb

dist\vue.esm.browser.min.js 91.26kb (gzipped: 33.38kb)

dist\vue.runtime.js 242.70kb

dist\vue.runtime.min.js 63.76kb (gzipped: 23.04kb)

dist\vue.js 347.56kb

dist\vue.min.js 91.98kb (gzipped: 33.47kb)

packages\vue-template-compiler\build.js 145.59kb

packages\vue-template-compiler\browser.js 253.25kb

packages\vue-server-renderer\build.dev.js 254.91kb

packages\vue-server-renderer\build.prod.js 79.47kb (gzipped: 28.99kb)

packages\vue-server-renderer\basic.js 340.69kb

packages\vue-server-renderer\server-plugin.js 4.00kb

packages\vue-server-renderer\client-plugin.js 4.02kb

Tipnpm run build 来自 package.json,运行前需要安装依赖 npm i

只生成 vue.runtime.esm.js

如何让 npm run build 只生成 vue.runtime.esm.js 这一个文件?我们先分析:

首先,运行 npm run build 就是运行 node scripts/build.js,也就是执行 vuev2.5.20/scripts/build.js 这个文件。

如果我们将这个文件内容替换成 console.log('i am build.js'),再次编译,发现什么事都不会去做,仅仅输出 i am build.js

vuev2.5.20> npm run build    

> vue@2.6.14 build

> node scripts/build.js

i am build.js

于是我们知道应该从 build.js 入手。核心代码如下:

// build.js

// 现代构建工具

const rollup = require('rollup')

let builds = require('./config').getAllBuilds()

// 构建

build(builds)

里面提到 config.jsgetAllBuilds 方法:

exports.getAllBuilds = () => Object.keys(builds).map(genConfig)

最后定位到 builds 变量:

const builds = {

...

// Runtime only ES modules build (for bundlers)

'web-runtime-esm': {

entry: resolve('web/entry-runtime.js'),

dest: resolve('dist/vue.runtime.esm.js'),

format: 'es',

banner

},

// Runtime+compiler development build (Browser)

'web-full-dev': {

entry: resolve('web/entry-runtime-with-compiler.js'),

dest: resolve('dist/vue.js'),

format: 'umd',

env: 'development',

alias: { he: './entity-decoder' },

banner

},

...

}

修改 builds 并重新打包:

// 只保留一个

const builds = {

// Runtime only ES modules build (for bundlers)

'web-runtime-esm': {

entry: resolve('web/entry-runtime.js'),

dest: resolve('dist/vue.runtime.esm.js'),

format: 'es',

banner

}

}

vuev2.5.20> npm run build

> vue@2.6.14 build

> node scripts/build.js

dist\vue.runtime.esm.js 231.45kb

至此,每次编译,则只会生成一个文件。

构建 vue.runtime.esm.js 的过程

web/entry-runtime.js

从下面这段代码,我们猜测 vue.runtime.esm.js 的入口是 web/entry-runtime.js:

'web-runtime-esm': {

entry: resolve('web/entry-runtime.js'),

dest: resolve('dist/vue.runtime.esm.js'),

format: 'es',

banner

}

// vuev2.5.20/src/platforms/web/entry-runtime.js 全部内容:

/* @flow */

import Vue from './runtime/index'

export default Vue

替换 entry-runtime.js 的内容如下,重新构建:

// entry-runtime.js

const Vue = function () { }

export default Vue

vuev2.5.20> npm run build

> vue@2.6.14 build

> node scripts/build.js

dist\vue.runtime.esm.js 0.13kb

// dist/vue.runtime.esm.js 全部内容:

/*!

* Vue.js v2.6.14

* (c) 2014-2022 Evan You

* Released under the MIT License.

*/

var Vue = function () { };

export default Vue;

根据打包后的内容,说明 web/entry-runtime.js 确实就是入口。

runtime/index

根据上文的分析,我们已知晓 vue.runtime.esm.js 的构建的过程就是在 runtime/index 中定义的。以下是与 Vue 相关的代码:

// vuev2.5.20/src/platforms/web/runtime/index.js

/* @flow */

import Vue from 'core/index'

// install platform specific utils

// 安装平台特定的工具

Vue.config.mustUseProp = mustUseProp

Vue.config.isReservedTag = isReservedTag

Vue.config.isReservedAttr = isReservedAttr

Vue.config.getTagNamespace = getTagNamespace

Vue.config.isUnknownElement = isUnknownElement

// install platform runtime directives & components

extend(Vue.options.directives, platformDirectives)

extend(Vue.options.components, platformComponents)

// install platform patch function

Vue.prototype.__patch__ = inBrowser ? patch : noop

// public mount method

Vue.prototype.$mount = function (

el?: string | Element,

hydrating?: boolean

): Component {

el = el && inBrowser ? query(el) : undefined

return mountComponent(this, el, hydrating)

}

export default Vue

关键代码就是第一行 import Vue from 'core/index',也即是引入 vue 的核心代码。

core/index

// vuev2.5.20/src/core/index.js 全部代码:

// 返回 Vue 构造函数,并准备好实例方法

import Vue from './instance/index'

import { initGlobalAPI } from './global-api/index'

import { isServerRendering } from 'core/util/env'

import { FunctionalRenderContext } from 'core/vdom/create-functional-component'

// 初始化全局 api

initGlobalAPI(Vue)

Object.defineProperty(Vue.prototype, '$isServer', {

get: isServerRendering

})

Object.defineProperty(Vue.prototype, '$ssrContext', {

get () {

/* istanbul ignore next */

return this.$vnode && this.$vnode.ssrContext

}

})

// expose FunctionalRenderContext for ssr runtime helper installation

Object.defineProperty(Vue, 'FunctionalRenderContext', {

value: FunctionalRenderContext

})

Vue.version = '__VERSION__'

export default Vue

关键代码是 instance/index(构造函数和实例方法) 和 global-api/index(全局方法):

// vuev2.5.20/src/core/instance/index.js 全部代码:

import { initMixin } from './init'

import { stateMixin } from './state'

import { renderMixin } from './render'

import { eventsMixin } from './events'

import { lifecycleMixin } from './lifecycle'

import { warn } from '../util/index'

// 构造函数

function Vue (options) {

if (process.env.NODE_ENV !== 'production' &&

!(this instanceof Vue)

) {

warn('Vue is a constructor and should be called with the `new` keyword')

}

this._init(options)

}

initMixin(Vue)

// 状态相关

stateMixin(Vue)

// 事件相关

eventsMixin(Vue)

// 生命周期相关

lifecycleMixin(Vue)

renderMixin(Vue)

export default Vue

// vuev2.5.20/src/core/global-api/index.js 

/* @flow */

import config from '../config'

import { initUse } from './use'

import { initMixin } from './mixin'

import { initExtend } from './extend'

import { initAssetRegisters } from './assets'

import { set, del } from '../observer/index'

import { ASSET_TYPES } from 'shared/constants'

import builtInComponents from '../components/index'

import { observe } from 'core/observer/index'

import {

warn,

extend,

nextTick,

mergeOptions,

defineReactive

} from '../util/index'

// 初始化全局 api

export function initGlobalAPI (Vue: GlobalAPI) {

// config

const configDef = {}

configDef.get = () => config

if (process.env.NODE_ENV !== 'production') {

configDef.set = () => {

warn(

'Do not replace the Vue.config object, set individual fields instead.'

)

}

}

Object.defineProperty(Vue, 'config', configDef)

// exposed util methods.

// NOTE: these are not considered part of the public API - avoid relying on

// them unless you are aware of the risk.

Vue.util = {

warn,

extend,

mergeOptions,

defineReactive

}

// 定义全局 api:set、delete、nextTick...

Vue.set = set

Vue.delete = del

Vue.nextTick = nextTick

// 2.6 explicit observable API

Vue.observable = <T>(obj: T): T => {

observe(obj)

return obj

}

Vue.options = Object.create(null)

ASSET_TYPES.forEach(type => {

Vue.options[type + 's'] = Object.create(null)

})

// this is used to identify the "base" constructor to extend all plain-object

// components with in Weex's multi-instance scenarios.

Vue.options._base = Vue

extend(Vue.options.components, builtInComponents)

initUse(Vue)

initMixin(Vue)

initExtend(Vue)

initAssetRegisters(Vue)

}

其他章节请看:

vue 快速入门 系列

以上是 vue 快速入门 系列 —— Vue(自身) 项目结构 的全部内容, 来源链接: utcz.com/z/377872.html

回到顶部