Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Build boot.js script using a build script #144

Open
wants to merge 19 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,5 @@ report/
node_modules/
!test/spec/**/node_modules
out/
boot.js
boot/preload-boilerplate.js
4 changes: 3 additions & 1 deletion .jshintignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
**/node_modules/**
**/test/spec/**
**/report/**
**/packages/**
**/packages/**
boot/boilerplate.js
boot.js
20 changes: 19 additions & 1 deletion .jshintrc
Original file line number Diff line number Diff line change
Expand Up @@ -7,26 +7,44 @@
"noarg": true,
"noempty": true,
"nonew": true,
"newcap": false,
"undef": true,
"unused": "paramsignore",
"trailing": true,
"indent": 4,
"boss": true,
"eqnull": true,
"node": true,
"browser": true,
"globals": {
"CustomEvent": true,
"WebSocket": false,

"location": true,
"console": false,
"alert": true,
"hasOwnProperty": true,

"phantom": true,

"require": false,
"exports": false,
"module": false,
"global": false,
"__dirname": false,
"__filename": false,

"WeakMap": true,
"Map": true,
"Set": true,
"URL": true,

"console": false
"describe": true,
"ddescribe": true,
"xdescribe": true,
"it": true,
"iit": true,
"xit": true,
"expect": true
}
}
2 changes: 1 addition & 1 deletion adhoc.html
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<!doctype html>
<html>
<head>
<script src="bootstrap.js" data-module="adhoc"></script>
<script src="boot.js" data-module="adhoc"></script>
</head>
<body>
<p>Open your browser&apos;s console.</p>
Expand Down
4 changes: 2 additions & 2 deletions adhoc.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* global URL:true */
/* global -URL */

var URL = require("mini-url");
var URL = require("url");
var QS = require("qs");

var a = document.createElement("a");
Expand Down
2 changes: 0 additions & 2 deletions bin/mr

This file was deleted.

2 changes: 2 additions & 0 deletions bin/mr-phantom.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
#!/usr/bin/env node
require("../phantom/boot")();
30 changes: 30 additions & 0 deletions bin/mr.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#!/usr/bin/env node

var Require = require("../require");
var Optimist = require("optimist");
var URL = require("url");
var Q = require("q");
var FS = require("q-io/fs");

var argv = Optimist.argv;
var program = argv._.shift();

Require.findPackageLocationAndModuleId(program)
.then(function (info) {
return Require.loadPackage(info.location, {})
.then(function (pkg) {
return pkg.async(info.id);
});
}, function (error) {
var location = Require.filePathToLocation(program);
var directory = URL.resolve(location, "./");
var file = FS.relativeFromDirectory(directory, location);
var descriptions = {};
descriptions[directory] = Q({});
return Require.loadPackage(directory, {
descriptions: descriptions
}).then(function (pkg) {
return pkg.async(file);
});
})
.done();
31 changes: 31 additions & 0 deletions bin/mrs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#!/usr/bin/env node

var optimist = require("optimist");
var build = require("../build");

var argv = optimist
.default("execute", "")
.alias("e", "execute")
.alias("h", "help")
.argv;

function usage() {
console.log("Usage: mrs <entry> [-e <expression>]");
console.log("");
console.log(" Creates a <script> from CommonJS modules");
process.exit(-1);
}

if (argv.help) {
usage();
}
if (argv._.length !== 1) {
usage();
}

var path = argv._[0];
build(path)
.then(function (bundle) {
console.log(bundle + argv.execute);
}).done();

109 changes: 109 additions & 0 deletions boot/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@

These files serve several purposes, all related to booting the Mr module
loader in development or production. A build processes composes some of
them to make the `<script>` `mr/boot.js`, used in browsers while a
project is in development. The same tool composes them to form the
header of a bundle of modules in production. They are not necessary for
booting a module system in Node.js.

By way of background, the `mr/build.js` is capable of creating a kind of
module bundle that supports a very limited subset of Mr’s features.
Since a lot of Mr’s bootstrapping internals themselves are modular and
reusable, it’s necessary to use `build.js` to generate `boot.js` to fix
the chicken-and-egg problem.

A `mr` bundle can only link modules through `require` and `exports` and
embeds the entire working set of modules from a particular entry point.
It is largely inspired by the bundle format generated by [Browserify][].
The bundle starts with a bit of boilerplate. The boilerplate is a
function expression that accepts a highly compact array of pre-analyzed
[dependency, factory] pairs, provides linkage, and returns the exports
of the first module. `build.js` concatenates a function call with the
module payload, such that the expression as a whole evaluates to the
exports of the first module, suitable for chaining.

[Browserify]: https://github.com/substack/node-browserify

The chaining is significant because in `boot.js`, the entry module,
`boot/browser.js`, returns a function that accepts an optional
asynchronous bundle preloading completion promise and returns a promise
for when bootstrapping has completed. The build script adds `().done()`
to the end of the expression to call this function without a preloading
plan and surface any asynchronous errors that might have occurred during
bootstrapping.

Mop builds on this, using `boot/preload-entry.js` as an entry
point. `boot/browser.js` is reused as a module. The preloader entry
module returns a function that accepts a preloading plan, generated by
Mop. The preloading plan is an array of arrays of bundle file names to
load with script injection. `boot/preload.js` exports a function that
takes the plan, injecting each of these scripts at their proper times,
and returning a promise for the completion of preloading. That gets
passed into the `boot/browser.js` function. `boot/preload-entry.js`
forwards the bootstrapping promise to its caller. Mop thus has to
construct a bundle that has `boot/boilerplate.js`, followed by a Mr
bundle using the `boot/preload-entry.js` entry point, followed by a
function invocation with the preloading plan, followed by `.done()` to
surface any asynchronous errors from the combined operation.

- `boilerplate.js`

This is a header for `../boot.js`, `preload-boilerplate.js`, and any
other `<script>` generated by `../build.js`. It is a function
expression that should be invoked with an array of module and
linkage information. The function returns the exports of the first
module.

- `browser.js`

This is the entry module for the `../boot.js` script for use during
development. It is also used as a module for production bundles
generated by Mop. It exports a single function that reads
parameters and location data from the `<script>` tag that includes
it, sets up a module loader, and asynchronously loads an executes an
entry module, returning a promise for the exports.

- `preload-entry.js`

This is the entry module for the `../preload-boilerplate.js`. It is
a thin wrapper that harnesses the `browser.js` bootstrapper and the
`preload.js` Mop bundle preloader.

- `preload-boilerplate.js`

This file is built by `npm run build` or `npm run build-preload`,
using `../build.js` using `preload-entry.js` as an entry point.
Mop, which itself is a more sophisticated variant of `../build.js`,
uses this header to create self-executing module bundles for
production use of Mr applications.

- `preload.js`

This module accepts a module preloading plan, an array of arrays of
script URL’s, injects those scripts into the page in phases, and
returns a promise when all of those scripts have registered that
they have been loaded. It uses a global `bundleLoaded` hook that
occurs at the end of every bundle generated by Mop to notice when
each of the scripts finishes loading.

This module is used both for Mr’s boilerplate and Montage’s
boilerplate.

- `script-injection.js`

This is a utility for injecting `<script>` tags in the browser.

- `script-params.js`

This module exports a single function that searches the DOM for the
`<script>` that included `boot.js`, a preloading bundle, or the
boot script from Montage, `montage.js`. It is used in production
bundles generated by Mop for both Mr and Montage. It is not used
during development.

- `../boot.js`

This is a `<script>` generated by `npm run build` or `npm run
build-boot`, using `browser.js` as an entry-point, which can
bootstrap a module system for a developer envirionment.

46 changes: 46 additions & 0 deletions boot/boilerplate.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
global = this;

(function (modules) {

// Bundle allows the run-time to extract already-loaded modules from the
// boot bundle.
var bundle = {};

// Unpack module tuples into module objects.
for (var i = 0; i < modules.length; i++) {
var module = modules[i];
modules[i] = new Module(module[0], module[1], module[2], module[3]);
bundle[module[0]] = bundle[module[1]] || {};
bundle[module[0]][module[1]] = module;
}

function Module(name, id, map, factory) {
// Package name and module identifier are purely informative.
this.name = name;
this.id = id;
// Dependency map and factory are used to instantiate bundled modules.
this.map = map;
this.factory = factory;
}

Module.prototype.getExports = function () {
var module = this;
if (module.exports === void 0) {
module.exports = {};
var require = function (id) {
var index = module.map[id];
var dependency = modules[index];
if (!dependency)
throw new Error("Bundle is missing a dependency: " + id);
return dependency.getExports();
}
module.exports = module.factory(require, module.exports, module) || module.exports;
}
return module.exports;
};

// Communicate the bundle to all bundled modules
Module.prototype.bundle = bundle;

return modules[0].getExports();
})
49 changes: 49 additions & 0 deletions boot/jasminum.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@

var Suite = require("jasminum");

var Require = require("../require");
var URL = require("url");
var Q = require("q");
var getParams = require("./script-params");

module.exports = boot;
function boot(preloaded, params) {
params = params || getParams("jasminum.js");

var config = {preloaded: preloaded};
var applicationLocation = URL.resolve(window.location, params.package || ".");
var moduleId = params.module || "";

if ("autoPackage" in params) {
Require.injectPackageDescription(applicationLocation, {});
}

return Require.loadPackage({
location: applicationLocation,
hash: params.applicationHash
})
.then(function (applicationRequire) {
return applicationRequire.loadPackage({
location: params.location,
hash: params.mrHash
}, config)
.then(function (mrRequire) {
return mrRequire.loadPackage({
name: "q",
location: params.qLocation,
hash: params.qHash
})
.then(function (qRequire) {
return applicationRequire.deepLoad(moduleId)
.then(function () {
var suite = new Suite(moduleId).describe(function () {
applicationRequire(moduleId);
});
return suite.runAndReport();
});
});
});
});

}

10 changes: 10 additions & 0 deletions boot/preload-entry.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@

var boot = require("./require");
var preload = require("./preload");
var getParams = require("./script-params");

module.exports = function bootstrapPreload(plan) {
var params = getParams();
return boot(preload(plan, params), params);
};

Loading