Skip to content

Commit

Permalink
Configurable Build Delay (Currently Hardcoded at 200ms) (#3502)
Browse files Browse the repository at this point in the history
* add ability to configure build delay

* Change logic to take time diff from configs and add tests

* Add documentation

Co-authored-by: Lukas Taegert-Atkinson <lukas.taegert-atkinson@tngtech.com>
Co-authored-by: Lukas Taegert-Atkinson <lukastaegert@users.noreply.github.com>
  • Loading branch information
3 people committed May 10, 2020
1 parent f63e54d commit 7e5c0d2
Show file tree
Hide file tree
Showing 6 changed files with 144 additions and 66 deletions.
1 change: 1 addition & 0 deletions docs/01-command-line-reference.md
Expand Up @@ -97,6 +97,7 @@ export default { // can be an array (for multiple inputs)
},

watch: {
buildDelay,
chokidar,
clearScreen,
skipWrite,
Expand Down
1 change: 1 addition & 0 deletions docs/02-javascript-api.md
Expand Up @@ -184,6 +184,7 @@ const watchOptions = {
...inputOptions,
output: [outputOptions],
watch: {
buildDelay,
chokidar,
clearScreen,
skipWrite,
Expand Down
14 changes: 8 additions & 6 deletions docs/999-big-list-of-options.md
Expand Up @@ -1274,28 +1274,31 @@ For each key, the first number represents the elapsed time while the second repr

These options only take effect when running Rollup with the `--watch` flag, or using `rollup.watch`.

#### watch.buildDelay
Type: `number`<br>
Default: `0`

Configures how long Rollup will wait for further changes until it triggers a rebuild in milliseconds. By default, Rollup does not wait but there is a small debounce timeout configured in the chokidar instance. Setting this to a value greater than `0` will mean that Rollup will only triger a rebuild if there was no change for the configured number of milliseconds. If several configurations are watched, Rollup will use the largest configured build delay.

#### watch.chokidar
Type: `ChokidarOptions`<br>

An optional object of watch options that will be passed to the bundled [chokidar](https://github.com/paulmillr/chokidar) instance. See the [chokidar documentation](https://github.com/paulmillr/chokidar#api) to find out what options are available.

#### watch.clearScreen
Type: `boolean`<br>
CLI: `--watch.clearScreen`/`--no-watch.clearScreen`<br>
Default: `true`

Whether to clear the screen when a rebuild is triggered.

#### watch.skipWrite
Type: `boolean`<br>
<!-- CLI: `--watch.skipWrite`<br> -->
Default: `false`

Whether to skip the `bundle.write()` step when a rebuild is triggered.

#### watch.exclude
Type: `string`<br>
CLI: `--watch.exclude <excludedPattern>`
Type: `string`

Prevent files from being watched:

Expand All @@ -1310,8 +1313,7 @@ export default {
```

#### watch.include
Type: `string`<br>
CLI: `--watch.include <includedPattern>`
Type: `string`

Limit the file-watching to certain files:

Expand Down
1 change: 1 addition & 0 deletions src/rollup/types.d.ts
Expand Up @@ -634,6 +634,7 @@ export interface ChokidarOptions {
}

export interface WatcherOptions {
buildDelay?: number;
chokidar?: ChokidarOptions;
clearScreen?: boolean;
exclude?: string[];
Expand Down
39 changes: 23 additions & 16 deletions src/watch/watch.ts
Expand Up @@ -7,17 +7,16 @@ import {
RollupBuild,
RollupCache,
RollupWatcher,
WatcherOptions,
WatcherOptions
} from '../rollup/types';
import { mergeOptions } from '../utils/mergeOptions';
import { ensureArray, GenericConfigObject } from '../utils/parseOptions';
import { FileWatcher } from './fileWatcher';

const DELAY = 200;

export class Watcher {
emitter: RollupWatcher;

private buildDelay = 0;
private buildTimeout: NodeJS.Timer | null = null;
private invalidatedIds: Set<string> = new Set();
private rerun = false;
Expand All @@ -27,7 +26,15 @@ export class Watcher {
constructor(configs: GenericConfigObject[] | GenericConfigObject, emitter: RollupWatcher) {
this.emitter = emitter;
emitter.close = this.close.bind(this);
this.tasks = ensureArray(configs).map((config) => new Task(this, config));
const configArray = ensureArray(configs);
this.tasks = configArray.map(config => new Task(this, config));
this.buildDelay = configArray.reduce(
(buildDelay, { watch }: any) =>
watch && typeof watch.buildDelay === 'number'
? Math.max(buildDelay, (watch as WatcherOptions).buildDelay!)
: buildDelay,
this.buildDelay
);
this.running = true;
process.nextTick(() => this.run());
}
Expand Down Expand Up @@ -63,14 +70,14 @@ export class Watcher {
this.invalidatedIds.clear();
this.emit('restart');
this.run();
}, DELAY);
}, this.buildDelay);
}

private async run() {
this.running = true;

this.emit('event', {
code: 'START',
code: 'START'
});

try {
Expand All @@ -79,13 +86,13 @@ export class Watcher {
}
this.running = false;
this.emit('event', {
code: 'END',
code: 'END'
});
} catch (error) {
this.running = false;
this.emit('event', {
code: 'ERROR',
error,
error
});
}

Expand Down Expand Up @@ -119,7 +126,7 @@ export class Task {
this.skipWrite = config.watch && !!(config.watch as GenericConfigObject).skipWrite;
this.options = mergeOptions(config);
this.outputs = this.options.output;
this.outputFiles = this.outputs.map((output) => {
this.outputFiles = this.outputs.map(output => {
if (output.file || output.dir) return path.resolve(output.file || output.dir!);
return undefined as any;
});
Expand All @@ -129,7 +136,7 @@ export class Task {
this.fileWatcher = new FileWatcher(this, {
...watchOptions.chokidar,
disableGlobbing: true,
ignoreInitial: true,
ignoreInitial: true
});
}

Expand All @@ -156,15 +163,15 @@ export class Task {

const options = {
...this.options,
cache: this.cache,
cache: this.cache
};

const start = Date.now();

this.watcher.emit('event', {
code: 'BUNDLE_START',
input: this.options.input,
output: this.outputFiles,
output: this.outputFiles
});

try {
Expand All @@ -173,13 +180,13 @@ export class Task {
return;
}
this.updateWatchedFiles(result);
this.skipWrite || (await Promise.all(this.outputs.map((output) => result.write(output))));
this.skipWrite || (await Promise.all(this.outputs.map(output => result.write(output))));
this.watcher.emit('event', {
code: 'BUNDLE_END',
duration: Date.now() - start,
input: this.options.input,
output: this.outputFiles,
result,
result
});
} catch (error) {
if (this.closed) {
Expand All @@ -192,7 +199,7 @@ export class Task {
}
}
if (error.id) {
this.cache.modules = this.cache.modules.filter((module) => module.id !== error.id);
this.cache.modules = this.cache.modules.filter(module => module.id !== error.id);
}
throw error;
}
Expand Down Expand Up @@ -222,7 +229,7 @@ export class Task {
if (!this.filter(id)) return;
this.watched.add(id);

if (this.outputFiles.some((file) => file === id)) {
if (this.outputFiles.some(file => file === id)) {
throw new Error('Cannot import the generated bundle');
}

Expand Down
154 changes: 110 additions & 44 deletions test/watch/index.js
Expand Up @@ -66,6 +66,11 @@ describe('rollup.watch', () => {
});
}

function getTimeDiffInMs(previous) {
const [seconds, nanoseconds] = process.hrtime(previous);
return seconds * 1e3 + nanoseconds / 1e6;
}

it('watches a file and triggers reruns if necessary', () => {
let triggerRestart = false;

Expand Down Expand Up @@ -491,46 +496,6 @@ describe('rollup.watch', () => {
});
});

it('recovers from an error even when erroring dependency was "renamed" (#38)', () => {
return sander
.copydir('test/watch/samples/dependency')
.to('test/_tmp/input')
.then(() => {
watcher = rollup.watch({
input: 'test/_tmp/input/main.js',
output: {
file: 'test/_tmp/output/bundle.js',
format: 'cjs'
}
});

return sequence(watcher, [
'START',
'BUNDLE_START',
'BUNDLE_END',
'END',
() => {
assert.strictEqual(run('../_tmp/output/bundle.js'), 43);
sander.unlinkSync('test/_tmp/input/dep.js');
sander.writeFileSync('test/_tmp/input/dep.js', 'export nope;');
},
'START',
'BUNDLE_START',
'ERROR',
() => {
sander.unlinkSync('test/_tmp/input/dep.js');
sander.writeFileSync('test/_tmp/input/dep.js', 'export const value = 43;');
},
'START',
'BUNDLE_START',
'BUNDLE_END',
'END',
() => {
assert.strictEqual(run('../_tmp/output/bundle.js'), 44);
}
]);
});
});
it('handles closing the watcher during a build', () => {
return sander
.copydir('test/watch/samples/basic')
Expand Down Expand Up @@ -1074,6 +1039,110 @@ describe('rollup.watch', () => {
});
});

it('rebuilds immediately by default', async () => {
await sander.copydir('test/watch/samples/basic').to('test/_tmp/input');
watcher = rollup.watch({
input: 'test/_tmp/input/main.js',
output: {
file: 'test/_tmp/output/bundle.js',
format: 'cjs'
}
});

let startTime;
return sequence(watcher, [
'START',
'BUNDLE_START',
'BUNDLE_END',
'END',
() => {
assert.strictEqual(run('../_tmp/output/bundle.js'), 42);
sander.writeFileSync('test/_tmp/input/main.js', 'export default 43;');
startTime = process.hrtime();
},
'START',
'BUNDLE_START',
'BUNDLE_END',
'END',
() => {
assert.strictEqual(run('../_tmp/output/bundle.js'), 43);
const timeDiff = getTimeDiffInMs(startTime);
assert.ok(timeDiff < 400, `Time difference ${timeDiff} < 400`);
}
]);
});

it('observes configured build delays', async () => {
await sander.copydir('test/watch/samples/basic').to('test/_tmp/input');
watcher = rollup.watch([
{
input: 'test/_tmp/input/main.js',
output: {
file: 'test/_tmp/output/bundle.js',
format: 'cjs'
}
},
{
input: 'test/_tmp/input/main.js',
watch: { clearScreen: true },
output: {
file: 'test/_tmp/output/bundle.js',
format: 'cjs'
}
},
{
input: 'test/_tmp/input/main.js',
watch: { buildDelay: 1000 },
output: {
file: 'test/_tmp/output/bundle.js',
format: 'cjs'
}
},
{
input: 'test/_tmp/input/main.js',
watch: { buildDelay: 50 },
output: {
file: 'test/_tmp/output/bundle.js',
format: 'cjs'
}
}
]);

let startTime;
return sequence(watcher, [
'START',
'BUNDLE_START',
'BUNDLE_END',
'BUNDLE_START',
'BUNDLE_END',
'BUNDLE_START',
'BUNDLE_END',
'BUNDLE_START',
'BUNDLE_END',
'END',
() => {
assert.strictEqual(run('../_tmp/output/bundle.js'), 42);
sander.writeFileSync('test/_tmp/input/main.js', 'export default 43;');
startTime = process.hrtime();
},
'START',
'BUNDLE_START',
'BUNDLE_END',
'BUNDLE_START',
'BUNDLE_END',
'BUNDLE_START',
'BUNDLE_END',
'BUNDLE_START',
'BUNDLE_END',
'END',
() => {
assert.strictEqual(run('../_tmp/output/bundle.js'), 43);
const timeDiff = getTimeDiffInMs(startTime);
assert.ok(timeDiff > 1000, `Time difference ${timeDiff} > 1000`);
}
]);
});

describe('addWatchFile', () => {
it('supports adding additional watch files in plugin hooks', () => {
const watchChangeIds = [];
Expand Down Expand Up @@ -1256,10 +1325,7 @@ describe('rollup.watch', () => {
transform(code, id) {
if (id.endsWith('dep1.js')) {
this.addWatchFile(path.resolve('test/_tmp/input/dep2.js'));
const text = sander
.readFileSync('test/_tmp/input/dep2.js')
.toString()
.trim();
const text = sander.readFileSync('test/_tmp/input/dep2.js').toString().trim();
return `export default ${JSON.stringify(text)}`;
}
}
Expand Down

0 comments on commit 7e5c0d2

Please sign in to comment.