From 46e14d1c40fb8a3eabe23e9371a658145cf7b154 Mon Sep 17 00:00:00 2001 From: Anders Kaseorg Date: Mon, 24 Feb 2020 19:34:09 -0800 Subject: [PATCH] webpack: Expose a version of require() for use in the browser console. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This adds a global require() function that makes JS modules accessible to the browser console without adding them to the global window object: » const typeahead = require("./static/shared/js/typeahead"); » typeahead.popular_emojis Array(6) [ "1f44d", "1f389", "1f642", "2764", "1f6e0", "1f419" ] The list of known modules is exposed via the keys of require.ids object. This will allow us to migrate more modules to ES6 without losing access to this debugging functionality. I’ll probably upload this plugin to NPM at some point, but I figured I’ll let it bake in-tree first. Signed-off-by: Anders Kaseorg --- tools/debug-require-webpack-plugin.ts | 87 +++++++++++++++++++++++++++ tools/webpack.config.ts | 2 + 2 files changed, 89 insertions(+) create mode 100644 tools/debug-require-webpack-plugin.ts 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'