I'm attempting to write a custom webpack loader that loads demo javascript files together with their source for documentation purposes. It should
- Load the indicated javascript file (e.g.
Simple.demo.js
) - Attach the raw source of the file to the default export as a new field
When this is done, I should be able to configure the loader in my webpack config:
{
test: /\.demo.js$/,
exclude: /node_modules/,
use: [{
loader: "babel-loader",
}, {
loader: resolve("./demo-loader.js"),
}]
}
…then access the source code for my demo after import:
import SimpleDemo from 'demo-loader!./Simple.demo.js';
console.log(SimpleDemo.__source__);
SimpleDemo(); //run the demo code
Attempt 1
I have successfully loaded the raw source and done a named export, using the following loader:
const { readFile } = require("fs");
const escapeSource = require("js-string-escape");
module.exports = function withSourceLoader(content, map, meta) {
const onComplete = this.async();
const fileName = this.resource;
readFile(fileName, (error, fileContents) => {
if ( error ) {
return onComplete(error);
}
const exportSource = `export const rawSource = "${ escapeSource(fileContents)}";`;
const newContent = `${ content }\n\n${ exportSource }`;
onComplete(null, newContent, map, meta);
});
};
This lets me do
import SimpleDemo, { rawSource } from 'demo-loader!./Simple.demo.js`;
console.log(rawSource);
SimpleDemo(); //run the demo code
…but I want to pass the whole SimpleDemo
as an object to another function, so I'd like to import them as a single block (there will be lots of demos).
Attempt 2
Because I don't know the internal variable name used for the default export in the demo file (without doing a full AST parse), I can't just append an extra line to the file to modify it. Instead I tried to write a wrapper file that imports and re-exports the default, like this:
const { readFile } = require("fs");
const { basename } = require("path");
const escapeSource = require("js-string-escape");
module.exports = function withSourceLoader(content, map, meta) {
const onComplete = this.async();
const fileName = this.resource;
readFile(fileName, (error, fileContents) => {
if ( error ) {
return onComplete(error);
}
const newContent = `
const rawSource = "${ escapeSource(fileContents) }";
import DemoDefault from './${ basename(fileName) }';
DemoDefault.__source__ = rawSource;
export default DemoDefault;
`;
onComplete(null, newContent, map, meta);
});
};
My hope was that this new import
directive would get resolved by webpack (I'm aware that this could lead to an infinite regression, because it should just trigger the same loader again based on filename). This doesn't seem to happen, however - I just get
TypeError: _Simple_demo_js__WEBPACK_IMPORTED_MODULE_0__.default is undefined
which appears to be because webpack has already built its dependency tree and doesn't want to add anything else to it. I have also tried using a require()
instead of import
, this doesn't improve matters.
Am I going about this the wrong way? I wanted to use something simple with chaining, by combining the output of other loaders, but the API doesn't seem to have a way to do that.