带你由浅入深探索webpack4(二)
在前一篇文章已经介绍了webpack4从入门到一些核心常用的用法,大家可以从上一篇文章看起。带你由浅入深探索webpack4(一)
接着上一章,接下来我们会继续探讨webpack4中的各种实用用法,让我们共同探讨认知前端的另一个世界。
三: webpack中的高级配置
3.1代码分离
在我们之前的项目中,打包的文件都是放在一个main.js中,每次使用时,都是加载整个main.js,造成性能上的浪费。
代码分离是webpack中最引人注目的特性之一。他可以把代码分离到不同的budle中,然后可以按需加载或者并行加载这些文件。
代码分离一般有3个常用的分离方法:
1.入口起点:使用ertry配置手动分离代码。
2.防止重复:配置optimization去重和分离chunk.
3.动态导入:(异步代码)通过模块的内联函数调用来分离代码。
3.1.1入口起点
这个很简单,就是自己手动分离代码,将代码分离出多个js文件,将其配置到打包入口文件,这样就可以打包出不同模块。
其缺点很明显,要我们手动进行分离不够灵活,并且重复都模块都会被引入到打包后的文件中。
3.1.2防止重复
假设我们打包多个js入口文件,且他们又引用了同一个第三库,这样打包出来的每个出口文件都含用一份第三方库,性能消耗是巨大的。
所以我们可以使用CommonsChunkPlugin插件帮助我们去重,其是将重复的模块分离出单独的chunk。
我们在src下创建两个入口文件.another1.js、another2.js分别引入第三方模块lodash
npm install lodash -D
src/another1.js、src/another2.js:
import _ from 'lodash'
console.log(
_.json(['A','B','C'],' ')
)
webpack.common.js:
entry:{ //配置入口文件
another1:'./src/another1.js',
anotehr2:'./src/another2.js'
}
直接运行npx webpack打包,可以看到打包出来的文件都挺大的:
我们现在webpack.common.js配置一下先:
.......
module.exports = {
entry:{ //配置入口文件
another1:'./src/another1.js',
anotehr2:'./src/another2.js'
},
......
output:{ //打包文件的出口
filename:'[name].js', //打包后的文件名
path:path.resolve(__dirname,'dist') //打包后文件存放的位置
},
optimization:{
splitChunks:{
cacheGroups: {
commons: {
name: 'commons', //+++抽离出公共模块的名称
chunks: 'initial', //+++入口文件中的共享代码
minChunks: 2 //+++最少的入口文件数
}
}
}
}
}
然后我们再运行npx webpack就会发现抽离出了一个commons.js文件,总体代码减轻了一大截。
在这里可能查看中文文档可能有个坑,由于中文文档更新有点慢,其推荐的配置是使用CommonsChunkPlugin,但在最新版webpack中其已经被删除了,采用optimization代替了。
3.13动态导入
首先我们先移除another2.js先,再删除optimization配置。
要实现异步打包,需要借助一个插件:babel-plugin-syntax-dynamic-import。所以我们先安装一下:
npm install babel-plugin-syntax-dynamic-import -D
我们引入了插件,因为其是异步代码分离,所以我们先将another1.js修改为异步代码
src/another1.js:
function getComponent() {
return import(/* webpackChunkName:"lodash" */ 'lodash').then(({ default: _ }) => {
var element = document.createElement('div');
element.innerHTML = _.join(['1', '2'], '-');
return element;
})
} getComponent().then(element => {
document.body.appendChild(element);
});
因为其实babel的插件,接着我们还需要修改一下.babelrc文件的配置.
.babelrc:
{
"presets": [
[
"babel-preset-env", {
"targets": { //在这些版本以上的浏览器中不转换为es5
"chrome": "67",
"firfox":"60",
"edge":"17",
"safari":"11.1"
},
"useBuiltIns": "usage"
}
]
],
"plugins": ["babel-plugin-syntax-dynamic-import"] //+++
}
分类方便区分哪些是抽离出的公共模块,我们配置一下出口文件名:
output:{ //打包文件的出口
filename:'[name].js', //打包后的文件名
chunkFilename:'[name].chunk.js', //+++抽离出的公共模块的文件名
path:path.resolve(__dirname,'dist') //打包后文件存放的位置
},
最后,我们直接运行打包,可以看到lodash被抽离出来了。
3.2css代码分割
如果我们将全部代码打包到js文件中,这就会显得什么臃肿,而且不利于代码分离按需加载!
我们要将css分离出来单独的模块,就需要用到官方推荐的插件:mini-css-extract-plugin(4.3版本之前不支持模块热替换)
首先,我们先安装一下这个插件:
npm install mini-css-extract-plugin --save-dev
所以我们需要修改一下css的翻译官因为style-loader会与其发生冲突,接着需要配置一下plugin,修改如下
webpack.common.js
......
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
plugins: [
new MiniCssExtractPlugin({
// Options similar to the same options in webpackOptions.output
// both options are optional
filename: '[name].css', //分离后css的文件名
chunkFilename: '[id].css',
}),
],
module: {
rules: [
{
test: /\.css$/,
use: [
{
loader: MiniCssExtractPlugin.loader,
options: {
// you can specify a publicPath here
// by default it uses publicPath in webpackOptions.output
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({
// Options similar to the same options in webpackOptions.output
// both options are optional
filename: devMode ? '[name].css' : '[name].[hash].css',
chunkFilename: devMode ? '[id].css' : '[id].[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文件,并且,当我们引入多个css文件时,mini-css-extract-plugin插件会帮我们自动合并为一个文件,但是,如果我们需要将css文件压缩,我们还需要引用一个插件:
npm install optimize-css-assets-webpack-plugin -D
这时我们修改一下webpack.common.js:
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin'); //+++
module.exports = {
optimization: {
minimizer: [ new OptimizeCSSAssetsPlugin({})], //+++
},
plugins: [
new MiniCssExtractPlugin({
filename: '[name].css',
chunkFilename: '[id].css',
}),
],
module: {
rules: [
{
test: /\.css$/,
use: [MiniCssExtractPlugin.loader, 'css-loader'],
},
],
},
};
3.3懒加载
在上面的代码中,我们需要将公共模块抽离出来。但是,有时候还没要用到某些模块,其都会在页面加载时请求他们,对性能产生负面影响。
懒加载和tree shaking又是不同概念,不要搞混。
tree shaking:删除掉没有用到的冗余代码。
懒加载:又叫延迟加载,即没用到该资源的时候不加载该资源,等用到时才开始加载该资源。
我们引用官网的实例看,我们动态添加了一个按钮,当我们触发这个按钮时,我们才开始加载‘lodash'模块,大大提升了初始加载页面的速度。
function component() {
var element = document.createElement('div'); var button = document.createElement('button');
var br = document.createElement('br'); button.innerHTML = 'Click me and look at the console!';
element.innerHTML = _.join(['Hello', 'webpack'], ' ');
element.appendChild(br);
element.appendChild(button); // Note that because a network request is involved, some indication
// of loading would need to be shown in a production-level site/app.
button.onclick = e => import(/* webpackChunkName: "print" */ './print').then(module => {
var print = module.default; print();
}); return element;
} document.body.appendChild(component());
3.4 缓存
当我们加载一次页面时,浏览器会使用一种叫“缓存”的技术,在一定时间保存我们请求的资源。当我们再次修改上存而没有修改资源文件名时,浏览器就会认为它没有被更新,而请求其之前缓存的资源,导致用户无法实时获取新的资源代码。
首先我们上个例子将入口文件换回src/another2.js文件
所以我们在输出文件配置中,使其打包后的文件名发生改变。
output:{ //打包文件的出口
filename:'[name].[contenthash].js', //打包后的文件名
path:path.resolve(__dirname,'dist') //打包后文件存放的位置
},
在以前的版本包括现在的webpack中文文档(估计没更新)都是使用chunkhash值的,而最新的版本改用为contenthash值。
这里插入一下大致讲解下hash、chunkhash、contenthash他们之间有什么区别。
hash:这个跟整个项目构建有关,只要项目文件有更改,整个项目构建的hash值都会改变,并且全部文件都共用相同的hash值。
chunkhash:它会根据不同的入口文件进行依赖文件解析,构建对应的chunk,生成对应的哈希值。且只要我们不改动公共库的代码时, 就可以保证哈希值不会受到影响。但是其有个问题,就是当我们将css分离出单独的模块(后面会有介绍)时,其hash值会与主入口的文件 公用同一个哈希值,当我们修改css或主入口文件时,其也会导致主入口文件和css文件的哈希值都发生改变。
contenthash:它只会根据文件内容的变化而改变其hash值,既即使css文件所处的模块里的其他模块文件内容文件发生变化,只要css文 件内容不变,其就不会重复构建。
在这里我们给他们添加了一个contenthash值,我们看一下打包后的文件名。
按照常理说当我们内容文件不改变时,是不会改变其的hash值。当我们再运行多几次打包看下它的哈希值会不会改变,然而。。。
(我的电脑运行并不会出现下列问题,官网说可能是有些版本差异才会出现这种问题,但为了更可靠起见,还是建议用下面介绍的方法)
在某些版本中遇到上述问题,是因为webpack中包括了某些样板,特别是runtime和manifest。下面介绍下解决方法:
提取模块
提取模块主要用到CommonsChunkPlugin插件,在前面代码分离第二part我们已经用过这个插件,我们主要用来提取公共模块,这章我们用其来将代码拆分成单独块:
optimization:{
runtimeChunk:'single'
}
当我们运行时,其就会帮助我们将第三方模块拆分为单独的模块
让我们在配置一下其参数,当我们再将node_modules中的模块给分离出来
optimization:{
splitChunks: {
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all'
}
}
},
runtimeChunk:'single'
}
我们重新打包一下就会发现,剩下的main.js竟然只有2.9KB了!!!
3.5预取/预加载模块
当我们要加载一个组件时,如果使用懒加载,等到触发的时候才开始加载时,性能上会有一定损耗,给用户的体验感也不是很好。
所以我们可以在页面加载完成后有空闲时间时,再加载一下将来需要用到的模块,这样在触发该功能时,就可以直接调用缓存中的模块,这就可以大大提升用户的体验感。
在webpack4.6+的版本中webpack添加了预取和预加载模块。
下面介绍一下怎样使用预取/预加载模块,很简单,只需要在导入模块中加入一个注释便可。
import(/* webpackPrefetch: true */ 'LoginModal');
其会在页面头部附加<link rel=“prefetch”href=“login modal chunk.js”>
这里可以使用webpackPrefetch和webpackPreload这两个属性,其还是有些区别的:
webpackPrefetch: true :先把主加载流程加载完毕,在空闲时在加载其模块,等再点击其他时,只需要从缓存中读取即可,性能更好。推荐使用,提高代码利用率。把一些交互后才能用到的代码写到异步组件里,通过懒加载的形式,去把这块的代码逻辑加载进来,性能提升,页面访问速度更快。
webpackPreload: true : 和主加载流程一起并行加载。而一个预加载的块应该在加载完成时立即被其父类调用。
3.6Slimming(垫片)
当我们引入类似lodash、jquery这些第三方库的时候,这些库可能回创建一些需要被导出的全局变量,造成了环境的污染。
slimming在这里就起到了很大的作用,它可以只导出需要使用到的全局变量。来看下面的例子
我们添加一个plugin插件的配置如下:
const path = require('path');
const webpack = require('webpack'); module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
}
},
plugins: [
new webpack.ProvidePlugin({ //+++
_: 'lodash', //+++引用全局变量_时,自动引用lodash这个库
join:['lodash','join'] //+++引动全局变量join时,调用lodash库中的join方法
})
]
};
这样,我们就无需在每个模块中都import这个第三方模块了,我们修改一下src/index.js
src/index.js:
function component() {
var element = document.createElement('div'); element.innerHTML = _.join(['Hello', 'webpack'], ' '); //+++调用lodash
element.innerHTML = join(['Hello', 'webpack'], ' '); //+++直接调用lodash中的方法 return element;
} document.body.appendChild(component());
当我们只需要使用join方法,就无需要导出整个库,这样就可以很好的和tree shaking相配合。
在这里有一个问题就是,一些传统的模块依赖中的this指向的是window对象,当模块以运行在CommonJS环境下就可能指向的是module.exports,所以我们需要修改一下模块中this的指向,将其指向window。一般情况下不需要设置。
const path = require('path');
const webpack = require('webpack'); module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
},
module: {
rules: [
{
test: /\.js$/, //+++当解析index.js的时候
use: 'imports-loader?this=>window' //+++将this指向window
}
]
},
plugins: [
new webpack.ProvidePlugin({
join: ['lodash', 'join']
})
]
};
3.7library
当我们需要开发一个库时,我们希望外包可以通过各种方式引用我们的库,这时,我们就需要用到library
例如我们创建一个libray.js的方法类。
[{
"num": 1,
"word": "One"
}, {
"num": 2,
"word": "Two"
}, {
"num": 3,
"word": "Three"
}, {
"num": 4,
"word": "Four"
}, {
"num": 5,
"word": "Five"
}, {
"num": 0,
"word": "Zero"
}]
然后通过src/index.js引入其:
src/index.js
import _ from 'lodash';
import numRef from './ref.json'; export function numToWord(num) {
return _.reduce(numRef, (accum, ref) => {
return ref.num === num ? ref.word : accum;
}, '');
}; export function wordToNum(word) {
return _.reduce(numRef, (accum, ref) => {
return ref.word === word && word.toLowerCase() ? ref.num : accum;
}, -1);
};
然后我们将其打包,如果我们想要通过各种方式引入该库,应该怎么做:
// ES2015 模块引入
import * as webpackNumbers from 'webpack-numbers';
// CommonJS 模块引入
var webpackNumbers = require('webpack-numbers');
// ...
// ES2015 和 CommonJS 模块调用
webpackNumbers.wordToNum('Two');
// ...
// AMD 模块引入
require(['webpackNumbers'], function ( webpackNumbers) {
// ...
// AMD 模块调用
webpackNumbers.wordToNum('Two');
// ...
}); <script src="index.js">
我们就需要在入口文件中配置一下:
webpack.config.js:
const path = require('path') module.exports = {
mode:'production',
entry:'./src/index.js',
output:{
path:path.resolve(__dirname,'dist'),
filename:'index.js',
library:'index',
libraryTarget:'umd'
}
}
但是,我们在src/index.js引入了lodash的库,如果在外部可能引入了lodash这就显得什么耗费性能,所以我们可以这样设置
const path = require('path') module.exports = {
mode:'production',
entry:'./src/index.js',
output:{
path:path.resolve(__dirname,'dist'),
filename:'index.js',
library:'index',
libraryTarget:'umd'
},
externals: {
lodash: {
commonjs: 'lodash',
commonjs2: 'lodash',
amd: 'lodash',
root: '_'
}
}
}
这样,当我们引入index.js库时,就必须需要引入一个lodash的依赖,这样就能防止外部与内部引入重复的库。
带你由浅入深探索webpack4(二)的更多相关文章
- 带你由浅入深探索webpack4(一)
相信你或多或少也听说过webpack.gulp等这些前端构建工具.近年来webpack越来越火,可以说成为了前端开发者必备的工具.如果你有接触过vue或者react项目,我想你应该对它有所了解. 这几 ...
- 关于 Unity WebGL 的探索(二)
关于 Unity WebGL 的探索(二) 上一篇博客记录了关于 WebGL 移植的第一步:部分 C/C++ 插件的编译,目前项目中的部分插件使用该方法通过,接下来比较大的一部分工作量是网络模块 We ...
- 「kuangbin带你飞」专题二十 斜率DP
layout: post title: 「kuangbin带你飞」专题二十 斜率DP author: "luowentaoaa" catalog: true tags: mathj ...
- 「kuangbin带你飞」专题二十二 区间DP
layout: post title: 「kuangbin带你飞」专题二十二 区间DP author: "luowentaoaa" catalog: true tags: - ku ...
- quartz2.3.0系列目录——带您由浅入深全面掌握quartz2.3.0
quartz2.3.0系列目录 官网下载地址:http://www.quartz-scheduler.org/downloads/ 本系列demo全部来源于官网,仅仅是简化和汉化了注释!一部分代码de ...
- 深入浅出带你玩转sqlilabs(二)
MYSQL高权限注入 mysql跨库注入 详情请看上一篇:深入浅出带你玩转sqlilabs(一) mysql文件操作注入-sqlilabs less7 可能用到的函数: into outfile()函 ...
- java自带的监控工具VisualVM(二)远程监控
ps:尝试了网上的几个网友提供的方法,始终不得其法,汇总后,终于尝试成功!将一些需要注意的细节也记录下来以后备用! 我们经常需要对我们的开发的软件做各种测试, 软件对系统资源的使用情况更是不可少, 目 ...
- 【算法系列学习】[kuangbin带你飞]专题十二 基础DP1 G - 免费馅饼
https://vjudge.net/contest/68966#problem/G 正解一: http://www.clanfei.com/2012/04/646.html #include< ...
- 关于解决乱码问题的一点探索之二(涉及Unicode(utf-16)和GBK)
在上篇日志中(链接),我们讨论了utf-8编码和GBK编码之间转化的乱码问题,这一篇我们讨论Unicode(utf-16编码方式)与GBK编码之间转换的乱码问题. 在Windows系统 ...
随机推荐
- python---购物车
购物车功能如下: 1. 输入收入多少,购买商品 2. 打印购物清单,根据清单选择商品: 3. 结算,打印购物清单及总金额 # -*- coding:utf-8 -*- # LC goods=[[1,' ...
- 从JavaWeb危险字符过滤浅谈ESAPI使用
事先声明:只是浅谈,我也之用了这个组件的一点点. 又到某重要XX时期(但愿此文给面临此需求的同仁有所帮助),某Web应用第一次面临安全加固要求,AppScan的安全测试报告还是很清爽的,内容全面,提示 ...
- Docker快速入门(二)
上篇文章<Docker快速入门(一)>介绍了docker的基本概念和image的相关操作,本篇将进一步介绍image,容器和Dockerfile. 1 image文件 (1)Docker ...
- 《Complete Guide to Value Investing》读书总结
大好的周末,决定写一篇读书笔记.:) 最近读了一些股票估值以及价值投资相关的文章和书籍.今天将其中的一本做一些笔记以及简单的总结. 该书名为<Complete Guide to Value In ...
- 五年级--python函数高级运用
一.装饰器 二.迭代器 三.生成器 四.练习 一.装饰器 1.1 闭包函数用法 # 需求: # 执行一个函数前需要认证是否登录,如果登录则不需再登录. # 只认证一次,后续操作无需认证 # 要求认证使 ...
- [Java算法分析与设计]--线性结构与顺序表(List)的实现应用
说到线性结构,我们应该立马能够在脑子里蹦出"Array数组"这个词.在Java当中,数组和对象区别基本数据类型存放在堆当中.它是一连串同类型数据存放的一个整体.通常我们定义的方式为 ...
- 瞎捣鼓的code highlight
int a ; int b; public int a ;int b char c; h2 { text-align: left;}.postTitle{ background-color:#F ...
- MySQL下载安装配置和Navicat for MySQL的安装配置
MySQL 一.下载 地址:MySQL :: Download MySQL Installer 选择那个几百M的msi文件下载 二.安装 第一步: 安装许可 双击安装文件,在如下图所示界面中勾选&qu ...
- vue中keep-alive的用法
1.keep-alive的作用以及好处 在做电商有关的项目中,当我们第一次进入列表页需要请求一下数据,当我从列表页进入详情页,详情页不缓存也需要请求下数据,然后返回列表页,这时候我们使用keep-al ...
- 原生javascript写自己的运动库(匀速运动篇)
网上有很多JavaScript的运动库,这里和大家分享一下用原生JavaScript一步一步写一个运动函数的过程,如读者有更好的建议欢迎联系作者帮助优化完善代码.这个运动函数完成后,就可以用这个运动函 ...