Vue源码解析-响应式原理
课程目标
- Vue.js的静态成员和实例成员的初始化过程
- 初次渲染过程
- 数据响应式原理
源码目录结构
准备工作-调试
调试设置
-
打包
- 打包工具 Rollup
- Vue.js 源码的打包工具使用的是 Rollup,比 webpack 轻量
- webpack 会把所有文件当做模块,Rollup 只处理 js 文件,更适合在 Vue.js 这样的库中使用(开发项目使用 webpack,开发库使用 Rollup)
- Rollup打包不会生成冗余的代码
- 打包工具 Rollup
-
安装依赖
npm i
- 设置sourcemap
- package.json 文件的 dev 脚本中添加参数 --sourcemap
"dev": "rollup -w -c scripts/config.js --sourcemap --environment TARGET:web-full-dev"
- 执行dev
- npm run dev 执行打包,用的是rollup, -w 参数是监听文件的变化,文件变化自动重新打包; -c是执行的配置文件
- 结果
- 以vue源码中examples中的grids为例进行调试
- 在进行代码调试的时候,如果没有开启sourcemap,则不会生成src目录(实际上是dist中的map文件指向src),断点不会进入src(源码),而直接进入打包后(dist)目录中压缩的vue.js文件,因为是压缩,编译后的代码,不方便调试,而我们希望断点直接进入src目录下的源码中调试,所以需要设置sourcemap
准备工作-Vue的不同构建版本
- npm run build 重新打包所有文件
- 官方文档-对不同构建版本的解释
- 完整版:同时包含编译器和运行时的版本。
- 编译器:用来将模板字符串编译成为 JavaScript 渲染函数的代码。=>将template装换成render函数
- 运行时:用来创建 Vue 实例、渲染并处理虚拟 DOM 等的代码。基本上就是除去编译器的其它一切。
完整版Vue举例
<div id="app"></div>
<!-- 完整版 -->
<script src="../../dist/vue.js"></script>
<script>
const vm = new Vue({
el: "#app",
template: "<h1>{
{msg}}</h1>",
data: {
msg: "Hello Vue",
},
});
</script>
- 结果:可以正常显示
<div id="app"></div>
<!-- 运行时版本 -->
<script src="../../dist/vue.runtime.js"></script>
<script>
const vm = new Vue({
el: "#app",
template: "<h1>{
{msg}}</h1>",
data: {
msg: "Hello Vue",
},
});
</script>
- 结果:报错
- 使用的是Vue的仅运行时版本,其中模板编译器不可用。方法一:将模板手动预编译为render函数,方法二:使用带编译器的vue版本
- 方法二就是上面的引用完整版本的vue,下面介绍方法一
- 方法一,将template模板字符串转换成render函数
<div id="app"></div>
<!-- 运行时版本 -->
<script src="../../dist/vue.runtime.js"></script>
<script>
const vm = new Vue({
el: "#app",
render(h) {
return h("h1", this.msg);
},
data: {
msg: "Hello Vue",
},
});
- 结果:能正常显示
小结
vue-cli默认引用的是运行时版本(不带编译器),并且是ESModule=>vue.runtime.esm.js
- 因为vue-cli对webpack做了一个深度的封装,我们在vue-cli创建的项目中,看不到引入vue的版本,但vue-cli提供的一个命令行工具,通过这个工具可以查看webpack的配置
- 命令行输入vue inspect
- webpack配置项在命令行中显示查看起来不太友好,直接输出到文件中(>代表把前面命令生成的结果输入到指定文件中)
vue inspect > output.js
- 我们在开发项目的时候,会有很多单文件组件(.vue文件),这些单文件组件浏览器是不支持的,所以在打包的时候我们会将这些单文件组件转换成js对象,在转换js对象的过程中,还会将template模板转换成render函数(vue-loader来实现),所以单文件组件的运行是不需要编译器的
寻找入口文件
- 查看dist/vue.js的构建过程
执行构建
npm run build
"build": "node scripts/build.js",
通过node 生成所有版本的vue
npm run dev
"dev": "rollup -w -c scripts/config.js --sourcemap --environment TARGET:web-full-dev"
rollup:通过rollup构建工具生成单一的vue版本
- environment:设置环境变量TARGET:web-full-dev
- web:生成web平台的vue版本
- full:生成完整版的vue(含编译器+运行时)
- dev:开发环境下的vue版本(不压缩)
- script/config.js 的执行过程
- 作用:生成rollup构建的配置文件
- 使用环境变量TARGET:web-full-dev
script/config.js文件执行过程
- 以npm run dev 为 例
"dev": "rollup -w -c scripts/config.js --sourcemap --environment TARGET:web-full-dev",
const path = require('path')
const buble = require('rollup-plugin-buble')
const alias = require('rollup-plugin-alias')
const cjs = require('rollup-plugin-commonjs')
const replace = require('rollup-plugin-replace')
const node = require('rollup-plugin-node-resolve')
const flow = require('rollup-plugin-flow-no-whitespace')
const version = process.env.VERSION || require('../package.json').version
const weexVersion = process.env.WEEX_VERSION || require('../packages/weex-vue-framework/package.json').version
const featureFlags = require('./feature-flags')
const banner =
'/*!\n' +
` * Vue.js v${
version}\n` +
` * (c) 2014-${
new Date().getFullYear()} Evan You\n` +
' * Released under the MIT License.\n' +
' */'
const weexFactoryPlugin = {
intro () {
return 'module.exports = function weexFactory (exports, document) {'
},
outro () {
return '}'
}
}
const aliases = require('./alias')
const resolve = p => {
const base = p.split('/')[0]
if (aliases[base]) {
return path.resolve(aliases[base], p.slice(base.length + 1))
} else {
return path.resolve(__dirname, '../', p)
}
}
const builds = {
// Runtime only (CommonJS). Used by bundlers e.g. Webpack & Browserify
'web-runtime-cjs-dev': {
entry: resolve('web/entry-runtime.js'),
dest: resolve('dist/vue.runtime.common.dev.js'),
format: 'cjs',
env: 'development',
banner
},
'web-runtime-cjs-prod': {
entry: resolve('web/entry-runtime.js'),
dest: resolve('dist/vue.runtime.common.prod.js'),
format: 'cjs',
env: 'production',
banner
},
// Runtime+compiler CommonJS build (CommonJS)
'web-full-cjs-dev': {
// resolve函数将相对路径转换成绝对路径(web是别名)
entry: resolve('web/entry-runtime-with-compiler.js'),
dest: resolve('dist/vue.common.dev.js'),
format: 'cjs',
env: 'development',
alias: {
he: './entity-decoder' },
banner
},
'web-full-cjs-prod': {
entry: resolve('web/entry-runtime-with-compiler.js'),
dest: resolve('dist/vue.common.prod.js'),
format: 'cjs',
env: 'production',
alias: {
he: './entity-decoder' },
banner
},
// 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 ES modules build (for bundlers)
'web-full-esm': {
entry: resolve('web/entry-runtime-with-compiler.js'),
dest: resolve('dist/vue.esm.js'),
format: 'es',
alias: {
he: './entity-decoder' },
banner
},
// Runtime+compiler ES modules build (for direct import in browser)
'web-full-esm-browser-dev': {
entry: resolve('web/entry-runtime-with-compiler.js'),
dest: resolve('dist/vue.esm.browser.js'),
format: 'es',
transpile: false,
env: 'development',
alias: {
he: './entity-decoder' },
banner
},
// Runtime+compiler ES modules build (for direct import in browser)
'web-full-esm-browser-prod': {
entry: resolve('web/entry-runtime-with-compiler.js')