webpack实战——生产环境配置【中】
前言
上一篇中,描述了一些关于生产环境的配置:环境变量的使用、配置文件描述、开启生产模式、环境变量自定义配置等,从这几个方面入手都可以对生产环境产生一些有利影响。
那么本篇,从source map和资源压缩方面入手,继续深入探究。
1. source map
source map 指的是将编译、打包、压缩后的代码映射回源代码的过程。
经过webpack打包压缩后的代码基本上已经不具备可读性,此时若是代码抛出错误,想要回溯它的调用栈是非常困难的,而有了source map,加上浏览器调试工具(dev tools),要做到这一点就会变得很容易。同时,它对于线上问题的追查也有一定帮助。
1.1 原理
工作原理:webpack对于工程源代码的每一步处理都有可能会改变代码的位置、结构、甚至是所处文件,因此每一步都需要生成对应的source map。
如果我们启用了devtool
配置,那么source map就会跟随源代码一步步被传递,直到生成最后的map文件。这个文件默认就是打包后的文件名字上加上后缀[.map],例如bundle.js.map。
在生成map文件的同时,bundle文件中会追加一句注释来标识map文件的位置,例如:
// bundle.js
(function() {
// bundle的内容
...
})()
// # sourceMappingURL=bundle.js.map
而当我们打开浏览器开发者工具后,其实map文件同时也会被加载进来,这时浏览器会使用它来对打包后的bundle文件来进行解析,分析出源代码的目录结构和内容。
亲自尝试过的朋友可能会发现,打包后,map文件会比较大,甚至超出源文件几倍的体积大小,不过不用担心,不打开开发者工具是不会加载这些map文件的,因此对于普通用户来讲没有什么影响。但是要注意的是,虽然普通用户看不到,不过有经验的“特殊人群”还是可以通过dev tools看到工程源码的。因此建议如果是生产环境,还是要解决一下。如何解决呢?下面会提到。
1.2 配置
在webpack.config.js中添加devtool即可完成对source map的配置。
// webpack.config.js
module.exports = {
// ...
devtool: 'source-map'
}
而对于CSS、SCSS及Less来说,则需要添加额外的source map配置项。如:
const path = require('path');
module.exports = {
// ...
devtool: 'source-map',
module: {
rules: [
// scss
{
test: /\.scss$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
sourceMap: true,
}
},
{
loader: 'sass-loader',
options: {
sourceMap: true
}
}
]
}
]
}
}
webpack给出多种source map形式:
- source-map
- cheap-source-map
- eval-source-map
- ...
在开发环境中,通常使用module-eval-source-map,因为在打包速度和源码信息还原程度都属于良好程度。
而在生产环境中,通常我们会对代码进行压缩,而最常见的压缩插件UglifyjsWebpackPlugin
目前只支持source-map形式。
1.3 安全
在1.1中我们抛出一个安全问题,就是在开启source-map的时候任何人都可以通过浏览器的开发者工具devtool来看到工程源码,因此对于安全性来讲是一个极大的隐患。那么如何能在保持其功能的同时又能防止暴漏源码呢?
webpack提供了两种安全策略:
- hidden-source-map
- nosources-source-map
hidden-source-map
hidden-source-map意味着Webpack仍然会产出完整的map文件,但是不会在bundle文件中添加对于map文件的引用。这样当打开浏览器开发者工具时,是无法看到map文件的,自然也就无法解析。如果我们自己想要追溯源码,可使用一些第三方服务,将map文件上传到第三方服务中。目前比较流行的是Sentry
(错误跟踪平台),有兴趣的可以自行搜索了解一下。
nosources-source-map
它对于安全性保护不如hidden-source-map,但是使用方式相对简单。当打包部署后,我们可以在浏览器开发者工具的sources选项卡中看到源码的目录结构,但是文件内容会被隐藏起来。这样,对于错误来讲,我们仍然可以在console控制台中查看源代码的错误栈,或者console日志的准确行数。对于追溯错误来说基本上够使用。
另外的方案则是服务端配合处理,例如正常打包出source map,服务端通过服务器的nginx配置,将.map文件只对固定的白名单(如公司内网)开放,这样其余用户就无法获取到它们了,也不失为一个小妙招。
2. 资源压缩
资源在发布到生产环境之前,通常会进行代码压缩,也叫uglify,意思是移除多余的空格、换行、执行不到的代码块等,同时缩短变量名,在执行结果不变的前提下替换为更短的形式。
一般工程代码在被压缩后整个体积会显著缩小。
但同时,uglify之后的代码基本上不具有可读性,从另一个层面讲,一定程度上提高了代码的安全性。
2.1 压缩JavaScript
压缩JS(JavaScript)的工具terser(optomization)
在webpack中已集成(webpack4),并且支持ES6+的代码压缩,偏面向未来。
示例:
// webpack.config.js
module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js'
},
// 压缩配置
optomization: {
minimize: true
}
}
2.2 压缩CSS
CSS文件的压缩前提是使用相关插件处理,先将样式提取出来,然后进行压缩。例如常使用extract-text-webpack-plugin或mini-css-extract-plugin将样式提取,然后使用optimize-css-assets-webpack-plugin来进行压缩。这个插件本质上使用的是压缩器cssnano,当然我们可以对其进行配置:
// webpack.config.js
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin')
module.exports = {
// ...
module: {
rules: [
{
test: /\.css$/,
use: ExtractTextPlugin.extract({
fallback: 'style-loader',
use: 'css-loader'
})
}
]
},
// css 压缩
plugins: [new ExtractTextPlugin('style.css')];
optimization: {
minimizer: [new OptimizeCssAssetsPlugin({
// 生效范围,只压缩匹配到的资源
assetNameRegExp: /\.optimize\.css$/g,
// 压缩处理器指定,默认为 cssnano
cssProcessor: require('cssnano'),
// 压缩处理器配置
cssProcessorOptions: {
discardComments: {
removeAll: true
}
},
// 是否打印log
canPrint: true
})]
}
}
小结
本篇介绍了关于生产环境配置中比较重量级的两种配置:source-map和资源压缩。
开发环境中我们关注打包速度,而在生产环境中我们关心的则是线上错误处理、输出资源的体积以及资源渲染等问题,而比较好的利用source-map和资源压缩都可以帮助我们处理处理或优化生产环境中的一些问题,因此比较重要,但同时也要注意解决所存在的安全隐患问题。
下一篇则从缓存和bundle体积监控入手继续描述生产环境配置的其他方面优化问题。