Search code examples
javascriptrequirejswebpackamdcommonjs

Webpack: How can I create a loader for "webpack" which takes an array of dependencies?


For example, I use AMD definition in my project, and use "webpack" for project building. It's possible to create some loader which will take a dependencies in array format?

define(
    [
        'mySuperLoader![./path/dependency-1, ./path/dependency-2, ...]'
    ],
    function() {
        // ... some logic here
    }
)

Project example: gitHub


Solution

  • If you want to port the load-plugin's behavior to webpack, you need to do this:

    1. Create a custom resolver

    This is because mySuperLoader![./path/dependency-1, ./path/dependency-2, ...] does not point to a single file. When webpack tries to load a file, it first:

    • resolves the file path
    • loads the file content
    • matches and resolves all loaders
    • passes the file content to the loader chain

    Since [./path/dependency-1, ./path/dependency-2, ...] is not a proper file path, there is some work to do. It is even not a proper JSON.

    So, our first goal is to turn this into mySuperLoader!some/random/file?["./path/dependency-1", "./path/dependency-2", ...]. This is usually done by creating a custom resolver:

    // webpack.config.js
    var customResolverPlugin = {
        apply: function (resolver) {
            resolver.plugin("resolve", function (context, request) {
                const matchLoadRequest = /^\[(.+)]$/.exec(request.path);
    
                if (matchLoadRequest) {
                    request.query = '?' + JSON.stringify(
                        matchLoadRequest[1]
                        .split(", ")
                    );
                    request.path = __filename;
                }
            });
        }
    };
    
    module.exports = {
        ...
        plugins: [
            {
                apply: function (compiler) {
                    compiler.resolvers.normal.apply(customResolverPlugin);
                }
            }
        ]
    };
    

    Notice request.path = __filename;? We just need to give webpack an existing file so that it does not throw an error. We will generate all the content anyway. Probably not the most elegant solution, but it works.

    2. Create our own load-loader (yeah!)

    // loadLoader.js
    const path = require("path");
    
    function loadLoader() {
        return JSON.parse(this.request.match(/\?(.+?)$/)[1])
            .map(module =>
                `exports['${path.basename(module, '.js')}'] = require('${module}');`
            )
            .join('\n');
    }
    
    module.exports = loadLoader;
    

    This loader parses the request's query we have re-written with our custom resolver and creates a CommonJS module that looks like this

    exports['dependency-1'] = require('path/to/dependency-1');
    exports['dependency-2'] = require('path/to/dependency-2');
    

    3. Alias our own load-loader

    // webpack.config.js
    
        ...
        resolveLoader: {
            alias: {
                load: require.resolve('./loadLoader.js')
            }
        },
    

    4. Configure root

    Since /path/to/dependency-1 is root-relative, we need to add the root to the webpack config

    // webpack.config.js
    resolve: {
        root: '/absolute/path/to/root' // usually just __dirname
    },
    

    This is neither a beautiful nor an ideal solution, but should work as a makeshift until you've ported your modules.