• Stars
    star
    207
  • Rank 189,769 (Top 4 %)
  • Language
    JavaScript
  • License
    MIT License
  • Created over 4 years ago
  • Updated over 2 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Convert CommonJS (CJS) modules to UMD and ESM formats

Moduloze

npm Module Modules License

Convert CommonJS (CJS) modules to UMD and ESM formats.

Overview

Moduloze enables authoring JS modules in the CommonJS (CJS) format that's native to the Node.js ecosystem, and converting those modules to Universal Module Definition (UMD) and ES Modules (ESM) formats.

UMD is particularly useful in browsers where ESM is not already being used in the application. CJS continues to work fully in all versions of Node, but in the latest Node versions, the ESM format for modules is also working, albeit with some unique limitations. UMD also works in all versions of Node, though it basically works identically to CJS.

The most common envisioned use-case for Moduloze is to author a utility that's designed to work in both Node and the browser, as many OSS libs/frameworks are. By authoring in the CJS format, and using Moduloze as a build process, the UMD/ESM formats are seamlessly available for use in the browser without additional authoring effort.

Alternatively, Moduloze can be used as a one-time "upgrade" code-mod, to take a set of CJS modules and convert them to ESM format.

Moduloze comes as a library that can be used directly, but also includes a helpful CLI that drives a lot of the logic necessary to convert a tree of files from CJS to UMD/ESM formats. It's recommended to use the CLI unless there are specific concerns that must be worked around.

Module Format Conversion

Moduloze recognizes and handles a wide range of typical CJS require(..) and module.exports usage patterns.

For example, consider this CJS import:

var Whatever = require("./path/to/whatever.js");

The ESM-build equivalent would (by default) be:

import Whatever from "./path/to/whatever.js";

The UMD-build equivalent is handled in the UMD wrapper, where Whatever would automatically be set as an identifier (parameter) in scope for your UMD module code; thus, the entire require(..) containing statement would be removed.

And for this CJS export:

module.exports = Whatever(42);

The ESM-build equivalent would (by default) be:

export default Whatever(42);

The UMD-build equivalent would be:

// auto inserted at the top of a UMD module that has exports
var _exp1 = {};

// ..

_exp1 = Whatever(42);

// ..

// auto inserted at the bottom of a UMD module that has exports
return _exp1;

For a much more detailed illustration of all the different conversion forms, please see the Conversion Guide.

Unsupported

There are variations which are not supported, since they are impossible (or impractical) to express in the target UMD or (more often) ESM format.

For example, require(..) calls for importing dependencies must have a single string-literal argument. Any sort of variable or expression in the argument position will reject the require(..) call and fail the build. The main reason is that ESM import statements require string literals.

Yes, JS recently added a dynamic import(..) function, which can handle expression arguments, but import(..) has a bunch of other usage nuances that are impractical for Moduloze to support, such as being async (returning promises). Moreover, the UMD wrapper pattern doesn't support arbitrary expression logic for computing the dependency paths; it would make the UMD wrapper intractably complex.

Both require(..) calls and module.exports must also be at the top level scope, not inside loops or conditionals. Again, this is primarily because ESM import and export statements must be at the top level scope and not wrapped in any block or other statement. Additionally, supporting these variations would make the UMD wrapper intractably complex.

For more details on limitations, please see the Conversion Guide.

Dependency Map

The dependency-map is required configuration for the UMD build, as it maps a specifier path (like src/whatever/something.js) to a lexical variable name in the UMD build format (like Something or MyModule). It also serves as a validation check, as by default dependencies encountered that are not in the dependency-map are treated as "unknown" and result in a fatal error.

If the ignoreUnknownDependency configuration setting is set, these errors will be suppressed, and Moduloze will auto-generate names (like Mz_34281238375) for these unknown dependencies. For most environments, this shouldn't break the code, but for the browser usage of the UMD build, where global variables are registered, these auto-generated dependency names are unpredictable/unreliable and will thus be essentially inaccessible to the rest of your application. As such, you should try to avoid relying on auto-naming of unknown dependencies.

Consider a module like this:

var Something = require("./whatever/something.js");
var Another = require("./another/index.js");

// ..

A suggested dependency-map registering this module's dependencies might look like:

{
    "whatever/something.js": "Something",
    "another/index.js": "Another"
}

The leading ./ in the paths in your Node code does not need to be included in the dependency-map entries, as it's assumed to be relative to the root from path you specifiy when running Moduloze. Including the unnecessary ./ in dependency-map entries is allowed, but discouraged.

The names (Something or Another, above) in the dependency map must be unique, and must be suitable as lexical identifiers (aka, variables) -- so no spaces or punctuation!

The names specified are arbitrary, and not particularly relevant outside of your built modules, except when being used in the browser environment with the UMD format. In that case, those names indicate the global variables registered for your modules; so, the choices there matter to other parts of your application if they rely on being able to access these built modules.

External Dependencies

If a module requires one or more external dependencies (not handled by Moduloze) -- for example, built-in Node packages like fs or npm-installed packages like lodash, these will by default be treated as "unknown dependencies".

It's strongly recommended not to rely on auto-naming of external dependencies via the ignoreUnknownDependency configuration setting. The built code may still work correctly, it might behave in unexpected ways depending on the conditions in how the code is run.

The more preferred way to handle external dependencies is to affirmatively list them in the dependency-map configuration setting, using a special ::: prefix on the key (specifier path).

For example, consider this module:

var fs = require("fs");
var lodash = require("lodash");

var myModule = require("./src/my-module.js");

// ..

The suggested dependency-map would be:

{
    ":::fs": "NodeFS",
    ":::lodash": "LoDash",
    "src/my-module.js": "MyModule"
}

The ::: prefix tells Moduloze not to apply path semantics to these specifiers, and to leave them as-is in the built modules. That allows you to ensure those external dependencies are otherwise provided in the target environment (via the indicated specifier or name), even though not handled by Moduloze.

Again, the choice of names (like "NodeFS" and "LoDash" above) are arbitrary -- except of course they still need to be unique and also suitable as lexical variables.

CLI

npm Module

To use the CLI:

mz --from="./src" [--to="./dist"] [--recursive] [--build-umd] [--build-esm] [--bundle-umd] [--dep-map="./path/to/dep-map.json"] [--config="./path/to/.mzrc"] [--minify] [--prepend="prepend some text"]

See mz --help output for all available parameter flags.

CLI Flags

  • --from=PATH: specifies the path to a directory (or a single file) containing the module(s) to convert; defaults to ./ in the current working directory

  • --to=PATH: specifies the path to a directory to write the converted module file(s), in sub-directories corresponding to the chosen build format (umd/ and esm/, respectively); defaults to ./.mz-build in the current working directory

  • --recursive (alias -r): traverse the source directory recursively

  • --build-umd (alias -u): builds the UMD format (umd/* in the output path)

  • --build-esm (alias -e): builds the ESM format (esm/* in the output path)

  • --bundle-umd (alias -b): specifies a path to write out a UMD bundle file (single UMD module exposing/exporting all converted UMD modules, by name); if specified but empty, defaults to ./umd/bundle.js in the output directory; if omitted, skips UMD bundle

  • --dep-map (alias -m): specifies the path to a JSON file to load the dependency map from; defaults to "./package.json", in which it will look for a mz-dependencies field to get the dependency map contents; otherwise, should be to a standalone JSON file with the dependency map contents specified directly

  • --minify (alias -n): minify the output (using terser)

  • --prepend (alias -p): prepend some text (like copyright info) to each file. If the token #FILENAME# is present in the text, it will be replaced by each output file's base filename.

  • --config (alias -c): specifies the path to a configuration file (JSON format) for some or all settings; defaults to ./.mzrc in the current working directory; see Configuration Settings

The CLI tool will also read the following settings from the current process environment (or source them from a .env file in the current working directory):

  • RCPATH: corresponds to the --config parameter (see above)
  • FROMPATH: corresponds to the --from parameter (see above)
  • TOPATH: corresponds to the --to parameter (see above)
  • DEPMAPPATH: corresponds to the --dep-map parameter (see above)

Library

npm Module Modules

To use the library directly in code, instead of as a CLI tool:

var {
    build,
    bundleUMD,            /* optional */
    umdIndex,             /* optional */
    esmIndex,             /* optional */
    defaultLibConfig,     /* optional */
} = require("moduloze");

build(..)

The build(..) method is the primary utility of the library, that does the main work of converting a single module from its CJS format to UMD and/or ESM formats.

Parameters:

  • config (object): configuration object; (see Configuration Settings)

  • pathStr (string): the path to the CJS module file being converted

  • code (string): contents of the CJS module file being converted

  • depMap (object): a map of the dependencies (from their path to a local/common name for the module) that will/may be encountered in this file's require(..) statements

The return value from build(..) is an object containing properties corresponding the chosen build format(s): umd (for a UMD-format build) and esm (for an ESM-format build). Each build-format result object contains properties holding the converted code and other relevant metadata:

  • code (string): converted module code ready to write to another file

  • ast (string): the Babylon parser's AST (node tree object)

  • depMap (object): the depMap as passed into build(..); may have been modified to include discovered resources/dependencies

  • refDeps (object): map of dependencies actually encountered in the file (same structure as depMap parameter above)

  • pathStr: (string): the resolved/normalized (and potentially renamed) path for the source module

  • origPathStr: (string): same as pathStr but without the renaming that may have occurred as a result of .mjs or .cjs configuration settings

  • name (string): the local/common name of the module (from the depMap, or auto-generated if unknown)

Example usage:

var fs = require("fs");
var { build } = require("moduloze");

var srcPath = "src/whatever.js";
var moduleContents = fs.readFileSync(srcPath,{ encoding: "utf-8" });

var config = {
    buildUMD: true,
    buildESM: true
};

var depMap = {
    "src/whatever.js": "Whatever",
    "src/another.js": "Another"
};

var { umd, esm } = build(
    config,
    srcPath,
    moduleContents,
    depMap
);

console.log(umd.code);
// (function UMD(name,context,depen...

console.log(esm.code);
// import Another from "./anoth...

bundleUMD(..)

Docs coming soon.

umdIndex(..)

Docs coming soon.

esmIndex(..)

Docs coming soon.

defaultLibConfig(..)

Docs coming soon.

Configuration Settings

The configuration object (either in a JSON file like .mzrc or passed into the library directly) can include the following settings:

  • buildESM (boolean): build the ESM format; defaults to false

  • buildUMD (boolean): build the UMD format; defaults to false

  • ignoreUnknownDependency (boolean): suppresses exceptions when encountering an require(..) with a dependency path that is not in the known dependency map, useful if you rely on external dependencies that aren't being converted by Moduloze; defaults to false

  • ignoreCircularDependency (boolean): suppresses exceptions when encountering a circular dependency in the converted modules; defaults to false; Note: because of how UMD works, circular dependencies will always fail in UMD, but ESM circular dependencies are generally OK.

  • .mjs (boolean): rename outputed ESM modules from .js (or .cjs) file extensions to .mjs, which can make using the ESM format modules in Node easier; defaults to false; Note: the "." is intentionally part of the configuration name!

  • .cjs (boolean): when traversing the source files that have .cjs file extensions, rename them to .js for the UMD build and either .js (or .mjs, depending on that configuration) for the ESM build; defaults to false; Note: the "." is intentionally part of the configuration name!

  • namespaceImport (boolean): for ESM builds, assume dependencies should be imported as namespaces (import * as .. from ..) rather than as default imports (import .. from ..); defaults to false; for example: xyx = require("./xyz.js") will be converted to import * as xyz from "./xyz.js" instead of import xyz from "./xyz.js"

  • namespaceExport (boolean): for ESM builds, when generating the "index" roll-up build, assume dependencies should be re-exported as namespaces (export * as .. from ..) rather than as default exports (export .. from ..); defaults to false; for example: use export * as xyz from "./xyz.js" instead of export { default as xyz } from "./xyz.js"; Note: the xyz identifier name here comes from the dependency map (or auto-generated, if unknown)

  • exportDefaultFrom (boolean): for ESM builds, overrides namespaceExport to switch to the TC39-proposed export xyz from "./xyz.js" form for the index builds (warning: not yet officially part of the JS specification); defaults to false

CLI-only configurations

  • from string: path for the source of module(s) to convert (CLI-only configuration)

  • to string: path to write out the converted modules (CLI-only configuration)

  • depMap string, object: if a string, path to load the dependency map; otherwise, an object that contains the dependency map

  • bundleUMDPath string: (see the CLI --bundle-umd parameter)

  • skip array: a list of strings containing glob patterns that (relatively) specify files from the source path to skip over

  • copyOnSkip boolean: copy skipped files to the target output path

  • copyFiles array: a list of strings containing paths of files to copy from the source path to the target build output

  • recursive boolean: (see the CLI --recursive parameter)

  • buildESM boolean: (see the CLI --build-esm parameter)

  • buildUMD boolean: (see the CLI --build-umd parameter)

  • generateIndex boolean: for each build format, generates the "index.js" equivalent roll-up that "imports and re-exports" all source modules

License

License

All code and documentation are (c) 2021 Kyle Simpson and released under the MIT License. A copy of the MIT License is also included.

More Repositories

1

You-Dont-Know-JS

A book series on JavaScript. @YDKJS on twitter.
178,746
star
2

Functional-Light-JS

Pragmatic, balanced FP in JavaScript. @FLJSBook on twitter.
JavaScript
16,593
star
3

LABjs

Loading And Blocking JavaScript: On-demand parallel loader for JavaScript with execution order dependencies
HTML
2,277
star
4

asynquence

Asynchronous flow control (promises, generators, observables, CSP, etc)
JavaScript
1,743
star
5

CAF

Cancelable Async Flows (CAF)
JavaScript
1,332
star
6

monio

The most powerful IO monad implementation in JS, possibly in any language!
JavaScript
1,049
star
7

TNG-Hooks

Provides React-inspired 'hooks' like useState(..) for stand-alone functions
JavaScript
1,011
star
8

native-promise-only

A polyfill for native ES6 Promises as close as possible (no extensions) to the strict spec definitions.
JavaScript
723
star
9

A-Tale-Of-Three-Lists

Comparing various async patterns for a single demo
JavaScript
651
star
10

fasy

FP iterators that are both eager and asynchronous
JavaScript
546
star
11

FPO

FP library for JavaScript. Supports named-argument style methods.
JavaScript
449
star
12

youperiod.app

YouPeriod.app -- the privacy-first period tracking app
JavaScript
442
star
13

JSON.minify

Simple minifier for JSON to remove comments and whitespace
409
star
14

TypL

The Type Linter for JS
JavaScript
374
star
15

h5ive-DEPRECATED

**DEPRECATED** A collection of thin facade APIs wrapped around HTML5 JavaScript features.
JavaScript
324
star
16

eslint-plugin-proper-arrows

ESLint rules to ensure proper arrow function definitions
JavaScript
305
star
17

foi-lang

Foi: a different kind of functional programming language
JavaScript
303
star
18

grips

Simple-logic templates
JavaScript
287
star
19

ES-Feature-Tests

Feature Tests for JavaScript
JavaScript
199
star
20

let-er

DEPRECATED: Transpile non-ES6 let-blocks into ES6 (or ES3)
JavaScript
192
star
21

Incomplete-JS

"The Incomplete Guide to JavaScript" (book). @IncompleteJS on twitter.
190
star
22

revocable-queue

Specialized async queue data structure, supports revocation of values
JavaScript
176
star
23

deePool

highly-efficient pool for JavaScript objects
JavaScript
118
star
24

getify.github.io

JavaScript
113
star
25

concrete-syntax-tree

Defining a standard JavaScript CST (concrete syntax tree) to complement ASTs
107
star
26

eslint-plugin-proper-ternary

ESLint rules to ensure proper usage of ternary/conditional expressions
JavaScript
96
star
27

cloud-sweeper

A casual game built for the web.
JavaScript
94
star
28

BikechainJS

JavaScript VM engine (powered by V8); server-side environment modules; server-side synchronous web app controllers
C++
82
star
29

wepuzzleit

Demo PoC game for various advanced HTML5 js APIs
JavaScript
79
star
30

workshop-periodic-table

66
star
31

elasi

EL/ASI: Encrypt Locally, Account Secure Interchange
JavaScript
62
star
32

remote-csp-channel

Remote bridge for CSP channels
JavaScript
55
star
33

ScanTree

Scan a JS file tree to build an ordered and grouped dependency listing
JavaScript
51
star
34

dwordly-game

A game where words dwindle down to the shortest possible
JavaScript
42
star
35

stable-timers

timers with less race conditions
JavaScript
38
star
36

emdash

Simple blogging with node/iojs + GitHub.
36
star
37

domio

DOM and Event helpers for Monio
JavaScript
30
star
38

eslint-plugin-no-optional-call

ESLint plugin to disallow the optional-call operator
JavaScript
30
star
39

eslint-plugin-arrow-require-this

DEPRECATED: ESLint rule to require arrow functions to reference the 'this' keyword
JavaScript
28
star
40

import-remap

Rewrite ES module import specifiers using an import-map.
JavaScript
25
star
41

gum-try-hd

Try to enforce HD (16:9) camera aspect ratio for web-video calls
JavaScript
25
star
42

Mock-DOM-Resources

A mock of (parts of) the DOM API to simulate resource preloading and loading
JavaScript
25
star
43

make-a-game

some project files for a tutorial on making a simple web game
JavaScript
25
star
44

mpAjax

framework plugin for handling multi-part Ajax responses
JavaScript
23
star
45

asyncGate.js

DEPRECATED. asynchronous gate for javascript
JavaScript
21
star
46

tic-tac-toe-workshop

Workshop files for building tic-tac-toe in JS and <canvas>
21
star
47

esre

esre: fully configurable JS code formatting
20
star
48

workshop-chess-diagonals

17
star
49

featuretests.io

JavaScript Feature Tests... as a service
JavaScript
16
star
50

FoilScript

FoilScript: a new dialect of JS that fixes the sucky parts but still looks and feels like JS
16
star
51

literalizer

Specialized heuristic lexer for JS to identify complex literals
JavaScript
16
star
52

normalize-selector

Normalize CSS selectors
JavaScript
14
star
53

DOMEventBridge

Bridge DOM events to a JS event hub (for pubsub)
JavaScript
14
star
54

pong-around-workshop

Workshop files for building a pong-variant game in JS and <canvas>
12
star
55

workshop-wordy-unscrambler

11
star
56

shortie.me

Proof-of-concept demo for server-side JavaScript driven "middle-end" architecture
JavaScript
11
star
57

workshop-knights-dialer

8
star
58

middleend-boilerplate

Boilerplate Starting Point for Middle-end Web Architecture
JavaScript
8
star
59

demo-app-weatheround

JavaScript
7
star
60

the-economy-of-keystrokes-slides

Slides code built for "The Economy of Keystrokes" talk
JavaScript
6
star
61

santa-connect-app

Santa Connect: keeping track of your kids' nice/naughty status
JavaScript
5
star
62

nyc-bug-demo

bug demo for NYC code coverage tool
JavaScript
4
star
63

unnamed

unnamed (for now). nothing to see here. ;-)
JavaScript
2
star
64

my-lifesheets

CSS
1
star
65

breakthewebforward.com

JavaScript
1
star