Access vanilla JS module code in webpack plugin

66 Views Asked by At

Context: I'm working with the project written mostly in plain ES6 javascript and built with webpack wrapped in gulp. Plus, it uses a custom js loader to remove some macro-s before the processing, and import 'shortcuts' that are replaced with relative paths during using webpack aliases. All that to say that there is some useful processing going on, I do not want to lose.

Goal: Get processes js sources without webpack bundling and webpack 'fluff' and store them as separate files preserving the structure of the folders, so that I could use those files in "modular" fashion in web content.

My assumption is that I can intercept webpack after the processing of the sources and before it all wrapped up in webpack package. I've looked into webpack plugins, compiler emit hook looks promising, and it looks like Compilation object does have a list of modules and Modules have .source() method, but I have no idea how to call it properly.

Does anyone have any idea how where I can tap into to get me sources?

Also, can webpack docs be more useless?

1

There are 1 best solutions below

0
On

Ok, here is 'picked by the ear' hacky solution. I've tapped into webpack with the custom plugin below to intercept processed source of each involved file. One thing it does not do is that the sources still contain references to relative module paths, not the actual resolved file paths from node_modules or elsewhere. For me it is not a big issue since I can use webpack's config resolve.alias and imprtmap to make it work. I will leave it at that for now, but happy to learn about better solution.

Here is the plugin:

class SourceInterceptorPlugin {
    handleModulesRecursively(modules: Set<Module>, sources: Map<string, string>) {
        for (let module of modules) {
            if (module.type != 'javascript/esm') {
                continue;
            }
            //this is were typization goes out of the window.
            //some modules are recursive, and I can not find a sane/better/typed way to detect that.
            let innerModules = <Set<Module>> module['modules']; 
            if(innerModules) {
                this.handleModulesRecursively(innerModules, sources);
                continue;
            }
            let source = module.originalSource()?.source();
            if (!source) {
                console.warn("get-me-sources: idk, something is not right");
                continue;
            }
            if (module instanceof webpack.NormalModule) {
                sources.set(module.resource, '' + source)
            } else {
                console.warn("get-me-sources: idk, something is not right #2");
            }
        }
    }   
    apply(compiler: Compiler) {
        compiler.hooks.done.tapAsync('get-me-sources',
        (stats:Stats, callback) => {
            const compilation = stats.compilation;

            let sources = new Map<string, string>();
            this.handleModulesRecursively(compilation.modules, sources);
            
            let outputDist = compilation.outputOptions.path || '';
            for (const [resPath, source] of sources) {
                let relPath = path.relative(__dirname, resPath);
                let fullPath = path.join(outputDist, relPath);
                fs.mkdirSync(path.dirname(fullPath), { recursive: true });
                fs.writeFileSync(fullPath, source);
            }
            callback();
        })
    }
}