From 757b67362e1fce076241fa31afe2179db93cff18 Mon Sep 17 00:00:00 2001 From: Jasper De Moor Date: Fri, 8 Dec 2017 06:39:00 +0100 Subject: [PATCH] Basic TypeScript Support (#84) * Tiny gitignore cleanup * firefox fix, #36 * ts support * tsx added * transpileoptions improvements and bugfix * tsconfig.json support * add basic typescript test * Default to ES2015 to stay consistent with the js default * remove custom transform, implement check in js transform for ts and put codegen back to commonjs * keep ts transform addition within ts module * ts testing and bugfix * improve jsx parameter and default to preserve and let babel handle it * improve jsx parameter and default to preserve and let babel handle it --- .gitignore | 1 + package.json | 3 +- src/Parser.js | 3 +- src/assets/TypeScriptAsset.js | 35 +++++++++ src/builtins/prelude.js | 3 +- test/integration/typescript-env/index.ts | 3 + test/integration/typescript-json/index.ts | 5 ++ test/integration/typescript-json/local.json | 4 + test/integration/typescript-raw/index.ts | 5 ++ test/integration/typescript-raw/test.txt | 1 + test/integration/typescript-require/Local.ts | 11 +++ test/integration/typescript-require/index.ts | 6 ++ test/integration/typescript/Local.ts | 9 +++ test/integration/typescript/index.ts | 6 ++ test/typescript.js | 82 ++++++++++++++++++++ 15 files changed, 173 insertions(+), 4 deletions(-) create mode 100644 src/assets/TypeScriptAsset.js create mode 100644 test/integration/typescript-env/index.ts create mode 100644 test/integration/typescript-json/index.ts create mode 100644 test/integration/typescript-json/local.json create mode 100644 test/integration/typescript-raw/index.ts create mode 100644 test/integration/typescript-raw/test.txt create mode 100644 test/integration/typescript-require/Local.ts create mode 100644 test/integration/typescript-require/index.ts create mode 100644 test/integration/typescript/Local.ts create mode 100644 test/integration/typescript/index.ts create mode 100644 test/typescript.js diff --git a/.gitignore b/.gitignore index 69cf6c4adec..d648e5c3fc5 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ coverage dist lib !test/**/node_modules +.vscode/ \ No newline at end of file diff --git a/package.json b/package.json index 1f6589a6b44..44d71780df1 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,8 @@ "postcss-modules": "^0.8.0", "posthtml-include": "^1.1.0", "rimraf": "^2.6.1", - "stylus": "^0.54.5" + "stylus": "^0.54.5", + "typescript": "^2.6.2" }, "scripts": { "test": "cross-env NODE_ENV=test mocha", diff --git a/src/Parser.js b/src/Parser.js index ebf35497e7e..800fda6e783 100644 --- a/src/Parser.js +++ b/src/Parser.js @@ -10,6 +10,8 @@ class Parser { this.registerExtension('js', './assets/JSAsset'); this.registerExtension('jsx', './assets/JSAsset'); this.registerExtension('es6', './assets/JSAsset'); + this.registerExtension('ts', './assets/TypeScriptAsset'); + this.registerExtension('tsx', './assets/TypeScriptAsset'); this.registerExtension('json', './assets/JSONAsset'); this.registerExtension('yaml', './assets/YAMLAsset'); this.registerExtension('yml', './assets/YAMLAsset'); @@ -46,7 +48,6 @@ class Parser { if (typeof parser === 'string') { parser = this.extensions[extension] = require(parser); } - return parser; } diff --git a/src/assets/TypeScriptAsset.js b/src/assets/TypeScriptAsset.js new file mode 100644 index 00000000000..7c5b5afceee --- /dev/null +++ b/src/assets/TypeScriptAsset.js @@ -0,0 +1,35 @@ +const JSAsset = require('./JSAsset'); +const config = require('../utils/config'); +const localRequire = require('../utils/localRequire'); + +class TypeScriptAsset extends JSAsset { + async transform() { + super.transform(); + + await this.parseIfNeeded(); + this.isAstDirty = true; + } + + async parse(code) { + // require typescript, installed locally in the app + let typescript = localRequire('typescript', this.name); + + let transpilerOptions = { + compilerOptions: { + module: typescript.ModuleKind.CommonJS, + jsx: typescript.JsxEmit.Preserve + }, + fileName: this.basename + } + + let tsconfig = await config.load(this.name, ['tsconfig.json']); + // Overwrite default if config is found + if (tsconfig) transpilerOptions.compilerOptions = tsconfig.compilerOptions; + transpilerOptions.compilerOptions.noEmit = false; + + // Transpile Module using TypeScript and parse result as ast format through babylon + return await super.parse(typescript.transpileModule(code, transpilerOptions).outputText); + } +} + +module.exports = TypeScriptAsset; \ No newline at end of file diff --git a/src/builtins/prelude.js b/src/builtins/prelude.js index 7301555c20c..8d4219e2e45 100644 --- a/src/builtins/prelude.js +++ b/src/builtins/prelude.js @@ -28,7 +28,6 @@ require = (function (modules, cache, entry) { if (previousRequire) { return previousRequire(name, true); } - var err = new Error('Cannot find module \'' + name + '\''); err.code = 'MODULE_NOT_FOUND'; throw err; @@ -58,7 +57,7 @@ require = (function (modules, cache, entry) { newRequire.modules = modules; newRequire.cache = cache; newRequire.parent = previousRequire; - + for (var i = 0; i < entry.length; i++) { newRequire(entry[i]); } diff --git a/test/integration/typescript-env/index.ts b/test/integration/typescript-env/index.ts new file mode 100644 index 00000000000..c737c8c00a7 --- /dev/null +++ b/test/integration/typescript-env/index.ts @@ -0,0 +1,3 @@ +export function env() { + return process.env.NODE_ENV; +} \ No newline at end of file diff --git a/test/integration/typescript-json/index.ts b/test/integration/typescript-json/index.ts new file mode 100644 index 00000000000..0fd492e620f --- /dev/null +++ b/test/integration/typescript-json/index.ts @@ -0,0 +1,5 @@ +const local = require('./local.json'); + +export function count() { + return local.a + local.b; +} \ No newline at end of file diff --git a/test/integration/typescript-json/local.json b/test/integration/typescript-json/local.json new file mode 100644 index 00000000000..e7c43495efc --- /dev/null +++ b/test/integration/typescript-json/local.json @@ -0,0 +1,4 @@ +{ + "a": 1, + "b": 2 +} \ No newline at end of file diff --git a/test/integration/typescript-raw/index.ts b/test/integration/typescript-raw/index.ts new file mode 100644 index 00000000000..43998e4d359 --- /dev/null +++ b/test/integration/typescript-raw/index.ts @@ -0,0 +1,5 @@ +const url = require('./test.txt'); + +export function getRaw() { + return url; +} \ No newline at end of file diff --git a/test/integration/typescript-raw/test.txt b/test/integration/typescript-raw/test.txt new file mode 100644 index 00000000000..37d4e6c5c48 --- /dev/null +++ b/test/integration/typescript-raw/test.txt @@ -0,0 +1 @@ +hi there diff --git a/test/integration/typescript-require/Local.ts b/test/integration/typescript-require/Local.ts new file mode 100644 index 00000000000..2facbc78f7b --- /dev/null +++ b/test/integration/typescript-require/Local.ts @@ -0,0 +1,11 @@ +class Local { + a: number; + b: number; + + constructor(a: number, b: number) { + this.a = a; + this.b = b; + } +} + +module.exports = Local; \ No newline at end of file diff --git a/test/integration/typescript-require/index.ts b/test/integration/typescript-require/index.ts new file mode 100644 index 00000000000..c49a5c94f7c --- /dev/null +++ b/test/integration/typescript-require/index.ts @@ -0,0 +1,6 @@ +const Local = require('./Local'); + +export function count() { + let local = new Local(1, 2); + return local.a + local.b; +} \ No newline at end of file diff --git a/test/integration/typescript/Local.ts b/test/integration/typescript/Local.ts new file mode 100644 index 00000000000..5a90173a910 --- /dev/null +++ b/test/integration/typescript/Local.ts @@ -0,0 +1,9 @@ +export class Local { + a: number; + b: number; + + constructor(a: number, b: number) { + this.a = a; + this.b = b; + } +} \ No newline at end of file diff --git a/test/integration/typescript/index.ts b/test/integration/typescript/index.ts new file mode 100644 index 00000000000..593febb0ae2 --- /dev/null +++ b/test/integration/typescript/index.ts @@ -0,0 +1,6 @@ +import { Local } from './Local'; + +export function count() { + let local = new Local(1, 2); + return local.a + local.b; +} \ No newline at end of file diff --git a/test/typescript.js b/test/typescript.js new file mode 100644 index 00000000000..b5f48e83e40 --- /dev/null +++ b/test/typescript.js @@ -0,0 +1,82 @@ +const assert = require('assert'); +const fs = require('fs'); +const {bundle, run, assertBundleTree} = require('./utils'); + +describe('typescript', function () { + it('should produce a ts bundle using ES6 imports', async function () { + let b = await bundle(__dirname + '/integration/typescript/index.ts'); + + assert.equal(b.assets.size, 2); + assert.equal(b.childBundles.size, 0); + + let output = run(b); + assert.equal(typeof output.count, 'function'); + assert.equal(output.count(), 3); + }); + + it('should produce a ts bundle using commonJS require', async function () { + let b = await bundle(__dirname + '/integration/typescript-require/index.ts'); + + assert.equal(b.assets.size, 2); + assert.equal(b.childBundles.size, 0); + + let output = run(b); + assert.equal(typeof output.count, 'function'); + assert.equal(output.count(), 3); + }); + + it('should support json require', async function () { + let b = await bundle(__dirname + '/integration/typescript-json/index.ts'); + + assert.equal(b.assets.size, 2); + assert.equal(b.childBundles.size, 0); + + let output = run(b); + assert.equal(typeof output.count, 'function'); + assert.equal(output.count(), 3); + }); + + it('should support env variables', async function () { + let b = await bundle(__dirname + '/integration/typescript-env/index.ts'); + + assert.equal(b.assets.size, 1); + assert.equal(b.childBundles.size, 0); + + let output = run(b); + assert.equal(typeof output.env, 'function'); + assert.equal(output.env(), 'test'); + }); + + it('should support importing a URL to a raw asset', async function () { + let b = await bundle(__dirname + '/integration/typescript-raw/index.ts'); + + assertBundleTree(b, { + name: 'index.js', + assets: ['index.ts', 'test.txt'], + childBundles: [{ + type: 'txt', + assets: ['test.txt'], + childBundles: [] + }] + }); + + let output = run(b); + assert.equal(typeof output.getRaw, 'function'); + assert(/^[0-9a-f]+\.txt$/.test(output.getRaw())); + assert(fs.existsSync(__dirname + '/dist/' + output.getRaw())); + }); + + /*it('should minify in production mode', async function () { + let b = await bundle(__dirname + '/integration/typescript-require/index.ts', { production: true }); + + assert.equal(b.assets.size, 2); + assert.equal(b.childBundles.size, 0); + + let output = run(b); + assert.equal(typeof output.count, 'function'); + assert.equal(output.count(), 3); + + let js = fs.readFileSync(__dirname + '/dist/index.js', 'utf8'); + assert(!js.includes('local.a')); + });*/ +});