Web-Bundler-CheatSheet
题注:Web Bundler CheatSheet 属于 Awesome-CheatSheet 系列,盘点数个常用的开发打包工具清单。欢迎加入阿里南京前端团队,欢迎关注阿里南京技术专刊了解更多讯息。
Web Bundler CheatSheet | Web 构建与打包工具盘点
工欲善其事,必先利其器,当我们准备开始某个

尺有所短,寸有所长,不同的构建工具有其不同的适用场景。
NPM & Yarn
yarn config set registry https://registry.npm.taobao.org -g
yarn config set disturl https://npm.taobao.org/dist -g
yarn config set electron_mirror https://npm.taobao.org/mirrors/electron/ -g
yarn config set sass_binary_site https://npm.taobao.org/mirrors/node-sass/ -g
yarn config set phantomjs_cdnurl https://npm.taobao.org/mirrors/phantomjs/ -g
yarn config set chromedriver_cdnurl https://cdn.npm.taobao.org/dist/chromedriver -g
yarn config set operadriver_cdnurl https://cdn.npm.taobao.org/dist/operadriver -g
yarn config set fse_binary_host_mirror https://npm.taobao.org/mirrors/fsevents -g
Parcel
# 安装 Parcel
$ npm install -g parcel-bundler
# 启动开发服务器
$ parcel index.html
# 执行线上编译
$ parcel build index.js
# 指定编译路径
$ parcel build index.js -d build/output
// 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();
}
然后定义入口的
<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
运行开发服务器即可。
// someModule.js
console.log("someModule.js loaded");
module.exports = {
render: function (element) {
element.innerHTML = "You clicked a button";
},
};
在入口文件中使用
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"));
});
});
};
最后值得一提的是,
// 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));
这里
#[no_mangle]
pub fn add(a: i32, b: i32) -> i32 {
return a + b
}
Rollup + Microbundle
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,...
.babelrc
文件,然后引入
import { rollup } from 'rollup';
import babel from 'rollup-plugin-babel';
rollup({
entry: 'main.js',
plugins: [
babel({
exclude: 'node_modules/**'
})
]
}).then(...)
对于
import typescript from "rollup-plugin-typescript";
export default {
entry: "./main.ts",
plugins: [typescript()],
};
Microbundle 则是
{
"scripts": {
"build": "microbundle",
"dev": "microbundle watch"
}
}
index.js 是CommonJS 模块,是Node.js 内置的模块类型,使用类似于require('MyModule')
语法导入index.m.js 是ECMAScript 模块,使用类似于import MyModule from 'my-module'
语法导入index.umd.js 是UMD 模块index.d.ts 是TypeScript 的类型声明文件
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()],
};
module.exports = [{
entry: './app.js',
output: ...,
...
}, {
entry: './app.js',
output: ...,
...
}]
我们代码中的
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";
/******/ (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"]
代码分割
代码分割是提升

SplitChunksPlugin
不同于
- 新的块是在多个模块间共享,或者来自于
node_modules 目录; - 新的块在压缩之前的大小应该超过
30KB ; - 页面所需并发加载的块数量应该小于或者等于
5 ; - 初始页面加载的块数量应该小于或者等于
3 ;
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
}
}
}
当两个chunka~index~chunkb
等类似的代码,值得一提的是,这里的initial
async
all
三个配置,上述配置即是分别针对初始initial
,那么它将忽略通过动态导入的模块包包含的第三方库代码。而
{
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
}
}
}
}
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) {
...
},
});
如果是使用
更多关于
Backpack
$ npm i backpack-core --save
然后在
{
"scripts": {
"dev": "backpack",
"build": "backpack build"
}
}
在 Backend-Boilerplate/node 中可以查看
// backpack.config.js
module.exports = {
webpack: (config, options, webpack) => {
// Perform customizations to config
// Important: return the modified config
return config;
},
};
或者添加
{
"presets": ["backpack-core/babel", "stage-0"]
}
Module Loader | 模块加载器
SystemJS
Configurable module loader enabling backwards compatibility workflows for ES modules in browsers.
<script type="systemjs-packagemap">
{
"packages": {
"lodash": "https://unpkg.com/lodash@4.17.10/lodash.js"
}
}
</script>
<!-- Alternatively:
<script type="systemjs-packagemap" src="path/to/map.json">
-->
<!-- SystemJS must be loaded after the package map -->
<script src="system.js"></script>
<script>
System.import("/js/main.js");
</script>
To load ES modules directly in older browsers with SystemJS we can install and use the Babel plugin:
<script src="system.js"></script>
<script src="extras/transform.js"></script>
<script src="plugin-babel/dist/babel-transform.js"></script>
<script>
// main and all its dependencies will now run through transform before loading
System.import("/js/main.js");
</script>
import moment from "moment";
export function test() {
const m1 = moment().format("LLL");
const m2 = moment().fromNow();
return `The moment is ${m1}, which was ${m2}`;
}
const SystemJS = require("systemjs");
SystemJS.config({
map: {
traceur: "node_modules/traceur/bin/traceur.js",
moment: "node_modules/moment/src",
},
packages: {
moment: {
main: "moment.js",
},
},
});
SystemJS.import("./test.js")
.then(function (test) {
var t = test.test();
console.log(t);
})
.catch(function (e) {
console.error(e);
});