feat: use npm directory-import

This commit is contained in:
DIYgod
2024-01-25 13:29:00 +08:00
parent 2550b6ecf6
commit d5e38c8f5d
9 changed files with 10 additions and 440 deletions
+1 -2
View File
@@ -1,4 +1,4 @@
import { directoryImport } from '@/utils/directory-import';
import { directoryImport } from 'directory-import';
import type { Handler } from 'hono'
type Root = {
@@ -6,7 +6,6 @@ type Root = {
}
const imports = directoryImport({
callerDirectoryPath: __dirname,
targetDirectoryPath: './v3',
importPattern: /router\.js$/,
});
@@ -1,41 +0,0 @@
import { promises as fs } from 'node:fs';
import { ImportedModulesPrivateOptions } from './types.d';
/**
* Async version of the directory reader.
*
* I use *for loop* instead of forEach because it is faster.
* I define the *counter* variable outside the loop because it is faster. Because it is not redefined every iteration.
* @param {ImportedModulesPrivateOptions} options - The private options generated by the preparePrivateOptions function.
* @param {string} targetDirectoryPath - The path to the directory to import modules from. Required for recursive calls.
* @returns {Promise<string[]>} An object containing all imported modules.
*/
export default async function readDirectoryAsync(
options: ImportedModulesPrivateOptions,
targetDirectoryPath: string,
): Promise<string[]> {
const receivedItemsPaths = await fs.readdir(targetDirectoryPath);
const result = await Promise.all(
receivedItemsPaths.map(async (itemPath) => {
const receivedFilesPaths = [];
const relativeItemPath = `${targetDirectoryPath}/${itemPath}`;
const stat = await fs.stat(relativeItemPath);
if (stat.isDirectory() && options.includeSubdirectories) {
const files = await readDirectoryAsync(options, relativeItemPath);
receivedFilesPaths.push(...files);
} else if (stat.isFile()) {
receivedFilesPaths.push('/' + relativeItemPath);
}
return receivedFilesPaths;
}),
);
return result.flat();
}
@@ -1,46 +0,0 @@
import * as fs from 'node:fs';
import { ImportedModulesPrivateOptions } from './types.d';
/**
* Sync version of the directory reader.
*
* !important this function will block the event loop until it finishes.
* I use *for loop* instead of forEach because it is faster.
* I define the *counter* variable outside the loop because it is faster. Because it is not redefined every iteration.
* @param {ImportedModulesPrivateOptions} options - The private options generated by the preparePrivateOptions function.
* @param {string} targetDirectoryPath - The path to the directory to import modules from. Required for recursive calls.
* @returns {string[]} An array of file paths.
*/
export default function readDirectorySync(
options: ImportedModulesPrivateOptions,
targetDirectoryPath: string,
): string[] {
const receivedItemsPaths = fs.readdirSync(targetDirectoryPath);
const receivedDirectoriesPaths = [];
const receivedFilesPaths = [];
// Get the files and directories paths from the target directory.
let itemsCounter = 0;
for (itemsCounter; itemsCounter < receivedItemsPaths.length; itemsCounter += 1) {
const itemPath = `${targetDirectoryPath}/${receivedItemsPaths[itemsCounter]}`;
const stat = fs.statSync(itemPath);
if (stat.isDirectory() && options.includeSubdirectories) {
receivedDirectoriesPaths.push(itemPath);
} else if (stat.isFile()) {
receivedFilesPaths.push('/' + itemPath);
}
}
// Recursively get the files paths from the subdirectories.
let directoriesCounter = 0;
for (directoriesCounter; directoriesCounter < receivedDirectoriesPaths.length; directoriesCounter += 1) {
const files = readDirectorySync(options, receivedDirectoriesPaths[directoriesCounter] as string);
receivedFilesPaths.push(...files);
}
// Return the files paths.
return receivedFilesPaths;
}
@@ -1,105 +0,0 @@
import * as path from 'node:path';
import readDirectoryAsync from './directory-reader-async';
import readDirectorySync from './directory-reader-sync';
import { ImportedModules, ImportedModulesPrivateOptions } from './types.d';
const VALID_IMPORT_EXTENSIONS = new Set(['.js', '.mjs', '.ts', '.json']);
const handlers = { async: asyncHandler, sync: syncHandler };
/**
* Synchronously import modules from the specified directory.
* @param {ImportedModulesPrivateOptions} options - The private options generated by the preparePrivateOptions function.
* @returns {ImportedModules} An object containing all imported modules.
*/
function syncHandler(options: ImportedModulesPrivateOptions): ImportedModules {
const modules = {};
const filesPaths = readDirectorySync(options, options.targetDirectoryPath);
let index = 0;
for (const filePath of filesPaths) {
const isModuleImported = importModule(filePath, index, options, modules);
if (isModuleImported) index += 1;
if (index === options.limit) break;
}
return modules;
}
/**
* Asynchronously import modules from the specified directory.
* @param {ImportedModulesPrivateOptions} options - The private options generated by the preparePrivateOptions function.
* @returns {Promise<ImportedModules>} An object containing all imported modules.
*/
async function asyncHandler(options: ImportedModulesPrivateOptions): Promise<ImportedModules> {
const modules = {};
const filesPaths = await readDirectoryAsync(options, options.targetDirectoryPath);
let index = 0;
for (const filePath of filesPaths) {
const isModuleImported = importModule(filePath, index, options, modules);
if (isModuleImported) index += 1;
if (index === options.limit) break;
}
return modules;
}
/**
* Import a module and add it to the modules object.
* @param {string} filePath - The path to the file to import.
* @param {number} index - The index of the module.
* @param {ImportedModulesPrivateOptions} options - The private options generated by the preparePrivateOptions function.
* @param {ImportedModules} modules - The object containing all imported modules.
* @returns {boolean} Whether the module was imported or not.
*/
function importModule(
filePath: string,
index: number,
options: ImportedModulesPrivateOptions,
modules: ImportedModules,
) {
const { name: fileName, ext: fileExtension } = path.parse(filePath);
const isValidModuleExtension = VALID_IMPORT_EXTENSIONS.has(fileExtension);
const isDeclarationFile = filePath.endsWith('.d.ts');
const isValidFilePath = options.importPattern ? options.importPattern.test(filePath) : true;
if (!isValidModuleExtension) return false;
if (!isValidFilePath) return false;
if (isDeclarationFile) return false;
const relativeModulePath = filePath.slice(options.targetDirectoryPath.length + 1);
// eslint-disable-next-line security/detect-non-literal-require, @typescript-eslint/no-var-requires, unicorn/prefer-module
const importedModule = require(filePath) as unknown;
modules[relativeModulePath] = importedModule;
if (options.callback) {
options.callback(fileName, relativeModulePath, importedModule, index);
}
return true;
}
/**
* Import all modules from the specified directory synchronously or asynchronously.
* @param {ImportedModulesPrivateOptions} options - The private options generated by the preparePrivateOptions function.
* @returns {ImportedModules | Promise<ImportedModules>} An object containing all imported modules.
*/
export default function importModules(
options: ImportedModulesPrivateOptions,
): ImportedModules | Promise<ImportedModules> {
if (!handlers[options.importMode]) {
throw new Error(`Expected sync or async import method, but got: ${options.importMode}`);
}
return handlers[options.importMode](options);
}
-92
View File
@@ -1,92 +0,0 @@
import importModules from './import-modules';
import preparePrivateOptions from './prepare-private-options';
import {
ImportedModules,
ImportedModulesPublicOptions,
ImportModulesCallback,
ImportModulesInputArguments,
ImportModulesMode,
} from './types.d';
/**
* Import modules from the current directory synchronously
* @returns {ImportedModules} An object containing all imported modules.
*/
function directoryImport(): ImportedModules;
/**
* Import modules from the current directory synchronously and call the provided callback for each imported module.
* @param {ImportModulesCallback} callback - The callback function to call for each imported module.
* @returns {ImportedModules} An object containing all imported modules.
*/
function directoryImport(callback: ImportModulesCallback): ImportedModules;
/**
* Import modules from the specified directory synchronously.
* @param {string} targetDirectoryPath - The path to the directory to import modules from.
* @returns {ImportedModules} An object containing all imported modules.
*/
function directoryImport(targetDirectoryPath: string): ImportedModules;
/**
* Import all modules from the specified directory synchronously and call the provided callback for each imported module.
* @param {string} targetDirectoryPath - The path to the directory to import modules from.
* @param {ImportModulesCallback} callback - The callback function to call for each imported module.
* @returns {ImportedModules} An object containing all imported modules.
*/
function directoryImport(targetDirectoryPath: string, callback: ImportModulesCallback): ImportedModules;
/**
* Import all modules from the specified directory synchronously or asynchronously.
* @param {string} targetDirectoryPath - The path to the directory to import modules from.
* @param {ImportModulesMode} mode - The import mode. Can be 'sync' or 'async'.
* @returns {ImportedModules} An object containing all imported modules.
*/
function directoryImport(targetDirectoryPath: string, mode: ImportModulesMode): ImportedModules;
/**
* Import all modules from the specified directory synchronously or asynchronously and call the provided callback for each imported module.
* @param {string} targetDirectoryPath - The path to the directory to import modules from.
* @param {ImportModulesMode} importMode - The import mode. Can be 'sync' or 'async'.
* @param {ImportModulesCallback} callback - The callback function to call for each imported module.
* @returns {ImportedModules} An object containing all imported modules.
*/
function directoryImport(
targetDirectoryPath: string,
importMode: ImportModulesMode,
callback: ImportModulesCallback,
): ImportedModules;
/**
* Import all modules from the specified directory
* @param {ImportedModulesPublicOptions} options - The options object.
* @returns {ImportedModules} An object containing all imported modules.
*/
function directoryImport(options: ImportedModulesPublicOptions): ImportedModules;
/**
* Import all modules from the specified directory and call the provided callback for each imported module.
* @param {ImportedModulesPublicOptions} options - The options object.
* @param {ImportModulesCallback} callback - The callback function to call for each imported module.
* @returns {ImportedModules} An object containing all imported modules.
*/
function directoryImport(options: ImportedModulesPublicOptions, callback: ImportModulesCallback): ImportedModules;
/**
* Import all modules from the specified directory with the given options and call the provided callback for each imported module.
* @param {ImportModulesInputArguments} arguments_ - The arguments.
* @returns {ImportedModules|Promise<ImportedModules>} An object containing all imported modules.
*/
function directoryImport(...arguments_: ImportModulesInputArguments): ImportedModules | Promise<ImportedModules> {
const options = preparePrivateOptions(...arguments_);
try {
return importModules(options);
} catch (error) {
Object.assign(error as Error, options);
throw error;
}
}
export { directoryImport };
@@ -1,121 +0,0 @@
import * as path from 'node:path';
import {
ImportedModulesPrivateOptions,
ImportedModulesPublicOptions,
ImportModulesInputArguments,
ImportModulesMode,
} from './types.d';
const getDefaultOptions = (): ImportedModulesPrivateOptions => {
const options = {
includeSubdirectories: true,
importMode: 'sync' as ImportModulesMode,
importPattern: /.*/,
limit: Number.POSITIVE_INFINITY,
callerFilePath: '/',
callerDirectoryPath: '/',
targetDirectoryPath: '/',
};
options.callerFilePath = (new Error('functional-error').stack as string)
.split('\n')[4]
?.match(/at file:\/\/(.+):\d+|at .+ \((.+):\d+:\d+\)/)?.slice(1).find(Boolean) as string;
options.callerDirectoryPath = options.callerFilePath?.split('/').slice(0, -1).join('/');
options.targetDirectoryPath = options.callerDirectoryPath;
return options;
};
/**
* Prepare the options object from the provided arguments.
* @param {...any} arguments_ - The arguments.
* @returns {ImportedModulesPrivateOptions} The options object.
*/
export default function preparePrivateOptions(
...arguments_: ImportModulesInputArguments
): ImportedModulesPrivateOptions {
const options = { ...getDefaultOptions() };
// * Check first argument
// ** If user provided nothing as first argument,
// ** it means that he wants to use the default options
// ** return the default options
if (arguments_[0] === undefined) {
return options;
}
// ** If user provided an object as first argument,
// ** it means that he wants to set custom options
// ** use it to oweverwrite the default options
else if (typeof arguments_[0] === 'object') {
const result = {
...getDefaultOptions(),
...(arguments_[0] as ImportedModulesPublicOptions),
};
result.targetDirectoryPath = path.resolve(result.callerDirectoryPath, result.targetDirectoryPath);
result.callback = typeof arguments_[1] === 'function' ? arguments_[1] : undefined;
return result;
}
// ** If user provided a string
// ** it means that he wants to set the target directory path as first argument
// ** use it to oweverwrite the default options
else if (typeof arguments_[0] === 'string') {
options.targetDirectoryPath = path.resolve(options.callerDirectoryPath, arguments_[0]);
}
// ** If user provided a function
// ** it means that he wants to set the callback as first argument
// ** use it to oweverwrite the default options
else if (typeof arguments_[0] === 'function') {
options.callback = arguments_[0];
}
// ** If user provided something else
// ** it means that he made a mistake
// ** throw an error
else {
throw new TypeError(
`Expected undefined, object, string or function as first argument, but got: ${typeof arguments_[0]}`,
);
}
// * Check second argument
// ** If user provided string as second argument
// ** it means that he wants to set the import mode (sync or async) as second argument
// ** use it to oweverwrite the default options
if (typeof arguments_[1] === 'string') {
// ** But if the import mode is not sync or async
// ** throw an error
if (arguments_[1] !== 'sync' && arguments_[1] !== 'async') {
throw new TypeError(`Expected sync or async as second argument, but got: ${arguments_[1]}`);
}
options.importMode = arguments_[1];
}
// ** If user provided a function as second argument
// ** it means that he wants to set the callback as second argument
// ** use it to oweverwrite the default options
else if (typeof arguments_[1] === 'function') {
options.callback = arguments_[1];
}
// * Check third argument
// ** If user provided a function as third argument
// ** it means that he wants to set the callback as third argument
// ** use it to oweverwrite the default options
if (typeof arguments_[2] === 'function') {
options.callback = arguments_[2];
}
// * Return the options object
return options;
}
-33
View File
@@ -1,33 +0,0 @@
export type ModuleName = string;
export type ModulePath = string;
export type ModuleData = unknown;
export type ModuleIndex = number;
export type ImportModulesMode = 'sync' | 'async';
export type ImportModulesCallback = (name: ModuleName, path: ModulePath, data: ModuleData, index: ModuleIndex) => void;
export type ImportModulesInputArguments = [
targetDirectoryPathOrOptionsOrCallback?: string | ImportedModulesPublicOptions | ImportModulesCallback,
modeOrCallback?: ImportModulesMode | ImportModulesCallback,
callback?: ImportModulesCallback,
];
export interface ImportedModules {
[key: ModulePath]: ModuleData;
}
export interface ImportedModulesPublicOptions {
includeSubdirectories?: boolean;
callerDirectoryPath?: string;
targetDirectoryPath?: string;
importPattern?: RegExp;
importMode?: ImportModulesMode;
limit?: number;
}
export interface ImportedModulesPrivateOptions extends Required<ImportedModulesPublicOptions> {
callback?: ImportModulesCallback;
callerFilePath: string;
callerDirectoryPath: string;
}
+1
View File
@@ -91,6 +91,7 @@
"crypto-js": "4.2.0",
"currency-symbol-map": "5.1.0",
"dayjs": "1.11.8",
"directory-import": "3.2.1",
"dotenv": "16.3.2",
"entities": "4.5.0",
"etag": "1.8.1",
+8
View File
@@ -53,6 +53,9 @@ dependencies:
dayjs:
specifier: 1.11.8
version: 1.11.8
directory-import:
specifier: 3.2.1
version: 3.2.1
dotenv:
specifier: 16.3.2
version: 16.3.2
@@ -3421,6 +3424,11 @@ packages:
engines: {node: '>=0.3.1'}
dev: true
/directory-import@3.2.1:
resolution: {integrity: sha512-hIMc9WJHs4HRZbBiNAvKGxkhjaJ2wOaFcFA0K+yj16GyBJDg28e6QgQ3hFuFB0ZRETNYZu3u85rmxIxoxWEwbA==}
engines: {node: '>=18.17.0'}
dev: false
/doctrine@3.0.0:
resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==}
engines: {node: '>=6.0.0'}