• Vue系列---源码构建过程(四)


    在了解源码如何构建之前,我们有必要了解下 项目中一个简单的目录结构如下:

    |---- vue
    |  |---- dist               # 打包后的存放文件目录
    |  |---- scripts            # 存放构建相关的代码
    |  |  |--- alias.js
    |  |  |--- build.js
    |  |  |--- config.js        # 配置文件
    |  |  |--- ..... 其他的更多
    |  |---- src                # src目录是vue核心代码库
    |  |  |--- compiler
    |  |  |--- core
    |  |  |--- platforms
    |  |  | |--- web            # web平台
    |  |  | | |--- compiler
    |  |  | | |--- runtime
    |  |  | | |--- server
    |  |  | | |--- util
    |  |  | | |--- entry-runtime-with-compiler.js  # 运行+模板编译的入口文件
    |  |  | |--- weex
    |  |  |--- server
    |  |  |--- sfc
    |  |  |--- shared
    |  |---- package.json

    如上只是一个非常简单的一个目录,为了节约篇幅,只是把入口构建的相关的目录画出来。

    我们看任何库相关的代码的第一步先把视线转移到 package.json 中来。然后看下 "scripts" 这个,如下:

    {
      ......
      "scripts": {
        "dev": "rollup -w -c scripts/config.js --environment TARGET:web-full-dev --sourcemap",
        "dev:cjs": "rollup -w -c scripts/config.js --environment TARGET:web-runtime-cjs-dev",
        "dev:esm": "rollup -w -c scripts/config.js --environment TARGET:web-runtime-esm",
        "dev:test": "karma start test/unit/karma.dev.config.js",
        "dev:ssr": "rollup -w -c scripts/config.js --environment TARGET:web-server-renderer",
        "dev:compiler": "rollup -w -c scripts/config.js --environment TARGET:web-compiler ",
        "dev:weex": "rollup -w -c scripts/config.js --environment TARGET:weex-framework",
        "dev:weex:factory": "rollup -w -c scripts/config.js --environment TARGET:weex-factory",
        "dev:weex:compiler": "rollup -w -c scripts/config.js --environment TARGET:weex-compiler ",
        "build": "node scripts/build.js",
        "build:ssr": "npm run build -- web-runtime-cjs,web-server-renderer",
        "build:weex": "npm run build -- weex",
        "test": "npm run lint && flow check && npm run test:types && npm run test:cover && npm run test:e2e -- --env phantomjs && npm run test:ssr && npm run test:weex",
        "test:unit": "karma start test/unit/karma.unit.config.js",
        "test:cover": "karma start test/unit/karma.cover.config.js",
        "test:e2e": "npm run build -- web-full-prod,web-server-basic-renderer && node test/e2e/runner.js",
        "test:weex": "npm run build:weex && jasmine JASMINE_CONFIG_PATH=test/weex/jasmine.js",
        "test:ssr": "npm run build:ssr && jasmine JASMINE_CONFIG_PATH=test/ssr/jasmine.js",
        "test:sauce": "npm run sauce -- 0 && npm run sauce -- 1 && npm run sauce -- 2",
        "test:types": "tsc -p ./types/test/tsconfig.json",
        "lint": "eslint src scripts test",
        "flow": "flow check",
        "sauce": "karma start test/unit/karma.sauce.config.js",
        "bench:ssr": "npm run build:ssr && node benchmarks/ssr/renderToString.js && node benchmarks/ssr/renderToStream.js",
        "release": "bash scripts/release.sh",
        "release:weex": "bash scripts/release-weex.sh",
        "release:note": "node scripts/gen-release-note.js",
        "commit": "git-cz"
      },
      .....
    }

    这边我们只要关注 "dev": "rollup -w -c scripts/config.js --environment TARGET:web-full-dev --sourcemap", 这块就可以了,其他的命令也是类似的。如上使用的 rollup 进行打包,然后我们会看到命令中有 scripts/config.js 这个配置文件,因此我们需要把视线找到 这个 scripts/config.js 这个文件上来。

    scripts/config.js 代码如下:

    ......
    
    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 = {
      .....
      '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
      },
      .....
    };
    
    function genConfig (name) {
      const opts = builds[name]
      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)
          }
        }
      }
    
      .... 
    
      return config
    }
    if (process.env.TARGET) {
      module.exports = genConfig(process.env.TARGET)
    } else {
      exports.getBuild = genConfig
      exports.getAllBuilds = () => Object.keys(builds).map(genConfig)
    }

    然后把视线移到最后的代码,if条件判断 process.env.TARGET 是否存在,存在的话,就执行 getConfig(process.env.TARGET) 这个函数,最后把结果导出 module.exports = genConfig(process.env.TARGET);  从命令行中,我们可以看到 process.env.TARGET 值为:'web-full-dev'; 因此 const opts = builds['web-full-dev']; 因此最后 opts的值变为如下:

    const opts = {
      entry: resolve('web/entry-runtime-with-compiler.js'),
      dest: resolve('dist/vue.js'),
      format: 'umd',
      env: 'development',
      alias: { he: './entity-decoder' },
      banner
    }

    再看看 resolve 函数如下:

    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)
      }
    }

    如上 resolve 函数,首先会获取基路径,比如 'web/entry-runtime-with-compiler.js' 的基路径就是 'web',因此 base = 'web'; 然后判断 if (aliases[base]) {} aliases 是否有 key为web的,如果有的话,直接返回:return path.resolve(aliases[base], "entry-runtime-with-compiler.js"); 同理其他的也一样。

    我们再结合下面的 alias.js 代码

    alias.js 代码如下:

    const path = require('path');
    
    const resolve = p => path.resolve(__dirname, '../', p); // 到项目的根目录下
    
    module.exports = {
      vue: resolve('src/platforms/web/entry-runtime-with-compiler'),
      compiler: resolve('src/compiler'),
      core: resolve('src/core'),
      shared: resolve('src/shared'),
      web: resolve('src/platforms/web'),
      weex: resolve('src/platforms/weex'),
      server: resolve('src/server'),
      sfc: resolve('src/sfc')
    };

    由代码可知:alias.js 代码可以理解为如下:

    module.exports = {
      vue: '项目的根目录' + '/src/platforms/web/entry-runtime-with-compiler',
      compiler: '项目的根目录' + '/src/compiler',
      core: '项目的根目录' + '/src/core',
      shared: '项目的根目录' + '/src/shared',
      web: '项目的根目录' + '/src/platforms/web',
      weex: '项目的根目录' + '/src/platforms/weex',
      server: '项目的根目录' + '/src/server',
      sfc: '项目的根目录' + '/src/sfc'
    };

    分析可知最后的opts对象变为如下:

    const opts = {
      entry: '项目的根目录' + '/src/platforms/web/entry-runtime-with-compiler.js',
      dest: '项目的根目录' + '/dist/vue.js',
      format: 'umd',
      env: 'development',
      alias: { he: './entity-decoder' },
      banner
    };

    因此 genConfig 函数内的config对象值变为如下:

    const config = {
      input: '项目的根目录' + '/src/platforms/web/entry-runtime-with-compiler.js',
      external: '',
      plugins: [
        flow(),
        alias(Object.assign({}, aliases, opts.alias))
      ].concat(opts.plugins || []),
      output: {
        file: '项目的根目录' + '/dist/vue.js',
        format: 'umd',
        banner: '',
        name: opts.moduleName || 'Vue'
      },
      onwarn: (msg, warn) => {
        if (!/Circular/.test(msg)) {
          warn(msg)
        }
      }
    }; 

    如上代码打包的含义可以理解为如下:

    找到 '项目的根目录' + '/src/platforms/web/entry-runtime-with-compiler.js', 路径下的js文件 打包到'项目的根目录' + '/dist/vue.js',目录下的 vue.js 文件。因此我们需要把视线转移到 '/src/platforms/web/entry-runtime-with-compiler.js' 文件内了。该文件就是我们的vue的入口文件。

    entry-runtime-with-compiler.js 基本的代码如下:

    /* @flow */
    
    import config from 'core/config'
    import { warn, cached } from 'core/util/index'
    import { mark, measure } from 'core/util/perf'
    
    import Vue from './runtime/index'
    import { query } from './util/index'
    import { compileToFunctions } from './compiler/index'
    import { shouldDecodeNewlines, shouldDecodeNewlinesForHref } from './util/compat'
    
    ....
    
    const mount = Vue.prototype.$mount;
    
    Vue.prototype.$mount = function() {
      .....
    };
    
    ....
    
    export default Vue;

    如上其他的代码,我们这边先不管,我们先看的 import Vue from './runtime/index' 这句代码,为什么要看这句代码呢,那是因为 它引入了该文件,并且直接使用 export default Vue; 导出该 Vue.因此我们会找到 src/platforms/web/runtime/index.js 代码如下:

    import Vue from 'core/index'
    import config from 'core/config'
    import { extend, noop } from 'shared/util'
    import { mountComponent } from 'core/instance/lifecycle'
    import { devtools, inBrowser } from 'core/util/index'
    
    import {
      query,
      mustUseProp,
      isReservedTag,
      isReservedAttr,
      getTagNamespace,
      isUnknownElement
    } from 'web/util/index'
    
    import { patch } from './patch'
    import platformDirectives from './directives/index'
    import platformComponents from './components/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
    
    .....
    
    export default Vue;

    该文件的代码也是一样,先引入 import Vue from 'core/index' 文件后,然后导出 export default Vue;

    因此我们继续找到 src/core/index.js 代码如下:

    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'
    
    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;

    如上代码,我们主要看 import Vue from './instance/index'; 和一些全局API import { initGlobalAPI } from './global-api/index' 的代码。

    首先我们看 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;

    如上代码,我们终于看到Vue的构造函数了,我们在Vue页面初始化 new Vue({}); 这样调用的时候,就会调用该构造函数,而我们传入的参数就传给了options。该函数首先会判断是不是正式环境 及 是否使用 new 来实列Vue。
    最后会调用 this._init(options) 该函数。该函数在 src/core/instance/init.js 里面,也就是我们下面的initMixin(Vue) 函数调用初始化了。它会把一些方法挂载到Vue的原型上,比如 _init()方法,如下代码:
    Vue.prototype._init = function() {} 这样的。

    下面我们继续来看下该方法,在 src/core/instance/init.js 代码如下:

    /* @flow */
    
    import config from '../config'
    import { initProxy } from './proxy'
    import { initState } from './state'
    import { initRender } from './render'
    import { initEvents } from './events'
    import { mark, measure } from '../util/perf'
    import { initLifecycle, callHook } from './lifecycle'
    import { initProvide, initInjections } from './inject'
    import { extend, mergeOptions, formatComponentName } from '../util/index'
    
    export function initMixin (Vue: Class<Component>) {
      Vue.prototype._init = function (options?: Object) {
        .......
      }
    }
    
    export function initInternalComponent (vm: Component, options: InternalComponentOptions) {
      .....
    }
    
    export function resolveConstructorOptions (Ctor: Class<Component>) {
      ....
    }

    如上就是 init.js 代码。

    initGlobalAPI

    下面我们再看下 src/core/global-api/index.js, Vue在初始化过程中,不仅给他的原型prototype上扩展方法,还会给Vue这个对象本身扩展很多全局的静态方法。那么扩展的全局的静态方法和属性就是在该函数内做的。在 src/core/global-api 其实有如下js文件

    |--- vue
    | |--- src
    | | |--- core
    | | | |--- global-api
    | | | | |--- assets.js
    | | | | |--- extends.js
    | | | | |--- index.js
    | | | | |--- mixin.js
    | | | | |--- use.js

    src/core/global-api/index.js 源码可以去看vue(v2.6.10)上去看了。在后面我们会逐渐讲解挂载了哪些全局属性和原型方法的。

  • 相关阅读:
    五大常用算法之三:贪心算法
    五大常用算法之二:动态规划算法
    pycharm最新激活码2020
    pgsql安装 centos自带的9.2.4
    centos 7+ 安装python虚拟环境
    常见HTTP状态码
    python搜索引擎和框架
    中文分词工具jieba的使用
    django分页功能 views与templates
    Django-Celery异步发送激活邮件,以及注意点
  • 原文地址:https://www.cnblogs.com/tugenhua0707/p/11756578.html
Copyright © 2020-2023  润新知