jquery-cookie 贡献指南:从环境搭建、构建与测试到提交 Pull Request 的完整开发工作流
jquery-cookie 贡献指南:从环境搭建、构建与测试到提交 Pull Request 的完整开发工作流
jquery-cookie 是一个简单、轻量的 jQuery 插件,用于读取、写入和删除 Cookie(README.md)。本文以仓库中的 CONTRIBUTING.md 为骨架,完整梳理该项目的贡献流程:从 Issue 报告规范、Pull Request 分支与测试要求,到基于 Node.js + Grunt + QUnit 的开发环境搭建、构建与测试命令,并结合 Gruntfile.js、package.json 与 test/ 下的真实实现,逐条讲清每条命令背后实际执行的构建任务。读完后,你可以在本地独立复现构建成功时 "Done, without errors." 的输出,在浏览器中运行带 Livereload 的 QUnit 测试套件,并按照项目规范提交一份携带测试用例的补丁。
项目背景:这份贡献文档对应的仓库现状
在开始贡献之前,有必要先了解仓库现状。README 首页明确标注:本项目已停止维护,并被 js-cookie 取代,新问题应提交到 js-cookie 项目(README.md)。当前仓库对应的是历史版本 v1.4.1(package.json),CHANGELOG.md 记录了从 1.1 到 1.4.1 的功能演进,例如 1.4.0 增加 AMD 支持、引入 $.removeCookie,1.4.1 增加 CommonJS 支持并修正了 expires 小数天数解析。
因此,本指南中的贡献流程主要适用于:维护历史版本、修复遗留 bug、为研究该插件实现而搭建本地开发环境,或作为向 js-cookie 迁移时对比参考的场景。贡献入口则按 CONTRIBUTING 所述统一走 GitHub 平台。
报告 Issue:把问题压缩成最小可复现示例
CONTRIBUTING.md 对 Issue 提交流程给出了两条要求:
- 在 GitHub Issues 中提交 bug 报告或功能请求(feature requests)。
- 如果报告的是 bug,请附带一个简化示例(simplified example),即短小、自包含、正确、可编译的示例,让维护者无需猜测即可复现问题。
"简化示例"这一要求并非空话,它对应着仓库中真实的测试组织方式:test/tests.js 里的每个测试用例本身就是"最小可复现示例"的模板。例如针对 IE 下畸形 Cookie 值(first=one; ; second=two 这类重复 "; " 分隔符)导致的读取问题(issue #88/#117),测试通过 iframe 加载 test/malformed_cookie.html,在其中用 Object.defineProperty 对 document.cookie 打桩,然后断言 $.cookie('second') 能正确返回 'two'(test/tests.js)。可见,一份好的 bug 报告应当像这些测试一样,把问题环境压缩到最小的可复现片段。
提交 Pull Request:分支、测试用例与构建三要素
CONTRIBUTING.md 对 Pull Request 提出三条硬性要求:
- 每次独立的变更新建一个 topic branch。不要在一个分支里混入多个无关改动,便于维护者逐项审查与回滚。
- 修复 bug 或实现重要功能时,必须创建对应的测试用例(test case)。这是保证插件长期行为稳定的关键,测试用例写在哪里、怎么写,下文会结合
test/tests.js详细展开。 - 确保构建成功运行。提交前必须执行完整的构建与测试任务链,具体命令见下文。
其中第 3 条的"构建"不是概念性的,而是有明确命令和明确成功标志的,这正是下一节开始要逐步讲清楚的内容。
开发工具链:QUnit、Node.js 与 Grunt 的分工
CONTRIBUTING.md 在 Development 一节列明了三件工具:
- QUnit:用于测试的单元测试框架。仓库中 test/index.html 引入了 QUnit 1.14.0 的 CSS 与 JS,以及 jQuery 1.11.1,再加载
../src/jquery.cookie.js和tests.js,构成完整的测试页面。 - Node.js:运行 Grunt 所需的运行时环境,按文档需要先行安装。
- Grunt:任务管理工具,负责把静态检查、测试、压缩、体积对比等步骤串成一条可重复执行的流水线。
从 package.json 的 devDependencies 可以看到这条流水线的真实依赖:grunt ~0.4.1 为骨架,grunt-contrib-jshint、grunt-contrib-uglify、grunt-contrib-qunit、grunt-contrib-watch、grunt-contrib-connect 分别承担检查、压缩、测试、监听和本地服务器,另有 grunt-compare-size(体积对比)与 grunt-saucelabs(跨浏览器云测试)。此外,仓库还通过 bower.json、component.json、cookie.jquery.json 声明了对包管理器的支持,CHANGELOG 1.4.1 一节提到同时支持 Jam、volo、Component、jspm 等分发渠道。
环境搭建:三条命令跑起构建与测试
CONTRIBUTING.md 的 Getting started 给出了从零开始的完整步骤,可归结为三步:
第 1 步:安装 Node.js。
第 2 步:全局安装 grunt-cli(提供 grunt 命令,让项目内的 Grunt 任务可以被执行):
$ npm install -g grunt-cli
第 3 步:进入项目根目录,安装开发依赖:
$ npm install -d
这里的 -d 表示安装 devDependencies(即上节列出的全部 Grunt 插件);如果你只想研究插件本身而不搭建开发环境,README.md 中的用法是通过 <script> 标签引入 src/jquery.cookie.js,无需任何 Node 环境。
第 4 步:执行构建与测试:
$ grunt
构建成功后,控制台会输出绿色的成功信息:
Done, without errors.
补充一个等价入口:package.json 中声明了 "test": "grunt"(package.json),因此在依赖安装完成后直接运行 npm test 的效果与 grunt 完全一致。另外注意 .jshintignore 将 build 与 node_modules 排除在静态检查之外,前者是构建产物目录,后者是本地依赖目录。
grunt 做了什么:default 任务链逐步拆解
grunt 之所以能一次性完成"检查 + 测试 + 构建",是因为 Gruntfile.js 把默认任务注册为一条四段任务链:
grunt.registerTask('default', ['jshint', 'qunit', 'uglify', 'compare_size']);
下面逐段拆解,看看每个任务在仓库中的真实配置。
jshint:源码与测试的静态检查
jshint 任务(Gruntfile.js)使用 jshintrc: true 读取项目根目录的 .jshintrc 配置,检查对象覆盖三部分:Gruntfile.js 本身、src/**/*.js(插件源码)以及 test/**/*.js(测试代码)。也就是说,贡献者新增的源码与测试用例都会在构建的第一步接受代码风格与语法检查。
qunit:以 test/index.html 为入口的测试套件
qunit 任务(Gruntfile.js)把 test/index.html 作为唯一测试入口,在构建过程中以无头(headless)方式自动执行整套 QUnit 测试,而不需要打开浏览器。该页面依次加载 QUnit、jQuery、../src/jquery.cookie.js 和 test/tests.js,页面内仅包含 QUnit 要求的 #qunit 与 #qunit-fixture 两个容器(test/index.html)。
uglify:产出压缩构建
uglify 任务(Gruntfile.js)把 src/jquery.cookie.js 压缩为 build/jquery.cookie-1.4.1.min.js(文件名中的版本号取自 pkg.version),并在产物头部注入 banner:/*! jquery.cookie v1.4.1 | MIT */。这正是插件分发给生产环境使用的压缩版本。
compare_size:体积与 gzip 对比
compare_size 任务(Gruntfile.js)对比压缩产物与未压缩源码的体积,并通过 gzip-js 计算两者 gzip 后的字节数,让维护者可以直观看到压缩收益,防止构建产物体积悄悄膨胀。
整条链路全部通过,才会输出 "Done, without errors."。如果其中任何一段失败(比如测试断言未通过、jshint 报错),构建即中断——这正是 CONTRIBUTING "Make sure the build runs successfully" 的可验证含义。
在浏览器中运行测试:grunt connect:tests
CONTRIBUTING.md 同时提供了浏览器中的测试方式——启动本地测试服务器:
$ grunt connect:tests
该命令对应的配置在 Gruntfile.js:服务器监听 9998 端口,站点根目录为 ['.', 'test'](即仓库根目录与 test 目录),启动后自动打开 http://127.0.0.1:9998,并且 keepalive 保持进程常驻、livereload 已启用。也就是说,只要保持该进程运行,浏览器页面会随测试文件变更自动刷新。
CONTRIBUTING.md 特别提醒:
建议在运行测试前清理浏览器中的所有 Cookie,这可以避免误报(false positive failures)。
为什么这条提醒如此重要?因为插件直接读写真实的 document.cookie(src/jquery.cookie.js),而测试用例(如 $.cookie() 读取全部 Cookie 的断言,见 test/tests.js)作用在同一个 Cookie 存储上。如果浏览器中残留了其他页面在同域写入的 Cookie,就可能让"读取全部 Cookie"或"读取指定 Cookie"的用例得到非预期结果。虽然 test/tests.js 的 teardown 会在每个用例后通过 $.each($.cookie(), $.removeCookie) 清理 Cookie(test/tests.js),但它只能清理测试运行时可见的 Cookie,无法覆盖测试开始前就已存在的残留,因此手动清理是规避误报最稳妥的手段。
自动构建:grunt watch
开发迭代场景下,每改一次文件都手动跑一次 grunt 很繁琐。CONTRIBUTING.md 提供了监听模式:
$ grunt watch
watch 任务的配置(Gruntfile.js)监听 {src,test}/**/*.js——即所有源码与测试文件——一旦发生变更,就自动重新执行 default 任务链(jshint → qunit → uglify → compare_size),并且同样开启了 livereload。这样,浏览器中已打开的测试页面会随用例的修改自动重跑,形成"改代码 → 自动测试 → 自动刷新"的闭环,非常适合配合 grunt connect:tests 一起使用。
为 Pull Request 编写测试用例:以 test/tests.js 为模板
CONTRIBUTING.md 要求"修复 bug 或实现重要功能时创建测试用例",那么测试用例应该写成什么样?仓库中 test/tests.js 就是现成的规范模板。
测试按功能分为四个模块(module),分别覆盖插件的四条核心能力:
- read(test/tests.js):读取语义,包括简单值、空值、不存在的 Cookie(返回
undefined)、RFC 2068 引号字符串、raw/json配置下的读取、加号转空格、畸形编码容错等。 - write(test/tests.js):写入语义,包括字符串原始值/包装对象、数字、
null、undefined、expires天数/小数/Date 对象三种写法、默认值与单次选项的优先级、写入返回值等。 - removeCookie(test/tests.js):删除语义,包括删除成功返回
true、删除不存在或删除失败的 Cookie 的行为、必须携带与写入时一致的path/domain/secure选项、不得修改传入的 options 对象等。 - conversion(test/tests.js):读取转换器,例如
$.cookie('c', Number)把读取到的字符串转为数字。
每个模块都挂载了统一的 lifecycle.teardown(test/tests.js),它负责三件事:重置 $.cookie.defaults、删除 raw 与 json 配置、清理全部 Cookie。这意味着每个用例都是独立、可重复的,不会互相污染。
编写新用例时可以对照现有模式:先用 $.cookie(...) 写入前置状态,再用 strictEqual / deepEqual / ok 断言期望行为,并声明 expect(n) 的断言数量。对于需要在受限环境(如无法直接操作 document.cookie 的 IE 场景)验证的行为,仓库还提供了 iframe 沙箱的先例(test/malformed_cookie.html),值得借鉴。
构建成功的自动化保障:CI 与跨浏览器矩阵
CONTRIBUTING.md 要求提交前构建成功,而仓库用持续集成把这条要求自动化了。.travis.yml 配置了 CI:运行环境为 Node.js 0.10,before_script 阶段全局安装 grunt-cli,script 阶段执行 grunt ci --verbose。
grunt ci 任务(Gruntfile.js)注册为:
grunt.registerTask('ci', ['jshint', 'qunit', 'saucelabs']);
它在本地任务链(jshint + qunit)之后追加了 saucelabs 跨浏览器验证:通过 connect:saucelabs(监听 9999 端口,站点根同样为 <a href="https://link.gitcode.com/i/36937f5a731dec48306199425d3850d9" target="_blank">'.', 'test'],见 [Gruntfile.js)提供本地测试页面,再由 saucelabs-qunit 任务把同一套测试丢到云端浏览器矩阵中执行。该矩阵覆盖 iPhone/iPad(iOS 7.1)、Android 4.3、Safari 6/7、Firefox 28/29、Chrome 34 以及 IE 8~11 等十余种浏览器配置(Gruntfile.js)。这正是"确保构建成功"在真实浏览器兼容性层面的落点——本地 grunt 通过只是第一步,跨浏览器矩阵通过才算真正满足提交要求。
贡献时的注意事项与延伸阅读
最后汇总几条实践要点:
- Issue 阶段:bug 报告务必附带简化可复现示例,功能请求说明使用场景与预期行为。
- PR 阶段:每个变更独立 topic branch;新增或修改行为必须配套测试用例;提交前先跑
grunt,确认输出 "Done, without errors."。 - 测试阶段:浏览器测试前清理全部 Cookie,避免残留导致误报;开发迭代可用
grunt connect:tests+grunt watch组合。 - CI 阶段:留意 CI 上的跨浏览器矩阵结果,IE 8+ 等老浏览器行为与 Chrome/Firefox 可能不同(例如
test/tests.js中针对 IE 的畸形 Cookie 与空值测试)。
想进一步深入,可以继续阅读仓库内的这些文件:
- CONTRIBUTING.md:本文依据的贡献规范原文;
- Gruntfile.js:全部构建任务的真实配置;
- package.json:依赖清单与
npm test入口; - test/tests.js:测试用例的完整模板;
- src/jquery.cookie.js:插件核心实现(写入、读取、删除与配置解析);
- README.md:完整的 API 用法(
$.cookie、$.removeCookie、raw/json配置、converters); - CHANGELOG.md:各版本演进与行为变更说明。
注意:当前仓库为只读归档,本文所述命令均应在你本地克隆的副本中执行;直接在仓库内改动不会产生可提交的成果。