乐趣区

关于前端:VUE-30-源码-rollupconfigjs-文件整体功能介绍

文件门路:VUE 3.0 源码 /rollup.config.js

上一篇文章中,独自介绍了各种类型的输入文件的差别,明天这篇文章将残缺的介绍一下该配置文件

一、rollup.config.js 配置信息时如何失效的?

scripts/dev.js | scripts/build.js 文件中都有这么一个函数:

execa('rollup',  [ '-c', '--environment', [ // ...], ])

要害就在 ‘-c’ 这个参数上:作用是 rollup 打包指定应用配置文件 rollup.config.js

二、整体形容一下该配置文件的执行过程:整体围绕生成 packageConfigs 配置信息的创立开展,创立实现后:export default packageConfigs,具体细节如下:

1、dev 非正式环境下回执行函数 createConfig() 创立配置信息,prod 环境会调用 createProductionConfig() 和 createMinifiedConfig() 两个函数生成相应的配置信息。

2、createConfig 过程:

(1)会配置 input 入口文件。
(2)external 内部依赖的名称。
(3)plugins 插件信息。
(4)调用 createReplacePlugin 函数:配置替换插件中须要替换的变量,打包过程中替换文件中的字符串。
(5)配置 output 输入信息。
(6)设置 onwarn 拦挡正告音讯. 如果未配置,正告信息将被去重并打印到控制台。
(7)配置 treeshake 升高代码体积。

三、上源码,内容有点多,依据下面梳理的思路,能够疾速浏览一下各个环节的代码:

/**
 * path 是 node 的默认库咱们在启动我的项目的时候应用的是 node 的环境所以 path 库就能够间接应用了,而不须要在装置了,* 这就是为什么 package.json 中没有 path 的起因
 */
import path from 'path'
/** 带有 typescript 编译器谬误的的 rollup 插件, 它能够打印出 typescript 语法和语义诊断音讯 */
import ts from 'rollup-plugin-typescript2'
/** 替换文件中的指标字符串 */
import replace from '@rollup/plugin-replace'
/**
 * 容许 Rollup 从 JSON 文件中导入数据
 * 例如:import {version} from '../package.json';
 */
import json from '@rollup/plugin-json'

// 环境变量中指标模块为设置异样解决,因为命令行中输出的模块名跟 packages 目录项模块不匹配导致
if (!process.env.TARGET) {throw new Error('TARGET package must be specified via --environment flag.')
}

// 根目录下 package.json 中定义的版本号
const masterVersion = require('./package.json').version
// 根目录下 packages 门路
const packagesDir = path.resolve(__dirname, 'packages')
// 命令行中输出的模块名称在 packages 目录中所匹配的模块门路
const packageDir = path.resolve(packagesDir, process.env.TARGET)
// packages 目录下某模块名称
const name = path.basename(packageDir)
const resolve = p => path.resolve(packageDir, p)
// 引入以后模块下的 package.json 文件
const pkg = require(resolve(`package.json`))
// 引入以后模块下的 package.json 文件 中设定的 buildOptions 属性
const packageOptions = pkg.buildOptions || {}

// ensure TS checks only once for each build
// 全局记录 TS 检测状态,实现一次检测后记录状态,避免屡次检测,呈现谬误。let hasTSChecked = false

/**
 * 一、全局打包: vue(.runtime).global(.prod).js:【应用 CDN 或没有构建工具】*
 * 1、全局打包不是 UMD 构建的,它们被打包成 IIFEs,并且仅用于通过 <script src="..."> 间接应用。*    format: `iife`
 *    https://developer.mozilla.org/en-US/docs/Glossary/IIFE
 *    iife 的全称是 Immediately Invoked Function Expression,即 "立刻调用的函数表达式",*    能够很容易的用 JS 来表白:(function () {}())
 *    用于通过 <script src="..."> 间接应用。* 2、若要通过浏览器中的 <script src="..."> 间接应用,则裸露 Vue 全局。* 3、vue.global.js 是蕴含编译器和运行时的“残缺”构建版本,因而它反对动静编译模板。* 4、vue.runtime.global.js 只蕴含运行时,并且须要在构建步骤期间预编译模板。*
 * 二、vue(.runtime).esm-browser(.prod).js:【应用 CDN 或没有构建工具】*
 * 1、用于通过原生 ES 模块导入应用 (在浏览器中通过 <script type="module"> 来应用)。* 2、与全局构建版本共享雷同的运行时编译、依赖内联和硬编码的 prod/dev 行为。*
 * 三、vue(.runtime).esm-bundler.js:【应用构建工具】*
 * 1、vue.runtime.esm-bundler.js (默认) 仅运行时,并要求所有模板都要事后编译。*    这是构建工具的默认入口 (通过 package.json 中的 module 字段),*    因为在应用构建工具时,模板通常是事后编译的 (例如:在 *.vue 文件中)。* 2、vue.esm-bundler.js 蕴含运行时编译器。*    如果你应用了一个构建工具,但依然想要运行时的模板编译 (例如,DOM 内 模板或通过内联 JavaScript 字符串的模板),*    请应用这个文件。你须要配置你的构建工具,将 vue 设置为这个文件
 *
 * 四、vue.cjs(.prod).js:【服务端渲染】*
 * 1、通过 require() 在 Node.js 服务器端渲染应用。* 2、如果你将应用程序与带有 target: 'node' 的 webpack 打包在一起,并正确地将 vue 内部化,则将加载此文件。* 3、dev/prod 文件是预构建的,然而会依据 process.env.NODE_ENV 主动加载相应的文件。*/
const outputConfigs = {
  'esm-bundler': {file: resolve(`dist/${name}.esm-bundler.js`),
    format: `es`
  },
  'esm-browser': {file: resolve(`dist/${name}.esm-browser.js`),
    format: `es`
  },
  cjs: {file: resolve(`dist/${name}.cjs.js`),
    format: `cjs`
  },
  global: {file: resolve(`dist/${name}.global.js`),
    format: `iife`
  },

  // runtime-only builds, for main "vue" package only
  'esm-bundler-runtime': {file: resolve(`dist/${name}.runtime.esm-bundler.js`),
    format: `es`
  },
  'esm-browser-runtime': {file: resolve(`dist/${name}.runtime.esm-browser.js`),
    format: 'es'
  },
  'global-runtime': {file: resolve(`dist/${name}.runtime.global.js`),
    format: 'iife'
  }
}

// 模块默认打包形式
const defaultFormats = ['esm-bundler', 'cjs']
/**
 * 命令行中输出的 -f | --formats 信息会在 scripts/dev.js 中赋值给环境变量 env.FORMATS。* 如 'esm-bundler,cjs' 多个值时两头可用逗号分隔.
 * process.env.FORMATS 默认值:'global'
 */

const inlineFormats = process.env.FORMATS && process.env.FORMATS.split(',')

/**
 * 模块打包类型优先级:* 第 1 优先级: inlineFormats 即命令行输出的 格局类型
 * 第 2 优先级: packageOptions.formats 即命 /packages 目录下子模块 package.json 中 buildOptions 属性定义的 formats
 * 第 2 优先级: defaultFormats 即默认打包形式 ['esm-bundler', 'cjs']
 */
const packageFormats = inlineFormats || packageOptions.formats || defaultFormats

/**
 * 正式环境打包时 (非 dev 开发环境),须要增加 --prodOnly | -p 参数,* -- 在 scripts/build.js 中,会查看命令行参数设置,参数正确时会设置 env.PROD_ONLY 环境变量。* 1、正式环境 打包配置信息为 [];
 * 2、其余非正式环境 通过函数 createConfig 创立配置信息;*/
const packageConfigs = process.env.PROD_ONLY
  ? []
  : packageFormats.map(format => createConfig(format, outputConfigs[format]))

if (process.env.NODE_ENV === 'production') {
  packageFormats.forEach(format => {if (packageOptions.prod === false) {return}
    if (format === 'cjs') {packageConfigs.push(createProductionConfig(format))
    }
    if (/^(global|esm-browser)(-runtime)?/.test(format)) {packageConfigs.push(createMinifiedConfig(format))
    }
  })
}

export default packageConfigs

/**
 * 生成非正式环境模块打包配置信息
 * @param {*} format 以后模块 formats 中的一个,如: /packages/vue/package.json buildOptions 属性中 formats 列表元素
 * @param {*} output 定义输入配置信息 类型:{file: string, format: string} 具体可参考变量: outputConfigs 元素属性值
 * @param {*} plugins
 * @returns
 */
function createConfig(format, output, plugins = []) {
  // 没有定义 package 输入相干配置信息:打印正告信息 & 线程退出
  if (!output) {console.log(require('chalk').yellow(`invalid format: "${format}"`))
    process.exit(1)
  }

  // 依据全局环境变量 SOURCE_MAP 信息,给 output 变量设置 sourcemap 属性。// 受控于命令行参数 --formats | -f 的影响
  output.sourcemap = !!process.env.SOURCE_MAP
  /**
   * 给 output 变量设置 externalLiveBindings 属性,默认值(true)* 当设置为 false 时,Rollup 不会生成代码来反对内部导入的模块 (假如导出的模块不会随工夫而扭转的前提下)。* 这将容许 Rollup 生成更优化的代码。请留神,当存在波及内部依赖项的循环依赖项时,这可能会导致问题。* 这将防止大多数状况下 Rollup 在代码中生成 getter,因而在许多状况下可用于使代码 IE8 兼容。*/
  output.externalLiveBindings = false

  /**
   * 判断是否是正式环境打包:依据环境变量__DEV__ || 打包文件的输入门路名称是否以 '.prod.js' 结尾
   * __DEV__ 什么时候被赋值的 (大概率跟 process.env.NODE_ENV 相干)
   */
  const isProductionBuild =
    process.env.__DEV__ === 'false' || /\.prod\.js$/.test(output.file)

  /* esm-bundler esm-browser cjs global 类型 详见 38~71 行正文 */
  const isBundlerESMBuild = /esm-bundler/.test(format)
  const isBrowserESMBuild = /esm-browser/.test(format)
  const isNodeBuild = format === 'cjs'
  const isGlobalBuild = /global/.test(format)

  /**
   * 全局打包时,读取模块下 package.json 文件 中设定的 buildOptions 属性中的 name 属性赋值给 output.name
   */
  if (isGlobalBuild) {output.name = packageOptions.name}

  // 判断是否生成 .d.ts 和 .d.ts.map 文件
  const shouldEmitDeclarations = process.env.TYPES != null && !hasTSChecked

  /** 配置 TS 插件 */
  const tsPlugin = ts({
    // check: 设置为 false 可防止对代码进行任何诊断查看。check: process.env.NODE_ENV === 'production' && !hasTSChecked,
    /**
     * tsconfig 的门路.json。如果您的 tsconfig 在我的项目目录中有其余名称或绝对地位,请设置此选项。* 默认状况下,将尝试加载 /tsconfig.json,但如果文件失落,则不会失败,除非明确设置该值。*/
    tsconfig: path.resolve(__dirname, 'tsconfig.json'),
    /* 缓存的门路。默认为 node_modules 目录下的一个的文件夹 */
    cacheRoot: path.resolve(__dirname, 'node_modules/.rts2_cache'),
    /**
     * 笼罩原有的 tsconfigDefaults 配置信息 = merge tsconfig.json to tsconfigDefaults;
     */
    tsconfigOverride: {
      compilerOptions: {
        sourceMap: output.sourcemap,
        /**
         * 为我的项目中 TypeScript 和 JavaScript 生成.d.ts 文件
         */
        declaration: shouldEmitDeclarations,
        /**
         * 开启 --declarationMap,编译器会同时生成 .d.ts 和 .d.ts.map 文件。* 语言服务当初可能正确辨认这些映射文件,并且应用它们来映射到源码。* 也就是说,在应用“跳到定义之处”性能时,会间接跳转到源码文件,而不是 .d.ts 文件。*/
        declarationMap: shouldEmitDeclarations
      },
      // 指定解析 include 属性配置信息时,应该跳过的文件名称
      exclude: ['**/__tests__', 'test-dts']
    }
  })
  // we only need to check TS and generate declarations once for each build.
  // it also seems to run into weird issues when checking multiple times
  // during a single build.

  // 防止出现问题,每次构建只做一次 TS 检测
  hasTSChecked = true

  // 定义模块入口文件
  const entryFile = /runtime$/.test(format) ? `src/runtime.ts` : `src/index.ts`

  // 指出应将哪些模块视为内部模块,不会与你的库打包在一起。const external =
    isGlobalBuild || isBrowserESMBuild
      ? packageOptions.enableNonBrowserBranches
        ? // externalize postcss for @vue/compiler-sfc
          // because @rollup/plugin-commonjs cannot bundle it properly
          ['postcss']
        : // normal browser builds - non-browser only imports are tree-shaken,
          // they are only listed here to suppress warnings.
          ['source-map', '@babel/parser', 'estree-walker']
      : // Node / esm-bundler builds. Externalize everything.
        [...Object.keys(pkg.dependencies || {}),
          ...Object.keys(pkg.peerDependencies || {}),
          ...['path', 'url'] // for @vue/compiler-sfc
        ]

  // the browser builds of @vue/compiler-sfc requires postcss to be available
  // as a global (e.g. http://wzrd.in/standalone/postcss)

  /**rollup 通过 `external` + `output.globals` 来标记内部依赖 */
  output.globals = {postcss: 'postcss'}

  /**
   * node 插件
   * 1. @rollup/plugin-node-resolve 插件:应用节点解析算法在 node_modules 中查找第三方模块
   * 2. @rollup/plugin-commonjs 插件:将 CommonJS 模块转换为 ES6,以便它们能够蕴含在 Rollup bundle 中
   * 3. rollup-plugin-node-builtins 插件: 容许 node 内置模块 to be required/imported.
   * 4. rollup-plugin-node-globals 插件: 用于嵌入 node 全局信息,包含用于 browserify 的代码,即便它应用过程或缓冲区,也应该能够工作
   */
  const nodePlugins =
    packageOptions.enableNonBrowserBranches && format !== 'cjs'
      ? [require('@rollup/plugin-node-resolve').nodeResolve({
            // 如果 'true',插件将更优先应用内置模块(例如 'fs','path`)。如果'false',插件将查找本地装置的同名模块。preferBuiltins: true
          }),
          require('@rollup/plugin-commonjs')({sourceMap: false}),
          require('rollup-plugin-node-builtins')(),
          require('rollup-plugin-node-globals')()]
      : []

  return {
    // 入口文件
    input: resolve(entryFile),
    // Global and Browser ESM builds inlines everything so that they can be
    // used alone.
    // 内部依赖的名称
    external,
    plugins: [
      // 蕴含 JSON 插件
      json({
        // 为 JSON 对象的每个属性生成命名导出。默认为 true
        namedExports: false
      }),
      tsPlugin,
      // 配置替换插件中须要替换的变量,打包过程中替换文件中的字符串。createReplacePlugin(
        isProductionBuild,
        isBundlerESMBuild,
        isBrowserESMBuild,
        // isBrowserBuild?
        (isGlobalBuild || isBrowserESMBuild || isBundlerESMBuild) &&
          !packageOptions.enableNonBrowserBranches,
        isGlobalBuild,
        isNodeBuild
      ),
      ...nodePlugins,
      ...plugins
    ],
    output,
    // 拦挡正告音讯. 如果未配置,正告信息将被去重并打印到控制台。onwarn: (msg, warn) => {if (!/Circular/.test(msg)) {warn(msg)
      }
    },
    treeshake: {
      // code from imported modules will only be retained if at least one exported value is used
      // 如果引入的模块代码想要保留,至多须要有一个该模块元素被应用过。moduleSideEffects: false
    }
  }
}

/**
 * 配置 Replace 插件
 */
function createReplacePlugin(
  isProduction,
  isBundlerESMBuild,
  isBrowserESMBuild,
  isBrowserBuild,
  isGlobalBuild,
  isNodeBuild
) {
  // 打包文件时替换文件中的字符串。const replacements = {__COMMIT__: `"${process.env.COMMIT}"`,
    __VERSION__: `"${masterVersion}"`,
    __DEV__: isBundlerESMBuild
      ? // preserve to be handled by bundlers
        `(process.env.NODE_ENV !== 'production')`
      : // hard coded dev/prod builds
        !isProduction,
    // this is only used during Vue's internal tests
    __TEST__: false,
    // If the build is expected to run directly in the browser (global / esm builds)
    __BROWSER__: isBrowserBuild,
    __GLOBAL__: isGlobalBuild,
    __ESM_BUNDLER__: isBundlerESMBuild,
    __ESM_BROWSER__: isBrowserESMBuild,
    // is targeting Node (SSR)?
    __NODE_JS__: isNodeBuild,

    // feature flags
    __FEATURE_SUSPENSE__: true,
    __FEATURE_OPTIONS_API__: isBundlerESMBuild ? `__VUE_OPTIONS_API__` : true,
    __FEATURE_PROD_DEVTOOLS__: isBundlerESMBuild
      ? `__VUE_PROD_DEVTOOLS__`
      : false,
    ...(isProduction && isBrowserBuild
      ? {
          'context.onError(': `/*#__PURE__*/ context.onError(`,
          'emitError(': `/*#__PURE__*/ emitError(`,
          'createCompilerError(': `/*#__PURE__*/ createCompilerError(`,
          'createDOMCompilerError(': `/*#__PURE__*/ createDOMCompilerError(`}
      : {})
  }
  // allow inline overrides like
  //__RUNTIME_COMPILE__=true yarn build runtime-core
  Object.keys(replacements).forEach(key => {if (key in process.env) {replacements[key] = process.env[key]
    }
  })
  return replace(replacements)
}

/**
 * Prod 正式环境:format === 'cjs' 生成服务端渲染包时 额定配置信息
 * @param {*} format
 * @returns
 */
function createProductionConfig(format) {
  return createConfig(format, {file: resolve(`dist/${name}.${format}.prod.js`),
    format: outputConfigs[format].format
  })
}

/**
 * Prod 正式环境: global-runtime|esm-browser-runtime 的额定配置
 * @param {*} format
 * @returns
 */
function createMinifiedConfig(format) {
  // minify generated es bundle 压缩 ES code
  const {terser} = require('rollup-plugin-terser')
  return createConfig(
    format,
    {file: outputConfigs[format].file.replace(/\.js$/, '.prod.js'),
      format: outputConfigs[format].format
    },
    [
      terser({module: /^esm/.test(format), // true is set when format is esm or es
        compress: {
          // 5 | 2015 | 2016 | 2017 | 2018 | 2019 | 2020;
          ecma: 2015,
          pure_getters: true
        }
      })
    ]
  )
}

如果您对“前端源码”情有独钟,能够微信扫码关注上面的公众号二维码,内容始终继续更新中!
以后 VUE3.0 源码正在解析中,欢送捧场!

退出移动版