webpacksystemjsdependency-resolver

Custom Webpack resolver for SystemJS style imports ! with-loader


I have a project that is using SystemJS - this cannot change. We are also using StoryBooks, which uses WebPack - this too cannot change. So far the two play very nicely with each other. However, I have a module which loads files as plain text and parses them:

import stuff from './some-module!systemjs-plugin-text';
parseStuff(stuff);

If my project were using WebPack, the above code would look like this:

import stuff from 'raw-loader!./some-module';
parseStuff(stuff);

Since SystemJS is the "primary" module loader, I want all application code to stay SystemJS-centric. I would like to write a custom resolver for webpack to take the SystemJS-style imports and "flip" them (so to speak). The code would look like this:

const moduleMap = {
  'systemjs-plugin-text': 'raw-loader'
};
const formatModuleIdForWebPack (systemjsId) {
  const webpackId = systemjsId.split('!').reverse();
  webpackId[0] = moduleMap[ webpackId[0] ] || webpackId[0];
  return webpackId.join('!');
}
formatModuleIdForWebPack('./some-module!systemjs-plugin-text');
//-> 'raw-loader!./some-module'

I have followed several examples for creating a custom resolver (here and here), but none of the payloads contain all of the data I need to reconstruct the request object. Is there a way can I hook into WebPack very early in the module parsing to achieve my goal?


Solution

  • I was able to use a custom loader to achieve my goal. This is not ideal, but the parsing logic is fairly simple and can be made stronger with time. Using this strategy allowed me to apply this only to project files (anything not in node_modules).

      {
        test: /\.js$/,
        loader: path.resolve('systemjs-loader.js'),
        exclude: [
          path.resolve('node_modules')
        ],
        options: {
          loaderMap: {
            'systemjs-plugin-text': 'raw-loader'
          }
        }
      }
    

    ... and the loader code:

    const { getOptions } = require('loader-utils');
    const escapeRegexp = require('escape-string-regexp');
    /**
     * Replaces systemjs-style loader syntax with webpack-style:
     * 
     *   ./some-module!systemjs-plugin  ->  webpack-plugin!./some-module
     */
    module.exports = function (source) {
      const options = getOptions(this);
      const map = options.loaderMap;
      if (!map) {
        throw new Error('There is no need to use the systemjs loader without providing a `loaderMap`');
      }
      Object.keys(map).forEach(loader => {
        // indexOf should be faster than regex.test
        if (source.indexOf(loader) !== -1) {
          const exp = new RegExp(`(['"])([^!]+)!${escapeRegexp(loader)}['"]`, 'g');
          source = source.replace(exp, `$1${map[loader]}!$2$1`);
        }
      });
      return source;
    };