在今时本日,webpack 已经成为前端开发异常重要的东西之一。本质上它是一个 Javascript 模组封装东西,但透过 loaders 和 plugins 它也能够转换封装其他前端的资源档像是 HTML,CSS,以至是图片等,让我们能够掌握程式发出 HTTP 请求的数量(编译结果的档案数量)。我们能够运用偏好的体式格局去撰写这些资源档像是 Jade, Sass, ES6 等等。同时也让我们能够轻易的运用来自 npm 的套件。
这篇文章目标读者是那些刚接触 webpack 的新手。内容将会包括设定,模组的运用,loaders,plugins,code splitting(分拆程式码),热替换(Hot module replacement)。
每过一阵子,从新学习 webpack 就会有些新的发现 – 废话。
为了完成文章中的练习您须要些前置作业:安装 Nodejs(译者运用 v6.9.2),假如您还么安装那么这边有篇完全运用 nvm 安装的教学。
设定
让我们开始来运用 npm 初始化专案与安装 webpack。
$ mkdir webpack-demo
$ cd webpack-demo
$ npm init -y
$ npm i webpack@2 -D
$ npm view webpack version
# 2.2.1
$ mkdir src
$ touch index.html src/app.js webpack.config.js
上面这些档案的大要说明:
index.html – 首页,载入运用编译好的 Javascript 档案。
src/ 目录 – 许多开发者会把 Source Code 的目录定名为 src 但这并不强迫。
webpack.config.js – 设定 webpack 行为的设定档,这边我们能够先大要相识一下 webpack 的行为能够靠
设定档
或许指令的参数
调整,而 webpack.config.js 是预设的设定档名称。src/app.js – 这是我们程式的 Entry point。
接着编辑我们刚产出的这些档案
index.html
<!DOCTYPE html>
<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>Hello webpack 2</title>
</head>
<body>
<div id="root"></div>
<script src="dist/bundle.js"></script>
</body>
</html>
src/app.js
const root = document.querySelector('#root')
root.innerHTML = `<p>Hello webpack 2</p>`
webpack.config.js
const path = require('path')
const config = {
/**
* webpack 执行环境,即 webpack 载入档案时相对路径的根目录环境
* 预设(没有设定时)为执行指令(webpack)地点的那个目录
*
* 【其他】
* 假设自身新增一个 build/build.js 档案,运用载入 webpack 的作法
*
* 比方范例:https://github.com/andyyou/webpack-context-prove/blob/master/build/build.js#L14
*
* 在差别目录执行:
* > node build/build.js (in root/ folder)
* > node build.js (in build/ folder)
* 预设 context 会分别为 root/ 和 root/build/
*/
context: path.join(__dirname, 'src'),
/**
* Entry point
* 因为设定了 context 所以不须要加上 src/ 了
*/
entry: './app.js',
/**
* 输前途径与档名
*/
output: {
path: path.join(__dirname, 'dist'),
filename: 'bundle.js'
},
/**
* loaders 对应运用规则
*/
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: [
{
/* webpack 2.x 移除了省略 -loader 的写法 */
loader: 'babel-loader',
options: {
presets: [
/* Loose mode and No native modules(Tree Shaking) */
['es2015', { modules: false, loose: false }]
]
}
}
]
}
]
}
}
module.exports = config
上面这些是常见的基础设定,它告诉 webpack 怎样编译我们的原始码,进入点是 src/app.js
输出的档案则放在 dist/bundle.js
。
一切的 .js
档案都会运用 Bable 从 ES2015 被编译成 ES5。
Babel 从 6.13.0 之后供应额外的参数 loose 和 modules
loose: 供应 loose 编译形式,该形式启动下 Babel 会尽能够产生较精简的 ES5 程式码,预设 false 会尽能够产出靠近 ES2015 规范的程式码。
modules: 转换 ES2015 module 的语法(import)为别的类型,预设为 true 转换为 commonjs。
为了要能够执行我们须要安装 babel-core
,babel-loader
和 babel-preset-es2015
。上面还有一个值得注重的处所就是 {modules: false}
关闭这个设定是为了供应 Tree Shaking
的特征 – 移除没有运用到的 exports 来缩小编译的档案大小。
$ npm i babel-core babel-loader babel-preset-es2015 -D
最后我们在 package.json
补上 scripts
的部份。
"scripts": {
"start": "webpack --watch",
"build": "webpack -p"
}
到这步我们就能够运用 npm start
来执行 webpack,到场参数 –watch 会让 webpack 进入监视形式,当发现档案有异动时就会马上从新编译我们的原始码。在 console 画面会输出类似下面的讯息来示知我们 bundle 已经被竖立了。同时有个小小的重点那就是我们可已观察编译后的档案大小。
Webpack is watching the files…
Hash: f4fadf78c49f43d8a078
Version: webpack 2.2.1
Time: 1342ms
Asset Size Chunks Chunk Names
bundle.js 2.6 kB 0 [emitted] main
[0] ./app.js 87 bytes {0} [built]
在专案目录下执行 open index.html
能够观察住手现在为止的结果。
开启 dist/bundle.js
看看 webpack 编译的结果,上半部是 webpack 处理模组载入的程式码,最下面则是我们的模组。
到这您能够不觉得有什么特别的,不过一旦我们开始运用 ES2015 来开发并将程式模组化,那么 webpack 就能够替我们处理后续的事情让我们的原始码编译成能够在浏览器执行的版本。
接着,我们能够透过 Ctrl + C
来住手 webpack,换成执行 npm run build
能够编译产品形式的 bundle(压缩)。
您能够注重到档案大小从 2.6kB 变成 587 bytes,再次观察 dist/bundle.js 会发现程式码已经被 Uglify 了。
至此我们初始化了专案并对 webpack 设定有了基础的相识。
模组
webpack 自身晓得该怎样处理载入各种花样的 Javascript 模组,比较值得注重的有两个:
ES2015
import
CommonJS
require()
我们运用 lodash
来试验看看
$ npm i lodash -S
src/app.js
import {groupBy} from 'lodash/collection'
const people = [{
manager: 'Jen',
name: 'Bob'
}, {
manager: 'Jen',
name: 'Sue'
}, {
manager: 'Bob',
name: 'Shirley'
}, {
manager: 'Bob',
name: 'Terrence'
}]
const managerGroups = groupBy(people, 'manager')
const root = document.querySelector('#root')
root.innerHTML = `<pre>${JSON.stringify(managerGroups, null, 2)}</pre>`
执行 npm start
然后在浏览器从新载入 index.html 应该要看到我们透过 lodash 区分群组的结果。
接着让我们把 people 的资料搬移到 src/people.js
src/people.js
const people = [{
manager: 'Jen',
name: 'Bob'
}, {
manager: 'Jen',
name: 'Sue'
}, {
manager: 'Bob',
name: 'Shirley'
}, {
manager: 'Bob',
name: 'Terrence'
}]
export default people
在 src/app.js
运用 import 载入 people.js
import {groupBy} from 'lodash/collection'
import people from './people'
const managerGroups = groupBy(people, 'manager')
const root = document.querySelector('#root')
root.innerHTML = `<pre>${JSON.stringify(managerGroups, null, 2)}</pre>`
注重:不运用相对路径的比方 lodash/collection
模组将会从 /node_modules
载入,我们自定义的模组平常运用相对路径。
汇入模组时的 path 分红
函式库 node_modules
相对路径
绝对路径
函式库:什么都不加,单纯 library name
相对路径:
./
开头绝对路径:
/
开头
第二小节我们示范了模组的运用,也就是我们能够把程式模组化再运用 webpack 来处理汇入运用的部份。
Loaders
上面我们已经运用了 babel-loader
,它是众多 loader 中的一个,服从就是把 ES2015 转成 ES5。而 loaders 的服从就是告诉 webpack 该怎样处理汇入的档案,平常是 Javascript 但 webpack 不限于处理 Javascript,其他资源档像是 Sass,图片等也都能够处理,只需供应对应的 loader。
同时 loader 也能够串连运用,概念上类似于 Linux 中的 pipe,A Loader
处理完之后把结果交给 B Loader
继续转换,以此类推。
最好的示范范例就是汇入 Sass 的流程,下面就让我们来看看。
Sass
我们的目标是要把 Sass 编译封装到我们的 bundle 中(Javascript)。这个转换过程须要一些 loaders 和函式库:
$ npm i css-loader style-loader sass-loader node-sass -D
为处理 .scss
类型的档案到场新的编译规则
module: {
rules: [
// {...},
{
test: /\.scss$/,
/**
* use 属性是用来套用,串接多个 loaders。
* v2 为了相容的要素保存 loaders 属性,loaders 为 use 的别名,
* 尽能够的运用 use 替代 loaders
*/
use: [
'style-loader',
'css-loader',
'sass-loader'
]
}
// {...}
]
}
每当我们修正了 webpack.config.js 我们就必须重启。
Ctrl + C
->npm start
设定 loaders 的阵列实际上会反过来一一执行:
sass-loader – 编译 Sass 成为 CSS
css-loader – 剖析 CSS 转换成 Javascript 同时剖析相依的资源
style-loader – 输出 CSS 到 document 的
<style>
元素内
我们能够想成像下面这样调用 function
styleLoader(cssLoader(sassLoader('source')))
让我们来到场 Sass
src/style.scss
$bg-color: #2B3A43;
pre {
padding: 15px;
background: $bg-color;
color: #DEDEDE;
}
现在我们能够在 app.js
汇入 scss 了。
import './style.scss'
重载 index.html 我们应该能够看到样式已经套用了。
CSS in JS
上一步我们完成了从 JS 中把 Sass 当作一个模组载入。
打开 dist/bundle.js
搜寻 pre {
,我们看到 Sass 已经被编译为字串并存为一个模组。当我们汇入该模组时 style-loader
会把该字串嵌入 <style>
标签中。
为什么我们要这么作呢?
这边我们不想探讨太多这个议题,不过的确有些来由让我们这么作:
追念我们在运用 jQuery 套件或 Javascript 元件时,假如这些元件包括些资源档像是 HTML,CSS,图片,SVG 等等,平常这时候我们就须要把这档案搬到对应的目录下,又或许我们有洁癖愿望图档等放在我们定义的目录下,这时我们就须要去修正路径。当悉数都封装在 Javascript 时我们在组织时就轻易许多。
轻易移除不须要的程式码 – 当 Javascript 元件不在被运用时, CSS 等资源档内容也会一并被移除。
CSS Module – 随着 CSS 样式越来越多,定名经常轻易冲突,透过 CSS Module 的体式格局能够对特定元件或模组套用其专用(local)的样式,只要该模组能够套用样式,这样一来也相对轻易维护。
减少 HTTP request 的数量。
图片
最后我们要在介绍一个常碰到的需求 – 图片。我们将要运用 url-loader
来处理图片的载入。
在标准的 HTML 文件中图片须要透过 <img>
标签或 CSS 的 background-image
来载入。运用 webpack 我们能够优化图片并根据档案大小分别处理。像是把比较小的图片转成 base64 字串存在 Javascript 中。这么作浏览器能够预先载入,而且不会发出额外的 HTTP 请求。
npm i file-loader url-loader -D
当然我们记住了,每当我们须要一个 loader 来帮我们处理某类型的档案时,我们除了安装该 loader 外,还须要设定 rules
。
rules: [
// ...
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
use: [
{
loader: 'url-loader',
options: {
limit: 10000 /* 小于 10kB 的图片转成 base64 */
}
}
]
}
]
让我们下载张图片并将其放在 src/
$ curl http://i.imgur.com/5Hk42Ct.png --output src/code.png
接着在 app.js
中运用下面范例载入
src/app.js
import imageURL from './code.png'
const img = document.createElement('img')
img.src = imageURL
img.style = 'background: #2B3A4F; padding: 15px;'
img.width = 32
document.body.appendChild(img)
检查 HTML 该图片元素,我们看到会以下图片的来源被转换成 base64 了。
<img src="data:image/png;base64,iVBORw0KGgoAAAANSU..." style="background: rgb(43, 58, 79) none repeat scroll 0% 0%; padding: 15px;" width="32">
不只是在 Javascript 载入的图片,我们之前提到 css-loader 同时也会剖析相依的资源,然则要处理图片 css-loader
也是须要 url-loader
来帮忙。我们能够从 css-loader 的文件 options 一节得知。
src/style.scss
pre {
background: $bg-color url('code.png') no-repeat center center;
}
也会被编译成
pre {
background: $bg-color url('data:image/png;base64,iVBORw0KG...') no-repeat center center;
}
从模组到静态资源
现在您应该邃晓 loaders 是怎样协助我们编译各式各样的档案,这也是 webpack 2 官方文件首页 出现图片所要表达的。
透过 Javascript 的 Entry point 进入点,webpack 就会邃晓我们须要那些模组以及各种类型的档案该怎样处理。
心法:组织好专案架构后,安装 webpack,设定的基础是 entry,output 以及想编译哪些类型的档案,对应安装相关 loaders 接着设定 rules。
Plugins
开始讲 plugins 之前,其实我们已经看过一个 webpack 内建的 plugin。它就是当我们执行 webpack -p
时运用的 UglifyJsPlugin。
简单说,loaders 的任务是针对单一档案协助转换,而 plugins 的任务则针对转换后的程式码片断作处理。
通用程式码
commons-chunk-plugin 是另一个内建的中心套件,能够将多个 Entry point 中共用模组的部份抽出来独立成一个模组。
到这边我们都只要单一个 Entry point 然后只输出一个 bundle。在实务上您能够会须要多个进入点来切割 bundle,比方:我们自身开发的部份归纳在 app.js
,其他外部的函式库归纳在 vendor.js
。
又或许假设我们的网站有两个服从上须要切割的部份 – 平常运用者 app.js
和管理者 admin.js
。这么一来我们就能够像下面这样处理:
const path = require('path')
const webpack = require('webpack')
const extractCommons = new webpack.optimize.CommonsChunkPlugin({
name: 'commons',
filename: 'commons.js'
})
const config = {
context: path.join(__dirname, 'src'),
entry: {
app: './app.js',
admin: './admin.js'
},
output: {
path: path.join(__dirname, 'dist'),
filename: '[name].bundle.js'
},
// ...
plugins: [
extractCommons
]
}
module.exports = config
注重到 output.filename
部份我们在前面加上了 [name]
,同时 entry
也从字串换成物件花样,物件 key 的名称会对应到 [name]
,也就是照上面的设定会产生 app.bundle.js
和 admin.bundle.js
两只档案。
而 commons-chunk-plugin
则会把这两个 entry point 中共用的模组抽出来产生第三只档案 commons.js
。
我们先来看看范例:
src/app.js
import './style.scss'
import {groupBy} from 'lodash/collection'
import people from './people'
const managerGroups = groupBy(people, 'manager')
const root = document.querySelector('#root')
root.innerHTML = `<pre>${JSON.stringify(managerGroups, null, 2)}</pre>`
src/admin.js
import people from './people'
const root = document.querySelector('#root')
root.innerHTML = `<p>There are ${people.length} people.</p>`
再次执行 npm start
就会看到 webpack 产生了 3 只档案
app.bundle.js 包括了 style 和 lodash/collection
admin.bundle.js 没有其他额外的模组
commons.js 包括 people 模组
然后调整 HTML 的部份:
index.html
<!DOCTYPE html>
<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>Hello webpack 2</title>
<style>
html, body {
padding: 0;
margin: 0;
}
</style>
</head>
<body>
<div id="root"></div>
<script src="dist/commons.js"></script>
<script src="dist/app.bundle.js"></script>
</body>
</html>
admin.html
<!DOCTYPE html>
<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>Hello webpack 2 - Admin</title>
<style>
html, body {
padding: 0;
margin: 0;
}
</style>
</head>
<body>
<div id="root"></div>
<script src="dist/commons.js"></script>
<script src="dist/admin.bundle.js"></script>
</body>
</html>
当我们分别造访两个页面的时候,commons.js 也能够因为前一次被 cache 了而加快了网页载入的速率。
输出 CSS 档案
前面我们把 CSS 也当作模组汇入并打包进 Javascript,但实务上我们想要让浏览器非同步载入和平行处理 CSS 所以我们须要输出独立的 CSS 档案。
这时我们就要别的一个异常热门的 plugin – extract-text-webpack-plugin 它能够将模组汇出成档案。
下面我们将改写 .scss
rule 的部份,将编译好的 CSS 汇出成档案,而不是放在 Javascript。
# extract-text-webpack-plugin 2 正处于 rc 阶段
# 我们能够透过下面的指令检察一切的版本
$ npm show extract-text-webpack-plugin versions
# 安装
$ npm i extract-text-webpack-plugin@2.0.0-rc.3 -D
接着修正 webpack.config.js
const path = require('path')
const webpack = require('webpack')
const extractCommons = new webpack.optimize.CommonsChunkPlugin({
name: 'commons',
filename: 'commons.js'
})
const ExtractTextPlugin = require('extract-text-webpack-plugin')
const extractCSS = new ExtractTextPlugin('[name].bundle.css')
const config = {
context: path.join(__dirname, 'src'),
entry: {
app: './app.js',
admin: './admin.js'
},
output: {
path: path.join(__dirname, 'dist'),
filename: '[name].bundle.js'
},
module: {
rules: [
// ...
{
test: /\.scss$/,
loader: extractCSS.extract(['css-loader', 'sass-loader'])
/*
use: [
'style-loader',
'css-loader',
'sass-loader'
]
*/
}
]
},
plugins: [
extractCommons,
extractCSS
]
}
module.exports = config
重启 webpack 您应该能够看到 webpack 汇出了 app.bundle.css
,于是我们就能够在 HTML 中补上连结。每一个 entry 内汇入的 Sass 都会被抽出来成一只独立的档案。由于 admin 没有汇入 Sass 所以没有输出。
<!DOCTYPE html>
<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>Hello webpack 2</title>
<style>
html, body {
padding: 0;
margin: 0;
}
</style>
<link rel="stylesheet" href="dist/app.bundle.css">
</head>
<body>
<div id="root"></div>
<script src="dist/commons.js"></script>
<script src="dist/app.bundle.js"></script>
</body>
</html>
分拆原始码
我们已经看了一些分拆程式码的体式格局:
手动竖立多个 Entry point
运用 commons-chunk-plugin 分拆出共用模组的部份
运用 extract-text-webpack-plugin
别的一个分拆的体式格局是运用 System.import
或 require.ensure
。透过调用这些要领设定,我们能够划分程式码让它们在须要的时候才在执行时期(runtime)载入,而不是一口气悉数载入。这样能够有用的改良载入所形成的效能问题。System.import
运用 module 名称作为参数,接着回传一个 Promise。require.ensure
则是传入相依套件的列表, callback,和一个可选的参数来设定该程式片断的名称。
System.import 是 ES2015 模组载入的 API
假如您的程式中某部份具有大批相依函式库,且程式的其他处所不须要。这种情况恰好就适合将其拆分出来。看看下面的范例,我们的 dashboard.js 须要 d3,但能够见得的别的处所不须要 d3。
$ npm i d3 -S
src/dashboard.js
import * as d3 from 'd3'
console.log('Loaded', d3)
export const draw = () => {
console.log('Draw!')
}
接着在 app.js 的最下方我们模拟晚一点载入(须要的时候才载入)
function gotoDashboard () {
System.import('./dashboard')
.then(function (dashboard) {
dashboard.draw()
})
.catch(function (err) {
console.log('Chunk loading failed')
})
}
setTimeout(gotoDashboard, 5000)
因为我们运用的是 System.import('./dashboard')
这样的相对路径,在线上的情况下会变成 http://example.com/dashboard.js
这样的路径是错误的。所以须要加上 output.publicPath
的设定,这样才会是 http://example.com/dist/dashboard.js
。
output: {
path: path.join(__dirname, 'dist'),
publicPath: '/dist/',
filename: '[name].bundle.js'
}
重启 webpack 会发现 console 有一个新鲜的 0.bundle.js
Hash: e96d4e3ab03b79a320aa
Version: webpack 2.2.1
Time: 4030ms
Asset Size Chunks Chunk Names
0.bundle.js 452 kB 0 [emitted] [big]
app.bundle.js 184 kB 1 [emitted] app
admin.bundle.js 461 bytes 2 [emitted] admin
commons.js 5.91 kB 3 [emitted] commons
app.bundle.css 1.31 kB 1 [emitted] app
webpack 用了较为凸起的颜色显示 [big]
让我们去注重它。
这个 0.bundle.js
将会在须要的时候发出 JSONP 的请求,这个时候假如我们继续直接读取档案的体式格局是拿不到资料的。
暂时我们能够在专案目录下运用 python 供应的简易伺服器(因为 Linux,OSX 内定都有 python 我们不须要在作其他安装)。
$ python -m SimpleHTTPServer
浏览 http://localhost:8000
,5 秒后我们应该能够看到一个 GET 请求 /dist/0.bundle.js
同时 console 显示 Loaded
载入完成。
Webpack Dev Server
Live reload 的出现,大大的改良了我们的开发体验也替开发者们节省了许多的时间。简单的说就是当档案发生变动时浏览器会自动从新载入页面。
只须要安装并运用 webpack-dev-server 这个开发伺服器我们就能够轻松获得这个服从。
$ npm i webpack-dev-server@2 -D
接着我们须要修正 package.json
scripts start 的部份:
"scripts": {
"start": "webpack-dev-server --inline",
"build": "webpack -p"
},
执行 npm start
就能够透过 http://localhost:8080
来浏览网页。
现在,只需我们修正 src
目录下的档案,比方:people.js
或 style.scss
就能够看到浏览器马上更新结果。
热替换(Hot Module Replacement)
假如您对于 Live reload 印象深入的话,那么 HMR 能够将令你觉得惊讶。
假如您已经在开发 SPA (Signle Page Application),您应该已经遭受过了这种恼人的情况 – 在你的开发过程经常因为要测试某元件而反复操纵一些流程。什么意义?假如我们正在开发付款流程的页面,分别有 4 个步骤,我们的元件在第 3 步,于是每当我们一修正,一切状态因为重载的关系回到预设,然后我们就只好反复执行步骤 1, 2。
Hot Module Replacement 就是为了挽救我们脱离这个回圈而出现了。
我们抱负的开发流程应该是:每当我们修正我们的模组,然后应该只需编译该模组,在不革新浏览器,不影响其他模组的情况下把新的程式码换上去,当我们须要 reset 状态时在重载页面。大致上这就是 HMR 的服从。
我们只须要到场一个参数 --hot
就能够启用这个服从:
"scripts": {
"start": "webpack-dev-server --inline --hot",
"build": "webpack -p"
}
为了让我们的模组也增援 HMR 我们须要在 app.js 的最上面补上下面这段程式码,好让 webpack 晓得我们模组的边界以及更新底下相依的元件。
if (module.hot) {
module.hot.accept()
}
注重:webpack-dev-server --hot
会把 module.hot
设为 true
而且只要在开发形式才增援。在 production 形式下 module.hot
会是 false
,相关的程式码不会出现在 bundle 中。
到场 NamedModulesPlugin
到 webpack.config.js 的 plugins 阵列,云云一来我们在浏览器的 console 就能够看出是哪个档案更新。
plugins: [
// ...
new webpack.NamedModulesPlugin()
]
最后,我们到场 <input>
到 HTML ,从新执行 npm start
并在页面的输入框输入一些字,编辑 people.js 中的人名,存档。我们能够观察到 HMR 的行为,的确不是整个页面革新。
Hot Reloading CSS
修正 style.scss
中 <pre>
的背景色,我们注重到 HMR 并没有对应更新
pre {
background: red;
}
当我们运用 style-loader
时 HMR 是会更新的,我们不须要作其他设定。不过因为我们运用了 extract-text-webpack-plugin 把 CSS 独立出去成为档案,所以也就不增援 HMR。
HTTP/2
运用 webpack 这类的封装东西一个重要的好处就是能够掌握最终资源档被请求的数量。在过去几年这是最好的实作体式格局,不过 HTTP/2 的出现,整合成单档的体式格局不再是唯一,疏散成许多小档案在 HTTP/2 中是相对好的作法。
不过 webpack 的作者 Tobias Koppers 写了篇文章阐述纵然在 HTTP/2 的情况下,封装东西仍有其重要性。连结