(updated ) by

Error: Not Supported

Node 12.x

If you are working with Node.js 12.x which adds full ESModule support, you may find you run into this error. This occurs using async dynamic ESModule loading (which is recommended to load ESModules .mjs into CommonJS .cjs code).

x.cjs

import('./myESModule.mjs').then(({ addRow }) => {
	addRow('rockerBOO')
})

Node 13.2

What I would suggest is upgrading to Node.js 13.2, which has much better error messages. (sort-of)

TypeError: Invalid host defined options
    at module.exports (./webpack.config.cjs:3:33)
TypeError [ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING]: A dynamic import callback was not specified.
    at exports.importModuleDynamicallyCallback (internal/process/esm_loader.js:35:9)

How to fix?

Not sure yet. I think some of the tooling surrounding node.js is a little outdated and making it very complicated to integrate ESModules into their configuration process. For instance, right now, Webpack only supports CommonJS modules as it loads all config files (or all js files) through require.

webpack.config.cjs

module.exports = new Promise($export => {
	import('./webpack.config.mjs').then(config => {
		$export(config)
	})
})

This should allow a promise to return the config from a ESModule adjancent but I get one of these errors.

(node:2964) UnhandledPromiseRejectionWarning: TypeError: Invalid host defined options
    at C:\...\webpack.config.cjs:4:2
    at new Promise (<anonymous>)
    at Object.<anonymous> (C:\...\webpack.config.cjs:3:18)
$ node -v
v13.2.0

An hour later

So later I learned from this webpack-cli commit that -r @std/esm is an option to preload the @std/esm module before loading the webpack config. @std/esm is superceded by esm, -r esm.

Using this I was able to try some more options and eventually got ESM to work load my ESModule config through a small CommonJS Loader.

module.exports = async (env, argv) => {
	const config = await import('./webpack.config.mjs');
	return config.default(env, argv)
}

One thing to note is if you import from a ESModule into a CommonJS file, you will need to use the named parameters.

const { default } = await import('./webpack.config.mjs')

But of course that doesn’t work. default is a special keyword.

const config = await import('./webpack.config.mjs')
return config.default(env, argv)

So then I figure, what happens if I remove the -r esm. And, well, it just worked.

webpack.config.mjs

const config = (env, argv) => {
	return {
		entry: ...,
		...
	}
}

export default config

Future

There is currently talks (as of 5 days ago) to allow .mjs support natively in webpack-cli

node 13.2 has been released with .mjs support without flag: https://github.com/nodejs/node/releases/tag/v13.2.0

can we think about use webpack.config.mjs and webpack.config.cjs as config file?

Definitely, …

More