文章目录
1. CSS的分割
之前的文章中讨论主要围绕JS的代码分割和应用,这里我们来看一下针对CSS的分割
1.1 MinicssExtractPlugin介绍和使用
这个插件帮助我们将CSS压缩分割到多个文件中
这里我们先尝试新写一个style.css文件:
body{
background:green;
}
然后打包,我们发现打包文件夹并没有该CSS文件,但是页面却渲染了对应的style,这是因为打包时将CSS打包到了JS文件内部,若我们希望将其打包成独立CSS文件,就需要借助这个插件
接着我们借助该插件进行打包,参考官方文档,这里我们去线上webpack配置文件里面使用,先引入,然后使用:
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
plugins: [
new MiniCssExtractPlugin({
filename: '[name].css',
chunkFilename: '[id].css'
}),
],
module: {
rules: [
{
test: /\.css$/,
use: [
{
loader: MiniCssExtractPlugin.loader,
options: {
// 公共路径
// 默认情况下,使用的是webpackOptions.output中publicPath
publicPath: '../',
//开发环境配置热更新
hmr: process.env.NODE_ENV === 'development',
},
},
'css-loader',
'postcss-loader'
}
]
}
}
高级配置如下(既可以在开发中使用 HMR,也可以在生成版本的文件中提取样式):
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const devMode = process.env.NODE_ENV !== 'production';
module.exports = {
plugins: [
new MiniCssExtractPlugin({
filename: devMode ? '[name].js' : '[name].[hash].css',
chunkFilename: devMode ? '[id].css' : '[name].[hash].css',
});
],
module: {
rules: [
{
test: /\.(sa|sc|c)ss$/,
use: [
{
loader: MiniCssExtractPlugin.loader,
options: {
hmr: process.env.NODE_ENV === ‘development’
}
},
'css-loader',
'postcss-loader',
'sass-loader'
]
}
]
}
}
打包之后若还是没有出现单独的css文件,这里有可能是因为tree-shaking的缘故:
你配置了optimaization和sideeffect,进行了tree-shaking,所以把css文件干掉了,因为里面的css属性没用到,这里我们可以对tree-shaking进行修改让其忽略css文件,取package.json进行修改:
"sideEffects":[
"*.css"
]
1.2 filename和chunkFilename
上述filename和ChunkFilename配置项有什么区别呢?
这里我们举个例子,在有的框架中webpack配置文件你会看到如下的output字段:
output:{
filename:'[name].js',
chunkFilename:'[name].chunk.js'
}
这样打包之后我们会发现文件夹下除了main.js外还有一个vendors~lodash.chunk.js,为啥index.js打包之后遵循filename配置项,而lodash的chunk则遵循另外一个配置项呢?
这是因为main.js里面引入了lodash,lodash并不是入口js文件,而是异步加载的文件。
其实你也可以这样理解,打包的index.html只引入了main.js文件,这个就是入口,而lodash则是main.js里面间接引用的
上述css的两个filename也是同理~
1.3 生产环境优化压缩(production) - 合并压缩
这里可以借助optimize-css-assets-webpack-plugin这样的插件来优化压缩结果。
设置optimization.minimizer会覆盖 webpack 提供的默认值,因此确保要指定JS minimalizer:
//webpack.config.js
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
module.exports = {
optimization: {
minimizer: [new UglifyJsPlugin({
include: /\/src/,
exclude: /\/excludes/,
cache: true,
sourceMap: true,
parallel: true,//多进程并行运行
comments: false, //禁止构建注释
}), new OptimizeCSSAssetsPlugin({})],
},
plugins: [
new MiniCssExtractPlugin({
filename: '[name].css',
chunkFilename: '[id].css',
}),
],
module: {
rules: [
{
test: /\.css$/,
use: [MiniCssExtractPlugin.loader, 'css-loader'],
},
],
},
};
另外这里可以通过splitChunks插件的cacheGroups概念来将所有 CSS 样式提取到单个文件中:
//webpack.config.js
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
optimization: {
splitChunks: {
cacheGroups: {
styles: {
name: 'styles',
test: /\.css$/,
chunks: 'all',
enforce: true,
},
},
},
},
plugins: [
new MiniCssExtractPlugin({
filename: '[name].css',
}),
],
module: {
rules: [
{
test: /\.css$/,
use: [MiniCssExtractPlugin.loader, 'css-loader'],
},
],
},
};
2. Shimming
打包过程中,我们往往需要处理一些打包过程中遇到的兼容性问题,例如之前polyfill解决低版本浏览器兼容性问题。其实这就是一个webpack的shimming-垫片。
当然这种兼容性不仅仅只是上述低版本兼容性。
2.1 ProvidePlugin插件
这里我们先看一个例子,我们新增一个库文件:
export function ui(){
${'body'}.css('background','red')
}
然后引入:
import $ from 'jquery';
import {ui} from './jquery.ui'
ui()
这里我们想一下他能不能执行呢?肯定是不行的,会报错jquery.ui.js文件里面找不到$对象,这是因为webpack的模块化打包策略导致A文件是不可能用B文件的导入变量的,为了避免耦合,出现相互影响!
除非我们在jquery.ui中也import $才行
但是这里我们可以借助这个插件实现Shimming行为来解决。
打开webpack的配置文件:
new webpack.ProvidePlugin({
$:'jquery'
})
如果发现 符 号 , 那 么 会 自 动 引 入 j q u e r y 这 个 模 块 , 并 叫 做 符号,那么会自动引入jquery这个模块,并叫做 符号,那么会自动引入jquery这个模块,并叫做,例如之前的jquery.js就会帮你自动import jquery
重新打包就会发现没有报错了。
这里就可以把这个插件理解成垫片。
例如我们希望直接用_join形式来调用_.join方法,我们就可以这样写:
new webpack.ProvidePlugin({
$:'jquery',
_join:['lodash','join']
})
2.2 imports-loader
这里再来看一个loader来实现垫片行为
我们在index.js使用的this指向谁?其实是这个模块自身,有时候我希望this能够指向window,当然这里是不等于window全局变量的,能不能修改呢?
这里可以借助import-loader来解决:(记住首先需要安装)
rules:[{
test:/\.js$/,
use:[{
loader:'babel-loader'
},{
loader:'imports-loader?this=>window'
}]
}]
上述就是标准写法,然后交给babel-loader进行编译就可以了。
然后就可以看到this指向变了,就是window对象了~
上述行为也叫做shimming行为,垫片是一种行为,上述都是这种概念
3. content-hash
这个webpack相关概念主要用于解决浏览器缓存导致客户端展示旧页面的问题。
有时候我们修改了index.js,重新打包上传,刷新浏览器看到的还是老的页面,这是因为文件名字没有变,所以会去拿缓存,所以为了解决浏览器缓存问题,引入了content-hash概念
开发环境下其实无所谓,但是线上环境的打包配置文件就需要修改了:
//webpack.prod.js
output:{
filename:'[name].[contenthash].js',
chunkFilename:'[name].[contenthash].chunk.js'
}
其实就是根据content生成的一个hash值,只要content不变,hash就不变,名称就不变。
这样用户访问新的页面就会加载新的main.js文件,然后vendors.js这些库的chunk会使用缓存,通过这种形式用户就只会重新加载会更新的JS代码,没变的就用缓存~
4. 环境变量
这里直接通过一个webpack设置开发和线上配置文件的实例来说明环境变量的应用
我们修改一下原先的webpack.dev.js和webpack.prod.js文件,不再引入common内容,也不进行merge
接着打开common.js配置文件,引入merge和devConfig与prodConfig:
const devConfig = require('./webpack.dev.js')
const prodConfig = require('./webpack.prod.js')
不直接导出common的config对象,改为一个变量:
const commonConfig = {
....(原先common配置对象)
}
最后导出一个根据env变量来进行一个mergeConfig操作:
module.exports = (env) =>{
if(env && env.production){
return merge(commonConfig,prodConfig)
}else {
merge(commonConfig,devConfig)
}
}
其实上述操作的目的就是把原先的环境判断从原先的通过不同JS文件进行改为通过一个env变量进行
接着我们需要传递一个env这个环境变量,去package.json文件,这里在scripts里面新增环境变量,同时把目标配置文件都改成webpack.common.js:
"scripts":{
"dev-build":"webpack --config ./build/webpack.common.js",
"dev":"webpack-dev-server --config ...",
"build":"webpack --env.production --config ..."
}
通过添加–env.production来添加这个属性,默认为true。而不填的默认不存在。
你也可以指定具体的值:
--env.production = abc
if(env && env.produciton === 'abc')