Nx 18.x:React Monorepo管理

数字揽汐人
• 阅读 217

从零创建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": {}
}
  1. 在根目录的package.json中配置run-scripts
  2. 添加"nx": {},后续即可在命令行中通过nx调用该script task
  3. 在命令行运行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.*文件配置环境变量
  1. 默认寻找.env文件
  2. 若需要不同环境设置不同的环境变量,需要结合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
  3. 在js环境中使用环境变量

    上述只能在node环境中使用变量,若需要在js环境中使用,需要将变量注入打包后的代码。
    const RUN_ENV = process.env.RUN_ENV
    
    export default defineConfig({
    root: __dirname,
    define: {  // 通过define将环境变量注入代码
        RUN_ENV
    },
    })

浏览器兼容

查看当前项目浏览器兼容性配置

https://nx.dev/recipes/tips-n-tricks/browser-support#debuggin...

兼容Chrome64+

具体操作:

  1. 安装依赖

    npm i -D @vitejs/plugin-legacy
    npm i -S abortcontroller-polyfill
  2. 更新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,
        }),
    ]
    ...
  3. 更新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+的基础上操作
  1. 安装依赖

    npm i -S react-app-polyfill
  2. 更新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']
        }
        },
    ]
    ...
  3. 更新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>
    );
  4. 修改index.html

    Chrome49不支持type="module"形式引入script,需要手动或自定义脚本修改script的引入方式:

    1. vite-plugin-html未实现
    2. 自定义插件示例: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

辅助资料:

React使用技巧

useSearchParams

获取的是hash后边的参数,即#/?a=test

Form#action

前置条件:

  1. <Form>必须携带methodget的配置,否则不会触发action
  2. 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 # 依据任务锚点定义新任务
点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
美凌格栋栋酱 美凌格栋栋酱
6个月前
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(
Easter79 Easter79
3年前
typeScript数据类型
//布尔类型letisDone:booleanfalse;//数字类型所有数字都是浮点数numberletdecLiteral:number6;lethexLiteral:number0xf00d;letbinaryLiteral:number0b101
Wesley13 Wesley13
3年前
VBox 启动虚拟机失败
在Vbox(5.0.8版本)启动Ubuntu的虚拟机时,遇到错误信息:NtCreateFile(\\Device\\VBoxDrvStub)failed:0xc000000034STATUS\_OBJECT\_NAME\_NOT\_FOUND(0retries) (rc101)Makesurethekern
Wesley13 Wesley13
3年前
FLV文件格式
1.        FLV文件对齐方式FLV文件以大端对齐方式存放多字节整型。如存放数字无符号16位的数字300(0x012C),那么在FLV文件中存放的顺序是:|0x01|0x2C|。如果是无符号32位数字300(0x0000012C),那么在FLV文件中的存放顺序是:|0x00|0x00|0x00|0x01|0x2C。2.  
Wesley13 Wesley13
3年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Stella981 Stella981
3年前
ELK学习笔记之配置logstash消费kafka多个topic并分别生成索引
0x00 filebeat配置多个topicfilebeat.prospectors:input_type:logencoding:GB2312fields_under_root:truefields:添加字段
Wesley13 Wesley13
3年前
PHP创建多级树型结构
<!lang:php<?php$areaarray(array('id'1,'pid'0,'name''中国'),array('id'5,'pid'0,'name''美国'),array('id'2,'pid'1,'name''吉林'),array('id'4,'pid'2,'n
Wesley13 Wesley13
3年前
Java日期时间API系列36
  十二时辰,古代劳动人民把一昼夜划分成十二个时段,每一个时段叫一个时辰。二十四小时和十二时辰对照表:时辰时间24时制子时深夜11:00凌晨01:0023:0001:00丑时上午01:00上午03:0001:0003:00寅时上午03:00上午0
Wesley13 Wesley13
3年前
MBR笔记
<bochs:100000000000e\WGUI\Simclientsize(0,0)!stretchedsize(640,480)!<bochs:2b0x7c00<bochs:3c00000003740i\BIOS\$Revision:1.166$$Date:2006/08/1117
数字揽汐人
数字揽汐人
Lv1
我喜欢你,就像深夜头顶白色云朵的妙曼。
文章
4
粉丝
0
获赞
0