@openshift-console/dynamic-plugin-sdk-webpack
Version:
Provides webpack ConsoleRemotePlugin used to build all dynamic plugin assets.
114 lines (113 loc) • 5.87 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.ExtensionValidator = exports.findWebpackModules = exports.collectCodeRefData = exports.guessModuleFilePath = void 0;
const fs = require("fs");
const path = require("path");
const _ = require("lodash");
const coderef_resolver_1 = require("../coderefs/coderef-resolver");
const object_1 = require("../utils/object");
const BaseValidator_1 = require("./BaseValidator");
/**
* Guess the file path of the module (e.g., full path with extension,
* or relevant barrel file) based on the given base path.
*
* Returns the base path if no relevant module file is found.
*
* @param basePath The base file path to start guessing from.
* @param msgCallback Optional callback to log messages.
*/
const guessModuleFilePath = (basePath, msgCallback = _.noop) => {
// Path already contains a file extension (no extra guessing needed)
if (path.extname(basePath)) {
return basePath;
}
// Check if the path refers to an barrel file (base path only specified the directory)
const barrelFile = ['index.ts', 'index.js', 'index.tsx', 'index.jsx']
.map((i) => path.resolve(basePath, i))
.find(fs.existsSync);
if (barrelFile) {
msgCallback(`The module ${basePath} refers to an barrel file ${barrelFile}. Barrel files are not recommended as they may cause unnecessary code to be loaded. Consider specifying the module file path directly.`);
return barrelFile;
}
// Check if the base path neglected to include a file extension
const moduleFile = ['.tsx', '.ts', '.jsx', '.js']
.map((ext) => `${basePath}${ext}`)
.find(fs.existsSync);
if (moduleFile) {
msgCallback(`The module ${basePath} refers to file ${moduleFile}, but a file extension was not specified.`);
return moduleFile;
}
// No relevant file could be found, return the original base path.
return basePath;
};
exports.guessModuleFilePath = guessModuleFilePath;
const collectCodeRefData = (extensions) => extensions.reduce((acc, e, index) => {
const data = { index, propToCodeRefValue: {} };
(0, object_1.deepForOwn)(e.properties, coderef_resolver_1.isEncodedCodeRef, (ref, key) => {
data.propToCodeRefValue[key] = ref.$codeRef;
});
if (!_.isEmpty(data.propToCodeRefValue)) {
acc.push(data);
}
return acc;
}, []);
exports.collectCodeRefData = collectCodeRefData;
const findWebpackModules = (compilation, exposedModules, pluginBasePath) => {
const webpackModules = Array.from(compilation.modules);
return Object.keys(exposedModules).reduce((acc, moduleName) => {
const absolutePath = pluginBasePath &&
(0, exports.guessModuleFilePath)(path.resolve(pluginBasePath, exposedModules[moduleName]));
acc[moduleName] = webpackModules.find((m) => {
// @ts-expect-error rootModule is internal to webpack's ModuleConcatenationPlugin
const rootModule = m?.rootModule;
/* first strategy: check if the module name matches the rawRequest */
const rawRequest = m?.rawRequest || rootModule?.rawRequest;
const matchesRawRequest = rawRequest && rawRequest === exposedModules[moduleName];
/* second strategy: check if the absolute path matches the resource */
const resource = m?.resource || rootModule?.resource;
const matchesAbsolutePath = resource && resource === absolutePath;
return matchesRawRequest || matchesAbsolutePath;
});
return acc;
}, {});
};
exports.findWebpackModules = findWebpackModules;
class ExtensionValidator extends BaseValidator_1.BaseValidator {
validate(compilation, extensions, exposedModules, pluginBasePath) {
const codeRefs = (0, exports.collectCodeRefData)(extensions);
const webpackModules = (0, exports.findWebpackModules)(compilation, exposedModules, pluginBasePath);
// Each exposed module must have at least one code reference
Object.keys(exposedModules).forEach((moduleName) => {
const moduleReferenced = codeRefs.some((data) => Object.values(data.propToCodeRefValue).some((value) => {
const [parsedModuleName] = (0, coderef_resolver_1.parseEncodedCodeRefValue)(value);
return parsedModuleName && moduleName === parsedModuleName;
}));
if (!moduleReferenced) {
this.result.addError(`Exposed module '${moduleName}' is not referenced by any extension`);
}
});
// Each code reference must point to a valid webpack module export
codeRefs.forEach((data) => {
Object.entries(data.propToCodeRefValue).forEach(([propName, codeRefValue]) => {
const [moduleName, exportName] = (0, coderef_resolver_1.parseEncodedCodeRefValue)(codeRefValue);
const errorTrace = `in extension [${data.index}] property '${propName}'`;
if (!moduleName || !exportName) {
this.result.addError(`Invalid code reference '${codeRefValue}' ${errorTrace}`);
return;
}
const foundModule = webpackModules[moduleName];
if (!foundModule) {
this.result.addError(`Invalid module '${moduleName}' ${errorTrace}`);
return;
}
const moduleExports = compilation.moduleGraph.getProvidedExports(foundModule);
const exportValid = Array.isArray(moduleExports) && moduleExports.includes(exportName);
if (!exportValid) {
this.result.addError(`Invalid module export '${exportName}' ${errorTrace}`);
}
});
});
return this.result;
}
}
exports.ExtensionValidator = ExtensionValidator;