From 6b343abab587f53a769379987e3bd24203e0e8bb Mon Sep 17 00:00:00 2001 From: Lukas Taegert-Atkinson Date: Sat, 5 Jan 2019 12:28:58 +0100 Subject: [PATCH] Make sure this.addWatchFile also declares transform dependencies, (#2633) fix some error messages and inconsistencies in the changelog and+ document this.addWatchFile --- CHANGELOG.md | 4 +- docs/05-plugins.md | 10 +- src/utils/pluginDriver.ts | 15 +- src/utils/transform.ts | 5 + test/hooks/index.js | 2 +- test/watch/index.js | 1341 ++++++++++------- .../watch/samples/transform-dependencies/asdf | 1 - .../main.js | 0 test/watch/samples/watch-files/watched | 1 + 9 files changed, 812 insertions(+), 567 deletions(-) delete mode 100644 test/watch/samples/transform-dependencies/asdf rename test/watch/samples/{transform-dependencies => watch-files}/main.js (100%) create mode 100644 test/watch/samples/watch-files/watched diff --git a/CHANGELOG.md b/CHANGELOG.md index 905de2863bd..4e97b081186 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -57,8 +57,8 @@ - transformChunk - ongenerate - onwrite -* Plugin transform dependencies are deprecated in favour of the `watchChange` plugin hook (#2409) -* Accessing `this.watcher` in plugin hooks is deprecated in favour of the `watchChange` plugin hook (#2409) +* Plugin transform dependencies are deprecated in favour of using the `this.addWatchFile` plugin context function (#2409) +* Accessing `this.watcher` in plugin hooks is deprecated in favour of the `watchChange` plugin hook and the `this.addWatchFile` plugin context function (#2409) * Using dynamic import statements will by default create a new chunk unless `inlineDynamicImports` is used (#2293) * Rollup now uses acorn@6 which means that acorn plugins must be compatible with this version; acorn is now external for non-browser builds to make plugins work (#2293) diff --git a/docs/05-plugins.md b/docs/05-plugins.md index 8147b237436..4b4dde5cf09 100644 --- a/docs/05-plugins.md +++ b/docs/05-plugins.md @@ -169,7 +169,7 @@ Defines a custom resolver. A resolver loader can be useful for e.g. locating thi #### `transform` Type: `(code: string, id: string) => string | { code: string, map?: string | SourceMap, ast? : ESTree.Program } | null | Promise<...>` -Can be used to transform individual modules. +Can be used to transform individual modules. Note that in watch mode, the result of this hook is cached when rebuilding and the hook is only triggered again for a module `id` if either the `code` of the module has changed or a file has changed that was added via `this.addWatchFile` the last time the hook was triggered for this module. #### `watchChange` Type: `(id: string) => void` @@ -199,6 +199,14 @@ More properties may be supported in future, as and when they prove necessary. A number of utility functions and informational bits can be accessed from within all [hooks](guide/en#hooks) via `this`: +#### `this.addWatchFile(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`. + +**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. + +In general, it is recommended to use `this.addWatchfile` from within the hook that depends on the watched file. + #### `this.emitAsset(assetName: string, source: string) => void` Emits a custom file to include in the build output, returning its `assetId`. You can defer setting the source if you provide it later via `this.setAssetSource(assetId)`. A string or Buffer source must be set for each asset through either method or an error will be thrown on generate completion. diff --git a/src/utils/pluginDriver.ts b/src/utils/pluginDriver.ts index 9a40c195be9..916577fec5d 100644 --- a/src/utils/pluginDriver.ts +++ b/src/utils/pluginDriver.ts @@ -57,18 +57,17 @@ export function createPluginDriver( ): PluginDriver { const plugins = [...(options.plugins || []), getRollupDefaultPlugin(options)]; const { emitAsset, getAssetFileName, setAssetSource } = createAssetPluginHooks(graph.assetsById); - const existingPluginKeys: string[] = []; + const existingPluginKeys: { [key: string]: true } = {}; let hasLoadersOrTransforms = false; const pluginContexts: PluginContext[] = plugins.map((plugin, pidx) => { let cacheable = true; if (typeof plugin.cacheKey !== 'string') { - if (typeof plugin.name !== 'string') { + if (typeof plugin.name !== 'string' || existingPluginKeys[plugin.name]) { cacheable = false; } else { - if (existingPluginKeys.indexOf(plugin.name) !== -1) cacheable = false; - existingPluginKeys.push(plugin.name); + existingPluginKeys[plugin.name] = true; } } @@ -90,14 +89,16 @@ export function createPluginDriver( cacheInstance = uncacheablePlugin(plugin.name); } - const firstWatchHandler = true; + let watcherDeprecationWarningShown = false; function deprecatedWatchListener(event: string, handler: () => void): EventEmitter { - if (firstWatchHandler) + if (!watcherDeprecationWarningShown) { context.warn({ code: 'PLUGIN_WATCHER_DEPRECATED', - message: `this.watcher usage is deprecated in plugins. Use the watchChange plugin hook instead.` + message: `this.watcher usage is deprecated in plugins. Use the watchChange plugin hook and this.addWatchFile() instead.` }); + watcherDeprecationWarningShown = true; + } return watcher.on(event, handler); } diff --git a/src/utils/transform.ts b/src/utils/transform.ts index 369076e92d6..bf8eb240629 100644 --- a/src/utils/transform.ts +++ b/src/utils/transform.ts @@ -130,6 +130,11 @@ export default function transform( pluginContext.error(err); }, emitAsset, + addWatchFile(id: string) { + if (!transformDependencies) transformDependencies = []; + transformDependencies.push(id); + pluginContext.addWatchFile(id); + }, setAssetSource(assetId, source) { pluginContext.setAssetSource(assetId, source); if (!customTransformCache && !setAssetSourceErr) { diff --git a/test/hooks/index.js b/test/hooks/index.js index 90fb2874ab1..089356efb80 100644 --- a/test/hooks/index.js +++ b/test/hooks/index.js @@ -1139,7 +1139,7 @@ module.exports = input; assert.equal(warning.pluginCode, 'PLUGIN_WATCHER_DEPRECATED'); assert.equal( warning.message, - 'this.watcher usage is deprecated in plugins. Use the watchChange plugin hook instead.' + 'this.watcher usage is deprecated in plugins. Use the watchChange plugin hook and this.addWatchFile() instead.' ); }, plugins: [ diff --git a/test/watch/index.js b/test/watch/index.js index a8b55dd03ad..6f65562713e 100644 --- a/test/watch/index.js +++ b/test/watch/index.js @@ -91,7 +91,7 @@ describe('rollup.watch', () => { 'BUNDLE_END', 'END', () => { - assert.equal(run('../_tmp/output/bundle.js'), 42); + assert.strictEqual(run('../_tmp/output/bundle.js'), 42); sander.writeFileSync('test/_tmp/input/main.js', 'export default 43;'); }, 'START', @@ -99,7 +99,7 @@ describe('rollup.watch', () => { 'BUNDLE_END', 'END', () => { - assert.equal(run('../_tmp/output/bundle.js'), 43); + assert.strictEqual(run('../_tmp/output/bundle.js'), 43); } ]); }); @@ -117,14 +117,12 @@ describe('rollup.watch', () => { file: 'test/_tmp/output/bundle.js', format: 'cjs' }, - plugins: [ - { - watchChange(id) { - watchChangeCnt++; - assert.equal(id, path.resolve('test/_tmp/input/main.js')); - } + plugins: { + watchChange(id) { + watchChangeCnt++; + assert.strictEqual(id, path.resolve('test/_tmp/input/main.js')); } - ], + }, watch: { chokidar } }); @@ -134,17 +132,8 @@ describe('rollup.watch', () => { 'BUNDLE_END', 'END', () => { - assert.equal(run('../_tmp/output/bundle.js'), 42); - assert.equal(watchChangeCnt, 0); - sander.writeFileSync('test/_tmp/input/main.js', 'export default 43;'); - }, - 'START', - 'BUNDLE_START', - 'BUNDLE_END', - 'END', - () => { - assert.equal(run('../_tmp/output/bundle.js'), 43); - assert.equal(watchChangeCnt, 1); + assert.strictEqual(run('../_tmp/output/bundle.js'), 42); + assert.strictEqual(watchChangeCnt, 0); sander.writeFileSync('test/_tmp/input/main.js', 'export default 43;'); }, 'START', @@ -152,8 +141,8 @@ describe('rollup.watch', () => { 'BUNDLE_END', 'END', () => { - assert.equal(run('../_tmp/output/bundle.js'), 43); - assert.equal(watchChangeCnt, 2); + assert.strictEqual(run('../_tmp/output/bundle.js'), 43); + assert.strictEqual(watchChangeCnt, 1); sander.writeFileSync('test/_tmp/input/main.js', 'export default 43;'); }, 'START', @@ -161,47 +150,8 @@ describe('rollup.watch', () => { 'BUNDLE_END', 'END', () => { - assert.equal(run('../_tmp/output/bundle.js'), 43); - assert.equal(watchChangeCnt, 3); - } - ]); - }); - }); - - it('provides the watcher through the plugin context', () => { - const events = []; - return sander - .copydir('test/watch/samples/basic') - .to('test/_tmp/input') - .then(() => { - const watcher = rollup.watch({ - input: 'test/_tmp/input/main.js', - output: { - file: 'test/_tmp/output/bundle.js', - format: 'cjs' - }, - watch: { chokidar }, - plugins: [ - { - buildStart(id) { - if (!this.watcher) throw new Error('No Watcher'); - - this.watcher.on('event', event => { - events.push(event); - }); - } - } - ] - }); - - return sequence(watcher, [ - 'START', - 'BUNDLE_START', - 'BUNDLE_END', - 'END', - () => { - assert.equal(events.length, 2); - assert.equal(run('../_tmp/output/bundle.js'), 42); + assert.strictEqual(run('../_tmp/output/bundle.js'), 43); + assert.strictEqual(watchChangeCnt, 2); sander.writeFileSync('test/_tmp/input/main.js', 'export default 43;'); }, 'START', @@ -209,8 +159,8 @@ describe('rollup.watch', () => { 'BUNDLE_END', 'END', () => { - assert.equal(run('../_tmp/output/bundle.js'), 43); - assert.equal(events.length, 8); + assert.strictEqual(run('../_tmp/output/bundle.js'), 43); + assert.strictEqual(watchChangeCnt, 3); } ]); }); @@ -236,8 +186,8 @@ describe('rollup.watch', () => { 'BUNDLE_END', 'END', () => { - assert.equal(run('../_tmp/output/main1.js'), 21); - assert.equal(run('../_tmp/output/main2.js'), 42); + assert.strictEqual(run('../_tmp/output/main1.js'), 21); + assert.strictEqual(run('../_tmp/output/main2.js'), 42); sander.writeFileSync('test/_tmp/input/shared.js', 'export const value = 22;'); }, 'START', @@ -245,8 +195,8 @@ describe('rollup.watch', () => { 'BUNDLE_END', 'END', () => { - assert.equal(run('../_tmp/output/main1.js'), 22); - assert.equal(run('../_tmp/output/main2.js'), 44); + assert.strictEqual(run('../_tmp/output/main1.js'), 22); + assert.strictEqual(run('../_tmp/output/main2.js'), 44); } ]); }); @@ -275,8 +225,8 @@ describe('rollup.watch', () => { 'BUNDLE_END', 'END', () => { - assert.equal(run('../_tmp/output/_main_1.js'), 21); - assert.equal(run('../_tmp/output/subfolder/_main_2.js'), 42); + assert.strictEqual(run('../_tmp/output/_main_1.js'), 21); + assert.strictEqual(run('../_tmp/output/subfolder/_main_2.js'), 42); sander.writeFileSync('test/_tmp/input/shared.js', 'export const value = 22;'); }, 'START', @@ -284,8 +234,8 @@ describe('rollup.watch', () => { 'BUNDLE_END', 'END', () => { - assert.equal(run('../_tmp/output/_main_1.js'), 22); - assert.equal(run('../_tmp/output/subfolder/_main_2.js'), 44); + assert.strictEqual(run('../_tmp/output/_main_1.js'), 22); + assert.strictEqual(run('../_tmp/output/subfolder/_main_2.js'), 44); } ]); }); @@ -311,7 +261,7 @@ describe('rollup.watch', () => { 'BUNDLE_END', 'END', () => { - assert.equal(run('../_tmp/output/bundle.js'), 42); + assert.strictEqual(run('../_tmp/output/bundle.js'), 42); sander.writeFileSync('test/_tmp/input/main.js', 'export nope;'); }, 'START', @@ -325,7 +275,7 @@ describe('rollup.watch', () => { 'BUNDLE_END', 'END', () => { - assert.equal(run('../_tmp/output/bundle.js'), 43); + assert.strictEqual(run('../_tmp/output/bundle.js'), 43); } ]); }); @@ -351,7 +301,7 @@ describe('rollup.watch', () => { 'BUNDLE_END', 'END', () => { - assert.equal(run('../_tmp/output/bundle.js'), 42); + assert.strictEqual(run('../_tmp/output/bundle.js'), 42); sander.unlinkSync('test/_tmp/input/main.js'); sander.writeFileSync('test/_tmp/input/main.js', 'export nope;'); }, @@ -367,15 +317,15 @@ describe('rollup.watch', () => { 'BUNDLE_END', 'END', () => { - assert.equal(run('../_tmp/output/bundle.js'), 43); + assert.strictEqual(run('../_tmp/output/bundle.js'), 43); } ]); }); }); - it('watches and rebuilds transform dependencies', () => { + it('refuses to watch the output file (#15)', () => { return sander - .copydir('test/watch/samples/transform-dependencies') + .copydir('test/watch/samples/basic') .to('test/_tmp/input') .then(() => { const watcher = rollup.watch({ @@ -384,15 +334,6 @@ describe('rollup.watch', () => { file: 'test/_tmp/output/bundle.js', format: 'cjs' }, - plugins: [ - { - transform(code) { - const dependencies = ['./asdf']; - const text = sander.readFileSync('test/_tmp/input/asdf').toString(); - return { code: `export default "${text}"`, dependencies }; - } - } - ], watch: { chokidar } }); @@ -402,24 +343,30 @@ describe('rollup.watch', () => { 'BUNDLE_END', 'END', () => { - assert.equal(run('../_tmp/output/bundle.js'), 'asdf'); - sander.unlinkSync('test/_tmp/input/asdf'); - sander.writeFileSync('test/_tmp/input/asdf', 'next'); + assert.strictEqual(run('../_tmp/output/bundle.js'), 42); + sander.writeFileSync('test/_tmp/input/main.js', `import '../output/bundle.js'`); + }, + 'START', + 'BUNDLE_START', + 'ERROR', + event => { + assert.strictEqual(event.error.message, 'Cannot import the generated bundle'); + sander.writeFileSync('test/_tmp/input/main.js', 'export default 43;'); }, 'START', 'BUNDLE_START', 'BUNDLE_END', 'END', () => { - assert.equal(run('../_tmp/output/bundle.js'), 'next'); + assert.strictEqual(run('../_tmp/output/bundle.js'), 43); } ]); }); }); - it('supports transform cache opt-out via cacheKey and custom watchFiles', () => { + it('ignores files that are not specified in options.watch.include, if given', () => { return sander - .copydir('test/watch/samples/transform-dependencies') + .copydir('test/watch/samples/ignored') .to('test/_tmp/input') .then(() => { const watcher = rollup.watch({ @@ -428,17 +375,10 @@ describe('rollup.watch', () => { file: 'test/_tmp/output/bundle.js', format: 'cjs' }, - plugins: [ - { - cacheKey: 'asdf', - transform() { - this.addWatchFile('test/_tmp/input/asdf'); - const text = sander.readFileSync('test/_tmp/input/asdf').toString(); - return `export default "${text}"`; - } - } - ], - watch: { chokidar } + watch: { + chokidar, + include: ['test/_tmp/input/+(main|foo).js'] + } }); return sequence(watcher, [ @@ -447,59 +387,36 @@ describe('rollup.watch', () => { 'BUNDLE_END', 'END', () => { - assert.equal(run('../_tmp/output/bundle.js'), 'asdf'); - sander.unlinkSync('test/_tmp/input/asdf'); - sander.writeFileSync('test/_tmp/input/asdf', 'next'); + assert.deepStrictEqual(run('../_tmp/output/bundle.js'), { + foo: 'foo-1', + bar: 'bar-1' + }); + sander.writeFileSync('test/_tmp/input/foo.js', `export default 'foo-2';`); }, 'START', 'BUNDLE_START', 'BUNDLE_END', 'END', () => { - assert.equal(run('../_tmp/output/bundle.js'), 'next'); - } - ]); - }); - }); - - it('throws if transform dependency doesnt exist', () => { - return sander - .copydir('test/watch/samples/transform-dependencies') - .to('test/_tmp/input') - .then(() => { - const watcher = rollup.watch({ - input: 'test/_tmp/input/main.js', - output: { - file: 'test/_tmp/output/bundle.js', - format: 'cjs' + assert.deepStrictEqual(run('../_tmp/output/bundle.js'), { + foo: 'foo-2', + bar: 'bar-1' + }); + sander.writeFileSync('test/_tmp/input/bar.js', `export default 'bar-2';`); }, - plugins: [ - { - transform(code) { - const dependencies = ['./doesnotexist']; - const text = sander.readFileSync('test/_tmp/input/asdf').toString(); - return { code: `export default "${text}"`, dependencies }; - } - } - ], - watch: { chokidar } - }); - - return sequence(watcher, [ - 'START', - 'BUNDLE_START', - 'FATAL', - event => { - assert.ok(event.error.message.startsWith('Transform dependency')); - assert.ok(event.error.message.endsWith('does not exist.')); + () => { + assert.deepStrictEqual(run('../_tmp/output/bundle.js'), { + foo: 'foo-2', + bar: 'bar-1' + }); } ]); }); }); - it('watches and rebuilds transform dependencies that are modules', () => { + it('ignores files that are specified in options.watch.exclude, if given', () => { return sander - .copydir('test/watch/samples/transform-dependencies') + .copydir('test/watch/samples/ignored') .to('test/_tmp/input') .then(() => { const watcher = rollup.watch({ @@ -508,19 +425,10 @@ describe('rollup.watch', () => { file: 'test/_tmp/output/bundle.js', format: 'cjs' }, - plugins: [ - { - transform(code) { - const dependencies = ['./main.js']; - const text = sander - .readFileSync('test/_tmp/input/main.js') - .toString() - .trim(); - return { code: `export default "${text}"`, dependencies }; - } - } - ], - watch: { chokidar } + watch: { + chokidar, + exclude: ['test/_tmp/input/bar.js'] + } }); return sequence(watcher, [ @@ -529,96 +437,97 @@ describe('rollup.watch', () => { 'BUNDLE_END', 'END', () => { - assert.equal(run('../_tmp/output/bundle.js'), 'export default 42;'); - sander.unlinkSync('test/_tmp/input/main.js'); - sander.writeFileSync('test/_tmp/input/main.js', 'next'); + assert.deepStrictEqual(run('../_tmp/output/bundle.js'), { + foo: 'foo-1', + bar: 'bar-1' + }); + sander.writeFileSync('test/_tmp/input/foo.js', `export default 'foo-2';`); }, 'START', 'BUNDLE_START', 'BUNDLE_END', 'END', () => { - assert.equal(run('../_tmp/output/bundle.js'), 'next'); + assert.deepStrictEqual(run('../_tmp/output/bundle.js'), { + foo: 'foo-2', + bar: 'bar-1' + }); + sander.writeFileSync('test/_tmp/input/bar.js', `export default 'bar-2';`); + }, + () => { + assert.deepStrictEqual(run('../_tmp/output/bundle.js'), { + foo: 'foo-2', + bar: 'bar-1' + }); } ]); }); }); - it('watches and rebuilds transform dependencies directories', () => { - let v = 1; + it('only rebuilds the appropriate configs', () => { return sander - .copydir('test/watch/samples/transform-dependencies') + .copydir('test/watch/samples/multiple') .to('test/_tmp/input') .then(() => { - const watcher = rollup.watch({ - input: 'test/_tmp/input/main.js', - output: { - file: 'test/_tmp/output/bundle.js', - format: 'cjs' + const watcher = rollup.watch([ + { + input: 'test/_tmp/input/main1.js', + output: { + file: 'test/_tmp/output/bundle1.js', + format: 'cjs' + }, + watch: { chokidar } }, - plugins: [ - { - transform(code) { - const dependencies = ['./']; - return { code: `export default ${v++}`, dependencies }; - } - } - ], - watch: { chokidar } - }); + { + input: 'test/_tmp/input/main2.js', + output: { + file: 'test/_tmp/output/bundle2.js', + format: 'cjs' + }, + watch: { chokidar } + } + ]); return sequence(watcher, [ 'START', 'BUNDLE_START', 'BUNDLE_END', + 'BUNDLE_START', + 'BUNDLE_END', 'END', () => { - assert.equal(run('../_tmp/output/bundle.js'), 1); - sander.unlinkSync('test/_tmp/input/asdf'); + assert.deepStrictEqual(run('../_tmp/output/bundle1.js'), 42); + assert.deepStrictEqual(run('../_tmp/output/bundle2.js'), 43); + sander.writeFileSync('test/_tmp/input/main2.js', 'export default 44'); }, 'START', 'BUNDLE_START', 'BUNDLE_END', 'END', () => { - assert.equal(run('../_tmp/output/bundle.js'), 2); - watcher.close(); + assert.deepStrictEqual(run('../_tmp/output/bundle1.js'), 42); + assert.deepStrictEqual(run('../_tmp/output/bundle2.js'), 44); } ]); }); }); - it('watches and rebuilds transform dependencies, with transform cache opt-out for custom cache', () => { - const file = 'test/_tmp/input/asdf'; - let v = 1; + it('respects output.globals', () => { return sander - .copydir('test/watch/samples/transform-dependencies') + .copydir('test/watch/samples/globals') .to('test/_tmp/input') .then(() => { const watcher = rollup.watch({ input: 'test/_tmp/input/main.js', output: { file: 'test/_tmp/output/bundle.js', - format: 'cjs' - }, - plugins: [ - { - name: 'x', - buildStart() { - try { - const text = sander.readFileSync(file).toString(); - this.emitAsset('test', text); - } catch (err) { - if (err.code !== 'ENOENT') throw err; - } - }, - transform(code) { - this.cache.set('asdf', 'asdf'); - return { code: `export default "${v++}"`, dependencies: [path.resolve(file)] }; - } + format: 'iife', + globals: { + jquery: 'jQuery' } - ], - watch: { chokidar } + }, + watch: { chokidar }, + external: ['jquery'] }); return sequence(watcher, [ @@ -627,24 +536,18 @@ describe('rollup.watch', () => { 'BUNDLE_END', 'END', () => { - assert.equal(run('../_tmp/output/bundle.js'), 1); - sander.unlinkSync('test/_tmp/input/asdf'); - }, - 'START', - 'BUNDLE_START', - 'BUNDLE_END', - 'END', - () => { - assert.equal(run('../_tmp/output/bundle.js'), 2); + const generated = sander.readFileSync('test/_tmp/output/bundle.js', { + encoding: 'utf-8' + }); + assert.ok(/jQuery/.test(generated)); } ]); }); }); - it('watches and rebuilds asset transform dependencies', () => { - let v = 1; + it('treats filenames literally, not as globs', () => { return sander - .copydir('test/watch/samples/transform-dependencies') + .copydir('test/watch/samples/non-glob') .to('test/_tmp/input') .then(() => { const watcher = rollup.watch({ @@ -653,24 +556,6 @@ describe('rollup.watch', () => { file: 'test/_tmp/output/bundle.js', format: 'cjs' }, - plugins: [ - { - transform(code, id) { - const file = 'test/_tmp/input/asdf'; - try { - const text = sander.readFileSync(file).toString(); - this.emitAsset('test', text); - } catch (err) { - if (err.code !== 'ENOENT') throw err; - this.emitAsset('test', 'test'); - } - return { - code: `export default ${v++}`, - dependencies: v === 2 ? [path.resolve(file)] : [] - }; - } - } - ], watch: { chokidar } }); @@ -680,42 +565,38 @@ describe('rollup.watch', () => { 'BUNDLE_END', 'END', () => { - assert.equal(run('../_tmp/output/bundle.js'), 1); - sander.unlinkSync('test/_tmp/input/asdf'); + assert.strictEqual(run('../_tmp/output/bundle.js'), 42); + sander.writeFileSync('test/_tmp/input/[foo]/bar.js', `export const bar = 43;`); }, 'START', 'BUNDLE_START', 'BUNDLE_END', 'END', () => { - assert.equal(run('../_tmp/output/bundle.js'), 2); + assert.strictEqual(run('../_tmp/output/bundle.js'), 43); } ]); }); }); - it('watches and rebuilds transform dependencies created and removed between runs', () => { - let v = 1; + it('updates the right hashes on dependency changes', () => { + let dynamicName; + let staticName; + let chunkName; return sander - .copydir('test/watch/samples/transform-dependencies') + .copydir('test/watch/samples/hashing') .to('test/_tmp/input') .then(() => { const watcher = rollup.watch({ - input: 'test/_tmp/input/main.js', + input: ['test/_tmp/input/main-static.js', 'test/_tmp/input/main-dynamic.js'], output: { - file: 'test/_tmp/output/bundle.js', - format: 'cjs' + dir: 'test/_tmp/output', + format: 'cjs', + entryFileNames: '[name].[hash].js', + chunkFileNames: '[name].[hash].js' }, - plugins: [ - { - transform(code) { - let dependencies = []; - if (v === 2) dependencies = ['./asdf']; - return { code: `export default ${v++}`, dependencies }; - } - } - ], - watch: { chokidar } + watch: { chokidar }, + experimentalCodeSplitting: true }); return sequence(watcher, [ @@ -724,353 +605,703 @@ describe('rollup.watch', () => { 'BUNDLE_END', 'END', () => { - assert.equal(run('../_tmp/output/bundle.js'), 1); - sander.unlinkSync('test/_tmp/input/main.js'); - sander.writeFileSync('test/_tmp/input/main.js', 'next'); + [chunkName, dynamicName, staticName] = sander.readdirSync('test/_tmp/output').sort(); + sander.rimrafSync('test/_tmp/output'); + + // this should only update the hash of that particular entry point + sander.writeFileSync( + 'test/_tmp/input/main-static.js', + "import {value} from './shared';export default 2*value;" + ); }, 'START', 'BUNDLE_START', 'BUNDLE_END', 'END', () => { - assert.equal(run('../_tmp/output/bundle.js'), 2); - sander.unlinkSync('test/_tmp/input/asdf'); + const [newChunkName, newDynamicName, newStaticName] = sander + .readdirSync('test/_tmp/output') + .sort(); + sander.rimrafSync('test/_tmp/output'); + assert.notEqual(newStaticName, staticName); + assert.strictEqual(newDynamicName, dynamicName); + assert.strictEqual(newChunkName, chunkName); + staticName = newStaticName; + + // this should update all hashes + sander.writeFileSync('test/_tmp/input/shared.js', 'export const value = 42;'); }, 'START', 'BUNDLE_START', 'BUNDLE_END', 'END', () => { - assert.equal(run('../_tmp/output/bundle.js'), 3); - sander.writeFileSync('test/_tmp/input/asdf', 'ignored'); - return new Promise(resolve => setTimeout(resolve, 50)); + const [newChunkName, newDynamicName, newStaticName] = sander + .readdirSync('test/_tmp/output') + .sort(); + assert.notEqual(newStaticName, staticName); + assert.notEqual(newDynamicName, dynamicName); + assert.notEqual(newChunkName, chunkName); } ]); }); }); - it('refuses to watch the output file (#15)', () => { - return sander - .copydir('test/watch/samples/basic') - .to('test/_tmp/input') - .then(() => { - const watcher = rollup.watch({ - input: 'test/_tmp/input/main.js', - output: { - file: 'test/_tmp/output/bundle.js', - format: 'cjs' - }, - watch: { chokidar } + describe('addWatchFile', () => { + it('supports adding additional watch files in plugin hooks', () => { + const watchChangeIds = []; + const buildStartFile = path.resolve('test/_tmp/input/buildStart'); + const loadFile = path.resolve('test/_tmp/input/load'); + const resolveIdFile = path.resolve('test/_tmp/input/resolveId'); + const transformFile = path.resolve('test/_tmp/input/transform'); + const watchFiles = [buildStartFile, loadFile, resolveIdFile, transformFile]; + return sander + .copydir('test/watch/samples/basic') + .to('test/_tmp/input') + .then(() => { + for (const file of watchFiles) sander.writeFileSync(file, 'initial'); + const watcher = rollup.watch({ + input: 'test/_tmp/input/main.js', + output: { + file: 'test/_tmp/output/bundle.js', + format: 'cjs' + }, + plugins: { + buildStart() { + this.addWatchFile(buildStartFile); + }, + load() { + this.addWatchFile(loadFile); + }, + resolveId() { + this.addWatchFile(resolveIdFile); + }, + transform() { + this.addWatchFile(transformFile); + }, + watchChange(id) { + watchChangeIds.push(id); + } + }, + watch: { chokidar } + }); + + return sequence(watcher, [ + 'START', + 'BUNDLE_START', + 'BUNDLE_END', + 'END', + () => { + assert.strictEqual(run('../_tmp/output/bundle.js'), 42); + assert.deepStrictEqual(watchChangeIds, []); + for (const file of watchFiles) sander.writeFileSync(file, 'changed'); + }, + 'START', + 'BUNDLE_START', + 'BUNDLE_END', + 'END', + () => { + assert.strictEqual(run('../_tmp/output/bundle.js'), 42); + assert.deepStrictEqual(watchChangeIds.sort(), watchFiles.sort()); + } + ]); }); + }); - return sequence(watcher, [ - 'START', - 'BUNDLE_START', - 'BUNDLE_END', - 'END', - () => { - assert.equal(run('../_tmp/output/bundle.js'), 42); - sander.writeFileSync('test/_tmp/input/main.js', `import '../output/bundle.js'`); - }, - 'START', - 'BUNDLE_START', - 'ERROR', - event => { - assert.equal(event.error.message, 'Cannot import the generated bundle'); - sander.writeFileSync('test/_tmp/input/main.js', 'export default 43;'); - }, - 'START', - 'BUNDLE_START', - 'BUNDLE_END', - 'END', - () => { - assert.equal(run('../_tmp/output/bundle.js'), 43); - } - ]); - }); - }); + it('respects changed watched files in the load hook', () => { + return sander + .copydir('test/watch/samples/watch-files') + .to('test/_tmp/input') + .then(() => { + const watcher = rollup.watch({ + input: 'test/_tmp/input/main.js', + output: { + file: 'test/_tmp/output/bundle.js', + format: 'cjs' + }, + plugins: { + load() { + this.addWatchFile('test/_tmp/input/watched'); + return `export default "${sander + .readFileSync('test/_tmp/input/watched') + .toString() + .trim()}"`; + } + }, + watch: { chokidar } + }); - it('ignores files that are not specified in options.watch.include, if given', () => { - return sander - .copydir('test/watch/samples/ignored') - .to('test/_tmp/input') - .then(() => { - const watcher = rollup.watch({ - input: 'test/_tmp/input/main.js', - output: { - file: 'test/_tmp/output/bundle.js', - format: 'cjs' - }, - watch: { - chokidar, - include: ['test/_tmp/input/+(main|foo).js'] - } + return sequence(watcher, [ + 'START', + 'BUNDLE_START', + 'BUNDLE_END', + 'END', + () => { + assert.strictEqual(run('../_tmp/output/bundle.js'), 'initial'); + sander.writeFileSync('test/_tmp/input/watched', 'next'); + }, + 'START', + 'BUNDLE_START', + 'BUNDLE_END', + 'END', + () => { + assert.strictEqual(run('../_tmp/output/bundle.js'), 'next'); + } + ]); }); + }); - return sequence(watcher, [ - 'START', - 'BUNDLE_START', - 'BUNDLE_END', - 'END', - () => { - assert.deepEqual(run('../_tmp/output/bundle.js'), { - foo: 'foo-1', - bar: 'bar-1' - }); - sander.writeFileSync('test/_tmp/input/foo.js', `export default 'foo-2';`); - }, - 'START', - 'BUNDLE_START', - 'BUNDLE_END', - 'END', - () => { - assert.deepEqual(run('../_tmp/output/bundle.js'), { - foo: 'foo-2', - bar: 'bar-1' - }); - sander.writeFileSync('test/_tmp/input/bar.js', `export default 'bar-2';`); - }, - () => { - assert.deepEqual(run('../_tmp/output/bundle.js'), { - foo: 'foo-2', - bar: 'bar-1' - }); - } - ]); - }); - }); + it('respects changed watched files in the transform hook', () => { + return sander + .copydir('test/watch/samples/watch-files') + .to('test/_tmp/input') + .then(() => { + const watcher = rollup.watch({ + input: 'test/_tmp/input/main.js', + output: { + file: 'test/_tmp/output/bundle.js', + format: 'cjs' + }, + plugins: { + transform() { + this.addWatchFile('test/_tmp/input/watched'); + return `export default "${sander + .readFileSync('test/_tmp/input/watched') + .toString() + .trim()}"`; + } + }, + watch: { chokidar } + }); - it('ignores files that are specified in options.watch.exclude, if given', () => { - return sander - .copydir('test/watch/samples/ignored') - .to('test/_tmp/input') - .then(() => { - const watcher = rollup.watch({ - input: 'test/_tmp/input/main.js', - output: { - file: 'test/_tmp/output/bundle.js', - format: 'cjs' - }, - watch: { - chokidar, - exclude: ['test/_tmp/input/bar.js'] - } + return sequence(watcher, [ + 'START', + 'BUNDLE_START', + 'BUNDLE_END', + 'END', + () => { + assert.strictEqual(run('../_tmp/output/bundle.js'), 'initial'); + sander.writeFileSync('test/_tmp/input/watched', 'next'); + }, + 'START', + 'BUNDLE_START', + 'BUNDLE_END', + 'END', + () => { + assert.strictEqual(run('../_tmp/output/bundle.js'), 'next'); + } + ]); }); + }); - return sequence(watcher, [ - 'START', - 'BUNDLE_START', - 'BUNDLE_END', - 'END', - () => { - assert.deepEqual(run('../_tmp/output/bundle.js'), { - foo: 'foo-1', - bar: 'bar-1' - }); - sander.writeFileSync('test/_tmp/input/foo.js', `export default 'foo-2';`); - }, - 'START', - 'BUNDLE_START', - 'BUNDLE_END', - 'END', - () => { - assert.deepEqual(run('../_tmp/output/bundle.js'), { - foo: 'foo-2', - bar: 'bar-1' - }); - sander.writeFileSync('test/_tmp/input/bar.js', `export default 'bar-2';`); - }, - () => { - assert.deepEqual(run('../_tmp/output/bundle.js'), { - foo: 'foo-2', - bar: 'bar-1' - }); - } - ]); - }); - }); + it('respects changed watched modules that are already part of the graph in the transform hook', () => { + return sander + .copydir('test/watch/samples/watch-files') + .to('test/_tmp/input') + .then(() => { + const watcher = rollup.watch({ + input: 'test/_tmp/input/main.js', + output: { + file: 'test/_tmp/output/bundle.js', + format: 'cjs' + }, + plugins: { + transform() { + this.addWatchFile('test/_tmp/input/main.js'); + return `export default "${sander + .readFileSync('test/_tmp/input/main.js') + .toString() + .trim()}"`; + } + }, + watch: { chokidar } + }); - it('only rebuilds the appropriate configs', () => { - return sander - .copydir('test/watch/samples/multiple') - .to('test/_tmp/input') - .then(() => { - const watcher = rollup.watch([ - { - input: 'test/_tmp/input/main1.js', + return sequence(watcher, [ + 'START', + 'BUNDLE_START', + 'BUNDLE_END', + 'END', + () => { + assert.strictEqual(run('../_tmp/output/bundle.js'), 'export default 42;'); + sander.writeFileSync('test/_tmp/input/main.js', 'next'); + }, + 'START', + 'BUNDLE_START', + 'BUNDLE_END', + 'END', + () => { + assert.strictEqual(run('../_tmp/output/bundle.js'), 'next'); + } + ]); + }); + }); + + it('respects changed watched directories in the transform hook', () => { + return sander + .copydir('test/watch/samples/watch-files') + .to('test/_tmp/input') + .then(() => { + const watcher = rollup.watch({ + input: 'test/_tmp/input/main.js', output: { - file: 'test/_tmp/output/bundle1.js', + file: 'test/_tmp/output/bundle.js', format: 'cjs' }, + plugins: { + transform() { + this.addWatchFile('test/_tmp/input'); + return `export default ${sander.existsSync('test/_tmp/input/watched')}`; + } + }, watch: { chokidar } - }, - { - input: 'test/_tmp/input/main2.js', + }); + + return sequence(watcher, [ + 'START', + 'BUNDLE_START', + 'BUNDLE_END', + 'END', + () => { + assert.strictEqual(run('../_tmp/output/bundle.js'), true); + sander.unlinkSync('test/_tmp/input/watched'); + }, + 'START', + 'BUNDLE_START', + 'BUNDLE_END', + 'END', + () => { + assert.strictEqual(run('../_tmp/output/bundle.js'), false); + watcher.close(); + } + ]); + }); + }); + + it('does not rerun the transform hook if a non-watched change triggered the re-run', () => { + let transformRuns = 0; + return sander + .copydir('test/watch/samples/watch-files') + .to('test/_tmp/input') + .then(() => { + sander.writeFileSync('test/_tmp/input/alsoWatched', 'initial'); + const watcher = rollup.watch({ + input: 'test/_tmp/input/main.js', output: { - file: 'test/_tmp/output/bundle2.js', + file: 'test/_tmp/output/bundle.js', format: 'cjs' }, + plugins: { + buildStart() { + this.addWatchFile('test/_tmp/input/alsoWatched'); + }, + transform() { + transformRuns++; + this.addWatchFile('test/_tmp/input/watched'); + return `export default "${sander + .readFileSync('test/_tmp/input/watched') + .toString() + .trim()}"`; + } + }, watch: { chokidar } - } - ]); + }); - return sequence(watcher, [ - 'START', - 'BUNDLE_START', - 'BUNDLE_END', - 'BUNDLE_START', - 'BUNDLE_END', - 'END', - () => { - assert.deepEqual(run('../_tmp/output/bundle1.js'), 42); - assert.deepEqual(run('../_tmp/output/bundle2.js'), 43); - sander.writeFileSync('test/_tmp/input/main2.js', 'export default 44'); - }, - 'START', - 'BUNDLE_START', - 'BUNDLE_END', - 'END', - () => { - assert.deepEqual(run('../_tmp/output/bundle1.js'), 42); - assert.deepEqual(run('../_tmp/output/bundle2.js'), 44); - } - ]); - }); + return sequence(watcher, [ + 'START', + 'BUNDLE_START', + 'BUNDLE_END', + 'END', + () => { + assert.strictEqual(transformRuns, 1); + sander.writeFileSync('test/_tmp/input/alsoWatched', 'next'); + }, + 'START', + 'BUNDLE_START', + 'BUNDLE_END', + 'END', + () => { + assert.strictEqual(transformRuns, 1); + } + ]); + }); + }); }); - it('respects output.globals', () => { - return sander - .copydir('test/watch/samples/globals') - .to('test/_tmp/input') - .then(() => { - const watcher = rollup.watch({ - input: 'test/_tmp/input/main.js', - output: { - file: 'test/_tmp/output/bundle.js', - format: 'iife', - globals: { - jquery: 'jQuery' + describe('deprecated features', () => { + it('provides the watcher through the plugin context', () => { + const events = []; + return sander + .copydir('test/watch/samples/basic') + .to('test/_tmp/input') + .then(() => { + const watcher = rollup.watch({ + input: 'test/_tmp/input/main.js', + output: { + file: 'test/_tmp/output/bundle.js', + format: 'cjs' + }, + onwarn(warning) { + assert.strictEqual( + warning.message, + 'this.watcher usage is deprecated in plugins. Use the watchChange plugin hook and this.addWatchFile() instead.' + ); + }, + watch: { chokidar }, + plugins: { + buildStart(id) { + if (!this.watcher) throw new Error('No Watcher'); + + this.watcher.on('event', event => { + events.push(event); + }); + } } - }, - watch: { chokidar }, - external: ['jquery'] + }); + + return sequence(watcher, [ + 'START', + 'BUNDLE_START', + 'BUNDLE_END', + 'END', + () => { + assert.strictEqual(events.length, 2); + assert.strictEqual(run('../_tmp/output/bundle.js'), 42); + sander.writeFileSync('test/_tmp/input/main.js', 'export default 43;'); + }, + 'START', + 'BUNDLE_START', + 'BUNDLE_END', + 'END', + () => { + assert.strictEqual(run('../_tmp/output/bundle.js'), 43); + assert.strictEqual(events.length, 8); + } + ]); }); + }); - return sequence(watcher, [ - 'START', - 'BUNDLE_START', - 'BUNDLE_END', - 'END', - () => { - const generated = sander.readFileSync('test/_tmp/output/bundle.js', { - encoding: 'utf-8' - }); - assert.ok(/jQuery/.test(generated)); - } - ]); - }); - }); + it('watches and rebuilds transform dependencies', () => { + return sander + .copydir('test/watch/samples/watch-files') + .to('test/_tmp/input') + .then(() => { + const watcher = rollup.watch({ + input: 'test/_tmp/input/main.js', + output: { + file: 'test/_tmp/output/bundle.js', + format: 'cjs' + }, + plugins: { + transform() { + return { + code: `export default "${sander + .readFileSync('test/_tmp/input/watched') + .toString() + .trim()}"`, + dependencies: ['./watched'] + }; + } + }, + watch: { chokidar } + }); - it('treats filenames literally, not as globs', () => { - return sander - .copydir('test/watch/samples/non-glob') - .to('test/_tmp/input') - .then(() => { - const watcher = rollup.watch({ - input: 'test/_tmp/input/main.js', - output: { - file: 'test/_tmp/output/bundle.js', - format: 'cjs' - }, - watch: { chokidar } + return sequence(watcher, [ + 'START', + 'BUNDLE_START', + 'BUNDLE_END', + 'END', + () => { + assert.strictEqual(run('../_tmp/output/bundle.js'), 'initial'); + sander.writeFileSync('test/_tmp/input/watched', 'next'); + }, + 'START', + 'BUNDLE_START', + 'BUNDLE_END', + 'END', + () => { + assert.strictEqual(run('../_tmp/output/bundle.js'), 'next'); + } + ]); }); + }); - return sequence(watcher, [ - 'START', - 'BUNDLE_START', - 'BUNDLE_END', - 'END', - () => { - assert.equal(run('../_tmp/output/bundle.js'), 42); - sander.writeFileSync('test/_tmp/input/[foo]/bar.js', `export const bar = 43;`); - }, - 'START', - 'BUNDLE_START', - 'BUNDLE_END', - 'END', - () => { - assert.equal(run('../_tmp/output/bundle.js'), 43); - } - ]); - }); - }); + it("throws if transform dependency doesn't exist", () => { + return sander + .copydir('test/watch/samples/watch-files') + .to('test/_tmp/input') + .then(() => { + const watcher = rollup.watch({ + input: 'test/_tmp/input/main.js', + output: { + file: 'test/_tmp/output/bundle.js', + format: 'cjs' + }, + plugins: { + transform() { + return { + code: `export default "${sander + .readFileSync('test/_tmp/input/watched') + .toString() + .trim()}"`, + dependencies: ['./doesnotexist'] + }; + } + }, + watch: { chokidar } + }); - it('updates the right hashes on dependency changes', () => { - let dynamicName; - let staticName; - let chunkName; - return sander - .copydir('test/watch/samples/hashing') - .to('test/_tmp/input') - .then(() => { - const watcher = rollup.watch({ - input: ['test/_tmp/input/main-static.js', 'test/_tmp/input/main-dynamic.js'], - output: { - dir: 'test/_tmp/output', - format: 'cjs', - entryFileNames: '[name].[hash].js', - chunkFileNames: '[name].[hash].js' - }, - watch: { chokidar }, - experimentalCodeSplitting: true + return sequence(watcher, [ + 'START', + 'BUNDLE_START', + 'FATAL', + event => { + assert.ok(event.error.message.startsWith('Transform dependency')); + assert.ok(event.error.message.endsWith('does not exist.')); + } + ]); }); + }); - return sequence(watcher, [ - 'START', - 'BUNDLE_START', - 'BUNDLE_END', - 'END', - () => { - [chunkName, dynamicName, staticName] = sander.readdirSync('test/_tmp/output').sort(); - sander.rimrafSync('test/_tmp/output'); + it('watches and rebuilds transform dependencies that are modules', () => { + return sander + .copydir('test/watch/samples/watch-files') + .to('test/_tmp/input') + .then(() => { + const watcher = rollup.watch({ + input: 'test/_tmp/input/main.js', + output: { + file: 'test/_tmp/output/bundle.js', + format: 'cjs' + }, + plugins: { + transform() { + const dependencies = ['./main.js']; + const text = sander + .readFileSync('test/_tmp/input/main.js') + .toString() + .trim(); + return { code: `export default "${text}"`, dependencies }; + } + }, + watch: { chokidar } + }); - // this should only update the hash of that particular entry point - sander.writeFileSync( - 'test/_tmp/input/main-static.js', - "import {value} from './shared';export default 2*value;" - ); - }, - 'START', - 'BUNDLE_START', - 'BUNDLE_END', - 'END', - () => { - const [newChunkName, newDynamicName, newStaticName] = sander - .readdirSync('test/_tmp/output') - .sort(); - sander.rimrafSync('test/_tmp/output'); - assert.notEqual(newStaticName, staticName); - assert.equal(newDynamicName, dynamicName); - assert.equal(newChunkName, chunkName); - staticName = newStaticName; + return sequence(watcher, [ + 'START', + 'BUNDLE_START', + 'BUNDLE_END', + 'END', + () => { + assert.strictEqual(run('../_tmp/output/bundle.js'), 'export default 42;'); + sander.writeFileSync('test/_tmp/input/main.js', 'next'); + }, + 'START', + 'BUNDLE_START', + 'BUNDLE_END', + 'END', + () => { + assert.strictEqual(run('../_tmp/output/bundle.js'), 'next'); + } + ]); + }); + }); - // this should update all hashes - sander.writeFileSync('test/_tmp/input/shared.js', 'export const value = 42;'); - }, - 'START', - 'BUNDLE_START', - 'BUNDLE_END', - 'END', - () => { - const [newChunkName, newDynamicName, newStaticName] = sander - .readdirSync('test/_tmp/output') - .sort(); - assert.notEqual(newStaticName, staticName); - assert.notEqual(newDynamicName, dynamicName); - assert.notEqual(newChunkName, chunkName); - } - ]); - }); + it('watches and rebuilds transform dependencies directories', () => { + let v = 1; + return sander + .copydir('test/watch/samples/watch-files') + .to('test/_tmp/input') + .then(() => { + const watcher = rollup.watch({ + input: 'test/_tmp/input/main.js', + output: { + file: 'test/_tmp/output/bundle.js', + format: 'cjs' + }, + plugins: { + transform() { + const dependencies = ['./']; + return { code: `export default ${v++}`, dependencies }; + } + }, + watch: { chokidar } + }); + + return sequence(watcher, [ + 'START', + 'BUNDLE_START', + 'BUNDLE_END', + 'END', + () => { + assert.strictEqual(run('../_tmp/output/bundle.js'), 1); + sander.unlinkSync('test/_tmp/input/watched'); + }, + 'START', + 'BUNDLE_START', + 'BUNDLE_END', + 'END', + () => { + assert.strictEqual(run('../_tmp/output/bundle.js'), 2); + watcher.close(); + } + ]); + }); + }); + + it('watches and rebuilds transform dependencies, with transform cache opt-out for custom cache', () => { + const file = 'test/_tmp/input/watched'; + let v = 1; + return sander + .copydir('test/watch/samples/watch-files') + .to('test/_tmp/input') + .then(() => { + const watcher = rollup.watch({ + input: 'test/_tmp/input/main.js', + output: { + file: 'test/_tmp/output/bundle.js', + format: 'cjs' + }, + plugins: { + name: 'x', + buildStart() { + try { + const text = sander.readFileSync(file).toString(); + this.emitAsset('test', text); + } catch (err) { + if (err.code !== 'ENOENT') throw err; + } + }, + transform() { + this.cache.set('someValue', 'someContent'); + return { code: `export default ${v++}`, dependencies: [path.resolve(file)] }; + } + }, + watch: { chokidar } + }); + + return sequence(watcher, [ + 'START', + 'BUNDLE_START', + 'BUNDLE_END', + 'END', + () => { + assert.strictEqual(run('../_tmp/output/bundle.js'), 1); + sander.unlinkSync('test/_tmp/input/watched'); + }, + 'START', + 'BUNDLE_START', + 'BUNDLE_END', + 'END', + () => { + assert.strictEqual(run('../_tmp/output/bundle.js'), 2); + } + ]); + }); + }); + + it('watches and rebuilds asset transform dependencies', () => { + let v = 1; + return sander + .copydir('test/watch/samples/watch-files') + .to('test/_tmp/input') + .then(() => { + const watcher = rollup.watch({ + input: 'test/_tmp/input/main.js', + output: { + file: 'test/_tmp/output/bundle.js', + format: 'cjs' + }, + plugins: { + transform() { + const file = 'test/_tmp/input/watched'; + try { + const text = sander.readFileSync(file).toString(); + this.emitAsset('test', text); + } catch (err) { + if (err.code !== 'ENOENT') throw err; + this.emitAsset('test', 'test'); + } + return { + code: `export default ${v++}`, + dependencies: v === 2 ? [path.resolve(file)] : [] + }; + } + }, + watch: { chokidar } + }); + + return sequence(watcher, [ + 'START', + 'BUNDLE_START', + 'BUNDLE_END', + 'END', + () => { + assert.strictEqual(run('../_tmp/output/bundle.js'), 1); + sander.unlinkSync('test/_tmp/input/watched'); + }, + 'START', + 'BUNDLE_START', + 'BUNDLE_END', + 'END', + () => { + assert.strictEqual(run('../_tmp/output/bundle.js'), 2); + } + ]); + }); + }); + + it('watches and rebuilds transform dependencies created and removed between runs', () => { + let v = 1; + return sander + .copydir('test/watch/samples/watch-files') + .to('test/_tmp/input') + .then(() => { + const watcher = rollup.watch({ + input: 'test/_tmp/input/main.js', + output: { + file: 'test/_tmp/output/bundle.js', + format: 'cjs' + }, + plugins: { + transform() { + let dependencies = []; + if (v === 2) dependencies = ['./watched']; + return { code: `export default ${v++}`, dependencies }; + } + }, + watch: { chokidar } + }); + + return sequence(watcher, [ + 'START', + 'BUNDLE_START', + 'BUNDLE_END', + 'END', + () => { + assert.strictEqual(run('../_tmp/output/bundle.js'), 1); + sander.writeFileSync('test/_tmp/input/main.js', 'next'); + }, + 'START', + 'BUNDLE_START', + 'BUNDLE_END', + 'END', + () => { + assert.strictEqual(run('../_tmp/output/bundle.js'), 2); + sander.unlinkSync('test/_tmp/input/watched'); + }, + 'START', + 'BUNDLE_START', + 'BUNDLE_END', + 'END', + () => { + assert.strictEqual(run('../_tmp/output/bundle.js'), 3); + sander.writeFileSync('test/_tmp/input/watched', 'ignored'); + return new Promise(resolve => setTimeout(resolve, 50)); + } + ]); + }); + }); }); } }); diff --git a/test/watch/samples/transform-dependencies/asdf b/test/watch/samples/transform-dependencies/asdf deleted file mode 100644 index 5e40c087705..00000000000 --- a/test/watch/samples/transform-dependencies/asdf +++ /dev/null @@ -1 +0,0 @@ -asdf \ No newline at end of file diff --git a/test/watch/samples/transform-dependencies/main.js b/test/watch/samples/watch-files/main.js similarity index 100% rename from test/watch/samples/transform-dependencies/main.js rename to test/watch/samples/watch-files/main.js diff --git a/test/watch/samples/watch-files/watched b/test/watch/samples/watch-files/watched new file mode 100644 index 00000000000..e79c5e8f964 --- /dev/null +++ b/test/watch/samples/watch-files/watched @@ -0,0 +1 @@ +initial