resolveComponentPath({
    theme,
    component: originalComponent,
    originalRequestComponent,
  }) {
    const [component, content] = splitComponentPath(originalComponent)

    // don't include matching theme in possible shadowing paths
    const themes = this.themes.filter(
      ({ themeName }) => themeName !== theme.themeName
    )

    const themesArray = [
      path.join(this.projectRoot, `src`, theme.themeName),
    ].concat(
      themes
        .reverse()
        .map(({ themeDir }) => path.join(themeDir, `src`, theme.themeName))
    )

    const acceptableShadowFileNames = this.getAcceptableShadowFileNames(
      path.basename(component),
      originalRequestComponent
    )

    for (const theme of themesArray) {
      const possibleComponentPath = path.join(theme, component)
      debug(`possibleComponentPath`, possibleComponentPath)

      let dir
      try {
        // we use fs/path instead of require.resolve to work with
        // TypeScript and alternate syntaxes
        dir = fs.readdirSync(path.dirname(possibleComponentPath))
      } catch (e) {
        continue
      }
      const existsDir = dir.map(filepath => path.basename(filepath))

      // if no exact path, search for extension
      const matchingShadowFile = acceptableShadowFileNames.find(shadowFile =>
        existsDir.includes(shadowFile)
      )
      if (matchingShadowFile) {
        const shadowPath = path.join(
          path.dirname(possibleComponentPath),
          matchingShadowFile
        )

        if (content) {
          return `${shadowPath}?__contentFilePath=${content}`
        }

        return shadowPath
      }
    }
    return null
  }
An issuer is the file making the require request. It can
be in a user's site or a theme. If the issuer is requesting
a path in the shadow chain that it participates in, then we
will let the request through as normal. Otherwise, we
engage the shadowing algorithm.