Skip to content

Commit

Permalink
feat: add batch for watcher change event (#662)
Browse files Browse the repository at this point in the history
  • Loading branch information
XGHeaven authored Oct 31, 2024
1 parent 0c52dd5 commit c2b0b0e
Show file tree
Hide file tree
Showing 6 changed files with 111 additions and 29 deletions.
5 changes: 5 additions & 0 deletions .changeset/sharp-starfishes-eat.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@ice/pkg': minor
---

feat: add batch for watcher change event
22 changes: 13 additions & 9 deletions packages/pkg/src/commands/start.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,14 @@ import consola from 'consola';
import { RollupOptions } from 'rollup';
import { getBuildTasks } from '../helpers/getBuildTasks.js';
import { getRollupOptions } from '../helpers/getRollupOptions.js';
import { createWatcher } from '../helpers/watcher.js';
import { createBatchChangeHandler, createWatcher } from '../helpers/watcher.js';
import { watchBundleTasks } from '../tasks/bundle.js';
import { watchTransformTasks } from '../tasks/transform.js';

import type {
OutputResult,
Context,
WatchEvent,
TaskRunnerContext,
TaskRunnerContext, WatchChangedFile,
} from '../types.js';

export default async function start(context: Context) {
Expand All @@ -34,9 +33,12 @@ export default async function start(context: Context) {
});

const watcher = createWatcher(taskConfigs);
watcher.on('add', async (id) => await handleChange(id, 'create'));
watcher.on('change', async (id) => await handleChange(id, 'update'));
watcher.on('unlink', async (id) => await handleChange(id, 'delete'));
const batchHandler = createBatchChangeHandler(runChangedCompile);
batchHandler.beginBlock();

watcher.on('add', (id) => batchHandler.onChange(id, 'create'));
watcher.on('change', (id) => batchHandler.onChange(id, 'update'));
watcher.on('unlink', (id) => batchHandler.onChange(id, 'delete'));
watcher.on('error', (error) => consola.error(error));

const transformOptions = buildTasks
Expand Down Expand Up @@ -83,14 +85,16 @@ export default async function start(context: Context) {

await applyHook('after.start.compile', outputResults);

async function handleChange(id: string, event: WatchEvent) {
batchHandler.endBlock();

async function runChangedCompile(changedFiles: WatchChangedFile[]) {
const newOutputResults = [];
try {
const newTransformOutputResults = transformWatchResult.handleChange ?
await transformWatchResult.handleChange(id, event) :
await transformWatchResult.handleChange(changedFiles) :
[];
const newBundleOutputResults = bundleWatchResult.handleChange ?
await bundleWatchResult.handleChange(id, event) :
await bundleWatchResult.handleChange(changedFiles) :
[];
newOutputResults.push(
...newTransformOutputResults,
Expand Down
65 changes: 64 additions & 1 deletion packages/pkg/src/helpers/watcher.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import * as chokidar from 'chokidar';
import { unique } from '../utils.js';
import type { TaskConfig } from '../types.js';
import type { TaskConfig, WatchChangedFile, WatchEvent } from '../types.js';

const WATCH_INTERVAL = 250;

type WatchCallback = (changedFiles: WatchChangedFile[]) => (Promise<void>);

export const createWatcher = (taskConfigs: TaskConfig[]) => {
const outputs = unique(taskConfigs.map((taskConfig) => taskConfig.outputDir));
Expand All @@ -17,3 +21,62 @@ export const createWatcher = (taskConfigs: TaskConfig[]) => {

return watcher;
};

export function createBatchChangeHandler(changeCallback: WatchCallback) {
let nextChangedFiles: WatchChangedFile[] = [];
let runningTask: Promise<void> | null = null;
let enableBatch = false;
let timer: any = 0;

async function onChange(id: string, event: WatchEvent) {
nextChangedFiles.push({ path: id, event });
if (enableBatch) {
return;
}
tryRunTask();
}

function tryRunTask() {
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(() => {
runTask();
timer = null;
}, WATCH_INTERVAL);
}

function runTask() {
if (!nextChangedFiles.length) {
return;
}

if (!runningTask) {
const changedFiles = nextChangedFiles;
nextChangedFiles = [];
const task = changeCallback(changedFiles);
runningTask = task.finally(() => {
runningTask = null;
tryRunTask();
});
}
}


return {
/**
* Block and cache file changes event, do not trigger change handler
*/
beginBlock() {
enableBatch = true;
},
/**
* Trigger change handler since beginBlock
*/
endBlock() {
enableBatch = false;
tryRunTask();
},
onChange,
};
}
16 changes: 9 additions & 7 deletions packages/pkg/src/tasks/bundle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,10 @@ export const watchBundleTasks: RunTasks = async (taskOptionsList, context, watch
handleChangeFunctions.push(handleChange);
}

const handleChange: HandleChange<OutputResult[]> = async (id, event) => {
const handleChange: HandleChange<OutputResult[]> = async (changedFiles) => {
const newOutputResults: OutputResult[] = [];
for (const handleChangeFunction of handleChangeFunctions) {
const newOutputResult = await handleChangeFunction(id, event);
const newOutputResult = await handleChangeFunction(changedFiles);
newOutputResults.push(newOutputResult);
}

Expand Down Expand Up @@ -215,16 +215,18 @@ async function rawWatch(
}
};

const handleChange: HandleChange = async (id: string, event: string) => {
const handleChange: HandleChange = async (changedFiles) => {
const changeStart = performance.now();

logger.debug('Bundle start...');

for (const task of watcher.tasks) {
task.invalidate(id, {
event,
isTransformDependency: false,
});
for (const file of changedFiles) {
task.invalidate(file.path, {
event: file.event,
isTransformDependency: false,
});
}
}

const outputResult = await getOutputResult();
Expand Down
24 changes: 13 additions & 11 deletions packages/pkg/src/tasks/transform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,17 +40,17 @@ export const watchTransformTasks: RunTasks = async (taskOptionsList, context, wa
}
outputResults.push(outputResult);

handleChangeFunctions.push(async (id, event) => {
if (event === 'update' || event === 'create') {
return await runTransform(rollupOptions, taskRunnerContext, context, id);
handleChangeFunctions.push(async (changedFiles) => {
if (changedFiles.some((file) => file.event === 'update' || file.event === 'create')) {
return await runTransform(rollupOptions, taskRunnerContext, context, changedFiles.map((file) => file.path));
}
});
}

const handleChange: HandleChange<OutputResult[]> = async (id, event) => {
const handleChange: HandleChange<OutputResult[]> = async (changedFiles) => {
const newOutputResults: OutputResult[] = [];
for (const handleChangeFunction of handleChangeFunctions) {
const newOutputResult = await handleChangeFunction(id, event);
const newOutputResult = await handleChangeFunction(changedFiles);
newOutputResults.push(newOutputResult);
}

Expand Down Expand Up @@ -81,7 +81,7 @@ async function runTransform(
rollupOptions: RollupOptions,
taskRunnerContext: TaskRunnerContext,
context: Context,
updatedFile?: string,
updatedFiles?: string[],
): Promise<OutputResult> {
let isDistContainingSWCHelpers = false;
let isDistContainingJSXRuntime = false;
Expand All @@ -100,11 +100,13 @@ async function runTransform(

const files: OutputFile[] = [];

if (updatedFile) {
for (const entryDir of entryDirs) {
if (updatedFile.startsWith(entryDir)) {
files.push(getFileInfo(updatedFile, entryDir));
break;
if (updatedFiles) {
for (const updatedFile of updatedFiles) {
for (const entryDir of entryDirs) {
if (updatedFile.startsWith(entryDir)) {
files.push(getFileInfo(updatedFile, entryDir));
break;
}
}
}
} else {
Expand Down
8 changes: 7 additions & 1 deletion packages/pkg/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,13 @@ export interface OutputResult {
export type NodeEnvMode = 'development' | 'production' | string;

export type WatchEvent = 'create' | 'update' | 'delete';
export type HandleChange<R = OutputResult> = (id: string, event: WatchEvent) => Promise<R>;

export interface WatchChangedFile {
path: string;
event: WatchEvent;
}

export type HandleChange<R = OutputResult> = (changedFiles: WatchChangedFile[]) => Promise<R>;

export interface TaskResult {
handleChange?: HandleChange<OutputResult[]>;
Expand Down

0 comments on commit c2b0b0e

Please sign in to comment.