笔记来源:拉勾教育 - 大前端就业集训营
文章内容:学习过程中的笔记、感悟、和经验
webpack打包工具
问题
- ES Module存在环境兼容问题
- 模块文件过多,网络请求过于频繁
- 所有前端资源都需要模块化,不仅仅js
解决思路:
- 把新标准js文件转位ES5代码
- 把所有js文件打包到一个文件
- 不仅仅打包js文件还要打包其他文件,css、html文件等
模块打包工具 - webpack为例
webpack核心特性满足了上面的需求
- 模块打包工具:把零散的模块代码打包到一起
- 模块加载器:模块代码转换
- 代码拆分:按照代码需求打包
- 支持资源模块:支持文件按照资源方式引入
打包工具解决前端整体的模块化,不单单指前面所说的js模块化
webpack上手
-
创建三个文件,使用ES Moudle模式,这里可以使用serve运行一下看效果
<body> <!-- 引入js文件,设置为mudule模式 --> <script type="module" src="./src/index.js"></script> </body>// index.js // 引入模块 import h1 from './code.js' // 使用模块 const h = h1() document.body.append(h)// code.js // 导出模块 export default () => { const h1 = document.createElement('h1') h1.innerText = '123' h1.onclick = function () { console.log('123') } return h1 } -
利用yarn创建新项目:
yarn init --yes -
利用yarn安装两个依赖:
yarn webpack webpack-cli --dev -
利用yarn查看一下安装的版本:
yarn webpack --version -
利用yarn执行webpack打包:
yarn webpack -
会默认打包到dist目录下,更改htmnl的drc属性即可,同时可以删掉type="module",因为webpack已经把文件转换为ES5
可以使用npm脚本设置自动化指令
webpack配置文件
webpack4以后,可以0配置,默认将src下面的index作为打包入口,打包到dist的main.js文件如果想自定义一些配置,需要在根目录下添加一个webpack.config.js文件,在文件内配置
// webpack.config.js
// 引入path模块,后面要使用其中的方法
const path = require('path')
// webpack配置文件需要导出
module.exports = {
// 设置js入口文件,默认为src下面的index.js文件
entry: './src/one.js',
// 设置输出相关
output: {
// 输出文件的名称,默认main.js
filename: 'output.js',
// 设置输出路径,这里只能设置绝对路径,所以利用path的方法设置路径
path: path.join(__dirname, 'output')
}
}
webpack工作模式
可理解成针对不同环境的预设的配置,目前一共有三种模式:
production:线上模式,默认打包模式,压缩代码体积最小,但是不方便阅读development:开发模式,速度快,会添加一些有用的注释信息none:原始模式,只是打包,不会做其他处理
书写方式
-
yarn webpack --mode 模式名中断运行 -
书写在配置文件,这是就不需要在命令中设置模式了
module.exports = { mode: 'development' };
webpack打包原理
webpack将我们需要引入的所有模块全部打包在一个文件中,然后使用一些webpack内部的代码实现保持这些模块的依赖效果
资源加载模块
webpack只会默认打包js文件,如果没有特殊操作无法打包其他文件
如果想要处理其他文件需要使用加载器(loader)进行处理,就需要安装依赖
这里解析css文件要安装的两个依赖分别是:style-loader 和 css-loader两个插件
npm install --save-dev style-loader css-loader
yarn add --dev css-loader style-loader(如果你安装了yarn)
// webpack.config.js
// 配置文件
module.exports = {
module: {
//配置其他加载器
rules: [{ // 配置css打包
// test:是一个正则表达式,这里表示要匹配的文件要以css结尾
test: /\.css$/i,
// use: 要使用的加载器,这里使用css-loader打包css,使用style-loader把打包的css利用style标签的形式插入页面
use: ['style-loader', 'css-loader'],
}, ],
}
};
// 注意:加载器是从后往前加载的!!!!!!!!!!
// 这里的use: ['style-loader', 'css-loader']表示先使用css-loader再使用style-loader
loader是整个webpack的核心特性,借助不同的loader实现加载不同类型的资源
导入资源模块
通常情况下,我们还是以js文件为打包入口,我们在入口文件中引入我们需要的css文件,从而再使用打包
// 入口文件
// 引入模块
import h1 from './code.js'
// 在这里引入css模块,因为是直接运行,就不需要命名
import './main.css'
// 使用模块
const h = h1()
document.body.append(h)
// 然后把webpack的配置文件入口文件设置好即可
webpack眼中js驱动更个前端应用
- 逻辑合理,js需要资源文件的配合
- 保证上线时确保资源文件都是必要的,不会缺失
文件资源加载器
加载文件需要使用file-loader加载器:
yarn add --dev file-loader
// 入口文件
// 引入文件,这里引入一张图片
import img from './1.png'
// 使用图片插入页面
const m = new Image()
m.src = img
document.body.append(m)
// 配置文件
output: {
// 输出文件的名称,默认main.js
filename: 'output.js',
// 设置输出路径,这里只能设置绝对路径,所以利用path的方法设置路径
path: path.join(__dirname, 'output'),
// 在这里设置一下加载路径,否则可能出错,把加载路径设置为输出路径就可以了!!!!!!!
publicPath: 'output/'
},
module: {
rules: [{ // 使用file-loader
// 设置文件匹配
test: /\.png$/,
// 使用加载器
use: 'file-loader'
}]
}
一定要设置一下加载路径,否则很有可能出错,注意路径后面加/,例如:publicPath: 'output/',/会和后面进行拼接形成路径
URL加载器
通过data urls的方式表示文件,理论上可以表示任何类型的文件
data urls方式表示文件:协议:媒体类型编码,文件内容
例如:data:[<mediatype>][;base64],<data>
这种表示文件的方式已经包含了文件内容,使用的时候就不需要申请http请求,如果碰到无法使用文字表示的文件,比如图片、字体等文件,可以使用base64表示内容
webpack支持这种方式,需要使用url-loader加载器实现
module: {
rules: [{// 使用url-loader
// 和file类似的方法
test: /\.png$/,
// 如果只是想把文件转换为data-urls的方式,直接使用use: 'url-loader'即可,这样会无条件的吧文件转换为data-urls
// use: 'url-loader'
// 但是实际上,我们可能只需要把比较小的或者某些文件转换,因为base64编码转换大文件比较慢,所以更适合小文件
// 配置转换条件
use: {
// 加载器还是使用url-loader
loader: 'url-loader',
// 配置
options: {
// 转换小于等于10k的文件
limit: 10 * 1024 // 10kb,这里是按照字节计算的
}
}
}]
}
注意:使用url-loader同时要安装file-loader(除非全部转换url),因为不满足转换条件的会默认调用file-loader
工作建议
- 小文件使用data-urls,减少请求
- 大文件单独存放
常用加载器类型
- 编译转换类,把加载到的资源转换为js代码,例如:css-loader
- 文件操作类:把资源复制拷贝到输出目录,把文件的访问路径导出,例如file-loader
- 代码检查类:对代码进行校验,统一代码风格,提高质量
webpack和ES2015
webpack因为打包的需要,会处理export和import模块,但是并不会处理其他的ES6的代码
想要处理ES6+新特性需要安装babel-loader:babel要依赖多个插件
nary add --dev babel-loader @babel/core @babel/preset-env
// 配置文件
module: {
rules: [{ // 使用babel-loader
// 编译js文件
test: /\.js$/,
// babel需要配置
use: {
// 使用loader
loader: 'babel-loader',
options: {
// 编译库,这里包含所有需要的功能
presets: ['@babel/preset-env']
}
}
}
}
webpack只是打包工具,加载器才可以进行编译和转换
加载资源的方式
- 遵循ES Modules标准的import声明
- 遵循CommonJS标准的require函数
- 遵循AMD标准的define函数和require函数
除非必要不要混合使用
loader加载非js代码也会触发资源加载 ,例如css/html-loader遇到src、url和@import(css引入css)指令也会触发资源加载,当遇到资源加载的时候也会使用相应的加载器
- 样式代码中的@import指令和url函数
- HTML代码中图片标签的src属性等
总结:在webpack中凡是代码中需要引用的资源及其可能性都会被找出来,根据配置交给不同的loader处理,输出到打包目录
核心工作原理
loader是webpack的核心
工作原理:
- 根据配置找到入口文件
- 根据入口文件顺藤摸瓜找到所有需要加载的资源
- 将这些资源根据配置文件交给不同的loader(加载器)处理
- 最终把所有处理完的数据导出到输出目录,并把所有能够合并到一起的写入到一个js文件中
- js文件中继承了这些各种依赖关系,确保加载过程中不会发生错误
尝试开发一个loader
loader负责资源文件从输入到输出的转换
对于同一资源可以一次使用多个loader,可以讲此次loader的结果交给下一个loader处理
webpack要求一个loader工作之后的返回结果必须是一段js代码或者使用其他加载器返回的结果
// 写的一个loader加载器
// 安装markdown解析插件 - marked,在这里引入
const marked = require('marked')
// loader解析器是一个向外输出的结果,向外输出内容,这里接受一个参数source为我们要解析器输入的内容
module.exports = source => {
console.log(source) // 我们可以打印出来看一下
// return返回值要求必须输出一段js代码
// return 'console.log(hello)'
// 我们可以使用marked解析一下输入的内容,会转换为一段html代码
const html = marked(source)
// 我们可以在这里输出,这里使用moudel.export和export default都可以输出,webpack都支持
// 但是注意这里的html是一段html代码,直接输出可能会导致错误,我们先把他转换为json
// return `moudel.export = ${JSON.stringify(html)}`
// return `export default ${JSON.stringify(html)}`
// 也可以直接输出html交给下一个loader处理
return html
}
// 配置文件
// 引入path 后面要用
const path = require('path')
module.exports = {
// 打包模式
mode: 'none',
// 入口文件文件
entry: './src/index.js',
// 输出配置
output: {
//名字
filename: 'dist.js',
// 路径
path: path.join(__dirname, 'dist'),
// 资源路径
publicPath: 'dist/'
},
// loaders
module: {
rules: [{
// 解析md文件
test: /\.md$/,
// 如果我们写的loader直接输出可用,可以直接写use: './md-loader'
// use: './md-loader'
// 如果我们写的loader还需要另一个loader处理名、那么写多个loader即可,注意前后顺序,执行顺序从后往前
use: ['html-loader', './md-loader']
}]
}
}
总结
- loader就是一个从输入到输出数据的转换工具
- loader可以看做是一种管道,我们可以使用一个loader转换文件,也可以把多个loader组合起来转换,例如css-loader到style-loader
插件机制 - Plugin
增强webpack自动化能力
Plugin解决除了资源加载之外的其他自动化工作
例如每次打包之前删除文件、压缩代码等等
常用插件
自动清除输出目录插件
插件地址:www.npmjs.com/package/cle…
使用插件:clean-webpack-plugin => yarn add --dev clean-webpack-plugin
// webpack配置文件
// 引入path 后面要用
const path = require('path')
// 引入clean-webpack-plugin插件,解构出来一个构造函数!!!!!!!
const {
CleanWebpackPlugin
} = require('clean-webpack-plugin');
module.exports = {
// 打包模式
mode: 'none',
// 入口文件文件
entry: './src/index.js',
// 输出配置
output: {
//名字
filename: 'dist.js',
// 路径
path: path.join(__dirname, 'dist'),
// 资源路径
publicPath: 'dist/'
},
// loaders
module: {
rules: [{
// 解析md文件
test: /\.md$/,
// 如果我们写的loader直接输出可用,可以直接写use: './md-loader'
// use: './md-loader'
// 如果我们写的loader还需要另一个loader处理名、那么写多个loader即可,注意前后顺序,执行顺序从后往前
use: ['html-loader', './md-loader']
}]
},
// 所有插件都要放在这里,是一个数组!!!!!
plugins: [
// 添加插件!!!!!
new CleanWebpackPlugin()
]
}
自动生成html插件
让html文件也参与webpack的构建过程
文档地址:webpack.docschina.org/plugins/htm…
使用插件:html-webpack-plugin => yarn add --dev html-webpack-plugin
// 引入path 后面要用
const path = require('path')
// 引入clean-webpack-plugin插件,解构出来一个构造函数
const {
CleanWebpackPlugin
} = require('clean-webpack-plugin');
// 安装html生成模板,这个不需要解构!!!!!!!!!!!!!!!!!!!!!!!
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
// 打包模式
mode: 'none',
// 入口文件文件
entry: './src/index.js',
// 输出配置
output: {
//名字
filename: 'dist.js',
// 路径
path: path.join(__dirname, 'dist'),
// 资源路径
publicPath: 'dist/'
},
// loaders
module: {
rules: [{
// 解析md文件
test: /\.md$/,
// 如果我们写的loader直接输出可用,可以直接写use: './md-loader'
// use: './md-loader'
// 如果我们写的loader还需要另一个loader处理名、那么写多个loader即可,注意前后顺序,执行顺序从后往前
use: ['html-loader', './md-loader']
}]
},
// 所有插件都要放在这里,是一个数组
plugins: [
// 添加插件
new CleanWebpackPlugin(),
// 创建新实例。里面的参数可配置!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
new HtmlWebpackPlugin({
// 设置标题
title: '我是标题',
// 设置meta属性
meta: {
viewport: 'width=device-width'
},
// 如果想要代入某些结构可以使用模版,因为默认生成的是一个空的html
template: './src/index.html'
}),
// 如果想生成多个html文件,需要创建多个实例
new HtmlWebpackPlugin({
// 设置名字,因为默认的名字为index.html,如果不重新命名会和上一个冲突
filename: 'tow.html'
})
]
}
<!-- 这是一个模版 -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div class="box">
<!-- 模版内部可以使用模版字符串 -->
<h1><%= htmlWebpackPlugin.options.title %></h1>
</div>
</body>
</html>
复制文件插件
一些不参与构建的静态文件,直接复制到输出目录极即可
文档地址:webpack.docschina.org/plugins/cop…
使用插件:copy-webpack-plugin => arn add --dev copy-webpack-plugin
// 引入path 后面要用
const path = require('path')
// 引入clean-webpack-plugin插件,解构出来一个构造函数
const {
CleanWebpackPlugin
} = require('clean-webpack-plugin');
// 安装html生成模板,这个不需要解构
const HtmlWebpackPlugin = require('html-webpack-plugin');
// 引入拷贝插件!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
const CopyPlugin = require("copy-webpack-plugin");
module.exports = {
// 打包模式
mode: 'none',
// 入口文件文件
entry: './src/index.js',
// 输出配置
output: {
//名字
filename: 'dist.js',
// 路径
path: path.join(__dirname, 'dist'),
// 资源路径
publicPath: 'dist/'
},
// loaders
module: {
rules: [{
// 解析md文件
test: /\.md$/,
// 如果我们写的loader直接输出可用,可以直接写use: './md-loader'
// use: './md-loader'
// 如果我们写的loader还需要另一个loader处理名、那么写多个loader即可,注意前后顺序,执行顺序从后往前
use: ['html-loader', './md-loader']
}]
},
// 所有插件都要放在这里,是一个数组
plugins: [
// 添加插件
new CleanWebpackPlugin(),
// 创建新实例。里面的参数可配置
new HtmlWebpackPlugin({
// 设置标题
title: '我是标题',
// 设置meta属性
meta: {
viewport: 'width=device-width'
},
// 如果想要代入某些结构可以使用模版,因为默认生成的是一个空的html
template: './src/index.html'
}),
// 如果想生成多个html文件,需要创建多个实例
// 使用拷贝插件!!!!!!!!!!!!!!!!!!!!!!!!!
new CopyPlugin({
// 配置
patterns: [{
// 从根目录下的src
from: "src",
// 拷贝到输出路径下的src
to: "src"
}, ],
}),
]
}
尝试开发一个插件
webpack.docschina.org/api/compile…
pulgin利用的是钩子机制实现的
插件是通过在生命周期中的钩子上挂载函数的方式实现扩展的
我明白这个原理吧,老师讲的不是很细,没太看懂
webpack开发体验的问题
理想的开发环境:
- 以HTTP server的方式运行
- 自动编译,自动刷新
- 提供source map支持,快速定位问题
增强开发体验
自动编译
利用webpack的watch工作模式,监听文件变化,自动重新打包
方法实现:yarn webpack --watch
运行webpack命令的时候添加--watch,这样webpack打包后不会立即退出,会进入监视模式,当文件发生变化,就会立即重新打包
自动刷新浏览器
利用browserSync工具
- 操作上比较麻烦,要同时使用两个工具
- 效率降低了,要不断写入读取
webpack dev server
webpack官方提供的开发工具,提供一个开发服务器,集成自动编译和自动刷新浏览器等功能
安装:yarn add --dev webpack-dev-server
执行:yarn webpack-dev-server --open,加open可以自动打开浏览器
注意:
- 注意webpack-cli版本,版本太高可能会报错,练习的时候我的版本为4X就报错了,实验可行的版本为
"webpack-cli": "^3.3.12",如果你的版本报错,可以尝试刚改为这个版本 - 此方法不会在本地进行打包,而是将打包的结果存在内存里面,从内存里面读取,所以减少了本地的读写操作,更高效
访问静态资源文件
在实际开发中,我们可能并不会一直使用copy-webpack-plugin,因为如果需要拷贝的文件过多,会导致我们每次都要拷贝,所以一般情况下,我们只有在上线之前才会使用copy
所以在webpack dev server中,我们要添加设置参数,把这些文件也要打包进去
方法,在webpack配置文件中添加devServer配置
devServer: {
// 要拷贝的静态资源
contentBase: '路径'
},
注意:使用的时候要注意html文件的位置,最好放在src目录下,这样才能正确打开,我联系的时候放在了根目录,open以后只能看到静态资源,看不到页面
代理API
问题根源:开发阶段接口跨域问题
解决方案:webpack-dev-server支持配置代理服务
名词:
endpoint - 接入端点 / 入口
devServer: {
contentBase: './123',
// 添加代理服务
proxy: {
//代理地址相关设置
'/api': {
// 要代理的网址,例如http://localhost:8080/api/users => https://api.github.com/api/users
target: 'https://api.github.com',
// 重写路径,如果不想api传递过去,可以替换为空,这里面的参数会通过正则匹配
pathRewrite: {
'^api': ''
}
},
// 设置请求头中的请求地址,替换为target地址,因为服务器种可能存在多个网站信息,我们调试使用的请求头服务器可能不认识,不使用localhost:8080
changeOrigin: true
}
},
source map - 源代码地图
webpack.docschina.org/configurati…
- 目前的调试和报错都是基于转换过后的运行代码
- source map文件映射转换后代码和源代码之间的关系
- 可以利用映射关系,精确定位到错误的在源代码中的错误信息
Source Map解决了源代码与运行代码不一致所产生的问题
webpack配置source map
打包结果生成source map文件
// webpack配置文件
//打包的时候创建source-map文件
devtool: 'source-map'
// 这是最简单的生成map文件的方式,但是目前一共有12种不同方式,美中方式生成的效果和速度都是不一样的
eval模式source map
eval是js中的一个函数,可以运行函数内部的代码
eval(要运行的代码 可选//sourceURL=路径)
可以在参数内部添加注释(//sourceURL)指向这段代码运行的文件
eval模式不会产生map文件,会将所有打包的代码都放到eval函数中执行,但这种方法只能定位文件,无法定位到行和列,所以构建速度最快
不同模式之间的差异
关键词:
- eval - 使用eval执行模块代码
- cheap - 是否包含行信息,这种方式不会包含列
- module - 得到原始未经转换的源代码
例如:
**cheap-module-sval-source map **=> 未经转换的,包含行信息的使用eval执行
选择合适的模式
一般应用开发时候不会用多种模式
老师的个人选择:
- 开发环境下:
cheap-module-sval-source map- 一般每行不会写过多代码,不需要定位列
- 转换后代码可能和转换之前代码差别较大,需要知道我转换之前哪里出了错误
- 首次打包比较慢,但是重新打包相对较快
- 生产环境(最后一次打包):none - 不生成
- map会暴露源代码到生产环境,可能被复原代码
- 调试应该是开发阶段的事情,生产阶段应该不需要调试
- 如果想使用map:nosource-source map
- 会提示位置但不会暴露源代码
webpack自动刷新问题
问题:webpack-dev-server每次都会完全刷新整个网页,一旦页面整体刷新,页面中的操作状态都会丢失
需求:在页面不刷新的前提下,代码也可以及时更新进去
HMR-模块热更新/替换
在应用程序过程中实时替换模块内容,且不影响应用运行状态,只会将修改的模块替换到应用中
开启HMR
集成在了webpack-dev-server中
开启方法:
- 方法1:运行代码时直接加--hot
- 直接在终端运行
yarn webpack-dev-server --hot
- 直接在终端运行
- 方法2:修改配置文件
- 配置文件devserver中添加
hot:true - 配置文件中引入webpack,再创建**webpack.HotModuleReplacementPlugin()**实例
- 配置文件devserver中添加
// 引入webpack
const webpack = require('webpack')
module.exports = {
......
// 设置webpack-dev-server
devServer:{
// 开启热更新
hot:true
}
// 创建实例!!!注意,这个要写在插件中(plugins)
new webpack.HotModuleReplacementPlugin()
}
但是我们运行起来发现,css文件修改后确实可以开启热更新,但是当我们秀js文件后发现页面还是刷新了
HMR疑问
webpack中的MHR并不和其他插件一样“开箱即用”,需要手动处理模块热更新替换逻辑
css可以实现热更新是因为在style-loader里面已经进热更新进行了处理,就不需要手动配置了
css代码逻辑比较简单,有规律,可以更好的进行替换,所以可以进行热更新
而js代码没有什么特别的规律可言,可能有很多种变化,所以我们无法找到代码运行规律从而实现逻辑替换
使用框架开发的时候,每个文件都是有规律的,所以js可以进行热更新,通过脚手架创建的项目内部已经继承了HMR方案
总结:我们需要手动处理js模块更新后的热替换
HMR Apis
手动处理js热更新
**module.hot.accept('模块路径',回调函数)**
webpack处理js模块热替换
module.hot.accept('模块路径',回调函数)
// 引入模块
import html from './html.js'
// 引入css文件
import '../css/index.css'
// 引入图片
import src from '../img/截屏2021-02-07 11.21.07.png'
//使用html模块。生成结构添加进页面
const input = html()
document.body.append(input)
const img = new Image()
// 创建一个图片,插入页面
img.src = src
document.body.append(img)
console.log('111')
// 创建一个变量接收当前的input标签,存起来
let lastInput = input
// 配置热更新,第一个参数是模块的文件的地址,第二个参数是回调函数
module.hot.accept('./html.js', () => {
// 回调函数内部是当模块发生更新后怎么做的代码,也就是说,如果这里为空,模块更新后就不会生效
const value = lastInput.value
const newInput = document.createElement('input')
newInput.value = value
newInput.className = 'input'
newInput.onclick = () => {
console.log('我是input2')
}
})
注意事项
-
问题:如果HMR代码错误会报错,但此时会导致自动刷新,会丢失错误信息,不容易发现
-
解决方案:不使用hot而使用
hotOnly:true,一旦系统检测到错误,就不会刷新页面,从而使报错信息得以保留 -
// webpack配置文件 devServer: { hotOnly: true },
-
-
问题:plugins没有启动HMR插件的情况下,HMR插件会报错
-
解决方案:在使用MHRapi之前先判断有没有module.hot这个对象,如果有再使用,否则不使用
-
// 先判断一下,如果module.hot存在,说明启用的HMR插件 if (module.hot) { ......... }
-
-
代码中有很多与业务功能无关的代码会不会影响生产环境
- 解答:打包之后不会打包if语句中间的代码(在我们关闭mhr的情况下),打包也不会打包这些代码。不会影响生产环境
生产环境优化
当代码处于生产环境中的时候,我们使用webpack打包的代码可能会出现一些无用代码冗余
webpack中提供了很多模式,我们可以为不同的工作环境创建不同的配置
不同环境下的配置
- 配置文件根据环境不同导出不同的配置(只适用于中小型项目)
- 使用的时候执行
yarn webpack --env 环境名(和配置文件对应)
- 使用的时候执行
- 一个环境对应一个配置文件(大项目建议使用)
- 可以安装webpack-merge模块用作配置对象合并
yarn add webpack-merge --dev - 因为没有了默认配置文件,执行打包时应该输入
yarn webpack --config webpack.....js,其中webpack.....js为要使用的配置文件的名字
- 可以安装webpack-merge模块用作配置对象合并
// 方案1:根据传入的文件不同而输出不同的配置
// webpack.config.js配置文件
// 引入模块
const path = require('path') // path
const webpack = require('webpack') // webpack
const { // 清除输出目录
CleanWebpackPlugin
} = require('clean-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin') // html模块
const CopyPlugin = require("copy-webpack-plugin") // 拷贝文件
// 根据传入参数不同判断环境从而输出不同配置
// module.exports支持添加一个函数,参数1表示运行环境(在命令行传入),参数2表示传入参数(一般不需要)
module.exports = (env, argv) => {
// 创建一个开发环境下的模版
const config = {
// 打包模式=none
mode: 'none',
// 入口文件
entry: './src/js/index.js',
// 输出配置
output: {
// 输出文件名
filename: 'index.js',
// 输出目录
path: path.join(__dirname, 'dist')
},
// 使用的loader
module: {
rules: [{ // 转css
test: /\.css$/,
use: ['style-loader', 'css-loader']
},
{ //转图片
test: /\.png$/,
use: {
loader: 'url-loader',
options: {
milit: 200 * 1024
}
}
},
{ // 转js到ES5
test: /\.js$/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
}
}
]
},
// 插件配置
plugins: [
// html插件
new HtmlWebpackPlugin({
title: '练习',
}),
// 热更新插件
new webpack.HotModuleReplacementPlugin()
],
// webpack-dev-server
devServer: {
// 根目录
contentBase: './dist',
// 开启热更新
hot: true
},
// 开启source-map
devtool: 'eval-cheap-module-source-map'
}
// 判断传入的参数是否为production,如果传入production,修改模版
if (env === 'production') {
// 设置打包模式为生产模式
config.mode = 'production'
// 禁用source-map
config.devtool = false
// 设置插件,添加两个插件
config.plugins = [
// 这里使用ES6语法解构原有的config.plugins
...config.plugins,
new CleanWebpackPlugin(),
new CopyPlugin({
patterns: [{
from: "./src/other",
to: "other"
}],
})
]
}
// 返回配置,如果是开发环境直接输出原始模版,如果是生产环境,输出处理过后的模版
return config
}
// 使用的时候执行 yarn webpack 会按照默认配置文件执行打包(因为没有传入参数,所以不会进入if语句)
// 使用的时候执行yarn webpack --env production 即可以生产环境的方式打包文件
// 方案2:给每个环境配置不同的配置文件
// 引入模版配置模块,这里直接引入模版js文件即可
const common = require('./webpack.common.js')
// 引入webpack-merge插件
const merge = require('webpack-merge')
const { // 清除输出目录插件
CleanWebpackPlugin
} = require('clean-webpack-plugin')
const CopyPlugin = require("copy-webpack-plugin") // 拷贝文件插件
// 利用webpack-merge插件合并配置参数,这个函数会直接将两个对象合并
module.exports = merge(common, {
// 打包模式
mode = 'production',
// 模块使用
plugins: [
new CleanWebpackPlugin(),
new CopyPlugin({
patterns: [{
from: "./src/other",
to: "other"
}],
})
]
})
DefinePlugin - 注入全局成员
- DefinePlugin是webpack内置的一个类,可以在全局注入一个成员
- 会将注入的成员的值直接替换到打包好代码当中
// 必须先引入webpack模块
const webpack = require('webpack')
module.exports = merge(common, {
.........
plugins: [
// 在模块中使用
new webpack.DefinePlugin({
// 这里要加上js代码片段 ,所以解析后会去掉一层引号
API_BASE_URL: '"https://api.baidu.com"'
})
]
})
Tree-shaking - 去除未引用代码
去掉代码中没有使用的代码,这样可以减少体积优化代码
是一组功能搭配后的优化结果,不是某一个插件或者方法,生产模式下会自动启用
// webpack配置文件
module.exports = {
.........
// 配置Tree-shaking功能
optimization: {
// 不输出未引用代码,开启后会发现打包后的结果这些未引用的代码颜色变淡了
usedExporta: true,
// 开启压缩代码,会压缩打包的代码,并且删除未应用的代码
minimize: true
}
}
如果把代码看做一棵大树
- usedExporta负责标记枯树叶(未应用代码)
- minimize负责把枯树叶摇下来(删除)
concatenateModules - 合并模块函数
默认打包是把每个模块单独放在一个函数当中,如果我们模块过多就导致会有很多函数
concatenateModules可以把所有模块合并到一起,输出到一个函数中,缩小代码体积提高运行效率,也叫scope Hoisting(作用于提升)
使用方法:直接在上面的optimization中添加concatenateModules:true
Tree-shaking和babel
很多资料表示使用babel-loader后会导致Tree-shaking失效
Tree-shaking必须使用ES Module实现
新版的babel-loader已经支持ES Moudel特性,不会导致Tree-shaking失效了
如果你不能确定是否是新版本,可以强制设置babel,强制开启
module: {
rules: [{
// 在babel-loader中设置
test: /\.js$/,
use: {
loader: 'babel-loader',
options: {
presets:[
// 强制不转换modules
['@babel/preset-env', {modules: flase}]
]
}
}
}
]
},
sideEffects - 副作用
一般只有在开发npm模块的时候才会用到
副作用:模块执行时除了导出成员之外所做的事情,功能开启后就不会打包没有用到的模块
使用方法:
- 在配置文件中的optimization开启功能:
sideEffects:true - 在package.json中添加sideEffects字段:
“sideEffects”: "true",告知系统,我们所有代码都没有副作用
注意事项
- 问题:要确定你的代码没有副作用,否则系统会认为所有代码都没有副作用,例如一些css模块,例如给内置对象添加方法这一类操作都会被忽略掉
- 解决方法
- 关掉package.json中的副作用声明
- 告诉系统哪些文件有副作用,这样系统会打包有副作用的代码
“sideEffects”: [文件路径,文件路径......]
webpack 代码分割 - code splitting
问题
- 所有代码之后都被打包到了一起,可能导致体积过大
- 并不是每个模块都是启动后必须的,有一些代码只会在特定操作下才会起效
解决方法:分包,按需加载
- 多入口打包
- 动态导入
多入口打包
一般适用于传统的多页应用程序,最常见一个页面一个打包入口,公共部分单独提取
module.exports = {
entry: {
// 配置两个入口文件,就可以多入口打包
index: './.../.../.js',
index2: './.../.../.js',
},
output: {
// 输出文件名适应name动态添加,name会接受两个入口的名字
filename: '[name].bindle.js',
path: path.join(__dirname, 'dist')
},
plugins: [
// 当我们打包之后会发现html内部引用了所有打包的js代码,我们手动修改chunks让文件只引用需要的js文件
new HtmlWebpackPlugin({
title: '练习',
template: './code3/src/index.js',
filename: 'index.html',
chunks: ['index']
}),
new HtmlWebpackPlugin({
title: '练习2',
template: './code3/src/index2.js',
filename: 'index2.html',
chunks: ['index2']
})
]
}
提取公共模块
多入口打包无法提取公共部分,会有很多相同的模块代码
解决方法就是把公共的模块单独提取出来,单独存放,就不需要在每个打包的文件中都存一份
// webpack配置文件
module.export = {
.......
optimization: {
solitChinks: {
// 单独打包所有公共模块
chunks:'all'
}
}
}
动态导入
按需加载:需要某个模块时候,再去加载这个模块
动态导入模块会将所有动态导入的模块都会自动打包、分包
动态导入使用的是ES Moudel的方法
// 我们可以在判断条件或者for循环中使用动态加载模块,这样代码只会在使用的时候加载,不会在启动后直接加载
if (x === 1) {
// 导入模块,并使用then方法使用导入的模块
import('./src/tow.js').then(({
// 重命名导入的模块为one
default: one
}) => {
// 使用one
console.log(one)
})
} else {
import('./src/tow.js').then(({
default: tow
}) => {
console.log(tow)
})
}
魔法注释
给打包的文件添命名
使用方法:直接在动态导入的参数里面添加行内注释,例如:
// webpackChunkName可以给打包的文件添加前缀名字
// 给这个文件添加one前缀
import(/*webpackChunkName:'one'*/'./src/tow.js').then(({
// 重命名导入的模块为one
default: one
}) => {
// 使用one
console.log(one)
})
//打包结束后名字为 one.配置文件设置名.js
注意:如果两个动态导入的行内注释是相同的,那么这两个人间就会被打包到一个文件中
MiniCssExtractPlugin - css提取到单个文件
中文文档:webpack.docschina.org/plugins/min…
需要插件:yarn add mini-css-extract-plugin --dev
使用此插件就不需要使用style-loader了,因为这个插件会把css文件提取到单独文件,需要使用link引入
将style-loader替换成MiniCssExtractPlugin.loader
//上面需要引入插件,可以查看文档
{
test: /\.css$/,
// 这里使用MiniCssExtractPlugin.loader替换掉style-loader
use: [MiniCssExtractPlugin.loader , 'css-loader']
},
建议:css文件超过150kb再使用MiniCssExtractPlugin.loader,否则可能会适得其反
OptimizeCssAssetsWebpackPlugin - 压缩css文件
安装插件:
- 压缩css插件:
yarn add optimize-css-assets-webpack-plugin --dev - 压缩js插件:
yarn add --dev terser-webpack-plugin- 因为如果把上一个插件添加到minimizer中之后系统会认为你想手动配置插件,会导致webpack默认压缩js代码失效,所以我们要手动加回来
文档:
webpack没有找到yarn add optimize-css-assets-webpack-plugin,但是提供了这个插件
webpack.docschina.org/plugins/css…
使用方法:
- 直接在plugins中创建实例对象
- 写在minimizer中
// 上面要引入插件再使用
// webpack配置文件
module.export = {
// 方法1:写在minimizer中
optimization: {
minimizer: [
new OptimizeCssAssetsWebpackPlugin(),
// 如果以生产模式打包会导致会导致js无法正常压缩,因为一旦我们设置了minimizer,系统就会认为我们使用了自定义,就会不使用默认的js压缩,我们要手动引入另一款插件解决
new TerserWebpackPlugin()
]
},
// 方法2:写在plugins中,也不需要设计其他插件,但官方好像不是很建议这样做,所以还是按照方法1做吧
plugins: [
new OptimizeCssAssetsWebpackPlugin(),
]
}
输出文件名Hash
建议在生产模式下,文件名使用hash
使用方法:在webpack配置文件中,需要修改名字的地方都可以使用hash,例如:outpot中的filename、html插件中的filename
// 只要是配置文件中涉及到命名的地方都可以使用
output: {
filename: '[name]-[hash]-bundle.js'
}
new HtmlWebpackPlugin({
filename: 'index-[[hash:8]].html',
})
// [...hash:x],x可以指定生成多少位的hash名字,例如[contenthash:8]就生成一个8位的hash
hash三种值:
- hash:整个目录级别的,每次更改文件都会更改全部文件名
- chunkhash:路级别的(依赖),只会更改发生更改的那一路文件的名字
- (建议使用)contenthash:文件级别的,只会更改修改的文件的名字
建议使用8位hash就可以了