From 97ad5fa7260f3e3ab34b098c010835bf3f88f31f Mon Sep 17 00:00:00 2001 From: alex8088 <244096523@qq.com> Date: Wed, 2 Nov 2022 01:34:19 +0800 Subject: [PATCH] feat: make a SWC plugin to emit decorator metadata (#48) --- package.json | 7 +++ pnpm-lock.yaml | 164 +++++++++++++++++++++++++++++++++++++++++++++++++ src/index.ts | 1 + src/swc.ts | 112 +++++++++++++++++++++++++++++++++ 4 files changed, 284 insertions(+) create mode 100644 src/swc.ts diff --git a/package.json b/package.json index 8147868..cd4a9c3 100644 --- a/package.json +++ b/package.json @@ -50,12 +50,19 @@ ] }, "peerDependencies": { + "@swc/core": "^1.0.0", "vite": "^3.0.0" }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + } + }, "devDependencies": { "@microsoft/api-extractor": "^7.33.5", "@rollup/plugin-node-resolve": "^15.0.1", "@rollup/plugin-typescript": "^9.0.2", + "@swc/core": "^1.3.11", "@types/node": "16.18.3", "@typescript-eslint/eslint-plugin": "^5.42.0", "@typescript-eslint/parser": "^5.42.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d923465..95af9cb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -6,6 +6,7 @@ specifiers: '@microsoft/api-extractor': ^7.33.5 '@rollup/plugin-node-resolve': ^15.0.1 '@rollup/plugin-typescript': ^9.0.2 + '@swc/core': ^1.3.11 '@types/node': 16.18.3 '@typescript-eslint/eslint-plugin': ^5.42.0 '@typescript-eslint/parser': ^5.42.0 @@ -35,6 +36,7 @@ devDependencies: '@microsoft/api-extractor': 7.33.5 '@rollup/plugin-node-resolve': 15.0.1_rollup@2.79.1 '@rollup/plugin-typescript': 9.0.2_ee2lsxs7bbggrcawnn34dqpc4e + '@swc/core': 1.3.11 '@types/node': 16.18.3 '@typescript-eslint/eslint-plugin': 5.42.0_6xw5wg2354iw4zujk2f3vyfrzu '@typescript-eslint/parser': 5.42.0_wyqvi574yv7oiwfeinomdzmc3m @@ -501,6 +503,168 @@ packages: string-argv: 0.3.1 dev: true + /@swc/core-android-arm-eabi/1.3.11: + resolution: {integrity: sha512-LC9JlMcdFmTU94KKmQkJKaPSeVmYTfVm2rKGESMiFrgIjopXL/Zeg+XHA97ucnh5iUMkWIpXAMzSOaVRs33K5g==} + engines: {node: '>=10'} + cpu: [arm] + os: [android] + requiresBuild: true + dependencies: + '@swc/wasm': 1.2.122 + dev: true + optional: true + + /@swc/core-android-arm64/1.3.11: + resolution: {integrity: sha512-M7FamR3kFpVTyTw73FzKcOZmS7/TWHX75eqtwBTaU9fW4shf0KTLr/h9DnMxNKAnwUMeub/lqlINUe5EKFIKwQ==} + engines: {node: '>=10'} + cpu: [arm64] + os: [android] + requiresBuild: true + dependencies: + '@swc/wasm': 1.2.130 + dev: true + optional: true + + /@swc/core-darwin-arm64/1.3.11: + resolution: {integrity: sha512-LOoiw3uQDuoKBLW3Mn8p6wIccpYjAoDI3ROdto4MksLQSraHMufXY8bqqncfVuy1750XZmC1qnU39RC3yihPfA==} + engines: {node: '>=10'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@swc/core-darwin-x64/1.3.11: + resolution: {integrity: sha512-ycjrEbWmpU8MTDdVLdf76ClxQCSTfNqSoP59hieLzhmXpXUa7Oy4sN/v6WSQgp4I1euGs1Wp5kPU5hH5f7XBJQ==} + engines: {node: '>=10'} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@swc/core-freebsd-x64/1.3.11: + resolution: {integrity: sha512-02uqYktPp6WmZfZ2Crc/yIVOcgANtjo8ciHcT7yLHvz7v+S7gx1I2tyNGUFtTX5hcR2IFNGrL8Yj4DvpTABFHg==} + engines: {node: '>=10'} + cpu: [x64] + os: [freebsd] + requiresBuild: true + dependencies: + '@swc/wasm': 1.2.130 + dev: true + optional: true + + /@swc/core-linux-arm-gnueabihf/1.3.11: + resolution: {integrity: sha512-nZ2T/gPFncsIiFGhVeVY9vRCTX0hTdqso8OEvFhSwRfRvcEYOpb/rhMG09are7YoB44GMiku5tSzEiNmvT3GuQ==} + engines: {node: '>=10'} + cpu: [arm] + os: [linux] + requiresBuild: true + dependencies: + '@swc/wasm': 1.2.130 + dev: true + optional: true + + /@swc/core-linux-arm64-gnu/1.3.11: + resolution: {integrity: sha512-EWEd8NnGwhykEDFIet/r4Fcfr6805ecnBniHZWmG8UVYUp5tz7LYEMZesxCxa0+aGVpCmxHL5/Cdk1uEIrVIzg==} + engines: {node: '>=10'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@swc/core-linux-arm64-musl/1.3.11: + resolution: {integrity: sha512-ms7CLj2+8sfTM2QnnwqDheYRky9rgPpX2hXmc0KltX+AiSAs7WURjn2JwXWkaICDzXL+djennfswxSspJ53knw==} + engines: {node: '>=10'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@swc/core-linux-x64-gnu/1.3.11: + resolution: {integrity: sha512-Fq5/QEY0IbzpIrqlvQT59aMf+nLk//esL3Aj4nvZdsvF4iZaD2oHtXW+/MBi0i1HV9OBWApGZMygYNVS0uVZkQ==} + engines: {node: '>=10'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@swc/core-linux-x64-musl/1.3.11: + resolution: {integrity: sha512-S/KoEgRHwGhs7VunHiz4jLrnFOJvqZe391j2MiYN1p2EThoGI3rvwcUoHkoxLCXVuDbi4E91qodOheaMGetWNA==} + engines: {node: '>=10'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@swc/core-win32-arm64-msvc/1.3.11: + resolution: {integrity: sha512-fFby7KOQIxolR6w4Gie8MSkgQ3ee6j3r7A6PX4ekzu+509QsZogLPZnWFTJ8WFo7ui0tx/ocA5X8BV4ZNBVlKQ==} + engines: {node: '>=10'} + cpu: [arm64] + os: [win32] + requiresBuild: true + dependencies: + '@swc/wasm': 1.2.130 + dev: true + optional: true + + /@swc/core-win32-ia32-msvc/1.3.11: + resolution: {integrity: sha512-fii7Y33S9Z5oZ/BTsXa8Ou/RZ4T/ZsAOFpG7mMvifpdAP6LVWdhLoNy7SeeEHTEAWGcWNA/FslA6p2WETvEEfA==} + engines: {node: '>=10'} + cpu: [ia32] + os: [win32] + requiresBuild: true + dependencies: + '@swc/wasm': 1.2.130 + dev: true + optional: true + + /@swc/core-win32-x64-msvc/1.3.11: + resolution: {integrity: sha512-cDF4qBQLf3U0KypnLn3T1HtE12QmEplPGjWs2Xd/hzVkjsPixVR8XIgLlczsj7Pk7Of7VDnUDLIl52aIdgTm8w==} + engines: {node: '>=10'} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@swc/core/1.3.11: + resolution: {integrity: sha512-lnCnnnNCsnbrhW/gwkoN0sAeIqOyoHLS4ZB20xmPJjKVfvTnJrAcNnLSiwlYdcoUSFqT2GYZjUAG6usEzRQASA==} + engines: {node: '>=10'} + hasBin: true + requiresBuild: true + optionalDependencies: + '@swc/core-android-arm-eabi': 1.3.11 + '@swc/core-android-arm64': 1.3.11 + '@swc/core-darwin-arm64': 1.3.11 + '@swc/core-darwin-x64': 1.3.11 + '@swc/core-freebsd-x64': 1.3.11 + '@swc/core-linux-arm-gnueabihf': 1.3.11 + '@swc/core-linux-arm64-gnu': 1.3.11 + '@swc/core-linux-arm64-musl': 1.3.11 + '@swc/core-linux-x64-gnu': 1.3.11 + '@swc/core-linux-x64-musl': 1.3.11 + '@swc/core-win32-arm64-msvc': 1.3.11 + '@swc/core-win32-ia32-msvc': 1.3.11 + '@swc/core-win32-x64-msvc': 1.3.11 + dev: true + + /@swc/wasm/1.2.122: + resolution: {integrity: sha512-sM1VCWQxmNhFtdxME+8UXNyPNhxNu7zdb6ikWpz0YKAQQFRGT5ThZgJrubEpah335SUToNg8pkdDF7ibVCjxbQ==} + requiresBuild: true + dev: true + optional: true + + /@swc/wasm/1.2.130: + resolution: {integrity: sha512-rNcJsBxS70+pv8YUWwf5fRlWX6JoY/HJc25HD/F8m6Kv7XhJdqPPMhyX6TKkUBPAG7TWlZYoxa+rHAjPy4Cj3Q==} + requiresBuild: true + dev: true + optional: true + /@types/argparse/1.0.38: resolution: {integrity: sha512-ebDJ9b0e702Yr7pWgB0jzm+CX4Srzz8RcXtLJDJB+BSccqMa36uyH/zUsSYao5+BD1ytv3k3rPYCq4mAE1hsXA==} dev: true diff --git a/src/index.ts b/src/index.ts index 4b7017c..bb3e88b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,3 +5,4 @@ export { build } from './build' export { preview } from './preview' export * from './plugin' export { compileToBytecode } from './bytecode' +export * from './swc' diff --git a/src/swc.ts b/src/swc.ts new file mode 100644 index 0000000..51da44e --- /dev/null +++ b/src/swc.ts @@ -0,0 +1,112 @@ +import { createRequire } from 'node:module' +import type { SourceMap } from 'rollup' +import type { TransformConfig, Output as TransformResult, JscConfig } from '@swc/core' +import type { Plugin, UserConfig, FilterPattern } from 'vite' +import { createFilter } from 'vite' + +type SwcTransformResult = Omit & { + map: SourceMap +} + +type SwcTransformOptions = { + sourcemap?: boolean | 'inline' | undefined + minify?: boolean +} & TransformConfig + +async function transformWithSWC(code: string, id: string, options: SwcTransformOptions): Promise { + const { sourcemap = false, minify = false } = options + + delete options.sourcemap + delete options.minify + + const isTs = /\.tsx?$/.test(id) + + const require = createRequire(import.meta.url) + + const swc: typeof import('@swc/core') = require('@swc/core') + + if (!swc) { + throw new Error('swc plugin require @swc/core, you need to install it.') + } + + const jsc: JscConfig = { + parser: { + syntax: isTs ? 'typescript' : 'ecmascript', + decorators: true + }, + transform: { + legacyDecorator: true, + decoratorMetadata: true, + ...options + }, + keepClassNames: true, + target: 'es2022', + minify: { + format: { + comments: false + } + } + } + + const result = await swc.transform(code, { + jsc, + sourceMaps: sourcemap, + minify, + configFile: false, + swcrc: false + }) + + const map: SourceMap = sourcemap && result.map ? JSON.parse(result.map) : { mappings: '' } + + return { + code: result.code, + map + } +} + +export type SwcOptions = { + include?: FilterPattern + exclude?: FilterPattern + transformOptions?: TransformConfig +} + +export function swcPlugin(options: SwcOptions = {}): Plugin { + const filter = createFilter(options.include || /\.(m?ts|[jt]sx)$/, options.exclude || /\.js$/) + let sourcemap: boolean | 'inline' = false + let minify: boolean | 'esbuild' | 'terser' = false + return { + name: 'vite:swc', + config(): UserConfig { + return { + esbuild: false + } + }, + async configResolved(resolvedConfig): Promise { + sourcemap = resolvedConfig.build?.sourcemap === 'inline' ? 'inline' : !!resolvedConfig.build?.sourcemap + minify = resolvedConfig.build?.minify + }, + async transform(code, id): Promise { + if (filter(id)) { + const result = await transformWithSWC(code, id, { sourcemap, ...(options.transformOptions || {}) }) + return { + code: result.code, + map: result.map + } + } + }, + async renderChunk(code, chunk): Promise { + if (!minify || minify === 'terser') { + return null + } + const result = await transformWithSWC(code, chunk.fileName, { + sourcemap, + minify: true, + ...(options.transformOptions || {}) + }) + return { + code: result.code, + map: result.map + } + } + } +}