in fusion-core/src/base-app.js [151:325]
resolve<TResolved>() {
if (!this.renderer) {
throw new Error('Missing registration for RenderToken');
}
this._register(RenderToken, this.renderer);
const resolved = new Map(); // Token.ref || Token => Service
const nonPluginTokens = new Set(); // Token
const resolving = new Set(); // Token.ref || Token
const registered = this.registered; // Token.ref || Token -> {value, aliases, enhancers}
const resolvedPlugins = []; // Plugins
const appliedEnhancers = [];
const resolveToken = (token: Token<TResolved>, tokenAliases) => {
// Base: if we have already resolved the type, return it
if (tokenAliases && tokenAliases.has(getTokenRef(token))) {
const newToken = tokenAliases.get(getTokenRef(token));
if (newToken) {
token = newToken;
}
}
if (resolved.has(getTokenRef(token))) {
return resolved.get(getTokenRef(token));
}
// Base: if currently resolving the same type, we have a circular dependency
if (resolving.has(getTokenRef(token))) {
throw new Error(`Cannot resolve circular dependency: ${token.name}`);
}
// Base: the type was never registered, throw error or provide undefined if optional
let {value, aliases, enhancers} =
registered.get(getTokenRef(token)) || {};
if (value === undefined) {
// Early return if token is optional
const isOptional =
token instanceof TokenImpl && token.type === TokenType.Optional;
if (isOptional && (!enhancers || !enhancers.length)) {
return;
}
const dependents = Array.from(this.registered.entries());
/**
* Iterate over the entire list of dependencies and find all
* dependencies of a given token.
*/
const findDependentTokens = () => {
return dependents
.filter(entry => {
if (!entry[1].value || !entry[1].value.deps) {
return false;
}
return Object.values(entry[1].value.deps).includes(token);
})
.map(entry => entry[1].token.name);
};
const findDependentEnhancers = () => {
return appliedEnhancers
.filter(([, provides]) => {
if (!provides || !provides.deps) {
return false;
}
return Object.values(provides.deps).includes(token);
})
.map(([enhancer]) => {
const enhancedToken = this.enhancerToToken.get(enhancer);
return `EnhancerOf<${
enhancedToken ? enhancedToken.name : '(unknown)'
}>`;
});
};
const dependentTokens = [
...findDependentTokens(),
...findDependentEnhancers(),
];
const base =
'A plugin depends on a token, but the token was not registered';
const downstreams =
'This token is required by plugins registered with tokens: ' +
dependentTokens.map(token => `"${token}"`).join(', ');
const stack = token.stacks.find(t => t.type === 'token');
const meta = `Required token: ${
token ? token.name : ''
}\n${downstreams}\n${stack ? stack.stack : ''}`;
const clue = 'Different tokens with the same name were detected:\n\n';
const suggestions = token
? this.plugins
.filter(p => p.name === token.name)
.map(p => {
const stack = p.stacks.find(t => t.type === 'token');
return `${p.name}\n${stack ? stack.stack : ''}\n\n`;
})
.join('\n\n')
: '';
const help =
'You may have multiple versions of the same plugin installed.\n' +
'Ensure that `yarn list [the-plugin]` results in one version, ' +
'and use a yarn resolution or merge package version in your lock file to consolidate versions.\n\n';
throw new Error(
`${base}\n\n${meta}\n\n${suggestions && clue + suggestions + help}`
);
}
// Recursive: get the registered type and resolve it
resolving.add(getTokenRef(token));
function resolvePlugin(plugin) {
const registeredDeps = (plugin && plugin.deps) || {};
const resolvedDeps = {};
for (const key in registeredDeps) {
const registeredToken = registeredDeps[key];
resolvedDeps[key] = resolveToken(registeredToken, aliases);
}
// `provides` should be undefined if the plugin does not have a `provides` function
let provides =
plugin && plugin.provides ? plugin.provides(resolvedDeps) : undefined;
if (plugin && plugin.middleware) {
resolvedPlugins.push(plugin.middleware(resolvedDeps, provides));
}
return provides;
}
let provides = value;
if (value && value.__plugin__) {
provides = resolvePlugin(provides);
if (value.cleanup) {
this.cleanups.push(function() {
return typeof value.cleanup === 'function'
? value.cleanup(provides)
: Promise.resolve();
});
}
} else {
nonPluginTokens.add(token);
}
if (enhancers && enhancers.length) {
enhancers.forEach(e => {
let nextProvides = e(provides);
appliedEnhancers.push([e, nextProvides]);
if (nextProvides && nextProvides.__plugin__) {
// if the token has a plugin enhancer, allow it to be registered with no dependents
nonPluginTokens.delete(token);
if (nextProvides.deps) {
Object.values(nextProvides.deps).forEach(token =>
this._dependedOn.add(getTokenRef(token))
);
}
nextProvides = resolvePlugin(nextProvides);
}
provides = nextProvides;
});
}
resolved.set(getTokenRef(token), provides);
resolving.delete(getTokenRef(token));
return provides;
};
for (let i = 0; i < this.plugins.length; i++) {
resolveToken(this.plugins[i]);
}
for (const token of nonPluginTokens) {
if (
token !== ElementToken &&
token !== RenderToken &&
!this._dependedOn.has(getTokenRef(token))
) {
throw new Error(
`Registered token without depending on it: "${token.name}". See https://github.com/fusionjs/fusionjs/tree/master/fusion-core#registered-without-depending.`
);
}
}
this.plugins = resolvedPlugins;
this._getService = token => resolved.get(getTokenRef(token));
}