heya-globalize

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

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-globalizedoes 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 (distby 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 static require()calls, and assigning the module result to module.exports.
    • --es6— generate ES6 module prologue using static importstatements, 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— process files from srcdirectory, and its sub-directories. If specified, it overrides a value specified by browserGlobal["!from"]variable of package.jsondescribed below.
    • --target=trg— save processed files in trgdirectory retaining the original sub-directories. If specified, it overrides a value specified by browserGlobal["!dist"]variable of package.jsondescribed below.
    • --config=cfg— use configuration files (package.json, bower.json) from cfgdirectory. Default: "."(the current directory).

It is advisable to add it to a package.jsonfile of a project in question in scriptssection, 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 gruntor gulprunners. See npm-scriptsfor more details on scripts.

Configuration

The converter takes its configuration from following sources:

  • package.jswith following sections used:
    • maincan be used indirectly by browsersection.
    • nameto provide a default for a global variable that will host package modules.
    • browserto rename/skip files, while preparing a distribution for a browser.
    • browserGlobalsto 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 !distand !from.
  • bower.jsonwith following sections used:
    • ignoreto skip files, while preparing a distribution for a browser.

browserGlobals

This section of package.jsoncan 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 !rootis heya.example, ./awill be resolved as heya.example.a. Default: nameof the package taken from package.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:

  1. ./box, which defines the main functionality,
  2. ./boxExt, which extends the main functionality,
    • Depends on ./box.
  3. ./wrenchis 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 anviluses a global variable window.anvil. We want our package to be anchored at window.heya.box, our main module ./boxshould map to that variable as well, as ./boxExt, and all modules below ./belt/utilsshould be anchored at window.toolbox. This is how our browserGlobalsshould 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 dclto 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-coreis 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:

  1. package.jsonand bower.jsonare read from the current directory. The latter is optional.
  2. All *.jsfiles are collected from the current directory recursively.
  3. Certain directories are always excluded:
    1. node_modules
    2. bower_components
    3. The distdirectory (can be overridden in !distvalue of browserGlobalssection of package.json).
  4. Directories and files from ignoresection 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 distdirectory (can be overridden in !distvalue of browserGlobalssection of package.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 !rootvariable 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 idand filenameis 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 !rootto 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.jswas 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.jswas 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.jsis 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.jsis copied to dist/d.js(note that this file includes moduleobject, 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.jsis 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.jsis 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.jsabove:

a.jswas 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.jsabove:

a.jswas 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.jsabove:

a.jswas 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

Repository

https://github.com/heya/globalize


上一篇:heya-unify
下一篇:heya-ice

相关推荐

  • strong-globalize

    StrongLoop Globalize API strongglobalize This module is the runtime library for globalization. ...

    1 年前
  • heya-unit

    Super simple unit test harness. Unit Build statustravisimagetravisurl Dependenciesdepsimagedepsur...

    9 个月前
  • heya-unify

    Unify: a unification tool with a deep equivalence and partitioning of objects. Unify Build status...

    9 个月前
  • heya-ice

    ICE: logging, debugging, and assert facility. ICE Build statustravisimagetravisurl Dependenciesde...

    9 个月前
  • globalize

    A JavaScript library for internationalization and localization that leverages the official Unicode C...

    2 年前

官方社区

扫码加入 JavaScript 社区