JavaScript package and module system for interactive development
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
cjs
doc
src
tests
tools
.gitignore
.npmignore
.travis.yml
CHANGELOG.md
LICENSE
README.md
index.js
package.json
systemjs-init.js

README.md

lively.modules Build Status

JavaScript package and module system for interactive development.

Module and package system

Live and interactive devlopment

Goals

lively.modules provides a framework for loading, defining and interactively modifying JavaScript modules. A module is an entity containing JavaScript source code that adheres to the definition of the ECMAScript Language Specification.

For an intro to the topic see ES6 In Depth: Modules at mozilla.org

Its main purpose is to

  • Provide an interface to load modules and groups of modules (packages)
  • Provide an interface to access and modify the runtime state of a module, i.e. its
    • dependencies (modules imported and modules that import it)
    • imported and exported values
    • source code
    • internal definitions
  • Provide a user friendly and practical implementation of how imported modules are resolved *.
  • For the purpose of grouping modules together and providing a method for module lookup introduce a lightweight concept of a package.

* The ES specification explicitly leaves the semantics for "HostResolveImportedModule" open to module implementations

For more please see doc/rationale.md.

Usage

To load lively.modules you can use the pre-build dist/lively.modules-with-lively.vm.js file. Once that happens the lively.modules global will provide an interface for loading packages, modifying modules, evaluating source code in module contexts etc.

So on a webpage you would typically link via

<script src="../node_modules/lively.modules/dist/lively.modules-with-lively.vm.js"></script>

See the examples in lively-system-examples for more details.

API

main interface

lively.modules.importPackage(packageName)

To load a project into your runtime you will typically use lively.modules.importPackage('some-package-name'). 'some-package-name' should resolve to a directory with a JSON package config file (typically package.json) that at least defines a name field. The package will be imported, i.e. the main module of the package will be loaded via lively.modules.System.import('some-package-name/index.js'). By default the name of main is 'index.js' but this can be customized via the main field of the package config file.

The result of the importPackage call is the promise for loading the main module.

Specifics of the lively package format

The main purpose of the lively package format is to make it easy to integrate dependent packages in the lively.module and es6 module systems. It allows you to define a "lively" field in the main JSON that allows to set a separate main module, a "packageMap" object that maps names that can be used in import statements to directories of sub-packages. When sub-packages are discovered while importing a package, those are recursively imported as well.

Here is an example how a config inside a package.json file could look like.

{
  "name": "some-package",
  "main": "main-for-non-es6.js",
  "lively": {
    "main": "for-es6.js",
    "packageMap": {
      "dep1": "./node_modules/dep1",
      "dep2": "./libs/dep2"
    }
  }
}

For more examples, see lively.modules/package.json, or lively.ast/package.json.

lively.modules.System

The main lively.modules interface provides access to a System loader object (currently from the SystemJS library that has some improvements added, e.g. the name normalization respects the lively package conventions, translate is used to instrument code by default, etc.

By default the loader instance is the same as the global loader (e.g. window.System). Note: The System instance can be easily changed to support multiple, isolated environnments.

Example:

var testSystem = lively.modules.getSystem("my-test-system");
lively.modules.changeSystem(testSystem, true); // true: make the System global
System.import("some-module"); // uses the new System loader

Now all state (what modules are loaded, their metadata, etc) are stored in testSystem. Changing to another System allows to define different name resolution approach etc.

Side note: Since all System related implementation functions defined in the modules in src/ will take a System loader object as first parameter, the implementation is loader independent.

Loader state / module state

  • lively.modules.loadedModules(): Returns a list of ids of the currently loaded modules.

  • lively.modules.printSystemConfig(): Returns a stringified version of the SystemJS config. Useful for debugging SystemJS issues

lively.modules.requireMap()

Will return a JS object whose keys are module ids and the corresponding values are lists of module ids of those modules that dependent on the key module (including the key module itself). I.e. the importers of that module.

instrumentation

By default lively.modules will hook into the System.translate process so that source code of modules get transformed to allow recording of their internal evaluation state (that is then captured in moduleEnvs). You can enable and disable this behavior via

  • lively.modules.wrapModuleLoad()
  • lively.modules.unwrapModuleLoad()

evaluation

ModuleInterface

lively.modules.module(moduleId)

Returns an instance of ModuleInterface with the following methods:

ModuleInterface>>dependents()

Which modules (module ids) are (in)directly import module with id.

Let's say you have

  • module1.js: export var x = 23;
  • module2.js: import {x} from "module1.js"; export var y = x + 1;
  • module3.js: import {y} from "module2.js"; export var z = y + 1;

module("module1.js").dependents() returns [module("module2"), module("module3")]

ModuleInterface>>requirements()

which modules (module ids) are (in)directly required by module with id?

Let's say you have

  • module1: export var x = 23;
  • module2: import {x} from "module1.js"; export var y = x + 1;
  • module3: import {y} from "module2.js"; export var z = y + 1;

module("module3").requirements() will report [module("module2"), module("module1")]

async ModuleInterface>>changeSource(newSource, options)

To redefine a module's source code at runtime you can use the changeSource method. Given a.js from the previous example you can run module('a.js').changeSource('var x = 24;\nexport x;'). This will a) evaluate the changed code and b) try to modify the actual file behind the module. In browser environments this is done via a PUT request, in node.js fs.writeFile is used.

`async ModuleInterface>>reload(options)``

Will re-import the module identified by moduleName. By default this will also reload all direct and indirect dependencies of that module. You can control that behavior via options, the default value of it is {reloadDeps: true, resetEnv: true}.

ModuleInterface>>unload(options)

Will remove the module from the loaded module set of lively.modules.System. options are by default {forgetDeps: true, forgetEnv: true}.

async ModuleInterface>>imports() and async ModuleInterface>>exports()

Import and export state. For exports this includes the local name of the exported variable, its export name, etc. For imports it includes the imported variable name, the module from where it was imported etc.

Example:

await module("lively.modules/index.js").exports();
  // =>
  //   [{
  //       exported: "getSystem",
  //       local: "getSystem",
  //       fromModule: "http://localhost:9001/node_modules/lively.modules/index.js",
  //     }, ...]

await module("lively.modules/index.js").imports();
  //   [{
  //       fromModule: "lively.lang",
  //       local: "obj",
  //       localModule: "http://localhost:9001/node_modules/lively.modules/index.js"
  //     }, {
  //       fromModule: "./src/system.js",
  //       local: "getSystem",
  //       localModule: "http://localhost:9001/node_modules/lively.modules/index.js"
  //     }, ...]
  //   })
async ModuleInterface>>source()

Returns the source code of the module.

async ModuleInterface>>env()

Returns the evaluation environment of the module.

A "module env" is the object used for recording the evaluation state. Each module that is loaded with source instrumentation enabled as an according moduleEnv It is populated when the module is imported and then used and modified when users run evaluations using lively.vm.runEval() or change the module's code with ModuleInterface>>changeSource(). You can get access to the internal module state via module(...).env().recorder the recorder is a map of variable and function names.

Example: When lively.modules is bootstrapped you can access the state of its main module via:

var id = System.decanonicalize("lively.modules/index.js");
Object.keys(lively.modules.moduleEnv("lively.modules/index.js").recorder);
  // => ["defaultSystem", "changeSystem", "loadedModules", "sourceOf", "moduleEnv", ...]
lively.modules.moduleEnv("lively.modules/index.js").recorder.changeSystem
  // => function() {...} The actual object defined in the module scope

hooks

lively.modules provides an easy way to customize the behavior of the System loader object via installHook and removeHook. To extend the behavior of of lively.modules.System.fetch you can for example do

installHook("fetch", function myFetch(proceed, load) {
  if (load.name === "my-custom-module.js") return "my.custom.code()";
  return proceed(load); // default behavior
});

notification

There are five types of system-wide notifications:

  1. {type: "lively.modules/moduleloaded", module}
  2. {type: "lively.modules/modulechanged", module, oldSource, newSource, error, options}
  3. {type: "lively.modules/moduleunloaded", module}
  4. {type: "lively.modules/packageregistered", package}
  5. {type: "lively.modules/packageremoved", package}

These notifications are all emitted with lively.notifications.

Development

To bootstrap lively.modules please see the example in examples/bootstrap/. lively.modules is completely capable to "develop itself" and was done so from the beginning :)

To build a new version yourself run npm run build.

LICENSE

MIT