Skip to content

Commit

Permalink
Fix problems with polyfills (#2161)
Browse files Browse the repository at this point in the history
## Description

Babel was changing some construction to non-workletized functions inside the worklet function. For example, `Object.assign()` was sometimes changed to babel implementation - this implementation wasn't worklet. That was the cause of the worklet runtime error.

Fixes #2142

Known problem:
In a scenario when we have nested worklets the inner one won't be workletized with asString property of the outer one. It may cause problems in the future when sending functions between several threads will be more common. However, I don't think we have such examples in the reanimated world right now.
  • Loading branch information
piaskowyk committed Jun 30, 2021
1 parent 46c3ff4 commit 2603d4d
Showing 1 changed file with 23 additions and 68 deletions.
91 changes: 23 additions & 68 deletions plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const hash = require('string-hash-64');
const { visitors } = require('@babel/traverse');
const traverse = require('@babel/traverse').default;
const parse = require('@babel/parser').parse;
const { transformSync } = require("@babel/core");
/**
* holds a map of function names as keys and array of argument indexes as values which should be automatically workletized(they have to be functions)(starting from 0)
*/
Expand Down Expand Up @@ -258,16 +259,16 @@ function buildWorkletString(t, fun, closureVariables, name) {
]);
}

fun.traverse({
traverse(fun, {
enter(path) {
t.removeComments(path.node);
},
});

const workletFunction = t.functionExpression(
t.identifier(name),
fun.node.params,
prependClosureVariablesIfNecessary(closureVariables, fun.get('body').node)
fun.program.body[0].expression.params,
prependClosureVariablesIfNecessary(closureVariables, fun.program.body[0].expression.body)
);

return generate(workletFunction, { compact: true }).code;
Expand All @@ -285,9 +286,23 @@ function processWorkletFunction(t, fun, fileName, options = {}) {

// We use copy because some of the plugins don't update bindings and
// some even break them
const astWorkletCopy = parse('\n(' + fun.toString() + '\n)');
const code = '\n(' + fun.toString() + '\n)';
const transformed = transformSync(code, {
filename: fileName,
"presets": ["@babel/preset-typescript"],
"plugins": [
"@babel/plugin-transform-shorthand-properties",
"@babel/plugin-transform-arrow-functions",
"@babel/plugin-proposal-optional-chaining",
"@babel/plugin-proposal-nullish-coalescing-operator",
["@babel/plugin-transform-template-literals", { "loose": true }],
],
ast: true,
babelrc: false,
configFile: false,
});

traverse(astWorkletCopy, {
traverse(transformed.ast, {
ReferencedIdentifier(path) {
const name = path.node.name;
if (globals.has(name) || (fun.node.id && fun.node.id.name === name)) {
Expand Down Expand Up @@ -348,8 +363,7 @@ function processWorkletFunction(t, fun, fileName, options = {}) {
const privateFunctionId = t.identifier('_f');
const clone = t.cloneNode(fun.node);
const funExpression = t.functionExpression(null, clone.params, clone.body);

const funString = buildWorkletString(t, fun, variables, functionName);
const funString = buildWorkletString(t, transformed.ast, variables, functionName).replace("'worklet';", '');
const workletHash = hash(funString);

const loc = fun && fun.node && fun.node.loc && fun.node.loc.start;
Expand Down Expand Up @@ -542,59 +556,6 @@ function isPossibleOptimization(fun) {
return flags;
}

const PLUGIN_BLACKLIST_NAMES = ['@babel/plugin-transform-object-assign'];

const PLUGIN_BLACKLIST = PLUGIN_BLACKLIST_NAMES.map((pluginName) => {
try {
const blacklistedPluginObject = require(pluginName);
// All Babel polyfills use the declare method that's why we can create them like that.
// https://github.com/babel/babel/blob/32279147e6a69411035dd6c43dc819d668c74466/packages/babel-helper-plugin-utils/src/index.js#L1
const blacklistedPlugin = blacklistedPluginObject.default({
assertVersion: (_x) => true,
});

visitors.explode(blacklistedPlugin.visitor);
return blacklistedPlugin;
} catch (e) {
console.warn(`Plugin ${pluginName} couldn't be removed!`);
}
});

// plugin objects are created by babel internals and they don't carry any identifier
function removePluginsFromBlacklist(plugins) {
PLUGIN_BLACKLIST.forEach((blacklistedPlugin) => {
if (!blacklistedPlugin) {
return;
}

const toRemove = [];
for (let i = 0; i < plugins.length; i++) {
if (
JSON.stringify(Object.keys(plugins[i].visitor)) !==
JSON.stringify(Object.keys(blacklistedPlugin.visitor))
) {
continue;
}
let areEqual = true;
for (const key of Object.keys(blacklistedPlugin.visitor)) {
if (
blacklistedPlugin.visitor[key].toString() !==
plugins[i].visitor[key].toString()
) {
areEqual = false;
break;
}
}

if (areEqual) {
toRemove.push(i);
}
}

toRemove.forEach((x) => plugins.splice(x, 1));
});
}

module.exports = function ({ types: t }) {
return {
pre() {
Expand All @@ -607,21 +568,15 @@ module.exports = function ({ types: t }) {
},
visitor: {
CallExpression: {
exit(path, state) {
enter(path, state) {
processWorklets(t, path, state.file.opts.filename);
},
},
'FunctionDeclaration|FunctionExpression|ArrowFunctionExpression': {
exit(path, state) {
enter(path, state) {
processIfWorkletNode(t, path, state.file.opts.filename);
},
},
},
// In this way we can modify babel options
// https://github.com/babel/babel/blob/eea156b2cb8deecfcf82d52aa1b71ba4995c7d68/packages/babel-core/src/transformation/normalize-opts.js#L64
manipulateOptions(opts, _) {
const plugins = opts.plugins;
removePluginsFromBlacklist(plugins);
},
};
};

0 comments on commit 2603d4d

Please sign in to comment.