Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Gracefully handle boot file errors in SSR #17658

Open
yusufkandemir opened this issue Nov 22, 2024 · 1 comment
Open

Gracefully handle boot file errors in SSR #17658

yusufkandemir opened this issue Nov 22, 2024 · 1 comment

Comments

@yusufkandemir
Copy link
Member

server-entry uses a top-level await Promise.all(...) call. So, if there is a failing boot file, the app silently exits with code 13. Even if you use node --trace-warnings index.js, you don't get much useful info because the file consists of a single line for minification reasons.

client-entry template uses Promise.allSettled to load the boot files, then prints an error in case of an error. So, we should adjust the server-entry template to do the same.

This can be put in to the app-vite v2 / app-webpack v4, then also be ported back to app-vite v1 / app-webpack v3.

@LydiaSFCompetitionline
Copy link

I am working the whole week on this problem now.
The problem with the top level await in /dist/ssr/server/server-entry.mjs came up with the changes made in @quasar/app-vite-v2.0.0-beta.12
bootFiles was changed into bootFunctions and an await was moved from serverEntry to top level.

by running npx quasar build -m ssr --debug and open /dist/ssr/server/server-entry.mjs you can compare it:
unminified server-entry.mjs before updating to @quasar/app-vite-v2.0.0-beta.12:

const bootFiles =   Promise.all([ //no top level await
  [...] //not showing all imports
  Promise.resolve().then(() => axios$1),
  import("./assets/polyfill-BwV_9ARc.mjs"),
  import("./assets/sentry-BDCNs69F.mjs")
]).then((bootFiles2) => bootFiles2.map((entry) => entry.default).filter((entry) => typeof entry === "function"));
const serverEntry = (ssrContext) => {
  return new Promise(async (resolve, reject) => {
    const bootFunctions = await bootFiles; // here is the await

and since @quasar/app-vite-v2.0.0-beta.12:

const bootFunctions = await Promise.all([ // await is now top level
  [...] //not showing all imports
  Promise.resolve().then(() => axios$1),
  import("./assets/polyfill-BwV_9ARc.mjs"),
  import("./assets/sentry-BDCNs69F.mjs")
]).then((bootFiles) => bootFiles.map((entry) => entry.default).filter((entry) => typeof entry === "function"));
const serverEntry = (ssrContext) => {
  return new Promise(async (resolve, reject) => {
    // directly use awaited bootFunctions from above here

You can quickly test if your quasar project is running the build with this command: node index.js; echo $?
Then node code 13 will come up which means "Unsettled Top-Level Await: await was used outside of a function in the top-level code, but the passed Promise never settled." (see here)

Reproduction steps with new blank project

So i wondered if my project maybe have some custom issues. But no, i have node 18+ and set es2022 in quasar.conf.js as browser target, both support top level awaits. I tested if a new quasar project, created by quasar cli, will have node code 13 too.
Important Creation Steps:
yarn create quasar

  • App with Quasar CLI
  • Pick Quasar version: Quasar v2
  • Pick Quasar App CLI variant: Quasar App CLI with Vite 6 ([...] v2)
  • Check the features needed for your project:
    • axios (important, because the fresh new project needs to create the bootFunctions in /dist/ssr/server/server-entry.mjs to import axios by using this top level await)

cd quasar-project/
npx quasar build -m ssr --debug
cd dist/ssr/
node index.js; echo $?
By doing this you will get node code 13.

Try Reverting commit "tweak SSR boot files management" from beta.12

I also tried it the other way around in my real project. I changed back the changes made in @quasar/app-vite-v2.0.0-beta.12 for /dist/ssr/server/server-entry.mjs by doing this:

const bootFunctionsFix = Promise.all([ // renamed bootFunctions, no more top level await
  [...] //not showing all imports
  Promise.resolve().then(() => axios$1),
  import("./assets/polyfill-BwV_9ARc.mjs"),
  import("./assets/sentry-BDCNs69F.mjs")
]).then((bootFiles) => bootFiles.map((entry) => entry.default).filter((entry) => typeof entry === "function"));
const serverEntry = (ssrContext) => {
  return new Promise(async (resolve, reject) => {
    const bootFunctions = await bootFunctionsFix; // define bootFunction here again with await

I put the await back into serverEntry, away from top level and node index.js; echo $? will start the server listening at port 3000.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants