Bỏ qua để đến nội dung

Nx Generator Generator

Thêm một Nx Generator vào dự án TypeScript, giúp bạn tự động hóa các tác vụ lặp đi lặp lại như tạo scaffold cho các component hoặc thực thi các cấu trúc dự án cụ thể.

Bạn có thể tạo một generator theo hai cách:

  1. Cài đặt Nx Console VSCode Plugin nếu bạn chưa cài đặt
  2. Mở Nx Console trong VSCode
  3. Nhấp Generate (UI) trong phần "Common Nx Commands"
  4. Tìm kiếm @aws/nx-plugin - ts#nx-generator
  5. Điền các tham số bắt buộc
    • Nhấp Generate
    Tham số Kiểu Mặc định Mô tả
    project Bắt buộc string - Dự án TypeScript để thêm generator vào. Chúng tôi khuyến nghị sử dụng generator ts#nx-plugin để tạo dự án này.
    name Bắt buộc string - Tên generator
    description string - Mô tả về generator của bạn
    directory string - Thư mục trong thư mục nguồn của dự án plugin để thêm generator vào (mặc định: <name>)

    Generator sẽ tạo các tệp dự án sau trong project đã cho:

    • Thư mụcsrc/<name>/
      • schema.json Schema cho đầu vào của generator
      • schema.d.ts Các kiểu TypeScript cho schema
      • generator.ts Triển khai generator cơ bản
      • generator.spec.ts Các bài kiểm tra cho generator
      • README.md Tài liệu cho generator
    • generators.json Cấu hình Nx để định nghĩa các generator
    • package.json Được tạo hoặc cập nhật để thêm mục “generators”
    • tsconfig.json Được cập nhật để sử dụng CommonJS

    Sửa đổi dự án

    Generator này sẽ cập nhật project đã chọn để sử dụng CommonJS, vì Nx Generators hiện chỉ hỗ trợ CommonJS (tham khảo GitHub issue này để biết về hỗ trợ ESM).

    Chọn dự án nx-plugin cục bộ của bạn khi chạy generator ts#nx-generator, và chỉ định tên cùng với thư mục và mô tả tùy chọn.

    Tệp schema.json định nghĩa các tùy chọn mà generator của bạn chấp nhận. Nó tuân theo định dạng JSON Schema với các phần mở rộng đặc biệt của Nx.

    Một tệp schema.json có cấu trúc cơ bản như sau:

    {
    "$schema": "https://json-schema.org/schema",
    "$id": "YourGeneratorName",
    "title": "Your Generator Title",
    "description": "Description of what your generator does",
    "type": "object",
    "properties": {
    // Các tùy chọn generator của bạn ở đây
    },
    "required": ["requiredOption1", "requiredOption2"]
    }

    Đây là một ví dụ đơn giản với một số tùy chọn cơ bản:

    {
    "$schema": "https://json-schema.org/schema",
    "$id": "ComponentGenerator",
    "title": "Create a Component",
    "description": "Creates a new React component",
    "type": "object",
    "properties": {
    "name": {
    "type": "string",
    "description": "Component name",
    "x-priority": "important"
    },
    "directory": {
    "type": "string",
    "description": "Directory where the component will be created",
    "default": "src/components"
    },
    "withTests": {
    "type": "boolean",
    "description": "Whether to generate test files",
    "default": true
    }
    },
    "required": ["name"]
    }

    Bạn có thể tùy chỉnh các lời nhắc hiển thị khi chạy generator thông qua CLI bằng cách thêm thuộc tính x-prompt:

    "name": {
    "type": "string",
    "description": "Component name",
    "x-prompt": "What is the name of your component?"
    }

    Đối với các tùy chọn boolean, bạn có thể sử dụng lời nhắc yes/no:

    "withTests": {
    "type": "boolean",
    "description": "Whether to generate test files",
    "x-prompt": "Would you like to generate test files?"
    }

    Đối với các tùy chọn có một tập hợp các lựa chọn cố định, sử dụng enum để người dùng có thể chọn từ một trong các tùy chọn.

    "style": {
    "type": "string",
    "description": "The styling approach to use",
    "enum": ["css", "scss", "styled-components", "none"],
    "default": "css"
    }

    Một mẫu phổ biến là cho phép người dùng chọn từ các dự án hiện có trong workspace:

    "project": {
    "type": "string",
    "description": "The project to add the component to",
    "x-prompt": "Which project would you like to add the component to?",
    "x-dropdown": "projects"
    }

    Thuộc tính x-dropdown: "projects" cho Nx biết để điền vào dropdown với tất cả các dự án trong workspace.

    Bạn có thể cấu hình các tùy chọn để được truyền dưới dạng đối số vị trí khi chạy generator từ dòng lệnh:

    "name": {
    "type": "string",
    "description": "Component name",
    "x-priority": "important",
    "$default": {
    "$source": "argv",
    "index": 0
    }
    }

    Điều này cho phép người dùng chạy generator của bạn như nx g your-generator my-component thay vì nx g your-generator --name=my-component.

    Sử dụng thuộc tính x-priority để chỉ ra các tùy chọn nào quan trọng nhất:

    "name": {
    "type": "string",
    "description": "Component name",
    "x-priority": "important"
    }

    Các tùy chọn có thể có mức độ ưu tiên là "important" hoặc "internal". Điều này giúp Nx sắp xếp các thuộc tính trong tiện ích mở rộng Nx VSCode và Nx CLI.

    Bạn có thể cung cấp giá trị mặc định cho các tùy chọn:

    "directory": {
    "type": "string",
    "description": "Directory where the component will be created",
    "default": "src/components"
    }

    Để biết thêm chi tiết về schemas, tham khảo tài liệu Nx Generator Options.

    Cùng với schema.json, generator tạo một tệp schema.d.ts cung cấp các kiểu TypeScript cho các tùy chọn generator của bạn:

    export interface YourGeneratorSchema {
    name: string;
    directory?: string;
    withTests?: boolean;
    }

    Interface này được sử dụng trong triển khai generator của bạn để cung cấp type safety và code completion:

    import { YourGeneratorSchema } from './schema';
    export default async function (tree: Tree, options: YourGeneratorSchema) {
    // TypeScript biết các kiểu của tất cả các tùy chọn của bạn
    const { name, directory = 'src/components', withTests = true } = options;
    // ...
    }

    Sau khi tạo generator mới như trên, bạn có thể viết triển khai của mình trong generator.ts.

    Một generator là một hàm biến đổi một hệ thống tệp ảo (Tree), đọc và ghi các tệp để thực hiện các thay đổi mong muốn. Các thay đổi từ Tree chỉ được ghi vào đĩa khi generator kết thúc thực thi, trừ khi nó được chạy ở chế độ “dry-run”. Một generator trống trông như sau:

    export const myGenerator = async (tree: Tree, options: MyGeneratorSchema) => {
    // Sử dụng tree để áp dụng các thay đổi
    };
    export default myGenerator;

    Dưới đây là một số thao tác phổ biến bạn có thể muốn thực hiện trong generator của mình:

    // Đọc một tệp
    const content = tree.read('path/to/file.ts', 'utf-8');
    // Ghi một tệp
    tree.write('path/to/new-file.ts', 'export const hello = "world";');
    // Kiểm tra xem tệp có tồn tại không
    if (tree.exists('path/to/file.ts')) {
    // Làm gì đó
    }

    Bạn có thể tạo các tệp bằng tiện ích generateFiles từ @nx/devkit. Điều này cho phép bạn định nghĩa các template theo cú pháp EJS, và thay thế các biến.

    import { generateFiles, joinPathFragments } from '@nx/devkit';
    // Tạo các tệp từ templates
    generateFiles(
    tree,
    joinPathFragments(__dirname, 'files'), // Thư mục template
    'path/to/output', // Thư mục đầu ra
    {
    // Các biến để thay thế trong templates
    name: options.name,
    nameCamelCase: camelCase(options.name),
    nameKebabCase: kebabCase(options.name),
    // Thêm các biến khác nếu cần
    },
    );

    Bạn có thể sử dụng GritQL để tìm kiếm và biến đổi mã nguồn một cách khai báo trong các generator của bạn. GritQL hỗ trợ nhiều ngôn ngữ bao gồm TypeScript, JavaScript, Python, HCL (Terraform), và nhiều hơn nữa — vì vậy bạn có thể sử dụng cùng một cú pháp pattern trên toàn bộ stack của mình.

    Nx Plugin for AWS cung cấp hai helper:

    • applyGritQL(tree, filePath, pattern) — áp dụng một pattern viết lại GritQL vào một tệp và trả về Promise<boolean> cho biết liệu có thay đổi nào được thực hiện hay không
    • matchGritQL(tree, filePath, pattern) — kiểm tra xem một pattern GritQL có khớp ở bất kỳ đâu trong tệp hay không và trả về Promise<boolean>
    import { applyGritQL, matchGritQL } from '@aws/nx-plugin/sdk/utils/ast';
    // Thay thế một lời gọi hàm
    await applyGritQL(
    tree,
    'src/app.ts',
    '`console.log($msg)` => `logger.info($msg)`',
    );
    // Thêm một phần tử vào mảng chỉ khi chưa có
    await applyGritQL(
    tree,
    'src/plugins.ts',
    '`plugins: [$items]` => `plugins: [$items, myPlugin()]` where { $items <: not contains `myPlugin` }',
    );
    // Kiểm tra xem một pattern có tồn tại trước khi thực hiện thay đổi
    if (!(await matchGritQL(tree, filePath, '`import { Auth } from "./auth"`'))) {
    // Thêm import
    }

    Các pattern GritQL cũng hoạt động trên các tệp không phải TypeScript. Thêm tiền tố language <name> vào pattern của bạn để nhắm đến các ngôn ngữ khác:

    // Python: thay thế các câu lệnh print bằng các lời gọi logging
    await applyGritQL(
    tree,
    'src/handler.py',
    'language python\n`print($msg)` => `logger.info($msg)`',
    );

    Các pattern GritQL sử dụng các đoạn mã được phân tách bằng backtick với $metavariables làm ký tự đại diện. Sử dụng => cho việc viết lại và các mệnh đề where cho các điều kiện.

    import { addDependenciesToPackageJson } from '@nx/devkit';
    // Thêm dependencies vào package.json
    addDependenciesToPackageJson(
    tree,
    {
    'new-dependency': '^1.0.0',
    },
    {
    'new-dev-dependency': '^2.0.0',
    },
    );
    import { formatFilesInSubtree } from '@aws/nx-plugin/sdk/utils/format';
    // Định dạng tất cả các tệp đã được sửa đổi
    await formatFilesInSubtree(tree, 'optional/path/to/format');
    import { readJson, updateJson } from '@nx/devkit';
    // Đọc một tệp JSON
    const packageJson = readJson(tree, 'package.json');
    // Cập nhật một tệp JSON
    updateJson(tree, 'tsconfig.json', (json) => {
    json.compilerOptions = {
    ...json.compilerOptions,
    strict: true,
    };
    return json;
    });

    Mở rộng một Generator từ Nx Plugin for AWS

    Phần tiêu đề “Mở rộng một Generator từ Nx Plugin for AWS”

    Bạn có thể import các generator từ Nx Plugin for AWS, và mở rộng hoặc kết hợp chúng theo ý muốn, ví dụ bạn có thể muốn tạo một generator xây dựng dựa trên một dự án TypeScript:

    import { tsProjectGenerator } from '@aws/nx-plugin/sdk/ts';
    export const myGenerator = async (tree: Tree, schema: MyGeneratorSchema) => {
    const callback = await tsProjectGenerator(tree, { ... });
    // Mở rộng generator dự án TypeScript ở đây
    // Trả về callback để đảm bảo dependencies được cài đặt.
    // Bạn có thể wrap callback nếu muốn thực hiện các thao tác bổ sung trong generator callback.
    return callback;
    };

    Bạn có thể sử dụng và mở rộng các generator mà chúng tôi sử dụng cho TypeScript clients và hooks theo cách tương tự như trên:

    import { openApiTsClientGenerator } from '@aws/nx-plugin/sdk/open-api';
    export const myGenerator = async (tree: Tree, schema: MyGeneratorSchema) => {
    await openApiTsClientGenerator(tree, { ... });
    // Thêm các tệp bổ sung ở đây
    };

    Chúng tôi cũng cung cấp một phương thức cho phép bạn xây dựng một cấu trúc dữ liệu có thể được sử dụng để lặp qua các operations trong một đặc tả OpenAPI và do đó điều khiển việc tạo mã của riêng bạn, ví dụ:

    import { buildOpenApiCodeGenerationData } from '@aws/nx-plugin/sdk/open-api.js';
    export const myGenerator = async (tree: Tree, schema: MyGeneratorSchema) => {
    const data = await buildOpenApiCodeGenerationData(tree, 'path/to/spec.json');
    generateFiles(
    tree,
    joinPathFragments(__dirname, 'files'), // Thư mục template
    'path/to/output', // Thư mục đầu ra
    data,
    );
    };

    Điều này cho phép bạn viết các template như:

    files/my-operations.ts.template
    export const myOperationNames = [
    <%_ allOperations.forEach((op) => { _%>
    '<%- op.name %>',
    <%_ }); _%>
    ];

    Tham khảo codebase trên GitHub để biết các ví dụ template phức tạp hơn.

    Bạn có thể chạy generator của mình theo hai cách:

    1. Cài đặt Nx Console VSCode Plugin nếu bạn chưa cài đặt
    2. Mở Nx Console trong VSCode
    3. Nhấp Generate (UI) trong phần "Common Nx Commands"
    4. Tìm kiếm @my-project/nx-plugin - my-generator
    5. Điền các tham số bắt buộc
      • Nhấp Generate

      Các bài kiểm tra đơn vị cho generators rất đơn giản để triển khai. Đây là một mẫu điển hình:

      import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
      import { yourGenerator } from './generator';
      describe('your generator', () => {
      let tree;
      beforeEach(() => {
      // Tạo một workspace tree trống
      tree = createTreeWithEmptyWorkspace();
      // Thêm bất kỳ tệp nào đã tồn tại trong tree
      tree.write(
      'project.json',
      JSON.stringify({
      name: 'test-project',
      sourceRoot: 'src',
      }),
      );
      tree.write('src/existing-file.ts', 'export const existing = true;');
      });
      it('should generate expected files', async () => {
      // Chạy generator
      await yourGenerator(tree, {
      name: 'test',
      // Thêm các tùy chọn bắt buộc khác
      });
      // Kiểm tra rằng các tệp đã được tạo
      expect(tree.exists('src/test/file.ts')).toBeTruthy();
      // Kiểm tra nội dung tệp
      const content = tree.read('src/test/file.ts', 'utf-8');
      expect(content).toContain('export const test');
      // Bạn cũng có thể sử dụng snapshots
      expect(tree.read('src/test/file.ts', 'utf-8')).toMatchSnapshot();
      });
      it('should update existing files', async () => {
      // Chạy generator
      await yourGenerator(tree, {
      name: 'test',
      // Thêm các tùy chọn bắt buộc khác
      });
      // Kiểm tra rằng các tệp hiện có đã được cập nhật
      const content = tree.read('src/existing-file.ts', 'utf-8');
      expect(content).toContain('import { test } from');
      });
      it('should handle errors', async () => {
      // Mong đợi generator ném ra lỗi trong các điều kiện nhất định
      await expect(
      yourGenerator(tree, {
      name: 'invalid',
      // Thêm các tùy chọn sẽ gây ra lỗi
      }),
      ).rejects.toThrow('Expected error message');
      });
      });

      Các điểm chính để kiểm tra generators:

      • Sử dụng createTreeWithEmptyWorkspace() để tạo một hệ thống tệp ảo
      • Thiết lập bất kỳ tệp tiên quyết nào trước khi chạy generator
      • Kiểm tra cả việc tạo các tệp mới và cập nhật các tệp hiện có
      • Sử dụng snapshots cho nội dung tệp phức tạp
      • Kiểm tra các điều kiện lỗi để đảm bảo generator của bạn thất bại một cách nhẹ nhàng

      Bạn cũng có thể sử dụng ts#nx-generator để tạo scaffold cho một generator trong @aws/nx-plugin.

      Khi generator này được chạy trong repository của chúng tôi, nó sẽ tạo các tệp sau cho bạn:

      • Thư mụcpackages/nx-plugin/src/<name>/
        • schema.json Schema cho đầu vào của generator
        • schema.d.ts Các kiểu TypeScript cho schema
        • generator.ts Triển khai generator
        • generator.spec.ts Các bài kiểm tra cho generator
      • Thư mụcdocs/src/content/docs/guides/
        • <name>.mdx Trang tài liệu cho generator
      • packages/nx-plugin/generators.json Được cập nhật để bao gồm generator của bạn

      Sau đó bạn có thể bắt đầu triển khai generator của mình.