Skip to content

Commit

Permalink
More Performant Templates Clonning (#3)
Browse files Browse the repository at this point in the history
* simple cloneNode(true)

* optimization for exactly same and static elements

* fix typo

* using helper function to create iife

* impove tests perf

* fix tests
  • Loading branch information
yhdgms1 authored May 22, 2022
1 parent 7995177 commit 8283d00
Show file tree
Hide file tree
Showing 20 changed files with 141 additions and 58 deletions.
5 changes: 4 additions & 1 deletion src/compiler/plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,10 @@ const compileJSXPlugin = (babel, options) => {
throw path.buildCodeFrameError("JSXFragment is not supported.");
},
Program(path) {
shared.set("templateFunctionName", path.scope.generateUidIdentifier("tmpl").name);
shared.set(
"templateFunctionName",
path.scope.generateUidIdentifier("template").name
);
shared.set("firstElementChild", path.scope.generateUidIdentifier("fec").name);
shared.set("nextElementSibling", path.scope.generateUidIdentifier("nes").name);
shared.set("spreadFunctionName", path.scope.generateUidIdentifier("sprd").name);
Expand Down
3 changes: 3 additions & 0 deletions src/compiler/shared.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ const shared = Osake({
spreadFunctionName: "grim_$s",
firstElementChild: "grim_$fec",
nextElementSibling: "grim_$nes",

programPath: /** @type {babel.NodePath<babel.types.Program> | null} */ (null),
sharedNodes: /** @type {Record<string, babel.types.VariableDeclaration>} */ ({}),
});

export { shared };
84 changes: 65 additions & 19 deletions src/compiler/transforms/jsxElement.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
getJSXElementName,
getAttributeName,
createTemplateLiteralBuilder,
createIIFE,
} from "../utils";

/**
Expand All @@ -17,7 +18,7 @@ import {
function JSXElement(path) {
const { parent, node } = path;

const { babel, enableCommentOptions, inuse } = shared();
const { babel, enableCommentOptions, inuse, programPath } = shared();
const { types: t } = babel;

if (
Expand Down Expand Up @@ -51,7 +52,7 @@ function JSXElement(path) {

const template = createTemplateLiteralBuilder();

let templateName = t.identifier("tmpl");
let templateName = (programPath || path).scope.generateUidIdentifier("el");

const opts = { enableStringMode: shared().enableStringMode };

Expand Down Expand Up @@ -143,7 +144,7 @@ function JSXElement(path) {
if (t.isIdentifier(expression) || t.isMemberExpression(expression)) {
const right =
current.length > 0
? createMemberExpression(templateName, ...current) ?? templateName
? createMemberExpression(templateName, ...current) || templateName
: templateName;

for (const item of current) {
Expand Down Expand Up @@ -173,7 +174,7 @@ function JSXElement(path) {

template.push(insertAttrubute(name, value));
} else if (t.isJSXExpressionContainer(attr.value)) {
const expression = attr.value.expression;
const { expression } = attr.value;

if (t.isObjectExpression(expression)) {
const attr = objectExpressionToAttribute(expression);
Expand Down Expand Up @@ -276,24 +277,69 @@ function JSXElement(path) {
template.push(`</svg>`);
}

if (expressions.length > 0) {
path.replaceWith(
t.callExpression(
t.arrowFunctionExpression(
[],
t.blockStatement([
t.variableDeclaration("const", [
t.variableDeclarator(templateName, templateCall),
]),
...expressions,
t.returnStatement(templateName),
])
/**
* We are lucky today, because this is just an _static_ html.
* TemplateLiteral does not have any expressions, so template could be extracted
*/
if (programPath && template.template.quasis.length === 1) {
const current_raw = template.template.quasis[0].value.raw;
const { sharedNodes } = shared();

/** @type {babel.types.VariableDeclaration | null} */
let decl = null;

/**
* If there are identical elements, we reuse them
*/
if (sharedNodes[current_raw]) {
decl = sharedNodes[current_raw];
} else {
decl = t.variableDeclaration("let", [
t.variableDeclarator(
(programPath || path).scope.generateUidIdentifier("tmpl"),
templateCall
),
[]
)
]);

programPath.node.body.unshift(decl);

shared().sharedNodes[current_raw] = decl;
}

/** @type {babel.types.Identifier} */
// @ts-ignore - We create these declarations and know it is Identifier
const object = decl.declarations[0].id;

const call = t.callExpression(
t.memberExpression(object, t.identifier("cloneNode")),
[t.booleanLiteral(true)]
);

if (expressions.length > 0) {
path.replaceWith(
createIIFE(
t.variableDeclaration("let", [t.variableDeclarator(templateName, call)]),
...expressions,
t.returnStatement(templateName)
)
);
} else {
path.replaceWith(call);
}
} else {
path.replaceWith(templateCall);
if (expressions.length > 0) {
path.replaceWith(
createIIFE(
t.variableDeclaration("let", [
t.variableDeclarator(templateName, templateCall),
]),
...expressions,
t.returnStatement(templateName)
)
);
} else {
path.replaceWith(templateCall);
}
}
}
}
Expand Down
2 changes: 2 additions & 0 deletions src/compiler/transforms/post.js
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,8 @@ function post(file) {
} else {
produceImports();
}

shared.set("sharedNodes", {});
}

export { post };
3 changes: 3 additions & 0 deletions src/compiler/transforms/pre.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ const createPre = (options) => {
}
}
}

shared.set("programPath", file.path);
shared.set("sharedNodes", {});
}

return pre;
Expand Down
14 changes: 14 additions & 0 deletions src/compiler/utils/create-iife.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { shared } from "../shared";

/**
*
* @param {...babel.types.Statement} body
* @returns
*/
const createIIFE = (...body) => {
const { types: t } = shared().babel;

return t.callExpression(t.arrowFunctionExpression([], t.blockStatement(body)), []);
};

export { createIIFE };
1 change: 1 addition & 0 deletions src/compiler/utils/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ export { getAttributeName } from "./get-attribute-name";
export { jsxMemberExpressionToMemberExpression } from "./jsx-member-expression-to-member-expression";
export { createTemplateLiteralBuilder } from "./template-literal-builder";
export { isObject } from "./is-object";
export { createIIFE } from "./create-iife";
export * as constants from "./constants";
4 changes: 2 additions & 2 deletions tests/dynamic attrs and child/expected.snapshot
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { template as _tmpl } from "grim-jsx/runtime.js";
import { template as _template } from "grim-jsx/runtime.js";
import styles from './styles.module.css';

const Paragraph = ({
children
}) => {
const p = _tmpl(`<p class="${styles.paragraph}">${children}</p>`);
const p = _template(`<p class="${styles.paragraph}">${children}</p>`);

return p;
};
4 changes: 2 additions & 2 deletions tests/dynamic tag name/expected.snapshot
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { template as _tmpl } from "grim-jsx/runtime.js";
import { template as _template } from "grim-jsx/runtime.js";

const As = props => {
const el = _tmpl(`<${props.as}></${props.as}>`);
const el = _template(`<${props.as}></${props.as}>`);

return el;
};
7 changes: 4 additions & 3 deletions tests/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,12 +63,13 @@ async function main() {
plugins: [
[
compileJSXPlugin,
options !== null
? { ...defaultOptions, ...options }
: defaultOptions,
options !== null ? { ...defaultOptions, ...options } : defaultOptions,
],
],
babelrc: false,
browserslistConfigFile: false,
configFile: false,
highlightCode: false,
comments: false,
filename: entry.replaceAll(" ", "_"),
});
Expand Down
4 changes: 2 additions & 2 deletions tests/inline runtime/expected.snapshot
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ const _sprd = (props, attr) => {
return Object.entries(props).map(([key, value]) => value == null ? '' : attr ? `${key}:${value};` : `${key}="${value}"`).join(" ");
};

const _tmpl = (html, isSVG) => {
const _template = (html, isSVG) => {
const t = document.createElement("template");
t.innerHTML = html;
let node = t.content.firstChild;
Expand All @@ -16,5 +16,5 @@ let cmp = props => {
className,
...attrs
} = props;
return _tmpl(`<div class="${className}" ${_sprd(attrs)}><p>${children}</p><button type="button">Inc</button></div>`);
return _template(`<div class="${className}" ${_sprd(attrs)}><p>${children}</p><button type="button">Inc</button></div>`);
};
10 changes: 6 additions & 4 deletions tests/object as attribute/expected.snapshot
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { template as _tmpl, spread as _sprd } from "grim-jsx/runtime.js";
import { template as _template, spread as _sprd } from "grim-jsx/runtime.js";

const div = _tmpl(`<div style="color:red;">Hello</div>`);
let _tmpl = _template(`<div style="color:red;">Hello</div>`);

const div = _tmpl.cloneNode(true);

let key = window.style_key;
let value = window.style_value();
const elements = [_tmpl(`<span style="${_sprd({
const elements = [_template(`<span style="${_sprd({
[key]: value
}, true)}">Hello</span>`), _tmpl(`<span style="${_sprd({
}, true)}">Hello</span>`), _template(`<span style="${_sprd({
[window.style_key]: window.style_value()
}, true)}">Hello</span>`)];
1 change: 0 additions & 1 deletion tests/simple node/code.snapshot

This file was deleted.

3 changes: 0 additions & 3 deletions tests/simple node/expected.snapshot

This file was deleted.

4 changes: 2 additions & 2 deletions tests/spread/expected.snapshot
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
import { template as _tmpl, spread as _sprd } from "grim-jsx/runtime.js";
import { template as _template, spread as _sprd } from "grim-jsx/runtime.js";

const Component = props => _tmpl(`<div ${_sprd(props)}></div>`);
const Component = props => _template(`<div ${_sprd(props)}></div>`);
7 changes: 5 additions & 2 deletions tests/string mode/expected.snapshot
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { template as _tmpl } from "grim-jsx/runtime.js";
import { template as _template } from "grim-jsx/runtime.js";

let _tmpl = _template(`<div></div>`);

const people = ["Artem", "Ivan", "Arina", "Roman", "Kenzi"];
let str = `<div><h1>Hello!</h1><ul>${people.map(person => `<li>${person}</li>`).join("")}</ul></div>`;

const node = _tmpl(`<div></div>`);
const node = _tmpl.cloneNode(true);

str = `<div>It should be a string</div>`;
10 changes: 7 additions & 3 deletions tests/svg/expected.snapshot
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import { template as _tmpl } from "grim-jsx/runtime.js";
import { template as _template } from "grim-jsx/runtime.js";

const icon = _tmpl(`<svg width="24" height="24" fill="none" viewBox="0 0 24 24"></svg>`);
let _tmpl2 = _template(`<svg><path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M18.25 12.25L5.75 12.25"></path></svg>`, true);

const path = _tmpl(`<svg><path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M18.25 12.25L5.75 12.25"></path></svg>`, true);
let _tmpl = _template(`<svg width="24" height="24" fill="none" viewBox="0 0 24 24"></svg>`);

const icon = _tmpl.cloneNode(true);

const path = _tmpl2.cloneNode(true);

icon.appendChild(path);
19 changes: 12 additions & 7 deletions tests/using refs/expected.snapshot
Original file line number Diff line number Diff line change
@@ -1,18 +1,23 @@
import { template as _tmpl, firstElementChild as _fec, nextElementSibling as _nes } from "grim-jsx/runtime.js";
import { template as _template, firstElementChild as _fec, nextElementSibling as _nes } from "grim-jsx/runtime.js";

let _tmpl2 = _template(`<footer><p>Copyright © 2069</p></footer>`);

let _tmpl = _template(`<article><h1>Hello!</h1><p>Today we are going to find out something</p><button>Find out!</button></article>`);

let button;

const article = (() => {
const tmpl = _tmpl(`<article><h1>Hello!</h1><p>Today we are going to find out something</p><button>Find out!</button></article>`);
const _el = _tmpl.cloneNode(true);

button = tmpl[_fec][_nes][_nes];
return tmpl;
button = _el[_fec][_nes][_nes];
return _el;
})();

let foo;

const footer = (() => {
const tmpl = _tmpl(`<footer><p>Copyright © 2069</p></footer>`);
const _el2 = _tmpl2.cloneNode(true);

foo = tmpl;
return tmpl;
foo = _el2;
return _el2;
})();
4 changes: 2 additions & 2 deletions tests/variables scoping/code.snapshot
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
let _tmpl = 'Noodles';
let _template = 'Noodles';

let eggs;

const list = (
<ul>
<li>{_tmpl}</li>
<li>{_template}</li>
<li>Soup</li>
<li>Rice</li>
<li ref={eggs}>Eggs</li>
Expand Down
10 changes: 5 additions & 5 deletions tests/variables scoping/expected.snapshot
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { template as _tmpl2, firstElementChild as _fec, nextElementSibling as _nes } from "grim-jsx/runtime.js";
let _tmpl = 'Noodles';
import { template as _template2, firstElementChild as _fec, nextElementSibling as _nes } from "grim-jsx/runtime.js";
let _template = 'Noodles';
let eggs;

const list = (() => {
const tmpl = _tmpl2(`<ul><li>${_tmpl}</li><li>Soup</li><li>Rice</li><li>Eggs</li></ul>`);
const _el = _template2(`<ul><li>${_template}</li><li>Soup</li><li>Rice</li><li>Eggs</li></ul>`);

eggs = tmpl[_fec][_nes][_nes][_nes];
return tmpl;
eggs = _el[_fec][_nes][_nes][_nes];
return _el;
})();

0 comments on commit 8283d00

Please sign in to comment.