Skip to content

Commit

Permalink
feat(type-check): implement incremental build
Browse files Browse the repository at this point in the history
  • Loading branch information
fathyb committed Dec 10, 2017
1 parent 2c11361 commit fd771e8
Show file tree
Hide file tree
Showing 12 changed files with 85 additions and 78 deletions.
49 changes: 29 additions & 20 deletions src/backend/config-loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,34 +7,43 @@ import findUp = require('find-up')

import {readFile} from '../utils/fs'

const configCache = new Map<string, ParsedCommandLine>()

export class ConfigurationLoader<T> {
private readonly promise: Promise<T>

constructor(path: string, then: (config: ParsedCommandLine) => T) {
this.promise = (async () => {
const configPath = await findUp('tsconfig.json', {
cwd: dirname(path)
})

const tsconfig = configPath && commentsJson.parse(await readFile(configPath))
const transpilerOptions = {
compilerOptions: {
module: 'commonjs',
jsx: 'preserve'
const cwd = dirname(path)
let options = configCache.get(cwd)

if(!options) {
const configPath = await findUp('tsconfig.json', {
cwd: dirname(path)
})

const tsconfig = configPath && commentsJson.parse(await readFile(configPath))
const transpilerOptions = {
compilerOptions: {
module: 'commonjs',
jsx: 'preserve'
}
} as any

// Overwrite default if config is found
if(tsconfig) {
transpilerOptions.compilerOptions = tsconfig.compilerOptions
transpilerOptions.files = tsconfig.files
transpilerOptions.include = tsconfig.include
transpilerOptions.exclude = tsconfig.exclude
}
} as any

// Overwrite default if config is found
if(tsconfig) {
transpilerOptions.compilerOptions = tsconfig.compilerOptions
transpilerOptions.files = tsconfig.files
transpilerOptions.include = tsconfig.include
transpilerOptions.exclude = tsconfig.exclude
}

transpilerOptions.compilerOptions.noEmit = false
transpilerOptions.compilerOptions.noEmit = false

const options = parseJsonConfigFileContent(transpilerOptions, sys, process.cwd())
options = parseJsonConfigFileContent(transpilerOptions, sys, process.cwd())

configCache.set(cwd, options)
}

return then(options)
})()
Expand Down
14 changes: 9 additions & 5 deletions src/backend/format.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@ export function formatDiagnostic(diagnostics: Diagnostic[], context: string): st
if(diagnostic.file != null && diagnostic.start != null) {
const lineChar = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start)
const source = diagnostic.file.text || diagnostic.source
const red = chalk.red(`🚨 ${diagnostic.file.fileName}(${lineChar.line + 1},${lineChar.character + 1})`)

const messages = [chalk.dim(` ${lineChar.line + 1}:${lineChar.character + 1} `) + messageText]
const messages = [`${red}\n${chalk.redBright(messageText)}`]

if(source != null) {
const frame = codeFrame(
Expand All @@ -26,10 +27,13 @@ export function formatDiagnostic(diagnostics: Diagnostic[], context: string): st
lineChar.character,
{linesAbove: 1, linesBelow: 1, highlightCode: true}
)
.split('\n')
.map(str => ` ${str}`)
.join('\n')
messages.push(frame)

messages.push(
frame
.split('\n')
.map(str => ` ${str}`)
.join('\n')
)
}
message = messages.join('\n')
}
Expand Down
16 changes: 2 additions & 14 deletions src/backend/reporter.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,10 @@
import chalk from 'chalk'

import {TypeCheckingResult} from '../interfaces'
import {formatDiagnostic} from './format'

export function reportDiagnostics(
file: string,
{semanticDiagnostics, syntacticDiagnostics}: TypeCheckingResult
): void {
// TODO: properly log this
if(syntacticDiagnostics.length > 0) {
const frame = formatDiagnostic(syntacticDiagnostics, process.cwd())

throw new Error(frame)
}

if(semanticDiagnostics.length > 0) {
const codeFrame = formatDiagnostic(semanticDiagnostics, process.cwd())
const frame = formatDiagnostic(syntacticDiagnostics.concat(semanticDiagnostics), process.cwd())

console.error('🚨 %s: \n%s', chalk.redBright(file), codeFrame)
}
console.error(frame)
}
15 changes: 14 additions & 1 deletion src/backend/service/host.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,19 @@ export class LanguageServiceHost implements ts.LanguageServiceHost {
this.fileNames = fileNames
}

public invalidate(file: string): void {
const entry = this.files[file]

if(entry) {
entry.version++
}
else {
this.files[file] = {
version: 0
}
}
}

public getScriptFileNames() {
return this.fileNames
}
Expand All @@ -26,7 +39,7 @@ export class LanguageServiceHost implements ts.LanguageServiceHost {
return this.files[fileName] && this.files[fileName].version.toString()
}

public getScriptSnapshot(fileName: string): ts.IScriptSnapshot|undefined {
public getScriptSnapshot(fileName: string) {
/*const cached = this.fileCache.get(fileName)
if(cached) {
Expand Down
8 changes: 5 additions & 3 deletions src/backend/service/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,18 @@ import {TypeCheckingResult} from '../../interfaces'

export class LanguageService {
private readonly service: ts.LanguageService
private readonly host: LanguageServiceHost

constructor(json: any) {
const host = new LanguageServiceHost(json)

this.service = ts.createLanguageService(host, ts.createDocumentRegistry())
this.host = new LanguageServiceHost(json)
this.service = ts.createLanguageService(this.host, ts.createDocumentRegistry())
}

public parse(path: string): TypeCheckingResult {
const {service} = this

this.host.invalidate(path)

return {
syntacticDiagnostics: service.getSyntacticDiagnostics(path),
semanticDiagnostics: service.getSemanticDiagnostics(path),
Expand Down
6 changes: 2 additions & 4 deletions src/backend/transpiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,10 @@ export class Transpiler {

public transpile(code: string, fileName: string): TranspilationResult {
const {compilerOptions} = this
const {outputText} = ts.transpileModule(code, {compilerOptions, fileName})
const {outputText: js, sourceMapText: sourceMap} = ts.transpileModule(code, {compilerOptions, fileName})

return {
sources: {
js: outputText
}
sources: {js, sourceMap}
}
}
}
5 changes: 1 addition & 4 deletions src/frontend/asset.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
import JSAsset = require('parcel-bundler/src/assets/JSAsset')

import {dispatchCheckFile, inject} from '../injector/worker'

import {ConfigurationLoader} from '../backend/config-loader'
import {Transpiler} from '../backend/transpiler'

inject()
import {dispatchCheckFile} from './injector/worker'

export = class TSAsset extends JSAsset {
private transpiler: ConfigurationLoader<Transpiler>
Expand Down
27 changes: 14 additions & 13 deletions src/injector/master.ts → src/frontend/injector/master.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import WorkerFarm = require('parcel-bundler/src/WorkerFarm')

import {ConfigurationLoader} from '../backend/config-loader'
import {reportDiagnostics} from '../backend/reporter'
import {LanguageService} from '../backend/service'
import {InjectedMessage} from '../interfaces'
import {ConfigurationLoader} from '../../backend/config-loader'
import {reportDiagnostics} from '../../backend/reporter'
import {LanguageService} from '../../backend/service'
import {InjectedMessage} from '../../interfaces'

let serviceConfig: ConfigurationLoader<LanguageService>|null = null

Expand All @@ -14,17 +14,21 @@ let serviceConfig: ConfigurationLoader<LanguageService>|null = null
* and then dispatched to the type-checking worker.
*/
export function inject() {
const {receive} = WorkerFarm.prototype
if(WorkerFarm.prototype.receive !== injectedReceive) {
WorkerFarm.prototype.receive = injectedReceive
}
}

WorkerFarm.prototype.receive = async function(this: any, data: InjectedMessage) {
const injectedReceive = ((receive: () => void) =>
function(this: any, data: InjectedMessage) {
if(data && data.__parcelTypeScript) {
receiveMessage(data)
}
else {
receive.call(this, data)
return receive.call(this, data)
}
}
}
)(WorkerFarm.prototype.receive)

export async function receiveMessage(data: InjectedMessage) {
if(!data || !data.__parcelTypeScript) {
Expand All @@ -33,10 +37,7 @@ export async function receiveMessage(data: InjectedMessage) {

const {__parcelTypeScript: message} = data

if(message.type === 'ready') {
// console.log('\nWorker is ready\n')
}
else if(message.type === 'check-file') {
if(message.type === 'check-file') {
const {file} = message

// TODO: move this to a background thread
Expand All @@ -47,7 +48,7 @@ export async function receiveMessage(data: InjectedMessage) {
const service = await serviceConfig.wait()
const result = service.parse(file)

reportDiagnostics(file, result)
reportDiagnostics(result)
}
else {
throw new Error(`Unknown Parcel TypeScript message "${message}"`)
Expand Down
15 changes: 5 additions & 10 deletions src/injector/worker.ts → src/frontend/injector/worker.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,23 @@
import {CheckFileMessage, InjectedMessage, ReadyMessage} from '../interfaces'
import {InjectedMessage} from '../../interfaces'

import {receiveMessage} from './master'

export function inject() {
dispatchMessage({
__parcelTypeScript: {
type: 'ready'
}
} as ReadyMessage)
}

export function dispatchCheckFile(file: string) {
dispatchMessage({
__parcelTypeScript: {
type: 'check-file',
file
}
} as CheckFileMessage)
})
}

function dispatchMessage(message: InjectedMessage) {
// if process.send is defined then we are in a worker
if(process.send) {
process.send(message)
}
// else we are in the main thread
// this may happen on the first build
else {
receiveMessage(message)
}
Expand Down
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {inject} from './injector/master'
import {inject} from './frontend/injector/master'

export = (bundler: any) => {
if(bundler.farm !== null) {
Expand Down
3 changes: 1 addition & 2 deletions src/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,11 @@ export interface Message<T extends string, U extends {} = {}> {
}
}

export type ReadyMessage = Message<'ready'>
export type CheckFileMessage = Message<'check-file', {
file: string
}>

export type InjectedMessage = ReadyMessage | CheckFileMessage
export type InjectedMessage = CheckFileMessage

export interface TranspilationResult {
sources: {
Expand Down
3 changes: 2 additions & 1 deletion tslint.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"quotemark": [true, "single", "avoid-escape"],
"indent": [true, "tabs"],
"no-namespace": false,
"arrow-parens": [true, "ban-single-arg-parens"]
"arrow-parens": [true, "ban-single-arg-parens"],
"only-arrow-functions": false
}
}

0 comments on commit fd771e8

Please sign in to comment.