Giter VIP home page Giter VIP logo

uhtml's Introduction

webreflection

WebReflection Ltd

uhtml's People

Contributors

danielbeeke avatar dependabot[bot] avatar humphd avatar lightgazer avatar sirpepe avatar webreflection avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

uhtml's Issues

Question: Live layout + style + values + input

Hi, I'm coming from a bit different background (long-running reliable distributed systems), so bear with me in this context of web development as I'll think of it differently than genuine web devs.

I'm looking at different projects which could fit in the following use case and uhtml with other libs in the wild seems so far the best I could find IMHO.

I envision having a server and client whereas server persists everything going through it (both in & out). Both server and client are nearly not limited, so I'd highly prefer using most modern technologies. Backward compatibility is a non-goal and I'm fine with supporting only one web browser (e.g. Chromium).

Any event the server will generate (e.g. in response to disk corruption) will be immediately sent (multiplexed aka "muxed") to all currently connected clients. Any event client will generate (e.g. user pressed a button or wrote a character to text input) shall be immediately sent to the server and the server shall first validate the event (incl. authorization, data integrity, ...), then process it, then persist the outcome (e.g. the pressed button changed label so the server shall since that very moment server only the new label on that button to all clients) and then distribute ("push") the changed of the state (in this case the new label) to all currently connected clients and all these clients shall in response to the push change the button label (note that any newly connected clients afterwards will already get the new button label as it got persisted and won't need any such "patches" to be sent around).

So far I thought that the communication channel between server and clients could be a websocket (to maintain the connection state and make muxing presumably easy) and that the server could be fastify (with fastify-websocket) and on the client uhtml with concur-js.

But I'm struggling deciding how the data to be sent over the websocket channel should look like. I'd naturally expect to have two types of messages from server to client (s2c) and one type of messages from client to server (c2s). For s2c it'd be "keyframes" (i.e. whole HTML to be rendered by client whereas client would take it and render as is) and "patches" (i.e. just parts of an already rendered HTML whereas client would apply these patches). For c2s it'd be only "requests for patch" with "version of the HTML on the client with which the request was generated" (server could e.g. choose to not satisfy the request if the version would be different than the latest version in the given s2c connection queue if the queue is not empty by the time the request arrived). The "keyframes" would be sent e.g. when a new client connected to the web server (incl. forced reload). Otherwise only "patches" and "requests for patches" would be sent.

In this architecture I'm not sure how to generate the diffs on server and how to apply them on client. uhtml (or maybe ucontent similarly to Isomorphic Hacker News?) doesn't seem to have any API allowing creation (and later their application) and (de)serialization of patches for transfer over a websocket.

(other, but slow and missing features, alternatives could be https://github.com/ms-jpq/noact and https://github.com/zserge/o )

Judging based on #20 it doesn't seem impossible, but I'll rather take advice from an expert than banging my head in the future 😉.

Question: How to avoid duplicating work in uhtml?

I think I misunderstand the purpose of html.for(). I'm attempting to avoid unnecessary work on rerender. Right now, if given a list of 1000 items, upon changing just one of them, the entire 1000 items will rerender. Or at least their render functions will run unnecessarily. DOM manipulation may only be limited to the item that changed.

Coming from a React background, it's fairly easy to avoid entire render trees if the props of the parents did not change. How do you suggest avoiding rerenders of nested components when their input data does not change? Is there a uhtml-specific way? Or is the solution to roll my own memoizer? I'm just not sure how I should be approaching the problem.

Secondarily, but related, how do you suggest we implement nested components that themselves do not generate html, but only help prepare child components to render? Right now, I'm creating an unnecessary template string with a single value in it:

const HelperComponent = (item, index) => {
    const computedIndex = index + 1;
    return html.for(item)`${ChildComponent(item, index, computedIndex)}`;
};

Admittedly, my approach is very component-centric given my React background. Is this an inappropriate approach to use when using uhtml?

I've created a jsfiddle to demonstrate the question: https://jsfiddle.net/8dk5x4wr/1/

And the code for it is duplicated below:

<!doctype html>
<title>List</title>

<div id="root"></div>

<script src="https://unpkg.com/[email protected]/min.js"></script>
<script>
const { html, render } = uhtml;

const appState = {
    list: [],
};

const root = document.getElementById('root');

// Initialize state
for (let i = 0; i < 1000; i += 1) {
    appState.list.push({
        id: i,
        text: `Item ${i + 1}`,
        checked: false,
    });
}

const ListItem = (item, index, onChangeCheckboxAt) => {
    // Every time one checkbox is clicked, 1000 ListItems rerender (or at least their render functions run, duplicating unnecessary work)
    console.log('Some expensive calculation');

    const onChangeCheckbox = () => {
        onChangeCheckboxAt(!item.checked, index);
    };

    return html`
        <li>
            <label>
                <input type="checkbox" onChange=${onChangeCheckbox} .checked=${item.checked} />
                ${item.text}
            </label>
        </li>
    `;
};

const onChangeCheckboxAt = (value, index) => {
    // Immutably update list while retaining old item references
    appState.list = [
        ...appState.list.slice(0, index),
        {
            ...appState.list[index],
            checked: value,
        },
        ...appState.list.slice(index + 1),
    ];

    // Rerender
    render(root, App(appState));
};

const App = (state) => {
    return html`
        <div>
            <h1>My List</h1>

            <ul>
                ${state.list.map((item, index) => {
                    // How do I render a child component that doesn't require surrounding html?
                    // Right now, I have to surround it in a meaningless template string.
                    // Also, because the function for ListItem is called immediately in order to inject it into the template string,
                    //   it is evaluated immediately, doing unnecessary work for items that have not changed
                    // How do we tell uhtml "use the results of the last render instead of running this component again?"
                    return html.for(item)`${ListItem(item, index, onChangeCheckboxAt)}`;
                })}
            </ul>
        </div>
    `;
};

// Startup
render(root, App(appState));
</script>

Render arguments into existing HTMLElement

I'm looking into applying uhtml in a context similar to Vue, where there is one code entity creating a component:

html`<div> <my-icon id="icon1" fa="warning" onclick=${handler} /> </div>`

And the component itself which also specifies its attributes and internal children:

html`<my-icon internalattrs="..." onenter=${internalhandler}> <implementation-bits /> </my-icon>`

Can the uhtml logic be used in a way, so that when my-icon#icon1 is provided as HTMLElement, the rendering of my-icon in the second fragment will apply to this existing instance (i.e. setting internalattrs and onenter) instead of creating a new one?

So in a way, the attributes from the first and second template literal are merged into a single HTMLElement.

Alternatively, can the attribute apply logic be used in isolation on an existing HTMLElement, e.g. if just the attribute part of the second template string is provided (which could be extracted with moderate effort)? I.e. something like:

render_attrs(existing_element)` internalattrs="..." onenter=${internalhandler} `;

Incompatible with NextJs

I've created a custom element based on Uhtml and imported it into the NextJs app. I faced with this error:

image

Do you have any plans for SSR support? I need it urgently.

node is not instanceof HTMLElement when imported from uhtml v3

I upgraded uhtml to v3 in my package.json and then build the library using esbuild.

I import uhtml like so but nodes from tagged html.node are not instanceof HTMLElement. This printed true when I install [email protected]

import {render, html, svg} from 'uhtml'

let node = html.node`<div>Foo`

console.log(node instanceof HTMLElement) //false

Oddly though I tried to test this in a codepen and everything worked as expected.

[email protected] as well as 3.0 both return the node as instanceof HTMLElement.

// import { html } from "https://unpkg.com/[email protected]/esm/index.js?module";

import { html } from "https://unpkg.com/[email protected]/esm/index.js?module";

let node = html.node`<div>Foo`

console.log(node instanceof HTMLElement) //true

Not really sure what I am doing wrong or how to better decribe the issue.

Anyways. Not a biggie since we are super happy with [email protected] but maybe something we can look into at our side.

Weird issue with re-render containing html.node / html.for

I've been experiencing a strange issue using render to change content in a project which uses html.node.

<html>
  <head>
    <script type="module">
      import {render, html} from 'https://unpkg.com/uhtml?module';

      window.addEventListener('load', () => {
        const testTemplate = template => render(document.body, html`${template}`);
        const div = html.node`<div>reference</div>`;
        testTemplate(html`${div}`);
        testTemplate(html`${[div]}`);
      });
    </script>
  </head>
  <body>
  </body>
</html>

I know this is not a practical example, it's what I was able to create as a minimal example of this issue. During the second render the <div> is removed and an exception is thrown. This also happens if you use html.for to initialize div.

Script tag execution

With lighterhtml, if a script tag is included in the HTML template, it is inserted and executed when rendered. With uhtml it is inserted but not executed. Is this expected behavior?

To test, just include the below as part of a template.

<script>console.log("I was executed")</script>

It is OK if this is expected, I will just use lighterhtml.

Nested map (for cells in a grid)

Hey thanks for the lib looks sweet. I'm checking I have the syntax here right (guessing not) as it throws on subsequent renders after the first one:

  render = () => {
    console.log(this._columnRange); // [0, 1]
    console.log(this._rowRange); // [0, 1, 2, 3, 4, 5, 6, 7]

    const canvas = html`
      <div class="canvas" style=${`width:${this._canvasWidth}px;height:${this._canvasHeight}px`}>
        ${this._rowRange.map(
          rowIndex =>
            html.node`${this._columnRange.map(columnIndex => {
              return html`<div>cell</div>`;
            })}`
        )}
      </div>
    `;

    render(this._root, canvas);
  };

This is to render a range of cells in a virtual window, and the second render has the same range values. On the second render it throws the following error:

Screenshot 2021-01-07 at 23 22 44

Where am I going wrong here? Also I see perf seems to be about the same with non-keyed items on https://github.com/krausest/js-framework-benchmark which is cool, but not sure how it's being done e.g. maybe the html strings need to be unique? (which they would be once I add the styles to them). Thanks

Supporting `@...` for events

Kudos, Andrea, you really have some pretty cool projects running 👍

A little (slightly opinionated 😉) proposal (I do not know, whether this has already been discussed somewhere):

Now that uhml supports . and ? ...

<an-element an-attr="..." .aProp=${...} ?a-bool-attr=${...} onan-event=${...}/>

... wouldn't it make sense to go the extra mile and also add support for @... for events (similar to lit-html) as alternative to on...?
on... should of course continue to be allowed (I personally would declare on... deprecated and get rid of it in the major version after the next major version, but anyway).

<an-element an-attr="..." .aProp=${...} ?a-bool-attr=${...} @an-event=${...}/>

I guess that if the development of uhtml had been started in 2021 with all the current knowledge, there would not have been this ugly JSX-like on... convention (which makes sense in JSX but not in uhtml, or does it?)

Things would be at least a bit more compatible with lit-html .... and please compare the readabilty of

<sl-details summary="..." onsl-show=${...} onsl-hide=${...}>...</sl-details>

to

<sl-details summary="..." @sl-show=${...} @sl-hide=${...}>...</sl-details>

Script tags generated with uhtml do not execute when appended

I am in the process of replacing hyperhtml with microhtml in my library. This works great apart from one tiny thing which I cannot work out.

I used to add script tags with hyperhtml which would execute when appended. This doesn't seem to work with microhtml.

I created a small codepen to test the behaviour.

https://codepen.io/dbauszus-glx/pen/MWyYQWK

I created an alert('foo') script which I put on my github pages.

https://geolytix.github.io/public/foo.js

This will execute the script.

  .appendChild(
    hyperHTML.wire()`<script src="https://geolytix.github.io/public/foo.js">`
  );

But this won't.

  .appendChild(
    html.node`<script src="https://geolytix.github.io/public/foo.js">`
  );

Is this a bug or am I doing something wrong with uhtml?

input type date value is not updated when data changed

Hi @WebReflection,

Please look at this

import {render, html} from '//cdn.skypack.dev/uhtml/json';

let myDate = "2021-12-21";
const greetings = who => html.json`
  <h1>Hello ${who} 👋</h1>
  <input type=date .value=${myDate} />
  <button onclick=${e => {myDate = new Date().toISOString().substr(0,10); console.log(myDate)}}>Update</button>
  <p>Posted @ ${new Date().toISOString()}</p>
`;

render(document.body, greetings("uhtml"));

Conditional directly inside a Map not rendering correctly

I don't know if this is by design or if it's a bug, but here goes:

The following snippet (from an accommodation application) correctly adds extra rows when the number of rooms or number of children increases, it also correctly removes rows when children are removed from rooms.
However, when removing rooms, the rows aren't removed from the DOM - room removal is done via array slice, so each change to the rooms are creating a new instance of the array.

${this.state.rooms?.value?.map((room, index) => (html`
	${room?.children.length > 0 ? (html`
		<div>
			${room.allChildrenAgesFilledIn() ? html`&#x2713;` : html`&nbsp;`}
		</div>
		<div>
			<span class="room-title">
				Room ${index + 1}
			</span>
			requires children ages
		</div>
	`) : null}
`))}

When wrapping the inner "loop" in a DOM element

${this.state.rooms?.value?.map((room, index) => (html`
        <div>
	${room?.children.length > 0 ? (html`
		<div>
			${room.allChildrenAgesFilledIn() ? html`&#x2713;` : html`&nbsp;`}
		</div>
		<div>
			<span class="room-title">
				Room ${index + 1}
			</span>
			requires children ages
		</div>
	`) : null}
	</div>
`))}

or simply adding a DOM element in the inner loop

${this.state.rooms?.value?.map((room, index) => (html`
        <!-- DOM update workaround -->
	${room?.children.length > 0 ? (html`
		<div>
			${room.allChildrenAgesFilledIn() ? html`&#x2713;` : html`&nbsp;`}
		</div>
		<div>
			<span class="room-title">
				Room ${index + 1}
			</span>
			requires children ages
		</div>
	`) : null}
`))}

then the issue resolves itself and DOM updates are correctly removing nodes

Maybe html` ` with emptyspace is the issue here

Writing it with a filter and then a map, works, and seems like a cleaner solution which is what I'll be doing in my case:

${this.state.rooms?.value?.filter(room => room.children.length > 0).map((room) => html`...`

Invalid value used as weak map key

This very simple example results in an error. I am probably doing something very simple wrong, but I sure can't figure out what. I have attempted multiple other "wires" via "for" without success.

<html>
 	<head>
		<script src="https://unpkg.com/uhtml"></script>
	<head>
	<body>
		<script>
		const html = uhtml.html,
			render = uhtml.render,
			items = [{text:"test item"}];
		render(document.getElementById("app"), html`
		  <ul>
		    ${items.map(item => html.for(item)`
		      <li>Keyed row with content: ${item.text}</li>
		    `)}
		  </ul>
		`);
		</script>
		<div id="app">
		
		</div>
	</body>
</html>

min.js:1 Uncaught TypeError: Invalid value used as weak map key
at WeakMap.set ()
at Object.set (min.js:1)
at e.render (min.js:1)
at uhtml.html:10

Issue when re-rendering empty

note: Maybe this is not an issue, probably a wrong usage of the API, but as this wasted few hours I think it worth discussing.

I've a hooked-element/uhtml (however only uhtml seems involved in the issue) project where a component was rendering a set of items.

this.html`
      <ul>
        ${items.map((item) =>
            checkSomething(item) ? html`<li>${item.something}</li>` : html``
        )}
      </ul>`
  );

Some items must not be displayed, based on external factors.

I started this by using a react-like approach, where inside an items.map I was returning "null" to "render nothing".
I noted that returning "null" or empty strings is the wrong way, so I tried html`` and it worked.

But the issue arise when there's a re-render. In this case I get an error:

Uncaught DOMException: Node.insertBefore: Child to insert before is not a child of this node

I suspect the issue is related to multiple empty block one near another.

Here a codesandbox example:
https://codesandbox.io/s/uhtml-empty-issue-c7otb?file=/src/index.js

  • first run at page load: everything is OK
  • after click on "run" again: you (can) get the error

Question: although I easily fixed this by filtering out elements I don't want to display I'm wondering if this is an issue, or maybe something to be better documented. In any case I'm curious about what's wrong with html``.

Thanks!

Custom Element MutationObserver

I am not sure if this is related to uhtml or I am doing someting wrong. I am trying to attach a MutationObserver to a Custom Element with shadow DOM and watch for changes in the template (outside shadow DOM, not sure what the name is for this).

html`
  <custom-element>${someValue}</custom-element>
`

I am looking to catch re-render event and apply content changes to my shadow DOM.

I am attaching [non-]working pen to illustrate my issue:
https://codepen.io/lemmonjuice/pen/rNMxxOw

When re-rendering the template nothing happens. However if I access the element directly and either change the textContent value or append a child the observer gets triggered. I was expecting that the re-render will trigger the observer as well. Checking the browser inspector I am seeing that the contetn was in fact updated properly.

Thanks.

Describe IE11 support more precisely in Readme

Hi,

I like using this project, thank you. The readme states that IE11 is supported, but in my project I need to setup webpack so that it transpiles uhtml in node_modules too. I understand that ES6 NPM modules are the future and this should even help with bundling, but I am wondering if I am doing something wrong?

This is my webpack setup, note where I use needsES5Support:

const path = require("path");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const AddAssetHtmlPlugin = require("add-asset-html-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");

const isProduction = require(path.join(__dirname, "is-production.js"));
const isEvergreenBuild = require(path.join(__dirname, "is-evergreen-build.js"));
const needsES5Support = require(path.join(__dirname, "needs-es5-support.js"));

const babelLoaderEntry = {
  loader: "babel-loader",
  options: {
    presets: [
      [
        "@babel/preset-env",
        {
          // debug: !isProduction,
          modules: false, // Needed for tree shaking to work.
          corejs: 3,
          useBuiltIns: "usage",
          include: [
            // usage of syntax in uhtml expressions are not detected automatically
            "@babel/plugin-proposal-optional-chaining",
          ],
          targets: isEvergreenBuild
            ? "last 2 Chrome versions, last 2 Firefox versions"
            : undefined,
        },
      ],
      "@babel/preset-typescript", // ignoring Webpacks (get-started) docs, fixes 'useBuiltIns: "usage"', from https://2ality.com/2019/10/babel-loader-typescript.html
    ],
  },
};

module.exports = {
  mode: isProduction ? "production" : "development",
  devServer: isProduction
    ? undefined
    : {
        contentBase: path.join(__dirname, "dist"),
      },
  devtool: isProduction ? undefined : "eval-source-map",
  entry: [path.join(__dirname, "src", "index.ts")],
  output: {
    filename: isProduction ? "[name].[contenthash].js" : "[name].js",
    chunkFilename: isProduction
      ? "[name].[contenthash].chunk.js"
      : "[name].chunk.js",
    path: path.resolve(__dirname, "dist"),
    publicPath: isProduction ? undefined : "/", // for devServer
  },
  optimization: {
    usedExports: true,
    moduleIds: "hashed",
    runtimeChunk: "single",
    splitChunks: {
      cacheGroups: {
        vendor: {
          test: /[\\/]node_modules[\\/](?!immer).*/,
          name: isProduction ? false : "vendor",
          chunks: "all",
        },
        dynamic: {
          test: /[\\/]node_modules[\\/](immer)[\\/]/,
          name: isProduction ? false : "dynamic",
        },
      },
    },
  },
  plugins: [
    new CleanWebpackPlugin({
      cleanStaleWebpackAssets: isProduction ? true : false,
    }),
    new HtmlWebpackPlugin({
      title: "Test",
    }),
    new AddAssetHtmlPlugin({
      filepath: path.resolve(__dirname, "env-script.js"),
    }),
    new MiniCssExtractPlugin({
      filename: "[name].[contenthash].css",
    }),
  ],
  resolve: {
    extensions: [".ts", ".js"],
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        // test: /\.(sa|sc|c)ss$/,
        use: [
          {
            loader: MiniCssExtractPlugin.loader,
            options: {
              esModule: true,
            },
          },
          "css-loader",
          // "postcss-loader",
          // "sass-loader",
        ],
      },
      {
        test: /\.(woff|woff2|eot|ttf|otf)$/,
        use: ["file-loader"],
      },
      ...(needsES5Support
        ? [
            {
              // uhtml package is ES6
              test: /node_modules(?:\/|\\)uhtml|uparser|uwire|umap|uarray|udomdiff|uhandlers/,
              use: babelLoaderEntry,
            },
          ]
        : []),
      {
        test: /\.ts$/,
        exclude: /node_modules/,
        use: babelLoaderEntry,
      },
    ],
  },
};

xlink:href attribute doesn't work

I'd like to dynamically set the <use xlink:href> attribute, so I can use SVG sprites easier. This is my ideal:

const name = 'check'
const href = `#${name}` // Or `icons.svg#${name}`

render(target, html`
  <svg>
    <use xlink:href=${href}></use>
  </svg>
`)

However, uhtml doesn't seem to work well with the xlink:href attribute. Could you please review this, and determine whether this attribute should be included in some exception? Perhaps there's a different way to do this, that you'd recommend?

I put together a demo on CodePen with several examples to show how v1.9.0 works (1, 2, 4) and doesn't work (3, 5) with sprites. If it works, you should see a green check in the square box. I also commented out a line that would import the latest version of lighterhtml, so you can easily see that the same issue exists in that library. (I started experimenting with uce this week, so that's why I posted this issue to uhtml.)

Thanks for all your wonderful work and for your consideration!

uhtml factory function

Hello 👋

I just wanted to say I really appreciate your various OSS libaries greatly! I'm usually careful about my dependencies and all your *html and related projects are a no-brainer for me and I've been using them for rendering as much as I can, thank you 🙏

I'm working on my own tooling that would does some orchestration around when to render some "components" using requestAnimationFrame/requestIdleCallback/etc, and I'd love to be able to run uhtml directly within my tests using linkedom as the DOM implementation, but I quickly ran into issues where uhtml makes use of the document global.

I wouldn't ask to change the API, it works great as it is for the standard case, but I wonder if it'd be possible to create a factory function which is capable of making isolated runtimes with their own caches and other state and reference to globals more condusive to testing? Maybe I'm using it wrong? Either way I'd love to hear what you think

Chrome fires blur event on node removal causing error when rendering on more than 1 callback

Hi, thanks again for making uhtml, it's very stable! The following is browser specific issue, though uhtml could do thing that tolerates this rare problematic behaviour.

This only happen on Chrome (tested on latest version):

When a node is removed, Chrome fires blur event on node removal, when we have another callback rendering html`` that causes node removed && added (re-attached), uhtml can throws this exception:

Uncaught NotFoundError: Failed to execute 'removeChild' on 'Node': The node to be removed is no longer a child of this node. Perhaps it was moved in a 'blur' event handler?"

https://jsfiddle.net/um0kzdjx/31/
Inline:

import {html, render} from "https://unpkg.com/[email protected]/esm/index.js?module"

var items = [{key: "a"},{key: "b"}, {key: "c"}]
const renderItems = (items) => 
  html`
    <ul id="list">
      ${items.map((item, i) => html.for(item, item.key)`
      <li 
        tabindex="-1"
      	onblur="${e => rerender(items)}"
        onclick="${e => {
          items[i] = {...item, key: item.key + "_"} 
          rerender(items)
        }}"
      >
      	${item.key}
      </li>`)}
    </ul>
  `
 
const rerender = (items) => render(document.body, renderItems(items))
rerender(items)

This is not uhtml issue, so feel free to skip if it's considered unnecessary enhancement 😄 . (I fixed this by try not losing item reference, so no node removed). Thanks!

Handling null/undefined values in arrays

Hey,
I've been using uhtml in a project, and it's been great! Thank you for your great work!
I have one pet peeve though.
For example, you can have a ternary conditional to decide if you want to render a node or not, and null values are properly skipped over. Hovever, this doesn't work in arrays.

const show = false
console.log(html.node`<div>${show ? html`<span>5</span>` : null}</div>`)
console.log(html.node`<div>${[html`<span>5</span>`, null]}</div>`)

The use-case is simple, I'd like to .map over an array and skip some items based on some condition.
Now, I know that I could have a .filter at the end, to filter out the null or undefined values. But maybe it'd make sense to handle the two cases the same way.
Right now the array case throws an error:

Uncaught TypeError: Cannot read property 'nodeType' of null

Also, this version results doesn't even result in the error, but in an incorrect tree:

console.log(html.node`<div>${[undefined, html`<span>5</span>`]}</div>`)

// Returns:
// <div>,[object HTMLSpanElement]<!--isµ0--></div>

What do you think? Is this a bug or expected behavior?

Issue rendering dynamic comments

(probably follows discussion on #41)

Although I'm applying your suggestion about filtering instead of trying to return nullish stuff, sometimes I ended up applying the html`<!-- something -->` approach as, in some cases, it's easy for me to use the ternary operator.

I just noticed that I can't use a dynamic value inside the comment. This fails:

const comment = 'here we are'

html`${html`<!-- ${comment} -->`}

This is the error:

bad template: <!-- <!--isµ0--> -->

Example: https://codesandbox.io/s/admiring-cori-tzc6h?file=/src/index.js

Not a real issue for me, it was more a debugging code, but I'm wondering if this is an issue. 👋

Question: Dynamic tags

Hi. Firstly, thanks for this library.

I had a use-case and was wondering if it is possible using uhtml.

Given a list of objects as:

elements = [{ tag: "div", content: "Hello" }, { tag: "h1", content: "World" }]

I would like to render nodes with the corresponding tag to a div for example.

I tried the following code and it (understandably) did not work.

render(
  this.element,
  html`${elements.map((elem) => html.node`<${elem.tag}>${elem.content}</${elem.tag}>`)}`
)

I was wondering if there is some way to achieve what I am looking for.

Textarea with state based content

Hi,
First thanks for uhtml !
Second, I'd say this is more of a question than a bug. I tried to create a textarea and make it state based. My first try was to add an onkeyup on my textarea to update my state value and display this state into my textarea. codepen
With onkeyup, textarea is updated before uhtml is called to re-render it and when I empty my state, it looks like a reference is lost.
Then I tried the onkeydown codepen with a event.preventDefault() to cancel textarea update. Then uhtml correctly render state value and empty it.
I guess I should use document.querySelector(..).value in this case but wanted to know if I missed something here.

Interpolated content that should have been removed sometimes "come back" in future renders

I found a weird bug in my project where something that was removed from a previous render reappeared unexpectedly when moving the return value of html.for`${something}` to another parent.

For example

import {render, html} from 'uhtml';
const thing1 = html.for({})`Thing 1`;
const thing2 = html.for({})`Thing 2`;

let thing = thing1;

const parentHtml = html.for({});

const renderParent = () => {
  return parentHtml`${thing}`
}

parent = renderParent();

render(container1, parent);

thing = thing2;

renderParent(); // everything works as expected until here

render(container2, parent); container2 says "Thing 1"

If i wrap everthing in a div, like html.for({})`<div>${...}</div>` everything seems to work OK.

I made a codepen that demonstrates the bug better than the above https://codepen.io/richardkazuomiller/pen/vYmPXOV?editors=1111

Is it safe against XSS?

Hi, sorry if this might be a rather trivial question:

I am using a CSP server and use DOMPurify to protect the tagged templates. Sadly, this is removing all the EventListener on the HTMLElements.
So what would you do in order to have safe code but also make good use of this Library?
Thank you 😊

Setting boolean attributes (e.g. checked on checkbox type input)

Should it be possible to set boolean attributes like checked on a checkbox type input with template literals in uhtml?

This works:

render(document.getElementById('chk'), html`
<input
  type="checkbox"
  checked
  onchange=${e => {
    console.log(e.target.checked);
}}>`)

But this fails:

const isChecked = true

render(document.getElementById('chk'), html`
<input
  type="checkbox"
  ${isChecked && 'checked'}
  onchange=${e => {
    console.log(e.target.checked);
}}>`)

I created a codepen for this. https://codepen.io/dbauszus-glx/pen/xxVzjdb

Potential tweaks to allow smaller terser output

I've been using uhtml in my own projects, I noticed a couple small tweaks that could be made to allow better size reduction by terser. This isn't so much a bug report but just seeking feedback before I propose any PR's / investigate further.

Use arrow functions everywhere.

This is tiny, in my own build switching the two functions reduces final bundle output by 23 bytes. This is because terser can take all arrow functions and output var f1=()=>{},f2=()=>{}; where regular functions it has to output function f1(){}function f2(){}. Since arrow functions are already used elsewhere I think this should be a non-issue for compatibility.

Make it easier to detect const IE = importNode.length != 1; in esm/node.js.

For builds targeting modern browsers a babel plugin changing this to const IE = false; reduces the bundle by 96 bytes. This is because it allows terser to perform constant evaluation and eliminate IE == true branches. This isn't strictly required as I could have babel detect the IE variable declaration in uhtml/esm/node.js and replace the initialization value.

One question I have is ESM even supported on any platform where document.importNode.length != 1?

Consider having @ungap/create-content export createSVG and createHTML as separate functions.

This would make it possible for uhtml to use these two functions directly from the html and svg functions, allow tree shaking to eliminate the svg code if it is unused in a project. The idea is that uhtml/esm/index.js would have:

const html = tag(createHTML);
const svg = tag(createSVG);

I haven't fully evaluated the changes that would be needed to have uhtml use these two functions directly instead of having the string type argument and I also haven't looked to see how much this would actually reduce the size. Also not sure if this would be considered a breaking change for the Hole object to no longer have type: 'html'.

Error When Removing Attributes on SVGs (possibly other elements)

According to the documentation, an attribute can be removed from an element by setting its value to null or undefined. When attempting to do this with an SVG element, such as rect with a mask, uhtml attempts to call a non-existent function, removeAttributeNodeNS.

The specific error is: Uncaught TypeError: t.removeAttributeNodeNS is not a function

There are namespace-capable attribute removal functions, but not that one.

Here's a minimal example showing the issue:

import {svg, render} from 'uhtml';

let doMask = true;

const renderSvg = () => {
    return svg`
        <svg viewBox="0 0 100 100" style="width: 100px; height: 100px;">
            <mask id="mask">
                <rect
                    x="50"
                    y="50"
                    width="50"
                    height="50"
                    fill="#fff" /></rect>
            </mask>
            <rect
                x="0"
                y="0"
                width="100"
                height="100"
                mask=${doMask ? 'url(#mask)' : null}
                fill="#f00" /></rect>
        </svg>
    `;
}

render(document.body, renderSvg());

doMask = false;

render(document.body, renderSvg());

Hardcoded list, best way?

I have a fixed list of items, created as following:

var options = "";
for (var i = 1; i <= 15; ++i)
{
    options += `<option value="${i}" selected="${i === 7}">${formatNumber(i)}</option>`;
}

[... insert options inside select using .innerHtml]

I need to insert that list inside a "select", that I'm rendering using uhtml (as part of a template). I know I can solve that with .map over a fixed array, but I would like to know if there is a better way to solve this kind of situations, where I really don't need an array to create my list.

SVG object loading

Hi,
I tried to render an interactive svg with the following code
the issue : it loops (n loaded on console, blinking on screen).
Thank you.
I can write a code pen if needed.

const dispatch=(id, detail)=>()=>document.dispatchEvent(new CustomEvent(id, {detail:detail}))
const svg_test=(id,path)=> html`
        <object
            type="image/svg+xml"
            .data=${path}
            onload=${dispatch(id)}
        </object>
`;
const m= {
    id:'_svg_',
    path:'a valid path'
}
const update=()=> {
    render(document.body, svg_test(m.id, m.path));
}
document.addEventListener(m.id, ()=> {
    console.log("loaded");
    // do some work here like changing the svgs height/scale
    update();
});
update();

HTML Entities

Hello,

I came across some behaviour I wasn't sure if it is intentional or not.
If a HTML Entity is inside a variable that is later rendered the entity is kept as is.

Example:

import { html, render } from "uhtml";

const entityString = 'Tom &amp; Jerry';

render(document.body, html`
  <span>${ entityString }</span>
  </br>
  <span>Tom &amp; Jerry</span>
`);

Expected output(?):

Tom & Jerry
Tom & Jerry

Current output:

Tom &amp; Jerry
Tom & Jerry

Is this behaviour desirable? Or did i miss something?

Thank you.

Rerender input text

Hello.
I've been using µhtml since yesterday, but I'm having some issues with text type input. It's probably not a problem coming from µhtml, but since I'm stuck and a demo could help improve the documentation, I'm posting it here. So here is the code:

const d = {exp: ''}
const SearchModule = () => {
	console.log('searchModule render :', d.exp)
	const
		refresh = () => render(document.getElementById('hookSearchModule'), SearchModule()),
		changeExp = e => {
			d.exp = e.target.value
			refresh()
		},
		clear = () => {
			d.exp = ''
			refresh()
		}
	return html`
		<input value=${d.exp} oninput=${e => changeExp(e)}>
		${d.exp !== '' ? html`<span onclick=${() => clear()}>Clear</span>` : ''}
	`
}

Two problems :

  1. When the changeExp() function is executed, the text field loses focus.
  2. When we click "Clear", the text remains in the text field, while d.exp = ''.

Can you help me ?

Object attribute support

I need something like .dataset=${object} without data- prefix.

Input
<div .attr="${{foo: 1, 'foo-bar': 2}}"></div>

Output
<div foo="1" foo-bar="2"></div>

Boolean Attributes

I am wondering what is the reason behind setting boolean attributes doesn't work like this:

html`
  <input
    type="checkbox"
    checked=${booleanValue}
  />
`

I understand that you could do either checked=${booleanValue || null} (which is quite odd) or pass it as a value .checked=${booleanValue}. Such statements trigger my OCD, so I had to ask... 😉

You mentioned here #23 (comment) that "you want to put a dot in front". Are there any performance benefits to using value instead of attribute?

Thanks!

SVG Uppercase Attribute Names Lowercased When Injecting Values

Create a normal SVG with a hard-coded viewBox, get correct output:

import {html, svg, render} from 'uhtml';
render(document.body, html`<svg viewBox="0 0 100 100" />`);
// Output in body: <svg viewBox="0 0 100 100"></svg>

Create an SVG with injected viewBox value, get lowercase viewbox attribute, which is invalid and is ignored by the browser, breaking the SVG:

import {html, svg, render} from 'uhtml';
const viewBoxText = '0 0 100 100';
render(document.body, html`<svg viewBox=${viewBoxText} />`);
// Output in body: <svg viewbox="0 0 100 100"></svg>

Using the svg function makes no difference here, still incorrect:

import {html, svg, render} from 'uhtml';
const viewBoxText = '0 0 100 100';
render(document.body, svg`<svg viewBox=${viewBoxText} />`);
// Output in body: <svg viewbox="0 0 100 100"></svg>

There are dozens of SVG attributes that require uppercase letters. Not sure how to proceed.

Question about extending

Hi Andrea,

I have a use case where I want to add css classes to the DOM and let them easily be replaced by the developer.
I am creating a form renderer and I want the developer to be able to choose the css classes.
So I am using uHtml as the template engine. Maybe code explains it a bit better:

What I have currently working:

class MyForm {
  constructor () {
    this.cssClasses = {
      wrapper: ['wrapper', 'container', 'greenish']
    }
  }

  template () {
    return html`<div class=${this.cssClasses.wrapper.join(' ')}>...</div>`
  }
}

I would like to create the following:

  template () {
    return html`<div classy="wrapper: wrapper container greenish">...</div>`
  }

Classy.addReplacement('wrapper', ['my-own-wrapper-class'])

It places the default classes in the same line of code where it is used.
So I am wondering if you plan to build extensibility in the library. Or maybe I could wrap html, do some regex and hand it over to the real html.

What do you think? I would be more than happy to provide a proof of concept or merge request.

Help with hello world

Hi there, I can't get this to work. The esm version runs fine.

<script src="https://unpkg.com/uhtml">

    const { render, html } = uhtml;

    render(document.body, html`<h1>Hello µhtml</h1>`);

</script>

Tried both firefox and chrome latest versions, both through file:// and a node created local server

Question about components (not web components)

I have recently been approaching the lib coming from vdom based libs.

I see I can simply declare components like

const li = v => html`<li class="test">${v}</li>`

const ul = list => html`<ul class="test">${list.map(li)}</ul>`

Is there a way to attache an event listerner to a component (the result of the function) and be able intercept only events from that component?
Try to explain the API I would like to obtain

const isolate =  component => {
   //...
   return {
       component: ...,
       on: selector => event => callback => {
         // .... return unsubsribe
         }
   }
}

const LI = v => html`<li class="test">${v}</li>`

const UL = list => html`<ul class="test"><li>ADD</li>${list.map(isolate(LI))}</ul>`

const {ul, on} = isolate(UL)

on('li')('click')(console.log)

Then the delegate (on the UL? and if I have other siblings?) would act on events on the first

  • but not on other because owned by different components.

    Any suggestion on how to obtain something similar? I think I can hack the template strings and add ref="" to root elements to react to element creations and mark root elements of components in some way. Or there are better solutions?

  • µhtml.node -> valueOf() and remove()

    I will preface this by saying I don't plan on using html.node but the documentation mentions following:

    µhtml fragments have also two special methods: valueOf(), that allow you to move all nodes initially assigned to the fragment somewhere else, or remove(), which would remove all nodes initially assigned in one shot.

    Codepen Sample

    I could not get it work, when:

    • single sibling -> HTMLElement
      • Uncaught TypeError: Failed to execute 'removeChild' on 'Node': parameter 1 is not of type 'Node
    • multiple siblings -> DocumentFragment
      • Uncaught TypeError: fragment2.remove is not a function

    Not sure if functions are still valid, or am missing something fundamental?

    Any suggestion for testing framework?

    as for now im trying to use vitest, using render doesnt work to render, so what i did is i create a custom element container (lit-element) then use the render function of lit-element

    problems with tables

    of course HTML with <no-des> in the wild has issues when it comes to <table> content, and likely <ul> too ... with lighterhtml there's no issue 'cause the crawler looks for exact comments, but here the technique doesn't really work, so there's something to improve here, as it's impossible to deal with tables in a reasonable way.

    DOMException: The supplied node is incorrect or has an incorrect ancestor for this operation.

    Hi Andrea,

    I have a strange bug. Maybe it is because of nesting things in the wrong way.

    It happens at: http://rdf-form.danielbeeke.nl/
    If you scroll down till you see a form.
    There is a field Author.
    If you empty it, type something that it would not find,
    click on Add sghfghdg as text without reference..
    Then it will crash with:

    DOMException: The supplied node is incorrect or has an incorrect ancestor for this operation.

    Source code:
    https://github.com/danielbeeke/rdf-form/blob/master/src/RdfForm.ts
    https://github.com/danielbeeke/rdf-form/blob/master/src/FormElements/FormElementBase.ts

    I have not yet used html.for(object)`` at that place I do not have an object. I only have a key. Would .for help in this situation?

    How to parse HTML string content to Hole?

    I need a solution for virtual dom diffing when the HTML is provided as a string.

    Background

    For some background I am working on an alternative to GrapesJS that solve it's longstanding XSS vulnerability in the live preview. To do this, I need an iframe that can accept HTML content over the postMessage API and display it. Since postMessage is a string-based API, I am unable to send rich components to be rendered in the iframe. The current solution relies on setting document.body.innerHTML = contentFromPostMessage to render the content, but this removes state when using components with state such as <details> tags.

    For this situation I have previously used html-react-parser but I am dissatisfied with it's lack of support for full HTML including template, style and comment tags.

    Other virtual dom libraries have similar libraries for parsing from HTML, such as html2hscript for virtual-dom and toVNode in snabbdom. Solutions that don't use virtual dom, such as morphdom do not work as they are too eager in updating the dom.

    Is there a utility for uhtml to transform raw HTML into a "Hole"?

    Parsing HTML is just a matter of using document.createElement("template").innerHTML = content, but there doesn't seem to be a way to turn HTML element (or document fragment) into a Hole.

    Recommend Projects

    • React photo React

      A declarative, efficient, and flexible JavaScript library for building user interfaces.

    • Vue.js photo Vue.js

      🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

    • Typescript photo Typescript

      TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

    • TensorFlow photo TensorFlow

      An Open Source Machine Learning Framework for Everyone

    • Django photo Django

      The Web framework for perfectionists with deadlines.

    • D3 photo D3

      Bring data to life with SVG, Canvas and HTML. 📊📈🎉

    Recommend Topics

    • javascript

      JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

    • web

      Some thing interesting about web. New door for the world.

    • server

      A server is a program made to process requests and deliver data to clients.

    • Machine learning

      Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

    • Game

      Some thing interesting about game, make everyone happy.

    Recommend Org

    • Facebook photo Facebook

      We are working to build community through open source technology. NB: members must have two-factor auth.

    • Microsoft photo Microsoft

      Open source projects and samples from Microsoft.

    • Google photo Google

      Google ❤️ Open Source for everyone.

    • D3 photo D3

      Data-Driven Documents codes.