From 10d2c2a03d0b2a97fdb1e5d70d244cfb5b383d44 Mon Sep 17 00:00:00 2001 From: Robert Jackson Date: Tue, 18 Apr 2017 18:21:24 -0400 Subject: [PATCH 1/2] Create official `loader` module API. Provides an internal `loader` module with the following named exports: * `getIds` - Returns an array containing the `id`'s of all registered modules. * `has` - Returns `true` or `false` if a given module exists. * `define` - Defines a new module. * `require` - Requires a module (optionally relative to the current module). * `resolve` - Returns the expanded module name given a relative path. --- build.js | 1 - index.js | 1 + lib/loader/loader.js | 30 +++++++++ tests/all.js | 155 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 186 insertions(+), 1 deletion(-) diff --git a/build.js b/build.js index 291c534..4497f51 100755 --- a/build.js +++ b/build.js @@ -19,4 +19,3 @@ var stripped = transform(source, { fs.writeFileSync('./dist/loader/loader.instrument.js', instrumented); fs.writeFileSync('./dist/loader/loader.js', stripped); - diff --git a/index.js b/index.js index d701d1b..e4d4d1a 100644 --- a/index.js +++ b/index.js @@ -1,4 +1,5 @@ 'use strict'; + var shouldUseInstrumentedBuild = require('./utils').shouldUseInstrumentedBuild; module.exports = { diff --git a/lib/loader/loader.js b/lib/loader/loader.js index 2b93d37..634f0b3 100644 --- a/lib/loader/loader.js +++ b/lib/loader/loader.js @@ -95,6 +95,34 @@ var loader, define, requireModule, require, requirejs; var defaultDeps = ['require', 'exports', 'module']; + function LoaderModule(source) { + this.source = source; + } + + LoaderModule.prototype.default = function(dep) { + return this.require(dep); + }; + + LoaderModule.prototype.define = function(name, deps, callback) { + return define(name, deps, callback); + }; + + LoaderModule.prototype.require = function(dep) { + return require(resolve(dep, this.source)); + }; + + LoaderModule.prototype.has = function(dep) { + return has(resolve(dep, this.source)); + }; + + LoaderModule.prototype.resolve = function(dep) { + return resolve(dep, this.source); + }; + + LoaderModule.prototype.getIds = function() { + return Object.keys(registry); + }; + function Module(name, deps, callback, alias) { heimdall.increment(modules); this.id = uuid++; @@ -196,6 +224,8 @@ var loader, define, requireModule, require, requirejs; entry.exports = this.module.exports; } else if (dep === 'require') { entry.exports = this.makeRequire(); + } else if (dep === 'loader') { + entry.exports = new LoaderModule(this.name); } else if (dep === 'module') { entry.exports = this.module; } else { diff --git a/tests/all.js b/tests/all.js index c885e5f..b43a6d4 100644 --- a/tests/all.js +++ b/tests/all.js @@ -1647,3 +1647,158 @@ test('redefining a module when "finalized" should no-op', function(assert) { require('foo'); assert.notOk(second, 'second module definition never used'); }); + +test('loader module API: require', function() { + define('foo/baz/index', function () { + return 'I AM baz'; + }); + + define('foo/index', ['loader'], function (loader) { + return loader.require('./baz'); + }); + + equal(require('foo'), 'I AM baz'); + + var stats = statsForMonitor('loaderjs', tree); + + deepEqual(stats, { + findDeps: 2, + define: 2, + exports: 2, + findModule: 2, + modules: 2, + reify: 2, + require: 2, + resolve: 1, + resolveRelative: 1, + pendingQueueLength: 2 + }); +}); + +test('loader module API: has', function() { + define('foo/baz/index', function () { + return 'I AM baz'; + }); + + define('foo/index', ['loader'], function (loader) { + if (loader.has('./baz')) { + return loader.require('./baz'); + } + }); + + equal(require('foo'), 'I AM baz'); + + var stats = statsForMonitor('loaderjs', tree); + + deepEqual(stats, { + findDeps: 2, + define: 2, + exports: 2, + findModule: 2, + modules: 2, + reify: 2, + require: 2, + resolve: 2, + resolveRelative: 2, + pendingQueueLength: 2 + }); +}); + +test('loader module API: resolve', function() { + expect(2); + + define('foo/baz/index', function () { + return 'I AM baz'; + }); + + define('foo/index', ['loader'], function (loader) { + equal(loader.resolve('./baz'), 'foo/baz'); + }); + + require('foo'); + + var stats = statsForMonitor('loaderjs', tree); + + deepEqual(stats, { + findDeps: 1, + define: 2, + exports: 1, + findModule: 1, + modules: 2, + reify: 1, + require: 1, + resolve: 1, + resolveRelative: 1, + pendingQueueLength: 1 + }); +}); + +test('loader module API: define', function() { + expect(3); + + define('foo/index', ['loader'], function (loader) { + loader.define('foo/derp', function() { + ok(true, 'does not need have to have deps'); + + return 'derp!'; + }); + + loader.define('foo/baz', ['foo/derp'], function(derp) { + equal(derp, 'derp!', 'can have deps'); + + return 'I AM baz'; + }); + + return loader.require('./baz'); + }); + + require('foo'); + + var stats = statsForMonitor('loaderjs', tree); + + deepEqual(stats, { + findDeps: 3, + define: 3, + exports: 3, + findModule: 3, + modules: 3, + reify: 3, + require: 2, + resolve: 2, + resolveRelative: 1, + pendingQueueLength: 3 + }); +}); + +test('loader module API: getIds', function() { + expect(2); + + define('foo/derp', function() { + return 'derp!'; + }); + + define('foo/baz', ['foo/derp'], function() { + return 'I AM baz'; + }); + + define('foo/index', ['loader'], function (loader) { + deepEqual(loader.getIds(), ['foo/derp', 'foo/baz', 'foo/index']); + }); + + require('foo'); + + var stats = statsForMonitor('loaderjs', tree); + + deepEqual(stats, { + findDeps: 1, + define: 3, + exports: 1, + findModule: 1, + modules: 3, + reify: 1, + require: 1, + resolve: 0, + resolveRelative: 0, + pendingQueueLength: 1 + }); +}); From 5d4e593eaf068b99a41e879596a2d99d4ee72e9a Mon Sep 17 00:00:00 2001 From: Robert Jackson Date: Tue, 18 Apr 2017 19:13:42 -0400 Subject: [PATCH 2/2] Add documentation to the README. --- README.md | 64 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/README.md b/README.md index b7e65e0..2b5477a 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,70 @@ Note: To be able to take advantage of alternate `define` method name, you will a build tooling generates using the alternate. An example of this is done in the [emberjs-build](https://github.com/emberjs/emberjs-build) project in the [babel-enifed-module-formatter plugin](https://github.com/emberjs/emberjs-build/blob/v0.4.2/lib/utils/babel-enifed-module-formatter.js). +## Public API + +`loader.js` provides the following as named exports from a `loader` module: + +```ts +interface LoaderModule { + /* + Return the list of module id's that are present in the registry + */ + getIds(): string[]; + + /* + Returns `true` if a module was found for the given module id. + If the `id` provided is relative, it is resolved relative to + the current module before checking the registry. + */ + has(idOrRelativeName: string): boolean; + + /* + Adds a new module for the provided module id. + */ + define(id: string, dependencies?: string[], callback: Function): void; + + /* + Returns the exports of the module id provided. If the provided id was + relative, it is resolved relative to the current module first. + */ + require(idOrRelativeName: string): any; + + /* + Resolves a relative module name from the current module. + */ + resolve(relativeName: string): string +} +``` + +In order to interact with `loader.js` from within a module, you should add `loader` as a dependency to your module. + +When using ES modules that would look like: + +```js +import { + has, + getIds, + define, + require, + resolve +} from 'loader'; +``` + +Or if using `AMD` it would look like: + +```js +define(['loader'], function(loader) { + const { + has, + getIds, + define, + require, + resolve + } = loader; +}); +``` + ## wrapModules It is possible to hook loader to augment or transform the loaded code. `wrapModules` is an optional method on the loader that is called as each module is originally loaded. `wrapModules` must be a function of the form `wrapModules(name, callback)`. The `callback` is the original AMD callback. The return value of `wrapModules` is then used in subsequent requests for `name`