Webpack 5: how to use a custom loader that is written as ES Module
Asked Answered
M

2

6

How do I tell Webpack that it should treat my loader as an ES Module and not CommonJS?

My goal is to write a Loader that uses the ES Module syntax (export default...)

Demo (not working): https://stackblitz.com/edit/webpack-webpack-js-org-7gmdc8?file=webpack.config.js

Apparently Webpack is trying to load the module as CommonJS. If you look at loadLoader, the condition loader.type === "module" fails, since loader.type is undefined.


// webpack.conf.js:
const path = require('path');

module.exports = {
  mode: 'development',
  entry: './src/app.js',
  output: {
    filename: 'app.js',
    path: path.resolve(__dirname, 'dist'),
    clean: true,
  },

  module: {
    rules: [
      {
        test: /\.js$/,
        use: ['my-loader'],
      },
    ],
  },

  resolveLoader: {
    alias: {
      // WEBPACK IS TRYING TO LOAD THIS AS COMMONJS... WHY???
      'my-loader':
        '/home/projects/webpack-webpack-js-org-7gmdc8/my-loader/main.mjs',
    },
  },
};

Sample stacktrace:

ERROR in ./index.js
Module build failed (from ../my-webpack-loader-esm/dist/index.bundle.mjs):
Error [ERR_REQUIRE_ESM]: require() of ES Module /workspaces/webpack-loader-lab/my-webpack-loader-esm/dist/index.bundle.mjs not supported.
Instead change the require of /workspaces/webpack-loader-lab/my-webpack-loader-esm/dist/index.bundle.mjs to a dynamic import() which is available in all CommonJS modules.
    at loadLoader (/workspaces/webpack-loader-lab/the-client/node_modules/loader-runner/lib/loadLoader.js:23:17)
Magistrate answered 24/8, 2022 at 13:24 Comment(2)
Looks like it's still not supported in webpack per github.com/webpack/loader-runner/commit/… I tried "type": "module" and <file>.mjs to no avail. There's also an issue for it: github.com/webpack/loader-runner/issues/61Didi
I faced the same problem with combination of extensions and modules configuration in resolveLoaderCorr
C
2

UPDATE:

The support was added somewhere in between webpack version 5.76 and 5.81, as of June 2023 version 5.88.0 Support ESM modules as loaders

OLD ANSWER:

Its not currently possible to get it out of the box due to non merged PR, but I managed to develop a workaround plugin for it.

First I looked at at where loadLoader was getting data from. It turned from LoaderRunner.js, webpack is using intermediate object and navigating up the callstack I found following code:

try {
    hooks.beforeLoaders.call(this.loaders, this, loaderContext);
} catch (err) {
    processResult(err);
    return;
}

This told me there was intermediate object can be altered by hook and by searching in webpack code for beforeLoaders I was able to find sample plugin that I adjusted for this purpose, here is the result:

import NormalModule from 'webpack/lib/NormalModule.js'

const pluginName = 'MjsLoaderPlugin'
export default class MjsLoaderPlugin {
    apply(compiler) {
        compiler.hooks.compilation.tap(pluginName, compilation => {
            NormalModule.getCompilationHooks(compilation).beforeLoaders.tap(
                pluginName,
                (loaders, normalModule) => {
                    /*
                    loader = {
                        loader: 'F:\\<blah blah>\\webpack-loaders\\my-module-loader.mjs',
                        options: 'name=value',
                        ident: undefined
                    }
                    */

                    for(let loader of loaders) {
                        if(loader.loader.endsWith(".mjs")) {
                            loader.type = 'module'
                        }
                    }
                }
            )
        })
    }
}

Also not sure if relevant, here is my configuration part for folder for loaders that tells webpack it can search for mjs loaders in this folder automatically


import { dirname, resolve } from 'path'
import { fileURLToPath } from 'url'
const __dirname = dirname(fileURLToPath(import.meta.url))

export default {
    resolveLoader: {
        modules: [
            'node_modules',
            resolve(__dirname, 'tools/webpack-loaders')
        ],
        extensions: ['.js', '.mjs']
    }
}
Corr answered 17/3, 2023 at 19:16 Comment(0)
P
1

ESM loader support for webpack was added in v5.80.0 by pull request 15198.

Here are some of the related issues:

Here is an example loader written in ESM and supporting dual CJS and ESM builds via conditional exports:

So to write a custom ESM loader you do the standard node things to indicate your module should be loaded as ESM, i.e., specify "type": "module" in your package.json or use an .mjs extension. You also need to have your runner loaded by webpack >= v5.80.0.

Proximate answered 26/6, 2023 at 13:7 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.