前言
Monorepo 最早的出处是软件开发策略的一个分支,”mono” 表示单一 “repo” 是”repository”的缩写,是指将多个项目的代码存储在同一个版本库中的软件开发方法。多个项目共用一个代码库来管理依赖关系,同一套配置文件,统一构建部署流程等等。这种方法可以提高代码共享和重用的效率,同时也可以简化代码管理和部署过程。社区常见 Monorepo 架构管理方法有:
- lerna —–例如:Antd、ahooks
- pnpm 工作空间
不管使用什么方式进行管理,在确定将项目设计成 Monorepo 架构前,需要考虑以下几点。
- 项目规模较大,需要多个子项目协同开发和维护。
- 子项目之间存在明确的依赖关系,需要共享代码和资源。
- 需要统一管理和控制版本,方便协作和发布。
- 需要提高开发效率和代码复用性。
- 需要统一的构建、测试和部署流程。
环境&技术栈
- Node 18.x
- Vite 4.4.x
- React
- TypeScript
- postCsss
- Pnpm
- …
Monorepo 环境搭建
初始化项目
在 cmd 终端输入命令行,使用 vite 脚手架
pnpm create vite lowcode-platform-react --template react-ts
没有安装 pnpm,全局安装下 npm i -g pnpm
一个 react 项目模版快速建好了
目录结构设计
一个好的项目目录结构设计会直接影响到项目后续的开发和维护,一般情况下 Monorepo
架构的文件目录,主要以 packages
文件为主包。然后通过结合各类工具包和配置文件组合成项目的整体架构
├── husky // git提交拦截
├── config // 配置相关
├── packages // 主包
│ ├── editor // 编辑器子包
│ │ └── // 按需配置
│ ├── shared // 全局共享子包
│ │ └──
├── src
│ │ ...
├── static // 主包三方不打包资源
│ └──
│ │
├── .babelrc // babel解析配置
├── .browserslistrc // 浏览器兼容配置
├── .postcssrc.json // CSS解析配置
├── tailwind.config.js // 原子化CSS配置
├── tsconfig.json // TSC
├── .eslintrc.json // eslint配置
├── .gitignore // git 忽略项
├── .prettierrc.json // 代码美化配置
├── pnpm-workspace.yaml // pnpm工作空间配置
└── package.json // package.json
以上目录结构中,packages
文件夹用于存放所有的子项目。每个子项目都是一个独立的文件夹,其中包含其自己的源代码和配置文件。
在根目录下,vite.config.ts
文件用于配置monorepo的根项目,其中可以指定输出目录、自定义构建选项等
通过这种目录结构设计,我们可以方便地管理多个子项目,并在它们之间共享依赖和代码。每个子项目都可以独立运行,但又能够方便地在根目录中进行统一管理和构建。
配置pnpm工作区间 workspace
- 在根目录的
package.json
文件中,添加workspaces
字段,并指定子项目的目录路径,例如:
{
"private": true,
"workspaces": [
"packages/*"
]
}
- 在根目录下创建一个
pnpm-workspace.yaml
文件,用于配置PNPM工作区间的特定设置
packages:
- 'packages/*'
初始化子项目
新建两个子项目,editor 、shared,在packages文件下新建,并初始化子项目
# 新建editor包
mkdir editor
cd editor
pnpm init
# 新建shared包
mkdir shared
cd shared
pnpm init
初始化好子项目后,分别在 package.json
中进行配置,如 editor
子包
- 给包名添加自己的包管理前缀
@jeffery/editor
- 以及添加node启动模式
"type": "module"
- 修包改根路径的指向
"main": "src/index.ts"
{
"name": "@jeffery/editor",
"version": "1.0.0",
"description": "",
"main": "index.ts",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"type": "module",
"keywords": [],
"author": "JefferyXZF",
"license": "ISC"
}
完成俩个子包的初始化,那么怎么让他们建立联系呢? 也就是如何在一个包(editor)中,引入另一个包 (shared)并使用
建立包之间的关联
1. 在 editor
子包中,安装 shared
子包使用
在这里通过 pnpm 的 –filter 方法将本项目中的一个包,导入另一个包
pnpm add @jeffery/shared --filter @jeffery/editor
在项目根目录下执行以上命令后,将在 packages/editor/package.json
包中发现多了一个 workspace:^
依赖
"dependencies": {
"@jeffry/shared": "workspace:^"
}
解释下 --filter
,它可以用来对特定的 package
进行某些操作,--filter
参数跟着的是 package
下的 package.json
的 name
字段,并不是目录名,如给某个 pkg1
子包安装依赖
pnpm add react --filter pkg1(项目名)
# 将package下的pkg1包安装到pkg2中
pnpm add pkg1 --filter pkg2
2. 在主项目根目录中,安装 editor
子包
pnpm 提供了 -w
, –workspace-root 参数,可以将依赖包安装到工程的根目录下,作为所有 package 的公共依赖。
pnpm add @jeffery/editor -w
如果是一个开发依赖的话,可以加上 -D
参数,表示这是一个开发依赖,会装到 pacakage.json
中的 devDependencies
中,比如:
pnpm add react -wD
项目代码规范
安装配置Eslint
Eslint : 一般多用来做代码的检测(逻辑、功能)
根目录下安装 eslint
pnpm add eslint -D -w
npx eslint --init
安装 eslint
插件支持检查 TyprScript
pnpm add @typescript-eslint/eslint-plugin @typescript-eslint/parser -D -w
.eslintrc.js
配置文件
/* eslint-env node */
module.exports = {
root: true,
env: {
browser: true,
es2020: true
},
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:@typescript-eslint/recommended-requiring-type-checking',
'plugin:react-hooks/recommended',
],
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
project: true,
tsconfigRootDir: __dirname,
},
plugins: ['react-refresh'],
rules: {
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
],
'@typescript-eslint/no-non-null-assertion': 'off',
},
}
除了安装插件,我们也可以通过 Vite 插件的方式在开发阶段进行 ESLint 扫描,以命令行的方式展示出代码中的规范问题,并能够直接定位到原文件
pnpm add vite-plugin-eslint -D -w
在 vite.config.ts 中接入:
// vite.config.ts
import viteEslint from 'vite-plugin-eslint';
// 具体配置
{
plugins: [
// 省略其它插件
viteEslint(),
]
}
重新启动项目, ESLint 的错误已经能够及时显示到命令行窗口中
安装 Prettier
prettier :一般用来做代码格式化,美化代码提高开发效率
根目录下继续安装 prettier
pnpm add prettier -D -w
创建配置文件 .prettierrc.js
module.exports = {
// 单行代码的最大宽度
printWidth: 120,
// 指定每个缩进级别的空格数
tabWidth: 2,
// 使用制表符 (tab) 缩进
useTabs: false,
// 在语句末尾打印分号
semi: true,
// 多行时尽可能打印尾随逗号
trailingComma: 'none',
// 使用单引号而不是双引号
singleQuote: true,
// 在对象文字中打印括号之间的空格
bracketSpacing: true,
// 将 > 多行 JSX 元素放在最后一行的末尾,而不是单独放在下一行(不适用于自闭元素)。
jsxBracketSameLine: false,
// auto | lf | crlf | cr
endOfLine: 'lf'
}
prettier 配置到 .eslintrc.js
,安装 eslint-config-prettier
解决 Prettier 与 ESLint 的配置冲突
pnpm add eslint-config-prettier eslint-plugin-prettier -D -w //为了防止 Prettier 与 ESLint 的配置冲突
在 .eslintrc.js
手动配置 prettier
, 可以省略 eslint-config
"plugins": [
"prettier"
],
样式规范工具: Stylelint
Stylelint,一个强大的现代化样式 Lint 工具,用来帮助你避免语法错误和统一代码风格。
pnpm add stylelint stylelint-prettier stylelint-config-prettier stylelint-config-recess-order stylelint-config-standard stylelint-config-standard-scss -D -w
在 Stylelint 的配置文件 .stylelintrc.js
中一一使用这些工具套件
// .stylelintrc.js
module.exports = {
// 注册 stylelint 的 prettier 插件
plugins: ['stylelint-prettier'],
// 继承一系列规则集合
extends: [
// standard 规则集合
'stylelint-config-standard',
// standard 规则集合的 scss 版本
'stylelint-config-standard-scss',
// 样式属性顺序规则
'stylelint-config-recess-order',
// 接入 Prettier 规则
'stylelint-config-prettier',
'stylelint-prettier/recommended'
],
// 配置 rules
rules: {
// 开启 Prettier 自动格式化功能
'prettier/prettier': true
}
};
在 package.json
中,增加如下的 scripts
配置
{
"scripts": {
// 整合 lint 命令
"lint": "npm run lint:script && npm run lint:style",
// stylelint 命令
"lint:style": "stylelint --fix \"src/**/*.{css,scss}\""
}
}
执行 pnpm run lint:style
即可完成样式代码的规范检查和自动格式化,在 VSCode 中安装Stylelint插件,这样能够在开发阶段即时感知到代码格式问题,提前进行修复。
也可以直接在 Vite 中集成 Stylelint。社区中提供了 Stylelint 的 Vite 插件,实现在项目开发阶段提前暴露出样式代码的规范问题
pnpm add vite-plugin-stylelint -D -w
在 Vite
添加配置
import viteStylelint from '@amatlash/vite-plugin-stylelint';
// 注意: Vite 3.x 以及以后的版本需要引入 vite-plugin-stylelint
// 具体配置
{
plugins: [
// 省略其它插件
viteStylelint({
// 对某些文件排除检查
exclude: /windicss|node_modules/
}),
]
}
CSS 前端工程化方案
在使用原生 CSS 开发时,会遇到各种问题,如
- CSS 不支持选择器的嵌套
- 样式污染问题
- 浏览器兼容问题
- 打包后的代码体积问题
因为存在这些问题,所以出现CSS前端工程化解决这些问题,针对以上问题,社区常见的方案有
CSS 预处理器
:主流的包括Sass
、Less
和Stylus
。这些方案各自定义了一套语法,让 CSS 也能使用嵌套规则,甚至能像编程语言一样定义变量、写条件判断和循环语句,大大增强了样式语言的灵活性,解决原生 CSS 的开发体验问题。CSS Modules
:能将 CSS 类名处理成哈希值,这样就可以避免同名的情况下样式污染的问题。CSS 后处理器PostCSS
:用来解析和处理 CSS 代码,可以实现的功能非常丰富,比如将 px 转换为 rem、根据目标浏览器情况自动加上类似于–moz–、-o-的属性前缀等等。CSS in JS 方案
,主流的包括emotion
、styled-components
等等,顾名思义,这类方案可以实现直接在 JS 中写样式代码,基本包含CSS 预处理器和 CSS Modules 的各项优点,非常灵活,解决了开发体验和全局样式污染的问题。CSS 原子化框架
,如Tailwind CSS
、Windi CSS
,通过类名来指定样式,大大简化了样式写法,提高了样式开发的效率,主要解决了原生 CSS 开发体验的问题
CSS 预处理器
Sass
和 Less
提供类似的功能,如变量、嵌套、混合(Mixin)、继承等。然而,Sass
在某些方面可能更强大,如支持条件语句、循环、函数等更高级的功能。Sass 可以实现更复杂的功能和灵活性,并且前端框架(如Vue和React)也是更倾向于 Sass
pnpm add sass -D -w
Sass
公共变量不希望在每个文件中引入,配置 Vite
import {
defineConfig,
normalizePath
} from 'vite'
import react from '@vitejs/plugin-react'
import path from 'path'
// 全局 scss 文件的路径
// 用 normalizePath 解决 window 下的路径问题
const variablePath = normalizePath(path.resolve('./src/assets/scss/variable.scss'));
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
// css 相关的配置
css: {
preprocessorOptions: {
scss: {
// additionalData 的内容会在每个 scss 文件的开头自动注入
additionalData: `@import "${variablePath}";`
}
}
}
})
CSS Modules
CSS Modules
在 Vite 也是一个开箱即用的能力,Vite 会对后缀带有 .module
的样式文件自动应用 CSS Modules
例如
// index.module.scss
.header {
color: $theme-color;
}
// index.tsx
import styles from './index.module.scss';
export function Header() {
return <p className={styles.header}>This is Header</p>
};
PostCSS 后处理器
PostCSS
本体功能比较单一,它提供一种用 JavaScript 来处理 CSS 的方式。PostCSS 会把 CSS 解析成 AST(Abstract Syntax Tree 抽象语法树),之后由其他插件进行不同的处理。
常见的 PostCSS
插件有
Autoprefixer
为 CSS 中的属性添加浏览器特定的前缀。postcss-preset-env
根据 browserslist 指定的目标浏览器将一些 CSS 的新特性转换为目标浏览器所支持的语法。cssnano
提供 CSS 压缩功能。postcss-px-to-viewport
提供 px 转 vw 功能。postcss-pxtorem
: 用来将 px 转换为 rem 单位,在适配移动端的场景下很常用。
PostCSS 插件网站:www.postcss.parts
以 autoprefixer
安装为例
pnpm add autoprefixer -D -w
// vite.config.ts 增加如下的配置
import autoprefixer from 'autoprefixer';
export default {
css: {
// 进行 PostCSS 配置
postcss: {
plugins: [
autoprefixer({
// 指定目标浏览器
overrideBrowserslist: ['Chrome > 40', 'ff > 31', 'ie 11']
})
]
}
}
}
CSS In JS
使用CSS-in-JS方案(如 styled-components
和 Emotion
)可以提供更好的组件封装、样式隔离和可维护性。选择哪个方案可以根据下面对比,以 Emotion
安装为例
// vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
react({
babel: {
// 加入 babel 插件
// 以下插件包都需要提前安装
// 当然,通过这个配置你也可以添加其它的 Babel 插件
plugins: [
// 适配 styled-component
"babel-plugin-styled-components"
// 适配 emotion
"@emotion/babel-plugin"
]
},
// 注意: 对于 emotion,需要单独加上这个配置
// 通过 `@emotion/react` 包编译 emotion 中的特殊 jsx 语法
jsxImportSource: "@emotion/react"
})
]
})
CSS 原子化框架
CSS 原子化框架主要包括 Tailwind CSS
和 Windi CSS
,以 Windi CSS
安装为例
pnpm add windicss vite-plugin-windicss -D -w
在配置文件中来使用它
// vite.config.ts
import windi from "vite-plugin-windicss";
export default {
plugins: [
// 省略其它插件
windi()
]
}
在src/main.tsx中引入一个必需的 import 语句
// main.tsx
// 用来注入 Windi CSS 所需的样式,一定要加上!
import "virtual:windi.css";
在组件中使用
export function Header() {
return (
<div className="p-20px text-center">
<h1 className="font-bold text-2xl mb-2">
hello world
</h1>
</div>
);
}
Git 提交工作流
Husky + lint-staged
- 安装依赖
pnpm add husky lint-staged -D -w
- 初始化 Husky: npx husky install,并将 husky install作为项目启动前脚本,如:
{
"scripts": {
// 会在安装 npm 依赖后自动执行
"prepare": "husky install"
}
}
添加 Husky 钩子,在终端执行如下命令:
npx husky add .husky/pre-commit "npm run lint"
- 在
package.json
中添加lint-staged
配置
{
"lint-staged": {
"**/*.{js,jsx,tsx,ts}": [
"npm run lint:script",
"git add ."
],
"**/*.{scss}": [
"npm run lint:style",
"git add ."
]
}
}
在 Husky 中应用lint-stage,回到.husky/pre-commit脚本中,将原来的npm run lint换成如下脚本:
npx --no -- lint-staged
提交时的 commit 信息规范
安装符合 Angular 的 Commit message 格式的提交规范
pnpm add commitizen cz-conventional-changelog -D -w
在 package.json 配置
{
...
"config": {
"commitizen": {
"path": "./node_modules/cz-conventional-changelog"
}
}
...
}
在 package.json scrips 添加 “commit”: “git-cz” 命令
scripts: {
"commit": "git-cz"
}
提交支持更多的功能,详细配置,参考之前的编写的文章 组件库代码规范husky+lint-staged+Eslint+Prettier+Stylelint
欢迎关注,让我们一起开发一个低代码项目,源码地址。