Skip to content

Commit

Permalink
feat: support for vite.config
Browse files Browse the repository at this point in the history
  • Loading branch information
9romise committed Jan 13, 2025
1 parent 0e1c1be commit a15359d
Show file tree
Hide file tree
Showing 13 changed files with 283 additions and 22 deletions.
9 changes: 7 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "eslint-import-resolver-oxc",
"type": "module",
"version": "0.8.0",
"packageManager": "[email protected].2",
"packageManager": "[email protected].3",
"description": "A simply wrapped `oxc-resolver` for `eslint-plugin-import-x` and `eslint-plugin-import`",
"author": "Vida Xie<https://github.com/9romise>",
"license": "MIT",
Expand Down Expand Up @@ -45,14 +45,18 @@
"peerDependencies": {
"eslint": "*",
"eslint-plugin-import": "*",
"eslint-plugin-import-x": "*"
"eslint-plugin-import-x": "*",
"vite": "*"
},
"peerDependenciesMeta": {
"eslint-plugin-import": {
"optional": true
},
"eslint-plugin-import-x": {
"optional": true
},
"vite": {
"optional": true
}
},
"dependencies": {
Expand All @@ -61,6 +65,7 @@
"devDependencies": {
"@types/node": "^22.10.4",
"@vida0905/eslint-config": "^1.2.0",
"es-toolkit": "^1.31.0",
"eslint": "^9.17.0",
"eslint-vitest-rule-tester": "^0.7.1",
"lint-staged": "^15.3.0",
Expand Down
62 changes: 62 additions & 0 deletions pnpm-lock.yaml

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

51 changes: 51 additions & 0 deletions src/bundler/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import type { BundlerConfigTransformer, BundlerOption, SupportedBundler } from '@/typings'
import { basename, resolve } from 'node:path'
import { cwd } from 'node:process'
import { detectFile, log } from '@/utils'
import vite from './vite'

const bundlerConfig: Record<SupportedBundler, BundlerConfigTransformer> = {
vite,
}

export async function getBundlerConfig(options?: BundlerOption | null) {
if (options == null)
return {}

if (typeof options === 'string') {
options = {
path: options,
}
}

let { type, path } = options

if (!type && !path) {
log('either `type` or `path` must be specified')
return {}
}

if (!type) {
type = basename(path!).split('.')[0] as SupportedBundler
}

const config = bundlerConfig[type]

if (!config) {
log(`"${type}" config is not supported now`)
return {}
}

if (!path) {
const { filename, extensions } = config
path = detectFile(extensions.map((ext) => `${filename}.${ext}`))
}

if (!path) {
log(`cannot find "${type}" config`)
return {}
}

path = resolve(cwd(), path)
return await config.transformConfig(path)
}
42 changes: 42 additions & 0 deletions src/bundler/vite.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import type { BundlerConfigTransformer } from '@/typings'
import type { NapiResolveOptions } from 'oxc-resolver'
import type { Alias } from 'vite'
import { loadConfigFromFile } from 'vite'

export async function transformViteConfig(path: string): Promise<NapiResolveOptions> {
const { config } = await loadConfigFromFile({ command: 'build', mode: 'production' }, path) || {}

if (!config?.resolve)
return {}

const { alias } = config.resolve

const resolvedAlias: NapiResolveOptions['alias'] = {}

if (Array.isArray(alias)) {
const _alias = alias as Alias[]
_alias.forEach(({ find, replacement }) => {
if (typeof find === 'string')
resolvedAlias[find] = [replacement]
})
} else if (alias) {
const _alias = alias as { [find: string]: string }
for (const find in _alias) {
resolvedAlias[find] = [_alias[find]]
}
}

return {
alias: resolvedAlias,
mainFields: config.resolve.mainFields,
extensions: config.resolve.extensions,
conditionNames: config.resolve.conditions,
symlinks: config.resolve.preserveSymlinks,
}
}

export default {
filename: 'vite.config',
extensions: ['ts', 'js', 'mts', 'mjs', 'cts', 'cjs'],
transformConfig: transformViteConfig,
} as BundlerConfigTransformer
29 changes: 25 additions & 4 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
import type { NapiResolveOptions } from 'oxc-resolver'
import type { ImportResolver, OxcResolverOptions } from './typings'
import { isBuiltin } from 'node:module'
import { dirname } from 'node:path'
import { ResolverFactory } from 'oxc-resolver'
import { normalizeOptions } from './normalizeOptions'
import { getBundlerConfig } from './bundler'
import { mergeOptions, normalizeOptions } from './normalizeOptions'
import { hashObject } from './utils'

export { transformViteConfig } from './bundler/vite'
export { mergeOptions } from './normalizeOptions'

let cachedOptionsHash: string | undefined
let cachedResolver: ResolverFactory | undefined

export function resolve(source: string, file: string, options?: NapiResolveOptions | null, resolver: ResolverFactory | null = null): { found: boolean, path: string | null | undefined } {
export function resolve(source: string, file: string, options?: OxcResolverOptions | null, resolver: ResolverFactory | null = null): { found: boolean, path: string | null | undefined } {
if (isBuiltin(source))
return { found: true, path: null }

Expand Down Expand Up @@ -36,8 +41,24 @@ export function resolve(source: string, file: string, options?: NapiResolveOptio

export const interfaceVersion = 2

export function createOxcImportResolver(options?: NapiResolveOptions | null) {
const resolver = new ResolverFactory(normalizeOptions(options))
export function createOxcImportResolver(options?: NapiResolveOptions): ImportResolver
export function createOxcImportResolver(options?: OxcResolverOptions): Promise<ImportResolver>
export function createOxcImportResolver(options?: OxcResolverOptions | NapiResolveOptions) {
if (options && Object.prototype.hasOwnProperty.call(options, 'bundlerConfig')) {
return new Promise((resolve) => {
getBundlerConfig((options as OxcResolverOptions).bundlerConfig).then((bundlerOptions) => {
const resolver = createResolver(mergeOptions(options, bundlerOptions))
resolve(resolver)
})
})
} else {
return createResolver(options)
}
}

function createResolver(options?: OxcResolverOptions) {
const resolvedOptions = normalizeOptions(options)
const resolver = new ResolverFactory(resolvedOptions)

return {
interfaceVersion: 3,
Expand Down
29 changes: 17 additions & 12 deletions src/normalizeOptions.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import type { NapiResolveOptions } from 'oxc-resolver'
import fs from 'node:fs'
import path from 'node:path'
import type { OxcResolverOptions } from './typings'
import { cwd } from 'node:process'
import { isPlainObject, mergeWith } from 'es-toolkit'
import { detectFile } from './utils'

// @keep-sorted
/**
* some default options copy from
* https://github.com/import-js/eslint-import-resolver-typescript/blob/master/src/index.ts
* https://github.com/rolldown/rolldown/blob/main/crates/rolldown_resolver/src/resolver.rs
*/
const defaultOptions: NapiResolveOptions = {
const defaultOptions: OxcResolverOptions = {
aliasFields: [
['browser'],
],
Expand Down Expand Up @@ -68,26 +68,31 @@ const defaultOptions: NapiResolveOptions = {
roots: [cwd()],
}

function findConfigFile(files: string[]) {
while (files.length) {
const file = files.shift()!
const absPath = path.resolve(cwd(), file)
if (fs.existsSync(absPath)) {
return absPath
export function mergeOptions(a?: OxcResolverOptions | null, b?: OxcResolverOptions | null): OxcResolverOptions {
a ??= {}
b ??= {}

function mergeFunc(objVal: any, srcVal: any) {
if (Array.isArray(objVal) || Array.isArray(srcVal)) {
return objVal.concat(srcVal)
} else if (isPlainObject(objVal) && isPlainObject(srcVal)) {
return mergeWith(objVal, srcVal, mergeFunc)
}
}
return mergeWith(a, b, mergeFunc)
}

export function normalizeOptions(options: NapiResolveOptions | null = {}): NapiResolveOptions {
export function normalizeOptions(options: OxcResolverOptions = {}): OxcResolverOptions {
if (!options?.tsconfig) {
const configFile = findConfigFile(['tsconfig.json', 'jsconfig.json'])
const configFile = detectFile(['tsconfig.json', 'jsconfig.json'])
if (configFile) {
defaultOptions.tsconfig = {
configFile,
references: 'auto',
}
}
}

return {
...defaultOptions,
...options,
Expand Down
25 changes: 25 additions & 0 deletions src/typings.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import type { NapiResolveOptions } from 'oxc-resolver'

export interface ImportResolver {
interfaceVersion: 3
name: string
resolve: (source: string, file: string) => { found: boolean, path: string | null | undefined }
}

export type SupportedBundler = 'vite'

export type BundlerOption = string | {
type?: SupportedBundler
path?: string
}

export interface BundlerConfigTransformer {
filename: string
extensions: string[]
transformConfig: (conf: any) => Promise<NapiResolveOptions>
}

export interface OxcResolverOptions extends NapiResolveOptions {
/** @experimental detect bundler's resolve config */
bundlerConfig?: BundlerOption
}
17 changes: 17 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import type { NapiResolveOptions } from 'oxc-resolver'
import { createHash } from 'node:crypto'
import { existsSync } from 'node:fs'
import { resolve } from 'node:path'
import { cwd } from 'node:process'

export const hashCache = new WeakMap<NapiResolveOptions, string>()

Expand Down Expand Up @@ -29,3 +32,17 @@ export function hashObject(obj: NapiResolveOptions): string {
hashCache.set(obj, hash)
return hash
}

export function detectFile(files: string[]) {
for (const file of files) {
const absPath = resolve(cwd(), file)
if (existsSync(absPath)) {
return absPath
}
}
}

export function log(...args: any[]) {
// eslint-disable-next-line no-console
return console.log('[eslint-import-resolver-oxc]: ', ...args)
}
Loading

0 comments on commit a15359d

Please sign in to comment.