Created
October 12, 2023 14:00
-
-
Save DanielDornhardt/9b235fe400d5e87778e2fe5548aa6a08 to your computer and use it in GitHub Desktop.
Babel Plugin to De-Asyncify Meteor Client Code
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* This is a babel plugin to de-asyncify MeteorJS client code. | |
* | |
* Note: This is currently experimental. | |
* | |
* This is primarily meant to restore the full reactivity of the Tracker module by removing the Async code necessary | |
* for the server, and instead calling synchronous versions of the same which work on the client because of minimongo being able | |
* to work synchronously. | |
* | |
* It does the following transformations: | |
* | |
* - removes all async and await statements / declarations from client code | |
* - changes all calls to known / declared *Async functions (see below) to the sync ones, eg. findOneAsync => findOne etc. | |
* | |
* CAVEAT: This de-asyncifies ALL async functions & awaits on the client. | |
* | |
* In order to mitigate this where necessary, here are 2 options for now: | |
* | |
* a) use Promises instead of native async / await | |
* | |
* b) use the "tag" @dontDeasync in a comment before your async function to skip its deAsyncification! Awesome. | |
* | |
* | |
* like this: | |
* | |
* // @dontDeasync | |
* async function iNeedToStayAsync() { | |
* // ... | |
* } | |
*/ | |
/** | |
* Replace these functions with their sync counterparts. Client code only. | |
*/ | |
const functionReplaceMap = { | |
// Mongo.Collection: | |
'findOneAsync': 'findOne', | |
'insertAsync': 'insert', | |
'removeAsync': 'remove', | |
'updateAsync': 'update', | |
'upsertAsync': 'upsert', | |
// Collection.Cursor: | |
'countAsync': 'count', | |
'fetchAsync': 'fetch', | |
'forEachAsync': 'forEach', | |
'mapAsync': 'map', | |
// accounts-base: | |
'userAsync': 'user', | |
// callback-hook:forEachAsync | |
// forEachAsync | |
// ddp-server | |
// callAsync | |
}; | |
module.exports = function (babel) { | |
const { types: t, caller } = babel; | |
let callerInfo | |
caller(function (c) { | |
callerInfo = c | |
}) | |
// detect server/client for Meteor, we only need this for client code | |
let isServer = false | |
/** | |
* Utility function to check for the presence of the "@dont-deasync" tag in leading comments | |
* | |
* @param node | |
* @return {boolean} | |
*/ | |
function hasDontDeasyncTag(path) { | |
const checkCommentForTag = (comment) => comment.value.includes('@dontDeasync'); | |
let currentPath = path; | |
while (currentPath) { | |
const node = currentPath.node; | |
if (node.leadingComments && node.leadingComments.some(checkCommentForTag)) { | |
return true; | |
} | |
currentPath = currentPath.parentPath; | |
} | |
return false; | |
} | |
return { | |
name: "async-to-sync", | |
visitor: { | |
Program: { | |
enter(_, state) { | |
isServer = callerInfo?.arch.startsWith('os.') | |
}, | |
}, | |
Function(path) { | |
if (isServer) { | |
return | |
} | |
if (hasDontDeasyncTag(path)) { | |
return; // Skip this function if the tag is present | |
} | |
if (path.node.async) { | |
path.node.async = false; | |
} | |
}, | |
AwaitExpression(path) { | |
if (isServer) { | |
return | |
} | |
if (hasDontDeasyncTag(path)) { | |
return; // Skip this function if the tag is present | |
} | |
path.replaceWith(path.node.argument); | |
}, | |
CallExpression(path) { | |
if (isServer) { | |
return | |
} | |
if (hasDontDeasyncTag(path)) { | |
return; // Skip this function if the tag is present | |
} | |
if (t.isMemberExpression(path.node.callee) && t.isIdentifier(path.node.callee.property)) { | |
const calleeName = path.node.callee.property.name; | |
// Use hasOwnProperty so if hasOwnProperty is called in client code doesn't run into issues here | |
// because hasOwnProperty would be found in functionReplaceMap's own property chain & | |
// then tried to be replaced otherwise... O_O :D | |
if (functionReplaceMap.hasOwnProperty(calleeName)) { | |
path.node.callee.property.name = functionReplaceMap[calleeName]; | |
} } | |
}, | |
}, | |
}; | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment