Skip to content

Commit

Permalink
WIP 4
Browse files Browse the repository at this point in the history
  • Loading branch information
overlookmotel committed Dec 27, 2022
1 parent 4a9713a commit c181b04
Show file tree
Hide file tree
Showing 7 changed files with 297 additions and 38 deletions.
16 changes: 16 additions & 0 deletions lib/constants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
'use strict';

module.exports = {
// Message types
INCOMING: 1,
SUCCESS: 2,
FAIL: 3,
NEXT: 4,
// Hook types
INIT: 1,
RESOLVE: 2,
LOAD: 3,
// Error types
ERROR: 1,
OTHER: 2
};
54 changes: 47 additions & 7 deletions lib/createLoader.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,60 @@
'use strict';

// Modules
const pathJoin = require('path').join;
// Imports
const {createChannels, defer, unwrapError} = require('./utils.js'),
{RESOLVE, LOAD, FAIL} = require('./constants.js');

// Exports

module.exports = function createLoader(loaderUrl) {
let readyDeferred = defer(),
resolve,
load;

return {
globalPreload({port}) { // eslint-disable-line no-unused-vars
async resolve(specifier, context, nextResolve) {
if (readyDeferred) await readyDeferred.promise;
if (resolve) return resolve(specifier, context, nextResolve);
return nextResolve(specifier, context);
},

async load(url, context, nextLoad) {
if (readyDeferred) await readyDeferred.promise;
if (load) return load(url, context, nextLoad);
return nextLoad(url);
},

globalPreload({port}) {
const {listen, addCallback, createChannel} = createChannels(port, 'outer');

addCallback(0, (type, data) => {
if (type === FAIL) {
readyDeferred.reject(unwrapError(data));
} else {
if (data.resolve) resolve = createHandler(RESOLVE);
if (data.load) load = createHandler(LOAD);

readyDeferred.resolve();
}

readyDeferred = undefined;
port.unref();

return true;
});

function createHandler(hook) {
return (arg1, arg2, next) => createChannel(
hook, [arg1, arg2], (inType, inData) => next(...inData)
);
}

listen();

return [
`console.log('loaderUrl in preload:', ${JSON.stringify(loaderUrl)});`,
"const {createRequire} = getBuiltin('module');",
`const require = createRequire(${JSON.stringify(__filename)});`,
`const importSync = require(${JSON.stringify(pathJoin(__filename, '../importSync.js'))});`,
`const loader = importSync(${JSON.stringify(loaderUrl)});`,
'console.log("loader:", loader);'
`require('./preload.js')(${JSON.stringify(loaderUrl)}, port, getBuiltin);`
].join('\n');
}
};
Expand Down
13 changes: 0 additions & 13 deletions lib/importSync.js

This file was deleted.

48 changes: 48 additions & 0 deletions lib/preload.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
'use strict';

// Modules
const SynchronousWorker = require('@matteo.collina/worker');

// Imports
const {createChannels, wrapError} = require('./utils.js'),
{RESOLVE, SUCCESS, FAIL, INIT, NEXT} = require('./constants.js');

// Exports

module.exports = function preload(loaderUrl, port, getBuiltin) {
const {listen, message, post} = createChannels(port, 'inner');
port.unref();

let loader;
try {
loader = importSync(loaderUrl);
} catch (err) {
post(0, FAIL, INIT, wrapError(err));
return;
}

const {resolve, load} = loader;

listen((channelId, hook, data) => (hook === RESOLVE ? resolve : load)(
...data,
(specifier, context) => message(channelId, NEXT, hook, [specifier, context])
));

post(0, SUCCESS, INIT, {resolve: !!loader.resolve, load: !!loader.load});

if (loader.globalPreload) {
// TODO Proxy outer port
const preloadCode = loader.globalPreload();

// TODO Proxy inner port
// eslint-disable-next-line no-new-func
new Function('getBuiltin', preloadCode)(getBuiltin);
}
};

function importSync(url) {
const worker = new SynchronousWorker();
const workerImport = worker.createRequire(__filename)('./import.js');
const promise = workerImport(url);
return worker.runLoopUntilPromiseResolved(promise);
}
108 changes: 108 additions & 0 deletions lib/utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
'use strict';

// Modules
const {isFunction} = require('is-it-type');

// Imports
const {INCOMING, SUCCESS, FAIL, ERROR, OTHER} = require('./constants.js');

// Exports

module.exports = {createChannels, defer, wrapError, unwrapError};

function createChannels(port, logName) {
let nextChannelId = 1;
const callbacks = new Map();

function listen(onIncoming) {
port.onmessage = ({data: {id, type, hook, data}}) => {
// TODO Remove console.log
// eslint-disable-next-line no-console
console.log(`${logName} received message:`, {id, type, hook, data});

if (type === INCOMING) {
onIncoming(id, hook, data).then(
res => post(id, SUCCESS, hook, res),
err => post(id, FAIL, hook, wrapError(err))
);
return;
}
callbacks.get(id)(type, data);
};
}

function addCallback(messageId, callback) {
callbacks.set(messageId, (type, data) => {
const shouldDelete = callback(type, data);
if (shouldDelete) callbacks.delete(messageId);
});
}

function createChannel(hook, data, onMessage) {
const channelId = nextChannelId++;
return message(channelId, INCOMING, hook, data, onMessage);
}

function message(channelId, type, hook, data, onMessage) {
const deferred = defer();
addCallback(channelId, (replyType, replyData) => {
if (replyType === SUCCESS) {
deferred.resolve(replyData);
return true;
}

if (replyType === FAIL) {
deferred.reject(unwrapError(replyData));
return true;
}

onMessage(replyType, replyData).then(
value => post(channelId, SUCCESS, hook, value),
err => post(channelId, FAIL, hook, wrapError(err))
);
return false;
});

post(channelId, type, hook, data);

return deferred.promise;
}

function post(id, type, hook, data) {
// TODO Remove console.log
// eslint-disable-next-line no-console
console.log(`${logName} sending message:`, {id, type, hook, data});
port.postMessage({id, type, hook, data});
}

return {listen, addCallback, createChannel, message, post};
}

function defer() {
const deferred = {promise: null, resolve: null, reject: null};
deferred.promise = new Promise((resolve, reject) => {
deferred.resolve = resolve;
deferred.reject = reject;
});
return deferred;
}

function wrapError(err) {
if (isFunction(err)) return {type: ERROR, message: 'Unserializable error', stack: null, props: null};

if (err instanceof Error) {
const {message, stack, ...props} = err;
return {type: ERROR, message, stack, props};
}

return {type: OTHER, value: err};
}

function unwrapError(errDef) {
if (errDef.type !== ERROR) return errDef.value;

const err = new Error(errDef.message);
if (errDef.stack) err.stack = errDef.stack;
Object.assign(err, errDef.props);
return err;
}
Loading

0 comments on commit c181b04

Please sign in to comment.