Skip to content

Instantly share code, notes, and snippets.

@williamledoux
Last active February 5, 2016 00:20
Show Gist options
  • Save williamledoux/d79d2a2d4873d157047b to your computer and use it in GitHub Desktop.
Save williamledoux/d79d2a2d4873d157047b to your computer and use it in GitHub Desktop.
How to create files in a meteor application from a meteor package

How to create files in a meteor application from a meteor package

The goal is to create or generate some files in the user's app directory, so that he can make some uses of it. For instance, provide LESS files that uses end-user's LESS variables (eg. the user's custom bootstrap theme).

This gist is just explaining the method Marco Pfeiffer has found to make its (awesome) highly configurable LESS boostrap package.

How to create some files

In order to create some file in the user's app workspace, you need to create a build plugin with the registerBuildPlugin API. Then you need to get it called at least once during build. This is done by registering a sourceHandler on some file extension. Your build plugin we get called once per file matching the extension. For now only one build plugin can register as a handler for a given extension, so be creative on the extension :).

Once you get called, you can use the path of the file(s) whose compilation triggered your build plugin as a base to make up a path and create your files, with node's fs and path APIs.

How to copy / generate content

If you plan to generate your file from scratch in the build plugin, you can stop reading here. Otherwise, if you want to read some static files to help you creating the files, you can use Meteor's Assets API. This API allows to retrieve the content of an asset (text or binary).

An asset is a file that has been added with addFile and a special option {isAsset:true}. Although it is not stated in the documentation, not only apps can use the Asset API. Packages can too, but they can only access the assets they have exported themselves.

How to do both

As the Asset API is restrictive on what assets you can read, you will not be able to access the assets of your package from the build plugin. But you can use a package in a build plugin. So the workaround is to have two dependent packages:

  1. foo contains the build plugin, and maybe the rest of your package's code.
  2. foo-assets contains only the assets, and export functions to pipe the assets' content
  3. foo's build plugin uses foo-assets and use the function to get the file content.

Show me some code

Package foo-assets

// pipe_foo_assets.js
PipeFooAssets = function(file) {
  return Assets.getText(file);
}
// foo.import.less
foo{
   background-color: @navbar-inverse-bg; // may be customized by package users
}
// package.js
Package.on_use(function (api) {
  api.addFiles("foo.import.less", 'server', {isAsset:true});
  api.addFiles('pipe_foo_assets.js', 'server');
  api.export(['PipeFooAssets']);
});

Package foo

// foo_build_plugin.js
var fs   = Npm.require('fs');
var path = Npm.require('path');

// Get called once per file matching *.foo.json
// {archMatching: 'web'} tell meteor that we will only create files that 
// are useful to the client, so no need to restart the server upon change.
Plugin.registerSourceHandler('foo.json', {archMatching: 'web'}, handler);

var handler = function (compileStep, isLiterate){
  // copy foo.import.less in the same directory as *.foo.json at each build 
  // meaning that user won't be able to edit foo.import.less
  var destPath = path.join(path.dirname(compileStep._fullInputPath), 'foo.import.less');
  fs.writeFileSync(destPath, PipeFooAssets('foo.import.less'));
}
// package.js
Package._transitional_registerBuildPlugin({
  name: 'foo_build_plugin',
  use:['foo-assets'],
  sources: ['foo_build_plugin.js'],
  npmDependencies: {}
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment