diff --git a/index.js b/index.js index e10ca5c..1e959ef 100644 --- a/index.js +++ b/index.js @@ -90,8 +90,13 @@ function compile(source, options) { const optionsCopy = util.deepClone(options); const { ast, plugins, presets } = optionsCopy; delete optionsCopy.plugins; + delete optionsCopy.typescript; optionsCopy.ast = true; + if (options.typescript) { + precompileTypeScript(result, options); + } + function transform(presets) { optionsCopy.plugins = [{ parserOverride: parse @@ -130,6 +135,36 @@ function compile(source, options) { return result; } +function precompileTypeScript(result, options) { + const fileName = options.filename || options.sourceFileName; + if (fileName && ! fileName.endsWith(".ts") && ! fileName.endsWith(".tsx")) { + return; + } + + const ts = require("typescript"); + const tsResult = ts.transpileModule(result.code, { + fileName, + compilerOptions: { + target: ts.ScriptTarget.ES2018, + // Leave module syntax intact so that Babel/Reify can handle it. + module: ts.ModuleKind.ESNext, + sourceMap: true, + inlineSources: true, + } + }); + + result.code = tsResult.outputText.replace( + /\/\/# sourceMappingURL=.*?(\n|$)/g, + "$1" // preserve trailing \n characters + ); + + result.map = JSON.parse(tsResult.sourceMapText); + if (fileName) { + result.map.file = fileName; + result.map.sources = [fileName]; + } +} + exports.minify = function minify(source, options) { // We are not compiling the code in this step, only minifying, so reify // is not used. diff --git a/options.js b/options.js index b1696c0..dca7299 100644 --- a/options.js +++ b/options.js @@ -65,7 +65,6 @@ exports.getDefaults = function getDefaults(features) { } maybeAddReactPlugins(features, combined); - maybeAddTypeScriptPreset(features, combined.presets); if (features && features.jscript) { combined.plugins.push( @@ -75,7 +74,7 @@ exports.getDefaults = function getDefaults(features) { } } - return finish([combined]); + return finish(features, [combined]); }; function maybeAddReactPlugins(features, options) { @@ -89,12 +88,6 @@ function maybeAddReactPlugins(features, options) { } } -function maybeAddTypeScriptPreset(features, presets) { - if (features && features.typescript) { - presets.push(require("@babel/preset-typescript")); - } -} - function getDefaultsForModernBrowsers(features) { const combined = { presets: [], @@ -111,17 +104,16 @@ function getDefaultsForModernBrowsers(features) { } maybeAddReactPlugins(features, combined); - maybeAddTypeScriptPreset(features, combined.presets); } - return finish([combined]); + return finish(features, [combined]); } const parserOpts = require("reify/lib/parsers/babel.js").options; const util = require("./util.js"); -function finish(presets) { - return { +function finish(features, presets) { + const options = { compact: false, sourceMaps: false, ast: false, @@ -132,6 +124,14 @@ function finish(presets) { parserOpts: util.deepClone(parserOpts), presets: presets }; + + if (features && features.typescript) { + // This additional option will be consumed by the meteorBabel.compile + // function before the options are passed to Babel. + options.typescript = true; + } + + return options; } function isObject(value) { @@ -191,10 +191,9 @@ function getDefaultsForNode8(features) { if (! compileModulesOnly) { maybeAddReactPlugins(features, combined); - maybeAddTypeScriptPreset(features, combined.presets); } - return finish([combined]); + return finish(features, [combined]); } exports.getMinifierDefaults = function getMinifierDefaults(features) { diff --git a/package-lock.json b/package-lock.json index 45557b6..b854052 100644 --- a/package-lock.json +++ b/package-lock.json @@ -375,14 +375,6 @@ "@babel/helper-plugin-utils": "^7.0.0" } }, - "@babel/plugin-syntax-typescript": { - "version": "7.3.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.3.3.tgz", - "integrity": "sha512-dGwbSMA1YhVS8+31CnPR7LB4pcbrzcV99wQzby4uAfrkZPYZlQ7ImwdpzLqi6Z6IL02b8IAL379CaMwo0x5Lag==", - "requires": { - "@babel/helper-plugin-utils": "^7.0.0" - } - }, "@babel/plugin-transform-arrow-functions": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.2.0.tgz", @@ -618,16 +610,6 @@ "@babel/helper-plugin-utils": "^7.0.0" } }, - "@babel/plugin-transform-typescript": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.5.0.tgz", - "integrity": "sha512-z3T4P70XJFUAHzLtEsmJ37BGVDj+55/KX8W8TBSBF0qk0KLazw8xlwVcRHacxNPgprzTdI4QWW+2eS6bTkQbCA==", - "requires": { - "@babel/helper-create-class-features-plugin": "^7.5.0", - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-syntax-typescript": "^7.2.0" - } - }, "@babel/plugin-transform-unicode-regex": { "version": "7.4.4", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.4.4.tgz", @@ -650,15 +632,6 @@ "@babel/plugin-transform-react-jsx-source": "^7.0.0" } }, - "@babel/preset-typescript": { - "version": "7.3.3", - "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.3.3.tgz", - "integrity": "sha512-mzMVuIP4lqtn4du2ynEfdO0+RYcslwrZiJHXu4MGaC1ctJiW2fyaeDrtjJGs7R/KebZ1sgowcIoWf4uRpEfKEg==", - "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-transform-typescript": "^7.3.2" - } - }, "@babel/runtime": { "version": "7.5.0", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.5.0.tgz", @@ -2258,6 +2231,11 @@ "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=" }, + "typescript": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.5.2.tgz", + "integrity": "sha512-7KxJovlYhTX5RaRbUdkAXN1KUZ8PwWlTzQdHV6xNqvuFOs7+WBo10TQUqT19Q/Jz2hk5v9TQDIhyLhhJY4p5AA==" + }, "unicode-canonical-property-names-ecmascript": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz", diff --git a/package.json b/package.json index dfb9953..db98da3 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,6 @@ "@babel/plugin-transform-modules-commonjs": "^7.5.0", "@babel/plugin-transform-runtime": "^7.5.0", "@babel/preset-react": "^7.0.0", - "@babel/preset-typescript": "^7.3.3", "@babel/runtime": "^7.5.0", "@babel/template": "^7.4.4", "@babel/traverse": "^7.5.0", @@ -46,7 +45,8 @@ "convert-source-map": "^1.6.0", "lodash": "^4.17.11", "meteor-babel-helpers": "0.0.3", - "reify": "^0.20.12" + "reify": "^0.20.12", + "typescript": "^3.5.2" }, "devDependencies": { "@babel/plugin-proposal-decorators": "7.4.4", diff --git a/test/class-properties.ts b/test/class-properties.ts index 03a3237..1750341 100644 --- a/test/class-properties.ts +++ b/test/class-properties.ts @@ -1,4 +1,7 @@ +const enum TestResult { PASS, FAIL } + export class Test { public property: number = 1234; + public result = TestResult.PASS; constructor(public value: string) {} } diff --git a/test/react.tsx b/test/react.tsx new file mode 100644 index 0000000..10f531b --- /dev/null +++ b/test/react.tsx @@ -0,0 +1,3 @@ +export function Component() { + return
oyez
; +} diff --git a/test/tests.js b/test/tests.js index 343e900..6d95403 100644 --- a/test/tests.js +++ b/test/tests.js @@ -288,9 +288,47 @@ describe("meteor-babel", () => { const tsTest = new Test("asdf"); assert.strictEqual(tsTest.property, 1234); assert.strictEqual(tsTest.value, "asdf"); + assert.strictEqual(typeof tsTest.result, "number"); const jsTest = new (class { foo = 42 }); assert.strictEqual(jsTest.foo, 42); }); + + it("can compile TypeScript syntax", () => { + const options = meteorBabel.getDefaultOptions({ + typescript: true, + }); + + assert.strictEqual(options.typescript, true); + + const result = meteorBabel.compile([ + "export namespace Test {", + " export const enabled = true;", + "}", + ].join("\n"), options); + + assert.strictEqual(result.code, [ + "module.export({", + " Test: function () {", + " return Test;", + " }", + "});", + "var Test;", + "", + "(function (Test) {", + " Test.enabled = true;", + "})(Test || module.runSetters(Test = {}));", + ].join("\n")); + }); + + it("can handle JSX syntax in .tsx files", () => { + const { Component } = require("./react.tsx"); + assert.strictEqual(typeof Component, "function"); + assert.strictEqual(String(Component), [ + 'function Component() {', + ' return React.createElement("div", null, "oyez");', + '}', + ].join("\n")); + }); }); describe("Babel", function() {