Nx 18.x:React Monorepo

2024-05-23  本文已影响0人  说叁两事

从零创建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
    }
  }
}

独立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
    },
    })

    
    

浏览器兼容

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

兼容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 # 依据任务锚点定义新任务
上一篇下一篇

猜你喜欢

热点阅读