Vue 源码解析

一、FLow类型检测

  • 安装Flow

    • npm install flow-bin
  • 初始化flow项目

    • flow init
  • flow检查项目 flow 文件

  • /*@flow*/
    接下去的代码就需要flow检查了
    

二、Vue目录源码设计

  • 源码GitHub路径

  • Vue.js 的源码都在 src 目录下,其目录结构如下。

    • src
      ├── compiler        # 编译相关 
      ├── core            # 核心代码 
      ├── platforms       # 不同平台的支持
      ├── server          # 服务端渲染
      ├── sfc             # .vue 文件解析
      ├── shared          # 共享代码
      
    • compiler

      • Vue2.0有一个改变,添加了watch dom,watch dom生成实际执行的是render function,平时很少写render function 大部分写template ,将template转成render function,就在complier 中。编译的代码都在complier目录下
    • core

      • component:内部存放了很多组件,比如keep-alive
      • global-api:全局api
      • instance:存放的是渲染的一些函数,事件、初始化、生命周期
      • observer:跟响应式数据相关
      • util:存放工具方法
      • vdom:watch dom 核心代码放到这里
    • platforms

      • web:浏览器程序
        • complier:平台相关的编译
        • runtime:平台运行代码
        • server:平台相关的server render代码
        • util:辅助的工具函数
      • weex:混合app
    • server:跟服务端渲染的相关代码

    • sfc:vue的解析器,将单文件(.vue)编译成一个对象

    • shared:辅助的方法,比如以下常亮和工具方法

    通过模块化方式,这让这些相互引用,再通过编译工具(Rollup)生成独立的js

三、源码构建

  • Vue.js 源码是基于 Rollup 构建的,它的构建相关配置都在 scripts 目录下。

    • Rollup 和webpack区别:
      • webpack将静态资源全部编译成js,而Rollup适合js库的编译,只处理js,不管理其他文件,更加轻量
    • package.json
      • mian:npm包入口,import vue,从这里查找vue文件路径
      • module:webpack2以上,将module将这个当做默认入口
      • scripts:
        • 构建项目的执行命令有:build、build:ssr、build:weex
          • "build": "node scripts/build.js":编译成web相关的
          • "build:ssr": "npm run build -- web-runtime-cjs,web-server-renderer":输出和renderer相关的
          • "build:weex": "npm run build -- weex":输出和weex相关的
  • 构建过程:

    • scripts/build.js

      • let builds = require('./config').getAllBuilds():拿到相关的配置

      • 将配置进行过滤

        • // filter builds via command line arg
          if (process.argv[2]) {
            const filters = process.argv[2].split(',')
            builds = builds.filter(b => {
              return filters.some(f => b.output.file.indexOf(f) > -1 || b._name.indexOf(f) > -1)
            })
          } else {
            // filter out weex builds by default
            builds = builds.filter(b => {
              return b.output.file.indexOf('weex') === -1
            })
          }
          
        • build(builds):执行构建

    • 分析如何拿到配置文件当前目录下的config.js

      • if (process.env.TARGET) {
          module.exports = genConfig(process.env.TARGET)
        } else {
          exports.getBuild = genConfig
          exports.getAllBuilds = () => Object.keys(builds).map(genConfig)
        }
        
        • 暴露一个方法getAllBuilds,通过Object.keys(builds)拿到builds的所有keys形成数组,map遍历

        • builds是什么:

          • 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': {
                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'),
                dest: resolve('dist/vue.esm.browser.min.js'),
                format: 'es',
                transpile: false,
                env: 'production',
                alias: { he: './entity-decoder' },
                banner
              },
              // runtime-only build (Browser)
              'web-runtime-dev': {
                entry: resolve('web/entry-runtime.js'),
                dest: resolve('dist/vue.runtime.js'),
                format: 'umd',
                env: 'development',
                banner
              },
              // runtime-only production build (Browser)
              'web-runtime-prod': {
                entry: resolve('web/entry-runtime.js'),
                dest: resolve('dist/vue.runtime.min.js'),
                format: 'umd',
                env: 'production',
                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
              },
              // Runtime+compiler production build  (Browser)
              'web-full-prod': {
                entry: resolve('web/entry-runtime-with-compiler.js'),
                dest: resolve('dist/vue.min.js'),
                format: 'umd',
                env: 'production',
                alias: { he: './entity-decoder' },
                banner
              },
              // Web compiler (CommonJS).
              'web-compiler': {
                entry: resolve('web/entry-compiler.js'),
                dest: resolve('packages/vue-template-compiler/build.js'),
                format: 'cjs',
                external: Object.keys(require('../packages/vue-template-compiler/package.json').dependencies)
              },
              // Web compiler (UMD for in-browser use).
              'web-compiler-browser': {
                entry: resolve('web/entry-compiler.js'),
                dest: resolve('packages/vue-template-compiler/browser.js'),
                format: 'umd',
                env: 'development',
                moduleName: 'VueTemplateCompiler',
                plugins: [node(), cjs()]
              },
              // Web server renderer (CommonJS).
              'web-server-renderer-dev': {
                entry: resolve('web/entry-server-renderer.js'),
                dest: resolve('packages/vue-server-renderer/build.dev.js'),
                format: 'cjs',
                env: 'development',
                external: Object.keys(require('../packages/vue-server-renderer/package.json').dependencies)
              },
              'web-server-renderer-prod': {
                entry: resolve('web/entry-server-renderer.js'),
                dest: resolve('packages/vue-server-renderer/build.prod.js'),
                format: 'cjs',
                env: 'production',
                external: Object.keys(require('../packages/vue-server-renderer/package.json').dependencies)
              },
              'web-server-renderer-basic': {
                entry: resolve('web/entry-server-basic-renderer.js'),
                dest: resolve('packages/vue-server-renderer/basic.js'),
                format: 'umd',
                env: 'development',
                moduleName: 'renderVueComponentToString',
                plugins: [node(), cjs()]
              },
              'web-server-renderer-webpack-server-plugin': {
                entry: resolve('server/webpack-plugin/server.js'),
                dest: resolve('packages/vue-server-renderer/server-plugin.js'),
                format: 'cjs',
                external: Object.keys(require('../packages/vue-server-renderer/package.json').dependencies)
              },
              'web-server-renderer-webpack-client-plugin': {
                entry: resolve('server/webpack-plugin/client.js'),
                dest: resolve('packages/vue-server-renderer/client-plugin.js'),
                format: 'cjs',
                external: Object.keys(require('../packages/vue-server-renderer/package.json').dependencies)
              },
              // Weex runtime factory
              'weex-factory': {
                weex: true,
                entry: resolve('weex/entry-runtime-factory.js'),
                dest: resolve('packages/weex-vue-framework/factory.js'),
                format: 'cjs',
                plugins: [weexFactoryPlugin]
              },
              // Weex runtime framework (CommonJS).
              'weex-framework': {
                weex: true,
                entry: resolve('weex/entry-framework.js'),
                dest: resolve('packages/weex-vue-framework/index.js'),
                format: 'cjs'
              },
              // Weex compiler (CommonJS). Used by Weex's Webpack loader.
              'weex-compiler': {
                weex: true,
                entry: resolve('weex/entry-compiler.js'),
                dest: resolve('packages/weex-template-compiler/build.js'),
                format: 'cjs',
                external: Object.keys(require('../packages/weex-template-compiler/package.json').dependencies)
              }
            }
            
            • builds的可以是不同版本的vuejs编译配置
              • entry:表示入口文件
              • dest:输出目标
              • format:构建出来的文件格式
              • banner:添加注释,版本,创建这之类的
          • genConfig方式,将builds配置转换成Rollup的配置文件格式

          • 最终 let builds = require('./config').getAllBuilds():拿到相关配置文件数组

        • 继续返回build.js

          • // filter builds via command line arg
            if (process.argv[2]) {
              const filters = process.argv[2].split(',')
              builds = builds.filter(b => {
                return filters.some(f => b.output.file.indexOf(f) > -1 || b._name.indexOf(f) > -1)
              })
            } else {
              // filter out weex builds by default
              builds = builds.filter(b => {
                return b.output.file.indexOf('weex') === -1
              })
            }
            
            • process.argv[2] 拿到的scripts配置文件的参数
              • 比如:"build:weex": "npm run build -- weex":process.argv[2] 拿到的是weex这个参数
            • filter 方法,根据参数,将不要的配置给过滤掉,如果没有参数的话,将weex给过滤掉,也就是打包web平台
          • 编译的方法build()

            • function build (builds) {
                // 计数器,用于取出配置文件
                let built = 0
                // 配置文件的个数
                const total = builds.length
                // 循环配置文件执行编译
                const next = () => {
                  buildEntry(builds[built]).then(() => {
                    built++
                    if (built < total) {
                      next()
                    }
                  }).catch(logError)
                }
              
                next()
              }
              
              /**
               * Rollup打包构建
               * @param config 配置文件
               * @returns {Promise}
               */
              function buildEntry (config) {
                // 输出文件配置
                /*
                  const config = {
                    input: opts.entry,
                    external: opts.external,
                    plugins: [
                      flow(),
                      alias(Object.assign({}, aliases, opts.alias))
                    ].concat(opts.plugins || []),
                    output: {
                      file: opts.dest,
                      format: opts.format,
                      banner: opts.banner,
                      name: opts.moduleName || 'Vue'
                    },
                    onwarn: (msg, warn) => {
                      if (!/Circular/.test(msg)) {
                        warn(msg)
                      }
                    }
                  }
                */
                const output = config.output
                // file:输出文件名和路径  banner:添加注释,版本,创建这之类的
                const { file, banner } = output
                // 根据文件后缀名判断需不需要压缩
                const isProd = /(min|prod)\.js$/.test(file)
                // 根据配置文件开始构建
                return rollup.rollup(config)
                  .then(bundle => bundle.generate(output))
                  .then(({ output: [{ code }] }) => {
                    // 如果是需要压缩
                    if (isProd) {
                      const minified = (banner ? banner + '\n' : '') + terser.minify(code, {
                        toplevel: true,
                        output: {
                          ascii_only: true
                        },
                        compress: {
                          pure_funcs: ['makeMap']
                        }
                      }).code
                      // 将编译好的代码写入对应的file指定的路径并命名指定的文件名
                      return write(file, minified, true)
                    } else {
                      return write(file, code)
                    }
                  })
              }
              
              /**
               * // 将编译好的代码写入对应的file指定的路径并命名指定的文件名
               * @param dest 输出的路径和名称
               * @param code 代码
               * @param zip 是否压缩
               * @returns {Promise}
               */
              function write (dest, code, zip) {
                return new Promise((resolve, reject) => {
                  function report (extra) {
                    console.log(blue(path.relative(process.cwd(), dest)) + ' ' + getSize(code) + (extra || ''))
                    resolve()
                  }
              
                  fs.writeFile(dest, code, err => {
                    if (err) return reject(err)
                    if (zip) {
                      zlib.gzip(code, (err, zipped) => {
                        if (err) return reject(err)
                        report(' (gzipped: ' + getSize(zipped) + ')')
                      })
                    } else {
                      report()
                    }
                  })
                })
              }
              
  * Runtime Only 和Runtime + Complier比较

    * Runtime Only:将template模板编译成render函数,编译后是reader函数版本,运行时候不带编译,编译在离线时候做

    * Runtime + Complier:可以不对代码做预编译,不使用vue单文件方式,可以在vue在运行时候,将template编译成render函数

      * ```js
        // 需要编译器的版本(Runtime + Complier)
        new Vue({
          template: '
{{ hi }}
' }) // 这种情况不需要编译器的版本,只需要Runtime Only就行了 new Vue({ render (h) { return h('div', this.hi) } }) ``` * 我们写的.vue 文件,在真正运行时候,早就编译成了javascript函数了,并且template模板已经编译成了render函数了 * 将目标编译,是很耗性能的,所以开发阶段建议使用Runtime Only

四、从入口开始 import 开始

  • 我们研究的是Runtime + Complier 版本

  • 入口文件 src/platforms/web/entry-runtime-with-compiler.js

  • 从入口文件,按照Vue的路径,一路找到了 src/core/instance/index.js

    • 路径:src/platforms/web/entry-runtime-with-compiler.js ➡️ src/platforms/web/runtime/index.js ➡️ src/core/index.js ➡️ src/core/instance/index.js

    • 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'
        
        // ES5 实现的 Class
        function Vue (options) {
          if (process.env.NODE_ENV !== 'production' &&
            // 这个就是强制实行new Vue,直接调用会提示下面的警告的
            !(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
        
        
        • initMixin方法:向原型上挂载_init 方法
        • stateMixin方法:向原型挂载delete、$watch等方法
        • 为什么Vue使用ES5实现而不是ES6实现:因为,哪些Mixin方式,实际是向Vue原型上挂在方法,如果用ES6就做不到将挂载的方法拆分出来管理,这种做法有利于代码管理,按照模块的组织关系把Vue的原型拆分到不同的文件中
      • src/core/index.js

        • initGlobalAPI(Vue):往Vue上挂载一些静态属性

你可能感兴趣的:(Vue 源码解析)