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

Watch files onbuild, even if build fails #3081

Merged
merged 20 commits into from Sep 8, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
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
3 changes: 1 addition & 2 deletions src/Graph.ts
Expand Up @@ -222,7 +222,6 @@ export default class Graph {
for (const module of this.moduleById.values()) {
if (module instanceof Module) {
this.modules.push(module);
this.watchFiles[module.id] = true;
mhkeller marked this conversation as resolved.
Show resolved Hide resolved
} else {
this.externalModules.push(module);
}
Expand Down Expand Up @@ -331,7 +330,7 @@ export default class Graph {
return {
modules: this.modules.map(module => module.toJSON()),
plugins: this.pluginCache
} as any;
};
}

includeMarked(modules: Module[]) {
Expand Down
5 changes: 3 additions & 2 deletions src/Module.ts
Expand Up @@ -218,7 +218,7 @@ export default class Module {
private graph: Graph;
private magicString!: MagicString;
private namespaceVariable: NamespaceVariable = undefined as any;
private transformDependencies: string[] | null = null;
private transformDependencies: string[] = [];
private transitiveReexports?: string[];

constructor(graph: Graph, id: string, moduleSideEffects: boolean, isEntry: boolean) {
Expand All @@ -244,7 +244,6 @@ export default class Module {
error(props: RollupError, pos: number) {
if (pos !== undefined) {
props.pos = pos;

let location = locate(this.code, pos, { offsetLine: 1 });
try {
location = getOriginalLocation(this.sourcemapChain, location);
Expand Down Expand Up @@ -272,6 +271,8 @@ export default class Module {
props.frame = getCodeFrame(this.originalCode, location.line, location.column);
}

props.watchFiles = Object.keys(this.graph.watchFiles);

mhkeller marked this conversation as resolved.
Show resolved Hide resolved
error(props);
}

Expand Down
1 change: 1 addition & 0 deletions src/ModuleLoader.ts
Expand Up @@ -280,6 +280,7 @@ export class ModuleLoader {

const module: Module = new Module(this.graph, id, moduleSideEffects, isEntry);
this.modulesById.set(id, module);
this.graph.watchFiles[id] = true;
mhkeller marked this conversation as resolved.
Show resolved Hide resolved
const manualChunkAlias = this.getManualChunk(id);
if (typeof manualChunkAlias === 'string') {
this.addModuleToManualChunk(manualChunkAlias, module);
Expand Down
5 changes: 3 additions & 2 deletions src/rollup/types.d.ts
Expand Up @@ -5,6 +5,7 @@ export const VERSION: string;

export interface RollupError extends RollupLogProps {
stack?: string;
watchFiles?: string[];
}

export interface RollupWarning extends RollupLogProps {
Expand Down Expand Up @@ -105,7 +106,7 @@ export interface TransformModuleJSON {
originalSourcemap: ExistingDecodedSourceMap | null;
resolvedIds?: ResolvedIdMap;
sourcemapChain: DecodedSourceMapOrMissing[];
transformDependencies: string[] | null;
transformDependencies: string[];
}

export interface ModuleJSON extends TransformModuleJSON {
Expand Down Expand Up @@ -527,7 +528,7 @@ export interface SerializablePluginCache {
}

export interface RollupCache {
modules?: ModuleJSON[];
modules: ModuleJSON[];
plugins?: Record<string, SerializablePluginCache>;
}

Expand Down
6 changes: 3 additions & 3 deletions src/watch/fileWatchers.ts
Expand Up @@ -53,16 +53,16 @@ export default class FileWatcher {
// can't watch files that don't exist (e.g. injected
// by plugins somehow)
return;
} else {
throw err;
}
throw err;
}

const handleWatchEvent = (event: string) => {
if (event === 'rename' || event === 'unlink') {
this.close();
group.delete(id);
this.trigger(id);
return;
mhkeller marked this conversation as resolved.
Show resolved Hide resolved
} else {
let stats: fs.Stats;
try {
Expand All @@ -87,7 +87,7 @@ export default class FileWatcher {
group.set(id, this);
}

addTask(task: Task, isTransformDependency = false) {
addTask(task: Task, isTransformDependency: boolean) {
if (isTransformDependency) this.transformDependencyTasks.add(task);
else this.tasks.add(task);
}
Expand Down
90 changes: 38 additions & 52 deletions src/watch/index.ts
Expand Up @@ -5,10 +5,10 @@ import createFilter from 'rollup-pluginutils/src/createFilter';
import rollup, { setWatcher } from '../rollup/index';
import {
InputOptions,
ModuleJSON,
OutputOptions,
RollupBuild,
RollupCache,
RollupError,
RollupWatcher,
WatcherOptions
} from '../rollup/types';
Expand All @@ -25,7 +25,6 @@ export class Watcher {
private invalidatedIds: Set<string> = new Set();
private rerun = false;
private running: boolean;
private succeeded = false;
private tasks: Task[];

constructor(configs: GenericConfigObject[] | GenericConfigObject) {
Expand All @@ -48,9 +47,9 @@ export class Watcher {

close() {
if (this.buildTimeout) clearTimeout(this.buildTimeout);
this.tasks.forEach(task => {
for (const task of this.tasks) {
mhkeller marked this conversation as resolved.
Show resolved Hide resolved
task.close();
});
}

this.emitter.removeAllListeners();
}
Expand All @@ -72,7 +71,9 @@ export class Watcher {

this.buildTimeout = setTimeout(() => {
this.buildTimeout = null;
this.invalidatedIds.forEach(id => this.emit('change', id));
for (const id of this.invalidatedIds) {
this.emit('change', id);
}
this.invalidatedIds.clear();
this.emit('restart');
this.run();
Expand All @@ -90,7 +91,6 @@ export class Watcher {
for (const task of this.tasks) taskPromise = taskPromise.then(() => task.run());
return taskPromise
.then(() => {
this.succeeded = true;
this.running = false;

this.emit('event', {
Expand All @@ -100,7 +100,7 @@ export class Watcher {
.catch(error => {
this.running = false;
this.emit('event', {
code: this.succeeded ? 'ERROR' : 'FATAL',
code: 'ERROR',
error
});
})
Expand All @@ -114,7 +114,7 @@ export class Watcher {
}

export class Task {
cache: RollupCache;
cache: RollupCache = { modules: [] };
watchFiles: string[] = [];

private chokidarOptions: WatchOptions;
Expand All @@ -129,7 +129,6 @@ export class Task {
private watcher: Watcher;

constructor(watcher: Watcher, config: GenericConfigObject) {
this.cache = null as any;
this.watcher = watcher;

this.closed = false;
Expand Down Expand Up @@ -172,20 +171,19 @@ export class Task {

close() {
this.closed = true;
this.watched.forEach(id => {
for (const id of this.watched) {
deleteTask(id, this, this.chokidarOptionsHash);
});
}
}

invalidate(id: string, isTransformDependency: boolean) {
this.invalidated = true;
if (isTransformDependency) {
(this.cache.modules as ModuleJSON[]).forEach(module => {
if (!module.transformDependencies || module.transformDependencies.indexOf(id) === -1)
return;
for (const module of this.cache.modules) {
if (module.transformDependencies.indexOf(id) === -1) return;
// effective invalidation
module.originalCode = null as any;
});
}
}
this.watcher.invalidate(id);
}
Expand All @@ -211,27 +209,7 @@ export class Task {
return rollup(options)
.then(result => {
if (this.closed) return undefined as any;
const previouslyWatched = this.watched;
const watched = (this.watched = new Set());

this.cache = result.cache;
this.watchFiles = result.watchFiles;
for (const module of this.cache.modules as ModuleJSON[]) {
if (module.transformDependencies) {
module.transformDependencies.forEach(depId => {
watched.add(depId);
this.watchFile(depId, true);
});
}
}
for (const id of this.watchFiles) {
watched.add(id);
this.watchFile(id);
}
for (const id of previouslyWatched) {
if (!watched.has(id)) deleteTask(id, this, this.chokidarOptionsHash);
}

this.updateWatchedFiles(result);
return Promise.all(this.outputs.map(output => result.write(output))).then(() => result);
})
.then((result: RollupBuild) => {
Expand All @@ -243,31 +221,39 @@ export class Task {
result
});
})
.catch((error: Error) => {
.catch((error: RollupError) => {
if (this.closed) return;

if (this.cache) {
// this is necessary to ensure that any 'renamed' files
// continue to be watched following an error
if (this.cache.modules) {
this.cache.modules.forEach(module => {
if (module.transformDependencies) {
module.transformDependencies.forEach(depId => {
this.watchFile(depId, true);
});
}
});
}
this.watchFiles.forEach(id => {
if (Array.isArray(error.watchFiles)) {
for (const id of error.watchFiles) {
lukastaegert marked this conversation as resolved.
Show resolved Hide resolved
this.watchFile(id);
});
}
}
throw error;
});
}

watchFile(id: string, isTransformDependency = false) {
private updateWatchedFiles(result: RollupBuild) {
mhkeller marked this conversation as resolved.
Show resolved Hide resolved
const previouslyWatched = this.watched;
this.watched = new Set();
this.watchFiles = result.watchFiles;
this.cache = result.cache;
for (const id of this.watchFiles) {
this.watchFile(id);
}
for (const module of this.cache.modules) {
for (const depId of module.transformDependencies) {
this.watchFile(depId, true);
}
}
for (const id of previouslyWatched) {
if (!this.watched.has(id)) deleteTask(id, this, this.chokidarOptionsHash);
}
}

private watchFile(id: string, isTransformDependency = false) {
if (!this.filter(id)) return;
this.watched.add(id);
mhkeller marked this conversation as resolved.
Show resolved Hide resolved

if (this.outputFiles.some(file => file === id)) {
throw new Error('Cannot import the generated bundle');
Expand Down
Expand Up @@ -6,6 +6,7 @@ module.exports = {
code: 'CANNOT_CALL_NAMESPACE',
message: `Cannot call a namespace ('foo')`,
pos: 28,
watchFiles: [path.resolve(__dirname, 'main.js')],
loc: {
file: path.resolve(__dirname, 'main.js'),
line: 2,
Expand Down
Expand Up @@ -6,6 +6,7 @@ module.exports = {
code: 'CANNOT_CALL_NAMESPACE',
message: `Cannot call a namespace ('foo')`,
pos: 33,
watchFiles: [path.resolve(__dirname, 'main.js'), path.resolve(__dirname, 'foo.js')],
loc: {
file: path.resolve(__dirname, 'main.js'),
line: 2,
Expand Down
5 changes: 5 additions & 0 deletions test/function/samples/default-not-reexported/_config.js
Expand Up @@ -6,6 +6,11 @@ module.exports = {
code: 'MISSING_EXPORT',
message: `'default' is not exported by foo.js`,
pos: 7,
watchFiles: [
path.resolve(__dirname, 'main.js'),
path.resolve(__dirname, 'foo.js'),
path.resolve(__dirname, 'bar.js')
],
loc: {
file: path.resolve(__dirname, 'main.js'),
line: 1,
Expand Down
1 change: 1 addition & 0 deletions test/function/samples/double-default-export/_config.js
Expand Up @@ -6,6 +6,7 @@ module.exports = {
code: 'PARSE_ERROR',
message: `Duplicate export 'default'`,
pos: 25,
watchFiles: [path.resolve(__dirname, 'main.js'), path.resolve(__dirname, 'foo.js')],
loc: {
file: path.resolve(__dirname, 'foo.js'),
line: 2,
Expand Down
1 change: 1 addition & 0 deletions test/function/samples/double-named-export/_config.js
Expand Up @@ -6,6 +6,7 @@ module.exports = {
code: 'PARSE_ERROR',
message: `Duplicate export 'foo'`,
pos: 38,
watchFiles: [path.resolve(__dirname, 'main.js'), path.resolve(__dirname, 'foo.js')],
loc: {
file: path.resolve(__dirname, 'foo.js'),
line: 3,
Expand Down
1 change: 1 addition & 0 deletions test/function/samples/double-named-reexport/_config.js
Expand Up @@ -6,6 +6,7 @@ module.exports = {
code: 'PARSE_ERROR',
message: `Duplicate export 'foo'`,
pos: 38,
watchFiles: [path.resolve(__dirname, 'main.js'), path.resolve(__dirname, 'foo.js')],
loc: {
file: path.resolve(__dirname, 'foo.js'),
line: 3,
Expand Down
1 change: 1 addition & 0 deletions test/function/samples/duplicate-import-fails/_config.js
Expand Up @@ -6,6 +6,7 @@ module.exports = {
code: 'PARSE_ERROR',
message: `Identifier 'a' has already been declared`,
pos: 36,
watchFiles: [path.resolve(__dirname, 'main.js')],
loc: {
file: path.resolve(__dirname, 'main.js'),
line: 2,
Expand Down
Expand Up @@ -6,6 +6,7 @@ module.exports = {
code: 'PARSE_ERROR',
message: `Identifier 'a' has already been declared`,
pos: 12,
watchFiles: [path.resolve(__dirname, 'main.js')],
loc: {
file: path.resolve(__dirname, 'main.js'),
line: 1,
Expand Down
Expand Up @@ -22,6 +22,7 @@ module.exports = {
code: 'MISSING_EXPORT',
message: `'default' is not exported by empty.js`,
pos: 44,
watchFiles: [path.resolve(__dirname, 'main.js'), path.resolve(__dirname, 'empty.js')],
loc: {
file: path.resolve(__dirname, 'main.js'),
line: 1,
Expand Down
1 change: 1 addition & 0 deletions test/function/samples/error-parse-json/_config.js
Expand Up @@ -7,6 +7,7 @@ module.exports = {
code: 'PARSE_ERROR',
message: 'Unexpected token (Note that you need rollup-plugin-json to import JSON files)',
pos: 10,
watchFiles: [path.resolve(__dirname, 'main.js'), path.resolve(__dirname, 'file.json')],
loc: {
file: path.resolve(__dirname, 'file.json'),
line: 2,
Expand Down
Expand Up @@ -8,6 +8,7 @@ module.exports = {
message:
'Unexpected token (Note that you need plugins to import files that are not JavaScript)',
pos: 0,
watchFiles: [path.resolve(__dirname, 'main.js'), path.resolve(__dirname, 'file.css')],
loc: {
file: path.resolve(__dirname, 'file.css'),
line: 1,
Expand Down
Expand Up @@ -6,6 +6,7 @@ module.exports = {
code: 'PARSE_ERROR',
message: `'import' and 'export' may only appear at the top level`,
pos: 19,
watchFiles: [path.resolve(__dirname, 'main.js')],
loc: {
file: path.resolve(__dirname, 'main.js'),
line: 2,
Expand Down