其他打包工具

414 阅读11分钟

一、Rollup 打包工具

1.1、Rollup 概述

Rollup 是一款 ES Modules 的打包器 可以将项目中一些散落的细小模块打包为整块的代码 -> 从而使划分的模块可以更好的运行在流量器环境或者 node 环境
=> 从作用上来看: Rollup 和 webpack 非常相似 相比较与 Webpack -> Rollup 更为小巧 因为 Webpack 在配合一些插件的情况下 几乎可以完成开发过程中前端工程化的绝大部分工作 -> 而 Rollup 仅仅是一款 ESM 打包器 并没有任何其他额外的功能 -> Ex: Webpack 中有对开发者十分友好的 HMR(模块热替换) 在Rollup中就没法完全支持
=> Rollup 诞生的目的并不是要与 Webpack 之类的工具全面竞争 他的初衷只是希望可以提供一个高效的 ESM 打包器 充分利用 ESM 的各项特性构建出结构比较扁平 性能比较出众的类库 至于它其他的特点和优势 需要上手后才能了解;

1.2、Rollup 快速上手

项目目录结构:

//messages.js
export default {
  hi: 'Hey Andy, I am Lcy~'
}
//logger.js
export const log = msg => {
  console.log('------ INFO ------')
  console.log(msg)
  console.log('------------------')
}
export const error = msg => {
  console.log('------ ERROR ------')
  console.log(msg)
  console.log('-------------------')
}
//index.js
//导入模块成员
import { log } from './logger'
import message from './message'
//使用模块成员
const msg = message.hi
log(msg)

通过命令行安装 Rollup 模块

cnpm i rollup -D

安装完成后 Rollup 模块会在 node_modules/.bin 提供 CLI 程序 -> 开发者可以使用该 CLI 程序进行打包

通过命令运行 Rollup 打包项目

yarn rollup 

在不传递任何参数的情况下 控制台会打印出 Rollup 的帮助信息 -> 通过帮助信息可以知道 Rollup 的正确用法 -> Usage: rollup [options] -> 应该通过参数指定打包入口文件

yarn rollup ./src/index.js

此时控制台输出错误:

=> 意思是: 应该指定代码打包输出的格式 -> 输出格式: 希望把 ESM 的代码转换过后以什么样的格式输出 命令行参数以 --format 指定输出格式 -> 这里以最符合浏览器端的格式: iife(自调用函数格式)

yarn rollup ./src/index.js --format iife

此时打包结果被输出在控制台面板 -> 还可以通过 --file 参数指定打包输出文件路径 -> 这样的话 打包结果就会输出到文件中

yarn rollup ./src/index.js --format iife --file dist/bundle.js

打包完成后: 文件输出到 dist/bundle.js 中

=> 查看打包结果: 打包结果很简洁 基本上和手写代码一样 -> 相比较于 Webpack 打包结果中大量的引导代码以及模块函数 这里的输出结果几乎没有多余的代码 就是将打包过程中的各个模块按照模块的依赖顺序先后拼接到一起 -> 在打包结果中 只会保留已引用的代码 未引用代码都没有输出 -> 这是因为 Rollup 会自动开启 Tree-shaking 功能优化输出结果 -> Tree-Shaking 这个概念最早是在 Rollup 工具中提出的;

1.3、Rollup 配置文件

Rollup 同样支持以配置文件的方式配置打包过程中的各种参数

在根目录下新建rollup.config.js

该文件同样运行在 node 环境中 不过 Rollup 自身会额外处理该配置文件 -> 这里可以直接使用 ESM

//rollup.config.js//配置文件中需要导出配置对象export default {  input: './src/index.js', //指定打包入口文件  output: {    //指定输出的相关配置: 要求是一个对象    file: './dist/bundle.js', //指定输出的文件名    format: 'iife' //指定输出文件的格式  }}

运行 Rollup 打包项目

这里需要注意的是: 需要通过 --config 参数 表明使用项目中跟的配置文件 默认是不会读取配置文件的 必须使用 --config 参数

=> 也可以通过 --config 来指定不同配置的名称: Ex: yarn rollup --config rollup.production.js -> 这样就可以针对开发和生产环境使用不同的配置文件;

yarn rollup --config

打包结果就输出到了 dist/bundle.js

1.4、Rollup 使用插件

Rollup 自身的功能就只是 ESM 模块的合并打包 如果项目有更高级的需求 Ex: 想要加载其他类型的资源文件 或者是 要在代码中导入 CommonJs 模块 又或是 想要 Rollup 帮我们编译 ECMAScript 新特性 -> 这些额外的需求 Rollup 同样支持使用插件的方式扩展实现 而且插件是 Rollup 唯一的扩展方式 不像 Webpack 中划分了 Loader Plugin 和 Minimizer 这三种扩展方式;

=> 这里先来尝试可以在代码中带入 JSON 文件的插件 通过这个过程了解 如何在 Rollup 中使用插件 -> 这里使用插件的名称 rollup-plugin-json

安装 rollup-plugin-json

cnpm i rollup-plugin-json -D

编辑 rollup.config.js

由于 rollup.config.js 可以使用 ESM 所有这里直接使用 import 的方式导入插件模块

import rollupPluginJson from 'rollup-plugon-json'//该插件模块默认导出的是一个插件函数 我们可以将插件函数调用的结果添加到 plugins 数组中export default {  ......,  plugins: [  	rollupPluginJson()  ]}

配置完成后 就可以在代码中通过 import 的方式导入 JSON 文件了

编辑 index.js 尝试通过 import 导入 package.json 文件

//index.js//导入模块成员......,// JSON文件的每一个属性就是一个单独的导出成员import { name, version } from '../package.json'//使用模块成员......,log(name)log(version)

运行 Rollup 打包

yarn rollup --config

查看输出结果: bundle.js 中 package.json 中的 name 和 version 被打包了 而其他没有引用的属性没有被打包

=> 这就是在 Rollup 中如何使用插件

1.5、Rollup 加载 npm 模块

Rollup 默认只能按照文件路径的方式加载本地模块 对于 node_modules 目录下的第三方模块 并不能像 Webpack 一样通过模块的名称导入对应的模块 为了解决这个问题 -> Rollup 官方给出了一个 rollup-plugin-node-resolve 插件 -> 通过使用该插件 就可以直接在代码中使用模块名称导入对应的模块

通过命令行安装 rollup-plugin-node-resolve

cnpm i rollup-plugin-node-resolve -D

编辑配置文件 rollup.config.js

......,import resolve from 'rollup-plugin-node-resolve'export default {  ......,  plugins: [  	......,  	resolve()  ]}

配置完成后在代码中就可以直接使用模块名称导入对应的模块了

编辑 index.js

//导入模块成员......import _ form 'lodash-es'//使用模块成员......log(_.camelCase('hello world'))

通过命令行 Rollup 打包(yarn rollup --config) -> 此时 Lodash-es 所对应的代码就被可以打包到 bundle.js 中 -> 这里使用 lodash ESM 版本 而不是使用 lodash 普通版本的原因是因为: Rollup 默认只能够处理 ESM 模块 如果要使用普通版本 需要做额外的处理

1.6、Rollup 加载 COmmonJs 模块

Rollup 设计的就是只处理 ESM 模块的打包 如果在代码中导入 CommonJs 模块 默认是不被支持的 -> 但是目前那还是会有大量的 npm 模块使用 CommonJs 方式导出成员 所以 为了兼容这些模块 官方又给出了一个插件 rollup-plugin-commonjs

通过命令行安装 rollup-plugin-commonjs

cnpm i rollup-plugin-commonjs -D

编辑配置文件 rollup.config.js

......import commonJs from 'rollup-plugin-commonjs'export default {  ......,  plugins: [  	......,  	commonJs()  ]}

配置完成后就可以在代码中直接导入 commonJs 模块

在 src 目录下 添加 commonJs 的示例文件: cjs-module.js

// cjs-module.jsmodule.exports = {  foo: 'bar'}

编辑 index.js 使用 cjs-module.js 模块

// 导入模块成员......import cJs from './cjs-module.js'// 使用模块成员......log(cJs)

通过命令运行 rollup 打包

yarn rollup --config

此时 commonJs 模块也就可以被打包进 bundle.js -> 查看 bundle.js -> cjs-module.js 默认导出就以一个对象的形式出现在 bundle.js 中

1.7、Rollup 代码拆分

在 Rollup 最新的版本中已经支持代码拆分 使用 ESM 标准的动态导入的方式实现模块的按需加载 Rollup内部也会自动处理代码的拆分(也就是我们说的分包)

编辑 index.js

// 注释静态导入的代码// 导入模块成员// import { log } from './logger'// import message from './messages'// 使用模块成员// const msg = message.hi// log(msg)// 使用动态导入的方式导入代码import('./logger').then(({ log }) => {  log('code splitting ~')})

尝试运行 Rollup 打包

控制台输出错误:

=> 错误信息: Rollup 使用代码拆分方式打包要求输出格式不能为 IIFE -> 原因: 因为自执行还是回将所有代码放到同一个函数中 并没有引导代码 -> 所以没有办法实现代码拆分

=> 如果要使用代码拆分方式 就不许使用 AMD 或着 CommonJs 这类的其他标准 -> 在浏览器环境中只能使用 AMD 的标准 -> 所以这里需要使用 AMD 的格式输出打包结果

通过 --format 命令行参数修改输出格式 执行 Rollup 打包

yarn Rollup --config --format amd

控制台输出错误:

=> 错误信息: 使用代码拆分方式打包时不能使用 output.file 的方式 -> 因为需要输出多个文件 file 这种方式是指定单个文件输出的输出文件名 -> 如果需要输出多个参数 需要使用 dir 的参数

修改配置文件 rollup.config.js

/* export default {	input: './src/index.js',	output: {		file: 'dist/bundle.js',		format: 'iife'	}} */export default {  input: './src/index.js',  output: {    dir: 'dist',    format: 'amd'  }}

再次通过命令行运行 Rollup 打包(yarn rollup --config)

打包完成后 -> 查看 dist 目录 -> 通过刚刚的动态导入生成一个入口的 bundle 以及 动态导入的 bundle -> 他们都是采用 AMD 标准输出的
=> 这就是在 Rollup 中如何实现代码拆分 -> 使用动态导入实现的;

1.8、Rollup 多入口打包

Rollup 同样支持多入口打包 而且对于不同入口中公共部分也会自动提取到单个文件中作为独立的 bundle

我们具体来看是怎么配置的? -> 项目目录:

//index.jsimport fetchApi from './fetch'import { log } from './logger'ferchApi('/posts').then(data => {  data.forEach(item => {    log(item)  })})
// album.jsimport fetchApi from './fetch'import { log } from './logger'fetchApi('/photos?albumId=1').then(data => {  data.forEach(item => {    log(item)  })})
// fetch.jsexport default endpoint => {  return fetch(`https://jsonplaceholder.typicode.com${endpoint}`)  .then(response => response.json())}
// logger.jsexport const log = msg => {  console.log('------ INFO ------')  console.log(msg)  console.log('------------------')}export const error = msg => {  console.error('------ ERROR ------')  console.error(msg)  console.error('-------------------')}

配置 rollup.config.js

Rollup 配置多入口打包方式非常简单 -> 将 input属性的值修改为一个数组就可以了 也可以使用对象键值对的方式配置 -> 这里要注意 多入口打包会提取公共资源 也就是内部会使用代码拆分 -> 输出格式不能使用 IIFE -> 将输出格式修改为 AMD

// rollup.config.jsexport default {  // input: ['src/index.js', 'src/album.js']  input: {    foo: 'src/index.js',    bar: 'src/album.js'  },  output: {    dir: 'dist',    format: 'amd'  }}

通过命令行运行 Rollup 打包

yarn rollup --config

打包完成后: dist 目录下就会多出三个 JS 文件 -> 两个不能入口的打包结果 以及 公共资源提取出来的公共模块 JS

=> 另外需要注意一点: 对于 AMD 这种格式的输出文件 不能直接引用到页面上 而必须需要通过实现 AMD 标准的库去加载

在 dist 目录下手动创建 index.html 文件 并编辑该 html 文件

<!DOCTYPE html><html lang="zh-CN">  <head>    <meta charset="UTF_8">    <meta name="Viewport" content="width=device-width, initial-scale=1.0">    <meta http-equiv="X-UA-Compatible" content="ie=edge">    <title>Document</title>  </head>  <body>    <!-- 尝试在文件中使用打包后的 bundle.js -->    <!-- <script src="foo.js"></script> -->    <!-- 这里采用的 requireJS 这个库加载这里以 AMD 标准输出的 bundle 可以通过 data-main 参数指定 require 加载的模块的入口文件路径  -->    <script src="https://unpkg.com/requirejs@2.3.6/require.js" data-main="foo.js"></script>  </body></html>

通过 serve 将 dist 目录运行起来

运行起来后: 打开浏览器 -> 查看开发者人员工具 -> Network 和 Console 可以看到 boo.js 正常加载进来了 也正常的工作了

1.9、Rollup 选用原则

通过以上的探索和尝试 发现 Rollup 确实有它的优势:

  • 输出结果更加扁平 -> 执行效率就会更高
  • 自动移除未引用的代码
  • 打包结果基本上和手写的是一致的 -> 依然完全可读

但是他的确定也非常明显:

  • 加载非 ESM 的第三方模块比较复杂 -> 需要配置一大堆插件
  • 模块最终都被打包到一个函数中, 无法实现HMR
  • 浏览器环境中, 代码拆分功能依赖 AMD 库 -> 必选使用像 require 这样的库 -> 因为它的代码拆分必选输出 AMD 这样的格式

综合以上的特点: 如果我们正在开发应用程序 肯定要面临大量引入第三方模块这样的需求 同时也需要像 HMR 这样的功能提升开发效率 -> 而且应用的体积大了以后 还涉及到要分包 -> 这些需求在满足上都有一些欠缺;
=> 如果我们正在开发一个框架或者类库 Rollup 的这些优点就非常有必要 缺点也可以忽略

=> 所以 大多数知名框架/库都在使用 Rollup 作为模块打包器 并非是 Webpack

=> 但是在目前为止 开源社区中大多数人还是希望这两个工具可以共同存在和共同发展 并且可以相互支持和借鉴 -> 原因也很简单: 就算想让更专业的工具去做更专业的事;

=> 总结: Webpack 大而全 Rollup 小而美 -> 在对这两者的选择上: 应用开发使用 Webpack 库/框架开发使用 Rollup -> 这也不是一个绝对标准 只是经验而谈 -> 因为 Rollup 同样也可以构建绝大多数的应用程序 而Webpack 也同样可以构建类库/框架 只不过相对来讲 术业有专攻的感觉 -> 随着这几年 Webpack 的发展 Rollup中的很多优势几乎已经被抹平了 Ex: 扁平化输出 在 Webpack 中就可以使用 concatenateModules 也可以实现类似的输出;

二、parcel 打包工具

parcel 是一款完全零配置的打包工具 提供了近乎傻瓜式的打包体验 -> 只需要了解它所提供的几个简单的命令 就可以直接使用它构建前端应用程序

创建一个新的空项目 -> 通过命令行初始化项目的 package.json

yarn init --yes / cnpm init --yes

通过命令行安装 parcel 所对应的模块

这里注意: parcel 的 npm 模块叫做 parcel-bundler

cnpm i parcel-bundler -D

安装完成过后 parcel 模块会在 node_modules/.bin 目录下提供 parcel.cmd 的 CLI 程序 后续可以使用该 CLI 执行对应用的打包

在项目的根目录下创建 src/index.html

index.html 待会作为 parcel 打包的入口文件 -> 虽然 parcel 和 Webpack 一样都支持以任意文件作为打包入口 不过 parcel 官方建议使用 html 文件作为打包入口 -> 官方给出来的理由是: 因为 html 文件是应用运行在浏览器端的入口

=> 在 html 入口文件中可以像平时一样正常编写 也可以在这里引用一些资源文件 -> 这里的引用资源最终会被 parcel 打包到一起 最终输出到输出目录

编辑 index.html

<!DOCTYPE html><html lang="zh-CN">  <head>    <meta charset="UTF_8">    <meta name="Viewport" content="width=device-width, initial-scale=1.0">    <meta http-equiv="X-UA-Compatible" content="ie=edge">    <title>Parcel Tutorials</title>  </head>  <body>    <script src="main.js"></script>  </body></html>

在 src 目录下新建 main.js 及 foo.js

// foo.jsexport default {  bar: () => {    console.log('hello parcel~')  }}
//mian.jsimport foo from './foo'foo.bar()

Parcel 同样支持 ESM 模块的打包

通过命令行 执行 parcel 打包

Parcel 打包时需要传入打包入口文件的路径

yarn parcel src/index.html

执行命令 -> Parcel 会根据传入的参数 找到 index.html 文件 -> 然后根据 文件中的引入的 main.js 找到 main.js -> 然后在顺着 import 语句找到 foo 模块 从而完成整体项目的打包

=> 执行命令后: Parcel 不仅仅打包了应用 同时还开启了开发服务器 -> 该开发服务器跟 Webpack 中的 devServer 一样 -> 根据终端中提供的地址 在浏览器中打开 -> 打开开发者工具 此时就可以使用自动刷新功能

-> 修改源代码 -> Parcel 重新打包并刷新浏览器 -> 如果需要模块热替换的体验 Parcel 也同样支持

编辑 main.js 实现 HMR

......if (module.hot) {  // 环境中存在 module.hot 对象 -> Parcel 就支持 HMR 的 API: 就可以通过 module.hot.accept() 方法处理 HMR 逻辑    module.hot.accept(() => {      //这里的 accept 跟 Webpack 所提供的 API 不一样: Webpack 所提供的 API 支持接受两个参数 用来处理指定模块更新后的逻辑处理 -> Parcel 的 accept 只支持接受一个参数(回调函数): 当当前模块更新或者是当前模块所依赖的模块更新后的处理逻辑      console.log('HMR')    })}

此时修改 foo.js -> 代码会自动被热替换 然后执行

=> 处了热替换 Parcel 还支持一个非常友好的功能: 自动安装依赖

编辑 main.js

......import $ from 'jquery'......

由于有自动安装依赖功能 在开发阶段可以直接导入某些模块 -> 保存文件后 Parcel 会自动安装 刚刚导入的新模块 -> 极大程度上避免了手动操作

=> 除此之外 Parcel 同样支持加载其他类型的资源模块 -> 而且相比较于其他的模块打包工具 在 Parcel 中加载任意模块 同样是零配置的

在 src 目录下添加 style.css 文件 并编辑

body{  background-color: #2F4056;}

回到 main.js 中 通过 import 引入 style.css

......import './style.css'......

保存文件之后 该样式就回自动生效

=> 还可以添加图片到项目中

在 src 目录下添加 bodybg.png -> 编辑 main.js

......imprt bodybg from './bodybg.png'......$(document.body).append(`<img src="${bodybg}" />`)......

图片自动显示到浏览器中 -> 整个过程并没有停下来做其他事情

=> 总之: Parcel 希望给开发者的体验就是: 你想做什么就只管去做 额外的事情就由工具去负责处理

=> 另外 Parcel 同样支持动态导入 代码如果使用了动态导入 Parcel 打包也会自动拆分代码

编辑 main.js 实现动态导入

// import $ form 'jquery'......// 使用动态导入的方式 导入 jqueryimport('jquery').then($ => {  $(document.body).append(`<img src="${bodybg}" />`)})// $(document.body).append(`<img src="${bodybg}" />`)......

保存后回到浏览器 -> 在开发人员工具中的 Network 中就会找到 刚刚拆分出来的 jqury 所对应的 bundle.js 的请求

=> 以上就是 Parcel 中最常用的特性 在使用上Parcel 几乎没有任何难度 从头到尾只是长执行了 parcel 打包命令 所有的其他事情都是 Parcel 内部自动完成的

结束 Parcel 命令 -> 再来体验一下 Parcel 如何以生产模式打包

Parcel 以生产模式打包: 需要执行 Parcel CLI 命令所提供的 build 命令 + 打包入口路径

yarn parcel build src/index.html

对于相同体量的项目打包 Parcel 的构建速度会比 Webpack 快很多 -> 因为在 Parcel 的内部使用的是 多进程同时工作 充分发挥了多核 CPU 的性能 -> Webpack 也可以使用 happypack 的一个插件来实现多线程打包

=> 查看打包后的代码: 输出的所有文件都会被压缩 而且样式代码也被单独提取到单个文件中

=> 整体感觉 Parcel 使用起来非常简单 舒服

=> Parcel 首个版本发布于 2017 年 出现的原因: 但是 Webpack 在使用上过于繁琐 而且官方的文档也不是很清晰明了 -> 所以 Parcel 一经推出 就被推上了风口浪尖 其核心特性就是: 真正意义上做到了 零配置 对项目没有任何的侵入 -> 而且整个过程有自动安装依赖体验 让我们的开发过程可以更专注于编码 -> 还有一点就是: Parcel 的构建速度更快

=> Parcel 的有点固然很明显 但是目前实际上的使用情况 绝大多数的项目打包 还是会使用 Webpack -> 原因:

  • Webpack 有更好的生态 -> 扩展会更加丰富 -> 出现问题很容易解决
  • 随着这两年的发展 Webpack 越来越好用 -> 开发者随着使用变得越来越熟悉