学vue源码——入口文件

42 阅读2分钟

今年十月份换了工作,经过众多面试官的折磨,各种原理拷打,裸辞了三月终于找到一份工作,痛下决心,作为一名菜鸟,开始了我的阅读vue源码之路。
首先,vue3源码地址:https://github.com/vuejs/core

npm i pnpm
pnpm i

找到顶层的package.json文件,找到命令

image.png
执行了scripts/dev.js文件,测试:

image.png 断点分析:
获取prod默认表明开发环境,__dirname为当前文件路径

image.png 开始遍历targets数组,其中relative生成相对路径,resolve生成绝对路径,

image.png

outfile = "E:\\学习\\vue3\\core\\packages\\vue\\dist\\vue.global.js"
relativeOutfile="packages\\vue\\dist\\vue.global.js"

指向vue.global.js
...
esbuild构建

image.png
入口文件entryPoints:xxxx\core\packages\vue\src\index.ts
DEV: prod ? false : true,环境变量为开发环境
文件更改时,执行此行,在终端打印

image.png 文件全部注释:

// @ts-check

// Using esbuild for faster dev builds.
// We are still using Rollup for production builds because it generates
// smaller files and provides better tree-shaking.

import esbuild from 'esbuild'
import fs from 'node:fs'
import { dirname, relative, resolve } from 'node:path'
import { fileURLToPath } from 'node:url'
import { createRequire } from 'node:module'
import { parseArgs } from 'node:util'
import { polyfillNode } from 'esbuild-plugin-polyfill-node'

const require = createRequire(import.meta.url)
// 当前文件夹绝对路径
const __dirname = dirname(fileURLToPath(import.meta.url))
// format:global,prod:false,inline:false
// positionals: ['vue']
// parseArgs: 解析命令行参数
const {
  values: { format: rawFormat, prod, inline: inlineDeps },
  positionals,
} = parseArgs({
  allowPositionals: true,
  options: {
    format: {
      type: 'string',
      short: 'f',
      default: 'global',
    },
    prod: {
      type: 'boolean',
      short: 'p',
      default: false,
    },
    inline: {
      type: 'boolean',
      short: 'i',
      default: false,
    },
  },
})

const format = rawFormat || 'global'
// ['vue']
const targets = positionals.length ? positionals : ['vue']

// resolve output iife:立即执行函数表达式, cjs:CommonJS, esm:ESM
const outputFormat = format.startsWith('global')
  ? 'iife'
  : format === 'cjs'
    ? 'cjs'
    : 'esm'
// global
const postfix = format.endsWith('-runtime')
  ? `runtime.${format.replace(/-runtime$/, '')}`
  : format

const privatePackages = fs.readdirSync('packages-private')

for (const target of targets) {
  const pkgBase = privatePackages.includes(target)
    ? `packages-private`
    : `packages`
  // pkgBasePath: '../packages/vue'
  const pkgBasePath = `../${pkgBase}/${target}`
  const pkg = require(`${pkgBasePath}/package.json`)
  // packages\vue\dist\vue.global.js
  const outfile = resolve(
    __dirname,
    `${pkgBasePath}/dist/${
      target === 'vue-compat' ? `vue` : target
    }.${postfix}.${prod ? `prod.` : ``}js`,
  )
  const relativeOutfile = relative(process.cwd(), outfile)

  // resolve externals
  // TODO this logic is largely duplicated from rollup.config.js
  /** @type {string[]} */
  let external = []
  if (!inlineDeps) {
    // cjs & esm-bundler: external all deps
    if (format === 'cjs' || format.includes('esm-bundler')) {
      external = [
        ...external,
        ...Object.keys(pkg.dependencies || {}),
        ...Object.keys(pkg.peerDependencies || {}),
        // for @vue/compiler-sfc / server-renderer
        'path',
        'url',
        'stream',
      ]
    }

    if (target === 'compiler-sfc') {
      const consolidatePkgPath = require.resolve(
        '@vue/consolidate/package.json',
        {
          paths: [resolve(__dirname, `../packages/${target}/`)],
        },
      )
      const consolidateDeps = Object.keys(
        require(consolidatePkgPath).devDependencies,
      )
      external = [
        ...external,
        ...consolidateDeps,
        'fs',
        'vm',
        'crypto',
        'react-dom/server',
        'teacup/lib/express',
        'arc-templates/dist/es5',
        'then-pug',
        'then-jade',
      ]
    }
  }
  /** @type {Array<import('esbuild').Plugin>} */
  const plugins = [
    {
      name: 'log-rebuild',
      setup(build) {
        build.onEnd(() => {
          // 'packages\\vue\\dist\\vue.global.js'
          console.log(`built: ${relativeOutfile}`)
        })
      },
    },
  ]

  if (format !== 'cjs' && pkg.buildOptions?.enableNonBrowserBranches) {
    plugins.push(polyfillNode())
  }
  // 打包入口文件\core\packages\vue\src\index.ts
  esbuild
    .context({
      entryPoints: [resolve(__dirname, `${pkgBasePath}/src/index.ts`)],
      outfile, // 'xxx\\core\\packages\\vue\\dist\\vue.global.js'
      bundle: true,
      external, // []
      sourcemap: true,
      format: outputFormat, // iife
      globalName: pkg.buildOptions?.name, // Vue
      platform: format === 'cjs' ? 'node' : 'browser', // browser
      plugins,
      // define 提供了一种用常量表达式替换全局标识符的方法。 它可以在不改变代码本身的情况下改变某些构建之间代码的行为
      define: {
        __COMMIT__: `"dev"`,
        __VERSION__: `"${pkg.version}"`,
        __DEV__: prod ? `false` : `true`,
        __TEST__: `false`,
        __BROWSER__: String(
          format !== 'cjs' && !pkg.buildOptions?.enableNonBrowserBranches,
        ), // true
        __GLOBAL__: String(format === 'global'), // true
        __ESM_BUNDLER__: String(format.includes('esm-bundler')), // false
        __ESM_BROWSER__: String(format.includes('esm-browser')), // false
        __CJS__: String(format === 'cjs'), // false
        __SSR__: String(format !== 'global'), // false
        __COMPAT__: String(target === 'vue-compat'), // false
        __FEATURE_SUSPENSE__: `true`,
        __FEATURE_OPTIONS_API__: `true`,
        __FEATURE_PROD_DEVTOOLS__: `false`,
        __FEATURE_PROD_HYDRATION_MISMATCH_DETAILS__: `true`,
      },
    })
    .then(ctx => ctx.watch()) // ctx.watch() 监听文件变化
}

core\packages\vue\src\index.ts

打开packages\vue\examples\transition\modal.html断点分析index.ts文件;
compileToFunction:将template转换成成render函数

image.png