快速上手
本地使用
可以在项目中使用以下命令安装。
pnpm i @lcap/nasl-unified-frontend-generator
游乐场
上手NASL前端翻译器最快的办法就是使用游乐场(nasl-frontend-generator-playground):一边自定义编译过程,一边查看它对样例应用翻译出来的制品的影响。
配方
列举一些常见需求的实现方法:
自定义目录结构
在ProjectOrganizerPlugin中,存在着一个moveFilesAsYouWant
方法。这个方法会在项目布局阶段被调用。
这个方法的默认实现会将src/__views__
文件夹下的文件统一移动到src/pages
文件夹下。
你可以通过直接修改ProjectOrganizerPlugin插件的原型来覆写此方法,将翻译器生成出来的文件移动到你想要的位置。
src/__views__
文件夹下的所有文件都移动到src/views
下ProjectOrganizerPlugin.prototype.moveFilesAsYouWant = async function (placedFiles: PlacedFile[]) {
for (const f of placedFiles) {
const originalViewFolderMark = `/__views__/`;
if (f.originalAbsolutePath.includes(originalViewFolderMark)) {
const to = f.getFolder().replace(originalViewFolderMark, '/views/');
f.moveFileTo(to);
}
}
}
修改 package.json
package.json
描述了整个前端应用的依赖、启动命令等等。在定制源码时,可能会有添加依赖、调整依赖版本、自定义构建命令的需求。
接下来,我们将展示如何通过插件的方式修改package.json
。
创建一个名为NpmPackageJSONPlugin
的新的插件。在这个插件里,你可以通过注入FileSystemProvider
以获得读写虚拟文件系统的能力,并提供patch
方法,将虚拟文件系统中的package.json
读出、修改并写回。
// playground/customization/npm-package-plugin.ts
import "@abraham/reflection";
import {
GeneratorInfrastructureDomain,
ServiceMetaKind,
} from "@lcap/nasl-unified-frontend-generator";
import { inject, injectable } from "inversify";
import { merge } from "lodash";
export type PackageJSON = Record<string, any>;
export const NpmPackageJSONPluginSymbol = Symbol.for("NpmPackageJSONPlugin");
@injectable()
export class NpmPackageJSONPlugin {
constructor(
@inject(ServiceMetaKind.FileSystemProvider)
private fileSystemProvider: GeneratorInfrastructureDomain.FileSystemProvider
) {}
private packageJSONPath = "/package.json";
private readPkg(): PackageJSON | undefined {
try {
const pkg = this.fileSystemProvider.read(this.packageJSONPath);
if (pkg) {
return JSON.parse(pkg);
}
} catch (error) {}
return undefined;
}
private writePkg(pkg: PackageJSON) {
this.fileSystemProvider.write(
this.packageJSONPath,
JSON.stringify(pkg, null, 2)
);
}
patch(obj: {}) {
const pkg = this.readPkg() ?? {};
// recursively merge obj to pkg
merge(pkg, obj);
this.writePkg(pkg);
}
}
添加自定义ESLint配置
在NpmPackageJSONPlugin
的基础上,我们可以创建一个新的插件MyEslintPlugin
来自定义ESLint配置:
MyEslintPlugin
插件需要继承LifeCycleHooksPlugin
,在其中实现afterAllFilesGenerated
方法。afterAllFilesGenerated
是生命周期钩子,在代码生成结束后会被代码生成器自动地调用。这个钩子适合用来对文件系统的内容做一些修改。- 在
afterAllFilesGenerated
中,调用NpmPackageJSONPlugin
来写入ESLint配置文件和.eslintignore
文件。
// playground/customization/custom-eslint.ts
import {
LifeCycleHooksPlugin,
ServiceMetaKind,
GeneratorInfrastructureDomain,
Logger,
} from "@lcap/nasl-unified-frontend-generator";
import dedent from "dedent";
import { injectable, inject, Container } from "inversify";
import {
NpmPackageJSONPluginSymbol,
NpmPackageJSONPlugin,
} from "./npm-package-plugin";
export function setupEslint(container: Container) {
@injectable()
class MyEslintPlugin extends LifeCycleHooksPlugin {
constructor(
@inject(ServiceMetaKind.FileSystemProvider)
protected fileSystemProvider: GeneratorInfrastructureDomain.FileSystemProvider,
@inject(NpmPackageJSONPluginSymbol)
private npmPackageJSONPlugin: NpmPackageJSONPlugin
) {
super(fileSystemProvider);
}
afterAllFilesGenerated(): void {
const logger = Logger("自定义Lint插件");
logger.info("写入eslint配置");
this.fileSystemProvider.write(
"/eslint.config.mjs",
dedent`
// @ts-check
import eslint from '@eslint/js';
import tseslint from 'typescript-eslint';
export default tseslint.config(
eslint.configs.recommended,
...tseslint.configs.recommended,
);
`
);
this.fileSystemProvider.write(
"/.eslintignore",
dedent`
src/packages/**/*.js
`
);
logger.info("在package.json中添加依赖");
this.npmPackageJSONPlugin.patch({
devDependencies: {
eslint: "8.57.0",
typescript: "5.4.3",
"typescript-eslint": "7.4.0",
},
});
logger.info("在package.json中添加eslint启动脚本");
this.npmPackageJSONPlugin.patch({
scripts: {
lint: "pnpm eslint --fix",
},
});
const res = this.fileSystemProvider.read("/package.json");
logger.info(res, "package.json内容");
}
}
container
.bind<NpmPackageJSONPlugin>(NpmPackageJSONPluginSymbol)
.to(NpmPackageJSONPlugin);
container
.bind<GeneratorInfrastructureDomain.CodeGenerationLifecycleHooks>(
ServiceMetaKind.CodeGenerationLifecycleHooks
)
.to(MyEslintPlugin);
return container;
}
在playground/index.ts
中,引入刚刚实现的setupEslint
,并在翻译流程中调用:
// playground/index.ts
import { makeDefaultContainer } from "@lcap/nasl-unified-frontend-generator";
import { startDemoTranslation } from "@lcap/nasl-unified-frontend-generator/playground";
import { setupEslint } from "./customization/custom-eslint";
Promise.resolve()
// 创建新的默认插件容器
.then(makeDefaultContainer)
// 新增eslint配置和脚本
.then(setupEslint)
// 在做完插件修改后,使用此时的插件容器启动Demo的翻译
.then(startDemoTranslation);
将页面组件拆分为逻辑和视图
ReactStateManagerMobxPlugin
插件负责React下MobX状态管理的能力,被ReactCodeGenPlugin
插件依赖。
其拥有一个produceHooks
方法,用来在React组件中输出所有用到的Hooks。
为了将用到的Hooks和页面视图拆成两个文件,可以进行如下修改:
- 修改
produceHooks
方法,使其返回一个ReactFragment
而不是原来的ReactHook[]
。 - 在
ReactCodeGenPlugin
中,需要处理produceHooks
方法返回的ReactFragment
:- 修改
ReactFragment.fromComponentIR
方法 - 修改
ReactCodeGenPlugin
的genReactComponent
方法
- 修改
下一步...
- API 参考:前端翻译器的所有API。