- 前端
- 开发工具
【免费下载链接】middleman
Hand-crafted frontend development
本文围绕 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:4200ember 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 buildember 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 | :ember | required: true | 管道唯一名称,用于日志标识与多实例区分(该扩展supports_multiple_instances = true,可同时挂多个外部工具) |
command | cd test-app/ && ember ... | required: true | 要执行的初始化命令,在子进程中运行 |
source | test-app/dist | required: true | 外部命令的输出目录,会被挂载为 Middleman 的资源源,合并进 sitemap |
latency | 2 | 默认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中,管道启动后依次做了四件事:
- 确保 source 目录存在:
FileUtils.mkdir_p(source_path),否则后续文件监听会直接抛错; - 注册文件监听:
app.files.watch :source, path: source_path, latency: options[:latency], frontmatter: false——把test-app/dist作为不解析 frontmatter 的纯资源源接入 Middleman 的 files 体系,这样其产物会像普通source/文件一样进入 sitemap 与构建输出; - 挂载热重载钩子:
app.reload(&method(:reload!)),当 Middleman 配置变化触发重载时,会先stop当前正在运行的外部命令子进程(reload!); - 启动外部命令:构建模式(
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
相关推荐
在 Vercel 上部署 Ember 应用:基于 ember-v3 fixture 的完整指南
在 Vercel 上部署 Ember 应用:基于 ember v3 fixture 的完整指南 本文以本仓库 packages/static build/tes
CLI后端云原生Polly.JS Ember CLI Addon 实战指南:在 Ember 应用中集成 HTTP 录制与回放
Polly.JS Ember CLI Addon 实战指南:在 Ember 应用中集成 HTTP 录制与回放 本篇技术指南围绕 Polly.JS 官方提供的 @
测试开发工具Ember 5 应用开发与部署指南:基于 vercel 仓库 ember-v5 Fixture 的完整实战解读
Ember 5 应用开发与部署指南:基于 vercel 仓库 ember v5 Fixture 的完整实战解读 导读 :本文以 vercel 仓库中 packa
CLI后端云原生
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考