175 lines
6.0 KiB
JavaScript
175 lines
6.0 KiB
JavaScript
"use strict";
|
|
|
|
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
|
|
|
|
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
|
|
|
|
const path = require(`path`);
|
|
|
|
const debug = require(`debug`)(`gatsby:component-shadowing`);
|
|
|
|
const fs = require(`fs`);
|
|
|
|
const _ = require(`lodash`);
|
|
|
|
const pathWithoutExtension = fullPath => {
|
|
const parsed = path.parse(fullPath);
|
|
return path.join(parsed.dir, parsed.name);
|
|
};
|
|
|
|
module.exports = class GatsbyThemeComponentShadowingResolverPlugin {
|
|
constructor({
|
|
projectRoot,
|
|
themes
|
|
}) {
|
|
(0, _defineProperty2.default)(this, "cache", {});
|
|
debug(`themes list`, themes.map(({
|
|
themeName
|
|
}) => themeName));
|
|
this.themes = themes;
|
|
this.projectRoot = projectRoot;
|
|
}
|
|
|
|
apply(resolver) {
|
|
resolver.hooks.relative.tapAsync(`GatsbyThemeComponentShadowingResolverPlugin`, (request, stack, callback) => {
|
|
const matchingThemes = this.getMatchingThemesForPath(request.path); // 0 matching themes happens a lot for paths we don't want to handle
|
|
// > 1 matching theme means we have a path like
|
|
// `gatsby-theme-blog/src/components/gatsby-theme-something/src/components`
|
|
|
|
if (matchingThemes.length > 1) {
|
|
throw new Error(`Gatsby can't differentiate between themes ${matchingThemes.map(theme => theme.themeName).join(` and `)} for path ${request.path}`);
|
|
}
|
|
|
|
if (matchingThemes.length !== 1) {
|
|
return callback();
|
|
} // theme is the theme package from which we're requiring the relative component
|
|
|
|
|
|
const [theme] = matchingThemes; // get the location of the component relative to src/
|
|
|
|
const [, component] = request.path.split(path.join(theme.themeDir, `src`));
|
|
|
|
if (
|
|
/**
|
|
* if someone adds
|
|
* ```
|
|
* modules: [path.resolve(__dirname, 'src'), 'node_modules'],
|
|
* ```
|
|
* to the webpack config, `issuer` is `null`, so we skip this check.
|
|
* note that it's probably a bad idea in general to set `modules`
|
|
* like this in a theme, but we also shouldn't artificially break
|
|
* people that do.
|
|
*/
|
|
request.context.issuer &&
|
|
/**
|
|
* 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.
|
|
*/
|
|
this.requestPathIsIssuerShadowPath({
|
|
requestPath: request.path,
|
|
issuerPath: request.context.issuer,
|
|
userSiteDir: path.resolve(`.`)
|
|
})) {
|
|
return resolver.doResolve(resolver.hooks.describedRelative, request, null, {}, callback);
|
|
} // This is the shadowing algorithm.
|
|
|
|
|
|
const builtComponentPath = this.resolveComponentPath({
|
|
matchingTheme: theme.themeName,
|
|
themes: this.themes,
|
|
component
|
|
});
|
|
return resolver.doResolve(resolver.hooks.describedRelative, Object.assign({}, request, {
|
|
path: builtComponentPath || request.path
|
|
}), null, {}, callback);
|
|
});
|
|
} // check the cache, the user's project, and finally the theme files
|
|
|
|
|
|
resolveComponentPath({
|
|
matchingTheme: theme,
|
|
themes: ogThemes,
|
|
component
|
|
}) {
|
|
// don't include matching theme in possible shadowing paths
|
|
const themes = ogThemes.filter(({
|
|
themeName
|
|
}) => themeName !== theme);
|
|
|
|
if (!this.cache[`${theme}-${component}`]) {
|
|
this.cache[`${theme}-${component}`] = [path.join(path.resolve(`.`), `src`, theme)].concat(Array.from(themes).reverse().map(({
|
|
themeDir
|
|
}) => path.join(themeDir, `src`, theme))).map(dir => path.join(dir, component)).find(possibleComponentPath => {
|
|
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) {
|
|
return false;
|
|
}
|
|
|
|
const exists = dir.map(filepath => {
|
|
const ext = path.extname(filepath);
|
|
const filenameWithoutExtension = path.basename(filepath, ext);
|
|
return filenameWithoutExtension;
|
|
}).includes(path.basename(possibleComponentPath, path.extname(possibleComponentPath)));
|
|
return exists;
|
|
});
|
|
}
|
|
|
|
return this.cache[`${theme}-${component}`];
|
|
}
|
|
|
|
getMatchingThemesForPath(filepath) {
|
|
// find out which theme's src/components dir we're requiring from
|
|
const allMatchingThemes = this.themes.filter(({
|
|
themeDir
|
|
}) => filepath.includes(path.join(themeDir, `src`))); // The same theme can be included twice in the themes list causing multiple
|
|
// matches. This case should only be counted as a single match for that theme.
|
|
|
|
return _.uniqBy(allMatchingThemes, `themeName`);
|
|
} // given a theme name, return all of the possible shadow locations
|
|
|
|
|
|
getBaseShadowDirsForThemes(theme) {
|
|
return Array.from(this.themes).reverse().map(({
|
|
themeName,
|
|
themeDir
|
|
}) => {
|
|
if (themeName === theme) {
|
|
return path.join(themeDir, `src`);
|
|
} else {
|
|
return path.join(themeDir, `src`, theme);
|
|
}
|
|
});
|
|
}
|
|
|
|
requestPathIsIssuerShadowPath({
|
|
requestPath,
|
|
issuerPath,
|
|
userSiteDir
|
|
}) {
|
|
// get the issuer's theme
|
|
const matchingThemes = this.getMatchingThemesForPath(requestPath);
|
|
|
|
if (matchingThemes.length !== 1) {
|
|
return false;
|
|
}
|
|
|
|
const [theme] = matchingThemes; // get the location of the component relative to src/
|
|
|
|
const [, component] = requestPath.split(path.join(theme.themeDir, `src`)); // get list of potential shadow locations
|
|
|
|
const shadowFiles = this.getBaseShadowDirsForThemes(theme.themeName).concat(path.join(userSiteDir, `src`, theme.themeName)).map(dir => path.join(dir, component)); // if the issuer is requesting a path that is a potential shadow path of itself
|
|
|
|
return shadowFiles.includes(pathWithoutExtension(issuerPath));
|
|
}
|
|
|
|
};
|
|
//# sourceMappingURL=index.js.map
|