Search code examples
webpackwebpack-4webpack-loader

Register file to bundle inside loader


Is it possible to register file to bundle inside loader?

Let's say I have a own loader for .cmp.html files and I require files like this:

require("component/index.cmp.html");

then I'd like my loader to require also "component/index.cmp.js" to bundle, so it will be parsed by all loaders that apply and will appear in final bundle.js output

Is it possible with loaders?

// Code so far, it search for <StaticComponent name="xyz"> tags in html file and then replace it with html content of xyz, last thing to do is to bundle also xyz.js

components/static/header/index.cmp.html

<div class="some-html-of-this-component">
</div>

components/static/header/index.cmp.js

// this file has to be required to whole bundle

loaders/static-component-loader.js - this loader needs html string as input, please read from bottom to top

const fs = require("fs");
const JSDOM = require("jsdom").JSDOM;

const readFile = function(path) {
    this.addDependency(path);

    return fs.readFileSync(path, 'utf8');    
}

const getNodeAttrs = function(node) {
    const attributes = {};

    for ( const attr of node.attributes )
        attributes[attr.nodeName] = attr.nodeValue;

    return attributes;
}

const setNodeAttrs = function(node, attributes) {
    node.setAttribute("data-cmpid", attributes.id);
    node.setAttribute("data-cmpname", attributes.name);
    node.setAttribute("class", `cmp cmpstatic cmpid-${attributes.id} ${attributes.class || ""}`.trim());

    if ( attributes.style )
        node.setAttribute("style", attributes.style);
}

const replaceNode = function(node) {
    const nodeParent = node.parentElement;

    const nodeAttributes = getNodeAttrs(node);

    const componentPath = `${__dirname}/../src/components/static/${nodeAttributes.name}`;
    const componentPathHtml = `${componentPath}/index.cmp.html`;
    const componentPathJs = `${componentPath}/index.cmp.js`;
    const componentContentHtml = readFile.call(this, componentPathHtml);

    node.innerHTML = `<div>${componentContentHtml}</div>`;

    const nNode = node.children[0];

    nodeParent.replaceChild(nNode, node);

    setNodeAttrs(nNode, nodeAttributes);

    return nNode;
}

const processNode = function(targetNode) {
    const nodes = targetNode.querySelectorAll("static-component");

    for ( const node of nodes ) {
        const newNode = replaceNode.call(this, node);

        processNode.call(this, newNode);
    }
}

module.exports = function StaticComponentLoader(content) {
    const jsdom = new JSDOM(content);

    processNode.call(this, jsdom.window.document.body);

    return jsdom.serialize();
}

somehow I need to include file from path componentPathJs into whole bundle and then find a way to require it in runtime


Solution

  • Yes, there is a loader that does just this (when it parses a file, it will also require additional files). It's called baggage-loader: https://github.com/deepsweet/baggage-loader. It offers file name placeholders to use in its configuration, so that, for example, whenever xyz.js is loaded, it will insert a require() for xyz.scss, or xyz.json, etc., depending on your configuration.

    If you take a look at the source code, it's actually pretty simple -- basically, the loader just adds a require() to the code that it's loading and returns the result. If you don't want to use baggage-loader, you could easily write your own custom loader that does something similar, all you need to do is prepend require('whatever'), as a string, to the code the loader receives.

    (disclaimer: I am technically a maintainer of baggage-loader, thought it's been a while since I worked on it)