diff --git a/RequireJsLoaderPlugin.js b/RequireJsLoaderPlugin.js index 61f51ef..2af57f5 100644 --- a/RequireJsLoaderPlugin.js +++ b/RequireJsLoaderPlugin.js @@ -1,18 +1,23 @@ 'use strict'; -const ConcatSource = require('webpack-sources').ConcatSource; +const { ConcatSource } = require('webpack-sources'); let RequireJsLoaderPlugin = function () { }; +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 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); + if (isNormalModule(module) && String(module.request).indexOf('requirejs-loader') !== -1) { + needsImport.push('mixins!' + normalizeRequest(module.rawRequest)); } } @@ -28,32 +33,71 @@ function generateEpilog(imports) { return `});`; } -function registerHook(object, oldName, newName, cb) { - if (object.hooks) { - object.hooks[newName].tap('RequireJsLoader', cb); +RequireJsLoaderPlugin.prototype.apply = function (compiler) { + const isWebpack5 = Boolean(compiler.webpack); + + if (isWebpack5) { + compiler.hooks.compilation.tap('RequireJsLoaderPlugin', (compilation) => { + const Compilation = compiler.webpack.Compilation; + + compilation.hooks.processAssets.tap( + { + name: 'RequireJsLoaderPlugin', + stage: Compilation.PROCESS_ASSETS_STAGE_SUMMARIZE, + }, + () => { + const processedFiles = new Set(); + + for (const chunk of compilation.chunks) { + const chunkModules = Array.from( + compilation.chunkGraph.getChunkModulesIterable(chunk) + ); + + const needsImport = gatherRequireJsImports(chunkModules); + + if (needsImport.length === 0) continue; + + 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 { - object.plugin(oldName, cb); - } -} + compiler.hooks.compilation.tap('RequireJsLoaderPlugin', (compilation) => { + compilation.hooks.chunkAsset.tap('RequireJsLoaderPlugin', (chunk, filename) => { + if (!filename.endsWith('.js')) return; -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); - } + // Avoid applying imports twice. + if ('--requirejs-export:done' in chunk) { + return; + } + + const modules = chunk.modulesIterable ? Array.from(chunk.modulesIterable) : []; + 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); + } + + 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 0920d28..f544756 100644 --- a/index.js +++ b/index.js @@ -1,19 +1,49 @@ '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(); + + 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; + } - // 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); - 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 ce6d855..8d32f3a 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,12 @@ { "name": "@sdinteractive/requirejs-loader", - "version": "3.0.0", - "description": "webpack loader to import requirejs browser published modules.", + "version": "4.0.0", + "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" },