diff --git a/packages/core/parcel-bundler/src/Bundler.js b/packages/core/parcel-bundler/src/Bundler.js index 98238faa5a2..f30ae772a52 100644 --- a/packages/core/parcel-bundler/src/Bundler.js +++ b/packages/core/parcel-bundler/src/Bundler.js @@ -19,7 +19,7 @@ const installPackage = require('./utils/installPackage'); const bundleReport = require('./utils/bundleReport'); const prettifyTime = require('./utils/prettifyTime'); const getRootDir = require('./utils/getRootDir'); -const {glob} = require('./utils/glob'); +const {glob, isGlob} = require('./utils/glob'); /** * The Bundler is the main entry point. It resolves and loads assets, @@ -29,7 +29,9 @@ class Bundler extends EventEmitter { constructor(entryFiles, options = {}) { super(); - this.entryFiles = this.normalizeEntries(entryFiles); + entryFiles = this.normalizeEntries(entryFiles); + this.watchedGlobs = entryFiles.filter(entry => isGlob(entry)); + this.entryFiles = this.findEntryFiles(entryFiles); this.options = this.normalizeOptions(options); this.resolver = new Resolver(this.options); @@ -82,6 +84,10 @@ class Bundler extends EventEmitter { entryFiles = [process.cwd()]; } + return entryFiles; + } + + findEntryFiles(entryFiles) { // Match files as globs return entryFiles .reduce((p, m) => p.concat(glob.sync(m)), []) @@ -372,7 +378,12 @@ class Bundler extends EventEmitter { if (process.env.NODE_ENV === 'test' && !this.watcher.ready) { await new Promise(resolve => this.watcher.once('ready', resolve)); } + this.watchedGlobs.forEach(glob => { + this.watcher.add(glob); + }); + this.watcher.on('add', this.onAdd.bind(this)); this.watcher.on('change', this.onChange.bind(this)); + this.watcher.on('unlink', this.onUnlink.bind(this)); } if (this.options.hmr) { @@ -758,7 +769,24 @@ class Bundler extends EventEmitter { } } + async onAdd(path) { + path = Path.join(process.cwd(), path); + + let asset = this.parser.getAsset(path, this.options); + this.loadedAssets.set(path, asset); + + this.entryAssets.add(asset); + + await this.watch(path, asset); + this.onChange(path); + } + async onChange(path) { + // The path to the newly-added items are not absolute. + if (!Path.isAbsolute(path)) { + path = Path.resolve(process.cwd(), path); + } + let assets = this.watchedAssets.get(path); if (!assets || !assets.size) { return; @@ -779,6 +807,19 @@ class Bundler extends EventEmitter { }, 100); } + async onUnlink(path) { + // The path to the newly-added items are not absolute. + if (!Path.isAbsolute(path)) { + path = Path.resolve(process.cwd(), path); + } + + let asset = this.getLoadedAsset(path); + this.entryAssets.delete(asset); + this.unloadAsset(asset); + + this.bundle(); + } + middleware() { this.bundle(); return Server.middleware(this); diff --git a/packages/core/parcel-bundler/test/detect-dir-change.js b/packages/core/parcel-bundler/test/detect-dir-change.js new file mode 100644 index 00000000000..7dd8d9a847f --- /dev/null +++ b/packages/core/parcel-bundler/test/detect-dir-change.js @@ -0,0 +1,129 @@ +const assert = require('assert'); +const Path = require('path'); +const fs = require('@parcel/fs'); +const {sleep, rimraf, ncp, bundler} = require('@parcel/test-utils'); + +let inputRoot = Path.join(__dirname, 'input', 'detect-dir-change'); + +describe('detect directory changes', function() { + beforeEach(async function() { + await rimraf(inputRoot); + await fs.mkdirp(inputRoot); + await ncp(__dirname + '/integration/detect-dir-change/', inputRoot); + }); + + describe('when a file matches a glob', async function() { + it('should rebuild when the file is added', async function() { + // 1. Bundle + let b = bundler('test/input/detect-dir-change/src/*.js', { + watch: true + }); + await b.bundle(); + + assert(await fs.exists(Path.join(__dirname, '/dist/', 'index.js'))); + + // We'll check if bundle is called with this listener. + let bundled = false; + b.on('bundled', () => { + bundled = true; + }); + + // 2. Write file and assert. + await fs.writeFile( + Path.join(inputRoot, './src/app.js'), + "module.exports = function () { return 'app' }" + ); + + await sleep(1000); + + assert(bundled); + }); + + it('should rebuild when the file is removed', async function() { + // 1. Add file and check the result bundle has all files. + let filePath = Path.join(inputRoot, './src/app.js'); + await fs.writeFile( + filePath, + "module.exports = function () { return 'app' }" + ); + + let b = bundler('test/input/detect-dir-change/src/*.js', { + watch: true + }); + + let bundle = await b.bundle(); + + let childBundleNames = Array.from(bundle.childBundles.values()).map( + bundle => Path.basename(bundle.name) + ); + + assert(childBundleNames.includes('index.js')); + assert(childBundleNames.includes('app.js')); + + // We'll check if bundle is called with this listener. + let bundled = false; + b.on('bundled', () => { + bundled = true; + }); + + // 2. Check dist file removed correctly. + await fs.unlink(filePath); + + await sleep(1000); + assert(bundled); + }); + }); + + describe('when a file does not match a glob', async function() { + it('should not rebuild when the file is added', async function() { + // 1. Bundle + let b = bundler('test/input/detect-dir-change/src/*.js', { + watch: true + }); + await b.bundle(); + + assert(await fs.exists(Path.join(__dirname, '/dist/', 'index.js'))); + + // We'll check if bundle is called with this listener. + let bundled = false; + b.on('bundled', () => { + bundled = true; + }); + + // 2. Create unrelated file and assert + await fs.writeFile( + Path.join(inputRoot, './src/app2.ts'), + "module.exports = function () { return 'app' }" + ); + + await sleep(1000); + assert(!bundled); + }); + + it('should not rebuild when the file is removed', async function() { + // 1. Add file and check bundle has all files. + let filePath = Path.join(inputRoot, './src/test.html'); + await fs.writeFile(filePath, ''); + + let b = bundler('test/input/detect-dir-change/src/*.js', { + watch: true + }); + + let bundle = await b.bundle(); + + assert(Path.basename(bundle.name), 'index.js'); + + // We'll check if bundle is called with this listener. + let bundled = false; + b.on('bundled', () => { + bundled = true; + }); + + // 2. Remove file and assert that bundle() isn't called. + await fs.unlink(filePath); + + await sleep(1000); + assert(!bundled); + }); + }); +}); diff --git a/packages/core/parcel-bundler/test/integration/detect-dir-change/src/index.js b/packages/core/parcel-bundler/test/integration/detect-dir-change/src/index.js new file mode 100644 index 00000000000..dc1d27ea630 --- /dev/null +++ b/packages/core/parcel-bundler/test/integration/detect-dir-change/src/index.js @@ -0,0 +1,3 @@ +module.exports = function() { + return 'hello'; +} \ No newline at end of file