OpenProject 前端开发指南:Angular 与 Rails 混合架构的构建、测试与插件体系
OpenProject 前端开发指南Angular 与 Rails 混合架构的构建、测试与插件体系【免费下载链接】openprojectOpenProject is the leading open source project management software for product, project and portfolio management. A powerful Jira alternative with agile planning, issue tracking, roadmaps, Gantt charts, time tracking, collaboration features, and more. Available on premises or in the cloud. ⭐ Star us on GitHub项目地址: https://gitcode.com/GitHub_Trending/op/openprojectOpenProject 采用 Rails Angular 的混合架构Rails 负责服务端渲染与业务逻辑Angular基于 Vite/esbuild 的 CLI承载工作包Work Packages等 SPA 模块。本文以 frontend/doc/README.md 为主线系统讲解本地开发服务器的代理机制、AOT 编译与生产构建、前端测试策略以及社区插件的前端联动方案并深入对应源码验证实现细节帮助你快速上手 OpenProject 前端开发。OpenProject 的官方文档将前端开发体系拆分为 README.md总览、TESTING.md测试、STYLING.md样式、PLUGINS.md插件四份文档本文按同一脉络组织并结合仓库源码逐一印证。前端架构总览Angular SPA 与 Rails 的协作模式OpenProject 是一个混合型应用大部分页面由 Rails 服务端渲染含少量遗留 directive而工作包、看板等 SPA 模块则使用 Angular 独立实现。Angular 前端代码全部位于frontend/src由 Angular CLI 负责编译与本地服务CLI 在开发环境使用 Vite、在生产环境使用 esbuild见 frontend/package.json 中的angular/cli与 frontend/angular.json 中angular-builders/custom-esbuild构建器。两个进程的协作关系如下Angular CLI 开发服务器默认运行在http://localhost:4200作为代理服务器将无法处理的请求转发给 Rails 服务器。Rails 服务器默认运行在http://localhost:3000会反过来把静态资源请求转发给 CLI 代理。也就是说开发时请求链是双向的浏览器访问 Rails 页面时页面中的前端资源polyfills.js、main.js、styles.css由 Rails 视图动态指向 CLI 代理从内存中即时获取编译产物而 SPA 发出的/api与媒体请求则由 CLI 代理回传给 Rails。这正是 frontend/src/proxy.conf.mjs 所配置的内容CLI 将/api和/assets/frontend/media上下文代理到http://localhost:3000。你可以始终通过http://localhost:3000访问 Rails 服务器。若想跳过代理直接让 Rails 从磁盘读取静态资源只需向 Rails 进程传入空的环境变量OPENPROJECT_CLI_PROXY rails server当代理被禁用时Rails 会回退到磁盘上的资源例如rake assets:precompile的输出产物这正是生产环境的运行方式。底层实现FrontendAssetHelper 的资源路由代理行为的核心实现在 app/helpers/frontend_asset_helper.rbCLI_DEFAULT_PROXY begin host ENV.fetch(FE_HOST, localhost) port ENV.fetch(FE_PORT, 4200) http://#{host}:#{port} end CLI_PROXY ENV.fetch(OPENPROJECT_CLI_PROXY, CLI_DEFAULT_PROXY) def self.assets_proxied? ENV[OPENPROJECT_DISABLE_DEV_ASSET_PROXY].blank? !Rails.env.production? cli_proxy.present? end关键点如下代理地址默认取http://localhost:4200但可通过FE_HOST与FE_PORT覆盖与npm run serve脚本中读取的环境变量保持一致见下文OPENPROJECT_CLI_PROXY为空字符串时cli_proxy.present?为假代理随即失效Rails 改从磁盘加载资源若想彻底禁用开发代理即使环境变量非空可设置OPENPROJECT_DISABLE_DEV_ASSET_PROXY生产环境Rails.env.production?永远不会启用代理。视图通过include_frontend_assets输出polyfills.js与main.js两个模块脚本并链接styles.cssinclude_spot_assets则链接 Primer 的spot.css。资源路径由variable_asset_path决定代理模式下拼接http://host:port/assets/frontend/file非代理模式则调用OpenProject::Assets.lookup_asset从 manifest 中查找带哈希的文件名。从源码可以推断assets_proxied?的判断逻辑保证了三种工作模式互不干扰开发代理、预编译磁盘资源、生产静态文件。启动开发环境前端开发服务器npm run servecd frontend npm run serve该命令的完整定义为见 frontend/package.jsonserve: PORT${FE_PORT:-4200} node --max_old_space_size8192 ./node_modules/angular/cli/bin/ng serve --host ${FE_HOST:-localhost} --port ${FE_PORT:-4200} --serve-path ${RAILS_RELATIVE_URL_ROOT}/assets/frontend注意--serve-path参数CLI 将前端资源挂载在/assets/frontend路径下与 Rails 侧frontend_asset_path拼接出的/assets/frontend/file完全对应两者通过这一约定完成寻址。Rails 服务器在项目根目录启动 Railsrails server如前所述Rails 默认把前端资源请求代理给 4200 端口上的 CLI。整个开发循环为修改 Angular 源码 → CLI 增量重编译 → 刷新浏览器即可看到变化无需手动重启 Rails。编译模式AOT 与生产构建Ahead-of-Time 编译AOT开发模式下默认关闭 AOT 编译以加快增量构建速度。需要强制开启时执行npm run serve --aot而在生产构建中ng build --prod默认启用 AOT该行为由 frontend/angular.json 的构建配置决定。从 frontend/angular.json 可以看到更细的生产配置优化optimization对样式与字体开启压缩脚本优化关闭哈希outputHashing: all产物文件名带哈希配合assets:rebuild_manifest生成的 manifest 供 Rails 查找环境替换fileReplacements将src/environments/environment.ts替换为environment.prod.ts输出目录../public/assets/frontend即 Rails 的public/assets/frontendCommonJS 警告白名单allowedCommonJsDependencies声明了moment-timezone、dragula、tablesorter等允许的依赖。生产构建的两种触发方式Rake 任务推荐前后端一体化rake assets:precompile手动 npm 构建仅编译 Angularcd frontend npm run buildrake assets:precompile会同时编译 legacy 与 Angular 前端。其内部流程见 lib/tasks/assets.rake 的assets:angular任务先调用openproject:plugins:register_frontend链接插件前端再进入frontend目录执行npm run build最后执行assets:rebuild_manifest重建资源清单。npm run build使用ng build --configuration production --named-chunks --source-map并设置了较大的 Node 堆内存上限--max_old_space_size4096以应对大型应用的编译需求。前端测试Frontend SpecsOpenProject 的测试策略与其混合架构一致Angular 前端模块服务、组件使用 frontend specs 进行单元测试Rails 侧另有完整的集成与特性测试。当前仓库的测试运行器已从文档时代的 karma-jasmine 演进为基于 Vitest 的ng test见 frontend/package.json 的test脚本与 devDependencies 中的vitest、playwright。常用命令cd frontend npm test # 单次运行等价于 ng test --watchfalse npm run test:watch # 监听模式等价于 ng test --watchtrue只有以.spec.ts结尾的文件会被匹配并编译。无依赖服务的隔离测试范例对于没有依赖的服务直接实例化类即可完成隔离测试。仓库中的典型示例是 frontend/src/app/core/current-project/current-project.service.spec.ts它通过TestBed.configureTestingModule注册被测服务CurrentProjectService并用apiV3Stub替换掉ApiV3Service依赖beforeEach(() { TestBed.configureTestingModule({ providers: [ CurrentProjectService, PathHelperService, { provide: ApiV3Service, useValue: apiV3Stub }, ], }); currentProject TestBed.inject(CurrentProjectService); }); describe(with no meta present, () { it(returns null values, () { expect(currentProject.id).toBeNull(); expect(currentProject.identifier).toBeNull(); expect(currentProject.name).toBeNull(); // ... }); });该测试通过 stub 隔离网络层验证服务在页面 meta 缺失时返回空值的边界行为是典型的无依赖服务直接测试模式。测试相关的完整方法论集成测试与单元测试的取舍、legacy 与 Angular 的测试边界见 frontend/doc/TESTING.md。插件前端联动注册与开发流程OpenProject Community 版本包含一些自带前端的插件如 Costs、My Project Page。开发这类插件时必须将其前端链接到主应用让 Legacy 与 Angular 前端都能发现并处理它们。完整指南见 frontend/doc/PLUGINS.md。第一步在 Gemfile.plugins 中声明插件将插件克隆到本地后在Gemfile.plugins中通过 path 引用group :opf_plugins do gem openproject-costs, path: ../plugins/openproject-costs end然后执行bundle install此时插件已被 Rails 应用识别但其前端尚未链接。第二步注册前端在运行任何前端构建或 CLI 命令之前先执行 rake 任务./bin/rake openproject:plugins:register_frontend该任务定义于 lib/tasks/plugins.rake它会调用OpenProject::Plugins::FrontendLinking.regenerate!将带有 Angular 模块frontend/module/main.ts导出的插件 symlink 到frontend/src/app/features/plugins/linked/目录。从源码实现lib/open_project/plugins/frontend_linking/generator.rb可以还原完整的注册流程读取 Bundler 中属于:opf_plugins组的 gemload_known_opf_plugins筛选出存在frontend/module目录的插件all_frontend_plugins将其 symlink 到frontend/src/app/features/plugins/linked/插件名create_frontend_plugins_links使用FileUtils.ln_sf筛选出存在frontend/module/main.ts的插件通过 ERB 模板 linked-plugins.module.ts.erb 生成frontend/src/app/features/plugins/linked-plugins.module.ts注册表筛选出提供global_styles.*的插件生成对应的 Sass 注册文件。注册表模块的生成逻辑位于 lib/open_project/plugins/frontend_linking/erb_context.rb其中importable_name将插件名转换为可导入的模块名例如openproject-costs→OpenprojectCosts。生成的模块形如import {NgModule} from angular/core; import {PluginModule as OpenprojectCosts} from ./linked/openproject-costs/main; NgModule({ imports: [ OpenprojectCosts, ], }) export class LinkedPluginsModule { }仓库中保留了生成的示例文件 linked-plugins.module.ts.example 与 linked-plugins.styles.sass.example该路径已从文档所述app/modules/plugins迁移到app/features/plugins从源码结构看是目录重构所致。另外注意rake assets:precompile内部也会先执行该注册任务lib/tasks/assets.rake确保生产构建时插件前端被正确纳入。第三步编写插件入口模块以 Costs 插件为例其frontend/目录结构为module ├── main.ts └── wp-display ├── costs-by-type-display-field.module.ts └── currency-display-field.module.tsAngular 前端入口为frontend/module/main.ts必须导出一个名为PluginModule的 ngModule类名PluginModule是约定不可省略并在构造函数中执行初始化逻辑export function initializeCostsPlugin() { window.OpenProject.getPluginContext() .then((pluginContext:OpenProjectPluginContext) { // Register a field type to the core EditField functionality pluginContext.services.editField.extendFieldType(select, [Budget]); // Register a hook callback for a specific core hook pluginContext.hooks.workPackageSingleContextMenu(function(params:any) { return { key: log_costs, icon: icon-projects, indexBy: function(actions:any) { var index _.findIndex(actions, {key: log_time}); return index ! -1 ? index 1 : actions.length; }, resource: workPackage, link: logCosts }; }); }); } NgModule({ providers: [ ], }) export class PluginModule { // The name PluginModule is important! constructor() { initializeCostsPlugin(); } }该示例展示了插件与核心交互的两种方式通过pluginContext.services.editField.extendFieldType(select, [Budget])为编辑字段注册新的字段类型通过pluginContext.hooks.workPackageSingleContextMenu(...)向工作包右键菜单注入记录成本菜单项。插件上下文getPluginContext的实现位于 frontend/src/app/features/plugins/plugin-context.ts核心前端通过 hook-service.ts 提供钩子注册能力该服务也带有配套的 hook-service.spec.ts 测试。样式体系与 Living Styleguide前端样式与 Living Styleguide 强耦合styleguide 使用与生产应用完全相同的 Sass 文件通过 Asset 管道额外构建一份 CSS 用于展示指南页面。因此你在 styleguide 中看到的效果即应用的真实效果一份样式两处生效。所有 OpenProject 样式位于app/assets/stylesheets注意不是frontend目录——前端文件夹本身不含业务样式只含渲染产物与 styleguide 自身样式这一约定保证了 Rails 渲染的 legacy 页面与 Angular 组件视觉一致。完整说明见 frontend/doc/STYLING.md。使用 styleguidestyleguide 随 Rails 开发服务器提供http://localhost:3000/styleguide它是一个展示各组件的长 HTML 页面基础文件为styleguide.html。约定如下每个 Sass partial 配一个同名的 Markdown 文件.lsg描述其用法例如_accounts.sass对应_accounts.lsg。一个 Sass partial 理想情况下只对应一个组件但并非总是如此——例如_work_packages.sass描述的是整个工作包区域而非单一组件。CSS 命名约定Sass 代码尽量遵循模块化 CSS 类名命名约定由myabc引入。目前样式主要按组件分组为 Sass partial仍存在不少历史遗留代码尤其是插件中的旧样式核心的 legacy 代码集中在app/assets/stylesheets/_misc_legacy.sass。在 Angular 侧frontend/angular.json 的样式配置与之呼应组件内联样式语言为 sassinlineStyleLanguage: sass全局样式入口包括src/styles.scss与src/spot.scss并允许从src/assets/sass目录导入 Sass 文件stylePreprocessorOptions.includePaths。依赖管理package-lock 与 npm ciOpenProject 使用package-lock.json锁定运行时而非开发依赖。变更依赖时的规范新增或移除依赖时使用npm install它会同步更新 lockfilepackage-lock.json与package.json的任何改动应一并提交若只想按 lockfile 精确安装而不更新它使用npm ci此外仓库在postinstall阶段运行patch-package见 frontend/package.json用于应用第三方依赖的本地补丁因此首次npm ci后会自动完成补丁应用。开发资源速查以下是本文涉及的前端文档与关键源码的索引便于按需深入主题文档关键实现开发总览与构建frontend/doc/README.mdapp/helpers/frontend_asset_helper.rb、frontend/angular.json、frontend/package.json测试frontend/doc/TESTING.mdfrontend/src/app/core/current-project/current-project.service.spec.ts样式frontend/doc/STYLING.mdapp/assets/stylesheets含_misc_legacy.sass、frontend/angular.json 的 styles 配置插件联动frontend/doc/PLUGINS.mdlib/open_project/plugins/frontend_linking/generator.rb、linked-plugins.module.ts.erb、lib/tasks/plugins.rake、plugin-context.ts资源编译管线—lib/tasks/assets.rake掌握上述流程后你可以完整走通启动双服务器开发 → 编写 Angular 组件与测试 → 接入插件前端 → 执行生产构建的整条链路。对于深入调试代理与资源路径的场景建议重点研读 app/helpers/frontend_asset_helper.rb 中assets_proxied?与环境变量的组合关系对于插件开发则以 lib/open_project/plugins/frontend_linking/generator.rb 的四个筛选与生成步骤为入口逐层展开。【免费下载链接】openprojectOpenProject is the leading open source project management software for product, project and portfolio management. A powerful Jira alternative with agile planning, issue tracking, roadmaps, Gantt charts, time tracking, collaboration features, and more. Available on premises or in the cloud. ⭐ Star us on GitHub项目地址: https://gitcode.com/GitHub_Trending/op/openproject创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

相关新闻

Cloudflare Agents MCP 客户端能力实战:在原生 Durable Object 中组合 MCPClientManager

Cloudflare Agents MCP 客户端能力实战:在原生 Durable Object 中组合 MCPClientManager

Cloudflare Agents MCP 客户端能力实战:在原生 Durable Object 中组合 MCPClientManager 【免费下载链接】agents Build and deploy AI Agents on Cloudflare 项目地址: https://gitcode.com/GitHub_Trending/agents1/agents 导读 本文基于开源仓库 agents…

2026/9/18 21:42:07 阅读更多 →
通达信指数强度副图指标编写实战:从公式语法到买卖信号

通达信指数强度副图指标编写实战:从公式语法到买卖信号

简介:这份通达信指标公式教程,围绕「指数强度副图指标」源码展开,面向需要对比个股与大盘相对强弱的投资者,以及希望学习公式编写的通达信用户。文档以逐行解析方式讲解核心逻辑:先设定120日高低点周期与5日均线参数&a…

2026/9/18 21:41:06 阅读更多 →
LeetCode 1004 最大连续 1 的个数 III:基于滑动窗口的一次遍历解法(含换皮题模型总结)

LeetCode 1004 最大连续 1 的个数 III:基于滑动窗口的一次遍历解法(含换皮题模型总结)

LeetCode 1004 最大连续 1 的个数 III:基于滑动窗口的一次遍历解法(含换皮题模型总结) 【免费下载链接】leetcode LeetCode Solutions: A Record of My Problem Solving Journey.( leetcode题解,记录自己的leetcode解题之路。) …

2026/9/18 21:41:06 阅读更多 →

最新新闻

论文生成软件怎么选?DeepSeek V3.5 的模型通道改到 TaoToken

论文生成软件怎么选?DeepSeek V3.5 的模型通道改到 TaoToken

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/18 22:30:42 阅读更多 →
BERT模型原理与实战:从NLP基础到应用优化

BERT模型原理与实战:从NLP基础到应用优化

1. 项目概述今天咱们来聊聊自然语言处理(NLP)这个让机器"读懂"人类语言的神奇领域,特别是近年来大火的BERT模型。作为一名在NLP领域摸爬滚打多年的从业者,我见证了从传统机器学习方法到深度学习,再到Transformer架构的革命性突破。…

2026/9/18 22:30:42 阅读更多 →
2026毕业AI论文工具红黑榜|实测避雷!别再乱用工具挂科

2026毕业AI论文工具红黑榜|实测避雷!别再乱用工具挂科

2026年高校论文重复率AIGC双重审核全面落地,很多同学论文查重合格,却因为AI痕迹超标、文献造假、格式不规范直接退回重写。市面上AI论文工具五花八门,通用AI、国外科研工具、单一查重工具各有短板,盲目使用只会耽误毕业进度。今天…

2026/9/18 22:30:42 阅读更多 →
实时数字人从零跑通:LiveTalking 用 Wav2Lip 口型模型完成首推流的完整路径

实时数字人从零跑通:LiveTalking 用 Wav2Lip 口型模型完成首推流的完整路径

实时数字人从零跑通:LiveTalking 用 Wav2Lip 口型模型完成首推流的完整路径 【免费下载链接】metahuman-stream Real time interactive streaming digital human 项目地址: https://gitcode.com/GitHub_Trending/me/metahuman-stream 部署流式数字人&#xf…

2026/9/18 22:30:42 阅读更多 →
react-hook-form 快速上手与验证机制全解析:安装、register 注册、handleSubmit 提交与内置校验规则实战指南

react-hook-form 快速上手与验证机制全解析:安装、register 注册、handleSubmit 提交与内置校验规则实战指南

react-hook-form 快速上手与验证机制全解析:安装、register 注册、handleSubmit 提交与内置校验规则实战指南 【免费下载链接】react-hook-form 📋 React Hooks for form state management and validation (Web React Native) 项目地址: https://gitc…

2026/9/18 22:30:42 阅读更多 →
Security-101 基础设施安全核心概念:安全卫生、安全态势管理、补丁管理与容器安全实践

Security-101 基础设施安全核心概念:安全卫生、安全态势管理、补丁管理与容器安全实践

Security-101 基础设施安全核心概念:安全卫生、安全态势管理、补丁管理与容器安全实践 【免费下载链接】Security-101 8 Lessons, Kick-start Your Cybersecurity Learning. 项目地址: https://gitcode.com/GitHub_Trending/se/Security-101 课程定位&#xf…

2026/9/18 22:29:41 阅读更多 →

日新闻

Matlab手写逻辑回归:从数学原理到多变量概率预测模型实现

Matlab手写逻辑回归:从数学原理到多变量概率预测模型实现

很多朋友第一次看到"逻辑回归"这四个字,第一反应就是——这玩意儿是个回归模型吧?我当年也是在Matlab里跑完一段代码,看着输出的0.73、0.86这种概率值,才回过神来:这家伙其实是披着回归外衣的分类神器&#…

2026/9/18 0:00:28 阅读更多 →
高值医用耗材研报PDF:用Python完成字段抽取、清洗与趋势预测

高值医用耗材研报PDF:用Python完成字段抽取、清洗与趋势预测

简介:这份报告是2023-2028年高值医用耗材行业调研及发展前景趋势预测报告,面向医疗器械企业管理者、投资机构、行业研究人员及关注政策变化的从业者,用于把握行业监管动向、市场格局与未来趋势。报告以PDF格式呈现,共1个文件、整体…

2026/9/18 0:00:28 阅读更多 →
三维高斯场赋能世界模型:几何语义蒸馏与机器人决策实战

三维高斯场赋能世界模型:几何语义蒸馏与机器人决策实战

先把我自己的背景交代一下:我之前在搞具身智能和机器人导航相关的项目,很长一段时间里都被“环境表示”这件事卡着。传统做法是用点云或者网格做几何建模,语义信息另外再跑分割模型,两套东西各管各的,时间一长就会发现…

2026/9/18 0:00:28 阅读更多 →

周新闻

AI SDK Harness 依赖更新指南:掌握 harness 包 SDK 依赖的升级、桥接同步与一致性校验

AI SDK Harness 依赖更新指南:掌握 harness 包 SDK 依赖的升级、桥接同步与一致性校验

AI SDK Harness 依赖更新指南:掌握 harness 包 SDK 依赖的升级、桥接同步与一致性校验 【免费下载链接】ai The AI Toolkit for TypeScript. From the creators of Next.js, the AI SDK is a free open-source library for building AI-powered applications and ag…

2026/9/16 19:03:19 阅读更多 →
Refine v5 Ant Design NumberField 组件实战:基于 Intl 的本地化数字格式化

Refine v5 Ant Design NumberField 组件实战:基于 Intl 的本地化数字格式化

Refine v5 Ant Design NumberField 组件实战:基于 Intl 的本地化数字格式化 【免费下载链接】refine A React Framework for building internal tools, admin panels, dashboards & B2B apps with unmatched flexibility. 项目地址: https://gitcode.com/GitH…

2026/9/17 7:57:36 阅读更多 →
Flutter应用改名全指南:从Android到iOS的配置与工具实践

Flutter应用改名全指南:从Android到iOS的配置与工具实践

刚接一个外包项目时,甲方要求把工程里临时用的应用名改成正式产品名。我本来觉得“改名”这种小事,打开配置文件改一行不就完了?结果真动手才发现,Flutter项目里“应用名称”根本不是一处配置,而是一整套散落在 Androi…

2026/9/17 10:19:14 阅读更多 →

月新闻

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能分类:[AI/大模型]细分主题:AI 增强型 CI/CD 流水线自动化与 GitOps 实践:Agent 工作流、工具调用与任务拆解:从原型到生产的验收清单很多团队在尝试用大…

2026/9/16 22:31:27 阅读更多 →
容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场分类:[工程技术]细分主题:Kubernetes 生产环境运维与排障实战:可复制的项目复盘模板与决策记录大部分团队的事故复盘报告,最后都变成了躺在 Confluence 或钉…

2026/9/15 21:39:18 阅读更多 →
容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步分类:[工程技术]细分主题:Docker 容器化技术与镜像安全管理:核心链路的逐步实现与关键代码取舍面对一个积累了五六年历史包袱的单体架构应用(包含 Web 接口、后台…

2026/9/16 22:32:59 阅读更多 →