solid icon indicating copy to clipboard operation
solid copied to clipboard

Universal Renderer's cleanup method doesn't remove existing node

Open nyanrus opened this issue 1 year ago • 5 comments

Describe the bug

Universal Renderer's cleanup method (the return value of render function) doesn't remove the existing node, which is inconvenient for using HMR.

Your Example Website or App

https://stackblitz.com/edit/solidjs-templates-ycgqgq?file=src%2Findex.tsx

Steps to Reproduce the Bug or Issue

  1. See the rendered result.

Expected behavior

I ran the cleanup method and I expected the node to be removed.

Screenshots or Videos

image

Platform

  • OS: Windows 11
  • Browser: Firefox
  • Version: 115

Additional context

I am using solid-js for Firefox XUL, non-standard HTML, for developing a custom browser. While I making HMR using Vite, I encountered the problem. Thank you for the good project!

nyanrus avatar Jun 30 '24 14:06 nyanrus

Universal render method is different client render method.

client render method will clean nodes element.textContent = "";

I am not sure whether universal render need add this logic.

image

this-spring avatar Jun 30 '24 16:06 this-spring

Thank you for the reply. I'll implement with overriding the render method. Thanks! By the way, I think it will be good if there are some docs or codes that guides/allows to implement custom cleanup method.

nyanrus avatar Jul 01 '24 06:07 nyanrus

emmm, I think cleanup is inner logic. It should not implement custom by users. Override render method is good way.

this-spring avatar Jul 01 '24 06:07 this-spring

Yeah I added the cleanup to web after the fact possibly.. There is no universal API currently to accomplish this I think? I'm open to suggestions on what sort of API you'd want to see to support this.

ryansolid avatar Aug 08 '24 23:08 ryansolid

Currently, I am overriding the render function for support vite's HMR. In Firefox codebase, I have to insert element in existing node a lot, and the dispose function seems only on render. so I made options(third param) in render with marker and hotCtx (solid-refresh doing not expected work so I am not using).

const _render = (
  code: () => JSX.Element,
  node: JSX.Element,
  options?: { marker?: Element; hotCtx?: ViteHotContext },
) => {
  let disposer: () => void = () => {};
  createRoot((dispose) => {
    const elem = insert(node, code(), options ? options.marker : undefined);
    disposer = () => {
      dispose();
      if (elem instanceof Element) {
        elem.remove();
      } else if (
        Array.isArray(elem) &&
        elem.every((e) => e instanceof Element)
      ) {
        elem.forEach((e) => e.remove());
      }
    };
    if (options?.hotCtx) {
      hotCtxMap.set(options.hotCtx, [
        ...(hotCtxMap.get(options.hotCtx) ?? []),
        disposer,
      ]);
      console.log("register disposer to hotCtx");
      options.hotCtx.dispose(() => {
        hotCtxMap.get(options.hotCtx!)?.forEach((v) => v());
        hotCtxMap.delete(options.hotCtx!);
      });
    }
  });
  return disposer;
};

https://github.com/nyanrus/noraneko/blob/b8881a58a3a49f4fbca234a8fd9b36c61729cc80/packages/solid-xul/index.ts#L118

If the marker are as default, and custom disposer as

~~~
  mergeProps,
} = createRenderer<JSX.Element>({
  /**
   * @param elem output of insert func
   * @param options render func's third param
   * may the render func's param can be in this func's param.
   */
  cleanupRender: (elem, options) => {
    if (elem instanceof Element) {
      elem.remove();
    } else if (
      Array.isArray(elem) &&
      elem.every((e) => e instanceof Element)
    ) {
      elem.forEach((e) => e.remove());
    }
  },
  createElement: (tag: string): Element => {
    if (tag.startsWith("xul:")) {
      return document.createXULElement(tag.replace("xul:", ""));
    }
    return document.createElement(tag);
  },
~~~

original from https://github.com/nyanrus/noraneko/blob/b8881a58a3a49f4fbca234a8fd9b36c61729cc80/packages/solid-xul/index.ts#L35

, then I could make less the render func's overriding as

const _render = (
  code: () => JSX.Element,
  node: JSX.Element,
  options?: { marker?: Element; hotCtx?: ViteHotContext },
) => {
  const disposer = render(code,node,options);
  if (options?.hotCtx) {
    hotCtxMap.set(options.hotCtx, [
      ...(hotCtxMap.get(options.hotCtx) ?? []),
      disposer,
    ]);
    console.log("register disposer to hotCtx");
    options.hotCtx.dispose(() => {
      hotCtxMap.get(options.hotCtx!)?.forEach((v) => v());
      hotCtxMap.delete(options.hotCtx!);
    });
  }
  return disposer;
};

and it seems comfortable.

nyanrus avatar Aug 09 '24 09:08 nyanrus