Rich-Harris / pancake

Experimental charting library for Svelte
MIT License
1.29k stars 61 forks source link

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

Closed jdolearydl closed 4 years ago

jdolearydl commented 4 years ago

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?

jdolearydl commented 4 years ago

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.

jdolearydl commented 4 years ago

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'
  ]
}
Vehmloewff commented 4 years ago

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?

jdolearydl commented 4 years ago

@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.

jdolearydl commented 4 years ago

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>
Vehmloewff commented 4 years ago

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

Vehmloewff commented 4 years ago

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

jdolearydl commented 4 years ago

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)
Vehmloewff commented 4 years ago

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

jdolearydl commented 4 years ago

require throws the error

Vehmloewff commented 4 years ago

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.

jdolearydl commented 4 years ago

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

jdolearydl commented 4 years ago

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.

jdolearydl commented 4 years ago

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.

Vehmloewff commented 4 years ago

Yeah, this sounds like a pretty nasty bug.

jdolearydl commented 4 years ago

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?

briancray commented 4 years ago

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.

milkbump commented 4 years ago

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.

KristerV commented 4 years ago

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).

pngwn commented 4 years ago

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.

mhkeller commented 4 years ago

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

mhkeller commented 4 years ago

See this comment for a solution: https://github.com/sveltejs/svelte/issues/5185#issuecomment-665369367

jdolearydl commented 4 years ago

@mhkeller, thanks! Linking directly to his PR here: https://github.com/mhkeller/lc-ssr-test/pull/1#issue-457257369

mhkeller commented 4 years ago

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