Skip to content

Commit

Permalink
feat: add extraOutdir option
Browse files Browse the repository at this point in the history
  • Loading branch information
sxzz committed Sep 10, 2024
1 parent bc56d70 commit 1f3002e
Show file tree
Hide file tree
Showing 9 changed files with 125 additions and 107 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,9 @@ export interface Options {
/** Only for typescript transformer */
transformOptions?: TranspileOptions
ignoreErrors?: boolean

/** An extra directory layer for output files. */
extraOutdir?: string
}
```

Expand Down
5 changes: 4 additions & 1 deletion src/core/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ export type Options = {
exclude?: FilterPattern
enforce?: 'pre' | 'post' | undefined
ignoreErrors?: boolean
/** An extra directory layer for output files. */
extraOutdir?: string
} & (
| {
/**
Expand All @@ -28,7 +30,7 @@ type Overwrite<T, U> = Pick<T, Exclude<keyof T, keyof U>> & U

export type OptionsResolved = Overwrite<
Required<Options>,
Pick<Options, 'enforce'>
Pick<Options, 'enforce' | 'extraOutdir'>
>

export function resolveOptions(options: Options): OptionsResolved {
Expand All @@ -38,5 +40,6 @@ export function resolveOptions(options: Options): OptionsResolved {
enforce: 'enforce' in options ? options.enforce : 'pre',
transformer: options.transformer || 'oxc',
ignoreErrors: options.ignoreErrors || false,
extraOutdir: options.extraOutdir,
}
}
167 changes: 91 additions & 76 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
tsTransform,
type TransformResult,
} from './core/transformer'
import type { PluginBuild } from 'esbuild'
import type { Plugin, PluginContext } from 'rollup'

export type { Options }
Expand Down Expand Up @@ -43,11 +44,15 @@ export const IsolatedDecl: UnpluginInstance<Options | undefined, false> =
return this.error('entryFileNames must be a string')
}

const entryFileNames = outputOptions.entryFileNames.replace(
let entryFileNames = outputOptions.entryFileNames.replace(
/\.(.)?[jt]s$/,
(_, s) => `.d.${s || ''}ts`,
)

if (options.extraOutdir) {
entryFileNames = path.join(options.extraOutdir, entryFileNames)
}

for (const [filename, source] of Object.entries(outputFiles)) {
this.emitFile({
type: 'asset',
Expand All @@ -69,66 +74,11 @@ export const IsolatedDecl: UnpluginInstance<Options | undefined, false> =
},

transform(code, id): Promise<undefined> {
return transform.call(this, code, id)
return transform(this, code, id)
},

esbuild: {
setup(build) {
build.onEnd(async (result) => {
const esbuildOptions = build.initialOptions

const entries = esbuildOptions.entryPoints
if (
!(
entries &&
Array.isArray(entries) &&
entries.every((entry) => typeof entry === 'string')
)
)
throw new Error('unsupported entryPoints, must be an string[]')

const outBase = lowestCommonAncestor(...entries)
const jsExt = esbuildOptions.outExtension?.['.js']
let outExt: string
switch (jsExt) {
case '.cjs':
outExt = 'cts'
break
case '.mjs':
outExt = 'mts'
break
default:
outExt = 'ts'
break
}

const write = build.initialOptions.write ?? true
if (write) {
if (!build.initialOptions.outdir)
throw new Error('outdir is required when write is true')
} else {
result.outputFiles ||= []
}

const textEncoder = new TextEncoder()
for (const [filename, source] of Object.entries(outputFiles)) {
const outDir = build.initialOptions.outdir
const outFile = `${path.relative(outBase, filename)}.d.${outExt}`
const filePath = outDir ? path.resolve(outDir, outFile) : outFile
if (write) {
await mkdir(path.dirname(filePath), { recursive: true })
await writeFile(filePath, source)
} else {
result.outputFiles!.push({
path: filePath,
contents: textEncoder.encode(source),
hash: '',
text: source,
})
}
}
})
},
setup: esbuildSetup,
},
rollup,
rolldown: rollup as any,
Expand All @@ -140,7 +90,7 @@ export const IsolatedDecl: UnpluginInstance<Options | undefined, false> =
}

async function transform(
this: UnpluginBuildContext & UnpluginContext,
context: UnpluginBuildContext & UnpluginContext,
code: string,
id: string,
): Promise<undefined> {
Expand All @@ -162,9 +112,9 @@ export const IsolatedDecl: UnpluginInstance<Options | undefined, false> =
const { code: sourceText, errors } = result
if (errors.length) {
if (options.ignoreErrors) {
this.warn(errors[0])
context.warn(errors[0])
} else {
this.error(errors[0])
context.error(errors[0])
return
}
}
Expand All @@ -187,34 +137,99 @@ export const IsolatedDecl: UnpluginInstance<Options | undefined, false> =
)
})

const resolve = async (id: string, importer: string) => {
const context = this.getNativeBuildContext?.()
if (context?.framework === 'esbuild') {
return (
await context.build.resolve(id, {
importer,
resolveDir: path.dirname(importer),
kind: 'import-statement',
})
).path
}
return (await (this as any as PluginContext).resolve(id, importer))?.id
}
for (const i of typeImports) {
const resolved = await resolve(i.source.value, id)
const resolved = await resolve(context, i.source.value, id)
if (resolved && filter(resolved) && !outputFiles[stripExt(resolved)]) {
let source: string
try {
source = await readFile(resolved, 'utf8')
} catch {
continue
}
await transform.call(this, source, resolved)
await transform(context, source, resolved)
}
}
}

function esbuildSetup(build: PluginBuild) {
build.onEnd(async (result) => {
const esbuildOptions = build.initialOptions

const entries = esbuildOptions.entryPoints
if (
!(
entries &&
Array.isArray(entries) &&
entries.every((entry) => typeof entry === 'string')
)
)
throw new Error('unsupported entryPoints, must be an string[]')

const outBase = lowestCommonAncestor(...entries)
const jsExt = esbuildOptions.outExtension?.['.js']
let outExt: string
switch (jsExt) {
case '.cjs':
outExt = 'cts'
break
case '.mjs':
outExt = 'mts'
break
default:
outExt = 'ts'
break
}

const write = build.initialOptions.write ?? true
if (write) {
if (!build.initialOptions.outdir)
throw new Error('outdir is required when write is true')
} else {
result.outputFiles ||= []
}

const textEncoder = new TextEncoder()
for (const [filename, source] of Object.entries(outputFiles)) {
const outDir = build.initialOptions.outdir
let outFile = `${path.relative(outBase, filename)}.d.${outExt}`
if (options.extraOutdir) {
outFile = path.join(options.extraOutdir, outFile)
}
const filePath = outDir ? path.resolve(outDir, outFile) : outFile
if (write) {
await mkdir(path.dirname(filePath), { recursive: true })
await writeFile(filePath, source)
} else {
result.outputFiles!.push({
path: filePath,
contents: textEncoder.encode(source),
hash: '',
text: source,
})
}
}
})
}
})

const resolve = async (
context: UnpluginBuildContext,
id: string,
importer: string,
) => {
const nativeContext = context.getNativeBuildContext?.()
if (nativeContext?.framework === 'esbuild') {
return (
await nativeContext.build.resolve(id, {
importer,
resolveDir: path.dirname(importer),
kind: 'import-statement',
})
).path
}
return (await (context as PluginContext).resolve(id, importer))?.id
}

function stripExt(filename: string) {
return filename.replace(/\.(.?)[jt]s$/, '')
}
Expand Down
16 changes: 3 additions & 13 deletions tests/__snapshots__/esbuild.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,16 @@ export {
num
};
",
"// main.d.ts
"// temp/main.d.ts
import { type Num } from "./types";
export type Str = string;
export declare function hello(s: Str): Str;
export declare let num: Num;
",
"// types.d.ts
"// temp/types.d.ts
export type Num = number;
",
"// types2.d.ts
"// temp/types2.d.ts
export type Num2 = number;
",
]
Expand All @@ -34,16 +34,6 @@ exports[`esbuild > write mode 1`] = `
export type Str = string;
export declare function hello(s: Str): Str;
export declare let num: Num;
",
"// tests/fixtures/main.ts
function hello(s) {
return "hello" + s;
}
var num = 1;
export {
hello,
num
};
",
"export type Num = number;
",
Expand Down
16 changes: 8 additions & 8 deletions tests/__snapshots__/rolldown.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,6 @@

exports[`rolldown 1`] = `
[
"// main.d.ts
import { type Num } from "./types";
export type Str = string;
export declare function hello(s: Str): Str;
export declare let num: Num;
",
"// main.js
//#region tests/fixtures/main.ts
Expand All @@ -18,10 +12,16 @@ let num = 1;
//#endregion
export { hello, num };",
"// types.d.ts
"// temp/main.d.ts
import { type Num } from "./types";
export type Str = string;
export declare function hello(s: Str): Str;
export declare let num: Num;
",
"// temp/types.d.ts
export type Num = number;
",
"// types2.d.ts
"// temp/types2.d.ts
export type Num2 = number;
",
]
Expand Down
6 changes: 3 additions & 3 deletions tests/__snapshots__/rollup.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,16 @@ let num = 1;
export { hello, num };
",
"// main.d.ts
"// temp/main.d.ts
import { type Num } from "./types";
export type Str = string;
export declare function hello(s: Str): Str;
export declare let num: Num;
",
"// types.d.ts
"// temp/types.d.ts
export type Num = number;
",
"// types2.d.ts
"// temp/types2.d.ts
export type Num2 = number;
",
]
Expand Down
10 changes: 6 additions & 4 deletions tests/esbuild.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,27 +12,29 @@ describe('esbuild', () => {
const dist = path.resolve(__dirname, 'temp')
await build({
entryPoints: [input],
plugins: [UnpluginIsolatedDecl()],
plugins: [UnpluginIsolatedDecl({ extraOutdir: 'temp' })],
logLevel: 'silent',
bundle: true,
external: Object.keys(dependencies),
platform: 'node',
outdir: dist,
format: 'esm',
})

const outDir = path.resolve(dist, 'temp')
await expect(
Promise.all(
(await readdir(dist))
(await readdir(outDir))
.sort()
.map((file) => readFile(path.resolve(dist, file), 'utf8')),
.map((file) => readFile(path.resolve(outDir, file), 'utf8')),
),
).resolves.toMatchSnapshot()
})

test('generate mode', async () => {
const { outputFiles } = await build({
entryPoints: [input],
plugins: [UnpluginIsolatedDecl()],
plugins: [UnpluginIsolatedDecl({ extraOutdir: 'temp' })],
logLevel: 'silent',
bundle: true,
external: Object.keys(dependencies),
Expand Down
2 changes: 1 addition & 1 deletion tests/rolldown.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ test('rolldown', async () => {

const bundle = await rolldown({
input,
plugins: [UnpluginIsolatedDecl()],
plugins: [UnpluginIsolatedDecl({ extraOutdir: 'temp' })],
logLevel: 'silent',
})

Expand Down
Loading

0 comments on commit 1f3002e

Please sign in to comment.