heya-globalize
v1.2.1
Published
Make a browser version of JS files using globals from a Heya-style UMD, or a simple AMD.
Downloads
55
Maintainers
Readme
heya-globalize
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, consider using grunt-transform-amd, which is based on this project.
Install
npm install --save-dev heya-globalize
Usage
For simplicity heya-globalize
does not install a global command opting to be called directly:
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
— generate simple AMD prologue. This option is useful to remove UMD prologues to conserve space.--cjs
— generate CommonJS prologue using staticrequire()
calls, and assigning the module result tomodule.exports
.--es6
— generate ES6 module prologue using staticimport
statements, and declaring the module result asexport 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
— process files fromsrc
directory, and its sub-directories. If specified, it overrides a value specified bybrowserGlobal["!from"]
variable ofpackage.json
described below.--target=trg
— save processed files intrg
directory retaining the original sub-directories. If specified, it overrides a value specified bybrowserGlobal["!dist"]
variable ofpackage.json
described below.--config=cfg
— use configuration files (package.json
,bower.json
) fromcfg
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:
{
// ... 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:
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 for more details on scripts.
Configuration
The converter takes its configuration from following sources:
package.js
with following sections used:main
can be used indirectly bybrowser
section.name
to provide a default for a global variable that will host package modules.browser
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
.
- AMD/CommonJS/ES6 modes ignore the mapping itself, but still respect directory settings, like
bower.json
with following sections used: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
— a root variable to resolve all local modules. For example, if!root
isheya.example
,./a
will be resolved asheya.example.a
. Default:name
of the package taken frompackage.json
.!dist
— a folder name where to copy all transformed files. Default:dist
.!from
— 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:
./box
, which defines the main functionality,./boxExt
, which extends the main functionality,- Depends on
./box
.
- Depends on
./wrench
is a simple module../belt/utils
, which provides some additional functionality,./belt/utils/hammer/small
, which is a specialized algorithm.- Depends on
./boxExt
, and modules from an external packageanvil
.
- Depends on
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:
{
// ... 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:
./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 to accommodate existing (as of 1.1.3) globals:
{
// ... 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:
./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:
{
// ... package.json settings ...
"browserGlobals": {
"!root": "kore"
},
// ... more package.json settings ...
}
Now our modules will be mapped like that:
./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:
package.json
andbower.json
are read from the current directory. The latter is optional.- All
*.js
files are collected from the current directory recursively. - Certain directories are always excluded:
node_modules
bower_components
- The
dist
directory (can be overridden in!dist
value ofbrowserGlobals
section ofpackage.json
).
- Directories and files from
ignore
section ofbower.json
, if any, are excluded too. - 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 ofbrowserGlobals
section ofpackage.json
) preserving the directory structure.
The latter step means that files are copied like that:
./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 inbrowserGlobals
. - 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
{
// ... 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:
./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:
{
// ... 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
:
// 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
:
// 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
:
// 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!
— the undeclared one will generate a warning):
// 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
:
// 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
:
// 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:
node node_modules/heya-globalize/index.js --amd
Example: AMD
Using a.js
above:
a.js
was copied to dist/a.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:
node node_modules/heya-globalize/index.js --cjs
Example: CommonJS
Using a.js
above:
a.js
was copied to dist/a.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. It is invoked like that:
node node_modules/heya-globalize/index.js --es6
Example: ES6 module
Using a.js
above:
a.js
was copied to dist/a.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 — Bugfix: more conservative ES6 module prologue.
- 1.2.0 — Added command-line parameters to override configuration.
- 1.1.0 — Added new prologue generators: AMD, CommonJS, ES6 modules.
- 1.0.3 — Bugfixes: following sym links, and normalizing module names.
- 1.0.2 — More internal restructuring.
- 1.0.1 — Internal restructuring to accommodate grunt-transform-amd.
- 1.0.0 — The initial public release.
License
BSD