webpack: Expose a version of require() for use in the browser console.

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 <anders@zulipchat.com>
This commit is contained in:
Anders Kaseorg 2020-02-24 19:34:09 -08:00 committed by Tim Abbott
parent 59b047c862
commit 46e14d1c40
2 changed files with 89 additions and 0 deletions

View File

@ -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;`
),
"}",
]);
}
);
});
}
}

View File

@ -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'