Skip to content

Instantly share code, notes, and snippets.

@sep2
Last active March 7, 2020 14:17
Show Gist options
  • Save sep2/8963236a7880fade8f5ced3134a11191 to your computer and use it in GitHub Desktop.
Save sep2/8963236a7880fade8f5ced3134a11191 to your computer and use it in GitHub Desktop.
Parcel v1 watch mode with typescript checking types after bundle end to speed up compiling and proxy api request

Parcel v1 watch mode with typescript checking types after bundle end to speed up compiling and proxy api request

Install: yarn add -D parcel-bundler express http-proxy-middleware rxjs typescript ts-node

Run: ts-node --skip-project watch.ts

Modify as you need

see also modify parcel build output directory structure

import * as ts from 'typescript'
import * as Bundler from 'parcel-bundler'
import * as express from 'express'
import { createProxyMiddleware, Options as ProxyOptions } from 'http-proxy-middleware'
import { merge, Observable, partition, Subscriber } from 'rxjs'
import { buffer, bufferCount, filter, map, share, skipWhile, tap, timestamp } from 'rxjs/operators'
//============================================================
process.env.NODE_ENV = 'development'
const protocol = 'http'
const entryPoint = 'src/index.html'
const port = Number(process.env.PORT || 1234)
const proxies: Record<string, ProxyOptions> = {
'/api': {
target: `${protocol}://localhost:3000`,
logLevel: 'warn',
},
}
const parcelOptions = {
autoInstall: false,
open: true, // this does not work
} as Bundler.ParcelOptions
//============================================================
class BundlerEvent {}
class BundlerBuildStartEvent extends BundlerEvent {}
class BundlerBuildEndEvent extends BundlerEvent {}
type WatcherCallback = () => void
class CompilerEvent {}
class CompilerStartEvent extends CompilerEvent {}
class CompilerEndEvent extends CompilerEvent {}
const bundler$ = new Observable((subscriber: Subscriber<BundlerEvent>) => {
const bundler = new Bundler(entryPoint, parcelOptions)
bundler.on('buildStart', () => subscriber.next(new BundlerBuildStartEvent()))
bundler.on('buildEnd', () => subscriber.next(new BundlerBuildEndEvent()))
const app = express()
Object.keys(proxies).forEach(path => app.use(path, createProxyMiddleware(proxies[path])))
app.use(bundler.middleware())
app.listen(port)
}).pipe(share())
const tsc$ = new Observable((subscriber: Subscriber<WatcherCallback | CompilerEvent>) => {
const configPath = ts.findConfigFile('./', ts.sys.fileExists, 'tsconfig.json')
if (!configPath) {
throw new Error("Could not find a valid 'tsconfig.json'.")
}
const formatHost: ts.FormatDiagnosticsHost = {
getCanonicalFileName: path => path,
getCurrentDirectory: ts.sys.getCurrentDirectory,
getNewLine: () => ts.sys.newLine,
}
const host = ts.createWatchCompilerHost(
configPath,
{ preserveWatchOutput: true, pretty: true },
ts.sys,
ts.createSemanticDiagnosticsBuilderProgram,
diagnostic => console.error(ts.formatDiagnosticsWithColorAndContext([diagnostic], formatHost)),
ts.createBuilderStatusReporter(ts.sys, true),
{ synchronousWatchDirectory: false },
)
// Delay watcher callbacks until we decide when to notify the compiler
const origWatchFile = host.watchFile
host.watchFile = function(path, callback, pollingInterval, options) {
const newCb = (fileName, eventKind) => subscriber.next(() => callback(fileName, eventKind))
return origWatchFile.call(this, path, newCb, pollingInterval, options)
}
const origWatchDirectory = host.watchDirectory
host.watchDirectory = function(path, callback, recursive, options) {
const newCb = fileName => subscriber.next(() => callback(fileName))
return origWatchDirectory.call(this, path, newCb, recursive, options)
}
// Emit start and end events for time measuring purpose
const origCreateProgram = host.createProgram
host.createProgram = function(...args) {
subscriber.next(new CompilerStartEvent())
return origCreateProgram.call(this, ...args)
}
const origAfterProgramCreate = host.afterProgramCreate
host.afterProgramCreate = function(...args) {
subscriber.next(new CompilerEndEvent())
return origAfterProgramCreate?.call(this, ...args)
}
ts.createWatchProgram(host)
}).pipe(share())
const buildStart$ = bundler$.pipe(filter<BundlerBuildStartEvent>(x => x instanceof BundlerBuildStartEvent))
const buildEnd$ = bundler$.pipe(filter<BundlerBuildEndEvent>(x => x instanceof BundlerBuildEndEvent))
const [compiler$, watcher$] = partition(tsc$, x => x instanceof CompilerEvent) as [
Observable<CompilerEvent>,
Observable<WatcherCallback>,
]
merge(
buildStart$.pipe(tap(() => console.clear())),
buildEnd$.pipe(tap(() => console.log(`Server running at ${protocol}://localhost:${port}`))),
watcher$.pipe(
// Buffer all watcher callbacks until a build end event occurred,
// indicating Parcel finished its job so we have free CPU cycles to do type checking
buffer(buildEnd$),
// Apply all buffered watcher callbacks to notify tsc
tap((cbs: WatcherCallback[]) => cbs.forEach(cb => cb())),
),
compiler$.pipe(
tap(x => x instanceof CompilerStartEvent && console.log("We're about to type checking...")),
// Make sure the stream comes in the order like [start, end, start, ...] instead of [end, start, end, ...]
skipWhile(x => x instanceof CompilerEndEvent),
timestamp(),
bufferCount(2),
map(([start, end]) => (end.timestamp - start.timestamp) / 1000),
tap(cost => console.log('Type checking in', cost, 'sec.')),
),
).subscribe()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment