目录
开始使用
mkdir webpack-demo && cd webpack-demo
npm init -y
npm install --save-dev webpack@3.5.5
管理依赖
为什么要?
如果导入模块时requrie 含有表达式﴾expressions﴿,会创建一个上下文﴾context﴿,因为在编译时﴾compile time﴿并不清楚具体是哪一个模块被导入。这会导致所有可能用到的模块都包含在 bundle 中。
require("./template/" + name + ".ejs");
方法选择?
可以使用 require.context() 方法来创建自己的(模块)上下文。给这个方法传 3 个参数:要搜索的文件夹目录,是否还应该搜索它的子目录,以及一个匹配文件的正则表达式。
require.context(directory, useSubdirectories = false, regExp = /^.//)
使用 require.context() 方法来创建自己的(模块)上下文,每个模块上下文有 3 个属性: resolve , keys , id 。 resolve 是一个函数,它返回请求被解析后得到的模块 id。 keys 也是一个函数,它返回一个数组,由所有可能被上下文模块处理的请求组成。id 是上下文模块里面所包含的模块 id. 可能在你使用 module.hot.accept 的时候被用到。
如何进行?
准备收集需要创建的模块上下文
//inex.js
var cache = {};
function importAll(r) {
r.keys().forEach(key => cache[key] = r(key));
}
使用require.context()创建上下文
//index.js
importAll(require.context('../components/', true, /\.js$/));
// 在构建时,所有被 require 的模块都会被存到cache 里面。
管理资源
为什么要?
动态打包 (dynamically bundle)所有依赖项(创建所谓的依赖图﴾dependency graph﴿)。这是极好的创举,因为现在每个模块都可以明确表述它自身的依赖,我们将避免打包未使用的模块。
可以以更直观的方式将模块和资源组合在一起。无需依赖于含有全部资源的 /assets 目录,而是将资源与代码组合在一起。会使代码更具备可移植性。
方法选择?
加载样式:style‐loader 、css‐loader
加载图片:file‐loader、url-loader
加载字体:file‐loader、url-loader
加载数据:file‐loader、csv‐ loader、xml‐loader
如何进行?
通过加载器在js模块引入非js文件。从 JavaScript 模块中 import一个 CSS 文件,使用style‐loader 和 css‐loader。在依赖于此样式的文件中 import './style.css'(声明依赖) 。现在,当该模块运行时,含有 CSS 字符串的<style>标签会注入到html文件的head中(资源嵌入)。
假想,现在我们正在下载 CSS,但是我们的背景和图标这些图片,要如何处理呢?使用file‐loader,我们可以轻松地将这些内容混合到 CSS 中。当你 import MyImage from './my‐image.png'(声明依赖),该图像将被处理并添加到 output 目录,并且 MyImage变量将包含该图像在处理后的最终 url。当使用 css‐loader 时,如上所示,你的 CSS 中的 url('./my‐image.png') 会使用 类似的过程去处理(资源嵌入)。loader 会识别这是一个本地文件,并将 './my‐image.png' 路径,替换为 输出 目录中图像的最终路径。html‐loader 以相同的方式处理(资源嵌入)。
通过配置好 loader 并将字体文件放在合适的地方,你可以通过一个 @font‐face 声明引入。本地的 url(...) 指令 会被 webpack 获取处理,就像它处理图片资源一样。
可以加载的有用资源还有数据,如 JSON 文件,CSV、TSV 和 XML。类似于 NodeJS,JSON 支持实际上是内置 的,也就是说 import Data from './data.json' 默认将正常运行。要导入 CSV、TSV 和 XML,你可以使用 csv‐ loader 和 xml‐loader。
安装依赖到开发环境中
npm install ‐‐save‐dev style‐loader css‐loader
npm install ‐‐save‐dev file‐loader
在配置文件中用加载器
module: {
rules: [{
test: /\.css$/,
use: [
'style‐loader',
'css‐loader'
]
},
{
test:/\.(png|svg|jpg|gif)$/,
use: [
'file‐loader'
]
},
{
test:/\.(woff|woff2|eot|otf)$/,
use: [
'file‐loader'
]
}
]
}
在文件中声明依赖
//index.js
import './style.css';
管理输出
为什么要?
问题01:到目前为止,我们在 index.html 文件中手动引入所有资源,然而随着应用程序增长,并且一旦开始对文件名使用哈 希﴾hash﴿]并输出多个 bundle,手动地对 index.html 文件进行管理,一切就会变得困难起来。——内容注入
。
问题02:假如输出文件都是输出到/dist目录,用多了总会遗留下来,导致我们的 /dist 文件夹相当杂乱。webpack 会生成文件,然后将这些文件放置在 /dist 文件夹中,但是 webpack 无法追踪到哪些文件是实际在项目中用到的。 通常,在每次构建前清理 /dist 文件夹,是比较推荐的做法,因此只会生成用到的文件。让我们完成这个需求。——目录清理
。
问题03:有时候,我们希望webpack 能够对「我们的模块映射到输出 bundle 的过程」保持追踪。比如收集静态资源的依赖关系、打包关系,以便实现模块化和组件化开发。——文件清单
。
方法选择?
问题01:可以用 HtmlWebpackPlugin 来解决对html文件注入内容:。
问题02:clean‐webpack‐plugin插件清除输出目录。
问题03:用WebpackManifestPlugin将模块映射到输出的关系提取到一个 json 文件,以供使用。
如何进行?
步骤01:安装依赖到开发环境中
npm install ‐‐save‐dev html‐webpack‐plugin
npm install ‐‐save‐dev clean‐webpack‐plugin
npm install ‐‐save‐dev WebpackManifestPlugin
步骤02:在配置文件中使用插件
const HtmlWebpackPlugin = require('html‐webpack‐plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const ManifestPlugin = require('webpack-manifest-plugin');
plugins: [
new CleanWebpackPlugin(['dist']),
new HtmlWebpackPlugin({title: 'Output Management'}),
new ManifestPlugin()
]
代码分离
为什么要?
把代码分离到不同的 bundle 中。可以按需加载或并行加载这些文件;可以用于获取更小的 bundle,以及控制资源加载优先级,如果使用合理,会极大影响加载时间。
分离方法?
入口起点:使用 entry 配置手动地分离代码。
防止重复:使用 CommonsChunkPlugin 去重和分离 chunk。
动态导入:通过模块的内联函数调用来分离代码。
怎么分离?
通过设置多个入口
进行分离,是迄今为止最简单、最直观的分离代码的方式。
entry: {
index: './src/index.js',
another: './src/another‐module.js'
}
如果入口 chunks 之间包含重复的模块,那些重复模块都会被引入到各个 bundle 中。接着,我们通过使用 CommonsChunkPlugin插件来移除重复的模块到一个公共文件中。这常用于提取公共类库
,三方类库
,异步类库
,文件清单
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: 'common' // 指定公共 bundle 的名称。
})
]
但是,通过设置多个入口进行分离,还有一不足。这种方法不够灵活,不能将核心应用程序逻辑进行动态拆分代码。为此,引入动态分离。
//index.js
async function getComponent() {
var element = document.createElement('div');
const _ = await
import( /* webpackChunkName: "lodash" */ 'lodash');
element.innerHTML = _.join(['Hello', 'webpack'], ' ');
return element;
}
//webpack.config.js
output: {
filename: '[name].bundle.js',
//决定非入口文件的名称
chunkFilename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist')
}
除了上述对脚本的分离,ExtractTextPlugin插件用于将 CSS 从主应用程序中分离。
分析输出
如果我们以分离代码作为开始,那么就以检查模块作为结束,分析输出结果是很有用处的。webpack‐visualizer: 可视化并分析你的 bundle,检查哪些模块占用空间,哪些可能是重复使用的。
//step01:运行命令
webpack --json --profile > stats.json
//step02:开浏览器
http://chrisbateman.github.io/webpack-visualizer/
or use in nodejs:
https://npm.taobao.org/package/webpack-visualizer-plugin
管理缓存
为什么要?
问题01:使用 webpack 来打包我们的模块化后的应用程序,webpack 会生成一个可部署的 /dist 目录,然后把打包后的内容放置在此目录中。只要 /dist 目录中的内容部署到服务器上,客户端(通常是浏览器)就能够访问网站此 服务器的网站及其资源。而最后一步获取资源是比较耗费时间的,这就是为什么浏览器使用一种名为缓存的技术。可以通过命中缓存,以降低网络流量,使网站加载速度更快,然而,如果我们在部署新版本时不更改资源的文件名,浏 览器可能会认为它没有被更新,就会使用它的缓存版本。由于缓存的存在,当你需要获取新的代码时,就会显得很棘手。
我们需要确保 webpack 编译生成的文件能够被客户端缓存,而在文件内容变化后,能够请求到新的文件。——文件指纹
什么方法?
改文件名法:
通过使用 output.filename 进行文件名替换,可以确保浏览器获取到修改后的文件。 [hash] 替换可以用于在文件名中包含一个构建相关﴾build‐specific﴿的 hash,但是更好的方式是使用基于文件内容摘要的[chunkhash] 替换。
提取样板法:CommonsChunkPlugin 可以用于将模块分离到单独的文件中。然而 CommonsChunkPlugin 有一个较少有人知道的功能是,能够在每次修改后的构建结果中,将 webpack的样板 ﴾boilerplate﴿和 manifest 提取出来。通过指定 entry 配置中未用到的名称,此插件会自动将我们需要的内容提取到单独的包中。将第三方库﴾library﴿(例如 lodash 或 react )提取到单独的 vendor chunk 文件中,是比较推荐的做法,这是因为,它们很少像本地的源代码那样频繁修改。因此通过实现以上步骤,利用客户端的长效缓存机制,可以通过命中缓 存来消除请求,并减少向服务器获取资源,同时还能保证客户端代码和服务器端代码版本一致。
模块标识法:向项目中再添加一个模块,每个 module.id 会基于默认的解析顺序﴾resolve order﴿进行增量(模块的id基于数字标识)。也就是说,当解析顺序发生变化,ID 也会随之改变。会造成第三方类库的hash也跟着改变。插件NamedModulesPlugin ,将使用模块的路径,而不是数字标识符。虽然此插件有助于在开发过程中输出结果的可读性,然而执行时间会长一些。第二个选择是使用 HashedModuleIdsPlugin ,推荐用于生产环境构建。
如何操作?
(文件名法)
//webpack.config.js
output: {
filename: '[name].[chunkhash].js',
}
bundle 的名称是它内容(通过 hash)的映射。如果我们不做修改,然后再次运行构建,我们以为文件名会 保持不变。然而,如果我们真的运行,可能会发现情况并非如此——如果不做修改,文件名可能会变,也可能不会。这是因为 webpack 在入口 chunk 中,包含了某些样板﴾boilerplate﴿,特别是 runtime 和 manifest。
(提取样板)
const webpack = require('webpack');
module.exports = {
entry: {
main: './src/index.js',
vendor: [
'lodash'
]
},
plugins: [
//提取第三方的包
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor'
}),
//提取 manifest
new webpack.optimize.CommonsChunkPlugin({
name: 'manifest'
})
],
output: {
filename: '[name].[chunkhash].js',
path: path.resolve(__dirname, 'dist')
}
};
(模块标识)
const webpack = require('webpack');
module.exports = {
entry: {
main: './src/index.js',
vendor: [
'lodash'
]
},
plugins: [
//模块基于路径标识
new webpack.HashedModuleIdsPlugin(),//生产环境
// new webpack.NamedModulesPlugin(),//开发环境
//提取第三方的包
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor'
}),
//提取 manifest
new webpack.optimize.CommonsChunkPlugin({
name: 'manifest'
})
],
output: {
filename: '[name].[chunkhash].js',
path: path.resolve(__dirname, 'dist')
}
};
模块刷新
为什么要?
问题01:有时候在运行时更新各种模块,而无需进行完全刷新。——自动刷新
刷新方法?
内置刷新服务器法:webpack‐dev‐server
自建刷新服务器法:webpack‐dev‐middleware + webpack‐hot‐middleware +express
怎么刷新?
(使用内置刷新服务器)
安装依赖
npm install ‐‐save‐dev NamedModulesPlugin
npm install ‐‐save‐dev HotModuleReplacementPlugin
配置文件
// built-in-hot-dev-server.config.js
entry: {
app: './src/index.js'
},
devServer: {
contentBase: './dist',
hot: true
},
plugins: [
new CleanWebpackPlugin(['dist']),
new HtmlWebpackPlugin({
title: 'Hot Module Replacement'
}),
//以便更容易查看要修补﴾patch﴿的依赖
new webpack.NamedModulesPlugin(),
new webpack.HotModuleReplacementPlugin()
]
入口文件
//./src/index.js
import printMe from './print.js';
if(module.hot) {
module.hot.accept('./print.js', function() {
console.log('Accepting the updated printMe module!');
printMe();
})
}
包的配置
//package.json
"scripts": {
"start": "webpack --config built-in-hot-dev-server.config.js"
}
运行命令
npm run start
(使用自建刷新服务器)
开发环境
为什么要?
问题01:使用错误追踪功能。为了更容易地追踪错误和警告,将编译后的代码映射回原始源代码。如果一个错误来自于 b.js ,source map 就会明确的告诉你。——资源映射
问题02:使用自动编译功能。每次要编译代码时,手动运行 npm run build 就会变得很麻烦。工具可以帮助你在代码发生变化后自动编译代码。——文件监控+自动刷新
- webpack's Watch Mode——解决
自刷脚本
的问题 - webpack‐dev‐server ——解决了
自刷脚本+自刷样式+自刷内容
的问题 - webpack‐dev‐middleware ——解决了
自刷脚本+自刷样式+自刷内容
的问题
如何进行?
(错误追踪)
//step01:设置配置文件
devtool: 'inline‐source‐map',
//step02:编译及打开浏览器
//step03:在控制台查看错误
(自动编译)
方案1:webpack的监控模式实现自动编译
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"watch": "webpack ‐‐watch",
"build": "webpack"
},
方案2:webpack‐dev‐server实现自动编译
//step01:安装依赖
npm install ‐‐save‐dev webpack‐dev‐server
//step02:设置配置
devServer: {
//告诉开发服务器﴾dev server﴿,在哪里查找文件
contentBase: './dist'
}
//step03:包的配置
"scripts": {
"start": "webpack‐dev‐server ‐‐open"
}
方案3:webpack-dev-middleware实现自动编译
webpack‐dev‐middleware是一个容器,它可以把 webpack 处理后的文件传递给一个服务器 ﴾server﴿。 webpack‐dev‐server 在内部就是使用了它,同时,它也可以作为一个单独的包来使用,以便进行更多自定义设置来实现更多的需求。
1.安装依赖
npm install ‐‐save‐dev express webpack‐dev‐middleware
2.设置配置
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist'),
publicPath: '/'
}
3.建服务器dev-server.js
const express = require('express');
const webpack = require('webpack');
const webpackDevMiddleware = require('webpack‐dev‐middleware');
const app = express();
const config = require('./webpack.config.js');
const compiler = webpack(config);
// Tell express to use the webpack‐dev‐middleware and use the webpack.config.js
// configuration file as a base.
app.use(webpackDevMiddleware(compiler, {
publicPath: config.output.publicPath
}));
// Serve the files on port 3000.
app.listen(3000, function() {
console.log('Example app listening on port 3000!\n');
});
4.包的配置
"scripts": {
"start": "node dev-server.js"
}
生产环境
为什么要?
开发环境(development)和生产环境(production)的构建目标差异很大。在开发环境中,我们需要具有强大的、具有实时 重新加载﴾live reloading﴿或热模块替换﴾hot module replacement﴿能力的 source map 和 localhost server。而在生产环境 中,我们的目标则转向于关注更小的 bundle,更轻量的 source map,以及更优化的资源,以改善加载时间。由于要遵 循逻辑分离,我们通常建议为每个环境编写彼此独立的 webpack 配置。
如何构建?
遵循不重复原则﴾Don't repeat yourself ‐ DRY﴿,保留一个“通用”配置。为了将这些配置合并在一起,我们将使用一个名为 webpack‐merge 的工具。 通过“通用”配置,我们不必在环境特定﴾environment‐specific﴿的配置中重复代码。——代码压缩
step01:配置文件
//webpack.common.js 通用配置文件
//webpack.pro.js 产品环境文件
//webpack.dev.js 开发环境文件
step02:安装依赖
npm install ‐‐save‐dev webpack‐merge
step03:包的配置
"scripts": {
"start": "webpack‐dev‐server ‐‐open ‐‐config webpack.dev.js",
"build": "webpack ‐‐config webpack.pro.js"
}
step04:运行命令
# 开发时
Npm run start
# 构建时
Npm run build
环境变量
为什么要?
问题01:要在开发和生产构建之间,消除 webpack.config.js 的差异,可能需要环境变量。
什么方法?
怎么设置?
运行命令时传入变量:webpack 命令行环境配置中,通过设置 ‐‐env 可以使你根据需要,传入尽可能多的环境变量。
webpack ‐‐env.NODE_ENV=local ‐‐env.production ‐‐progress –config webpack.config.pro.js
配置文件中访问变量:在 webpack.config.js 文件中可以访问到这些环境变量。
//webpack.config.pro.js
module.exports = env => {
// Use env.<YOUR VARIABLE> here:
console.log('NODE_ENV: ', env.NODE_ENV) // 'local'
console.log('Production: ', env.production) // true
return {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
}
}
}
公共路径
为什么要?
问题01:在开发模式中,我们通常有一个 assets/ 文件夹,它往往存放在和首页一个级别的目录下。这样是挺方便;但是如果在生产环境下,你想把这些静态文件统一使用CDN加载
,那该怎么办?——公共路径
什么方法?
环境变量法:设置了一个名为 ASSET_PATH 的变量。
即时设置法:webpack 提供一个全局变量供你设置,它名 叫 webpack_public_path。
怎么设置?
(环境变量法)
step01:配置文件
//webpack.config.js
import webpack from 'webpack';
// 设置变量
const ASSET_PATH = process.env.ASSET_PATH || '/';
export default {
output: {
publicPath: ASSET_PATH
},
plugins: [
// 该插件帮助我们安心地使用环境变量
new webpack.DefinePlugin({
'process.env.ASSET_PATH': JSON.stringify(ASSET_PATH)
})
]
};
step02:运行命令
(即时设置法)
step01:入口文件
//index.js
__webpack_public_path__ = process.env.ASSET_PATH;
step02:运行命令
构建性能
为什么要?
如何进行?
使用最新
的 webpack 稳定版本
。我们会经常进行性能优化。
保持最新的 Node.js稳定版本
。
保证最新的包管理工具稳定版本
。(npm/yarn)
将 loaders 应用于最少数的必要模块中。因为范围太广,查找耗时。
尽量少使用不同的工具。因为每个额外的 loader/plugin 都有启动时间。
尽量减少
resolve.modules , resolve.extensions , resolve.mainFiles , resolve.descriptionFiles 中类目的数量
,因为他们会增加文件系统调用的次数。
提高解析速度。如果你不使用 symlinks ,可以设置 resolve.symlinks: false ﴾例如 npm link 或者 yarn link ﴿.如果你使用自定义解析 plugins ,并且没有指定 context 信息,可以设置 resolve.cacheWithContext: false 。
减少编译的整体大小,以提高构建性能。尽量保持 chunks 小巧。使用 更少/更小 的库。 在多页面应用程序中使用 CommonsChunksPlugin 。 在多页面应用程序中以 async 模式使用 CommonsChunksPlugin 。 移除不使用的代码。 只编译你当前正在开发部分的代码。
开发环境时:
增量编译。使用 webpack 的监听模式。不要使用其他工具来监听你的文件和调用 webpack 。在监听模式下构建会记录时间戳并将信息传递给编译让缓存失效。 在某些设置中,监听会回退到轮询模式。有许多监听文件会导致 CPU 大量负载。在这些情况下,你可以使 用 watchOptions.poll 来增加轮询的间隔。
在内存中编译。以下几个实用工具通过在内存中进行代码的编译和资源的提供,但并不写入磁盘来提高性能: webpack‐dev‐server webpack‐hot‐middleware webpack‐dev‐middleware
产品环境时:
某些实用工具, plugins 和 loaders 都只能在构建生产环境时才有用。例如,在开发时使用 UglifyJsPlugin 来压缩和修改代码是没有意义的。
UglifyJsPlugin
ExtractTextPlugin
[hash] / [chunkhash]
AggressiveSplittingPlugin
AggressiveMergingPlugin
ModuleConcatenationPlugin
多个编译时 当进行多个编译时,以下工具可以帮助到你: parallel‐webpack : 它允许编译工作在 worker 池中进行。 cache‐loader : 缓存可以在多个编译时之间共享。
离线应用
(渐进式的网络应用程序)
为什么要?
我们应该要实现的是,停止服务器然后刷新,仍然可以查看应用程序 正常运行。
如何进行?
搭建一个服务器
1.安装依赖
npm install http‐server ‐‐save‐dev
2.包的配置
"scripts": {
"start": " http‐server dist"
}
3.启动服务
启动 http‐server,服务目录是 dist
如果你打开浏览器访问 http://localhost:8080 ﴾即 http://127.0.0.1 ﴿,你应该会看到在 dist 目录创建出服务, 并可以访问 webpack 应用程序。如果停止服务器然后刷新,则 webpack 应用程序不再可访问。
//1.安装依赖
npm install workbox‐webpack‐plugin ‐‐save‐dev
//2.配置文件
const path = require('path');
const HtmlWebpackPlugin = require('html‐webpack‐plugin');
const CleanWebpackPlugin = require('clean‐webpack‐plugin');
const WorkboxPlugin = require('workbox‐webpack‐plugin');
plugins: [
new CleanWebpackPlugin(['dist']),
new WorkboxPlugin({
// 这些选项帮助 ServiceWorkers 快速启用
// 不允许遗留任何“旧的” ServiceWorkers
clientsClaim: true,
skipWaiting: true
})
]
//3.使用
import _ from 'lodash';
import printMe from './print.js';
if('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/sw.js').then(registration => {
console.log('SW registered: ', registration);
}).catch(registrationError => {
console.log('SW registration failed: ', registrationError);
});
});
}