Issue
When building the application with Webpack into a single bundle, the library migrate-mongo is unable to find the migration schema module eg migrations/[some-number]-[some-name]-migration.js. This is probably due to the dynamic import of the migration module by the library itself and because webpack replaces how require loads modules.
How can I configure Webpack that it registers the migration js files in it's build, so when the library calls the require it will be able to retrieve the modules.
Library tries requires the module in the following absolute path strategy:
// Roughly
path.join(pwd(), migrationsDir, fileName)
Error received
Could not migrate up [file-name]-migration.js. Cannot find module [absolute-path]/migrations/[file-name]-migration.js
Even though the file exists, tried multiple relative locations just to be sure.
State
I'm using Webpack for building Nest.js application and migrate-mongo for migrations in an NX project. Migration files are located in migrations/ directory and migration up is triggered by application on startup. Migration works normally when app is started in development mode.
migrate.ts
// This function is executed on app bootstrap
export const migrate = async (): Promise<void> => {
Logger.log('[Migration]: Started');
config.set(createConfig());
const {db, client} = await database.connect();
const migrated = await up(db, client);
migrated.forEach((fileName) => {
Logger.log(`[Migration]: migrated ${fileName}`);
});
Logger.log('[Migration]: End');
};
webpack.config.js
module.exports = {
mode: 'development',
target: 'node',
entry: './src/main.ts',
module: {
rules: [
{
test: /\.ts?$/,
use: 'ts-loader',
exclude: [/node_modules/, /other-app/],
}
],
},
resolve: {
extensions: ['.ts', '.js'],
plugins: [
new TsconfigPathsPlugin({
configFile: path.resolve(__dirname, '../../tsconfig.json'),
}),
],
},
output: {
path: path.resolve(__dirname, '../../dist/apps/my-backend'),
filename: 'src/main.js',
},
externals: [
nodeExternals(),
],
plugins: [/* ... */],
}
Tried solutions
Tried using CopyPlugin to copy the migrations, but as found out all modules are placed within the built bundle.
Tried updating externals to include the directory with migrations
Tried requiring or importing the file and directory in app to register the modules
Related
Frankly, I've tried it all. I'm not a total whiz with Webpack, however I seem to be getting along pretty well over the years with configuring new projects.
What I cannot seem to do now is set up the NewRelic service into an existing Node/Typescript/Express/Webpack application.
As it stands, my app gets nicely bundled to a single file in my /dist folder and runs quick and nimble. Seems like this 'node agent' put out by New Relic doesn't play well with Typescript imports.
Webpack Config
const path = require('path');
const webpack = require('webpack');
const nodeExternals = require('webpack-node-externals');
const NodemonPlugin = require ('nodemon-webpack-plugin');
module.exports = (env = {}) => {
const config = {
entry: ['./src/app.ts'],
mode: env.development ? 'development' : 'production',
target: 'node',
devtool: env.development ? 'inline-source-map' : false,
resolve: {
extensions: ['.ts', '.js'],
modules: ['node_modules', 'src', 'package.json'],
},
module: {
rules: [
{
test: /\.ts$/,
use: ['ts-loader', 'eslint-loader'],
// exclude: /node_modules/,
},
],
},
plugins: [],
externals: [ 'newrelic', nodeExternals() ]
};
if (env.nodemon) {
config.watch = true;
config.plugins.push(new NodemonPlugin())
}
return config;
};
there exists a standard /project_root/.newrelic file
CircleCi picks up this project up and runs "build:ci" script from package.json ==> "webpack"
output is /dist/main.js
references
https://docs.newrelic.com/docs/agents/nodejs-agent/installation-configuration/install-nodejs-agent
https://docs.newrelic.com/docs/agents/nodejs-agent/installation-configuration/nodejs-agent-configuration
https://discuss.newrelic.com/t/node-agent-fails-with-webpack/24874
Your first line of the starting point of the app should be
import newrelic from 'newrelic';
Of course, run npm install newrelic --save first
Then, create a newrelic.js file on the root of the repo (outside of src).
Then you put in the details like:
'use strict'
exports.config = {
app_name: ['appName'],
license_key: '1234567890',
allow_all_headers: true,
attributes: {
exclude: [
'request.headers.cookie',
'request.headers.authorization',
'request.headers.proxyAuthorization',
'request.headers.setCookie*',
'request.headers.x*',
'response.headers.cookie',
'response.headers.authorization',
'response.headers.proxyAuthorization',
'response.headers.setCookie*',
'response.headers.x*'
]
}
}
I am using webpack to bundle a number of back-end scripts into a single file during my deployment process.
When connecting to the MongoDB database there is an optional dependency, which throws a warning if it is not included.
Warning: no saslprep library specified. Passwords will not be sanitized
In my development environment this error is easily resolved by installing the optional dependency.
npm install saslprep --save
However when bundling with webpack the optional dependency is not included and the warning persists in the production deployment. I can trace the cause of this easily enough, the mongodb library is requiring this as an optional dependency:
let saslprep;
try {
saslprep = require('saslprep');
} catch (e) {
// don't do anything;
}
I have tried following the webpack documentation using shimming, externals, plugins and frankly am quite lost as to the correct approach to resolve this issue. Here is my current webpack.config file (attempting to require this as a plugin).
const path = require('path');
const webpack = require('webpack');
module.exports = {
entry: './src/api/index.ts',
target: 'node',
mode: 'production',
module: {
rules: [
{
test: /\.tsx?$/,
loader: 'ts-loader',
exclude: /node_modules/
}
]
},
resolve: {
extensions: ['.js', '.tsx', '.ts', '.json']
},
output: {
filename: 'api.js',
path: path.resolve(__dirname, 'dist'),
},
plugins: [
new webpack.IgnorePlugin(/fsevents/),
new webpack.IgnorePlugin(/blessed/),
new webpack.ProvidePlugin({
saslprep: path.resolve(__dirname, "node_modules/saslprep/index.js")
})
],
};
Thanks in advance.
Thanks to Brendan for steering me in the right direction. Ultimately the answer was found here: http://www.matthiassommer.it/software-architecture/webpack-node-modules/
The key piece of information being:
Webpack’s compiler converts the require() call to its own webpack_require(). At runtime it looks into its internal module registry.
Following the steps outlined therein the resolution becomes:
const path = require('path');
const webpack = require('webpack');
module.exports = {
entry: './src/api/index.ts',
target: 'node',
mode: 'production',
module: {
rules: [
{
test: /\.tsx?$/,
loader: 'ts-loader',
exclude: /node_modules/
}
]
},
resolve: {
extensions: ['.js', '.tsx', '.ts', '.json'],
},
output: {
filename: 'api.js',
path: path.resolve(__dirname, 'dist'),
},
plugins: [
new webpack.IgnorePlugin(/fsevents/),
new webpack.IgnorePlugin(/blessed/),
],
externals: {
"saslprep": "require('saslprep')"
}
};
Please note that in my testing the quotes around "saslprep" do appear to berequired when importing externals.
The module saslprep is included into the bundle but it will fail to load because it's trying to read the file code-points.mem that doesn't exist.
You have to copy this file from the saslprep package into the root of your project (the relative path must be ../code-points.mem).
It doesn't matter if you copy it by hand or with the copy-webpack-plugin plugin.
module.exports = {
plugins: [
new CopyPlugin({
patterns: [
{
from: require.resolve("saslprep/code-points.mem"),
to: "../code-points.mem"
}
]
})
],
}
webpack should be 'seeing' that require('saslprep') code and bundling saslprep. You can confirm this by looking at the file(s) created by webpack and searching for something distinctive from the library's source code. I would look at the MongoDB code that is checking for that dependency -- it may be that the way they're checking that it exists doesn't work with the way that webpack bundles and defines the dependency. (Most authors of node libraries are probably not thinking about how the code will operate when bundled -- webpack can be used for Node, but it rarely is.) If that's the case, you might be able to reverse-engineer what the code is checking for and put it in place via an alias, etc.
I you use the webpack-node-externals package, you can also do this:
const nodeExternals = require("webpack-node-externals")
...
const webpackConfig = {
...
externals: [nodeExternals({
allowlist: ['saslprep']
})],
...
}
I'm using yarn workspaces where the root directory has a package directory with all my repos. Each repo has its own node_modules directory containing its dependencies. The root node_modules directory contains all the dev dependencies for the whole project as well as all other dev related things such as webpack.config files. Webpack uses hot module reload for the express server package.
The problem I have is, how to configure webpack externals to exclude all node_modules directories through the whole project, not just in the root?
webpack-node-externals doesn't seem to work given this scenario.
Error message:
WARNING in ./packages/servers/express/node_modules/colors/lib/colors.js
127:29-43 Critical dependency: the request of a dependency is an expression
WARNING in ./packages/servers/express/node_modules/express/lib/view.js
79:29-41 Critical dependency: the request of a dependency is an expression
Webpack config:
const webpack = require('webpack');
const path = require('path');
const nodeExternals = require('webpack-node-externals');
const StartServerPlugin = require('start-server-webpack-plugin');
module.exports = {
entry: [
'babel-polyfill',
'webpack/hot/poll?1000',
path.join(__dirname, '../packages/servers/express/server/index.js')
],
watch: true,
target: 'node',
externals: [
nodeExternals({
whitelist: ['webpack/hot/poll?1000']
})
],
resolve: {
alias: {
handlebars: 'handlebars/dist/handlebars.js'
}
},
module: {
rules: [
{
test: /\.js?$/,
use: 'babel-loader',
exclude: /node_modules/
}
]
},
plugins: [
new StartServerPlugin('server.js'),
new webpack.NamedModulesPlugin(),
new webpack.HotModuleReplacementPlugin(),
new webpack.NoEmitOnErrorsPlugin(),
new webpack.DefinePlugin({
'process.env': { BUILD_TARGET: JSON.stringify('server') }
})
],
output: {
path: path.join(__dirname, '../packages/servers/express/.build'),
filename: 'server.js'
}
};
If using yarn workspaces with webpack-node-externals a better solution than setting modulesFromFile: true is to use the following externals setting in your webpack config:
externals: [
nodeExternals(),
nodeExternals({
modulesDir: path.resolve(__dirname, 'path/to/root/node_modules'),
}),
],
Essentially using two instances of nodeExternals. 1 for the package node_modules and one for the root node_modules.
Thanks to #blackxored I was able to fix it on my project.
In your webpack config file do the following:
import nodeExternals from 'webpack-node-externals'
Then add
externals: [
nodeExternals({
modulesFromFile: true,
}),
],
Yarn workspaces hoist compatible modules to the root node_modules directory leaving any incompatible (different semver, etc.) modules with the dependent workspace's node_modules directory. If a package is requested without using a relative path it is either native, from node_module's, or possibly a symlinked package from one of your workspaces. You probably want all of those packages to be external.
how to configure webpack externals to exclude all node_modules directories through the whole project, not just in the root?
I would try using a function with webpack's external option. You are passed the context of the require, the name of the module requested, and a callback to indicate whether this particular import (require) should be considered external.
externals: [
(ctx, req, cb) => {
if (!/node_modules/.test(ctx) && req[0] !== '.') {
// Assumes you have defined an "entries" variable
let notAnEntry = (path) => {
return Object.keys(entries).every((entry) => {
return entries[entry] !== path
});
};
if (notAnEntry(require.resolve(req))) {
// This module is external in a commonjs context
return cb(null, `commonjs ${req}`);
}
}
cb();
}
]
Hi im using socket io in my application. Which is requiring fs. when i try to bundle my javascript using below webpack config. im getting error can not resolve 'fs'.
Module not found: Error: Can't resolve 'fs' in 'my application path/node_modules/socket.io/lib'
i found by adding target:'node' and node:{fs:'empty'}. this issue got resolved.
But there is an issue in sass-loader. Getting below error.
ERROR in javascript/bundle.js from UglifyJs
Unexpected token: name (zlibLimiter) [javascript/bundle.js:60019,4]
Child extract-text-webpack-plugin ../../../node_modules/extract-text-webpack-plugin/dist ../../../node_modules/css-loader/index.js??ref--2-2!../../../node_modules/sass-loader/lib/loader.js!s
running the application ignoring above error. getting below error.
external "crypto":1 Uncaught ReferenceError: require is not defined
at Object.__decorate (external "crypto":1)
at __webpack_require__ (bootstrap 93620a17882f7a2aa1d3:19)
at Object.byteToHex (rng.js:4)
at __webpack_require__ (bootstrap 93620a17882f7a2aa1d3:19)
Below is my webpack config and versions. Can some one please help me to resolve this issue.
"webpack": "~3.6.0",
npm -v 5.8.0
node -v v8.4.0
const webpack = require('webpack');
const env = process.env.NODE_ENV;
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const path = require('path');
const extractSass = new ExtractTextPlugin({
filename: 'css/[name].css',
allChunks: false
});
let output = {
path: __dirname + '/src/main/resources/static/',
filename: 'javascript/[name].js'
};
if (env === 'debug' || env === 'nondev') {
output = {
path: __dirname + '/target/classes/static/',
filename: 'javascript/[name].js'
};
}
let config = {
context: __dirname + '/app/js/src',
entry: {
bundle: './index.jsx',
application: './static/scss/application.scss',
'application-pdap': './static/scss/application-pdap.scss'
},
output: output,
devtool: 'cheap-module-source-map',
module: {
rules: [
{
test: /.jsx?$/,
loader: 'babel-loader',
exclude: /node_modules/,
query: {presets: ['es2015', 'react']}
},
{
test: /\.(woff|woff2|eot|ttf|svg|png|jpg|gif)$/,
loader: 'file-loader?limit=1024&name=images/[name].[ext]'
},
{
test: /\.(scss|css)$/,
include: [path.resolve(__dirname, 'app/js/src/static/scss')],
use: ExtractTextPlugin.extract({
publicPath: '../',
use: [
{
loader: 'css-loader',
options: {
minimize: true,
sourceMap: false
}
},
{loader: 'sass-loader'}
],
fallback: 'style-loader'
})
}
]
},
plugins: [extractSass],
};
if (env === 'production' || env === 'nondev') {
config.devtool = 'nosources-source-map';
config.plugins.push(
new webpack.DefinePlugin({
'process.env': {NODE_ENV: '"production"'}
})
);
config.plugins.push(new webpack.optimize.UglifyJsPlugin({
compress: {warnings: false},
comments: false,
sourceMap: false,
minimize: false
}));
}
module.exports = config;
The solution depends on the type of application you want to build. Usually front-end and back-end JavaScript code are bundled separately, effectively creating two output bundles.
Front-end
For a frontend/web project, add the socket.io client libary to your app bundle. There is no need to include any node dependencies (fs) or mock entries like node: { fs:'empty' }. You can choose target:'web' or leave it out, as it is the default.
Back-end
Pick target:'node' and install socket.io server library. You do not need to specify externals: ["fs"] as shown in the other answer, because target: 'node' will take care of not bundling path, fs and other built-in modules.
Better avoid npm i fs - this is a needless escape hatch and a security risk. There have already happened cases of malicious npm packages with common package names.
You could even deliberate about, wether a node backend bundle is needed at all. An alternative is to install webpack-node-externals, which treats either all or specific npm packages as "externals" and excludes them from the bundle:
var nodeExternals = require('webpack-node-externals');
module.exports = {
target: 'node', // ignore built-in modules like path, fs, etc.
externals: [nodeExternals()], // ignore all modules in node_modules folder
// ...
};
This makes sense for the back-end, as all dependencies are installed in node_modules at server start time and don't need to be included in the bundle.
Exclude files from loaders
To exclude files from certain loaders and their transformations, you can use exclude module rule. An example is to omit node_modules from babel-loader transformations:
{ test: /\.(jsx|js)$/, exclude: /node_modules/, loader: "babel-loader" }
Further reading
Just install fs (npm i fs), and add at your webpack config
externals: ["fs"],
If someone is still facing this issue, you can try the following workaround. Update your webpack file to including the following configuration:
node: {
fs: 'empty',
}
We had a similar issue within our project and adding this specific configuration resolved the 'fs' related error in webpack.
It's also worth checking the version of Webpack version. we had to revert back the webpack and webpack cli versions to 4.0.0 and 4.2.0 respectively.
I'm using yarn workspaces where the root directory has a package directory with all my repos. Each repo has its own node_modules directory containing its dependencies. The root node_modules directory contains all the dev dependencies for the whole project as well as all other dev related things such as webpack.config files. Webpack uses hot module reload for the express server package.
The problem I have is, how to configure webpack externals to exclude all node_modules directories through the whole project, not just in the root?
webpack-node-externals doesn't seem to work given this scenario.
Error message:
WARNING in ./packages/servers/express/node_modules/colors/lib/colors.js
127:29-43 Critical dependency: the request of a dependency is an expression
WARNING in ./packages/servers/express/node_modules/express/lib/view.js
79:29-41 Critical dependency: the request of a dependency is an expression
Webpack config:
const webpack = require('webpack');
const path = require('path');
const nodeExternals = require('webpack-node-externals');
const StartServerPlugin = require('start-server-webpack-plugin');
module.exports = {
entry: [
'babel-polyfill',
'webpack/hot/poll?1000',
path.join(__dirname, '../packages/servers/express/server/index.js')
],
watch: true,
target: 'node',
externals: [
nodeExternals({
whitelist: ['webpack/hot/poll?1000']
})
],
resolve: {
alias: {
handlebars: 'handlebars/dist/handlebars.js'
}
},
module: {
rules: [
{
test: /\.js?$/,
use: 'babel-loader',
exclude: /node_modules/
}
]
},
plugins: [
new StartServerPlugin('server.js'),
new webpack.NamedModulesPlugin(),
new webpack.HotModuleReplacementPlugin(),
new webpack.NoEmitOnErrorsPlugin(),
new webpack.DefinePlugin({
'process.env': { BUILD_TARGET: JSON.stringify('server') }
})
],
output: {
path: path.join(__dirname, '../packages/servers/express/.build'),
filename: 'server.js'
}
};
If using yarn workspaces with webpack-node-externals a better solution than setting modulesFromFile: true is to use the following externals setting in your webpack config:
externals: [
nodeExternals(),
nodeExternals({
modulesDir: path.resolve(__dirname, 'path/to/root/node_modules'),
}),
],
Essentially using two instances of nodeExternals. 1 for the package node_modules and one for the root node_modules.
Thanks to #blackxored I was able to fix it on my project.
In your webpack config file do the following:
import nodeExternals from 'webpack-node-externals'
Then add
externals: [
nodeExternals({
modulesFromFile: true,
}),
],
Yarn workspaces hoist compatible modules to the root node_modules directory leaving any incompatible (different semver, etc.) modules with the dependent workspace's node_modules directory. If a package is requested without using a relative path it is either native, from node_module's, or possibly a symlinked package from one of your workspaces. You probably want all of those packages to be external.
how to configure webpack externals to exclude all node_modules directories through the whole project, not just in the root?
I would try using a function with webpack's external option. You are passed the context of the require, the name of the module requested, and a callback to indicate whether this particular import (require) should be considered external.
externals: [
(ctx, req, cb) => {
if (!/node_modules/.test(ctx) && req[0] !== '.') {
// Assumes you have defined an "entries" variable
let notAnEntry = (path) => {
return Object.keys(entries).every((entry) => {
return entries[entry] !== path
});
};
if (notAnEntry(require.resolve(req))) {
// This module is external in a commonjs context
return cb(null, `commonjs ${req}`);
}
}
cb();
}
]