diff --git a/package-lock.json b/package-lock.json index 53b61845..328b5829 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,8 +9,9 @@ "version": "0.7.1", "license": "MIT", "dependencies": { - "ebec": "^0.1.0", - "glob": "^8.1.0" + "ebec": "^0.2.1", + "glob": "^8.1.0", + "jiti": "^1.16.2" }, "devDependencies": { "@babel/core": "^7.20.12", @@ -6228,13 +6229,18 @@ } }, "node_modules/ebec": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/ebec/-/ebec-0.1.0.tgz", - "integrity": "sha512-QP7lg0ZqM266C//l53J2ywUPSi+rsZx8/uNxkBMUPL+GpGyeEy0V5opqc1fKS6fcV/vLNnCBe2z2kNxDCQMVqg==", + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/ebec/-/ebec-0.2.1.tgz", + "integrity": "sha512-JFYoMVXROnfl0k53pir1/I1OyEtObxObz5og62aIiiCzlIK82lQLq80fJS8hHyFr0Xh5p4ZsBBn+iyk8Tt0H1A==", "dependencies": { - "smob": "^0.0.6" + "smob": "^0.1.0" } }, + "node_modules/ebec/node_modules/smob": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/smob/-/smob-0.1.0.tgz", + "integrity": "sha512-u6ezVF7hN3AxA1onkbMsl46XZr1HYrtMksmckSVpkLI8bYJ5I34kHMvDZk8qNCfA0y54VnhWqCoU6DQMMStzpw==" + }, "node_modules/ecc-jsbn": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", @@ -10781,6 +10787,14 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, + "node_modules/jiti": { + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.16.2.tgz", + "integrity": "sha512-OKBOVWmU3FxDt/UH4zSwiKPuc1nihFZiOD722FuJlngvLz2glX1v2/TJIgoA4+mrpnXxHV6dSAoCvPcYQtoG5A==", + "bin": { + "jiti": "bin/jiti.js" + } + }, "node_modules/js-sdsl": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.2.0.tgz", @@ -16039,7 +16053,8 @@ "node_modules/smob": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/smob/-/smob-0.0.6.tgz", - "integrity": "sha512-V21+XeNni+tTyiST1MHsa84AQhT1aFZipzPpOFAVB8DkHzwJyjjAmt9bgwnuZiZWnIbMo2duE29wybxv/7HWUw==" + "integrity": "sha512-V21+XeNni+tTyiST1MHsa84AQhT1aFZipzPpOFAVB8DkHzwJyjjAmt9bgwnuZiZWnIbMo2duE29wybxv/7HWUw==", + "dev": true }, "node_modules/source-map": { "version": "0.6.1", @@ -22059,11 +22074,18 @@ } }, "ebec": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/ebec/-/ebec-0.1.0.tgz", - "integrity": "sha512-QP7lg0ZqM266C//l53J2ywUPSi+rsZx8/uNxkBMUPL+GpGyeEy0V5opqc1fKS6fcV/vLNnCBe2z2kNxDCQMVqg==", + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/ebec/-/ebec-0.2.1.tgz", + "integrity": "sha512-JFYoMVXROnfl0k53pir1/I1OyEtObxObz5og62aIiiCzlIK82lQLq80fJS8hHyFr0Xh5p4ZsBBn+iyk8Tt0H1A==", "requires": { - "smob": "^0.0.6" + "smob": "^0.1.0" + }, + "dependencies": { + "smob": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/smob/-/smob-0.1.0.tgz", + "integrity": "sha512-u6ezVF7hN3AxA1onkbMsl46XZr1HYrtMksmckSVpkLI8bYJ5I34kHMvDZk8qNCfA0y54VnhWqCoU6DQMMStzpw==" + } } }, "ecc-jsbn": { @@ -25432,6 +25454,11 @@ } } }, + "jiti": { + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.16.2.tgz", + "integrity": "sha512-OKBOVWmU3FxDt/UH4zSwiKPuc1nihFZiOD722FuJlngvLz2glX1v2/TJIgoA4+mrpnXxHV6dSAoCvPcYQtoG5A==" + }, "js-sdsl": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.2.0.tgz", @@ -29280,7 +29307,8 @@ "smob": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/smob/-/smob-0.0.6.tgz", - "integrity": "sha512-V21+XeNni+tTyiST1MHsa84AQhT1aFZipzPpOFAVB8DkHzwJyjjAmt9bgwnuZiZWnIbMo2duE29wybxv/7HWUw==" + "integrity": "sha512-V21+XeNni+tTyiST1MHsa84AQhT1aFZipzPpOFAVB8DkHzwJyjjAmt9bgwnuZiZWnIbMo2duE29wybxv/7HWUw==", + "dev": true }, "source-map": { "version": "0.6.1", diff --git a/package.json b/package.json index 40118a31..0709c0c6 100644 --- a/package.json +++ b/package.json @@ -84,8 +84,9 @@ "typescript": "^4.9.4" }, "dependencies": { - "ebec": "^0.1.0", - "glob": "^8.1.0" + "ebec": "^0.2.1", + "glob": "^8.1.0", + "jiti": "^1.16.2" }, "config": { "commitizen": { diff --git a/src/loader/file-type/index.ts b/src/loader/built-in/index.ts similarity index 87% rename from src/loader/file-type/index.ts rename to src/loader/built-in/index.ts index 13178f17..bd2ea9de 100644 --- a/src/loader/file-type/index.ts +++ b/src/loader/built-in/index.ts @@ -6,4 +6,4 @@ */ export * from './script'; -export * from './json'; +export * from './json/module'; diff --git a/src/loader/built-in/json/index.ts b/src/loader/built-in/json/index.ts new file mode 100644 index 00000000..aca6cea2 --- /dev/null +++ b/src/loader/built-in/json/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright (c) 2023. + * Author Peter Placzek (tada5hi) + * For the full copyright and license information, + * view the LICENSE file that was distributed with this source code. + */ + +export * from './module'; diff --git a/src/loader/built-in/json/module.ts b/src/loader/built-in/json/module.ts new file mode 100644 index 00000000..201414c8 --- /dev/null +++ b/src/loader/built-in/json/module.ts @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2022-2023. + * Author Peter Placzek (tada5hi) + * For the full copyright and license information, + * view the LICENSE file that was distributed with this source code. + */ + +import fs from 'node:fs'; +import { LocatorInfo } from '../../../locator'; +import { handleFileLoadError } from '../../../utils'; +import { Loader } from '../../type'; +import { buildLoaderFilePath } from '../../utils'; + +export class JSONLoader implements Loader { + async execute(input: LocatorInfo) { + const filePath = buildLoaderFilePath(input, true); + + try { + const file = await fs.promises.readFile(filePath); + return JSON.parse(file.toString('utf-8')); + } catch (e) { + return handleFileLoadError(e); + } + } + + executeSync(input: LocatorInfo) { + const filePath = buildLoaderFilePath(input, true); + + try { + const file = fs.readFileSync(filePath); + return JSON.parse(file.toString('utf-8')); + } catch (e) { + return handleFileLoadError(e); + } + } +} diff --git a/src/loader/file-type/script/index.ts b/src/loader/built-in/script/index.ts similarity index 82% rename from src/loader/file-type/script/index.ts rename to src/loader/built-in/script/index.ts index 2773e360..abe9fb98 100644 --- a/src/loader/file-type/script/index.ts +++ b/src/loader/built-in/script/index.ts @@ -5,7 +5,6 @@ * view the LICENSE file that was distributed with this source code. */ -export * from './async'; -export * from './sync'; +export * from './module'; export * from './type'; export * from './utils'; diff --git a/src/loader/built-in/script/module.ts b/src/loader/built-in/script/module.ts new file mode 100644 index 00000000..fffa8204 --- /dev/null +++ b/src/loader/built-in/script/module.ts @@ -0,0 +1,200 @@ +/* + * Copyright (c) 2023. + * Author Peter Placzek (tada5hi) + * For the full copyright and license information, + * view the LICENSE file that was distributed with this source code. + */ + +import { BaseError } from 'ebec'; +import type { JITI } from 'jiti'; +import createJITI from 'jiti'; +import { pathToFileURL } from 'node:url'; +import { LocatorInfo, pathToLocatorInfo } from '../../../locator'; +import { handleFileLoadError, hasStringProperty, isObject } from '../../../utils'; +import { Loader } from '../../type'; +import { buildLoaderFilePath } from '../../utils'; +import { ScriptFileLoadOptions } from './type'; + +export class ScriptLoader implements Loader { + protected jiti : JITI; + + constructor() { + /* + const loaderMap : Record = { + '.mjs': 'js', + '.js': 'js', + '.jsx': 'jsx', + '.ts': 'ts', + '.tsx': 'tsx', + }; + */ + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + this.jiti = createJITI(undefined, { + esmResolve: true, + /* + transform: (options) => { + let loader : ESLoader = 'ts'; + if (options.filename) { + const info = pathToLocatorInfo(options.filename, true); + if (loaderMap[info.extension]) { + loader = loaderMap[info.extension] as ESLoader; + } + } + + return transformSync(options.source, { + target: 'ES2020', + jsx: 'transform', + format: 'cjs', + loader, + }); + }, + + */ + }); + } + + async execute(info: LocatorInfo) { + const filePath = buildLoaderFilePath(info); + + let output : any; + + try { + output = await this.load(info); + } catch (e) { + output = this.jiti(filePath); + } + + return output; + } + + executeSync(info: LocatorInfo) { + const filePath = buildLoaderFilePath(info); + + let output : any; + + try { + output = this.loadSync(info); + } catch (e) { + output = this.jiti(filePath); + } + + return output; + } + + // --------------------------------------------------------------------------- + + async load( + data: LocatorInfo | string, + options?: ScriptFileLoadOptions, + ) : Promise { + let locatorInfo : LocatorInfo; + + if (typeof data === 'string') { + locatorInfo = pathToLocatorInfo(data); + } else { + locatorInfo = data; + } + + options = options || {}; + + let filePath = buildLoaderFilePath(locatorInfo, options.withExtension); + if (options.withFilePrefix) { + filePath = pathToFileURL(filePath).href; + } + + try { + return await import(filePath); + } catch (e) { + /* istanbul ignore next */ + if ( + isObject(e) && + hasStringProperty(e, 'code') + ) { + if ( + !options.withExtension && + ( + e.code === 'ERR_MODULE_NOT_FOUND' || + e.code === 'MODULE_NOT_FOUND' + ) + ) { + return this.load(locatorInfo, { + ...options, + withExtension: true, + }); + } + + if ( + !options.withFilePrefix && + ( + e.code === 'ERR_UNSUPPORTED_ESM_URL_SCHEME' || + e.code === 'UNSUPPORTED_ESM_URL_SCHEME' + ) + ) { + return this.load(locatorInfo, { + ...options, + withFilePrefix: true, + }); + } + + throw new BaseError({ + code: e.code, + message: hasStringProperty(e, 'message') ? e.message : undefined, + stack: hasStringProperty(e, 'stack') ? e.stack : undefined, + }); + } + + /* istanbul ignore next */ + return handleFileLoadError(e); + } + } + + loadSync( + data: LocatorInfo | string, + options?: ScriptFileLoadOptions, + ) : unknown { + let locatorInfo : LocatorInfo; + + if (typeof data === 'string') { + locatorInfo = pathToLocatorInfo(data); + } else { + locatorInfo = data; + } + + options = options || {}; + + const filePath = buildLoaderFilePath(locatorInfo, options.withExtension); + + try { + // eslint-disable-next-line global-require,import/no-dynamic-require + return require(filePath); + } catch (e) { + /* istanbul ignore next */ + if ( + isObject(e) && + hasStringProperty(e, 'code') + ) { + if ( + !options.withExtension && + ( + e.code === 'ERR_MODULE_NOT_FOUND' || + e.code === 'MODULE_NOT_FOUND' + ) + ) { + return this.loadSync(locatorInfo, { + ...options, + withExtension: true, + }); + } + + throw new BaseError({ + code: e.code, + message: hasStringProperty(e, 'message') ? e.message : undefined, + stack: hasStringProperty(e, 'stack') ? e.stack : undefined, + }); + } + + return handleFileLoadError(e); + } + } +} diff --git a/src/loader/file-type/script/type.ts b/src/loader/built-in/script/type.ts similarity index 100% rename from src/loader/file-type/script/type.ts rename to src/loader/built-in/script/type.ts diff --git a/src/loader/file-type/script/utils.ts b/src/loader/built-in/script/utils.ts similarity index 100% rename from src/loader/file-type/script/utils.ts rename to src/loader/built-in/script/utils.ts diff --git a/src/loader/file-type/json.ts b/src/loader/file-type/json.ts deleted file mode 100644 index 2d3b1bbf..00000000 --- a/src/loader/file-type/json.ts +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (c) 2022-2022. - * Author Peter Placzek (tada5hi) - * For the full copyright and license information, - * view the LICENSE file that was distributed with this source code. - */ - -import fs from 'node:fs'; -import path from 'node:path'; -import { LocatorInfo } from '../../locator'; -import { handleFileLoadError } from '../../utils'; -import { buildLoaderFilePath } from '../utils'; - -export async function loadJsonFile(input: LocatorInfo | string) : Promise { - let filePath : string; - - if (typeof input === 'string') { - filePath = path.isAbsolute(input) ? input : path.resolve(process.cwd(), input); - } else { - filePath = buildLoaderFilePath(input, true); - } - - try { - const file = await fs.promises.readFile(filePath); - return JSON.parse(file.toString('utf-8')); - } catch (e) { - return handleFileLoadError(e); - } -} - -export function loadJsonFileSync(input: LocatorInfo | string) : unknown { - let filePath : string; - - if (typeof input === 'string') { - filePath = path.isAbsolute(input) ? input : path.resolve(process.cwd(), input); - } else { - filePath = buildLoaderFilePath(input, true); - } - - try { - const file = fs.readFileSync(filePath); - return JSON.parse(file.toString('utf-8')); - } catch (e) { - return handleFileLoadError(e); - } -} diff --git a/src/loader/file-type/script/async.ts b/src/loader/file-type/script/async.ts deleted file mode 100644 index 97603772..00000000 --- a/src/loader/file-type/script/async.ts +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright (c) 2022. - * Author Peter Placzek (tada5hi) - * For the full copyright and license information, - * view the LICENSE file that was distributed with this source code. - */ - -import { BaseError } from 'ebec'; -import { pathToFileURL } from 'node:url'; -import { LocatorInfo, pathToLocatorInfo } from '../../../locator'; -import { - handleFileLoadError, hasStringProperty, isObject, -} from '../../../utils'; -import { buildLoaderFilePath } from '../../utils'; -import { LoaderFilterFn, ScriptFileExportItem, ScriptFileLoadOptions } from './type'; -import { getExportItem } from './utils'; - -export async function loadScriptFile( - data: LocatorInfo | string, - options?: ScriptFileLoadOptions, -) : Promise { - let locatorInfo : LocatorInfo; - - if (typeof data === 'string') { - locatorInfo = pathToLocatorInfo(data); - } else { - locatorInfo = data; - } - - options = options || {}; - - let filePath = buildLoaderFilePath(locatorInfo, options.withExtension); - if (options.withFilePrefix) { - filePath = pathToFileURL(filePath).href; - } - - try { - return await import(filePath); - } catch (e) { - /* istanbul ignore next */ - if ( - isObject(e) && - hasStringProperty(e, 'code') - ) { - if ( - !options.withExtension && - ( - e.code === 'ERR_MODULE_NOT_FOUND' || - e.code === 'MODULE_NOT_FOUND' - ) - ) { - return loadScriptFile(locatorInfo, { - ...options, - withExtension: true, - }); - } - - if ( - !options.withFilePrefix && - ( - e.code === 'ERR_UNSUPPORTED_ESM_URL_SCHEME' || - e.code === 'UNSUPPORTED_ESM_URL_SCHEME' - ) - ) { - return loadScriptFile(locatorInfo, { - ...options, - withFilePrefix: true, - }); - } - - throw new BaseError({ - code: e.code, - message: hasStringProperty(e, 'message') ? e.message : undefined, - stack: hasStringProperty(e, 'stack') ? e.stack : undefined, - }); - } - - /* istanbul ignore next */ - return handleFileLoadError(e); - } -} - -export async function loadScriptFileExport( - data: LocatorInfo | string, - filterFn?: LoaderFilterFn, -) : Promise { - const output = await loadScriptFile(data); - - if (typeof output === 'object' && !!output) { - return getExportItem(output, filterFn); - } - - throw new BaseError('Cannot extract specific module export'); -} diff --git a/src/loader/file-type/script/sync.ts b/src/loader/file-type/script/sync.ts deleted file mode 100644 index 687c04ca..00000000 --- a/src/loader/file-type/script/sync.ts +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright (c) 2022. - * Author Peter Placzek (tada5hi) - * For the full copyright and license information, - * view the LICENSE file that was distributed with this source code. - */ - -import { BaseError } from 'ebec'; -import { LoaderFilterFn, ScriptFileExportItem, ScriptFileLoadOptions } from './type'; -import { getExportItem } from './utils'; -import { LocatorInfo, pathToLocatorInfo } from '../../../locator'; -import { buildLoaderFilePath } from '../../utils'; -import { - handleFileLoadError, hasStringProperty, isObject, -} from '../../../utils'; - -export function loadScriptFileSync( - data: LocatorInfo | string, - options?: ScriptFileLoadOptions, -) : unknown { - let locatorInfo : LocatorInfo; - - if (typeof data === 'string') { - locatorInfo = pathToLocatorInfo(data); - } else { - locatorInfo = data; - } - - options = options || {}; - - const filePath = buildLoaderFilePath(locatorInfo, options.withExtension); - - try { - // eslint-disable-next-line global-require,import/no-dynamic-require - return require(filePath); - } catch (e) { - /* istanbul ignore next */ - if ( - isObject(e) && - hasStringProperty(e, 'code') - ) { - if ( - !options.withExtension && - ( - e.code === 'ERR_MODULE_NOT_FOUND' || - e.code === 'MODULE_NOT_FOUND' - ) - ) { - return loadScriptFileSync(locatorInfo, { - ...options, - withExtension: true, - }); - } - - throw new BaseError({ - code: e.code, - message: hasStringProperty(e, 'message') ? e.message : undefined, - stack: hasStringProperty(e, 'stack') ? e.stack : undefined, - }); - } - - return handleFileLoadError(e); - } -} - -export function loadScriptFileExportSync( - data: LocatorInfo | string, - filterFn?: LoaderFilterFn, -) : ScriptFileExportItem { - const output = loadScriptFileSync(data); - - if (typeof output === 'object' && !!output) { - return getExportItem(output, filterFn); - } - - throw new BaseError('Cannot extract specific module export'); -} diff --git a/src/loader/helpers.ts b/src/loader/helpers.ts new file mode 100644 index 00000000..d5e99e04 --- /dev/null +++ b/src/loader/helpers.ts @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2022. + * Author Peter Placzek (tada5hi) + * For the full copyright and license information, + * view the LICENSE file that was distributed with this source code. + */ + +import { LocatorInfo, pathToLocatorInfo } from '../locator'; +import { useLoaderManager } from './singleton'; + +export async function loadFile(input: LocatorInfo | string) : Promise { + let info : LocatorInfo; + if (typeof input === 'string') { + info = pathToLocatorInfo(input); + } else { + info = input; + } + + const manager = useLoaderManager(); + + return manager.execute(info); +} + +export function loadFileSync(input: LocatorInfo | string) : unknown { + let info : LocatorInfo; + if (typeof input === 'string') { + info = pathToLocatorInfo(input); + } else { + info = input; + } + + const manager = useLoaderManager(); + + return manager.executeSync(info); +} diff --git a/src/loader/index.ts b/src/loader/index.ts index 96b98931..7b8bdb14 100644 --- a/src/loader/index.ts +++ b/src/loader/index.ts @@ -5,6 +5,6 @@ * view the LICENSE file that was distributed with this source code. */ -export * from './file-type'; -export * from './module'; +export * from './built-in'; +export * from './helpers'; export * from './utils'; diff --git a/src/loader/module.ts b/src/loader/module.ts index d94b6fec..8840349f 100644 --- a/src/loader/module.ts +++ b/src/loader/module.ts @@ -1,44 +1,99 @@ /* - * Copyright (c) 2022. + * Copyright (c) 2023. * Author Peter Placzek (tada5hi) * For the full copyright and license information, * view the LICENSE file that was distributed with this source code. */ -import { LocatorInfo, pathToLocatorInfo } from '../locator'; -import { - loadJsonFile, - loadJsonFileSync, - loadScriptFile, - loadScriptFileSync, -} from './file-type'; - -export async function loadFile(input: LocatorInfo | string) : Promise { - let info : LocatorInfo; - if (typeof input === 'string') { - info = pathToLocatorInfo(input); - } else { - info = input; +import { LocatorInfo } from '../locator'; +import { JSONLoader } from './built-in'; +import { ScriptLoader } from './built-in/script/module'; +import { Loader, Rule } from './type'; +import { buildLoaderFilePath } from './utils'; + +export class LoaderManager { + protected loaders : Record; + + protected rules : Rule[]; + + constructor() { + this.loaders = {}; + this.rules = [ + { test: ['.js', '.mjs', '.cjs', '.ts'], loader: 'script' }, + { test: ['.json'], loader: 'json' }, + ]; } - if (info.extension === '.json') { - return loadJsonFile(info); + register(rule: Rule) { + this.rules.push(rule); } - return loadScriptFile(info); -} + async execute(info: LocatorInfo) : Promise { + const rule = this.findRule(info); + if (!rule) { + throw new Error(`No loader registered for extension: "${info.extension}"`); + } + + const loader = this.resolve(rule.loader); + return loader.execute(info); + } + + executeSync(info: LocatorInfo) : any { + const rule = this.findRule(info); + if (!rule) { + throw new Error(`No loader registered for extension: ${info.extension}`); + } -export function loadFileSync(input: LocatorInfo | string) : unknown { - let info : LocatorInfo; - if (typeof input === 'string') { - info = pathToLocatorInfo(input); - } else { - info = input; + const loader = this.resolve(rule.loader); + return loader.executeSync(info); } - if (info.extension === '.json') { - return loadJsonFileSync(info); + findRule(info: LocatorInfo) : Rule | undefined { + for (let i = 0; i < this.rules.length; i++) { + const { test } = this.rules[i] as Rule; + if (Array.isArray(test)) { + if (test.indexOf(info.extension) !== -1) { + return this.rules[i]; + } + } else if (test.test(buildLoaderFilePath(info))) { + return this.rules[i]; + } + } + + return undefined; } - return loadScriptFileSync(info); + /** + * Resolve loader by id. + * + * @param id + */ + resolve(id: string | Loader) : Loader { + if (typeof id !== 'string') { + return id; + } + + if (this.loaders[id]) { + return this.loaders[id] as Loader; + } + + let loader : Loader | undefined; + + switch (id) { + case 'script': { + loader = new ScriptLoader(); + break; + } + case 'json': { + loader = new JSONLoader(); + break; + } + } + + if (typeof loader !== 'undefined') { + return loader; + } + + throw new Error(`The loader ${id} could not be resolved.`); + } } diff --git a/src/loader/singleton.ts b/src/loader/singleton.ts new file mode 100644 index 00000000..546df536 --- /dev/null +++ b/src/loader/singleton.ts @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2023. + * Author Peter Placzek (tada5hi) + * For the full copyright and license information, + * view the LICENSE file that was distributed with this source code. + */ + +import { LoaderManager } from './module'; + +let instance : LoaderManager; +export function useLoaderManager() { + if (typeof instance !== 'undefined') { + return instance; + } + + instance = new LoaderManager(); + + return instance; +} diff --git a/src/loader/type.ts b/src/loader/type.ts new file mode 100644 index 00000000..16e58dae --- /dev/null +++ b/src/loader/type.ts @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2023. + * Author Peter Placzek (tada5hi) + * For the full copyright and license information, + * view the LICENSE file that was distributed with this source code. + */ + +import { LocatorInfo } from '../locator'; + +export type Loader = { + execute: (info: LocatorInfo) => Promise, + executeSync: (info: LocatorInfo) => any +}; + +export type Rule = { + test: RegExp | string[], + loader: Loader | string +}; diff --git a/src/locator/async.ts b/src/locator/async.ts index f1a450c6..b7a69a67 100644 --- a/src/locator/async.ts +++ b/src/locator/async.ts @@ -42,10 +42,18 @@ export async function locateFiles( return items; } +export async function locateFile( + pattern: string | string[], + options: Partial & { soft: false } +) : Promise; export async function locateFile( pattern: string | string[], options?: Partial, -) : Promise { +) : Promise; +export async function locateFile( + pattern: string | string[], + options?: Partial, +) : Promise { options = buildLocatorOptions(options); const patterns = Array.isArray(pattern) ? @@ -68,5 +76,9 @@ export async function locateFile( } } + if (!options.soft) { + throw new Error('The file could not be located.'); + } + return undefined; } diff --git a/src/locator/type.ts b/src/locator/type.ts index ba8a562d..2d669ea2 100644 --- a/src/locator/type.ts +++ b/src/locator/type.ts @@ -8,10 +8,11 @@ export type LocatorInfo = { path: string, name: string, - extension: '.js' | '.cjs' | '.mjs' | '.ts' | '.json' | string + extension: '.js' | '.cjs' | '.mjs' | '.ts' | '.json' | string, }; export type LocatorOptions = { path: string | string[], - ignore: string | string[] + ignore: string | string[], + soft: boolean }; diff --git a/src/locator/utils.ts b/src/locator/utils.ts index 28cf5657..5b338438 100644 --- a/src/locator/utils.ts +++ b/src/locator/utils.ts @@ -18,6 +18,7 @@ export function buildLocatorOptions(options?: Partial) : Locator options.path.push(process.cwd()); } + options.soft ??= true; options.ignore ??= []; return options as LocatorOptions; diff --git a/test/data/file.mjs b/test/data/file.mjs new file mode 100644 index 00000000..370d6b4c --- /dev/null +++ b/test/data/file.mjs @@ -0,0 +1,8 @@ +/* + * Copyright (c) 2023. + * Author Peter Placzek (tada5hi) + * For the full copyright and license information, + * view the LICENSE file that was distributed with this source code. + */ + +export const foo = 'bar'; diff --git a/test/unit/loader.spec.ts b/test/unit/loader.spec.ts index 2ec9e1d1..0b83f853 100644 --- a/test/unit/loader.spec.ts +++ b/test/unit/loader.spec.ts @@ -7,142 +7,156 @@ import path from "path"; import { - buildLoaderFilePath, + getExportItem, loadFile, loadFileSync, - loadScriptFile, - loadScriptFileExport, - loadScriptFileExportSync, - loadScriptFileSync, - locateFile, - locateFileSync, - LocatorInfo } from "../../src"; const basePath = path.join(__dirname, '..', 'data'); describe('src/loader/**', () => { - it('should load .js file', async () => { - let locatorInfo = await locateFile( 'file.js', {path: [basePath]}); - expect(locatorInfo).toBeDefined(); + it('should load .mjs file', async () => { + const filePath = path.join(basePath, 'file.mjs'); - let loaderContent : Record; + const loaderContent = await loadFile(filePath) as Record; + expect(loaderContent).toBeDefined(); + expect(loaderContent.foo).toEqual('bar'); + }); - if(locatorInfo) { - loaderContent = await loadFile(locatorInfo) as Record; - expect(loaderContent).toBeDefined(); - expect(loaderContent.default).toBeDefined(); - expect(loaderContent.foo).toEqual('bar'); + it('should load .mjs file sync', () => { + const filePath = path.join(basePath, 'file.mjs'); + const loaderContent = loadFileSync(filePath) as Record; + expect(loaderContent).toBeDefined(); + expect(loaderContent.foo).toEqual('bar'); + }) - loaderContent = await loadFile(buildLoaderFilePath(locatorInfo)) as Record; - expect(loaderContent).toBeDefined(); + it('should load .mjs file with default export', async () => { + const filePath = path.join(basePath, 'file-default.mjs'); - loaderContent = await loadScriptFileExport(locatorInfo) as Record; - expect(loaderContent).toBeDefined(); - expect(loaderContent.key).toEqual('default'); - expect(loaderContent.value).toEqual({foo: 'bar'}); + const loaderContent = await loadFile(filePath) as Record; + expect(loaderContent.default).toBeDefined(); + expect(loaderContent.default.foo).toEqual('bar'); + }); - loaderContent = await loadScriptFile(buildLoaderFilePath(locatorInfo)) as Record; - expect(loaderContent).toBeDefined(); - } - // -------------------------------------------------------------------- + it('should load .mjs file with default export sync', async () => { + const filePath = path.join(basePath, 'file-default.mjs'); - locatorInfo = locateFileSync('file.js', {path: [basePath]}); - expect(locatorInfo).toBeDefined(); + const loaderContent = loadFileSync(filePath) as Record; + expect(loaderContent.default).toBeDefined(); + expect(loaderContent.default.foo).toEqual('bar'); + }); - if(locatorInfo) { - loaderContent = loadFileSync(locatorInfo) as Record; - expect(loaderContent).toBeDefined(); - expect(loaderContent.default).toBeUndefined(); - expect(loaderContent.foo).toEqual('bar'); + it('should load .js file', async () => { + const filePath = path.join(basePath, 'file.js'); - loaderContent = loadFileSync(buildLoaderFilePath(locatorInfo)) as Record; - expect(loaderContent).toBeDefined(); + const loaderContent = await loadFile(filePath) as Record; + expect(loaderContent).toBeDefined(); + expect(loaderContent.foo).toEqual('bar'); + }); - loaderContent = loadScriptFileExportSync(locatorInfo) as Record; - expect(loaderContent).toBeDefined(); - expect(loaderContent.key).toEqual('default'); - expect(loaderContent.value).toEqual({foo: 'bar'}); + it('should load .js file sync', () => { + const filePath = path.join(basePath, 'file.js'); - loaderContent = loadScriptFileSync(buildLoaderFilePath(locatorInfo)) as Record; - expect(loaderContent).toBeDefined(); - } + const loaderContent = loadFileSync(filePath) as Record; + expect(loaderContent).toBeDefined(); + expect(loaderContent.default).toBeUndefined(); + expect(loaderContent.foo).toEqual('bar'); + }); + + + it('should load .js file with named export', async () => { + const filePath = path.join(basePath, 'file.js'); + + let loaderContent = await loadFile(filePath) as Record; + loaderContent = getExportItem(loaderContent); + expect(loaderContent).toBeDefined(); + expect(loaderContent.key).toEqual('default'); + expect(loaderContent.value).toEqual({foo: 'bar'}); + }) + + it('should load .js file with named export sync', () => { + const filePath = path.join(basePath, 'file.js'); + + let loaderContent = loadFileSync(filePath) as Record; + loaderContent = getExportItem(loaderContent); + expect(loaderContent).toBeDefined(); + expect(loaderContent.key).toEqual('default'); + expect(loaderContent.value).toEqual({foo: 'bar'}); }); it('should load .ts file', async () => { - let locatorInfo = await locateFile( 'file-ts.ts', {path: [basePath]}); - expect(locatorInfo).toBeDefined(); + const filePath = path.join(basePath, 'file-ts.ts'); - let loaderContent; + let loaderContent = await loadFile(filePath) as Record; + expect(loaderContent).toBeDefined(); + expect(loaderContent.bar).toEqual('baz'); + }); - if(locatorInfo) { - loaderContent = await loadFile(locatorInfo) as Record; - expect(loaderContent).toBeDefined(); - expect(loaderContent.default).toBeDefined(); - expect(loaderContent.bar).toEqual('baz'); + it('should load .ts file', async () => { + const filePath = path.join(basePath, 'file-ts.ts'); - loaderContent = await loadScriptFileExport(locatorInfo) as Record; - expect(loaderContent).toBeDefined(); - expect(loaderContent.key).toEqual('default'); - expect(loaderContent.value).toEqual({bar: 'baz'}); - } + let loaderContent = await loadFile(filePath) as Record; + loaderContent = getExportItem(loaderContent); + expect(loaderContent).toBeDefined(); + expect(loaderContent.key).toEqual('default'); + expect(loaderContent.value).toEqual({bar: 'baz'}); + }); - // -------------------------------------------------------------------- + it('should load .ts file sync', () => { + const filePath = path.join(basePath, 'file-ts.ts'); - locatorInfo = locateFileSync( 'file-ts.ts', {path: [basePath]}); - expect(locatorInfo).toBeDefined(); + let loaderContent = loadFileSync(filePath) as Record; + expect(loaderContent).toBeDefined(); + expect(loaderContent.bar).toEqual('baz'); + }); - if(locatorInfo) { - loaderContent = loadFileSync(locatorInfo) as Record; - expect(loaderContent).toBeDefined(); - expect(loaderContent.bar).toEqual('baz'); + it('should load .ts file with named export sync', () => { + const filePath = path.join(basePath, 'file-ts.ts'); - loaderContent = loadScriptFileExportSync(locatorInfo) as Record;; - expect(loaderContent).toBeDefined(); - expect(loaderContent.key).toEqual('default'); - expect(loaderContent.value).toEqual({bar: 'baz'}); - } + let loaderContent = loadFileSync(filePath) as Record; + loaderContent = getExportItem(loaderContent); + expect(loaderContent).toBeDefined(); + expect(loaderContent.key).toEqual('default'); + expect(loaderContent.value).toEqual({bar: 'baz'}); }); it('should filter .ts file', async () => { - let locatorInfo = await locateFile( 'file-many-ts.ts', {path: [basePath]}); - let loaderContent : Record; + const filePath = path.join(basePath, 'file-many-ts.ts'); - if(locatorInfo) { - loaderContent = await loadScriptFileExport(locatorInfo, (key) => { - return key === 'bar'; - }) as Record; + let loaderContent = await loadFile(filePath) as Record; + loaderContent = await getExportItem(loaderContent, (key) => { + return key === 'bar'; + }) as Record; - expect(loaderContent).toBeDefined(); - expect(loaderContent.key).toEqual('bar'); - expect(loaderContent.value).toEqual('baz'); - } + expect(loaderContent).toBeDefined(); + expect(loaderContent.key).toEqual('bar'); + expect(loaderContent.value).toEqual('baz'); + }); - locatorInfo = locateFileSync( 'file-many-ts.ts', {path: [basePath]}); - expect(locatorInfo).toBeDefined(); + it('should filter .ts file sync', () => { + const filePath = path.join(basePath, 'file-many-ts.ts'); - if(locatorInfo) { - loaderContent = loadScriptFileExportSync(locatorInfo, (key) => { - return key === 'bar'; - }) as Record; - expect(loaderContent).toBeDefined(); - expect(loaderContent.key).toEqual('bar'); - expect(loaderContent.value).toEqual('baz'); - } + let loaderContent = loadFileSync(filePath) as Record; + loaderContent = getExportItem(loaderContent, (key) => { + return key === 'bar'; + }) as Record; + expect(loaderContent).toBeDefined(); + expect(loaderContent.key).toEqual('bar'); + expect(loaderContent.value).toEqual('baz'); }) it('should load .json file', async () => { - let locatorInfo = await locateFile( 'file.json', {path: [basePath]}) as LocatorInfo; - expect(locatorInfo).toBeDefined(); + const filePath = path.join(basePath, 'file.json'); - let loaderContent : Record = await loadFile(locatorInfo) as Record; + let loaderContent : Record = await loadFile(filePath) as Record; expect(loaderContent).toBeDefined(); expect(loaderContent.foo).toEqual('bar'); + }); - locatorInfo = locateFileSync( 'file.json', {path: [basePath]}) as LocatorInfo; - expect(locatorInfo).toBeDefined(); + it('should load .json file sync', () => { + const filePath = path.join(basePath, 'file.json'); - loaderContent = loadFileSync(locatorInfo) as Record; + let loaderContent = loadFileSync(filePath) as Record; expect(loaderContent).toBeDefined(); expect(loaderContent.foo).toEqual('bar'); });