Skip to content

Commit

Permalink
feat(module): improve Module.resolve()
Browse files Browse the repository at this point in the history
  • Loading branch information
jlenon7 committed Oct 5, 2023
1 parent 815024a commit 624aa0d
Show file tree
Hide file tree
Showing 6 changed files with 234 additions and 43 deletions.
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@athenna/common",
"version": "4.17.1",
"version": "4.17.2",
"description": "The Athenna common helpers to use in any Node.js ESM project.",
"license": "MIT",
"author": "João Lenon <[email protected]>",
Expand All @@ -22,8 +22,8 @@
"scripts": {
"build": "node node_modules/@athenna/tsconfig/src/build.js",
"lint:fix": "eslint \"{bin,src,tests}/**/*.ts\" --fix",
"test": "npm run --silent lint:fix && node --import=@athenna/tsconfig bin/test.ts",
"test:debug": "cross-env NODE_DEBUG=athenna:* node --inspect --import=@athenna/tsconfig bin/test.ts",
"test": "npm run --silent lint:fix && node --enable-source-maps --import=@athenna/tsconfig bin/test.ts",
"test:debug": "cross-env NODE_DEBUG=athenna:* node --inspect --enable-source-maps --import=@athenna/tsconfig bin/test.ts",
"test:coverage": "c8 npm run --silent test"
},
"files": [
Expand Down
99 changes: 73 additions & 26 deletions src/helpers/Module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,16 @@
*/

import { debug } from '#src/debug'
import { Path, File, Folder } from '#src'
import { createRequire } from 'node:module'
import { fileURLToPath, pathToFileURL } from 'node:url'
import { extname, dirname, resolve, isAbsolute } from 'node:path'
import { dirname, extname, isAbsolute } from 'node:path'
import { Path, File, Folder, type ModuleResolveOptions, Options } from '#src'

export class Module {
/**
* Get the module first export match or default.
*/
public static async get(module: any | Promise<any>): Promise<any> {
public static async get<T = any>(module: any | Promise<any>): Promise<T> {
module = await module

if (module.default) {
Expand Down Expand Up @@ -47,10 +47,10 @@ export class Module {
* console.log(alias) // 'App/Services/MyService'
* console.log(module) // [class MyService]
*/
public static async getWithAlias(
public static async getWithAlias<T = any>(
module: any | Promise<any>,
subAlias: string
): Promise<{ alias: string; module: any }> {
): Promise<{ alias: string; module: T }> {
module = await Module.get(module)

if (!subAlias.endsWith('/')) {
Expand Down Expand Up @@ -96,7 +96,7 @@ export class Module {
/**
* Same as get method, but import the path directly.
*/
public static async getFrom(path: string): Promise<any> {
public static async getFrom<T = any>(path: string): Promise<T> {
const module = await Module.import(path)

return Module.get(module)
Expand All @@ -105,10 +105,10 @@ export class Module {
/**
* Same as getWithAlias method, but import the path directly.
*/
public static async getFromWithAlias(
public static async getFromWithAlias<T = any>(
path: string,
subAlias: string
): Promise<{ alias: string; module: any }> {
): Promise<{ alias: string; module: T }> {
const module = await Module.import(path)

return Module.getWithAlias(module, subAlias)
Expand Down Expand Up @@ -160,7 +160,7 @@ export class Module {
* Import a full path using the path href to ensure compatibility
* between OS's.
*/
public static async import(path: string): Promise<any> {
public static async import<T = any>(path: string): Promise<T> {
debug('trying to import the path: %s', path)

if (!isAbsolute(path)) {
Expand All @@ -175,7 +175,7 @@ export class Module {
* module does not exist, catching the error throw from bad
* import.
*/
public static async safeImport(path: string): Promise<any | null> {
public static async safeImport<T = any>(path: string): Promise<T | null> {
try {
return await Module.import(path)
} catch (err) {
Expand All @@ -184,40 +184,87 @@ export class Module {
}

/**
* Resolve the module path by meta url and import it.
* Resolve the module path by parent URL.
*/
public static async resolve(path: string, meta: string): Promise<any> {
public static async resolve<T = any>(
path: string,
parentURL: string,
options: ModuleResolveOptions = {}
): Promise<T> {
options = Options.create(options, {
import: true,
getModule: true
})

const splitted = path.split('?')
const queries = splitted[1] || ''

path = splitted[0]

if (!path.startsWith('#') && extname(path)) {
path = resolve(path)
const resolve = async (path: string) => {
if (queries) {
path = path.concat('?', queries)
}

if (!options.import) {
return path
}

if (!options.getModule) {
return import(path)
}

return Module.get(import(path))
}

if (isAbsolute(path)) {
path = pathToFileURL(path).href
debug(
"path is absolute and don't need to be resolved, importing path: %s and query params: %s",
path,
queries
)

return resolve(pathToFileURL(path).href)
}

if (!path.startsWith('#') && extname(path)) {
debug(
'trying to resolve relative path: %s, with parent URL: %s and query params: %s',
path,
parentURL,
queries
)

return resolve(new URL(path, parentURL).href)
}

if (process.argv.includes('--experimental-import-meta-resolve')) {
debug(
'trying to resolve import alias path: %s with parent URL: %s and query params: %s using import.meta.resolve',
path,
parentURL,
queries
)

return resolve(await import.meta.resolve(path, parentURL))
}

const require = Module.createRequire(parentURL)

debug(
'trying to resolve path: %s, with parent URL: %s and query params: %s',
'trying to resolve import alias path: %s with parent URL: %s and query params: %s using require.resolve',
path,
meta,
parentURL,
queries
)

// `await` is not needed for `import.meta.resolve` method,
// but TypeScript complains on it.
let resolvedPath = await import.meta.resolve(path, meta)

debug('resolved path: %s', resolvedPath)

if (queries) {
resolvedPath = resolvedPath.concat('?', queries)
try {
path = require.resolve(path)
} catch (error) {
path = error.message.match(/'(.*?)'/)[1]
}

return Module.get(import(resolvedPath))
return resolve(pathToFileURL(path).href)
}

/**
Expand Down
22 changes: 22 additions & 0 deletions src/types/ModuleResolveOptions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/**
* @athenna/common
*
* (c) João Lenon <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

export type ModuleResolveOptions = {
/**
* Automatically import the module instead of returning
* the module path.
*/
import?: boolean

/**
* Automatically get the imported module using `Module.get()`
* method.
*/
getModule?: boolean
}
1 change: 1 addition & 0 deletions src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export * from '#src/types/json/FileJson'
export * from '#src/types/json/FolderJson'
export * from '#src/types/json/ExceptionJson'

export * from '#src/types/ModuleResolveOptions'
export * from '#src/types/pagination/PaginationOptions'
export * from '#src/types/pagination/PaginatedResponse'

Expand Down
Loading

0 comments on commit 624aa0d

Please sign in to comment.