Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

0.39.0 Updates #353

Merged
merged 35 commits into from
Sep 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
f16ff0c
use `importNode` when img is loading lazy `<img loading="lazy"/>`
titoBouzout Sep 6, 2024
0445722
use a regexp as a last resort
titoBouzout Sep 6, 2024
4d82309
add iframe to importNode when it has attribute loading=lazy
titoBouzout Sep 6, 2024
62f87b9
document expression
titoBouzout Sep 6, 2024
9c15d6f
get rid of the regexp
titoBouzout Sep 7, 2024
fd4c85f
rename var
titoBouzout Sep 7, 2024
ffee20f
Improve event delegation handling of open shadow DOM (#315)
olivercoad Sep 9, 2024
5e0b259
code golf
ryansolid Sep 9, 2024
0bee0fe
allows `addEventListener` to use `handleEvent` syntax while re-using …
titoBouzout Sep 9, 2024
2f4123f
add an aditional check for `is` in element attributes/props for custo…
titoBouzout Sep 9, 2024
b504cce
Merge branch 'main' of https://github.com/ryansolid/dom-expressions i…
ryansolid Sep 9, 2024
2208050
preliminary support for `bool:` [needs help] (#347)
titoBouzout Sep 9, 2024
c036e1d
fix tests by adding them to the element whitelist
ryansolid Sep 9, 2024
7feb5fd
Improve event delegation handling of open shadow DOM (#315)
olivercoad Sep 9, 2024
661f530
code golf
ryansolid Sep 9, 2024
02547f8
allows `addEventListener` to use `handleEvent` syntax while re-using …
titoBouzout Sep 9, 2024
f9a00f7
add an aditional check for `is` in element attributes/props for custo…
titoBouzout Sep 9, 2024
a21a043
preliminary support for `bool:` [needs help] (#347)
titoBouzout Sep 9, 2024
40c572a
fix tests by adding them to the element whitelist
ryansolid Sep 9, 2024
57d4677
Merge branch 'titoBouzout-lazyimg' into next
ryansolid Sep 9, 2024
16085d2
fix tests
ryansolid Sep 9, 2024
71af9d6
export client-side APIs from dom-expressions server.js, make them thr…
trusktr Sep 9, 2024
5790bfc
fix #344: missing semicolon
ryansolid Sep 9, 2024
4300742
improve hydration error to include template
ryansolid Sep 9, 2024
f52b19c
adjust bool server spread
ryansolid Sep 11, 2024
553935b
Add `useTitle` (#311)
lxsmnsyc Sep 11, 2024
95f3af4
Stream title updates/skip hydration
ryansolid Sep 11, 2024
fe6a695
Merge branch 'main' of https://github.com/ryansolid/dom-expressions i…
ryansolid Sep 20, 2024
921d68c
Revert "Add `useTitle` (#311)"
ryansolid Sep 20, 2024
3fbad1f
Merge branch 'main' of https://github.com/ryansolid/dom-expressions i…
ryansolid Sep 23, 2024
3e321e9
validates whole partial (#350)
titoBouzout Sep 23, 2024
145d448
Revert "Stream title updates/skip hydration"
ryansolid Sep 23, 2024
f5ab39e
bump lock
ryansolid Sep 23, 2024
5a9a871
Merge branch 'main' of https://github.com/ryansolid/dom-expressions i…
ryansolid Sep 23, 2024
0b23f00
update github action
ryansolid Sep 23, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/main-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ jobs:
github-token: ${{ secrets.GITHUB_TOKEN }}

- name: Archive production artifacts
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v4
with:
name: dist-folder
path: |
Expand Down
2 changes: 2 additions & 0 deletions packages/babel-plugin-jsx-dom-expressions/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
"@babel/plugin-syntax-jsx": "^7.18.6",
"@babel/types": "^7.20.7",
"html-entities": "2.3.3",
"jest-diff": "^29.7.0",
"jsdom": "^25.0.0",
"validate-html-nesting": "^1.2.1"
},
"peerDependencies": {
Expand Down
111 changes: 101 additions & 10 deletions packages/babel-plugin-jsx-dom-expressions/src/dom/element.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,15 +61,19 @@ export function transformElement(path, info) {
config = getConfig(path),
wrapSVG = info.topLevel && tagName != "svg" && SVGElements.has(tagName),
voidTag = VoidElements.indexOf(tagName) > -1,
isCustomElement = tagName.indexOf("-") > -1 || !!path.get("openingElement").get("attributes").find(a => a.node.name?.name === "is"),
isCustomElement = tagName.indexOf("-") > -1 || path.get("openingElement").get("attributes").some(a => a.node?.name?.name === "is" || a.name?.name === "is"),
isImportNode = (tagName === 'img'||tagName === 'iframe') && path.get("openingElement").get("attributes").some(a => a.node.name?.name === "loading" && a.node.value?.value === "lazy"
),
results = {
template: `<${tagName}`,
templateWithClosingTags: `<${tagName}`,
declarations: [],
exprs: [],
dynamics: [],
postExprs: [],
isSVG: wrapSVG,
hasCustomElement: isCustomElement,
isImportNode,
tagName,
renderer: "dom",
skipTemplate: false
Expand All @@ -95,13 +99,17 @@ export function transformElement(path, info) {
return results;
}
}
if (wrapSVG) results.template = "<svg>" + results.template;
if (wrapSVG) {
results.template = "<svg>" + results.template;
results.templateWithClosingTags = "<svg>" + results.templateWithClosingTags;
}
if (!info.skipId) results.id = path.scope.generateUidIdentifier("el$");
transformAttributes(path, results);
if (config.contextToCustomElements && (tagName === "slot" || isCustomElement)) {
contextToCustomElement(path, results);
}
results.template += ">";
results.templateWithClosingTags += ">";
if (!voidTag) {
// always close tags can still be skipped if they have no closing parents and are the last element
const toBeClosed =
Expand All @@ -114,6 +122,7 @@ export function transformElement(path, info) {
} else results.toBeClosed = info.toBeClosed;
if (tagName !== "noscript") transformChildren(path, results, config);
if (toBeClosed) results.template += `</${tagName}>`;
results.templateWithClosingTags += `</${tagName}>`;
}
if (info.topLevel && config.hydratable && results.hasHydratableEvent) {
let runHydrationEvents = registerImportMethod(
Expand All @@ -123,7 +132,10 @@ export function transformElement(path, info) {
);
results.postExprs.push(t.expressionStatement(t.callExpression(runHydrationEvents, [])));
}
if (wrapSVG) results.template += "</svg>";
if (wrapSVG) {
results.template += "</svg>";
results.templateWithClosingTags += "</svg>";
}
return results;
}

Expand Down Expand Up @@ -216,6 +228,13 @@ export function setAttr(path, elem, name, value, { isSVG, dynamic, prevId, isCE,
return t.assignmentExpression("=", t.memberExpression(elem, t.identifier("data")), value);
}

if(namespace === 'bool') {
return t.callExpression(
registerImportMethod(path, "setBoolAttribute", getRendererConfig(path, "dom").moduleName),
[elem, t.stringLiteral(name), value]
);
}

const isChildProp = ChildProperties.has(name);
const isProp = Properties.has(name);
const alias = getPropAlias(name, tagName.toUpperCase());
Expand Down Expand Up @@ -270,7 +289,7 @@ function transformAttributes(path, results) {
attributes = path.get("openingElement").get("attributes");
const tagName = getTagName(path.node),
isSVG = SVGElements.has(tagName),
isCE = tagName.includes("-"),
isCE = tagName.includes("-") || attributes.some(a => a.node.name?.name === 'is'),
hasChildren = path.node.children.length > 0,
config = getConfig(path);

Expand Down Expand Up @@ -542,15 +561,29 @@ function transformAttributes(path, results) {
children = value;
} else if (key.startsWith("on")) {
const ev = toEventName(key);
if (key.startsWith("on:") || key.startsWith("oncapture:")) {
const listenerOptions = [t.stringLiteral(key.split(":")[1]), value.expression];
if (key.startsWith("on:")) {
const args = [elem, t.stringLiteral(key.split(":")[1]), value.expression];

results.exprs.unshift(
t.expressionStatement(
t.callExpression(
registerImportMethod(
path,
"addEventListener",
getRendererConfig(path, "dom").moduleName,
),
args,
),
),
);
} else if (key.startsWith("oncapture:")) {
// deprecated see above condition
const args = [t.stringLiteral(key.split(":")[1]), value.expression, t.booleanLiteral(true)];
results.exprs.push(
t.expressionStatement(
t.callExpression(
t.memberExpression(elem, t.identifier("addEventListener")),
key.startsWith("oncapture:")
? listenerOptions.concat(t.booleanLiteral(true))
: listenerOptions
args
)
)
);
Expand Down Expand Up @@ -697,6 +730,54 @@ function transformAttributes(path, results) {
isCE,
tagName
});
} else if(key.slice(0, 5) === 'bool:'){

// inline it on the template when possible
let content = value;

if (t.isJSXExpressionContainer(content)) content = content.expression;

function addBoolAttribute() {
results.template += `${needsSpacing ? " " : ""}${key.slice(5)}`;
needsSpacing = true;
}

switch (content.type) {
case "StringLiteral": {
if (content.value.length && content.value !== "0") {
addBoolAttribute();
}
return;
}
case "NullLiteral": {
return;
}
case "BooleanLiteral": {
if (content.value) {
addBoolAttribute();
}
return;
}
case "Identifier": {
if (content.name === "undefined") {
return;
}
break;
}
}

// when not possible to inline it in the template
results.exprs.push(
t.expressionStatement(
setAttr(
attribute,
elem,
key,
t.isJSXExpressionContainer(value) ? value.expression : value,
{ isSVG, isCE, tagName },
),
),
);
} else {
results.exprs.push(
t.expressionStatement(
Expand All @@ -718,6 +799,7 @@ function transformAttributes(path, results) {
} else {
!isSVG && (key = key.toLowerCase());
results.template += `${needsSpacing ? ' ' : ''}${key}`;
results.templateWithClosingTags += `${needsSpacing ? ' ' : ''}${key}`;
if (!value) {
needsSpacing = true;
return;
Expand All @@ -737,6 +819,7 @@ function transformAttributes(path, results) {
if (!text.length) {
needsSpacing = false;
results.template += `=""`;
results.templateWithClosingTags += `=""`;
return;
}

Expand All @@ -762,9 +845,11 @@ function transformAttributes(path, results) {
if (needsQuoting) {
needsSpacing = false;
results.template += `="${escapeHTML(text, true)}"`;
results.templateWithClosingTags += `="${escapeHTML(text, true)}"`;
} else {
needsSpacing = true;
results.template += `=${escapeHTML(text, true)}`;
results.templateWithClosingTags += `=${escapeHTML(text, true)}`;
}
}
}
Expand Down Expand Up @@ -818,6 +903,7 @@ function transformChildren(path, results, config) {
const i = memo.length;
if (transformed.text && i && memo[i - 1].text) {
memo[i - 1].template += transformed.template;
memo[i - 1].templateWithClosingTags += transformed.templateWithClosingTags || transformed.template;
} else memo.push(transformed);
return memo;
}, []);
Expand All @@ -830,6 +916,9 @@ function transformChildren(path, results, config) {
}

results.template += child.template;
results.templateWithClosingTags += child.templateWithClosingTags || child.template ;
results.isImportNode = results.isImportNode || child.isImportNode;

if (child.id) {
if (child.tagName === "head") {
if (config.hydratable) {
Expand Down Expand Up @@ -878,6 +967,7 @@ function transformChildren(path, results, config) {
childPostExprs.push(...child.postExprs);
results.hasHydratableEvent = results.hasHydratableEvent || child.hasHydratableEvent;
results.hasCustomElement = results.hasCustomElement || child.hasCustomElement;
results.isImportNode = results.isImportNode || child.isImportNode;
tempPath = child.id.name;
nextPlaceholder = null;
i++;
Expand Down Expand Up @@ -931,6 +1021,7 @@ function createPlaceholder(path, results, tempPath, i, char) {
config = getConfig(path);
let contentId;
results.template += `<!${char}>`;
results.templateWithClosingTags += `<!${char}>`;
if (config.hydratable && char === "/") {
contentId = path.scope.generateUidIdentifier("co$");
results.declarations.push(
Expand Down Expand Up @@ -980,7 +1071,7 @@ function detectExpressions(children, index, config) {
} else if (t.isJSXElement(child)) {
const tagName = getTagName(child);
if (isComponent(tagName)) return true;
if (config.contextToCustomElements && (tagName === "slot" || tagName.indexOf("-") > -1))
if (config.contextToCustomElements && (tagName === "slot" || tagName.indexOf("-") > -1 || child.openingElement.attributes.some(a => a.name?.name === 'is')))
return true;
if (
child.openingElement.attributes.some(
Expand Down
9 changes: 7 additions & 2 deletions packages/babel-plugin-jsx-dom-expressions/src/dom/template.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,17 @@ export function appendTemplates(path, templates) {
cooked: template.template,
raw: escapeStringForTemplate(template.template)
};

const shouldUseImportNode = template.isCE || template.isImportNode

return t.variableDeclarator(
template.id,
t.addComment(
t.callExpression(
registerImportMethod(path, "template", getRendererConfig(path, "dom").moduleName),
[t.templateLiteral([t.templateElement(tmpl, true)], [])].concat(
template.isSVG || template.isCE
? [t.booleanLiteral(template.isCE), t.booleanLiteral(template.isSVG)]
template.isSVG || shouldUseImportNode
? [t.booleanLiteral(!!shouldUseImportNode), t.booleanLiteral(template.isSVG)]
: []
)
),
Expand Down Expand Up @@ -75,8 +78,10 @@ function registerTemplate(path, results) {
templates.push({
id: templateId,
template: results.template,
templateWithClosingTags: results.templateWithClosingTags,
isSVG: results.isSVG,
isCE: results.hasCustomElement,
isImportNode: results.isImportNode,
renderer: "dom"
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import * as t from "@babel/types";
import { getRendererConfig, registerImportMethod } from "./utils";
import { appendTemplates as appendTemplatesDOM } from "../dom/template";
import { appendTemplates as appendTemplatesSSR } from "../ssr/template";
import { isInvalidMarkup } from "./validate.js";
const { diff } = require("jest-diff");

// add to the top/bottom of the module.
export default path => {
Expand All @@ -16,6 +18,21 @@ export default path => {
);
}
if (path.scope.data.templates?.length) {
for (const template of path.scope.data.templates) {
const html = template.templateWithClosingTags;
// not sure when/why this is not a string
if (typeof html === "string") {
const result = isInvalidMarkup(html);
if (result) {
const message =
"The HTML provided is malformed and will yield unexpected output when evaluated by a browser.\n";
console.warn(message);
console.log(diff(result.html, result.browser));
console.warn("Original HTML:\n", html);
// throw path.buildCodeFrameError();
}
}
}
let domTemplates = path.scope.data.templates.filter(temp => temp.renderer === "dom");
let ssrTemplates = path.scope.data.templates.filter(temp => temp.renderer === "ssr");
domTemplates.length > 0 && appendTemplatesDOM(path, domTemplates);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const JSXValidator = {
JSXElement(path) {
const elName = path.node.openingElement.name;
const parent = path.parent;

if (!t.isJSXElement(parent) || !t.isJSXIdentifier(elName)) return;
const elTagName = elName.name;
if (isComponent(elTagName)) return;
Expand Down
5 changes: 3 additions & 2 deletions packages/babel-plugin-jsx-dom-expressions/src/shared/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@ export const reservedNameSpaces = new Set([
"style",
"use",
"prop",
"attr"
"attr",
"bool"
]);

export const nonSpreadNameSpaces = new Set(["class", "style", "use", "prop", "attr"]);
export const nonSpreadNameSpaces = new Set(["class", "style", "use", "prop", "attr", "bool"]);

export function getConfig(path) {
return path.hub.file.metadata.config;
Expand Down
Loading
Loading