Skip to content

Commit

Permalink
Add JSX optimization
Browse files Browse the repository at this point in the history
  • Loading branch information
lxsmnsyc committed May 8, 2023
1 parent e5e7bae commit ce4ace5
Show file tree
Hide file tree
Showing 6 changed files with 165 additions and 56 deletions.
144 changes: 134 additions & 10 deletions packages/forgetti/src/core/is-constant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -233,10 +233,139 @@ function isObjectExpressionConstant(
return false;
}
}
if (isPathValid(property, t.isObjectMethod)) {
// TODO
}
}
return true;
}

function isNewExpressionConstant(
instance: OptimizerInstance,
path: babel.NodePath<t.NewExpression>,
): boolean {
const callee = path.get('callee');
if (isPathValid(callee, t.isExpression) && isConstant(instance, callee)) {
const args = path.get('arguments');
for (let i = 0, len = args.length; i < len; i++) {
const arg = args[i];
if (isPathValid(arg, t.isExpression) && !isConstant(instance, arg)) {
return false;
}
if (isPathValid(arg, t.isSpreadElement) && !isConstant(instance, arg.get('argument'))) {
return false;
}
}
return true;
}
return false;
}

function isSequenceExpressionConstant(
instance: OptimizerInstance,
path: babel.NodePath<t.SequenceExpression>,
): boolean {
const exprs = path.get('expressions');
for (let i = 0, len = exprs.length; i < len; i++) {
if (!isConstant(instance, exprs[i])) {
return false;
}
}
return true;
}

function isTaggedTemplateExpressionConstant(
instance: OptimizerInstance,
path: babel.NodePath<t.TaggedTemplateExpression>,
): boolean {
return isConstant(instance, path.get('tag'))
&& isConstant(instance, path.get('quasi'));
}

function isJSXChildrenConstant(
instance: OptimizerInstance,
path: babel.NodePath<t.JSXFragment | t.JSXElement>,
): boolean {
const children = path.get('children');
for (let i = 0, len = children.length; i < len; i++) {
const child = children[i];
if (isPathValid(child, t.isJSXElement) && !isJSXElementConstant(instance, child)) {
return false;
}
if (isPathValid(child, t.isJSXFragment) && !isJSXChildrenConstant(instance, child)) {
return false;
}
if (isPathValid(child, t.isJSXSpreadChild) && !isConstant(instance, child.get('expression'))) {
return false;
}
if (isPathValid(child, t.isJSXExpressionContainer)) {
const expr = child.get('expression');
if (isPathValid(expr, t.isExpression) && !isConstant(instance, expr)) {
return false;
}
}
}
return true;
}

function isJSXNameConstant(
instance: OptimizerInstance,
path: babel.NodePath<t.JSXIdentifier | t.JSXMemberExpression | t.JSXNamespacedName>,
): boolean {
if (isPathValid(path, t.isJSXNamespacedName)) {
return true;
}
if (isPathValid(path, t.isJSXMemberExpression)) {
return isJSXNameConstant(instance, path.get('object'));
}
if (isPathValid(path, t.isJSXIdentifier)) {
if (/^[A-Z]/.test(path.node.name)) {
return isForeignBinding(instance.path, path, path.node.name);
}
}
return true;
}

function isJSXOpeningElementConstant(
instance: OptimizerInstance,
path: babel.NodePath<t.JSXOpeningElement>,
): boolean {
if (isJSXNameConstant(instance, path.get('name'))) {
const attrs = path.get('attributes');
for (let i = 0, len = attrs.length; i < len; i++) {
const attr = attrs[i];
if (isPathValid(attr, t.isJSXAttribute)) {
const value = attr.get('value');
if (isPathValid(value, t.isJSXElement) && !isJSXElementConstant(instance, value)) {
return false;
}
if (isPathValid(value, t.isJSXFragment) && !isJSXChildrenConstant(instance, value)) {
return false;
}
if (isPathValid(value, t.isJSXExpressionContainer)) {
const expr = value.get('expression');
if (isPathValid(expr, t.isExpression) && !isConstant(instance, expr)) {
return false;
}
}
}
if (isPathValid(attr, t.isJSXSpreadAttribute) && !isConstant(instance, attr.get('argument'))) {
return false;
}
}
return true;
}
return false;
}

function isJSXElementConstant(
instance: OptimizerInstance,
path: babel.NodePath<t.JSXElement>,
): boolean {
return isJSXOpeningElementConstant(instance, path.get('openingElement'))
&& isJSXChildrenConstant(instance, path);
}

export default function isConstant(
instance: OptimizerInstance,
path: babel.NodePath<t.Expression>,
Expand Down Expand Up @@ -291,24 +420,19 @@ export default function isConstant(
return isObjectExpressionConstant(instance, path);
}
if (isPathValid(path, t.isNewExpression)) {
// TODO
return false;
return isNewExpressionConstant(instance, path);
}
if (isPathValid(path, t.isSequenceExpression)) {
// TODO
return false;
return isSequenceExpressionConstant(instance, path);
}
if (isPathValid(path, t.isTaggedTemplateExpression)) {
// TODO
return false;
return isTaggedTemplateExpressionConstant(instance, path);
}
if (isPathValid(path, t.isJSXFragment)) {
// TODO
return false;
return isJSXChildrenConstant(instance, path);
}
if (isPathValid(path, t.isJSXElement)) {
// TODO
return false;
return isJSXElementConstant(instance, path);
}
if (isPathValid(path, t.isImport)) {
return true;
Expand Down
6 changes: 6 additions & 0 deletions packages/forgetti/src/core/optimizer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -868,6 +868,12 @@ export default class Optimizer {
mergeDependencies(conditions, optimized.deps);
}
}
} else if (isPathValid(child, t.isJSXSpreadChild)) {
const optimized = this.createDependency(child.get('expression'));
if (optimized) {
child.node.expression = optimized.expr;
mergeDependencies(conditions, optimized.deps);
}
}
}

Expand Down
28 changes: 7 additions & 21 deletions packages/forgetti/test/__snapshots__/expressions.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -108,21 +108,6 @@ function Example(props) {
}"
`;

exports[`expressions > should optimize await/yield expressions 1`] = `
"import { useMemo as _useMemo } from \\"react\\";
import { $$cache as _$$cache } from \\"forgetti/runtime\\";
import { $$equals as _$$equals } from \\"forgetti/runtime\\";
async function Example(props) {
let _c = _$$cache(_useMemo, 3),
_eq = _$$equals(_c, 0, props),
_v = _eq ? _c[0] : _c[0] = props,
_v2 = _eq ? _c[1] : _c[1] = _v.call(),
_eq2 = _$$equals(_c, 2, _v2),
_v3 = _eq2 ? _c[2] : _c[2] = _v2;
return await _v3;
}"
`;

exports[`expressions > should optimize binary expressions 1`] = `
"import { useMemo as _useMemo } from \\"react\\";
import { $$cache as _$$cache } from \\"forgetti/runtime\\";
Expand Down Expand Up @@ -200,7 +185,7 @@ exports[`expressions > should optimize guaranteed literals 1`] = `
import { $$cache as _$$cache } from \\"forgetti/runtime\\";
function Example(props) {
let _c = _$$cache(_useMemo, 1),
_v = _c[0] ||= 1 + 2;
_v = 0 in _c ? _c[0] : _c[0] = 1 + 2;
return _v;
}"
`;
Expand Down Expand Up @@ -255,11 +240,12 @@ exports[`expressions > should optimize new expressions 1`] = `
import { $$cache as _$$cache } from \\"forgetti/runtime\\";
import { $$equals as _$$equals } from \\"forgetti/runtime\\";
function Example(props) {
let _c = _$$cache(_useMemo, 2),
_eq = _$$equals(_c, 0, props),
_v = _eq ? _c[0] : _c[0] = props,
_v2 = _eq ? _c[1] : _c[1] = new X(_v);
return _v2;
let _c = _$$cache(_useMemo, 3),
_v = 0 in _c ? _c[0] : _c[0] = X,
_eq = _$$equals(_c, 1, props),
_v2 = _eq ? _c[1] : _c[1] = props,
_v3 = _eq ? _c[2] : _c[2] = new _v(_v2);
return _v3;
}"
`;

Expand Down
12 changes: 6 additions & 6 deletions packages/forgetti/test/__snapshots__/hooks.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ import { $$cache as _$$cache } from \\"forgetti/runtime\\";
import { useCallback } from 'react';
function Example(props) {
let _c = _$$cache(_useMemo, 2),
_v = _c[0] ||= [],
_v2 = _c[1] ||= () => props.value();
_v = 0 in _c ? _c[0] : _c[0] = [],
_v2 = 1 in _c ? _c[1] : _c[1] = () => props.value();
return _v2;
}"
`;
Expand Down Expand Up @@ -67,7 +67,7 @@ import { $$cache as _$$cache } from \\"forgetti/runtime\\";
import { useEffect } from 'react';
function Example(props) {
let _c = _$$cache(_useMemo, 1),
_v = _c[0] ||= [];
_v = 0 in _c ? _c[0] : _c[0] = [];
useEffect(() => props.value(), [_v]);
}"
`;
Expand Down Expand Up @@ -110,8 +110,8 @@ import { $$cache as _$$cache } from \\"forgetti/runtime\\";
import { useMemo } from 'react';
function Example(props) {
let _c = _$$cache(_useMemo, 2),
_v = _c[0] ||= [],
_v2 = _c[1] ||= (() => props.value())();
_v = 0 in _c ? _c[0] : _c[0] = [],
_v2 = 1 in _c ? _c[1] : _c[1] = (() => props.value())();
return _v2;
}"
`;
Expand All @@ -137,7 +137,7 @@ import { $$cache as _$$cache } from \\"forgetti/runtime\\";
import { useRef } from 'react';
function Example(props) {
let _c = _$$cache(_useMemo, 1),
_v = _c[0] ||= {
_v = 0 in _c ? _c[0] : _c[0] = {
current: props.value
};
return _v;
Expand Down
23 changes: 12 additions & 11 deletions packages/forgetti/test/__snapshots__/statements.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -130,14 +130,14 @@ function Example(props) {
let _c2 = _$$branch(_c3, 0, 1);
{
let _c = _$$branch(_c2, 0, 2),
_v = _c[0] ||= console.log(\\"face\\");
_v = 0 in _c ? _c[0] : _c[0] = console.log(\\"face\\");
_v;
break foo;
let _v2 = _c[1] ||= console.log(\\"this will not be executed\\");
let _v2 = 1 in _c ? _c[1] : _c[1] = console.log(\\"this will not be executed\\");
_v2;
}
}
let _v3 = _c3[1] ||= console.log(\\"swap\\");
let _v3 = 1 in _c3 ? _c3[1] : _c3[1] = console.log(\\"swap\\");
_v3;
}"
`;
Expand Down Expand Up @@ -190,14 +190,15 @@ exports[`statements > should optimize throw statements 1`] = `
import { $$cache as _$$cache } from \\"forgetti/runtime\\";
import { $$equals as _$$equals } from \\"forgetti/runtime\\";
function Example(props) {
let _c = _$$cache(_useMemo, 4),
_eq = _$$equals(_c, 0, props),
_v = _eq ? _c[0] : _c[0] = props,
_v2 = _eq ? _c[1] : _c[1] = _v.message,
_eq2 = _$$equals(_c, 2, _v2),
_v3 = _eq2 ? _c[2] : _c[2] = _v2,
_v4 = _eq2 ? _c[3] : _c[3] = createError(_v3);
throw _v4;
let _c = _$$cache(_useMemo, 5),
_v = 0 in _c ? _c[0] : _c[0] = createError,
_eq = _$$equals(_c, 1, props),
_v2 = _eq ? _c[1] : _c[1] = props,
_v3 = _eq ? _c[2] : _c[2] = _v2.message,
_eq2 = _$$equals(_c, 3, _v3),
_v4 = _eq2 ? _c[3] : _c[3] = _v3,
_v5 = _eq2 ? _c[4] : _c[4] = _v(_v4);
throw _v5;
}"
`;

Expand Down
8 changes: 0 additions & 8 deletions packages/forgetti/test/expressions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,14 +85,6 @@ function Example(props) {
function Example(props) {
return props.call();
}
`;
expect(await compile(code)).toMatchSnapshot();
});
it('should optimize await/yield expressions', async () => {
const code = `
async function Example(props) {
return await props.call();
}
`;
expect(await compile(code)).toMatchSnapshot();
});
Expand Down

0 comments on commit ce4ace5

Please sign in to comment.