一、webpack 开篇
1.什么是 webpack?
webpack 是一套基于 NodeJS 的"模块打包工具",
在 webpack 刚推出的时候就是一个单纯的 JS 模块打包工具,可以将多个模块的 JS 文件合并打包到一个文件中
但是随着时间的推移、众多开发者的追捧和众多开发者的贡献
现在 webpack 不仅仅能够打包 JS 模块, 还可以打包 CSS/LESS/SCSS/图片等其它文件
2.为什么要分模块?
如果将所有的 JS 代码都写到一个文件中, 十分不利于代码的维护和复用
所以我们可以将不同的功能写到不同的模块中, 这样就提升了代码的维护性和复用性,但是当将代码写到不同模块时新的问题又出现了,
例如: 导入资源变多了, 请求次数变多了, 网页性能也就差了
例如: 不同功能都放到了不同模块中了, 那么如何维护模块之间的关系也变成一个难题了
<script src="./header.js"></script>
<script src="./content.js"></script>
<script src="./index.js"></script>
<script src="./footer.js"></script>
// 如果index.js中用到了footer,就会报错
3.如何解决上述问题
项目上线时将用到的所有模块都合并到一个文件中
在 index.html 中只导入主文件, 再主文件中再导入依赖模块
4.如何通过 webpack 来打包 JS 模块
4.1 安装 webpack
$npm init -y
$npm install --save-dev webpack
$npm install --save-dev webpack-cli
4.2 在终端中输入打包的指令
$npx webpack index.js
注意点:
index.js 就是需要打包的文件
打包之后的文件会放到 dist 目录中, 名称叫做 main.js
二.webpack 配置文件
1.什么是 webpack 配置文件?
我们在打包 JS 文件的时候需要输入:
$npx webpack index.js
这句指令的含义是: 利用 webpack 将 index.js 和它依赖的模块打包到一个文件中
其实在 webpack 指令中除了可以通过命令行的方式告诉 webpack 需要打包哪个文件以外,
还可以通过配置文件的方式告诉 webpack 需要打包哪个文件
2.webpack 常见配置
- entry: 需要打包的文件
- output: 打包之后输出路径和文件名称
- mode: 打包模式 development/production
- development: 不会压缩打包后的 JS 代码
- production: 会自动压缩打包后的 JS 代码
3.webpack 配置注意事项
3.1. 配置文件的名称必须叫做:webpack.config.js,否则直接输入 npx webpack 打包会出错
如果要使用其他名称,name 在输入打包命名的时候必须通过 npx webpack --config 指定的配置文件名称
$npx webpack --config xxx
3.2. 打包命令简化
在 package.json 文件中对scripts进行修改,打包时可以直接输入 npm test(test也可以写成其他名称)
"scripts": {
"test": "npx webpack --config xxx"
},
三、webpack-sourcemap
1.什么是sourcemap?
webpack打包后的文件会自动添加很多代码, 在开发过程中非常不利于我们去调试
因为如果运行webpack打包后的代码,错误提示的内容也是打包后文件的内容
所以为了降低调试的难度, 提高错误代码的阅读性, 我们就需要知道打包后代码和打包之前代码的映射关系
只要有了这个映射关系我们就能很好的显示错误提示的内容, 存储这个映射关系的文件我们就称之为sourcemap
2.如何开启sourcemap
2.1在webpack.config.js中添加
devtool: "xxx",
2.2各配置项说明:
-
eval:
不会单独生成sourcemap文件, 会将映射关系存储到打包的文件中, 并且通过eval存储
优势: 性能最好
缺点: 业务逻辑比较复杂时候提示信息可能不全面不正确 -
source-map:
会单独生成sourcemap文件, 通过单独文件来存储映射关系
优势: 提示信息全面,可以直接定位到错误代码的行和列
缺点: 打包速度慢 -
inline:
不会单独生成sourcemap文件, 会将映射关系存储到打包的文件中, 并且通过base64字符串形式存储 -
cheap:
生成的映射信息只能定位到错误行不能定位到错误列 -
module:
不仅希望存储我们代码的映射关系, 还希望存储第三方模块映射关系, 以便于第三方模块出错时也能更好的排错
2.3企业开发配置:
-
development: cheap-module-eval-source-map
只需要行错误信息, 并且包含第三方模块错误信息, 并且不会生成单独sourcemap文件 -
production: cheap-module-source-map
只需要行错误信息, 并且包含第三方模块错误信息, 并且会生成单独sourcemap文件
四、webpack-loader
1.什么是loader
webapck的本质是一个模块打包工具, 所以webpack默认只能处理JS文件,不能处理其他文件,
因为其他文件中没有模块的概念, 但是在企业开发中我们除了需要对JS进行打包以外,
还有可能需要对图片/CSS等进行打包, 所以为了能够让webpack能够对其它的文件类型进行打包,
在打包之前就必须将其它类型文件转换为webpack能够识别处理的模块,
用于将其它类型文件转换为webpack能够识别处理模块的工具我们就称之为loader
将webpack不能识别的类型转换为可以识别的类型
2.如何使用loader
webpack中的loader都是用NodeJS编写的, 但是在企业开发中我们完全没有必要自己编写,
因为已经有众多大神帮我们编写好了企业中常用的loader, 我们只需要安装、配置、使用即可
2.1通过npm安装对应的loader
2.2按照loader作者的要求在webpack进行相关配置
2.3使用配置好的loader
3.fileloader使用
当未安装file-loader,在文件中引入了图片文件进行打包,会出现下面的报错:
专门处理文件的,将文件转换为webpack能够识别的模块的
https://www.webpackjs.com/loaders/file-loader/
3.1安装file-loader
npm install --save-dev file-loader
3.2在webpack.config.js中配置file-loader
module: {
rules: [
{
test: /\.(png|jpg|gif)$/,
use: [
{
loader: 'file-loader',
options: {}
}
]
}
]
}
module:告诉webpack如何处理webpack不能够识别的文件
3.3 file-loader其他配置
-
注意点1:
默认情况下fileloader生成的图片名就是文件内容的 MD5 哈希值
如何想打包后不修改图片的名称, 那么可以新增配置 name: "[name].[ext]"
其它命名规则详见: placeholders -
注意点2:
默认情况下fileloader会将生成的图片放到dist根目录下面
如果想打包之后放到指定目录下面, 那么可以新增配置 outputPath: “images/” -
注意点3:
如果需要将图片托管到其它服务器, 那么只需在打包之前配置 publicPath: "托管服务器地址"即可
3.4 打包字体图标
字体图标中也用到了url用到了文件, 所以我们需要通过file-loader来处理字体图标文件
{
test: /\.(eot|svg|ttf|woff|woff2)$/,
use:[{
loader: "file-loader",
options: {
name: "[name].[ext]",
outputPath: "font/",
}
}]
}
因为字体图标中有以下这些文件,.css文件可以使用css-loader,其他的通过file-loader
4.url-loader使用
4.1.urlloader
url-loader 功能类似于 file-loader,
但是在文件大小(单位 byte)低于指定的限制时,可以返回一个 DataURL
url-loder可以将图片转成字符串
4.2.urlloader使用
https://www.webpackjs.com/loaders/url-loader/
4.2.1安装urlloader
$ npm install --save-dev url-loader
4.2.2配置urlloader
{
test: /\.(png|jpg|gif)$/,
use: [
{
loader: 'url-loader',
options: {
name: "[name].[ext]",
outputPath: "/images",
/*
limit:指定图片限制的大小
如果被打包的图片超过了限制的大小,就会将图片保存为一个文件
反之,就会将图片转换成base64的图片
*/
limit: 1024*100
}
}
]
}
limit: 1024*100,图片154 KB,图片大小超过限制的大小
limit: 1024*200,图片154 KB,图片大小没有超过限制的大小
优势:
图片比较小的时候直接转换成base64字符串图片, 减少请求次数
图片比较大的时候由于生成的base64字符串图片也比较大, 就保持原有的图片
注意点:
对于比较小的图片, 我们将图片转换成base64的字符串之后, 可以提升网页的性能(因为减少了请求的次数)
对于比较大的图片, 哪怕我们将图片转换成了base64的字符串之后, 也不会提升网页的性能, 还有可能降低网页的性能
(因为图片如果比较大, 那么转换之后的字符串也会比较多, 那么网页的体积就会变大, 那么访问的速度就会变慢)
5.css-loader
5.1.css-loader
和图片一样webpack默认能不能处理CSS文件, 所以也需要借助loader将CSS文件转换为webpack能够处理的类型
5.2.css-loader使用:
5.2.1安装cssloader
$ npm install --save-dev css-loader
5.2.2安装styleloader
$ npm install style-loader --save-dev
5.2.3配置css-loader
{
test: /\.css$/,
use: [ 'style-loader', 'css-loader' ]
}
5.3.css-loader和style-loader作用
css-loader: 解析css文件中的@import依赖关系
style-loader: 将webpack处理之后的内容插入到HTML的HEAD代码中
将webpack处理之后的内容插入到HTML的HEAD代码中
5.4 css-loader模块化
通过inport ‘./index.css’,导入的样式是全局的
options:{ modules:true }
[
{loader:"style-loader"},
{
loader:"css-loader",
options:{
modules:true
}
}
]
import icon from "./lnj.jpg";
// import "./index.css"
import cssModule from "./index.css"
import {addImage} from "./custom.js"; // 创建图片
console.log(cssModule);
let oImg = document.createElement("img");
oImg.src = icon;
// oImg.setAttribute("class", "size");
oImg.setAttribute("class", cssModule.size);
document.body.appendChild(oImg);
addImage();
6.loader特点:
6.1单一原则, 一个loader只做一件事情
6.2多个loader会按照从右至左, 从下至上的顺序执行
例如: 从右至左
[ 'style-loader', 'css-loader' ]
先执行css-loader解析css文件关系拿到所有内容,
再执行style-loader将内容插入到HTML的HEAD代码中
- 例如: 从下至上
[{
loader: "style-loader"
},{
loader: "css-loader"
}]
先执行css-loader解析css文件关系拿到所有内容,
再执行style-loader将内容插入到HTML的HEAD代码
7.less-loader
自动将less转换为CSS
7.1 less-loader使用:
7.1.0 安装less
npm install --save-dev less
7.1.1 安装less-loader
npm install --save-dev less-loader
7.1.2 配置less-loader
{
test: /\.less$/,
use: [{
loader: "style-loader"
}, {
loader: "css-loader"
}, {
loader: "less-loader"
}]
}
注意点:
因为loader是从右至左****从下至上,所以必须先由less-loader处理往后才能交给其他loader处理
8.sass-loader
1.sass-loader
自动将scss转换为CSS
2.sass-loader使用:
2.0安装sass
npm install --save-dev node-sass
2.1安装sass-loader
npm install --save-dev sass-loader
2.2配置sass-loader
{
test: /\.scss$/,
use: [{
loader: "style-loader" // 将 JS 字符串生成为 style 节点
}, {
loader: "css-loader" // 将 CSS 转化成 CommonJS 模块
}, {
loader: "sass-loader" // 将 Sass 编译成 CSS
}]
}
注意点:
因为loader是从右至左从下至上,所以必须先由sass-loader处理往后才能交给其他loader处理
9.postcss-loader
1.什么是PostCSS?
PostCSS和sass/less不同, 它不是CSS预处理器
PostCSS是一款使用插件去转换CSS的工具,
PostCSS有许多非常好用的插件
例如
autoprefixer(自动补全浏览器前缀)
postcss-pxtorem(自动把px代为转换成rem)
2.使用PostCSS自动补全浏览器前缀
2.1安装postcss-loader
npm i -D postcss-loader
2.2安装需要的插件
npm i -D autoprefixer
2.3配置postcss-loader
在css-loader or less-loader or sass-loader之前添加postcss-loader
{
test: /\.css$/i,
// use: ["style-loader", "css-loader","postcss-loader"],
use: [
{
loader: "style-loader",
},
{
loader: "css-loader",
},
{
loader: "postcss-loader",
},
],
},
{
test: /\.less$/i,
loader: [
// compiles Less to CSS
"style-loader",
"css-loader",
"less-loader",
"postcss-loader"
],
},
// 打包sass的规则
{
test: /\.s[ac]ss$/i,
use: [
// 将 JS 字符串生成为 style 节点
"style-loader",
// 将 CSS 转化成 CommonJS 模块
"css-loader",
// 将 Sass 编译成 CSS
"sass-loader",
"postcss-loader"
],
},
2.4创建postcss-loader配置文件
创建文件名叫postcss.config.js的文件
https://github.com/browserslist/browserslist#queries
2.5在配置文件中配置autoprefixer
// postcss.config.js
module.exports = {
plugins: {
"autoprefixer": {
"overrideBrowserslist": [
// "ie >= 8", // 兼容IE7以上浏览器
// "Firefox >= 3.5", // 兼容火狐版本号大于3.5浏览器
// "chrome >= 35", // 兼容谷歌版本号大于35浏览器,
// "opera >= 11.5" // 兼容欧朋版本号大于11.5浏览器,
"chrome >= 36", // 如果需要适配的浏览器完全兼容则不会添加前缀
]
}
}
};
3.使用PostCSS自动将px转换成rem
https://www.npmjs.com/package/postcss-pxtorem
let scale = 1.0 / window.devicePixelRatio;
let text = `<meta name="viewport" content="width=device-width, initial-scale=${scale}, maximum-scale=${scale}, minimum-scale=${scale}, user-scalable=no">`;
document.write(text);
document.documentElement.style.fontSize = window.innerWidth / 7.5 + "px";
3.1安装postcss-pxtorem
npm install postcss-pxtorem -D
3.2在配置文件中配置postcss-pxtorem
"postcss-pxtorem": {
rootValue: 100, // 根元素字体大小
// propList: ['*'] // 可以从px更改到rem的属性,*代表所有的属性都可以转换
propList: ["height"] // 只有height转换
}
- rootValue (Number) root 元素的字体大小。
- unitPrecision (Number) 允许REM单位增长到的十进制数。
- propList ( array ) 可以从px更改到rem的属性。
值需要精确匹配。
使用通配符 * 启用所有属性。 示例:[’’]
在单词的开头或者结尾使用 。 ( [‘position’] 将匹配 background-position-y )
使用 与属性不匹配。! 示例:[’’,‘letter-spacing’]!
将"非"前缀与其他前缀合并。 示例:[’’,‘font*’]! - selectorBlackList ( array ) 要忽略和离开的选择器。
如果值为字符串,它将检查选择器是否包含字符串。
[‘body’] 将匹配 .body-class
如果值为 regexp,它将检查选择器是否匹配正则表达式。
[/^body$/] 将匹配 body,但不匹配 .body - replace (Boolean) 替代包含rems的规则,而不是添加回退。
- mediaQuery (Boolean) 允许在媒体查询中转换 px。
- minPixelValue (Number) 设置要替换的最小像素值。
五.ES6模块化
在ES6出现之前,JS不像其他语言拥有“模块化”这一概念,于是为了支持JS模块化
我们使用类、立即执行函数或者第三方插件(RequireJS、seaJS)来实现模块化
但是在ES6出现之后, 上述解决方案都已经被废弃, 因为ES6中正式引入了模块化的概念
ES6模块化模块和NodeJS中一样, 一个文件就是一个模块, 模块中的数据都是私有的
ES6模块化模块和NodeJS中一样, 可以通过对应的关键字暴露模块中的数据,
可以通过对应的关键字导入模块, 使用模块中暴露的数据
1.ES6模块化使用
1.1常规导出
1.1.1 分开导入导出
// ES6模块化的第一种方式
导出数据: export {xxx};
导入数据: import {xxx} from "path";
注意点:
1.如果是通过export {xxx};方式导出数据, 那么在导入接收的时候接收的变量名称必须和导出的名称一致
究其原因是因为在导入的时候本质上是ES6的解构赋值
2.如果是通过export {xxx};方式导出数据, 又想在导入数据的时候修改接收的变量名称, 那么可以使用as来修改
但是如果通过as修改了接收的变量名称, 那么原有的变量名称就会失效
1.1.2 一次性导入导出
export {xxx, yyy, zzz};
import {xxx, yyy, zzz} from “path”;
注意点:
接收导入变量名必须和导出变量名一致
如果想修改接收变量名可以通过 xxx as newName方式
变量名被修改后原有变量名自动失效
1.2默认导入导出
export default xxx;
import xxx from "path";
注意点:
1.一个模块只能使用一次默认导出, 多次无效
2.默认导出时, 导入的名称可以和导出的名称不一致
1.3 两种方式混合使用
六、plugin
1.什么是插件(plugin)?
plugin 用于扩展webpack的功能
当然loader也是变相的扩展了webpack ,但是它只专注于转化文件这一个领域。
而plugin的功能更加的丰富,而不仅局限于资源的加载。
2.HtmlWebpackPlugin
2.1 什么是HtmlWebpackPlugin?
HtmlWebpackPlugin会在打包结束之后自动创建一个index.html, 并将打包好的JS自动引入到这个文件中
2.2 HtmlWebpackPlugin使用
2.3 安装HtmlWebpackPlugin
npm install --save-dev html-webpack-plugin
2.4 配置HtmlWebpackPlugin
const HtmlWebpackPlugin = require('html-webpack-plugin');
plugins: [new HtmlWebpackPlugin()]
2.5 HtmlWebpackPlugin高级使用
默认情况下HtmlWebpackPlugin生成的html文件是一个空的文件,
如果想指定生成文件中的内容可以通过配置模板的方式来实现
plugins: [new HtmlWebpackPlugin({
template: "index.html"
})]
默认情况下生成html文件并没有压缩,
如果想让html文件压缩可以设置
new HtmlWebpackPlugin({
template: "index.html",
minify: {
collapseWhitespace: true
}
})]
3.clean-webpack-plugin
3.1 什么是clean-webpack-plugin?
webpack-clean-plugin会在打包之前将我们指定的文件夹清空
应用场景每次打包前将dist目录清空, 然后再存放新打包的内容, 避免新老混淆问题
3.2 clean-webpack-plugin使用
webpack官网的plugin的左侧找不到clean-webpack-plugin,因为webpack官网放置的是部分推荐的
clean-webpack-plugin官网介绍
3.3 安装clean-webpack-plugin
npm install --save-dev clean-webpack-plugin
3.4 配置clean-webpack-plugin
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
plugins: [new CleanWebpackPlugin()]
4.copy-webpack-plugin
4.1 什么是copy-webpack-plugin?
在打包项目的时候除了JS/CSS/图片/字体图标等需要打包以外, 可能还有一些相关的文档也需要打包
文档内容是固定不变的, 我们只需要将对应的文件拷贝到打包目录中即可
那么这个时候我们就可以使用copy-plugin来实现文件的拷贝
4.2 copy-webpack-plugin使用
4.3 安装copy-webpack-plugin
npm install --save-dev copy-webpack-plugin
4.4 配置copy-webpack-plugin
const CopyWebpackPlugin = require('copy-webpack-plugin');
plugins: [
new CopyWebpackPlugin([{from:"./doc", to:"doc"}])
];
如果上述方法出现错误:copy-webpack-plugin
const CopyPlugin = require("copy-webpack-plugin");
module.exports = {
plugins: [
new CopyPlugin({
patterns: [
{ from: "source", to: "dest" },
{ from: "other", to: "public" },
],
}),
],
};
5.css-webpack-plugin
使用css-loader时将css插入在HEAD中,css-webpack-plugin则可以将打包的css单独放在一个文件中
5.1 什么是mini-css-extract-plugin?
mini-css-extract-plugin是一个专门用于将打包的CSS内容提取到单独文件的插件
前面我们通过style-loader打包的CSS都是直接插入到head中的
5.2 mini-css-extract-plugin使用
5.3 mini-css-extract-plugin安装
npm install --save-dev mini-css-extract-plugin
5.4 配置mini-css-extract-plugin
const MiniCssExtractPlugin = require(‘mini-css-extract-plugin’);
new MiniCssExtractPlugin({
filename: ‘./css/[name].css’,
})
5.5 替换style-loader
loader: MiniCssExtractPlugin.loader,
use: [
{
// loader: "style-loader",
loader: MiniCssExtractPlugin.loader,
},
{
loader: "css-loader",
options: {
modules: true,
},
},
{
loader: "postcss-loader",
},
],
注意点: 如果相关文件资源无法显示, 需要根据打包后的结构手动设置公开路径
options: {
publicPath: “xxx”
}
5.6 mini-css-extract-plugin压缩css
5.6.0 方法一:
webpack@5以前的
5.6.0.1安装JS代码压缩插件
npm install --save-dev terser-webpack-plugin
5.6.0.2 安装CSS代码压缩插件
npm install --save-dev optimize-css-assets-webpack-plugin
5.6.0.3 导入插件
const TerserJSPlugin = require('terser-webpack-plugin');
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
5.6.0.4 optimization配置webpack优化项
optimization: {
minimizer: [new TerserJSPlugin({}), new OptimizeCSSAssetsPlugin({})],
}
注意: 由于配置了webpack的optimization.minimizer项目会覆盖默认的JS压缩选项,
所以JS代码也需要通过插件自己压缩
5.6.1 方法二:
webpack@5
5.6.1.1 安装CSS代码压缩插件
npm install --save-dev optimize-css-assets-webpack-plugin
5.6.1.2 导入插件
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
5.6.1.3 optimization配置webpack优化项
module.exports = {
optimization: {
minimizer: [
// For webpack@5 you can use the `...` syntax to extend existing minimizers (i.e. `terser-webpack-plugin`), uncomment the next line
`...`,
new CssMinimizerPlugin(),
],
},
}
注意: 由于配置了webpack的optimization.minimizer项目会覆盖默认的JS压缩选项,
所以JS代码也需要通过插件自己压缩
七、watch
1.什么是watch?
webpack 可以监听打包文件变化,当它们修改后会重新编译打包
那么如何监听打包文件变化呢? 使用 watch
不用频繁的打包编译
2.watch相关配置watchOptions
针对watch的额外配置
- poll: 1000 // 每隔多少时间检查一次变动
- aggregateTimeout: // 防抖, 和函数防抖一样, 改变过程中不重新打包, 只有改变完成指定时间后才打包
- ignored: 排除一些巨大的文件夹, 不需要监控的文件夹, 例如 node_modules
七、webpack-dev-server
1.什么是webpack-dev-server?
webpack-dev-server和watch一样可以监听文件变化
webpack-dev-server可以将我们打包好的程序运行在一个服务器环境下
webpack-dev-server可以解决企业开发中"开发阶段"的跨域问题
2.webpack-dev-server使用
2.1安装webpack-dev-server
https://www.npmjs.com/package/webpack-dev-server
npm install webpack-dev-server --save-dev
2.2配置webpack-dev-server
devServer: {
contentBase: "./bundle", // 把哪个目录运行在服务器环境下
open: true, // 编译打包后,是否需要自动打开
port: 9090
}
// package.json
"start": "npx webpack-dev-server --config webpack.config.js" // webpack@5以前
"start": "npx webpack serve --config webpack.config.js" // webpack@5
修改了webpack配置文件需要重新编译打包
3.CORS
3.1 前端跨域问题?
同源策略(Same origin policy)是一种约定,它是浏览器最核心也最基本的安全功能
所谓同源是指: 协议,域名,端口都相同,就是同源, 否则就是跨域
http://127.0.0.1:8080
http://127.0.0.1:8080 // 同源
http://127.0.0.1:8080
http://127.0.0.1:9090 // 跨域
3.2.利用webpack-dev-server代理解决跨域问题
devServer: {
contentBase: "./dist",
open: true,
port: 9090,
proxy: {
// 所有API开头的请求都会被代理到target
// 例如: 我们发送请求地址: http://127.0.0.1:9090/api
// 实际发送请求地址: http://127.0.0.1:3000/api
// 只能解决开发阶段的跨域问题
"/api": {
target: "http://127.0.0.1:3000", // 代理地址
changeOrigin: true, // 域名跨域
secure: false, // https跨域
}
}
}
devServer: {
contentBase: "./dist",
open: true,
port: 9090,
proxy: [{
context:["/api", "/login"],
target: "http://127.0.0.1:3000", // 代理地址
changeOrigin: true, // 域名跨域
secure: false, // https跨域
}]
}
3.3.利用webpack-dev-server重写请求路径
proxy: [{
context:["/api", "/login"],
target: "http://127.0.0.1:3000", // 代理地址
changeOrigin: true, // 域名跨域
secure: false, // https跨域
pathRewrite:{"^/api": ""} // 路径重写, 将路径中的api替换为空
}]
注意点
devServer只能解决开发阶段的跨域问题, 并不能解决项目上线之后的跨域问题
原因非常简单, 因为项目上线之后是将打包好的文件上传到服务器, 而打包好的文件中并没有devServer
所以项目上线之后要想解决跨域问题还是需要依赖后端开发人员
八、HMR-热更新
1.什么是HMR?
1.1通过webpack-dev-server自动打包并没有真正的放到指定的目录中
因为读写磁盘是非常耗时和消耗性能的,
所以为了提升性能webpack-dev-server将转换好的内容直接放到了内存中
注意:
webpack-dev-server自动打包看不见打包的文件夹
1.2通过webpack-dev-server可以实现实时监听打包内容的变化
每次打包之后都会自动刷新网页, 但是正是因为每当内容被修改时都会自动刷新网页
所以给我们带来了很多不便, 这时就需要通过HMR插件来优化调试开发
1.3 HMR(HotModuleReplacementPlugin)热更新插件
会在内容发生改变的时候时时的更新修改的内容但是不会重新刷新网站
2.HMR使用:
HotModuleReplacementPlugin是一个内置插件, 所以不需要任何安装直接引入webpack模块即可使用
const Webpack = require("webpack")
2.1在devServer中开启热更新
module.exports = {
devServer:{
hot: true, // 开启热更新
hotOnly: true // 即使热更新不生效,浏览器也不自动刷新
}
}
2.2在webpack.config.js中创建热更新插件
plugins:[
new Webpack.HotModuleReplacementPlugin()
]
3.注意点:
3.1 使用了MiniCssExtractPlugin.loader来处理CSS
如果是通过style-loader来处理CSS, 那么经过前面两步就已经实现了热更新
如果是通过 MiniCssExtractPlugin.loader来处理CSS, 那么还需要额外配置MiniCssExtractPlugin.loader
use:[
// loader:"style-loader",
loader:MiniCssExtractPlugin.loader,
options:{
hmr: true
}
]
注意:在 webpack 5 中 HMR 已自动支持。无需配置。
如果你使用的是 webpack-dev-server,那么你无需使用 HotModuleReplacementPlugin 插件。 webpack-dev-server 使用 hot 选项来控制启用/禁用 HMR。
3.2 JS模块使用HMR注意点?
import "./index.less"
对于css模块而言, 在css-loader中已经帮我们实现了热更新, 只要css代码被修改就会立即更新
import copy from "./test.js"
但是对于js模块而言, 系统默认并没有给我们实现热更新, 所以修改了js模块代码并不会立即更新
3.2.1 JS模块如何实现热更新?
3.2.1.1 手动监听模块变化
// 需要实现热更新的js文件中
if(module.hot){ // 判断是否开启热更新
module.hot.accept("./test.js", function () { // 监听指定JS模块变化
});
}
3.2.1.2 手动编写模块变化后的业务逻辑
if(module.hot){
module.hot.accept("./test.js", function () {
let div = document.querySelector("div");
console.log(div);
document.body.removeChild(div);
copy();
});
}
九、webpack-babel-转换ES678语法
1.webpack-ES6语法处理
在企业开发中为了兼容一些低级版本的浏览器, 我们需要将ES678高级语法转换为ES5低级语法
否则在低级版本浏览器中我们的程序无法正确执行
默认情况下webpack是不会将我们的代码转换成ES5低级语法的, 如果需要转换我们需要使用babel来转换
2.如何使用babel?
2.1安装转换到ES5的相关包
npm install --save-dev babel-loader @babel/core @babel/preset-env
2.2配置babel
{
test: /\.js$/,
exclude: /node_modules/, // 不做处理的目录
loader: "babel-loader",
options: {
presets: ["@babel/preset-env"],
},
}
3.presets优化:
在实际企业开发中默认情况下babel会将所有高于ES5版本的代码都转换为ES5代码
但是有时候可能我们需要兼容的浏览器已经实现了更高版本的代码, 那么这个时候我们就不需要转换
因为如果浏览器本身已经实现了, 我们再去转换就会增加代码的体积,就会影响到网页的性能
所以我们通过配置presets的方式来告诉webpack我们需要兼容哪些浏览器
然后babel就会根据我们的配置自动调整转换方案, 如果需要兼容的浏览器已经实现了, 就不转换了
presets: [["@babel/preset-env",{
targets: {
"chrome": "58",
"ie": "10"
},
}]],
4.利用babel实现低版本语法
对于有对应关系的语法而言, 经过上面的配置就已经能够实现自动转换了
但是对于没有对应关系的语法而言, 经过上面的配置还不能实现自动转换
什么叫有对应关系, 什么叫做没有对应关系?
有对应关系就是指ES5中有对应的概念, 例如: 箭头函数对应普通函数, let对应var, 这个就叫做有对应关系
没有对应关系就是指ES5中根本就没有对应的语法, 例如Promise, includes等方法是ES678新增的
ES5中根本就没有对应的实现, 这个时候就需要再增加一些额外配置, 让babel自己帮我们实现对应的语法
4.1安装不存在代码的实现包
npm install --save @babel/polyfill
4.2在用到不存在代码的文件中导入包
import "@babel/polyfill";
注意点:
如果导入了polyfill,那么无论我们有没有用到不存在的语法都会打包到文件中
但是这样会增加打包后文件的大小, 我们希望的是只将用到的不存在语法打包到文件中
那么就需要在webpack.config.js中再配置一下
{
test: /\.m?js$/,
exclude: /node_modules/, // 告诉webpack不处理哪个文件夹
use: {
loader: "babel-loader",
options: {
presets: [
[
"@babel/preset-env",
{
targets: {
chrome: "58",
},
"useBuiltIns": "usage" // 只打包使用到的
},
],
],
},
},
},
5.直接在文件中导入polyfill模块的弊端
直接导入polyfill的方式只适用于一般项目开发, 但是如果是在编写一些第三方模块的时候这种方式会出现一些问题
因为这种方式是通过全局变量的方式来注入代码, 会污染全局环境. 所以我们再来看一下polyfill的第二种配置方式
6.第二种配置方式
babel-plugin-transform-runtime
6.1安装相关模块
npm install --save @babel/polyfill
npm install --save-dev @babel/plugin-transform-runtime
npm install --save @babel/runtime
6.2配置相关信息
plugins: [
["@babel/plugin-transform-runtime",
{
"absoluteRuntime": false,
"corejs": 2,
"helpers": true,
"regenerator": true,
"useESModules": false
}
]
]
完整的位置
{
test: /\.js$/,
exclude: /node_modules/, // 告诉webpack不处理哪一个文件夹
loader: "babel-loader",
options: {
"presets": [["@babel/preset-env", {
targets: {
// "chrome": "58",
},
// useBuiltIns: "usage"
}]],
"plugins": [
[
"@babel/plugin-transform-runtime",
{
"absoluteRuntime": false,
"corejs": 2,
"helpers": true,
"regenerator": true,
"useESModules": false
}
]
]
}
}
注意点:
"corejs": false, 还是全局注入,还是会污染全局环境
"corejs": 2, 则不会污染全局环境
npm install --save @babel/runtime-corejs2
babel-使用技巧
1.1查看错误提示
1.2根据错误信息查询文档
1.3根据文档缺什么安装配置什么
十、html-withimg-loader
1.什么是html-withimg-loader?
我们通过file-loader或者url-loader已经可以将JS或者CSS中用到的图片打包到指定目录中了
但是file-loader或者url-loader并不能将HTML中用到的图片打包到指定目录中
所以此时我们就需要再借助一个名称叫做"html-withimg-loader"的加载器来实现HTML中图片的打包
2.html-withimg-loader使用
2.1安装html-withimg-loader
npm install html-withimg-loader --save
2.2配置html-withimg-loader
{
test: /\.(htm|html)$/i,
loader: 'html-withimg-loader'
}
注意:
配置好了之前,打包的图片如果在指定的目录找不到,查看url-loader的limit的限制
十一、图片压缩和合并
压缩
在企业开发中为了提升网页的访问速度, 我们除了会压缩HTML/CSS/JS以外
还会对网页上的图片进行压缩和合并, 压缩可以减少网页体积, 合并可以减少请求次数
1.压缩打包之后的图片
每次在打包图片之前,我们可以通过配置webpack对打包的图片进行压缩, 以较少打包之后的体积
2.如何压缩图片?
利用image-webpack-loader/img-loader压缩图片
image-webpack-loader
img-loader
{
test: /\.(png|jpg|gif)$/i,
use: [
{
loader: "url-loader",
options: {
limit: 1024,
name: "[name].[ext]",
outputPath: "images/",
},
},
{
loader: 'image-webpack-loader',
options: {
mozjpeg: {
progressive: true,
},
// optipng.enabled: false will disable optipng
optipng: {
enabled: false,
},
pngquant: {
quality: [0.65, 0.90],
speed: 4
},
gifsicle: {
interlaced: false,
},
// the webp option will enable WEBP
webp: {
quality: 75
}
}
}
],
},
3.webpack图片压缩image-webpack-loader无法安装或安装卡死的解决办法
3.1 若安装过 image-webpack-loader 先卸载
npm uninstall image-webpack-loader
3.2 使用 cnpm , 这一步意思就是安装 cnpm 然后将全局的 registry 设置成阿里的镜像,国内阿里比较快
npm install cnpm -g --registry=https://registry.npm.taobao.org
3.3 使用 cnpm 安装 image-webpack-loader 会发现很快就安装好了
cnpm install --save-dev image-webpack-loader
合并
4.将打包之后的图片合成精灵图
过去为了减少网页请求的次数, 我们需要"UI设计师"给我们提供精灵图,
并且在使用时还需要手动的去设置每张图片的位置
但是有了webpack之后我们只需要让"UI设计师"给我们提供切割好的图片
我们可以自己合成精灵图, 并且还不用手动去设置图片的位置
5.如何合并图片
利用postcss-sprites/webpack-spritesmith合并图片
postcss-sprites
webpack-spritesmith
5.1 安装
npm install --save-d postcss-sprites postcss
5.2 postcss.config.js配置
module.exports = {
plugins: {
"postcss-sprites": {
// 告诉webpack合并之后的图片保存到什么地方
spritePath: "./bundle/images",
// 告诉webpack合并图片的时候如何分组
groupBy: function (image) {
// url: '../images/animal/animal1.png',
let path = image.url.substr(0, image.url.lastIndexOf("/"));
// console.log(path, "!!!!!!");
let name = path.substr(path.lastIndexOf("/") + 1);
// console.log(name, "!!!!!!!!");
return Promise.resolve(name);
},
// 过滤掉不想合并的图片
filterBy: function (image) {
let path = image.url;
if(!/\.png$/.test(path)){
return Promise.reject();
}
return Promise.resolve();
}
}
}
};
注意
6. webpack打包图片路径问题
webpack打包之后给我们的都是相对路径
但是正是因为是相对路径, 所以会导致在html中使用的图片能够正常运行, 在css中的图片不能正常运行
例如: 打包之后的路径是 images/lnj.jpg
那么在html中, 会去html文件所在路径下找images,正好能找到所以不报错
但是在css中, 会去css文件所在路径下找images, 找不到所以报错
|—bundle
|—css
|—index.css
|—js
|—index.js
|—images
|—lnj.jpg
|—index.html
解决方案
在开发阶段将publicPath设置为dev-server服务器地址
在上线阶段将publicPath设置为线上服务器地址
{
loader: "url-loader",
options: {
limit: 1024,
name: "[name].[ext]",
outputPath: "images/",
publicPath: "http://127.0.0.1:9090/images",
},
},
十二、Webpack-ESlint
1.什么是eslint?
ESLint 是一个插件化的 javascript 代码检测工具,
它可以用于检查常见的 JavaScript 代码错误,也可以进行"代码规范"检查,
在企业开发中项目负责人会定制一套 ESLint 规则,然后应用到所编写的项目上,
从而实现辅助编码规范的执行,有效控制项目代码的质量。
在编译打包时如果语法有错或者有不符合规范的语法就会报错, 并且会提示相关错误信息
2.如何使用eslint
2.1对应环境和loader
npm install eslint --save-dev
npm install eslint-loader --save-dev
2.2生成eslint配置文件
2.2.1 webpack.config.js
module: {
rules: [
{
// 由于loader是从右至左从下至上执行
// 而如果先执行了babel-loader就会对我们的代码进行转换
// 而我们想检查代码规范的代码并不是转换之后的代码
// 所以通过enforce: 'pre'告诉webpack在所有loader执行之前执行
enforce: 'pre',
test: /\.js$/,
exclude: /node_modules/, // 排除不检查的文件夹
include: path.resolve(__dirname, 'src'), // 指定检查的文件夹
loader: 'eslint-loader',
options: {
// eslint options (if necessary)
// fix: true
}
}
]
},
2.2.2 新建 .eslintrc.js
// .eslintrc.js
// .eslintrc.js
// https://eslint.org/docs/user-guide/configuring
module.exports = {
/*
不重要,永远写true
* */
root: true,
parserOptions: {
// parser: 'babel-eslint',
/*
默认设置为 3,5(默认), 你可以使用 6、7、8、9 或 10 来指定你想要使用的 ECMAScript 版本
* */
"ecmaVersion": 10,
/*
设置为 "script" (默认) 或 "module"(如果你的代码是 ECMAScript 模块)。
* */
"sourceType": "module",
/*
ecmaFeatures - 这是个对象,表示你想使用的额外的语言特性:
globalReturn - 允许在全局作用域下使用 return 语句
impliedStrict - 启用全局 strict mode (如果 ecmaVersion 是 5 或更高)
jsx - 启用 JSX
* */
"ecmaFeatures": {}
},
// 指定代码运行的宿主环境
env: {
browser: true, // 浏览器
node: true, // node
/*
支持 ES6 语法并不意味着同时支持新的 ES6 全局变量或类型(比如 Set 等新类型)。
对于 ES6 语法,使用 { "parserOptions": { "ecmaVersion": 6 } }
* */
es6: true,
},
extends: [
/*
引入standard代码规范
* */
// https://github.com/standard/standard/blob/master/docs/RULES-en.md
// npm install standard --save-dev
'standard'
],
/*
扩展或覆盖规则
* */
rules: {
// 强制语句结束添加,分号
semi: ["error", "always"],
// 强制缩进2个空格
indent: ["error", 4],
// 方法名和刮号之间不加空格
'space-before-function-paren': ['error', 'never'],
"no-unexpected-multiline": "off"
}
};
3.如何提升开发效率
通过阅读打包错误信息来修复不符合规范语法非常低效
所以我们可以通过webstrom插件来帮我们完成提示
webstrom–setting–eslint–autoxxx
十三、配置文件优化
1.区分开发环境和线上环境
1.1在开发阶段我们为了提升运行效率以及调试效率, 一般会通过dev-server来打包
在开发阶段我们为了提升打包效率,不会对打包的内容进行压缩
… …
1.2在上线阶段我们需要拿到真实的打包文件, 所以不会通过dev-server来打包
在上线阶段我们为了提升访问的效率, 所以在打包时需要对打包的内容进行压缩
… …
1.3但是当前我们将"开发环境和线上环境"的配置都写到了一个文件中, 这样非常不利于我们去维护配置文件
所以我们需要针对不同的环境将不同的配置写到不同的文件中
2.区分开发环境和线上环境优化
区分完不同环境配置文件之后发现两个文件之间存在大量重复配置
这我们可以利用webpack-merge模块来实现冗余代码的抽离和合并进一步优化配置文件
2.1将冗余代码抽取到 webpack.config.common.js中
2.2在dev.js和prod.js中导入common.js, 利用merge合并即可
npm install --save-d webpack-merge
// common
const path = require('path');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
/*
entry: 指定需要打包的文件
* */
entry: './src/js/index.js',
/*
output: 指定打包之后的文件输出的路径和输出的文件名称
* */
output: {
/*
filename: 指定打包之后的JS文件的名称
* */
filename: 'js/bundle.js',
/*
path: 指定打包之后的文件存储到什么地方
* */
path: path.resolve(__dirname, 'bundle')
},
/*
module: 告诉webpack如何处理webpack不能够识别的文件
* */
module: {
rules: [
// 检查编码规范的规则
{
// enforce: "pre"作用: 让当前的loader再其它loader之前执行
enforce: "pre",
test: /\.js$/,
exclude: /node_modules/,
include: path.resolve(__dirname, "src"),
loader: 'eslint-loader',
options: {
// eslint options (if necessary)
fix: true
},
},
// 打包JS规则
{
test: /\.js$/,
exclude: /node_modules/, // 告诉webpack不处理哪一个文件夹
loader: 'babel-loader',
options: {
presets: [['@babel/preset-env', {
targets: {
// "chrome": "58",
}
// useBuiltIns: "usage"
}]],
plugins: [
['@babel/plugin-proposal-class-properties', { loose: true }],
[
'@babel/plugin-transform-runtime',
{
absoluteRuntime: false,
corejs: 2,
helpers: true,
regenerator: true,
useESModules: false
}
]
]
}
},
// 打包字体图标规则
{
test: /\.(eot|json|svg|ttf|woff|woff2)$/,
use: [
{
loader: 'file-loader',
options: {
// 指定打包后文件名称
name: '[name].[ext]',
// 指定打包后文件存放目录
outputPath: 'font/'
}
}
]
},
// 打包图片规则
{
test: /\.(png|jpg|gif)$/,
use: [
{
loader: 'url-loader',
options: {
// 指定图片限制的大小
limit: 1024,
// 指定打包后文件名称
name: '[name].[ext]',
// 指定打包后文件存放目录
outputPath: 'images/',
publicPath: 'http://127.0.0.1:9090/images'
}
},
{
loader: 'image-webpack-loader',
options: {
mozjpeg: {
progressive: true,
quality: 65
},
// optipng.enabled: false will disable optipng
optipng: {
enabled: false
},
pngquant: {
quality: [0.65, 0.90],
speed: 4
},
gifsicle: {
interlaced: false
},
// the webp option will enable WEBP
webp: {
quality: 75
}
}
}
]
},
{
test: /\.(htm|html)$/i,
loader: 'html-withimg-loader'
},
// 打包CSS规则
{
test: /\.css$/,
use: [
{
// loader: "style-loader"
loader: MiniCssExtractPlugin.loader,
options: {
// hmr: true
/*
注意:在 webpack 5 中 HMR 已自动支持。无需配置。
*/
}
},
{
loader: 'css-loader',
options: {
// modules: true // 开启CSS模块化
}
},
{
loader: 'postcss-loader'
}
]
},
// 打包LESS规则
{
test: /\.less$/,
use: [{
loader: 'style-loader'
}, {
loader: 'css-loader'
}, {
loader: 'less-loader'
}, {
loader: 'postcss-loader'
}]
},
// 打包SCSS规则
{
test: /\.scss$/,
use: [{
loader: 'style-loader'
}, {
loader: 'css-loader'
}, {
loader: 'sass-loader'
}, {
loader: 'postcss-loader'
}]
}
]
},
/*
plugins: 告诉webpack需要新增一些什么样的功能
* */
plugins: [
new CleanWebpackPlugin(),
new CopyWebpackPlugin([{
from: './doc',
to: 'doc'
}]),
new MiniCssExtractPlugin({
filename: 'css/[name].css'
}),
]
};
// dev 开发
const Webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const Merge = require("webpack-merge");
const CommonConfig = require("./webpack.config.common.js");
const DevConfig = {
/*
devServer: 自动检测文件变化配置
* */
devServer: {
contentBase: './bundle',
open: true,
port: 9090,
proxy: [{
context: ['/user', '/login'],
target: 'http://127.0.0.1:3000',
changeOrigin: true, // 域名跨域
secure: false, // https跨域
pathRewrite: { '': '/api' } // 路径重写, 将路径中的api替换为空
}],
hot: true, // 开启热更新, 只要开启了热更新就不会自动刷新网页了
hotOnly: true // 哪怕不支持热更新也不要刷新网页
},
/*
配置sourcemap
development: cheap-module-eval-source-map
production: cheap-module-source-map
* */
devtool: 'cheap-module-eval-source-map',
/*
mode: 指定打包的模式, 模式有两种
一种是开发模式(development): 不会对打包的JS代码进行压缩
还有一种就是上线(生产)模式(production): 会对打包的JS代码进行压缩
* */
mode: 'development', // "production" | "development"
/*
plugins: 告诉webpack需要新增一些什么样的功能
* */
plugins: [
new HtmlWebpackPlugin({
// 指定打包的模板, 如果不指定会自动生成一个空的
template: './src/index.html',
}),
new Webpack.HotModuleReplacementPlugin()
]
};
module.exports = Merge(CommonConfig, DevConfig);
// production 生产
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const TerserJSPlugin = require('terser-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const Merge = require("webpack-merge");
const CommonConfig = require("./webpack.config.common.js");
const ProdConfig = {
/*
optimization: 配置webpack的优化项
* */
optimization: {
minimizer: [new TerserJSPlugin({}), new OptimizeCSSAssetsPlugin({})]
},
/*
ES5
optimization: {
minimizer: [
// For webpack@5 you can use the `...` syntax to extend existing minimizers (i.e. `terser-webpack-plugin`), uncomment the next line
`...`,
new CssMinimizerPlugin(),
],
},
*/
/*
配置sourcemap
development: cheap-module-eval-source-map
production: cheap-module-source-map
* */
devtool: 'eval-cheap-module-source-map',
/*
mode: 指定打包的模式, 模式有两种
一种是开发模式(development): 不会对打包的JS代码进行压缩
还有一种就是上线(生产)模式(production): 会对打包的JS代码进行压缩
* */
mode: 'production', // "production" | "development"
/*
plugins: 告诉webpack需要新增一些什么样的功能
* */
plugins: [
new HtmlWebpackPlugin({
// 指定打包的模板, 如果不指定会自动生成一个空的
template: './src/index.html',
minify: {
// 告诉htmlplugin打包之后的html文件需要压缩
collapseWhitespace: true,
}
}),
]
};
module.exports = Merge(CommonConfig, ProdConfig);
"scripts": {
"start": "npx webpack-dev-server --config webpack.config.dev.js", // 不会显示bundle文件,开发
"dev": "npx webpack --config webpack.config.dev.js", // 会显示bundle文件,开发
"prod": "npx webpack --config webpack.config.prod.js" // 生产
},
注意
报错 TypeError: merge is not a function
webpack-merge源码的export
通过es6提供的解构赋值,即按需加载
const {merge} = require('webpack-merge');
学习笔记,版权归Jonathan