qwik icon indicating copy to clipboard operation
qwik copied to clipboard

Static / server-only components

Open kylecordes opened this issue 3 years ago • 5 comments

Is your feature request related to a problem?

Based on reading this:

https://qwik.builder.io/tutorial/understanding/treeshaking

And elsewhere, I'm aiming to have certain components never download any JavaScript.

Currently this seems to be in emergent behavior, if you don't happen to have any bindings that mutate the JS won't be downloaded. But it is there, it could be downloaded, and you might even prefetch it (which would be 100% waste).

Such code is also always one mistake away from no longer being static at all; the JavaScript is sitting there, bundled, ready to be downloaded.

Describe the solution you'd like

I'd like a way of marking a component static (i.e. server-rendered once, client-rendered never). Such a component would be executed only once; trying to use mutable() inside it would be an error.

The optimizer arranging things such that no client-use JS bundle would exists for the render code. It would be impossible to preload or on-demand load.

Mutating state and expecting a re-render would emit an error from a bit of stub code.

How about declaring such a thing was something like componentStatic$()?

Describe alternatives you've considered

The current design is a reasonable alternative for now. You just have to be careful and manually ensure certain components won’t ever be re-rendered.

Additional context

There is something slightly similar to this in Vue. Not quite at the component level, but it has a way of not providing any JS-based rendering at all for swaths of static HTML.

React Server Components also has some similarities.

An example use I have in mind for this: a component that renders a nontrivial amount of HTML, maybe even with markdown processing or similar. An application with a pile of such components could have lot of "dead" JavaScript code that knows how to create lots of VDOM (and take a dependency on a markdown processor) for HTML/DOM intended to never change. Careful use and checking of the resulting application could ensure this was never downloaded. But a switch to flip and be immediately 100% sure would be better.

kylecordes avatar Jun 30 '22 03:06 kylecordes

I dont dislike the idea, seems more like a enforce rule, than a feature, because such component would be already a server-only component.

how about an option in component$(, {ensureStatic: true}), I dont like the idea of developer setting a component like static or not, because kind of goes against the point of qwik.

manucorporat avatar Jun 30 '22 07:06 manucorporat

That bit of API would work great.

I think right now a developer can already attempt to make a component static; but they must do it by carefully studying the documentation, experimenting, finding bits they missed, realizing a different developer edited a few character somewhere and greatly increased the potential maximum download size etc. The proposed feature would provide a direct, intentional way to do so.

kylecordes avatar Jun 30 '22 16:06 kylecordes

I think this is a worthwhile idea, but I would put it on the backlog for now. The current focus is getting the basics working. So I think it is post v1.0 request

mhevery avatar Jul 12 '22 22:07 mhevery

I have a component that uses node-only code in onGet and onStaticGenerate. I can use the dev server, but I can't build it to SSG.

Here's the error

1: import MarkdownIt from "markdown-it";
2: import { readFile, readdir } from "fs/promises";
            ^
3: import * as path from "path";
4: import fileURLToPath from "file-uri-to-path";
error during build:
Error: 'readFile' is not exported by __vite-browser-external, imported by src/lib/md-post.js

I didn't use src/lib/md-post.js in any other function. It should tree shake in theory.

iacore avatar Oct 16 '22 23:10 iacore

I found the solution: add node modules to blacklist (as external module).

vite.config.ts:


export default defineConfig(() => {
  return {
    build: {
      rollupOptions: {
        external(source, importer, isResolved): boolean | void {
          if (['fs/promises', 'path'].indexOf(source) != -1) return true
        },
      }
    }
  };
});

If you don't use them in browser, it works.

iacore avatar Oct 17 '22 00:10 iacore