From ec2f0b91bd72a1d535e0e7884adb6f12c8348316 Mon Sep 17 00:00:00 2001 From: Neli Mintcheva Date: Tue, 16 Jun 2026 19:01:47 -0400 Subject: [PATCH 1/2] Add updates as of now --- RequireJsLoaderPlugin.js | 112 +++++++++++++++++++++++++-------------- index.js | 16 +++--- package.json | 2 +- 3 files changed, 84 insertions(+), 46 deletions(-) diff --git a/RequireJsLoaderPlugin.js b/RequireJsLoaderPlugin.js index 61f51ef..02e864f 100644 --- a/RequireJsLoaderPlugin.js +++ b/RequireJsLoaderPlugin.js @@ -1,58 +1,92 @@ 'use strict'; -const ConcatSource = require('webpack-sources').ConcatSource; +const { ConcatSource } = require('webpack-sources'); + +// Matches window.require("mixins!foo") or window.require('mixins!foo') in asset source +const MIXINS_PATTERN = /window\.require\(["'](mixins![^"']+)["']\)/g; let RequireJsLoaderPlugin = function () { }; -function gatherRequireJsImports(modules) { - let needsImport = []; - for (let module of modules) { - // If the requirejs-loader was used, then we need to wrap and import this module. - // It's safe to use mixins! in all cases, and necessary for anything where require('mixins').hasMixins(module) is true. - // TODO: Clean up this check. - if (module.request && module.request.indexOf('requirejs-loader') !== -1) { - needsImport.push('mixins!' + module.rawRequest); - } +/** + * Scan compiled asset source for window.require("mixins!...") calls. + * This is more reliable than module enumeration because webpack 5 scope-hoisting + * (ConcatenatedModule) hides inner modules from chunkGraph iteration. + */ +function gatherImportsFromSource(source) { + const imports = new Set(); + let match; + MIXINS_PATTERN.lastIndex = 0; + while ((match = MIXINS_PATTERN.exec(source)) !== null) { + imports.add(match[1]); } - - return needsImport; + return Array.from(imports); } function generateProlog(imports) { - const jsonImports = JSON.stringify(imports); - return `window.require(${jsonImports}, function () {`; + return `window.require(${JSON.stringify(imports)}, function () {`; } -function generateEpilog(imports) { +function generateEpilog() { return `});`; } -function registerHook(object, oldName, newName, cb) { - if (object.hooks) { - object.hooks[newName].tap('RequireJsLoader', cb); - } else { - object.plugin(oldName, cb); - } -} - RequireJsLoaderPlugin.prototype.apply = function (compiler) { - registerHook(compiler, 'compilation', 'compilation', (compilation, data) => { - registerHook(compilation, 'chunk-asset', 'chunkAsset', (chunk, filename) => { - // Avoid applying imports twice. - if ('--requirejs-export:done' in chunk) { - return; - } - - const modules = chunk.modulesIterable ? Array.from(chunk.modulesIterable) : modules; - const needsImport = gatherRequireJsImports(modules); - if (needsImport.length != 0) { - let prolog = generateProlog(needsImport); - let epilog = generateEpilog(needsImport); - - compilation.assets[filename] = new ConcatSource(prolog, "\n", compilation.assets[filename], "\n", epilog); - } - }); + compiler.hooks.compilation.tap('RequireJsLoader', (compilation) => { + if (compilation.hooks.processAssets) { + // webpack 5 + const webpack = compiler.webpack || require('webpack'); + compilation.hooks.processAssets.tap( + { + name: 'RequireJsLoader', + stage: webpack.Compilation + ? webpack.Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE_COMPATIBILITY + : 1000, + }, + (assets) => { + const processed = new Set(); + for (const [filename, asset] of Object.entries(assets)) { + if (processed.has(filename)) continue; + + const source = asset.source(); + // Only wrap JS files that actually contain requirejs-loader calls + if (typeof source !== 'string' || !source.includes('window.require("mixins!')) { + continue; + } + + const needsImport = gatherImportsFromSource(source); + if (needsImport.length === 0) continue; + + compilation.updateAsset( + filename, + old => new ConcatSource(generateProlog(needsImport), '\n', old, '\n', generateEpilog()) + ); + processed.add(filename); + } + } + ); + } else { + // webpack 4 fallback: module enumeration still works here + compilation.hooks.chunkAsset.tap('RequireJsLoader', (chunk, filename) => { + if (chunk.__requirejsLoaderDone) return; + + const modules = chunk.modulesIterable ? Array.from(chunk.modulesIterable) : []; + const needsImport = []; + for (const mod of modules) { + if (mod.request && mod.request.indexOf('requirejs-loader') !== -1) { + needsImport.push('mixins!' + mod.rawRequest); + } + } + if (needsImport.length === 0) return; + + compilation.assets[filename] = new ConcatSource( + generateProlog(needsImport), '\n', + compilation.assets[filename], '\n', + generateEpilog() + ); + chunk.__requirejsLoaderDone = true; + }); + } }); }; diff --git a/index.js b/index.js index 0920d28..9d97026 100644 --- a/index.js +++ b/index.js @@ -1,18 +1,22 @@ 'use strict'; -const path = require('path'); -const ConcatSource = require('webpack-sources').ConcatSource; - module.exports = function () { }; module.exports.pitch = function (remainingRequest) { - this.cacheable && this.cacheable(); + this.cacheable && this.cacheable(); // Route through window.require. // It's safe to use mixins! in all cases, and necessary for anything where require('mixins').hasMixins(module) is true. - // TODO: We use rawRequest to grab the original request (including text! or etc.) - const jsonName = JSON.stringify('mixins!' + this._module.rawRequest); + // webpack 5: rawRequest is still on NormalModule but accessed via this._module + const rawRequest = this._module && this._module.rawRequest; + const jsonName = JSON.stringify('mixins!' + rawRequest); + + // For jquery, fall back to window.jQuery if the RequireJS proxy hasn't resolved .fn yet + if (rawRequest === 'jquery') { + return `var _r=window.require(${jsonName});module.exports=(_r&&_r.fn)?_r:(window.jQuery||_r);`; + } + return `module.exports = window.require(${jsonName});`; }; diff --git a/package.json b/package.json index ce6d855..c1b0a7c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@sdinteractive/requirejs-loader", - "version": "3.0.0", + "version": "4.0.0", "description": "webpack loader to import requirejs browser published modules.", "main": "index.js", "scripts": { From ce2aac4ee3f30927c58f7cff52232942abb4e913 Mon Sep 17 00:00:00 2001 From: Neli Mintcheva Date: Wed, 17 Jun 2026 20:10:07 -0400 Subject: [PATCH 2/2] Update files --- RequireJsLoaderPlugin.js | 132 +++++++++++++++++++++------------------ index.js | 48 ++++++++++---- package.json | 6 +- 3 files changed, 113 insertions(+), 73 deletions(-) diff --git a/RequireJsLoaderPlugin.js b/RequireJsLoaderPlugin.js index 02e864f..2af57f5 100644 --- a/RequireJsLoaderPlugin.js +++ b/RequireJsLoaderPlugin.js @@ -2,92 +2,102 @@ const { ConcatSource } = require('webpack-sources'); -// Matches window.require("mixins!foo") or window.require('mixins!foo') in asset source -const MIXINS_PATTERN = /window\.require\(["'](mixins![^"']+)["']\)/g; - let RequireJsLoaderPlugin = function () { }; -/** - * Scan compiled asset source for window.require("mixins!...") calls. - * This is more reliable than module enumeration because webpack 5 scope-hoisting - * (ConcatenatedModule) hides inner modules from chunkGraph iteration. - */ -function gatherImportsFromSource(source) { - const imports = new Set(); - let match; - MIXINS_PATTERN.lastIndex = 0; - while ((match = MIXINS_PATTERN.exec(source)) !== null) { - imports.add(match[1]); +function isNormalModule(module) { + return Boolean(module && typeof module.request === 'string' && module.rawRequest != null); +} + +function normalizeRequest(rawRequest) { + return String(rawRequest).replace(/^mixins!/, '').replace(/\.js$/, ''); +} + +function gatherRequireJsImports(modules) { + let needsImport = []; + for (let module of modules) { + if (isNormalModule(module) && String(module.request).indexOf('requirejs-loader') !== -1) { + needsImport.push('mixins!' + normalizeRequest(module.rawRequest)); + } } - return Array.from(imports); + + return needsImport; } function generateProlog(imports) { - return `window.require(${JSON.stringify(imports)}, function () {`; + const jsonImports = JSON.stringify(imports); + return `window.require(${jsonImports}, function () {`; } -function generateEpilog() { +function generateEpilog(imports) { return `});`; } RequireJsLoaderPlugin.prototype.apply = function (compiler) { - compiler.hooks.compilation.tap('RequireJsLoader', (compilation) => { - if (compilation.hooks.processAssets) { - // webpack 5 - const webpack = compiler.webpack || require('webpack'); + const isWebpack5 = Boolean(compiler.webpack); + + if (isWebpack5) { + compiler.hooks.compilation.tap('RequireJsLoaderPlugin', (compilation) => { + const Compilation = compiler.webpack.Compilation; + compilation.hooks.processAssets.tap( { - name: 'RequireJsLoader', - stage: webpack.Compilation - ? webpack.Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE_COMPATIBILITY - : 1000, + name: 'RequireJsLoaderPlugin', + stage: Compilation.PROCESS_ASSETS_STAGE_SUMMARIZE, }, - (assets) => { - const processed = new Set(); - for (const [filename, asset] of Object.entries(assets)) { - if (processed.has(filename)) continue; - - const source = asset.source(); - // Only wrap JS files that actually contain requirejs-loader calls - if (typeof source !== 'string' || !source.includes('window.require("mixins!')) { - continue; - } + () => { + const processedFiles = new Set(); + + for (const chunk of compilation.chunks) { + const chunkModules = Array.from( + compilation.chunkGraph.getChunkModulesIterable(chunk) + ); + + const needsImport = gatherRequireJsImports(chunkModules); - const needsImport = gatherImportsFromSource(source); if (needsImport.length === 0) continue; - compilation.updateAsset( - filename, - old => new ConcatSource(generateProlog(needsImport), '\n', old, '\n', generateEpilog()) - ); - processed.add(filename); + const prolog = generateProlog(needsImport); + const epilog = generateEpilog(needsImport); + + for (const filename of chunk.files) { + if (!filename.endsWith('.js')) continue; + if (processedFiles.has(filename)) continue; + + processedFiles.add(filename); + compilation.updateAsset( + filename, + (old) => new ConcatSource(prolog, '\n', old, '\n', epilog) + ); + } } } ); - } else { - // webpack 4 fallback: module enumeration still works here - compilation.hooks.chunkAsset.tap('RequireJsLoader', (chunk, filename) => { - if (chunk.__requirejsLoaderDone) return; + }); + } else { + compiler.hooks.compilation.tap('RequireJsLoaderPlugin', (compilation) => { + compilation.hooks.chunkAsset.tap('RequireJsLoaderPlugin', (chunk, filename) => { + if (!filename.endsWith('.js')) return; + + // Avoid applying imports twice. + if ('--requirejs-export:done' in chunk) { + return; + } const modules = chunk.modulesIterable ? Array.from(chunk.modulesIterable) : []; - const needsImport = []; - for (const mod of modules) { - if (mod.request && mod.request.indexOf('requirejs-loader') !== -1) { - needsImport.push('mixins!' + mod.rawRequest); - } + const needsImport = gatherRequireJsImports(modules); + + if (needsImport.length !== 0) { + let prolog = generateProlog(needsImport); + let epilog = generateEpilog(needsImport); + + compilation.assets[filename] = new ConcatSource(prolog, "\n", compilation.assets[filename], "\n", epilog); } - if (needsImport.length === 0) return; - - compilation.assets[filename] = new ConcatSource( - generateProlog(needsImport), '\n', - compilation.assets[filename], '\n', - generateEpilog() - ); - chunk.__requirejsLoaderDone = true; + + chunk['--requirejs-export:done'] = true; }); - } - }); + }); + } }; -module.exports = RequireJsLoaderPlugin; +module.exports = RequireJsLoaderPlugin; \ No newline at end of file diff --git a/index.js b/index.js index 9d97026..f544756 100644 --- a/index.js +++ b/index.js @@ -6,18 +6,44 @@ module.exports = function () { module.exports.pitch = function (remainingRequest) { this.cacheable && this.cacheable(); - // Route through window.require. - // It's safe to use mixins! in all cases, and necessary for anything where require('mixins').hasMixins(module) is true. - // webpack 5: rawRequest is still on NormalModule but accessed via this._module - const rawRequest = this._module && this._module.rawRequest; - const jsonName = JSON.stringify('mixins!' + rawRequest); - - // For jquery, fall back to window.jQuery if the RequireJS proxy hasn't resolved .fn yet - if (rawRequest === 'jquery') { - return `var _r=window.require(${jsonName});module.exports=(_r&&_r.fn)?_r:(window.jQuery||_r);`; + const options = (this.getOptions ? this.getOptions() : this.query) || {}; + + // This loader is normally applied via a `module.rules` entry rather than an + // inline `mixins!` prefix, so `remainingRequest` here resolves to the absolute + // filesystem path of the file, not the bare specifier (e.g. "jquery", or a + // RequireJS-style module id) that was originally written in source. We need + // that original, unresolved specifier to build a `window.require(...)` id + // RequireJS will actually recognize. `this._module.rawRequest` is the only + // way to recover it; it's a documented-but-discouraged loader context + // property webpack has flagged for eventual removal, but there's currently + // no public replacement that preserves the as-written request, so we keep + // using it and only fall back to `remainingRequest` if it's unavailable. + const rawRequest = (this._module && this._module.rawRequest) || remainingRequest; + const requestName = String(rawRequest).replace(/^mixins!/, '').replace(/\.js$/, ''); + + if (options.debug) { + console.log('[requirejs-loader] pitch', { + resourcePath: this.resourcePath, + remainingRequest, + rawRequest, + requestName, + }); + } + + let output; + if (requestName === 'jquery') { + const jqueryRequest = JSON.stringify('mixins!' + requestName); + output = `module.exports = window.jQuery || window.$ || (typeof window.require === 'function' ? window.require(${jqueryRequest}) : undefined);`; + } else { + const jsonName = JSON.stringify('mixins!' + requestName); + output = `module.exports = window.require(${jsonName});`; + } + + if (typeof options.onTransform === 'function') { + output = options.onTransform(output, this.resourcePath) || output; } - return `module.exports = window.require(${jsonName});`; + return output; }; -module.exports.RequireJsLoaderPlugin = require('./RequireJsLoaderPlugin.js'); +module.exports.RequireJsLoaderPlugin = require('./RequireJsLoaderPlugin.js'); \ No newline at end of file diff --git a/package.json b/package.json index c1b0a7c..8d32f3a 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,12 @@ { "name": "@sdinteractive/requirejs-loader", "version": "4.0.0", - "description": "webpack loader to import requirejs browser published modules.", + "description": "webpack loader to import requirejs browser published modules. Supports webpack 4 and webpack 5.", "main": "index.js", + "peerDependencies": { + "webpack": "^4.0.0 || ^5.0.0", + "webpack-sources": "^1.4.3 || ^2.0.0 || ^3.0.0" + }, "scripts": { "test": "echo \"Error: no test specified\" && exit 1" },