Files
meteor/packages/hot/plugin.js

118 lines
3.1 KiB
JavaScript

const {
comparePrelinkResult
} = require('./changesets');
// Meteor can load the plugin multiple times
// when it is a local package
// Any state that should be preserved is stored in
// this object
const sharedState = global.__hotState || {
initialized: false,
wsServer: null,
wsMessageHandler: null,
prelinkResultHandler: null,
previousPrelinkResults: {},
};
global.__hotState = sharedState;
function findLast(array, compare) {
for (let i = array.length - 1; i > 0; i--) {
if (compare(array[i])) {
return array[i]
}
}
}
function prelinkResultHandler(prelinkResult) {
const {
isApp,
name,
bundleArch
} = prelinkResult;
// TODO: we should limit the number of change sets that are saved
// to avoid a memory leak
sharedState.previousPrelinkResults[bundleArch] =
sharedState.previousPrelinkResults[bundleArch] || [];
// TODO: support HMR in packages
// TODO: support HMR in legacy bundle
if (!isApp || bundleArch !== 'web.browser') {
// Require a full reload whenever a package is modified
sharedState.previousPrelinkResults[bundleArch].push({
name,
reloadable: false,
linkedAt: Date.now()
})
return;
}
// TODO: Meteor should cache some of this data with the linker cache
// so we have something to compare with when linking the first time
// after meteor is started
const changeSets = sharedState.previousPrelinkResults[bundleArch];
let previousChangeset = findLast(changeSets, (changeSet) => {
return changeSet.name === name;
});
const changeset = comparePrelinkResult(previousChangeset, prelinkResult);
sharedState.previousPrelinkResults[bundleArch].push(changeset);
}
function wsMessageHandler(conn, _message) {
const message = JSON.parse(_message);
switch(message.type) {
case 'request-changes': {
const {
after,
arch
} = message;
const changesets = sharedState.previousPrelinkResults[arch] || [];
const newChanges = changesets.filter(({ linkedAt }) => {
return linkedAt > after;
});
conn.send(JSON.stringify({
type: 'changes',
changeSets: newChanges
}));
break;
}
default:
console.warn('Unknown HMR message:', message.type);
}
}
// Update handlers so event listeners added during initialization can
// use the latest handlers if this package was modified and rebuilt
sharedState.prelinkResultHandler = prelinkResultHandler;
sharedState.wsMessageHandler = wsMessageHandler;
function init() {
sharedState.initialized = true;
// TODO: port should be based on port app is using
// TODO: look into using sockjs instead
const WebSocket = require('ws');
sharedState.wsServer = new WebSocket.Server({ port: 3124 });
// TODO: should require connections to send a secret before
// being able to receive changes
sharedState.wsServer.on('connection', function (ws) {
ws.on('message', (message) => {
sharedState.wsMessageHandler(ws, message);
});
});
Plugin._onPreLinked(function (prelinkResult) {
sharedState.prelinkResultHandler(prelinkResult);
});
}
if (!sharedState.initialized) {
init();
}