1. 模块热替换
1.1 启用 HMR
启用此功能需要更新 webpack-dev-server 的配置,和使用 webpack 内置的 HMR 插件。
webpack.config.js
module.exports = {
// ...
devServer: {
// ...
hot: true
}
};
worker.js
if (module.hot) {
module.hot.accept('./print.js', function() {
console.log('Accepting the updated printMe module!');
printMe();
})
}
1.2 HMR 修改样式表
CSS 的模块热更新,借助于 style-loader
。
2. Tree shaking
2.1 sideEffects
false
,所有文件代码都没有副作用- 数组,指定文件代码是有副作用
「副作用」的定义是,在导入时会执行特殊行为的代码,而不是仅仅暴露一个 export 或多个 export。举例说明,例如 polyfill,它影响全局作用域,并且通常不提供 export。有副作用的不能tree sharking!
注意,任何导入的文件都会受到 tree shaking 的影响。这意味着,如果在项目中使用类似 css-loader
并导入 CSS 文件,则需要将其添加到 side effect 列表中,以免在生产模式中无意中将它删除。
{
"name": "your-project",
"sideEffects": [
"./src/some-side-effectful-file.js",
"*.css"
]
}
还可以在
module.rules
配置选项 中设置"sideEffects"
2.2 压缩输出
从 webpack 4 开始,也可以通过 "mode"
配置选项轻松切换到压缩输出,只需设置为 "production"
。
webpack.config.js
module.exports = {
// ...
mode: "production"
};
2.3 tree sharking 条件
- 使用 ES2015 模块语法(即
import
和export
) - 在项目
package.json
文件中,添加一个sideEffects
入口 - 引入一个能够删除未引用代码(dead code)的压缩工具(minifier)(例如
UglifyJSPlugin
, webpack4 开始可以设置mode
:"production"
来代替)
3. devtool
生产环境,在使用
uglifyjs-webpack-plugin
时,你必须提供sourceMap:true
选项来启用 source map 支持。鼓励你在生产环境中启用 source map,因为它们对调试源码(debug)和运行基准测试(benchmark tests)很有帮助。
3.1 对于开发环境
eval
- 映射到转换后的代码eval-source-map
- 行数能够正确映射,会映射到原始代码中。cheap-eval-source-map
- 类似eval-source-map
,每个模块使用eval()
执行。这是 "cheap(低开销)" 的 source map,因为它没有生成列映射(column mapping),只是**映射行数。它会忽略源自 loader 的 source map,**并且仅显示转译后的代码,就像eval
devtool。cheap-module-eval-source-map
- 类似cheap-eval-source-map
,并且,在这种情况下,源自 loader 的 source map 会得到更好的处理结果。然而,loader source map 会被简化为每行一个映射(mapping)。
3.2 对于生产环境
这些选项通常用于生产环境中:
-
(none)
(省略devtool
选项) - 不生成 source map。这是一个不错的选择。 -
source-map
- 整个 source map 作为一个单独的文件生成。它为 bundle 添加了一个引用注释,以便开发工具知道在哪里可以找到它。你应该将你的服务器配置为,不允许普通用户访问 source map 文件!
-
hidden-source-map
- 与source-map
相同,但不会为 bundle 添加引用注释。如果你只想 source map 映射那些源自错误报告的错误堆栈跟踪信息,但不想为浏览器开发工具暴露你的 source map,这个选项会很有用。你不应将 source map 文件部署到 web 服务器。而是只将其用于错误报告工具。
-
nosources-source-map
- 创建的 source map 不包含sourcesContent(源代码内容)
。它可以用来映射客户端上的堆栈跟踪,而无须暴露所有的源代码。你可以将 source map 文件部署到 web 服务器。这仍然会暴露反编译后的文件名和结构,但它不会暴露原始代码。
3.3 特定场景
以下选项对于开发环境和生产环境并不理想。他们是一些特定场景下需要的,例如,针对一些第三方工具。
inline-source-map
- source map 转换为 DataUrl 后添加到 bundle 中。cheap-source-map
- 没有列映射(column mapping)的 source map,忽略 loader source map。inline-cheap-source-map
- 类似cheap-source-map
,但是 source map 转换为 DataUrl 后添加到 bundle 中。cheap-module-source-map
- 没有列映射(column mapping)的 source map,将 loader source map 简化为每行一个映射(mapping)。inline-cheap-module-source-map
- 类似cheap-module-source-map
,但是 source mapp 转换为 DataUrl 添加到 bundle 中。
4. 指定环境变量
4.1 基本使用
许多 library 将通过与 process.env.NODE_ENV
环境变量关联,以决定 library 中应该引用哪些内容。例如,当不处于生产环境中时,某些 library 为了使调试变得容易,可能会添加额外的日志记录(log)和测试(test)。其实,**当使用 process.env.NODE_ENV === 'production'
时,一些 library 可能针对具体用户的环境进行代码优化,从而删除或添加一些重要代码。**我们可以使用 webpack 内置的 DefinePlugin
为所有的依赖定义这个变量:
NODE_ENV 属性:
- 这个变量并不是 pocess.env 直接就有的,而是通过设置得到的。
- 可以通过判断这个变量区分开发环境或生产环境。
webpack.prod.js
const webpack = require('webpack');
const merge = require('webpack-merge');
const common = require('./webpack.common.js');
module.exports = merge(common, {
plugins: [
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify('production')
})
]
});
技术上讲,NODE_ENV
是一个由 Node.js 暴露给执行脚本的系统环境变量。通常用于决定在开发环境与生产环境(dev-vs-prod)下,服务器工具、构建脚本和客户端 library 的行为。然而,与预期不同的是,无法在构建脚本 webpack.config.js
中,将 process.env.NODE_ENV
设置为 "production"
,请查看 #2537。
例如 process.env.NODE_ENV === 'production' ? '[name].[hash].bundle.js' : '[name].bundle.js'
这样的条件语句,在 webpack 配置文件中,无法按照预期运行。
任何位于
/src
的本地代码都可以关联到 process.env.NODE_ENV 环境变量,所以以下检查也是有效的:
src/worker.js
import { cube } from './math.js';
if (process.env.NODE_ENV !== 'production') {
console.log('Looks like we are in development mode!');
}
4.2疑问?
为什么 webpack官网 里面说:
process.env.NODE_ENV === 'production' ? '[name].[hash].bundle.js' : '[name].bundle.js'`这样的条件语句,在 webpack 配置文件中,无法按照预期运行。
但是 MiniCssExtractPlugin 官网 里面可以如下使用:
const devMode = process.env.NODE_ENV !== 'production'
4.3 解惑
- 在初始状态下 " 配置文件 " 和 " src 下面的文件 " 输出的
process.env.NODE_ENV
值都是undefined
。 - webpack config文件中定义的变量是为了你将要打包的文件中用的。配置如下:
// 方式一
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify('production')
})
// 方式二
{
mode: 'production'
}
只设置
NODE_ENV
,则会自动设置mode
。
- 如何在
webpack
配置文件里获取NODE_ENV
的值呢,这样就可以根据不同的值定义相关的参数了,如上所述,答案是:corss-env
,在package.json里增加如下配置:
"scripts": {
"build-cross-env":"cross-env NODE_ENV=production webpack"
}
通过
cross-env NODE_ENV=production
,信息传递给了webpack的配置文件, src文件下面不能访问。
5. 代码分离
5.1 概述
把代码分离到不同的 bundle 中,然后可以按需加载或并行加载这些文件。代码分离可以用于获取更小的 bundle,以及控制资源加载优先级,如果使用合理,会极大影响加载时间。
有三种常用的代码分离方法:
- 入口起点:使用
entry
配置手动地分离代码。 - 防止重复:使用
CommonsChunkPlugin
去重和分离 chunk。 - 动态导入:通过模块的内联函数调用来分离代码。
5.2 动态导入(dynamic imports)
当涉及到动态代码拆分时,webpack 提供了两个类似的技术。对于动态导入,第一种,也是优先选择的方式是,使用符合 ECMAScript 提案 的 import()
语法。第二种,则是使用 webpack 特定的 require.ensure
。让我们先尝试使用第一种……
import()
调用会在内部用到 promises。如果在旧有版本浏览器中使用import()
,记得使用 一个 polyfill 库(例如 es6-promise 或 promise-polyfill),来 shimPromise
。
worker.js
// 方式一
const _ = await import(/* webpackChunkName: "lodash" */ 'lodash');
// 方式二
import(/* webpackChunkName: "print" */ './print').then(module => {
var print = module.default;
print();
});
注意当调用 ES6 模块的
import()
方法(引入模块)时,必须指向模块的.default
值,因为**它才是 promise 被处理后返回的实际的module
对象*。*
webpack.config.js
output: {
filename: '[name].bundle.js',
chunkFilename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist')
}
5.3 bundle 分析
6. 缓存
6.1 hash、chunkhash和contenthash三者的区别
hash
跟整个webpack构建项目相关的,每次项目构建hash对应的值都是不同的,即使项目文件没有做**“任何修改”**。
其实是有修改的,因为每次webpack打包编译都会注入webpack的运行时代码,导致整个项目有变化,所以每次hash值都会变化的。
chunkhash
**根据不同的入口文件(Entry)进行依赖文件解析、构建对应的chunk,生成对应的hash值。**在生产环境里把一些公共库和程序入口文件区分开,单独打包构建,采用chunkhash的方式生成hash值,那么只要不改动公共库的代码,就可以保证其hash值不会受影响。
对css使用了chunkhash之后,它与依赖它的chunk共用chunkhash,测试后会发现,css与js文件名的chunkhash值是一样的,JS
和 CSS
相互影响。
css文件最好使用contenthash。
contenthash
contenthash表示由文件内容产生的hash值,内容不同产生的contenthash值也不一样。
6.2 实现图片/字体的缓存
对于图片、字体等静态资源,生成对应的文件hash值是由对应的file-loader
来计算的。
那么这些静态文件的hash值使用的是什么hash值呢?
其实就是hash
属性值。此hash非webpack每次项目构建的hash,它是***由file-loader根据文件内容计算出来的,不是webpack构建的hash***。
7. 多种webpack配置类型(configuration types)
7.1 导出为一个函数
module.exports = function(env, argv) {
return {
mode: env.production ? 'production' : 'development',
devtool: env.production ? 'source-maps' : 'eval',
plugins: [
new webpack.optimize.UglifyJsPlugin({
compress: argv['optimize-minimize'] // 只有传入 -p 或 --optimize-minimize
})
]
};
};
当 webpack 配置对象导出为一个函数时,可以向起传入一个"环境对象(environment)"。也可以通过 [指定环境变量](#4. 指定环境变量) 中的 cross-env NODE_ENV=production webpack"
配置
webpack --env.NODE_ENV=local --env.production --progress
如果设置
env
变量,却没有赋值,--env.production
默认将--env.production
设置为true
。还有其他可以使用的语法。有关详细信息,请查看 webpack CLI 文档。
7.2 导出一个 Promise
webpack 将运行由配置文件导出的函数,并且等待 Promise 返回。便于需要异步地加载所需的配置变量。
module.exports = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({
entry: './app.js',
/* ... */
})
}, 5000)
})
}
7.3 导出一个配置对象
7.4 导出数组,多个配置对象
8. webpack-dev-server的刷新模式inline和iframe详解
iframe
- 在网页中嵌入了一个
iframe
,将我们自己的应用注入到这个iframe
当中去。 - 在页面头部有一个提示框,用于显示构建过程的状态信息。
- 加载了
live.bundle.js
文件,其不但创建了iframe
标签,同时包含socket.io
的client
代码,以和webpack-dev-server
进行websocket
通讯,从而完成自动编译打包、页面自动刷新的功能。
inline
- 构建消息在浏览器控制台显示。
socket.io
的client
代码被打包进了你的包(bundle
)中,以此来与webpack-dev-server
进行websocket
通讯,从而完成自动编译打包、页面自动刷新的功能。- 但是,每一个入口文件都会被插入上述的一段脚本,使得打包后的
bundle
文件很臃肿。
总结
Iframe mode
和Inline mode
最后达到的效果都是一样的,都是监听文件的变化,然后再将编译后的文件推送到前端,完成页面的reload
的。- 通过设置
devServer.inline
切换两种模式,默认为inline
模式。 - 当使用HMR功能时,推荐使用
inline mode
。
9. 模式
记住,只设置
NODE_ENV
,则不会自动设置mode
。
9.1 用法
只在配置中提供 mode
选项:
module.exports = {
mode: 'production'
};
或者从 CLI 参数中传递:
webpack --mode=production
9.2 production
会将
process.env.NODE_ENV
的值设为production
。启用FlagDependencyUsagePlugin
,FlagIncludedChunksPlugin
,ModuleConcatenationPlugin
,NoEmitOnErrorsPlugin
,OccurrenceOrderPlugin
,SideEffectsFlagPlugin
和UglifyJsPlugin
.
mode: production
// webpack.production.config.js
module.exports = {
+ mode: 'production',
- plugins: [
- new UglifyJsPlugin(/* ... */),
- new webpack.DefinePlugin({ "process.env.NODE_ENV": JSON.stringify("production") }),
- new webpack.optimize.ModuleConcatenationPlugin(),
- new webpack.NoEmitOnErrorsPlugin()
- ]
}
9.3 development
会将
process.env.NODE_ENV
的值设为development
。启用NamedChunksPlugin
和NamedModulesPlugin
。
mode: development
// webpack.development.config.js
module.exports = {
+ mode: 'development'
- plugins: [
- new webpack.NamedModulesPlugin(),
- new webpack.DefinePlugin({ "process.env.NODE_ENV": JSON.stringify("development") }),
- ]
}
10. output
10.1 output.library
对于用途广泛的 library,我们希望它能够兼容不同的环境,例如 CommonJS,AMD,Node.js 或者作为一个全局变量。为了让你的 library 能够在各种用户环境(consumption)中可用,需要在 output
中添加 library
属性。
为了让 library 和其他环境兼容,还需要在配置文件中添加 libraryTarget
属性。这是可以控制 library 如何以不同方式暴露的选项,output.libraryTarget
的默认选项是 var
。
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'webpack-numbers.js',
library: 'webpackNumbers'
library: 'webpackNumbers',
libraryTarget: 'umd'
},
10.2 output.publicPath 和 devServer.publicPath
output.publicPath 和 devServer.publicPath 的区别
devServer.publicPath 的意义就是决定外部能以怎样的路径通过 devServer来访问构建在内存中的文件,这个字段未显式设定时,则会去沿用 output.publicPath
字段的显式值(如果output.publicPath有值的话,否则就用自己的 default
值)。output.publicPath 的意义是用来为构建的文件生成满足特定需求的前缀,并将这个前缀提供给需要的 resolver、plugin 或者其他的配置字段。
HtmlWebpackPlugin 中的filename 也会依赖于public.publicPath
参考博客-你一定能看懂的关于 devServer.publicPath、output.publicPath 和 output.path 的意义的阐述
10.3 output.path
这个字段只在 production
配置下有效,如果你去试一下的话,你会发现在 development
配置下,无论你给把它配置成什么值(前提必须是一个合规的绝对地址),它都不会对你访问开发时构建在内存中的文件产生影响,所以 development
配置下,直接忽略就好了。另外,它的 default
值是 `path.resolve(__dirname, './dist')
11. webpack.optimize.ModuleConcatenationPlugin
过去 webpack 打包时的一个取舍是将 bundle 中各个模块单独***打包成闭包***。这些打包函数使你的 JavaScript 在浏览器中***处理的更慢***。相比之下,一些工具像 Closure Compiler 和 RollupJS 可以提升(hoist)或者***预编译所有模块到一个闭包中***,提升你的代码在浏览器中的执行速度。这个插件会在 webpack 中实现以上的预编译功能。
12. devServer
devServer
构建的文件是在内存里的,而非你电脑的磁盘上,但是如果内存中找不到想要的文件时,devServer
会根据文件的路径尝试去电脑的磁盘上找,如果这样还找不到才会404
。开发时在内存和
contentBase
下真实的磁盘路径中存在着同样文件名的文件,那么devServer
返回的是内存的那个
12.1 devServer.historyApiFallback
historyApiFallback: {
rewrites: [
{ from: /.*/, to: path.posix.join(devConfig.assetsPublicPath, 'index.html') },
],
},
当使用 HTML5 History API 时,任意的 404
响应都可能需要被替代为 index.html
。通过传入以下启用:
historyApiFallback: true
通过传入一个对象,比如使用 rewrites
这个选项,此行为可进一步地控制:
historyApiFallback: {
rewrites: [
{ from: /^\/$/, to: '/views/landing.html' },
{ from: /^\/subpage/, to: '/views/subpage.html' },
{ from: /./, to: '/views/404.html' }
]
}
12.2 devServer.publicPath
决定外部能够以什么样的路径访问到构建的文件。
与 output.publicPath 的关系 [查看](#10.2 output.publicPath 和 devServer.publicPath)
13. VUE-CLI 中常见配置
13.1 .babelrc文件
这个文件放在根目录下面设置转码规则的。例如要想在代码中使用es6,就要在这个文件中配置"presets": ["es2015"]
。在项目代码中要使用jsx语法除了安装babel-plugin-transform-vue-jsx插件之外,还要配置"plugins": ["transform-runtime", "transform-vue-jsx"]
。
13.2 .editorconfig文件
这个文件配置编辑器的编码风格
13.3 .eslintrc
配置 ESLint
可以通过以下三种方式配置 ESLint:
- 使用 .eslintrc 文件(支持 JSON 和 YAML 两种语法);
- 在 package.json 中添加 eslintConfig 配置块;
- 直接在代码文件中定义。
.eslintrc 文件示例:
{
"env": {
"browser": true,
},
"globals": {
"angular": true,
},
"rules": {
"camelcase": 2,
"curly": 2,
"brace-style": [2, "1tbs"],
"quotes": [2, "single"],
"semi": [2, "always"],
"space-in-brackets": [2, "never"],
"space-infix-ops": 2,
}
}
放在项目根目录,则会应用到整个项目;如果子目录中也包含 .eslintrc 文件,则子目录会忽略根目录的配置文件,应用该目录中的配置文件。这样可以方便地对不同环境的代码应用不同的规则。
13.4 .eslintignore文件
想要引入三方js库,但是这些库不符合eslint规范,可以在这个文件里忽略掉,例如:
build/*.js
config/*.js
static
13.5 .gitignore文件
这个文件用于配置不需要加入版本管理的文件,例如:
.DS_Store
node_modules/
npm-debug.log
test/unit/coverage
test/e2e/reports
selenium-debug.log
.idea
/clear
/src/modules/cache.js
13.6 .browserslist
在不同前端工具之间共享目标浏览器和Node.js版本的配置。
使用方法
(1) package.json (推荐)
{
"browserslist": [
"last 1 version",
"> 1%",
"maintained node versions",
"not dead"
]
}
(2) .browserslistrc
# Browsers that we support
last 1 version
> 1%
maintained node versions
not dead
Browserslist 的数据都是来自Can I Use的。如果你想知道配置语句的查询结果可以使用online demo
13.7 .postcssrc.js
只是多种配置方式的一种
module.exports = {
"plugins": {
"postcss-import": {},
"postcss-url": {},
// to edit target browsers: use "browserslist" field in package.json
"autoprefixer": {}
}
}
14. eslint
15. watch 和 watchOption
webpack-dev-server 和 webpack-dev-middleware 里 Watch 模式默认开启。
16. SplitChunksPlugin
默认配置即是我们推荐的web最佳实践,但是你项目的最佳策略根据项目类型可能会有所不同
默认将所有来源于node_modules
的模块分配到叫做venders
的缓存组,所有引用超过两次的模块分配到default
缓存组.