Skip to content
This repository has been archived by the owner on Oct 2, 2021. It is now read-only.

Commit

Permalink
Use actual TypeScript instead of @babel/preset-typescript. (#25)
Browse files Browse the repository at this point in the history
* Use actual TypeScript instead of @babel/preset-typescript.

Babel's TypeScript implementation has a few unfortunate caveats:
https://babeljs.io/docs/en/babel-plugin-transform-typescript#caveats

Most notably, the lack of *full* support for namespaces is painful:
babel/babel#8244
babel/babel#9785

By precompiling TypeScript code with the actual TypeScript compiler, we
can support features like namespaces without relying on Babel. Of course,
Babel still handles everything after TypeScript syntax has been removed.

* Test that JSX syntax works in .tsx files.
  • Loading branch information
benjamn committed Jul 6, 2019
1 parent de2f326 commit 40b260e
Show file tree
Hide file tree
Showing 7 changed files with 99 additions and 43 deletions.
35 changes: 35 additions & 0 deletions index.js
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down
27 changes: 13 additions & 14 deletions options.js
Expand Up @@ -65,7 +65,6 @@ exports.getDefaults = function getDefaults(features) {
}

maybeAddReactPlugins(features, combined);
maybeAddTypeScriptPreset(features, combined.presets);

if (features && features.jscript) {
combined.plugins.push(
Expand All @@ -75,7 +74,7 @@ exports.getDefaults = function getDefaults(features) {
}
}

return finish([combined]);
return finish(features, [combined]);
};

function maybeAddReactPlugins(features, options) {
Expand All @@ -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: [],
Expand All @@ -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,
Expand All @@ -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) {
Expand Down Expand Up @@ -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) {
Expand Down
32 changes: 5 additions & 27 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Expand Up @@ -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",
Expand All @@ -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",
Expand Down
3 changes: 3 additions & 0 deletions 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) {}
}
3 changes: 3 additions & 0 deletions test/react.tsx
@@ -0,0 +1,3 @@
export function Component() {
return <div>oyez</div>;
}
38 changes: 38 additions & 0 deletions test/tests.js
Expand Up @@ -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() {
Expand Down

0 comments on commit 40b260e

Please sign in to comment.