编写 Atmosphere 包

要开始编写包,请使用 Meteor 命令行工具

meteor create --package my-package

如果您计划将您的包发布到 Atmosphere,则您的 `my-package` 名称必须采用 `username:my-package` 的形式,其中 `username` 是您的 Meteor 开发者用户名。

如果您在应用程序内部运行此命令,它会将新生成的包放置在该应用程序的 `packages/` 目录中。在应用程序外部,它只会创建一个独立的包目录。此命令还会为您生成一些样板文件。

my-package
├── README.md
├── package.js
├── my-package-tests.js
└── my-package.js

每个 Meteor 包的主要文件是 `package.js` 文件。这是一个 JavaScript 文件,定义了 Meteor 包的元数据、加载的文件、架构、npm 包和 Cordova 包。

在本指南文章中,我们将介绍构建包的一些重要要点,但不会解释 `package.js` API 的每个部分。要了解所有选项,请阅读 Meteor 文档中有关 `package.js` API 的内容。

完成包的开发后,不要忘记运行meteor add [my-package] 以使用它;这适用于仅供内部使用的本地包,或者您已将包发布到 Atmosphere 的情况。

添加文件和资产

Atmosphere 包的主要功能是包含源代码(JS、CSS 和任何转译语言)和资产(图像、字体等),这些代码和资产将在不同的应用程序之间共享。

添加 JavaScript

要将 JavaScript 文件添加到包中,请在包的 `onUse` 块中使用 api.mainModule() 指定一个入口点(这将由上面的 `meteor create --package` 完成)。

Package.onUse(function(api) {
  api.mainModule('my-package.js');
});

从该入口点,您可以像在应用程序中一样在包中导入其他文件。

如果要在客户端和服务器上包含不同的文件,则可以使用函数的第二个参数指定多个入口点。

Package.onUse(function(api) {
  api.mainModule('my-package-client.js', 'client');
  api.mainModule('my-package-server.js', 'server');
});

您还可以以类似的方式添加任何将编译为 JS 文件的源文件(例如 CoffeeScript 文件),假设您依赖于适当的构建插件。

添加 CSS

要使用包包含 CSS 文件,可以使用 api.addFiles()

Package.onUse(function(api) {
  api.addFiles('my-package.css', 'client');
});

CSS 文件将自动加载到使用您的包的任何应用程序中。

添加 Sass、Less 或 Stylus 混合/变量

就像包可以导出 JavaScript 代码一样,它们也可以导出可重用的 CSS 预处理器代码片段。您还可以拥有一个实际上不包含任何 CSS 的包,但仅导出不同的可重用混合和变量片段。要了解更多详细信息,请参阅 Meteor 构建工具 CSS 预处理器

Package.onUse(function(api) {
  api.addFiles('my-package.scss', 'client');
});

此 Sass 文件将被急切地求值,并且其编译后的形式将立即添加到应用程序的 CSS 中。

Package.onUse(function(api) {
  api.addFiles([
    'stylesheets/_util.scss',
    'stylesheets/_variables.scss'
  ], 'client', {isImport: true});
});

这两个 Sass 文件将被延迟求值,并且仅在从其他文件中导入时才包含在应用程序的 CSS 中。

添加其他资产

您可以使用 api.addAssets 将其他资产(如字体、图标或图像)包含到您的包中。

Package.onUse(function(api) {
  api.addAssets([
    'font/OpenSans-Regular-webfont.eot',
    'font/OpenSans-Regular-webfont.svg',
    'font/OpenSans-Regular-webfont.ttf',
    'font/OpenSans-Regular-webfont.woff',
  ], 'client');
});

然后,您可以从客户端通过 URL ` /packages/username_my-package/font/OpenSans-Regular-webfont.eot` 访问这些文件,或使用 Assets API 从服务器访问。

导出

虽然某些包仅用于向应用程序提供副作用,但大多数包都提供可重用的代码片段,使用者可以使用 `import` 来使用它。要从包中导出符号,请在您的 `mainModule` 中使用 ES2015 `export` 语法。

// in my-package.js:
export const myName = 'my-package';

现在,您的包的用户可以使用以下方式导入符号:

import { myName } from 'meteor/username:my-package';

依赖项

您的包很可能需要使用其他包。为了确保它们可用,您可以声明依赖项。Atmosphere 包既可以依赖其他 Atmosphere 包,也可以依赖 npm 包。

Atmosphere 依赖项

要依赖另一个 Atmosphere 包,请使用 api.use

Package.onUse(function(api) {
  // This package depends on 1.2.0 or above of validated-method
  api.use('mdg:validated-method@1.2.0');
});

Atmosphere 包系统的一个重要特性是它是单加载的:同一应用程序中的两个包不能依赖于单个包的冲突版本。有关详细信息,请参阅下面关于版本约束的部分。

取决于 Meteor 版本

请注意,Meteor 发行版版本号大多是营销产物 - Meteor 核心包本身通常不共享此版本号。这意味着包只能依赖于 Meteor 发行版内部特定版本的包,但不能依赖于特定发行版本身。我们有一个有用的简写 api,称为 api.versionsFrom,它可以为您处理此问题,通过自动填写特定发行版中的包版本号。

// Use versions of core packages from Meteor 1.2.1
api.versionsFrom('1.2.1');

api.use([
  // Don't need to specify version because of versionsFrom above
  'ecmascript',
  'check',

  // Still need to specify versions of non-core packages
  'mdg:validated-method@1.2.0',
  'mdg:validation-error@0.1.0'
]);

以上代码片段等效于以下代码,该代码单独指定了所有版本号。

api.use([
  'ecmascript@0.1.6',
  'check@1.1.0',
  'mdg:validated-method@1.2.0',
  'mdg:validation-error@0.1.0'
]);

此外,您可以多次调用 `api.versionsFrom(<release>)`,或者使用数组(例如 `api.versionsFrom([<release1>, <release2>])`)。Meteor 会将其解释为该包将与所有列出版本中的包一起使用。

api.versionsFrom('1.2.1');
api.versionsFrom('1.4');
api.versionsFrom('1.8');

// or

api.versionsFrom(['1.2.1', '1.4', '1.8']);

这通常不是必需的,但可以帮助您在支持核心包的多个主要版本时使用。

语义版本控制和版本约束

Meteor 的包系统严重依赖于 语义版本控制 或 SemVer。当一个包声明对另一个包的依赖时,它总是带有版本约束。然后,Meteor 的工业级版本求解器会解决这些版本约束,以获得满足所有要求的包版本集,或者如果找不到解决方案则显示有用的错误。

此处的思维模型是

  1. **主版本必须始终完全匹配。**如果包 `a` 依赖于 `b@2.0.0`,则只有当包 `b` 的版本以 `2` 开头时,才会满足约束。这意味着您永远不能在同一个应用程序中拥有两个不同主版本的包。
  2. **次版本号和修订版本号必须大于或等于请求的版本。**如果依赖项请求版本 `2.1.3`,则 `2.1.4` 和 `2.2.0` 将起作用,但 `2.0.4` 和 `2.1.2` 将不起作用。

约束求解器是必要的,因为 Meteor 的包系统是**单加载**的 - 也就是说,您永远不能在同一个应用程序中并排加载同一个包的两个不同版本。这对于包含大量客户端代码或期望为单例的包尤其有用。

请注意,版本求解器也具有“重力”的概念 - 当针对某些依赖项存在多种可能的解决方案时,它总是选择最旧的可能版本。如果您正在尝试开发一个要交付给大量用户的包,这将非常有用,因为它确保您的包与依赖项的最低公分母兼容。如果您的包需要比当前为某个依赖项选择的版本更新的版本,则需要更新您的 `package.js` 以具有更新的版本约束。

如果您的包支持依赖项的多个主要版本,您可以像这样将两个版本都提供给 `api.use`

api.use('blaze@1.0.0 || 2.0.0');

Meteor 将使用与您的其他包兼容的任何主版本,或给定选项中的最新版本。

npm 依赖项

Meteor 包可以包含 npm 包 以使用 Meteor 包生态系统之外的 JavaScript 代码,或包含具有原生依赖项的 JavaScript 代码。在 `package.js` 文件的顶层使用 Npm.depends。例如,以下是如何包含 `github` npm 包:

Npm.depends({
  github: '0.2.4'
});

如果要使用本地 npm 包(例如在开发期间),可以提供目录而不是版本号。

Npm.depends({
  my-package: 'file:///home/user/npms/my-package'
});

您可以像在 应用程序 中一样,从包代码中导入依赖项。

import github from 'github';

对等 npm 依赖项

Npm.depends() 非常严格(您只能依赖于精确的版本),并且如果许多不同的 Atmosphere 包依赖于同一个 npm 包,通常会导致安装多个版本的包。这使得它不太适合在客户端使用,因为在客户端将同一个包代码的多个副本发送到浏览器是不切实际的。客户端包通常也假设只加载一个副本。例如,如果在应用程序捆绑包中包含 React 多次,React 会报错。

为了避免这种情况,作为包作者,您可以请求包的用户在应用程序级别安装您要使用的 npm 包。这类似于 npm 包的 对等依赖项(尽管工具的支持较少)。您可以使用 tmeasday:check-npm-versions 包来确保他们已经执行了此操作,并在未执行此操作时发出警告。

例如,如果您正在编写 React 包,则不应直接依赖于 react,而应使用 `check-npm-versions` 检查用户是否已安装它。

import { checkNpmVersions } from 'meteor/tmeasday:check-npm-versions';

checkNpmVersions({
  'react': '0.14.x'
}, 'my:awesome-package');

// If you are using the dependency in the same file, you'll need to use require, otherwise
// you can continue to `import` in another file.
const React = require('react');

请注意,如果用户安装了不兼容版本的 npm 包,则 `checkNpmVersions` 仅输出警告。因此,您的 `require` 调用可能无法提供您期望的结果。这与 npm 处理 对等依赖项 的方式一致。

Cordova 插件

Atmosphere 包可以包含 Cordova 插件 来为 Meteor 移动应用容器提供原生代码。这样,您就可以与原生相机界面交互,使用陀螺仪,在本地保存文件等等。

通过使用 Cordova.depends 在您的 Meteor 包中包含 Cordova 插件。

移动指南 中阅读有关在 Meteor 中使用 Cordova 的更多信息。

测试包

Meteor 针对使用 meteor test-packages 命令调用的包提供了一种测试模式。导航到您包的目录,然后使用该命令运行一个仅包含您包“测试”版本的特殊应用。

如果您正在使用 Tinytest 进行包测试,您可以运行

meteor test-packages ./

如果您正在使用其他测试框架进行包测试,则需要指定一个 driver-package。例如,如果您正在使用 Mocha,请运行以下命令启动 Mocha 的 测试驱动程序包

meteor test-packages ./ --driver-package meteortesting:mocha

当您的包在测试模式下启动时,Meteor 会加载 onTest 块,而不是加载 onUse 块。

Package.onTest(function(api) {
  // You almost definitely want to depend on the package itself,
  // this is what you are testing!
  api.use('my-package');

  // You should also include any packages you need to use in the test code
  api.use(['ecmascript', 'random', 'meteortesting:mocha']);

  // Finally add an entry point for tests
  api.mainModule('my-package-tests.js');
});

在您的测试入口点中,您可以像在包本身中一样导入其他文件。

您还可以使用 mtest 来测试您的包,如下所示

mtest --package ./ --once 2.14

如果您希望在 CI/CD 设置中测试您的包,这将非常有帮助。您可以在 此处 查看一个示例。

您可以在 测试文章 中阅读更多关于 Meteor 测试的信息。

发布您的包

要首次将您的包发布到 Atmosphere,请从包目录运行 meteor publish --create。包名必须遵循 username:my-package 的格式,并且包必须包含 SemVer 版本号。当您要发布包的更新时,请更改 package.js 中的版本号,然后运行 meteor publish

请注意,如果您的包中包含本地 node_modules 目录,请在运行 meteor publish 之前将其删除。虽然 Meteor 包中允许使用本地 node_modules 目录,但它们的路径在发布时可能会与 Npm.depends 依赖项的路径冲突。

缓存格式

如果您曾经查看过 Meteor 的包缓存 ~/.meteor/packages,您就会知道,构建后的 Meteor 包的磁盘格式与您开发包时源代码的外观完全不同。其理念是,即使开发 API 发生变化,包的目标格式也可以保持一致。

本地包

作为在 Atmosphere 上发布包的替代方案,如果您想将包保持私有,您可以将其放在 Meteor 应用的 packages/ 目录中,例如 packages/foo/,然后使用 meteor add foo 将其添加到您的应用中。

使用本地版本覆盖已发布的包

如果您需要修改 Atmosphere 包以执行已发布版本未执行的操作,您可以编辑计算机上的包的本地版本。

Meteor 应用可以通过三种方式之一加载 Atmosphere 包,并且会按照以下顺序查找匹配的包名称

  1. 应用内部 packages/ 目录中的包源代码。
  2. 在运行任何 meteor 命令之前,通过设置 METEOR_PACKAGE_DIRS 环境变量指示的目录中的包源代码。您可以在 OSX 或 Linux 上使用 : 分隔路径,在 Windows 上使用 ; 分隔路径来添加多个目录。例如:METEOR_PACKAGE_DIRS=../first/directory:../second/directory,或者在 Windows 上:set PACKAGE_DIRS=..\first\directory;..\second\directory

    注意:在 Meteor 1.4.2 之前,METEOR_PACKAGE_DIRSPACKAGE_DIRS。出于兼容性考虑,开发人员应继续使用 METEOR_PACKAGE_DIRS

  3. 来自 Atmosphere 的预构建包。该包缓存在 Mac/Linux 上的 ~/.meteor/packages 或 Windows 上的 %LOCALAPPDATA%\.meteor\packages 中,并且仅在构建时加载到您的应用中。

您可以使用 (1) 或 (2) 覆盖来自 Atmosphere 的版本。您甚至可以这样做来加载 Meteor 核心包的修补版本 - 只需从 Meteor 的 GitHub 存储库 中复制包的代码,然后进行编辑。

在 GitHub 上编辑
// 搜索框