Plugins
我们已经看到一个内置webpack插件的示例,npm run bulid
脚本调用的webpack -p
使用UglifyPlugin,它附带了webpack,可以再生产过程中缩减bundle
。
当loaders
对单个文件进行转换操作时,插件会在较大的代码块上操作。
Common code
commons-chunk-plugin 是webpack附带的另一个核心插件,可用于创建一个具有多入口点的共享代码的独立模块。到目前为止,我们一直使用单个入口点和单个输出包,有很多real-world scenarios,我们可以从中分离出多个条目和输出文件,而得到好处。
如果在你的应用程序里有两个不同的区域都共享模块,例如面向公众的应用程序app.js
和管理区域的admin.js
,则可以为其创建单独的入口点。
// webpack.config.js
const webpack = require('webpack')
const path = require('path')
const extractCommons = new webpack.optimize.CommonsChunkPlugin({
name: 'commons',
filename: 'commons.js'
})
const config = {
context: path.resolve(__dirname, 'src'),
entry: {
app: './app.js',
admin: './admin.js'
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].bundle.js'
},
module: {
// ...
},
plugins: [
extractCommons
]
}
module.exports = config
注意到output.filename
的更改,现在包含[name]
,这被替换成块名称,所以我们可以期待这个配置的两个输出bundle:app.bundle.js
和admin.bundle.js
,为我们的两个入口点。
commonschunk
插件生成的第三个文件common.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>`
入口点可以输出如下的文件:
app.bundle.js
包括style
和lodash/collection
模块admin.bundle.js
不包括额外的模块commons.js
包括我们的people
模块
接着,我们可以在下面两个地方包含commons
块:
<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Hello webpack</title>
</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">
<title>Hello webpack</title>
</head>
<body>
<div id="root"></div>
<script src="dist/commons.js"></script>
<script src="dist/admin.bundle.js"></script>
</body>
</html>
尝试在浏览器中加载index.html
和index.html
,以查看它们是否与自动创建的公共模块一起运行。
Extracting CSS
另一个流行的插件是extract-text-webpack-plugin,可以用来将模块提取到自己的输出文件中。
下面将修改我们的.scss
规则来编译我们的SASS
,记载CSS,将每个提取到自己的CSS包,然后再我们的JavaScript包中删除它们。
npm install extract-text-webpack-plugin@2.0.0-beta.4 --save-dev
// webpack.config.js
const ExtractTextPlugin = require('extract-text-webpack-plugin')
const extractCSS = new ExtractTextPlugin('[name].bundle.css')
const config = {
// ...
module: {
rules: [{
test: /\.scss$/,
loader: extractCSS.extract(['css-loader','sass-loader'])
}, {
// ...
}]
},
plugins: [
extractCSS,
// ...
]
}
重新启动webpack,你应该可以拉到一个新的包app.bundle.js
,你可以像往常一样直接链接。
<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Hello webpack</title>
<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>
刷新页面,确保我们的CSS已经编译并从app.bundle.js
中移动到app.bundle.css
,成功!
Code Splitting
我们已经见过几种分割代码的方法:
手动创建单独的entry points
把共享代码自动拆分为commons chunk
使用extract-text-webpack-plugin从我们编译的bundle中提取块
另一种拆分我们bundle的方法是使用System.import或者require.ensure。通过这些函数中包装的代码段,你可以创建一个在运行时按需加载的块,这可以通过在开始时不向客户端发送所有内容来显著减少加载时间,提高加载性能。System.import
将模块名称作为参数,并返回一个Promise
;require.ensure
获取依赖的列表——回调和块的可选名称。
如果你的应用程序的某一部分具有沉重的依赖关系,而其余部分不需要,这是把它分割成自己的捆绑包的好场景。我们可以通过一个名为dashboard.js
(需要d3)的新模块来演示这一点.
npm install webpack-dev-server@2.2.0-rc.0 --save-dev
// src/dashboard.js
import * as d3 from 'd3'
console.log('Loaded!', d3)
export const draw = () => {
console.log('Draw!')
}
从app.js
的底部导入dashboard.js
:
// ...
const routes = {
dashboard: () => {
System.import('./dashboard').then((dashboard) => {
dashboard.draw()
}).catch((err) => {
console.log("Chunk loading failed")
})
}
}
// demo async loading with a timeout
setTimeout(routes.dashboard, 1000)
因为我们添加了模块的异步加载,所以我们需要在配置中使用output.publicPath
属性,以便webpack知道在哪里获取它们。
// webpack.config.js
const config = {
// ...
output: {
path: path.resolve(__dirname, 'dist'),
publicPath: '/dist/',
filename: '[name].bundle.js'
},
// ...
}
重新启动构建,你会看到一个神秘的包0.bundle.js
注意到,webpack通过突出显示
[big]
包来获取你的关注,从而保持你的忠诚。
0.bundle.js
将根据需要使用JSONP请求获取,因此文件系统直接加载文件不会再削减它。我们需要运行一个服务器,任何服务器都会做。
python -m SimpleHTTPServer 8001
打开 http://localhost:8001/
加载完后,你应该可以看到一个GET
请求我们动态生成的包/dist/0.bundle.js
和“Loaded!”记录在控制台上,成功!
Webpack Dev Server
实时重新加载——每当文件更改时自动刷新,可以真正改善开发者的体验。只需要安装它,并启动它和webpack-dev-server
,你就可以进行你的比赛了。
npm install webpack-dev-server@2.2.0-rc.0 --save-dev
修改package.json
中的start
:
"start": "webpack-dev-server --inline",
运行npm start
开启服务器,在浏览器中打开 http://localhost:8080
尝试通过更改任何的src
文件,例如更改people.js
中的名称或者style.scss
中的样式,查看它在你眼前的变化。
Hot Module Replacement
如果你偏爱现场重新加载,那么hot module replacement(HMB)会让你叹为观止。
这是2017年,可能你已经在去全球的单页面应用程序工作。在开发期间,你可能对组件进行很多小修改,并希望在浏览器中看到这些修改——你可以在其中看到输出并与其进行交互。通过手动刷新页面或使用实时重新加载,你的全局状态就不存在,你需要从头开始。Hot module replacement
永远更改了这一点。
在你梦想中的开发人员工作流程中,你可以更改模块,并在运行时编译和交换,而不刷新浏览器(不改变本地缓存)或影响到其他模块。虽然HMR有时仍然需要你手动刷新,但其依旧可以为你节省大量时间,感觉就像未来。
对package.json
中的start
进行最后一次修改:
"start": "webpack-dev-server --inline --hot",
在app.js
头部告诉webpack接受此模块及其任何依赖的HMR:
if (module.hot) {
module.hot.accept()
}
// ...
注意:
webpack-dev-server --hot
设置module.hot
为true
,其中包括此仅供开发。当在生产模式中把module.hot
设置为false
,这些就都会被剥离出来。
把NamedModulesPlugin
添加到webpack.config.js
中的插件列表,以改进控制台的输出日志。
plugins: [
new webpack.NamedModulesPlugin(),
// ...
]
最后,向页面中添加一个<input>
元素,还可以添加一些文本,以确保我们对模块进行更改时不会发生整个页面的刷新。
<body>
<input />
<div id="root"></div>
...
运行npm start
重启服务器,看那热重装。
可以尝试这样做:在input
中输入HMR规则”,然后再people.js
中改变名称,以查出是否更换了,而没有刷新页面(没有丢失input
的输入状态)。
这是一个简单的例子,但可以帮助你看到这是多么有用。对于使用基于组件的开发(例如React)尤其有用,其中很多"dumb"组件与其状态分离,组件可以被交换出来并重新呈现而不丢失状态,因此你可以获得即时反馈循环。
Hot Reloading CSS
更改style.css
中<pre>
元素的背景颜色,你会注意到它没有被HMR替换。
pre {
background: red;
}
事实证明,CSS的HMR是免费的,当你使用style-loader
,你不需要做什么特别的事情。我们只是通过将CSS模块提取到外部CSS文件中而无法被替换,从而打破了链中的link
。
如果我们将Sass规则恢复到原始状态,并从插件列表中删除extractCSS
,你可以看到热重载加载你的Sass。
{
test: /\.scss$/,
loader: ['style-loader', 'css-loader','sass-loader']
}
HTTP/2
使用像webpack这样的模块打包工具的主要好处之一视它可以帮助你提高性能,让你可以控制如果构建资源,并可以在客户端上获取资源。多年来,人们认为最佳实践是连接文件以减少需要在客户端上进行的请求数。这仍然有效,但HTTP/2 now allows multiple files to be delivered in a single request,所以并置不再是一个银子弹。实际上你的应用程序可能受益于多个小文件的单独缓存,客户端可以获取一个更改的模块,而不必重新获取一个完整的包——大多数内容是相同的。
webpack的创造者Tobias Koppers撰写了一篇资料性的文章,解释了我什么打包是很重要的,即使在HTTP/2时代。
有关这方面的详细内容,请点阅webpack & HTTP/2。
Over to You
我非常希望你发现了本文,并且能够使用它进行你的伟大创造。它可能需要一些时间使你的头脑围绕webpack的配置、加载程序和插件,但学习这个工具是值得的。
文档扔在更新中,如果你想将现有的webpack1项目迁移到新的热点中,将会有一个方便从v1迁移到v2的指南。
作者:Mark Brown
原文链接:A Beginner’s Guide to Webpack 2 and Module Bundling
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。