项目及工程化配置以及主要功能配置、规范化管理和使用细节应该都没啥问题了。
后面若发现构建及配置问题、以及可优化的细节部分、包括功能的进一步迭代也会使项目不断更新完善的~
目前主要是做了规范及工程化的配置,后期打算开发并开源一个react-admin
标准后台管理系统,用于实践并巩固一些前端技术框架与组件开发等。
最简单的demo运行效果
为什么要自己搭建一个webpack-react-ts
的标准化项目?
在很早之前的公司时,项目实际开发过程中产生过这种需求:
每次开发新项目时希望有个完整功能、配置好用、标准化规范的模板可以快速使用
完整功能:如果是使用create-react-app
创建项目,属于最简易版的一个实现了,各种loader
、plugin
、babel
等webpack
配置缺失,作为现代前端开发不可或缺的构建工具,其对项目的大部分功能支持是必须的。
配置好用:webpack
对各种插件的配置与集成,基本路由模块、页面模块、组件模块、store状态管理模块等基本常用功能的简要示例及demo,要有一定的覆盖率。
标准规范:这个可能是工程化构建最重要的部分之一了,eslint
、prettier
、stylelint
、commitlint
等lint规范和格式化以及test测试,还有目录结构规范、命名规范等。对于规范化的意义其重要性不言而喻,可读性、可维护性、开发效率、bug率等可见一斑
当然网上肯定有很多优秀的轮子供我们使用,他们有的功能更全、有的细节更好,有的示例更完整。这都是我们学习的榜样,对于其源码阅读和学习也是极为有益的,不过,始终避免不了一点,看的再多都不如自己实践来的有效果,所以,这也是我考虑的原因之一,为了自己实践和操作来提升技术熟练度。
这也是源于在实际开发过程中的需求,即,不同技术平台及场景适应于不同的技术框架和路线。
举个栗子,
管理后台系统:这个多适用于PC端,使用单页面应用开发,主流技术框架 React
、Vue
、Angular
等
网站类:这类项目一般追求美观和SEO搜索的重要性,而不会像管理后台系统一样有很复杂的逻辑,更多的是一些动效及UI交互,因为SPA
的首屏加载及SEO
问题,社区有成熟的服务端渲染SSR
方案:next
、nuxt
,分别对应react
和vue
技术栈,对于习惯了单页面开发的同学还是很友好的。
移动端:轻量化、轻量化、轻量化,重要的事情说三遍。手机端流量第一,项目越小越轻量性能越好加载也就越快,对于用户体验也就越好。比较手机端一般对应的都是C端用户,不像管理后台主要是给B端及公司内部使用。用户体验要求是完全不一样的。如,移动端版react
——preact
,又如svelte
、solidjs
等无虚拟DOM
框架,减少大量运行时代码。
所以,我认为每个技术都有其对应的优势和适合的场景,单一框架并不是完全适合梭哈到底的。
这也是我开发的node-cli
自动化初始化项目的一个框架模板之一。
接下来对各个不同技术方案及最佳实践也会考虑构建具有实战意义的模板以供使用
主要方向大概分为:webpack+react/vue+typescript
等webpack
方向、vite+react/vue+typescript
等vite
方向、nuxt/next/egg/koa
等服务端渲染SSR
方向、svelte/solidjs
等无虚拟DOM
的轻量级框架方向、rollup/parcel
等构建工具方向、esbuild/swc
等新型编译器与构建工具的结合使用等、以及小程序/跨平台等。
有点飘啊,想法还挺多的~
Webpack5.x
+ React17.x
+ Antd4.x
+ TypeScript4.x
+ Less
Eslint
+ Stylelint
+ Prettier
+ Commitlint
ES6
、JSX
,Babel
支持HMR
热更新Antd
按需加载、自定义主题、css module
js
、css
压缩、chunk
拆分、Gzip
dependencies
"dependencies": {
"antd": "^4.16.10", // 懂得都懂
"axios": "^0.21.1", // 懂得都懂
"clsx": "^1.1.1", // 条件处理 React className 类名
"core-js": "3", // 现代JS语法polyfills库,用于兼容浏览器及ES6、ES7语法
"dayjs": "^1.10.6", // 日期处理库,比moment小很多,只有几kb
"history": "^5.0.1", // H5 history库
"lodash": "^4.17.21", // 非常全面的方法库
"react": "^17.0.2", // 懂得都懂
"react-dom": "^17.0.2", // 懂得都懂
"react-helmet-async": "^1.0.9", // 在react中优雅的添加 HTML header 各种属性
"react-router-dom": "^5.2.0" // react 路由库,经典 react 三件套
}
devDependencies
"devDependencies": {
// babel
"@babel/cli": "^7.14.8", // 可以让babel以cli的方式执行 如:babel src --out-dir dist --watch
"@babel/core": "^7.15.0", // babel 核心包
"@babel/plugin-proposal-class-properties": "^7.14.5", // @babel/preset-env 插件已包含
"@babel/plugin-syntax-dynamic-import": "^7.8.3", // 动态导入、懒加载
"@babel/plugin-transform-modules-commonjs": "^7.15.0", // 转化成CommonJS 规范的代码
"@babel/plugin-transform-react-constant-elements": "^7.14.5", // React 常量元素转换器 : 它会寻找不随 props 改变的所有静态元素,并将它们从渲染方法(或者无状态函数式组件)中抽离出来,以避免多余地调用 createElement
"@babel/plugin-transform-react-inline-elements": "^7.14.5", // React 行内元素转换器 : 它会将所有 JSX 声明(或 者 createElement 调用)替换成优化过的版本,以便代码可以更快执行
"@babel/plugin-transform-runtime": "^7.15.0", // 抽离提取 Babel的注入代码,防止重复加载,减小体积
"@babel/preset-env": "^7.15.0", // 提供的预设,允许我们使用最新的JavaScript
"@babel/preset-react": "^7.14.5", // react 支持
"@babel/preset-typescript": "^7.15.0", // typescript 支持
"babel-plugin-dynamic-import-node": "^2.3.3", // 为node提供加载转换 import => require
"babel-plugin-import": "^1.13.3", // 按需引入、加载
"babel-plugin-lodash": "^3.3.4", // 按需加载
"babel-plugin-transform-react-remove-prop-types": "^0.4.24", // 从生产生成中删除不必要的类型
// commitlint 是 git commit 执行规则相关插件
"@commitlint/cli": "^13.1.0",
"@commitlint/config-conventional": "^13.1.0",
// react hot 配合webpack实现热更新插件
"react-refresh": "^0.10.0",
"@pmmmwh/react-refresh-webpack-plugin": "^0.4.3",
// @types 开头的是对应包的 TypeScript 类型声明
"@types/enzyme": "^3.10.9",
"@types/enzyme-adapter-react-16": "^1.0.6",
"@types/enzyme-to-json": "^1.5.4",
"@types/react-dom": "^17.0.9",
"@types/react-helmet": "^6.1.2",
"@types/react-router-dom": "^5.1.8",
"@types/webpack-env": "^1.16.2",
// enzyme 测试库
"enzyme": "^3.11.0",
"enzyme-adapter-react-16": "^1.15.6",
"enzyme-to-json": "^3.6.2",
// eslint
"eslint": "^7.32.0",
"eslint-config-airbnb-typescript": "^12.3.1", // airbnb 规范
"eslint-config-prettier": "^8.3.0", // 关闭所有不必要或可能与[Prettier]冲突的规则。
"eslint-import-resolver-typescript": "^2.4.0", // 添加 ts 语法支持 eslint-plugin-import
"eslint-import-resolver-webpack": "^0.13.1", // 支持 eslint-plugin-import 读写模块解析
"eslint-plugin-import": "^2.23.4", // ES6+ import/export 语法支持
"eslint-plugin-jsx-a11y": "^6.4.1", // JSX元素的可访问性规则的静态AST检查器
"eslint-plugin-prettier": "^3.4.0", // 以 eslint的规则运行 prettier 格式化
"eslint-plugin-react": "^7.24.0", // react 相关规则
"eslint-plugin-react-hooks": "^4.2.0", // react-hooks 相关规则
"eslint-plugin-redux-saga": "^1.2.1", // redux-saga 相关规则
"@typescript-eslint/eslint-plugin": "^4.29.0", // 使 eslint 支持 typescript,.eslintrc.js 的 plugins 参数
"@typescript-eslint/parser": "^4.29.0", // 使 eslint 支持 typescript ,.eslintrc.js 的 parser 参数
// webpack
"webpack": "^5.49.0",
"webpack-bundle-analyzer": "^4.4.2", // 包依赖分析 可视化
"webpack-cli": "^4.7.2", // cli
"webpack-dev-middleware": "^5.0.0", // 中间件,可配合 express以服务的方式开发使用
"webpack-dev-server": "^3.11.2", // dev-server
"webpack-hot-middleware": "^2.25.0", // 热加载
"webpack-pwa-manifest": "^4.3.0", // 生成 pwa 相关配置
// webpack loader:解析对应文件
"babel-loader": "^8.2.2",
"style-loader": "^3.2.1", // 添加 css 到 HTML
"css-loader": "^6.2.0", // css加载器 处理 @import/url()
"postcss-loader": "^6.1.1", // 处理 css
"less-loader": "^10.0.1", // less => css
"file-loader": "^6.2.0", // 通过 import/require() 加载的图片等解析为 url
"html-loader": "^2.1.2", // 压缩HTML
"svg-url-loader": "^7.1.1",
"url-loader": "^4.1.1",
// webpack plugin
"html-webpack-plugin": "^5.3.2", // 简化HTML文件的创建 ,配合webpack包含hash的bundle使用
"mini-css-extract-plugin": "^2.2.0", // css 压缩
"terser-webpack-plugin": "^5.1.4", // 使用 terser 压缩 js (terser 是一个管理和压缩 ES6+ 的工具)
"clean-webpack-plugin": "^4.0.0-alpha.0", // 用于删除/清理生成的 build 文件
"compression-webpack-plugin": "^8.0.1", // Gzip压缩
// prettier 格式化
"prettier": "^2.3.2",
"pretty-quick": "^3.1.1", // 在更改的文件上运行 prettier
// stylelint css样式规范
"stylelint": "^13.13.1",
"stylelint-config-recess-order": "^2.4.0", // 按照session和Bootstrap的方式对CSS属性进行排序。
"stylelint-config-standard": "^22.0.0", // 基本规范
// 工具
"husky": "^7.0.1", // 自动配置 Git hooks 钩子
"lint-staged": "^11.1.2", // 对暂存的git文件运行linter
"rimraf": "^3.0.2", // 删除cli,兼容不同平台
"yargs": "^17.1.0", // 读取命令参数
// 其他
"typescript": "^4.3.5",
"less": "^4.1.1", // less 的解析库
"postcss": "^8.3.6", // 专门处理样式的工具
"postcss-nested": "^5.0.6", // 解析处理嵌套规则
"autoprefixer": "^10.3.1", // 自动生成各浏览器前缀 postcss 的一个插件
"serve": "^12.0.0", // 本地启动一个服务,可以查看静态文件
}
├── dist // 默认的 build 输出目录
├── .husky // pre-commit hook
├── config // 全局配置文件及webpack配置文件
├── public // 静态文件
├── test // 测试目录
└── src // 源码目录
├── assets // 公共的文件(如image、css、font等)
├── components // 项目组件
├── constants // 常量/接口地址等
├── layout // 全局布局
├── routes // 路由
├── store // 状态管理器
├── utils // 工具库
├── pages // 页面模块
├── Home // Home模块,建议组件统一大写开头
├── ...
├── App.tsx // react顶层文件
├── main.ts // 项目入口文件
├── typing.d.ts // ts类型文件
├── .editorconfig // IDE格式规范
├── .eslintignore // eslint忽略
├── .eslintrc // eslint配置文件
├── .gitignore // git忽略
├── .npmrc // npm配置文件
├── .prettierignore // prettierc忽略
├── .prettierrc // prettierc配置文件
├── .stylelintignore // stylelint忽略
├── .stylelintrc // stylelint配置文件
├── babel.config.js // babel配置文件
├── commitlint.config.js // commit配置文件
├── LICENSE.md // LICENSE
├── package.json // package
├── postcss.config.js // postcss
├── README.md // README
├── setupEnzyme.ts // Enzyme测试配置文件
├── tsconfig.json // typescript配置文件
使用 npx create-react-app xxx --typescript
可以快速创建 TS 项目,我们可以基于创建完的项目再进行一些自定义配置。
这里我们使用 babel
处理,当然对于 TypeScript
也可以使用 ts-loader
解析,这里就不做演示了,网上也有很多对比测试
babel
相关依赖: yarn add @babel/preset-env -D
.babelrc.js
中添加预设module.exports = {
presets: [
[
'@babel/preset-env',
{
// 这里可以配置相关浏览器兼容规则,暂时我们先不管
// corejs: 3,
// debug: true,
// useBuiltIns: 'usage', // 开启浏览器兼容 polyfills,会根据browserslist配置,引入需要的库,需要安装对应版本的 core-js@3
}
]
]
}
yarn add @babel/preset-react -D
.babelrc.js
中添加预设module.exports = {
presets: ['@babel/preset-env', '@babel/preset-react']
}
yarn add typescript @babel/preset-typescript -D
.babelrc.js
中添加预设module.exports = {
presets: ['@babel/preset-env', '@babel/preset-react', '@babel/preset-typescript']
}
webpack
中配置 loader
module.exports = {
module: {
rules: [
// .ts .tsx
{
test: /\.(ts|tsx)$/,
use: 'babel-loader',
exclude: /node_modules/,
},
]
}
}
antd
作为react
的好基友,在国内后台系统开发中几乎快成为标配了,我们以antd
为例配置按需加载和主题定制化
yarn add antd
yarn add babel-plugin-import -D
.babelrc.js
中配置plugins
module.exports = {
presets: [],
plugins: [
[
'import',
{
libraryName: 'antd',
libraryDirectory: 'es',
style: true, // or 'css'
},
'antd',
]
]
}
css-loader、style-loader 也是必须的
module.exports = {
module: {
rules: [
// 处理 .css
{
test: /\.css$/,
use: ['style-loader', 'css-loader'],
},
// 处理 .less
{
test: /\.less$/,
use: [
'style-loader',
'css-loader',
// less-loader
{
loader: 'less-loader',
options: {
lessOptions: {
// 替换antd的变量,去掉 @ 符号即可
// https://ant.design/docs/react/customize-theme-cn
modifyVars: {
'primary-color': '#1DA57A',
},
javascriptEnabled: true, // 支持js
},
},
},
],
},
]
}
}
安装ts解析器:yarn add @typescript-eslint/parser -D
安装相关拓展:yarn add eslint-config-airbnb-typescript eslint-plugin-prettier -D
安装相关插件:yarn add eslint-plugin-react eslint-plugin-react-hooks eslint-plugin-jsx-a11y @typescript-eslint/eslint-plugin -D
添加 .eslintrc.js
module.exports = {
parser: '@typescript-eslint/parser',
// 使用 airbnb 拓展插件规范相关库
// prettier 已内置了许多相关插件
extends: ['airbnb-typescript', 'prettier'],
// 拓展和支持相关能力的插件库
plugins: ['prettier', 'react', 'react-hooks', 'jsx-a11y', '@typescript-eslint'],
}
yarn add eslint eslint-plugin-import eslint-import-resolver-typescript eslint-import-resolver-webpack -D
module.exports = {
parser: '@typescript-eslint/parser',
// 使用 airbnb 拓展插件规范相关库
// prettier 已内置了许多相关插件
extends: ['airbnb-typescript', 'prettier'],
// 拓展和支持相关能力的插件库
plugins: ['prettier', 'react', 'react-hooks', 'jsx-a11y', '@typescript-eslint'],
settings: {
'import/parsers': {
'@typescript-eslint/parser': ['.ts', '.tsx'],
},
'import/resolver': {
webpack: {
config: './config/webpack.base.js',
},
typescript: {
alwaysTryTypes: true, // always try to resolve types under `@types` directory even it doesn't contain any source code, like `@types/unist`
directory: './tsconfig.json',
},
},
'import/ignore': ['types'], // Weirdly eslint cannot resolve exports in types folder (try removing this later)
}
}
prettier
插件,支持以eslint
的规范进行格式化,并提供保存时存在冲突时的解决方案.prettierrc
,这里再定义下格式化基本风格yarn add prettier -D
{
"printWidth": 100,
"tabWidth": 2,
"useTabs": false,
"semi": true,
"singleQuote": true,
"trailingComma": "all",
"arrowParens": "avoid",
"endOfLine": "auto"
}
yarn add stylelint stylelint-config-recess-order stylelint-config-standard -D
.stylelintrc.js
module.exports = {
extends: ['stylelint-config-standard', 'stylelint-config-recess-order'],
};
针对以上代码规范检查,我们也应该添加对应 ignore 文件以忽略非必要的检查文件,以优化性能
如:.eslintignore
node_modules
dist
git hook
钩子的实现,这里我们使用 husky
+ lint-staged
方案
yarn add husky lint-staged -D
git commit
规范,这里我们使用 @commitlint/config-conventional
yarn add "@commitlint/cli @commitlint/config-conventional -D
这里也可以使用
pretty-quick
代替prettier
格式化:yarn add pretty-quick -D
,使得prettier
格式化更简单易用
.husky
文件夹与commitlint.config.js
文件,并在.husky
文件夹下添加 precommit
、commit-msg
、以及.gitignore
commitlint.config.js
用于配置 commitlint
相关规范
// git commit 规范
// <类型>[可选的作用域]: <描述>
// # 主要type
// feat: 增加新功能
// fix: 修复bug
// # 特殊type
// docs: 只改动了文档相关的内容
// style: 不影响代码含义的改动,例如去掉空格、改变缩进、增删分号
// build: 构造工具的或者外部依赖的改动,例如webpack,npm
// refactor: 代码重构时使用
// revert: 执行git revert打印的message
// # 暂不使用type
// test: 添加测试或者修改现有测试
// perf: 提高性能的改动
// ci: 与CI(持续集成服务)有关的改动
// chore: 不修改src或者test的其余修改,例如构建过程或辅助工具的变动
module.exports = {
extends: ['@commitlint/config-conventional'],
// rules: [] // 可以自定义规则
};
precommit
git提供的钩子函数,在该阶段执行eslint
等验证规范,再根据执行结果判断是否提交
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
# npx --no-install pretty-quick --staged
npx --no-install lint-staged
commit-msg
验证 git commit -m "xx"
提交的 message
信息格式和规范
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npx --no-install commitlint --edit $1
.gitignore
作用是为了忽略 husky
安装产生的非项目代码文件
_
package.json
配置
"scripts": {
// 这里换成 postinstall 也可以,npm提供的一个生命周期钩子
// 主要是为了我们在安装完 husky依赖后可以执行husky脚本,使git hook钩子生效
"prepare": "husky install",
"commitlint": "commitlint --from=master",
// lint 相关规则和规范执行命令
"lint:eslint:fix": "eslint --ext .ts,.tsx --no-error-on-unmatched-pattern --quiet --fix",
"lint:style": "stylelint --fix \"src/**/*.less\" --syntax less",
"lint": "npm run lint:eslint:fix && npm run lint:style",
// prettier 格式化的一个拓展库
"pretty-quick": "pretty-quick",
},
"lint-staged": {
"*.{ts,tsx}": [
"npm run lint:eslint:fix",
"git add --force"
],
"*.{md,json}": [
"pretty-quick --staged",
"git add --force"
]
}
eslint
、prettier
拓展插件.vscode/settings.json
配置文件,或者全局设置 vscode settings.json
// 保存时格式化
"editor.formatOnSave": true,
// 相当于是 vscode 保存时调用的钩子事件
"editor.codeActionsOnSave": {
// 保存时使用 ESLint 修复可修复错误
"source.fixAll.eslint": true
}
// 针对对应文件设置默认格式化插件
"[javascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[javascriptreact]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescriptreact]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
这里我们使用 react-refresh
+webpack
方案
yarn add react-refresh @pmmmwh/react-refresh-webpack-plugin -D
webpack
配置const { HotModuleReplacementPlugin } = require('webpack');
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin');
module.exports = {
devServer: {
port: 3010, // 端口
publicPath: '/', // 基础路径
open: true, // 打开页面
hot: true, // 开启热更新
historyApiFallback: true, // 把404响应定位到 index.html
// proxy:{} // 开发环境,接口代理
},
plugins: [new HotModuleReplacementPlugin(), new ReactRefreshWebpackPlugin()]
}
css
压缩插件:yarn add mini-css-extract-plugin css-minimizer-webpack-plugin -D
js
压缩插件:yarn add terser-webpack-plugin -D
gzip
压缩插件:yarn add compression-webpack-plugin -D
webpack-bundle-analyzer
css
压缩的webpack
配置:
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
// 支持动态注入 link 标签到 html中
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
module: {
rules: [
// .css
{
test: /\.css$/,
use: [
// 该插件是为了把 CSS提取到单独的文件中
MiniCssExtractPlugin.loader,
'css-loader'
],
},
// less
{
test: /\.less$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
'less-loader'
],
},
]
},
plugins: [
// 简化 HTML 文件的创建
new HtmlWebpackPlugin({
template: 'public/index.html',
}),
// MiniCss
new MiniCssExtractPlugin({
filename: "[name].css",
chunkFilename: "[id].css",
}),
],
optimization: {
minimizer: [
// 压缩
new CssMinimizerPlugin(),
]
}
}
js
压缩的webpack
配置:
webpack5.x内置有terser-webpack-plugin
,不过如果想要自定义配置还是需要安装的
// 使用 terser 压缩 JavaScript
const TerserPlugin = require("terser-webpack-plugin");
module.exports = {
optimization: {
minimize: true, // 开启压缩
minimizer: [
// 1. 简单使用默认配置
new TerserPlugin() // 使用插件
// 2. 自定义配置一些参数
new TerserPlugin({
terserOptions: {
output: {
comments: false, // 在输出中省略注释
},
// ie8: true 开启 ie8 支持
},
}),
],
},
}
chunk
拆分
这里的可玩性还是比较高的,各种性能优化啥的,有兴趣的朋友可以研究研究,我们这里只做简单使用配置~
module.exports = {
optimization: {
splitChunks: {
chunks: 'all', // 处理哪种chunk,可选值为:all、async、initial(all包括异步和非异步)
maxInitialRequests: 10, // 入口点的最大并行请求数。
minSize: 0, // 生成 chunk 的最小体积(以 bytes 为单位)
// 缓存组,该组内配置可继承、覆盖splitChunks中的配置
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/, // [\\/] 是为了分别匹配 win、Unix系统路径
// 拆分 vendor,自定义name(vendor默认是合并为一个 chunk)
name(module) {
// 把 npm 库拆分独立打包
const packageName = module.context.match(/[\\/]node_modules[\\/](.*?)([\\/]|$)/)[1];
return `npm.${packageName.replace('@', '')}`;
},
},
},
}
},
}
Gzip
压缩
const CompressionPlugin = require('compression-webpack-plugin');
module.exports = {
plugins: [
new CompressionPlugin({
algorithm: 'gzip', // 压缩算法
test: /\.js$|\.css$|\.html$/,
threshold: 10240, // 只压缩大于 10240 bytes 的chunk
minRatio: 0.8, // 只压缩大于该值的 minRatio = Compressed Size / Original Size
})
]
}
本文着重描述了项目的工程化配置,涉及相关代码规范、开发环境搭建、生产环境优化等,旨在打造出一个可快速使用的现代化webpack+react+typescript
模板,以供在以后的实际业务场景需求中零成本使用。
目前这只算是最简陋的一个demo,对webpack
相关的各种调优与项目优化还有很多方向可研究。
接下来就是搞实际场景的业务组件的开发、第三方开源组件的集成使用、通用组件的使用和开发封装了~
项目地址:https://github.com/JS-banana/webpack-react-ts