mirror of
https://github.com/egoist/tsup.git
synced 2025-12-08 20:35:58 +00:00
feat: allow passing custom swc configuration to swcPlugin (#1313)
This commit is contained in:
parent
769aa49cae
commit
fdfd59afb2
@ -609,6 +609,64 @@ tsup --tsconfig tsconfig.prod.json
|
||||
|
||||
By default, tsup try to find the `tsconfig.json` file in the current directory, if it's not found, it will use the default tsup config.
|
||||
|
||||
### Using custom Swc configuration
|
||||
|
||||
When you use legacy TypeScript decorator by enabling `emitDecoratorMetadata` in your tsconfig, tsup will automatically use [SWC](https://swc.rs) to transpile
|
||||
decorators. In this case, you can give extra swc configuration in the `tsup.config.ts` file.
|
||||
|
||||
For example, if you have to define `useDefineForClassFields`, you can do that as follows:
|
||||
```ts
|
||||
import { defineConfig } from 'tsup'
|
||||
|
||||
export default defineConfig({
|
||||
entry: ['src/index.ts'],
|
||||
splitting: false,
|
||||
sourcemap: true,
|
||||
clean: true,
|
||||
swc: {
|
||||
jsc: {
|
||||
transform: {
|
||||
useDefineForClassFields: true
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
Note: some SWC options cannot be configured:
|
||||
|
||||
```json
|
||||
{
|
||||
"parser": {
|
||||
"syntax": "typescript",
|
||||
"decorators": true
|
||||
},
|
||||
"transform": {
|
||||
"legacyDecorator": true,
|
||||
"decoratorMetadata": true
|
||||
},
|
||||
"keepClassNames": true,
|
||||
"target": "es2022"
|
||||
}
|
||||
```
|
||||
|
||||
You can also define a custom `.swcrc` configuration file. Just set `swcrc` to `true`
|
||||
in `tsup.config.ts` to allow SWC plugin to discover automatically your custom swc config file.
|
||||
|
||||
```ts
|
||||
import { defineConfig } from 'tsup'
|
||||
|
||||
export default defineConfig({
|
||||
entry: ['src/index.ts'],
|
||||
splitting: false,
|
||||
sourcemap: true,
|
||||
clean: true,
|
||||
swc: {
|
||||
swcrc: true
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### error: No matching export in "xxx.ts" for import "xxx"
|
||||
|
||||
@ -181,6 +181,9 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"swc": {
|
||||
"type": "object"
|
||||
},
|
||||
"globalName": {
|
||||
"type": "string"
|
||||
},
|
||||
|
||||
@ -138,7 +138,7 @@ export async function runEsbuild(
|
||||
skipNodeModulesBundle: options.skipNodeModulesBundle,
|
||||
tsconfigResolvePaths: options.tsconfigResolvePaths,
|
||||
}),
|
||||
options.tsconfigDecoratorMetadata && swcPlugin({ logger }),
|
||||
options.tsconfigDecoratorMetadata && swcPlugin({ ...options.swc, logger }),
|
||||
nativeNodeModulesPlugin(),
|
||||
postcssPlugin({
|
||||
css,
|
||||
|
||||
101
src/esbuild/swc.test.ts
Normal file
101
src/esbuild/swc.test.ts
Normal file
@ -0,0 +1,101 @@
|
||||
import { describe, expect, test, vi } from 'vitest'
|
||||
import { swcPlugin, type SwcPluginConfig } from './swc'
|
||||
import { localRequire } from '../utils'
|
||||
|
||||
vi.mock('../utils')
|
||||
|
||||
const getFixture = async (opts: Partial<SwcPluginConfig> = {}) => {
|
||||
const swc = {
|
||||
transformFile: vi.fn().mockResolvedValue({
|
||||
code: 'source-code',
|
||||
map: JSON.stringify({
|
||||
sources: ['file:///path/to/file.ts'],
|
||||
}),
|
||||
}),
|
||||
}
|
||||
|
||||
const logger = {
|
||||
warn: vi.fn(),
|
||||
error: vi.fn(),
|
||||
info: vi.fn(),
|
||||
}
|
||||
|
||||
const build = {
|
||||
initialOptions: {
|
||||
keepNames: true,
|
||||
},
|
||||
onLoad: vi.fn(),
|
||||
}
|
||||
|
||||
vi.mocked(localRequire).mockReturnValue(swc)
|
||||
|
||||
const plugin = swcPlugin({
|
||||
...opts,
|
||||
logger: logger as never,
|
||||
})
|
||||
|
||||
await plugin.setup(build as never)
|
||||
|
||||
const onLoad = build.onLoad.mock.calls[0][1] as Function
|
||||
|
||||
return { swc, onLoad, logger, build }
|
||||
}
|
||||
describe('swcPlugin', () => {
|
||||
test('swcPlugin transforms TypeScript code with decorators and default plugin swc option', async () => {
|
||||
const { swc, onLoad } = await getFixture()
|
||||
|
||||
await onLoad({
|
||||
path: 'file.ts',
|
||||
})
|
||||
|
||||
expect(swc.transformFile).toHaveBeenCalledWith('file.ts', {
|
||||
configFile: false,
|
||||
jsc: {
|
||||
keepClassNames: true,
|
||||
parser: {
|
||||
decorators: true,
|
||||
syntax: 'typescript',
|
||||
},
|
||||
target: 'es2022',
|
||||
transform: {
|
||||
decoratorMetadata: true,
|
||||
legacyDecorator: true,
|
||||
},
|
||||
},
|
||||
sourceMaps: true,
|
||||
swcrc: false,
|
||||
})
|
||||
})
|
||||
test('swcPlugin transforms TypeScript code and use given plugin swc option', async () => {
|
||||
const { swc, onLoad } = await getFixture({
|
||||
jsc: {
|
||||
transform: {
|
||||
useDefineForClassFields: true,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
await onLoad({
|
||||
path: 'file.ts',
|
||||
})
|
||||
|
||||
expect(swc.transformFile).toHaveBeenCalledWith('file.ts', {
|
||||
configFile: false,
|
||||
jsc: {
|
||||
keepClassNames: true,
|
||||
parser: {
|
||||
decorators: true,
|
||||
syntax: 'typescript',
|
||||
},
|
||||
target: 'es2022',
|
||||
transform: {
|
||||
decoratorMetadata: true,
|
||||
legacyDecorator: true,
|
||||
useDefineForClassFields: true,
|
||||
},
|
||||
},
|
||||
sourceMaps: true,
|
||||
swcrc: false,
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -3,11 +3,13 @@
|
||||
*/
|
||||
import path from 'node:path'
|
||||
import { localRequire } from '../utils'
|
||||
import type { JscConfig } from '@swc/core'
|
||||
import type { JscConfig, Options } from '@swc/core'
|
||||
import type { Plugin } from 'esbuild'
|
||||
import type { Logger } from '../log'
|
||||
|
||||
export const swcPlugin = ({ logger }: { logger: Logger }): Plugin => {
|
||||
export type SwcPluginConfig = { logger: Logger } & Options
|
||||
|
||||
export const swcPlugin = ({ logger, ...swcOptions }: SwcPluginConfig): Plugin => {
|
||||
return {
|
||||
name: 'swc',
|
||||
|
||||
@ -29,11 +31,14 @@ export const swcPlugin = ({ logger }: { logger: Logger }): Plugin => {
|
||||
const isTs = /\.tsx?$/.test(args.path)
|
||||
|
||||
const jsc: JscConfig = {
|
||||
...swcOptions.jsc,
|
||||
parser: {
|
||||
...swcOptions.jsc?.parser,
|
||||
syntax: isTs ? 'typescript' : 'ecmascript',
|
||||
decorators: true,
|
||||
},
|
||||
transform: {
|
||||
...swcOptions.jsc?.transform,
|
||||
legacyDecorator: true,
|
||||
decoratorMetadata: true,
|
||||
},
|
||||
@ -42,10 +47,11 @@ export const swcPlugin = ({ logger }: { logger: Logger }): Plugin => {
|
||||
}
|
||||
|
||||
const result = await swc.transformFile(args.path, {
|
||||
...swcOptions,
|
||||
jsc,
|
||||
sourceMaps: true,
|
||||
configFile: false,
|
||||
swcrc: false,
|
||||
swcrc: swcOptions.swcrc ?? false,
|
||||
})
|
||||
|
||||
let code = result.code
|
||||
|
||||
@ -4,6 +4,7 @@ import type { MinifyOptions } from 'terser'
|
||||
import type { MarkRequired } from 'ts-essentials'
|
||||
import type { Plugin } from './plugin'
|
||||
import type { TreeshakingStrategy } from './plugins/tree-shaking'
|
||||
import type { SwcPluginConfig } from './esbuild/swc.js'
|
||||
|
||||
export type KILL_SIGNAL = 'SIGKILL' | 'SIGTERM'
|
||||
|
||||
@ -256,6 +257,8 @@ export type Options = {
|
||||
* @default true
|
||||
*/
|
||||
removeNodeProtocol?: boolean
|
||||
|
||||
swc?: SwcPluginConfig;
|
||||
}
|
||||
|
||||
export interface NormalizedExperimentalDtsConfig {
|
||||
@ -272,4 +275,5 @@ export type NormalizedOptions = Omit<
|
||||
tsconfigResolvePaths: Record<string, string[]>
|
||||
tsconfigDecoratorMetadata?: boolean
|
||||
format: Format[]
|
||||
swc?: SwcPluginConfig
|
||||
}
|
||||
|
||||
@ -4,5 +4,6 @@ export default defineConfig({
|
||||
test: {
|
||||
testTimeout: 50000,
|
||||
globalSetup: 'vitest-global.ts',
|
||||
include: ["test/*.test.ts", "src/**/*.test.ts"]
|
||||
},
|
||||
})
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user