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

Create official loader module API. #118

Closed
wants to merge 2 commits into from
Closed
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
64 changes: 64 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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[];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

May just be me, but for some reason, I do not think id when I think of the identifier for a module. Usually path or name instead. When I first saw getIds() I had no idea what information it was actually returning.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is the unique id, usually name is relative or not where as this is registry specific.

https://whatwg.github.io/loader/#loader-import if you look here name is used for APIs like import() but the registry uses keys.

Would getKeys() be ok with you?

I hate naming things.


/*
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`
Expand Down
1 change: 0 additions & 1 deletion build.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,3 @@ var stripped = transform(source, {

fs.writeFileSync('./dist/loader/loader.instrument.js', instrumented);
fs.writeFileSync('./dist/loader/loader.js', stripped);

1 change: 1 addition & 0 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
'use strict';

var shouldUseInstrumentedBuild = require('./utils').shouldUseInstrumentedBuild;

module.exports = {
Expand Down
30 changes: 30 additions & 0 deletions lib/loader/loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -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++;
Expand Down Expand Up @@ -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 {
Expand Down
155 changes: 155 additions & 0 deletions tests/all.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
});
});