Skip to content

Commit

Permalink
fix: patch entry alias
Browse files Browse the repository at this point in the history
closes #34
  • Loading branch information
sxzz committed Nov 21, 2024
1 parent 219231c commit c27fe46
Show file tree
Hide file tree
Showing 12 changed files with 260 additions and 75 deletions.
47 changes: 47 additions & 0 deletions src/core/ast.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import path from 'node:path'
import MagicString from 'magic-string'
import { debug } from './utils'
import type * as OxcTypes from '@oxc-project/types'

export type OxcImport = (
| OxcTypes.ImportDeclaration
| OxcTypes.ExportAllDeclaration
| OxcTypes.ExportNamedDeclaration
) & { source: OxcTypes.StringLiteral }

export function filterImports(program: OxcTypes.Program): OxcImport[] {
return program.body.filter(
(node): node is OxcImport =>
(node.type === 'ImportDeclaration' ||
node.type === 'ExportAllDeclaration' ||
node.type === 'ExportNamedDeclaration') &&
!!node.source,
)
}

export function patchEntryAlias(
source: string,
s: MagicString | undefined,
imports: OxcImport[],
outname: string,
offset: string,
): string {
debug('Patching entry alias:', outname, 'offset:', offset)

s ||= new MagicString(source)
for (const i of imports) {
if (i.source.value[0] === '.') {
s.overwrite(
i.source.start + 1,
i.source.end - 1,
`./${path.join(offset, i.source.value)}`,
)
}
}

if (s.hasChanged()) {
debug('Patched entry alias:', outname)
return s.toString()
}
return source
}
1 change: 0 additions & 1 deletion src/core/types.ts

This file was deleted.

21 changes: 21 additions & 0 deletions src/core/utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import path from 'node:path'
import Debug from 'debug'

export const debug: Debug.Debugger = Debug('unplugin-isolated-decl')

export function lowestCommonAncestor(...filepaths: string[]): string {
if (filepaths.length === 0) return ''
Expand Down Expand Up @@ -28,3 +31,21 @@ export function lowestCommonAncestor(...filepaths: string[]): string {
export function stripExt(filename: string): string {
return filename.replace(/\.(.?)[jt]sx?$/, '')
}

export function resolveEntry(input: string[] | Record<string, string>): {
map: Record<string, string> | undefined
outBase: string
} {
const map = !Array.isArray(input)
? Object.fromEntries(
Object.entries(input).map(([k, v]) => [
path.resolve(stripExt(v as string)),
k,
]),
)
: undefined
const arr = Array.isArray(input) && input ? input : Object.values(input)
const outBase = lowestCommonAncestor(...arr)

return { map, outBase }
}
147 changes: 78 additions & 69 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import { mkdir, readFile, writeFile } from 'node:fs/promises'
import path from 'node:path'
import { createFilter } from '@rollup/pluginutils'
import Debug from 'debug'
import MagicString from 'magic-string'
import { parseAsync } from 'oxc-parser'
import {
Expand All @@ -15,14 +14,20 @@ import {
type UnpluginContext,
type UnpluginInstance,
} from 'unplugin'
import { filterImports, patchEntryAlias, type OxcImport } from './core/ast'
import { resolveOptions, type Options } from './core/options'
import {
oxcTransform,
swcTransform,
tsTransform,
type TransformResult,
} from './core/transformer'
import { lowestCommonAncestor, stripExt } from './core/utils'
import {
debug,
lowestCommonAncestor,
resolveEntry,
stripExt,
} from './core/utils'
import type {
JsPlugin,
NormalizedConfig,
Expand All @@ -37,11 +42,13 @@ import type {
PluginContext,
} from 'rollup'

const debug = Debug('unplugin-isolated-decl')

export type { Options }

export type * from './core/types'
interface Output {
source: string
imports: OxcImport[]
s?: MagicString
}

/**
* The main unplugin instance.
Expand All @@ -52,9 +59,12 @@ export const IsolatedDecl: UnpluginInstance<Options | undefined, false> =
const filter = createFilter(options.include, options.exclude)

let farmPluginContext: UnpluginBuildContext
const outputFiles: Record<string, string> = {}
function addOutput(filename: string, source: string) {
outputFiles[stripExt(filename)] = source

const outputFiles: Record<string, Output> = {}
function addOutput(filename: string, output: Output) {
const name = stripExt(filename)
debug('Add output:', name)
outputFiles[name] = output
}

const rollup: Partial<Plugin> = {
Expand All @@ -63,6 +73,7 @@ export const IsolatedDecl: UnpluginInstance<Options | undefined, false> =
const farm: Partial<JsPlugin> = {
renderStart: { executor: farmRenderStart },
}

return {
name: 'unplugin-isolated-decl',

Expand Down Expand Up @@ -92,36 +103,6 @@ export const IsolatedDecl: UnpluginInstance<Options | undefined, false> =
code: string,
id: string,
): Promise<undefined> {
let program: OxcTypes.Program | undefined
try {
program = (await parseAsync(code, { sourceFilename: id })).program
} catch {}
if (options.autoAddExts && program) {
const imports = program.body.filter(
(node) =>
node.type === 'ImportDeclaration' ||
node.type === 'ExportAllDeclaration' ||
node.type === 'ExportNamedDeclaration',
)
const s = new MagicString(code)
for (const i of imports) {
if (!i.source || path.basename(i.source.value).includes('.')) {
continue
}

const resolved = await resolve(context, i.source.value, id)
if (!resolved || resolved.external) continue
if (resolved.id.endsWith('.ts') || resolved.id.endsWith('.tsx')) {
s.overwrite(
i.source.start,
i.source.end,
JSON.stringify(`${i.source.value}.js`),
)
}
}
code = s.toString()
}

const label = debug.enabled && `[${options.transformer}]`
debug(label, 'transform', id)

Expand All @@ -140,7 +121,7 @@ export const IsolatedDecl: UnpluginInstance<Options | undefined, false> =
(options as any).transformOptions,
)
}
const { code: sourceText, errors } = result
let { code: dts, errors } = result
debug(
label,
'transformed',
Expand All @@ -155,12 +136,28 @@ export const IsolatedDecl: UnpluginInstance<Options | undefined, false> =
return
}
}
addOutput(id, sourceText)

if (!program) {
debug('cannot parse', id)
return
const { program } = await parseAsync(dts, { sourceFilename: id })
const imports = filterImports(program)

let s: MagicString | undefined
if (options.autoAddExts) {
s = new MagicString(dts)

for (const i of imports) {
if (path.basename(i.source.value).includes('.')) continue

const resolved = await resolve(context, i.source.value, id)
if (!resolved || resolved.external) continue
if (resolved.id.endsWith('.ts') || resolved.id.endsWith('.tsx')) {
i.source.value = `${i.source.value}.js`
s.overwrite(i.source.start + 1, i.source.end - 1, i.source.value)
}
}
dts = s.toString()
}
addOutput(id, { source: dts, s, imports })

const typeImports = program.body.filter(
(
node,
Expand Down Expand Up @@ -195,7 +192,6 @@ export const IsolatedDecl: UnpluginInstance<Options | undefined, false> =
return false
},
)

for (const i of typeImports) {
if (!i.source) continue
const resolved = (await resolve(context, i.source.value, id))?.id
Expand Down Expand Up @@ -234,12 +230,28 @@ export const IsolatedDecl: UnpluginInstance<Options | undefined, false> =
entryFileNames = path.join(options.extraOutdir, entryFileNames)
}

for (let [outname, source] of Object.entries(outputFiles)) {
const name: string = map?.[outname] || path.relative(outBase, outname)
const fileName = entryFileNames.replace('[name]', name)
for (let [outname, { source, s, imports }] of Object.entries(
outputFiles,
)) {
const entryAlias = map?.[outname]
const relativeName = path.relative(outBase, outname)
const fileName = entryFileNames.replace(
'[name]',
entryAlias || relativeName,
)

if (entryAlias && entryAlias !== relativeName) {
const offset = path.relative(
path.dirname(path.resolve(outBase, fileName)),
path.dirname(outname),
)
source = patchEntryAlias(source, s, imports, outname, offset)
}

if (options.patchCjsDefaultExport && fileName.endsWith('.d.cts')) {
source = patchCjsDefaultExport(source)
}

debug('[rollup] emit dts file:', fileName)
this.emitFile({
type: 'asset',
Expand Down Expand Up @@ -282,13 +294,28 @@ export const IsolatedDecl: UnpluginInstance<Options | undefined, false> =
entryFileNames = path.join(options.extraOutdir, entryFileNames)
}

for (let [outname, source] of Object.entries(outputFiles)) {
const name: string = map?.[outname] || path.relative(outBase, outname)
const fileName = entryFileNames.replace('[entryName]', name)
for (let [outname, { source, s, imports }] of Object.entries(
outputFiles,
)) {
const entryAlias = map?.[outname]
const relativeName = path.relative(outBase, outname)
const fileName = entryFileNames.replace(
'[name]',
entryAlias || relativeName,
)

if (entryAlias && entryAlias !== relativeName) {
const offset = path.relative(
path.dirname(path.resolve(outBase, fileName)),
path.dirname(outname),
)
source = patchEntryAlias(source, s, imports, outname, offset)
}

if (options.patchCjsDefaultExport && fileName.endsWith('.d.cts')) {
source = patchCjsDefaultExport(source)
}

debug('[farm] emit dts file:', fileName)
farmPluginContext.emitFile({
type: 'asset',
Expand Down Expand Up @@ -338,7 +365,7 @@ export const IsolatedDecl: UnpluginInstance<Options | undefined, false> =
}

const textEncoder = new TextEncoder()
for (let [filename, source] of Object.entries(outputFiles)) {
for (let [filename, { source }] of Object.entries(outputFiles)) {
const outDir = build.initialOptions.outdir
let outFile = `${path.relative(outBase, filename)}.d.${outExt}`
if (options.extraOutdir) {
Expand Down Expand Up @@ -366,24 +393,6 @@ export const IsolatedDecl: UnpluginInstance<Options | undefined, false> =
}
})

function resolveEntry(input: string[] | Record<string, string>): {
map: Record<string, string> | undefined
outBase: string
} {
const map = !Array.isArray(input)
? Object.fromEntries(
Object.entries(input).map(([k, v]) => [
path.resolve(stripExt(v as string)),
k,
]),
)
: undefined
const arr = Array.isArray(input) && input ? input : Object.values(input)
const outBase = lowestCommonAncestor(...arr)

return { map, outBase }
}

async function resolve(
context: UnpluginBuildContext,
id: string,
Expand Down
6 changes: 4 additions & 2 deletions tests/__snapshots__/esbuild.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ export declare let c: React.JSX.Element;
export declare let num: Num;
",
"// temp/types.d.ts
export type Num = number;
import type { Num2 } from './types2';
export type Num = Num2;
",
"// temp/types2.d.ts
export type Num2 = number;
Expand All @@ -49,7 +50,8 @@ export declare function hello(s: Str): Str;
export declare let c: React.JSX.Element;
export declare let num: Num;
",
"export type Num = number;
"import type { Num2 } from './types2';
export type Num = Num2;
",
"export type Num2 = number;
",
Expand Down
3 changes: 2 additions & 1 deletion tests/__snapshots__/rolldown.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ export declare let c: React.JSX.Element;
export declare let num: Num;
",
"// temp/types.d.ts
export type Num = number;
import type { Num2 } from "./types2";
export type Num = Num2;
",
"// temp/types2.d.ts
export type Num2 = number;
Expand Down
Loading

0 comments on commit c27fe46

Please sign in to comment.