Setting config.resolve.mainFields in the webpack method in next.config.js has no effect.
Despite setting it to [ 'module', 'main', ], the bundle referred to by the main field is still used
next.config.js file:module.exports = (phase, { defaultConfig, }) => {
return {
webpack: (config, { dev, isServer, }) => {
config.resolve.mainFields = [ 'module', 'main', ];
return config;
},
};
};
main and a module field in its package.json The build should use the bundle specified in the package's module field, but instead, the one in main is used.
This sounds unrelated to Next.js itself, not sure what to do with it.
I think it's actually somewhat similar to #2259. Packages, lodash-es and others use the module field to ship bundles that that they don't need to be processed with Babel because they are already transpiled, excluding import statements, which allow bundlers to tree-shake efficiently.
According to the relevant Webpack docs, Webpack's default is to resolve a package from the path specified in the browser, module and main fields of that package's package.json when the target is web and module and then main when for non web targets.
However, next seems to resolve the main field, even if a module is specified.
As mentioned, explicitly setting config.resolve.mainFieldsto[ 'module', 'main', ]` does not seem to have any effect.
Okay, I managed to solve it with:
// next.config.js
module.exports = {
webpack: (config, { dev, isServer, }) => {
config.module.rules = [
...config.module.rules,
{
test: /\.(mjs|js|jsx)$/,
include: /node_modules/,
resolve: { mainFields: [ 'module', 'main', ], },
},
];
return config;
}
}
I feel this should be the default though, as it ensures better tree shaking behavior and will likely result in smaller bundle sizes for users
Sorry, this doesn't work after all. Any hint would be welcome
@TxHawks try to console.log the final webpack configuration to see if the mainFields are sent the way you set them.
Look for this line:
compiler.options = new WebpackOptionsApply().process(options, compiler);
in node_modules/webpack/lib/webpack.js
and do a console.log(compiler.options) after that line.
Thanks for the suggestion @nickdima.
resolve.mainFields is set to ['browser', 'module', 'main', ], in client build, and ['module', 'main', ] on the server, which seems fine, but packages are still resolved according to the main field.
Pasted below is the entire output of console.log(compiler.options)):
{
mode: 'production',
devtool: false,
name: 'client',
cache: true,
target: 'web',
externals: [],
optimization: {
runtimeChunk: {
name: 'static/runtime/webpack.js'
},
splitChunks: {
cacheGroups: {
default: false,
vendors: false,
commons: {
name: 'commons',
chunks: 'all',
minChunks: 3
},
react: {
name: 'commons',
chunks: 'all',
test: {}
}
},
chunks: 'all',
hidePathInfo: true,
minSize: 30000,
minChunks: 1,
maxAsyncRequests: 5,
automaticNameDelimiter: '~',
maxInitialRequests: 3,
name: true
},
minimizer: [
{
options: {
test: {},
extractComments: false,
sourceMap: false,
cache: true,
parallel: true,
terserOptions: {
output: {
comments: {}
}
}
}
}
],
removeAvailableModules: true,
removeEmptyChunks: true,
mergeDuplicateChunks: true,
flagIncludedChunks: true,
occurrenceOrder: true,
sideEffects: true,
providedExports: true,
usedExports: true,
concatenateModules: true,
noEmitOnErrors: true,
checkWasmTypes: true,
mangleWasmImports: false,
namedModules: false,
hashedModuleIds: false,
namedChunks: false,
portableRecords: true,
minimize: true,
nodeEnv: 'production'
},
recordsPath: '~/project-root/packages/apps/package-name/.next/records.json',
context: '~/project-root/packages/apps/package-name',
output: {
path: '~/project-root/packages/apps/package-name/.next',
libraryTarget: 'jsonp',
hotUpdateChunkFilename: 'static/webpack/[id].[hash].hot-update.js',
hotUpdateMainFilename: 'static/webpack/[hash].hot-update.json',
chunkFilename: 'static/chunks/[name].[contenthash].js',
strictModuleExceptionHandling: true,
webassemblyModuleFilename: '[modulehash].module.wasm',
library: '',
hotUpdateFunction: 'webpackHotUpdate',
jsonpFunction: 'webpackJsonp',
chunkCallbackName: 'webpackChunk',
globalObject: 'window',
devtoolNamespace: '',
pathinfo: false,
sourceMapFilename: '[file].map[query]',
crossOriginLoading: false,
jsonpScriptType: false,
chunkLoadTimeout: 120000,
hashFunction: 'md4',
hashDigest: 'hex',
hashDigestLength: 20,
devtoolLineToLine: false
},
performance: {
hints: false,
maxAssetSize: 250000,
maxEntrypointSize: 250000
},
resolve: {
extensions: [ '.wasm', '.mjs', '.js', '.jsx', '.json' ],
modules: [
~/project-root/node_modules/next/node_modules,
node_modules
],
alias: {
next: '~/project-root/node_modules/next'
},
unsafeCache: true,
mainFiles: [ 'index' ],
aliasFields: [ 'browser' ],
mainFields: [ 'browser', 'module', 'main' ],
cacheWithContext: false
},
resolveLoader: {
modules: [
~/project-root/node_modules/next/node_modules,
node_modules,
~/project-root/node_modules/next/dist/build/webpack/loaders
],
unsafeCache: true,
mainFields: [ 'loader', 'main' ],
extensions: [ '.js', '.json' ],
mainFiles: [ 'index' ],
cacheWithContext: false
},
module: {
rules: [
{
test: {},
include: [ '~/project-root/packages/apps/package-name' ],
exclude: {},
use: {
loader: 'next-babel-loader',
options: {
dev: false,
isServer: false,
cwd: '~/project-root/packages/apps/package-name'
}
}
},
{
test: {},
include: {},
resolve: {
mainFields: [ 'module', 'main' ]
}
}
],
unknownContextRequest: '.',
unknownContextRegExp: false,
unknownContextRecursive: true,
unknownContextCritical: true,
exprContextRequest: '.',
exprContextRegExp: false,
exprContextRecursive: true,
exprContextCritical: true,
wrappedContextRegExp: {},
wrappedContextRecursive: true,
wrappedContextCritical: false,
strictExportPresence: false,
strictThisContextOnImports: false,
unsafeCache: true,
defaultRules: [
{
type: 'javascript/auto',
resolve: {}
},
{
test: {},
type: 'javascript/esm',
resolve: {
mainFields: [ 'browser', 'main' ]
}
},
{
test: {},
type: 'json'
},
{
test: {},
type: 'webassembly/experimental'
}
]
},
plugins: [
{},
{ filename: 'react-loadable-manifest.json' },
{
options: {
name: 'client',
color: 'green',
profile: false,
compiledIn: true,
done: null,
minimal: true,
stream: null
}
},
{
options: {
resourceRegExp: {},
contextRegExp: {}
}
},
{
options: {}
},
{ definitions: { process.browser: 'true' } },
{},
{}
],
node: {
console: false,
process: true,
global: true,
Buffer: true,
setImmediate: true,
__filename: 'mock',
__dirname: 'mock'
}
}
{
mode: 'production',
devtool: false,
name: 'server',
cache: true,
target: 'node',
externals: [null],
optimization: {
splitChunks: false,
minimize: false,
removeAvailableModules: true,
removeEmptyChunks: true,
mergeDuplicateChunks: true,
flagIncludedChunks: true,
occurrenceOrder: true,
sideEffects: true,
providedExports: true,
usedExports: true,
concatenateModules: true,
noEmitOnErrors: true,
checkWasmTypes: true,
mangleWasmImports: false,
namedModules: false,
hashedModuleIds: false,
namedChunks: false,
portableRecords: true,
minimizer: [{}],
nodeEnv: 'production'
},
recordsPath: '~/project-root/packages/apps/package-name/.next/server/records.json',
context: '~/project-root/packages/apps/package-name',
output: {
path: '~/project-root/packages/apps/package-name/.next/server',
libraryTarget: 'commonjs2',
hotUpdateChunkFilename: 'static/webpack/[id].[hash].hot-update.js',
hotUpdateMainFilename: 'static/webpack/[hash].hot-update.json',
chunkFilename: '[name].[contenthash].js',
strictModuleExceptionHandling: true,
webassemblyModuleFilename: '[modulehash].module.wasm',
library: '',
hotUpdateFunction: 'webpackHotUpdate',
jsonpFunction: 'webpackJsonp',
chunkCallbackName: 'webpackChunk',
globalObject: 'global',
devtoolNamespace: '',
pathinfo: false,
sourceMapFilename: '[file].map[query]',
crossOriginLoading: false,
jsonpScriptType: false,
chunkLoadTimeout: 120000,
hashFunction: 'md4',
hashDigest: 'hex',
hashDigestLength: 20,
devtoolLineToLine: false
},
performance: {
hints: false,
maxAssetSize: 250000,
maxEntrypointSize: 250000
},
resolve: {
extensions: ['.wasm', '.mjs', '.js', '.jsx', '.json'],
modules: [
'~/project-root/node_modules/next/node_modules',
'node_modules'
],
alias: {
next: '~/project-root/node_modules/next'
},
unsafeCache: true,
mainFiles: ['index'],
aliasFields: [],
mainFields: ['module', 'main'],
cacheWithContext: false
},
resolveLoader: {
modules: [
'~/project-root/node_modules/next/node_modules',
'node_modules',
'~/project-root/node_modules/next/dist/build/webpack/loaders'
],
unsafeCache: true,
mainFields: ['loader', 'main'],
extensions: ['.js', '.json'],
mainFiles: ['index'],
cacheWithContext: false
},
module: {
rules: [
{
test: {},
include: ['~/project-root/packages/apps/package-name'],
exclude: {},
use: {
loader: 'next-babel-loader',
options: {
dev: false,
isServer: true,
cwd: '~/project-root/packages/apps/package-name'
}
}
},
{
test: {},
include: {},
resolve: {
mainFields: ['module', 'main']
}
}
],
unknownContextRequest: '.',
unknownContextRegExp: false,
unknownContextRecursive: true,
unknownContextCritical: true,
exprContextRequest: '.',
exprContextRegExp: false,
exprContextRecursive: true,
exprContextCritical: true,
wrappedContextRegExp: {},
wrappedContextRecursive: true,
wrappedContextCritical: false,
strictExportPresence: false,
strictThisContextOnImports: false,
unsafeCache: true,
defaultRules: [
{
type: 'javascript/auto',
resolve: {}
},
{
test: {},
type: 'javascript/esm',
resolve: {
mainFields: ['main']
}
},
{
test: {},
type: 'json'
},
{
test: {},
type: 'webassembly/experimental'
}
]
},
plugins: [
{},
{
options: {
name: 'server',
color: 'green',
profile: false,
compiledIn: true,
done: null,
minimal: true,
stream: null
}
},
{
options: {
resourceRegExp: {},
contextRegExp: {}
}
},
{
options: {}
},
{ definitions: { process.browser: 'false' } },
{},
{},
{
options: {
outputPath: '~/project-root/packages/apps/package-name/.next/server'
}
}
],
node: {
console: false,
process: true,
global: true,
Buffer: true,
setImmediate: true,
__filename: 'mock',
__dirname: 'mock'
}
}
I have the same problem and couldn't find a solution yet. But seeing that the mainFields are setup correctly I guess next.js is not the problem here but webpack or babel.
I'll keep investigating.
@nickdima -
Okay, please update if you find anything. I'll make sure to do the same
@timneutkens Following up on this (the problem is still present).
I believe the reason behind this issue is that next.js server build externalizes all .js files in node_modules:
With commonjs target that means that the packages are resolved in runtime by the standard node.js require() - which only reads the main field of the package.json.
What was the reason for not compiling everything with webpack? This is an unexpected and hard to debug - server and client side actually use different code for most of the packages.
I think there should be at least an option to blacklist some packages from this behaviour - unless you see some blocker for that I can give it a shot and submit a PR.
Technically you can override it in next config (cc @nickdima @TxHawks - you may find it helpful):
webpack(config) {
config.externals = [];
return config;
}
This matches the behaviour for client-side / serverless in current implementation, but can break/deoptimize some stuff if something else is added to the array in the future.
Most helpful comment
Technically you can override it in next config (cc @nickdima @TxHawks - you may find it helpful):
This matches the behaviour for client-side / serverless in current implementation, but can break/deoptimize some stuff if something else is added to the array in the future.