题注:Web Bundler CheatSheet 属于 Awesome-CheatSheet 系列,盘点数个经常使用的开发打包工具清单。欢迎加入阿里南京前端团队,欢迎关注阿里南京技术专刊了解更多讯息。css
工欲善其事,必先利其器,当咱们准备开始某个 Web 相关的项目时,合适的脚手架会让咱们事半功倍。在 2016-个人前端之路:工具化与工程化一文中,咱们讨论了工具化与工程化相关的内容,其中重要的章节就是关于所谓的打包工具。Grunt、Glup 属于 Task Runner,即任务执行器; 实际上,npm package.json 中定义的脚本也能够看作 Task Runner,而 Rollup,Parcel 以及 Webpack 则是属于 Bundler,即打包工具。html
尺有所短,寸有所长,不一样的构建工具备其不一样的适用场景。Webpack 是很是优秀的构建与打包工具,可是其提供了基础且复杂的功能支持,使得并不适用于所有的场景。Parcel 这样的零配置打包工具适合于应用型的原型项目构建,而 Rollup 或者 Microbundle 适合于库的打包,Backpack 则可以帮咱们快速构建 Node.js 项目。笔者在本文中列举讨论的仅是平常工做中会使用的工具,更多的 Browserify、Fusebox 等等构建工具查看 Web 构建与打包工具资料索引或者现代 Web 开发实战/进阶篇。前端
Parcel 是著名的零配置的应用打包工具,在 TensorflowJS 或者 gh-craft 等算法实验/游戏场景构建中,都可以快速地搭建应用。vue
# 安装 Parcel $ npm install -g parcel-bundler # 启动开发服务器 $ parcel index.html # 执行线上编译 $ parcel build index.js # 指定编译路径 $ parcel build index.js -d build/output
Parcel 会为咱们自动地下载安装依赖,而且内置了 ES、SCSS 等常见的处理器。在 fe-boilerplate 中提供了 React, React & TypeScript, Vue.js 等 Parcel 常见的示例,这里以 React 为例,首先定义组件与渲染:node
// index.js import React from 'react'; import ReactDOM from 'react-dom'; import logo from '../public/logo.svg'; import './index.css'; const App = () => ( <div className="App"> <img className="App-Logo" src={logo} alt="React Logo" /> <h1 className="App-Title">Hello Parcel x React</h1> </div> ); ReactDOM.render(<App />, document.getElementById('root')); // Hot Module Replacement if (module.hot) { module.hot.accept(); }
而后定义入口的 index.html 文件:react
<html lang="en"> <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 React Example</title> </head> <body> <div id="root"></div> <script src="./index.js"></script> </body> </html>
而后使用 parcel index.html
运行开发服务器便可。Parcel 中一样也是支持异步加载的,假设咱们将部分代码定义在 someModule.js 文件中,而后在用户真实须要时再进行加载:webpack
// someModule.js console.log('someModule.js loaded'); module.exports = { render: function(element) { element.innerHTML = 'You clicked a button'; } };
在入口文件中使用 import 进行异步加载:git
console.log('index.js loaded'); window.onload = function() { document.querySelector('#bt').addEventListener('click', function(evt) { console.log('Button Clicked'); import('./someModule').then(function(page) { page.render(document.querySelector('.holder')); }); }); };
最后值得一提的是,Parcel 内建支持 WebAssembly 与 Rust,经过简单的 import 导入,便可以使用 WASM 模块:github
// synchronous import import {add} from './add.wasm'; console.log(add(2, 3)); // asynchronous import const {add} = await import('./add.wasm'); console.log(add(2, 3)); // synchronous import import {add} from './add.rs'; console.log(add(2, 3)); // asynchronous import const {add} = await import('./add.rs'); console.log(add(2, 3));
这里 add.rs 是使用 Rust 编写的简单加法计算函数:web
#[no_mangle] pub fn add(a: i32, b: i32) -> i32 { return a + b }
Rollup 是较为为纯粹的模块打包工具,其相较于 Parcel 与 Webpack 等,更适合于构建 Library,譬如 React、Vue.js、Angular、D三、Moment、Redux 等一系列优秀的库都是采用 Rollup 进行构建。。Rollup 可以将按照 ESM(ES2015 Module)规范编写的源码构建输出为 IIFE、AMD、CommonJS、UMD、ESM 等多种格式,而且其较早地支持 Tree Shaking,Scope Hoisting 等优化特性,保证模块的简洁与高效。这里咱们使用的 Rollup 示例配置项目存放在了 fe-boilerplate/rollup。最简单的 rollup.config.js 文件配置以下:
export default { // 指定模块入口 entry: 'src/scripts/main.js', // 指定包体文件名 dest: 'build/js/main.min.js', // 指定文件格式 format: 'iife', // 指定 SourceMap 格式 sourceMap: 'inline' };
若是咱们只是对简单的 sayHello
函数进行打包,那么输出的文件中也只是会简单地链接与调用,而且清除未真实使用的模块:
(function() { 'use strict'; ... function sayHelloTo(name) { ... } ... const result1 = sayHelloTo('Jason'); ... })(); //# sourceMappingURL=data:application/json;charset=utf-8;base64,...
Rollup 一样具备丰富的插件系统,在 fe-boilerplate/rollup 中咱们也引入了常见的别名、ESLint、环境变量定义、包体压缩与分析等插件。这里咱们以最经常使用的 Babel 与 TypeScript 为例,若是咱们须要在项目中引入 Babel,则一样在根目录配置 .babelrc
文件,而后引入 rollup-plugin-babel 插件便可:
import { rollup } from 'rollup'; import babel from 'rollup-plugin-babel'; rollup({ entry: 'main.js', plugins: [ babel({ exclude: 'node_modules/**' }) ] }).then(...)
对于 TypeScript 则是引入 rollup-plugin-typescript 插件:
import typescript from 'rollup-plugin-typescript'; export default { entry: './main.ts', plugins: [typescript()] };
Microbundle 则是 Developit 基于 Rollup 封装的零配置的轻量级打包工具,其目前已经内建支持 TypeScript 与 Flow,不须要额外的配置;笔者在 js-swissgear/x-fetch 项目的打包中也使用了该工具。
{ "scripts": { "build": "microbundle", "dev": "microbundle watch" } }
require('MyModule')
语法导入import MyModule from 'my-module'
语法导入做为著名的打包工具,Webpack 容许咱们指定项目的入口地址,而后自动将用到的资源,经由 Loader 与 Plugin 的转换,打包到包体文件中。Webpack 相关的项目模板能够参考:fe-boilerplate/react-webpack, fe-boilerplate/react-webpack-ts, fe-boilerplate/vue-webpack 等。
Webpack 目前也支持零配置运行
$ npm install webpack webpack-cli webpack-dev-server --save-dev
"scripts": { "start": "webpack-dev-server --mode development", "build": "webpack --mode production" },
const config = { // 定义入口 entry: { app: path.join(__dirname, 'app') }, // 定义包体文件 output: { // 输出目录 path: path.join(__dirname, 'build'), // 输出文件名 filename: '[name].js' // 使用 hash 做为文件名 // filename: "[name].[chunkhash].js", }, // 定义如何处理 module: { rules: [ { test: /\.js$/, use: 'babel-loader', exclude: /node_modules/ } ] }, // 添加额外插件操做 plugins: [new webpack.DefinePlugin()] };
Webpack 一样支持添加多个配置:
module.exports = [{ entry: './app.js', output: ..., ... }, { entry: './app.js', output: ..., ... }]
咱们代码中的 require 与 import 解析规范,则由 resolve 模块负责,其包含了扩展、别名、模块等部分:
const config = { resolve: { alias: { /*...*/ }, extensions: [ /*...*/ ], modules: [ /*...*/ ] } };
const config = { module: { rules: [ { // **Conditions** test: /\.js$/, // Match files enforce: 'pre', // "post" too // **Restrictions** include: path.join(__dirname, 'app'), exclude: path => path.match(/node_modules/), // **Actions** use: 'babel-loader' } ] } };
// Process foo.png through url-loader and other matches import 'url-loader!./foo.png'; // Override possible higher level match completely import '!!url-loader!./bar.png';
babel-loader 或者 awesome-typescript-loader 来处理 JavaScript 或者 TypeScript 文件
/******/ (function(modules) { // webpackBootstrap ... /* 0 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony default export */ __webpack_exports__["default"] = ((text = "Hello world") => { const element = document.createElement("div"); element.innerHTML = text; return element; }); /***/ }) /******/ ]);
use: ["style-loader", "css-loader"]
css-loader 会自动地解析 @import 与 url(),而 style-loader 则会将 CSS 注入到 DOM 中,而且实现 HMR 的特性,而对于 SASS、LESS 等 CSS 预处理器,也有专门的 sass-loader 或者 less-loader 来处理;在生产环境下,咱们也经常会将 CSS 抽取到独立的样式文件中,此时就可使用 mini-css-extract-plugin (MCEP) 等工具。一样,咱们可使用 url-loader/file-loader 来处理图片等资源文件,
代码分割是提高 Web 性能表现的重要分割,咱们常作的代码分割也分为公共代码提取与按需加载等方式。公共代码提取便是将第三方渲染模块或者库与应用自己的逻辑代码分割,或者将应用中多个模块间的公共代码提取出来,划分到独立的 Chunk 中,以方便客户端进行缓存等操做。
不一样于 Webpack 3 中须要依赖 CommonChunksPlugin 进行配置,Webpack 4 引入了 SplitChunksPlugin,并为咱们提供了开箱即用的代码优化特性,Webpack 会根据如下状况自动进行代码分割操做:
SplitChunksPlugin 的默认配置以下:
splitChunks: { chunks: "async", minSize: 30000, minChunks: 1, maxAsyncRequests: 5, maxInitialRequests: 3, automaticNameDelimiter: '~', name: true, cacheGroups: { vendors: { test: /[\\/]node_modules[\\/]/, priority: -10 }, default: { minChunks: 2, priority: -20, reuseExistingChunk: true } } }
值得一提的是,这里的 chunks 选项有 initial
, async
与 all
三个配置,上述配置便是分别针对初始 chunks、按需加载的 chunks 与所有的 chunks 进行优化;若是将 vendors 的 chunks 设置为 initial
,那么它将忽略经过动态导入的模块包包含的第三方库代码。而 priority 则用于指定某个自定义的 Cache Group 捕获代码的优先级,其默认值为 0。在 common-chunk-and-vendor-chunk 例子中,咱们即针对入口进行优化,提取出入口公共的 vendor 模块与业务模块:
{ splitChunks: { cacheGroups: { commons: { chunks: "initial", minChunks: 2, maxInitialRequests: 5, // The default limit is too small to showcase the effect minSize: 0 // This is example is too small to create commons chunks }, vendor: { test: /node_modules/, chunks: "initial", name: "vendor", priority: 10, enforce: true } } } }
Webpack 的 optimization 还包含了 runtimeChunk 属性,当该属性值被设置为 true 时,即会为每一个 Entry 添加仅包含运行时信息的 Chunk; 当该属性值被设置为 single 时,即为全部的 Entry 建立公用的包含运行时的 Chunk。咱们也能够在代码中使用 import 语句,动态地进行块划分,实现代码的按需加载:
// Webpack 3 以后支持显式指定 Chunk 名 import(/* webpackChunkName: "optional-name" */ './module') .then(module => { /* ... */ }) .catch(error => { /* ... */ });
webpackJsonp([0], { KMic: function(a, b, c) { ... }, co9Y: function(a, b, c) { ... }, });
若是是使用 React 进行项目开发,推荐使用 react-loadable 进行组件的按需加载,他可以优雅地处理组件加载、服务端渲染等场景。Webpack 还内建支持基于 ES6 Module 规范的 Tree Shaking 优化,即仅从导入文件中提取出所须要的代码。
更多关于 Webpack 的使用技巧能够参阅 Webpack CheatSheet 或者现代 Web 开发基础与工程实践/Webpack 章节。
Backpack 是面向 Node.js 的极简构建系统,受 create-react-app, Next.js 以及 Nodemon 的影响,可以以零配置的方式建立 Node.js 项目。Backpack 为咱们处理了文件监控、热加载、转换、打包等工做,默认支持 ECMAScript 最新的 async/await, 对象扩展、类属性等语法。咱们可使用 npm 安装依赖:
$ npm i backpack-core --save
而后在 package.json 中配置运行脚本:
{ "scripts": { "dev": "backpack", "build": "backpack build" } }
在 Backend-Boilerplate/node 中能够查看 Backpack 的典型应用,咱们也能够覆盖默认的 Webpack 配置:
// backpack.config.js module.exports = { webpack: (config, options, webpack) => { // Perform customizations to config // Important: return the modified config return config; } };
或者添加 Babel 插件:
{ "presets": ["backpack-core/babel", "stage-0"] }