ReviewOS

stacks/stx

An unresolved component renders its error message into the page as text, with no console error and no build failure

#2004
Closed glennmichael123 opened this 9 days ago ·
9 days ago

What happens

@stacksjs/defaults' components resolve by bare name. @stacksjs/components' do not — they need an explicit import in a <script client> block. That is reasonable on its own. What is not reasonable is how the difference announces itself.

Writing <Skeleton /> with no import produces this in the rendered page, as visible text:

[Error loading component: ENOENT: no such file or directory, open 'skeleton']
Searched paths:
  - /…/resources/views/components/skeleton.stx
  - /…/resources/components/skeleton.stx
  … 24 more absolute paths …
  • HTTP 200
  • nothing in the browser console
  • nothing in the dev server output
  • the build succeeds
  • every searched absolute filesystem path is printed into the document

In an app of any size this is a page that looks broken to a user, leaks the developer's directory layout, and gives the developer no signal at all except by reading the rendered HTML.

Which names are affected

In a stock Stacks app, of the components documented in @stacksjs/components:

bare <Name>resolves?
Badge, Card, Avatar, Image, Videoyes — also present in @stacksjs/defaults
Skeleton, Spinner, Switch, Progress, Dialogno — error text into the page

Dialog is the one that hurts. It is the documented way to build a modal, and it is in the group that silently fails. An app can have <Dialog :open="…"> on a page, ship it, and the only symptom is a block of text where the modal was — or nothing at all, if the surrounding markup swallows it.

Expectation

Any of these would fix it, roughly in order of preference:

  1. Make the names resolve. If @stacksjs/components is installed, its components should answer to a bare name the way the defaults' do. The import requirement is invisible in the docs and the failure is silent.
  2. Fail the build, or at minimum log an error to the dev server console and the browser console.
  3. Render nothing (an HTML comment) rather than visible text, and never print absolute filesystem paths into the document.

(3) alone would be a big improvement — the current behaviour is a production hazard independent of the resolution question.

Reproducing

<!-- resources/views/anything.stx -->
<Skeleton />

curl the page. The error is in the body. Add import { Skeleton } from '@stacksjs/components' in a <script client> block and it renders correctly.

@stacksjs/components 0.2.339, stacks 0.75.35.

  • stacksjs/stx#2000 — shortcut rules emitted twice, so a shortcut silently overrides utilities written to modify it. Same shape of problem: correct-looking output, no diagnostic.
9 days ago

Fixed by 988a0e627f fix(components): an unresolved component no longer prints the machine's paths into the page, which is on main and released in 0.2.345.

That is expectation (3) from above, plus the useful half of (2): the page gets an HTML comment, and the searched paths go to console.error, where someone who can act on them is actually looking. packages/stx/test/components/unresolved-component.test.ts pins it, 7 tests, and its header quotes this issue.

Verified on main:

bun test packages/stx/test/components/unresolved-component.test.ts
  7 pass  0 fail

Neither Error loading component nor Searched paths is produced anywhere in packages/*/src any more; the only remaining occurrences are assertions that they are absent, in this test and in config-dir-anchor.test.ts, dynamic-component.test.ts and script-tag-stashing.test.ts.

So the production hazard this issue was titled for, absolute filesystem paths rendered into a 200 response with no other signal, is gone. Closing on that basis.

What is not fixed, and is now its own issue

Expectation (1), the one I listed first, is untouched: @stacksjs/components names still do not answer to a bare tag. Resolution searches project directories only:

// packages/stx/src/utils.ts:748
push(path.resolve(configuredRoot, 'resources/views/components'))
push(path.resolve(configuredRoot, 'resources/components'))

Nothing looks in the installed package, so <Dialog :open="…"> without an import still renders nothing. It is no longer a hazard, just silent, which is a different and much smaller problem than the one this issue described. Split out rather than left attached to a closed fix.

9 days ago

Split tracked as #2011.

Sign in to comment on this issue.