mmir-webpack
Version:
Integration for MMIR library in webpack builds
204 lines (171 loc) • 9.03 kB
text/typescript
import { existsSync } from 'fs';
import { sep, normalize, resolve } from 'path';
import { Compiler, Module, Compilation, ChunkGraph } from 'webpack';
import { log } from 'mmir-tooling/utils/log-utils';
const dir = __dirname;
const reNormalize = sep !== '/'? new RegExp(sep.replace(/\\/g, '\\\\'), 'g') : null;
function idFromPath(path: string): string {
if(reNormalize){
path = path.replace(reNormalize, '/');
}
return path;
}
function lookUpAlias(aliasId: string, aliasMap: {[id: string]: string}): string | undefined {
// NOTE webpack aliases may have suffix $ which indicates that the alias must match exactly (i.e. is not part of the ID itself!)
return aliasMap[aliasId + '$'] || aliasMap[aliasId];
}
const reCleanLookUpId = /\$$/;
function normalizeLookUpId(lookUpId: string): string | undefined {
// NOTE webpack aliases may have suffix $ which indicates that the alias must match exactly -> needs to be removed, since it is not part of the ID itself
return lookUpId.replace(reCleanLookUpId, '');
}
/**
* tries to find the (raw) module ID for a path (or the module's original ID)
*
* @param modPaths the mapping for (raw) module IDs (i.e. webpack alias definitions) to file/directory paths
* @param path the path for which to find the (raw) module ID (if `undefined`, the `originalId` is used for finding the appropriate ID)
* @param fileExtensions the file extension (if there is any) of param `path`
* @param originalId the original module ID
* @return the "raw" module ID (i.e. webpack alias entry); may need to be normalized before further usage, see [[normalizeLookUpId]]
*/
function getModId(modPaths: {[id: string]: string}, path: string | undefined, fileExtensions: RegExp, originalId: string): string {
if(!path){
// console.log(' ReplaceModuleIdPlugin: handle empty path for (original) ID '+originalId+' -> alias: ', modPaths[originalId], modPaths);//DEBUG
if(originalId){
if(lookUpAlias(originalId, modPaths)){
// console.log(' ReplaceModuleIdPlugin: handle empty path, but original ID '+originalId+' has module ID entry, using the orginial ID...');//DEBUG
return originalId;
}
var normalizedOrigId = idFromPath(originalId);
if(lookUpAlias(normalizedOrigId, modPaths)){
// console.log(' ReplaceModuleIdPlugin: handle empty path, but normalized original ID '+normalizedOrigId+' has module ID entry, using the normalized ID...');//DEBUG
return normalizedOrigId;
}
}
return '';
}
let clpath: string = path.replace(fileExtensions, '');
clpath = clpath === path? null : clpath;
let val: string;
const results: string[] = [];
for (var p in modPaths) {
val = modPaths[p];
if (val === path || val === clpath) {
results.push(p);
} else if(clpath && clpath.indexOf(val) === 0){
// console.log(' handle package alias "'+clpath+'" -> ', p + idFromPath(clpath.substring(val.length)));//DEBUG
results.push(p + idFromPath(clpath.substring(val.length)));
}
}
const len = results.length;
if(len > 0){
if(len === 1){
return results[0];
}
//if there are multiple entries/IDs -> use shortest one!
results.sort(function(a, b){
return a.length - b.length;
});
// console.log(' handle multiple results: ', results, ': '+originalId+' -> "'+results[0]+'"');//DEBUG
return results[0];
}
}
function doGetAbsolutePath(ctxPath: string, list: string[], id: string) {
let fullpath: string = resolve(ctxPath, id);
if(existsSync(fullpath)){
return fullpath;
}
for(let i = 0, size = list.length - 1; i < size; ++i){
fullpath = resolve(list[i], id);
if(existsSync(fullpath)){
return fullpath;
}
}
}
function getAbsolutePath(compiler: Compiler, mmirDir: string, id: string) {
return doGetAbsolutePath(compiler.options.context, [process.cwd(), dir, mmirDir], id);
}
function getWebpackModuleId(module: Module, chunkGraph: ChunkGraph){
if(chunkGraph && chunkGraph.getModuleId){
// for webpack5: use ChunkGraph API for retrieving module ID
return chunkGraph.getModuleId(module);
} else {
// otherwise: just return the module ID field
return module.id;
}
}
function setWebpackModuleId(module: Module, newId: string, chunkGraph: ChunkGraph){
if(chunkGraph && chunkGraph.getModuleId){
// for webpack5: use ChunkGraph API for setting module ID
return chunkGraph.setModuleId(module, newId);
} else {
// otherwise: just set the module ID field
return module.id = newId;
}
}
export interface FixedIdModule extends Module {
libIdent(options: any): string;
}
//based on https://stackoverflow.com/a/34637718/4278324
export class ReplaceModuleIdPlugin {
constructor(public alias: {[id: string]: string}, public mmirDir: string, public fileExtensions: RegExp) {
this.alias = alias || {};
// console.log('ReplaceModuleIdPlugin.constructor: alias -> ', JSON.stringify(alias));
}
apply(compiler: Compiler) {
var processModules = (modules: Iterable<Module>, compilation: Compilation) => {
// console.log('ReplaceModuleIdPlugin.beforeModuleIds: ', modules.filter(function(mod){return /parsingResult/.test(mod.rawRequest)}));
const aliasLookup = this.alias;
const fileExtensions = this.fileExtensions;
// console.log('ReplaceModuleIdPlugin.beforeModuleIds: current dir "'+__dirname+'", mmir-lib dir "'+this.mmirDir+'", checking '+JSON.stringify(aliasLookup)); //DEBUG
Array.from(modules).forEach(function(module) {
if (getWebpackModuleId(module, compilation.chunkGraph) === null && module.libIdent) {
const resolvedId: string = module.libIdent({
context: compiler.options.context
});
let lookUpId: string;
let id: string;
if(resolvedId){
id = normalize(resolvedId);
const fullpath = getAbsolutePath(compiler, this.mmirDir, id);
// console.log('ReplaceModuleIdPlugin.beforeModuleIds->forEach id ', id, ', fullpath ', fullpath); //, ', module ', module);//DEBUG
lookUpId = getModId(aliasLookup, fullpath, fileExtensions, id);
// if(/mmir-plugin-/.test(fullpath)) console.log('ReplaceModuleIdPlugin.beforeModuleIds->forEach id ',id, ', fullpath ', fullpath, ' -> ', lookUpId? lookUpId : 'UNKNOWN');//, ', module ', module);//DEBUG
// if(/mmir-plugin-/.test(id)) console.log('ReplaceModuleIdPlugin.beforeModuleIds->forEach id ',id, ', fullpath ', fullpath, ' -> ', lookUpId? lookUpId : 'UNKNOWN');//, ', module ', module);//DEBUG
} else {
log('[INFO] ReplaceModuleIdPlugin.beforeModuleIds: not resolved ', module);
}
if (lookUpId) {
// console.log('ReplaceModuleIdPlugin.beforeModuleIds->forEach id ', id, ' -> ', normalizeLookUpId(lookUpId)); //, ', module ', module);//DEBUG
// normalize the "raw" lookup ID
id = normalizeLookUpId(lookUpId);
module.libIdent = function() {
return id;
}
setWebpackModuleId(module, id, compilation.chunkGraph);
// console.log('TEST replaced id result: ', id)
}
// else if(/mmirf\\controller\\/.test(aliasLookup)){
//
// }
} else if(process.env.verbose) {
log('[INFO] ReplaceModuleIdPlugin.beforeModuleIds: cannot process module ', module);
}
}, this);
};
if (!compiler.hooks || !compiler.hooks.compilation) {
// backwards compatiblity with webpack 3
(compiler as any).plugin('compilation', function(compilation: any) {
compilation.plugin('before-module-ids', (modules: Iterable<any>) => {
processModules(modules, compilation)
})
});
} else {
compiler.hooks.compilation.tap('ReplaceModuleIdPlugin', compilation => {
compilation.hooks.beforeModuleIds.tap('ReplaceModuleIdPlugin', (modules) => {
processModules(modules, compilation)
});
});
}
}
}