From 492712894faf4575cb033f77c6f6e01f90d4055a Mon Sep 17 00:00:00 2001 From: Nico Rausch Date: Tue, 28 Sep 2021 17:28:49 +0200 Subject: [PATCH] feat(ModuleImporter): implement import module compatible with bundlers Implement ModuleImporter, which dynamically includes modules based on the used bundler or file system. Change ActionsManager to use the new util instead of directly accessing the file system. close #6681 --- src/client/actions/ActionsManager.js | 9 ++- src/util/ModuleImporter.js | 102 +++++++++++++++++++++++++++ 2 files changed, 106 insertions(+), 5 deletions(-) create mode 100644 src/util/ModuleImporter.js diff --git a/src/client/actions/ActionsManager.js b/src/client/actions/ActionsManager.js index a40b5be0aad2..581602caba9c 100644 --- a/src/client/actions/ActionsManager.js +++ b/src/client/actions/ActionsManager.js @@ -1,16 +1,15 @@ 'use strict'; -const fs = require('fs'); +const ModuleImporter = require('../../util/ModuleImporter'); class ActionsManager { constructor(client) { this.client = client; - const files = fs.readdirSync(__dirname); + const modules = ModuleImporter.import('./client/actions', ['Action.js', 'ActionsManager.js']); - for (const file of files) { - if (['Action.js', 'ActionsManager.js'].includes(file)) continue; - this.register(require(`./${file}`)); + for (const module of modules) { + this.register(module); } } diff --git a/src/util/ModuleImporter.js b/src/util/ModuleImporter.js new file mode 100644 index 000000000000..27a489f01720 --- /dev/null +++ b/src/util/ModuleImporter.js @@ -0,0 +1,102 @@ +'use strict'; + +const fs = require('fs'); +const path = require('path'); + +/** + * The ModuleImporter helper class is used to import local files, by using the bundler or file system, whichever is + * needed in the current build. + * + * @private + */ +class ModuleImporter extends null { + /** + * This method is used to import a variable number of files in a specific directory and is built to also work with + * webpack and potentially other bundlers. + * + * @param {string} pathToDirectory the path to the searched directory (relative to the `src` directory) + * @param {string[]} [excludedFiles=[]] an array of files which are excluded from the import + * + * @returns {unknown[]} an array of dynamically imported modules + */ + static import(pathToDirectory, excludedFiles = []) { + pathToDirectory = path.normalize(pathToDirectory); + + let moduleMap; + + if (typeof __webpack_require__ === 'function') { + moduleMap = this.importWebpack(pathToDirectory); + } else { + moduleMap = this.importFilesystem(pathToDirectory); + } + + const requiredModules = []; + for (const [fileName, requireFunction] of moduleMap.entries()) { + if (excludedFiles.includes(path.basename(fileName))) continue; + requiredModules.push(requireFunction()); + } + + return requiredModules; + } + + /** + * Helper function to import all modules in a given directory via file system. + * + * @param {string} pathToDirectory the path to the searched directory (relative to the `src` directory) + * @returns {Map} a map of resolved file names to their require function + * + * @private + */ + static importFilesystem(pathToDirectory) { + const basePath = path.resolve(__dirname, '..', pathToDirectory); + const files = fs.readdirSync(basePath); + // Require Function is returned for lazy evaluation + return new Map(files.map(file => [file, () => require(path.join(basePath, file))])); + } + + /** + * Helper function to import all modules in a given directory via webpack.context. + * + * @param {string} pathToDirectory the path to the searched directory (relative to the `src` directory) + * @returns {Map} a map of resolved file names to their require function + * + * @private + */ + static importWebpack(pathToDirectory) { + // The "function" `require.context` is actually evaluated at build/bundling time, and needs to be called with + // literals because of that. By building a context of '..' we actually cover the whole codebase and can + // dynamically import any subdirectory. When not using webpack, the following line is never evaluated. + const context = require.context('..', true, /\.js$/); + + const files = context + .keys() + // We have all files in our context, so now we filter the files in the correct directory + .filter(file => !path.normalize(file).startsWith(pathToDirectory)); + + return new Map(files.map(file => [file, () => context(file)])); + } + + /** + * Helper function to import all modules in a given directory via webpack.context. + * + * @param {string} pathToDirectory the path to the searched directory (relative to the `src` directory) + * @returns {Map} a map of resolved file names to their require function + * + * @private + */ + static importRollup(pathToDirectory) { + // The "function" `require.context` is actually evaluated at build/bundling time, and needs to be called with + // literals because of that. By building a context of '..' we actually cover the whole codebase and can + // dynamically import any subdirectory. When not using webpack, the following line is never evaluated. + const context = import('..'); + + const files = context + .keys() + // We have all files in our context, so now we filter the files in the correct directory + .filter(file => !path.normalize(file).startsWith(pathToDirectory)); + + return new Map(files.map(file => [file, () => context(file)])); + } +} + +module.exports = ModuleImporter;