Open kangyana opened 1 year ago
生产环境,在使用 uglifyjs-webpack-plugin
时,你必须提供 sourceMap:true
选项来启用 source map 支持。
鼓励你在生产环境中启用 source map,因为它们对调试源码(debug)和运行基准测试(benchmark tests)很有帮助。
eval
:映射到转换后的代码eval-source-map
:行数能够正确映射,会映射到原始代码中。cheap-eval-source-map
:类似 eval-source-map
,每个模块使用 eval()
执行。
这是 低开销 的 source map,因为它没有生成列映射,只是映射行数。
它会忽略源自 loader
的 source map,并且仅显示转译后的代码,就像 eval devtool
。cheap-module-eval-source-map
:类似 cheap-eval-source-map
。
并且,在这种情况下,源自 loader
的 source map 会得到更好的处理结果。
然而,loader
source map 会被简化为每行一个映射。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 不包含 源代码内容。
它可以用来映射客户端上的堆栈跟踪,而无须暴露所有的源代码。
你可以将 source map 文件部署到 web 服务器。
这仍然会暴露反编译后的文件名和结构,但它不会暴露原始代码。以下选项对于开发环境和生产环境并不理想。 他们是一些特定场景下需要的,例如,针对一些第三方工具。
inline-source-map
:source map 转换为 DataUrl 后添加到 bundle
中。cheap-source-map
:没有列映射的 source map,忽略 loader
source map。inline-cheap-source-map
:类似 cheap-source-map
,但是 source map 转换为 DataUrl 后添加到 bundle
中。cheap-module-source-map
:没有列映射的 source map,将 loader
source map 简化为每行一个映射。inline-cheap-module-source-map
:类似 cheap-module-source-map
,但是 source map 转换为 DataUrl 添加到 bundle
中。NODE_ENV
属性:
这个变量并不是 pocess.env
直接就有的,而是通过设置得到的。
可以通过判断这个变量区分开发环境或生产环境。
我们可以使用 webpack 内置的 DefinePlugin
为所有的依赖定义这个变量:
// 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')
})
]
});
任何位于 /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!');
}
在 package.json
配置 corss-env
:
"scripts": {
"build-prod":"cross-env NODE_ENV=production webpack"
}
通过 cross-env NODE_ENV=production
,信息传递给了 webpack 的配置文件, src 文件下面不能访问。
把代码分离到不同的 bundle 中,然后可以 按需加载或并行加载 这些文件。 代码分离可以用于获取 更小的 bundle,以及 控制资源加载优先级,如果使用合理,会极大缩小加载时间。
有三种常用的代码分离方法:
entry
配置手动地分离代码。CommonsChunkPlugin
去重和分离 chunk。当涉及到动态代码拆分时,webpack 提供了两个类似的技术:
import() 语法
。优先选择的方式。require.ensure
。已被 import()
取代。重点讲第一种。 worker.js
// 方式一
const _ = await import(/* webpackChunkName: "lodash" */ 'lodash');
// 方式二
import(/* webpackChunkName: "print" */ './print').then(module => {
var print = module.default;
// ...
});
注意:当调用 ES6 模块的 import()
方法时,必须指向模块的 .default
值,因为它才是 promise 被处理后返回的实际的 module
对象。
webpack.config.js
output: {
filename: '[name].bundle.js',
chunkFilename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist')
}
介绍几种常用分析工具:
在 webpack 中有时需要使用 hash 来做静态资源实现增量更新方案之一,文件名的 hash 值可以有三种 hash 生成方式,每一种都有不同应用场景,那么三者有何区别呢?
hash 一般是结合CDN缓存来使用,通过 webpack 构建之后,生成对应文件名自动带上对应的 MD5 值。 如果文件内容发生改变的话,那么对应文件 hash 值也会改变,对应的 HTML 引用的 URL 地址也会改变, 触发 CDN 服务器从原服务器上拉取对应数据,进而更新本地缓存。 但是实际使用时,这三种 hash 计算还是有一定区别。
hash
是跟整个项目的构建相关,构建生成的文件 hash 值都是一样的,所以 hash
计算是跟整个项目的构建相关。
同一次构建过程中生成的 hash
都是一样的,只要项目里有文件更改,整个项目构建的 hash 值都会更改。
如果出口是 hash
,那么一旦针对项目中任何一个文件的修改,都会构建整个项目,重新获取 hash 值,缓存的目的将失效。
采用 hash
计算的话,每一次构建后生成的 hash 值都不一样,即使文件内容压根没有改变。
这样子是没办法实现缓存效果,我们需要另一种 hash 值计算方法,即 chunkhash
。
chunkhash
和 hash
不一样,它根据不同的入口文件(Entry)进行依赖文件解析、构建对应的 chunk,生成对应的 hash 值。
我们在生产环境里把一些公共库和程序入口文件区分开,单独打包构建,接着我们采用 chunkhash
的方式生成 hash 值,那么只要我们不改动公共库的代码,就可以保证其 hash 值不会受影响。
由于采用 chunkhash
,所以项目主入口文件 main.js
及其对应的依赖文件 main.css
由于被打包在同一个模块,
所以共用相同的 chunkhash
,但是公共库由于是不同的模块,所以有单独的 chunkhash
。
这样子就保证了在线上构建时只要文件内容没有更改就不会重复构建。
entry:{
main: path.join(__dirname,'./main.js'),
vendor: ['vue']
},
output:{
path:path.join(__dirname,'./dist'),
publicPath: '/dist/',
filname: 'bundle.[chunkhash].js'
}
contenthash
表示由文件内容产生的 hash 值,内容不同产生的 contenthash
值也不一样。
在项目中,通常做法是把项目中 css 都抽离出对应的 css 文件来加以引用。
在这里我用 mini-css-extract-plugin
替代了 extract-text-webpack-plugin
。
const miniCssExtractPlugin=require("mini-css-extract-plugin");
module.exports={
module:{
rules:[
{
test: /\.css$/,
use:[
miniCssExtractPlugin.loader,
'css-loader'
]
}
]
},
plugins:[
new miniExtractPlugin({
filename: 'main.[contenthash:7].css'
})
}
}
打包后即使 css 文件所处的模块里就算其他文件内容改变,只要 css 文件内容不变,那么就不会重复构建。
如果对 css 使用了 chunkhash
之后,它与依赖它的 chunk 共用 chunkhash
,
测试后会发现,css 与 js 文件名的 chunkhash
值是一样的,如果我修改了 js 文件,js 的 hash 值会变化,
css 的文件名的 hash 还是和变化后的 js 文件的 hash 值一样,如果我修改了 css 文件,也会导致重新构建,css 的 hash 值和 js 的 hash 值还是一样的,即使 js 文件没有被修改。
这样会导致缓存作用失效,所以 css 文件最好使用 contenthash
。
对于图片、字体等静态资源,生成对应的文件 hash 值是由对应的 file-loader
来计算的。
那么这些静态文件的 hash 值使用的是什么 hash 值呢?
其实就是 hash 属性值。
此 hash 非 webpack 每次项目构建的 hash
,它是由 file-loader
根据文件内容计算出来的,不是 webpack 构建的 hash
。
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)
。
也可以通过 指定环境变量
中的 cross-env NODE_ENV=production webpack
配置。
webpack --env.NODE_ENV=local --env.production --progress
注意:如果设置 env
变量,却没有赋值,默认将 --env.production
设置为 true
。
还有其他可以使用的语法。有关详细信息,请查看 webpack CLI 文档。
webpack 将运行由配置文件导出的函数,并且等待 Promise 返回。 便于需要异步地加载所需的配置变量。
module.exports = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({
entry: './app.js',
/* ... */
})
}, 5000)
})
}
iframe
,将我们自己的应用注入到这个 iframe
当中去。live.bundle.js
文件,其不但创建了 iframe
标签,同时包含 socket.io的client
代码,以和 webpack-dev-server
进行 websocket
通讯,从而完成自动编译打包、页面自动刷新的功能。socket.io
的 client
代码被打包进了你的包(bundle)中,以此来与 webpack-dev-server
进行 websocket
通讯,从而完成自动编译打包、页面自动刷新的功能。bundle
文件很臃肿。iframe
和 inline
实现的效果都是一样的,都是监听文件的变化,然后再将编译后的文件推送到前端,完成页面的 reload
的。devServer.inline
切换两种模式,默认为 inline
模式。HMR
功能时,推荐使用 inline
。设置 NODE_ENV
时,不会自动设置 mode
。
只在配置中提供 mode
选项:
// webpack.config.js
module.exports = {
mode: 'production'
};
或者从 CLI 参数中传递:
webpack --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()
- ]
}
// webpack.development.config.js
module.exports = {
+ mode: 'development'
- plugins: [
- new webpack.NamedModulesPlugin(),
- new webpack.DefinePlugin({ "process.env.NODE_ENV": JSON.stringify("development") }),
- ]
}
对于用途广泛的 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'
},
devServer.publicPath
的意义就是决定外部能以怎样的路径通过 devServer
来访问构建在内存中的文件,
这个字段未显式设定时,则会去沿用 output.publicPath
字段的显式值(如果 output.publicPath
有值的话,否则就用自己的 default 值)。
output.publicPath
的意义是用来为构建的文件生成满足特定需求的前缀,并将这个前缀提供给需要的 resolver
、plugin
或者其他的配置字段。
注意:HtmlWebpackPlugin
中的 filename
也会依赖于 public.publicPath
。
这个字段只在 production
配置下有效。
另外,它的 default 值是 path.resolve(__dirname, './dist')
。
过去 webpack 打包时的一个取舍是将 bundle 中各个模块单独 打包成闭包。
这些打包函数使你的 JavaScript 在浏览器中 处理的更慢。
相比之下,一些工具像 Closure Compiler
和 RollupJS
可以提升(hoist)或者 预编译所有模块到一个闭包中,提升你的代码在浏览器中的执行速度。
这个插件会在 webpack 中实现以上的预编译功能。
devServer
构建的文件是在内存里的,而非你电脑的磁盘上,但是如果内存中找不到想要的文件时,devServer
会根据文件的路径尝试去电脑的磁盘上找,如果这样还找不到才会 404。
开发时在内存和 contentBase
下真实的磁盘路径中存在着同样文件名的文件,那么 devServer
返回的是内存的那个。
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' }
]
}
决定外部能够以什么样的路径访问到构建的文件。
与 output.publicPath
的关系,参考第10小节的 [output.publicPath 和 devServer.publicPath 的区别]。
webpack 可以监听文件变化,当它们修改后会重新编译。
boolean = false
启用 Watch 模式。这意味着在初始构建之后,webpack 将继续监听任何已解析文件的更改。
// webpack.config.js
module.exports = {
watch: true,
};
注意:webpack-dev-server
和 webpack-dev-middleware
里 Watch 模式默认开启。
object
一组用来定制 watch 模式的选项:
// webpack.config.js
module.exports = {
watchOptions: {
aggregateTimeout: 200,
poll: 1000,
},
};
number = 20
当第一个文件更改,会在重新构建前增加延迟。 这个选项允许 webpack 将这段时间内进行的任何其他更改都聚合到一次重新构建里。以毫秒为单位:
// webpack.config.js
module.exports = {
watchOptions: {
aggregateTimeout: 600,
},
};
RegExp
string
[string]
对于某些系统,监听大量文件会导致大量的 CPU 或内存占用。 所以可以选择下面一种方法来排除文件/目录:
// webpack.config.js
const path = require('path');
module.exports = {
watchOptions: {
ignored: /node_modules/, // 正则模式
ignored: '**/node_modules', // glob 模式
ignored: ['**/files/**/*.js', '**/node_modules'], // 多 glob 匹配模式
ignored: [path.posix.resolve(__dirname, './ignored-dir')], // 绝对路径
},
};
当使用 glob 模式时,我们使用 glob-to-regexp 将其转为正则表达式。
因此,在使用 watchOptions.ignored
的 glob 模式之前,请确保自己熟悉它。
注意:如果你使用 require.context
,webpack 会监听你的整个目录。
你应该忽略一些文件和目录,以便那些不需要监听的文件修改后不会触发重新构建。
boolean = false
number
通过传递 true 开启 polling
,或者指定毫秒为单位进行轮询。
// webpack.config.js
const path = require('path');
module.exports = {
watchOptions: {
poll: 1000, // 每秒检查一次变动
},
};
boolean
根据软链接查找文件。这通常是不需要的,因为 webpack 已经使用 resolve.symlinks
解析了软链接。
// webpack.config.js
const path = require('path');
module.exports = {
watchOptions: {
followSymlinks: true,
},
};
boolean
当 stdin 流结束时停止监听。
// webpack.config.js
const path = require('path');
module.exports = {
watchOptions: {
stdin: true,
},
};
SplitChunksPlugin
是拆分模块的插件。
开箱即用的 SplitChunksPlugin
对于大部分用户来说非常友好。
默认情况下,它只会影响到按需加载的 chunks,因为修改 initial chunks 会影响到项目的 HTML 文件中的脚本标签。
webpack 将根据以下条件自动拆分 chunks:
node_modules
文件夹下面这个配置对象代表 SplitChunksPlugin
的默认参数。
// webpack.config.js
module.exports = {
optimization: {
splitChunks: {
chunks: 'async',
minSize: 20000,
minRemainingSize: 0,
minChunks: 1,
maxAsyncRequests: 30,
maxInitialRequests: 30,
enforceSizeThreshold: 50000,
cacheGroups: {
defaultVendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10,
reuseExistingChunk: true,
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true,
},
},
},
},
};
1. 模块热替换
启用 HMR
启用此功能需要更新
webpack-dev-server
的配置,和使用 webpack 内置的 HMR 插件。webpack.config.js
worker.js
HMR 修改样式表
CSS 的模块热更新,借助于
style-loader
。2. Tree shaking
设置 sideEffects
在
package.json
中:sideEffects 可选值:
「副作用」的定义是,在导入时会执行特殊行为的代码,而不是仅仅暴露一个
export
或多个export
。 举例说明,例如 polyfill,它影响全局作用域,并且通常不提供export
。有副作用的不能tree sharking
!注意,任何导入的文件都会受到
tree shaking
的影响。 这意味着,如果在项目中使用类似css-loader
并导入 CSS 文件,则需要将其添加到sideEffect
列表中,以免在生产模式中无意中将它删除。还可以在
module.rules
配置选项 中设置sideEffects
压缩输出
从 v4 开始,也可以通过
mode
配置选项轻松切换到压缩输出,只需设置为production
。tree sharking 条件
import
和export
)package.json
文件中,添加一个sideEffects
入口UglifyJSPlugin
, v4 开始可以设置mode: "production"
来代替)