大家好,我是前端小张同学,这是webpack内容第二次更新了,接下来我会将webpack一步一步深入,并分享给大家。
我正在参与四月更文挑战,一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第1天,点击查看活动详情。
今天我们的主题
是 ,webpack打包的进阶篇
上篇文章中我们讲到了,webpack的一些基础配置,配置loader
,配置打包入出口文件
,配置sourceMap
等等,基础知识,那我们这篇文章就介绍一下 , webpack的进阶篇 , 什么是进阶呢?请往下看。
1 :进阶篇
进阶篇 ,包含了webpack的,多入口打包,根据对应的环境进行打包,对.html
文件中的静态资源的处理,如何注入第三方库资源到全局,等等,接下来跟我一起探索吧。
1.1 知识点目录
-
webpack插件介绍
-
HTML中引入静态资源的处理
-
第三方库作为全局变量进行配置
-
根据环境进行打包配置
-
跨域&& 跨域的解决方案和原理
-
配置webpack的HMR
首先声明观点,我的文章是纯技术文章,不参杂任何的水分,每一个字和一行代码都是作者亲自敲出来的,如果你觉得我的文章写的有疑问,可以在评论区进行留言,欢迎讨论。
2 : webpack插件介绍
问题 : 在学习之前我们需要先弄清除一个问题 , 什么是插件 ? 它作用是什么 ?
顾名思义 : 插件就是 东西本身不含有的东西,由外部接入的,称为插件,能够帮助程序或者电脑完成某些事情的,称为插件。
2.1 webpack plugin
2.1.1 CleanWebpackPlugin
功能:自动清除dist目录下所有内容,并将最新打包的结果输出到dist目录下,在webpack打包完成时触发。
配置
1 : 下载 clean-webpack-plugin
yarn add clean-webpack-plugin --save-dev 或者
npm i --save-dev clean-webpack-plugin
在webpack.donfig.js 配置文件中,plugins节点下,进行配置,当然它还有配置选项,具体可以看npm上
cleanWebpackplugin选项配置。
// 将 该插件实例出来即可,执行打包则会自动清除 dist 目录
import CleanWebpackPlugin = require('clean-webpack-plugin')
plugins : [
new CleanWebpackPlugin()
]
2.1.2 HtmlWebpackPlugin
功能:将项目根目录下的index.html
复制到,dist 目录下,并自动将打包完的js文件引入。
配置
// webpack5 请执行
npm i --save-dev html-webpack-plugin 或
yarn add html-webpack-plugin --dev
// webpack 4 则需要 @4 指定版本
npm i --save-dev html-webpack-plugin@4 或
yarn add html-webpack-plugin@4 --dev
依然是在 plugins
节点下 进行 创建,更多内容请看 webpack 官网 –>htmlWebpackPlugin
import htmlWebpackPlugin = require('html-webpack-plugin')
plugins : [
new htmlWebpackPlugin({
filename : 'index.html', // 生成的html文件名称
template : './index.html' // 以哪一个模板作为操作的对象
})
]
2.1.3 copyWebpackPlugin
功能:将指定的资源,复制到指定的目录下去,通常将静态资源图片复制到dist/assets目录下。
配置
npm i --save-dev copy-webpack-plugin 或
yarn add copy-webpack-plugin --D
import CopyWebpackPlugin = require('copy-webpack-plugin')
plugins:[
new CopyWebpackPlugin({
// 注意 这里的配置 , 考虑到 复制的资源不仅仅只有一个 ,所以 patterns 可以传入多个配置对象
patterns : [
{
// 我们设置资源起点 设置绝对路径或者相对路径
from : path.resolve(__dirname , 'assets'),
// 复制完成后 输出到哪里 assets 就代表 dist/assets
// 想想为什么输出到 dist/assets 目录下,文章末尾给出答案
to : 'assets'
}
]
})
]
2.1.4 BannerPlugin
功能:在打包完的文件头部生成注释,注意这是一个webpack内置插件
,更多配置项请看官网 bannerPlugin
官网解释 : 为每个 chunk 文件头部添加 banner。
配置
const webpack = require('webpack')
plugins : [
new webpack.BannerPlugin({
banner : '测试bannerPlugin插件', // 添加注释的内容
entryOnly : true // 如果值为 true,将只在入口 chunks 文件中添加
})
]
ok,到此就介绍这四个插件,后面还有更多的好用插件,我会继续更新,接下来继续往下看。
3. HTML中引入静态资源的处理
需求: 我们经常用 Vue
或 react
开发,我们都知道 单页面应用,
但因为功能变更,我需要在项目的index.html
引入一些静态图片进行展示(注意这些资源是不会参与打包的),那我们上线后,在index.html
资源消失了,怎么办?
接下来 html-withimg-loader
就为大家解决这个问题。
我们可以借助一个 loader ,当然这个插件已经很老了,在这里只是提一下,最好的解决方案还是通过将静态资源 copy到dist/asstes 下直接引用
npm install html-withimg-loader --save
你就可以 直接 在html中以相对路径进行引用即可,这个loader会帮助你加载。
补充一点
在webpack 5 中,官方已经内置了静态资源模块,可以无需使用 Url-loader
进行静态资源处理,配置写在下方了,具体详情可见 webpack官网(静态资源处理)
配置
module : {
rules : [
{
test: /.(htm|html)$/i,
use: ['html-withimg-loader']
},
// webpack 5 中静态资源处理 发送一个单独的文件并导出 URL
{
test : /\.(png|jpeg|jpg|bmp)/,
type : 'asset/resource'
},
]
}
4: 第三方库作为全局变量进行配置
如何把 第三方库,比如 jquery
,lodash
,这些库 引入到全局,作为一个全局属性进行使用呢?
我们用Vue
的开发者应该都知道,通常我们要用一些第三方库,想在所有的Vue组件中都用到 是怎么做的?大家可以回忆一下,是不是,将该库导入创建实例,并把实例挂载到Vue原型身上,然后通过 this 访问 ?
import Vue from 'vue'
impo axios from 'axios'
// 在Vue2 中我们可能会这么做
vue.prototype.$ajax = axios
但是 其实 webpack
也能帮你解决这个问题,借助 expose-loader
。
老规矩,先下包。
npm install expose-loader --save-dev //or
yarn add -D expose-loader // or
pnpm add -D expose-loader // and
// 下载jquery 方便测试
yarn add jquery --save-dev
配置
module : {
rules : [
{
// [`require.resolve`(https://nodejs.org/api/modules.html#modules_require_resolve_request_options)
// 调用是一个 Node.js
//函数(和 webpack 进程中的 `require.resolve` 无关)。 `require.resolve` 给出模块的绝对路径
test : require.resove('jquery'),
loader : 'expose-loader',
options : {
// 映射关系 $ 对应的jquery 构造函数
exposes : ['$','jquery']
}
}
]
}
配置完成后
我们就可以在入口文件 index.js
文件中进行导入
index.js
import $ from 'jquery'
import 'useJqueryTest.js'
console.log('expose-loader' , $);
console.log('window-expose-loader' , window.$);
useJqueryTest.js
// 测试 是否能访问$ 暴露了一个 $属性在 window 身上
(function() {
console.log("子模块的jqurey" , $);
})()
分模块进行打印 $
属性 确认是否挂载到 window 身上, 如果 子模块能访问证明 已经挂载到 window 身上
在这里我们可以看到 window 身上已经有了 $
属性,说明该 expose-loader
已经生效了
5. webpack 根据环境进行打包配置
首先,在学习这个之前,我们先要理解,为什么要区分环境进行打包? 它的作用在哪里?能给我们带来什么用处,怎么学习它。
5.1 题外话
插句题外话,其实我们学习任何东西之前,我们一定要 三问自己
,搞清楚这三点,你的学习起来会很轻松,而不是强制性贯通到你的脑海里。
- 第一问 —> 这个东西是什么?
- 第二问 —> 它有什么作用
- 第三问 —> 我们应该怎么学习它
5.2 回到正题
5.2.1 为什么 我们要对webpack做环境区分呢?
因为我们开发时候与上线是某些操作是不一样的,如果开发环境
和生产环境
都是一样的操作,那我们就不必要上线了,我们开发时讲究的是 准确 , 高效 , 快捷
, 生产讲究的是 性能,速度,可访问性
等。
5.2.2 它的作用在哪里
它可以 帮助我们在 不同的环境 进行不同的操作 , 比如我们在开发环境中需要开启 sourceMap,更快的映射到准确的打印输出,比如我们在开发环境中访问的 192.168.0.1
服务器 , 但生产上 却访问的是 192.168.1.10
等等,在这种情况下我们就需要根据环境 来编写不同的配置
5.2.3 能给我们带来什么用处?
能够帮助我们在,不同的环境打包不同的配置,产出的结果也是不一样的,将我们编写的代码进行依赖分析
,css压缩
,treeShaking
scpoed-hoisting
等等。
5.3 如何区分环境?
在这里提供 两种方式
方式一 :
我们可以通过 在项目中 .env.developoment
和 env.production
文件中 配置一个属性 , NODE_ENV
在 .env.developoment
文件中 NODE_ENV
属性值 为 develpoment , 反之 在 env.production
NODE_ENV
属性值 为 production
例如 :
entry : './src/index.js',
output : {
path : path.join(__dirname , './dist/'),
// process.env.NODE_ENV 自动读取配置文件中的值
filename : process.env.NODE_ENV === 'developoment' ? 'main.js' : 'index.js'
},
方式二 :
通过运行不同的脚本指令执行不同的配置文件的方式(也是我接下来要讲的方式)
- 在根目录下创建
buildConfig
文件夹 - 在
buildConfig
下创建webpack.base.js
和webpack.dev.js
和webpack.prdo.js
文件 - 将已有的
webpack.connfig.js
文件中的内容服务到三个文件中去 - 根据环境进行区分
- 修改
package.json
文件中的script
1 :创建文件
2:区分配置(最关键的一步)
我们需要想想 , 哪些配置是 开发和生产环境都需要的,哪些是 开发时配置,哪些是生产配置?
教大家一个小技巧
如果 你发现 一个配置 生产环境
和 开发环境
都需要 那这个配置就是 公共配置没找出它们的交集
webpack.base.js
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin') // 开发也要生成html文件到dist 生产也需要 (公共配置)
const { CleanWebpackPlugin } = require('clean-webpack-plugin') // 开发也要,生产也需要 (公共配置)
const CpoyWebpackPlugin = require('copy-webpack-plugin') // 开发也要生产也要 (公共配置)
const webpack = require('webpack') // webpack核心包 根据情况而定,这里我们暂时先引用
module.exports = {
// 打包入口配置 开发和生产都需要打包并配置入口(公共配置)
entry : './src/index.js',
// 打包完成输出文件目录 开发和生产我们这里都 输出到 dist (公共配置)
output : {
path : path.join(__dirname , './dist/'),
filename : 'main.js'
},
//开发 时 主机时 localhost ,生产则应该是 0.0.0.0 (允许所有客户端访问) 所有这个不应该放在 webpack.base.js 中
/* devServer : {
// static : './dist/index.html',
open : true,
host : 'localhost'
port : 8081
},*/
/*
module 选项
集成了 css-loader 解析css 模块
集成了 less
集成了 静态资源处理
集成了 babel-loader 对 js 降级处理
集成了 html-withimg-loader
集成了 expose-loader
这些都是 开发环境 和 生产环境都需要的
*/
module : {
rules : [
{
test : /\.css$/,
use : ['style-loader' , 'css-loader']
},
{
test : /\.less$/,
use : ['style-loader' , 'css-loader' , 'less-loader']
},
// 配置 file-loader 解析文件
{
test : /\.(png|jpeg|jpg|bmp)/,
type : 'asset/resource'
},
{
test : /\.js$/,
use : {
loader : 'babel-loader',
},
exclude : /node_modules/
},
{
test : /\.(hmt|html)$/,
use : {
loader : 'html-withimg-loader'
}
},
{
test: require.resolve('jquery'),
loader : 'expose-loader',
options : {
exposes : ['$','jquery']
}
}
]
},
/*
插件
HtmlWebpackPlugin 前面说过开发时和生产都需要生成 html 到dist(公共配置)
CleanWebpackPlugin 开发时和生产时打包都需要自动清除旧的 dist 目录下的所有文件(公共配置)
CpoyWebpackPlugin 看情况 如果你需要复制静态资源 那就用 ,不需要则 不用就行 (自行决定)
BannerPlugin 看情况 如果你需要生成注释 那就用 ,不需要则 不用就行 (自行决定)
*/
plugins : [
new HtmlWebpackPlugin({
filename : 'index.html',
template : './index.html'
}),
new CleanWebpackPlugin(),
new CpoyWebpackPlugin ({
patterns : [
{
from : './assets',
to : 'assets'
}
]
}),
new webpack.BannerPlugin({
banner : '测试bannerPlugin插件',
entryOnly : true
})
],
// mode 这个肯定不是 公共配置 开发时 是development 生产时 production
// mode : 'production',
// 开启 sourceMap 开发时 需要开启 , 生产 不需要 所以这也不是公共配置
// devtool : 'cheap-module-source-map'
}
所以 最后生成的 文件应该是
webpack.config.js
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const CpoyWebpackPlugin = require('copy-webpack-plugin')
const webpack = require('webpack')
module.exports = {
entry : './src/index.js',
output : {
path : path.join(__dirname , './dist/'),
filename : 'main.js'
},
module : {
rules : [
{
test : /\.css$/,
use : ['style-loader' , 'css-loader']
},
{
test : /\.less$/,
use : ['style-loader' , 'css-loader' , 'less-loader']
},
// 配置 file-loader 解析文件
{
test : /\.(png|jpeg|jpg|bmp)/,
type : 'asset/resource'
},
{
test : /\.js$/,
use : {
loader : 'babel-loader',
},
exclude : /node_modules/
},
{
test : /\.(hmt|html)$/,
use : {
loader : 'html-withimg-loader'
}
},
{
test: require.resolve('jquery'),
loader : 'expose-loader',
options : {
exposes : ['$','jquery']
}
}
]
},
plugins : [
new HtmlWebpackPlugin({
filename : 'index.html',
template : './index.html'
}),
new CleanWebpackPlugin(),
new CpoyWebpackPlugin ({
patterns : [
{
from : './assets',
to : 'assets'
}
]
}),
new webpack.BannerPlugin({
banner : '测试bannerPlugin插件',
entryOnly : true
})
],
}
webpack.dev.js
模拟开发时 的配置
module.exports = {
devServer : {
host : 'localhost',
port : 8080 ,
open : true,
},
mode : 'development',
devtool : 'cheap-module-source-map'
}
webpack.prod.js
模拟 生产时的配置
module.exports = {
devServer : {
host : '0.0.0.0',
port : '8001',
open : true,
},
mode : 'production',
}
3.合并配置
我们可以借助 webpack-merge
这个包 进行 合并操作,将公共的配置与 开发环境 或生产环境配置进行合并。
yarn add webpack-merge --save-dev 或者
npm install webpack-merge --save-dev
配置
webpack-merge
这个包 解构出一个 merge
函数
合并 基础配置 和 生产时的配置
const { merge } = require('webpack-merge')
const baseConfig = require('./webpack.base.js')
// merge 函数接收 两个配置对象 , 最后合并成一个,如果 开发环境中也有与基础配置相同的属性 ,webpack-merge 会自动合并 并不会覆盖
module.exports = merge(baseConfig , {
devServer : {
host : '0.0.0.0',
port : '8001',
open : true,
},
mode : 'production',
})
4.配置 package.json
我们通过 –config 来指定 运行哪个配置文件 例如 当我们终端输入 npm run build
, webpack --config ./config/webpack.prod.js
就是运行我们刚刚合并的生产配置
和 基础配置
, 反之 当我们终端输入 npm run dev
, webpack-dev-server --config ./config/webpack.dev.js
则运行 合并的 开发配置
和 基础配置
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack --config ./config/webpack.prod.js",
"watch": "webpack --watch",
"dev": "webpack-dev-server --config ./config/webpack.dev.js"
},
到这里 ,恭喜你 ,你已经学会了 区分环境 进行打包了。
别急着高兴,想必做到这里 你已经遇到了一个问题,我们打包完的dist 目录好像生成的路径不对,dist
文件夹 怎么生成到 config 目录下了 , 难道不应该是 根目录下吗?
大家还记得,我们之前基础配置中的 output
配置选项吗 ?,到这里想必大家已经明白了 ,当前目录就是 /webpack-advanced/config
, 那 ./dist/
自然也是在 config 目录下生成了,那我们应该怎么做呢?
我们只需要向上翻一层 就可以了
path.join
方法使用特定于平台的分隔符作为定界符将所有给定的 path
片段连接在一起,然后规范化生成的路径。详情见
node-join
path.join(__dirname , '../', './dist/'),
output : {
// 以当前目录为基准合并出一个 绝对路径
path : path.join(__dirname , '../', './dist/'),
filename : 'main.js'
},
恭喜你,到这里你就学会了 webpack 区分环境 进行 打包配置了。
6. 跨域&& 跨域的解决方案和原理
什么是跨域,怎么解决?这个问题 ,不知道什么是跨域
的掘友希望你么可以自己去了解这个东西,我这就不细说了。
简单理解 客户端
与 服务端
协议
端口
主机
其中有一个不一致则就称为 跨域 ,主要原因是浏览器的同源策略
如何解决呢 ?
我们可以在 webpack 提供的 devServer
这个选项中进行配置 ..
配置
module.exports = {
devServer : {
host : 'localhost',
port : 8080 ,
open : true,
proxy : {
// 匹配请求路径 以 /api 开头的 代理到 http://localhost:3000 并且 将 /api 重写成 '' 空字符
"^/api" : {
target : 'http://localhost:3000',
pathRewrite: { '^/api': '' },
}
}
},
}
7.webpack的 HMR
什么是 HMR(Hot Module Replacement)
, 我的理解是 热更新替换 ,就是 当你的本地 与webpack开启的服务上的代码 不一致时,HMR将会通过长链接以打补丁的方式 进行替换更新从而使页面内容刷新。
module
模块选项中 提供了 hot.accept
方法 ,它能够允许你将指定的模块进行 热更新,当然 官方告诉我们,从webpack-dev-server v4.0.0 开始,热模块替换是默认开启的。
所以这里我们简单的对 热更新做一个了解。
原理 : 热更新的原理是 webpack 监测 本地代码 与 webpack开启的服务上的代码 不一致时,HMR将会通过长链接以打补丁的方式 进行替换更新从而使页面内容刷新。
如果我的理解有偏差,可以在评论区中进行参与评论解答,希望能够与大家一起学习
module.hot.accept('./testHRM.JS' , function () {
// 当我们 testHRM.js文件更新了 , 则会回调该方法 ,那我们重新引入 一下 则可以获取最新的内容。
const res = require('./testHRM.JS')
console.log(res);
})
结束啦。
到这里 webpack 进阶篇 你已经学会了很多,结合第一篇webpack基础,相信你对webpack 也有了一定的了解 ,如果你觉得对你有帮助,请帮忙点个star ,谢谢 ,我是前端小张同学
原文链接:https://juejin.cn/post/7223358128330096701 作者:前端小张同学