JavaScript package and module system for interactive development.
npm install lively.modulesJavaScript package and module system for interactive development.


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.
To load lively.modules you can use the pre-builddist/lively.modules-with-lively.vm.js file. Once that happens thelively.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
``html`
See the examples in
lively-system-examples
for more details.
To load a project into your runtime you will typically use
lively.modules.importPackage('some-package-name'). 'some-package-name'name
should resolve to a directory with a JSON package config file (typically
package.json) that at least defines a field. The package will belively.modules.System.import('some-package-name/index.js')
imported, i.e. the main module of the package will be loaded via. By default the'index.js'
name of main is 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"packageMap"
main module, a object that maps names that can be used inimport 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.
`json`
{
"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.
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:
`js`
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.
- 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.
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()
-
* This is handled by the lively.vm module!
#### 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;import {x} from "module1.js"; export var y = x + 1;
- module2.js: import {y} from "module2.js"; export var z = y + 1;
- module3.js:
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;import {x} from "module1.js"; export var y = x + 1;
- module2: import {y} from "module2.js"; export var z = y + 1;
- module3:
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 runmodule('a.js').changeSource('var x = 24;\nexport x;').PUT
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 request,fs.writeFile
in node.js is used.
##### async ModuleInterface>>reload(options)`
Will re-import the module identified by moduleName. By default this willoptions
also reload all direct and indirect dependencies of that module. You can
control that behavior via , 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:
`js
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'sModuleInterface>>changeSource()
code with . You can get access to the internal modulemodule(...).env().recorder
state via 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:
`js`
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
lively.modules provides an easy way to customize the behavior of the System
loader object via installHook and removeHook. To extend the behavior oflively.modules.System.fetch
of you can for example do
`js`
installHook("fetch", function myFetch(proceed, load) {
if (load.name === "my-custom-module.js") return "my.custom.code()";
return proceed(load); // default behavior
});
There are five types of system-wide notifications:
1. {type: "lively.modules/moduleloaded", module}{type: "lively.modules/modulechanged", module, oldSource, newSource, error, options}
2. {type: "lively.modules/moduleunloaded", module}
3. {type: "lively.modules/packageregistered", package}
4. {type: "lively.modules/packageremoved", package}
5.
These notifications are all emitted with lively.notifications.
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`.