Vite: 关于Rollup打包

news2024/7/6 19:35:49

概述

  • Rollup 是一款基于 ES Module 模块规范实现的 JavaScript 打包工具,在前端社区中赫赫有名,同时也在 Vite 的架构体系中发挥着重要作用
  • 不仅是 Vite 生产环境下的打包工具,其插件机制也被 Vite 所兼容,可以说是 Vite 的构建基石

快速上手

  • 首先让我们用 $ npm init -y 新建一个项目,然后安装 rollup 依赖: $ pnpm i rollup

  • 接着新增 src/index.js 和 src/util.js 和 rollup.config.js 三个文件,目录结构如下所示:

    ├── package.json
    ├── pnpm-lock.yaml
    ├── rollup.config.js
    └── src
     ├── index.js
     └── util.js
    
  • 文件的内容分别如下:

    // src/index.js
    import {
      add
    } from "./util";
    
    console.log(add(1, 2));
    
    // src/util.js
    export const add = (a, b) => a + b;
    export const multi = (a, b) => a * b;
    
    // rollup.config.js
    // 以下注释是为了能使用 VSCode 的类型提示
    /**
     * @type { import('rollup').RollupOptions }
     */
    const buildOptions = {
      input: ["src/index.js"],
      output: {
        // 产物输出目录
        dir: "dist/es",
        // 产物格式
        format: "esm",
      },
    };
    export default buildOptions;
    
  • 你可以在 package.json 中加入如下的构建脚本:

    {
    	 // rollup 打包命令,`-c` 表示使用配置文件中的配置
    	 "build": "rollup -c"
    }
    
  • 接着在终端执行一下 $ npm run build,可以看到如下的命令行信息:

  • OK,现在你已经成功使用 Rollup 打出了第一份产物! 我们可以去 dist/es 目录查看一下产物的内容

    // dist/es/index.js
    // 代码已经打包到一起
    const add = (a, b) => a + b;
    console.log(add(1, 2));
    
  • 同时你也可以发现, util.js 中的 multi 方法并没有被打包到产物中,这是因为 Rollup
    具有天然的 Tree Shaking 功能,可以分析出未使用到的模块并自动擦除。所谓 Tree Shaking (摇树),也是计算机编译原理中 DCE (Dead Code Elimination,即消除无用代码) 技术的一种实现。由于 ES 模块依赖关系是确定的,和运行时状态无关。因此 Rollup 可以在编译阶段分析出依赖关系,对 AST 语法树中没有使用到的节点进行删除,从而实现 Tree Shaking

常用配置


1 ) 多产物配置

  • 在打包 JavaScript 类库的场景中,我们通常需要对外暴露出不同格式的产物供他人使
    用,不仅包括 ESM ,也需要包括诸如 CommonJS 、 UMD 等格式,保证良好的兼容性。那
    么,同一份入口文件,如何让 Rollup 给我们打包出不一样格式的产物呢?我们基于上述
    的配置文件来进行修改:

    // rollup.config.js
    /**
     * @type { import('rollup').RollupOptions }
     */
    const buildOptions = {
      input: ["src/index.js"],
      // 将 output 改造成一个数组
      output: [{
          dir: "dist/es",
          format: "esm",
        },
        {
          dir: "dist/cjs",
          format: "cjs",
        },
      ],
    };
    export default buildOptions;
    
  • 从代码中可以看到,我们将 output 属性配置成一个数组,数组中每个元素都是一个描述
    对象,决定了不同产物的输出行为。

2 )多入口配置

  • 除了多产物配置,Rollup 中也支持多入口配置,而且通常情况下两者会被结合起来使
    用。接下来,就让我们继续改造之前的配置文件,将 input 设置为一个数组或者一个对
    象,如下所示
    {
    	 input: ["src/index.js", "src/util.js"]
    }
    
    // 或者
    {
    	 input: {
    		 index: "src/index.js",
    		 util: "src/util.js",
    	 },
    }
    
  • 通过执行 $ npm run build 可以发现,所有入口的不同格式产物已经成功输出
  • 如果不同入口对应的打包配置不一样,我们也可以默认导出一个 配置数组 ,如下所示:
    // rollup.config.js
    /**
     * @type { import('rollup').RollupOptions }
     */
    const buildIndexOptions = {
      input: ["src/index.js"],
      output: [
        // 省略 output 配置
      ],
    };
    /**
     * @type { import('rollup').RollupOptions }
     */
    const buildUtilOptions = {
      input: ["src/util.js"],
      output: [
        // 省略 output 配置
      ],
    };
    export default [buildIndexOptions, buildUtilOptions];
    
  • 如果是比较复杂的打包场景(如 Vite 源码本身的打包),我们需要将项目的代码分成几个
    部分,用不同的 Rollup 配置分别打包,这种配置就很有用了。

3 )自定义 output 配置

  • 刚才我们提到了 input 的使用,主要用来声明入口,可以配置成字符串、数组或者对
    象,使用比较简单。而 output 与之相对,用来配置输出的相关信息,常用的配置项如下:

     // 产物输出目录
     dir: path.resolve(__dirname, 'dist'),
       // 以下三个配置项都可以使用这些占位符:
       // 1. [name]: 去除文件后缀后的文件名
       // 2. [hash]: 根据文件名和文件内容生成的 hash 值
       // 3. [format]: 产物模块格式,如 es、cjs
       // 4. [extname]: 产物后缀名(带`.`)
       // 入口模块的输出文件名
       entryFileNames: `[name].js`,
       // 非入口模块(如动态 import)的输出文件名
       chunkFileNames: 'chunk-[hash].js',
       // 静态资源文件输出文件名
       assetFileNames: 'assets/[name]-[hash][extname]',
       // 产物输出格式,包括`amd`、`cjs`、`es`、`iife`、`umd`、`system`
       format: 'cjs',
       // 是否生成 sourcemap 文件
       sourcemap: true,
       // 如果是打包出 iife/umd 格式,需要对外暴露出一个全局变量,通过 name 配置变量名
       name: 'MyBundle',
       // 全局变量声明
       globals: {
         // 项目中可以直接用`$`代替`jquery`
         jquery: '$'
       }
     }
    

4 )依赖 external

  • 对于某些第三方包,有时候我们不想让 Rollup 进行打包,也可以通过 external 进行外部
    化:
{
 	external: ['react', 'react-dom']
}
  • 在 SSR 构建或者使用 ESM CDN 的场景中,这个配置将非常有用,

5 ) 接入插件能力

  • 在 Rollup 的日常使用中,我们难免会遇到一些 Rollup 本身不支持的场景,比如 兼容
    CommonJS 打包 、 注入环境变量 、 配置路径别名 、 压缩产物代码 等等。这个时候就需要我
    们引入相应的 Rollup 插件了。接下来以一个具体的场景为例带大家熟悉一下 Rollup 插
    件的使用。

  • 虽然 Rollup 能够打包 输出 出 CommonJS 格式的产物,但对于 输入 给 Rollup 的代码并
    不支持 CommonJS,仅仅支持 ESM。你可能会说,那我们直接在项目中统一使用 ESM
    规范就可以了啊,这有什么问题呢?需要注意的是,我们不光要考虑项目本身的代码,还
    要考虑第三方依赖。目前为止,还是有不少第三方依赖只有 CommonJS 格式产物而并未
    提供 ESM 产物,比如项目中用到 lodash 时,打包项目会出现这样的报错:

  • 因此,我们需要引入额外的插件去解决这个问题。首先需要安装两个核心的插件包:

    • $ pnpm i @rollup/plugin-node-resolve @rollup/plugin-commonjs
    • @rollup/plugin-node-resolve 是为了允许我们加载第三方依赖,否则像 import React from 'react' 的依赖导入语句将不会被 Rollup 识别。
    • @rollup/plugin-commonjs 的作用是将 CommonJS 格式的代码转换为 ESM 格式
  • 然后让我们在配置文件中导入这些插件:

    // rollup.config.js
    import resolve from "@rollup/plugin-node-resolve";
    import commonjs from "@rollup/plugin-commonjs";
    /**
     * @type { import('rollup').RollupOptions }
     */
    export default {
      input: ["src/index.js"],
      output: [{
          dir: "dist/es",
          format: "esm",
        },
        {
          dir: "dist/cjs",
          format: "cjs",
        },
      ],
      // 通过 plugins 参数添加插件
      plugins: [resolve(), commonjs()],
    };
    
  • 现在我们以 lodash 这个只有 CommonJS 产物的第三方包为例测试一下: $ pnpm i lodash

  • 在 src/index.js 加入如下的代码:

    import { merge } from "lodash";
    console.log(merge);
    
  • 然后执行 $ npm run build,你可以发现产物已经正常生成了

  • 在 Rollup 配置文件中, plugins 除了可以与 output 配置在同一级,也可以配置在
    output 参数里面,如:

    // rollup.config.js
    import { terser } from 'rollup-plugin-terser'
    import resolve from "@rollup/plugin-node-resolve";
    import commonjs from "@rollup/plugin-commonjs";
    export default {
    	 output: {
    		 // 加入 terser 插件,用来压缩代码
    		 plugins: [terser()]
    	 },
    	 plugins: [resolve(), commonjs()]
    }
    
  • 当然,你可以将上述的 terser 插件放到最外层的 plugins 配置中。

  • 需要注意的是, output.plugins 中配置的插件是有一定限制的,只有使用 Output 阶段
    相关钩子(具体内容将在下一节展开)的插件才能够放到这个配置中,可以去这个站点, 查看 Rollup 的 Output 插件列表

  • 另外,这里也给大家分享其它一些比较常用的 Rollup 插件库:

    • @rollup/plugin-json: 支持 .json 的加载,并配合 rollup 的 Tree Shaking 机制去
      掉未使用的部分,进行按需打包
    • @rollup/plugin-babel:在 Rollup 中使用 Babel 进行 JS 代码的语法转译
    • @rollup/plugin-typescript: 支持使用 TypeScript 开发
    • @rollup/plugin-alias:支持别名配置。
    • @rollup/plugin-replace:在 Rollup 进行变量字符串的替换
    • rollup-plugin-visualizer: 对 Rollup 打包产物进行分析,自动生成产物体积可视化分析图

JavaScript API 方式调用

  • 以上我们通过 Rollup 的配置文件结合 rollup -c 完成了 Rollup 的打包过程,但有些场
    景下我们需要基于 Rollup 定制一些打包过程,配置文件就不够灵活了,这时候我们需要
    用到对应 JavaScript API 来调用 Rollup,主要分为 rollup.rolluprollup.watch 两个
    API,接下来我们以具体的例子来学习一下。

  • 首先是 rollup.rollup ,用来一次性地进行 Rollup 打包,你可以新建 build.js ,内容如下:

    // build.js
    const rollup = require("rollup");
    // 常用 inputOptions 配置
    const inputOptions = {
      input: "./src/index.js",
      external: [],
      plugins: []
    };
    const outputOptionsList = [
      // 常用 outputOptions 配置
      {
        dir: 'dist/es',
        entryFileNames: `[name].[hash].js`,
        chunkFileNames: 'chunk-[hash].js',
        assetFileNames: 'assets/[name]-[hash][extname]',
        format: 'es',
        sourcemap: true,
        globals: {
          lodash: '_'
        }
      }
      // 省略其它的输出配置
    ];
    
    async function build() {
      let bundle;
      let buildFailed = false;
      try {
        // 1. 调用 rollup.rollup 生成 bundle 对象
        bundle = await rollup.rollup(inputOptions);
        for (const outputOptions of outputOptionsList) {
          // 2. 拿到 bundle 对象,根据每一份输出配置,调用 generate 和 write 方法分别生成和写入产物
          const {
            output
          } = await bundle.generate(outputOptions);
          await bundle.write(outputOptions);
        }
      } catch (error) {
        buildFailed = true;
        console.error(error);
      }
      if (bundle) {
        // 最后调用 bundle.close 方法结束打包
        await bundle.close();
      }
      process.exit(buildFailed ? 1 : 0);
    }
    build();
    
  • 主要的执行步骤如下:

    • 通过 rollup.rollup 方法,传入 inputOptions ,生成 bundle 对象
    • 调用 bundle 对象的 generate 和 write 方法,传入 outputOptions ,分别完成
      产物和生成和磁盘写入
    • 调用 bundle 对象的 close 方法来结束打包
  • 接着你可以执行 node build.js ,这样,我们就可以完成了以编程的方式来调用 Rollup
    打包的过程。除了通过 rollup.rollup 完成一次性打包,我们也可以通过 rollup.watch 来完成 watch
    模式下的打包,即每次源文件变动后自动进行重新打包。你可以新建 watch.js 文件,内容入下:

    // watch.js
    const rollup = require("rollup");
    const watcher = rollup.watch({
      // 和 rollup 配置文件中的属性基本一致,只不过多了`watch`配置
      input: "./src/index.js",
      output: [{
          dir: "dist/es",
          format: "esm",
        },
        {
          dir: "dist/cjs",
          format: "cjs",
        },
      ],
      watch: {
        exclude: ["node_modules/**"],
        include: ["src/**"],
      },
    });
    // 监听 watch 各种事件
    watcher.on("restart", () => {
      console.log("重新构建...");
    });
    watcher.on("change", (id) => {
      console.log("发生变动的模块id: ", id);
    });
    watcher.on("event", (e) => {
      if (e.code === "BUNDLE_END") {
        console.log("打包信息:", e);
      }
    });
    
  • 现在你可以通过执行 node watch.js 开启 Rollup 的 watch 打包模式,当你改动一个文件
    后可以看到如下的日志,说明 Rollup 自动进行了重新打包,并触发相应的事件回调函数:

    发生生变动的模块id: /xxx/src/index.js
    重新构建...
    打包信息: 
    
    {
      code: 'BUNDLE_END',
      duration: 10,
      input: './src/index.js',
      output: [
        // 输出产物路径
      ],
      result: {
        cache: {
          /* 产物具体信息 */ },
        close: [AsyncFunction: close],
        closed: false,
        generate: [AsyncFunction: generate],
        watchFiles: [
          // 监听文件列表
        ],
        write: [AsyncFunction: write]
      }
    }
    
  • 基于如上的两个 JavaScript API 我们可以很方便地在代码中调用 Rollup 的打包流程,相
    比于配置文件有了更多的操作空间,你可以在代码中通过这些 API 对 Rollup 打包过程进
    行定制,甚至是二次开发

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1871570.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

kubekey 安装高可用 kubernetes 集群

1. 准备环境 1.1 机器准备 4 台机器,操作系统:Ubuntu 24.04/RHEL8/CentOS9 10.111.3.53 master1 10.111.3.54 master2 10.111.3.55 master3 10.111.3.57 node41.2 安装依赖和配置 所有节点都需要执行: Ubuntu: apt-get install -y soca…

JeeSite中的数据库表动态建模与管理模块(DBM)

一、引言 在现代软件开发中,数据库作为系统数据存储和管理的核心,其设计和维护的灵活性、可扩展性对于系统的长期稳定运行至关重要。JeeSite作为一款流行的企业级快速开发平台,其数据库表动态管理模块(DBM)提供了强大…

UWB:DS-TWR( Double-sided two-way ranging)双边测距公式推导:为啥是乘法?

UWB DS-TWR( Double-sided two-way ranging)双边测距为啥是乘法?? 公式: 我们先看单边 Single-Sided Two-Way Ranging (SS-TWR) 单边很好理解。 symmetric double-sided TWR (SDS-TWR)对称的双边测距 再看双边 Trou…

相机系列——从相机畸变到托勒密地图

by 木一 标签:#相机畸变 #畸变纠正 #鱼眼相机 #折射定律 #托勒密地图 引言 前文[1][2]我们介绍了针孔相机模型,以及针孔相机模型的相机标定过程,但针孔相机模型是对相机成像最简单的描述,实际的相机成像过程要远复杂很多。 首先…

C++ | Leetcode C++题解之第201题数字范围按位与

题目&#xff1a; 题解&#xff1a; class Solution { public:int rangeBitwiseAnd(int m, int n) {while (m < n) {// 抹去最右边的 1n n & (n - 1);}return n;} };

Flutter循序渐进==>Dart之类型、控制流和循环

导言 磨刀不误砍柴工&#xff0c;想搞好Flutter&#xff0c;先学好Flutter&#xff0c;还是本着我学Python的方法&#xff0c;先从数据类型、控制流和循环开始&#xff0c;这是每一种编程语言必用的。编程语言是相通的&#xff0c;基本精通一种后&#xff0c;学其它的就变得很…

macos Automator自动操作 app, 创建自定义 应用程序 app 的方法

mac内置的这个 自动操作 automator 应用程序&#xff0c;可以帮助我们做很多的重复的工作&#xff0c;可以创建工作流&#xff0c; 可以录制并回放操作&#xff0c; 还可以帮助我们创建自定的应用程序&#xff0c;下面我们就以创建一个自定义启动参数的chrome.app为例&#xff…

Unity解决报错:Execution failed for task ‘:unityLibrary:BuildIl2CppTask‘

目录 编辑器版本2020.3.33f1 及 2021.3.15f1 直接导出apk或aar报错(虽然会自动生成temp的AS工程&#xff0c;经过打开验证 也是无解的)&#xff1b; 唯一解决办法&#xff1a;Unity导出As工程没问题&#xff1b; 编辑器版本2020.3.33f1 及 2021.3.15f1 直接导出apk或aar报…

黑马点评项目总结1-使用Session发送验证码和登录login和 使用Redis存储验证码和Redis的token登录

黑马先是总结了从session实现登录&#xff0c;然后是因为如果使用了集群方式的服务器的话&#xff0c;存在集群共享session互相拷贝效率低下的问题&#xff0c;接着引出了速度更快的内存型的kv数据库Redis&#xff0c; 使用Session发送验证码和登录login 举个例子&#xff1a…

深度神经网络——决策树的实现与剪枝

概述 决策树 是一种有用的机器学习算法&#xff0c;用于回归和分类任务。 “决策树”这个名字来源于这样一个事实&#xff1a;算法不断地将数据集划分为越来越小的部分&#xff0c;直到数据被划分为单个实例&#xff0c;然后对实例进行分类。如果您要可视化算法的结果&#xf…

SQL注入和防御方法

SQL注入是一种攻击手段&#xff0c;通过在SQL查询中插入恶意SQL代码片段&#xff0c;欺骗数据库服务器执行非授权的数据库操作。这种攻击可能导致数据泄露、篡改或丢失。为了防范SQL注入&#xff0c;可以采取以下几种策略&#xff1a; 1.使用预编译语句&#xff08;Prepared St…

OBD诊断(ISO15031) 01服务

文章目录 功能简介PID 的功能请求和响应1、read-supported PIDs1.1、请求1.2、肯定响应 2、read PID value1.1、请求1.2、肯定响应 3、同时请求多个PID3、同时读取多个PID数据 Parameter definition报文示例1、单个PID请求和读取2、多个PID请求和读取 功能简介 01服务&#xf…

Linux双网卡默认路由的metric设置不正确,导致SSH连接失败问题定位

测试环境 VMware虚拟机 RockyLinux 9 x86_64 双网卡&#xff1a;eth0(访问外网): 10.206.216.92/24; eth1(访问内网) 192.168.1.4/24 问题描述 虚拟机重启后&#xff0c;SSH连接失败&#xff0c;提示"Connection time out"&#xff0c;重启之前SSH连接还是正常的…

2.Android逆向协议-了解常用的逆向工具

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 内容参考于&#xff1a;微尘网校 上一个内容&#xff1a;1.Android逆向协议-环境搭建 常用的工具&#xff1a;AndroidKiller、jadx、JEB、IDA AndroidKiller…

华为云安全防护,九河云综合分解优劣势分析

随着全球化的发展&#xff0c;越来越多的企业开始寻求在国际市场上扩展业务&#xff0c;这一趋势被称为企业出海。然而&#xff0c;企业在海外扩张面临诸多隐患与安全挑战&#xff0c;其中因为地域的不同&#xff0c;在安全性方面与国内相比会变得薄弱&#xff0c;从而导致被黑…

Redis集群(Clustering in Redis)工作机制详解

Redis集群工作机制详解 Redis 集群是用于提高 Redis 可扩展性和高可用性的解决方案。 维基百科&#xff1a;Scalability is the property of a system to handle a growing amount of work by adding resources to the system. 可扩展性是系统的一种允许通过增加系统资源来处…

《征服数据结构》字典树(Trie树)

摘要&#xff1a; 1&#xff0c;字典树的介绍 2&#xff0c;字典树的插入 3&#xff0c;字典树的查询 4&#xff0c;字典树排序 5&#xff0c;字典树的删除 6&#xff0c;字典树的用途 1&#xff0c;字典树的介绍 字典树又称 Trie 树 &#xff0c;单词查找树&#xff0c;前缀树…

花卉寄售系统

摘 要 随着互联网的快速发展和普及&#xff0c;电子商务已经成为人们日常生活中不可或缺的一部分。在电子商务领域&#xff0c;花卉行业也逐渐崭露头角&#xff0c;成为一个具有巨大潜力的市场。传统的花卉销售模式通常是通过实体店面进行销售&#xff0c;这种模式存在着许多问…

Python | Leetcode Python题解之第202题快乐数

题目&#xff1a; 题解&#xff1a; def isHappy(self, n: int) -> bool:cycle_members {4, 16, 37, 58, 89, 145, 42, 20}def get_next(number):total_sum 0while number > 0:number, digit divmod(number, 10)total_sum digit ** 2return total_sumwhile n ! 1 an…

RISC-V知识总结 —— 向量(扩展)指令集

资源1:晏明 - RISC-V向量扩展指令架构及LLVM自动向量化支持 - 202112118 - 第13届开源开发工具大会&#xff08;OSDTConf2021&#xff09;_哔哩哔哩_bilibili资源2:张先轶 - 基于RISC-V向量指令集优化基础计算软件生态【第12届开源开发工具大会&#xff08;OSDT2020&#xff09…