Skip to content

Commit

Permalink
feat!: support explicit module type option, and support `excludeLoa…
Browse files Browse the repository at this point in the history
…ders`
  • Loading branch information
antfu committed May 18, 2024
1 parent effd742 commit ac0a7f7
Show file tree
Hide file tree
Showing 5 changed files with 426 additions and 129 deletions.
121 changes: 88 additions & 33 deletions src/detect.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,76 @@
import type { SupportedLoader } from './types'
import type { FeaturesOptions, SupportedLoader } from './types'

export interface LoaderMatrix {
name: SupportedLoader
supported?: boolean
cache: boolean[]
listDependencies: boolean[]
type: ('module' | 'commonjs')[]
importTS: boolean[]
}

export interface LoaderDetectionContext extends Required<FeaturesOptions> {
isTs: boolean
}

let _loaderMatrix: LoaderMatrix[]

/**
* Internal function for tests
*/
export async function _createLoaderMatrix(options: {
isNativeTsImportSupported: boolean
isRuntimeSupportsTsx: boolean
}) {
const matrix: LoaderMatrix[] = [
{
name: 'native',
cache: [true],
listDependencies: [false],
type: ['module', 'commonjs'],
importTS: options.isNativeTsImportSupported
? [true, false]
: [false],
},
{
name: 'tsx',
supported: options.isRuntimeSupportsTsx,
type: ['module'],
cache: [true, false],
listDependencies: [true, false],
importTS: [true, false],
},
{
name: 'jiti',
type: ['commonjs'],
cache: [true, false],
listDependencies: [true, false],
importTS: [true, false],
},
{
name: 'bundle-require',
type: ['commonjs', 'module'],
cache: [false],
listDependencies: [true],
importTS: [true, false],
},
]

return matrix
.filter(i => i.supported !== false)
}

export async function getLoaderMatrix(): Promise<LoaderMatrix[]> {
if (_loaderMatrix)
return _loaderMatrix

_loaderMatrix = await _createLoaderMatrix({
isNativeTsImportSupported: await isNativeTsImportSupported(),
isRuntimeSupportsTsx: isRuntimeSupportsTsx(),
})

return _loaderMatrix
}

let _isNativeTsImportSupported: boolean | undefined
/**
Expand Down Expand Up @@ -27,41 +99,24 @@ const nodeVersionNumbers = globalThis?.process?.versions?.node?.split('.').map(N
* @private
*/
export async function detectLoader(
cache: boolean | null,
listDependencies: boolean,
isTsFile: boolean,
): Promise<SupportedLoader> {
return detectLoaderLogic(
cache,
listDependencies,
isTsFile,
await isNativeTsImportSupported(),
isRuntimeSupportsTsx(),
)
}
context: LoaderDetectionContext,
matrix?: LoaderMatrix[],
): Promise<SupportedLoader | null> {
matrix = matrix || await getLoaderMatrix()

export function detectLoaderLogic(
cache: boolean | null,
listDependencies: boolean,
isTsFile: boolean,
isNativeTsImportSupported: boolean,
isTsxSupported: boolean,
): SupportedLoader {
function getLoader() {
if (isTsxSupported)
return 'tsx'
if (listDependencies)
return 'bundle-require'
return 'jiti'
for (const loader of matrix) {
if (context.excludeLoaders?.includes(loader.name))
continue
if (
(context.cache === null || loader.cache.includes(context.cache))
&& (context.listDependencies === null || loader.listDependencies.includes(context.listDependencies))
&& (context.type === null || loader.type.includes(context.type))
&& loader.importTS.includes(context.isTs)
)
return loader.name
}

if (cache === false || listDependencies)
return getLoader()

if (!isTsFile || isNativeTsImportSupported)
return 'native'

return getLoader()
return null
}

/**
Expand Down
22 changes: 18 additions & 4 deletions src/import.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { fileURLToPath, pathToFileURL } from 'node:url'
import { dirname, join } from 'node:path'
import Debug from 'debug'
import type { ImportxModuleInfo, ImportxOptions } from './types'
import type { LoaderDetectionContext } from './detect'
import { detectLoader, isTypeScriptFile } from './detect'

const debug = Debug('importx')
Expand Down Expand Up @@ -38,7 +39,9 @@ export async function importx<T = any>(_specifier: string | URL, _options: strin
loaderOptions = {},
parentURL: inputUserUrl,
cache = null,
listDependencies = false,
type = null,
excludeLoaders = [],
listDependencies = null,
ignoreImportxWarning = false,
...otherOptions
} = options
Expand All @@ -53,8 +56,19 @@ export async function importx<T = any>(_specifier: string | URL, _options: strin
: specifier

let loader = options.loader || 'auto'
if (loader === 'auto')
loader = await detectLoader(cache, listDependencies, isTypeScriptFile(specifier))
if (loader === 'auto') {
const context: LoaderDetectionContext = {
cache,
listDependencies,
type,
isTs: isTypeScriptFile(specifier),
excludeLoaders,
}
const _loader = await detectLoader(context)
if (!_loader)
throw new Error(`[importx] Cannot find a suitable loader for given requirements ${JSON.stringify(context)}`)
loader = _loader
}

const parentPath = (typeof inputUserUrl === 'string' && !inputUserUrl.includes('://'))
? inputUserUrl
Expand Down Expand Up @@ -103,7 +117,7 @@ export async function importx<T = any>(_specifier: string | URL, _options: strin
const cwd = dirname(parentPath)
return import('bundle-require')
.then(r => r.bundleRequire({
format: 'esm',
format: type === 'commonjs' ? 'cjs' : 'esm',
...loaderOptions.bundleRequire,
filepath: fullPath,
cwd,
Expand Down
88 changes: 54 additions & 34 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,38 +2,7 @@ type ArgumentTypes<T> = T extends (...args: infer U) => any ? U : never

export type SupportedLoader = 'tsx' | 'jiti' | 'bundle-require' | 'native'

export interface ImportxOptions {
/**
* Loader to use for importing the file.
* @default 'auto'
*/
loader?: SupportedLoader | 'auto'
/**
* Options for each loader
* Only the loader that is used will be applied.
*/
loaderOptions?: {
/**
* Options for `tsx` loader.
*
* @see https://tsx.is/node/ts-import
*/
tsx?: Omit<Partial<Exclude<ArgumentTypes<typeof import('tsx/esm/api').tsImport>['1'], string>>, 'parentURL'>
/**
* Options for `jiti` loader.
*
* @default { esmResolve: true }
* @see https://github.com/unjs/jiti#options
*/
jiti?: import('jiti').JITIOptions
/**
* Options for `bundle-require` loader.
*
* @see https://github.com/egoist/bundle-require
* @see https://www.jsdocs.io/package/bundle-require#Options
*/
bundleRequire?: Omit<Partial<import('bundle-require').Options>, 'filepath' | 'cwd'>
}
export interface FeaturesOptions {
/**
* Whether to cache the imported module.
*
Expand Down Expand Up @@ -64,9 +33,60 @@ export interface ImportxOptions {
* Affects `auto` resolution:
* - When set to `true`, `jiti` will be fallback to `bundle-require`
*
* @default false
* Setting to `null` means it doesn't matter for you.
*
* @default null
*/
listDependencies?: boolean | null
/**
* Whether module resolution and evaluation type
*
* Setting to `null` means it doesn't matter for you.
*
* @default null
*/
type?: 'module' | 'commonjs' | null

/**
* Exclude loaders from being used.
*
* Only affects `auto` resolution.
*/
excludeLoaders?: SupportedLoader[]
}

export interface ImportxOptions extends FeaturesOptions {
/**
* Loader to use for importing the file.
* @default 'auto'
*/
loader?: SupportedLoader | 'auto'
/**
* Options for each loader
* Only the loader that is used will be applied.
*/
listDependencies?: boolean
loaderOptions?: {
/**
* Options for `tsx` loader.
*
* @see https://tsx.is/node/ts-import
*/
tsx?: Omit<Partial<Exclude<ArgumentTypes<typeof import('tsx/esm/api').tsImport>['1'], string>>, 'parentURL'>
/**
* Options for `jiti` loader.
*
* @default { esmResolve: true }
* @see https://github.com/unjs/jiti#options
*/
jiti?: import('jiti').JITIOptions
/**
* Options for `bundle-require` loader.
*
* @see https://github.com/egoist/bundle-require
* @see https://www.jsdocs.io/package/bundle-require#Options
*/
bundleRequire?: Omit<Partial<import('bundle-require').Options>, 'filepath' | 'cwd'>
}
/**
* Bypass the `importx` options validation and import anyway.
*
Expand Down
Loading

0 comments on commit ac0a7f7

Please sign in to comment.