Nx 18.x:React Monorepo
从零创建workspaces
npx create-nx-workspace@latest wsps --preset=react-monorepo
通过对话框填写Project配置信息:
NX Let's create a new workspace [https://nx.dev/getting-started/intro]
✔ Application name · activity1
✔ Which bundler would you like to use? · vite
✔ Test runner to use for end to end (E2E) tests · none
✔ Default stylesheet format · scss
✔ Do you want Nx Cloud to make your CI fast? · none
运行Project:
# 切换到workspaces
cd wsps
# 运行指定项目 nx <target name> <project name> <option overrides>
nx serve activity1
nx run activity1:serve
# 指定项目运行多个target
nx run-many -t test lint e2e activity1
# 针对所有项目运行target
nx run-many -t serve --all // serve需要调整每个项目运行的端口号,<project>/webpack.config
nx run-many -t serve -p activity1 activity2
# 针对更改代码的项目运行target
nx affected -t test
自定义target
全局targert
task定义
// ./package.json 根目录
{
"name": "myorg",
"scripts": {
"docs": "nx exec -- node ./generateDocsSite.js", // nx exec -- 可执行npm scripts
},
"nx": {}
}
- 在根目录的
package.json
中配置run-scripts
- 添加
"nx": {}
,后续即可在命令行中通过nx
调用该script task
- 在命令行运行
nx docs
task配置
全局task配置项在
nx.json
中配置
{
"targetDefaults": {
"build": {
"cache": true // 如果运行两次构建任务,第二次操作将是即时的,因为它是从缓存中恢复的。
},
"test": {
"cache": true
}
}
}
-
targetDefaults#build#cache
在输入相同的情况下,始终产生相同的输出结果- 如果运行任务,第二次操作将是即时的,因为它是从缓存中恢复的。
- 首次运行任务后缓存存储在
.nx/cache
中。 - 可以通过
nx build header --skip-nx-cache
忽略项目缓存 - 通过
npx nx reset
清空所有缓存
独立Project target
// apps\activity1\project.json 配置project#targets
{
"name": "activity1",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "apps/activity1/src",
"projectType": "application",
"tags": [],
"// targets": "to see all targets run: nx show project activity1 --web",
"targets": {
"test": {
"command": "echo 1111111111111",
"dependsOn": [""], // 依赖其他target,执行该任务之前,确保依赖已成功执行。可选
"options": {
"cwd": "apps/activity1",
"args": [
"--node-env=development" // command的参数,echo 1111111 --node-env=development
]
}
}
}
}
nx缓存task
// apps\activity1\package.json
{
"name": "activity1",
"scripts": {
"docs": "nx exec -- node ./generateDocsSite.js",
"test": "nx exec -- npm run docs"
},
"nx": {}
}
依赖限制
Nx 有一个通用机制,允许你为项目指定
"tags"
。"tags "
是可分配给项目的任意字符串,可在以后定义项目之间的边界时使用。
library类型
例如: "feature" library, "utility" library, "data-access" library, "ui" library
// libs/toast/project.json
{
...
"tags": ["type:feature"]
}
// apps/activity1/project.json
{
...
"tags": ["type:feature"]
}
Project作用域
例如: "feature" library, "utility" library, "data-access" library, "ui" library
// libs/toast/project.json
{
...
"tags": ["type:feature", "scope:orders"]
}
// apps/activity1/project.json
{
...
"tags": ["type:feature", "scope:products"]
}
指定依赖规则
// .eslintrc.base.json
{
...
"overrides": [
{
...
"rules": {
"@nx/enforce-module-boundaries": [
"error",
{
"enforceBuildableLibDependency": true,
"allow": [],
"depConstraints": [
{
"sourceTag": "*",
"onlyDependOnLibsWithTags": ["*"]
},
{
"sourceTag": "type:feature",
"onlyDependOnLibsWithTags": ["type:feature", "type:ui"]
},
{
"sourceTag": "type:ui",
"onlyDependOnLibsWithTags": ["type:ui"]
},
{
"sourceTag": "scope:orders",
"onlyDependOnLibsWithTags": [
"scope:orders",
"scope:products",
"scope:shared"
]
},
{
"sourceTag": "scope:products",
"onlyDependOnLibsWithTags": ["scope:products", "scope:shared"]
},
{
"sourceTag": "scope:shared",
"onlyDependOnLibsWithTags": ["scope:shared"]
}
]
}
]
}
},
...
]
}
环境变量设置
通过
.env.*
文件配置环境变量
-
默认寻找
.env
文件 -
若需要不同环境设置不同的环境变量,需要结合
targets#<target-name>#<configuration-name>
使用- 注册环境
// apps/subproject/project.json { "name": "activity1", "$schema": "../../node_modules/nx/schemas/project-schema.json", "sourceRoot": "apps/activity1/src", "projectType": "application", "targets": { "serve": { "configurations": { "development": { "envFiles": [".env.development"] } } } } }
-
创建环境变量
在相应的项目下,
创建.env.development
文件,填充变量# apps/activity1/.env.development a=1
使用对应的环境变量
nx run-many -t serve --configuration=development
-
在js环境中使用环境变量
上述只能在node环境中使用变量,若需要在js环境中使用,需要将变量注入打包后的代码。
```
const RUN_ENV = process.env.RUN_ENVexport default defineConfig({
root: __dirname,
define: { // 通过define将环境变量注入代码
RUN_ENV
},
})
浏览器兼容
查看当前项目浏览器兼容性配置
兼容Chrome64+
- https://github.com/vitejs/vite/discussions/7915
- https://www.npmjs.com/package/abortcontroller-polyfill
具体操作:
- 安装依赖
npm i -D @vitejs/plugin-legacy npm i -S abortcontroller-polyfill
- 更新
vite.config.ts
import legacy from '@vitejs/plugin-legacy'; ... plugins: [ legacy({ targets: ['chrome >= 64', 'edge >= 79', 'safari >= 11.1', 'firefox >= 67'], renderLegacyChunks: false, /** * Polyfills required by modern browsers * * Since some low-version modern browsers do not support the new syntax * You need to load polyfills corresponding to the syntax to be compatible * At build, all required polyfills are packaged according to the target browser version range * But when the page is accessed, only the required part is loaded depending on the browser version * * Two configuration methods: * * 1. true * - Automatically load all required polyfills based on the target browser version range * - Demerit: will introduce polyfills that are not needed by modern browsers in higher versions, * as well as more aggressive polyfills. * * 2、string[] * - Add low-version browser polyfills as needed * - Demerit: It needs to be added manually, which is inflexible; * it will be discovered after the production is deployed, resulting in production failure! ! ! */ // modernPolyfills: ['es/global-this'], // or modernPolyfills: true, }), ] ...
- 更新
main.tsx
import 'abortcontroller-polyfill/dist/polyfill-patch-fetch' // 必须在首行引入 ... const rootNode = document.getElementById('root') as HTMLElement const root = ReactDOM.createRoot(rootNode); root.render( // <StrictMode> <Router /> // </StrictMode> );
兼容Chrome49+
页面使用图片等静态资源时,打包运行后会报错:
Unexpected token import
通过链接https://github.com/vitejs/vite/issues/9297可知,该问题是ES module打包导致的,需要调整打包输出格式
具体操作:
以下操作是在Chrome64+的基础上操作
-
安装依赖
npm i -S react-app-polyfill
-
更新
vite.config.ts
import legacy from '@vitejs/plugin-legacy'; import { BuildOptions } from 'vite'; ... plugins: [ ... legacy({ targets: ['chrome >= 49', 'edge >= 79', 'safari >= 11.1', 'firefox >= 67'], renderLegacyChunks: false, /** * Polyfills required by modern browsers * * Since some low-version modern browsers do not support the new syntax * You need to load polyfills corresponding to the syntax to be compatible * At build, all required polyfills are packaged according to the target browser version range * But when the page is accessed, only the required part is loaded depending on the browser version * * Two configuration methods: * * 1. true * - Automatically load all required polyfills based on the target browser version range * - Demerit: will introduce polyfills that are not needed by modern browsers in higher versions, * as well as more aggressive polyfills. * * 2、string[] * - Add low-version browser polyfills as needed * - Demerit: It needs to be added manually, which is inflexible; * it will be discovered after the production is deployed, resulting in production failure! ! ! */ // modernPolyfills: ['es/global-this'], // or modernPolyfills: true, }), build: { target: 'es2015', outDir: `../../dist/apps/${projectJSON.name}`, reportCompressedSize: true, commonjsOptions: { transformMixedEsModules: true, }, rollupOptions: { esModule: false, output: { format: 'umd' } as BuildOptions['rollupOptions'] } }, ] ...
-
更新
main.tsx
import 'react-app-polyfill/stable'; // 必须在顶部其他资源import之前引入 import 'abortcontroller-polyfill/dist/polyfill-patch-fetch' // 必须在顶部其他资源import之前引入 ... const rootNode = document.getElementById('root') as HTMLElement const root = ReactDOM.createRoot(rootNode); root.render( // <StrictMode> <Router /> // </StrictMode> );
-
修改index.html
Chrome49
不支持type="module"
形式引入script
,需要手动或自定义脚本修改script
的引入方式:-
vite-plugin-html
未实现 - 自定义插件示例:https://github.com/vitejs/vite/discussions/14116
Before After <script type="module" crossorigin src="./assets/polyfills-D8f3lBf8.js"></script>
<script src="./assets/polyfills-D8f3lBf8.js"></script>
还需要将
<script src="./assets/index-QuzEd4kM.js"></script>
位置调整到<div id="root"></div>
之后。 -
兼容IE
辅助资料:
- https://polyfill.io/
- https://vitejs.dev/guide/build#browser-compatibility
- https://blog.rxliuli.com/p/73331967c1814df480811eee598e714b/
React使用技巧
useSearchParams
获取的是hash
后边的参数,即#/?a=test
Form#action
前置条件:
- <Form>必须携带
method
非get
的配置,否则不会触发action
; -
action
必须有返回值,否则提交后会刷新页面;
获取表单数据:
const gotoNext = () => {
if (!formRef.current) return
if (!agreePolicy) {
Toast.info('请先同意服务协议');
return;
}
if (formError) {
Toast.info('请填写表单');
return;
}
const formData = new FormData(formRef.current); // 获取表单数据
const formDataEntryValue = Object.fromEntries(
formData.entries()
); // 将FormData转换为JSON
submit(
formDataEntryValue,
{
method: state?.update ? 'put' : 'post'
}
)
}
部署
静态资源路径
vite.config.js
通过配置base
修改资源引用路径:
export default defineConfig({
root: __dirname,
base: './', // 相对路径访问
define: {
RUN_ENV
},
})
html
文件绝对路径访问无需修改,只需要删除<base href="/" />
即可
<html lang="en">
<head>
<meta charset="utf-8" />
<title><%- title %></title>
<!-- <base href="/" /> -->
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
<link rel="stylesheet" href="/src/styles.scss" />
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
CI部署
# 项目根目录 gitlab-ci.yml
# variables: # 如果设定,通过GUI执行CI pipeline时,变量PROJECT_NAME会为undefined,所以注释不使用,通过每一个task导出全局变量
# PROJECT_NAME:
# description: 指定项目名称
image: nvm-node:latest
stages:
- install_dep
- build_project
- deploy_project
install:
stage: install_dep
only:
refs:
- develop
- preview
- main
changes:
- package.json
script:
- node -v
- npm -v
- rm -rf *-lock.json
- npm i
cache:
key:
files:
- package.json
prefix: ${CI_COMMIT_REF_SLUG}
paths:
- node_modules/
policy: push
.job_build:
stage: build_project
when: manual
allow_failure: false # 若失败,下一stage不执行
only:
refs:
- develop
- preview
- main
cache:
- key:
files:
- package.json
prefix: ${CI_COMMIT_REF_SLUG}
paths:
- node_modules/
policy: pull
script:
- export PROJECT_NAME=$PROJECT_NAME # 子yml中定义的
- echo "🚀 ~ current build project name is $PROJECT_NAME"
- fds build
.job_deploy:
stage: deploy_project
only:
refs:
- develop
- preview
- main
cache:
- key:
files:
- package.json
prefix: ${CI_COMMIT_REF_SLUG}
paths:
- node_modules/
policy: pull
script:
- export PROJECT_NAME=$PROJECT_NAME # 子yml中定义的
- echo "🚀 ~ $PROJECT_NAME start deploy:"
- fds deploy
include:
- '/apps/activity1/.gitlab-ci.yml'
- '/apps/activity2/.gitlab-ci.yml'
不同项目下.gitlab-ci.yml
:
# /apps/activity1/.gitlab-ci.yml
.set-activity1-env: &set-activity1-env
variables:
PROJECT_NAME: activity1
build_activity1:
<<: *set-activity1-env
extends: .job_build
deploy_activity1:
<<: *set-activity1-env
needs: // 上一stage存在多个任务时,如果只依赖其中一项或多项完成,而不是全部项完成时,可以使用need
- job: build_activity1 # build_activity1完成后,自动执行该任务
extends: .job_deploy # 依据任务锚点定义新任务