cc and abstract.
https://doc.webpack-china.org/guides/development/
2018.1.25 星期四 14:42
一 安装
1.1 前提条件
1.2 本地安装
1.3 全局安装
1.4 最新体验版本
二 起步
2.1 基本安装
2.2 创建一个bundle文件
2.3 模块
2.4 使用一个配置文件
2.5 NPM脚本
2.6 结论
三 管理资源
3.1 安装
3.2 加载CSS
3.3 加载字体
3.4 加载数据
3.5 全局资源
3.6 回退处理
3.7 下一章节指南
3.8 延伸阅读
四 管理输出
4.1 预先准备
4.2 设定HtmlWebpackPlugin
4.3 清理/dist文件夹
4.4 Manifest
4.5 结论
1
2
3
4
5
####
五 开发
5.1 使用source map
5.2 选择一个开发工具
5.3 使用观察模式
5.4 使用 webpack-dev-server
5.5 使用 Webpack-dev-middleware
5.6 调整文本编辑器
5.7 结论
六 模块热替换
6.1 启用 HMR
6.2 通过Node.js API
6.3 问题
6.4 HMR 修改样式表
6.5 其他代码和框架
七 Tree Shaking
tree shaking 是一个术语,通常用于描述移除 JavaScript 上下文中的未引用代码(dead-code)。它依赖于 ES2015 模块系统中的静态结构特性,例如 import 和 export。这个术语和概念实际上是兴起于 ES2015 模块打包工具 rollup。
webpack 2 内置支持 ES2015 模块(别名 harmony modules),并能检测出未使用的模块导出。
7.1 添加一个通用模块
注意,上面的 unused harmony export square 注释。如果你看下面的代码,你会注意到 square 没有被导出,但是,它仍然被包含在 bundle 中。我们将在下一节中解决这个问题。
7.2 精简输出
UglifyJSPlugin
显然,现在整个 bundle 都已经被精简过,但是如果仔细观察,则不会看到 square 函数被引入,但会看到 cube 函数的修改版本(function r(e){return eee}n.a=r)
7.3 警告
请注意,webpack 本身并不会执行 tree-shaking。它需要依赖于像 UglifyJS 这样的第三方工具来执行实际的未引用代码(dead code)删除工作。有些情况下,tree-shaking 可能不会生效。例如,考虑以下模块:
。。
一般来说,当一个工具不能保证某些特定的代码路径(path)不会导致副作用(side-effects)时,即使你确信它不应该存在生成的 bundle 中,但这个代码仍然会保留。常见的情况有:从第三方模块中调用一个函数,webpack 和/或 压缩工具(minifier)无法检查此模块;从第三方模块导入的函数被重新导出,等等。
7.4 结论
为了学会使用 tree shaking,你必须……
- 使用 ES2015 模块语法(即 import 和 export)。
- 引入一个能够删除未引用代码(dead code)的压缩工具(minifier)(例如 UglifyJSPlugin)。
八 生产环境构建
8.1 配置
开发环境(development)和生产环境(production)的构建目标差异很大。在开发环境中,我们需要具有强大的、具有实时重新加载(live reloading)或热模块替换(hot module replacement)能力的 source map 和 localhost server。而在生产环境中,我们的目标则转向于关注更小的 bundle,更轻量的 source map,以及更优化的资源,以改善加载时间。由于要遵循逻辑分离,我们通常建议为每个环境编写彼此独立的 webpack 配置。
8.2 NPM Script
8.3 Minification
注意,虽然 UglifyJSPlugin 是代码压缩方面比较好的选择,但是还有一些其他可选择项。以下有几个同样很受欢迎的插件:
8.4 source map
我们鼓励你在生产环境中启用 source map,因为它们对调试源码(debug)和运行基准测试(benchmark tests)很有帮助。虽然有如此强大的功能,然而还是应该针对生成环境用途,选择一个构建快速的推荐配置(具体细节请查看 devtool)。对于本指南,我们将在生产环境中使用 source-map 选项,而不是我们在开发环境中用到的inline-source-map:
避免在生产中使用 inline- 和 eval-,因为它们可以增加 bundle 大小,并降低整体性能。
8.5 指定环境
许多 library 将通过与 process.env.NODE_ENV 环境变量关联,以决定 library 中应该引用哪些内容。例如,当不处于生产环境中时,某些 library 为了使调试变得容易,可能会添加额外的日志记录(log)和测试(test)。其实,当使用 process.env.NODE_ENV === ‘production’ 时,一些 library 可能针对具体用户的环境进行代码优化,从而删除或添加一些重要代码。我们可以使用 webpack 内置的 DefinePlugin 为所有的依赖定义这个变量:
如果你正在使用像 react 这样的 library,那么在添加此 DefinePlugin 插件后,你应该看到 bundle 大小显著下降。还要注意,任何位于 /src 的本地代码都可以关联到 process.env.NODE_ENV 环境变量,所以以下检查也是有效的:
import { cube } from './math.js';
if (process.env.NODE_ENV !== 'production') {
console.log('Looks like we are in development mode!');
}
8.6 Split CSS
8.7 CLI 替代选项
九 代码分离
代码分离是 webpack 中最引人注目的特性之一。此特性能够把代码分离到不同的 bundle 中,然后可以按需加载或并行加载这些文件。代码分离可以用于获取更小的 bundle,以及控制资源加载优先级,如果使用合理,会极大影响加载时间。
有三种常用的代码分离方法:
- 入口起点:使用 entry 配置手动地分离代码。
- 防止重复:使用 CommonsChunkPlugin 去重和分离 chunk。
- 动态导入:通过模块的内联函数调用来分离代码。
9.1 入口起点
正如前面提到的,这种方法存在一些问题:
- 如果入口 chunks 之间包含重复的模块,那些重复模块都会被引入到各个 bundle 中。
- 这种方法不够灵活,并且不能将核心应用程序逻辑进行动态拆分代码。
以上两点中,第一点对我们的示例来说无疑是个问题,因为之前我们在 ./src/index.js 中也引入过 lodash,这样就在两个 bundle 中造成重复引用。接着,我们通过使用 CommonsChunkPlugin 来移除重复的模块。9.2 防止重复
CommonsChunkPlugin 插件可以将公共的依赖模块提取到已有的入口 chunk 中,或者提取到一个新生成的 chunk。让我们使用这个插件,将之前的示例中重复的 lodash 模块去除:
这里我们使用 CommonsChunkPlugin 之后,现在应该可以看出,index.bundle.js 中已经移除了重复的依赖模块。需要注意的是,CommonsChunkPlugin 插件将 lodash 分离到单独的 chunk,并且将其从 main bundle 中移除,减轻了大小。执行 npm run build 查看效果:
以下是由社区提供的,一些对于代码分离很有帮助的插件和 loaders:
- ExtractTextPlugin: 用于将 CSS 从主应用程序中分离。
- bundle-loader: 用于分离代码和延迟加载生成的 bundle。
- promise-loader: 类似于 bundle-loader ,但是使用的是 promises。
- CommonsChunkPlugin 插件还可以通过使用显式的 vendor chunks 功能,从应用程序代码中分离 vendor 模块。
9.3 动态导入
$_MORE:和ES import,async有关9.4 bundle分析
9.5 下一步
零 懒加载
实际上是先把你的代码在一些逻辑断点处分离开,然后在一些代码块中完成某些操作后,立即引用或即将引用另外一些新的代码块
0.1 示例
button.onclick = e => import(/* webpackChunkName: "print" */ './print').then(module => {
var print = module.default;
print();
});
注意当调用 ES6 模块的 import() 方法(引入模块)时,必须指向模块的 .default 值,因为它才是 promise 被处理后返回的实际的 module 对象。
0.2 框架
许多框架和类库对于如何用它们自己的方式来实现(懒加载)都有自己的建议。这里有一些例子:
- React: Code Splitting and Lazy Loading
*Vue: Lazy Load in Vue using Webpack’s code splitting
一 缓存
1.1 输出文件的文件名
可以看到,bundle 的名称是它内容(通过 hash)的映射。如果我们不做修改,然后再次运行构建,我们的文件名按照期望,依然保持不变。然而,如果我们再次运行,可能会发现情况并非如此:
这也是因为 webpack 在入口 chunk 中,包含了某些样板(boilerplate),特别是 runtime 和 manifest。(译注:样板(boilerplate)指 webpack 运行时的引导代码)
1.2 提取模板
就像我们之前从代码分离了解到的,CommonsChunkPlugin 可以用于将模块分离到单独的文件中。然而 CommonsChunkPlugin 有一个较少有人知道的功能是,能够在每次修改后的构建结果中,将 webpack 的样板(boilerplate)和 manifest 提取出来。通过指定 entry 配置中未用到的名称,此插件会自动将我们需要的内容提取到单独的包中:
const path = require('path');
const webpack = require('webpack');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: './src/index.js',
plugins: [
new CleanWebpackPlugin(['dist']),
new HtmlWebpackPlugin({
title: 'Caching'
}),
new webpack.optimize.CommonsChunkPlugin({
name: 'vender'
}),
new webpack.optimize.CommonsChunkPlugin({
name: 'manifest'
})
],
output: {
filename: '[name].[chunkhash].js',
path: path.resolve(__dirname, 'dist')
}
};
将第三方库(library)(例如 lodash 或 react)提取到单独的 vendor chunk 文件中,是比较推荐的做法,这是因为,它们很少像本地的源代码那样频繁修改。因此通过实现以上步骤,利用客户端的长效缓存机制,可以通过命中缓存来消除请求,并减少向服务器获取资源,同时还能保证客户端代码和服务器端代码版本一致。这可以通过使用新的 entry(入口) 起点,以及再额外配置一个 CommonsChunkPlugin 实例的组合方式来实现:
注意,引入顺序在这里很重要。CommonsChunkPlugin 的 ‘vendor’ 实例,必须在 ‘manifest’ 实例之前引入。
1.3 模块标识符
再次运行构建,然后我们期望的是,只有 main bundle 的 hash 发生变化,然而……
……我们可以看到这三个文件的 hash 都变化了。这是因为每个 module.id 会基于默认的解析顺序(resolve order)进行增量。也就是说,当解析顺序发生变化,ID 也会随之改变。因此,简要概括:
- main bundle 会随着自身的新增内容的修改,而发生变化。
- vendor bundle 会随着自身的 module.id 的修改,而发生变化。
- manifest bundle 会因为当前包含一个新模块的引用,而发生变化。
第一个和最后一个都是符合预期的行为 – 而 vendor 的 hash 发生变化是我们要修复的。幸运的是,可以使用两个插件来解决这个问题。第一个插件是 NamedModulesPlugin,将使用模块的路径,而不是数字标识符。虽然此插件有助于在开发过程中输出结果的可读性,然而执行时间会长一些。第二个选择是使用 HashedModuleIdsPlugin,推荐用于生产环境构建:1.4 结论
1.5 译注
二 创建Library
2.1 创建一个library
2.2 基本配置
2.3 外部化lodash
2.4 外部扩展的限制
2.5 暴露library
2.6 最终步骤
$_EXCLUDE:没有创建库,所以无法配置;目前不创建库
三 Shimming
2018.2.9 星期五 18:30
webpack 编译器(compiler)能够识别遵循 ES2015 模块语法、CommonJS 或 AMD 规范编写的模块。然而,一些第三方的库(library)可能会引用一些全局依赖(例如 jQuery 中的 $)。这些库也可能创建一些需要被导出的全局变量。这些“不符合规范的模块”就是 shimming 发挥作用的地方。
我们不推荐使用全局的东西!在 webpack 背后的整个概念是让前端开发更加模块化。也就是说,需要编写具有良好的封闭性(well contained)、彼此隔离的模块,以及不要依赖于那些隐含的依赖模块(例如,全局变量)。请只在必要的时候才使用本文所述的这些特性。
shimming 另外一个使用场景就是,当你希望 polyfill 浏览器功能以支持更多用户时。在这种情况下,你可能只想要将这些 polyfills 提供给到需要修补(patch)的浏览器(也就是实现按需加载)。
下面的文章将向我们展示这两种用例。
3.1 shimming全局变量
plugins: [
new webpack.ProvidePlugin({
_: 'lodash',
join: ['lodash', 'join']
})
]
我们还可以使用 ProvidePlugin 暴露某个模块中单个导出值,只需通过一个“数组路径”进行配置
3.2 细粒度shimming
一些传统的模块依赖的 this 指向的是 window 对象
module: {
rules: [
{
test: require.resolve(‘index.js’),
use: ‘imports-loader?this=>window’
}
]
},
$_PS: shimming的第一种情况。如,bootstrap需要jQuey,也是这种方式
3.3 全局exports
你可能从来没有在自己的源码中做过这些事情,但是你也许遇到过一个老旧的库(library),和上面所展示的代码类似。在这个用例中,我们可以使用 exports-loader,将一个全局变量作为一个普通的模块来导出。例如,为了将 file 导出为 file 以及将 helpers.parse 导出为 parse,做如下调整:
module: {
rules: [
{
test: require.resolve('globals.js'),
use: 'exports-loader?file,parse=helpers.parse'
}
]
},
$_PS: 还是第一种情况。比如jQuery1.11.3,或者其他老旧库,将一个全局变量作为一个普通的模块来导出
3.4 加载polyfills
有很多方法来载入 polyfills。例如,要引入 babel-polyfill 我们只需要如下操作:
npm install --save babel-polyfill
然后使用 import 将其添加到我们的主 bundle 文件:
请注意,我们没有将 import 绑定到变量。这是因为只需在基础代码(code base)之外,再额外执行 polyfills,这样我们就可以假定代码中已经具有某些原生功能。
polyfills 虽然是一种模块引入方式,但是并不推荐在主 bundle 中引入 polyfills,因为这不利于具备这些模块功能的现代浏览器用户,会使他们下载体积很大、但却不需要的脚本文件。
让我们把 import 放入一个新文件,并加入 whatwg-fetch polyfill:1
2
3
4
5
6
7
8
9
10
11
12
13<script>
var modernBrowser = (
'fetch' in window &&
'assign' in Object
);
if ( !modernBrowser ) {
var scriptElement = document.createElement('script');
scriptElement.async = false;
scriptElement.src = '/polyfills.bundle.js';
document.head.appendChild(scriptElement);
}
</script>
3.5 深度优化
babel-preset-env package 使用 browserslist 来转译那些你浏览器中不支持的特性。这里预设了 useBuiltIns 选项,默认值是 false,能将你的全局 babel-polyfill 导入方式,改进为更细粒度的 import 格式:
import 'core-js/modules/es7.string.pad-start';
import 'core-js/modules/es7.string.pad-end';
import 'core-js/modules/web.timers';
import 'core-js/modules/web.immediate';
import 'core-js/modules/web.dom.iterable';
$_PS: 开发库的时候使用。项目中还有一些API需要调用,自定义的对象,全局对象的API就不可以使用了Array.prototype.forEach
。开发中用上面babel-loader, useBuiltIns 设为true
3.6 Node内置
像 process 这种 Node 内置模块,能直接根据配置文件(configuration file)进行正确的 polyfills,且不需要任何特定的 loaders 或者 plugins。查看 node 配置页面获取更多信息。
3.7 其他工具
还有一些其他的工具能够帮助我们处理这些老旧的模块。
- script-loader 会在全局上下文中对代码进行取值,类似于通过一个 script 标签引入脚本。在这种模式下,每一个标准的库(library)都应该能正常运行。require, module 等的取值是 undefined。
当使用 script-loader 时,模块将转化为字符串,然后添加到 bundle 中。它不会被 webpack 压缩,所以你应该选择一个 min 版本。同时,使用此 loader 将不会有 devtool 的支持。
这些老旧的模块如果没有 AMD/CommonJS 规范版本,但你也想将他们加入 dist 文件,你可以使用 noParse 来标识出这个模块。这样就能使 webpack 将引入这些模块,但是不进行转化(parse),以及不解析(resolve) require() 和 import 语句。这个实践将提升构建性能。
例如 ProvidePlugin,任何需要 AST 的功能,都无法正常运行。
最后,有一些模块支持不同的模块格式,比如 AMD 规范、CommonJS 规范和遗留模块(legacy)。在大多数情况下,他们首先检查define,然后使用一些古怪的代码来导出一些属性。在这些情况下,可以通过imports-loader设置 define=>false 来强制 CommonJS 路径。
$_PS: 什么鬼,imports-loader之前不是用了吗
译者注:shim 是一个库(library),它将一个新的 API 引入到一个旧的环境中,而且仅靠旧的环境中已有的手段实现。polyfill 就是一个用在浏览器 API 上的 shim。我们通常的做法是先检查当前浏览器是否支持某个 API,如果不支持的话就加载对应的 polyfill。然后新旧浏览器就都可以使用这个 API 了。
$__PS: 下面这些plugin,loader,configuration 不是一个概念,注意使用
- ProvidePlugin:把模块作为应用程序中的一个全局变量。
- imports-loader: 模块需要某个依赖时使用,比如this指向window,bootstrap需要jQuery
- exports-loader:将一个全局变量作为一个普通的模块来导出,需要import
- expose-loader: 暴露一个全局变量,不需要import。注意和ProvidePlugin的区别:??ProvidePlugin可以暴露jQuery1.11.3的全局变量吗
The expose loader adds modules to the global object. This is useful for debugging, or supporting libraries that depend on libraries in globals.
- script-loader:估计和expose差不多
- noParse:expose,script,还有exports的模块应该可以用module.noParse配置
noParse:防止 webpack 解析那些任何与给定正则表达式相匹配的文件。忽略的文件中不应该含有 import, require, define 的调用,或任何其他导入机制。忽略大型的 library 可以提高构建性能。
module.externals :有人说不符合webpack模块管理了??
防止将某些 import 的包(package)打包到 bundle 中,而是在运行时(runtime)再去从外部获取这些扩展依赖(external dependencies)。
例如,从 CDN 引入 jQuery,而不是把它打包:DllPlugin:??如果不用externals排除,用该方法剥离不经常改动的第三方依赖
使用 DllPlugin 将更改不频繁的代码进行单独编译。这将改善引用程序的编译速度,即使它增加了构建过程的复杂性。
DLLPlugin 和 DLLReferencePlugin 用某种方法实现了拆分 bundles,同时还大大提升了构建的速度。- CommonsChunkPlugin:提取公共子模块,提供缓存。属于代码分离–防止重复。公共模块提出来,利用缓存,没有修改的部分,hashID不会更改
CommonsChunkPlugin,是一个可选的用于建立一个独立文件(又称作 chunk)的功能,这个文件包括多个入口 chunk 的公共模块。通过将公共模块拆出来,最终合成的文件能够在最开始的时候加载一次,便存起来到缓存中供后续使用。这个带来速度上的提升,因为浏览器会迅速将公共的代码从缓存中取出来,而不是每次访问一个新页面时,再去加载一个更大的文件。
- ExtractTextPlugin:也属于代码分离,用于将 CSS 从主应用程序中分离
- UglifyJSPlugin:Tree Shaking–精简输出
2.9 20:46
四 TypeScript
4.1 基础安装
4.2 Loader
4.3 source map
4.4 使用第三方库
4.5 导入其他资源
4.6 构建性能
$_EXCLUDE:typescript语法没有用到
五 渐进式网络应用程序
5.1 现在我们并没有离线环境
5.2 天津Workbox
5.3 注册我们的Service Worker
5.4 结论
$_EXCLUDE:PWA,最后构建
六 迁移到新版本
$_EXCLUDE:没有迁移
七 使用环境变量
$_PROBLEM:没实际实例,看不到目的,所以操作不明
八 构建性能
8.1 常规
1 保持版本最新
2 Loaders
3 Bootstrap
4 解析
5 Dlls
6 Samller = Faster
7 Worker Pool
8 持久化缓存
9 自定义 plugins/loaders
8.2 Development
1 增量编译
2 在内存中编译
3 Devtool
4 避免在生产环境下才会用
5 最小化入口chunk
8.3 Production
1 多个编译时
2 Source Maps
8.4 工具相关问题
1 Babel
2 Typescript
3 Sass
九 开发-Vagrant
9.1 项目配置
9.2 启动服务器
9.3 配合nginx的高级用法
9.4 小结
$_EXCLUDE:不需要
零 管理依赖
0.1 带表达式的require语句
0.2 require.context
0.3 上下文模块
$_MORE:不明白
一 公共路径(Public Path)
webpack 提供一个非常有用的配置,该配置能帮助你为项目中的所有资源指定一个基础路径。它被称为公共路径(publicPath)。
1.1 示例
1 在构建项目时设置路径值
在开发模式中,我们通常有一个 assets/ 文件夹,它往往存放在和首页一个级别的目录下。这样是挺方便;但是如果在生产环境下,你想把这些静态文件统一使用CDN加载,那该怎么办?
想要解决这个问题,你可以使用有着悠久历史的环境变量。比如说,我们设置了一个名为 ASSET_PATH 的变量:
import webpack from 'webpack';
// 如果预先定义过环境变量,就将其赋值给`ASSET_PATH`变量,否则赋值为根目录
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)
})
]
};
2 即时设定路径值
另一个可能出现的情况是,我们需要即时设置公共路径。webpack 提供一个全局变量供你设置,它名叫__webpack_public_path__
。所以在你的项目入口,你可以简单地设置如下:
__webpack_public_path__ = process.env.ASSET_PATH;
一切设置完成。因为我们已经在我们的配置项中使用了DefinePlugin, process.env.ASSET_PATH 就已经被定义了, 所以让我们能够安心地使用它了。
警告:请注意,如果你在入口文件中使用 ES6 模块导入,则在导入后对 webpack_public_path 进行赋值。在这种情况下,你必须将公共路径(public path)赋值移至自己的专属模块,然后将其导入到你的 entry.js 之上:
// entry.js
import './public-path';
import './app';
$_MORE:警告是什么意思??
二 集成(Intergrations)
2.1 NPM Scripts
2.2 Grunt
2.3 Gulp
2.4 Mocha
2.5 Karma
$_EXCLUDE:不需要集成,单独使用