《七》Webpack生产环境配置

环境配置的封装:

生产环境的配置与开发环境有所不同,比如要设置mode、环境变量,为文件名添加chunk hash作为版本号等。

让Webpack按照不同环境采用不同的配置,一般来说有两种方式:

  1. 使用相同的配置文件:比如令Webpack不管在什么环境下打包都使用webpack.config.js,只是在构建开始前将当前所属环境作为一个变量传进去,然后在webpack.config.js中通过各种判断条件来决定具体使用哪个配置。比如:
    //package.json
    {
    	...
    	"scripts": {
    		"dev" : "ENV=development webpack-dev-serve",
    		"build" : "ENV=production webpack"
    	}
    }
    
    //webpack.config.js
    const ENV = process.env.ENV;
    const isProd = ENV === 'production';
    module.exports = {
    	output: {
    		filename: isProd ? 'bundle@[chunkhash].js' : 'bundle.js'
    	},
    	mode: ENV
    }
    
    通过npm脚本命令传入了一个ENV环境变量,webpack.config.js则根据它的值来确定具体采用什么配置。
  2. 为不同的环境创建各自的配置文件。比如:可以单独创建一个webpack.production.config.js,开发环境的则可以叫webpack.development.config.js,然后修改package.json。
    {
    	...
    	"scripts": {
    		"dev": "webpack-dev-serve --config=webpack.development.config.js",
    		"build": "webpack --config=webpack.production.config.js"
    	}
    }
    
    通过–config指定打包时使用的配置文件。但这种方法存在一个问题:即webpack.production.config.js和webpack.development.config.js肯定会有重复的部分,一改都要改,不利于维护。在这种情况下,可以将公共的配置提取出来,比如单独创建一个webpack.common.config.js,然后让另外两个JS分别引用该文件,并添加上自身环境的配置即可。
    //development和production共有配置
    module.exports = {
    	entry: './src/index.js'
    }
    

开启production模式:

在早期的Webpack版本中,不同环境所使用的的配置项太多,管理起来复杂,以至于webpack4中直接加了一个mode配置项,让开发者可以通过它来直接切换打包模式。

//webpack.config.js
module.exports = {
	mode: 'production'
}

这意味着当前处于生产环境模式,Webpack会自动添加许多适用于生产环境的配置项,减少了人为手工的工作。

环境变量:

通常需要为生产环境和本地环境添加不同的环境变量,在Webpack中可以使用DefinePlugin进行设置。

//webpack.config.js
const Webpack = require('webpack');
module.exports = {
	entry: './app.js',
	output: {
		filename: 'bundle.js'
	},
	mode: 'production',
	plugins: [
		new Webpack.DefinePlugin({
			ENV: JSON.stringify('production')
		})
	]
}

//app.js
document.write(ENV);

上面的配置通过DefinePlugin设置了ENV环境变量,最终页面上输出的将会是字符串production。

在值的外面加上了JSON.stringify(),是因为DefinePlugin在替换环境变量时对于字符串类型的值进行的是完全替换,假如不添加JSON.stringify()的话,在替换后就会成为变量名,而非字符串值。因此对于字符串环境变量及包含字符串的对象都要加上JSON.stringify()。

许多框架与库都采用process.env.NODE_ENV作为一个区别开发环境和生产环境的变量。process.env是Node.js用于存放当前进程环境变量的对象;而NODE_ENV则可以让开发者指定当前的运行时环境,当它的值为proction时即代表当前为生产环境,库和框架在打包时如果发现了它就可以去掉一些开发环境的代码,如警告信息和日志等。

new Webpack.DefinePlugin({
	process.env.NODE_ENV: 'production'
})

如果启用mode:production,则Webpack已经设置好了process.env.NODE_ENV,不需要再人为添加了。

source map:

source map指的是将编译、打包、压缩后的代码映射回源代码的过程。

经过Webpack打包压缩后的代码基本上已经不具备可读性,此时若代码抛出了一个错误,要想回溯它的调用栈是非常困难的,而有了source map,再加上浏览器调试工具(dev tools),要做到这一点就非常容易了。

原理:

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文件有时会很大,但只要不打开开发者工具,浏览器是不会加载这些文件的,因此对于普通用户来说并没有影响。但是使用source map会有一定的安全隐患,即任何人都可以通过dev tools看到工程源码。

source map配置:

只要在webpack.config.js中添加devtools即可。

module.exports = {
	devtools: 'source-map'
}

开启source map之后,打开Chrome的开发者工具,在Sources选项卡下面的webpack://目录中可以找到解析后的工程源码。

对于CSS、SCSS、Less来说,则需要添加额外的source map配置项。

const path = require('path');
module.exports = {
	devtools: 'source-map',
	module: {
		rules: [
			{
				test: /\.scss$/,
				use: [
					'style-loader',
					{
						loader: 'css-loader',
						options: {
							sourceMap: true
						}
					},
					{
						loader: 'sass-loader',
						options: {
							sourceMap: true
						}
					}
				]
			}
		]
	}
}

Webpack支持多种source map的形式。除了配置为devtools:'source-map’以外,还可以根据不同的需求选择cheap-source-map、eval-source-map等。通常它们都是source map的一些简略版本。因为生成完整的source map会延长整体构建时间,如果对打包构建速度需求比较高的话,可以选择一个简化版的source-map,比如:在开发环境中,cheap-module-eval-source-map是一个 不错的选择,属于打包速度和源码信息还原程度的一个良好折中。

在生产环境中由于会对代码进行压缩,而最常见的压缩插件UgifyjsWebpackPlugin目前只支持完全的source-map,因此只能使用source-map、hidden-source-map、nosources-source-map这三者之一。

安全:

source map不仅可以帮助开发者调试代码,当线上有问题产生时也有助于查看调用栈信息,是线上查错十分重要的线索。同时,有了source map也就意味着任何人通过浏览器的开发者工具都可以看到工程源码,对于安全性来说也是极大的隐患。

Webpack提供了hidden-source-map、nosources-source-map两种策略来提升source map的安全性,在保持其功能的同时,防止暴露源码给用户。

  1. hidden-source-map:意味着Webpack仍然会产出完整的map文件,只不过不会在bundle文件中添加对map文件的引用。这样一来,当打开浏览器的开发者工具时,是看不到map文件的,浏览器自然也无法对bundle进行解析。如果想要追述源码,则要利用一些第三方服务,将map文件上传那上面。目前最流行的解决方案是Sentry。

Sentry是一个错误跟踪平台,开发者接入后可以进行错误的收集和聚类,以便于更好地发现和解决线上的问题。
Sentry支持JavaScript的source map,可以通过它所提供的命令行工具或者Webpack插件来自动上传map文件,同时还要在工程代码中添加Sentry对应的工具包,每当JavaScript执行出错时就会上报给Sentry。Sentry在接收到错误后,就会去找对应的map文件进行源码解析,并给出源码中的错误栈。

  1. nosources-source-map:它对于安全性的保护则没有那么强,但是使用方式相对简单。打包部署之后,可以在浏览器开发者工具的Sources选项卡中看到源码的目录结构,但是文件的具体内容会被隐藏起来。对于错误来说,仍然可以在Console控制台中查看源代码的错误栈,或者console日志的准确行数。它对于追溯错误来说基本足够,并且其安全性相对于可以看到整个源码的source-map配置来说要略高一些。
  2. 还有一种选择,就是可以正常打包出source map,然后通过服务器的nginx设置(或其他类似工具),将.map文件只对固定的白名单开放,这样我们仍然能看到源码,而在一般用户的浏览器中就无法获取到它们了。

资源压缩:

在将资源发布到线上环境前,通常都会进行代码压缩(或者叫uglify),意思是移除多余的空格、换行及执行不到的代码,缩短变量名,在执行结果不变的前提下降代码更换为更短的形式。一般正常的代码在uglify之后整体体积都将会显著缩小,同时,uglify之后的代码基本上不可读,在一定程度上提升了代码的安全性。

压缩JavaScript:

压缩JavaScript大多数时候使用的工具有两个:一是UglifyJS(Webpack3已集成),另一个是terser(Webpack4已集成)。后者由于支持ES6+代码的压缩,更加面向未来,因此官方在Webpack4中默认使用了terser的插件terser-webpack-plugin。

  1. UglifyJSPlugin:
    在Webpack3中的话,开启压缩需调用webpack.optimize.UglifyJsPlugin。

    const Webpack = require('webpack');
    module.exports = {
    	entry: './app.js',
    	output: {
    		filename: 'bundle.js'
    	},
    	plugins:[
    		new Webpack.optimize.UglifyJsPlugin()
    	]
    }
    

    从Webpack34之后,这项配置被移到了config.optimization.minimize(如果开启了mode:production,则不需要人为设置)。

    module.exports = {
    	entry: './app.js',
    	output: {
    		filename: 'bundle.js'
    	},
    	optimization: {
    		minimize: true
    	}
    }
    
  2. terser-webpack-plugin插件支持自定义配置:

    1. test:terser的作用范围,默认值为/\.m?js(\?.*)?$/i
    2. include:使terser额外对某些文件或目录生效,默认值为undefined。
    3. exclude: 排除某些文件或目录,默认值为undefined。
    4. cache:是否开启缓存,默认值为false。默认的缓存目录为node_modules/.cache/terser-webpack-plugin,通过传入字符串类型的值可以修改。
    5. parallel:强烈建议开启。允许使用多个进程来进行压缩。通过传入数字类型的值来指定,默认值为false。
    6. sourceMap:是否生成source map,默认值为false。
    7. terserOptions:terser压缩配置。
    const TerserPlugin = require('terser-webpack-plugin');
    module.exports = {
    	...
    	optimization: {
    		//覆盖默认的minimizer
    		minimizer: [
    			new TerserPlugin ({
    				test: /\.js(\?.*)?$/i,
    				exclude: /\/excludes/
    			})
    		]
    	}
    }
    
压缩CSS:

压缩CSS文件的前提是使用extract-text-webpack-plugin或mini-css-extract-plugin将样式提取出来,接着使用optimize-css-assets-webpack-plugin来进行压缩。这个插件本质上使用的是压缩器cssnano。

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'
				})
			}
		]
	},
	plugins: [new ExtractTextPlugin ('style.css')],
	optimization: {
		minimizer: [
			
		]
	}
}

猜你喜欢

转载自blog.csdn.net/wsln_123456/article/details/106850587