Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow this.addWatchFile in all hooks #5270

Merged
merged 5 commits into from Nov 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/plugin-development/index.md
Expand Up @@ -1182,7 +1182,7 @@ A number of utility functions and informational bits can be accessed from within
| ----: | :--------------------- |
| Type: | `(id: string) => void` |

Adds additional files to be monitored in watch mode so that changes to these files will trigger rebuilds. `id` can be an absolute path to a file or directory or a path relative to the current working directory. This context function can only be used in hooks during the build phase, i.e. in `buildStart`, `load`, `resolveId`, and `transform`.
Adds additional files to be monitored in watch mode so that changes to these files will trigger rebuilds. `id` can be an absolute path to a file or directory or a path relative to the current working directory. This context function can be used in all plugin hooks except `closeBundle`. However, it will not have an effect when used in [output generation hooks](#output-generation-hooks) if [`watch.skipWrite`](../configuration-options/index.md#watch-skipwrite) is set to `true`.

**Note:** Usually in watch mode to improve rebuild speed, the `transform` hook will only be triggered for a given module if its contents actually changed. Using `this.addWatchFile` from within the `transform` hook will make sure the `transform` hook is also reevaluated for this module if the watched file changes.

Expand Down
4 changes: 3 additions & 1 deletion src/rollup/rollup.ts
Expand Up @@ -100,7 +100,9 @@ export async function rollupInternal(

return handleGenerateWrite(false, inputOptions, unsetInputOptions, rawOutputOptions, graph);
},
watchFiles: Object.keys(graph.watchFiles),
get watchFiles() {
return Object.keys(graph.watchFiles);
},
async write(rawOutputOptions: OutputOptions) {
if (result.closed) return error(logAlreadyClosed());

Expand Down
6 changes: 1 addition & 5 deletions src/utils/PluginContext.ts
Expand Up @@ -10,10 +10,9 @@ import type {
import type { FileEmitter } from './FileEmitter';
import { createPluginCache, getCacheForUncacheablePlugin, NO_CACHE } from './PluginCache';
import { BLANK, EMPTY_OBJECT } from './blank';
import { BuildPhase } from './buildPhase';
import { getLogHandler } from './logHandler';
import { LOGLEVEL_DEBUG, LOGLEVEL_INFO, LOGLEVEL_WARN } from './logging';
import { error, logInvalidRollupPhaseForAddWatchFile, logPluginError } from './logs';
import { error, logPluginError } from './logs';
import { normalizeLog } from './options/options';
import { parseAst } from './parseAst';
import { ANONYMOUS_OUTPUT_PLUGIN_PREFIX, ANONYMOUS_PLUGIN_PREFIX } from './pluginUtils';
Expand Down Expand Up @@ -54,9 +53,6 @@ export function getPluginContext(

return {
addWatchFile(id) {
if (graph.phase >= BuildPhase.GENERATE) {
return this.error(logInvalidRollupPhaseForAddWatchFile());
}
graph.watchFiles[id] = true;
},
cache: cacheInstance,
Expand Down
7 changes: 0 additions & 7 deletions src/utils/logs.ts
Expand Up @@ -588,13 +588,6 @@ export function logInvalidFunctionPluginHook(hook: string, plugin: string): Roll
};
}

export function logInvalidRollupPhaseForAddWatchFile(): RollupLog {
return {
code: INVALID_ROLLUP_PHASE,
message: `Cannot call "addWatchFile" after the build has finished.`
};
}

export function logInvalidRollupPhaseForChunkEmission(): RollupLog {
return {
code: INVALID_ROLLUP_PHASE,
Expand Down
8 changes: 7 additions & 1 deletion src/watch/watch.ts
Expand Up @@ -207,7 +207,13 @@ export class Task {
return;
}
this.updateWatchedFiles(result);
this.skipWrite || (await Promise.all(this.outputs.map(output => result!.write(output))));
if (!this.skipWrite) {
await Promise.all(this.outputs.map(output => result!.write(output)));
if (this.closed) {
return;
}
this.updateWatchedFiles(result!);
}
await this.watcher.emitter.emit('event', {
code: 'BUNDLE_END',
duration: Date.now() - start,
Expand Down
22 changes: 0 additions & 22 deletions test/function/samples/add-watch-file-generate/_config.js

This file was deleted.

1 change: 0 additions & 1 deletion test/function/samples/add-watch-file-generate/main.js

This file was deleted.

Empty file.
23 changes: 23 additions & 0 deletions test/utils.js
Expand Up @@ -38,6 +38,29 @@ exports.wait = function wait(ms) {
});
};

/**
* @param {Promise<unknown>} promise
* @param {number} timeoutMs
* @param {() => unknown} onTimeout
* @return {Promise<unknown>}
*/
exports.withTimeout = function withTimeout(promise, timeoutMs, onTimeout) {
let timeoutId;
return Promise.race([
promise.then(() => clearTimeout(timeoutId)),
new Promise((resolve, reject) => {
timeoutId = setTimeout(() => {
try {
onTimeout();
resolve();
} catch (error) {
reject(error);
}
}, timeoutMs);
})
]);
};

function normaliseError(error) {
if (!error) {
throw new Error(`Expected an error but got ${JSON.stringify(error)}`);
Expand Down
50 changes: 41 additions & 9 deletions test/watch/index.js
Expand Up @@ -8,7 +8,7 @@ const { copy } = require('fs-extra');
* @type {import('../../src/rollup/types')} Rollup
*/
const rollup = require('../../dist/rollup');
const { atomicWriteFileSync, wait } = require('../utils');
const { atomicWriteFileSync, wait, withTimeout } = require('../utils');

describe('rollup.watch', () => {
let watcher;
Expand Down Expand Up @@ -612,7 +612,7 @@ describe('rollup.watch', () => {
input: 'test/_tmp/input/main.js',
plugins: {
async watchChange() {
await new Promise(resolve => setTimeout(resolve, 300));
await wait(300);
if (fail) {
this.error('Failed in watchChange');
}
Expand Down Expand Up @@ -1385,11 +1385,28 @@ describe('rollup.watch', () => {
describe('addWatchFile', () => {
it('supports adding additional watch files in plugin hooks', async () => {
const watchChangeIds = new Set();
const buildEndFile = resolve('test/_tmp/input/buildEnd');
const buildStartFile = resolve('test/_tmp/input/buildStart');
const generateBundleFile = resolve('test/_tmp/input/generateBUndle');
const loadFile = resolve('test/_tmp/input/load');
const moduleParsedFile = resolve('test/_tmp/input/moduleParsed');
const renderChunkFile = resolve('test/_tmp/input/renderChunk');
const renderStartFile = resolve('test/_tmp/input/renderStart');
const resolveIdFile = resolve('test/_tmp/input/resolveId');
const transformFile = resolve('test/_tmp/input/transform');
const watchFiles = [buildStartFile, loadFile, resolveIdFile, transformFile];
const writeBundleFile = resolve('test/_tmp/input/writeBundle');
const watchFiles = [
buildEndFile,
buildStartFile,
generateBundleFile,
loadFile,
moduleParsedFile,
renderChunkFile,
renderStartFile,
resolveIdFile,
transformFile,
writeBundleFile
];
await copy('test/watch/samples/basic', 'test/_tmp/input');

await Promise.all(watchFiles.map(file => writeFile(file, 'initial')));
Expand All @@ -1402,18 +1419,36 @@ describe('rollup.watch', () => {
exports: 'auto'
},
plugins: {
buildEnd() {
this.addWatchFile(buildEndFile);
},
buildStart() {
this.addWatchFile(buildStartFile);
},
generateBundle() {
this.addWatchFile(generateBundleFile);
},
load() {
this.addWatchFile(loadFile);
},
moduleParsed() {
this.addWatchFile(moduleParsedFile);
},
renderChunk() {
this.addWatchFile(renderChunkFile);
},
renderStart() {
this.addWatchFile(renderStartFile);
},
resolveId() {
this.addWatchFile(resolveIdFile);
},
transform() {
this.addWatchFile(transformFile);
},
writeBundle() {
this.addWatchFile(writeBundleFile);
},
watchChange(id) {
watchChangeIds.add(id);
}
Expand Down Expand Up @@ -1795,12 +1830,9 @@ function sequence(watcher, events, timeout = 300) {
go();
});

return Promise.race([
sequencePromise.then(() => wait(100)),
wait(20_000).then(() => {
throw new Error(`Test timed out\n${handledEvents.join('\n')}`);
})
]);
return withTimeout(sequencePromise, 20_000, () => {
throw new Error(`Test timed out\n${handledEvents.join('\n')}`);
}).then(() => wait(100));
}

function getTimeDiffInMs(previous) {
Expand Down