Search code examples
webpackserver-side-renderingbabel-loadersolid-jshydration

Hydrating script gets wrongly compiled | Solidjs | SSR


I am experimenting with the isomorphic approach for Solidjs. I followed the documentation quite faithfull: https://www.solidjs.com/guides/server.

Visually, everything seems to be working: my html is rendered in the browser as expected. However, if the hydration would be successful, I would see a log in the browser. Instead I see an error log.

The server:

app.get('/', (req, res) => {
    return res.send(getBasicHTMLSkeleton())
})

getBasicHTMLSkeleton:

import {renderToString, generateHydrationScript, Dynamic} from "solid-js/web";
import HelloWorld from "../../hybrid/components/HelloWorld";

export const getBasicHTMLSkeleton = () =>  {
    const hello = renderToString(() => <HelloWorld />)
    return `
  <html lang="en">
    <head>
      <title>🔥 Solid SSR 🔥</title>
      <meta charset="UTF-8" />
      <meta name="viewport" content="width=device-width, initial-scale=1.0" />
      <link rel="stylesheet" href="/_index.min.css" />
      <script src="hydrate.js" />
      ${generateHydrationScript()}
    </head>
    <body>${hello}</body>
  </html>
`;
}

The HelloWorld component:

import {createEffect} from "solid-js";

const HelloWorld = () => {
    createEffect(() => {
        console.log('lel!')
    })

    return <div>
        <h1>Hello</h1>
    </div>
}

export default HelloWorld

The hydrate.js-script (this script is pulled in at the client-side):

import {hydrate} from "solid-js/web";
import HelloWorld from "../hybrid/components/HelloWorld";

hydrate(() => <HelloWorld/>, document)

I get the following error in the browser's console:

Uncaught TypeError: (0 , solid_js_web__WEBPACK_IMPORTED_MODULE_0__.template) is not a function
    at ./src/hybrid/components/HelloWorld.tsx (hydrate.js:25:83)
    at __webpack_require__ (bootstrap:19:1)
    at publicPath:1:1
    at hydrate.js:3421:3
    at hydrate.js:3423:12

The webpack config for compiling the script:

const tsAndTsxRuleClient = {
    test: /\.(ts|tsx)$/i,
    exclude: ['/node_modules/'],
    use: [
        {
            loader: 'babel-loader',
            options: {
                presets: [["solid", {"generate": "dom", "hydratable": true}]],
            },
        },
        {
            loader: 'ts-loader',
        },
    ]
}


const configClient = {
    ...configServer,
    entry: './src/client/hydrate.tsx',
    module: {
        rules: [tsAndTsxRuleClient]
    },
    output: {
        filename: 'hydrate.js',
        path: path.resolve(__dirname, 'dist-client')
    }
}

Finally, the relevant part of the bundled hydrate.js

(() => {
  console.log('HYDRATING?');
  (0,solid_js_web__WEBPACK_IMPORTED_MODULE_1__.hydrate)(() => (0,solid_js_web__WEBPACK_IMPORTED_MODULE_2__.createComponent)(_hybrid_components_HelloWorld__WEBPACK_IMPORTED_MODULE_0__["default"], {}), document); // this code breaks everything
})();

Did I miss something?

I tried playing around with different webpack configs

eg: "generate"; "ssr" instead of "dom"

I also triple checked possible syntax mistakes and the documentation.

I also tried other domNodes instead of document, eg:

hydrate(() => <HelloWorld/>, helloWorldWrapper)

with the same result.


Solution

  • After a few days I found the solution:

    First I was having an webpack configuration error:

    const configClient = {
        ...configServer,
        entry: './src/client/hydrate.tsx',
        module: {
            rules: [tsAndTsxRuleClient]
        },
        output: {
            filename: 'hydrate.js',
            path: path.resolve(__dirname, 'dist-client')
        }
    }

    configServer contained: {target: node} while it should be: {target: web} Probably you should avoid merging server & client config like this because of this exact reason.

    The other problem was my hydration-script tag: <script defer src="hydrate.js"/> should have the async attribute: <script async src="hydrate.js"/>.

    I suggested to improve the documentation. (https://github.com/solidjs/solid/discussions/1857)