Skip to content

Commit

Permalink
feat: run checker in worker thread
Browse files Browse the repository at this point in the history
  • Loading branch information
fi3ework committed Jun 10, 2021
1 parent 5630901 commit c37097f
Show file tree
Hide file tree
Showing 5 changed files with 149 additions and 35 deletions.
2 changes: 1 addition & 1 deletion examples/react-ts/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import './App.css'

function App() {
// Try change <string> to <number> and the overlay will gone.
const [count, setCount] = useState<string>(0)
const [count, setCount] = useState<string>(1)
return (
<div className="App">
<header className="App-header">
Expand Down
41 changes: 28 additions & 13 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,30 +3,37 @@ import npmRunPath from 'npm-run-path'
import os from 'os'
import { ConfigEnv, Plugin } from 'vite'

import { Checker, CreateDiagnostic, PluginOptions } from './types'
import { BuildCheckBin, CheckWorker, OverlayErrorAction, PluginOptions } from './types'

export * from './types'
export * from './codeFrame'
export * from './utils'

function makeChecker(
function createServeAndBuild(
checker: PluginOptions['checker'],
userOptions?: Partial<PluginOptions>
): Checker {
): { serve: CheckWorker; build: { buildBin: BuildCheckBin } } {
if (typeof checker === 'string') {
// eslint-disable-next-line @typescript-eslint/no-require-imports
const tscCheckerFactory = require(`./presets/${checker}`).checkerFactory
return tscCheckerFactory(userOptions)
const createWorker = require(`./presets/${checker}`).createWorker
// eslint-disable-next-line @typescript-eslint/no-require-imports
const buildBin = require(`./presets/${checker}`).buildBin
// const tscCheckerFactory = require(`./presets/${checker}`).checkerFactory
return { serve: createWorker(userOptions), build: { buildBin } }
}

// @ts-ignore
return checker
}

export default function Plugin(userOptions?: Partial<PluginOptions>): Plugin {
const checker = makeChecker(userOptions?.checker || 'tsc', userOptions)
const {
serve: { worker, config: workerConfig, configureServer: workerConfigureServer },
build: { buildBin },
} = createServeAndBuild(userOptions?.checker || 'tsc', userOptions)
const enableBuild = userOptions?.enableBuild ?? true
let viteMode: ConfigEnv['command'] | undefined
let diagnostic: ReturnType<CreateDiagnostic> | null = null
// let diagnostic: ReturnType<CreateDiagnostic> | null = null

return {
name: 'ts-checker',
Expand All @@ -36,12 +43,16 @@ export default function Plugin(userOptions?: Partial<PluginOptions>): Plugin {
viteMode = env.command
if (viteMode !== 'serve') return

diagnostic = checker.createDiagnostic({
root: userOptions?.root,
tsconfigPath: userOptions?.tsconfigPath,
workerConfig({
hmr: config.server?.hmr,
env,
})
// diagnostic = checker.createDiagnostic({
// root: userOptions?.root,
// tsconfigPath: userOptions?.tsconfigPath,
// })

diagnostic.config(config, env)
// diagnostic.config(config, env)
},
buildStart: () => {
// for build mode
Expand All @@ -54,7 +65,7 @@ export default function Plugin(userOptions?: Partial<PluginOptions>): Plugin {
execPath: process.execPath,
})

const proc = spawn(checker.buildBin[0], checker.buildBin[1], {
const proc = spawn(buildBin[0], buildBin[1], {
cwd: process.cwd(),
stdio: 'inherit',
env: localEnv,
Expand All @@ -71,7 +82,11 @@ export default function Plugin(userOptions?: Partial<PluginOptions>): Plugin {
configureServer(server) {
// for dev mode (2/2)
// Get the server instance and keep reference in a closure
diagnostic!.configureServer(server)
workerConfigureServer({ root: server.config.root })
worker.on('message', (action: OverlayErrorAction) => {
server.ws.send(action.payload)
})

return () => {
server.middlewares.use((req, res, next) => {
next()
Expand Down
85 changes: 69 additions & 16 deletions src/presets/tsc.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,37 @@
import os from 'os'
import ts from 'typescript'
import { ErrorPayload } from 'vite'

import { CreateDiagnostic } from '../types'
import { isMainThread, parentPort, Worker, workerData } from 'worker_threads'

import {
ACTION_TYPES,
CheckWorker,
ConfigAction,
ConfigureServerAction,
CreateDiagnostic,
} from '../types'
import { ensureCall, formatHost, tsDiagnosticToViteError } from '../utils'

import type { CheckerFactory } from '../types'
import type { UserConfig, ViteDevServer } from 'vite'

import type { CheckerFactory, DiagnosticOfCheck, BuildCheckBin } from '../types'
/**
* Prints a diagnostic every time the watch status changes.
* This is mainly for messages like "Starting compilation" or "Compilation completed".
*/
export const createDiagnostic: CreateDiagnostic = (userOptions = {}) => {
const createDiagnostic: CreateDiagnostic = (userOptions = {}) => {
let overlay = true // Vite defaults to true
let currErr: ErrorPayload['err'] | null = null

return {
config: (config: UserConfig) => {
const hmr = config.server?.hmr
config: ({ hmr }) => {
const viteOverlay = !(typeof hmr === 'object' && hmr.overlay === false)

if (userOptions.overlay === false || !viteOverlay) {
overlay = false
}
},
configureServer(server: ViteDevServer) {
configureServer({ root }) {
const finalConfig = {
root: userOptions.root ?? server.config.root,
root: userOptions.root ?? root,
tsconfigPath: userOptions.tsconfigPath ?? 'tsconfig.json',
}

Expand Down Expand Up @@ -71,10 +75,17 @@ export const createDiagnostic: CreateDiagnostic = (userOptions = {}) => {
case 6193: // 1 Error
case 6194: // 0 errors or 2+ errors
if (currErr && overlay) {
server.ws.send({
type: 'error',
err: currErr,
parentPort?.postMessage({
type: 'ERROR',
payload: {
type: 'error',
err: currErr,
},
})
// server.ws.send({
// type: 'error',
// err: currErr,
// })
}

ensureCall(() => {
Expand All @@ -100,11 +111,53 @@ export const createDiagnostic: CreateDiagnostic = (userOptions = {}) => {
}
}

export const checkerFactory: CheckerFactory = () => {
const checkerFactory: CheckerFactory = () => {
return {
buildBin: ['tsc', ['--noEmit']],
createDiagnostic,
}
}

export type TscCheckerOptions = Record<string, never>
export const buildBin: BuildCheckBin = ['tsc', ['--noEmit']]

if (isMainThread) {
// initialized in main thread
const createWorker = (userConfigs?: Record<string, never>): CheckWorker => {
const worker = new Worker(__filename, {
workerData: userConfigs,
})

// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
return {
worker,
config: (config) => {
const configAction: ConfigAction = { type: ACTION_TYPES.config, payload: config }
worker.postMessage(configAction)
},
configureServer: (serverConfig) => {
const configureServerAction: ConfigureServerAction = {
type: ACTION_TYPES.configureServer,
payload: serverConfig,
}
worker.postMessage(configureServerAction)
},
}
}

module.exports.createWorker = createWorker
} else {
// runs in worker thread
let diagnostic: DiagnosticOfCheck | null = null
if (!parentPort) throw Error('should have parentPort as file runs in worker thread')

parentPort.on('message', (action: ConfigAction | ConfigureServerAction) => {
if (action.type === ACTION_TYPES.config) {
const checker = checkerFactory()
const userConfigs = workerData
diagnostic = checker.createDiagnostic(userConfigs)
diagnostic.config(action.payload)
} else if (action.type === ACTION_TYPES.configureServer) {
if (!diagnostic) throw Error('diagnostic should be initialized in `config` hook of Vite')
diagnostic.configureServer(action.payload)
}
})
}
1 change: 1 addition & 0 deletions src/presets/vue-tsc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import type { UserConfig, ViteDevServer } from 'vite'
* Prints a diagnostic every time the watch status changes.
* This is mainly for messages like "Starting compilation" or "Compilation completed".
*/
// @ts-ignore
export const createDiagnostic: CreateDiagnostic = (userOptions = {}) => {
return {
config: (config: UserConfig) => {
Expand Down
55 changes: 50 additions & 5 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,60 @@
import type { Plugin } from 'vite'
import type { HMRPayload, ServerOptions, ConfigEnv } from 'vite'
import type { Worker } from 'worker_threads'

export type CheckerFactory = (options?: unknown) => Checker

export type BuildCheckBin = [string, ReadonlyArray<string>]

export interface Checker {
buildBin: [string, ReadonlyArray<string>]
createDiagnostic: CreateDiagnostic
}

export type CreateDiagnostic = (
config?: Partial<PluginOptions>
) => Required<Pick<Plugin, 'config' | 'configureServer'>>
export interface DiagnosticOfCheck {
config: (options: Pick<ServerOptions, 'hmr'> & { env: ConfigEnv }) => unknown
configureServer: (options: { root: string }) => unknown
}

export type CreateDiagnostic = (config?: Partial<PluginOptions>) => DiagnosticOfCheck

export interface CheckWorker {
worker: Worker
config: (config: ConfigAction['payload']) => void
configureServer: (serverConfig: ConfigureServerAction['payload']) => void
}

/* ----------------------------- worker actions ----------------------------- */

export enum ACTION_TYPES {
overlayError = 'overlayError',
config = 'config',
configureServer = 'configureServer',
}

interface Action {
type: string
payload: unknown
}

export interface OverlayErrorAction extends Action {
type: ACTION_TYPES.overlayError
payload: HMRPayload
}

export interface ConfigAction extends Action {
type: ACTION_TYPES.config
payload: Pick<ServerOptions, 'hmr'> & { env: ConfigEnv }
}

export interface ConfigureServerAction extends Action {
type: ACTION_TYPES.configureServer
payload: {
root: string
}
}

export type Actions = OverlayErrorAction | ConfigAction | ConfigureServerAction

/* ----------------------------- worker actions ----------------------------- */

export interface PluginOptions {
/**
Expand Down

0 comments on commit c37097f

Please sign in to comment.