From b9cd8d5f0973f94d494b8b0d0e65121e5712ee49 Mon Sep 17 00:00:00 2001 From: EGOIST Date: Mon, 30 May 2022 21:29:37 +0800 Subject: [PATCH] feat: new option `outExtension` --- docs/README.md | 33 ++++++++++++++++++++++++++++++++- package.json | 2 +- pnpm-lock.yaml | 30 ++++++++++++++---------------- src/esbuild/index.ts | 41 +++++++++++++++++++++++++++++++---------- src/index.ts | 13 ++----------- src/options.ts | 28 ++++++++++++++++++++++++++-- test/index.test.ts | 26 ++++++++++++++++++++++++++ 7 files changed, 132 insertions(+), 41 deletions(-) diff --git a/docs/README.md b/docs/README.md index ea8f673d..71b0ff83 100644 --- a/docs/README.md +++ b/docs/README.md @@ -224,6 +224,37 @@ dist └── index.js ``` +### Output extension + +You can also change the output extension of the files by using `outExtension` option: + +```ts +export default defineConfig({ + outExtension({ format }) { + return { + js: `.${format}.js`, + } + }, +}) +``` + +This will generate your files to `[name].[format].js`. + +The signature of `outExtension` is: + +```ts +type OutExtension = (ctx: Context) => Result + +type Context = { + options: NormalizedOptions + format: Format + /** "type" field in project's package.json */ + pkgType?: string +} + +type Result = { js?: string } +``` + ### Code Splitting Code splitting currently only works with the `esm` output format, and it's enabled by default. If you want code splitting for `cjs` output format as well, try using `--splitting` flag which is an experimental feature to get rid of [the limitation in esbuild](https://esbuild.github.io/api/#splitting). @@ -319,7 +350,7 @@ export default defineConfig({ }) ``` -### Tree Shaking +### Tree shaking esbuild has [tree shaking](https://esbuild.github.io/api/#tree-shaking) enabled by default, but sometimes it's not working very well, see [#1794](https://github.com/evanw/esbuild/issues/1794) [#1435](https://github.com/evanw/esbuild/issues/1435), so tsup offers an additional option to let you use Rollup for tree shaking instead: diff --git a/package.json b/package.json index 123744cb..9167aa6a 100644 --- a/package.json +++ b/package.json @@ -63,7 +63,7 @@ "svelte": "3.46.4", "ts-essentials": "9.1.2", "tsconfig-paths": "3.12.0", - "tsup": "5.12.5", + "tsup": "6.0.1", "typescript": "4.6.3", "vitest": "0.8.4", "wait-for-expect": "3.0.2" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e095f871..bc6ee04c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -37,7 +37,7 @@ specifiers: tree-kill: ^1.2.2 ts-essentials: 9.1.2 tsconfig-paths: 3.12.0 - tsup: 5.12.5 + tsup: 6.0.1 typescript: 4.6.3 vitest: 0.8.4 wait-for-expect: 3.0.2 @@ -81,7 +81,7 @@ devDependencies: svelte: 3.46.4 ts-essentials: 9.1.2_typescript@4.6.3 tsconfig-paths: 3.12.0 - tsup: 5.12.5_typescript@4.6.3 + tsup: 6.0.1_j2ph3i7mmhve2sgnzxcccndai4 typescript: 4.6.3 vitest: 0.8.4 wait-for-expect: 3.0.2 @@ -1166,7 +1166,6 @@ packages: /lodash.sortby/4.7.0: resolution: {integrity: sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=} - dev: false /loupe/2.3.1: resolution: {integrity: sha512-EN1D3jyVmaX4tnajVlfbREU4axL647hLec1h/PXAb8CPDMJiYitcWF2UeLVNttRqaIqQs4x+mRvXf+d+TlDrCA==} @@ -1333,7 +1332,6 @@ packages: /punycode/2.1.1: resolution: {integrity: sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==} engines: {node: '>=6'} - dev: false /queue-microtask/1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} @@ -1423,17 +1421,11 @@ packages: engines: {node: '>=0.10.0'} dev: true - /source-map/0.7.3: - resolution: {integrity: sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==} - engines: {node: '>= 8'} - dev: true - /source-map/0.8.0-beta.0: resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==} engines: {node: '>= 8'} dependencies: whatwg-url: 7.1.0 - dev: false /sourcemap-codec/1.4.8: resolution: {integrity: sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==} @@ -1519,7 +1511,6 @@ packages: resolution: {integrity: sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk=} dependencies: punycode: 2.1.1 - dev: false /tree-kill/1.2.2: resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} @@ -1545,15 +1536,23 @@ packages: strip-bom: 3.0.0 dev: true - /tsup/5.12.5_typescript@4.6.3: - resolution: {integrity: sha512-lKwzJsB49sDto51QjqOB4SdiBLKRvgTymEBuBCovcksdDwFEz3esrkbf3m497PXntUKVTzcgOfPdTgknMtvufw==} + /tsup/6.0.1_j2ph3i7mmhve2sgnzxcccndai4: + resolution: {integrity: sha512-2Pl1YkEEvzfg4aYgwUILaHV0wwZQKO7mNHMQChyzdCNlQqf11eug7gWk86XCb3CvRofCXBX87x73BTGjyS3cOQ==} + engines: {node: '>=14'} hasBin: true peerDependencies: + '@swc/core': ^1 + postcss: ^8.4.12 typescript: ^4.1.0 peerDependenciesMeta: + '@swc/core': + optional: true + postcss: + optional: true typescript: optional: true dependencies: + '@swc/core': 1.2.126 bundle-require: 3.0.2_esbuild@0.14.29 cac: 6.7.12 chokidar: 3.5.2 @@ -1562,10 +1561,11 @@ packages: execa: 5.1.1 globby: 11.0.4 joycon: 3.0.1 + postcss: 8.4.12 postcss-load-config: 3.1.0 resolve-from: 5.0.0 rollup: 2.74.1 - source-map: 0.7.3 + source-map: 0.8.0-beta.0 sucrase: 3.20.3 tree-kill: 1.2.2 typescript: 4.6.3 @@ -1656,7 +1656,6 @@ packages: /webidl-conversions/4.0.2: resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==} - dev: false /whatwg-url/7.1.0: resolution: {integrity: sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==} @@ -1664,7 +1663,6 @@ packages: lodash.sortby: 4.7.0 tr46: 1.0.1 webidl-conversions: 4.0.2 - dev: false /which/2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} diff --git a/src/esbuild/index.ts b/src/esbuild/index.ts index 1bb6ab70..3b686c08 100644 --- a/src/esbuild/index.ts +++ b/src/esbuild/index.ts @@ -18,23 +18,44 @@ import { truthy } from '../utils' import { swcPlugin } from './swc' import { nativeNodeModulesPlugin } from './native-node-module' import { PluginContainer } from '../plugin' +import { OutExtensionFactory } from '../options' -const getOutputExtensionMap = ( - pkgTypeField: string | undefined, +const defaultOutExtension = ({ + format, + pkgType, +}: { format: Format -) => { - const isModule = pkgTypeField === 'module' - const map: Record = {} + pkgType?: string +}): { js: string } => { + let jsExtension = '.js' + const isModule = pkgType === 'module' if (isModule && format === 'cjs') { - map['.js'] = '.cjs' + jsExtension = '.cjs' } if (!isModule && format === 'esm') { - map['.js'] = '.mjs' + jsExtension = '.mjs' } if (format === 'iife') { - map['.js'] = '.global.js' + jsExtension = '.global.js' + } + return { + js: jsExtension, + } +} + +const getOutputExtensionMap = ( + options: NormalizedOptions, + format: Format, + pkgType: string | undefined +) => { + const outExtension: OutExtensionFactory = + options.outExtension || defaultOutExtension + + const defaultExtension = defaultOutExtension({ format, pkgType }) + const extension = outExtension({ options, format, pkgType }) + return { + '.js': extension.js || defaultExtension.js, } - return map } export async function runEsbuild( @@ -62,7 +83,7 @@ export async function runEsbuild( ] const outDir = options.outDir - const outExtension = getOutputExtensionMap(pkg.type, format) + const outExtension = getOutputExtensionMap(options, format, pkg.type) const env: { [k: string]: string } = { ...options.env, } diff --git a/src/index.ts b/src/index.ts index e80d9d8d..feeb590f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -13,7 +13,7 @@ import execa from 'execa' import kill from 'tree-kill' import { version } from '../package.json' import { createLogger, setSilent } from './log' -import { DtsConfig, Format, Options } from './options' +import { NormalizedOptions, Format, Options } from './options' import { runEsbuild } from './esbuild' import { shebang } from './plugins/shebang' import { cjsSplitting } from './plugins/cjs-splitting' @@ -22,16 +22,7 @@ import { es5 } from './plugins/es5' import { sizeReporter } from './plugins/size-reporter' import { treeShakingPlugin } from './plugins/tree-shaking' -export type { Format, Options } - -export type NormalizedOptions = Omit< - MarkRequired, - 'dts' -> & { - dts?: DtsConfig - tsconfigResolvePaths: Record - tsconfigDecoratorMetadata?: boolean -} +export type { Format, Options, NormalizedOptions } export const defineConfig = ( options: diff --git a/src/options.ts b/src/options.ts index c7725e41..502fe761 100644 --- a/src/options.ts +++ b/src/options.ts @@ -1,10 +1,24 @@ import type { BuildOptions, Plugin as EsbuildPlugin, Loader } from 'esbuild' import type { InputOption } from 'rollup' -import { Plugin } from './plugin' -import { TreeshakingStrategy } from './plugins/tree-shaking' +import { MarkRequired } from 'ts-essentials' +import type { Plugin } from './plugin' +import type { TreeshakingStrategy } from './plugins/tree-shaking' export type Format = 'cjs' | 'esm' | 'iife' +export type ContextForOutPathGeneration = { + options: NormalizedOptions + format: Format + /** "type" field in project's package.json */ + pkgType?: string +} + +export type OutExtensionObject = { js?: string } + +export type OutExtensionFactory = ( + ctx: ContextForOutPathGeneration +) => OutExtensionObject + export type DtsConfig = { entry?: InputOption /** Resolve external types used in dts files from node_modules */ @@ -57,6 +71,7 @@ export type Options = { jsxFactory?: string jsxFragment?: string outDir?: string + outExtension?: OutExtensionFactory format?: Format[] globalName?: string env?: { @@ -158,3 +173,12 @@ export type Options = { */ treeshake?: TreeshakingStrategy } + +export type NormalizedOptions = Omit< + MarkRequired, + 'dts' +> & { + dts?: DtsConfig + tsconfigResolvePaths: Record + tsconfigDecoratorMetadata?: boolean +} diff --git a/test/index.test.ts b/test/index.test.ts index 891b8049..fae18544 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -860,3 +860,29 @@ test('use rollup for treeshaking', async () => { `import { inject } from 'vue'` ) }) + +test('custom output extension', async () => { + const { outFiles } = await run( + getTestName(), + { + 'input.ts': `export const foo = [1,2,3]`, + 'tsup.config.ts': `export default { + outExtension({ format }) { + return { + js: '.' + format + '.js' + } + } + }`, + }, + { + entry: ['input.ts'], + flags: ['--format', 'esm,cjs'], + } + ) + expect(outFiles).toMatchInlineSnapshot(` + [ + "input.cjs.js", + "input.esm.js", + ] + `) +})