webpack5 + react 搭建项目脚手架
文章转载至:webpack5 + react 搭建项目脚手架,下面正式开始脚手架的搭建。
首先看一下目前我这边搭建好的一个目录
下面就按照这个一步一步的来搭建一下吧,
一、基础搭建
如上图所示, 初始化项目和安装react以后,我们打开vscode 开始搭建项目
初始搭建的代码这边我简单贴一下
如图webpack的基本配置已经OK,代码为:
// webpack.config.base.js
const path = require("path");
// 根据相对路径获取绝对路径
const resolvePath = (relativePath) => path.resolve(__dirname, relativePath);
// 基础配置
const baseConfig = {
// 入口文件
entry: resolvePath("../src/index.tsx"),
// 出口文件
output: {
path: resolvePath("../dist"),
filename: "[name].bundle.js",
},
};
module.exports = {
baseConfig,
};
复制代码
然后对应的开发环境和生产环境的webpack配置代码为:
// webpack.config.dev.js
// 合并规则
const { merge } = require("webpack-merge");
// 导入基础配置
const { baseConfig } = require("./webpack.config.base");
module.exports = merge(baseConfig, {
// 环境设置:开发环境
mode: "development",
});
复制代码
// webpack.config.prod.js
// 合并规则
const { merge } = require("webpack-merge");
// 导入基础配置
const { baseConfig } = require("./webpack.config.base");
module.exports = merge(baseConfig, {
// 环境配置:生产环境
mode: "production"
});
复制代码
// webpack的入口文件:src/index.tsx
import React from "react";
import ReactDOM from "react-dom";
import "./index.less";
import logo from "./assets/logo.svg";
function App() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<h1> Webpack V5 + React </h1>
</header>
</div>
);
}
ReactDOM.render(<App />, document.getElementById("root"));
复制代码
// less文件也贴一下吧:src/index.less
.App {
text-align: center;
.App-logo {
width: 30vw;
pointer-events: none;
margin: 100px auto 10px;
}
}
@media (prefers-reduced-motion: no-preference) {
.App-logo {
animation: App-logo-spin infinite 20s linear;
}
}
@keyframes App-logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
复制代码
// html模板文件 public/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- 配置title的导入 -->
<title><%=htmlWebpackPlugin.options.title %></title>
</head>
<body>
<div id="root"></div>
</body>
</html>
复制代码
基本模板搭建搭建完以后,开始安装需要的包
// 1、webpack的安装
yarn add webpack webpack-cli -D
yarn add webpack webpack-cli -g
// 2、把打包的文件配置到html模板
yarn add html-webpack-plugin -D
// 3、入口文件中涉及到了 less 的文件 需要增加配置
yarn add style-loader css-loader less less-loader -D
// 4、入口文件中涉及到了 ts 的文件 需要增加的babel配置
yarn add babel-loader @babel/core @babel/preset-env @babel/preset-react -D
// 5、入口文件还涉及 svg 图片,这里不用增加配置文件,webpack 已经内置了他的处理
复制代码
安装完需要配置到基础配置里面
// webpack.config.base.js
const path = require("path");
// 根据相对路径获取绝对路径
const resolvePath = (relativePath) => path.resolve(__dirname, relativePath);
// HTML模板
const HtmlWebpackPlugin = require("html-webpack-plugin");
// 基础配置
const baseConfig = {
// 入口文件
entry: resolvePath("../src/index.tsx"),
// 出口文件
output: {
path: resolvePath("../dist"),
filename: "[name].bundle.js",
},
// 所有loader的配置都在 module.rules 中
module: {
rules: [
// 对css文件的处理
// use里的loader如果有多个的情况下,切记执行顺序是:从下到上(或者从右到左)
{
test: /.css$/,
use: ["style-loader", "css-loader"],
},
// 对less文件的处理
{
test: /.less$/,
use: [
"style-loader",
"css-loader",
"less-loader",
],
},
// 对ts|tsx文件的处理
{
test: /.(ts|tsx)$/,
use: "babel-loader",
},
// 对图片的处理
{
test: /.(svg|png|jpg|gif)$/,
type: "asset/resource",
},
],
},
// 插件的处理
plugins: [
new HtmlWebpackPlugin({
// title 配置
title: "Webpack V5 + React",
// 模板导入
template: resolvePath("../public/index.html"),
// 名称为
filename: "index.html",
}),
],
};
module.exports = {
baseConfig,
};
复制代码
同时还需要增加babel的配置
// babel.config.json
{
"presets": ["@babel/preset-react", "@babel/preset-env"]
}
复制代码
之后配置一下webpack的启动命令: build 和 serve
// package.json
{
"name": "webpack5-react",
"version": "1.0.0",
"description": "weback V5 + react ",
"main": "index.js",
"scripts": {
"test": "echo "Error: no test specified" && exit 1",
"build": "webpack --config ./webpack/webpack.config.prod.js",
"serve": "webpack-dev-server --config ./webpack/webpack.config.dev.js"
},
"author": "ndz",
"license": "MIT",
"dependencies": {
"react": "^18.0.0",
"react-dom": "^18.0.0"
},
"devDependencies": {
"html-webpack-plugin": "^5.5.0",
"webpack": "^5.70.0",
"webpack-cli": "^4.9.2"
}
}
复制代码
这里需serve命令时 咱们需要起一个服务需要安装:webpack-dev-server
// 安装webpack服务
yarn add webpack-dev-server -D
// 刚才还漏了一个配置合并的插件
yarn add webpack-merge -D
复制代码
安装完以后的配置如下
// 合并规则
const { merge } = require("webpack-merge");
// 导入基础配置
const { baseConfig } = require("./webpack.config.base");
module.exports = merge(baseConfig, {
// 环境设置:开发环境
mode: "development",
// 便于开发调试 这里开启source-map模式
devtool: "source-map",
// webpack-dev-server 的一下配置,webpack-dev-server 会提供一个本地服务(serve)
devServer: {
// host
host: "127.0.0.1",
// 端口
port: 8000,
// 热更新
hot: true,
// 启动时打开浏览器
open: true,
},
});
复制代码
然后启动看一下:
// 启动项目
yarn serve
复制代码
我们可以发现目前所有配置OK 项目启动成功了。
再看看打包成功与否。
//打包
yarn build
复制代码
可以看到打包完成以后,咱们的文件目录 dist 已经生成了。
二、优化
接下咱们将针对打包做一下优化
现在咱们的js和css文件是被统一打包在一个文件(dist/main.bundle.js)里面,如图
那么现在咱们需要单独对css做一些处理,首先安装需要的插件
// css分包处理
yarn add mini-css-extract-plugin -D
// css压缩处理
yarn add css-minimizer-webpack-plugin -D
// 进一步处理css文件,比如添加浏览器前缀等
yarn add postcss-loader autoprefixer -D
// 统一安装
yarn add mini-css-extract-plugin css-minimizer-webpack-plugin postcss-loader autoprefixer -D
复制代码
安装完以后的配置如下:
// 首先是基础配置
const path = require("path");
// 根据相对路径获取绝对路径
const resolvePath = (relativePath) => path.resolve(__dirname, relativePath);
// HTML模板
const HtmlWebpackPlugin = require("html-webpack-plugin");
// css 代码打包分离
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
// 基础配置
const baseConfig = {
// 入口文件
entry: resolvePath("../src/index.tsx"),
// 出口文件
output: {
path: resolvePath("../dist"),
filename: "[name].bundle.js",
},
// 所有loader的配置都在 module.rules 中
module: {
rules: [
// 对css文件的处理
// use里的loader如果有多个的情况下,切记执行顺序是:从下到上(或者从右到左)
// MiniCssExtractPlugin插件和style-loader冲突,所以这里用MiniCssExtractPlugin插件替换了style-loader
{
test: /.css$/,
use: [MiniCssExtractPlugin.loader, "css-loader", "postcss-loader"],
},
// 对less文件的处理
{
test: /.less$/,
use: [
MiniCssExtractPlugin.loader,
"css-loader",
"postcss-loader",
"less-loader",
],
},
// 对ts|tsx文件的处理
{
test: /.(ts|tsx)$/,
use: "babel-loader",
},
// 对图片的处理
{
test: /.(svg|png|jpg|gif)$/,
type: "asset/resource",
},
],
},
// 插件的处理
plugins: [
new HtmlWebpackPlugin({
// title 配置
title: "Webpack V5 + React",
// 模板导入
template: resolvePath("../public/index.html"),
// 名称为
filename: "index.html",
}),
new MiniCssExtractPlugin({
filename: `[name].[hash:8].css`,
}),
],
};
module.exports = {
baseConfig,
};
复制代码
这里需要注意的是 postcss-loader 需要添加配置文件
// postcss.config.js
// postcss配置文件
module.exports = {
plugins: {
autoprefixer: require("autoprefixer"),
},
};
复制代码
然后css文件的压缩只需要在生产环境配置
// webpack.config.prod.js
// 合并规则
const { merge } = require("webpack-merge");
// 压缩css文件
const CssMinimizerWebpackPlugin = require("css-minimizer-webpack-plugin");
// 导入基础配置
const { baseConfig } = require("./webpack.config.base");
module.exports = merge(baseConfig, {
// 环境配置:生产环境
mode: "production",
plugins:[
new CssMinimizerWebpackPlugin()
]
});
复制代码
然后build看一下效果
可以看到 css 文件已经被单独抽取出来了
接下来再做两个配置,一个是错误提示,另一个是清理上一次的打包文件
// 错误提示
yarn add friendly-errors-webpack-plugin -D
// 清理上一次的打包
yarn add clean-webpack-plugin -D
复制代码
错误提示我们是添加在开发环境的配置中
// webpack.config.dev.js
// 合并规则
const { merge } = require("webpack-merge");
// 错误提示插件
const FriendlyErrorsWebpackPlugin = require("friendly-errors-webpack-plugin");
// 导入基础配置
const { baseConfig } = require("./webpack.config.base");
module.exports = merge(baseConfig, {
// 环境设置:开发环境
mode: "development",
// 便于开发调试 这里开启source-map模式
devtool: "source-map",
// webpack-dev-server 的一下配置,webpack-dev-server 会提供一个本地服务(serve)
devServer: {
// host
host: "127.0.0.1",
// 端口
port: 8000,
// 热更新
hot: true,
// 启动时打开浏览器
open: true,
},
// 插件配置
plugins: [
new FriendlyErrorsWebpackPlugin(),
],
});
复制代码
清理上一次的打包文件是配置在生产环境中
// webpack.config.prod.js
// 合并规则
const { merge } = require("webpack-merge");
// 清理原来的打包文件
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
// 压缩css文件
const CssMinimizerWebpackPlugin = require("css-minimizer-webpack-plugin");
// 导入基础配置
const { baseConfig } = require("./webpack.config.base");
module.exports = merge(baseConfig, {
// 环境配置:生产环境
mode: "production",
plugins:[
new CleanWebpackPlugin(),
new CssMinimizerWebpackPlugin()
]
});
复制代码
到此咱们的基本配置和优化就算完成了。
但......但是,对于我这种有追求的人怎么可能少了代码检查呢?!,所以接下来 咱们继续搞Eslint 的配置。
三、Eslint 配置
这里我就把需要安装的都全部列出来 然后单个解释一下,然后贴配置代码
// webpack5 弃用了eslint-loader 支持了eslint-webpack-plugin
yarn add -D eslint-webpack-plugin
// eslint 和 prettier 结合校验
yarn add -D eslint prettier prettier-eslint eslint-config-prettier eslint-plugin-prettier
// 一个可扩展的共享配置
yarn add -D eslint-config-airbnb-base
// 用于react的eslint规则
yarn add -D eslint-plugin-react
// typescript相关规则 详细说明:https://www.npmjs.com/package/@typescript-eslint/parser
yarn add -D typescript @typescript-eslint/parser @typescript-eslint/eslint-plugin
// 添加一些.eslintrc文件的扩展特性
yarn add -D standard eslint-plugin-promise eslint-plugin-node eslint-plugin-import eslint-plugin-standard eslint-config-standard
复制代码
安装完以后配置如下
// 合并规则
const { merge } = require("webpack-merge");
// 错误提示插件
const FriendlyErrorsWebpackPlugin = require("friendly-errors-webpack-plugin");
// eslint插件
const ESLintPlugin = require("eslint-webpack-plugin");
// 导入基础配置
const { baseConfig } = require("./webpack.config.base");
module.exports = merge(baseConfig, {
// 环境设置:开发环境
mode: "development",
// 便于开发调试 这里开启source-map模式
devtool: 'source-map',
// webpack-dev-server 的一下配置,webpack-dev-server 会提供一个本地服务(serve)
devServer: {
// host
host: '127.0.0.1',
// 端口
port: 8000,
// 热更新
hot: true,
// 启动时打开浏览器
open: true,
},
// 插件配置
plugins: [
new FriendlyErrorsWebpackPlugin(),
new ESLintPlugin({
fix: true,
extensions: ["js", "ts", "tsx", "json"],
exclude: "/node_modules/",
})
],
});
复制代码
.eslintrc.js文件 的配置如下
// 增加.eslintrc.js文件
module.exports = {
// eslint的配置主要走的是:typescript-eslint
// 详细内容请参阅:https://typescript-eslint.io/
parser: "@typescript-eslint/parser",
// 可共享的配置 是一个npm包,输出的是一个配置对象。
extends: [
"standard",
"eslint:recommended",
"plugin:react/recommended",
"plugin:@typescript-eslint/recommended",
"plugin:prettier/recommended",
"plugin:import/typescript",
],
// 指定脚本的运行环境。每种环境都有一组特定的预约义全局变量。
env: {
browser: true,
node: true,
es6: true,
mocha: true,
jest: true,
jasmine: true,
},
// 输出的规则
plugins: ["react", "prettier", "@typescript-eslint"],
// 为特定类型的文件(ts、tsx)指定处理器。
overrides: [
{
files: ["*.ts", "*.tsx"],
rules: {
"@typescript-eslint/no-unused-vars": [1, { args: "none" }],
},
},
],
// 规则集,会覆盖extends中的规则
rules: {
// 语句强制分号结尾
semi: [2, "always"],
// 布尔值类型的 propTypes 的 name 必须为 is 或 has 开头 (off 不强制要求写 propTypes)
"react/boolean-prop-naming": "off",
// 一个 defaultProps 必须有对应的 propTypes ()
"react/default-props-match-prop-types": "off",
// 组件必须有 displayName 属性 (off 不强制要求写 displayName)
"react/display-name": "off",
// 禁止在自定义组件中使用一些指定的 props (off 没必要限制)
"react/forbid-component-props": "off",
// 禁止使用一些指定的 elements (off 没必要限制)
"react/forbid-elements": "off",
// 禁止使用一些指定的 propTypes (off 不强制要求写 propTypes)
"react/forbid-prop-types": "off",
// 禁止直接使用别的组建的 propTypes (off 不强制要求写 propTypes)
"react/forbid-foreign-prop-types": "off",
// 禁止使用数组的 index 作为 key (off 不强制要求 太严格了!)
"react/no-array-index-key": "off",
// note you must disable the base rule as it can report incorrect errors
"no-use-before-define": "off",
"@typescript-eslint/no-use-before-define": ["off"],
"@typescript-eslint/no-var-requires": 0,
// 禁止使用 children 做 props
"react/no-children-prop": "error",
// 禁止使用 dangerouslySetInnerHTML (off 没必要限制)
"react/no-danger": "off",
// 禁止在使用了 dangerouslySetInnerHTML 的组建内添加 children
"react/no-danger-with-children": "error",
// 禁止使用已废弃的 api
"react/no-deprecated": "error",
// 禁止在 componentDidMount 里面使用 setState (off 同构应用需要在 didMount 里写 setState)
"react/no-did-mount-set-state": "off",
// 禁止在 componentDidUpdate 里面使用 setState
"react/no-did-update-set-state": "error",
// 禁止直接修改 this.state
"react/no-direct-mutation-state": "error",
// 禁止使用 findDOMNode
"react/no-find-dom-node": "error",
// 禁止使用 isMounted
"react/no-is-mounted": "error",
// 禁止在一个文件创建两个组件
"react/no-multi-comp": "off",
// 禁止在 PureComponent 中使用 shouldComponentUpdate
"react/no-redundant-should-component-update": "error",
// 禁止使用 ReactDOM.render 的返回值
"react/no-render-return-value": "error",
// 禁止使用 setState
"react/no-set-state": "off",
// 禁止拼写错误
"react/no-typos": "error",
// 禁止使用字符串 ref
"react/no-string-refs": "error",
// 禁止在组件的内部存在未转义的 >, ", ' 或 }
"react/no-unescaped-entities": "error",
// @fixable 禁止出现 HTML 中的属性,如 class
"react/no-unknown-property": "error",
// 禁止出现未使用的 propTypes
"react/no-unused-prop-types": "off",
// 定义过的 state 必须使用
"react/no-unused-state": "off",
// 禁止在 componentWillUpdate 中使用 setState
"react/no-will-update-set-state": "error",
// 必须使用 Class 的形式创建组件
"react/prefer-es6-class": ["error", "always"],
// 必须使用 pure function
"react/prefer-stateless-function": "off",
// 组件必须写 propTypes
"react/prop-types": "off",
// 出现 jsx 的地方必须 import React
"react/react-in-jsx-scope": "off",
// 非 required 的 prop 必须有 defaultProps
"react/require-default-props": "off",
// 组件必须有 shouldComponentUpdate
"react/require-optimization": "off",
// render 方法中必须有返回值
"react/require-render-return": "error",
// @fixable 组件内没有 children 时,必须使用自闭和写法
"react/self-closing-comp": "off",
// @fixable 组件内方法必须按照一定规则排序
"react/sort-comp": "off",
// propTypes 的熟悉必须按照字母排序
"react/sort-prop-types": "off",
// HTML 中的自闭和标签禁止有 children
"react/void-dom-elements-no-children": "error",
// @fixable 布尔值的属性必须显式的写 someprop={true}
"react/jsx-boolean-value": "off",
// @fixable 自闭和标签的反尖括号必须与尖括号的那一行对齐
"react/jsx-closing-bracket-location": [
"error",
{
nonEmpty: false,
selfClosing: "line-aligned",
},
],
// @fixable 结束标签必须与开始标签的那一行对齐
"react/jsx-closing-tag-location": "off",
// @fixable 大括号内前后禁止有空格
"react/jsx-curly-spacing": [
"error",
{
when: "never",
attributes: {
allowMultiline: true,
},
children: true,
spacing: {
objectLiterals: "never",
},
},
],
// @fixable props 与 value 之间的等号前后禁止有空格
"react/jsx-equals-spacing": ["error", "never"],
// 限制文件后缀
"react/jsx-filename-extension": "off",
// @fixable 第一个 prop 必须得换行
"react/jsx-first-prop-new-line": "off",
// handler 的名称必须是 onXXX 或 handleXXX
"react/jsx-handler-names": "off",
// 数组中的 jsx 必须有 key
"react/jsx-key": "error",
// @fixable 限制每行的 props 数量
"react/jsx-max-props-per-line": "off",
// jsx 中禁止使用 bind
"react/jsx-no-bind": "off",
// 禁止在 jsx 中使用像注释的字符串
"react/jsx-no-comment-textnodes": "error",
// 禁止出现重复的 props
"react/jsx-no-duplicate-props": "error",
// 禁止在 jsx 中出现字符串
"react/jsx-no-literals": "off",
// 禁止使用 target="_blank"
"react/jsx-no-target-blank": "off",
// 禁止使用未定义的 jsx elemet
"react/jsx-no-undef": "error",
// 禁止使用 pascal 写法的 jsx,比如 <TEST_COMPONENT>
"react/jsx-pascal-case": "error",
// @fixable props 必须排好序
"react/jsx-sort-props": "off",
// @fixable jsx 的开始和闭合处禁止有空格
"react/jsx-tag-spacing": [
"error",
{
closingSlash: "never",
beforeSelfClosing: "always",
afterOpening: "never",
},
],
// jsx 文件必须 import React
"react/jsx-uses-react": "error",
// 定义了的 jsx element 必须使用
"react/jsx-uses-vars": "error",
// @fixable 多行的 jsx 必须有括号包起来
"react/jsx-wrap-multilines": "off",
// 消除未使用的变量,函数和函数的参数。
"no-unused-vars": "off",
// jsdoc语法检查
"valid-jsdoc": [
"error",
{
requireReturn: false,
},
],
},
// 添加共享设置
settings: {
react: {
version: "detect",
},
polyfills: ["fetch", "promises", "url"],
},
};
复制代码
// 增加 eslint 需要忽略的校验文件:.eslintignore
.eslintrc.js
node_modules
dist
复制代码
配置完启动项目,发现确实报了很多错误,咱们恢复一下,如图:
这边我是对 vscode 做了配置,结合项目可以做到 保存(Ctrl + S)自动格式化。 关于vscode的配置如下
四、VS Code 配置
首先在vscode中安装Eslint 插件
然后打开配置文件(VSCode => 首选项 => 设置 => 用户设置 => 打开settings.json)做如下配置:
//对 VSCode settings.json 文件的配置
// 保存时开启自动修复 默认只支持 .js 文件
"eslint.autoFixOnSave": true,
// 为以下的文件类型开启保存自动修复
"eslint.validate": [
"javascriptreact",
"javascript",
"typescript",
"html",
"vue",
{
"language": "html",
"autoFix": true
},
{
"language": "vue",
"autoFix": true
},
{
"language": "ts",
"autoFix": true
},
{
"language": "tsx",
"autoFix": true
},
],
复制代码
到此配置完毕。之后如有更新继续加上
后续补充
一、Resolve 配置
这边在之后的开发中涉及到ts和tsx 文件,webpack默认只会查找js和json,因此不加后缀的情况下是搜索不到的。还有在搜索模块的时候,导入路劲太费劲,所以也可以加一个映射:@ 。
//对 webpack.config.base.js 增加 resolve 配置
......
// Resolve 配置 Webpack 如何寻找模块所对应的文件
resolve: {
// 在导入语句没带文件后缀时,Webpack 会自动带上后缀后去尝试访问文件是否存在。 resolve.extensions用于配置在尝试过程中用到的后缀列表,默认是:js 和 json
extensions: [".js", ".ts", ".tsx"],
// 配置项通过别名来把原导入路径映射成一个新的导入路径
alias: {
"@": resolvePath("../src"),
},
},
......
复制代码
项目地址:Github