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

Detect files added to/removed from directories. #2615

Merged
merged 10 commits into from Feb 13, 2019
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
130 changes: 130 additions & 0 deletions packages/core/parcel-bundler/test/detect-dir-change.js
@@ -0,0 +1,130 @@
const assert = require('assert');
const Path = require('path');
const fs = require('@parcel/fs');
const {rimraf, ncp, bundler} = require('./utils');
const {sleep} = 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';
}