Skip to content

Warning

You are viewing an old version of the Meteor documentation. Click here to go to the latest version.


Package.js

A package is a directory containing a package.js file, which contains roughly three major sections: a basic description, a package definition, and a test definition. By default, the directory name is the name of the package.

The package.js file below is an example of how to use the packaging API. The rest of this section will explain the specific API commands in greater detail.

js
// Information about this package:
Package.describe({
  // Short two-sentence summary
  summary: 'What this does',
  // Version number
  version: '1.0.0',
  // Optional, default is package directory name
  name: 'username:package-name',
  // Optional GitHub URL to your source repository
  git: 'https://github.com/something/something.git'
});

// This defines your actual package:
Package.onUse((api) => {
  // If no version is specified for an `api.use` dependency, use the one defined
  // in Meteor 1.12.1.
  api.versionsFrom('1.12.1');
  // Use the `underscore` package, but only on the server. Version not
  // specified, so it will be as of Meteor 1.12.1.
  api.use('underscore', 'server');
  // Use `ostrio:flow-router-extra`, version 3.9.0 or newer.
  api.use('ostrio:flow-router-extra@3.9.0');
  // Give users of this package access to active-route's JavaScript helpers.
  api.imply('zimme:active-route@2.3.2')
  // Export the object `Email` to packages or apps that use this package.
  api.export('Email', 'server');
  // Specify the source code for the package.
  api.addFiles('email.js', 'server');
  // When using `ecmascript` or `modules` packages, you can use this instead of
  // `api.export` and `api.addFiles`.
  api.mainModule('email.js', 'server');
});

// This defines the tests for the package:
Package.onTest((api) => {
  // Sets up a dependency on this package.
  api.use('username:package-name');
  // Use the Mocha test framework.
  api.use('practicalmeteor:mocha@2.4.5_6');
  // Specify the source code for the package tests.
  api.addFiles('email_tests.js', 'server');
});

// This lets you use npm packages in your package:
Npm.depends({
  simplesmtp: '0.3.10',
  'stream-buffers': '0.2.5'
});

api.mainModule is documented in the modules section.

Build plugins are created with Package.registerBuildPlugin. See the coffeescript package for an example. Build plugins are fully-fledged Meteor programs in their own right and have their own namespace, package dependencies, source files and npm requirements.

You can use local packages to define custom build plugins for your app, with one caveat. In published packages, build plugins are already bundled with their transitive dependencies. So if you want a dependency of a build plugin to be satisfied by a local package, you must use a local copy of the package that defines the plugin (even if you make no changes to that package) so that Meteor will pick up the local dependency.

In a lifecycle of a package there might come time to end the development for various reasons, or it gets superseded. In either case Meteor allows you to easily notify the users of the package by setting the deprecated flag to true: deprecated: true in the package description. In addition, you replace it with a string that tells the users where to find replacement or what to do.

Provide basic package information with Package.describe(options). To publish a package, you must define summary and version.

api.describe

package.js only

Summary:

Provide basic package information.

Arguments:

Source code
NameTypeDescriptionRequired
optionsObjectYes

Options:

NameTypeDescriptionRequired
summaryString

A concise 1-2 sentence description of the package, required for publication.

No
versionString

The (extended) semver version for your package. Additionally, Meteor allows a wrap number: a positive integer that follows the version number. If you are porting another package that uses semver versioning, you may want to use the original version, postfixed with _wrapnumber. For example, 1.2.3_1 or 2.4.5-rc1_4. Wrap numbers sort after the original numbers: 1.2.3 < 1.2.3_1 < 1.2.3_2 < 1.2.4-rc.0. If no version is specified, this field defaults to 0.0.0. If you want to publish your package to the package server, you must specify a version.

No
nameString

Optional name override. By default, the package name comes from the name of its directory.

No
gitString

Optional Git URL to the source repository.

No
documentationString

Optional Filepath to documentation. Set to 'README.md' by default. Set this to null to submit no documentation.

No
debugOnlyBoolean

A package with this flag set to true will not be bundled into production builds. This is useful for packages meant to be used in development only.

No
prodOnlyBoolean

A package with this flag set to true will ONLY be bundled into production builds.

No
testOnlyBoolean

A package with this flag set to true will ONLY be bundled as part of meteor test.

No
deprecatedBoolean

A flag that will mark the package as deprecated. Provide string to override the default message.

No
js

// api is an instance of PackageNamespace

const result = api.describe();
  options
);

Define dependencies and expose package methods with the Package.onUse handler. This section lets you define what packages your package depends on, what packages are implied by your package, and what object your package is exported to.

api.onUse

package.js only

Summary:

Define package dependencies and expose package methods.

Arguments:

Source code
NameTypeDescriptionRequired
funcfunction

A function that takes in the package control api object, which keeps track of dependencies and exports.

Yes
js

// api is an instance of PackageNamespace

const result = api.onUse();
  () => {}
);

api.versionsFrom

package.js only

Summary:

Use versions of core packages from a release. Unless provided, all packages will default to the versions released along with meteorRelease. This will save you from having to figure out the exact versions of the core packages you want to use. For example, if the newest release of meteor is METEOR@0.9.0 and it includes jquery@1.0.0, you can write api.versionsFrom('METEOR@0.9.0') in your package, and when you later write api.use('jquery'), it will be equivalent to api.use('jquery@1.0.0'). You may specify an array of multiple releases, in which case the default value for constraints will be the "or" of the versions from each release: api.versionsFrom(['METEOR@0.9.0', 'METEOR@0.9.5']) may cause api.use('jquery') to be interpreted as api.use('jquery@1.0.0 || 2.0.0').

Arguments:

Source code
NameTypeDescriptionRequired
meteorReleaseString or Array.<String>

Specification of a release: track@version. Just 'version' (e.g. "0.9.0") is sufficient if using the default release track METEOR. Can be an array of specifications.

Yes
js

// api is an instance of PackageAPI

const result = api.versionsFrom();
  "meteorRelease"
);

Choose Meteor versions carefully. First determine the minimum version of Meteor you need for the API you use in your package. This should be based on specific needs of your package like needed the *Async calls, which would require minimum version to be at least 2.8. Another example are where packages had a major version bump, for example this has happened with the accounts packages in Meteor 2.3. If you want to be backward and forward compatible it is good to include Meteor version before 2.3 and then 2.3.6 in the array. A general recommendation for most compatibility for accounts packages (unless you need API that was affected in Meteor 2.3) is to have the following array in versionsFrom: ['1.12.1', '2.3.6', '2.8.1'], this gives us the widest range. For general packages you can leave out version 2.3.6. If you want the widest compatibility range it is recommended that the lowest be 1.12.1 and that you also include another version near the current version of Meteor.

api.use

package.js only

Summary:

Depend on package packagename.

Arguments:

Source code
NameTypeDescriptionRequired
packageNamesString or Array.<String>

Packages being depended on. Package names may be suffixed with an @version tag.

In general, you must specify a package's version (e.g., 'accounts@1.0.0' to use version 1.0.0 or a higher compatible version (ex: 1.0.1, 1.5.0, etc.) of the accounts package). If you are sourcing core packages from a Meteor release with versionsFrom, you may leave off version names for core packages. You may also specify constraints, such as my:forms@=1.0.0 (this package demands my:forms at 1.0.0 exactly), or my:forms@1.0.0 || =2.0.1 (my:forms at 1.x.y, or exactly 2.0.1).

Yes
architectureString or Array.<String>

If you only use the package on the server (or the client), you can pass in the second argument (e.g., 'server', 'client', 'web.browser', 'web.cordova') to specify what architecture the package is used with. You can specify multiple architectures by passing in an array, for example ['web.cordova', 'os.linux'].

No
optionsObjectNo

Options:

NameTypeDescriptionRequired
weakBoolean

Establish a weak dependency on a package. If package A has a weak dependency on package B, it means that including A in an app does not force B to be included too — but, if B is included or by another package, then B will load before A. You can use this to make packages that optionally integrate with or enhance other packages if those packages are present. When you weakly depend on a package you don't see its exports. You can detect if the possibly-present weakly-depended-on package is there by seeing if Package.foo exists, and get its exports from the same place.

No
unorderedBoolean

It's okay to load this dependency after your package. (In general, dependencies specified by api.use are loaded before your package.) You can use this option to break circular dependencies.

No
js

// api is an instance of PackageAPI

const result = api.use();
  "packageNames",
"architecture", // this param is optional

options, // this param is optional
);

api.imply

package.js only

Summary:

Give users of this package access to another package (by passing in the string packagename) or a collection of packages (by passing in an array of strings [packagename1, packagename2]

Arguments:

Source code
NameTypeDescriptionRequired
packageNamesString or Array.<String>

Name of a package, or array of package names, with an optional @version component for each.

Yes
architectureString or Array.<String>

If you only use the package on the server (or the client), you can pass in the second argument (e.g., 'server', 'client', 'web.browser', 'web.cordova') to specify what architecture the package is used with. You can specify multiple architectures by passing in an array, for example ['web.cordova', 'os.linux'].

No
js

// api is an instance of PackageAPI

const result = api.imply();
  "packageNames",
"architecture", // this param is optional
);

api.export

package.js only

Summary:

Export package-level variables in your package. The specified variables (declared without var in the source code) will be available to packages that use your package. If your package sets the debugOnly, prodOnly or testOnly options to true when it calls Package.describe(), then packages that use your package will need to use Package["package-name"].ExportedVariableName to access the value of an exported variable.

Arguments:

Source code
NameTypeDescriptionRequired
exportedObjectsString or Array.<String>

Name of the object to export, or an array of object names.

Yes
architectureString or Array.<String>

If you only want to export the object on the server (or the client), you can pass in the second argument (e.g., 'server', 'client', 'web.browser', 'web.cordova') to specify what architecture the export is used with. You can specify multiple architectures by passing in an array, for example ['web.cordova', 'os.linux'].

No
exportOptionsObject----No
exportOptions.testOnlyBoolean

If true, this symbol will only be exported when running tests for this package.

Yes
js

// api is an instance of PackageAPI

const result = api.export();
  "exportedObjects",
"architecture", // this param is optional

exportOptions, // this param is optional

false,
);

api.addFiles

package.js only

Summary:

Specify source code files for your package.

Arguments:

Source code
NameTypeDescriptionRequired
filenamesString or Array.<String>

Paths to the source files.

Yes
architectureString or Array.<String>

If you only want to use the file on the server (or the client), you can pass this argument (e.g., 'server', 'legacy', 'client', 'web.browser', 'web.cordova') to specify what architecture the file is used with. You can call api.addFiles(files, "legacy") in your package.js configuration file to add extra files to the legacy bundle, or api.addFiles(files, "client") to add files to all client bundles, or api.addFiles(files, "web.browser") to add files only to the modern bundle. You can specify multiple architectures by passing in an array, for example ['web.cordova', 'os.linux']. By default, the file will be loaded on both server and client.

No
optionsObject

Options that will be passed to build plugins.

No

Options:

NameTypeDescriptionRequired
bareBoolean

If this file is JavaScript code or will be compiled into JavaScript code by a build plugin, don't wrap the resulting file in a closure. Has the same effect as putting a file into the client/compatibility directory in an app.

No
js

// api is an instance of PackageAPI

const result = api.addFiles();
  "filenames",
"architecture", // this param is optional

options, // this param is optional
);

api.addAssets

package.js only

Summary:

Specify asset files for your package. They can be accessed via the Assets API from the server, or at the URL /packages/username_package-name/file-name from the client, depending on the architecture passed.

Arguments:

Source code
NameTypeDescriptionRequired
filenamesString or Array.<String>

Paths to the asset files.

Yes
architectureString or Array.<String>

Specify where this asset should be available (e.g., 'server', 'client', 'web.browser', 'web.cordova'). You can specify multiple architectures by passing in an array, for example ['web.cordova', 'os.linux'].

Yes
js

// api is an instance of PackageAPI

const result = api.addAssets();
  "filenames",
"architecture",
);

Set up your tests with the Package.onTest handler, which has an interface that's parallel to that of the onUse handler. The tests will need to depend on the package that you have just created. For example, if your package is the email package, you have to call api.use('email') in order to test the package.

If you used meteor create to set up your package, Meteor will create the required scaffolding in package.js, and you'll only need to add unit test code in the _test.js file that was created.

api.onTest

package.js only

Summary:

Define dependencies and expose package methods for unit tests.

Arguments:

Source code
NameTypeDescriptionRequired
funcfunction

A function that takes in the package control 'api' object, which keeps track of dependencies and exports.

Yes
js

// api is an instance of PackageNamespace

const result = api.onTest();
  () => {}
);

Meteor packages can include NPM packages and Cordova plugins by using Npm.depends and Cordova.depends in the package.js file.

api.depends

package.js only

Summary:

Specify which NPM packages your Meteor package depends on.

Arguments:

Source code
NameTypeDescriptionRequired
dependenciesObject

An object where the keys are package names and the values are one of:

  1. Version numbers in string form
  2. http(s) URLs of npm packages
  3. Git URLs in the format described here

Https URL example:

Npm.depends({
  moment: "2.8.3",
  async: "https://github.com/caolan/async/archive/71fa2638973dafd8761fa5457c472a312cc820fe.tar.gz"
});

Git URL example:

Npm.depends({
  moment: "2.8.3",
  async: "git+https://github.com/caolan/async#master"
});
Yes
js

// api is an instance of PackageNpm

const result = api.depends();
  dependencies
);

Npm.require

Server only

Summary:

Require a package that was specified using Npm.depends().

Arguments:

Source code
NameTypeDescriptionRequired
nameString

The name of the package to require.

Yes
js



const result = Npm.require();
  "name"
);

api.depends

package.js only

Summary:

Specify which Cordova / PhoneGap plugins your Meteor package depends on.

Plugins are installed from plugins.cordova.io, so the plugins and versions specified must exist there. Alternatively, the version can be replaced with a GitHub tarball URL as described in the Cordova page of the Meteor wiki on GitHub.

Arguments:

Source code
NameTypeDescriptionRequired
dependenciesObject

An object where the keys are plugin names and the values are version numbers or GitHub tarball URLs in string form. Example:

Cordova.depends({
  "org.apache.cordova.camera": "0.3.0"
});

Alternatively, with a GitHub URL:

Cordova.depends({
  "org.apache.cordova.camera":
    "https://github.com/apache/cordova-plugin-camera/tarball/d84b875c449d68937520a1b352e09f6d39044fbf"
});
Yes
js

// api is an instance of PackageCordova

const result = api.depends();
  dependencies
);

api.registerBuildPlugin

package.js only

Summary:

Define a build plugin. A build plugin extends the build process for apps and packages that use this package. For example, the coffeescript package uses a build plugin to compile CoffeeScript source files into JavaScript.

Arguments:

Source code
NameTypeDescriptionRequired
optionsObjectNo

Options:

NameTypeDescriptionRequired
nameString

A cosmetic name, must be unique in the package.

No
useString

Meteor packages that this plugin uses, independent of the packages specified in api.onUse.

No
sourcesArray.<String>

The source files that make up the build plugin, independent from api.addFiles.

No
npmDependenciesObject

An object where the keys are NPM package names, and the values are the version numbers of required NPM packages, just like in Npm.depends.

No
js

// api is an instance of PackageNamespace

const result = api.registerBuildPlugin();
  options
);

Options

In some cases we need to offer options in packages where these options are not going to change in runtime.

We prefer to have these options defined in a configuration file instead of using JS code to call specific functions to define options in runtime.

For example, in quave:collections package you can force collections to be available only in the server like this:

json
  "packages": {
    "quave:collections": {
      "isServerOnly": true
    }
  }

We encourage every package author to follow this standard to offer options:

  1. Use the official Meteor settings file
  2. Inside the settings file read from a Meteor.packages.<package name>.<your option name>

    If it needs to be available in the client follow the same structure inside the public key.

You can use quave:settings package to read options in the format above already merging the private and public options.

This way we avoid having to call a specific code before another specific code in a package as the setting is stored in the settings, and the package can load it when necessary instead of relying on a specific order of calls from the developer in the app code.

We've started to adopt this standard also in core packages on Meteor 1.10.2.

Plugin.registerSourceHandler

Build Plugin only

Summary:

Inside a build plugin source file specified in Package.registerBuildPlugin, add a handler to compile files with a certain file extension.

Arguments:

Source code
NameTypeDescriptionRequired
fileExtensionString

The file extension that this plugin should handle, without the first dot. Examples: "coffee", "coffee.md".

Yes
handlerfunction

A function that takes one argument, a CompileStep object.

Documentation for CompileStep is available on the GitHub Wiki.

Yes
js



const result = Plugin.registerSourceHandler();
  "fileExtension",
() => {},
);

Build Plugins API

Meteor packages can provide build plugins - programs that integrate with the build tool Isobuild used to compile and bundle your application.

Starting with Meteor 1.2, the API used to plug into the build process is called "Build Plugins". There are 3 phases when a package's plugin can run: linting, compilation and minification. Here is an overview of operations Isobuild performs on the application and packages source:

  1. Gather source files from the app folder or read package.js file for a package.
  2. Lint all source files and print the linting warnings.
  3. Compile the source files like CoffeeScript, ES2015, Less, or Templates to plain JavaScript and CSS.
  4. Link the JavaScript files: wrap them into closures and provide necessary package imports.
  5. Minify JavaScript and CSS files. Can also include concatenation of all files.

Build plugins fill the phases 2, 3 and 5.

Usually build plugins implement a class that is given a list of files to process. Commonly, such files have the following methods:

  • getContentsAsBuffer - Returns the full contents of the file as a buffer.
  • getContentsAsString - Returns the full contents of the file as a string.
  • getPackageName - Returns the name of the package or null if the file is not in a package.
  • getPathInPackage - Returns the relative path of file to the package or app root directory. The returned path always uses forward slashes.
  • getSourceHash - Returns a hash string for the file that can be used to implement caching.
  • getArch - Returns the architecture that is targeted while processing this file.
  • getBasename - Returns the filename of the file.
  • getDirname - Returns the directory path relative to the package or app root. The returned path always uses forward slashes.
  • error - Call this method to raise a compilation or linting error for the file.

Linters

Linters are programs that check the code for undeclared variables or find code that doesn't correspond to certain style guidelines. Some of the popular examples of linters are JSHint and ESLint. Some of the non-JavaScript linter examples include CoffeeLint for CoffeeScript and CSSLint for CSS.

To register a linter build plugin in your package, you need to do a couple of things in your package.js:

  • depend on the isobuild:linter-plugin@1.0.0 package
  • register a build plugin: Package.registerBuildPlugin({ name, sources, ... }); (see docs)

In your build plugin sources, register a Linter Plugin: provide details such as a name, list of extensions and filenames the plugin will handle and a factory function that returns an instance of a linter class. Example:

js
Plugin.registerLinter({
  extensions: ['js'],
  filenames: ['.linterrc']
}, () => new MyLinter);

In this example, we register a linter that runs on all js files and also reads a file named .linterrc to get a configuration.

The MyLinter class should now implement the processFilesForPackage method. The method should accept two arguments: a list of files and an options object.

js
class MyLinter {
  processFilesForPackage(files, options) {
    files.forEach((file) => {
      // Lint the file.
      const lint = lintFile(file.getContentsAsString());

      if (lint) {
        // If there are linting errors, output them.
        const { message, line, column } = lint;
        file.error({ message, line, column });
      }
    });
  }
}

The globals are passed in the options object so that the linters can omit the warnings about the package imports that look like global variables.

Each file in the list is an object that has all the methods provided by all build plugins, described above.

See an example of a linting plugin implemented in Core: jshint.

Compilers

Compilers are programs that take the source files and output JavaScript or CSS. They also can output parts of HTML that is added to the <head> tag and static assets. Examples for the compiler plugins are: CoffeeScript, Babel.js, JSX compilers, Pug templating compiler and others.

To register a compiler plugin in your package, you need to do the following in your package.js file:

  • depend on the isobuild:compiler-plugin@1.0.0 package
  • register a build plugin: Package.registerBuildPlugin({ name, sources, ... }); (see docs)

In your build plugin source, register a Compiler Plugin: similar to other types of build plugins, provide the details, extensions and filenames and a factory function that returns an instance of the compiler. Ex.:

js
Plugin.registerCompiler({
  extensions: ['pug', 'tpl.pug'],
  filenames: []
}, () => new PugCompiler);

The compiler class must implement the processFilesForTarget method that is given the source files for a target (server or client part of the package/app).

js
class PugCompiler {
  processFilesForTarget(files) {
    files.forEach((file) => {
      // Process and add the output.
      const output = compilePug(file.getContentsAsString());

      file.addJavaScript({
        data: output,
        path: `${file.getPathInPackage()}.js`
      });
    });
  }
}

Besides the common methods available on the input files' class, the following methods are available:

  • getExtension - Returns the extension that matched the compiler plugin. The longest prefix is preferred.
  • getDeclaredExports - Returns a list of symbols declared as exports in this target. The result of api.export('symbol') calls in target's control file such as package.js.
  • getDisplayPath Returns a relative path that can be used to form error messages or other display properties. Can be used as an input to a source map.
  • addStylesheet - Web targets only. Add a stylesheet to the document. Not available for linter build plugins.
  • addJavaScript - Add JavaScript code. The code added will only see the namespaces imported by this package as runtime dependencies using 'api.use'. If the file being compiled was added with the bare flag, the resulting JavaScript won't be wrapped in a closure.
  • addAsset - Add a file to serve as-is to the browser or to include on the browser, depending on the target. On the web, it will be served at the exact path requested. For server targets, it can be retrieved using Assets.getTextAsync or Assets.getBinaryAsync.
  • addHtml - Works in web targets only. Add markup to the head or body section of the document.
  • hmrAvailable - Returns true if the file can be updated with HMR. Among other things, it checks if HMR supports the current architecture and build mode, and that the unibuild uses the hot-module-replacement package. There are rare situations where hmrAvailable returns true, but when more information is available later in the build process Meteor decides the file can not be updated with HMR.
  • readAndWatchFileWithHash - Accepts an absolute path, and returns { contents, hash } Makes sure Meteor watches the file so any changes to it will trigger a rebuild

Meteor implements a couple of compilers as Core packages, good examples would be the Blaze templating package and the ecmascript package (compiles ES2015+ to JavaScript that can run in the browsers).

Minifiers

Minifiers run last after the sources has been compiled and JavaScript code has been linked. Minifiers are only ran for the client programs (web.browser and web.cordova).

There are two types of minifiers one can add: a minifier processing JavaScript (registered extensions: ['js']) and a minifier processing CSS (extensions: ['css']).

To register a minifier plugin in your package, add the following in your package.js file:

  • depend on isobuild:minifier-plugin@1.0.0 package
  • register a build plugin: Package.registerBuildPlugin({ name, sources, ... }); (see docs)

In your build plugin source, register a Minifier Plugin. Similar to Linter and Compiler plugin, specify the interested extensions (css or js). The factory function returns an instance of the minifier class.

js
Plugin.registerMinifier({
  extensions: ['js']
}, () => new UglifyJsMinifier);

The minifier class must implement the method processFilesForBundle. The first argument is a list of processed files and the options object specifies if the minifier is ran in production mode or development mode.

INFO

This method can be asynchronous. If it returns a Promise, the build process will wait for it to resolve before continuing.

js
class UglifyJsMinifier {
  processFilesForBundle(files, options) {
    const { minifyMode } = options;

    if (minifyMode === 'development') {
      // Don't minify in development.
      file.forEach((file) => {
        file.addJavaScript({
          data: file.getContentsAsBuffer(),
          sourceMap: file.getSourceMap(),
          path: file.getPathInBundle()
        });
      });

      return;
    }

    // Minify in production.
    files.forEach((file) => {
      file.addJavaScript({
        data: uglifyjs.minify(file.getContentsAsBuffer()),
        path: file.getPathInBundle()
      });
    });
  }
}

In this example, we re-add the same files in the development mode to avoid unnecessary work and then we minify the files in production mode.

Besides the common input files' methods, these methods are available:

  • getPathInBundle - returns a path of the processed file in the bundle.
  • getSourcePath - returns absolute path of the input file if available, or null.
  • getSourceMap - returns the source-map for the processed file if there is such.
  • addJavaScript - same as compilers
  • addStylesheet - same as compilers
  • readAndWatchFileWithHash - only available for css minifiers. Same as compilers.

Right now, Meteor Core ships with the standard-minifiers package that can be replaced with a custom one. The source of the package is a good example how to build your own minification plugin.

In development builds, minifiers must meet these requirements to not prevent hot module replacement:

  • Call addJavasScript once for each file to add the file's contents
  • The contents of the files are not modified

In the future Meteor will allow minifiers to concatenate or modify files in development without affected hot module replacement.

Caching

Since the API allows build plugins to process multiple files at once, we encourage package authors to implement at least some in-memory caching for their plugins. Using the getSourceHash function for linters and compilers will allow quick incremental recompilations if the file is not reprocessed even when the contents didn't change.

For the fast rebuilds between the Isobuild process runs, plugins can implement on-disk caching. If a plugin implements the setDiskCacheDirectory method, it will be called from time to time with a new path on disk where the plugin can write its offline cache. The folder is correctly reset when the plugin is rebuilt or cache should be invalidated for any reason (for example, picked package versions set has changed).

Caching Compiler

There is a core package called caching-compiler that implements most of the common logic of keeping both in-memory and on-disk caches. The easiest way to implement caching correctly is to subclass the CachingCompiler or MultiFileCachingCompiler class from this package in your build plugin. CachingCompiler is for compilers that consider each file completely independently; MultiFileCachingCompiler is for compilers that allow files to reference each other. To get this class in your plugin namespace, add a dependency to the plugin definition:

js
Package.registerBuildPlugin({
  name: 'compileGG',
  use: ['caching-compiler@1.0.0'],
  sources: ['plugin/compile-gg.js']
});

Accessing File System

Since the build plugins run as part of the Meteor tool, they follow the same file-system access convention - all file system paths always look like a Unix path: using forward slashes and having a root at '/', even on Windows. For example: paths /usr/bin/program and /C/Program Files/Program/program.exe are valid paths, and C:\Program Files\Program\program.exe is not.

So whenever you get a path in your build plugin implementation, via getPathInPackage or in an argument of the setDiskCacheDirectory method, the path will be a Unix path.

Now, on running on Windows, the usual node modules fs and path expect to get a DOS path. To assist you to write correct code, the Plugin symbol provides its own versions of fs and path that you can use instead (note that all methods on fs are fiberized and sync versions prefer using Fibers rather than freezing the whole event loop).

Also Plugin provides helper functions convertToStandardPath and convertToOSPath to convert to a Unix path or to the path expected by the node libraries regardless of the path origin.

Example:

js
// On Windows
const fs = Plugin.fs;
const path = Plugin.path;

const filePath = path.join('/C/Program Files', 'Program/file.txt');
console.log(filePath); // Prints '/C/Program Files/Program/file.txt'

fs.writeFileSync(filePath, 'Hello.'); // Writes to 'C:\Program Files\Program\file.txt'

console.log(Plugin.convertToOsPath(filePath)); // Prints 'C:\Program Files\Program\file.txt'

Isobuild Feature Packages

Starting with Meteor 1.2, packages can declare that they need a version of the Meteor tool whose Isobuild build system supports a certain feature. For example, packages must write api.use('isobuild:compiler-plugin@1.0.0') in order to call Plugin.registerCompiler. This means that a package can transition from the old registerSourceHandler API to registerCompiler and Version Solver will properly prevent the registerCompiler version from being chosen by older tools that don't know how to handle it.

This is the known Isobuild feature "packages" sorted by the first release of Meteor which supports them.

Introduced in Meteor 1.2s

  • compiler-plugin@1.0.0: Allows use of Plugin.registerCompiler.
  • linter-plugin@1.0.0: Allows use of Plugin.registerLinter.
  • minifier-plugin@1.0.0: Allows use of Plugin.registerMinifier.
  • isopack-2@1.0.0: This package is published only in isopack-2 format and won't work in versions of Meteor that don't support that format.
  • prod-only@1.0.0: Allows use of the prodOnly flag in Package.describe.
  • isobuild:cordova@5.4.0: This package depends on a specific version of Cordova, most likely as a result of the Cordova plugins it depends on.