Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions src/Asset.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const objectHash = require('./utils/objectHash');
const md5 = require('./utils/md5');
const isURL = require('./utils/is-url');
const sanitizeFilename = require('sanitize-filename');
const config = require('./utils/config');

let ASSET_ID = 1;

Expand Down Expand Up @@ -33,6 +34,11 @@ class Asset {
this.depAssets = new Map();
this.parentBundle = null;
this.bundles = new Set();
this.cacheData = {};
}

shouldInvalidate() {
return false;
}

async loadIfNeeded() {
Expand Down Expand Up @@ -82,6 +88,19 @@ class Asset {
.generateBundleName();
}

async getConfig(filenames) {
// Resolve the config file
let conf = await config.resolve(this.name, filenames);
if (conf) {
// Add as a dependency so it is added to the watcher and invalidates
// this asset when the config changes.
this.addDependency(conf, {includedInParent: true});
return await config.load(this.name, filenames);
}

return null;
}

mightHaveDependencies() {
return true;
}
Expand Down
77 changes: 55 additions & 22 deletions src/Bundler.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ class Bundler extends EventEmitter {

this.pending = false;
this.loadedAssets = new Map();
this.watchedAssets = new Map();
this.farm = null;
this.watcher = null;
this.hmr = null;
Expand Down Expand Up @@ -267,11 +268,35 @@ class Bundler extends EventEmitter {
let asset = this.parser.getAsset(path, pkg, this.options);
this.loadedAssets.set(path, asset);

if (this.watcher) {
this.watch(path, asset);
return asset;
}

watch(path, asset) {
if (!this.watcher) {
return;
}

if (!this.watchedAssets.has(path)) {
this.watcher.add(path);
this.watchedAssets.set(path, new Set());
}

return asset;
this.watchedAssets.get(path).add(asset);
}

unwatch(path, asset) {
if (!this.watchedAssets.has(path)) {
return;
}

let watched = this.watchedAssets.get(path);
watched.delete(asset);

if (watched.size === 0) {
this.watchedAssets.delete(path);
this.watcher.unwatch(path);
}
}

async resolveDep(asset, dep) {
Expand Down Expand Up @@ -317,7 +342,7 @@ class Bundler extends EventEmitter {

// First try the cache, otherwise load and compile in the background
let processed = this.cache && (await this.cache.read(asset.name));
if (!processed) {
if (!processed || asset.shouldInvalidate(processed.cacheData)) {
processed = await this.farm.run(asset.name, asset.package, this.options);
if (this.cache) {
this.cache.write(asset.name, processed);
Expand All @@ -339,26 +364,25 @@ class Bundler extends EventEmitter {
// Resolve and load asset dependencies
let assetDeps = await Promise.all(
dependencies.map(async dep => {
let assetDep = await this.resolveDep(asset, dep);
if (!dep.includedInParent) {
if (dep.includedInParent) {
// This dependency is already included in the parent's generated output,
// so no need to load it. We map the name back to the parent asset so
// that changing it triggers a recompile of the parent.
this.watch(dep.name, asset);
} else {
let assetDep = await this.resolveDep(asset, dep);
await this.loadAsset(assetDep);
return assetDep;
}

return assetDep;
})
);

// Store resolved assets in their original order
dependencies.forEach((dep, i) => {
asset.dependencies.set(dep.name, dep);
let assetDep = assetDeps[i];
if (dep.includedInParent) {
// This dependency is already included in the parent's generated output,
// so no need to load it. We map the name back to the parent asset so
// that changing it triggers a recompile of the parent.
this.loadedAssets.set(dep.name, asset);
} else {
asset.dependencies.set(dep.name, dep);
asset.depAssets.set(dep.name, assetDep);
if (assetDep) {
asset.depAssets.set(dep, assetDep);
}
});

Expand Down Expand Up @@ -421,8 +445,7 @@ class Bundler extends EventEmitter {

asset.parentBundle = bundle;

for (let dep of asset.dependencies.values()) {
let assetDep = asset.depAssets.get(dep.name);
for (let [dep, assetDep] of asset.depAssets) {
this.createBundleTree(assetDep, dep, bundle);
}

Expand Down Expand Up @@ -463,21 +486,31 @@ class Bundler extends EventEmitter {
unloadAsset(asset) {
this.loadedAssets.delete(asset.name);
if (this.watcher) {
this.watcher.unwatch(asset.name);
this.unwatch(asset.name, asset);

// Unwatch all included dependencies that map to this asset
for (let dep of asset.dependencies.values()) {
if (dep.includedInParent) {
this.unwatch(dep.name, asset);
}
}
}
}

async onChange(path) {
let asset = this.loadedAssets.get(path);
if (!asset) {
let assets = this.watchedAssets.get(path);
if (!assets || !assets.size) {
return;
}

this.logger.clear();
this.logger.status(emoji.progress, `Building ${asset.basename}...`);
this.logger.status(emoji.progress, `Building ${Path.basename(path)}...`);

// Add the asset to the rebuild queue, and reset the timeout.
this.buildQueue.add(asset);
for (let asset of assets) {
this.buildQueue.add(asset);
}

clearTimeout(this.rebuildTimeout);

this.rebuildTimeout = setTimeout(async () => {
Expand Down
5 changes: 2 additions & 3 deletions src/HMRServer.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,8 @@ class HMRServer {
type: 'update',
assets: assets.map(asset => {
let deps = {};
for (let dep of asset.dependencies.values()) {
let mod = asset.depAssets.get(dep.name);
deps[dep.name] = mod.id;
for (let [dep, depAsset] of asset.depAssets) {
deps[dep.name] = depAsset.id;
}

return {
Expand Down
14 changes: 12 additions & 2 deletions src/assets/JSAsset.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ const fsVisitor = require('../visitors/fs');
const babel = require('../transforms/babel');
const generate = require('babel-generator').default;
const uglify = require('../transforms/uglify');
const config = require('../utils/config');

const IMPORT_RE = /\b(?:import\b|export\b|require\s*\()/;
const GLOBAL_RE = /\b(?:process|__dirname|__filename|global|Buffer)\b/;
Expand All @@ -26,6 +25,17 @@ class JSAsset extends Asset {
this.isAstDirty = false;
this.isES6Module = false;
this.outputCode = null;
this.cacheData.env = {};
}

shouldInvalidate(cacheData) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This causes an error on existing builds (as they already have cache files) and cacheData is undefined in these files.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm yeah, but should be ok with the npm version since the parcel version is part of the cache key right? so when people update parcel, everything will be invalidated already.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ow forgot about that, yeah should be fine

for (let key in cacheData.env) {
if (cacheData.env[key] !== process.env[key]) {
return true;
}
}

return false;
}

mightHaveDependencies() {
Expand Down Expand Up @@ -55,7 +65,7 @@ class JSAsset extends Asset {
// Check if there is a babel config file. If so, determine which parser plugins to enable
this.babelConfig =
(this.package && this.package.babel) ||
(await config.load(this.name, ['.babelrc', '.babelrc.js']));
(await this.getConfig(['.babelrc', '.babelrc.js']));
if (this.babelConfig) {
const file = new BabelFile({filename: this.name});
options.plugins.push(...file.parserOpts.plugins);
Expand Down
3 changes: 1 addition & 2 deletions src/assets/LESSAsset.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
const CSSAsset = require('./CSSAsset');
const config = require('../utils/config');
const localRequire = require('../utils/localRequire');
const promisify = require('../utils/promisify');

Expand All @@ -11,7 +10,7 @@ class LESSAsset extends CSSAsset {

let opts =
this.package.less ||
(await config.load(this.name, ['.lessrc', '.lessrc.js'])) ||
(await this.getConfig(['.lessrc', '.lessrc.js'])) ||
{};
opts.filename = this.name;
opts.plugins = (opts.plugins || []).concat(urlPlugin(this));
Expand Down
3 changes: 1 addition & 2 deletions src/assets/SASSAsset.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
const CSSAsset = require('./CSSAsset');
const config = require('../utils/config');
const localRequire = require('../utils/localRequire');
const promisify = require('../utils/promisify');
const path = require('path');
Expand All @@ -12,7 +11,7 @@ class SASSAsset extends CSSAsset {

let opts =
this.package.sass ||
(await config.load(this.name, ['.sassrc', '.sassrc.js'])) ||
(await this.getConfig(['.sassrc', '.sassrc.js'])) ||
{};
opts.includePaths = (opts.includePaths || []).concat(
path.dirname(this.name)
Expand Down
3 changes: 1 addition & 2 deletions src/assets/StylusAsset.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
const CSSAsset = require('./CSSAsset');
const config = require('../utils/config');
const localRequire = require('../utils/localRequire');
const Resolver = require('../Resolver');

Expand All @@ -11,7 +10,7 @@ class StylusAsset extends CSSAsset {
let stylus = await localRequire('stylus', this.name);
let opts =
this.package.stylus ||
(await config.load(this.name, ['.stylusrc', '.stylusrc.js']));
(await this.getConfig(['.stylusrc', '.stylusrc.js']));
let style = stylus(code, opts);
style.set('filename', this.name);
style.set('include css', true);
Expand Down
3 changes: 1 addition & 2 deletions src/assets/TypeScriptAsset.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
const JSAsset = require('./JSAsset');
const config = require('../utils/config');
const localRequire = require('../utils/localRequire');

class TypeScriptAsset extends JSAsset {
Expand All @@ -14,7 +13,7 @@ class TypeScriptAsset extends JSAsset {
fileName: this.basename
};

let tsconfig = await config.load(this.name, ['tsconfig.json']);
let tsconfig = await this.getConfig(['tsconfig.json']);

// Overwrite default if config is found
if (tsconfig) {
Expand Down
4 changes: 1 addition & 3 deletions src/packagers/JSPackager.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,7 @@ class JSPackager extends Packager {
}

let deps = {};
for (let dep of asset.dependencies.values()) {
let mod = asset.depAssets.get(dep.name);

for (let [dep, mod] of asset.depAssets) {
// For dynamic dependencies, list the child bundles to load along with the module id
if (dep.dynamic && this.bundle.childBundles.has(mod.parentBundle)) {
let bundles = [path.basename(mod.parentBundle.name)];
Expand Down
3 changes: 1 addition & 2 deletions src/transforms/babel.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
const babel = require('babel-core');
const config = require('../utils/config');

module.exports = async function(asset) {
if (!await shouldTransform(asset)) {
Expand Down Expand Up @@ -40,6 +39,6 @@ async function shouldTransform(asset) {
return true;
}

let babelrc = await config.resolve(asset.name, ['.babelrc', '.babelrc.js']);
let babelrc = await asset.getConfig(['.babelrc', '.babelrc.js']);
return !!babelrc;
}
3 changes: 1 addition & 2 deletions src/transforms/uglify.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
const {minify} = require('uglify-es');
const config = require('../utils/config');

module.exports = async function(asset) {
await asset.parseIfNeeded();

// Convert AST into JS
let code = asset.generate().js;

let customConfig = await config.load(asset.name, ['.uglifyrc']);
let customConfig = await asset.getConfig(['.uglifyrc']);
let options = {
warnings: true,
mangle: {
Expand Down
2 changes: 0 additions & 2 deletions src/utils/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,6 @@ async function resolve(filepath, filenames, root = path.parse(filepath).root) {
existsCache.set(file, true);
return file;
}

existsCache.set(file, false);
}

return resolve(filepath, filenames, root);
Expand Down
1 change: 1 addition & 0 deletions src/visitors/globals.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ module.exports = {
let val = types.valueToNode(process.env[key.value]);
morph(node, val);
asset.isAstDirty = true;
asset.cacheData.env[key.value] = process.env[key.value];
}
}
},
Expand Down
3 changes: 2 additions & 1 deletion src/worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ exports.run = async function(path, pkg, options, callback) {
callback(null, {
dependencies: Array.from(asset.dependencies.values()),
generated: asset.generated,
hash: asset.hash
hash: asset.hash,
cacheData: asset.cacheData
});
} catch (err) {
let returned = err;
Expand Down
7 changes: 7 additions & 0 deletions test/integration/babel/.babelrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"presets": [["env", {
"targets": {
"browsers": ["last 2 Chrome versions"]
}
}]]
}
6 changes: 6 additions & 0 deletions test/integration/babel/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"extends": "../.eslintrc.json",
"parserOptions": {
"sourceType": "module"
}
}
1 change: 1 addition & 0 deletions test/integration/babel/foo.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default class Foo {}
4 changes: 4 additions & 0 deletions test/integration/babel/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import Foo from './foo';

export {Foo};
export class Bar {}
Loading