React + Pnpm + Vite 搭建Monorepo低代码项目

前言

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 项目模版快速建好了

image.png

目录结构设计

一个好的项目目录结构设计会直接影响到项目后续的开发和维护,一般情况下 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

  1. 在根目录的 package.json 文件中,添加 workspaces 字段,并指定子项目的目录路径,例如:
{





  "private": true,
  "workspaces": [
    "packages/*"
  ]
}

  1. 在根目录下创建一个 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.jsonname 字段,并不是目录名,如给某个 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 预处理器:主流的包括 SassLessStylus。这些方案各自定义了一套语法,让 CSS 也能使用嵌套规则,甚至能像编程语言一样定义变量、写条件判断和循环语句,大大增强了样式语言的灵活性,解决原生 CSS 的开发体验问题。
  • CSS Modules:能将 CSS 类名处理成哈希值,这样就可以避免同名的情况下样式污染的问题。
  • CSS 后处理器PostCSS:用来解析和处理 CSS 代码,可以实现的功能非常丰富,比如将 px 转换为 rem、根据目标浏览器情况自动加上类似于–moz–、-o-的属性前缀等等。
  • CSS in JS 方案,主流的包括 emotionstyled-components 等等,顾名思义,这类方案可以实现直接在 JS 中写样式代码,基本包含CSS 预处理器和 CSS Modules 的各项优点,非常灵活,解决了开发体验和全局样式污染的问题。
  • CSS 原子化框架,如 Tailwind CSSWindi CSS,通过类名来指定样式,大大简化了样式写法,提高了样式开发的效率,主要解决了原生 CSS 开发体验的问题

CSS 预处理器

SassLess 提供类似的功能,如变量、嵌套、混合(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-componentsEmotion)可以提供更好的组件封装、样式隔离和可维护性。选择哪个方案可以根据下面对比,以 Emotion 安装为例

image.png

// 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 CSSWindi 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

  1. 安装依赖
pnpm add husky lint-staged -D -w
  1. 初始化 Husky: npx husky install,并将 husky install作为项目启动前脚本,如:
{





  "scripts": {

    // 会在安装 npm 依赖后自动执行
    "prepare": "husky install"
  }
}

添加 Husky 钩子,在终端执行如下命令:

npx husky add .husky/pre-commit "npm run lint"
  1. 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

欢迎关注,让我们一起开发一个低代码项目,源码地址

© 版权声明
THE END
喜欢就支持一下吧
点赞0

Warning: mysqli_query(): (HY000/3): Error writing file '/tmp/MY5bXzSn' (Errcode: 28 - No space left on device) in /www/wwwroot/583.cn/wp-includes/class-wpdb.php on line 2345
admin的头像-五八三
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

图形验证码
取消
昵称代码图片