I was trying to bundle a nestjs
application to run it in a lambda. But gave up. After a while I tried to do the same with a freshly created nestjs
application that connects to mysql
. The problem was that sequelize
requires mysql2
this way:
require(moduleName);
Well, it requires the dialect I'm asking it to require, but in this case it was mysql2
. Apparently webpack
can't do much about it on its own and bails out. I resolved it as they suggested. But then I thought, "I wonder if webpack
can replace a specific line in a specific file?" Or rather pretend that it differs. Pretend that instead of require(moduleName)
it reads require('mysql2')
.
There's a similar question, but I'm specifically targeting nodejs
. And I'm concerned about what one might call a context dependency in webpack
-speak. A dependency whose request is an expression, not a string.
ContextReplacementPlugin
can't be applied here, because for a single identifier the request is always .
(such requests are indistinguishible). NormalReplacementPlugin
isn't applicable because that's a context dependency. And DefinePlugin
doesn't seem to be up to the task, because it doesn't let you replace whatever you like, particularly local variables, and function parameters.
If you're interested in bundling a nodejs
application, you might want to check out this question. Here I'm concerned with webpack
and context dependencies.
P.S. Although I found a solution that doesn't require resolving context dependencies, I might run into it down the road. Or somebody else.
UPD And here's a case that can't be worked around like nestjs
sequelize
mysql2
. sequelize-typescript
loads models at runtime:
CodePudding user response:
Disclaimer. The provided implementation might not work for you as is. You might need to amend it to work with your version of webpack
. Also, I'm targeting nodejs
(not a browser), as such I'm ignoring source maps.
Let's say you have src/index.js
:
const mysql2 = require('whatever');
and the following packages:
{
"dependencies": {
"mysql2": "2.3.3",
"webpack": "5.64.2",
"webpack-cli": "4.9.1"
}
}
and webpack.config.js
:
const path = require('path');
const RewriteRequirePlugin = require('./rewrite-require-plugin');
module.exports = {
mode: 'development',
target: 'node',
module: {
rules: [
// {test: path.resolve('src/index.js'),
// use: [
// {loader: path.resolve('rewrite-require-loader.js'),
// options: {
// search: "'whatever'",
// replace: JSON.stringify('mysql2'),
// }},
// ]}
],
},
plugins: [
// new RewriteRequirePlugin([
// [path.resolve('src/index.js'),
// "'whatever'",
// JSON.stringify('mysql2')],
// ])
],
stats: {
modulesSpace: Infinity,
groupModulesByPath: false,
}
};
It won't build. But if you uncomment the plugin or the loader it will.
rewrite-require-loader.js
:
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#escaping
function escapeRegExp(string) {
return string.replace(/[.* ?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
}
function processFile(source, search, replace) {
const re = `require\\(${escapeRegExp(search)}\\)`;
return source.replace(
new RegExp(re, 'g'),
`require(${replace})`);
}
module.exports = function(source) {
const options = this.getOptions();
return processFile(source, options.search, options.replace);
};
rewrite-require-plugin.js
:
const path = require('path');
const NormalModule = require('webpack/lib/NormalModule');
// https://github.com/webpack/loader-runner/blob/v4.2.0/lib/LoaderRunner.js#L9-L16
function utf8BufferToString(buf) {
var str = buf.toString("utf-8");
if(str.charCodeAt(0) === 0xFEFF) {
return str.substr(1);
} else {
return str;
}
}
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#escaping
function escapeRegExp(string) {
return string.replace(/[.* ?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
}
function processFile(source, search, replace) {
source = Buffer.isBuffer(source) ? utf8BufferToString(source) : source;
const re = `require\\(${escapeRegExp(search)}\\)`;
return source.replace(
new RegExp(re, 'g'),
`require(${replace})`);
}
class RewriteRequirePlugin {
constructor(rewrites) {
this.rewrites = rewrites.map(r => [path.resolve(r[0]), r[1], r[2]]);
}
apply(compiler) {
compiler.hooks.compilation.tap('RewriteRequirePlugin', compilation => {
// https://github.com/webpack/webpack/blob/v5.64.2/lib/schemes/FileUriPlugin.js#L36-L43
const hooks = NormalModule.getCompilationHooks(compilation);
hooks.readResource
.for(undefined)
.tapAsync("FileUriPlugin", (loaderContext, callback) => {
const { resourcePath } = loaderContext;
loaderContext.addDependency(resourcePath);
loaderContext.fs.readFile(resourcePath, (err, data) => {
if (err) return callback(err, data);
callback(
err,
this.rewrites.reduce(
(prev, cur) =>
resourcePath == cur[0]
? processFile(data, cur[1], cur[2])
: data,
data));
});
});
});
}
};
module.exports = RewriteRequirePlugin;