Vue 是如何使用 rollup 进行打包的

431 阅读5分钟

「这是我参与2022首次更文挑战的第29天,活动详情查看:2022首次更文挑战

前言

最近我们介绍了 rollup 的自定义打包过程以及它简单的一个使用,学习一个技术,最好的方法就是在自己使用了以后,去看一下别的优秀的项目是如何去使用它的,再来对比一下自己的使用,在这其中就是不断的学习的一个过程。

所以今天我们就来介绍一下 rollupVue2 中是如何去应用的。

build 命令

首先看一个开源项目,我们需要先去看一下项目的 package.jsonbuild一般是作为我们项目的打包命令,那么在 scripts 我们也能够看到 Vue 的打包命令运行了一个 js文件:

image.png

并且不同的打包命令也都是在这个命令的基础上添加后缀生成的,那么我们就可以先去看一下 scripts 底下的 build.js 文件:

bulid.js

image.png

在引入的依赖中能够一眼看到,在这里引入了 rollup,这个依赖底下的 rollup.rollupgenerate 方法就是我们要去找的打包方法,至于为什么要找这两个方法,不清楚的可以看一下 rollup 官方文档中 JavaScript API 的教程介绍:

rollup.js (rollupjs.org)

在得知这两个方法就是打包方法之后,我们就能够去寻找整个项目打包的输入和输出,rollup.rollup 方法需要传入打包的输入选项,generate 方法则是需要传入输出选项,两个选项可有的配置项如下:

inputOptions = {
// 核心参数
input, // 唯一必填参数
external,
plugins,

// 高级参数
onwarn,
cache,

// 危险参数
acorn,
context,
moduleContext,
legacy
};
outputOptions = {
// 核心参数
file,   // 若有bundle.write,必填
format, // 必填
name,
globals,

// 高级参数
paths,
banner,
footer,
intro,
outro,
sourcemap,
sourcemapFile,
interop,

// 危险区域
exports,
amd,
indent
strict
};

然后在下方我们也能够去找到打包输入和输出配置项传入的地方

image.png

接着我们先来看一下输入配置项 config 一路往上就能够找到,configbuilds 属性中的 [built] 属性

image.png

builds 属性是由 config.js 文件传递过来的。

image.png

config.js

跟着方法我们寻找到 config.js 文件的底部:

if (process.env.TARGET) {
  module.exports = genConfig(process.env.TARGET);
} else {
  exports.getBuild = genConfig;
  exports.getAllBuilds = () => Object.keys(builds).map(genConfig);
}

或许当前我们不清楚这个判断分支的作用是什么,但是没有关系,我先去关心我们需要的部分,比较关键的也就是 genConfig 这个函数。

genConfig 函数内部会从一个设定好的 builds 对象中,获取当前对应的构建配置对象。

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;

然后在每个构建配置对象中,就会去定义不同的打包格式需要的不同的参数,分别有 entry 入口文件,dist 输出文件,format 输出格式,env 环境 等打包配合参数。

这里我们拿第一项来看一下:

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,
  },
  
  ...
}

其中的 banner 定义了打包进行的时间和 Vue 的一个版本:

const banner =
  "/*!\n" +
  ` * Vue.js v${version}\n` +
  ` * (c) 2014-${new Date().getFullYear()} Evan You\n` +
  " * Released under the MIT License.\n" +
  " */";

在看到入口的时候我们会发现,项目中并没有 web 目录,那么这个入口文件在什么地方呢?

其实入口文件只是利用 rolupalias 插件取了个别名,具体的映射规则写在了 scripts/alias.js 文件中:

image.png

根据上面的映射规则,我们就能够找到对应的路径,这样就能够拿到对象的构建配置,然后我们就可以回到打包的文件 build.js 中去

build.js

在获得配置对象之后,我们就能够调用 rollup 的 API 来进行打包的操作

image.png

这里要注意,Vue 并未使用 rollup 提供的 write 方法,而是自己定义了 write 方法,但是他们的作用都是相同的,都是用来生成文件的:

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

然后我们看完 bulid 命令,会发现后面还有两个和 bulid 很相似的命令,分别是:

 "build:ssr": "npm run build -- web-runtime-cjs,web-server-renderer",
 "build:weex": "npm run build -- weex",

它们则只是在 bulid 的基础上加了一层过滤,可以在 bulid.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;
  });
}

那么到这里我们就介绍完了 bulid 命令,接下来让我们看一下 dev 命令是如何执行的

dev 命令

dev 命令和 bulid 略有不同,我们也可以在 package.json 中去看到它:

image.png

可以看出来它是直接通过运行 rollup 来执行的,其中包含一些 rollup 的配置项:

  1. -c:指定 rollup 打包的配置文件

  2. -w:开启监听模式,当文件发生变化的时候,会自动进行打包

  3. --environment:设置环境变量,之后可以通过 process.env 来获取这个配置

rollup-c 配置项,我们得知配置文件为 scripts/config.js

scripts/config.js 文件的最下方有一个分支,刚刚在 build 命令中我们并没有去分析它,但是现在通过 dev 命令,我们很容易就能够看出来,这个分支是用于区分不同的打包获取不同配置,如果process.env 中存在 TARGET 这个属性,则会单独执行 genConfig 方法,并且将不同的环境变量当成参数传入进去,然后结合我们上面介绍过了 genConfig 方法,我们就可以根据不同的传入参数来获得不同的构建配置项。

image.png

到这里我们就介绍完了 Vue 是如何通过 rollup 进行打包的。

总结

本文稍微介绍了 Vue 通过 rollup 进行打包的一个大致流程,这只是 rollupVue2 中的一个应用, rollup 的打包还被应用在很多其他的出色开源项目当中,具体我们可以之后再来展开相关的学习。