mirror of
https://github.com/egoist/tsup.git
synced 2025-12-08 20:35:58 +00:00
1064 lines
24 KiB
TypeScript
1064 lines
24 KiB
TypeScript
import { test, expect, beforeAll } from 'vitest'
|
|
import path from 'path'
|
|
import execa from 'execa'
|
|
import fs from 'fs-extra'
|
|
import glob from 'globby'
|
|
import waitForExpect from 'wait-for-expect'
|
|
import { fileURLToPath } from 'url'
|
|
import { debouncePromise } from '../src/utils'
|
|
|
|
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
|
|
const cacheDir = path.resolve(__dirname, '.cache')
|
|
const bin = path.resolve(__dirname, '../dist/cli-default.js')
|
|
|
|
const getTestName = () => {
|
|
const name = expect
|
|
.getState()
|
|
.currentTestName?.replace(/^[a-z]+/g, '_')
|
|
.replace(/-/g, '_')
|
|
|
|
if (!name) {
|
|
throw new Error('No test name')
|
|
}
|
|
|
|
return name
|
|
}
|
|
|
|
beforeAll(async () => {
|
|
await fs.remove(cacheDir)
|
|
console.log(`Installing dependencies in ./test folder`)
|
|
await execa('pnpm', ['i'], { cwd: __dirname })
|
|
console.log(`Done... start testing..`)
|
|
})
|
|
|
|
function filenamify(input: string) {
|
|
return input.replace(/[^a-zA-Z0-9]/g, '-')
|
|
}
|
|
|
|
async function run(
|
|
title: string,
|
|
files: { [name: string]: string },
|
|
options: {
|
|
entry?: string[]
|
|
flags?: string[]
|
|
env?: Record<string, string>
|
|
} = {}
|
|
) {
|
|
const testDir = path.resolve(cacheDir, filenamify(title))
|
|
|
|
// Write entry files on disk
|
|
await Promise.all(
|
|
Object.keys(files).map((name) => {
|
|
return fs.outputFile(path.resolve(testDir, name), files[name], 'utf8')
|
|
})
|
|
)
|
|
|
|
const entry = options.entry || ['input.ts']
|
|
|
|
// Run tsup cli
|
|
const { exitCode, stdout, stderr } = await execa(
|
|
bin,
|
|
[...entry, ...(options.flags || [])],
|
|
{
|
|
cwd: testDir,
|
|
env: { ...process.env, ...options.env },
|
|
}
|
|
)
|
|
const logs = stdout + stderr
|
|
if (exitCode !== 0) {
|
|
throw new Error(logs)
|
|
}
|
|
|
|
// Get output
|
|
const outFiles = await glob('**/*', {
|
|
cwd: path.resolve(testDir, 'dist'),
|
|
}).then((res) => res.sort())
|
|
|
|
return {
|
|
get output() {
|
|
return fs.readFileSync(path.resolve(testDir, 'dist/input.js'), 'utf8')
|
|
},
|
|
outFiles,
|
|
logs,
|
|
outDir: path.resolve(testDir, 'dist'),
|
|
getFileContent(filename: string) {
|
|
return fs.readFile(path.resolve(testDir, filename), 'utf8')
|
|
},
|
|
}
|
|
}
|
|
|
|
test('simple', async () => {
|
|
const { output, outFiles } = await run(getTestName(), {
|
|
'input.ts': `import foo from './foo';export default foo`,
|
|
'foo.ts': `export default 'foo'`,
|
|
})
|
|
expect(output).toMatchSnapshot()
|
|
expect(outFiles).toEqual(['input.js'])
|
|
})
|
|
|
|
test('bundle graphql-tools with --dts flag', async () => {
|
|
await run(
|
|
getTestName(),
|
|
{
|
|
'input.ts': `export { makeExecutableSchema } from 'graphql-tools'`,
|
|
},
|
|
{
|
|
flags: ['--dts'],
|
|
}
|
|
)
|
|
})
|
|
|
|
test('bundle graphql-tools with --dts-resolve flag', async () => {
|
|
await run(
|
|
getTestName(),
|
|
{
|
|
'input.ts': `export { makeExecutableSchema } from 'graphql-tools'`,
|
|
},
|
|
{
|
|
flags: ['--dts-resolve'],
|
|
}
|
|
)
|
|
})
|
|
|
|
test('bundle vue and ts-essentials with --dts --dts-resolve flag', async () => {
|
|
await run(
|
|
getTestName(),
|
|
{
|
|
'input.ts': `export * from 'vue'
|
|
export type { MarkRequired } from 'ts-essentials'
|
|
`,
|
|
},
|
|
{
|
|
flags: ['--dts', '--dts-resolve'],
|
|
}
|
|
)
|
|
})
|
|
|
|
test('bundle @egoist/path-parser with --dts --dts-resolve flag', async () => {
|
|
const { getFileContent } = await run(
|
|
getTestName(),
|
|
{
|
|
'input.ts': `import { PathParser } from '@egoist/path-parser'
|
|
export type Opts = {
|
|
parser: PathParser
|
|
route: string
|
|
}
|
|
`,
|
|
},
|
|
{
|
|
flags: ['--dts', '--dts-resolve'],
|
|
}
|
|
)
|
|
})
|
|
|
|
test('enable --dts-resolve for specific module', async () => {
|
|
const { getFileContent } = await run(getTestName(), {
|
|
'input.ts': `export * from 'vue'
|
|
export type {MarkRequired} from 'foo'
|
|
`,
|
|
'node_modules/foo/index.d.ts': `
|
|
export type MarkRequired<T, RK extends keyof T> = Exclude<T, RK> & Required<Pick<T, RK>>
|
|
`,
|
|
'node_modules/foo/package.json': `{ "name": "foo", "version": "0.0.0" }`,
|
|
'tsup.config.ts': `
|
|
export default {
|
|
dts: {
|
|
resolve: ['foo']
|
|
},
|
|
}
|
|
`,
|
|
})
|
|
const content = await getFileContent('dist/input.d.ts')
|
|
expect(content).toMatchSnapshot()
|
|
})
|
|
|
|
test('bundle graphql-tools with --sourcemap flag', async () => {
|
|
const { outFiles } = await run(
|
|
getTestName(),
|
|
{
|
|
'input.ts': `export { makeExecutableSchema } from 'graphql-tools'`,
|
|
},
|
|
{
|
|
flags: ['--sourcemap'],
|
|
}
|
|
)
|
|
expect(outFiles).toEqual(['input.js', 'input.js.map'])
|
|
})
|
|
|
|
test('bundle graphql-tools with --sourcemap inline flag', async () => {
|
|
const { output, outFiles } = await run(
|
|
getTestName(),
|
|
{
|
|
'input.ts': `export { makeExecutableSchema } from 'graphql-tools'`,
|
|
},
|
|
{
|
|
flags: ['--sourcemap', 'inline'],
|
|
}
|
|
)
|
|
|
|
expect(output).toContain('//# sourceMappingURL=data:application/json;base64')
|
|
expect(outFiles).toEqual(['input.js'])
|
|
})
|
|
|
|
test('multiple formats', async () => {
|
|
const { output, outFiles } = await run(
|
|
getTestName(),
|
|
{
|
|
'input.ts': `
|
|
export const a = 1
|
|
`,
|
|
},
|
|
{
|
|
flags: ['--format', 'esm,cjs,iife'],
|
|
}
|
|
)
|
|
|
|
expect(outFiles).toEqual(['input.global.js', 'input.js', 'input.mjs'])
|
|
})
|
|
|
|
test('multiple formats and pkg.type is module', async () => {
|
|
const { output, outFiles } = await run(
|
|
getTestName(),
|
|
{
|
|
'input.ts': `
|
|
export const a = 1
|
|
`,
|
|
'package.json': JSON.stringify({ type: 'module' }),
|
|
},
|
|
{
|
|
flags: ['--format', 'esm,cjs,iife'],
|
|
}
|
|
)
|
|
|
|
expect(outFiles).toEqual(['input.cjs', 'input.global.js', 'input.js'])
|
|
})
|
|
|
|
test('minify', async () => {
|
|
const { output, outFiles } = await run(
|
|
getTestName(),
|
|
{
|
|
'input.ts': `
|
|
export function foo() {
|
|
return 'foo'
|
|
}
|
|
`,
|
|
},
|
|
{
|
|
flags: ['--minify'],
|
|
}
|
|
)
|
|
|
|
expect(output).toContain(`return"foo"`)
|
|
expect(outFiles).toEqual(['input.js'])
|
|
})
|
|
|
|
test('minify with es5 target', async () => {
|
|
const { output, outFiles } = await run(
|
|
getTestName(),
|
|
{
|
|
'input.ts': `
|
|
export function foo() {
|
|
return 'foo'
|
|
}
|
|
`,
|
|
},
|
|
{
|
|
flags: ['--minify', '--target', 'es5'],
|
|
}
|
|
)
|
|
|
|
expect(output).toContain(`return"foo"`)
|
|
expect(outFiles).toEqual(['input.js'])
|
|
})
|
|
|
|
test('env flag', async () => {
|
|
const { output, outFiles } = await run(
|
|
getTestName(),
|
|
{
|
|
'input.ts': `
|
|
export const env = process.env.NODE_ENV
|
|
`,
|
|
},
|
|
{
|
|
flags: ['--env.NODE_ENV', 'production'],
|
|
}
|
|
)
|
|
|
|
expect(output).toContain('var env = "production"')
|
|
expect(outFiles).toEqual(['input.js'])
|
|
})
|
|
|
|
test('import css', async () => {
|
|
const { output, outFiles } = await run(getTestName(), {
|
|
'input.ts': `
|
|
import './foo.css'
|
|
`,
|
|
'postcss.config.js': `
|
|
module.exports = {
|
|
plugins: [require('postcss-simple-vars')()]
|
|
}
|
|
`,
|
|
'foo.css': `
|
|
$color: blue;
|
|
|
|
.foo {
|
|
color: $color;
|
|
}
|
|
`,
|
|
})
|
|
|
|
expect(output, `""`).toMatchSnapshot()
|
|
expect(outFiles).toEqual(['input.css', 'input.js'])
|
|
})
|
|
|
|
test('import css in --dts', async () => {
|
|
const { output, outFiles } = await run(
|
|
getTestName(),
|
|
{
|
|
'input.ts': `
|
|
import './foo.css'
|
|
`,
|
|
'foo.css': `
|
|
.foo {
|
|
color: blue
|
|
}
|
|
`,
|
|
},
|
|
{ flags: ['--dts'] }
|
|
)
|
|
|
|
expect(output).toMatchSnapshot()
|
|
expect(outFiles).toEqual(['input.css', 'input.d.ts', 'input.js'])
|
|
})
|
|
|
|
test('node protocol', async () => {
|
|
const { output } = await run(getTestName(), {
|
|
'input.ts': `import fs from 'node:fs'; console.log(fs)`,
|
|
})
|
|
expect(output).toMatchSnapshot()
|
|
})
|
|
|
|
test('external', async () => {
|
|
const { output } = await run(getTestName(), {
|
|
'input.ts': `export {foo} from 'foo'
|
|
export {bar} from 'bar'
|
|
export {baz} from 'baz'
|
|
export {qux} from 'qux'
|
|
`,
|
|
'node_modules/foo/index.ts': `export const foo = 'foo'`,
|
|
'node_modules/foo/package.json': `{"name":"foo","version":"0.0.0"}`,
|
|
'node_modules/bar/index.ts': `export const bar = 'bar'`,
|
|
'node_modules/bar/package.json': `{"name":"bar","version":"0.0.0"}`,
|
|
'node_modules/baz/index.ts': `export const baz = 'baz'`,
|
|
'node_modules/baz/package.json': `{"name":"baz","version":"0.0.0"}`,
|
|
'node_modules/qux/index.ts': `export const qux = 'qux'`,
|
|
'node_modules/qux/package.json': `{"name":"qux","version":"0.0.0"}`,
|
|
'another/package.json': `{"name":"another-pkg","dependencies":{"qux":"0.0.0"}}`,
|
|
'tsup.config.ts': `
|
|
export default {
|
|
external: [/f/, 'bar', 'another/package.json']
|
|
}
|
|
`,
|
|
})
|
|
expect(output).toMatchSnapshot()
|
|
})
|
|
|
|
test('disable code splitting to get proper module.exports =', async () => {
|
|
const { output } = await run(
|
|
getTestName(),
|
|
{
|
|
'input.ts': `export = 123`,
|
|
},
|
|
{
|
|
flags: ['--no-splitting'],
|
|
}
|
|
)
|
|
expect(output).toMatchSnapshot()
|
|
})
|
|
|
|
test('bundle svelte', async () => {
|
|
const { output, getFileContent } = await run(
|
|
getTestName(),
|
|
{
|
|
'input.ts': `import App from './App.svelte'
|
|
export { App }
|
|
`,
|
|
'App.svelte': `
|
|
<script>
|
|
let msg = 'hello svelte'
|
|
</script>
|
|
|
|
<span>{msg}</span>
|
|
|
|
<style>
|
|
span {color: red}
|
|
</style>
|
|
`,
|
|
},
|
|
{
|
|
// To make the snapshot leaner
|
|
flags: ['--external', 'svelte/internal'],
|
|
}
|
|
)
|
|
expect(output).not.toContain('<script>')
|
|
const css = await getFileContent('dist/input.css')
|
|
expect(css).toContain('color: red;')
|
|
})
|
|
|
|
test('bundle svelte without styles', async () => {
|
|
const { outFiles } = await run(getTestName(), {
|
|
'input.ts': `import App from './App.svelte'
|
|
export { App }
|
|
`,
|
|
'App.svelte': `
|
|
<script>
|
|
let msg = 'hello svelte'
|
|
</script>
|
|
|
|
<span>{msg}</span>
|
|
`,
|
|
})
|
|
|
|
expect(outFiles).toEqual(['input.js'])
|
|
})
|
|
|
|
test('svelte: typescript support', async () => {
|
|
const { outFiles, output } = await run(getTestName(), {
|
|
'input.ts': `import App from './App.svelte'
|
|
export { App }
|
|
`,
|
|
'App.svelte': `
|
|
<script lang="ts">
|
|
import Component from './Component.svelte'
|
|
let say: string = 'hello'
|
|
let name: string = 'svelte'
|
|
</script>
|
|
|
|
<Component {name}>{say}</Component>
|
|
`,
|
|
'Component.svelte': `
|
|
<script lang="ts">
|
|
export let name: string
|
|
</script>
|
|
|
|
<slot /> {name}
|
|
`,
|
|
})
|
|
|
|
expect(outFiles).toEqual(['input.js'])
|
|
expect(output).toContain('// Component.svelte')
|
|
})
|
|
|
|
test('onSuccess', async () => {
|
|
const { logs } = await run(
|
|
getTestName(),
|
|
{
|
|
'input.ts': "console.log('test');",
|
|
},
|
|
{
|
|
flags: ['--onSuccess', 'echo hello && echo world'],
|
|
}
|
|
)
|
|
|
|
expect(logs.includes('hello')).toEqual(true)
|
|
expect(logs.includes('world')).toEqual(true)
|
|
})
|
|
|
|
test('onSuccess: use a function from config file', async () => {
|
|
const { logs } = await run(getTestName(), {
|
|
'input.ts': "console.log('test');",
|
|
'tsup.config.ts': `
|
|
export default {
|
|
onSuccess: async () => {
|
|
console.log('hello')
|
|
await new Promise((resolve) => {
|
|
setTimeout(() => {
|
|
console.log('world')
|
|
resolve('')
|
|
}, 1_000)
|
|
})
|
|
}
|
|
}`,
|
|
})
|
|
|
|
expect(logs.includes('hello')).toEqual(true)
|
|
expect(logs.includes('world')).toEqual(true)
|
|
})
|
|
|
|
test('custom tsconfig', async () => {
|
|
await run(
|
|
getTestName(),
|
|
{
|
|
'input.ts': `export const foo = 'foo'`,
|
|
'tsconfig.build.json': `{
|
|
"compilerOptions": {
|
|
"baseUrl":"."
|
|
}
|
|
}`,
|
|
},
|
|
{ flags: ['--tsconfig', 'tsconfig.build.json'] }
|
|
)
|
|
})
|
|
|
|
test('support baseUrl and paths in tsconfig.json', async () => {
|
|
const { getFileContent } = await run(getTestName(), {
|
|
'input.ts': `export * from '@/foo'`,
|
|
'foo.ts': `export const foo = 'foo'`,
|
|
'tsconfig.json': `{
|
|
"compilerOptions": {
|
|
"baseUrl":".",
|
|
"paths":{"@/*": ["./*"]}
|
|
}
|
|
}`,
|
|
})
|
|
expect(await getFileContent('dist/input.js')).toMatchSnapshot()
|
|
})
|
|
|
|
test('support baseUrl and paths in tsconfig.json in --dts build', async () => {
|
|
const { getFileContent } = await run(
|
|
getTestName(),
|
|
{
|
|
'input.ts': `export * from '@/foo'`,
|
|
'src/foo.ts': `export const foo = 'foo'`,
|
|
'tsconfig.json': `{
|
|
"compilerOptions": {
|
|
"baseUrl":".",
|
|
"paths":{"@/*": ["./src/*"]}
|
|
}
|
|
}`,
|
|
},
|
|
{ flags: ['--dts'] }
|
|
)
|
|
expect(await getFileContent('dist/input.d.ts')).toMatchSnapshot()
|
|
})
|
|
|
|
test('support baseUrl and paths in tsconfig.json in --dts-resolve build', async () => {
|
|
const { getFileContent } = await run(
|
|
getTestName(),
|
|
{
|
|
'input.ts': `export * from '@/foo'`,
|
|
'src/foo.ts': `export const foo = 'foo'`,
|
|
'tsconfig.json': `{
|
|
"compilerOptions": {
|
|
"baseUrl":".",
|
|
"paths":{"@/*": ["./src/*"]}
|
|
}
|
|
}`,
|
|
},
|
|
{ flags: ['--dts-resolve'] }
|
|
)
|
|
expect(await getFileContent('dist/input.d.ts')).toMatchSnapshot()
|
|
})
|
|
|
|
test(`transform import.meta.url in cjs format`, async () => {
|
|
const { getFileContent } = await run(
|
|
getTestName(),
|
|
{
|
|
'input.ts': `export default import.meta.url`,
|
|
},
|
|
{
|
|
flags: ['--shims'],
|
|
}
|
|
)
|
|
expect(await getFileContent('dist/input.js')).toContain('getImportMetaUrl')
|
|
})
|
|
|
|
test(`transform __dirname and __filename in esm format`, async () => {
|
|
const { getFileContent } = await run(
|
|
getTestName(),
|
|
{
|
|
'input.ts': `export const a = __dirname
|
|
export const b = __filename
|
|
`,
|
|
},
|
|
{
|
|
flags: ['--format', 'esm', '--shims'],
|
|
}
|
|
)
|
|
const code = await getFileContent('dist/input.mjs')
|
|
|
|
expect(code).toContain('getFilename')
|
|
expect(code).toContain('getDirname')
|
|
})
|
|
|
|
test('debounce promise', async () => {
|
|
try {
|
|
const equal = <T>(a: T, b: T) => {
|
|
const result = a === b
|
|
if (!result) throw new Error(`${a} !== ${b}`)
|
|
}
|
|
|
|
const sleep = (n: number = ~~(Math.random() * 50) + 20) =>
|
|
new Promise<void>((resolve) => setTimeout(resolve, n))
|
|
|
|
let n = 0
|
|
|
|
const debounceFunction = debouncePromise(
|
|
async () => {
|
|
await sleep()
|
|
++n
|
|
},
|
|
100,
|
|
(err: any) => {
|
|
expect.fail(err.message)
|
|
}
|
|
)
|
|
|
|
expect(n).toEqual(0)
|
|
|
|
debounceFunction()
|
|
debounceFunction()
|
|
debounceFunction()
|
|
debounceFunction()
|
|
|
|
await waitForExpect(() => {
|
|
equal(n, 1)
|
|
})
|
|
await sleep(100)
|
|
|
|
expect(n).toEqual(1)
|
|
|
|
debounceFunction()
|
|
|
|
await waitForExpect(() => {
|
|
equal(n, 2)
|
|
})
|
|
} catch (err: any) {
|
|
return expect.fail(err.message)
|
|
}
|
|
})
|
|
|
|
test('exclude dependencies', async () => {
|
|
const { getFileContent } = await run(getTestName(), {
|
|
'input.ts': `export {foo} from 'foo';export {nested} from 'foo/nested'`,
|
|
'package.json': `{"dependencies":{"foo":"0.0.0"}}`,
|
|
'node_modules/foo/index.js': `export const foo = 'foo'`,
|
|
'node_modules/foo/package.json': `{"name":"foo"}`,
|
|
})
|
|
const contents = await getFileContent('dist/input.js')
|
|
expect(contents).toContain('require("foo")')
|
|
expect(contents).toContain('require("foo/nested")')
|
|
})
|
|
|
|
test('code splitting in cjs format', async () => {
|
|
const { getFileContent } = await run(
|
|
getTestName(),
|
|
{
|
|
'input.ts': `const foo = () => import('./foo');export {foo}`,
|
|
'another-input.ts': `const foo = () => import('./foo');export {foo}`,
|
|
'foo.ts': `export const foo = 'bar'`,
|
|
},
|
|
{ flags: ['another-input.ts', '--splitting'] }
|
|
)
|
|
expect(await getFileContent('dist/input.js')).toMatchSnapshot()
|
|
expect(await getFileContent('dist/another-input.js')).toMatchSnapshot()
|
|
})
|
|
|
|
test('declaration files with multiple entrypoints #316', async () => {
|
|
const { getFileContent } = await run(
|
|
getTestName(),
|
|
{
|
|
'src/index.ts': `export const foo = 1`,
|
|
'src/bar/index.ts': `export const bar = 'bar'`,
|
|
},
|
|
{ flags: ['--dts'], entry: ['src/index.ts', 'src/bar/index.ts'] }
|
|
)
|
|
expect(
|
|
await getFileContent('dist/index.d.ts'),
|
|
'dist/index.d.ts'
|
|
).toMatchSnapshot()
|
|
expect(
|
|
await getFileContent('dist/bar/index.d.ts'),
|
|
'dist/bar/index.d.ts'
|
|
).toMatchSnapshot()
|
|
})
|
|
|
|
test('esbuild metafile', async () => {
|
|
const { outFiles } = await run(
|
|
getTestName(),
|
|
{ 'input.ts': `export const foo = 1` },
|
|
{
|
|
flags: ['--metafile'],
|
|
}
|
|
)
|
|
expect(outFiles).toEqual(['input.js', 'metafile-cjs.json'])
|
|
})
|
|
|
|
test('multiple entry with the same base name', async () => {
|
|
const { outFiles } = await run(
|
|
getTestName(),
|
|
{
|
|
'src/input.ts': `export const foo = 1`,
|
|
'src/bar/input.ts': `export const bar = 2`,
|
|
},
|
|
{
|
|
entry: ['src/input.ts', 'src/bar/input.ts'],
|
|
}
|
|
)
|
|
expect(outFiles).toEqual(['bar/input.js', 'input.js'])
|
|
})
|
|
|
|
test('windows: backslash in entry', async () => {
|
|
const { outFiles } = await run(
|
|
getTestName(),
|
|
{ 'src/input.ts': `export const foo = 1` },
|
|
{
|
|
entry: ['src\\input.ts'],
|
|
}
|
|
)
|
|
expect(outFiles).toEqual(['input.js'])
|
|
})
|
|
|
|
test('emit declaration files only', async () => {
|
|
const { outFiles } = await run(
|
|
getTestName(),
|
|
{
|
|
'input.ts': `export const foo = 1`,
|
|
},
|
|
{
|
|
flags: ['--dts-only'],
|
|
}
|
|
)
|
|
expect(outFiles).toEqual(['input.d.ts'])
|
|
})
|
|
|
|
test('decorator metadata', async () => {
|
|
const { getFileContent } = await run(getTestName(), {
|
|
'input.ts': `
|
|
function Injectable() {}
|
|
|
|
@Injectable()
|
|
export class Foo {
|
|
@Field()
|
|
bar() {}
|
|
}
|
|
`,
|
|
'tsconfig.json': `{
|
|
"compilerOptions": {
|
|
"emitDecoratorMetadata": true,
|
|
}
|
|
}`,
|
|
})
|
|
const contents = await getFileContent('dist/input.js')
|
|
expect(contents).toContain(`__metadata("design:type", Function)`)
|
|
})
|
|
|
|
test('inject style', async () => {
|
|
const { outFiles, output } = await run(
|
|
getTestName(),
|
|
{
|
|
'input.ts': `import './style.css'`,
|
|
'style.css': `.hello { color: red }`,
|
|
},
|
|
{
|
|
flags: ['--inject-style', '--minify'],
|
|
}
|
|
)
|
|
expect(outFiles).toEqual(['input.js'])
|
|
expect(output).toContain('.hello{color:red}')
|
|
})
|
|
|
|
test('inject style in multi formats', async () => {
|
|
const { outFiles, getFileContent } = await run(
|
|
getTestName(),
|
|
{
|
|
'input.ts': `export * from './App.svelte'`,
|
|
'App.svelte': `
|
|
<span>{msg}</span>
|
|
|
|
<style>
|
|
span {color: red}
|
|
</style>`,
|
|
},
|
|
{
|
|
flags: ['--inject-style', '--minify', '--format', 'esm,cjs,iife'],
|
|
}
|
|
)
|
|
expect(outFiles).toEqual(['input.global.js', 'input.js', 'input.mjs'])
|
|
for (const file of outFiles) {
|
|
expect(await getFileContent(`dist/${file}`)).toContain('{color:red}')
|
|
}
|
|
})
|
|
|
|
test('shebang', async () => {
|
|
const { outDir } = await run(
|
|
getTestName(),
|
|
{
|
|
'a.ts': `#!/usr/bin/env node\bconsole.log('a')`,
|
|
'b.ts': `console.log('b')`,
|
|
},
|
|
{
|
|
entry: ['a.ts', 'b.ts'],
|
|
}
|
|
)
|
|
|
|
if (process.platform === 'win32') {
|
|
return
|
|
}
|
|
|
|
expect(() => {
|
|
fs.accessSync(path.join(outDir, 'a.js'), fs.constants.X_OK)
|
|
}).not.toThrow()
|
|
expect(() => {
|
|
fs.accessSync(path.join(outDir, 'b.js'), fs.constants.X_OK)
|
|
}).toThrow()
|
|
})
|
|
|
|
test('es5 target', async () => {
|
|
const { output, outFiles } = await run(
|
|
getTestName(),
|
|
{
|
|
'input.ts': `
|
|
export class Foo {
|
|
hi (): void {
|
|
let a = () => 'foo'
|
|
|
|
console.log(a())
|
|
}
|
|
}
|
|
`,
|
|
},
|
|
{
|
|
flags: ['--target', 'es5'],
|
|
}
|
|
)
|
|
expect(output).toMatch(/createClass/)
|
|
expect(outFiles).toEqual(['input.js'])
|
|
})
|
|
|
|
test('es5 minify', async () => {
|
|
const { getFileContent, outFiles } = await run(
|
|
getTestName(),
|
|
{
|
|
'input.ts': `
|
|
export class Foo {
|
|
hi (): void {
|
|
let a = () => 'foo'
|
|
|
|
console.log(a())
|
|
}
|
|
}
|
|
`,
|
|
},
|
|
{
|
|
flags: [
|
|
'--target',
|
|
'es5',
|
|
'--format',
|
|
'iife',
|
|
'--globalName',
|
|
'FooAPI',
|
|
'--minify',
|
|
],
|
|
}
|
|
)
|
|
expect(outFiles).toEqual(['input.global.js'])
|
|
const iifeBundle = await getFileContent('dist/input.global.js')
|
|
expect(iifeBundle).toMatch(/var FooAPI/)
|
|
expect(iifeBundle).not.toMatch(/createClass/)
|
|
})
|
|
|
|
test('multiple targets', async () => {
|
|
const { output, outFiles } = await run(
|
|
getTestName(),
|
|
{
|
|
'input.ts': `
|
|
export const answer = 42
|
|
`,
|
|
},
|
|
{
|
|
entry: ['input.ts'],
|
|
flags: ['--target', 'es2020,chrome58,firefox57,safari11,edge16'],
|
|
}
|
|
)
|
|
expect(output).toMatchSnapshot()
|
|
expect(outFiles).toEqual(['input.js'])
|
|
})
|
|
|
|
test('dts only: ignore files', async () => {
|
|
const { outFiles } = await run(
|
|
getTestName(),
|
|
{
|
|
'input.ts': `
|
|
import './style.scss'
|
|
|
|
export const a = 1
|
|
`,
|
|
'style.scss': `
|
|
@keyframes gallery-loading-spinner {
|
|
0% {}
|
|
}
|
|
`,
|
|
},
|
|
{
|
|
entry: ['input.ts'],
|
|
flags: ['--dts-only'],
|
|
}
|
|
)
|
|
expect(outFiles).toMatchInlineSnapshot(`
|
|
[
|
|
"input.d.ts",
|
|
]
|
|
`)
|
|
})
|
|
|
|
test('native-node-module plugin should handle *.node(.js) import properly', async () => {
|
|
await run(
|
|
getTestName(),
|
|
{
|
|
'input.tsx': `export * from './hi.node'`,
|
|
'hi.node.js': `export const hi = 'hi'`,
|
|
},
|
|
{
|
|
entry: ['input.tsx'],
|
|
}
|
|
)
|
|
})
|
|
|
|
test('proper sourcemap sources path when swc is enabled', async () => {
|
|
const { getFileContent } = await run(
|
|
getTestName(),
|
|
{
|
|
'input.ts': `export const hi = 'hi'`,
|
|
'tsconfig.json': JSON.stringify({
|
|
compilerOptions: {
|
|
emitDecoratorMetadata: true,
|
|
},
|
|
}),
|
|
},
|
|
{
|
|
entry: ['input.ts'],
|
|
flags: ['--sourcemap'],
|
|
}
|
|
)
|
|
const map = await getFileContent('dist/input.js.map')
|
|
expect(map).toContain(`["../input.ts"]`)
|
|
})
|
|
|
|
// Fixing https://github.com/evanw/esbuild/issues/1794
|
|
test('use rollup for treeshaking', async () => {
|
|
const { getFileContent } = await run(
|
|
getTestName(),
|
|
{
|
|
'input.ts': `
|
|
export { useRoute } from 'vue-router'
|
|
`,
|
|
},
|
|
{
|
|
entry: ['input.ts'],
|
|
flags: ['--treeshake', '--external', 'vue', '--format', 'esm'],
|
|
}
|
|
)
|
|
expect(await getFileContent('dist/input.mjs')).toContain(
|
|
`function useRoute() {
|
|
return inject(routeLocationKey);
|
|
}`
|
|
)
|
|
})
|
|
|
|
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",
|
|
]
|
|
`)
|
|
})
|
|
|
|
test('custom config file', async () => {
|
|
const { outFiles } = await run(
|
|
getTestName(),
|
|
{
|
|
'input.ts': `export const foo = [1,2,3]`,
|
|
'custom.config.ts': `export default {
|
|
format: ['esm']
|
|
}`,
|
|
},
|
|
{
|
|
entry: ['input.ts'],
|
|
flags: ['--config', 'custom.config.ts'],
|
|
}
|
|
)
|
|
expect(outFiles).toMatchInlineSnapshot(`
|
|
[
|
|
"input.mjs",
|
|
]
|
|
`)
|
|
})
|
|
|
|
test('use an object as entry from cli flag', async () => {
|
|
const { outFiles } = await run(
|
|
getTestName(),
|
|
{
|
|
'input.ts': `export const foo = [1,2,3]`,
|
|
},
|
|
{
|
|
flags: ['--entry.foo', 'input.ts'],
|
|
}
|
|
)
|
|
expect(outFiles).toMatchInlineSnapshot(`
|
|
[
|
|
"foo.js",
|
|
]
|
|
`)
|
|
})
|
|
|
|
test('remove unused code', async () => {
|
|
const { getFileContent } = await run(
|
|
getTestName(),
|
|
{
|
|
'input.ts': `if (import.meta.foo) {
|
|
console.log(1)
|
|
} else {
|
|
console.log(2)
|
|
}`,
|
|
'tsup.config.ts': `export default {
|
|
define: {
|
|
'import.meta.foo': false
|
|
},
|
|
treeshake: true
|
|
}`,
|
|
},
|
|
{}
|
|
)
|
|
expect(await getFileContent('dist/input.js')).not.toContain('console.log(1)')
|
|
})
|
|
|
|
test('treeshake should work with hashbang', async () => {
|
|
const { getFileContent } = await run(
|
|
getTestName(),
|
|
{
|
|
'input.ts': '#!/usr/bin/node\nconsole.log(123)',
|
|
},
|
|
{
|
|
flags: ['--treeshake'],
|
|
}
|
|
)
|
|
expect(await getFileContent('dist/input.js')).toMatchInlineSnapshot(`
|
|
"#!/usr/bin/node
|
|
'use strict';
|
|
|
|
// input.ts
|
|
console.log(123);
|
|
"
|
|
`)
|
|
})
|