Bundles is a file bundler for anything. Bundles works similar to WebPack, RollupJS, Parcel, PostCSS except that while other bundlers are designed to compile to a specific output type (like JS, CSS, etc.), Bundles can compile from anything, to anything! Bundles has no assumptions and places no limitations on the type of input it accepts and outputs to; it processes your data however you tell it to. Use Bundles to compile anything your heart desires (and can dream up).
Node | CLI | ES Module | Browser | UMD |
---|---|---|---|---|
âś“ | âś“ | x | x | x |
For clarity's sake, the following terms are used as follows:
- Bundles (capitalized): The core package / tool for Bundles. Or the global
Bundles
Object, which is parsed from user configuration and then becomes the compiled result. - bundles: Configured and/or compiled bundles. More specifically, the
bundles
property of the globalBundles
configuration Object. - bundle (noun): A single or specific bundle in
Bundles.bundles
. - bundle (verb): The process of taking one or more user-configured bundles and compiling them -- through a series of
bundlers
-- to achieve a desired output. - bundler: Plugins that make Bundles work. Each
bundler
is a simple JavaScript plugin or function that compiles/processes Bundles input however you like. Withoutbundlers
, all Bundles does is outputs source input. Withbundlers
, Bundles can output just about anything you want. - config: The global
Bundles
configuration Object, which consists ofBundles.options
,Bundles.data
, andBundles.bundles
. - options: The
options
Object, which can be set on the global (Bundles.options
) or regional (bundle.options
) level. Bundles merges globalBundles.options
with eachbundle.options
. - data: The
data
Object, which can be be set on the global (Bundles.data
), regional (bundle.data
), or local (file.data
, or front matter) levels. Bundles merges globaldata
with eachbundle.data
, which are each merged with eachfile.data
(front matter).
Install globally:
npm install @bundles/core -g
or locally:
npm install @bundles/core -D
See the full list of configuration options for configuring Bundles.
# With --config:
bundles --config=<path/to/config.js> [options]
# Or with <input> and --bundlers:
bundle <input>... --bundlers='[...]' [options]
Note: bundle
is an alias to the bundles
command. Either may be use interchangeably.
See the Bundles
Node API for a full list of Bundles' methods.
const Bundles = require('@bundles/core');
Bundles.run(config);
A config file is the recommended way to configure Bundles. There are many ways to structure a Bundles config file.
If you only have a single bundle to configure, you may export a single bundle Object (just make sure it has input
and bundlers
properties).
module.exports = {
id: 'my-bundle',
input: [...],
bundlers: [...],
options: {...},
data: {...}
}
For multiple bundles, you may export an Array of bundle Objects.
module.exports = [{...}, {...}, {...}]
Or you may export an Object dictionary of bundles. In this case each key will become the bundle.id
.
module.exports = {
'bundle1': {...},
'bundle2': {...},
'bundle3': {...},
}
To easily share global data
and/or options
between bundles, you may export a global config Object, which has bundles
(required), options
, on
, and data
properties. The options
and data
Objects will be merged with each bundle in bundles
(Existing bundle.options
and bundle.data
will override global configuration). The bundles
property can be an Object or Array.
module.exports = {
bundles: [{...}, {...}, {...}],
// bundles: {...},
options: {},
data: {},
on: {}
}
Individual bundles can be configured in a config file/Object as outlined above. Configured bundles become Bundles.bundles
, which is an Array of bundle Objects. Each bundle should be configured as follows:
id
{String} The ID of the bundle, which can be used in other options. If this is not set, it will be the index value (as a String) of the order the bundle was configured.input
{String|[String]|Object|[Object]} (required) Source input files. Each entry can be a String or Object. Strings are file or directory paths, globs accepted. An Object is a single file wherefile.content
is a String or Buffer that represent the file's content, useful for passing a file's content directly. File Objects should also havefile.path
, the file's source path, which will likely be used when compiled by bundlers.bundlers
{[String]|[Function]|[Object]} (required) Array of bundlers. A String is a path to a node module, whereas a Function is the bundler function itself. An Object allows you to pass configuration to each bundler.bundler.run
is the only required property, and can also be a String or Function. Learn about authoring bundlers.options
{Object} Options for this individual bundle. See configuring options.on
{Object} [{}] Dictionary of hooks (callbacks) which allow you to tie into Bundles. See Bundles hooks.data
{Object} Data for this individual bundle. This will be merged with each output file in the bundle. See configuring data.
Options can be configured in the global config Object, as well as in individual bundles. With the command line interface, options can also be passed as parameters. The following options are available for configuration.
run
|--run
{Boolean|String|[String]} [true] Determines which bundles -- usingbundle.id
-- will be compiled at runtime. Atrue
orfalsy
value will run all bundles. A comma-separated String or an Array of bundle IDs will only run a bundle if its ID is listed. Bundles CLI only accepts a comma-separated String value.watch
|--watch
{Boolean|String|[String]} [false] Determines which bundles -- usingbundle.id
-- will be watched at runtime. Atrue
value will watch all bundles. A comma-separated String or an Array of bundle IDs will only watch a bundle if its ID is listed. Bundles CLI only accepts a comma-separated String value.watchFiles
{String|[String]} An Array or String (globs accepted) of additional files to watch, when the bundle is being watched. These files do not get compiled, they are only added to the watcher and simply kick off a rebundle if/when any of them change. This is useful, for example, for template partials that are depended on by other source input files in the bundle.cwd
|--cwd
{String} [process.cwd()
] The root or current working directory for input source paths.loglevel
|--loglevel
{String} ['info'] Level of logging. Can betrace
,debug
,info
,warn
,error
, orsilent
.glob
|--glob
{Object} Options passed to globby. Bundles CLI only accepts a JSON Object.frontMatter
|--frontMatter
{Object} Options passed to gray matter. Bundles CLI only accepts a JSON Object.chokidar
|--chokidar
{Object} Options passed to chokidar. Bundles CLI only accepts a JSON Object.
In addition to the config options listed above, Bundles CLI has the following configuration options available.
--config
{String|JSON Object} Global Bundles config Object. Can be a String filepath to the config file or a JSON Object. When--config
exists,<input>
files and the--bundlers
flag are ignored.<input>...
{String} Input files. Must be used in combination with--bundlers
, but will be overridden if--config
exists. Example:bundles file.txt dir/**/* my/other/dir --bundlers='[...]' [options]
.--bundlers
{String|JSON Array} Bundlers. Can be a comma-separated String of node modules or a JSON Array. Must be used in combination with<input>
files, but will be overridden if--config
exists.--data
{String|JSON Object} Global data. Can be a String filepath to a node module or a JSON Object.
Bundles hooks allow you to tie into the Bundles workflow. Like other global properties, hooks can be configured globally via Bundles.on
or per bundle via bundles.on
. The following hooks are available.
afterBundle
{Function}(Bundles) => {}
Called each time after all bundles are completed. Does not require a return value. This allows you to do things such as run/reload a development server, etc.
Data can be configured in the global config Object, in individual bundles, or locally in file content using front matter. Front matter is parsed with gray matter, which means front matter can exist in many languages (i.e., YAML, JSON, JS, etc.). All data is merged as follows:
// Local file/front matter data is merged on top of bundle.data,
// which is merged on top of global Bundles.data.
file.data = merge(frontMatter, bundle.data, Bundles.data);
The Bundles
global Object is returned by all Bundles
methods, and is formed as follows.
success
{Boolean}true
if all bundles ran successfully.configFile
{String|null} Path to config file, ornull
if a config file was not used.dataFiles
{[String]} Input source paths to theconfigFile
and its children data files, if a config file was used.watchingData
{Boolean}true
ifBundles
is watching config/data files.watcher
{Object} IfwatchingData
is true, this contains the chokidar watcher.options
{Object} Original global configuration options.on
{Object} [{}] Dictionary of hooks (callbacks) which allow you to tie into Bundles. See Bundles hooks.data
{Object} Original global data.bundles
{[Object]} Compiled bundle Objects.
Bundles.bundles
is an Array of individual bundle Objects. Each bundle contains the following combination of original user configuration and internally created data properties.
id
{String} The bundle's ID. If this was not configured by the user it will default to the bundle's index (from the order all bundles were run).input
{Map} Map which contains each user-configured input source path as the key, and resolved source paths as its value.output
{Map} Map of output files, containing compiled source input. The key for each file is its source path.changed
{Map} Map of output files that have been changed or modified since last bundle. The key for each file is its source path.removed
{Map} Map of output files that have been removed since last bundle. The key for each file is its source path.bundlers
{[Object]} Objects Array of configured bundlers which compiled the source input.options
{Object} Original user-configured global options, merged withbundle.options
.data
{Object} User-configured global data, merged withbundle.data
, merged withfile.data
(front matter).success
{Boolean} Whether allbundle.bundlers
completed successfully.valid
{Boolean} Whether the bundle is a valid/appropriately configured bundle.watching
{Boolean} Whether the bundle is watching files. Whentrue
, the source input will be rebundled when any of the source files change.watcher
{Object} Whenbundle.watching
istrue
, this will contain the chokidar watcher.
An Array of output file Objects is returned in Bundles.bundles[n].output
. Each output file is a compiled source input file, and is formed as follows.
content
{String|Buffer} The file's compiled content, which can be a String or Buffer.data
{Object} File data, which is the merged result ofmerge( {}, file.source.data, bundle.data, Bundles.data )
.encoding
{String} ['utf8'] The file's encoding, eitherutf8
(default) orbinary
.isBuffer
{Boolean} Whether the file is a Buffer (true
) or String (false
).source
{Object} Information about the input source, containing the following properties:path
{String} Input source file path, relative tooptions.cwd
.cwd
{String} The configured root directory, copied fromoptions.cwd
. The file path is resolved aspath.join(cwd, path)
.content
{String|Buffer} Input source content, which can be a String or node Buffer. Forutf8
content, front matter and excerpts are removed and assigned to other properties below.data
{Object} Local front matter data, parsed by gray matter. Gray matter is flexible enough to understand many types of front matter (see details).
config
{String|Object|[Object]} User configuration. Accepts anything a config file will accept. Also accepts a String path to a config file, orconfig.bundles
can be a String path to a config file.- @return {Object} The global
Bundles
Object.
Run/compile bundles from user configuration. Likely the only method you will need to use.
config
{String|Object|[Object]} User configuration. Accepts anything a config file will accept. Also accepts a String path to a config file, orconfig.bundles
can be a String path to a config file.- @return {Object} The global
Bundles
Object.
Parse user configuration and "refresh" the global Bundles
Object. This means if configuration already exists in Bundles
, it is merged with the new user configuration, leaving in tact the original configuration Objects.
idsToRun
{String|String[]} Comma-separated String or Array of bundle IDs to run. Atrue
orfalsy
value will run all bundles.- @return {Object} The global
Bundles
Object.
Runs bundles configured in Bundles.bundles
.
- @return {Object} The global
Bundles
Object.
Resets global Bundles
Object to its original state. Useful in a long-running process when you need a fresh state.
You want to create your own bundler? Great, it's very easy!! A bundler
is a simple Function, wrapped in a Node module, with very little boilerplate.
Here's a simple bundler which appends a new line at the end of each file:
module.exports = (bundle, bundler) => {
bundle.output.forEach((file) => {
file.content += '\n';
});
// Always return the bundle.
return bundle;
};
You may return a Promise:
const fs = require('fs');
module.exports = (bundle = {}, bundler = {}) => {
// Return a promise...
return new Promise((resolve) => {
bundle.output.forEach((file) => {
file.content += '\n';
});
// In a Promise, always resolve to the bundle Object.
return resolve(bundle);
});
};
-
A
bundler
must return a Function -- synchronous or asynchronous -- which returns thebundle
Object. As illustrated above, thebundle
andbundler
Objects are passed to this Function. See how these Objects are configured. -
Become familiar with file Objects. These contain all the data you typically need to modify. A bundler typically will iterate through the files they wish to modify and always returns the
bundle
. -
The purpose of most bundlers are typically to 1) select the files they wish to modify, and 2) modify them according to its goals. Bundles provides the following Map "sets" of files which can be iterated over:
bundle.output
: Contains all files.bundle.changed
: Only files that have changed since last compile.bundle.removed
: Only files that were removed since alst compile.
-
Each file set above is a JavaScript Map, so you can iterate over them with
Map.forEach()
method, or lookup and interact with specific files withMap.get()
,Map.set()
, or any of the other useful Map methods. -
When iterating over files, it is strongly recommended to iterate over
bundle.changed
, notbundle.output
. This will give you better performance as it takes advantage of Bundles' incremental bundle feature to only rebundle files that have changed.