Skip to content

Commit

Permalink
feat: fork type-checker to separate process
Browse files Browse the repository at this point in the history
  • Loading branch information
fathyb committed Dec 10, 2017
1 parent fd771e8 commit 5a18d78
Show file tree
Hide file tree
Showing 11 changed files with 100 additions and 43 deletions.
4 changes: 1 addition & 3 deletions src/backend/config-loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,7 @@ export class ConfigurationLoader<T> {
let options = configCache.get(cwd)

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

const tsconfig = configPath && commentsJson.parse(await readFile(configPath))
const transpilerOptions = {
Expand Down
4 changes: 2 additions & 2 deletions src/backend/reporter.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import {TypeCheckingResult} from '../interfaces'
import {TypeCheckResult} from '../interfaces'
import {formatDiagnostic} from './format'

export function reportDiagnostics(
{semanticDiagnostics, syntacticDiagnostics}: TypeCheckingResult
{semanticDiagnostics, syntacticDiagnostics}: TypeCheckResult
): void {
const frame = formatDiagnostic(syntacticDiagnostics.concat(semanticDiagnostics), process.cwd())

Expand Down
9 changes: 0 additions & 9 deletions src/backend/service/host.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ export class LanguageServiceHost implements ts.LanguageServiceHost {
public readDirectory = ts.sys.readDirectory

private readonly fileNames: string[]
private readonly fileCache = new Map<string, string>()
private readonly files: {[k: string]: {version: number}} = {}
private readonly options: ts.CompilerOptions

Expand Down Expand Up @@ -40,21 +39,13 @@ export class LanguageServiceHost implements ts.LanguageServiceHost {
}

public getScriptSnapshot(fileName: string) {
/*const cached = this.fileCache.get(fileName)
if(cached) {
return ts.ScriptSnapshot.fromString(cached)
}*/

if(!ts.sys.fileExists(fileName)) {
return
}

const content = ts.sys.readFile(fileName)

if(content) {
this.fileCache.set(fileName, content)

return ts.ScriptSnapshot.fromString(content)
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/backend/service/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as ts from 'typescript'

import {LanguageServiceHost} from './host'

import {TypeCheckingResult} from '../../interfaces'
import {TypeCheckResult} from '../../interfaces'

export class LanguageService {
private readonly service: ts.LanguageService
Expand All @@ -13,7 +13,7 @@ export class LanguageService {
this.service = ts.createLanguageService(this.host, ts.createDocumentRegistry())
}

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

this.host.invalidate(path)
Expand Down
4 changes: 2 additions & 2 deletions src/backend/transpiler.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as ts from 'typescript'

import {TranspilationResult} from '../interfaces'
import {TranspileResult} from '../interfaces'

export class Transpiler {
private readonly compilerOptions: ts.CompilerOptions
Expand All @@ -9,7 +9,7 @@ export class Transpiler {
this.compilerOptions = options.options
}

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

Expand Down
33 changes: 17 additions & 16 deletions src/frontend/injector/master.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import {ChildProcess, fork} from 'child_process'

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'

let serviceConfig: ConfigurationLoader<LanguageService>|null = null
import {typeCheck} from '../process/checker'

/**
* This function patch the WorkerFrame properties to interecpt
Expand All @@ -19,36 +17,39 @@ export function inject() {
}
}

// We create a proxy layer between the worker and the master
const injectedReceive = ((receive: () => void) =>
function(this: any, data: InjectedMessage) {
if(data && data.__parcelTypeScript) {
receiveMessage(data)
receiveMessage(data, true)
}
else {
return receive.call(this, data)
}
}
)(WorkerFarm.prototype.receive)

export async function receiveMessage(data: InjectedMessage) {
let forkedProcess: ChildProcess|null = null

export async function receiveMessage(data: InjectedMessage, fromWorker: boolean) {
if(!data || !data.__parcelTypeScript) {
throw new Error('Unknown Parcel TypeScript input message')
}

const {__parcelTypeScript: message} = data

if(message.type === 'check-file') {
const {file} = message
// If we are in a worker (in watch mode)
if(fromWorker) {
if(forkedProcess === null) {
forkedProcess = fork(require.resolve('../process'))
}

// TODO: move this to a background thread
if(serviceConfig === null) {
serviceConfig = new ConfigurationLoader(file, config => new LanguageService(config))
forkedProcess.send(message)
}
else {
typeCheck(message.file)
}

const service = await serviceConfig.wait()
const result = service.parse(file)

reportDiagnostics(result)
}
else {
throw new Error(`Unknown Parcel TypeScript message "${message}"`)
Expand Down
2 changes: 1 addition & 1 deletion src/frontend/injector/worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,6 @@ function dispatchMessage(message: InjectedMessage) {
// else we are in the main thread
// this may happen on the first build
else {
receiveMessage(message)
receiveMessage(message, false)
}
}
21 changes: 21 additions & 0 deletions src/frontend/process/checker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import {ConfigurationLoader} from '../../backend/config-loader'
import {reportDiagnostics} from '../../backend/reporter'
import {LanguageService} from '../../backend/service'

let serviceConfig: ConfigurationLoader<LanguageService>|null = null

export async function typeCheck(...files: string[]) {
if(files.length === 0) {
return
}

if(serviceConfig === null) {
serviceConfig = new ConfigurationLoader(files[0], config => new LanguageService(config))
}

const service = await serviceConfig.wait()

files.forEach(file =>
reportDiagnostics(service.parse(file))
)
}
9 changes: 9 additions & 0 deletions src/frontend/process/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import {CheckFileMessage} from '../../interfaces'
import {Batcher} from '../../utils/batcher'

import {typeCheck} from './checker'

// The batcher will batch all check queries each 50 milliseconds.
const batcher = new Batcher<CheckFileMessage>(messages => typeCheck(...messages.map(({file}) => file)), 50)

process.on('message', (message: CheckFileMessage) => batcher.emit(message))
16 changes: 8 additions & 8 deletions src/interfaces.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,27 @@
import {Diagnostic} from 'typescript'

export interface Message<T extends string, U extends {} = {}> {
__parcelTypeScript: U & {
type: T
}
export type Message<T extends string, U extends {} = {}> = U & {
type: T
}

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

export type InjectedMessage = CheckFileMessage
export interface InjectedMessage {
__parcelTypeScript: CheckFileMessage
}

export interface TranspilationResult {
export interface TranspileResult {
sources: {
js: string
sourceMap?: string
}
}

export interface TypeCheckingResult {
export interface TypeCheckResult {
syntacticDiagnostics: Diagnostic[]
semanticDiagnostics: Diagnostic[]

transpile(): TranspilationResult
transpile(): TranspileResult
}
37 changes: 37 additions & 0 deletions src/utils/batcher.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
export class Batcher<T> {
private timeout: NodeJS.Timer|null = null
private readonly queue: T[] = []

constructor(
private readonly work: (data: T[]) => void,
private readonly delay: number = 0
) {}

public emit(data: T) {
this.queue.push(data)

if(this.timeout === null) {
this.timeout = setTimeout(this.batch, this.delay)
}
}

public clear(): void {
if(this.timeout != null) {
clearTimeout(this.timeout)
}

this.drain()
}

private drain(): T[] {
return this.queue.splice(0)
}

private readonly batch = () => {
this.timeout = null

if(this.queue.length > 0) {
this.work(this.drain())
}
}
}

0 comments on commit 5a18d78

Please sign in to comment.