美文网首页
Nx 18.x:React Monorepo

Nx 18.x:React Monorepo

作者: 说叁两事 | 来源:发表于2024-05-23 18:32 被阅读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
        }
      }
    }
    
    • 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
      },
      })

      
      

    浏览器兼容

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

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

    相关文章

      网友评论

          本文标题:Nx 18.x:React Monorepo

          本文链接:https://www.haomeiwen.com/subject/dywtqjtx.html