Skip to content

Commit

Permalink
Merged in workerize-transformer (pull request #9)
Browse files Browse the repository at this point in the history
Implement workerize transformer

Approved-by: Will Binns-Smith
  • Loading branch information
padmaia committed Jan 17, 2020
2 parents f62f17b + bc83a7b commit 8339e3b
Show file tree
Hide file tree
Showing 11 changed files with 219 additions and 10 deletions.
56 changes: 47 additions & 9 deletions packages/core/core/src/AssetGraph.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import type {
AssetGraphNode,
AssetGroup,
AssetGroupNode,
AssetNode,
Dependency,
DependencyNode,
NodeId,
Expand Down Expand Up @@ -135,6 +136,7 @@ export default class AssetGraph extends Graph<AssetGraphNode> {
let existingNode = this.getNode(node.id);
if (
INCOMPLETE_TYPES.includes(node.type) &&
!node.complete &&
!node.deferred &&
(!existingNode || existingNode.deferred)
) {
Expand Down Expand Up @@ -209,17 +211,53 @@ export default class AssetGraph extends Graph<AssetGraphNode> {
return;
}

let assetNodes = assets.map(asset => nodeFromAsset(asset));
this.replaceNodesConnectedTo(assetGroupNode, assetNodes);
let dependentAssetKeys = [];
let assetTuples: Array<[AssetNode, Array<Asset>, boolean]> = [];
for (let asset of assets) {
let isDirect = dependentAssetKeys.includes(asset.uniqueKey)
? false
: true;

let dependentAssets = [];
for (let dep of asset.dependencies.values()) {
let dependentAsset = assets.find(
a => a.uniqueKey === dep.moduleSpecifier,
);
if (dependentAsset) {
dependentAssetKeys.push(dependentAsset.uniqueKey);
dependentAssets.push(dependentAsset);
}
}
assetTuples.push([nodeFromAsset(asset), dependentAssets, isDirect]);
}

for (let assetNode of assetNodes) {
let depNodes = [];
invariant(assetNode.type === 'asset');
for (let dep of assetNode.value.dependencies.values()) {
let depNode = nodeFromDep(dep);
depNodes.push(this.nodes.get(depNode.id) || depNode);
this.replaceNodesConnectedTo(
assetGroupNode,
assetTuples.filter(a => a[2]).map(a => a[0]),
);
for (let [assetNode, dependentAssets] of assetTuples) {
this.resolveAsset(assetNode, dependentAssets);
}
}

resolveAsset(assetNode: AssetNode, dependentAssets: Array<Asset>) {
let depNodes = [];
let depNodesWithAssets = [];
for (let dep of assetNode.value.dependencies.values()) {
let depNode = nodeFromDep(dep);
depNodes.push(this.nodes.get(depNode.id) || depNode);
let dependentAsset = dependentAssets.find(
a => a.uniqueKey === dep.moduleSpecifier,
);
if (dependentAsset) {
depNode.complete = true;
depNodesWithAssets.push([depNode, nodeFromAsset(dependentAsset)]);
}
this.replaceNodesConnectedTo(assetNode, depNodes);
}
this.replaceNodesConnectedTo(assetNode, depNodes);

for (let [depNode, dependentAssetNode] of depNodesWithAssets) {
this.replaceNodesConnectedTo(depNode, [dependentAssetNode]);
}
}

Expand Down
1 change: 1 addition & 0 deletions packages/core/core/src/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ export type DependencyNode = {|
id: string,
type: 'dependency',
value: Dependency,
complete?: boolean,
|};

export type RootNode = {|id: string, +type: 'root', value: string | null|};
Expand Down
2 changes: 2 additions & 0 deletions packages/core/utils/src/generateBundleReport.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export type BundleReport = {|
time: number,
largestAssets: Array<{|
filePath: string,
uniqueKey: ?string,
size: number,
time: number,
|}>,
Expand Down Expand Up @@ -41,6 +42,7 @@ export default function generateBundleReport(
time: bundle.stats.time,
largestAssets: assets.slice(0, largestAssetCount).map(asset => ({
filePath: asset.filePath,
uniqueKey: asset.uniqueKey,
size: asset.stats.size,
time: asset.stats.time,
})),
Expand Down
9 changes: 9 additions & 0 deletions packages/examples/workerize/.parcelrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"extends": "@parcel/config-default",
"transforms": {
"*.worker.js": [
"@parcel/transformer-workerize",
"..."
]
}
}
21 changes: 21 additions & 0 deletions packages/examples/workerize/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"name": "@parcel/workerize-example",
"version": "2.0.0-alpha.3.2",
"license": "MIT",
"private": true,
"scripts": {
"start": "parcel src/index.js",
"build": "parcel build src/index.js",
"clean-demo": "rm -rf .parcel-cache dist && yarn demo"
},
"devDependencies": {
"@parcel/babel-register": "^2.0.0-alpha.3.1",
"@parcel/core": "^2.0.0-alpha.3.2"
},
"main": "dist/index.js",
"dependencies": {
"lodash": "^4.17.11",
"react": "^16.6.3",
"react-dom": "^16.6.3"
}
}
3 changes: 3 additions & 0 deletions packages/examples/workerize/src/foo.worker.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export function foo() {
return 'hello!';
}
7 changes: 7 additions & 0 deletions packages/examples/workerize/src/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import worker from './foo.worker';

let instance = worker();

(async function() {
console.log(await instance.foo());
})();
4 changes: 3 additions & 1 deletion packages/reporters/cli/src/BundleReport.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,9 @@ export default function BundleReport(
for (let asset of bundle.largestAssets) {
// Add a row for the asset.
rows.push(
<Row key={`bundle:${bundle.filePath}:asset:${asset.filePath}`}>
<Row
key={`bundle:${bundle.filePath}:asset:${asset.filePath}:uniqueKey:${asset.uniqueKey}`}
>
<Cell>
{asset == bundle.largestAssets[bundle.largestAssets.length - 1]
? '└── '
Expand Down
13 changes: 13 additions & 0 deletions packages/transformers/workerize/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"name": "@parcel/transformer-workerize",
"version": "2.0.0-alpha.3.1",
"engines": {
"node": ">= 10.0.0",
"parcel": "^2.0.0-alpha.1.1"
},
"main": "lib/WorkerizeTransformer.js",
"source": "src/WorkerizeTransformer.js",
"dependencies": {
"@parcel/plugin": "^2.0.0-alpha.3.1"
}
}
49 changes: 49 additions & 0 deletions packages/transformers/workerize/src/WorkerizeTransformer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import {Transformer} from '@parcel/plugin';
import path from 'path';

import {generateMainCode, generateWorkerCode} from './generate';

export default new Transformer({
async transform({asset}) {
let code = await asset.getCode();

let exports = getExports(code);
let originalSpecifier = './' + path.basename(asset.filePath);
let generatedCode;
if (asset.env.context === 'browser') {
generatedCode = generateMainCode(originalSpecifier, exports);
asset.setCode(generatedCode);
return [asset];
} else if (asset.env.context === 'web-worker') {
generatedCode = generateWorkerCode(originalSpecifier);
asset.setCode(generatedCode);
let secondAsset = {
type: 'js',
code,
uniqueKey: 'original-worker',
};
return [asset, secondAsset];
}
},
});

function getExports(code) {
// match default
let exports = [...code.matchAll(/^(\s*)export\s+default\s+/m)].length
? ['default']
: [];
// match named
exports = exports.concat(
[
...code.matchAll(
/^(\s*)export\s+((?:async\s*)?function(?:\s*\*)?|const|let|var)(\s+)([a-zA-Z$_][a-zA-Z0-9$_]*)/gm,
),
].map(arr => arr[4]),
);

if (exports.length === 0) {
throw new Error('Cannot workerize a worker with no exports');
}

return exports;
}
64 changes: 64 additions & 0 deletions packages/transformers/workerize/src/generate.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
export function generateMainCode(originalSpecifier, methods) {
let methodsStr = `['${methods.join("','")}']`;
return `
const methods = ${methodsStr};
export default function createWorker() {
let counter = 0;
let callbacks = {};
let worker = new Worker('${originalSpecifier}');
let term = worker.terminate;
worker.kill = signal => {
worker.postMessage({ type: 'KILL', signal });
setTimeout(worker.terminate);
};
worker.terminate = () => {
term.call(worker);
};
worker.call = (method, params) => new Promise( (resolve, reject) => {
let id = 'rpc' + ++counter;
callbacks[id] = [resolve, reject];
worker.postMessage({ type: 'RPC', id, method, params });
});
for (let methodName of methods) {
worker[methodName] = function() {
return worker.call(methodName, [].slice.call(arguments));
};
}
worker.addEventListener('message', ({ data }) => {
let id = data.id;
let callback = callbacks[id];
if (callback==null) throw Error('Unknown callback ' + id);
delete callbacks[id];
if (data.error) callback[1](Error(data.error));
else callback[0](data.result);
})
return worker;
}
`;
}

export function generateWorkerCode() {
return `
import * as worker from 'original-worker';
export const messageHandler = ({ data }) => {
const { id, method, params } = data;
if (data.type!=='RPC' || id==null) return; // ? Do we need this?
if (data.method) {
let method = worker[data.method];
if (method==null) {
self.postMessage({ type: 'RPC', id, error: 'NO_SUCH_METHOD' });
}
else {
Promise.resolve()
.then( () => method.apply(null, data.params) )
.then( result => { self.postMessage({ type: 'RPC', id, result }); })
.catch( err => { self.postMessage({ type: 'RPC', id, error: ''+err }); });
}
}
};
self.addEventListener('message', messageHandler);
`;
}

0 comments on commit 8339e3b

Please sign in to comment.