UNPKG

heya-globalize

Version:

Make a browser version of JS files using globals from a Heya-style UMD, or a simple AMD.

439 lines (328 loc) 16.4 kB
# `heya-globalize` [![Build status][travis-image]][travis-url] [![Dependencies][deps-image]][deps-url] [![NPM version][npm-image]][npm-url] This utility is a simple source transformer for JavaScript modules written using either a Heya-style UMD prologue, or a simple AMD prologue. It can produce JavaScript modules, which use browser globals as their dependences and exports. Such modules can be directly including into HTML with `<script>`, or concatenated and minified by a builder of your choice. Additionally it can convert to AMD, CommonJS, or ES6 modules. If your project uses [grunt](http://gruntjs.com/), consider using [grunt-transform-amd](https://github.com/uhop/grunt-transform-amd), which is based on this project. ## Install ```sh npm install --save-dev heya-globalize ``` ## Usage For simplicity `heya-globalize` does not install a global command opting to be called directly: ```sh node node_modules/heya-globalize/index.js node node_modules/heya-globalize/index.js --amd node node_modules/heya-globalize/index.js --cjs node node_modules/heya-globalize/index.js --es6 ``` This command will convert all files that is detected as Heya-style UMD or simple AMD to globals copying them to a folder of your choice (`dist` by default). Alternative versions with an explicit option will generate AMD, CommonJS, or ES6 modules. Additional options allow to specify a source directory for files to be copied, and a target directory for transformed files. Full list of available options: * Format of generated modules: * `--amd` &mdash; generate simple AMD prologue. This option is useful to remove UMD prologues to conserve space. * `--cjs` &mdash; generate CommonJS prologue using static `require()` calls, and assigning the module result to `module.exports`. * `--es6` &mdash; generate ES6 module prologue using static `import` statements, and declaring the module result as `export default`. * Otherwise, if no above options are specified, an optimized prologue is generated, which relies on browser globals, and can assign the module result to a global as well. * Directories: * `--source=src` &mdash; process files from `src` directory, and its sub-directories. If specified, it overrides a value specified by `browserGlobal["!from"]` variable of `package.json` described below. * `--target=trg` &mdash; save processed files in `trg` directory retaining the original sub-directories. If specified, it overrides a value specified by `browserGlobal["!dist"]` variable of `package.json` described below. * `--config=cfg` &mdash; use configuration files (`package.json`, `bower.json`) from `cfg` directory. Default: `"."` (the current directory). It is advisable to add it to a `package.json` file of a project in question in `scripts` section, so it is always available: ```js { // ... package.json settings ... "scripts": { // ... project-specific scripts ... "dist": "node node_modules/heya-globalize/index.js" }, // ... more package.json settings ... } ``` This script can be invoked like that: ```sh npm run dist ``` It is possible to run the script on at lifecycle events, e.g., after installing that package, or integrate with existing project tooling, such as `grunt` or `gulp` runners. See [npm-scripts](https://docs.npmjs.com/misc/scripts) for more details on scripts. ## Configuration The converter takes its configuration from following sources: * `package.js` with following sections used: * [`main`](https://docs.npmjs.com/files/package.json#main) can be used indirectly by `browser` section. * [`name`](https://docs.npmjs.com/files/package.json#name) to provide a default for a global variable that will host package modules. * [`browser`](https://github.com/defunctzombie/package-browser-field-spec) to rename/skip files, while preparing a distribution for a browser. * `browserGlobals` to define how modules mapped to globals. This section is described in details below. * AMD/CommonJS/ES6 modes ignore the mapping itself, but still respect directory settings, like `!dist` and `!from`. * `bower.json` with following sections used: * [`ignore`](https://github.com/bower/spec/blob/master/json.md#ignore) to skip files, while preparing a distribution for a browser. ### `browserGlobals` This section of `package.json` can contain a simple key/value pairs as an object, where keys are module names, and values are corresponding globals. If a module is not listed there, its parents will be checked. If a parent is specified, it will be used to form an accessor. There are two special keys: * `!root` &mdash; a root variable to resolve all local modules. For example, if `!root` is `heya.example`, `./a` will be resolved as `heya.example.a`. Default: `name` of the package taken from `package.json`. * `!dist` &mdash; a folder name where to copy all transformed files. Default: `dist`. * `!from` &mdash; a folder name to serve as a root for source files. Default: the project's top folder. Some modules modify existing packages by augmenting their exports. They do not create their own globals using existing ones. In this case, a value of such module should be a global variable to use when referring to this module, but it should be prefixed with `'!'`. This prefix means that modules result is not assigned anywhere on its definition, the rest defines how to access it. External modules should be always resolved explicitly in `browserGlobals`. #### Example #1 We have five modules: 1. `./box`, which defines the main functionality, 2. `./boxExt`, which extends the main functionality, * Depends on `./box`. 3. `./wrench` is a simple module. 4. `./belt/utils`, which provides some additional functionality, 5. `./belt/utils/hammer/small`, which is a specialized algorithm. * Depends on `./boxExt`, and modules from an external package `anvil`. We know that `anvil` uses a global variable `window.anvil`. We want our package to be anchored at `window.heya.box`, our main module `./box` should map to that variable as well, as `./boxExt`, and all modules below `./belt/utils` should be anchored at `window.toolbox`. This is how our `browserGlobals` should look like: ```js { // ... package.json settings ... "browserGlobals": { "!root": "heya.box", "./box": "heya.box", "./boxExt": "!heya.box", "./belt/utils": "toolbox", "anvil": "anvil" }, // ... more package.json settings ... } ``` With this configuration our modules are mapped to globals like that: ```txt ./box => heya.box ./boxExt => heya.box ./wrench => heya.box.wrench ./belt/utils => toolbox ./belt/utils/hammer/small => toolbox.hammer.small anvil/x => anvil.x anvil/y/z => anvil.y.z ``` #### Example #2: dcl A possible map for the main part of [dcl](https://github.com/uhop/dcl) to accommodate existing (as of 1.1.3) globals: ```js { // ... package.json settings ... "browserGlobals": { "!root": "dcl", "./mini": "dcl", "./legacy": "dcl", "./dcl": "!dcl", "./debug": "dclDebug", "./advise": "advise", "./inherited": "!dcl.inherited" }, // ... more package.json settings ... } ``` #### Example #3: super simple For a simple mapping of all local files to a single root variable, we don't need to specify anything. For example, if our module is called `our-core`, following modules will be mapped like that: ```txt ./a => window["our-core"].a ./b => window["our-core"].b ./b/c => window["our-core"].b.c ./d/e/f => window["our-core"].d.e.f ``` Note that `our-core` is used as an anchor variable for all modules, but it is not an identifier in a JavaScript sense, so it is used with `[]` notation, rather than a dot notation. Let's fix it, and assign a simple root variable: ```js { // ... package.json settings ... "browserGlobals": { "!root": "kore" }, // ... more package.json settings ... } ``` Now our modules will be mapped like that: ```txt ./a => kore.a ./b => kore.b ./b/c => kore.b.c ./d/e/f => kore.d.e.f ``` ### Algorithm The precise algorithm works like that: 1. `package.json` and `bower.json` are read from the current directory. The latter is optional. 2. All `*.js` files are collected from the current directory recursively. 3. Certain directories are always excluded: 1. `node_modules` 2. `bower_components` 3. The `dist` directory (can be overridden in `!dist` value of `browserGlobals` section of `package.json`). 4. Directories and files from `ignore` section of `bower.json`, if any, are excluded too. 5. The remaining files are processed one by one. The result of a successful transformation is copied to `dist` directory (can be overridden in `!dist` value of `browserGlobals` section of `package.json`) preserving the directory structure. The latter step means that files are copied like that: ```txt ./a.js => ./dist/a.js ./b.js => ./dist/b.js ./b/c.js => ./dist/b/c.js ./d/e/f.js => ./dist/d/e/f.js ``` When files are processed they are checked against a standard Heya-style UMD header (it covers both AMD and CommonJS-style modules, but no globals), or a simple AMD header (the very first line starts with `define(`, and lists all dependencies as an array of strings). If a file is not identified as one of those, it is ignored and skipped. While resolving module names, the directory structure is preserved as well, and reflected as subobjects using a dot or `[]` notation (whichever is more appropriate). Important details: * All local modules are assumed to be anchored at `!root` variable specified in `browserGlobals`. * All modules are checked against `browserGlobals`, and if it is there, the specified variable is used. * Otherwise all parents are checked agaist `browserGlobals`, and the closest parent's variable is used for the rest as an anchor. If a module depends on a special module called `module`, a new object is generated and two its properties `id` and `filename` is set to a name of the current module. That way a module may report its name in errors and exceptions. Example #5 below shows a generated code. #### Example #4: multiple parents ```js { // ... package.json settings ... "browserGlobals": { "!root": "kore", "./b": "kore.base", "./b/c/d": "kore.bcd" }, // ... more package.json settings ... } ``` Given this map we can resolve following modules like that: ```txt ./a => kore.a ./b/a => kore.base.a ./b/c => kore.base.c ./b/c/d/e => kore.bcd.e ./b/c/d/e/f => kore.bcd.e.f ``` External modules are resolved the same way as local modules, but they require that at least top-level package names were defined, because they cannot use `!root` to form a name. #### Example #5: transforms This is complete example, which shows original and transformed sources. The config is: ```js { // ... package.json settings ... "browserGlobals": { "!root": "heya.example", "boom": "BOOM", "./d": "!heya.D", "./f": "!heya.F" }, // ... more package.json settings ... } ``` `a.js` was copied to `dist/a.js`: ```js // before /* UMD.define */ (typeof define=="function"&&define||function(d,f,m){m={module:module,require:require};module.exports=f.apply(null,d.map(function(n){return m[n]||require(n)}))}) (["./b", "./c"], function(b, c){}); // after (function(_,f){window.heya.example.a=f(window.heya.example.b,window.heya.example.c);}) (["./b", "./c"], function(b, c){}); ``` `b.js` was copied to `dist/b.js`: ```js // before /* UMD.define */ (typeof define=="function"&&define||function(d,f,m){m={module:module,require:require};module.exports=f.apply(null,d.map(function(n){return m[n]||require(n)}))}) (["./c"], function(c){}); // after (function(_,f){window.heya.example.b=f(window.heya.example.c);}) (["./c"], function(c){}); ``` `c.js` is copied to `dist/c.js`: ```js // before /* UMD.define */ (typeof define=="function"&&define||function(d,f,m){m={module:module,require:require};module.exports=f.apply(null,d.map(function(n){return m[n]||require(n)}))}) ([], function(){}); // after (function(_,f,g){g=window;g=g.heya||(g.heya={});g=g.example||(g.example={});g.c=f();}) ([], function(){}); ``` `d.js` is copied to `dist/d.js` (note that this file includes `module` object, two modules from a declared external module `boom`, and an undeclared one `wham!` &mdash; the undeclared one will generate a warning): ```js // before define(['module', 'boom', 'boom/Hello-World', 'wham!'], function(module, boom, hello, wham){}); // after (function(_,f,m){m={};m.id=m.filename="./d";f(m,window.BOOM,window.BOOM["Hello-World"],window["wham!"]);}) (['module', 'boom', 'boom/Hello-World', 'wham!'], function(module, boom, hello, wham){}); ``` `e.js` is copied to `dist/e.js`: ```js // before define(['./d'], function(d){}); // after (function(_,f){window.heya.example.e=f(window.heya.D);}) (['./d'], function(d){}); ``` `f.js` is copied to `dist/f.js`: ```js // before define(["./b", "./c"], function(b, c){}); // after (function(_,f){f(window.heya.example.b,window.heya.example.c);}) (["./b", "./c"], function(b, c){}); ``` As can be seen, the same module functions are used with new prologues, which replaces `define()` or an Heya-style UMD prologue, which itself approximates `define()` as well. New prologues form identical arguments using globals, and assign their results to correct global variables. ## Converting to AMD This mode behaves just like the browser globals mode, but produces AMD modules. It is invoked like that: ```sh node node_modules/heya-globalize/index.js --amd ``` ### Example: AMD Using `a.js` above: `a.js` was copied to `dist/a.js`: ```js // before /* UMD.define */ (typeof define=="function"&&define||function(d,f,m){m={module:module,require:require};module.exports=f.apply(null,d.map(function(n){return m[n]||require(n)}))}) (["./b", "./c"], function(b, c){}); // after define (["./b", "./c"], function(b, c){}); ``` ## Converting to CommonJS This mode behaves just like the browser globals mode, but produces CommonJS modules. It is invoked like that: ```sh node node_modules/heya-globalize/index.js --cjs ``` ### Example: CommonJS Using `a.js` above: `a.js` was copied to `dist/a.js`: ```js // before /* UMD.define */ (typeof define=="function"&&define||function(d,f,m){m={module:module,require:require};module.exports=f.apply(null,d.map(function(n){return m[n]||require(n)}))}) (["./b", "./c"], function(b, c){}); // after (function(_,f){module.exports=f(require("./b"),require("./c"));}) (["./b", "./c"], function(b, c){}); ``` ## Converting to ES6 module This mode behaves just like the browser globals mode, but produces ES6 modules compatible with [Babel](https://babeljs.io/). It is invoked like that: ```sh node node_modules/heya-globalize/index.js --es6 ``` ### Example: ES6 module Using `a.js` above: `a.js` was copied to `dist/a.js`: ```js // before /* UMD.define */ (typeof define=="function"&&define||function(d,f,m){m={module:module,require:require};module.exports=f.apply(null,d.map(function(n){return m[n]||require(n)}))}) (["./b", "./c"], function(b, c){}); // after import m0 from "./b";import m1 from "./c";export default (function(_,f){return f(m0,m1);}) (["./b", "./c"], function(b, c){}); ``` ## Versions - 1.2.1 &mdash; *Bugfix: more conservative ES6 module prologue.* - 1.2.0 &mdash; *Added command-line parameters to override configuration.* - 1.1.0 &mdash; *Added new prologue generators: AMD, CommonJS, ES6 modules.* - 1.0.3 &mdash; *Bugfixes: following sym links, and normalizing module names.* - 1.0.2 &mdash; *More internal restructuring.* - 1.0.1 &mdash; *Internal restructuring to accommodate [grunt-transform-amd](https://github.com/uhop/grunt-transform-amd).* - 1.0.0 &mdash; *The initial public release.* ## License BSD [npm-image]: https://img.shields.io/npm/v/heya-globalize.svg [npm-url]: https://npmjs.org/package/heya-globalize [deps-image]: https://img.shields.io/david/heya/globalize.svg [deps-url]: https://david-dm.org/heya/globalize [dev-deps-image]: https://img.shields.io/david/dev/heya/globalize.svg [dev-deps-url]: https://david-dm.org/heya/globalize#info=devDependencies [travis-image]: https://img.shields.io/travis/heya/globalize.svg [travis-url]: https://travis-ci.org/heya/globalize