Skip to content

Commit

Permalink
Merge pull request #3215 from parcel-bundler/plugin-version-invalidation
Browse files Browse the repository at this point in the history
Plugin version invalidation
  • Loading branch information
padmaia committed Jul 11, 2019
2 parents 8b7f7d1 + 48eda18 commit 1e0d685
Show file tree
Hide file tree
Showing 10 changed files with 205 additions and 98 deletions.
3 changes: 2 additions & 1 deletion packages/core/core/src/ConfigLoader.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
// @flow
import nullthrows from 'nullthrows';

import type {ConfigRequest, ParcelOptions} from '@parcel/types';
import type {ParcelOptions} from '@parcel/types';
import {md5FromString} from '@parcel/utils';

import Config from './public/Config';
import type ParcelConfig from './ParcelConfig';
import loadParcelConfig from './loadParcelConfig';
import loadPlugin from './loadParcelPlugin';
import type {ConfigRequest} from './types';

export default class ConfigLoader {
options: ParcelOptions;
Expand Down
15 changes: 13 additions & 2 deletions packages/core/core/src/Graph.js
Original file line number Diff line number Diff line change
Expand Up @@ -159,8 +159,19 @@ export default class Graph<TNode: Node> {

// Update a node's downstream nodes making sure to prune any orphaned branches
// Also keeps track of all added and removed edges and nodes
replaceNodesConnectedTo(fromNode: TNode, toNodes: Array<TNode>): void {
let childrenToRemove = new Set(this.outboundEdges.get(fromNode.id));
replaceNodesConnectedTo(
fromNode: TNode,
toNodes: Array<TNode>,
replaceFilter?: TNode => boolean
): void {
let outboundEdges = this.outboundEdges.get(fromNode.id);
let childrenToRemove = new Set(
replaceFilter
? [...outboundEdges].filter(toNodeId =>
replaceFilter(nullthrows(this.nodes.get(toNodeId)))
)
: outboundEdges
);
for (let toNode of toNodes) {
let existingNode = this.getNode(toNode.id);
if (!existingNode) {
Expand Down
132 changes: 104 additions & 28 deletions packages/core/core/src/RequestGraph.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,11 @@ import invariant from 'assert';
//$FlowFixMe
import {isMatch} from 'micromatch';
import nullthrows from 'nullthrows';
import path from 'path';

import {PromiseQueue, md5FromString} from '@parcel/utils';
import type {
AssetRequest,
ConfigRequest,
FilePath,
Glob,
ParcelOptions
} from '@parcel/types';
import {localResolve} from '@parcel/local-require';
import {PromiseQueue, md5FromString, md5FromObject} from '@parcel/utils';
import type {AssetRequest, FilePath, Glob, ParcelOptions} from '@parcel/types';
import type {Event} from '@parcel/watcher';
import WorkerFarm from '@parcel/workers';

Expand All @@ -24,8 +20,10 @@ import ResolverRunner from './ResolverRunner';
import type InternalAsset from './Asset';
import type {
AssetRequestNode,
ConfigRequest,
ConfigRequestNode,
DepPathRequestNode,
DepVersionRequestNode,
GlobNode,
NodeId,
RequestGraphNode,
Expand Down Expand Up @@ -68,6 +66,12 @@ const nodeFromConfigRequest = (configRequest: ConfigRequest) => ({
value: configRequest
});

const nodeFromDepVersionRequest = depVersionRequest => ({
id: md5FromObject(depVersionRequest),
type: 'dep_version_request',
value: depVersionRequest
});

const nodeFromFilePath = (filePath: string) => ({
id: filePath,
type: 'file',
Expand All @@ -84,7 +88,10 @@ export default class RequestGraph extends Graph<RequestGraphNode> {
// $FlowFixMe
inProgress: Map<NodeId, Promise<any>> = new Map();
invalidNodes: Map<NodeId, RequestNode> = new Map();
runTransform: TransformationOpts => Promise<Array<InternalAsset>>;
runTransform: TransformationOpts => Promise<{
assets: Array<InternalAsset>,
configRequests: Array<ConfigRequest>
}>;
loadConfigHandle: () => Promise<Config>;
resolverRunner: ResolverRunner;
configLoader: ConfigLoader;
Expand All @@ -95,6 +102,7 @@ export default class RequestGraph extends Graph<RequestGraphNode> {
config: ParcelConfig;
options: ParcelOptions;
globNodes: Array<GlobNode>;
depVersionRequestNodes: Array<DepVersionRequestNode>;

constructor({
onAssetRequestComplete,
Expand All @@ -107,6 +115,7 @@ export default class RequestGraph extends Graph<RequestGraphNode> {
this.options = options;
this.queue = new PromiseQueue();
this.globNodes = [];
this.depVersionRequestNodes = [];
this.onAssetRequestComplete = onAssetRequestComplete;
this.onDepPathRequestComplete = onDepPathRequestComplete;
this.config = config;
Expand Down Expand Up @@ -146,6 +155,8 @@ export default class RequestGraph extends Graph<RequestGraphNode> {
this.processNode(node);
if (node.type === 'glob') {
this.globNodes.push(node);
} else if (node.type === 'dep_version_request') {
this.depVersionRequestNodes.push(node);
}
return super.addNode(node);
}
Expand Down Expand Up @@ -188,6 +199,9 @@ export default class RequestGraph extends Graph<RequestGraphNode> {
case 'config_request':
promise = this.runConfigRequest(requestNode);
break;
case 'dep_version_request':
promise = this.runDepVersionRequest(requestNode);
break;
default:
// Do nothing
}
Expand All @@ -202,7 +216,7 @@ export default class RequestGraph extends Graph<RequestGraphNode> {
} catch (e) {
// Do nothing
// Main tasks will be caught by the queue
// Sun tasks will end up rejecting the main task promise
// Sub tasks will end up rejecting the main task promise
}
}
}
Expand All @@ -211,7 +225,7 @@ export default class RequestGraph extends Graph<RequestGraphNode> {
try {
let start = Date.now();
let request = requestNode.value;
let assets = await this.runTransform({
let {assets, configRequests} = await this.runTransform({
request,
loadConfig: this.loadConfigHandle,
parentNodeId: requestNode.id,
Expand All @@ -223,6 +237,16 @@ export default class RequestGraph extends Graph<RequestGraphNode> {
asset.stats.time = time;
}

let configRequestNodes = configRequests.map(configRequest => {
let id = nodeFromConfigRequest(configRequest).id;
return nullthrows(this.getNode(id));
});
this.replaceNodesConnectedTo(
requestNode,
configRequestNodes,
node => node.type === 'config_request'
);

return assets;
} catch (e) {
// TODO: add connectedFiles even if it failed so we can try a rebuild if those files change
Expand All @@ -247,13 +271,37 @@ export default class RequestGraph extends Graph<RequestGraphNode> {

async loadConfig(configRequest: ConfigRequest, parentNodeId: NodeId) {
let configRequestNode = nodeFromConfigRequest(configRequest);
if (!this.hasNode(configRequestNode.id)) this.addNode(configRequestNode);
if (!this.hasEdge(parentNodeId, configRequestNode.id))
this.addEdge(parentNodeId, configRequestNode.id);

let config = await this.getSubTaskResult(configRequestNode);
if (!this.hasNode(configRequestNode.id)) {
this.addNode(configRequestNode);
}
this.addEdge(parentNodeId, configRequestNode.id);

let config = nullthrows(await this.getSubTaskResult(configRequestNode));
invariant(config.devDeps != null);

let depVersionRequestNodes = [];
for (let [moduleSpecifier] of config.devDeps) {
let depVersionRequest = {
moduleSpecifier,
resolveFrom: path.dirname(nullthrows(config.resolvedPath)) // TODO: resolveFrom should be nearest package boundary
};
let depVersionRequestNode = nodeFromDepVersionRequest(depVersionRequest);
if (!this.hasNode(depVersionRequestNode.id)) {
this.addNode(depVersionRequestNode);
}
this.addEdge(configRequestNode.id, depVersionRequestNode.id);
depVersionRequestNodes.push(
nullthrows(this.getNode(depVersionRequestNode.id))
);

// TODO: add DepVersionRequests to invalidate when plugins change
let version = await this.getSubTaskResult(depVersionRequestNode);
config.setDevDep(depVersionRequest.moduleSpecifier, version);
}
this.replaceNodesConnectedTo(
configRequestNode,
depVersionRequestNodes,
node => node.type === 'dep_version_request'
);

return config;
}
Expand All @@ -277,11 +325,30 @@ export default class RequestGraph extends Graph<RequestGraphNode> {
invalidationNodes.push(nodeFromGlob(config.watchGlob));
}

this.replaceNodesConnectedTo(configRequestNode, invalidationNodes);
this.replaceNodesConnectedTo(
configRequestNode,
invalidationNodes,
node => node.type === 'file' || node.type === 'glob'
);

return config;
}

async runDepVersionRequest(requestNode: DepVersionRequestNode) {
let {value: request} = requestNode;
let {moduleSpecifier, resolveFrom} = request;
let [, resolvedPkg] = await localResolve(
`${moduleSpecifier}/package.json`,
`${resolveFrom}/index`
);

// TODO: Figure out how to handle when local plugin packages change, since version won't be enough
let version = nullthrows(resolvedPkg).version;
request.result = version;

return version;
}

addSubRequest(subRequestNode: SubRequestNode, nodeId: NodeId) {
if (!this.nodes.has(subRequestNode.id)) {
this.addNode(subRequestNode);
Expand All @@ -295,7 +362,8 @@ export default class RequestGraph extends Graph<RequestGraphNode> {
return subRequestNode;
}

async getSubTaskResult(node: SubRequestNode) {
//$FlowFixMe
async getSubTaskResult(node: SubRequestNode): any {
let result;
if (this.inProgress.has(node.id)) {
result = await this.inProgress.get(node.id);
Expand All @@ -308,8 +376,10 @@ export default class RequestGraph extends Graph<RequestGraphNode> {

getResultFromGraph(subRequestNode: SubRequestNode) {
let node = nullthrows(this.getNode(subRequestNode.id));
invariant(node.type === 'config_request');
return nullthrows(node.value.result);
invariant(
node.type === 'config_request' || node.type === 'dep_version_request'
);
return node.value.result;
}

connectFile(requestNode: RequestNode, filePath: FilePath) {
Expand Down Expand Up @@ -340,7 +410,8 @@ export default class RequestGraph extends Graph<RequestGraphNode> {
case 'dep_path_request':
this.invalidNodes.set(node.id, node);
break;
case 'config_request': {
case 'config_request':
case 'dep_version_request': {
this.invalidNodes.set(node.id, node);
let mainRequestNode = nullthrows(this.getMainRequestNode(node));
this.invalidNodes.set(mainRequestNode.id, mainRequestNode);
Expand All @@ -354,18 +425,23 @@ export default class RequestGraph extends Graph<RequestGraphNode> {
}

getMainRequestNode(node: SubRequestNode) {
if (node.type === 'config_request') {
let [mainRequestNode] = this.getNodesConnectedTo(node);
invariant(
mainRequestNode.type !== 'file' && mainRequestNode.type !== 'glob'
);
return mainRequestNode;
let [parentNode] = this.getNodesConnectedTo(node);
if (parentNode.type === 'config_request') {
[parentNode] = this.getNodesConnectedTo(parentNode);
}
invariant(parentNode.type !== 'file' && parentNode.type !== 'glob');
return parentNode;
}

// TODO: add edge types to make invalidation more flexible and less precarious
respondToFSEvents(events: Array<Event>) {
for (let {path, type} of events) {
if (path === this.options.lockFile) {
for (let depVersionRequestNode of this.depVersionRequestNodes) {
this.invalidateNode(depVersionRequestNode);
}
}

let node = this.getNode(path);

let connectedNodes =
Expand Down

0 comments on commit 1e0d685

Please sign in to comment.