Использование пользовательского загрузчика веб-пакетов для добавления дополнительных полей в экспорт по умолчанию

Я пытаюсь написать собственный загрузчик веб-пакетов, который загружает демонстрационные файлы javascript вместе с их исходным кодом для целей документирования. Должно

  • Загрузите указанный файл JavaScript (например, Simple.demo.js)
  • Прикрепите необработанный источник файла к экспорту по умолчанию в качестве нового поля

Когда это будет сделано, я смогу настроить загрузчик в своем конфиге веб-пакета:

{
    test: /\.demo.js$/,
    exclude: /node_modules/,
    use: [{
       loader: "babel-loader",
    }, {
       loader: resolve("./demo-loader.js"),
    }]
}

… Затем получите доступ к исходному коду для моей демонстрации после импорта:

import SimpleDemo from 'demo-loader!./Simple.demo.js';                  
console.log(SimpleDemo.__source__);
SimpleDemo(); //run the demo code

Попытка 1

Я успешно загрузил исходный код и выполнил именованный экспорт, используя следующий загрузчик:

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);
    });
};

Это позволяет мне сделать

import SimpleDemo, { rawSource } from 'demo-loader!./Simple.demo.js`;                  
console.log(rawSource);
SimpleDemo(); //run the demo code

... но я хочу передать все SimpleDemo как объект для другой функции, поэтому я хотел бы импортировать их как один блок (будет много демонстраций).

Попытка 2

Поскольку я не знаю имя внутренней переменной, используемой для экспорта по умолчанию в демонстрационном файле (без выполнения полного анализа AST), я не могу просто добавить дополнительную строку в файл, чтобы изменить его. Вместо этого я попытался написать файл-обертку, который импортирует и повторно экспортирует файл по умолчанию, например так:

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);
    });
};

Я надеюсь, что это новое import директива будет решена с помощью веб-пакета (я знаю, что это может привести к бесконечной регрессии, потому что она должна просто снова запустить тот же загрузчик в зависимости от имени файла). Кажется, этого не происходит - я просто получаю

TypeError: _Simple_demo_js__WEBPACK_IMPORTED_MODULE_0__.default is undefined

это происходит потому, что webpack уже построил свое дерево зависимостей и не хочет добавлять к нему что-либо еще. Я также пытался использовать require() вместо importэто не улучшит дела.

Я поступаю об этом неправильно? Я хотел использовать что-то простое с цепочкой, объединяя выходные данные других загрузчиков, но API, похоже, не может это сделать.

1 ответ

Выяснили, как это сделать, используя веб-пакетный загрузчик:

const { readFile } = require("fs");
const escapeSource = require("js-string-escape");
const { stringifyRequest } = require("loader-utils");

module.exports = function empty() { /* do nothing */ };

module.exports.pitch = function withSourceLoader(remainingRequest) {
    const onComplete = this.async();
    const filename = this.resource;
    readFile(filename, (error, fileContents) => {
        if ( error ) {
            return onComplete(error);
        }
        const newContent = `        
            module.exports = require(${stringifyRequest(this, "!!" + remainingRequest)});
            const rawSource = "${ escapeSource(fileContents) }";
            module.exports.default.__source__ = rawSource;
        `;

        onComplete(null, newContent);
    });
};

До сих пор не 100% ясно, на магию, как мой require() вызов разрешается, но работает отлично! Я планирую опубликовать в качестве модуля позже на этой неделе (с некоторыми другими функциями).

Другие вопросы по тегам