首页前端开发VUEVue2源码系列-开篇

Vue2源码系列-开篇

时间2023-07-07 23:33:02发布访客分类VUE浏览1216
导读:前言大概两年前有学习过vue源码,当时学的比较粗糙,学习到的东西也比较少,差不多都快忘完了。最近打算再次捡起来,同时希望通过博客的方式加深理解和记忆,更希望能遇到一起交流的小伙伴~Flow本次分析的 2.6 版本是使用 flow 作为类型检...

前言


大概两年前有学习过vue源码,当时学的比较粗糙,学习到的东西也比较少,差不多都快忘完了。最近打算再次捡起来,同时希望通过博客的方式加深理解和记忆,更希望能遇到一起交流的小伙伴~


Flow


本次分析的 2.6 版本是使用 flow 作为类型检查工具的,其主要的作用是对 JS 变量进行类型注释和类型推断,弥补JS作为一门弱类型语言的不足。感兴趣的朋友可以在官网上进行学习和了解。在 vue 中,有专门的 flow 文件夹对不同的变量类型进行定义,比如我们后面在源码中常见的 VNodeData


// flow/vnode.js
declare interface VNodeData {
    
  key?: string | number;
    
  slot?: string;
    
  ref?: string;
    
  is?: string;
    
  pre?: boolean;
    
  tag?: string;
    
  staticClass?: string;
    
  class?: any;

  staticStyle?: {
 [key: string]: any }
    ;
    
  style?: string | ArrayObject>
     | Object;
    
  normalizedStyle?: Object;

  props?: {
 [key: string]: any }
    ;

  attrs?: {
 [key: string]: string }
    ;

  domProps?: {
 [key: string]: any }
    ;

  hook?: {
 [key: string]: Function }
    ;

  on?: ?{
     [key: string]: Function | ArrayFunction>
 }
    ;

  nativeOn?: {
     [key: string]: Function | ArrayFunction>
 }
    ;
    
  transition?: Object;
    
  show?: boolean;
 // marker for v-show
  inlineTemplate?: {
    
    render: Function;
    
    staticRenderFns: ArrayFunction>
    ;

  }
    ;
    
  directives?: ArrayVNodeDirective>
    ;
    
  keepAlive?: boolean;

  scopedSlots?: {
 [key: string]: Function }
    ;

  model?: {
    
    value: any;
    
    callback: Function;

  }
    ;

}
    ;
    
复制代码


构建入口


我们可以先看看 package.json 中的构建脚本命令


// package.json
{
    
  "dev": "rollup -w -c scripts/config.js --environment TARGET:web-full-dev",
  "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"
}
    
复制代码


太多了有木有,根本学不过来。我们主要简单地了解下vue使用的打包工具不是 webpack,而是 rollup,rollup 作为一个轻量级的打包工具,可以很好的处理JS代码打包,使其成为很多JS库打包首选。还有一个知识点就是,vue可以打包成多种不同版本,包含 weex平台运行版本,SSR服务端渲染版本,esm模块化版本,带编译的版本等等。


接下来我们大概看看vue的打包过程,是如何实现的

// scripts/build.js
// 首先从 `config.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
  }
)
}
    
复制代码


关于版本配置我们也可以简单看看

// scripts/config.js
// 可以很清楚地看到不同版本的入口文件 `entry` 和打包路径 `dest`
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)
  }

}
    
复制代码

接着我们继续分析下获取配置文件后的打包流程。

// scripts/build.js
build(builds)
// 对不同的打包版本进行遍历调用buildEntry
function build (builds) {
    
  let built = 0
  const total = builds.length
  const next = () =>
 {
    
    buildEntry(builds[built]).then(() =>
 {

      built++
      if (built  total) {

        next()
      }

    }
).catch(logError)
  }

  next()
}

// 在buildEntry中可以看到rollup开始打包工作,具体rollup的打包配置及打包原理这边不作学习
function buildEntry (config) {

  const output = config.output
  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) {

        // 打包后的生产代码插入banner,就是在文件头部包含版本号,日期的那段注释,可以看看
        // scripts/config.js
        // const banner =
        //   '/*!\n' +
        //   ` * Vue.js v${
version}
\n` +
        //   ` * (c) 2014-${
new Date().getFullYear()}
 Evan You\n` +
        //   ' * Released under the MIT License.\n' +
        //   ' */'
        const minified = (banner ? banner + '\n' : '') + terser.minify(code, {

          toplevel: true,
          output: {

            ascii_only: true
          }
,
          compress: {

            pure_funcs: ['makeMap']
          }

        }
).code
        return write(file, minified, true)
      }
 else {

        return write(file, code)
      }

    }
)
}
    
复制代码

打包的流程我们就分析到这,主要是了解下 vue 打包不同版本的实现。


目录结构



vue的核心源码在src目录下,我们先来看看源码的主要目录结构

src
├── compiler        # 编译相关 
├── core            # 核心代码 (组件化,全局API,实例,VDOM,数据监听)
├── platforms       # 不同平台的支持(包括web/weex)
├── server          # 服务端渲染
├── sfc             # .vue 文件解析
├── shared          # 共享代码
复制代码

准备工作


在学习源码之前,我们先准备好我们本地的调试环境吧,首先通过 vue create xxx 新建vue项目,同时通过 git clone 在项目中下载 vue 项目源码, 然后再修改 main.js


// src/main.js
// 在入口文件中我们修改 Vue 的来源,可以引用 node_modules 中的 vue/dist 目录,也可以像我一样引用自己 clone 下来的 vue 库
import Vue from '../vue/dist/vue'
import App from './App.vue'
Vue.config.productionTip = false
new Vue({
    
  render: h =>
 h(App),
}
    ).$mount('#app')
复制代码


修改引用文件为我自己 clone 下来的 vue 库的时候,遇到一些坑


  • 首先就是我的编辑器 vscode 打开 vue 源码的时候会报 ts 校验的错误,通过修改 vscode 的配置文件处理好了

"typescript.validate.enable": false,
"javascript.validate.enable": false
复制代码

  • 其次就是一直报找不到模块 eslint-plugin-flowtype 的错误,按理说这个错误是在 vue/.eslintrc.js 中的报的,但是后面我在外面的 vue 项目中也安装上了 eslint-plugin-flowtype 才解决的报错,怀疑是它找的是外层项目的 node_modules,具体原因也没去了解

  • 在解决报错的过程中,我也为 vscode 中安装了一些支持 flow 的插件,同时屏蔽了项目 eslint 对于 vue 源码项目的检查

// .eslintignore
vue
复制代码


现在项目的目录结构是

xxx
├── node_modules        
├── public            
├── src      
├── vue       # vue为我们自己 clone 下来的源码
├── .eslintignore             
├── package.js         
├── ...       
复制代码

至此,我们可以通过 npm install 安装好依赖,再开启运行项目 npm run serve,运行 vue 源码,npm run dev 来进行源码调试了


声明:本文内容由网友自发贡献,本站不承担相应法律责任。对本内容有异议或投诉,请联系2913721942#qq.com核实处理,我们将尽快回复您,谢谢合作!


若转载请注明出处: Vue2源码系列-开篇
本文地址: https://pptw.com/jishu/294947.html
【HTML&&CSS】页面版块布局(亲身实践,帮你避坑) vue2-实例化过程

游客 回复需填写必要信息