From 50ee86b8d4f914b78312ae4c24c28e7dcfe3dab0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9e=20Kooi?= Date: Wed, 31 Jan 2018 15:12:25 +0100 Subject: [PATCH 1/2] Add TypeScript support. If `typescript` is installed, add the tsify plugin. If a syntax error occurs in a `.ts` file, recommend installing `typescript`. TODO: Catch and format errors from the typescript compiler. --- lib/cmd-build.js | 10 +++++-- lib/cmd-start.js | 7 ++--- lib/graph-script.js | 19 +++++++++++++ lib/is-electron-project.js | 24 ----------------- lib/tty-error.js | 7 +++++ lib/utils.js | 20 ++++++++++++++ package.json | 4 ++- test/script.js | 55 ++++++++++++++++++++++++++++++++++++++ 8 files changed, 116 insertions(+), 30 deletions(-) delete mode 100644 lib/is-electron-project.js diff --git a/lib/cmd-build.js b/lib/cmd-build.js index 6dd98d64..028bc9c3 100644 --- a/lib/cmd-build.js +++ b/lib/cmd-build.js @@ -18,7 +18,13 @@ function build (entry, opts) { var basedir = utils.dirname(entry) var outdir = path.join(basedir, 'dist') - mkdirp(outdir, function (err) { + utils.hasDependency(basedir, 'electron', function (err, hasElectron) { + if (err) return console.error(err) + opts.electron = hasElectron + mkdirp(outdir, onoutdir) + }) + + function onoutdir (err) { if (err) return console.error(err) var bankaiOpts = { @@ -232,7 +238,7 @@ function build (entry, opts) { } ], done) } - }) + } } function clr (text, color) { diff --git a/lib/cmd-start.js b/lib/cmd-start.js index db825011..f51e90c4 100644 --- a/lib/cmd-start.js +++ b/lib/cmd-start.js @@ -1,15 +1,16 @@ var getPort = require('get-port') +var path = require('path') -var isElectronProject = require('./is-electron-project') +var hasDependency = require('./utils').hasDependency var http = require('./http-server') var bankai = require('../http') module.exports = start function start (entry, opts) { - isElectronProject(process.cwd(), function (err, bool) { + hasDependency(path.dirname(entry), 'electron', function (err, isElectron) { if (err) throw err - opts.electron = bool + opts.electron = isElectron var handler = bankai(entry, opts) var state = handler.state // TODO: move all UI code into this file diff --git a/lib/graph-script.js b/lib/graph-script.js index 77937d56..3f08bb2e 100644 --- a/lib/graph-script.js +++ b/lib/graph-script.js @@ -16,6 +16,7 @@ var tinyify = require('tinyify') var glslify = require('glslify') var brfs = require('brfs') +var hasDependency = require('./utils').hasDependency var ttyError = require('./tty-error') var exorcise = require('./exorcise') @@ -39,8 +40,20 @@ module.exports = node function node (state, createEdge) { assert.equal(typeof state.metadata.entry, 'string', 'state.metadata.entries should be type string') + var self = this this.emit('progress', 'scripts', 0) + hasDependency(state.metadata.entry, 'typescript', function (err, hasTypescript) { + if (err) { + self.emit('error', err) + } else { + state.metadata.typescript = hasTypescript + } + startNode.call(self, state, createEdge) + }) +} + +function startNode (state, createEdge) { var self = this var entry = state.metadata.entry var fullPaths = Boolean(state.metadata.fullPaths) @@ -56,6 +69,10 @@ function node (state, createEdge) { }) } + if (state.metadata.typescript) { + b.plugin('tsify') + } + b.ignore('sheetify/insert') b.transform(sheetify) b.transform(glslify) @@ -106,6 +123,8 @@ function node (state, createEdge) { b.bundle(function (err, bundle) { if (err) { delete err.stream + // HACK to tell ttyError whether we have TypeScript + err.hasTypeScript = state.metadata.typescript err = ttyError('scripts', 'browserify.bundle', err) return self.emit('error', 'scripts', 'browserify.bundle', err) } diff --git a/lib/is-electron-project.js b/lib/is-electron-project.js deleted file mode 100644 index d62b53f7..00000000 --- a/lib/is-electron-project.js +++ /dev/null @@ -1,24 +0,0 @@ -var explain = require('explain-error') -var findup = require('findup') -var path = require('path') -var fs = require('fs') - -module.exports = isElectronProject - -function isElectronProject (dirname, cb) { - findup(dirname, 'package.json', function (err, dir) { - if (err) return cb(null, false) - fs.readFile(path.join(dir, 'package.json'), function (err, json) { - if (err) return cb(explain(err, 'bankai/lib/is-electron-project: error reading package.json')) - - try { - var pkg = JSON.parse(json) - var hasElectronDep = Boolean((pkg.dependencies && pkg.dependencies.electron) || - (pkg.devDependencies && pkg.devDependencies.electron)) - cb(null, hasElectronDep) - } catch (err) { - if (err) return cb(explain(err, 'bankai/lib/is-electron-project: error parsing package.json')) - } - }) - }) -} diff --git a/lib/tty-error.js b/lib/tty-error.js index 5be98eda..6eb5f0e5 100644 --- a/lib/tty-error.js +++ b/lib/tty-error.js @@ -4,6 +4,7 @@ var path = require('path') var fs = require('fs') var cwd = process.cwd() +var typescriptRx = /\.tsx?$/ module.exports = ttyError @@ -15,6 +16,7 @@ function ttyError (src, sub, err) { var loc = err.loc var line = loc.line var col = loc.column + 1 + var hasTypeScript = err.hasTypeScript var lineNum = String(line) + ' ' var padLen = lineNum.length @@ -38,6 +40,11 @@ function ttyError (src, sub, err) { str += clr(lineNum + '|', 'blue') + ` ${clr(code, 'white')}\n` str += clr(empty, 'blue') + clr(syntaxError, 'red') + '\n\n' str += clr(`Hmmm. We're having trouble parsing a file.`, 'white') + if (typescriptRx.test(longFilename) && !hasTypeScript) { + str += '\n\n' + str += clr(`To enable TypeScript in your project, install the TypeScript compiler: `, 'white') + '\n' + str += ' ' + clr('npm install --save-dev typescript', 'grey') + } err.pretty = str return err diff --git a/lib/utils.js b/lib/utils.js index bcfdd16c..09e86296 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -2,6 +2,8 @@ var recursiveWatch = require('recursive-watch') var findup = require('findup') var Debug = require('debug') var path = require('path') +var explain = require('explain-error') +var fs = require('fs') // Figure out what directory the file is in. // Does nothing if a directory is passed. @@ -93,3 +95,21 @@ try { } } exports.brotli = brotliCompress + +exports.hasDependency = function hasDependency (dirname, dep, cb) { + findup(dirname, 'package.json', function (err, dir) { + if (err) return cb(null, false) + fs.readFile(path.join(dir, 'package.json'), function (err, json) { + if (err) return cb(explain(err, 'bankai.hasDependency: error reading package.json')) + + try { + var pkg = JSON.parse(json) + var hasTypeScriptDep = Boolean((pkg.dependencies && pkg.dependencies[dep]) || + (pkg.devDependencies && pkg.devDependencies[dep])) + cb(null, hasTypeScriptDep) + } catch (err) { + if (err) return cb(explain(err, 'bankai.hasDependency: error parsing package.json')) + } + }) + }) +} diff --git a/package.json b/package.json index ee284ff6..f021105c 100644 --- a/package.json +++ b/package.json @@ -72,6 +72,7 @@ "tfilter": "^1.0.1", "through2": "^2.0.3", "tinyify": "^2.4.0", + "tsify": "^3.0.4", "v8-compile-cache": "^1.1.0", "wasm-brotli": "^1.0.2", "watchify": "^3.9.0", @@ -87,6 +88,7 @@ "rimraf": "^2.6.1", "standard": "^10.0.2", "tachyons": "^4.7.4", - "tape": "^4.7.0" + "tape": "^4.7.0", + "typescript": "^2.6.2" } } diff --git a/test/script.js b/test/script.js index 07c3c22a..920b9ad9 100644 --- a/test/script.js +++ b/test/script.js @@ -1,3 +1,4 @@ +var async = require('async-collection') var dedent = require('dedent') var rimraf = require('rimraf') var mkdirp = require('mkdirp') @@ -73,6 +74,60 @@ tape('output multiple bundles if `split-require` is used', function (assert) { }) }) +tape('adds tsify if typescript is installed', function (assert) { + assert.plan(3) + + var barePackage = JSON.stringify({ + dependencies: {} + }, null, 2) + var tsPackage = JSON.stringify({ + dependencies: {}, + devDependencies: { typescript: '^2.6.2' } + }, null, 2) + + var file = ` + class Counter { + private count: number; + public next (): number { + return this.count++; + } + } + export = Counter + ` + + var tmpDirname = path.join(__dirname, '../tmp', 'js-pipeline-' + (Math.random() * 1e4).toFixed()) + mkdirp.sync(tmpDirname) + fs.writeFileSync(path.join(tmpDirname, 'app.ts'), file) + + async.series([ + withoutTypescript, + withTypescript + ], assert.end) + + function withoutTypescript (cb) { + fs.writeFileSync(path.join(tmpDirname, 'package.json'), barePackage) + var compiler = bankai(path.join(tmpDirname, 'app.ts'), { watch: false }) + compiler.on('error', function (step, type, err) { + assert.equal(err.filename, path.join(tmpDirname, 'app.ts')) + cb() + }) + compiler.scripts('bundle.js', function () { + assert.fail('should have failed') + }) + } + function withTypescript (cb) { + fs.writeFileSync(path.join(tmpDirname, 'package.json'), tsPackage) + + var compiler = bankai(path.join(tmpDirname, 'app.ts'), { watch: false }) + compiler.on('error', assert.error) + compiler.scripts('bundle.js', function (err, res) { + assert.error(err, 'error compiling typescript') + assert.ok(res) + cb() + }) + } +}) + tape('use custom babel config for local files, but not for dependencies', function (assert) { assert.plan(2) From 2606758c2f72c62ff98507908a4fed652b72da75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9e=20Kooi?= Date: Fri, 2 Feb 2018 13:12:09 +0100 Subject: [PATCH 2/2] Improve syntax error messages --- lib/tty-error.js | 32 ++++++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/lib/tty-error.js b/lib/tty-error.js index 6eb5f0e5..06e3c6d0 100644 --- a/lib/tty-error.js +++ b/lib/tty-error.js @@ -9,11 +9,11 @@ var typescriptRx = /\.tsx?$/ module.exports = ttyError function ttyError (src, sub, err) { - if (!err.filename || !err.loc) return err + var longFilename = err.filename || err.fileName + if (!longFilename || !getErrorLocation(err)) return err - var longFilename = err.filename var filename = path.relative(cwd, longFilename) - var loc = err.loc + var loc = getErrorLocation(err) var line = loc.line var col = loc.column + 1 var hasTypeScript = err.hasTypeScript @@ -22,7 +22,7 @@ function ttyError (src, sub, err) { var padLen = lineNum.length var empty = padLeft('|', padLen + 1) var arrow = padLeft('--> ', padLen + 4 - 1) - var syntaxError = padLeft('', col) + '^ Syntax Error' + var syntaxError = padLeft('', col) + '^ ' + getErrorMessage(err) try { var file = fs.readFileSync(longFilename, 'utf8') @@ -72,3 +72,27 @@ function pad (len, char) { while (res.length < len) res += char return res } + +function getErrorLocation (err) { + if (err.loc) return err.loc + if (typeof err.line === 'number' && typeof err.column === 'number') { + return { + line: err.line, + column: err.column + } + } + return null +} + +function getErrorMessage (err) { + var loc = getErrorLocation(err) + var message = err.message + // strip file names + .replace(/^.*?:|while parsing file:.*?$/g, '') + // strip position in file + .replace('(' + loc.line + ':' + loc.column + ')', '') + // same, but for typescript + .replace('(' + loc.line + ',' + loc.column + ')', '') + + return message || 'Syntax Error' +}