@glimmer/resolver
Version:
Resolver for Glimmer apps.
130 lines (108 loc) • 4.28 kB
text/typescript
import {
Resolver as IResolver,
Specifier,
isSpecifierStringAbsolute,
isSpecifierObjectAbsolute,
deserializeSpecifier,
serializeSpecifier
} from '@glimmer/di';
import { assert } from './utils/debug';
import { detectLocalResolutionCollection } from './utils/specifiers';
import { ModuleRegistry } from './module-registry';
import { ResolverConfiguration } from './resolver-configuration';
export default class Resolver implements IResolver {
public config: ResolverConfiguration;
public registry: ModuleRegistry;
constructor(config: ResolverConfiguration, registry: ModuleRegistry) {
this.config = config;
this.registry = registry;
}
identify(specifier: string, referrer?: string): string {
if (isSpecifierStringAbsolute(specifier)) {
return specifier;
}
let s = deserializeSpecifier(specifier);
let result: string;
if (referrer) {
let r = deserializeSpecifier(referrer);
if (isSpecifierObjectAbsolute(r)) {
assert('Specifier must not include a rootName, collection, or namespace when combined with an absolute referrer', s.rootName === undefined && s.collection === undefined && s.namespace === undefined);
s.rootName = r.rootName;
s.collection = r.collection;
let definitiveCollection = this._definitiveCollection(s.type);
if (!s.name) {
/*
* For specifiers without a name use the referrer's name and
* do not fallback to any other resolution rules.
*/
s.namespace = r.namespace;
s.name = r.name;
return this._serializeAndVerify(s);
}
s.namespace = r.namespace ? r.namespace + '/' + r.name : r.name;
if (detectLocalResolutionCollection(s) === definitiveCollection) {
/*
* For specifiers with a name, try local resolution. Based on
* the referrer.
*/
if (result = this._serializeAndVerify(s)) { return result; }
}
// Look for a private collection in the referrer's namespace
if (definitiveCollection) {
s.namespace += '/-' + definitiveCollection;
if (result = this._serializeAndVerify(s)) { return result; }
}
// Because local and private resolution has failed, clear all but `name` and `type`
// to proceed with top-level resolution
s.rootName = s.collection = s.namespace = undefined;
} else {
assert('Referrer must either be "absolute" or include a `type` to determine the associated type', r.type);
// Look in the definitive collection for the associated type
s.collection = this._definitiveCollection(r.type);
if (!s.namespace) {
s.namespace = r.rootName;
}
assert(`'${r.type}' does not have a definitive collection`, s.collection);
}
}
// If the collection is unspecified, use the definitive collection for the `type`
if (!s.collection) {
s.collection = this._definitiveCollection(s.type);
assert(`'${s.type}' does not have a definitive collection`, s.collection);
}
if (!s.rootName) {
// If the root name is unspecified, try the app's `rootName` first
s.rootName = this.config.app.rootName || 'app';
if (result = this._serializeAndVerify(s)) { return result; }
// Then look for an addon with a matching `rootName`
if (s.namespace) {
s.rootName = s.namespace;
s.namespace = undefined;
} else {
s.rootName = s.name;
s.name = 'main';
}
}
if (result = this._serializeAndVerify(s)) { return result; }
}
retrieve(specifier: string): any {
return this.registry.get(specifier);
}
resolve(specifier: string, referrer?: string): any {
let id = this.identify(specifier, referrer);
if (id) {
return this.retrieve(id);
}
}
private _definitiveCollection(type: string): string {
let typeDef = this.config.types[type];
assert(`'${type}' is not a recognized type`, typeDef);
return typeDef.definitiveCollection;
}
private _serializeAndVerify(specifier: Specifier): string {
let serialized = serializeSpecifier(specifier);
if (this.registry.has(serialized)) {
return serialized;
}
}
}