QwikDev / qwik

Instant-loading web apps, without effort
https://qwik.dev
MIT License
20.71k stars 1.29k forks source link

[🐞] Cannot access `$routeLoader$` const before initialization in `npm run preview` #3634

Closed limjiechao closed 3 months ago

limjiechao commented 1 year ago

Which component is affected?

Qwik Runtime

Describe the bug

I am doing…

npm run preview

What I expect is

Preview of production server to run successfully to conduct E2E testing with playwright test.

What's actually happening

Unable to load any route because of the following error:

image

Error is happening because in entry.preview.mjs, usage of routeLoader$ happened before its declaration as a const. See screenshots of prettified build file below:

Usage happened at line 3865:

image

But routeLoader$ happened at line 3607.

image

Reproduction

https://github.com/limjiechao/qwik-error-reproduction

Steps to reproduce

I'm having difficulty reproducing it. But the repo link given is a minimal reproduction of my code.

Run npm run preview and the error described would have occurred in src/routes/layout.tsx.

Please do have a look. I'd be happy to assist further to correctly reproduce the problem for triaging if anyone has any idea what else could lead to it. Thank you!

Check out the repository above. Do a npm i && npm run preview.

System Info

System:
    OS: macOS 13.0
    CPU: (8) arm64 Apple M1
    Memory: 72.53 MB / 16.00 GB
    Shell: 5.8.1 - /bin/zsh
  Binaries:
    Node: 19.7.0 - ~/.nvm/versions/node/v19.7.0/bin/node
    npm: 9.5.0 - ~/.nvm/versions/node/v19.7.0/bin/npm
  Browsers:
    Chrome: 111.0.5563.146
    Safari: 16.1
  npmPackages:
    @builder.io/qwik: 0.100.0 => 0.100.0 
    @builder.io/qwik-city: 0.100.0 => 0.100.0 
    undici: 5.21.0 => 5.21.0 
    vite: 4.2.1 => 4.2.1

Additional Information

This error doesn't occur when I'm not on the RC:

If I downgrade to the following, everything works fine.

        "@builder.io/qwik": "0.24.0",
        "@builder.io/qwik-city": "0.7.1",
        "eslint-plugin-qwik": "^0.24.0",
limjiechao commented 1 year ago

These three const declaration happened after their usages in entry.preview.mjs. Once I moved them up before the usages, the preview could work.

// `routeLoaderQrl(…)`
// REF: https://github.com/BuilderIO/qwik/blob/7eebd491ff85812e54a899b43fccb07ee6bc31da/packages/qwik-city/runtime/src/server-functions.ts#L185-L227
const Be = (e, ...t) => {
  const { id: n, validators: s } = Wt(t, e);

  function a() {
    return L($t, r => {
      if (!(n in r)) throw new Error(`Loader (${n}) was used in a path where the 'loader$' was not declared.
    This is likely because the used loader was not exported in a layout.tsx or index.tsx file of the existing route.
    For more information check: https://qwik.builder.io/qwikcity/route-loader/`);
      return A(r, n);
    });
  }

  return a.__brand = "server_loader", a.__qrl = e, a.__validators = s, a.__id = n, a.use = a, Object.freeze(a), a;
};

// `getValidators(…)`
// REF: https://github.com/BuilderIO/qwik/blob/7eebd491ff85812e54a899b43fccb07ee6bc31da/packages/qwik-city/runtime/src/server-functions.ts#L337-L370
const Wt = (e, t) => {
  let n;
  const s = [];
  if (e.length === 1) {
    const a = e[0];
    a && typeof a == "object" && ("validate" in a ? s.push(a) : (n = a.id, a.validation && s.push(...a.validation)));
  } else e.length > 1 && s.push(...e.filter(a => !!a));
  return typeof n == "string" ? n = `id_${n}` : n = t.getHash(), { validators: s.reverse(), id: n };
};

// `routeActionQrl(…)`
// REF: https://github.com/BuilderIO/qwik/blob/7eebd491ff85812e54a899b43fccb07ee6bc31da/packages/qwik-city/runtime/src/server-functions.ts#L49-L149
const Ue = (e, ...t) => {
  const { id: n, validators: s } = Wt(t, e);

  function a() {
    const r = Z(), o = Mi(),
      i = { actionPath: `?${Ot}=${n}`, isRunning: !1, status: void 0, value: void 0, formData: void 0 }, u = U(() => {
        const p = o.value;
        if (p && (p == null ? void 0 : p.id) === n) {
          const g = p.data;
          if (g instanceof FormData && (i.formData = g), p.output) {
            const { status: y, result: k } = p.output;
            i.status = y, i.value = k;
          }
        }
        return i;
      }), h = m((p = {}) => {
        throw x(), new Error(`Actions can not be invoked within the server during SSR.
Action.run() can only be called on the browser, for example when a user clicks a button, or submits a form.`);
      }, "routeActionQrl_action_submit_A5bZC7WO00A", [o, n, r, u]);
    return i.submit = h, u;
  }

  return a.__brand = "server_action", a.__validators = s, a.__qrl = e, a.__id = n, a.use = a, Object.freeze(a), a;
};
timofei-iatsenko commented 1 year ago

The same with build for express

"build.server": "vite build --ssr src/entry.express.ts",
limjiechao commented 1 year ago

@thekip Are you able to reproduce it? So far, I've no luck in creating one without company code 😢

timofei-iatsenko commented 1 year ago

I was able to fix it by re-adding integration with express using qwik add. I examined files added/changed by CLI and there are slight changes in the configurtion.

So if you are like me updating Qwik from one of the older versions, try to generate a fresh project with latest cli and compare with what you have.

PacoDu commented 1 year ago

I have the same issue after upgrading to qwik v0.100. With minify: false, any route on preview server returns: ReferenceError: Cannot access 'routeLoaderQrl' before initialization.

Note that SSG is working as expected (and npm run dev). Downgrading to qwik 0.25.0 and qwik-city 0.7.1 "solves" the issue.

limjiechao commented 1 year ago

@PacoDu I followed @thekip's suggestion.

It seems like the order of imports is causing the problem in entry.preview.tsx:

image

Problem is resolved by putting import render from './entry.ssr'; after import qwikCityPlan from '@qwik-city-plan';

I don't really understand why the order matters but it did.

See https://github.com/BuilderIO/qwik/blob/7eebd491ff85812e54a899b43fccb07ee6bc31da/packages/docs/src/entry.preview.tsx

PacoDu commented 1 year ago

Thank you @limjiechao ! This also resolved the issue for me with qwik v0.100.

limjiechao commented 1 year ago

@thekip Thanks for the suggestion!

The issue is now reproducible.

Check out the repository https://github.com/limjiechao/qwik-error-reproduction.

npm i && npm run preview
gioboa commented 1 year ago

Hi @limjiechao unfortunately I can't reproduce this issue. with the latest version of Qwik is it still valid?

NiklasPor commented 1 year ago

@gioboa I just encountered this in my ~1.2.12 project and this discord message also looks like it.


FYI switching the import order also solved it for me.

gioboa commented 1 year ago

Thanks @NiklasPor for the update. Can we add a caveat on the official documentation for that? Would you like to create a PR?

NiklasPor commented 1 year ago

Thanks @NiklasPor for the update. Can we add a caveat on the official documentation for that? Would you like to create a PR?

Best thing would be to add a comment in the generated file. Also I created the Qwik app with qwik-nx and it was out of the box in the wrong order.

gioboa commented 1 year ago

Thanks, I'm looking forward your PR ( like the nx one ) ☺️

wmertens commented 7 months ago

@NiklasPor So in conclusion, the import order matters because rollup is strict about it, and we must make sure that starters add a comment?

jakovljevic-mladen commented 7 months ago

@wmertens it seems so for other people, but I had another fix which was even more weird. Please take a look at explanation here (and probably entire thread).

The issue that I had was that the object being used before initialization was a Valibot schema object that I had declared in one file, but the only thing that fixed the issue was to move that Valibot schema object to another file.

If these issues come in random order and in random files, I believe there should be a way to fix them so that they don't appear randomly. If needed, I could grant the access to the repo so you can verify it yourself.

wmertens commented 7 months ago

The order is actually strictly defined. When a module is read, an object holding the exports is prepared, and imports are read in order. When you import a module that is being read, you get that incomplete object.

If you run initialization code in the module, it should only reference already initialized modules.

Rollup makes that explicit by serializing the code in import order. See https://github.com/rollup/rollup/issues/4187

jakovljevic-mladen commented 7 months ago

Sure, it makes sense. But I have two other files with almost the same structure and they don't fail, but this one does.

I understand that this is something I should take care of, but I'm now wondering why it works with 2 out of 3 files that have the same structure. Maybe because of the import order that could be different in referenced files or something?

wmertens commented 7 months ago

The best way to find out is to read the bundle and see what order the modules are put in.

I suppose a workaround would be to make problematic exports be var so they get hoisted.

jakovljevic-mladen commented 7 months ago

The best way to find out is to read the bundle and see what order the modules are put in.

When you have 10-15 lines of code that starts with import ... then it gets really hard to understand the order 🙂

Anyway, thanks for help. And I hope this gets documented somehow, though I'm not sure how.

gioboa commented 3 months ago

I close this one because it's solved.