Вывод модуля UMD из динамических источников с помощью webpack

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

Например, скажем, мои источники выглядят так:

/src/snippets/red.js
/src/snippets/green.js
/src/snippets/blue.js

Я хочу, чтобы webpack создавал эти источники в одном модуле, который позволяет мне получить доступ к каждому из них в качестве подмодулей, например:

const red = require('snippets').red

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

const glob = require('glob')

module.exports = {
    entry: glob.sync('./src/snippets/*.js').reduce((files, file) => {
        files[path.basename(file,'.js')] = file

        return files
    }, {}),
    output: {
      path: __dirname + '/lib',
      filename: 'snippets.js',
      libraryTarget: 'umd'
    }
}

Эта ошибка: Conflict: Multiple assets emit to the same filename ./lib/snippets.js

Любая идея о том, как я могу выполнить то, что я ищу?

Ответ 1

Недавно я использовал модифицированную версию этого решения в производстве, и у меня не было никаких проблем с ним. (Моя компания настроена с более сложным фильтром, чтобы исключить некоторые модули, но в остальном они одинаковы).

Мы используем конструкцию script для обхода соответствующего каталога (в нашей настройке src, но в вашем src/snippets). Для каждого файла, который заканчивается на .js, мы импортируем его и реэкспортируем его в src/index.js. Если вам нужно что-то более надежное, например, переходить на несколько уровней, вам нужно будет изменить это, чтобы рекурсивно пересечь структуру каталогов.

Когда это будет сделано, мы выведем его в index.js вместе с напоминанием о том, что файл автоматически сгенерирован, чтобы отговорить людей от ручного добавления записей в файл.

const fs = require("fs");
const { EOL } = require("os");
const path = require("path");

let modules = 0;
const buffer = [
  "// auto-generated file", "",
];

const emitModule = file => {
  const moduleName = file.replace(".js", "");
  modules += 1;
  buffer.push(`exports.${moduleName} = require("./snippets/${moduleName}");`);
};

const files = fs.readdirSync(__dirname + "/snippets");

files
.filter(fname => fname !== "index.js" && !fname.startsWith("."))
.forEach(f => {
  const stats = fs.statSync(path.join(__dirname, "snippets", f));
  if (stats.isFile()) {
    emitModule(f);
  }
});

fs.writeFileSync(path.join(__dirname, "index.js"), buffer.join(EOL)+EOL);

console.info(`Built 'src/index.js' with ${modules} modules`);

Затем, в webpack.config.js, мы устанавливаем для libraryTarget значение umd следующим образом:

module.exports = {
  entry: path.resolve(__dirname, "src/index.js"),
  output: {
    path: path.resolve(__dirname, "build/"),
    filename: "mylib.js",
    libraryTarget: "umd"
  }
};

Наконец, для удобства в package.json мы используем следующее, чтобы автоматически запускать конструкцию script перед созданием (вы также можете использовать ее перед запуском webpack-dev-сервера или запусками тестов mocha).

Эта настройка выглядит довольно взломанной, но она работает очень хорошо. Единственная проблема, с которой я когда-либо сталкивался, заключается в том, что иногда порядок модулей изменяется (предположительно, из-за различий в среде), а перечисление файлов вызывает ложное срабатывание в git.

Я поставил весь пакет на GitHub здесь: https://github.com/akatechis/webpack-lib-poc


Обновление: Если вы не хотите вручную вызывать сборку script, добавив ее в свой пакет package.json scripts, вы всегда можете обернуть ее как плагин webpack. Вы можете прочитать подробности здесь

Короче говоря, плагин - это всего лишь объект с методом apply, который регистрируется с помощью компилятора webpack. Из документов:

В качестве умного разработчика JavaScript вы можете вспомнить метод Function.prototype.apply. Из-за этого метода вы можете передать любую функцию в качестве плагина (this укажет на компилятор). Вы можете использовать этот стиль для встроенных пользовательских плагинов в своей конфигурации.

Ниже приведены изменения между двумя настройками:

Измените конфигурацию webpack для импорта сборки script и добавьте ее в массив plugins:

const path = require("path");
const buildPlugin = require("./src/index.build");

module.exports = {
  entry: path.resolve(__dirname, "src/index.js"),
  output: {
    path: path.resolve(__dirname, "build/"),
    filename: "mylib.js",
    libraryTarget: "umd"
  },
  plugins: [buildPlugin]
};

Затем измените index.build.js, чтобы экспортировать функцию, которая регистрирует обратный вызов в компиляторе (получает его как this). Возьмите содержимое сборки script из ранее, поместите его в функцию build() и затем экспортируйте функцию плагина следующим образом:

module.exports = function () {
  this.plugin('run', function(compiler, callback) {
    console.log("Build script starting");
    build();
    callback();
  });
};

Удалите цель build-index из любых скриптов в package.json. Webpack теперь будет всегда вызывать вашу конструкцию script перед запуском.

Ответ 2

Попробуйте следующее:

Я в основном меняю запись немного по-другому, а затем использую NamedModulesPlugin.

module.exports = {
    entry: glob.sync('./snippets/*.js').reduce(function(entry, file){
        entry['./snippets'].push(file);
        return entry;
    }, { './snippets': []}),
    output: {
        path: path.resolve(__dirname, ''),
        filename: '[name].js'
        libraryTarget: 'umd'
    },
    module: {
        // only relevant portions shown
        plugins: [
            new webpack.NamedModulesPlugin()
        ]
    }
};

он должен работать.