Wednesday, October 5, 2022
HomeWeb DevelopmentUtilizing Internet Elements With Subsequent (or Any SSR Framework) | CSS-Tips

Utilizing Internet Elements With Subsequent (or Any SSR Framework) | CSS-Tips


In my earlier submit we checked out Shoelace, which is a element library with a full suite of UX parts which can be stunning, accessible, and — maybe unexpectedly — constructed with Internet Elements. This implies they can be utilized with any JavaScript framework. Whereas React’s Internet Part interoperability is, at current, lower than excellent, there are workarounds.

However one critical shortcoming of Internet Elements is their present lack of help for server-side rendering (SSR). There’s something known as the Declarative Shadow DOM (DSD) within the works, however present help for it’s fairly minimal, and it truly requires buy-in out of your net server to emit particular markup for the DSD. There’s presently work being finished for Subsequent.js that I look ahead to seeing. However for this submit, we’ll take a look at how one can handle Internet Elements from any SSR framework, like Subsequent.js, as we speak.

We’ll wind up doing a non-trivial quantity of guide work, and barely hurting our web page’s startup efficiency within the course of. We’ll then take a look at how one can reduce these efficiency prices. However make no mistake: this answer just isn’t with out tradeoffs, so don’t count on in any other case. All the time measure and profile.

The issue

Earlier than we dive in, let’s take a second and truly clarify the issue. Why don’t Internet Elements work nicely with server-side rendering?

Software frameworks like Subsequent.js take React code and run it by means of an API to primarily “stringify” it, which means it turns your parts into plain HTML. So the React element tree will render on the server internet hosting the net app, and that HTML can be despatched down with the remainder of the net app’s HTML doc to your consumer’s browser. Together with this HTML are some <script> tags that load React, together with the code for all of your React parts. When a browser processes these <script> tags, React will re-render the element tree, and match issues up with the SSR’d HTML that was despatched down. At this level, the entire results will begin operating, the occasion handlers will wire up, and the state will truly… include state. It’s at this level that the net app turns into interactive. The method of re-processing your element tree on the consumer, and wiring the whole lot up is named hydration.

So, what does this need to do with Internet Elements? Properly, if you render one thing, say the identical Shoelace <sl-tab-group> element we visited final time:

<sl-tab-group ref="{tabsRef}">
  <sl-tab slot="nav" panel="normal"> Basic </sl-tab>
  <sl-tab slot="nav" panel="customized"> Customized </sl-tab>
  <sl-tab slot="nav" panel="superior"> Superior </sl-tab>
  <sl-tab slot="nav" panel="disabled" disabled> Disabled </sl-tab>

  <sl-tab-panel identify="normal">That is the final tab panel.</sl-tab-panel>
  <sl-tab-panel identify="customized">That is the customized tab panel.</sl-tab-panel>
  <sl-tab-panel identify="superior">That is the superior tab panel.</sl-tab-panel>
  <sl-tab-panel identify="disabled">This can be a disabled tab panel.</sl-tab-panel>
</sl-tab-group>

…React (or actually any JavaScript framework) will see these tags and easily move them alongside. React (or Svelte, or Stable) are usually not chargeable for turning these tags into nicely-formatted tabs. The code for that’s tucked away inside no matter code you will have that defines these Internet Elements. In our case, that code is within the Shoelace library, however the code will be wherever. What’s necessary is when the code runs.

Usually, the code registering these Internet Elements can be pulled into your software’s regular code through a JavaScript import. Meaning this code will wind up in your JavaScript bundle and execute throughout hydration which implies that, between your consumer first seeing the SSR’d HTML and hydration taking place, these tabs (or any Internet Part for that matter) won’t render the right content material. Then, when hydration occurs, the correct content material will show, probably inflicting the content material round these Internet Elements to maneuver round and match the correctly formatted content material. This is called a flash of unstyled content material, or FOUC. In concept, you might stick markup in between all of these <sl-tab-xyz> tags to match the completed output, however that is all however not possible in apply, particularly for a third-party element library like Shoelace.

Transferring our Internet Part license plate

So the issue is that the code to make Internet Elements do what they should do gained’t truly run till hydration happens. For this submit, we’ll take a look at operating that code sooner; instantly, in reality. We’ll take a look at customized bundling our Internet Part code, and manually including a script on to our doc’s <head> so it runs instantly, and blocks the remainder of the doc till it does. That is usually a horrible factor to do. The entire level of server-side rendering is to not block our web page from processing till our JavaScript has processed. However as soon as finished, it implies that, because the doc is initially rendering our HTML from the server, the Internet Elements can be registered and can each instantly and synchronously emit the appropriate content material.

In our case, we’re simply seeking to run our Internet Part license plate in a blocking script. This code isn’t enormous, and we’ll look to considerably reduce the efficiency hit by including some cache headers to assist with subsequent visits. This isn’t an ideal answer. The primary time a consumer browses your web page will at all times block whereas that script file is loaded. Subsequent visits will cache properly, however this tradeoff won’t be possible for you — e-commerce, anybody? Anyway, profile, measure, and make the appropriate choice in your app. Apart from, sooner or later it’s solely attainable Subsequent.js will absolutely help DSD and Internet Elements.

Getting began

All the code we’ll be is in this GitHub repo and deployed right here with Vercel. The online app renders some Shoelace parts together with textual content that adjustments colour and content material upon hydration. You need to be capable of see the textual content change to “Hydrated,” with the Shoelace parts already rendering correctly.

Customized bundling Internet Part code

Our first step is to create a single JavaScript module that imports all of our Internet Part definitions. For the Shoelace parts I’m utilizing, my code appears like this:

import { setDefaultAnimation } from "@shoelace-style/shoelace/dist/utilities/animation-registry";

import "@shoelace-style/shoelace/dist/parts/tab/tab.js";
import "@shoelace-style/shoelace/dist/parts/tab-panel/tab-panel.js";
import "@shoelace-style/shoelace/dist/parts/tab-group/tab-group.js";

import "@shoelace-style/shoelace/dist/parts/dialog/dialog.js";

setDefaultAnimation("dialog.present", {
  keyframes: [
    { opacity: 0, transform: "translate3d(0px, -20px, 0px)" },
    { opacity: 1, transform: "translate3d(0px, 0px, 0px)" },
  ],
  choices: { length: 250, easing: "cubic-bezier(0.785, 0.135, 0.150, 0.860)" },
});
setDefaultAnimation("dialog.conceal", {
  keyframes: [
    { opacity: 1, transform: "translate3d(0px, 0px, 0px)" },
    { opacity: 0, transform: "translate3d(0px, 20px, 0px)" },
  ],
  choices: { length: 250, easing: "cubic-bezier(0.785, 0.135, 0.150, 0.860)" },
});

It masses the definitions for the <sl-tab-group> and <sl-dialog> parts, and overrides some default animations for the dialog. Easy sufficient. However the fascinating piece right here is getting this code into our software. We can’t merely import this module. If we did that, it’d get bundled into our regular JavaScript bundles and run throughout hydration. This may trigger the FOUC we’re making an attempt to keep away from.

Whereas Subsequent.js does have plenty of webpack hooks to customized bundle issues, I’ll use Vite as an alternative. First, set up it with npm i vite after which create a vite.config.js file. Mine appears like this:

import { defineConfig } from "vite";
import path from "path";

export default defineConfig({
  construct: {
    outDir: path.be part of(__dirname, "./shoelace-dir"),
    lib: {
      identify: "shoelace",
      entry: "./src/shoelace-bundle.js",
      codecs: ["umd"],
      fileName: () => "shoelace-bundle.js",
    },
    rollupOptions: {
      output: {
        entryFileNames: `[name]-[hash].js`,
      },
    },
  },
});

This may construct a bundle file with our Internet Part definitions within the shoelace-dir folder. Let’s transfer it over to the public folder in order that Subsequent.js will serve it. And we also needs to maintain monitor of the precise identify of the file, with the hash on the top of it. Right here’s a Node script that strikes the file and writes a JavaScript module that exports a easy fixed with the identify of the bundle file (it will come in useful shortly):

const fs = require("fs");
const path = require("path");

const shoelaceOutputPath = path.be part of(course of.cwd(), "shoelace-dir");
const publicShoelacePath = path.be part of(course of.cwd(), "public", "shoelace");

const information = fs.readdirSync(shoelaceOutputPath);

const shoelaceBundleFile = information.discover(identify => /^shoelace-bundle/.check(identify));

fs.rmSync(publicShoelacePath, { drive: true, recursive: true });

fs.mkdirSync(publicShoelacePath, { recursive: true });
fs.renameSync(path.be part of(shoelaceOutputPath, shoelaceBundleFile), path.be part of(publicShoelacePath, shoelaceBundleFile));
fs.rmSync(shoelaceOutputPath, { drive: true, recursive: true });

fs.writeFileSync(path.be part of(course of.cwd(), "util", "shoelace-bundle-info.js"), `export const shoelacePath = "/shoelace/${shoelaceBundleFile}";`);

Right here’s a companion npm script:

"bundle-shoelace": "vite construct && node util/process-shoelace-bundle",

That ought to work. For me, util/shoelace-bundle-info.js now exists, and appears like this:

export const shoelacePath = "/shoelace/shoelace-bundle-a6f19317.js";

Loading the script

Let’s go into the Subsequent.js _document.js file and pull within the identify of our Internet Part bundle file:

import { shoelacePath } from "../util/shoelace-bundle-info";

Then we manually render a <script> tag within the <head>. Right here’s what my whole _document.js file appears like:

import { Html, Head, Primary, NextScript } from "subsequent/doc";
import { shoelacePath } from "../util/shoelace-bundle-info";

export default operate Doc() {
  return (
    <Html>
      <Head>
        <script src={shoelacePath}></script>
      </Head>
      <physique>
        <Primary />
        <NextScript />
      </physique>
    </Html>
  );
}

And that ought to work! Our Shoelace registration will load in a blocking script and be accessible instantly as our web page processes the preliminary HTML.

Bettering efficiency

We might go away issues as they’re however let’s add caching for our Shoelace bundle. We’ll inform Subsequent.js to make these Shoelace bundles cacheable by including the next entry to our Subsequent.js config file:

async headers() {
  return [
    {
      source: "/shoelace/shoelace-bundle-:hash.js",
      headers: [
        {
          key: "Cache-Control",
          value: "public,max-age=31536000,immutable",
        },
      ],
    },
  ];
}

Now, on subsequent browses to our website, we see the Shoelace bundle caching properly!

DevTools Sources panel open and showing the loaded Shoelace bundle.

If our Shoelace bundle ever adjustments, the file identify will change (through the :hash portion from the supply property above), the browser will discover that it doesn’t have that file cached, and can merely request it contemporary from the community.

Wrapping up

This will likely have appeared like a whole lot of guide work; and it was. It’s unlucky Internet Elements don’t provide higher out-of-the-box help for server-side rendering.

However we shouldn’t neglect the advantages they supply: it’s good with the ability to use high quality UX parts that aren’t tied to a particular framework. It’s aldo good with the ability to experiment with model new frameworks, like Stable, with no need to search out (or hack collectively) some kind of tab, modal, autocomplete, or no matter element.

RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

- Advertisment -
Google search engine

Most Popular

Recent Comments