Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add TypeScript support, closes #285 #406

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions lib/cmd-build.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down Expand Up @@ -232,7 +238,7 @@ function build (entry, opts) {
}
], done)
}
})
}
}

function clr (text, color) {
Expand Down
7 changes: 4 additions & 3 deletions lib/cmd-start.js
Original file line number Diff line number Diff line change
@@ -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
Expand Down
19 changes: 19 additions & 0 deletions lib/graph-script.js
Original file line number Diff line number Diff line change
Expand Up @@ -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')

Expand All @@ -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)
Expand All @@ -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)
Expand Down Expand Up @@ -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)
}
Expand Down
24 changes: 0 additions & 24 deletions lib/is-electron-project.js

This file was deleted.

39 changes: 35 additions & 4 deletions lib/tty-error.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,25 @@ var path = require('path')
var fs = require('fs')

var cwd = process.cwd()
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

var lineNum = String(line) + ' '
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')
Expand All @@ -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
Expand Down Expand Up @@ -65,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'
}
20 changes: 20 additions & 0 deletions lib/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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'))
}
})
})
}
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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"
}
}
55 changes: 55 additions & 0 deletions test/script.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
var async = require('async-collection')
var dedent = require('dedent')
var rimraf = require('rimraf')
var mkdirp = require('mkdirp')
Expand Down Expand Up @@ -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)

Expand Down