From 8fb5d26bb4d2a881e9d7a8d81a8f24c1f8f6f0af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=A8=E6=9D=89?= Date: Wed, 8 Dec 2021 14:55:01 +0800 Subject: [PATCH 01/12] feat: Simple CSS injection support (#483) Co-authored-by: EGOIST <0x142857@gmail.com> --- src/cli-main.ts | 2 +- src/esbuild/index.ts | 2 +- src/esbuild/postcss.ts | 102 ++++++++++++++++++++++++++++++++--------- src/options.ts | 6 +-- test/index.test.ts | 15 ++++++ 5 files changed, 100 insertions(+), 27 deletions(-) diff --git a/src/cli-main.ts b/src/cli-main.ts index d9a0e17e..06876bf6 100644 --- a/src/cli-main.ts +++ b/src/cli-main.ts @@ -84,7 +84,7 @@ export async function main(options: Options = {}) { }) .option('--loader ', 'Specify the loader for a file extension') .option('--no-config', 'Disable config file') - .option('--no-shims', 'Disable cjs and esm shims') + .option('--inject-style', 'Inject style tag to document head') .action(async (files: string[], flags) => { const { build } = await import('.') Object.assign(options, { diff --git a/src/esbuild/index.ts b/src/esbuild/index.ts index eb5aba70..7ec68586 100644 --- a/src/esbuild/index.ts +++ b/src/esbuild/index.ts @@ -109,7 +109,7 @@ export async function runEsbuild( }), options.tsconfigDecoratorMetadata && swcPlugin({ logger }), nativeNodeModulesPlugin(), - postcssPlugin({ css }), + postcssPlugin({ css, inject: options.injectStyle }), sveltePlugin({ css }), ...(options.esbuildPlugins || []), ] diff --git a/src/esbuild/postcss.ts b/src/esbuild/postcss.ts index 2f07cf14..54a23d75 100644 --- a/src/esbuild/postcss.ts +++ b/src/esbuild/postcss.ts @@ -1,12 +1,14 @@ import fs from 'fs' import path from 'path' -import { Plugin } from 'esbuild' +import { Plugin, transform } from 'esbuild' import { getPostcss } from '../utils' export const postcssPlugin = ({ css, + inject, }: { css?: Map + inject?: boolean }): Plugin => { return { name: 'postcss', @@ -25,7 +27,7 @@ export const postcssPlugin = ({ const result = await loadConfig({}, path.dirname(file)) configCache.set(file, result) return result - } catch (error) { + } catch (error: any) { if (error.message.includes('No PostCSS Config found in')) { const result = { plugins: [], options: {} } return result @@ -34,11 +36,50 @@ export const postcssPlugin = ({ } } + build.onResolve({ filter: /^#style-inject$/ }, () => { + return { path: '#style-inject', namespace: '#style-inject' } + }) + + build.onLoad( + { filter: /^#style-inject$/, namespace: '#style-inject' }, + () => { + return { + // Taken from https://github.com/egoist/style-inject/blob/master/src/index.js (MIT) + contents: ` + export default function styleInject(css, { insertAt } = {}) { + if (!css || typeof document === 'undefined') return + + const head = document.head || document.getElementsByTagName('head')[0] + const style = document.createElement('style') + style.type = 'text/css' + + if (insertAt === 'top') { + if (head.firstChild) { + head.insertBefore(style, head.firstChild) + } else { + head.appendChild(style) + } + } else { + head.appendChild(style) + } + + if (style.styleSheet) { + style.styleSheet.cssText = css + } else { + style.appendChild(document.createTextNode(css)) + } + } + `, + loader: 'js', + } + } + ) + build.onLoad({ filter: /\.css$/ }, async (args) => { let contents: string if (css && args.path.endsWith('.svelte.css')) { - contents = css.get(args.path) as string + contents = css.get(args.path)! } else { contents = await fs.promises.readFile(args.path, 'utf8') } @@ -46,33 +87,50 @@ export const postcssPlugin = ({ // Load postcss config const { plugins, options } = await getPostcssConfig(args.path) - // Return if no postcss plugins are supplied - if (!plugins || plugins.length === 0) { - return { - contents, - loader: 'css', + if (plugins || plugins.length > 0) { + // Load postcss + const postcss = getPostcss() + if (!postcss) { + return { + errors: [ + { + text: `postcss is not installed`, + }, + ], + } } + + // Transform CSS + const result = await postcss + ?.default(plugins) + .process(contents, { ...options, from: args.path }) + + contents = result.css } - // Load postcss - const postcss = getPostcss() - if (!postcss) { + if (inject) { + contents = ( + await transform(contents, { + minify: build.initialOptions.minify, + minifyIdentifiers: build.initialOptions.minifyIdentifiers, + minifySyntax: build.initialOptions.minifySyntax, + minifyWhitespace: build.initialOptions.minifyWhitespace, + loader: 'css', + }) + ).code + + contents = `import styleInject from '#style-inject';styleInject(${JSON.stringify( + contents + )})` + return { - errors: [ - { - text: `postcss is not installed`, - }, - ], + contents, + loader: 'js', } } - // Transform CSS - const result = await postcss - ?.default(plugins) - .process(contents, { ...options, from: args.path }) - return { - contents: result.css, + contents, loader: 'css', } }) diff --git a/src/options.ts b/src/options.ts index 5a7cb9f5..7af195c9 100644 --- a/src/options.ts +++ b/src/options.ts @@ -123,8 +123,8 @@ export type Options = { */ tsconfig?: string /** - * CJS and ESM shims - * @default `true` + * Inject CSS as style tags to document head + * @default {false} */ - shims?: boolean + injectStyle?: boolean } diff --git a/test/index.test.ts b/test/index.test.ts index 3e30cc85..98deeabd 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -697,3 +697,18 @@ test('decorator metadata', async (t) => { const contents = await getFileContent('dist/input.js') t.assert(contents.includes(`Reflect.metadata("design:type"`)) }) + +test('inject style', async (t) => { + const { outFiles, output } = await run( + t.title, + { + 'input.ts': `import './style.css'`, + 'style.css': `.hello { color: red }` + }, + { + flags: ['--inject-style', '--minify'], + } + ) + t.deepEqual(outFiles, ['input.js']) + t.assert(output.includes('.hello{color:red}')) +}) From 12ca475381fdaf1f62d8e5118efd4483cdd1d012 Mon Sep 17 00:00:00 2001 From: EGOIST <0x142857@gmail.com> Date: Wed, 8 Dec 2021 17:14:24 +0800 Subject: [PATCH 02/12] plugin system --- package.json | 7 +- pnpm-lock.yaml | 421 ++--------------------------------- src/cli-main.ts | 1 - src/esbuild/index.ts | 92 ++------ src/fs.ts | 7 + src/options.ts | 14 +- src/plugin.ts | 135 +++++++++++ src/plugins/cjs-splitting.ts | 39 ++++ src/plugins/shebang.ts | 15 ++ src/utils.ts | 5 - test/index.test.ts | 24 +- types.d.ts | 1 + 12 files changed, 248 insertions(+), 513 deletions(-) create mode 100644 src/fs.ts create mode 100644 src/plugin.ts create mode 100644 src/plugins/cjs-splitting.ts create mode 100644 src/plugins/shebang.ts diff --git a/package.json b/package.json index 9a24e4ef..6cc6d7b6 100644 --- a/package.json +++ b/package.json @@ -45,21 +45,18 @@ "resolve-from": "^5.0.0", "rollup": "^2.60.0", "sucrase": "^3.20.1", - "tree-kill": "^1.2.2" + "tree-kill": "^1.2.2", + "source-map": "^0.7.3" }, "devDependencies": { - "@babel/core": "^7.13.15", "@rollup/plugin-json": "^4.1.0", "@swc/core": "^1.2.112", - "@types/babel__core": "^7.1.16", - "@types/buble": "^0.19.2", "@types/debug": "^4.1.5", "@types/flat": "^5.0.2", "@types/fs-extra": "^9.0.11", "@types/node": "^14.14.41", "@types/resolve": "^1.20.0", "ava": "^4.0.0-rc.1", - "buble": "^0.20.0", "colorette": "^2.0.16", "consola": "^2.15.3", "flat": "^5.0.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 52cb173c..ea07fea3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,18 +1,14 @@ lockfileVersion: 5.3 specifiers: - '@babel/core': ^7.13.15 '@rollup/plugin-json': ^4.1.0 '@swc/core': ^1.2.112 - '@types/babel__core': ^7.1.16 - '@types/buble': ^0.19.2 '@types/debug': ^4.1.5 '@types/flat': ^5.0.2 '@types/fs-extra': ^9.0.11 '@types/node': ^14.14.41 '@types/resolve': ^1.20.0 ava: ^4.0.0-rc.1 - buble: ^0.20.0 bundle-require: ^2.1.7 cac: ^6.7.12 chokidar: ^3.5.1 @@ -34,6 +30,7 @@ specifiers: rollup: ^2.60.0 rollup-plugin-dts: ^3.0.2 rollup-plugin-hashbang: ^2.2.2 + source-map: ^0.7.3 string-argv: ^0.3.1 sucrase: ^3.20.1 svelte: 3.37.0 @@ -56,22 +53,19 @@ dependencies: postcss-load-config: 3.1.0 resolve-from: 5.0.0 rollup: 2.60.1 + source-map: 0.7.3 sucrase: 3.20.3 tree-kill: 1.2.2 devDependencies: - '@babel/core': 7.16.0 '@rollup/plugin-json': 4.1.0_rollup@2.60.1 '@swc/core': 1.2.112 - '@types/babel__core': 7.1.16 - '@types/buble': 0.19.2 '@types/debug': 4.1.7 '@types/flat': 5.0.2 '@types/fs-extra': 9.0.13 '@types/node': 14.17.34 '@types/resolve': 1.20.1 ava: 4.0.0-rc.1 - buble: 0.20.0 colorette: 2.0.16 consola: 2.15.3 flat: 5.0.2 @@ -98,163 +92,13 @@ packages: dependencies: '@babel/highlight': 7.16.0 dev: true - - /@babel/compat-data/7.16.4: - resolution: {integrity: sha512-1o/jo7D+kC9ZjHX5v+EHrdjl3PhxMrLSOTGsOdHJ+KL8HCaEK6ehrVL2RS6oHDZp+L7xLirLrPmQtEng769J/Q==} - engines: {node: '>=6.9.0'} - dev: true - - /@babel/core/7.16.0: - resolution: {integrity: sha512-mYZEvshBRHGsIAiyH5PzCFTCfbWfoYbO/jcSdXQSUQu1/pW0xDZAUP7KEc32heqWTAfAHhV9j1vH8Sav7l+JNQ==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/code-frame': 7.16.0 - '@babel/generator': 7.16.0 - '@babel/helper-compilation-targets': 7.16.3_@babel+core@7.16.0 - '@babel/helper-module-transforms': 7.16.0 - '@babel/helpers': 7.16.3 - '@babel/parser': 7.16.4 - '@babel/template': 7.16.0 - '@babel/traverse': 7.16.3 - '@babel/types': 7.16.0 - convert-source-map: 1.8.0 - debug: 4.3.2 - gensync: 1.0.0-beta.2 - json5: 2.2.0 - semver: 6.3.0 - source-map: 0.5.7 - transitivePeerDependencies: - - supports-color - dev: true - - /@babel/generator/7.16.0: - resolution: {integrity: sha512-RR8hUCfRQn9j9RPKEVXo9LiwoxLPYn6hNZlvUOR8tSnaxlD0p0+la00ZP9/SnRt6HchKr+X0fO2r8vrETiJGew==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.16.0 - jsesc: 2.5.2 - source-map: 0.5.7 - dev: true - - /@babel/helper-compilation-targets/7.16.3_@babel+core@7.16.0: - resolution: {integrity: sha512-vKsoSQAyBmxS35JUOOt+07cLc6Nk/2ljLIHwmq2/NM6hdioUaqEXq/S+nXvbvXbZkNDlWOymPanJGOc4CBjSJA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - dependencies: - '@babel/compat-data': 7.16.4 - '@babel/core': 7.16.0 - '@babel/helper-validator-option': 7.14.5 - browserslist: 4.18.1 - semver: 6.3.0 - dev: true - - /@babel/helper-function-name/7.16.0: - resolution: {integrity: sha512-BZh4mEk1xi2h4HFjWUXRQX5AEx4rvaZxHgax9gcjdLWdkjsY7MKt5p0otjsg5noXw+pB+clMCjw+aEVYADMjog==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/helper-get-function-arity': 7.16.0 - '@babel/template': 7.16.0 - '@babel/types': 7.16.0 - dev: true - - /@babel/helper-get-function-arity/7.16.0: - resolution: {integrity: sha512-ASCquNcywC1NkYh/z7Cgp3w31YW8aojjYIlNg4VeJiHkqyP4AzIvr4qx7pYDb4/s8YcsZWqqOSxgkvjUz1kpDQ==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.16.0 - dev: true - - /@babel/helper-hoist-variables/7.16.0: - resolution: {integrity: sha512-1AZlpazjUR0EQZQv3sgRNfM9mEVWPK3M6vlalczA+EECcPz3XPh6VplbErL5UoMpChhSck5wAJHthlj1bYpcmg==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.16.0 - dev: true - - /@babel/helper-member-expression-to-functions/7.16.0: - resolution: {integrity: sha512-bsjlBFPuWT6IWhl28EdrQ+gTvSvj5tqVP5Xeftp07SEuz5pLnsXZuDkDD3Rfcxy0IsHmbZ+7B2/9SHzxO0T+sQ==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.16.0 - dev: true - - /@babel/helper-module-imports/7.16.0: - resolution: {integrity: sha512-kkH7sWzKPq0xt3H1n+ghb4xEMP8k0U7XV3kkB+ZGy69kDk2ySFW1qPi06sjKzFY3t1j6XbJSqr4mF9L7CYVyhg==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.16.0 - dev: true - - /@babel/helper-module-transforms/7.16.0: - resolution: {integrity: sha512-My4cr9ATcaBbmaEa8M0dZNA74cfI6gitvUAskgDtAFmAqyFKDSHQo5YstxPbN+lzHl2D9l/YOEFqb2mtUh4gfA==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/helper-module-imports': 7.16.0 - '@babel/helper-replace-supers': 7.16.0 - '@babel/helper-simple-access': 7.16.0 - '@babel/helper-split-export-declaration': 7.16.0 - '@babel/helper-validator-identifier': 7.15.7 - '@babel/template': 7.16.0 - '@babel/traverse': 7.16.3 - '@babel/types': 7.16.0 - transitivePeerDependencies: - - supports-color - dev: true - - /@babel/helper-optimise-call-expression/7.16.0: - resolution: {integrity: sha512-SuI467Gi2V8fkofm2JPnZzB/SUuXoJA5zXe/xzyPP2M04686RzFKFHPK6HDVN6JvWBIEW8tt9hPR7fXdn2Lgpw==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.16.0 - dev: true - - /@babel/helper-replace-supers/7.16.0: - resolution: {integrity: sha512-TQxuQfSCdoha7cpRNJvfaYxxxzmbxXw/+6cS7V02eeDYyhxderSoMVALvwupA54/pZcOTtVeJ0xccp1nGWladA==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/helper-member-expression-to-functions': 7.16.0 - '@babel/helper-optimise-call-expression': 7.16.0 - '@babel/traverse': 7.16.3 - '@babel/types': 7.16.0 - transitivePeerDependencies: - - supports-color - dev: true - - /@babel/helper-simple-access/7.16.0: - resolution: {integrity: sha512-o1rjBT/gppAqKsYfUdfHq5Rk03lMQrkPHG1OWzHWpLgVXRH4HnMM9Et9CVdIqwkCQlobnGHEJMsgWP/jE1zUiw==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.16.0 - dev: true - - /@babel/helper-split-export-declaration/7.16.0: - resolution: {integrity: sha512-0YMMRpuDFNGTHNRiiqJX19GjNXA4H0E8jZ2ibccfSxaCogbm3am5WN/2nQNj0YnQwGWM1J06GOcQ2qnh3+0paw==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.16.0 - dev: true + optional: true /@babel/helper-validator-identifier/7.15.7: resolution: {integrity: sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==} engines: {node: '>=6.9.0'} dev: true - - /@babel/helper-validator-option/7.14.5: - resolution: {integrity: sha512-OX8D5eeX4XwcroVW45NMvoYaIuFI+GQpA2a8Gi+X/U/cDUIRsV37qQfF905F0htTRCREQIB4KqPeaveRJUl3Ow==} - engines: {node: '>=6.9.0'} - dev: true - - /@babel/helpers/7.16.3: - resolution: {integrity: sha512-Xn8IhDlBPhvYTvgewPKawhADichOsbkZuzN7qz2BusOM0brChsyXMDJvldWaYMMUNiCQdQzNEioXTp3sC8Nt8w==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/template': 7.16.0 - '@babel/traverse': 7.16.3 - '@babel/types': 7.16.0 - transitivePeerDependencies: - - supports-color - dev: true + optional: true /@babel/highlight/7.16.0: resolution: {integrity: sha512-t8MH41kUQylBtu2+4IQA3atqevA2lRgqA2wyVB/YiWmsDSuylZZuXOUy9ric30hfzauEFfdsuk/eXTRrGrfd0g==} @@ -264,46 +108,7 @@ packages: chalk: 2.4.2 js-tokens: 4.0.0 dev: true - - /@babel/parser/7.16.4: - resolution: {integrity: sha512-6V0qdPUaiVHH3RtZeLIsc+6pDhbYzHR8ogA8w+f+Wc77DuXto19g2QUwveINoS34Uw+W8/hQDGJCx+i4n7xcng==} - engines: {node: '>=6.0.0'} - hasBin: true - dev: true - - /@babel/template/7.16.0: - resolution: {integrity: sha512-MnZdpFD/ZdYhXwiunMqqgyZyucaYsbL0IrjoGjaVhGilz+x8YB++kRfygSOIj1yOtWKPlx7NBp+9I1RQSgsd5A==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/code-frame': 7.16.0 - '@babel/parser': 7.16.4 - '@babel/types': 7.16.0 - dev: true - - /@babel/traverse/7.16.3: - resolution: {integrity: sha512-eolumr1vVMjqevCpwVO99yN/LoGL0EyHiLO5I043aYQvwOJ9eR5UsZSClHVCzfhBduMAsSzgA/6AyqPjNayJag==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/code-frame': 7.16.0 - '@babel/generator': 7.16.0 - '@babel/helper-function-name': 7.16.0 - '@babel/helper-hoist-variables': 7.16.0 - '@babel/helper-split-export-declaration': 7.16.0 - '@babel/parser': 7.16.4 - '@babel/types': 7.16.0 - debug: 4.3.2 - globals: 11.12.0 - transitivePeerDependencies: - - supports-color - dev: true - - /@babel/types/7.16.0: - resolution: {integrity: sha512-PJgg/k3SdLsGb3hhisFvtLOw5ts113klrpLuIPtCJIU+BB24fqq6lf8RWqKJEjzqXR9AEH1rIb5XTqwBHB+kQg==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/helper-validator-identifier': 7.15.7 - to-fast-properties: 2.0.0 - dev: true + optional: true /@napi-rs/triples/1.0.3: resolution: {integrity: sha512-jDJTpta+P4p1NZTFVLHJ/TLFVYVcOqv6l8xwOeBKNPMgY/zDYH/YH7SJbvrr/h1RcS9GzbPcLKGzpuK9cV56UA==} @@ -482,41 +287,6 @@ packages: '@swc/core-win32-x64-msvc': 1.2.112 dev: true - /@types/babel__core/7.1.16: - resolution: {integrity: sha512-EAEHtisTMM+KaKwfWdC3oyllIqswlznXCIVCt7/oRNrh+DhgT4UEBNC/jlADNjvw7UnfbcdkGQcPVZ1xYiLcrQ==} - dependencies: - '@babel/parser': 7.16.4 - '@babel/types': 7.16.0 - '@types/babel__generator': 7.6.3 - '@types/babel__template': 7.4.1 - '@types/babel__traverse': 7.14.2 - dev: true - - /@types/babel__generator/7.6.3: - resolution: {integrity: sha512-/GWCmzJWqV7diQW54smJZzWbSFf4QYtF71WCKhcx6Ru/tFyQIY2eiiITcCAeuPbNSvT9YCGkVMqqvSk2Z0mXiA==} - dependencies: - '@babel/types': 7.16.0 - dev: true - - /@types/babel__template/7.4.1: - resolution: {integrity: sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==} - dependencies: - '@babel/parser': 7.16.4 - '@babel/types': 7.16.0 - dev: true - - /@types/babel__traverse/7.14.2: - resolution: {integrity: sha512-K2waXdXBi2302XUdcHcR1jCeU0LL4TD9HRs/gk0N2Xvrht+G/BfJa4QObBQZfhMdxiCpV3COl5Nfq4uKTeTnJA==} - dependencies: - '@babel/types': 7.16.0 - dev: true - - /@types/buble/0.19.2: - resolution: {integrity: sha512-uUD8zIfXMKThmFkahTXDGI3CthFH1kMg2dOm3KLi4GlC5cbARA64bEcUMbbWdWdE73eoc/iBB9PiTMqH0dNS2Q==} - dependencies: - magic-string: 0.25.7 - dev: true - /@types/debug/4.1.7: resolution: {integrity: sha512-9AonUzyTjXXhEOa0DnqpzZi6VHlqKMswga9EXjpXnnqxwLtdvPPtlO8evrI5D9S6asFRCQ6v+wpiUKbw+vKqyg==} dependencies: @@ -553,33 +323,11 @@ packages: resolution: {integrity: sha512-Ku5+GPFa12S3W26Uwtw+xyrtIpaZsGYHH6zxNbZlstmlvMYSZRzOwzwsXbxlVUbHyUucctSyuFtu6bNxwYomIw==} dev: true - /acorn-dynamic-import/4.0.0_acorn@6.4.2: - resolution: {integrity: sha512-d3OEjQV4ROpoflsnUA8HozoIR504TFxNivYEUi6uwz0IYhBkTDXGuWlNdMtybRt3nqVx/L6XqMt0FxkXuWKZhw==} - peerDependencies: - acorn: ^6.0.0 - dependencies: - acorn: 6.4.2 - dev: true - - /acorn-jsx/5.3.2_acorn@6.4.2: - resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} - peerDependencies: - acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 - dependencies: - acorn: 6.4.2 - dev: true - /acorn-walk/8.2.0: resolution: {integrity: sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==} engines: {node: '>=0.4.0'} dev: true - /acorn/6.4.2: - resolution: {integrity: sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==} - engines: {node: '>=0.4.0'} - hasBin: true - dev: true - /acorn/8.6.0: resolution: {integrity: sha512-U1riIR+lBSNi3IbxtaHOIKdH8sLFv3NYfNv8sg7ZsNhcfl4HF2++BfqqrNAxoCLQW1iiylOj76ecnaUxz+z9yw==} engines: {node: '>=0.4.0'} @@ -618,6 +366,7 @@ packages: dependencies: color-convert: 1.9.3 dev: true + optional: true /ansi-styles/4.3.0: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} @@ -764,31 +513,6 @@ packages: dependencies: fill-range: 7.0.1 - /browserslist/4.18.1: - resolution: {integrity: sha512-8ScCzdpPwR2wQh8IT82CA2VgDwjHyqMovPBZSNH54+tm4Jk2pCuv90gmAdH6J84OCRWi0b4gMe6O6XPXuJnjgQ==} - engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} - hasBin: true - dependencies: - caniuse-lite: 1.0.30001283 - electron-to-chromium: 1.4.1 - escalade: 3.1.1 - node-releases: 2.0.1 - picocolors: 1.0.0 - dev: true - - /buble/0.20.0: - resolution: {integrity: sha512-/1gnaMQE8xvd5qsNBl+iTuyjJ9XxeaVxAMF86dQ4EyxFJOZtsgOS8Ra+7WHgZTam5IFDtt4BguN0sH0tVTKrOw==} - hasBin: true - dependencies: - acorn: 6.4.2 - acorn-dynamic-import: 4.0.0_acorn@6.4.2 - acorn-jsx: 5.3.2_acorn@6.4.2 - chalk: 2.4.2 - magic-string: 0.25.7 - minimist: 1.2.5 - regexpu-core: 4.5.4 - dev: true - /bundle-require/2.1.8_esbuild@0.13.15: resolution: {integrity: sha512-oOEg3A0hy/YzvNWNowtKD0pmhZKseOFweCbgyMqTIih4gRY1nJWsvrOCT27L9NbIyL5jMjTFrAUpGxxpW68Puw==} peerDependencies: @@ -805,10 +529,6 @@ packages: engines: {node: '>=12.20'} dev: true - /caniuse-lite/1.0.30001283: - resolution: {integrity: sha512-9RoKo841j1GQFSJz/nCXOj0sD7tHBtlowjYlrqIUS812x9/emfBLBt6IyMz1zIaYc/eRL8Cs6HPUVi2Hzq4sIg==} - dev: true - /cbor/8.1.0: resolution: {integrity: sha512-DwGjNW9omn6EwP70aXsn7FQJx5kO12tX0bZkaTjzdVFM6/7nhA4t0EENocKGx6D2Bch9PE2KzCUf5SceBdeijg==} engines: {node: '>=12.19'} @@ -824,6 +544,7 @@ packages: escape-string-regexp: 1.0.5 supports-color: 5.5.0 dev: true + optional: true /chalk/4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} @@ -904,6 +625,7 @@ packages: dependencies: color-name: 1.1.3 dev: true + optional: true /color-convert/2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} @@ -915,6 +637,7 @@ packages: /color-name/1.1.3: resolution: {integrity: sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=} dev: true + optional: true /color-name/1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} @@ -953,12 +676,6 @@ packages: resolution: {integrity: sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw==} dev: true - /convert-source-map/1.8.0: - resolution: {integrity: sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==} - dependencies: - safe-buffer: 5.1.2 - dev: true - /convert-to-spaces/1.0.2: resolution: {integrity: sha1-fj5Iu+bZl7FBfdyihoIEtNPYVxU=} engines: {node: '>= 4'} @@ -1017,10 +734,6 @@ packages: dependencies: path-type: 4.0.0 - /electron-to-chromium/1.4.1: - resolution: {integrity: sha512-9ldvb6QMHiDpUNF1iSwBTiTT0qXEN+xIO5WlCJrC5gt0z74ofOiqR698vaJqYWnri0XZiF0YmnrFmGq/EmpGAA==} - dev: true - /emittery/0.10.0: resolution: {integrity: sha512-AGvFfs+d0JKCJQ4o01ASQLGPmSCxgfU9RFXvzPvZdjKK8oscynksuJhWrSTSw7j7Ep/sZct5b5ZhYCi8S/t0HQ==} engines: {node: '>=12'} @@ -1190,6 +903,7 @@ packages: resolution: {integrity: sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=} engines: {node: '>=0.8.0'} dev: true + optional: true /escape-string-regexp/2.0.0: resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==} @@ -1300,11 +1014,6 @@ packages: resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==} dev: true - /gensync/1.0.0-beta.2: - resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} - engines: {node: '>=6.9.0'} - dev: true - /get-caller-file/2.0.5: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} engines: {node: 6.* || 8.* || >= 10.*} @@ -1341,11 +1050,6 @@ packages: path-is-absolute: 1.0.1 dev: true - /globals/11.12.0: - resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} - engines: {node: '>=4'} - dev: true - /globby/11.0.4: resolution: {integrity: sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg==} engines: {node: '>=10'} @@ -1377,6 +1081,7 @@ packages: resolution: {integrity: sha1-tdRU3CGZriJWmfNGfloH87lVuv0=} engines: {node: '>=4'} dev: true + optional: true /has-flag/4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} @@ -1531,6 +1236,7 @@ packages: /js-tokens/4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} dev: true + optional: true /js-yaml/3.14.1: resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==} @@ -1540,17 +1246,6 @@ packages: esprima: 4.0.1 dev: true - /jsesc/0.5.0: - resolution: {integrity: sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=} - hasBin: true - dev: true - - /jsesc/2.5.2: - resolution: {integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==} - engines: {node: '>=4'} - hasBin: true - dev: true - /json5/1.0.1: resolution: {integrity: sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==} hasBin: true @@ -1558,14 +1253,6 @@ packages: minimist: 1.2.5 dev: true - /json5/2.2.0: - resolution: {integrity: sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==} - engines: {node: '>=6'} - hasBin: true - dependencies: - minimist: 1.2.5 - dev: true - /jsonfile/6.1.0: resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==} dependencies: @@ -1701,10 +1388,6 @@ packages: resolution: {integrity: sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA=} engines: {node: '>=0.10.0'} - /node-releases/2.0.1: - resolution: {integrity: sha512-CqyzN6z7Q6aMeF/ktcMVTzhAHCEpf8SOarwpzpf8pNBY2k5/oM34UHldUwp8VKI7uxct2HxSRdJjBaZeESzcxA==} - dev: true - /nofilter/3.1.0: resolution: {integrity: sha512-l2NNj07e9afPnhAhvgVrCD/oy2Ai1yfLpuo3EpiO1jFTsB4sFz6oIfAfSZyQzVpkZQ9xS8ZS5g1jCBgq4Hwo0g==} engines: {node: '>=12.19'} @@ -1895,40 +1578,6 @@ packages: dependencies: picomatch: 2.3.0 - /regenerate-unicode-properties/8.2.0: - resolution: {integrity: sha512-F9DjY1vKLo/tPePDycuH3dn9H1OTPIkVD9Kz4LODu+F2C75mgjAJ7x/gwy6ZcSNRAAkhNlJSOHRe8k3p+K9WhA==} - engines: {node: '>=4'} - dependencies: - regenerate: 1.4.2 - dev: true - - /regenerate/1.4.2: - resolution: {integrity: sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==} - dev: true - - /regexpu-core/4.5.4: - resolution: {integrity: sha512-BtizvGtFQKGPUcTy56o3nk1bGRp4SZOTYrDtGNlqCQufptV5IkkLN6Emw+yunAJjzf+C9FQFtvq7IoA3+oMYHQ==} - engines: {node: '>=4'} - dependencies: - regenerate: 1.4.2 - regenerate-unicode-properties: 8.2.0 - regjsgen: 0.5.2 - regjsparser: 0.6.9 - unicode-match-property-ecmascript: 1.0.4 - unicode-match-property-value-ecmascript: 1.2.0 - dev: true - - /regjsgen/0.5.2: - resolution: {integrity: sha512-OFFT3MfrH90xIW8OOSyUrk6QHD5E9JOTeGodiJeBS3J6IwlgzJMNE/1bZklWz5oTg+9dCMyEetclvCVXOPoN3A==} - dev: true - - /regjsparser/0.6.9: - resolution: {integrity: sha512-ZqbNRz1SNjLAiYuwY0zoXW8Ne675IX5q+YHioAGbCw4X96Mjl2+dcX9B2ciaeyYjViDAfvIjFpQjJgLttTEERQ==} - hasBin: true - dependencies: - jsesc: 0.5.0 - dev: true - /require-directory/2.1.1: resolution: {integrity: sha1-jGStX9MNqxyXbiNE/+f3kqam30I=} engines: {node: '>=0.10.0'} @@ -1996,15 +1645,6 @@ packages: dependencies: queue-microtask: 1.2.3 - /safe-buffer/5.1.2: - resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} - dev: true - - /semver/6.3.0: - resolution: {integrity: sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==} - hasBin: true - dev: true - /semver/7.3.5: resolution: {integrity: sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==} engines: {node: '>=10'} @@ -2055,10 +1695,10 @@ packages: engines: {node: '>=0.10.0'} dev: true - /source-map/0.5.7: - resolution: {integrity: sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=} - engines: {node: '>=0.10.0'} - dev: true + /source-map/0.7.3: + resolution: {integrity: sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==} + engines: {node: '>= 8'} + dev: false /sourcemap-codec/1.4.8: resolution: {integrity: sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==} @@ -2150,6 +1790,7 @@ packages: dependencies: has-flag: 3.0.0 dev: true + optional: true /supports-color/7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} @@ -2184,11 +1825,6 @@ packages: engines: {node: '>=4'} dev: true - /to-fast-properties/2.0.0: - resolution: {integrity: sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=} - engines: {node: '>=4'} - dev: true - /to-regex-range/5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} @@ -2264,29 +1900,6 @@ packages: hasBin: true dev: true - /unicode-canonical-property-names-ecmascript/1.0.4: - resolution: {integrity: sha512-jDrNnXWHd4oHiTZnx/ZG7gtUTVp+gCcTTKr8L0HjlwphROEW3+Him+IpvC+xcJEFegapiMZyZe02CyuOnRmbnQ==} - engines: {node: '>=4'} - dev: true - - /unicode-match-property-ecmascript/1.0.4: - resolution: {integrity: sha512-L4Qoh15vTfntsn4P1zqnHulG0LdXgjSO035fEpdtp6YxXhMT51Q6vgM5lYdG/5X3MjS+k/Y9Xw4SFCY9IkR0rg==} - engines: {node: '>=4'} - dependencies: - unicode-canonical-property-names-ecmascript: 1.0.4 - unicode-property-aliases-ecmascript: 1.1.0 - dev: true - - /unicode-match-property-value-ecmascript/1.2.0: - resolution: {integrity: sha512-wjuQHGQVofmSJv1uVISKLE5zO2rNGzM/KCYZch/QQvez7C1hUhBIuZ701fYXExuufJFMPhv2SyL8CyoIfMLbIQ==} - engines: {node: '>=4'} - dev: true - - /unicode-property-aliases-ecmascript/1.1.0: - resolution: {integrity: sha512-PqSoPh/pWetQ2phoj5RLiaqIk4kCNwoV3CI+LfGmWLKI3rE3kl1h59XpX2BjgDrmbxD9ARtQobPGU1SguCYuQg==} - engines: {node: '>=4'} - dev: true - /universalify/2.0.0: resolution: {integrity: sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==} engines: {node: '>= 10.0.0'} diff --git a/src/cli-main.ts b/src/cli-main.ts index 06876bf6..64565a29 100644 --- a/src/cli-main.ts +++ b/src/cli-main.ts @@ -31,7 +31,6 @@ export async function main(options: Options = {}) { .option('--target ', 'Bundle target, "es20XX" or "esnext"', { default: 'es2017', }) - .option('--babel', 'Transform the result with Babel') .option( '--legacy-output', 'Output different formats to different folder instead of using different extensions' diff --git a/src/esbuild/index.ts b/src/esbuild/index.ts index 7ec68586..7ff331eb 100644 --- a/src/esbuild/index.ts +++ b/src/esbuild/index.ts @@ -1,6 +1,5 @@ import fs from 'fs' import path from 'path' -import { transform as transformToEs5 } from 'buble' import { build as esbuild, BuildResult, @@ -15,11 +14,14 @@ import { externalPlugin } from './external' import { postcssPlugin } from './postcss' import { sveltePlugin } from './svelte' import consola from 'consola' -import { getBabel, truthy } from '../utils' +import { truthy } from '../utils' import { PrettyError } from '../errors' import { transform } from 'sucrase' import { swcPlugin } from './swc' import { nativeNodeModulesPlugin } from './native-node-module' +import { PluginContainer } from '../plugin' +import { cjsSplitting } from '../plugins/cjs-splitting' +import { shebang } from '../plugins/shebang' const getOutputExtensionMap = ( pkgTypeField: string | undefined, @@ -114,6 +116,12 @@ export async function runEsbuild( ...(options.esbuildPlugins || []), ] + const pluginContainer = new PluginContainer([ + shebang(), + cjsSplitting(), + ...(options.plugins || []), + ]) + try { result = await esbuild({ entryPoints: options.entryPoints, @@ -123,8 +131,8 @@ export async function runEsbuild( globalName: options.globalName, jsxFactory: options.jsxFactory, jsxFragment: options.jsxFragment, - sourcemap: options.sourcemap, - target: options.target === 'es5' ? 'es2016' : options.target, + sourcemap: options.sourcemap ? 'external' : false, + target: options.target, footer: options.footer, banner: options.banner, tsconfig: options.tsconfig, @@ -228,74 +236,14 @@ export async function runEsbuild( const timeInMs = Date.now() - startTime logger.success(format, `⚡️ Build success in ${Math.floor(timeInMs)}ms`) - await Promise.all( - result.outputFiles.map(async (file) => { - const dir = path.dirname(file.path) - const outPath = file.path - const ext = path.extname(outPath) - const comeFromSource = ext === '.js' || ext === outExtension['.js'] - await fs.promises.mkdir(dir, { recursive: true }) - let contents = file.text - let mode: number | undefined - if (contents[0] === '#' && contents[1] === '!') { - mode = 0o755 - } - if (comeFromSource) { - if (options.babel) { - const babel = getBabel() - if (babel) { - contents = await babel - .transformAsync(contents, { - filename: file.path, - }) - .then((res) => res?.code || contents) - } else { - throw new PrettyError( - `@babel/core is not found in ${process.cwd()}` - ) - } - } - if (options.target === 'es5') { - try { - contents = transformToEs5(contents, { - source: file.path, - file: file.path, - transforms: { - modules: false, - arrow: true, - dangerousTaggedTemplateString: true, - spreadRest: true, - }, - }).code - } catch (error) { - if (error instanceof Error) { - throw new PrettyError( - `Error compiling to es5 target:\n${ - // @ts-expect-error not sure how to type error.snippet - error.snippet || error.message - }` - ) - } else { - throw error - } - } - } - // Workaround to enable code splitting for cjs format - // Manually transform esm to cjs - // TODO: remove this once esbuild supports code splitting for cjs natively - if (splitting && format === 'cjs') { - contents = transform(contents, { - filePath: file.path, - transforms: ['imports'], - }).code - } - } - await fs.promises.writeFile(outPath, contents, { - encoding: 'utf8', - mode, - }) - }) - ) + await pluginContainer.buildFinished({ + files: result.outputFiles, + context: { + format, + splitting, + options, + }, + }) } if (result.metafile) { diff --git a/src/fs.ts b/src/fs.ts new file mode 100644 index 00000000..485cb770 --- /dev/null +++ b/src/fs.ts @@ -0,0 +1,7 @@ +import path from 'path' +import fs from 'fs' + +export const outputFile = async (filepath: string, data: any) => { + await fs.promises.mkdir(path.dirname(filepath), { recursive: true }) + await fs.promises.writeFile(filepath, data) +} diff --git a/src/options.ts b/src/options.ts index 7af195c9..daba7fa7 100644 --- a/src/options.ts +++ b/src/options.ts @@ -1,5 +1,6 @@ import type { BuildOptions, Plugin as EsbuildPlugin, Loader } from 'esbuild' import type { InputOption } from 'rollup' +import { Plugin } from './plugin' export type Format = 'cjs' | 'esm' | 'iife' @@ -53,13 +54,11 @@ export type Options = { [k: string]: string } dts?: boolean | string | DtsConfig - sourcemap?: BuildOptions['sourcemap'] + sourcemap?: boolean /** Always bundle modules matching given patterns */ noExternal?: (string | RegExp)[] /** Don't bundle these modules */ external?: (string | RegExp)[] - /** Transform the result with `@babel/core` */ - babel?: boolean /** * Replace `process.env.NODE_ENV` with `production` or `development` * `production` when the bundled is minified, `development` otherwise @@ -127,4 +126,13 @@ export type Options = { * @default {false} */ injectStyle?: boolean + /** + * Inject cjs and esm shims if needed + * @default {true} + */ + shims?: boolean + /** + * TSUP plugins + */ + plugins?: Plugin[] } diff --git a/src/plugin.ts b/src/plugin.ts new file mode 100644 index 00000000..05af992d --- /dev/null +++ b/src/plugin.ts @@ -0,0 +1,135 @@ +import path from 'path' +import { OutputFile } from 'esbuild' +import { SourceMapConsumer, SourceMapGenerator, RawSourceMap } from 'source-map' +import { Format, NormalizedOptions } from '.' +import { outputFile } from './fs' + +export type ChunkInfo = { + type: 'chunk' + code: string + map?: string | RawSourceMap | null + path: string + /** + * Sets the file mode + */ + mode?: number +} + +export type AssetInfo = { + type: 'asset' + path: string + contents: Uint8Array +} + +type MaybePromise = T | Promise + +export type RenderChunk = ( + this: PluginContext, + code: string, + chunkInfo: ChunkInfo +) => MaybePromise< + | { + code: string + map?: object | string + } + | undefined + | null + | void +> + +export type Plugin = { + name: string + + renderChunk?: RenderChunk +} + +export type PluginContext = { + format: Format + splitting?: boolean + options: NormalizedOptions +} + +const parseSourceMap = (map?: string | object | null) => { + return typeof map === 'string' ? JSON.parse(map) : map +} + +const isJS = (path: string) => /\.(js|mjs|cjs)$/.test(path) +const isCSS = (path: string) => /\.css$/.test(path) + +export class PluginContainer { + plugins: Plugin[] + + constructor(plugins: Plugin[]) { + this.plugins = plugins + } + + async buildFinished({ + files, + context, + }: { + files: OutputFile[] + context: PluginContext + }) { + await Promise.all( + files.map(async (file) => { + const info: AssetInfo | ChunkInfo = + isJS(file.path) || isCSS(file.path) + ? { + type: 'chunk', + path: file.path, + code: file.text, + map: files.find((f) => f.path === `${file.path}.map`)?.text, + } + : { + type: 'asset', + path: file.path, + contents: file.contents, + } + for (const plugin of this.plugins) { + if (info.type === 'chunk' && plugin.renderChunk) { + const result = await plugin.renderChunk.call( + context, + info.code, + info + ) + if (result) { + info.code = result.code + if (result.map) { + const originalConsumer = await new SourceMapConsumer( + parseSourceMap(info.map) + ) + const newConsumer = await new SourceMapConsumer( + parseSourceMap(result.map) + ) + const generator = + SourceMapGenerator.fromSourceMap(originalConsumer) + generator.applySourceMap(newConsumer) + info.map = generator.toJSON() + originalConsumer.destroy() + newConsumer.destroy() + } + } + } + } + await outputFile( + info.path, + info.type === 'chunk' + ? info.code + getSourcemapComment(!!info.map, info.path) + : info.contents + ) + if (info.type === 'chunk' && info.map) { + const map = + typeof info.map === 'string' ? JSON.parse(info.map) : info.map + // map.sources = map.sources?.map((name: string) => + // path.relative(path.dirname(info.path), name) + // ) + await outputFile(`${info.path}.map`, JSON.stringify(map)) + } + }) + ) + } +} + +const getSourcemapComment = (hasMap: boolean, filepath: string) => { + return hasMap ? `//# sourceMappingURL=${path.basename(filepath)}.map` : '' +} diff --git a/src/plugins/cjs-splitting.ts b/src/plugins/cjs-splitting.ts new file mode 100644 index 00000000..26558d8f --- /dev/null +++ b/src/plugins/cjs-splitting.ts @@ -0,0 +1,39 @@ +// Workaround to enable code splitting for cjs format +// Manually transform esm to cjs +// TODO: remove this once esbuild supports code splitting for cjs natively +import path from 'path' +import { Plugin } from '../plugin' + +export const cjsSplitting = (): Plugin => { + return { + name: 'cjs-splitting', + + async renderChunk(code, info) { + if ( + !this.splitting || + this.format !== 'cjs' || + info.type !== 'chunk' || + !/\.(js|cjs)$/.test(info.path) + ) { + return + } + + const { transform } = await import('sucrase') + + const result = transform(code, { + filePath: info.path, + transforms: ['imports'], + sourceMapOptions: this.options.sourcemap + ? { + compiledFilename: info.path, + } + : undefined, + }) + + return { + code: result.code, + map: result.sourceMap, + } + }, + } +} diff --git a/src/plugins/shebang.ts b/src/plugins/shebang.ts new file mode 100644 index 00000000..8869fdb8 --- /dev/null +++ b/src/plugins/shebang.ts @@ -0,0 +1,15 @@ +import { Plugin } from '../plugin' + +export const shebang = (): Plugin => { + return { + name: 'shebang', + + renderChunk(_, info) { + if (info.type === 'chunk' && /\.(cjs|js|mjs)$/.test(info.path)) { + if (info.code.startsWith('#!')) { + info.mode = 0o755 + } + } + }, + } +} diff --git a/src/utils.ts b/src/utils.ts index 506980ae..cc77ac94 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -40,11 +40,6 @@ export function isExternal( return false } -export function getBabel(): null | typeof import('@babel/core') { - const p = resolveFrom.silent(process.cwd(), '@babel/core') - return p && require(p) -} - export function getPostcss(): null | typeof import('postcss') { const p = resolveFrom.silent(process.cwd(), 'postcss') return p && require(p) diff --git a/test/index.test.ts b/test/index.test.ts index 98deeabd..d90b52ae 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -187,28 +187,6 @@ test('bundle graphql-tools with --sourcemap inline flag', async (t) => { t.assert(output.includes('//# sourceMappingURL=')) }) -test('es5 target', async (t) => { - const { output, outFiles } = await run( - t.title, - { - 'input.ts': ` - export class Foo { - hi (): void { - let a = () => 'foo' - - console.log(a()) - } - } - `, - }, - { - flags: ['--target', 'es5'], - } - ) - t.snapshot(output) - t.deepEqual(outFiles, ['input.js']) -}) - test('multiple formats', async (t) => { const { output, outFiles } = await run( t.title, @@ -703,7 +681,7 @@ test('inject style', async (t) => { t.title, { 'input.ts': `import './style.css'`, - 'style.css': `.hello { color: red }` + 'style.css': `.hello { color: red }`, }, { flags: ['--inject-style', '--minify'], diff --git a/types.d.ts b/types.d.ts index 807ad76e..d5309f6f 100644 --- a/types.d.ts +++ b/types.d.ts @@ -1,3 +1,4 @@ declare module 'rollup-plugin-hashbang' declare module 'postcss-load-config' declare module 'jju/lib/parse' +declare module 'merge-source-map' From 3fe1fec9be0196fbf3a13218b8576df501473cfb Mon Sep 17 00:00:00 2001 From: EGOIST <0x142857@gmail.com> Date: Wed, 8 Dec 2021 17:59:07 +0800 Subject: [PATCH 03/12] test shebang --- src/fs.ts | 8 ++++++-- src/plugin.ts | 4 +++- test/index.test.ts | 21 ++++++++++++++++++++- types.d.ts | 2 -- 4 files changed, 29 insertions(+), 6 deletions(-) diff --git a/src/fs.ts b/src/fs.ts index 485cb770..f498cade 100644 --- a/src/fs.ts +++ b/src/fs.ts @@ -1,7 +1,11 @@ import path from 'path' import fs from 'fs' -export const outputFile = async (filepath: string, data: any) => { +export const outputFile = async ( + filepath: string, + data: any, + options?: { mode?: fs.Mode } +) => { await fs.promises.mkdir(path.dirname(filepath), { recursive: true }) - await fs.promises.writeFile(filepath, data) + await fs.promises.writeFile(filepath, data, options) } diff --git a/src/plugin.ts b/src/plugin.ts index 05af992d..c08c0ac3 100644 --- a/src/plugin.ts +++ b/src/plugin.ts @@ -111,11 +111,13 @@ export class PluginContainer { } } } + await outputFile( info.path, info.type === 'chunk' ? info.code + getSourcemapComment(!!info.map, info.path) - : info.contents + : info.contents, + { mode: info.type === 'chunk' ? info.mode : undefined } ) if (info.type === 'chunk' && info.map) { const map = diff --git a/test/index.test.ts b/test/index.test.ts index d90b52ae..5330f57b 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -1,5 +1,5 @@ import test from 'ava' -import { resolve } from 'path' +import { join, resolve } from 'path' import execa from 'execa' import fs from 'fs-extra' import glob from 'globby' @@ -65,6 +65,7 @@ async function run( }, outFiles, logs, + outDir: resolve(testDir, 'dist'), getFileContent(filename: string) { return fs.readFile(resolve(testDir, filename), 'utf8') }, @@ -690,3 +691,21 @@ test('inject style', async (t) => { t.deepEqual(outFiles, ['input.js']) t.assert(output.includes('.hello{color:red}')) }) + +test('shebang', async (t) => { + const { outDir } = await run( + t.title, + { + 'a.ts': `#!/usr/bin/env node\bconsole.log('a')`, + 'b.ts': `console.log('b')`, + }, + { + entry: ['a.ts', 'b.ts'], + } + ) + + const a = await fs.promises.stat(join(outDir, 'a.js')) + t.is(a.mode, 33261) + const b = await fs.promises.stat(join(outDir, 'b.js')) + t.is(b.mode, 33188) +}) diff --git a/types.d.ts b/types.d.ts index d5309f6f..f1f085a8 100644 --- a/types.d.ts +++ b/types.d.ts @@ -1,4 +1,2 @@ declare module 'rollup-plugin-hashbang' declare module 'postcss-load-config' -declare module 'jju/lib/parse' -declare module 'merge-source-map' From 612b9fe7c92f68dafd86f9b794ca9d6f65d2a5f5 Mon Sep 17 00:00:00 2001 From: EGOIST <0x142857@gmail.com> Date: Wed, 8 Dec 2021 18:07:38 +0800 Subject: [PATCH 04/12] check if file's executable --- test/index.test.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/test/index.test.ts b/test/index.test.ts index 5330f57b..a1fbcb39 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -704,8 +704,10 @@ test('shebang', async (t) => { } ) - const a = await fs.promises.stat(join(outDir, 'a.js')) - t.is(a.mode, 33261) - const b = await fs.promises.stat(join(outDir, 'b.js')) - t.is(b.mode, 33188) + t.notThrows(() => { + fs.accessSync(join(outDir, 'a.js'), fs.constants.X_OK) + }) + t.throws(() => { + fs.accessSync(join(outDir, 'b.js'), fs.constants.X_OK) + }) }) From cbc5941830695c418c97e5d732d7f9cd3a01d436 Mon Sep 17 00:00:00 2001 From: EGOIST <0x142857@gmail.com> Date: Wed, 8 Dec 2021 18:25:09 +0800 Subject: [PATCH 05/12] fix it on windows --- src/esbuild/index.ts | 12 ++---------- src/index.ts | 16 +++++++++++++--- src/plugin.ts | 21 +++++++++++++++++++++ src/plugins/shebang.ts | 17 ++++++++++++++++- 4 files changed, 52 insertions(+), 14 deletions(-) diff --git a/src/esbuild/index.ts b/src/esbuild/index.ts index 7ff331eb..f420c072 100644 --- a/src/esbuild/index.ts +++ b/src/esbuild/index.ts @@ -15,13 +15,9 @@ import { postcssPlugin } from './postcss' import { sveltePlugin } from './svelte' import consola from 'consola' import { truthy } from '../utils' -import { PrettyError } from '../errors' -import { transform } from 'sucrase' import { swcPlugin } from './swc' import { nativeNodeModulesPlugin } from './native-node-module' import { PluginContainer } from '../plugin' -import { cjsSplitting } from '../plugins/cjs-splitting' -import { shebang } from '../plugins/shebang' const getOutputExtensionMap = ( pkgTypeField: string | undefined, @@ -48,11 +44,13 @@ export async function runEsbuild( css, logger, buildDependencies, + pluginContainer, }: { format: Format css?: Map buildDependencies: Set logger: Logger + pluginContainer: PluginContainer } ) { const pkg = await loadPkg(process.cwd()) @@ -116,12 +114,6 @@ export async function runEsbuild( ...(options.esbuildPlugins || []), ] - const pluginContainer = new PluginContainer([ - shebang(), - cjsSplitting(), - ...(options.plugins || []), - ]) - try { result = await esbuild({ entryPoints: options.entryPoints, diff --git a/src/index.ts b/src/index.ts index 9681bee3..b7420eb8 100644 --- a/src/index.ts +++ b/src/index.ts @@ -16,6 +16,9 @@ import { version } from '../package.json' import { createLogger, setSilent } from './log' import { DtsConfig, Format, Options } from './options' import { runEsbuild } from './esbuild' +import { shebang } from './plugins/shebang' +import { cjsSplitting } from './plugins/cjs-splitting' +import { PluginContainer } from './plugin' export type { Format, Options } @@ -178,14 +181,21 @@ export async function build(_options: Options) { const css: Map = new Map() await Promise.all([ - ...options.format.map((format, index) => - runEsbuild(options, { + ...options.format.map(async (format, index) => { + const pluginContainer = new PluginContainer([ + shebang(), + cjsSplitting(), + ...(options.plugins || []), + ]) + await pluginContainer.buildStarted() + await runEsbuild(options, { + pluginContainer, format, css: index === 0 ? css : undefined, logger, buildDependencies, }) - ), + }), ]) await killPromise if (options.onSuccess) { diff --git a/src/plugin.ts b/src/plugin.ts index c08c0ac3..83517263 100644 --- a/src/plugin.ts +++ b/src/plugin.ts @@ -37,10 +37,17 @@ export type RenderChunk = ( | void > +export type BuildStart = () => MaybePromise +export type BuildEnd = () => MaybePromise + export type Plugin = { name: string + buildStart?: BuildStart + renderChunk?: RenderChunk + + buildEnd?: BuildEnd } export type PluginContext = { @@ -63,6 +70,14 @@ export class PluginContainer { this.plugins = plugins } + async buildStarted() { + for (const plugin of this.plugins) { + if (plugin.buildStart) { + await plugin.buildStart() + } + } + } + async buildFinished({ files, context, @@ -129,6 +144,12 @@ export class PluginContainer { } }) ) + + for (const plugin of this.plugins) { + if (plugin.buildEnd) { + await plugin.buildEnd() + } + } } } diff --git a/src/plugins/shebang.ts b/src/plugins/shebang.ts index 8869fdb8..2d12ad5d 100644 --- a/src/plugins/shebang.ts +++ b/src/plugins/shebang.ts @@ -1,15 +1,30 @@ +import fs from 'fs' import { Plugin } from '../plugin' export const shebang = (): Plugin => { + const executableFiles: Set = new Set() + return { name: 'shebang', + buildStart() { + executableFiles.clear() + }, + renderChunk(_, info) { if (info.type === 'chunk' && /\.(cjs|js|mjs)$/.test(info.path)) { if (info.code.startsWith('#!')) { - info.mode = 0o755 + executableFiles.add(info.path) } } }, + + async buildEnd() { + await Promise.all( + [...executableFiles].map(async (file) => { + await fs.promises.chmod(file, 0o755) + }) + ) + }, } } From 0fb1f1913c1f8d67c5afe8628b48a5fb7484eb3b Mon Sep 17 00:00:00 2001 From: EGOIST <0x142857@gmail.com> Date: Wed, 8 Dec 2021 18:52:28 +0800 Subject: [PATCH 06/12] well fs chmod does not work on windows --- src/plugins/shebang.ts | 17 +---------------- test/index.test.ts | 4 ++++ 2 files changed, 5 insertions(+), 16 deletions(-) diff --git a/src/plugins/shebang.ts b/src/plugins/shebang.ts index 2d12ad5d..8869fdb8 100644 --- a/src/plugins/shebang.ts +++ b/src/plugins/shebang.ts @@ -1,30 +1,15 @@ -import fs from 'fs' import { Plugin } from '../plugin' export const shebang = (): Plugin => { - const executableFiles: Set = new Set() - return { name: 'shebang', - buildStart() { - executableFiles.clear() - }, - renderChunk(_, info) { if (info.type === 'chunk' && /\.(cjs|js|mjs)$/.test(info.path)) { if (info.code.startsWith('#!')) { - executableFiles.add(info.path) + info.mode = 0o755 } } }, - - async buildEnd() { - await Promise.all( - [...executableFiles].map(async (file) => { - await fs.promises.chmod(file, 0o755) - }) - ) - }, } } diff --git a/test/index.test.ts b/test/index.test.ts index a1fbcb39..9c553751 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -704,6 +704,10 @@ test('shebang', async (t) => { } ) + if (process.platform === 'win32') { + return t.pass() + } + t.notThrows(() => { fs.accessSync(join(outDir, 'a.js'), fs.constants.X_OK) }) From 2a5b582262aeeb20724df0da8a0f62cfb7793d14 Mon Sep 17 00:00:00 2001 From: EGOIST <0x142857@gmail.com> Date: Thu, 9 Dec 2021 15:25:14 +0800 Subject: [PATCH 07/12] use docup 2 --- docs/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/index.html b/docs/index.html index 32d7ca93..209fb24c 100644 --- a/docs/index.html +++ b/docs/index.html @@ -12,7 +12,7 @@ From c433cc81b23318f25340855b7fca8f3ddf070a3a Mon Sep 17 00:00:00 2001 From: EGOIST <0x142857@gmail.com> Date: Fri, 10 Dec 2021 14:39:36 +0800 Subject: [PATCH 08/12] tweaks --- src/options.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/options.ts b/src/options.ts index deb6a147..daba7fa7 100644 --- a/src/options.ts +++ b/src/options.ts @@ -135,8 +135,4 @@ export type Options = { * TSUP plugins */ plugins?: Plugin[] - * Inject CSS as style tags to document head - * @default {false} - */ - injectStyle?: boolean } From 9e4e87660518f5c97e7e6fa72447cdfb2df5a895 Mon Sep 17 00:00:00 2001 From: EGOIST <0x142857@gmail.com> Date: Fri, 10 Dec 2021 15:04:03 +0800 Subject: [PATCH 09/12] bring back es5 target --- package.json | 4 ++-- pnpm-lock.yaml | 26 ++++++++++++++++++++++++-- src/esbuild/index.ts | 1 + src/index.ts | 2 ++ src/plugin.ts | 17 ++++++++++++++++- src/plugins/es5.ts | 37 +++++++++++++++++++++++++++++++++++++ test/index.test.ts | 22 ++++++++++++++++++++++ 7 files changed, 104 insertions(+), 5 deletions(-) create mode 100644 src/plugins/es5.ts diff --git a/package.json b/package.json index 6cc6d7b6..30445f0f 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ "ts" ], "require": [ - "sucrase//register" + "sucrase/register" ] }, "dependencies": { @@ -44,11 +44,11 @@ "postcss-load-config": "^3.0.1", "resolve-from": "^5.0.0", "rollup": "^2.60.0", - "sucrase": "^3.20.1", "tree-kill": "^1.2.2", "source-map": "^0.7.3" }, "devDependencies": { + "sucrase": "^3.20.3", "@rollup/plugin-json": "^4.1.0", "@swc/core": "^1.2.112", "@types/debug": "^4.1.5", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ea07fea3..da281fc9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -32,7 +32,7 @@ specifiers: rollup-plugin-hashbang: ^2.2.2 source-map: ^0.7.3 string-argv: ^0.3.1 - sucrase: ^3.20.1 + sucrase: ^3.20.3 svelte: 3.37.0 tree-kill: ^1.2.2 ts-essentials: ^7.0.1 @@ -54,7 +54,6 @@ dependencies: resolve-from: 5.0.0 rollup: 2.60.1 source-map: 0.7.3 - sucrase: 3.20.3 tree-kill: 1.2.2 devDependencies: @@ -77,6 +76,7 @@ devDependencies: rollup-plugin-dts: 3.0.2_rollup@2.60.1+typescript@4.5.2 rollup-plugin-hashbang: 2.2.2 string-argv: 0.3.1 + sucrase: 3.20.3 svelte: 3.37.0 ts-essentials: 7.0.3_typescript@4.5.2 tsconfig-paths: 3.12.0 @@ -382,6 +382,7 @@ packages: /any-promise/1.3.0: resolution: {integrity: sha1-q8av7tzqUugJzcA3au0845Y10X8=} + dev: true /anymatch/3.1.2: resolution: {integrity: sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==} @@ -492,6 +493,7 @@ packages: /balanced-match/1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + dev: true /binary-extensions/2.2.0: resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} @@ -506,6 +508,7 @@ packages: dependencies: balanced-match: 1.0.2 concat-map: 0.0.1 + dev: true /braces/3.0.2: resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} @@ -650,6 +653,7 @@ packages: /commander/4.1.1: resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} engines: {node: '>= 6'} + dev: true /common-path-prefix/3.0.0: resolution: {integrity: sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==} @@ -657,6 +661,7 @@ packages: /concat-map/0.0.1: resolution: {integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=} + dev: true /concordance/5.0.4: resolution: {integrity: sha512-OAcsnTEYu1ARJqWVGwf4zh4JDfHZEaSNlNccFmt8YjB2l/n19/PF2viLINHc57vO4FKIAFl2FWASIGZZWZ2Kxw==} @@ -1002,6 +1007,7 @@ packages: /fs.realpath/1.0.0: resolution: {integrity: sha1-FQStJSMVjKpA20onh8sBQRmU6k8=} + dev: true /fsevents/2.3.2: resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} @@ -1038,6 +1044,7 @@ packages: minimatch: 3.0.4 once: 1.4.0 path-is-absolute: 1.0.1 + dev: true /glob/7.2.0: resolution: {integrity: sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==} @@ -1140,9 +1147,11 @@ packages: dependencies: once: 1.4.0 wrappy: 1.0.2 + dev: true /inherits/2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + dev: true /irregular-plurals/3.3.0: resolution: {integrity: sha512-MVBLKUTangM3EfRPFROhmWQQKRDsrgI83J8GS3jXy+OwYqiR2/aoWndYQ5416jLE3uaGgLH7ncme3X9y09gZ3g==} @@ -1267,6 +1276,7 @@ packages: /lines-and-columns/1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + dev: true /load-json-file/7.0.1: resolution: {integrity: sha512-Gnxj3ev3mB5TkVBGad0JM6dmLiQL+o0t23JPBZ9sd+yvSLk05mFoqKBw5N8gbbkU4TNXyqCgIrl/VM17OgUIgQ==} @@ -1359,6 +1369,7 @@ packages: resolution: {integrity: sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==} dependencies: brace-expansion: 1.1.11 + dev: true /minimist/1.2.5: resolution: {integrity: sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==} @@ -1377,6 +1388,7 @@ packages: any-promise: 1.3.0 object-assign: 4.1.1 thenify-all: 1.6.0 + dev: true /nanoid/3.1.30: resolution: {integrity: sha512-zJpuPDwOv8D2zq2WRoMe1HsfZthVewpel9CAvTfc/2mBD1uUT/agc5f7GHGWXlYkFvi1mVxe4IjvP2HNrop7nQ==} @@ -1387,6 +1399,7 @@ packages: /node-modules-regexp/1.0.0: resolution: {integrity: sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA=} engines: {node: '>=0.10.0'} + dev: true /nofilter/3.1.0: resolution: {integrity: sha512-l2NNj07e9afPnhAhvgVrCD/oy2Ai1yfLpuo3EpiO1jFTsB4sFz6oIfAfSZyQzVpkZQ9xS8ZS5g1jCBgq4Hwo0g==} @@ -1406,11 +1419,13 @@ packages: /object-assign/4.1.1: resolution: {integrity: sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=} engines: {node: '>=0.10.0'} + dev: true /once/1.4.0: resolution: {integrity: sha1-WDsap3WWHUsROsF9nFC6753Xa9E=} dependencies: wrappy: 1.0.2 + dev: true /onetime/5.1.2: resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} @@ -1483,6 +1498,7 @@ packages: /path-is-absolute/1.0.1: resolution: {integrity: sha1-F0uSaHNVNP+8es5r9TpanhtcX18=} engines: {node: '>=0.10.0'} + dev: true /path-key/3.1.1: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} @@ -1509,6 +1525,7 @@ packages: engines: {node: '>= 6'} dependencies: node-modules-regexp: 1.0.0 + dev: true /pkg-conf/4.0.0: resolution: {integrity: sha512-7dmgi4UY4qk+4mj5Cd8v/GExPo0K+SlY+hulOSdfZ/T6jVH6//y7NtzZo5WrfhDBxuQ0jCa7fLZmNaNh7EWL/w==} @@ -1772,6 +1789,7 @@ packages: mz: 2.7.0 pirates: 4.0.1 ts-interface-checker: 0.1.13 + dev: true /supertap/2.0.0: resolution: {integrity: sha512-jRzcXlCeDYvKoZGA5oRhYyR3jUIYu0enkSxtmAgHRlD7HwrovTpH4bDSi0py9FtuA8si9cW/fKommJHuaoDHJA==} @@ -1814,11 +1832,13 @@ packages: engines: {node: '>=0.8'} dependencies: thenify: 3.3.1 + dev: true /thenify/3.3.1: resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} dependencies: any-promise: 1.3.0 + dev: true /time-zone/1.0.0: resolution: {integrity: sha1-mcW/VZWJZq9tBtg73zgA3IL67F0=} @@ -1845,6 +1865,7 @@ packages: /ts-interface-checker/0.1.13: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} + dev: true /tsconfig-paths/3.12.0: resolution: {integrity: sha512-e5adrnOYT6zqVnWqZu7i/BQ3BnhzvGbjEjejFXO20lKIKpwTaupkCPgEfv4GZK1IBciJUEhYs3J3p75FdaTFVg==} @@ -1936,6 +1957,7 @@ packages: /wrappy/1.0.2: resolution: {integrity: sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=} + dev: true /write-file-atomic/3.0.3: resolution: {integrity: sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==} diff --git a/src/esbuild/index.ts b/src/esbuild/index.ts index f420c072..73c822cc 100644 --- a/src/esbuild/index.ts +++ b/src/esbuild/index.ts @@ -94,6 +94,7 @@ export async function runEsbuild( { name: 'modify-options', setup(build) { + pluginContainer.modifyEsbuildOptions(build.initialOptions, { format }) if (options.esbuildOptions) { options.esbuildOptions(build.initialOptions, { format }) } diff --git a/src/index.ts b/src/index.ts index b7420eb8..2e7e7c65 100644 --- a/src/index.ts +++ b/src/index.ts @@ -19,6 +19,7 @@ import { runEsbuild } from './esbuild' import { shebang } from './plugins/shebang' import { cjsSplitting } from './plugins/cjs-splitting' import { PluginContainer } from './plugin' +import { es5 } from './plugins/es5' export type { Format, Options } @@ -185,6 +186,7 @@ export async function build(_options: Options) { const pluginContainer = new PluginContainer([ shebang(), cjsSplitting(), + es5(), ...(options.plugins || []), ]) await pluginContainer.buildStarted() diff --git a/src/plugin.ts b/src/plugin.ts index 83517263..75718184 100644 --- a/src/plugin.ts +++ b/src/plugin.ts @@ -1,5 +1,5 @@ import path from 'path' -import { OutputFile } from 'esbuild' +import { OutputFile, BuildOptions as EsbuildOptions } from 'esbuild' import { SourceMapConsumer, SourceMapGenerator, RawSourceMap } from 'source-map' import { Format, NormalizedOptions } from '.' import { outputFile } from './fs' @@ -40,9 +40,16 @@ export type RenderChunk = ( export type BuildStart = () => MaybePromise export type BuildEnd = () => MaybePromise +export type ModifyEsbuildOptions = ( + options: EsbuildOptions, + context: { format: Format } +) => void + export type Plugin = { name: string + esbuildOptions?: ModifyEsbuildOptions + buildStart?: BuildStart renderChunk?: RenderChunk @@ -70,6 +77,14 @@ export class PluginContainer { this.plugins = plugins } + modifyEsbuildOptions(options: EsbuildOptions, context: { format: Format }) { + for (const plugin of this.plugins) { + if (plugin.esbuildOptions) { + plugin.esbuildOptions(options, context) + } + } + } + async buildStarted() { for (const plugin of this.plugins) { if (plugin.buildStart) { diff --git a/src/plugins/es5.ts b/src/plugins/es5.ts new file mode 100644 index 00000000..4d1c263c --- /dev/null +++ b/src/plugins/es5.ts @@ -0,0 +1,37 @@ +import { Plugin } from '../plugin' +import { localRequire } from '../utils' + +export const es5 = (): Plugin => { + let enabled = false + return { + name: 'es5-target', + + esbuildOptions(options) { + if (options.target === 'es5') { + options.target = 'es2020' + enabled = true + } + }, + + async renderChunk(code, info) { + if (!enabled || !/\.(cjs|js)$/.test(info.path)) { + return + } + const swc: typeof import('@swc/core') = localRequire('@swc/core') + const result = await swc.transform(code, { + filename: info.path, + sourceMaps: this.options.sourcemap, + jsc: { + target: 'es5', + parser: { + syntax: 'ecmascript', + }, + }, + }) + return { + code: result.code, + map: result.map, + } + }, + } +} diff --git a/test/index.test.ts b/test/index.test.ts index 9c553751..fff059b8 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -715,3 +715,25 @@ test('shebang', async (t) => { fs.accessSync(join(outDir, 'b.js'), fs.constants.X_OK) }) }) + +test('es5 target', async (t) => { + const { output, outFiles } = await run( + t.title, + { + 'input.ts': ` + export class Foo { + hi (): void { + let a = () => 'foo' + + console.log(a()) + } + } + `, + }, + { + flags: ['--target', 'es5'], + } + ) + t.regex(output, /createClass/) + t.deepEqual(outFiles, ['input.js']) +}) From 7dd113c8244ef2988a2b0c4cdbcb8f6a9c193a3c Mon Sep 17 00:00:00 2001 From: EGOIST <0x142857@gmail.com> Date: Fri, 10 Dec 2021 15:06:48 +0800 Subject: [PATCH 10/12] tweaks --- package.json | 4 ++-- pnpm-lock.yaml | 24 +----------------------- 2 files changed, 3 insertions(+), 25 deletions(-) diff --git a/package.json b/package.json index 30445f0f..9e319aeb 100644 --- a/package.json +++ b/package.json @@ -45,10 +45,10 @@ "resolve-from": "^5.0.0", "rollup": "^2.60.0", "tree-kill": "^1.2.2", - "source-map": "^0.7.3" + "source-map": "^0.7.3", + "sucrase": "^3.20.3" }, "devDependencies": { - "sucrase": "^3.20.3", "@rollup/plugin-json": "^4.1.0", "@swc/core": "^1.2.112", "@types/debug": "^4.1.5", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index da281fc9..6a195594 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -54,6 +54,7 @@ dependencies: resolve-from: 5.0.0 rollup: 2.60.1 source-map: 0.7.3 + sucrase: 3.20.3 tree-kill: 1.2.2 devDependencies: @@ -76,7 +77,6 @@ devDependencies: rollup-plugin-dts: 3.0.2_rollup@2.60.1+typescript@4.5.2 rollup-plugin-hashbang: 2.2.2 string-argv: 0.3.1 - sucrase: 3.20.3 svelte: 3.37.0 ts-essentials: 7.0.3_typescript@4.5.2 tsconfig-paths: 3.12.0 @@ -382,7 +382,6 @@ packages: /any-promise/1.3.0: resolution: {integrity: sha1-q8av7tzqUugJzcA3au0845Y10X8=} - dev: true /anymatch/3.1.2: resolution: {integrity: sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==} @@ -493,7 +492,6 @@ packages: /balanced-match/1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - dev: true /binary-extensions/2.2.0: resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} @@ -508,7 +506,6 @@ packages: dependencies: balanced-match: 1.0.2 concat-map: 0.0.1 - dev: true /braces/3.0.2: resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} @@ -653,7 +650,6 @@ packages: /commander/4.1.1: resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} engines: {node: '>= 6'} - dev: true /common-path-prefix/3.0.0: resolution: {integrity: sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==} @@ -661,7 +657,6 @@ packages: /concat-map/0.0.1: resolution: {integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=} - dev: true /concordance/5.0.4: resolution: {integrity: sha512-OAcsnTEYu1ARJqWVGwf4zh4JDfHZEaSNlNccFmt8YjB2l/n19/PF2viLINHc57vO4FKIAFl2FWASIGZZWZ2Kxw==} @@ -1007,7 +1002,6 @@ packages: /fs.realpath/1.0.0: resolution: {integrity: sha1-FQStJSMVjKpA20onh8sBQRmU6k8=} - dev: true /fsevents/2.3.2: resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} @@ -1044,7 +1038,6 @@ packages: minimatch: 3.0.4 once: 1.4.0 path-is-absolute: 1.0.1 - dev: true /glob/7.2.0: resolution: {integrity: sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==} @@ -1147,11 +1140,9 @@ packages: dependencies: once: 1.4.0 wrappy: 1.0.2 - dev: true /inherits/2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} - dev: true /irregular-plurals/3.3.0: resolution: {integrity: sha512-MVBLKUTangM3EfRPFROhmWQQKRDsrgI83J8GS3jXy+OwYqiR2/aoWndYQ5416jLE3uaGgLH7ncme3X9y09gZ3g==} @@ -1276,7 +1267,6 @@ packages: /lines-and-columns/1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} - dev: true /load-json-file/7.0.1: resolution: {integrity: sha512-Gnxj3ev3mB5TkVBGad0JM6dmLiQL+o0t23JPBZ9sd+yvSLk05mFoqKBw5N8gbbkU4TNXyqCgIrl/VM17OgUIgQ==} @@ -1369,7 +1359,6 @@ packages: resolution: {integrity: sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==} dependencies: brace-expansion: 1.1.11 - dev: true /minimist/1.2.5: resolution: {integrity: sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==} @@ -1388,7 +1377,6 @@ packages: any-promise: 1.3.0 object-assign: 4.1.1 thenify-all: 1.6.0 - dev: true /nanoid/3.1.30: resolution: {integrity: sha512-zJpuPDwOv8D2zq2WRoMe1HsfZthVewpel9CAvTfc/2mBD1uUT/agc5f7GHGWXlYkFvi1mVxe4IjvP2HNrop7nQ==} @@ -1399,7 +1387,6 @@ packages: /node-modules-regexp/1.0.0: resolution: {integrity: sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA=} engines: {node: '>=0.10.0'} - dev: true /nofilter/3.1.0: resolution: {integrity: sha512-l2NNj07e9afPnhAhvgVrCD/oy2Ai1yfLpuo3EpiO1jFTsB4sFz6oIfAfSZyQzVpkZQ9xS8ZS5g1jCBgq4Hwo0g==} @@ -1419,13 +1406,11 @@ packages: /object-assign/4.1.1: resolution: {integrity: sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=} engines: {node: '>=0.10.0'} - dev: true /once/1.4.0: resolution: {integrity: sha1-WDsap3WWHUsROsF9nFC6753Xa9E=} dependencies: wrappy: 1.0.2 - dev: true /onetime/5.1.2: resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} @@ -1498,7 +1483,6 @@ packages: /path-is-absolute/1.0.1: resolution: {integrity: sha1-F0uSaHNVNP+8es5r9TpanhtcX18=} engines: {node: '>=0.10.0'} - dev: true /path-key/3.1.1: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} @@ -1525,7 +1509,6 @@ packages: engines: {node: '>= 6'} dependencies: node-modules-regexp: 1.0.0 - dev: true /pkg-conf/4.0.0: resolution: {integrity: sha512-7dmgi4UY4qk+4mj5Cd8v/GExPo0K+SlY+hulOSdfZ/T6jVH6//y7NtzZo5WrfhDBxuQ0jCa7fLZmNaNh7EWL/w==} @@ -1789,7 +1772,6 @@ packages: mz: 2.7.0 pirates: 4.0.1 ts-interface-checker: 0.1.13 - dev: true /supertap/2.0.0: resolution: {integrity: sha512-jRzcXlCeDYvKoZGA5oRhYyR3jUIYu0enkSxtmAgHRlD7HwrovTpH4bDSi0py9FtuA8si9cW/fKommJHuaoDHJA==} @@ -1832,13 +1814,11 @@ packages: engines: {node: '>=0.8'} dependencies: thenify: 3.3.1 - dev: true /thenify/3.3.1: resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} dependencies: any-promise: 1.3.0 - dev: true /time-zone/1.0.0: resolution: {integrity: sha1-mcW/VZWJZq9tBtg73zgA3IL67F0=} @@ -1865,7 +1845,6 @@ packages: /ts-interface-checker/0.1.13: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} - dev: true /tsconfig-paths/3.12.0: resolution: {integrity: sha512-e5adrnOYT6zqVnWqZu7i/BQ3BnhzvGbjEjejFXO20lKIKpwTaupkCPgEfv4GZK1IBciJUEhYs3J3p75FdaTFVg==} @@ -1957,7 +1936,6 @@ packages: /wrappy/1.0.2: resolution: {integrity: sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=} - dev: true /write-file-atomic/3.0.3: resolution: {integrity: sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==} From 775b76f17e805d1ca67634985bcd4e5cc8ddfd91 Mon Sep 17 00:00:00 2001 From: EGOIST <0x142857@gmail.com> Date: Fri, 10 Dec 2021 15:33:34 +0800 Subject: [PATCH 11/12] update docs --- docs/README.md | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/docs/README.md b/docs/README.md index 3631b7bf..fe354c97 100644 --- a/docs/README.md +++ b/docs/README.md @@ -72,7 +72,7 @@ All other CLI flags still apply to this command. You can also use `tsup` using file configurations or in a property inside your `package.json`, and you can even use `TypeScript` and have type-safety while you are using it. -> Most of these options can be overwritten using the CLI options +> INFO: Most of these options can be overwritten using the CLI options You can use any of these files: @@ -82,7 +82,7 @@ You can use any of these files: - `tsup.config.json` - `tsup` property in your `package.json` -> In all the custom files you can export the options either as `tsup`, `default` or `module.exports =` +> INFO: In all the custom files you can export the options either as `tsup`, `default` or `module.exports =` [Check out all available options](https://github.com/egoist/tsup/blob/master/src/options.ts). @@ -218,7 +218,7 @@ To disable code splitting altogether, try the `--no-splitting` flag instead. ### ES5 support -You can use `--target es5` to compile the code down to es5, it's processed by [buble](http://buble.surge.sh/). Some features are NOT supported by this target, namely: `for .. of`. +You can use `--target es5` to compile the code down to es5, in this target your code will be transpiled by esbuild to es2020 first, and then transpiled to es5 by [SWC](https://swc.rc). ### Compile-time environment variables @@ -240,24 +240,24 @@ tsup src/index.ts --watch Turn on watch mode. This means that after the initial build, tsup will continue to watch for changes in any of the resolved files. -> By default it always ignores `dist`, `node_modules` & `.git` +> INFO: By default it always ignores `dist`, `node_modules` & `.git` ```bash tsup src/index.ts --watch --ignore-watch ignore-this-folder-too ``` -> You can specify more than a folder repeating "--ignore-watch", for example: `tsup src src/index.ts --watch --ignore-watch folder1 --ignore-watch folder2` +> INFO: You can specify more than a folder repeating "--ignore-watch", for example: `tsup src src/index.ts --watch --ignore-watch folder1 --ignore-watch folder2` ### onSuccess You can specify command to be executed after a successful build, specially useful for **Watch mode** -> You should not use shell scripts, if you need to specify shell scripts you can add it in your "scripts" field and set for example `tsup src/index.ts --watch --onSuccess \"npm run dev\"` - ```bash tsup src/index.ts --watch --onSuccess "node dist/index.js" ``` +> Warning: You should not use shell scripts, if you need to specify shell scripts you can add it in your "scripts" field and set for example `tsup src/index.ts --watch --onSuccess \"npm run dev\"` + ### Minify output You can also minify the output, resulting into lower bundle sizes by using the `--minify` flag. @@ -364,6 +364,12 @@ For more details: tsup --help ``` +## Troubleshooting + +### error: No matching export in "xxx.ts" for import "xxx" + +This usualy happens when you have `emitDecoratorMetadata` enabled in your tsconfig.json, in this mode we use [SWC](https://swc.rc) to transpile decorators to JavaScript so exported types will be eliminated, that's why esbuild won't be able to find corresponding exports. You can fix this by changing your import statement from `import { SomeType }` to `import { type SomeType }` or `import type { SomeType }`. + ## License MIT © [EGOIST](https://github.com/sponsors/egoist) From 01331e446bd6e9eb3eb78750ac5d9f9abc2e26f9 Mon Sep 17 00:00:00 2001 From: EGOIST <0x142857@gmail.com> Date: Fri, 10 Dec 2021 15:42:04 +0800 Subject: [PATCH 12/12] tweaks --- src/options.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/options.ts b/src/options.ts index daba7fa7..5ccbe66e 100644 --- a/src/options.ts +++ b/src/options.ts @@ -133,6 +133,8 @@ export type Options = { shims?: boolean /** * TSUP plugins + * @experimental + * @alpha */ plugins?: Plugin[] }