Skip to content

Commit c27fe46

Browse files
committed
fix: patch entry alias
closes #34
1 parent 219231c commit c27fe46

File tree

12 files changed

+260
-75
lines changed

12 files changed

+260
-75
lines changed

src/core/ast.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import path from 'node:path'
2+
import MagicString from 'magic-string'
3+
import { debug } from './utils'
4+
import type * as OxcTypes from '@oxc-project/types'
5+
6+
export type OxcImport = (
7+
| OxcTypes.ImportDeclaration
8+
| OxcTypes.ExportAllDeclaration
9+
| OxcTypes.ExportNamedDeclaration
10+
) & { source: OxcTypes.StringLiteral }
11+
12+
export function filterImports(program: OxcTypes.Program): OxcImport[] {
13+
return program.body.filter(
14+
(node): node is OxcImport =>
15+
(node.type === 'ImportDeclaration' ||
16+
node.type === 'ExportAllDeclaration' ||
17+
node.type === 'ExportNamedDeclaration') &&
18+
!!node.source,
19+
)
20+
}
21+
22+
export function patchEntryAlias(
23+
source: string,
24+
s: MagicString | undefined,
25+
imports: OxcImport[],
26+
outname: string,
27+
offset: string,
28+
): string {
29+
debug('Patching entry alias:', outname, 'offset:', offset)
30+
31+
s ||= new MagicString(source)
32+
for (const i of imports) {
33+
if (i.source.value[0] === '.') {
34+
s.overwrite(
35+
i.source.start + 1,
36+
i.source.end - 1,
37+
`./${path.join(offset, i.source.value)}`,
38+
)
39+
}
40+
}
41+
42+
if (s.hasChanged()) {
43+
debug('Patched entry alias:', outname)
44+
return s.toString()
45+
}
46+
return source
47+
}

src/core/types.ts

Lines changed: 0 additions & 1 deletion
This file was deleted.

src/core/utils.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
import path from 'node:path'
2+
import Debug from 'debug'
3+
4+
export const debug: Debug.Debugger = Debug('unplugin-isolated-decl')
25

36
export function lowestCommonAncestor(...filepaths: string[]): string {
47
if (filepaths.length === 0) return ''
@@ -28,3 +31,21 @@ export function lowestCommonAncestor(...filepaths: string[]): string {
2831
export function stripExt(filename: string): string {
2932
return filename.replace(/\.(.?)[jt]sx?$/, '')
3033
}
34+
35+
export function resolveEntry(input: string[] | Record<string, string>): {
36+
map: Record<string, string> | undefined
37+
outBase: string
38+
} {
39+
const map = !Array.isArray(input)
40+
? Object.fromEntries(
41+
Object.entries(input).map(([k, v]) => [
42+
path.resolve(stripExt(v as string)),
43+
k,
44+
]),
45+
)
46+
: undefined
47+
const arr = Array.isArray(input) && input ? input : Object.values(input)
48+
const outBase = lowestCommonAncestor(...arr)
49+
50+
return { map, outBase }
51+
}

src/index.ts

Lines changed: 78 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
import { mkdir, readFile, writeFile } from 'node:fs/promises'
77
import path from 'node:path'
88
import { createFilter } from '@rollup/pluginutils'
9-
import Debug from 'debug'
109
import MagicString from 'magic-string'
1110
import { parseAsync } from 'oxc-parser'
1211
import {
@@ -15,14 +14,20 @@ import {
1514
type UnpluginContext,
1615
type UnpluginInstance,
1716
} from 'unplugin'
17+
import { filterImports, patchEntryAlias, type OxcImport } from './core/ast'
1818
import { resolveOptions, type Options } from './core/options'
1919
import {
2020
oxcTransform,
2121
swcTransform,
2222
tsTransform,
2323
type TransformResult,
2424
} from './core/transformer'
25-
import { lowestCommonAncestor, stripExt } from './core/utils'
25+
import {
26+
debug,
27+
lowestCommonAncestor,
28+
resolveEntry,
29+
stripExt,
30+
} from './core/utils'
2631
import type {
2732
JsPlugin,
2833
NormalizedConfig,
@@ -37,11 +42,13 @@ import type {
3742
PluginContext,
3843
} from 'rollup'
3944

40-
const debug = Debug('unplugin-isolated-decl')
41-
4245
export type { Options }
4346

44-
export type * from './core/types'
47+
interface Output {
48+
source: string
49+
imports: OxcImport[]
50+
s?: MagicString
51+
}
4552

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

5461
let farmPluginContext: UnpluginBuildContext
55-
const outputFiles: Record<string, string> = {}
56-
function addOutput(filename: string, source: string) {
57-
outputFiles[stripExt(filename)] = source
62+
63+
const outputFiles: Record<string, Output> = {}
64+
function addOutput(filename: string, output: Output) {
65+
const name = stripExt(filename)
66+
debug('Add output:', name)
67+
outputFiles[name] = output
5868
}
5969

6070
const rollup: Partial<Plugin> = {
@@ -63,6 +73,7 @@ export const IsolatedDecl: UnpluginInstance<Options | undefined, false> =
6373
const farm: Partial<JsPlugin> = {
6474
renderStart: { executor: farmRenderStart },
6575
}
76+
6677
return {
6778
name: 'unplugin-isolated-decl',
6879

@@ -92,36 +103,6 @@ export const IsolatedDecl: UnpluginInstance<Options | undefined, false> =
92103
code: string,
93104
id: string,
94105
): Promise<undefined> {
95-
let program: OxcTypes.Program | undefined
96-
try {
97-
program = (await parseAsync(code, { sourceFilename: id })).program
98-
} catch {}
99-
if (options.autoAddExts && program) {
100-
const imports = program.body.filter(
101-
(node) =>
102-
node.type === 'ImportDeclaration' ||
103-
node.type === 'ExportAllDeclaration' ||
104-
node.type === 'ExportNamedDeclaration',
105-
)
106-
const s = new MagicString(code)
107-
for (const i of imports) {
108-
if (!i.source || path.basename(i.source.value).includes('.')) {
109-
continue
110-
}
111-
112-
const resolved = await resolve(context, i.source.value, id)
113-
if (!resolved || resolved.external) continue
114-
if (resolved.id.endsWith('.ts') || resolved.id.endsWith('.tsx')) {
115-
s.overwrite(
116-
i.source.start,
117-
i.source.end,
118-
JSON.stringify(`${i.source.value}.js`),
119-
)
120-
}
121-
}
122-
code = s.toString()
123-
}
124-
125106
const label = debug.enabled && `[${options.transformer}]`
126107
debug(label, 'transform', id)
127108

@@ -140,7 +121,7 @@ export const IsolatedDecl: UnpluginInstance<Options | undefined, false> =
140121
(options as any).transformOptions,
141122
)
142123
}
143-
const { code: sourceText, errors } = result
124+
let { code: dts, errors } = result
144125
debug(
145126
label,
146127
'transformed',
@@ -155,12 +136,28 @@ export const IsolatedDecl: UnpluginInstance<Options | undefined, false> =
155136
return
156137
}
157138
}
158-
addOutput(id, sourceText)
159139

160-
if (!program) {
161-
debug('cannot parse', id)
162-
return
140+
const { program } = await parseAsync(dts, { sourceFilename: id })
141+
const imports = filterImports(program)
142+
143+
let s: MagicString | undefined
144+
if (options.autoAddExts) {
145+
s = new MagicString(dts)
146+
147+
for (const i of imports) {
148+
if (path.basename(i.source.value).includes('.')) continue
149+
150+
const resolved = await resolve(context, i.source.value, id)
151+
if (!resolved || resolved.external) continue
152+
if (resolved.id.endsWith('.ts') || resolved.id.endsWith('.tsx')) {
153+
i.source.value = `${i.source.value}.js`
154+
s.overwrite(i.source.start + 1, i.source.end - 1, i.source.value)
155+
}
156+
}
157+
dts = s.toString()
163158
}
159+
addOutput(id, { source: dts, s, imports })
160+
164161
const typeImports = program.body.filter(
165162
(
166163
node,
@@ -195,7 +192,6 @@ export const IsolatedDecl: UnpluginInstance<Options | undefined, false> =
195192
return false
196193
},
197194
)
198-
199195
for (const i of typeImports) {
200196
if (!i.source) continue
201197
const resolved = (await resolve(context, i.source.value, id))?.id
@@ -234,12 +230,28 @@ export const IsolatedDecl: UnpluginInstance<Options | undefined, false> =
234230
entryFileNames = path.join(options.extraOutdir, entryFileNames)
235231
}
236232

237-
for (let [outname, source] of Object.entries(outputFiles)) {
238-
const name: string = map?.[outname] || path.relative(outBase, outname)
239-
const fileName = entryFileNames.replace('[name]', name)
233+
for (let [outname, { source, s, imports }] of Object.entries(
234+
outputFiles,
235+
)) {
236+
const entryAlias = map?.[outname]
237+
const relativeName = path.relative(outBase, outname)
238+
const fileName = entryFileNames.replace(
239+
'[name]',
240+
entryAlias || relativeName,
241+
)
242+
243+
if (entryAlias && entryAlias !== relativeName) {
244+
const offset = path.relative(
245+
path.dirname(path.resolve(outBase, fileName)),
246+
path.dirname(outname),
247+
)
248+
source = patchEntryAlias(source, s, imports, outname, offset)
249+
}
250+
240251
if (options.patchCjsDefaultExport && fileName.endsWith('.d.cts')) {
241252
source = patchCjsDefaultExport(source)
242253
}
254+
243255
debug('[rollup] emit dts file:', fileName)
244256
this.emitFile({
245257
type: 'asset',
@@ -282,13 +294,28 @@ export const IsolatedDecl: UnpluginInstance<Options | undefined, false> =
282294
entryFileNames = path.join(options.extraOutdir, entryFileNames)
283295
}
284296

285-
for (let [outname, source] of Object.entries(outputFiles)) {
286-
const name: string = map?.[outname] || path.relative(outBase, outname)
287-
const fileName = entryFileNames.replace('[entryName]', name)
297+
for (let [outname, { source, s, imports }] of Object.entries(
298+
outputFiles,
299+
)) {
300+
const entryAlias = map?.[outname]
301+
const relativeName = path.relative(outBase, outname)
302+
const fileName = entryFileNames.replace(
303+
'[name]',
304+
entryAlias || relativeName,
305+
)
306+
307+
if (entryAlias && entryAlias !== relativeName) {
308+
const offset = path.relative(
309+
path.dirname(path.resolve(outBase, fileName)),
310+
path.dirname(outname),
311+
)
312+
source = patchEntryAlias(source, s, imports, outname, offset)
313+
}
288314

289315
if (options.patchCjsDefaultExport && fileName.endsWith('.d.cts')) {
290316
source = patchCjsDefaultExport(source)
291317
}
318+
292319
debug('[farm] emit dts file:', fileName)
293320
farmPluginContext.emitFile({
294321
type: 'asset',
@@ -338,7 +365,7 @@ export const IsolatedDecl: UnpluginInstance<Options | undefined, false> =
338365
}
339366

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

369-
function resolveEntry(input: string[] | Record<string, string>): {
370-
map: Record<string, string> | undefined
371-
outBase: string
372-
} {
373-
const map = !Array.isArray(input)
374-
? Object.fromEntries(
375-
Object.entries(input).map(([k, v]) => [
376-
path.resolve(stripExt(v as string)),
377-
k,
378-
]),
379-
)
380-
: undefined
381-
const arr = Array.isArray(input) && input ? input : Object.values(input)
382-
const outBase = lowestCommonAncestor(...arr)
383-
384-
return { map, outBase }
385-
}
386-
387396
async function resolve(
388397
context: UnpluginBuildContext,
389398
id: string,

tests/__snapshots__/esbuild.test.ts.snap

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ export declare let c: React.JSX.Element;
2828
export declare let num: Num;
2929
",
3030
"// temp/types.d.ts
31-
export type Num = number;
31+
import type { Num2 } from './types2';
32+
export type Num = Num2;
3233
",
3334
"// temp/types2.d.ts
3435
export type Num2 = number;
@@ -49,7 +50,8 @@ export declare function hello(s: Str): Str;
4950
export declare let c: React.JSX.Element;
5051
export declare let num: Num;
5152
",
52-
"export type Num = number;
53+
"import type { Num2 } from './types2';
54+
export type Num = Num2;
5355
",
5456
"export type Num2 = number;
5557
",

tests/__snapshots__/rolldown.test.ts.snap

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ export declare let c: React.JSX.Element;
3131
export declare let num: Num;
3232
",
3333
"// temp/types.d.ts
34-
export type Num = number;
34+
import type { Num2 } from "./types2";
35+
export type Num = Num2;
3536
",
3637
"// temp/types2.d.ts
3738
export type Num2 = number;

0 commit comments

Comments
 (0)