ESLint
ESLint 是一个静态代码分析工具(static program analysis,在没有任何程序指定的情况下,对代码进行分析)。
ESLint 可以帮助开发者在项目中建立统一的团队代码规范,确保正确、统一的代码风格,提高代码可读性、可维护性。
使用 ESLint
安装 eslint 依赖:
npm i eslint -D
生成 eslint 配置文件(.eslintrc.js):
npx eslint --init
生成的 eslint 配置文件大体如下(根据不同选项生成的内容不同):
// .eslintrc.js
module.exports = {
env: {
browser: true, // 运行环境是浏览器
es2021: true, // 书写代码是 es2021 的
},
extends: [
'airbnb-base', // 继承 airbnb-base 代码风格
],
parser: '@typescript-eslint/parser', // 指定编译器, 默认是 espree, ts 代码是 @typescript-eslint/parser
parserOptions: {
ecmaVersion: 12, // ECMAScript 版本, 与上面 es2021 对应
sourceType: 'module',
},
plugins: [
'@typescript-eslint', // eslint 插件
],
rules: {
// 自定义规则, rules 的规则会覆盖 extends 的规则
// off/0 关闭检测, wran/1 警告提示, error/2 报错编译失败
// [key]: ('off' | 0) | ('wran' | 1) | ('error' | 2),
},
};
配置完后就可以通过下面的命令对代码进行检测:
npx eslint 文件路径
VSCode 插件
每次都通过命令来做代码检测太过麻烦,可以安装一个 VSCode 插件来帮助我们在编写代码的过程中就对错误的语法进行提示,这个插件就是 ESLint:
ESLint 这个插件会帮助我们在代码开发的过程中就对错误的语法进行提示,但是需要我们手动进行修复这些错误的语法,如果我们想要自动修复这些错误语法的话,可以安装一个代码格式化工具 Prettier:
Prettier 插件还可以创建一个 .prettierrc 文件,来对格式化的效果做 配置:
// .prettierrc
{
"printWidth": 100, // 代码超过多少宽度换行
"singleQuote": true, // true 使用单引号格式化, false 使用双引号格式化
}
ESLint-loader
我们可以使用 eslint-loader 让 webpack 来对代码进行代码检测,而不必使用 eslint 命令行了。
npm i eslint-loader -D
修改 webpack 配置:
// webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.js$/,
use: [
'babel-loader',
'eslint-loader',
],
},
],
},
};
适配 Vue
webpack 中想要使用 Vue 的 .vue 文件只需要下载 vue-loader 和 vue-template-compiler 即可:
npm i vue-loader vue-template-compiler -D
修改 webpack 配置文件:
// webpack.config.js
const { VueLoaderPlugin } = require('vue-loader');
module.exports = {
module: {
rules: [
{
test: /\.vue$/,
use: [
{
loader: 'vue-loader',
},
],
},
// .vue 文件中的 style 标签, 直接配置对应 loader 即可
{
test: /\.css$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
importLoaders: 2,
},
},
'postcss-loader',
],
},
],
},
plugins: [
new VueLoaderPlugin(), // 注意:必须使用这个插件
],
}
搭建本地服务器
在运行代码之前需要先进行两步操作:
- 操作一:运行 `npm run build` 打包代码
- 操作二:打开 index.html 文件
每次修改代码后都要重复执行上面的操作十分影响开发效率,我们希望做到文件发生变化时可以自动打包,展示,为了实现这种效果,webpack 提供了几种方式: watch 、devServe。
watch
webpack 提供了 watch 模式,在该模式下,webpack 依赖图中所有的文件只要一个发生了变化,就会重新打包编译,就不需要手动运行 npm run build 了。
开启 watch 模式:
// 方法一: 在 package.json 添加 watch 脚本
{
"scripts": {
"build": "webpack --watch"
}
}
// 方法二: 在 webpack.config 配置文件中开启
module.exports = {
watch: true,
}
watch 可以自动打包编译,但是却不能让浏览器自动刷新,以及它会进行文件操作(打包出 build 文件夹),效率也不是很好。
DevServer
DevServer 也可以在文件发生变化时自动打包编译,同时也可以让自动刷新浏览器看到最新的效果,DevServer 在本地开了一个服务,同时编译后不会输出任何文件,而是将打包后的文件保存在了内存中减去了文件操作,提高了性能。
下载依赖:
npm i webpack-dev-server -D
配置 scripts 脚本:
// package.json
{
"scripts": {
"serve": "webpack serve" // 增加 serve
}
}
配置好后直接运行 npm run serve 就可以了,devServe 服务默认开启的端口时 8080。
模块热替换 (HMR)
HMR(Hot Module Replacement)模块热替换指的是在应用程序运行的过程中,替换、添加、删除模块,而无需刷新整个页面。
使用 DevServer 时如果修改了文件内容会刷新整个页面来看到最新的效果,但是我们只修改了一小部分却导致了整个页面的刷新,这样的开发效率是很低的,我们希望只更新变化的部分,节省开发时间,这时就要使用 HMR。
webpack-dev-server 已经支持了 HMR ,直接开启就可以了:
// webpack.config.js
module.exports = {
devServer: {
hot: true, // 开启 HMR
},
}
开启了配置后我们还需要指定哪些模块发生更新时,进行 HMR:
// 判断是否开启了 HMR
if (module.hot) {
// 指定对应模块进行 HMR
module.hot.accept('./util.js', () => {
// 发生更新的回调, 可以省略
});
}
browserslist 导致 HMR 失效问题
坑点:使用了browserslist (配置文件、package.json 中定义)会导致 HMR 失效,这时可以配置 target = web 来解决这个问题:
// webpack.config.js
module.exports = {
target: 'web',
};
React、Vue 的 HMR
Vue 的 HMR 只要使用 vue-loader 就可以了,React 的 HMR 需要下载对应插件来实现。
安装 React HMR 所需依赖:
npm install @pmmmwh/react-refresh-webpack-plugin react-refresh -D
修改 webpack 配置:
// webpack.config.js
const ReactRefreshPlugin = require('@pmmmwh/react-refresh-webpack-plugin');
module.exports = {
plugins: [
new ReactRefreshPlugin(),
],
}
修改 babel 配置,引入对应插件:
// babel.config.js
module.exports = {
plugins: [
['react-refresh/babel']
]
}
注:在打包的时候不能使用 react-refresh-webpack-plugin 这个插件,这个插件只能配合 devServe 使用,不然会打包失败。
HMR 的原理
webpack-dev-server 会创建两个服务:1. 提供静态资源的服务(使用的时 express 搭建),2. Socket 服务(net.Scoket)。
express 服务负责提供静态资源的服务(打包后的资源被浏览器请求和解析)。
HMR 是一个 socket 长连接,当服务器监听到对应的模块发生变化时,会生成两个文件 .json(manifest 文件)和 .js (update chunk 文件),通过 socket 连接将这两个文件主动发送给浏览器,浏览器拿到两个文件后,通过 HMR runtime 机制,加载这两个文件,并且对修改的模块进行更新。
CSS 文件的 HMR
style-loader 中自带 HMR 功能,所以直接使用 style-loader 就可以了。
PublicPath 属性配置
在 webpack 的 output 和 devServe 中都有一个 publicPath 的配置。
output 中的 PublicPath
指定 publickPath:
// webpack.config.js
module.exports = {
output: {
filename: 'main.js',
path: './build',
publicPath: '/', // 指定 publicPath 为 /
}
}
这个 publicPath 的作用是指定 index.html 文件打包引用的基本路径,它默认值是一个字符串,打包后引入 bundle 时路径为 bundle.js,指定 publicPath: ‘/‘ 后路径为 /bundle.js

浏览器会根据所在的域名 + 路径去请求资源,比如当前的域名是 http://localhost:8000,那么未指定的 publicPath 的完整路径为 http://localhost:8000/main.js (浏览器自动加上 /), 指定 publicPath 的也是这个路径,因为自己加了后浏览器就不会加了。
注:这个路径不能乱指定,不然就找不到对应的文件了。
使用示例一:
在 Vue-cli 中这个 publicPath 默认为 /,如果打包后直接打开 index.html 文件时会发现代码不能正常运行,这时因为这时去加载 main.js 文件时使用的是 file 协议,file 协议去找 /main.js 时,路径为:file:///C:/main.js ,因为 C 盘下面没有 main.js 所以报错,这时可以通过将 publicPath 改为 ./ 解决。
使用示例二:
如果在部署的时候不是部署在根目录,则也需要指定这个 publicPath,比如部署在 /app 目录下,则 publicPath 也需要指定为 /app (不过一般都会用 Nginx 部署,所以一般不用配置)。
devServer 中的 publicPath
devServer 中也有一个 publicPath 属性,这个属性的功能是指定 devServer 启动的本地服务所在的目录文件,它的的默认值是 / , 也就是直接访问端口即可 http://localhost:8080 , 如果将其设置为 /app, 那么这时访问时需要 http://localhost:8080/app 才可以。
注意: 如果设置了 devServer 的 publicPath 时,output 中的 publicPath 也要设置为相同值,不然访问不到打包后的文件。
// webpack.config.js
module.exports = {
output: {
publicPath: '/app',
},
devServer: {
publicPath: '/app',
},
}
devServer 的其他配置
hotOnly
如果代码中出错修复后,默认会重新刷新整个页面,配置 hotOnly 后不会重新刷新页面。
module.exports = {
devServer: {
hot: true,
hotOnly: true,
},
}
host
配置 host,默认值为 localhost。
module.exports = {
devServer: {
host: '0.0.0.0',
},
}
port
配置监听端口,默认为 8080:
module.exports = {
devServer: {
port: 8000,
},
}
open
运行打包命令后,自动开启浏览器。
module.exports = {
devServer: {
open: true,
},
}
compress
开启静态文件 Gizp 压缩(只是在开发环境哦)。
module.exports = {
devServer: {
compress: true,
},
}
Proxy 代理
配置代理解决跨域问题,proxy 常用的配置属性如下:
- target:代理的目标地址
- pathRewrite:配置重写 URL
- secure:默认不接受转发到 https 服务器,设置 secure 后开启支持。
- changOrigin:默认情况下发送的 requestHeader 的 host 字段为本地服务地址,配置 changeOrigin 后 host 字段为正确的服务器地址。
简单配置:
// webpack.config.js
module.exports = {
devServer: {
proxy: {
// 配置 /api 开启的请求代理到指定目标
'/api': 'http://www.baidu.com',
}
},
}
// 发送请求处, 不要写域名
fetch('/api/list').then((res) => res.json()).then((res) => {
console.log('fetch', res);
})
配置为对象:
module.exports = {
devServer: {
proxy: {
'/api': {
target: 'http://www.baidu.com',
pathRewrite: {
// 将 /api 开头的地址中的 api 替换为空
'^/api': '',
},
changOrigin: true,
},
}
},
}
changOrigin 图解:
假设 devServer 开启的本地服务地址 http://localhost:8000、服务器地址 http://localhost:3000, 为开启 changOrigin 时服务器收到的 requestHeaders 如下图:
这个 host 字段就是本地服务的地址,而开启了 changeOrigin 后 host 属性就变成正常的服务器地址了:
historyApiFallback
当我们使用 Vue、React 开发时,如果用到的路由模式时 H5 history 模式,当我们跳转到指定 path 后刷新页面,这时就会发现页面找不到了 404,这时因为浏览器会根据这个 url 去服务器寻找对应的文件,因为路由是在前端控制的,服务器并没有对应的文件所以就 404 了,这时我们就可以配置这个字段将其设置为 true,这样当本地服务器找不到对应得文件时,会将 index.html 返回,然后 Vue、React 会解析并跳转到指定路由。
module.exports = {
devServer: {
historyApiFallback: true,
},
}
也可以对 historyApiFallback 进行更细致的配置:
module.exports = {
//...
devServer: {
historyApiFallback: {
rewrites: [
// 当在 from 下刷新时, 返回 to 的内容
{ from: /^\/$/, to: '/views/landing.html' },
{ from: /^\/subpage/, to: '/views/subpage.html' },
{ from: /./, to: '/views/404.html' },
],
},
},
};
resolve 模块解析
当我们使用 require、import 方式导入模块时 webpack 会根据地址进行如下的解析:
- 绝对路径:不需要进一步解析
- 相对路径:import、require 的资源文件所处目录拼接上 import、require 给定的相对路径,生成决定路径。
- 模块路径:也就是
require('vue')/import vue from 'vue', 这时会在配置文件的 resolve.modules 指定的所有目录中检索,默认值为['node_modules']。
配置:
module.exports = {
resolve: {
modules: ['node_modules'],
}
}
文件和文件夹解析规则
我们导入一个文件可能会写出如下代码:
require('./js/index'); // 不带扩展名
require('./js'); // 只有文件夹名称
webpack 会判断引入的资源是一个文件还是一个文件夹,如果是一个文件则判断是否具有扩展名,有扩展名直接打包,没有则去 resolve.extensions 配置中查找文件扩展名解析。
module.exports = {
resolve: {
extensions: ['.wasm', '.mjs', '.js', '.json', '.jsx'],
}
}
如果是文件夹则会根据 resolve.mainFiles 配置中指定的文件顺序查找(默认为 index),然后再匹配 resolve.extensions 规则:
module.exports = {
resolve: {
mainFiles: ['index']
}
}
alias 配置
alias 为一个目录地址配置一个别名,这样导入模块的时候可以直接使用这个别名,而不用书写大量的 ../。
const path = require('path');
module.exports = {
resolve: {
alias: {
'@': path.resolve(__dirname, './src');
}
},
}
使用:
require('@/xxx.js');
// or
import xx from '@/xxx.js';
