Rich-Harris / pancake

Experimental charting library for Svelte

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Pancake doesn't work with node due to using ES Modules (SSR)

jdolearydl opened this issue · comments

The Real Problem

Pancake works fine when used with a bundler, but svelte/register doesn't work when trying to run directly with node. This is because ES Modules need to be imported (as opposed to require()) and because imports are asyncronous, so the .svelte extension isn't registered in time.

Original Issue

Hey Rich,
Love this library. I'm trying to get SSR working with the following from the svelte docs:

index.js

require('svelte/register');

const App = require('./App.svelte').default;

const { head, html, css } = App.render();
console.log("TCL: head, html, css", head, html, css)

and App.svelte contains your Life Expectancy chart from https://pancake-charts.surge.sh/

import Pancake from '@sveltejs/pancake'; 
import { countries, years } from './data.js';

However, when I run node index.js, I get:
Error: Cannot find module '@sveltejs/pancake'
I'm on Node v13.8.0

Am I missing a preprocessing step? The example makes it look like I can just require the .svelte file as i do in index.js above.

How do I render a chart on the server-side via node?

I did npm i @sveltejs/pancake on dev, but I was running in a container and forgot to just run npm i within the container.

Okay I started fresh using degit and the svelte template. I ran npm i @sveltejs/pancake, and it still wont work. @sveltejs/pancake is definitely in my package.json. Note that it does work just fine generating server side html for a plain svelte file. I will continue to post my findings here as I work on this in case it helps someone else.

oldsrc % node index.js          
internal/modules/cjs/loader.js:983
  throw err;
  ^

Error: Cannot find module '@sveltejs/pancake'
Require stack:
- /Users/jordan/git/DTE/svelte-tests/ssr2/my-svelte-project/src/oldsrc/App.svelte
- /Users/jordan/git/DTE/svelte-tests/ssr2/my-svelte-project/src/oldsrc/index.js
    at Function.Module._resolveFilename (internal/modules/cjs/loader.js:980:15)
    at Function.Module._load (internal/modules/cjs/loader.js:862:27)
    at Module.require (internal/modules/cjs/loader.js:1040:19)
    at require (internal/modules/cjs/helpers.js:72:18)
    at Object.<anonymous> (/Users/jordan/git/DTE/svelte-tests/ssr2/my-svelte-project/src/oldsrc/App.svelte:5:30)
    at Module._compile (internal/modules/cjs/loader.js:1151:30)
    at Object.require.extensions.<computed> [as .svelte] (/Users/jordan/git/DTE/svelte-tests/ssr2/my-svelte-project/node_modules/svelte/register.js:49:17)
    at Module.load (internal/modules/cjs/loader.js:1000:32)
    at Function.Module._load (internal/modules/cjs/loader.js:899:14)
    at Module.require (internal/modules/cjs/loader.js:1040:19) {
  code: 'MODULE_NOT_FOUND',
  requireStack: [
    '/Users/jordan/git/DTE/svelte-tests/ssr2/my-svelte-project/src/oldsrc/App.svelte',
    '/Users/jordan/git/DTE/svelte-tests/ssr2/my-svelte-project/src/oldsrc/index.js'
  ]
}

How and where did you import it? @sveltejs/pancake does not have the main field in package.json, only the svelte and module fields. This means that it has to be bundled with rollup or webpack, aka it can not be an external dependency. Based on the call stack, however, it looks like nodejs it trying to import it. Maybe you have it listed in your external dependencies, or you are not using rollup or webpack?

@Vehmloewff, I must be missing some basic understanding of how this works.
To do server side rendering, I have to build it first with rollup? It docs make it appear like you can just require the .svelte file. Can you give me an example? I referencing this part of the docs.

Some extra info: server side rendering a regular Svelte component (without pancake) to html and css works just fine without Rollup. I just run node index.js (see the source at the top of this issue) and it outputs

<main class="svelte-1tky8bj">
        <h1 class="svelte-1tky8bj">Hello undefined!</h1>
        <p>Visit the <a href="https://svelte.dev/tutorial">Svelte tutorial</a> to learn how to build Svelte apps.</p>
</main>

True, if you are using require('svelte/register'), you can import svelte files directly. But you can't import a module without a main field in the package.json or an index.js file in the root of the project in plain nodejs.

https://unpkg.com/@sveltejs/pancake@0.0.10/package.json

Just to be clear, what does the relevant part of your index.js look like?

Okay that makes sense, but how is it intended to be used for Server Side Rendering then? The surge site says "Pancake is designed with server-side rendering in mind". Maybe it's just not finished yet? I know this is an early project, I'm just excited to try it out - it would be perfect for my use case :)

My entire index.js is

require('svelte/register');

const App = require('./App.svelte').default;

const { head, html, css } = App.render();
console.log("TCL: head, html, css", head, html, css)

Does App.render() or require('./App.svelte') throw the error?

require throws the error

Then this package either needs a main field in the package.json, or svelte/register should take into consideration the svelte field in the package.json like rollup-plugin-svelte does.

I'll try that out in a fork and see if i can get it to work

When I add "main": "index.mjs", to pancake's package.json I now get:
Error [ERR_REQUIRE_ESM]: Must use import to load ES Module however I am using import
in the svelte file: import * as Pancake from '@sveltejs/pancake';

Also strangely, the stack trace reports the wrong line number in the svelte file as the cause.

I will continue to investigate.

So it appears that you can't require() a module, you must import it. So the problem is in the index.js. However, when I tried to convert my script to a module, because imports are static, svelte/register doesn't run in time and .svelte is not a recognized file extension.

Yeah, this sounds like a pretty nasty bug.

Note, however, that using svelte for SSR does work fine, it's only Pancake that causes an issue because Pancake uses ES Modules.
Since ES Modules are still experimental, would it be worth it to convert Pancake back to CommonJS so that SSR will work?

You can try esm package: https://www.npmjs.com/package/esm

Right now modules are a total mess in node, and I say that with the biggest anger I can muster. An absolute mess. esm fixes most* issues, so it's worth a try. Just put require = require("esm")(module) at the top of the module requiring the module breaking node.

I just encountered the same issue. A quick hack that worked for me was to just add "type": "module" to pancake's package.json which enables Node's experimental ES support.

None of the solutions above seem to work for me.

Since ES Modules are still experimental, would it be worth it to convert Pancake back to CommonJS so that SSR will work?

I don't know much, but reading this convo this makes sense. What is needed for this, rename .mjs -> .js?

Also I've made a community fork (and an issue about it) if you want to fix this and get PR accepted (or be a contributor why not).

commented

Using svelte/register is not a good way to do SSR except in the simplest of cases. You would need every module to be CJS by default which isn't practical as Svelte components are designed to be ESM first for compatibility with that ecosystem.

Using a bundler to compile for SSR will solve this issue without any changes.

@pngwn do you have an example for how you would set up a bundler to compile for SSR successfully?

See this comment for a solution: sveltejs/svelte#5185 (comment)

@mhkeller, thanks! Linking directly to his PR here:
mhkeller/lc-ssr-test#1 (comment)

This project has a fancier ssr rendering setup that you may want to look at too https://github.com/russellgoldenberg/svelte-starter/