Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

this.importModule broken in esm packages #18023

Open
jantimon opened this issue Jan 29, 2024 · 8 comments
Open

this.importModule broken in esm packages #18023

jantimon opened this issue Jan 29, 2024 · 8 comments

Comments

@jantimon
Copy link
Contributor

jantimon commented Jan 29, 2024

Bug report

What is the current behavior?

I am using the Webpack loader API this.importModule which invokes vm.runInThisContext to execute the module code.

However, as soon as the package.json file includes "type": "module" vm.runInThisContext does not have access to require and module anymore:

error screenshot

If the current behavior is a bug, please provide the steps to reproduce.

Here is a reproduction:

https://github.com/jantimon/reproduction-webpack-import-module-bug

webpack.config.js

module.exports = {
    module: {
        rules: [
            {
                test: /\.js$/,
                use: require.resolve('./loader.cjs'),
                issuerLayer: "",
                exclude: /node_modules/,
            },
        ],
    }
};

./loader.cjs

module.exports = function (source) {
    const callback = this.async();
    this.importModule('./banner.js', {
        layer: "banner-loader"
    }, function (err, result) {
        if (err) return callback(err);
        callback(null, (result?.banner || "") + source);
    });
}

./banner.js

const cowsay = require("cowsay");

console.log(cowsay.say({
    text : "I'm a moooodule",
    e : "oO",
    T : "U "
}));

module.exports = {
    banner: `;console.log("Build at ${new Date()}");\n`
}

What is the expected behavior?

module and require should be available in webpack/lib/javascript/JavascriptModulesPlugin.js

The following change would fix the bug:

diff --git a/node_modules/webpack/lib/javascript/JavascriptModulesPlugin.js b/node_modules/webpack/lib/javascript/JavascriptModulesPlugin.js
index 4249a2f..31cd251 100644
--- a/node_modules/webpack/lib/javascript/JavascriptModulesPlugin.js
+++ b/node_modules/webpack/lib/javascript/JavascriptModulesPlugin.js
@@ -441,8 +441,11 @@ class JavascriptModulesPlugin {
 					const { module, moduleObject } = options;
 					const code = source.source();
 
+					const { createRequire } = require("module");
+					const universalRequire = typeof require !== "function" ? createRequire(module.resource) : require;
+
 					const fn = vm.runInThisContext(
-						`(function(${module.moduleArgument}, ${module.exportsArgument}, ${RuntimeGlobals.require}) {\n${code}\n/**/})`,
+						`(function(${module.moduleArgument}, ${module.exportsArgument}, ${RuntimeGlobals.require}, require, module) {\n${code}\n/**/})`,
 						{
 							filename: module.identifier(),
 							lineOffset: -1
@@ -453,7 +456,9 @@ class JavascriptModulesPlugin {
 							moduleObject.exports,
 							moduleObject,
 							moduleObject.exports,
-							context.__webpack_require__
+							context.__webpack_require__,
+							universalRequire,
+							moduleObject
 						);
 					} catch (e) {
 						e.stack += printGeneratedCodeForStack(

This fix would requires Node 12: The 'module.createRequire' is not supported until Node.js 12.2.0

Other relevant information:
webpack version: 5.90.0
Node.js version: 20.9.0

In Next.JS the problem exists also for typescript files even for ESM syntax:
https://github.com/jantimon/reproduction-webpack-import-module-bug/tree/nextjs

ReferenceError: require is not defined Generated code for external commonjs "cowsay" 1 | module. exports = require("cowsay");

@alexander-akait
Copy link
Member

Honestly there are no problems here, you set "type": "module", it means all files are ESM, but banner.js is using require (you know that require does not exist in ESM), just rename banner.js to banner.cjs (or alternative solution is putting package.json with type: "commonjs" inside src). This is how Node.js and ESM logic works. I don't think we should change anything otherwise we won't follow and align ESM logic, also it will require introducing global variables and other things.

@jantimon
Copy link
Contributor Author

jantimon commented Jan 30, 2024

The problem is not about banner.js as it used webpack_require but rather the webpack generated code for the external import:

ReferenceError: require is not defined
Generated code for external commonjs "cowsay"
1 | module.exports = require("cowsay");

there is a nextjs branch in the reproduction repository - there banner.ts is using import but still the error says require is undefined:

https://github.com/jantimon/reproduction-webpack-import-module-bug/blob/nextjs/app/banner.ts

@alexander-akait
Copy link
Member

alexander-akait commented Jan 30, 2024

In the case of nextjs you have external commonjs "cowsay", but run code in ESM env (open .next/ and look at files), no problems with webpack here , if you rename next.config.mjs to next.config.cjs all works fine

technically this should work:

config.externals.unshift({
  "cowsay": "node-commonjs cowsay"
})

It's better to create this question in nextjs repo

@jantimon
Copy link
Contributor Author

renaming next.config.mjs to next.config.cjs works because next.config.cjs is ignored and therefore the loader will not be used

I am not sure what I could see inside the next folder this is the page.js:

throw Error('Module build failed (from ./loader.cjs):\nHookWebpackError: require is not defined\n    at tryRunOrWebpackError (/Users/jannicklas/Desktop/webpack-import-module-bug/node_modules/next/dist/compiled/webpack/bundle5.js:28:311563)\n    at __webpack_require_module__ (/Users/jannicklas/Desktop/webpack-import-module-bug/node_modules/next/dist/compiled/webpack/bundle5.js:28:131195)\n    at __nested_webpack_require_153754__ (/Users/jannicklas/Desktop/webpack-import-module-bug/node_modules/next/dist/compiled/webpack/bundle5.js:28:130634)\n    at Module.<anonymous> (javascript/auto|/Users/jannicklas/Desktop/webpack-import-module-bug/node_modules/next/dist/build/webpack/loaders/next-swc-loader.js??ruleSet[1].rules[14].oneOf[6].use[0]!/Users/jannicklas/Desktop/webpack-import-module-bug/app/banner.ts|banner-loader:5:64)\n    at /Users/jannicklas/Desktop/webpack-import-module-bug/node_modules/next/dist/compiled/webpack/bundle5.js:28:915116\n    at Hook.eval [as call] (eval at create (/Users/jannicklas/Desktop/webpack-import-module-bug/node_modules/next/dist/compiled/webpack/bundle5.js:13:28645), <anonymous>:7:1)\n    at /Users/jannicklas/Desktop/webpack-import-module-bug/node_modules/next/dist/compiled/webpack/bundle5.js:28:131228\n    at tryRunOrWebpackError (/Users/jannicklas/Desktop/webpack-import-module-bug/node_modules/next/dist/compiled/webpack/bundle5.js:28:311517)\n    at __webpack_require_module__ (/Users/jannicklas/Desktop/webpack-import-module-bug/node_modules/next/dist/compiled/webpack/bundle5.js:28:131195)\n    at __nested_webpack_require_153754__ (/Users/jannicklas/Desktop/webpack-import-module-bug/node_modules/next/dist/compiled/webpack/bundle5.js:28:130634)\n-- inner error --\nReferenceError: require is not defined\n    at Object.<anonymous> (external commonjs "cowsay":1:1)\n    at /Users/jannicklas/Desktop/webpack-import-module-bug/node_modules/next/dist/compiled/webpack/bundle5.js:28:915116\n    at Hook.eval [as call] (eval at create (/Users/jannicklas/Desktop/webpack-import-module-bug/node_modules/next/dist/compiled/webpack/bundle5.js:13:28645), <anonymous>:7:1)\n    at /Users/jannicklas/Desktop/webpack-import-module-bug/node_modules/next/dist/compiled/webpack/bundle5.js:28:131228\n    at tryRunOrWebpackError (/Users/jannicklas/Desktop/webpack-import-module-bug/node_modules/next/dist/compiled/webpack/bundle5.js:28:311517)\n    at __webpack_require_module__ (/Users/jannicklas/Desktop/webpack-import-module-bug/node_modules/next/dist/compiled/webpack/bundle5.js:28:131195)\n    at __nested_webpack_require_153754__ (/Users/jannicklas/Desktop/webpack-import-module-bug/node_modules/next/dist/compiled/webpack/bundle5.js:28:130634)\n    at Module.<anonymous> (javascript/auto|/Users/jannicklas/Desktop/webpack-import-module-bug/node_modules/next/dist/build/webpack/loaders/next-swc-loader.js??ruleSet[1].rules[14].oneOf[6].use[0]!/Users/jannicklas/Desktop/webpack-import-module-bug/app/banner.ts|banner-loader:5:64)\n    at /Users/jannicklas/Desktop/webpack-import-module-bug/node_modules/next/dist/compiled/webpack/bundle5.js:28:915116\n    at Hook.eval [as call] (eval at create (/Users/jannicklas/Desktop/webpack-import-module-bug/node_modules/next/dist/compiled/webpack/bundle5.js:13:28645), <anonymous>:7:1)\n\nGenerated code for external commonjs "cowsay"\n1 | module.exports = require("cowsay");\n\nGenerated code for javascript/auto|/Users/jannicklas/Desktop/webpack-import-module-bug/node_modules/next/dist/build/webpack/loaders/next-swc-loader.js??ruleSet[1].rules[14].oneOf[6].use[0]!/Users/jannicklas/Desktop/webpack-import-module-bug/app/banner.ts|banner-loader

@alexander-akait
Copy link
Member

I mean you will see files in ESM format, please open an issue in the nextjs repo, webpack works fine here

@alexander-akait
Copy link
Member

Also looks like there is the nextjs bug, because using:

config.externals.unshift({
  "cowsay": "node-commonjs cowsay"
})

Still generates module.exports = require("cowsay");, but should generate https://webpack.js.org/configuration/externals/#externalstypenode-commonjs

@jantimon
Copy link
Contributor Author

Thanks for your help and time @alexander-akait!

I'll try to investigate further to find out how module.exports = require("cowsay"); gets generated

@webpack-bot
Copy link
Contributor

This issue had no activity for at least three months.

It's subject to automatic issue closing if there is no activity in the next 15 days.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants