1. 初版构建工具
1.1. Grunt
Grunt 是前端第一个正式的构建工具,它基于 Node.js 开发。Grunt 同样是基于插件实现功能拓展增强,但对于像Webpack上很多能力,如HMR、Scope Hoisting等都是不支持的,可以作为学习Webpack前的了解。
Grunt 更像是一种自动化的配置工具集,就如官方所说,Grunt 是 The JavaScript Task Runner,每个 Grunt 任务通常必须创建中间文件将结果传递给其他任务。
所能实现的功能包括:检查每个 JS 文件语法、合并两个 JS 文件、将合并后的 JS 文件压缩、将 SCSS 文件编译、将Less文件转换为CSS文件等等。
module.exports = function(grunt) {
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
concat: {
options: {
separator: ';',
},
dist: {
src: ['src/**/*.js'],
dest: 'dist/<%= pkg.name %>.js'
}
},
uglify: {
options: {
banner: '/*! <%= pkg.name %> <%= grunt.template.today("dd-mm-yyyy") %> */\n'
},
dist: {
files: {
'dist/<%= pkg.name %>.min.js': ['<%= concat.dist.dest %>']
}
}
},
quit: {
files: ['test/**/*.html']
},
jshint: {
files: ['Gruntfile.js', 'src/**/*.js', 'test/**/*.js'],
options: {
// options here to override JSHint defaults
globals: {
jQuery: true,
console: true,
module: true,
document: true
}
}
},
watch: {
files: ['<%= jshint.files %>'],
tasks: ['jshint', 'quit']
}
});
grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.loadNpmTasks('grunt-contrib-jshint');
grunt.loadNpmTasks('grunt-contrib-quit');
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-contrib-concat');
grunt.registerTask('test', ['jshint', 'quit']);
grunt.registerTask('default', ['jshint', 'quit', 'concat', 'uglify']);
};
1.2. Gulp
Gulp跟Grunt类似,都是基于task驱动执行的,可以完成javascript/coffee/sass/less/html/image/css 等文件的测试、检查、合并、压缩、格式化、浏览器自动刷新、部署文件生成。Gulp的优点在于Gulp更倾向于写代码的方式,但相关的插件资源不如Grunt。
同样的,现代的构建工具基本不使用Gulp了,有兴趣看官网自行学习,这里不详细介绍。
const { series, parallel } = require('gulp');
function clean(cb) {
// body omitted
cb();
}
function css(cb) {
// body omitted
cb();
}
function javascript(cb) {
// body omitted
cb();
}
exports.build = series(clean, parallel(css, javascript));
2. 基于Webpack改进的构建工具
2.1. Rollup
Rollup 和 Webpack 都通过解析 JavaScript 的依赖树,将代码打包成适合浏览器或 Node.js 执行的指定版本 JavaScript。然而,Rollup 更轻量化,生成的代码不会像 Webpack 那样包含大量的内部结构,而是尽可能保持代码的原始状态。配置方面,Rollup 相对简单,但由于缺乏 devServer 和 HMR热模块替换功能,因此通常用于 JavaScript 库的开发,而非应用开发。
Rollup 主要负责将代码转码为目标 JavaScript,并且可以根据需要生成支持 UMD、CommonJS 和 ES 模块格式的代码。Vue、React、Angular 等框架的源码打包工具中都能看到 Rollup 的应用痕迹。
2.1.1. 使用方式
1. 浏览器环境使用的应用程序
(1). 无需考虑浏览器兼容问题
开发者编写ES模块代码 => Rollup 通过入口文件递归识别 ESM 模块 => 最终生成一个或多个 bundle.js => 浏览器可以直接通过 <script type="module"> 引入并运行。
(2). 需考虑浏览器兼容问题
这种情况下会复杂一些,需要额外使用 polyfill 库,或者与 Webpack 结合使用,以确保兼容性。
2. 打包成 npm 包
(1). ESM 模块打包
开发者编写 ESM 代码 => Rollup 通过入口文件递归识别 ESM 模块 => 可以配置输出多种模块格式(ESM、CJS、UMD、AMD) => 最终打包成一个或多个 bundle.js。
如果需要生成 CJS(CommonJS)格式的模块,开发者可以使用 @rollup/plugin-commonjs 插件。
(2). Rollup 更适合打包 JS 库
由于 Rollup 生成的库支持 ESM 的 tree shaking(摇树优化),这有助于将库的体积最小化。因此,许多像 React、Vue 等库的源码都是用 Rollup 打包的。
3. Webpack 与 Rollup 打包的差异
历史背景:Webpack 诞生于 ESM 标准出台之前,当时浏览器只能通过 script 标签加载模块,且没有作用域。为了实现模块的作用域隔离,Webpack 使用了 IIFE(立即执行函数表达式),这也是为什么 Webpack 打包后的代码结构看起来比较复杂,模块之间需要通过函数来隔离作用域。
CJS 兼容性:Webpack 需要在浏览器中模拟 CommonJS(CJS)的 require 和 module.exports 方法,因此注入了大量代码来实现这一功能,这也是 Webpack 打包后代码混乱的原因之一。
浏览器不支持 CJS 的原因:CJS 是同步的,在 Node.js 环境下运行速度快,因为无需等待网络响应。 浏览器是客户端,需要等待服务器响应,因此不能同步执行,否则会影响用户体验。 Webpack 为了兼容早期发布到 npm 的 CJS 包,保留了 IIFE 结构和代码注入,导致打包后的代码结构复杂且混乱。
Rollup 的优势:Rollup 诞生于 ESM 标准出台之后,专为 ESM 设计,没有历史包袱,因此能够生成精简且没有额外注入的代码。
2.1.2. 使用总结
rollup在构建JavaScript方面比Webpack有更大的优势:
构建速度明显快于Webpack。
生成的代码量很小。
配置方式其实非常简单。
2.2. Parcel
与 Webpack 相比,Parcel 采用 assets 方式组织文件,可以直接构建任何类型的文件,而 Webpack 则必须以 JS 为入口组织其他文件,这在使用体验上是一种提升。
Parcel 的速度优势来自于多核处理和文件系统缓存技术,这使得二次构建更快。其缓存机制使用 C++ 编写,效率更高,类似于 Webpack 的 dll 插件。此外,虽然 Parcel 本身是零配置的,但针对 HTML、JS 和 CSS 文件的处理仍需配置 .posthtmlrc、.babelrc 和 .postcssrc 文件。因此,Parcel 更适合小型、简单的项目,对于定制化需求较高的项目,建议使用 Webpack,因为其社区资源更为丰富。
2.2.1. 主要特点
Parcel 的主要特点包括: