在本文中,我們專注於使用 Nx 插件和生成器來自動化一些常見的工作流程
首先,我們安裝了必要的 Nx 插件。這些插件為 Nx 提供了核心功能,允許我們擴展和自定義 Nx 的能力
pnpm add @nx/plugin
並且創建一個本地plugin:
pnpm exec nx generate @nx/plugin:plugin plugins
接著來創建我們的generator (Docs)
pnpm exec nx generate @nx/plugin:generator component --project=plugins
先修改我們的template結構:
在創建自定義生成器之前,我們先查看了原始的生成器代碼,打開plugins\src\generators\component-generator\generator.ts
看一下我們原始的generator:
import { ComponentGeneratorSchema } from "./schema";
import { Tree, addProjectConfiguration, formatFiles, generateFiles } from "@nx/devkit";
import * as path from "path";
export async function componentGenerator(tree: Tree, options: ComponentGeneratorSchema) {
const projectRoot = `libs/${options.name}`;
addProjectConfiguration(tree, options.name, {
root: projectRoot,
projectType: "library",
sourceRoot: `${projectRoot}/src`,
targets: {}
});
generateFiles(tree, path.join(__dirname, "files"), projectRoot, options);
await formatFiles(tree);
}
export default componentGenerator;
componentGenerator
:
- projectRoot
定義了新項目的根目錄路徑
- 使用 addProjectConfiguration
方法添加新的項目配置到 Nx 的工作空間。
- generateFiles
方法用於根據提供的模板生成文件
- formatFiles
方法將對生成的文件進行格式化
現在我們來修改一下,我們先透過組合@nx/react:generators並且修改我們的generator
import { ComponentGeneratorSchema } from "./schema";
import { Tree, formatFiles, generateFiles } from "@nx/devkit";
import { componentGenerator as reactComponentGenerator } from "@nx/react";
import * as path from "path";
export async function componentGenerator(tree: Tree, options: ComponentGeneratorSchema) {
const libRoot = `libs/${options.name}`;
const componentPath = `${libRoot}/src/lib/${options.componentName}`;
await reactComponentGenerator(tree, {
project: options.name,
name: options.componentName,
pascalCaseFiles: true,
pascalCaseDirectory: true,
style: "none"
});
updateIndexFile(tree, libRoot, options.componentName);
generateFiles(tree, path.join(__dirname, "files"), componentPath, {
...options,
pascalCasedComponentName: toPascalCase(options.componentName)
});
await formatFiles(tree);
}
function updateIndexFile(tree: Tree, libRoot: string, componentName: string) {
const indexPath = `${libRoot}/src/index.ts`;
if (!tree.exists(indexPath)) return;
const pascalCasedComponentName = toPascalCase(componentName);
const exportStatement = `export * from './lib/${pascalCasedComponentName}';\n`;
const fileContent = tree.read(indexPath, "utf-8");
if (!fileContent.includes(exportStatement)) {
const updatedContent = fileContent + exportStatement;
tree.write(indexPath, updatedContent);
}
}
function toPascalCase(string: string): string {
return string
.split("-")
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
.join("");
}
export default componentGenerator;
componentGenerator
:
reactComponentGenerator
生成 React 組件updateIndexFile
函數更新 index.ts
文件以自動導出新的組件generateFiles
方法生成文件,這次將它放置在新組件的目錄下updateIndexFile
:index.ts
文件存在,然後將新組件的導出語句添加到文件的末尾toPascalCase
:my-component
會被轉換為 MyComponent
接下來修改我們的schema:
export interface ComponentGeneratorSchema {
libName: string;
name: string;
}
加入plugins\src\generators\component\schema.json
:
最後修改我們的template,plugins\src\generators\component\files\index.ts.template
:
import <%= pascalCasedComponentName %> from "./<%= pascalCasedComponentName %>";
export default <%= pascalCasedComponentName %>;
接著,我們來試著執行指令:
pnpm exec nx generate @iron-ecommerce-org/plugins:component
能看到創建了元件並更新export
我們透過使用Nx,成功地創建了自己的自定義react component的生成器。這不僅提高了我們的開發效率,不用一直重新創建,只要鈄過指令來進行創建我們元件。
你好,
請問樓主留有此份程式的源碼嗎~?
我跟著做卡關了,
似乎是某些套件依賴的版本其中的語法可能棄用了QQ
把版本換成固定版本應該就行了
{
"name": "@iron-ecommerce-org/source",
"version": "0.0.0",
"license": "MIT",
"scripts": {
"prepare": "husky install",
"commit": "cz"
},
"private": true,
"dependencies": {
"@apollo/client": "^3.8.4",
"@apollo/experimental-nextjs-app-support": "^0.4.3",
"@apollo/server": "^4.9.4",
"@devoxa/prisma-relay-cursor-connection": "^3.1.0",
"@hookform/resolvers": "^3.3.1",
"@master/css": "2.0.0-beta.173",
"@master/css-renderer": "2.0.0-beta.160",
"@master/css.react": "2.0.0-beta.173",
"@nestjs/apollo": "^12.0.9",
"@nestjs/common": "^10.0.2",
"@nestjs/config": "^3.1.1",
"@nestjs/core": "^10.0.2",
"@nestjs/graphql": "^12.0.9",
"@nestjs/jwt": "^10.1.1",
"@nestjs/passport": "^10.0.2",
"@nestjs/platform-express": "^10.0.2",
"@nx/devkit": "16.9.1",
"@nx/next": "16.8.1",
"@prisma/client": "^5.4.1",
"@radix-ui/themes": "2.0.0-rc.3",
"@swc/helpers": "~0.5.2",
"apollo-server-plugin-base": "^3.7.2",
"axios": "^1.0.0",
"bcrypt": "^5.1.1",
"class-validator": "^0.14.0",
"graphql": "^16.8.1",
"graphql-query-complexity": "^0.12.0",
"graphql-scalars": "^1.22.4",
"nestjs-prisma": "^0.22.0",
"next": "13.4.1",
"passport": "^0.6.0",
"passport-jwt": "^4.0.1",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-hook-form": "^7.46.2",
"recoil": "^0.7.7",
"reflect-metadata": "^0.1.13",
"rxjs": "^7.8.0",
"tslib": "^2.3.0",
"zod": "3.21.4"
},
"devDependencies": {
"@babel/core": "^7.14.5",
"@babel/preset-react": "^7.14.5",
"@commitlint/cli": "^17.7.1",
"@commitlint/config-conventional": "^17.7.0",
"@faker-js/faker": "^8.1.0",
"@graphql-codegen/cli": "^5.0.0",
"@graphql-codegen/typescript-operations": "^4.0.1",
"@graphql-codegen/typescript-react-apollo": "^4.0.0",
"@nestjs/schematics": "^10.0.1",
"@nestjs/testing": "^10.0.2",
"@nx/eslint-plugin": "16.8.1",
"@nx/jest": "16.9.1",
"@nx/js": "16.9.1",
"@nx/linter": "16.8.1",
"@nx/nest": "^16.9.1",
"@nx/node": "16.9.1",
"@nx/playwright": "16.8.1",
"@nx/plugin": "16.9.1",
"@nx/react": "16.8.1",
"@nx/storybook": "16.8.1",
"@nx/vite": "16.8.1",
"@nx/web": "16.8.1",
"@nx/webpack": "16.9.1",
"@nx/workspace": "16.8.1",
"@playwright/test": "^1.36.0",
"@storybook/addon-essentials": "7.4.3",
"@storybook/addon-interactions": "^7.2.1",
"@storybook/addon-links": "^7.4.4",
"@storybook/addon-onboarding": "^1.0.8",
"@storybook/addon-styling": "^1.3.7",
"@storybook/addon-themes": "^7.4.4",
"@storybook/core-server": "7.4.3",
"@storybook/jest": "~0.1.0",
"@storybook/react": "7.4.3",
"@storybook/react-vite": "7.4.3",
"@storybook/test-runner": "^0.13.0",
"@storybook/testing-library": "~0.2.0",
"@swc-node/register": "~1.4.2",
"@swc/cli": "~0.1.62",
"@swc/core": "~1.3.85",
"@testing-library/dom": "^9.3.3",
"@testing-library/jest-dom": "^6.1.3",
"@testing-library/react": "14.0.0",
"@testing-library/user-event": "^14.5.1",
"@types/jest": "^29.4.0",
"@types/node": "18.14.2",
"@types/react": "18.2.14",
"@types/react-dom": "18.2.6",
"@types/testing-library__jest-dom": "^6.0.0",
"@typescript-eslint/eslint-plugin": "^5.60.1",
"@typescript-eslint/parser": "^5.60.1",
"babel-jest": "^29.4.1",
"commitizen": "^4.3.0",
"cz-git": "^1.7.1",
"eslint": "~8.46.0",
"eslint-config-next": "13.4.1",
"eslint-config-prettier": "8.1.0",
"eslint-plugin-import": "2.27.5",
"eslint-plugin-jsx-a11y": "6.7.1",
"eslint-plugin-playwright": "^0.15.3",
"eslint-plugin-react": "7.32.2",
"eslint-plugin-react-hooks": "4.6.0",
"husky": "^8.0.3",
"jest": "^29.4.1",
"jest-environment-jsdom": "^29.4.1",
"jest-environment-node": "^29.4.1",
"lint-staged": "^14.0.1",
"nx": "16.8.1",
"prettier": "^2.6.2",
"prisma": "^5.4.1",
"ts-jest": "^29.1.0",
"ts-morph": "^20.0.0",
"ts-node": "10.9.1",
"typescript": "~5.1.3",
"vite": "~4.3.9"
},
"config": {
"commitizen": {
"path": "cz-git"
}
},
"prisma": {
"seed": "ts-node apps/iron-ecommerce-server/prisma/seed.ts"
}
}
謝謝大大