前言
- Webpack 是一个前端资源加载/打包工具。它将根据模块的依赖关系进行静态分析,然后将这些模块按照指定的规则生成对应的静态资源。
- Webpack整体配置主要围绕以下属性
- mode(开发模式),
- entry(打包入口)
- output(打包出口)
- devServer(开发环境自动化配置)
- devtool(调试模式)
- resolve (解析规则)
- module (模块)
- plugins(插件)
- optimization(优化策略)
- webpack项目的基本目录结构 webpack-cli ├─ config webpack配置文件 │ ├─ webpack.base.js // 公共配置 │ └─ webpack.deve.js // 开发模式配置 │ └─ webpack.prod.js // 生成模式配置 ├─ dist 资源打包生成文件 │ ├─ chunk.js │ └─ chunk.js.map │ └─ react.chunk.js // 分割后的react相关的chunk文件 │ └─ react.chunk.js.map // react代码映射文件 │ └─ index.html // 打包后的html模板文件 ├─ public 静态资源文件 │ ├─ index.html // html模板文件 │ └─ favicon.ico // 网站图标 ├─ src │ ├─ main.js // 打包入口文件 │ └─ App.jsx │ └─ pages │ └─ router ├─ node_modules // 依赖文件夹 ├─ .browserslistrc // 指定兼容性版本 ├─ babel.config.js //babel配置文件 ├─ postcss.config.js //postcss配置文件 ├─ .eslintrc.js // eslint配置文件 ├─ .eslintignore // eslint需要忽略的配置文件 ├─ package-lock.json ├─ package.json └─ webpack.config.js
Webpack基础用法
开发前准备
- yarn init 初始化项目
- 完成package.json文件的基础配置
{ "name": "webpack-demo", "version": "1.0.0", "main": "index.js", "license": "MIT", "scripts": { "build": "webpack" } }
- yarn add webpack webpack-cli -D 安装webpack插件
一、mode
- 用于指定当前构建环境
- 值为
development
production
node
- 默认值是
production
development
会设置环境变量process.env.NODE_ENV = 'development',并且会开启内置模块:NamedChunksPlugin
(当开启 HMR 的时候,显示更新包的名字)NamedModulesPlugin
(当开启 HMR 的时候,显示更新包的相对路径)
production
会设置环境变量process.env.NODE_ENV = 'production',并且会开启内置模块:FlagDependencyUsagePlugin
(编译时标记依赖)FlagIncludedChunksPlugin
(防止chunks多次加载)ModuleCoucatenationPlugin
(作用域提升(scope hosting))NoEmitOnErrorsPlugin
(在输出阶段时,遇到编译错误跳过)OccurrenceOrderPlugin
(按照chunk出现次数分配chunk id)SideEffectsFlagPlugin
(识别 package.json 或者 module.rules 的 sideEffects 标志)UglifyJsPlugin
(删除未引用代码,并压缩)
- none表示不启用任何优化选项
module.exports = {
mode:'development', // 指定为开发模式
}
二、entry
- 指定打包文件的入口
- 支持单文件和多文件打包
- 值为字符串时表示打包单个文件
- 值为数组时表示打包多个文件到单个文件
- 值为对象时表示打包多个文件到多个文件
- 单文件打包
module.exports = { mode:'development', entry:'./src/main.js', // 指定单个入口文件 }
- 多文件打包产出单个文件
module.exports = { mode: 'development', entry: ['./src/main.js','./src/index.js' ], // 指定多个入口文件 };
- 多文件打包产出多个文件
module.exports = { mode: 'development', entry: { main: './src/main.js', index: './src/index.js', }, // 指定多个入口文件 };
三、output
- 用来指定Webpack如何输出,输出内容和输出目录等。
- 常用的配置属性:
- filename 输出文件名称
- path 输出文件目录
- publicPath 公共资源路径前缀
- chunkFilename 开启代码分割时产生的chunk文件名称
- assetModuleFilename 统一的静态资源文件名称
- clean 在生成文件之前清空 output 目录
- library 当前库向外暴露的变量名称
- [name] chunk名称、[contenthash] 模块内容的Hash值、[contenthash:8] 模块内容的 Hash值的前八位、[ext] 文件后缀名、[query] 参数、[id] chunk的id。
const path = require('path')
module.exports = {
mode: 'development',
entry: {
main: './src/main.js',
index: './src/index.js',
},
output: {
path: path.resolve(__dirname, './dist'), // 设置打包文件目录
//filename:'build.js', // 单文件时可以直接固定输出文件名称~~
filename: '[name].[contenthash:8].js', // 多文件时必须动态输出文件名称,这里的[name]可entry中的key对应
assetModuleFilename:'static/media/[name].[contenthash:8][ext][query]', // 资源文件打包到输出文件路径下的/static中
chunkFilename:'[name].[contenthash:8].chunk.js', // chunk文件名称
publicPath: "", // 默认值 资源路径相对于 HTML 页面
clean: true, // 在生成文件之前清空 output 目录
},
};
四、devtool
- 此选项控制是否生成,以及如何生成 source map。
- [inline-|hidden-|eval_][nosources-][cheap-[module-]]source-map
- 第一段可选参数为:
- inline (生成行内base64格式source map文件,不单独产出文件)
- hidden (有map文件,没有sourceMappingURL)
- eval (所有的source map文件都被eval包裹)
- 第二段可选参数为
- nosources (提示错误信息,没有源代码信息)
- 第三段可选参数为
- cheap 映射转换过的代码,只显示行
- cheap-module 映射原代码,只显示行
- 开发环境推荐使用cheap-module-source-map,生产环境推荐使用hidden-source-map | devtool | 构建速度 | 重新构建速度 | 生产环境 | 品质(quality) | ------------------------------ | ---- | ------ | ---- | ----------- | | (none) | +++ | +++ | yes | 打包后的代码 | | eval | +++ | +++ | no | 生成后的代码 | | cheap-eval-source-map | + | ++ | no | 转换过的代码(仅限行) | | cheap-module-eval-source-map | o | ++ | no | 原始源代码(仅限行) | | eval-source-map | -- | + | no | 原始源代码 | | cheap-source-map | + | o | no | 转换过的代码(仅限行) | | cheap-module-source-map | o | - | no | 原始源代码(仅限行) | | inline-cheap-source-map | + | o | no | 转换过的代码(仅限行) | | inline-cheap-module-source-map | o | - | no | 原始源代码(仅限行) | | source-map | -- | -- | yes | 原始源代码 | | inline-source-map | -- | -- | no | 原始源代码 | | hidden-source-map | -- | -- | yes | 原始源代码 | | nosources-source-map | -- | -- | yes | 无源代码内容
const path = require('path');
module.exports = {
mode: 'development',
entry: ['./src/main.js', './src/index.js'], // 指定入口文件
output: {
path: path.resolve(__dirname, './dist'), // 设置打包文件目录
filename: '[name].[contenthash:8].js', // 多文件时必须动态输出文件名称,这里的[name]可entry中的key对应
assetModuleFilename: 'static/css/[name].[contenthash:8][ext][query]', // 资源文件打包到输出文件路径下的/static中
chunkFilename: '[name].[contenthash:8].chunk.js', // chunk文件名称
publicPath: '', // 默认值 相对于 HTML 页面
clean: true, // 在生成文件之前清空 output 目录
},
devtool: 'cheap-module-source-map', // 此选项控制是否生成,以及如何生成 source map。
};
从这里开始您可以将上面已经写好的配置属性直接合并到下文,为了减少代码体积,我不再做合并操作
五、devServer
- 开发模式下的自动化配置。
- 常用的配置属性:
- hot 开启HMR热模块替换
- open 默认打开浏览器
- compress 开启gzig压缩
- historyApiFallback 解决history模式下重定向问题,找不到请求路径时重定向到index.html文件
- host 指定主机地址
- port 指定端口号
- proxy 请求代理转发
module.exports = {
devServer:{
hot:true, // 开启HMR热模块替换
open:true, // 自动打开网页
compress:true, // 开启gzig压缩
historyApiFallback:true, // 解决history模式下重定向问题,找不到请求路径时重定向到index.html文件
host:'localhost', // 指定主机地址
port:3000, // 指定端口号
proxy: {
'/api': {
target: 'http://localhost:8888', // 需要转发的目标地址
changeOrigin:true, // 覆盖请求主机来源
pathRewrite: { '^/api': '' }, // 重写路径
},
},
client: {
progress: true, // 浏览器中以百分比显示进度
},
}
};
六、resolve
- Webpack的解析规则
- 常用属性介绍
- alias 路径别名,用来确保模块引入变得更简单
- extensions 补全后缀名,按数组中的顺序执行解析后缀名
- modules 模块的解析位置,默认是node_modules
- plugins 更多插件,可以在这里注册更多插件,一般都是在option.plugins中配置插件
const path = require('path');
module.exports = {
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
},
extensions: ['.js', '.jsx', '.tsx', '.vue'],
},
}
七、module
- 对不同类型文件的模块做出解析处理
- loader的解析顺序是从左到右,从下到上
- loader的执行优先级也受enforce字段影响, Pre loader > Inline loader > Normal loader > Post loader
- 单个loader使用loader属性注册,单个或多个loader使用use注册
module.exports = {
module: {
rules: [
// 处理css文件
{
test:/\.css$/
loader:'style-loader', // 单个loader
use:['style-loader','css-loader'], // 多个loader
}
],
}
}
八、loader资源处理和配置
1) css资源处理
1.处理css资源
yarn add style-loader css-loader -D 使用style-loader css-loader处理css资源
module.exports = {
module: {
rules: [
// 处理css文件
{
test:/\.css$/
use:['style-loader','css-loader'], // 多个loader
}
],
}
}
2.为css添加兼容性处理
yarn add style-loader css-loader postcss-loader postcss-preset-env -D
根目录新建.browserslistrc文件,用于指定兼容版本 .browserslistrc
> 0.01%
last 2 version
not dead
webpack.config.js
module.exports = {
module: {
rules: [
// 处理css文件
{
test:/\.css$/
use:[
'style-loader',
{
loader:'css-loader',
// 此处添加loader的配置信息
options:{
importLoaders:1, // 当处理css文件遇到@import导出的资源时,先使用上一级loader进行处理
}
},
{
loader:'postcss-loader',
// 此处添加loader的配置信息
options: {
postcssOptions: {
// 使用postcss-preset-env处理兼容性问题
// 在这里配置有一个弊端就是,再处理less sass等依然要写重复代码,官方推荐在根目录新建postcss.config.js文件进行配置
plugins: [require('postcss-preset-env')],
},
},
}
], // 多个loader
}
],
}
}
上面的配置过于繁琐,接下来我们使用官方推荐的,在跟目录下新建postcss.config.js完成兼容性配置 (后续Js的兼容性处理也使用单独文件处理)
// postcss.config.js
module.exports = {
plugins: [require('postcss-preset-env')], // 使用安装的postcss-preset-env插件
};
3.处理less和sass文件
module: {
rules: [
{
test: /\.css$/,
use: ['style-loader', { loader: 'css-loader', options: { importLoaders: 1 } }, 'postcss-loader'],
},
{
test: /\.less$/,
use: ['style-loader', { loader: 'css-loader', options: { importLoaders: 2 } }, 'postcss-loader', 'less-loader'],
},
{
test: /\.s[ac]ss$/,
use: ['style-loader', { loader: 'css-loader', options: { importLoaders: 2 } }, 'postcss-loader', 'sass-loader'],
},
],
},
4. loader简写
const setStyleLoaders = (i,loader)=> ['style-loader', { loader: 'css-loader', options: { importLoaders: i } }, 'postcss-loader',loader].filter(Boolean); // loader参数可能为undefined,过滤一下结果
module: {
rules: [
{
test: /\.css$/,
use: setStyleLoaders(1)
},
{
test: /\.less$/,
use: setStyleLoaders(2,'less-loader')
},
{
test: /\.s[ac]ss$/,
use: setStyleLoaders(2,'sass-loader'),
},
],
},
2) 图片资源处理
- 在Webpack4中一般使用row-loader | file-loader | url-loader处理图片等资源
- Webpack5内置asset资源模块类型(asset module type)
asset/resource
发送一个单独的文件并导出 URL。之前通过使用file-loader
实现。asset/inline
导出一个资源的 data URI。之前通过使用url-loader
实现。asset/source
导出资源的源代码。之前通过使用raw-loader
实现。asset
在导出一个 data URI 和发送一个单独的文件之间自动选择。之前通过使用url-loader
,并且配置资源体积限制实现。
- 当你仍然想使用其他laoder处理资源时,可通过javascript/auto来停止Webpack的默认处理
1.使用url-loader处理资源 (仅作为演示,不推荐)
yarn add url-loader -D
module.exports = {
module: {
rules: [
{
test: /.(png|jpe?g|gif|svg|webg)$/i,
use: [
{
loader: 'url-loader',
options: {
// 超过10kb导出文件,小于10kb直接使用base64格式添加到行内
limit: 10 * 1024,
}
},
],
type: 'javascript/auto'
},
]
},
}
2.使用asset处理资源 (推荐)
module.exports = {
module: {
rules: [
{
test: /.(png|jpe?g|gif|svg|webg)$/,
type: 'asset',
generator:{
// 输出的资源文件名称 (同上文output.assetModuleFilename)
filename:'static/media/[name][contenthash:8][ext][query]'
},
parser:{
dataUrlCondition:{
// 超过10kb导出文件,小于10kb直接使用base64格式添加到行内
maxSize:10 * 1024
}
}
},
]
},
}
3) 字体图标资源处理
module.exports = {
module: {
rules: [
{
test: /.(ttf|woff)$/,
type: 'asset/resource',
}
]
},
}
4) Js资源处理
- 可以使用babel-loader解析js代码
- 单独使用babel-loader并不会对代码进行兼容性处理
- 使用@babel/preset-env预设,对代码做出兼容处理
yarn add babel-loader @babel/preset-env core-js -D
新建babel.config.js文件
// babel.config.js
module.exports = {
presets: [
[
// 使用预设
'@babel/preset-env',
{
// 按需加载
useBuiltIns: 'usage',
// 指定core-js版本
corejs: 3,
},
],
],
};
webpack.config.js
module: {
rules: [
{
// js或者jsx的代码都使用babel-loader处理
test: /.(jsx?)$/,
exclude: /node_modules/, // 排除node_modules文件夹
loader: 'babel-loader',
},
],
},
八、plugin插件
- plugin是为了扩展webpack,增加自定义的构建行为
- 使用webpack执行更加广泛的内容,拥有更强的构建能力
- webpack在构建的过程中会执行一系列的钩子事件,插件所做的就是找到对应的钩子,在上面注册事件,这样在webpack构建的过程中,自定义的任务事件就会随着钩子事件的触发而执行
- 每一个插件都是一个构造函数,使用时需要实例化这个插件(每一个plugin都有apply方法,编译时会生成全局的compiler对象,可以在插件apply方法中调用compiler.hook上注册的方法(解析,执行,编译都会有不同的方法)来扩展webpack的构建能力) 模拟插件的注册过程
class TestPlugin{
apply(compiler){
// 执行emit事件的同步钩子
compiler.hook.emit.tap('TestPlugin',(compilation)=>{
console.log('emit tap')
})
}
}
module.exports = TestPlugin;
1) html-webpack-plugin使用
yarn add html-webpack-plugin -D
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
plugins: [
new HtmlWebpackPlugin({
template: path.resolve(__dirname, './public/index.html'), // html模板文件
title: 'webpack-template', // 添加变量
}),
],
}
新建plubic/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<!-- 使用模板变量 -->
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<div id="app"></div>
</body>
</html>
2) eslint-webpack-plugin
- yarn add eslint-webpack-plugin eslint -D
- 根目录新建.eslintignore
# 忽略build目录下类型为js的文件的语法检查
build/*.js
# 忽略src/assets目录下文件的语法检查
src/assets
# 忽略src/utils目录下文件的语法检查
src/utils
# 忽略public目录下文件的语法检查
public
- 根目录新建.eslintec.js
module.exports = {
root: true,
env: {
browser: true,
node: true,
es2021: true,
},
extends: ['eslint:recommended'],
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
},
rules: {
'no-unused-vars': 'off',
},
};
- 配置plugin
const EslintWebpackplugin = require('eslint-webpack-plugin')
module.exports = {
plugins: [
new EslintWebpackPlugin({
context: path.resolve(__dirname, './src'), // 指定文件根目录的字符串
exclude: 'node_modules', // 排除node_modules文件夹
}),
],
}
3) copy-webpack-plugin
yarn add copy-webpack-plugin -D
const CopyWebpackPlugin = require('copy-webpack-plugin');
module.exports = {
plugins: [
new CopyWebpackPlugin({
patterns: [
{
from: path.resolve(__dirname, './public'), // copy来源文件夹
to: path.resolve(__dirname, './dist'), // copy目的文件夹
globOptions: {
ignore: ['**/index.html'], // 排除index.html文件 (已经通过html-webpack-plugin处理)
},
},
],
}),
],
}
Webpack高级-优化章
一、缓存
- 缓存可以减少babel和eslint的编译次数,减少AST的构建过程,从而减少性能和编译时间开销
- 开启babel-loader 和eslint-webpack-plugin缓存
module:{
rules:[
{
test: /.(jsx?)$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
cacheDirectory: true, // 开启缓存
},
},
},
]
},
plugins:[
new EslintWebpackPlugin({
context: path.resolve(__dirname, './src'), // 指定文件根目录的字符串
exclude: 'node_modules', // 排除node_modules文件夹
cache: true, // 开启缓存
cacheLocation: path.resolve(__dirname, './node_modules/.cache/.eslintcache'), // 缓存文件地址
}),
]
二、使用thread-loader多线程处理
- 默认node中js的编译过程是单线程的,可以使用thread-loader开启多线程并发完成代码编译,完成后交给主线程处理,从而提高运行效率,减少编译时间
yarn add thread-loader -D
const threads = os.cpus().length; // cpu线程数量
module:{
rules:[
{
test: /.(jsx?)$/,
exclude: /node_modules/,
use: [
{
loader: 'babel-loader',
options: {
cacheDirectory: true, // 开启缓存
},
},
{
loader: 'thread-loader',
options: {
// 产生的worker数量
workers: threads,
// 一个worker工作量
workerParallelJobs: 50,
},
},
],
},
]
}
三、减少babel转换代码体积@babel/plugin-transform-runtime
yarn add @babel/plugin-transform-runtime -D
- @babel/plugin-transform-runtime
修改babel.config.js中的配置
module.exports = {
presets: [['@babel/preset-env']],
plugins: [
[
'@babel/plugin-transform-runtime',
{
helpers: true, // 是否自动引入辅助函数
corejs: 3, // core-js的版本
useESModules: false, // 是否使用es6模块化
},
],
],
};
四、Css代码压缩和Js代码压缩
- 代码压缩仅需要在生产环境配置
- 使用mini-css-extract-plugin和css-minimization-webpack-plugin转换和压缩css代码
- 使用terser-webpack-plugin压缩Js代码
const path = require('path');
const os = require('os');
// 用于压缩Css代码插件
const CssMinimizerWebpackPlugin = require('css-minimizer-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
// 用于压缩Js代码插件
// 将上文中使用的style-loader替换为MiniCssExtractPlugin.loader
const setStyleLoaders = (i, loader) => [MiniCssExtractPlugin.loader, { loader: 'css-loader', options: { importLoaders: i } }, 'postcss-loader', loader].filter(Boolean);
const threads = os.cpus().length;
module.exports = {
optimization: {
minimize:true, // 告诉webpack使用自己的插件压缩bundle
minimizer: [
new CssMinimizerWebpackPlugin(),
new TerserWebpackPlugin({
parallel: threads, // 开启多线程压缩
}),
],
},
plugins:[
new MiniCssExtractPlugin({
filename: '[name].[contenthash:10].css', // 指定文件名称
}),
]
}
五、单文件按需引入和代码分割
- 在开发单页面应用时,在使用import做静态引入时,代码分割尤为重要
- 使用splitChunks可以根据chunk大小进行分割,在代码提交和加载请求次数内均衡选择
optimization: {
splitChunks: {
chunks: 'all', // 代码分割优化仅选择initial(初始块),async(按需块),all(所有块)
minSize: 30000, // chunk被分割时的最小单位 默认值:30kb
minChunks: 1, // 一个模块被引用的次数达到多少时分割
maxAsyncRequests: 5, // 按需加载文件时 并行请求的最大数目。默认为5。
maxInitialRequests: 3, // 加载入口文件时 并行请求的最大数目。默认为3。
// 符合以上条件的chunk会进入cacheGroups做最后更细致的优化判断
// 后续搭建react-cli时就可以在这里将react react-dom react-router-dom相关的配置分割成一个chunk
cacheGroups: {
react: {
test: /[\\/]node_modules[\\/]react(.*)?[\\/]/, // 表示引入库存在于node_modules相关的react库
priority: 20, // 数字越大优先级越高
filename: 'react.chunk.js',
},
vendors: {
test: /[\/]node_modules[\/]/, // 表示引入库存在于node_modules
priority: -10, // 数字越大优先级越高
filename: 'vendors.chunk.js', // 设置代码分割后的文件名
},
},
},
},
测试代码分割
yarn add react react-dom
在打包入口文件src/index.js中添加react相关库,测试代码分割
// main.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import './style.css';
终端执行 yarn build 打包后的结果
六、关闭打包分析
module.exports = {
performance: false, // 关闭打包分析
}
七、清理控制台无用输出 (美化)
yarn add friendly-errors-webpack-plugin -D
// 隐藏控制台无用打印信息
const FriendlyErrorsWebpackPlugin = require('friendly-errors-webpack-plugin');
module.exports = {
stats: 'errors-only', // 控制台只显示错误信息
plugins: [
new WebpackBarPlugin({
color: '#85d3d1', // 默认green,进度条颜色支持HEX
basic: false, // 默认true,启用一个简单的日志报告器
profile: false, // 默认false,启用探查器。
}),
new FriendlyErrorsWebpackPlugin({
// 成功的时候输出
compilationSuccessInfo: {
messages: [`Your application is running here: localhost:4000`],
},
// 是否每次都清空控制台
clearConsole: true,
}),
],
}
八、控制台美化,添加构建进度条 (美化)
yarn add webpackbar -D
// 进度条美化
const WebpackBarPlugin = require('webpackbar');
module.exports = {
plugins: [
new WebpackBarPlugin({
color: '#85d3d1', // 默认green,进度条颜色支持HEX
basic: false, // 默认true,启用一个简单的日志报告器
profile: false, // 默认false,启用探查器。
}),
],
}
Webpack高级-搭建react-cli
- 新建文件夹 react-cli
- 初始化配置 yarn init
- package.json添加启动命令 (使用cross-env 添加环境变量) - 点击前往cross-env
- yarn add cross-env -D
{
"name": "react-cli",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"scripts": {
"dev":"npm run serve",
"serve":"cross-env NODE_ENV=development webpack server --config ./config/webpack.deve.js",
"build":"cross-env NODE_ENV=production webpack --config ./config/webpack.prod.js"
}
}
- 安装webpack相关依赖
- yarn add webpack webpack-cli webpack-dev-server -D
- 安装style相关依赖
- yarn add style-loader css-loader postcss-loader sass-loader node-sass -D
- 安装Js、babel等相关依赖
- yarn add babel-loader thread-loader -D
- 安装插件plugin
- yarn add css-minimizer-webpack-plugin mini-css-extract-plugin terser-webpack-plugin copy-webpack-plugin eslint-webpack-plugin eslint html-webpack-plugin webpackbar friendly-errors-webpack-plugin -D
- 安装react相关依赖
- yarn add react react-dom react-router-dom -S
- 安装react提供的预设和热更新插件
- yarn add babel-preset-react-app eslint-config-react-app @pmmmwh/react-refresh-webpack-plugin react-refresh -D
- 跟目录新建config文件夹 并添加webpack.deve.js和webpack.prod.js和webpack.base.js文件
- 跟目录新建src/main.js文件 (这是我们的入口文件)
- 跟目录新建public/index.html和favicon.ico图标,点击在线制作ico图标,然后将制作好的图表名称改为favicon.ico
- 跟目录新建.browserslistrc文件,新建babel.config.js,新建postcss.config.js,新建.eslintrc.js,新建.eslintignore
1. .browserslistrc文件
> 0.1%
last 2 version
not dead
2. babel.config.js
const isDevelopment = process.env.NODE_ENV === 'development';
module.exports = {
presets: ['react-app'], // 使用react提供的babel预设
// 只有在开发模式下使用react的热更新插件
plugins: [isDevelopment && require.resolve('react-refresh/babel')].filter(Boolean),
};
3. postcss.config.js
module.exports = {
plugins: [require('postcss-preset-env')],
};
4. .eslintrc.js 注:eslint配置详情
module.exports = {
root: true,
env: {
browser: true,
node: true,
es2021: true,
},
extends: ['react-app', 'eslint:recommended'],
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
babelOptions: {
presets: [['babel-preset-react-app', false], 'babel-preset-react-app/prod'],
},
},
rules: {
'no-unused-vars': 'off',
},
};
5. eslintignore
# 忽略build目录下类型为js的文件的语法检查
build/*.js
# 忽略src/assets目录下文件的语法检查
src/assets
# 忽略src/utils目录下文件的语法检查
src/utils
# 忽略public目录下文件的语法检查
public
6. webpack.base.js
const path = require('path');
const os = require('os');
const HtmlWebpackplugin = require('html-webpack-plugin');
const CssMinimizerWebpackPlugin = require('css-minimizer-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const TerserWebpackPlugin = require('terser-webpack-plugin');
const { DefinePlugin } = require('webpack');
const threads = os.cpus().length;
// 是否是生成环境
const isProduction = process.env.NODE_ENV === 'production';
const resolvePath = _path => path.resolve(__dirname, _path);
// 生产环境使用MiniCssExtractPlugin.loader 开发环境使用style-loader
const setStyleLoaders = (i, loader) => [isProduction ? MiniCssExtractPlugin.loader : 'style-loader', { loader: 'css-loader', options: { importLoaders: i } }, 'postcss-loader', loader].filter(Boolean); // loader参数可能为undefined,过滤一下结果
const baseConfig = {
entry: resolvePath('../src/main.js'), // 指定入口文件
output: {
path: isProduction ? undefined : resolvePath('../dist'), // 设置打包文件目录
filename: 'static/js/[name].[contenthash:8].js', // 多文件时必须动态输出文件名称,这里的[name]可entry中的key对应
assetModuleFilename: 'static/media/[name].[contenthash:8][ext][query]', // 资源文件打包到输出文件路径下的/static中
chunkFilename: 'static/js/[name].[contenthash:8].chunk.js', // chunk文件名称
publicPath: '', // 默认值 相对于 HTML 页面
clean: true, // 在生成文件之前清空 output 目录
},
resolve: {
alias: {
'@': resolvePath('../src'),
},
extensions: ['.js', '.jsx', '.tsx'],
},
module: {
rules: [
{
test: /\.css$/,
use: setStyleLoaders(1),
},
{
test: /\.less$/,
use: setStyleLoaders(2, 'less-loader'),
},
{
test: /\.s[ac]ss$/,
use: setStyleLoaders(2, 'sass-loader'),
},
{
test: /.(png|jpe?g|gif|svg|webg)$/,
type: 'asset',
parser: {
dataUrlCondition: {
// 超过10kb导出文件,小于10kb直接使用base64格式添加到行内
maxSize: 10 * 1024,
},
},
},
{
test: /.(ttf|woff)$/,
type: 'asset/resource',
},
{
test: /.(jsx?)$/,
exclude: /node_modules/,
use: [
{
loader: 'babel-loader',
options: {
cacheDirectory: true, // 开启缓存
},
},
{
loader: 'thread-loader',
options: {
// 产生的worker数量
workers: threads,
// 一个worker工作量
workerParallelJobs: 50,
},
},
],
},
],
},
optimization: {
// 只在生产环境开启压缩优化
minimize: isProduction, // 告诉webpack使用自己的插件压缩bundle
minimizer: [
new CssMinimizerWebpackPlugin(),
new TerserWebpackPlugin({
parallel: threads, // 开启多线程压缩
}),
], // 插件列表
splitChunks: {
chunks: 'all', // 代码分割优化仅选择initial(初始块),async(按需块),all(所有块)
minSize: 30000, // chunk被分割时的最小单位 默认值:30kb
minChunks: 1, // 一个模块被引用的次数达到多少时分割
maxAsyncRequests: 5, // 按需加载文件时 并行请求的最大数目。默认为5。
maxInitialRequests: 3, // 加载入口文件时 并行请求的最大数目。默认为3。
// 符合以上条件的chunk会进入cacheGroups做最后更细致的优化判断
// 后续搭建react-cli时就可以在这里将react react-dom react-router-dom相关的配置分割成一个chunk
cacheGroups: {
react: {
test: /[\\/]node_modules[\\/]react(.*)?[\\/]/, // 表示引入库存在于node_modules相关的react库
priority: 20, // 数字越大优先级越高 (-10大于-20)
filename: 'static/js/react.chunk.js',
},
vendors: {
test: /[\/]node_modules[\/]/, // 表示引入库存在于node_modules
priority: -10, // 数字越大优先级越高
filename: 'static/js/vendors.chunk.js', // 设置代码分割后的文件名
},
},
},
runtimeChunk: {
name: entrypoint => `runtime-${entrypoint.name}.chunk`,
},
},
plugins: [
new HtmlWebpackplugin({
template: resolvePath('../public/index.html'), // html模板文件
title: 'react-cli', // 添加变量
}),
new DefinePlugin({
BASE_URL: '"/"',
}),
],
performance: false, // 关闭打包分析
};
module.exports = {
baseConfig,
resolvePath,
};
7. webpack.deve.js
- webpack开发环境配置
const EslintWebpackPlugin = require('eslint-webpack-plugin');
const { merge } = require('webpack-merge');
const { resolvePath, baseConfig } = require('./webpack.base');
// react热更新插件
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin');
// 进度条美化
const WebpackBarPlugin = require('webpackbar');
// 隐藏控制台无用打印信息
const FriendlyErrorsWebpackPlugin = require('friendly-errors-webpack-plugin');
// 开发环境配置
module.exports = merge(baseConfig, {
mode: 'development',
stats: 'errors-only', // 控制台只显示错误信息
devtool: 'cheap-module-source-map', // 此选项控制是否生成,以及如何生成 source map。
devServer: {
hot: true, // 开启HMR热模块替换
open: true, // 自动打开网页
compress: true, // 开启gzig压缩
historyApiFallback: true, // 解决history模式下重定向问题,找不到请求路径时重定向到index.html文件
host: 'localhost', // 指定主机地址
port: 4000, // 指定端口号
proxy: {
'/api': {
target: 'http://localhost:8888', // 需要转发的目标地址
changeOrigin: true, // 覆盖请求主机来源
pathRewrite: { '^/api': '' }, // 重写路径
},
},
client: {
progress: true, // 浏览器中以百分比显示进度
},
},
plugins: [
new EslintWebpackPlugin({
context: resolvePath('../src'), // 指定文件根目录的字符串
exclude: 'node_modules', // 排除node_modules文件夹
cache: true, // 开启缓存
cacheLocation: resolvePath('../node_modules/.cache/.eslintcache'), // 缓存文件地址
}),
new ReactRefreshWebpackPlugin(), // react热更新插件
new WebpackBarPlugin({
color: '#85d3d1', // 默认green,进度条颜色支持HEX
basic: false, // 默认true,启用一个简单的日志报告器
profile: false, // 默认false,启用探查器。
}),
new FriendlyErrorsWebpackPlugin({
// 成功的时候输出
compilationSuccessInfo: {
messages: [`Your application is running here: localhost:4000`],
},
// 是否每次都清空控制台
clearConsole: true,
}),
],
});
8. webpack.prod.js
- webpack生产环境配置
const CopyWebpackPlugin = require('copy-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const { merge } = require('webpack-merge');
const { resolvePath, baseConfig } = require('./webpack.base');
// 生产环境配置
module.exports = merge(baseConfig, {
mode: 'production',
devtool: 'source-map', // 此选项控制是否生成,以及如何生成 source map。
plugins: [
new MiniCssExtractPlugin({
filename: 'static/css/[name].[contenthash:10].css',
}),
new CopyWebpackPlugin({
patterns: [
{
from: resolvePath('../public'), // copy来源文件夹
to: resolvePath('../dist'), // copy目的文件夹
globOptions: {
ignore: ['**/index.html'], // 排除index.html文件 (已经通过html-webpack-plugin处理)
},
},
],
}),
],
});
9. src/main.js
- 入口文件
import React from 'react';
import { createRoot } from 'react-dom/client';
import { BrowserRouter } from 'react-router-dom';
import App from './App';
const root = createRoot(document.getElementById('app'));
root.render(
<BrowserRouter>
<App />
</BrowserRouter>
);
10. src/App.js
- 入口文件
import React, { lazy, Suspense } from 'react';
import { Link, Routes, Route } from 'react-router-dom';
// 使用lazy动态加载组件
const Home = lazy(() => import('./pages/Home'));
const About = lazy(() => import('./pages/About'));
const App = () => {
return (
<>
<Link to="./home">home</Link>
<Link to="./about">about</Link>
<img src={require('./assets/hero.jpg')} width={500} height={300}></img>
<Suspense fallback={<>加载中...</>}>
<Routes>
<Route path="/home" element={<Home></Home>}></Route>
<Route path="/about" element={<About></About>}></Route>
</Routes>
</Suspense>
</>
);
};
export default App;