diff --git a/tools/debug-require-webpack-plugin.ts b/tools/debug-require-webpack-plugin.ts new file mode 100644 index 0000000000..506820b56c --- /dev/null +++ b/tools/debug-require-webpack-plugin.ts @@ -0,0 +1,87 @@ +// This plugin exposes a version of require() to the browser console to assist +// debugging. It also exposes the list of modules it knows about as the keys +// of the require.ids object. + +import webpack, { Template } from "webpack"; +import path from "path"; + +export default class DebugRequirePlugin { + apply(compiler: webpack.Compiler): void { + const resolved = new Map(); + const nameSymbol = Symbol("DebugRequirePluginName"); + + (compiler as any).resolverFactory.hooks.resolver + .for("normal") + .tap("DebugRequirePlugin", (resolver: any) => { + resolver.getHook("beforeRawModule").tap("DebugRequirePlugin", (req: any) => { + req[nameSymbol] = req[nameSymbol] || req.request; + }); + + resolver.getHook("beforeRelative").tap("DebugRequirePlugin", (req: any) => { + const inPath = path.relative(compiler.context, req.path); + if (!inPath.startsWith("../")) { + req[nameSymbol] = req[nameSymbol] || "./" + inPath; + } + }); + + resolver.getHook("beforeResolved").tap("DebugRequirePlugin", (req: any) => { + if (req[nameSymbol]) { + const names = resolved.get(req.path); + if (names) { + names.add(req[nameSymbol]); + } else { + resolved.set(req.path, new Set([req[nameSymbol]])); + } + } + }); + }); + + compiler.hooks.compilation.tap("DebugRequirePlugin", (compilation: any) => { + compilation.mainTemplate.hooks.beforeStartup.tap( + "DebugRequirePlugin", + (source: string, chunk: webpack.compilation.Chunk) => { + const ids: [string, string | number][] = []; + chunk.hasModuleInGraph( + (mod: any) => { + const { resource, rawRequest, id } = mod; + for (const name of resolved.get(resource) || []) { + if (name === "./static/js/debug.js") { + console.log(name, id, mod); + } + ids.push([ + rawRequest.slice(0, rawRequest.lastIndexOf("!") + 1) + name, + id, + ]); + } + return false; + }, + () => true + ); + ids.sort(); + + const { + outputOptions: { globalObject }, + requireFn, + } = compilation.mainTemplate; + + return Template.asString([ + source, + `function debugRequire(request) {`, + Template.indent(`return ${requireFn}(debugRequire.ids[request]);`), + "};", + `debugRequire.ids = ${JSON.stringify( + Object.fromEntries(ids), + null, + "\t" + )};`, + `if (typeof ${globalObject} !== "undefined") {`, + Template.indent( + `${globalObject}.require = ${globalObject}.require || debugRequire;` + ), + "}", + ]); + } + ); + }); + } +} diff --git a/tools/webpack.config.ts b/tools/webpack.config.ts index 154675c018..06f734161f 100644 --- a/tools/webpack.config.ts +++ b/tools/webpack.config.ts @@ -2,6 +2,7 @@ import { basename, resolve } from 'path'; import { cacheLoader, getExposeLoaders } from './webpack-helpers'; import BundleTracker from 'webpack4-bundle-tracker'; import CleanCss from 'clean-css'; +import DebugRequirePlugin from './debug-require-webpack-plugin'; import HtmlWebpackPlugin from 'html-webpack-plugin'; import MiniCssExtractPlugin from 'mini-css-extract-plugin'; import OptimizeCssAssetsPlugin from 'optimize-css-assets-webpack-plugin'; @@ -187,6 +188,7 @@ export default (env?: string): webpack.Configuration[] => { }, }, plugins: [ + new DebugRequirePlugin(), new BundleTracker({ filename: production ? 'webpack-stats-production.json'