swashata / wp-webpack-script

💥🔥📦👩‍💻 An easy to use, pre configured, hackable webpack setup & development server for WordPress themes and plugins.
https://wpack.io
MIT License
407 stars 57 forks source link

Recipe for SSG / Static html generation and preload #1245

Closed halmos closed 2 years ago

halmos commented 2 years ago

Hi,

This library has been immensely helpful - thank you so much for your continued development!

We're considering some options for something like modular SSG (static site generation) with php preload integration.

The idea is that React components would be pre-rendered at build time and then that static html could be loaded into wordpress / php template to have a static pre-render served by wordpress before client-side hydration. Ideally, it would work at a DOM-node or page level, rather than only as a headless, fully routed app (which could probably be managed without too much difficulty using next.js or similar).

I haven't found too much information about doing something like this, but it seem like it could be potentially worthwhile to explore a wpack.io-based solution since it is already handling the asset enqueuing on the wordpress side.

Have you seen any examples of wpack.io projects doing something like this, or, do you have any recommendations on how to best approach it?

swashata commented 2 years ago

Hello,

I do something like that in my own project. But I don't do it entirely through wpack.io.

  1. I use wpack.io to build and enqueue the entrypoint. It checks for mount node and if any HTML is found inside, then it would hydrate, otherwise it would render.
  2. Then I have a simple node script to render the component with react-dom/server and save the HTML output.
  3. That output is shipped as HTML file and PHP loads it inside the mount node.

The code for generating HTML goes something like this (copying directly from my project, to give you some idea):

/// <reference types="node" />

import * as React from 'react';
import { renderToString } from 'react-dom/server';
import * as path from 'path';
import * as fs from 'fs';

import Shadow from 'Components/Shadow';
import FallbackContainer from 'Front/components/FallbackContainer';
import PortalSkeleton from 'Components/PortalSkeleton';
import TokenForm from 'Front/submission/components/TokenForm';
import {
    SettingsAppearanceContainerLayoutEnum,
    SettingsAppearanceControlTypeEnum,
} from 'Apollo/generated/types/graphql-global-types';

const skeletons = {
    userPortalSkeleton: (
        <Shadow inShadow cssHandles={[]} ssr>
            <FallbackContainer
                themeStyle={{
                    scheme: 'teal',
                }}
                widthOverride="1024px"
                mode="live"
                containerLayoutOverride={SettingsAppearanceContainerLayoutEnum.FLUID}
                ssr
            >
                <PortalSkeleton />
            </FallbackContainer>
        </Shadow>
    ),
    summaryGeneralSSR: (
        <Shadow inShadow cssHandles={[]} ssr>
            <TokenForm
                controlType={SettingsAppearanceControlTypeEnum.BOXY}
                theme="teal"
                formLabel="Enter submission id"
                onSubmit={() => {}}
                ssr
            />
        </Shadow>
    ),
} as const;

const assetsPath = path.resolve(__dirname, '../static/skeletons');
console.log('Writing skeletons in:');
console.log(assetsPath);
fs.mkdirSync(assetsPath, { recursive: true });

Object.keys(skeletons).forEach(skeleton => {
    const skeletonMarkup = renderToString(
        skeletons[skeleton as keyof typeof skeletons]
    );
    const skeletonPath = path.resolve(assetsPath, `./${skeleton}.html`);
    console.log(`Writing skeleton for ${skeleton} in ${skeletonPath}`);
    fs.writeFileSync(skeletonPath, skeletonMarkup, 'utf-8');
    console.log(`Done writing skeleton for ${skeleton} in ${skeletonPath}`);
});

Hope it helps.

halmos commented 2 years ago

Thanks for sharing this - it's very helpful.

swashata commented 2 years ago

Great. Closing this now.