webpack4 学习之路(1)-起源
一、开篇小啰嗦
此次webpack学习总结是通过慕课网上webpack教学。根据课程内容以及自己的理解,写成系列文章,加强自己的学习印象,共勉
开始讲解之前,我们先了解一下为什么要使用webpack. webpack到底可以干什么啊? 好多面试的时候,都会问你会代码分割吗? 你使用框架开发,离开脚手架,还会配置吗?怎么配置啊?看起来都是小问题,但是问起来有时候真的要命。现在写一下webpack4的系列文章。最后就应该可以解决这类问题了。
二、开发模式的演变
1.面向过程开发
特点:乱七八糟。爱咋引就咋引
那个时候没有什么规范,一个html引入一个js文件。所有的js代码。全部写到一个js文件里面。
<!html的页面>
<div id="root"></div>
<script src="./index.js"></script>
// index.js代码
var root = document.getElementById('root');
// header模块
var header = document.createElement('div');
header.innerText = 'header';
root.appendChild(header);
// content模块
var content = document.createElement('div');
content.innerText = 'content';
root.appendChild(content);
// footer模块
var footer = document.createElement('div');
footer.innerText = 'footer';
root.appendChild(footer);
问题来了,一个简单的头部,内容,尾部的布局,需要写在一个js文件。要是几十个上百个模块。是不是就多的爆炸。找问题,能累死
2.面向对象开发
特点:把js的代码一块块的分割,每个部分就是一个单独的js的代码,遇到问题,快速查找,容易维护
<!-- index.html代码 -->
<p>这里是我们网页的内容</p>
<div id="root"></div>
<script src="./src/header.js"></script>
<script src="./src/content.js"></script>
<script src="./src/footer.js"></script>
<script src="./index.js"></script>
// header.js
function Header() {
var header = document.createElement('div');
header.innerText = 'header';
root.appendChild(header);
}
// content.js
function Content() {
var content = document.createElement('div');
header.innerText = 'content';
root.appendChild(content);
}
// footer.js
function Footer() {
var footer = document.createElement('div');
header.innerText = 'footer';
root.appendChild(footer);
}
// index.js代码
var root = document.getElementById('root');
new Header();
new Content();
new Footer();
大家好,我叫问题,我又来了。请问。js代码分隔开了,好处理了。html怎么办。一下子引入这么多js,而且顺序不能乱。否则就报错。少了还好,如果几百上千个咋整啊。我怎么拍顺序啊。
3.模块开发模式
特点:html 还是引入一个js代码,js代码还是分开。用模块化工具管理。咋看咋舒服。随后各种模块化加载越来越多,例如:ES Module(es6)、AMD(没用过)、CMD(没用过)以及CommonJS(服务端,node)等,我们介绍ES Module模块化加载方案,当然其他模块化标准也是生效的
<!-- index.html代码 -->
<p>这里是我们网页的内容</p>
<div id="root"></div>
<script src="./index.js"></script>
// header.js
export default function Header() {
var root = document.getElementById('root');
var header = document.createElement('div');
header.innerText = 'header';
root.appendChild(header);
}
// content.js代码
export default function Content() {
var root = document.getElementById('root');
var content = document.createElement('div');
content.innerText = 'content';
root.appendChild(content);
}
// footer.js
export default function Footer() {
var root = document.getElementById('root');
var footer = document.createElement('div');
footer.innerText = 'footer';
root.appendChild(footer);
}
// index.js代码
import Header from './src/header.js';
import Content from './src/content.js';
import Footer from './src/footer.js';
new Header();
new Content();
new Footer();
看的很爽啊,这样js代码既能分开,html也只需要自己引入一个js文件,爽啊,爽啊。哈哈,大家好。我叫浏览器,我不同意,怎么办。webpack来了。webpack就是编译一下,让浏览器同意
webpack也不是单纯的js编译器,官网和新定义是模块打包工具
webpack4 学习之路(2)-安装
一、开篇小啰嗦
上一篇文章说了,浏览器不会同意我们使用ES module模块化方式写代码。它不认,此时webpack出马就可以解决这个问题 。
二、开始安装吧
- 默认你装好node环境了啊。在桌面新建一个文件夹,创建一个node的包文件,生成一个package.json文件。
npm init
- 全局安装webpack
npm install webpack webpack-cli -g
大家好,我叫问题。我又来了,这个webpack是在全局安装的,如果你有两个项目,一个依赖是webpack3,一个依赖webpack4.那么webpack3项目就废了。所以这种全局的也不一定好使,最好那个项目用,那个项目本身安装webpack4,别打扰别人
- 全局卸载掉
npm uninstall webpack webpack-cli -g
- 进入项目文件夹
npm install webpack webpack-cli -D 或者 npm install webpack webpack-cli --save-dev
5.检查webpack版本号
npx webpack -v
如果你直接输入webpack -v,是直接默认查看你的全局webpack。现在我们卸载了,所以是空。node提供npx,可以查看本文件的安装情况
- 版本号安装
npm install webpack@4.25.0 -D
webpack4 学习之路(3)-配置文件
一、开篇小啰嗦
安装完,就该开始配置了。为什么webpack需要配置啊。我们说过webpack是个模块打包工具,但是打包js,图片,或者其他的后缀的文件,打包的流程肯定不一样的,所以需要配置文件。另外,webpack开发团队为了我们使用的爽。会有个一个文件默认配置基本的代码,我们就是在这个文件下修改代码。配置以满足我们的项目需求
二、开始配置吧
1.创建基础配置文件
在总结一下需要输入的命令吧
1. npm init(生成node规范的文件)
2. npm install webpack webpack-cli -D (进入项目文件夹,局部安装webpack)
3. 把第一章的几个js,html文件自己手动创建
4. 手动创建 webpack.config.js文件
命令敲完,应该会形成一个下面的目录
|-- webpack(名字随便起)
| |-- index.html
| |-- index.js
| |-- header.js
| |-- content.js
| |-- footer.js
| |-- webpack.config.js
| |-- package.json
webpack.config.js文件里面写代码
// path为Node的核心模块
const path = require('path');
module.exports = {
entry: './index.js', // 告诉webpack打包的入口文件在哪里,它从这里找,开始打包
output: { // 告诉webpack打好包以后放在那个文件夹。dist是自动生成的
filename: 'main.js',
path: path.resolve(__dirname, 'dist')
}
}
package.json 修改代码为
{
"name": "webpack",
"version": "1.0.0",
"description": "",
"private": true,
"scripts": {
"bundle": "webpack"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"webpack": "^4.31.0",
"webpack-cli": "^3.3.2"
}
}
执行命令
npm run bundle
看结果
2.webpack执行的方式小总结
到现在,webpack 执行方式有三种
(1)如果你是全局安装的,就执行 webpack index.js 打包
(2)如果你是项目安装的,就执行 npx webpack index.js 打包
(3)你如果配置了webpack.config.js文件了,而且在packag.json里面修改了script. 直接执行 npm run bundle 不用特指index.js文件了。因为配置文件里面给你指明了
3.看官方档案
讲的这里,就可以查看官网网址的开始了。看官方源码才是最原汁原味的讲解 看官网讲解
4.打包完页面信息解释
打完包,控制台会显示很多条信息。hash,version,等等啊。这些都是打包的信息提示,告知我们打包的状况,下面我们详细解释一下信息都是啥意思:
npm run bundle
-
Hash: hash代表本次打包的唯一hash值,每一次打包此值都是不一样的
-
Version: 详细展示了我们使用webpack的版本号
-
Time: 代表我们本次打包的耗时
-
Asset: 代表我们打包出的文件名称
-
Size: 代表我们打包出的文件的大小
-
Chunks: 代表打包后的.js文件对应的id,id从0开始,依次往后+1
-
Chunks Names: 代表我们打包后的.js文件的名字,至于为何是main,而不是其他的内容,这是因为在我们的webpack.config.js中,entry:'./index.js'是对如下方式的简写形式:
// path为Node的核心模块
const path = require('path');
module.exports = {
// entry: './index.js',
entry: {
main: './index.js'
}
// 其它配置
}
-
Entrypoint main = bundle.js: 代表我们打包的入口为main
-
warning in configuration: 提示警告,意思是我们没有给webpack.config.js设置mode属性,mode属性有三个值:development代表开发环境、production代表生产环境、none代表既不是开发环境也不是生产环境。如果不写的话,默认是生产环境,可在配置文件中配置此项,配置后再次打包将不会再出现此警告。
// path为Node的核心模块
const path = require('path');
module.exports = {
// 其它配置
mode: 'development'
}
webpack4 学习之路(4)-核心概念
一、开篇小啰嗦
基本配置完,大家就明白怎么回事了,现在如果有个需求需要你手动搭建一个脚手架,你就知道怎么下手了,哈哈哈,搭建vue.react的脚手架也是从这里开始的。只不过vue的后缀是.vue,webpack不认识。有的图片,后缀是.png.webapck也不认识,所以这章节就是webpack的核心配置。解决各种遇到的问题。让webpack发光发热
二、核心概念-loader
1. 啥是loader
loader是一种打包规则,它告诉了 Webpack 在遇到非.js文件时,应该如何处理这些文件,一个项目总有很多后缀不一样的文件。这个时候你要用loader告诉webpack怎么使用loader配置。
loader有如下几种固定的运用规则:(后面会解释)
使用test正则来匹配相应的文件
使用use来添加文件对应的loader
对于多个loader而言,从 右到左 依次调用
2.使用loader打包图片-file-loader/url-loader
开发场景中,几乎离不开图片,webpack怎么识别图片后缀呢? 蹬蹬蹬蹬。。。。
file-loader或者url-loader,后面介绍区别。需使用npm install进行安装 现在我们重点讲解file-loader
下面我们稍微修改一下代码,在网上随便找个图片放到项目文件夹里面
// index.js代码
import Header from './header.js';
import Content from './content.js';
import Footer from './footer.js';
import img from './1.png';
new Header();
new Content();
new Footer();
再次执行,npm run bundle 会报错,为啥。因为webpack 不认识png啊。咋办呢。在webpack.config.js配置文件里面配置
// path为Node的核心模块
const path = require('path');
module.exports = {
entry: './index.js', // 告诉webpack打包的入口文件在哪里,它从这里找,开始打包
module:{ // 告诉webpack打包的模块
rules:[{ // 遇到不同的后缀打包的规则
test:/\.png$/, // 正则,就是说遇到这个后缀怎么办
use:{ // 遇到上面的后缀文件就使用下面的loader
loader:'file-loader'
}
}]
},
output: { // 告诉webpack打好包以后放在那个文件夹。dist是自动生成的
filename: 'main.js',
path: path.resolve(__dirname, 'dist')
}
}
配置完别忘了下载啊
npm install file-loader -D
npm run bundle
哇擦擦擦。不报错了,解决了。
这个时候看dist包,里面就多了一个图片
这个时候再引入图片。挂在在页面上。打包就不会出错了
运用占位符
在以上打包图片的过程中,我们发现打包生成的图片好像名字是一串乱码,如果我们要原样输出原图片的名字的话,又该如何进行配置呢?这个问题,可以使用 占位符 进行解决。
文件占位符它有一些固定的规则,像下面这样:
[name]代表原本文件的名字
[ext]代表原本文件的后缀
[hash]代表一个md5的唯一编码
//webpack.config.js
// path为Node的核心模块
const path = require('path');
module.exports = {
// 其它配置
module: {
rules: [
{
test: /\.(png|jpg|gif)$/,
use: {
loader: 'file-loader',
options: {
name: '[name]_[hash].[ext]'
}
}
}
]
}
}
这样配置生成的文件名字就变了
|-- dist
| |-- avatar_bd7a45571e4b5ccb8e7c33b7ce27070a.jpg
说完了file-loader,那什么是url-loader呢
其实url-loader跟file-loader功能差不多,但是url-loader多了个功能就是图片打包的时候多了个配置项 limit
npm install url-loader -D
//webpack.config.js
// path为Node的核心模块
const path = require('path');
module.exports = {
// 其它配置
module: {
rules: [
{
test: /\.(png|jpg|gif)$/,
use: {
loader: 'url-loader',
options: {
name: '[name]_[hash].[ext]',
limit:2000 // 字节判断
}
}
}
]
}
}
limit 的意思是如果你的图片大小超过2000个字节的话,就跟file-loader吧图片打包到dist目录下,但是如果小于2000字节,就直接默认吧图片转换成base64添加到bundle.js里面。这样就能减少不必要的图片请求。增加性能。vue脚手架的webpack配置就是这样写的啊
3.使用loader打包静态样式-css-loader/style-loader
css打包部分就很复杂,设置很乱,因为css后缀有很多个文件,.css,.scss,.less 等等。而且css在写的时候,可能会有互相影响的问题,这就互相干扰了,所以打包的时候可能还要配置css模块化,避免互相干扰,别急,一步步来。慢慢把这块啃干净
打包说明
样式文件分为几种情况,每一种都需要不同的loader来处理:
普通.css文件,使用style-loader和css-loader来处理
.less文件,使用less-loader来处理
.sass或者.scss文件,需要使用sass-loader来处理
.styl文件,需要使用stylus-loader来处理
乍一看。我去。这么多loader,啥时候能记住啊,没事,一步步来。先说前两个
css-loader:负责解析 CSS 代码,主要是为了处理 CSS 中的依赖,例如 @import 和 url() 等引用外部文件的声明,比如一个css的文件,引入了另一个css的文件,css-loader就可以吧所有的css整合到一快。否则就是无法解析了
style-loader:会将 css-loader 解析的结果转变成 JS 代码,运行时动态插入 style 标签来让 CSS 代码生效。
样式生效以后,不会生成一个css的文件夹,webpack会通过 style-loader自动的吧样式加在头部的 style 里面
- 打包css文件
首先安装style-loader和css-loader
npm install style-loader -D
npm install css-loader -D
现在实战啊,
把页面目录该删删,改加加,改成这个样子,吧头部,内容。脚步的js文件删除,在根目录新建一个src的文件夹,吧index.js移动过去
|-- webpack(名字随便起)
| |--src
| |--index.js
| |--index.css
| |-- webpack.config.js
| |-- package.json
index.js代码如下
// index.js代码
import './index.css';
img.src = avatar;
img.classList.add('avatar');
root.appendChild(img);
index.css代码如下
.avatar{
width: 200px;
height: 200px;
border:1px solid #0f0;
}
webpack.config.js代码如下
// path为Node的核心模块
const path = require('path');
module.exports = {
entry: './src/index.js', // 告诉webpack打包的入口文件在哪里,它从这里找,开始打包
module:{
rules:[{
test:/\.png$/,
use:{
loader:'file-loader'
}
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader'] // 因为需要两个loader.用数组。
}]
},
output: { // 告诉webpack打好包以后放在那个文件夹。dist是自动生成的
filename: 'main.js',
path: path.resolve(__dirname, 'dist')
}
}
如果代码中使用sass怎么办呢。这个时候就是有sass-laoder了
npm install sass-loader -D
npm install node-sass -D
下载好以后,修改一下代码
// webpack.config.js代码如下
const path = require('path');
module.exports = {
entry: './src/index.js', // 告诉webpack打包的入口文件在哪里,它从这里找,开始打包
module:{
rules:[{
test:/\.png$/,
use:{
loader:'file-loader'
}
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader', 'sass-loader'] // 因为需要两个loader.用数组。
}]
},
output: { // 告诉webpack打好包以后放在那个文件夹。dist是自动生成的
filename: 'main.js',
path: path.resolve(__dirname, 'dist')
}
}
// 这里注意下,loader数组里面。都是从下到上,从右到左执行的,
//先是把scss语法转换为css,
//然后处理css文件的互相引用。
//最后挂载到html上。样式生效。
目录中的css文件改为 .scss后缀。里面的内容修改为 嵌套。就是这样
body{
.avatar{
.....
}
}
最后记得修改引用这个scss的文件的后缀
执行打包命令。页面上。语法会转换成 css的语法,并且生效。
写到css我们就不可避免的涉及到兼容啊。浏览器厂商支持程度啊之类的问题,尤其是css3 每个浏览器厂商支持的,兼容都需要前缀。webpack有这个loader 可以帮我们实现
- 安装这个loader
npm install postcss-lader -d
- 我们先修改样式文件,加一个css3的属性transform
body{
.avatar{
width: 150px;
height: 150px;
border:1px solid #0f0;
transform: translate(100px,100px)
}
}
-
我们看官网的这个loader配置方式。 官网
-
根据官网我们得知,接下来就是在根目录下新建一个文件
postcss.config.js
module.exports = {
plugins: [
require('autoprefixer')
]
}
别忘了下载这个插件
npm install autoprefixer -D
最后别忘了在webpack.config.js里面的loader数组再加一个postcss-loader
执行打包命令。看一下控制台的的样式 会加上厂商前缀
4.css打包的模块化
CSS的模块化打包的理解是:我是一个单独的模块。如果我引用了你的样式,就生效,如果没有引入,那就不要干扰我的样式。
开发中有这中场景。
我是一个独立的模块A。我里面有我自己的一堆东西。这个时候,index.js入口文件全局引入了一个样式。同时index.js引入了A。那么。此时全局的样式就会影响A。这个时候就需要css模块化打包解决问题
修改下目录和里面的内容,吧index.html移动到dist文件下面
|-- webpack(名字随便起)
| |--src
| |--1.png
| |--create.js
| |--index.js
| |--index.scss
| |-- webpack.config.js
| |-- package.json
create.js内容
// create.js内容
import avatar from './1.png';
function create() {
var root = document.getElementById('root');
var img = new Image();
img.src = avatar;
img.classList.add('avatar');
root.appendChild(img);
}
export default create
index.js内容
// index.js代码
import avatar from './1.png';
import './index.scss';
import create from './avator'
create()
var root = document.getElementById('root');
var img = new Image();
img.src = avatar;
img.classList.add('avatar');
root.appendChild(img);
此时修改完以后,执行打包的命令。你会发现,你的独立模块样式被index.css影响了。这可就不好了。怎么办呢
使用css模块化。修改代码 webpack.config.js代码
// path为Node的核心模块
const path = require('path');
module.exports = {
mode:'development',
entry: './src/index.js', // 告诉webpack打包的入口文件在哪里,它从这里找,开始打包
module:{
rules:[{
test:/\.png$/,
use:{
loader:'url-loader',
options:{
name:'[name]_[hash].[ext]',
outputPath:'images/',
limit:20000
}
}
},
{
test: /\.scss$/,
use: ['style-loader',
{
loader:'css-loader',
options:{
// 意思是如果一个css引入另一个css.那么也要把下面的loader走完
importLoaders:2,
// 开启css模块化
modules:true
}
},
'sass-loader',
'postcss-loader'
]
}]
},
output: { // 告诉webpack打好包以后放在那个文件夹。dist是自动生成的
filename: 'main.js',
path: path.resolve(__dirname, 'dist')
}
}
body{
.avatar{
width: 150px;
height: 150px;
border:1px solid #0f0;
transform: translate(100px,100px)
}
.mm{
width: 250px;
height: 250px;
border:1px solid #f00;
transform: translate(100px,100px)
}
}
index.js
// index.js代码
import avatar from './1.png';
import style from './index.scss';
import create from './avator'
create()
var root = document.getElementById('root');
var img = new Image();
img.src = avatar;
img.classList.add(style.avatar); // 样式使用要求模块化了
root.appendChild(img);
avator.js代码
import avatar from './1.png';
import style from './index.scss'
function create() {
var root = document.getElementById('root');
var img = new Image();
img.src = avatar;
img.classList.add(style.mm); // 这么使用样式,用啥就是用啥,互不干扰
root.appendChild(img);
}
export default create
三、webpacK的plugins使打包更加便捷
plugins 是插件的意思,插件的意思是,在执行的过程中,来帮助webpack做一些事
上面内容我们在写的时候,有这样的一个问题。就是webpack 只管吧js css 图片等文件通过loader 打包好以后,就直接放在dist里面,不管了,我们每次都需要手动创建一个index.html。来引入打包好的js文件,这样多费劲啊,如果能自动生成html.并引入js文件那该多好啊。下面就是插件们登场的时候了
npm install html-webpack-plugin -D
这个插件就能解决上面的问题
我们现在重新把页面目录删删减减
|-- webpack(名字随便起)
| |--src
| |--1.png
| |--index.js
| |--index.scss
| |--index.html
| |-- webpack.config.js
| |-- package.json
webpack.config.js代码
// path为Node的核心模块
const path = require('path');
const HtmlWebpackplugin = require('html-webpack-plugin')
module.exports = {
mode:'development',
entry: './src/index.js', // 告诉webpack打包的入口文件在哪里,它从这里找,开始打包
module:{
rules:[{
test:/\.png$/,
use:{
loader:'url-loader',
options:{
name:'[name]_[hash].[ext]',
outputPath:'images/',
limit:20000
}
}
},
{
test: /\.scss$/,
use: ['style-loader',
{
loader:'css-loader',
options:{
importLoaders:2,
modules:true
}
},
'sass-loader',
'postcss-loader'
]
}]
},
plugins:[new HtmlWebpackplugin({
// 注入模板,这个html的内容会注入到自动生成的html里面
template:'src/index.html'
})],
output: { // 告诉webpack打好包以后放在那个文件夹。dist是自动生成的
filename: 'main.js',
path: path.resolve(__dirname, 'dist')
}
}
index.js的代码
// index.js代码
import avatar from './1.png';
import style from './index.scss';
var root = document.getElementById('root');
var img = new Image();
img.src = avatar;
img.classList.add(style.avatar);
root.appendChild(img);
index.html代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<div id="root"></div>
</body>
</html>
现在吧dist 文件夹删除掉,重新打包,会发现dist文件下会自动生成html的文件,而且还自动引入了打包好的js文件
自动生成的html文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<div id="root"></div>
<script type="text/javascript" src="main.js"></script></body>
</html>
假如有以下场景,我们需要修改打包好的js文件。比如现在打包好我们叫bundle.js文件。我们现在改成dist.js。文件。执行以下打包,我们发现。dist的文件重新生成一个dist.js的文件。但是。。。但是 bundle.js 这个我们上次生成的文件居然还在。这可不行。我们需要一个插件就是每次生成新的打包js文件,就把以前的清除掉
npm install clean-webpack-plugin -D
webpack.config.js代码
// path为Node的核心模块
const path = require('path');
const HtmlWebpackplugin = require('html-webpack-plugin')
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
module.exports = {
mode:'development',
entry: './src/index.js', // 告诉webpack打包的入口文件在哪里,它从这里找,开始打包
module:{
......
},
plugins:[
new CleanWebpackPlugin(), // 在打包之前执行,清空所有的dist内容
new HtmlWebpackplugin({template:'src/index.html'})
],
output: { // 告诉webpack打好包以后放在那个文件夹。dist是自动生成的
filename: 'mm.js',
path: path.resolve(__dirname, 'dist')
}
}
注意版本问题,老版本的clean-webpack-plugin可能需要参数,但是新版本的不需要
四、entry和output的基本配置
- entry 可以是一个字符串,也可以是一个对象。 (1)假如entry 不设置名字的话,打包就以output的filename的输出为主
entry: './src/index.js', // 入口文件
output: { //输出文件
filename: 'app.js',
path: path.resolve(__dirname, 'dist') // path 会在根目录生成一个dist的文件
},
这种情形打包出来的是 app.js
(2)如果filename不设置名字,就医entry的名字为主
entry: {
main:'./src/index.js', // 入口文件
}
output: { //输出文件
path: path.resolve(__dirname, 'dist') // path 会在根目录生成一个dist的文件
},
这种情形打包出来的是 main.js
- 有这个需求,需要把打包文件里面的js文件生成两个, 修改entry和output的配置,让output的输出变成一个占位符。这样就能生成两个js
entry: {
main:'./src/index.js',
sub:'./src/index.js'
}, // 入口文件
output: { //输出文件
filename: '[name].js',
path: path.resolve(__dirname, 'dist') // path 会在根目录生成一个dist的文件
},
3.有时候我们需要打包好吧index.html给后端作为入口文件,里面引入的js文件前面加上域名,比如cdn什么的,这时候在output里面添加 publicPath:域名,
output: { // 告诉webpack打好包以后放在那个文件夹。dist是自动生成的
publicPath:'https://ww.cdn.com',
filename: '[name].js',
path: path.resolve(__dirname, 'dist')
}
打包好的html文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<div id="root"></div>
<script type="text/javascript" src="https://ww.cdn.com/aa.js"></script>
<script type="text/javascript" src="https://ww.cdn.com/bb.js"></script>
</body>
</html>
五、sourceMap的配置
我们知道webpack把所有的js代码打包到一快,有时候开发环境下没问题,但是生产环境下就有问题。但是代码全部都打包压缩了,根本不知道那里有问题,这个时候sourceMap就出场了。sourceMap顾名思义 就是 资源导图,它会告诉你现在打包好的js文件,运行出错的时候,对应没有打包的js文件的哪一行那一列。这下子就知道怎么修改了。
它是一种映射关系,它映射了打包后的代码和源代码之间的对应关系,一般通过devtool来配置。
webpack.config.js的代码
// path为Node的核心模块
const path = require('path');
const HtmlWebpackplugin = require('html-webpack-plugin')
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
module.exports = {
mode:'development',
devtool:'source-map', // 配置这一行
entry: {
}, // 告诉webpack打包的入口文件在哪里,它从这里找,开始打包
module:{
rules:[{
}]
},
plugins:[
],
output: { // 告诉webpack打好包以后放在那个文件夹。dist是自动生成的
}
}
devtool:的sourceMap的配置很多个选项。涉及到打包速度的快慢问题,这是官网的配置
devtool: 配置的选项
build: 第一次打包的速度
rebuild: 以后打包的速度
production: 是否是生产环境
quality: 打包的质量
-
devtools:'sourceMap',可以开启这个功能,如果配置了sourcemap.打包的速度会变慢的。
-
使用sourcemap以后,你会发现,打包好的文件里面,有个.js.map的映射文件
-
官方文档 配置 里面, 有个选项 devtool.里面有很详细的使用方法,
(1)sourceMap.打包出一个xx.js.map的文件
(2)inline-source-map,会把map的文件取消,转换成一个base64的行字符串加在打包的js文件里面.
(3)inline-cheap-source-map,上面的两个会把哪一行,那一列错的位置告诉我们,但是这个会把那一列去到,提高性能。
(4)cheap-module-eval-source-map,开发使用这个最好,全面,速度还快一点 开发环境
(5)cheap-module-source-map,生产使用这个比较好,mode:producton 生产环境
六、使用WebpackDevServer提升开发的效率
这一个插件很重要,因为跟我们前端开发日常息息相关。我们有时候使用vue,react 会发现启动了一个8080端口,3000端口。帮我们本地开了一个小型的服务。同时ajax请求数据(ajax必须在服务器上才能通过http协议请求数据),都是WebpackDevServer的功劳。搭配热更新效率更加好,下面我们就详细学习一下
-
我们在开发中会遇到这样的问题,就是每次修改完代码,都需要手动执行打包的命令,然后在dist 文件夹打开index.html的页面,更新了。还要重新打包,回到浏览器刷新,费劲。
-
现在有了WebpackDevServer就不用这么费劲了,因为它能时刻监听你是否更新,并且自动打包,生成一个小型本地服务,并自动更新浏览器的页面,那么怎们用呢
npm install webpack-dev-server -D
在index.js页面内容里面随便加一行代码
index.js代码
console.log(1233)
webpack.config.js代码
// path为Node的核心模块
const path = require('path');
const HtmlWebpackplugin = require('html-webpack-plugin')
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
module.exports = {
mode:'development',
devtool:'source-map',
entry: {
main:'./src/index.js',
},
devServer:{ // 添加一个devServer的模块,启动小服务,端口8020的(可以自己设置)
contentBase:'./dist',
port:8020
},
module:{
rules:[{
....
}]
},
plugins:[
...
],
output: { // 告诉webpack打好包以后放在那个文件夹。dist是自动生成的
filename: '[name].js',
path: path.resolve(__dirname, 'dist')
}
}
package.json里面
{
"name": "webpack-jue",
"version": "1.0.0",
"description": "",
"scripts": {
"bundle": "webpack", // 更新后需要每次手动的打包。。。。
"watch": "webpack --watch", // 更新后,不用手动打包,但是要手动刷新浏览器
"start": "webpack-dev-server" // 只要代码变更,保存,自动打包,刷新浏览器
},
"author": "",
"license": "ISC",
"devDependencies": {
"autoprefixer": "^9.6.1",
"clean-webpack-plugin": "^3.0.0",
"css-loader": "^3.1.0",
"file-loader": "^4.1.0",
"html-webpack-plugin": "^3.2.0",
"install": "^0.13.0",
"node-sass": "^4.12.0",
"npm": "^6.10.3",
"postcss-loader": "^3.0.0",
"sass-loader": "^7.1.0",
"style-loader": "^0.23.1",
"url-loader": "^2.1.0",
"webpack": "^4.38.0",
"webpack-cli": "^3.3.6",
"webpack-dev-server": "^3.8.0"
}
}
七、Hot Module Replacement 热模块更新
模块热更新(HMR)的理解:它能够让我们在不刷新浏览器(或自动刷新)的前提下,在运行时帮我们更新最新的代码。
模块热更新(HMR)已内置到 Webpack ,我们只需要在webpack.config.js中像下面这样简单的配置即可,无需安装别的东西。
webpack.config.js代码
const webpack = require('webpack');
module.exports = {
// 其它配置
devServer: {
contentBase: 'dist',
open: true,
port: 3000,
hot: true, // 启用模块热更新
hotOnly: true // 模块热更新启动失败时,重新刷新浏览器
},
plugins: [
// 其它插件
new webpack.HotModuleReplacementPlugin()
]
}
在模块热更新(HMR)配置完毕后,我们现在来想一下,什么样的代码是我们希望能够热更新的,我们发现大多数情况下,我们似乎只需要关心两部分内容:CSS文件和.js文件,根据这两部分,我们将分别来进行介绍。
CSS中的模块热更新
首先我们在src目录下新建一个style.css样式文件,它的代码可以这样下:
div:nth-of-type(odd) {
background-color: red;
}
随后我们改写一下src目录下的index.js中的代码,像下面这样子:
import './style.css';
var btn = document.createElement('button');
btn.innerHTML = '新增';
document.body.appendChild(btn);
btn.onclick = function() {
var dom = document.createElement('div');
dom.innerHTML = 'item';
document.body.appendChild(dom);
}
由于我们需要处理CSS文件,所以我们需要保留处理CSS文件的loader规则,像下面这样 (其他loader配置不用动,单独加这个css规则就行)
webpack.config.js代码
module.exports = {
// 其它配置
module: {
rules: [
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
}
]
}
}
在以上代码添加和配置完毕后,我们使用npm run start进行打包,我们点击按钮后,它会出现如下的情况 打包结果
理解: 由于item是动态生成的,当我们要将red颜色改变成yellow时,模块热更新能帮我们在不刷新浏览器的情况下,替换掉样式的内容。直白来说:自动生成的item依然存在,只是颜色变了。
在js中的模块热更新
我们开发中,vue-laoder已经把这个功能内置了,
所以不用单独去配置这个功能
所以不用单独写什么。每次vue的项目保存一下,
页面自动就改变了,就是这个热模快更新的作用
在介绍完CSS中的模块热更新后,我们接下来介绍在js中的模块热更新。
首先,我们在src目录下创建两个.js文件,分别叫counter.js和number.js,它的代码可以写成下面这样:
// counter.js代码
export default function counter() {
var dom = document.createElement('div');
dom.setAttribute('id', 'counter');
dom.innerHTML = 1;
dom.onclick = function() {
dom.innerHTML = parseInt(dom.innerHTML,10)+1;
}
document.body.appendChild(dom);
}
number.js中的代码是下面这样的:
// number.js代码
export default function number() {
var dom = document.createElement('div');
dom.setAttribute('id','number');
dom.innerHTML = '1000';
document.body.appendChild(dom);
}
添加完以上两个.js文件后,我们再来对index.js文件做一下小小的改动:
// index.js代码
import counter from './counter';
import number from './number';
counter();
number();
在以上都改动完毕后,我们使用npm run start进行打包,在页面上点击数字1,让它不断的累计到你喜欢的一个数值(记住这个数值),这个时候我们再去修改number.js中的代码,将1000修改为3000,也就是下面这样修改:
// number.js代码
export default function number() {
var dom = document.createElement('div');
dom.setAttribute('id','number');
dom.innerHTML = '3000';
document.body.appendChild(dom);
}
我们发现,虽然1000成功变成了3000,但我们累计的数值却重置到了1,这个时候你可能会问,我们不是配置了模块热更新了吗,为什么不像CSS一样,直接替换即可?
回答:这是因为CSS文件,我们是使用了loader来进行处理,有些loader已经帮我们写好了模块热更新的代码,我们直接使用即可(类似的还有.vue文件,vue-loader也帮我们处理好了模块热更新)。而对于js代码,还需要我们写一点点额外的代码,像下面这样子:
import counter from './counter';
import number from './number';
counter();
number();
// 额外的模块HMR配置
if(module.hot) {
module.hot.accept('./number.js', () => {
document.body.removeChild(document.getElementById('number'));
number();
})
}
写完上面的额外代码后,我们再在浏览器中重复我们刚才的操作,即:
累加数字1带你喜欢的一个值 修改number.js中的1000为你喜欢的一个值 以下截图是我的测试结果,同时我们也可以在控制台console上,看到模块热更新第二次启动时,已经成功帮我们把number.js中的代码输出到了浏览器。 模块热更新结果
小结:在更改CSS样式文件时,我们不用书写module.hot,这是因为各种CSS的loader已经帮我们处理了,相同的道理还有.vue文件的vue-loader,它也帮我们处理了模块热更新,但在.js文件中,我们还是需要根据实际的业务来书写一点module.hot代码的。
八、使用bable处理Es6语法
我们在开发的时候会写到ES6语法,但是这个语法,好多的浏览器不支持, 所以需要各种办法去吧ES6的语法转换我ES5,这就用到了bable 使用之前,先下载三个依赖
// babel-loader是和webpack建立连接的桥梁,
// @babel/core 是bable的核心库
$ npm install babel-loader @babel/core --save-dev
// @babel/preset-env这个模块是吧es6转换为es5的模块
$ npm install @babel/preset-env --save-dev
// @babel/polyfilles5转换为es5语法转了,但是有些变量或者方法,浏览器还是不
// 认识,所以这个模块就是这个个别的方法或者变量的补丁,
$ npm install @babel/polyfill --save-dev
安装完以后,我们改一下页面的目录 src只留下index.js入口文件就可以了 同事在根目录新建一个.babelrc的文件
|-- webpack(名字随便起)
| |--src
| |--index.js
| |--index.html
| |-- webpack.config.js
| |-- package.json
| |-- .babelrc
webpack.config.js的代码,loader部分新加一个babel-loader
// path为Node的核心模块
const path = require('path');
const HtmlWebpackplugin = require('html-webpack-plugin')
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
module.exports = {
mode:'development',
devtool:'source-map',
entry: {
main:'./src/index.js',
},
devServer:{
......
},
module:{
rules:[
{ test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader" ,
},
....
]
},
plugins:[
......
],
output: {
......
}
}
.babelrc文件
{
"presets": [["@babel/preset-env", {
"corejs": 2,// 目的是,按需引入需要的es6补充语法,如果不设置,就是全局引入,包会很大。
"useBuiltIns": "usage" // 如果不配置,polyfill补丁会把所有的es补丁补充上
,代码打包很大,这个配置上,按需补充,就会很小
}]]
}
index.js代码
// index.js代码
const arrppp = [
new Promise(() => {}),
new Promise(() => {})
]
arrppp.map( item => {
console.log(item)
})
执行打包命令
npx webpack
查看main.js的代码,会发现已经转换为es5语法,并且。补充了es6新特性的变量和语法
webpack4 学习之路(5)-高级概念
一、Tree shaking 概念以及如何使用
- Tree Shaking,树枝摇晃,是一个术语,通常用于描述移除js中未使用的代码。 比如我们在开发中,有一个模块有大量的方法,但是引用其中一个方法,打包的时候,其他没有用的方法也会打包,就会代码冗余,所以需要设置一下,吧不需要的方法,不打包。
注意
Tree Shaking 只适用于ES
Module语法(既通过export导出,import引入),因为它依赖于ES
Module的静态结构特性。
下面我们重新改造一下目录
我们需要现在src目录下新建一个math.js文件,它的代码如下:
export function add(a, b) {
console.log(a + b);
}
export function minus(a, b) {
console.log(a - b);
}
接下来我们对index.js做一下处理,它的代码像下面这样,从math.js中引用add方法并调用:
import { add } from './math'
add(3, 2);
对webpack.config.js做一下配置,让它支持Tree Shaking,它的改动如下:
const path = require('path');
module.exports = {
mode: 'development',
devtool: 'source-map',
entry: {
main: './src/index.js'
},
...
...
...
optimization: {
usedExports: true
},
...
...
...
output: {
filename: 'main.js',
path: path.resolve(__dirname,'dist')
}
}
修改package.json的文件配置,添加一行sideEffects的属性
由于Tree Shaking作用于所有通过import引入的文件,如果我们引入第三方库,例如:import _ from 'lodash'或者.css文件,例如import './style.css' 时,如果我们不 做限制的话,Tree Shaking将起副作用,会认为没有导出,就把这个模块全部否定不打包。SideEffects属性能帮我们解决这个问题:它告诉webpack,我们可以对哪些文件不做 Tree Shaking
// 如果不希望对任何文件进行此配置,可以设置sideEffects属性值为false
// *.css 表示 对所有css文件不做 Tree Shaking
// @babael/polyfill 表示 对@babel/polyfill不做 Tree Shaking
"sideEffects": [
"*.css",
"@babel/polyfill"
],
配置完毕后,我们依然使用npx webpack进行打包,可以看到,它的打包结果如下所示:
打包代码分析:以上代码是一段被压缩过后的代码,我们可以看到,上面只有add方法,未使用的minus方法并没有被打包进来,这说明在开发环境下我们的Tree Shaking起了作用。
这是开发环境,如果是生产环境,optimization 这个属性就不用配置了。但是sideEffects还是需要配置的。打包一下。还是这个效果,没有用的方法,就不会打包进去。
二、区分开发模式和生产模式
如果我们要区分Tree Shaking的开发环境和生产环境,那么我们每次打包的都要去更改webpack.config.js文件,是不是很麻烦啊!
区分开发环境和生产环境,最好的办法是把公用配置提取到一个配置文件,生产环境和开发环境只写自己需要的配置,在打包的时候再进行合并即可,webpack-merge 可以帮我们做到这个事情。
首先,我们模仿一下vue的脚手架的形式,把 Webpack 相关的配置都放在根目录下的build文件夹下,所以我们需要新建一个build文件夹,随后我们要在此文件夹下新建三个.js文件和删除webpack.config.js,它们分别是:
webpack.common.js:Webpack 公用配置文件
webpack.dev.js:开发环境下的 Webpack 配置文件
webpack.prod.js:生产环境下的 Webpack 配置文件
新建完webpack.common.js文件后,我们需要把公用配置提取出来,它的代码看起来应该是下面这样子的:
// path为Node的核心模块
const path = require('path');
const HtmlWebpackplugin = require('html-webpack-plugin')
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
module.exports = {
entry: {
main:'./src/index.js',
}, // 告诉webpack打包的入口文件在哪里,它从这里找,开始打包
module:{
rules:[
{ test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader" ,
},
{
test:/\.png$/,
use:{
loader:'url-loader',
options:{
name:'[name]_[hash].[ext]',
outputPath:'images/',
limit:20000
}
}
},
{
test: /\.scss$/,
use: ['style-loader',
{
loader:'css-loader',
options:{
importLoaders:2,
modules:true
}
},
'sass-loader',
'postcss-loader'
]
}]
},
plugins:[
new CleanWebpackPlugin(),
new HtmlWebpackplugin({template:'src/index.html'})
],
output: { // 告诉webpack打好包以后放在那个文件夹。dist是自动生成的
filename: '[name].js',
path: path.resolve(__dirname, '../dist')
}
}
提取完 Webpack 公用配置文件后,我们开发环境下的配置,也就是webpack.dev.js中的代码,将剩下下面这些:
// path为Node的核心模块
module.exports = {
mode:'development',
devtool:'source-map',
devServer:{
contentBase:'./dist',
port:8020
},
optimization: {
usedExports:true
},
}
而生产环境下的配置,也就是webpack.prod.js中的代码,可能是下面这样子的:
module.exports = {
mode:'production',
devtool:'source-map',
optimization: {
usedExports:true
},
}
在处理完以上三个.js文件后,我们需要做一件事情:
- 当处于开发环境下时,把webpack.common.js中的配置和webpack.dev.js中的配置合并在一起
- 当处于开发环境下时,把webpack.common.js中的配置和webpack.prod.js中的配置合并在一起
- 针对以上问题,我们可以使用webpack-merge进行合并,在使用之前,我们需要使用如下命令进行安装:
$ npm install webpack-merge -D
安装完毕后,我们需要对webpack.dev.js和webpack.prod.js做一下手脚,其中webpack.dev.js中的改动如下(代码高亮部分):
// path为Node的核心模块
const merge = require('webpack-merge');
const commonConfig = require('./webpack.common');
const devConfig = {
mode:'development',
devtool:'source-map',
devServer:{
contentBase:'./dist',
port:8020
},
optimization: {
usedExports:true
},
}
module.exports = merge(commonConfig, devConfig);
相同的代码,webpack.prod.js中的改动部分如下(代码高亮): js
// path为Node的核心模块
const merge = require('webpack-merge');
const commonConfig = require('./webpack.common');
const prodConfig = {
mode:'production',
devtool:'source-map',
optimization: {
usedExports:true
},
}
module.exports = merge(commonConfig, prodConfig);
要重新在package.json中配置一下我们的打包命令,它们是这样子写的:
"scripts": {
"dev": "webpack-dev-server --config ./build/webpack.dev.js",
"build": "webpack --config ./build/webpack.prod.js"
},
配置完打包命令,心急的你可能会马上开始尝试进行打包,你的打包目录可能长成下面这个样子:
|-- build
| |-- dist
| | |-- index.html
| | |-- main.js
| | |-- main.js.map
| |-- webpack.common.js
| |-- webpack.dev.js
| |-- webpack.prod.js
|-- src
| |-- index.html
| |-- index.js
| |-- math.js
|-- .babelrc
|-- postcss.config.js
|-- package.json
问题分析:当我们运行npm run build时,dist目录打包到了build文件夹下了,这是因为我们把Webpack 相关的配置放到了build文件夹下后,并没有做其他配置,Webpack 会认为build文件夹会是根目录,要解决这个问题,需要我们在webpack.common.js中修改output属性,具体改动的部分如下所示:
output: {
filename: '[name].js',
path: path.resolve(__dirname,'../dist')
}
那么解决完上面这个问题,赶紧使用你的打包命令测试一下吧,我的打包目录是下面这样子,如果你按上面的配置后,你的应该跟此目录类似
|-- build
| |-- webpack.common.js
| |-- webpack.dev.js
| |-- webpack.prod.js
|-- dist
| |-- index.html
| |-- main.js
| |-- main.js.map
|-- src
| |-- index.html
| |-- index.js
| |-- math.js
|-- .babelrc
|-- postcss.config.js
|-- package.json
三、webpack的code splitting 代码分割
每次面试都会问,你是怎么进行代码分割的,现在终于可以说出来了。
其实代码分割跟webpack 并没有实质联系。只是webpack 现在内置的插件可以帮我们进行代码分割,所有就绑在一块了。
Code Splitting 的核心是把很大的文件,分离成更小的块,让浏览器进行并行加载。
假如项目打包业务代码1m,引入的第三方库打包也是1m,如果不分割,
webapck就会打包出一个2m的文件,每次加载,都要加载一个2m的文件。很占空间
如果使用代码分割,就是打包出两个1m的文件。
我们知道,浏览器执行js代码,是可以并行加载的。所以速度会提升
另外,我们修改业务代码。第三方代码不改变,用户重新刷新,就只会
在单独加载一个1m的文件就好了。
常见的代码分割有三种形式:
手动进行分割:例如项目如果用到lodash,则把lodash单独打包成一个文件。
同步导入的代码:使用 Webpack 配置进行代码分割。
异步导入的代码:通过模块中的内联函数调用来分割代码。
1. 手动进行分割
手动进行分割的意思是在entry上配置多个入口,例如像下面这样:
module.exports = { entry: { main: './src/index.js', lodash: 'lodash' } } 这样配置后,我们使用npm run build打包命令,它的打包输出结果为:
Asset Size Chunks Chunk Names
index.html 462 bytes [emitted]
lodash.js 1.46 KiB 1 [emitted] lodash
lodash.js.map 5.31 KiB 1 [emitted] lodash
main.js 1.56 KiB 2 [emitted] main
main.js.map 5.31 KiB 2 [emitted] main
它输出了两个模块,也能在一定程度上进行代码分割,不过这种分割是十分脆弱的,如果两个模块共同引用了第三个模块,那么第三个模块会被同时打包进这两个入口文件中,而不是分离出来。(强烈不推荐)
所以我们常见的做法是关心后两种代码分割方法,无论是同步代码还是异步代码,都需要在webpack.common.js中配置splitChunks属性,像下面这样子:
module.exports = {
// 其它配置
...
...
...
optimization: {
splitChunks: {
chunks: 'all'
}
}
...
...
...
}
你可能已经看到了其中有一个chunks属性,它告诉 Webpack 应该对哪些模式进行打包,它的参数有三种:
- async:此值为默认值,只有异步导入的代码才会进行代码分割。
- initial:与async相对,只有同步引入的代码才会进行代码分割。
- all:表示无论是同步代码还是异步代码都会进行代码分割
以上只是大概介绍一下同步代码分割和异步代码分割的共同配置项。下面详细讲解。
2. 同步代码分割
首先,我们先介绍同步代码分割,所谓的同步代码分割,就是引入第三方库,正常使用,下面我们安装一个第三方库,例如:lodash,
npm install loadsh --save
然后对index.js中的代码做一些手脚,像下面这样:
import _ from 'lodash'
console.log(_.join(['Dell','Lee'], ' '));
就像上面提到的那样,同步代码分割,我们只需要在webpack.common.js配置chunks属性值为initial即可:
module.exports = {
// 其它配置
optimization: {
splitChunks: {
chunks: 'initial'
}
}
}
在webpack.common.js配置完毕后,我们使用npm run build来进行打包, 你的打包dist目录看起来应该像下面这样子:
|-- dist
| |-- index.html
| |-- main.js
| |-- main.js.map
| |-- vendors~main.js
| |-- vendors~main.js.map
打包分析:main.js使我们的业务代码,vendors~main.js是第三方模块的代码,在此案例中也就是_lodash中的代码。由此可见,业务代码main和第三方的代码vendors~main.js分割了。
3. 异步代码分割
如果我们只需要针对异步代码进行代码分割的话,我们只需要进行异步导入,Webpack会自动帮我们进行代码分割,异步代码分割它的配置如下:
module.exports = {
// 其它配置
optimization: {
splitChunks: {
chunks: 'async'
}
}
}
注意:由于异步导入语法目前并没有得到全面支持,需要通过 npm 安装
$ npm install @babel/plugin-syntax-dynamic-import -D 插件来进行转译
安装完毕后,我们需要在根目录下的.babelrc文件做一下改动,像下面这样子:
{
"presets": [["@babel/preset-env", {
"corejs": 2,
"useBuiltIns": "usage"
}]],
"plugins": ["@babel/plugin-syntax-dynamic-import"]
}
配置完毕后,我们需要对index.js做一下代码改动,让它使用异步导入代码块:
// 点击页面,异步导入lodash模块
document.addEventListener('click', () => {
getComponent().then((element) => {
document.getElementById('root').appendChild(element)
})
})
function getComponent () {
return import(/* webpackChunkName: 'lodash' */'lodash').then(({ default: _ }) => {
var element = document.createElement('div');
element.innerHTML = _.join(['Dell', 'lee'], ' ')
return element;
})
}
写好以上代码后,我们同样使用npm run build进行打包,dist打包目录的输出结果如下:
|-- dist
| |-- 1.js
| |-- 1.js.map
| |-- index.html
| |-- main.js
| |-- main.js.map
我们在浏览器中运行dist目录下的index.html,切换到network面板时,我们可以发现只加载了main.js,
当我们点击页面时,才 真正开始加载 第三方模块,
4. SplitChunksPlugin配置参数详解
webpack能进行代码分割的核心就是使用了SplitChunksPlugin这个插件, 这个插件有很多可以配置的属性,它也有一些默认的配置参数,它的默认配置参数如下所示,我们将在下面为一些常用的配置项做一些说明。
打开官网,找到这个插件 SplitChunksPlugin
module.exports = {
// 其它配置项
optimization: {
splitChunks: {
chunks: 'async', // 异步还是同步分割代码
minSize: 30000, // 如果模块大于30k就开始分割。否则就不分割
minChunks: 1, // 改模块必须引用一次以上才分割
maxAsyncRequests: 5, // 默认就行了
maxInitialRequests: 3,// 默认就行了
automaticNameDelimiter: '~',// 默认就行了
name: true,
cacheGroups: {
vendors: { //分组,如果模块满足在module包里面,就打包成vender.js形式
test: /[\\/]node_modules[\\/]/,
priority: -10// 值越大。越服从谁,比如一个loadsh的包,符合第一个组,也符合默认,就看priority的值,越大就打包到哪个组
},
default: { //分组,如果模块不在module包里面,打包成default.js形式
minChunks: 2,
priority: -20,
reuseExistingChunk: true // 如果一个模块已经被打包了,在遇到的时候,就忽略掉,直接使用以前的包
}
}
}
}
};
参数详细说明
# minSize
minSize默认值是30000,也就是30kb,当代码超过30kb时,才开始进行代码分割,小于30kb的则不会进行代码分割;与minSize相对的,maxSize默认值为0,为0表示不限制打包后文件的大小,一般这个属性不推荐设置,一定要设置的话,它的意思是:打包后的文件最大不能超过设定的值,超过的话就会进行代码分割。
为了测试以上两个属性,我们来写一个小小的例子,在src目录下新建一个math.js文件,它的代码如下:
export function add(a, b) {
return a + b;
}
新建完毕后,在index.js中引入math.js:
import { add } from './math.js'
console.log(add(1, 2));
打包分析:因为我们写的math.js文件的大小非常小,如果应用默认值,它是不会进行代码分割的,如果你要进一步测试minSize和maxSize,请自行修改后打包测试。
--------------------------------------------------------
# minChunks
默认值为1,表示某个模块复用的次数大于或等于一次,就进行代码分割。
如果将其设置大于1,例如:minChunks:2,在不考虑其他模块的情况下,以下代码不会进行代码分割:
// 配置了minChunks: 2,以下lodash不会进行代码分割,因为只使用了一次
import _ from 'lodash';
console.log(_.join(['Dell', 'Lee'], '-'));
--------------------------------------------------------
# maxAsyncRequests 和 maxInitialRequests
maxAsyncRequests:它的默认值是5,代表在进行异步代码分割时,前五个会进行代码分割,超过五个的不再进行代码分割。
maxInitialRequests:它的默认值是3,代表在进行同步代码分割时,前三个会进行代码分割,超过三个的不再进行代码分割。
--------------------------------------------------------
# automaticNameDelimiter
这是一个连接符,左边是代码分割的缓存组,右边是打包的入口文件的项,例如vendors~main.js
--------------------------------------------------------
# cacheGroups
说明
在进行代码分割时,会把符合条件的放在一组,然后把一组中的所有文件打包在一起,默认配置项中有两个分组,一个是vendors和default
vendors组: 以下代码的含义是,将所有通过引用node_modules文件夹下的都放在vendors组中
vendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10
}
default组: 默认组,意思是,不符合vendors的分组都将分配在default组中,如果一个文件即满足vendors分组,又满足default分组,那么通过priority的值进行取舍,值最大优先级越高。
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true
}
--------------------------------------------------------
# reuseExistingChunk
中文解释是复用已存在的文件。意思是,如果有一个a.js文件,它里面引用了b.js,但我们其他模块又有引用b.js的地方。开启这个配置项后,在打包时会分析b.js已经打包过了,直接可以复用不用再次打包。
// a.js
import b from 'b.js';
console.log('a.js');
// c.js
import b from 'b.js';
console.log('c.js');
四、shimming
有时候我们在引入第三方库的时候,不得不处理一些全局变量的问题,例如jQuery的‘$’符号,
lodash的_符号,但由于一些老的第三方库不能直接修改它的代码,每次每个页面使用。都需要单独的import ....,这样真的很费劲。这时我们能不能定义一个全局变量,当文件中存在$或者_的时候自动的帮他们引入对应的包。
- 我们可以使用ProvidePlugin插件来解决,这个插件已经被 Webpack 内置,无需安装,直接使用即可。
src目录下新建一个juqery.js的文件
export function ui() {
$('body').css('background','green')
}
index.js
import _ from 'lodash';
import $ from 'jquery';
import { ui } from './jquery';
ui();
var dom = $(`<div>${_.join(['ji', 'xin'], '---')}</div>`);
$('#root').append(dom);
这个时候npm run dev 打开页面。。你会发现报错,$ 符号找不到的错误
为啥呢? 因为index.js虽然引用了jquery的包。而且使用了ui的模块。但是模块之间是解耦的,没有关系。所以jquery的模块没有$的引用。为此使用ProvidePlugin插件可以在webpack帮忙全局引用
webpack.common.js
// path为Node的核心模块
const HtmlWebpackplugin = require('html-webpack-plugin')
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const webpack = require('webpack')
module.exports = {
entry: {
main:'./src/index.js',
}, // 告诉webpack打包的入口文件在哪里,它从这里找,开始打包
module:{
rules:[
...
...
}]
},
optimization: {
...
...
},
plugins:[
new CleanWebpackPlugin(),
new HtmlWebpackplugin({template:'src/index.html'}),
new webpack.ProvidePlugin({
$: 'jquery',
_: 'lodash'
})
],
}
再重新打包,就没有问题了。
四、webpack的性能优化
webpack如果配置好了,就可以增加打包的速度,所以webpack的性能问题也是一个面试或者工作中常见的问题。