Skip to content

Commit

Permalink
Detect files added to/removed from directories. (#2615)
Browse files Browse the repository at this point in the history
* Add new files to the main bundle tree. And remove them when they're removed (#667)

* Fixed the failing tests because entryFiles can be null.

* Added tests for detecting dir changes and rebuilds.

* Fixed eslint problems.

* Fixed tests that depended on old utils.js file.
  • Loading branch information
sainthkh authored and DeMoorJasper committed Feb 13, 2019
1 parent 01174eb commit a2e38f9
Show file tree
Hide file tree
Showing 3 changed files with 175 additions and 2 deletions.
45 changes: 43 additions & 2 deletions packages/core/parcel-bundler/src/Bundler.js
Expand Up @@ -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,
Expand All @@ -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);
Expand Down Expand Up @@ -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)), [])
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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;
Expand All @@ -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);
Expand Down
129 changes: 129 additions & 0 deletions 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, '<html></html>');

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);
});
});
});
@@ -0,0 +1,3 @@
module.exports = function() {
return 'hello';
}

0 comments on commit a2e38f9

Please sign in to comment.