Permalink
Fetching contributors…
Cannot retrieve contributors at this time
104 lines (87 sloc) 5.43 KB

How It Works

In broad strokes, Montage Require uses so-called "XML" HTTP requests to fetch modules, then uses a regular expression to scan for require calls within each JavaScript module, then executes the module with some variation of eval. Then, with the Montage Optimizer, mop, Montage Require can also serve as the runtime for loading modules with bundled script-injection with no alteration to the source code of an application. With script-injection, XHR and eval are not necessary, so applications are suitable for production, cross-domain, and with content security policies (CSP) that forbid eval.

In slightly thinner strokes, Montage Require has an asynchronous phase and a synchronous phase. In the asynchronous "loading" phase, Montage Require fetches every module that it will need in the synchronous phase. It then passes into the synchronous "execution" phase, where require calls actually occur. The asynchronous portion includes require.async, require.load, and require.deepLoad, which return Q promises. The synchronous phase employs require calls directly to transitively instantiate modules on demand. The system must be kicked off with require.async since no modules are loaded initially.

Some alternatives to Montage Require use a full JavaScript parser to cull the false positives you will occasionally see when using regular expressions to scan for static require calls. This is a trade-off between weight and accuracy. Montage Require does not block execution when it is unable to load these false-positive modules, but instead continues to the execution to "wait and see" whether the module can run to completion without the module that failed to load. Also, Montage Require can be configured to use an alternate dependency parser.

Around this system, Montage Require supports packages. This entails asynchronously loading and parsing package.json files, then configuring and connecting the module systems of each package in the "load" phase. Package dependencies are loaded on demand.

Each package has an isolated module identifier name space. The package.json dictates how that name space forwards to other packages through the dependencies property, as well as internal aliases from the package's name, main, and redirects properties.

Additionally, Montage Require is very configurable and pluggable. Montage itself vastly extends the capabilities of Montage Require so that it can load HTML templates. Montage's internal configuration includes middleware stacks for loading and compiling. The loader middleware stack can be overridden with config.makeLoader or config.load. The compiler middleware can be overridden with config.makeCompiler or config.compile. The makers are called to create loaders or compilers per package, each receiving the configuration for their particular package.

The signature of loader middleware is makeLoader(config, nextLoader) which must return a function of the form load(id, module). The signature of compiler middleware if makeCompiler(config, nextCompiler) which must return a function of the form compile(module).

As part of the bootstrapping process, configuration begins with a call to Require.loadPackage(dependency, config) that returns a promise for the require function of the package.

config is an optional base configuration that can contain alternate makeLoader, makeCompiler, and parseDependencies functions. Montage Require then takes ownership of the config object and uses it to store information shared by all packages like the registries of known packages by name and location, and memoized promises for each package while they load.

dependency declares the location of the package, and can also inform the module system of the consistent hash of the package. Dependency can be a location string for short, but gets internally normalized to an object with a location property. The hash is only necessary for optimized packages since they use script-injection. The injected scripts call define for each module, identifying the module by the containing package hash and module id.

The require function for any package has a similar loadPackage function that can take a dependency argument. That dependency may have a name instead of location. In that case, Montage Require infers the location based on the known locations of packages with that name, or assumes the package exists within the node_modules directory of the dependent package. This is a relatively safe assumption if the application was installed with NPM.

Montage Require also supports a form of dependency injection. These features were implemented because bootstrap.js (and in Montage proper, montage.js) would need to load and instantiate certain resources before being able to instantiate a module system. To avoid reloading these already-instantiated resources, the bootstrapper would inject them into the packages before handing control over to the application.

require.inject(id, exports) adds the exports for a given module to a package.

require.injectPackageDescription(location, description) allows the module system to read the content of a package.json for the package at location without fetching the corresponding file.

require.injectPackageDescriptionLocation(location, descriptionLocation) instructs the module system to look in an alternate location for the package.json for a particular package.