resolve()

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));
  }