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