- 代码生成
- 开发工具
- 后端
- 前端
【免费下载链接】generator-jhipster
JHipster is a development platform to quickly generate, develop, & deploy modern web applications & microservice architectures.
JHipster 的生成器(generator)本质上是一系列按“优先级(priority)”排队执行的任务流,而任务如何被声明、如何避免被意外排队,直接决定了生成器代码的可读性与健壮性。本篇基于官方 RFC《JHipster-RFC-3: Unambiguous priorities API》展开,结合当前仓库中generators/base-core/priorities.ts等源码实现,为你完整讲解:为什么传统get initializing()记法有歧义、新的get [INITIALIZING_PRIORITY]()常量记法如何解决该问题,以及这套设计在当前 JHipster 代码库中最终如何落地。读完你将掌握优先级常量的定义、队列前缀机制与迁移要点,能够编写符合 JHipster 标准、无歧义且可被 Blueprint 继承的生成器任务。
背景与动机:传统优先级声明方式的歧义
JHipster 的生成器建立在 Yeoman 的优先级模型之上。在传统写法中,每个优先级都是一个以优先级名命名的 getter,例如:
get initializing() { return { initializingTask() { this._sayHello(); } } } _sayHello() { console.log('hello'); } aTaskQueuedAtDefaultPriority() { console.log('I am being executed, why? I am a default priority task.'); }这段看似正常的代码存在两个问题(见 RFC 原文):
- 普通方法被意外排队:只要一个类成员函数的名字不带
_前缀,JHipster/Yeoman 就会把它当作default优先级的任务自动排队执行。上面的aTaskQueuedAtDefaultPriority()只是一个普通辅助函数,却会在生成流程中被悄悄执行——开发者常常对此感到困惑(RFC 原文中那句 "why?" 正是这一困惑的写照)。 - 不符合 JavaScript 惯例:JavaScript 社区约定
_前缀表示"私有方法",非_前缀表示普通类成员。传统记法把"是否被调度执行"建立在_前缀上,违背了这一惯例,导致类成员与任务之间的界限模糊。
RFC 的结论是:JHipster 的每个优先级都有明确用途(workflow is clear),优先级之外的任务既无必要也弊大于利,因此不应再有任何任务被默认排队到default优先级之外。我们需要一种无歧义的声明方式。
方案设计:用常量名 + 计算属性 getter 声明优先级
RFC 提出的核心方案是:用常量作为 getter 的名字(computed property name),常量的值带#前缀:
get [INITIALIZING_PRIORITY]() { return { initializingTask() { this.sayHello(); } } } sayHello() { console.log('hello'); } anOrdinaryClassMember() { console.log('I am not being executed, why? I am just an ordinary function.'); }对照传统写法,新记法的变化一目了然:
| 关注点 | 传统记法 | RFC-3 新记法 |
|---|---|---|
| 优先级 getter 命名 | get initializing() | get [INITIALIZING_PRIORITY]() |
| 常量取值 | 无 | 带#前缀,如#initializing |
| 普通类成员 | 必须加_前缀才不会被调度 | 保持原名即可,天然不会被调度 |
| 辅助方法 | _sayHello() | sayHello() |
新记法把"这是一个优先级任务"这一语义显式编码进常量名中:INITIALIZING_PRIORITY一看便知是优先级标识;而initializing、sayHello这些名字被释放出来,可以放心地作为普通类成员使用,无需再依赖_前缀约定。RFC 指出,JHipster 的模块化生成器(modular generators)在当时已经用新记法实现,证明了其可行性。
从 RFC 到实现:优先级常量与队列前缀在源码中的落地
RFC-3 在仓库中最终落地为generators/base-core/priorities.ts与generators/base-core/generator.ts两处核心实现。值得注意的是,实际实现中的前缀字符与 RFC 提案略有差异:RFC 建议使用#前缀(#initializing),而当前源码使用>作为优先级前缀、jhipster:作为队列前缀:
// generators/base-core/priorities.ts export const PRIORITY_PREFIX = '>'; export const QUEUE_PREFIX = 'jhipster:';完整优先级清单
generators/base-core/priorities.ts定义了完整的优先级名称集合,分为两类:
Yeoman 传统优先级:initializing、prompting、configuring、default、writing、transform、install、end。
JHipster 自定义优先级(通过jhipster:前缀队列注入 Yeoman 调度流程):
composing/composingComponent:组合其他生成器loading:加载应用配置composingBootstrap:组合引导生成器preparing/postPreparing:准备阶段及其后处理multistepTransform:多步模板变换postWriting:写文件后处理preConflicts:冲突解决前处理postInstall:安装后处理
这些名字被统一导出为PRIORITY_NAMES、PRIORITY_NAMES_LIST与QUEUES,并组合成CUSTOM_PRIORITIES数组(源码中通过.reverse()控制插入顺序,保证队列按声明顺序排列),供 Yeoman 在调度时按序执行。
静态常量生成
generators/base-core/generator.ts中通过asPriority工具函数把优先级名转换为带前缀的常量:
// generators/base-core/generator.ts const asPriority = (priorityName: string) => `${PRIORITY_PREFIX}${priorityName}`; static readonly INITIALIZING = asPriority(INITIALIZING); static readonly PROMPTING = asPriority(PROMPTING); static readonly CONFIGURING = asPriority(CONFIGURING); // ... 以及 COMPOSING、LOADING、PREPARING、DEFAULT、WRITING、 // POST_WRITING、INSTALL、POST_INSTALL、END 等于是每个优先级都对应一个>前缀的常量值(如>initializing),开发者可以直接用BaseGenerator.asPriority(name)或静态常量来声明自己的优先级 getter。
实体层面的扩展优先级
在generators/base-application/priorities.ts中,基础优先级进一步扩展出面向实体生成流程的细粒度优先级,同样遵循jhipster:队列前缀约定:
configuringEachEntity:逐个配置实体loadingEntities:加载实体preparingEachEntity/preparingEachEntityField/preparingEachEntityRelationship:逐个准备实体、字段、关系postPreparingEachEntitywritingEntities/postWritingEntitiesloadingTranslations:加载翻译资源
这些实体级队列通过before字段精确插入到基础优先级的间隙中(例如configuringEachEntity在loadingEntities之前、postPreparingEachEntity在default之前),并由ENTITY_PRIORITY_NAMES与QUEUES导出,体现了"模块化生成器"按阶段拆分任务的架构思想(可参见 RFC-6 文件结构提案)。
实战示例:新记法在生成器中的真实用法
仓库中generators/bootstrap/generator.ts是采用新记法的真实范例。它先通过静态方法生成优先级常量,再用计算属性 getter声明优先级任务:
// generators/bootstrap/generator.ts const MULTISTEP_TRANSFORM_PRIORITY = BaseGenerator.asPriority(MULTISTEP_TRANSFORM); const PRE_CONFLICTS_PRIORITY = BaseGenerator.asPriority(PRE_CONFLICTS); static readonly MULTISTEP_TRANSFORM = MULTISTEP_TRANSFORM_PRIORITY; static readonly PRE_CONFLICTS = PRE_CONFLICTS_PRIORITY; get multistepTransform(): Record<string, (this: this) => unknown> { return { queueMultistepTransform() { this.queueMultistepTransform(); }, }; } get [MULTISTEP_TRANSFORM_PRIORITY]() { return this.multistepTransform; }这个例子演示了新记法的完整模式:
- 用
BaseGenerator.asPriority(...)生成>multistepTransform常量; - 写一个普通类成员
multistepTransform(没有_前缀,也不会被误调度); - 用
get [MULTISTEP_TRANSFORM_PRIORITY]()把它注册到对应优先级队列中。
对比传统记法,普通方法multistepTransform与优先级 getter 职责分离、语义清晰,完全消除了"非_前缀方法被默认排队"的隐患。
迁移与影响:破坏性变更及 Blueprint 适配
RFC 明确指出这是一个破坏性变更(breaking change):
- 传统生成器必须切换到常量记法;在 JHipster 7 中迁移时,不能直接使用
#前缀(RFC 原文:they must not use the#prefix at JHipster 7),需要遵循当前仓库实际采用的>前缀方案。 - Blueprint(蓝图)作为基于 JHipster 基类扩展的生成器,必须同步采用相同模式(Blueprints will have to adopt the same pattern),否则其优先级 getter 将无法被正确识别。
- 若同时维护新旧两套生成器,要注意队列调度语义的差异:传统记法下任何裸方法都会进入
default队列,而新记法下只有显式声明为优先级 getter 的任务才会被执行。
从源码结构看,generators/base-core/generator.ts中静态常量的集中定义,为 Blueprint 提供了一致的引用入口(BaseGenerator.INITIALIZING等),这也正是 RFC 所追求的"更传统的类实现记法"——让生成器类回归普通 JavaScript 类的表达方式。
总结与展望
RFC-3 的价值在于把"任务的调度语义"从命名约定(_前缀)迁移到显式声明(常量 + 计算属性),从而:
- 消除普通类成员被意外排队的歧义;
- 让生成器代码遵循 JavaScript 标准惯例;
- 以
PRIORITY_NAMES/QUEUES/asPriority等统一抽象,支撑 JHipster 模块化生成器与 Blueprint 生态的持续演进。
当前仓库的priorities.ts、generator.ts与bootstrap/generator.ts完整实现了 RFC 的设计意图(前缀由#演变为>属于实现细节的迭代)。对于希望深入理解或二次开发 JHipster 生成器的开发者,建议按以下路径继续研读源码:
- RFC-3 完整提案
- 基础优先级定义与队列表
- 优先级静态常量与 asPriority 实现
- 实体级优先级扩展
- 新记法实战范例(bootstrap 生成器)
- 模块化生成器文件结构 RFC-6
- 代码生成
- 开发工具
- 后端
- 前端
【免费下载链接】generator-jhipster
JHipster is a development platform to quickly generate, develop, & deploy modern web applications & microservice architectures.
相关推荐
Parler-TTS深度解析:如何用交叉熵损失与延迟模式掩码打造高质量语音合成?
Parler TTS深度解析:如何用交叉熵损失与延迟模式掩码打造高质量语音合成? 你是否曾想过,一个开源项目如何仅用600M参数就能生成媲美商业产品的自然语音?
语音AI 应用深度学习nghttp2 服务器侧流优先级 API 详解:nghttp2_session_change_extpri_stream_priority 与 RFC 9218 可扩展优先级
nghttp2 服务器侧流优先级 API 详解:nghttp2_session_change_extpri_stream_priority 与 RFC 9218
可观测性云原生nghttp2_session_change_stream_priority 详解:已弃用的 RFC 7540 优先级接口与 RFC 9218 迁移指南
nghttp2_session_change_stream_priority 详解:已弃用的 RFC 7540 优先级接口与 RFC 9218 迁移指南 导读
可观测性云原生
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考