Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ModuleImporter): implement import module compatible with bundlers #6709

Merged
merged 3 commits into from Oct 8, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
9 changes: 4 additions & 5 deletions src/client/actions/ActionsManager.js
@@ -1,16 +1,15 @@
'use strict';

const fs = require('node: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);
}
}

Expand Down
80 changes: 80 additions & 0 deletions src/util/ModuleImporter.js
@@ -0,0 +1,80 @@
'use strict';

const fs = require('node:fs');
const path = require('node: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<string, Function>} 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<string, Function>} 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)]));
}
}

module.exports = ModuleImporter;