diff --git a/README.md b/README.md index 6aee9c4..2b97369 100644 --- a/README.md +++ b/README.md @@ -72,6 +72,10 @@ dist └── index.js ``` +### ES5 support + +You can `--target es5` or `"target": "es5" in `tsconfig.json` to compile the code down to es5, it's processed by [buble](http://buble.surge.sh/). Some features are supported by this target, namely: `for .. of`. + ### Watch mode ```bash diff --git a/package.json b/package.json index 3dc6c0f..c5232d8 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ }, "devDependencies": { "@rollup/plugin-json": "^4.1.0", + "@types/buble": "^0.19.2", "@types/fs-extra": "^9.0.1", "@types/jest": "^26.0.5", "@types/node": "^14.0.23", @@ -40,6 +41,8 @@ "rollup-plugin-hashbang": "^2.2.2", "ts-jest": "^26.1.3", "tsup": "^3.0.0", - "typescript": "^3.9.7" + "typescript": "^3.9.7", + "strip-json-comments": "^3.1.1", + "buble": "^0.20.0" } } diff --git a/src/index.ts b/src/index.ts index f453930..878f28b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,9 +2,11 @@ import fs from 'fs' import { dirname, join } from 'path' import { Worker } from 'worker_threads' import colors from 'chalk' +import { transform as transformToEs5 } from 'buble' import { Service, startService, BuildResult } from 'esbuild' -import { getDeps, resolveTsConfig } from './utils' +import { getDeps, loadTsConfig } from './utils' import { FSWatcher } from 'chokidar' +import { PrettyError } from './errors' const textDecoder = new TextDecoder('utf-8') @@ -63,11 +65,13 @@ export async function runEsbuild( globalName: options.globalName, jsxFactory: options.jsxFactory, jsxFragment: options.jsxFragment, + target: options.target === 'es5' ? 'es2016' : options.target, define: options.define, external, outdir: format === 'cjs' ? outDir : join(outDir, format), write: false, splitting: format === 'cjs' || format === 'esm', + logLevel: 'error' })) const timeInMs = Date.now() - startTime console.log( @@ -91,25 +95,41 @@ export async function runEsbuild( result.outputFiles.map(async (file) => { const dir = dirname(file.path) const outPath = file.path + if (!outPath.endsWith('.js')) return await fs.promises.mkdir(dir, { recursive: true }) let mode: number | undefined if (file.contents[0] === 35 && file.contents[1] === 33) { mode = 0o755 } - // Cause we need to transform to code from esm to cjs first - if (format === 'cjs' && outPath.endsWith('.js')) { - const content = transform(textDecoder.decode(file.contents), { - transforms: ['imports'], - }) - await fs.promises.writeFile(outPath, content.code, { - encoding: 'utf8', - mode, - }) - } else { - await fs.promises.writeFile(outPath, file.contents, { - mode, - }) + let contents = textDecoder.decode(file.contents) + 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) { + throw new PrettyError( + `Error compiling to es5 target:\n${error.snippet}` + ) + } } + // Cause we need to transform to code from esm to cjs first + if (format === 'cjs') { + contents = transform(contents, { + transforms: ['imports'], + }).code + } + await fs.promises.writeFile(outPath, contents, { + encoding: 'utf8', + mode, + }) }) ) } @@ -124,6 +144,8 @@ function stopServices() { } export async function build(options: Options) { + options = { ...options } + let watcher: FSWatcher | undefined let runServices: Array<() => Promise> | undefined @@ -150,11 +172,25 @@ export async function build(options: Options) { } try { - const tsconfig = resolveTsConfig(process.cwd()) - if (tsconfig) { - console.log(makeLabel('CLI', 'info'), `Using tsconfig: ${tsconfig}`) + const tsconfig = await loadTsConfig(process.cwd()) + if (tsconfig.path && tsconfig.data) { + console.log(makeLabel('CLI', 'info'), `Using tsconfig: ${tsconfig.path}`) + if (!options.target) { + options.target = tsconfig.data.compilerOptions?.target + } + if (!options.jsxFactory) { + options.jsxFactory = tsconfig.data.compilerOptions?.jsxFactory + } + if (!options.jsxFragment) { + options.jsxFragment = tsconfig.data.compilerOptions?.jsxFragmentFactory + } } + if (!options.target) { + options.target = 'es2018' + } + console.log(makeLabel('CLI', 'info'), `Target: ${options.target}`) + runServices = await Promise.all([ ...options.format.map((format) => runEsbuild(options, { format })), ]) @@ -164,7 +200,7 @@ export async function build(options: Options) { worker.postMessage({ options, }) - worker.on('message', data => { + worker.on('message', (data) => { if (data === 'exit') { worker.unref() } diff --git a/src/utils.ts b/src/utils.ts index 81a4cad..6dc40c0 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,7 +1,19 @@ +import fs from 'fs' import JoyCon from 'joycon' +import stripJsonComments from 'strip-json-comments' const joycon = new JoyCon() +joycon.addLoader({ + test: /\.json$/, + async load(filepath) { + const content = stripJsonComments( + await fs.promises.readFile(filepath, 'utf8') + ) + return JSON.parse(content) + }, +}) + // No backslash in path function slash(input: string) { return input.replace(/\\/g, '/') @@ -45,8 +57,8 @@ export function isExternal( return false } -export function resolveTsConfig(cwd: string) { - return joycon.resolveSync(['tsconfig.build.json', 'tsconfig.json'], cwd) +export function loadTsConfig(cwd: string) { + return joycon.load(['tsconfig.build.json', 'tsconfig.json'], cwd) } export async function getDeps(cwd: string) { diff --git a/test/__snapshots__/index.test.ts.snap b/test/__snapshots__/index.test.ts.snap index 6910f29..b54c74e 100644 --- a/test/__snapshots__/index.test.ts.snap +++ b/test/__snapshots__/index.test.ts.snap @@ -1,5 +1,19 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`es5 target 1`] = ` +"\\"use strict\\";Object.defineProperty(exports, \\"__esModule\\", {value: true});// input.ts +var Foo = function Foo () {}; + +Foo.prototype.hi = function hi () { + var a = function () { return \\"foo\\"; }; + console.log(a()); +}; + + +exports.Foo = Foo; +" +`; + exports[`simple 1`] = ` "\\"use strict\\";Object.defineProperty(exports, \\"__esModule\\", {value: true});// foo.ts var foo_default = \\"foo\\"; diff --git a/test/index.test.ts b/test/index.test.ts index ce7594e..202b35c 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -24,14 +24,16 @@ function runTest( return fs.outputFile(join(testDir, name), files[name], 'utf8') }) ) - const { exitCode } = await execa( + const { exitCode, stdout, stderr } = await execa( bin, ['input.ts', ...(options.flags || [])], { cwd: testDir, } ) - expect(exitCode).toBe(0) + if (exitCode !== 0) { + throw new Error(stdout + stderr) + } if (options.snapshot !== false) { const output = await fs.readFile(join(testDir, 'dist/input.js'), 'utf8') expect(output).toMatchSnapshot() @@ -51,6 +53,24 @@ runTest( }, { snapshot: false, - flags: ['--dts'] + flags: ['--dts'], + } +) + +runTest( + 'es5 target', + { + 'input.ts': ` + export class Foo { + hi (): void { + let a = () => 'foo' + + console.log(a()) + } + } + `, + }, + { + flags: ['--target', 'es5'], } ) diff --git a/yarn.lock b/yarn.lock index 2afcaec..02cffa4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -551,6 +551,13 @@ dependencies: "@babel/types" "^7.3.0" +"@types/buble@^0.19.2": + version "0.19.2" + resolved "https://registry.yarnpkg.com/@types/buble/-/buble-0.19.2.tgz#a4289d20b175b3c206aaad80caabdabe3ecdfdd1" + integrity sha512-uUD8zIfXMKThmFkahTXDGI3CthFH1kMg2dOm3KLi4GlC5cbARA64bEcUMbbWdWdE73eoc/iBB9PiTMqH0dNS2Q== + dependencies: + magic-string "^0.25.0" + "@types/color-name@^1.1.1": version "1.1.1" resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0" @@ -647,6 +654,11 @@ abab@^2.0.3: resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.3.tgz#623e2075e02eb2d3f2475e49f99c91846467907a" integrity sha512-tsFzPpcttalNjFBCFMqsKYQcWxxen1pgJR56by//QwvJc4/OUS3kPOOttx2tSIfjsylB0pYu7f5D3K1RCxUnUg== +acorn-dynamic-import@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/acorn-dynamic-import/-/acorn-dynamic-import-4.0.0.tgz#482210140582a36b83c3e342e1cfebcaa9240948" + integrity sha512-d3OEjQV4ROpoflsnUA8HozoIR504TFxNivYEUi6uwz0IYhBkTDXGuWlNdMtybRt3nqVx/L6XqMt0FxkXuWKZhw== + acorn-globals@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-6.0.0.tgz#46cdd39f0f8ff08a876619b55f5ac8a6dc770b45" @@ -655,11 +667,21 @@ acorn-globals@^6.0.0: acorn "^7.1.1" acorn-walk "^7.1.1" +acorn-jsx@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.2.0.tgz#4c66069173d6fdd68ed85239fc256226182b2ebe" + integrity sha512-HiUX/+K2YpkpJ+SzBffkM/AQ2YE03S0U1kjTLVpoJdhZMOWy8qvXVN9JdLqv2QsaQ6MPYQIuNmwD8zOiYUofLQ== + acorn-walk@^7.1.1: version "7.1.1" resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.1.1.tgz#345f0dffad5c735e7373d2fec9a1023e6a44b83e" integrity sha512-wdlPY2tm/9XBr7QkKlq0WQVgiuGTX6YWPyRyBviSoScBuLfTVQhvwg6wJ369GJ/1nPfTLMfnrFIfjqVg6d+jQQ== +acorn@^6.4.1: + version "6.4.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.4.1.tgz#531e58ba3f51b9dacb9a6646ca4debf5b14ca474" + integrity sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA== + acorn@^7.1.1: version "7.2.0" resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.2.0.tgz#17ea7e40d7c8640ff54a694c889c26f31704effe" @@ -931,6 +953,19 @@ bser@2.1.1: dependencies: node-int64 "^0.4.0" +buble@^0.20.0: + version "0.20.0" + resolved "https://registry.yarnpkg.com/buble/-/buble-0.20.0.tgz#a143979a8d968b7f76b57f38f2e7ce7cfe938d1f" + integrity sha512-/1gnaMQE8xvd5qsNBl+iTuyjJ9XxeaVxAMF86dQ4EyxFJOZtsgOS8Ra+7WHgZTam5IFDtt4BguN0sH0tVTKrOw== + dependencies: + acorn "^6.4.1" + acorn-dynamic-import "^4.0.0" + acorn-jsx "^5.2.0" + chalk "^2.4.2" + magic-string "^0.25.7" + minimist "^1.2.5" + regexpu-core "4.5.4" + buffer-from@1.x, buffer-from@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" @@ -983,7 +1018,7 @@ caseless@~0.12.0: resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= -chalk@^2.0.0: +chalk@^2.0.0, chalk@^2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== @@ -2430,6 +2465,11 @@ jsesc@^2.5.1: resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== +jsesc@~0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" + integrity sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0= + json-parse-better-errors@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" @@ -2552,6 +2592,13 @@ magic-string@^0.22.4: dependencies: vlq "^0.2.2" +magic-string@^0.25.0, magic-string@^0.25.7: + version "0.25.7" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.7.tgz#3f497d6fd34c669c6798dcb821f2ef31f5445051" + integrity sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA== + dependencies: + sourcemap-codec "^1.4.4" + make-dir@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" @@ -3020,6 +3067,18 @@ readdirp@~3.4.0: dependencies: picomatch "^2.2.1" +regenerate-unicode-properties@^8.0.2: + version "8.2.0" + resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-8.2.0.tgz#e5de7111d655e7ba60c057dbe9ff37c87e65cdec" + integrity sha512-F9DjY1vKLo/tPePDycuH3dn9H1OTPIkVD9Kz4LODu+F2C75mgjAJ7x/gwy6ZcSNRAAkhNlJSOHRe8k3p+K9WhA== + dependencies: + regenerate "^1.4.0" + +regenerate@^1.4.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.1.tgz#cad92ad8e6b591773485fbe05a485caf4f457e6f" + integrity sha512-j2+C8+NtXQgEKWk49MMP5P/u2GhnahTtVkRIHr5R5lVRlbKvmQ+oS+A5aLKWp2ma5VkT8sh6v+v4hbH0YHR66A== + regex-not@^1.0.0, regex-not@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.2.tgz#1f4ece27e00b0b65e0247a6810e6a85d83a5752c" @@ -3028,6 +3087,30 @@ regex-not@^1.0.0, regex-not@^1.0.2: extend-shallow "^3.0.2" safe-regex "^1.1.0" +regexpu-core@4.5.4: + version "4.5.4" + resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-4.5.4.tgz#080d9d02289aa87fe1667a4f5136bc98a6aebaae" + integrity sha512-BtizvGtFQKGPUcTy56o3nk1bGRp4SZOTYrDtGNlqCQufptV5IkkLN6Emw+yunAJjzf+C9FQFtvq7IoA3+oMYHQ== + dependencies: + regenerate "^1.4.0" + regenerate-unicode-properties "^8.0.2" + regjsgen "^0.5.0" + regjsparser "^0.6.0" + unicode-match-property-ecmascript "^1.0.4" + unicode-match-property-value-ecmascript "^1.1.0" + +regjsgen@^0.5.0: + version "0.5.2" + resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.5.2.tgz#92ff295fb1deecbf6ecdab2543d207e91aa33733" + integrity sha512-OFFT3MfrH90xIW8OOSyUrk6QHD5E9JOTeGodiJeBS3J6IwlgzJMNE/1bZklWz5oTg+9dCMyEetclvCVXOPoN3A== + +regjsparser@^0.6.0: + version "0.6.4" + resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.6.4.tgz#a769f8684308401a66e9b529d2436ff4d0666272" + integrity sha512-64O87/dPDgfk8/RQqC4gkZoGyyWFIEUTTh80CU6CWuK5vkCGyekIx+oKcEIYtP/RAxSQltCZHCNu/mdd7fqlJw== + dependencies: + jsesc "~0.5.0" + remove-trailing-separator@^1.0.1: version "1.1.0" resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" @@ -3344,6 +3427,11 @@ source-map@^0.7.3: resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383" integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ== +sourcemap-codec@^1.4.4: + version "1.4.8" + resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4" + integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA== + spdx-correct@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.0.tgz#fb83e504445268f154b074e218c87c003cd31df4" @@ -3456,6 +3544,11 @@ strip-final-newline@^2.0.0: resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== +strip-json-comments@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== + sucrase@^3.15.0: version "3.15.0" resolved "https://registry.yarnpkg.com/sucrase/-/sucrase-3.15.0.tgz#78596a78be7264a65b52ed8d873883413ef0220c" @@ -3683,6 +3776,29 @@ typescript@^3.9.7: resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.7.tgz#98d600a5ebdc38f40cb277522f12dc800e9e25fa" integrity sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw== +unicode-canonical-property-names-ecmascript@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz#2619800c4c825800efdd8343af7dd9933cbe2818" + integrity sha512-jDrNnXWHd4oHiTZnx/ZG7gtUTVp+gCcTTKr8L0HjlwphROEW3+Him+IpvC+xcJEFegapiMZyZe02CyuOnRmbnQ== + +unicode-match-property-ecmascript@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-1.0.4.tgz#8ed2a32569961bce9227d09cd3ffbb8fed5f020c" + integrity sha512-L4Qoh15vTfntsn4P1zqnHulG0LdXgjSO035fEpdtp6YxXhMT51Q6vgM5lYdG/5X3MjS+k/Y9Xw4SFCY9IkR0rg== + dependencies: + unicode-canonical-property-names-ecmascript "^1.0.4" + unicode-property-aliases-ecmascript "^1.0.4" + +unicode-match-property-value-ecmascript@^1.1.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.2.0.tgz#0d91f600eeeb3096aa962b1d6fc88876e64ea531" + integrity sha512-wjuQHGQVofmSJv1uVISKLE5zO2rNGzM/KCYZch/QQvez7C1hUhBIuZ701fYXExuufJFMPhv2SyL8CyoIfMLbIQ== + +unicode-property-aliases-ecmascript@^1.0.4: + version "1.1.0" + resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.1.0.tgz#dd57a99f6207bedff4628abefb94c50db941c8f4" + integrity sha512-PqSoPh/pWetQ2phoj5RLiaqIk4kCNwoV3CI+LfGmWLKI3rE3kl1h59XpX2BjgDrmbxD9ARtQobPGU1SguCYuQg== + union-value@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.1.tgz#0b6fe7b835aecda61c6ea4d4f02c14221e109847"