☰
Middleman 集成 Ember CLI 子应用:基于 ember-cli-app fixture 的外部管道实战指南
2026/10/15 16:18:04 网站建设 项目流程
  • 前端
  • 开发工具

【免费下载链接】middleman

Hand-crafted frontend development

项目地址:https://gitcode.com/gh_mirrors/mi/middleman
点击查看免费下载

本文围绕 Middleman 仓库内middleman-core/fixtures/ember-cli-app这一真实集成示例展开,以其中的 Ember 子应用test-app/README.md为骨架,完整讲解「在一个 Middleman 静态站点项目中嵌入并驱动一个独立 Ember CLI 应用」的安装、运行、测试、构建全流程,并深入到external_pipeline扩展的源码实现与错误处理机制。读完本文,你将掌握如何用一条config.rb配置把任意 Node 系前端构建工具(Ember CLI、Brunch 等)的产物自动并入 Middleman 的 sitemap 与构建流程。

场景背景:为什么 Middleman 里会有一个 Ember 子应用

Middleman 的核心定位是"手作(hand-crafted)前端开发"的静态站点生成器,它天然擅长 ERB/Slim/Haml 模板、Sass/CoffeeScript 编译和内容管线的编排。但遇到 Ember 这类重度依赖 JavaScript 运行时与模块化构建工具的 SPA 框架时,两者的构建体系无法直接融合——Ember 应用需要自己的编译、打包、测试链路,而 Middleman 需要的是最终可发布的静态产物。

Middleman 的解法是external_pipeline(外部管道)扩展:把 Ember 构建流程当作一个"外部命令"交给系统执行,再把它的输出目录作为资源源挂载进 Middleman 的 sitemap。仓库中的 ember-cli-app fixture 正是这一思路的最小可运行样板:一个 Middleman 站点根目录下嵌套一个完整的test-app/Ember 工程。

middleman-core/fixtures/ember-cli-app/ ├── config.rb # Middleman 侧:激活 external_pipeline ├── source/javascripts/file.js # Middleman 自己的静态资源 └── test-app/ # 完整的 Ember CLI 工程(本文主角) ├── README.md # Ember 子应用的协作文档 ├── app/ config/ public/ tests/ ├── Brocfile.js bower.json package.json testem.json └── ...

一、test-app/README.md:一个标准 Ember CLI 应用的协作文档骨架

被关联文档 test-app/README.md 本质上是早期 ember-cli 脚手架生成的工程 README 模板,它用最精炼的方式概括了一个 Ember 应用的四个核心生命周期:安装、运行、测试、构建。这些命令不仅适用于该 fixture,也适用于任何基于 ember-cli 的项目协作场景。

1. 安装(Installation)

# git clone 获取仓库代码后,依次安装两类依赖 git clone <this-repository> npm install # 安装 Node 侧构建工具链(ember-cli、broccoli 等) bower install # 安装前端运行依赖(ember、handlebars、jquery、qunit 等)

从 package.json 可以看到这种"双依赖"分工的实际形态:

  • devDependencies中锁定的是构建工具链:ember-cli: 0.0.39、broccoli-ember-hbs-template-compiler: ^1.5.0、broccoli-asset-rev: 0.0.11、express: ^4.1.1等,并声明"engines": { "node": ">= 0.10.0" }——这是一个 2014 年前后的早期 ember-cli 时代样板,Node 版本要求很低,现代环境只需将工具链替换为当前版本即可沿用同一套集成思路;
  • bower.json 中则是运行时前端库:ember: 1.5.1、handlebars: ~1.3.0、jquery: ^1.11.1,以及测试栈qunit: ~1.12.0、ember-qunit: ~0.1.5。

该package.json还预置了三个 npm scripts(start、build、test),分别映射到ember server、ember build、ember test,与 README 中的命令一一对应。

2. 运行(Running)

ember server # 浏览器访问 http://localhost:4200

ember server会启动 ember-cli 自带的开发服务器与文件监听器,默认监听 4200 端口。应用入口由 app/app.js 定义:创建Ember.Application实例、配置modulePrefix: 'test-app'并通过loadInitializers加载初始化器;路由由 app/router.js 声明,其location类型取自环境配置TestAppENV.locationType。环境配置集中在 config/environment.js:

var ENV = { environment: environment, baseURL: '/', locationType: 'auto', EmberENV: { FEATURES: { /* 实验特性开关 */ } }, APP: { /* 应用实例参数 */ } }; if (environment === 'development') { // 开启模块解析、视图查找等调试日志 ENV.LOG_MODULE_RESOLVER = true; ENV.APP.LOG_ACTIVE_GENERATION = true; ENV.APP.LOG_VIEW_LOOKUPS = true; // ... } if (environment === 'production') { // 生产环境可在此关闭调试日志、开启压缩等 }

该文件是一个返回函数的 Node 模块,按development/production等环境名返回不同的配置对象,这也是 Middleman 侧config.rb会向ember命令传递环境参数的原因(见第三节)。

3. 运行测试(Running Tests)

ember test # 单次运行全部测试 ember test --server # 启动测试服务器,文件变化时自动重跑

测试基础设施由三个文件协同:

  • tests/test-helper.js 负责把ember-qunit的 resolver 注册进 QUnit,并写入#ember-testing容器;
  • tests/helpers/start-app.js 是测试辅助器:合并测试默认属性(rootElement: '#ember-testing'、关闭调试日志)、通过Router.reopen({ location: 'none' })关闭路由跳转、最后在Ember.run中创建应用并注入测试辅助方法;
  • testem.json 声明测试运行器 Testem 的配置:"framework": "qunit"、测试页面tests/index.html,CI 环境用 PhantomJS 无头运行,本地开发环境则在 PhantomJS 与 Chrome 中执行。

4. 构建(Building)

ember build

ember build将应用打包输出到test-app/dist/目录——正是这一产物目录,成为 Middleman 外部管道要"接入"的关键交接点。构建的打包逻辑在 Brocfile.js 中定义:它通过ember-cli/lib/broccoli/ember-app创建EmberApp实例,并以app.toTree()导出最终的 Broccoli 构建树;文件中还注释说明了app.import的用法——引入额外库、按环境区分资源、以及导入 AMD/ES6 模块。

二、Middleman 侧:external_pipeline 如何"接管"Ember 产物

test-app/README.md描述的是一套完全独立的 Ember 工作流,而让它与 Middleman 产生联动的是父级 config.rb 中的四行配置:

activate :external_pipeline, name: :ember, command: "cd test-app/ && ember #{build? ? :build : :serve} --environment #{config[:environment]}", source: "test-app/dist", latency: 2

逐项拆解其语义(对应源码 external_pipeline.rb 中的 option 定义):

选项示例值源码定义作用说明
name:emberrequired: true管道唯一名称,用于日志标识与多实例区分(该扩展supports_multiple_instances = true,可同时挂多个外部工具)
commandcd test-app/ && ember ...required: true要执行的初始化命令,在子进程中运行
sourcetest-app/distrequired: true外部命令的输出目录,会被挂载为 Middleman 的资源源,合并进 sitemap
latency2默认0.25文件监听器轮询该目录的刷新间隔(秒),用于感知外部构建产物更新
disable_background_execution—默认false置为true时不在后台线程运行命令(如用于同步执行场景)
ignore_exit_code—默认false置为true时忽略外部命令的非零退出码,不中断 Middleman 进程

值得特别说明的是command中的动态拼装:build?是 Middleman 提供的构建态判断方法,config[:environment]则是当前环境名。因此:

  • 运行middleman server(开发预览)时,实际执行cd test-app/ && ember serve --environment development;
  • 运行middleman build(正式构建)时,实际执行cd test-app/ && ember build --environment production。

这个"一条命令两态复用"的写法正是该 fixture 的精髓——Ember 侧不需要任何 Middleman 感知,环境信息通过命令行参数单向传递。

源码视角:external_pipeline 的初始化与监听

在 external_pipeline.rb 的initialize中,管道启动后依次做了四件事:

  1. 确保 source 目录存在:FileUtils.mkdir_p(source_path),否则后续文件监听会直接抛错;
  2. 注册文件监听:app.files.watch :source, path: source_path, latency: options[:latency], frontmatter: false——把test-app/dist作为不解析 frontmatter 的纯资源源接入 Middleman 的 files 体系,这样其产物会像普通source/文件一样进入 sitemap 与构建输出;
  3. 挂载热重载钩子:app.reload(&method(:reload!)),当 Middleman 配置变化触发重载时,会先stop当前正在运行的外部命令子进程(reload!);
  4. 启动外部命令:构建模式(app.build?)或显式禁用后台执行时同步运行watch_command!(false)并立即poll_once!收集一次产物;开发模式下则以后台线程方式运行watch_command!(true)。

开发态 vs 构建态的行为差异

这是external_pipeline最值得理解的分支逻辑(external_pipeline.rb):

if app.build? || options[:disable_background_execution] watch_command!(false) # 同步:命令跑完后继续 @watcher.poll_once! # 立即同步一次资源源 else watch_command!(true) # 异步:后台线程持续托管子进程 end
  • 开发预览:ember serve常驻后台,其 stdout 会被逐行转发到 Middleman 日志(前缀== External:),dist/目录的每次更新由 latency 间隔的文件监听捕捉,实现"改 Ember 代码 → 增量编译 → Middleman 预览即时反映"的接近实时体验;
  • 正式构建:ember build同步执行完毕、产物落盘后再进入 Middleman 自己的模板渲染与资源处理流程,保证最终build/目录里包含完整的 Ember 应用静态产物。

失败即停:非零退出码的硬性处理

外部命令的成败直接决定整个构建的成败(external_pipeline.rb):

if !options[:ignore_exit_code] && !@current_thread.exitstatus.nil? && @current_thread.exitstatus != 0 logger.error '== External: Command failed with non-zero exit status' exit(1) end

当外部命令以非零状态退出时,Middleman 会记录错误并以状态码 1 终止进程;若命令根本不存在(Errno::ENOENT),同样会报错退出。这一行为有明确的测试用例背书:仓库的 builder.feature 中有Scenario: Build external_pipeline errors,其验证样本 external-pipeline-error/config.rb 故意配置了一条必然失败的命令:

activate :external_pipeline, name: :failing, command: "mv does-not-exist tmp/file.js", source: "tmp", latency: 2

期望结果即为Then the exit status should be 1——这从端到端特性测试层面锁定了"外部管道失败 → 构建失败"的契约。

三、一条完整的集成工作流

将以上两部分串起来,一个「Middleman + Ember CLI」项目的完整日常流程如下:

# 1. 安装依赖(首次) cd test-app && npm install && bower install && cd .. # 2. 开发:启动 Middleman 预览,外部管道自动拉起 ember serve middleman server # 访问 http://localhost:4567(Middleman 默认端口) # 修改 test-app/app/ 下的源码 → ember 增量编译 → dist/ 更新 → 页面即时刷新 # 3. 独立调试 Ember 侧(可选) cd test-app && ember test --server # 浏览器内持续跑单测 # 4. 构建发布:ember build 与 middleman build 联动 middleman build # 输出 build/ 目录,其中包含 dist/ 合并进来的完整 Ember 应用

四、实践要点与注意事项

  • 依赖必须先装齐:external_pipeline只负责执行命令,不负责安装依赖。首次克隆后必须先在test-app/内完成npm install与bower install,否则ember命令不可用,构建会以非零退出码失败;
  • 产物目录约定:source指向的dist/由 ember-cli 的ember build生成,若改动构建输出目录,需同步修改 config.rb 中的source值;
  • 版本差异适配:该 fixture 锁定的是早期工具链(ember-cli 0.0.39、ember 1.5.1、Broccoli 构建体系)。现代项目沿用此模式时,应把command调整为当前 ember-cli 的实际命令(现代写法为ember build/ember serve,产物默认仍在dist/),其余管道机制完全不变;
  • 失败要可见:不要把ignore_exit_code随意置为true,否则外部编译错误会被静默吞掉,产出残缺的静态站点;
  • 多工具并存:由于扩展支持多实例(supports_multiple_instances = true),可以在同一 config.rb 中按name区分,同时挂接多个外部前端构建流程。

结语

从 test-app/README.md 的四组命令,到 config.rb 的四行管道配置,再到 external_pipeline.rb 的完整实现,这个 fixture 完整演示了 Middleman 与重型 JS 框架共存的最佳实践:各司其职、以产物为界——Ember 负责应用逻辑与打包,Middleman 负责内容与整体站点的静态化输出,两者通过外部管道与dist/目录解耦协作。这套模式不仅限于 Ember,任何能产出静态目录的前端构建工具都可以按同样的方式接入 Middleman 的生态。

  • 前端
  • 开发工具

【免费下载链接】middleman

Hand-crafted frontend development

项目地址:https://gitcode.com/gh_mirrors/mi/middleman
点击查看免费下载

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询