1

Hi devs!

Currently i am trying to create a template engine with only react and htm. It's think out the box, so, no CRA, no Nextjs, no other framework or library. So, It's an hard way.

I need to undestand why i can't load the same librs from server-side-rendered into client-side-rendering.

NodeJs is setted in "type:module" and the ssr scripts is equal to that csr because it's the pre-runned printed version of it.

I'm going crazy for run all libs both in nodejs and on the web page.

exemple:


import React from 'react'
import ReactDOM from 'react-dom/client'
import ReactDOMServer from 'react-dom/server'
import htm from 'htm'
const html = htm.bind(React.createElement)

// it run on node middleware, it doesn't run on web and result is:
// Uncaught TypeError: Failed to resolve module specifier "react". Relative references must start with either "/", "./", or "../"

The docs import it in the same mode as you see in my codes, why he can do it???

https://github.com/developit/htm
https://react.dev/learn/add-react-to-an-existing-project




Goal: Finding a solution or correct approach for load and parallize the libraries and set correctly react and htm between server side and client web side.

berto.dev
  • 11
  • 3
  • Do you bundle your project? The error message looks like you don't bundle the project. Without a bundler, you have to use relative paths or absolute URLs. – jabaa Jun 08 '23 at 20:30
  • intersting... yes, I don't use bundlers. So.... can you suggest me how set a relative path of react? at who correct folder i can pointing of?? – berto.dev Jun 08 '23 at 20:46
  • Read the error message: `Relative references must start with either "/", "./", or "../"`. Or you have to use absolute URLs like `https://...`. In both cases, you have to configure your web server to serve the files. – jabaa Jun 08 '23 at 20:54
  • in short... I needed to make static node_modules and set all similar to: "import React from '../node_modules/react/index.js'" This is not the end of problems but it resolve the "common libs problem" and it's a big goal for now. Thank you!! – berto.dev Jun 08 '23 at 21:08
  • Please post an separate answer. Don't write an answer into the question. – jabaa Jun 10 '23 at 11:03
  • @jabaa you removing my second solution again remaking my post. This part of "First Basic Solution" it isn't good and going on errors for incompatibility! The second parts of post is useful for understand an find an alternative... why do you remove it??? – berto.dev Jun 11 '23 at 04:06
  • Create an answer for the solutions. Don't post them in the question. – jabaa Jun 11 '23 at 11:45
  • Ok, now i get it... thank you for all. – berto.dev Jun 12 '23 at 08:44

2 Answers2

1

Your browser can't import from react. Browsers (like ESM Node.js) use the same syntax for imports, but there's one really large difference: Node knows about node_modules, but browsers do not.

The typical solution for this is indeed a bundler like webpack, vite, etc.

If you're looking for a way to build universal code without a bundler, it's going to be very hard if you also want to use existing packages. You may be able to do something with import maps but if you're completely trying to avoid a build step, being able to use import maps is going to depend on how these specific packages are built. It will for sure be a challenge (but perhaps not impossible).

Evert
  • 93,428
  • 18
  • 118
  • 189
  • @jabaa not without import maps or a bundler. Browsers don't know how to resolve this, unless you have a static file named react. – Evert Jun 08 '23 at 20:58
  • @jabaa I'm commenting specifically on the line `import React from 'react'` which does not work unless some of those other conditions are true. if you're using a CDN that line looks different. – Evert Jun 08 '23 at 21:00
  • No disagreement there, but also not my point. – Evert Jun 08 '23 at 21:02
  • Yes, as I said that was specifically referring to the `import React from 'react'` line, not _generally_ using React in a browser which ofc is possible. Browsers do not know out of the box how to resolve the `react` path. I understand the confusion though. – Evert Jun 08 '23 at 21:03
  • 1
    yes. infact the first solution it find the correct relative or absolute path and set it. " in short... I needed to make static node_modules and set all similar to: "import React from '../node_modules/react/index.js' " this is not the end but it's a good part of process. – berto.dev Jun 08 '23 at 21:12
  • @jabaa that paragraph was always there. Maybe be a bit less hostile? This is totally a bad faith argument and not helping anyone. – Evert Jun 08 '23 at 21:14
  • @berto.dev glad that worked! It may be challenging to do this for everything, because for that to work it depends on all the dependencies that you use to have built browser-compatible versions and shipped them in their npm package. Looks like react did do this, so that's nice! – Evert Jun 08 '23 at 21:16
  • I think I misunderstood your wording. After reading it multiple times, it makes sense. – jabaa Jun 08 '23 at 21:27
  • @evert work the relative paths, the suggests of jabaa is good in this case. However i need to understand import maps, probably it's useful – berto.dev Jun 08 '23 at 21:32
  • Thank you @jabaa! – Evert Jun 08 '23 at 23:41
0

Solutions:

First Basic Solution:

Warning: THE APPROACH BELOW FOR MODULES CAN'T WORK! Some modules are plain scripts, some else are cjs, other umd... Its doesn't have a compatibility for taken this way.

"set all in relative path after make static the modules"

yourexpress.use( '/node_modules/', express.static( yourbaseroot+'/node_modules/') )
import React from '../node_modules/react/index.js'
import { hydrateRoot } from '../node_modules/react-dom/index.js'
import { renderToString } from '../node_modules/react-dom/server.js'
import { html } from '../node_modules/htm/react/index.js'




Second (and better) Approach:

One (or the only) of better solution it's make a cross load function. In my case i make an importer async make a first good solution.

// importer:

async function importModule(modulePath) {
    if(typeof window === 'undefined'){
        return require(modulePath)
    } else {
        const module = await import(modulePath)
        return module.default || module
    }
}

//usage:

const MyReactComponent = async (props) => {

    var React, MyLib

    if(typeof window === 'undefined'){

        React = await importModule('react')
        MyLib = await importModule('MyLib')

    } else {

        React = window.React 
        MyLib = window.MyLib

    }

    // do...

}

in any case, however, you need to use the horrible solution of export into window global object part of your app (like a builder, ex webpack)




Other notes, approach and deliriums

After different try I understand that right way is respect imports/exports types and not load the libs direct via nodejs modules paths in search of an unpraticable absolutism. However, commonJS and ES6 are an unconciliable enemies if you have its into the same page and you think to share something. One lock the others and vice versa in recursive loop.

So, it's a good idea to understand (obviously) the file structure of what we want to do, set up node for commonJS and get/share to parallelize your libraries both on the server and the web window object.

function myLibOrComponent () { ... }

typeof window === 'undefined' ? 
    module.exports = myLibOrComponent :        // node export
    window.myLibOrComponent = myLibOrComponent // web export

so, get it in same way... exemple:

const myLibOrComponent = typeof window === 'undefined' ?
    require('myLibOrComponent ') :  // node import 
    window.myLibOrComponent         // web global object

another way for components is:

import('./myLibOrComponent.js').then( () => { do... })
const myLibOrComponent = React.lazy(() => import('./myLibOrComponent'))

Exporting in window or self (like webpack) global object it's not a good practice but, unfortunately, I don't find any other way to reconcile the two type of script, formatted for two (or more) type of importing type.

berto.dev
  • 11
  • 3