-
Notifications
You must be signed in to change notification settings - Fork 134
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
parameterized routing & page loaders #1523
Conversation
Precedence:
Valid vs. invalid parameters:
|
Starting to head down the rabbit hole… 🎩 🐇 🤔 So far, this PR implements dynamic routing for data loaders: you can say e.g. It’ll be more useful when we also have dynamic routing for pages, so you can have e.g. But now imagine that we also have a JavaScript component For now, I’m tempted to do the latter, since I think adding parameterized aliasing for JavaScript components (and likewise stylesheets) would introduce quite a bit of complexity since we need to track both the serving path ( |
I'm excited to see progress on this feature! I have a question about use cases. For parameterized data loaders I can see a very clear use case. Parameterized pages I'm a little fuzzier about the details but still am on board. Where I struggle is the parameterized JS files. I'm trying to think of something that I could do with a parameterized JS file that I couldn't do in Framework today. That is to say, JS files already have a natural way of passing in extra parameters, which both pages and data loaders currently lack. import {widget} from "components/widget.js";
widget(parameter); I'm imagining that Markdown files will have access to their parameters somehow, and could pass them into JS files as needed. Though re-reading, I see now that you haven't specified any mechanism for that. 🤔 Do you have any specific use cases in mind that could be benefit from parameterized JavaScript components? |
The purpose of parameterized pages is essentially to enable parameterized data loaders (through relative paths as described in my previous comment). If we enumerate the parameter values in the config, we can build a page for each parameter value; the parameterized page can then reference a parameterized data loader as a relative path. I don’t have any use case imagined (yet) for parameterized JavaScript components or stylesheets. It’s just something that “falls out” of having parameterized pages. I’m not trying to actively support parameterized JavaScript components, but we’ll have to do something with path rewriting to avoid it if we have parameterized pages. |
As another example, say you have the parameterized page So when we resolve references from parameterized pages, we only want to preserve the parameterized-ness for resources that are generated by data loaders. For other resources we’ll need to rewrite the paths so that they are treated statically. |
I’ve thought of another challenge of trying to share static resources across parameter values. If a parameterized page imports a JavaScript component, we want that JavaScript component to be considered static (since the code will be the same across all parameter values and we’re not trying to support parameterized components). However, that JavaScript component could reference a file, and that file could be generated by a data loader which is parameterized. Since JavaScript components live in So I think either we should duplicate shared resources across parameterized pages (de-duplicating is just a performance optimization anyway, and can be done manually by moving the resources to a non-parameterized path) — or at least duplicate shared components. Or maybe we avoid the concept of parameterized pages entirely. But if we don’t have parameterized pages, then how would you practically take advantage of parameterized data loaders? You can’t pass a dynamic argument to Or maybe we don’t have separate |
I want to try leaning into parameterization. I think the answer here is that we don’t try to share static assets across parameters; if you have Then, for JavaScript components with parameterized paths, we likewise duplicate the components as needed: In addition, we can allow parameterized components and JavaScript code blocks in parameterized pages to refer to parameter values, say as With a bit more magic, this approach could even allow parameter values in arguments to |
I tried it on pangea and got the following error:
The version of glob that I have in yarn.lock is ancient:
as required by Shouldn't glob be a dependency, not a devDependency? Or is it because of the way I link it on pangea?
Adding glob 11 as a dependency in pangea fixes this, but then it chokes on my version of node (21). It wants node 20 or node 22. Using node 20 fixes that, and I can now run the project. |
When a page loader crashes, the preview server sometimes crashes. I think the reproduction is the following: create crash.md.js, with contents process.stdout.write(`# Hello, world`); open the preview server to /crash, the page shows hello world. Now edit the contents to be: process.stdout.write(`# Hello ${name}`); the server crashes. trace
Note: it does not crash if you directly load the erroneous page, in that case it fails gracefully as expected. |
Yes, glob should be a dependency, not a devDependency. We are using glob@10; glob@11 drops support for older versions of Node but we haven’t done the same (yet) in Framework so we shouldn’t use it. I’ve fixed it in the latest commit. I’ll investigate the Preview server crash when a page loader crashes next. I’ve seen that before, too. |
Okay, fixed the page loader crash during preview. I also fixed the logic so that the exponential backoff on reopening the socket works as intended when the socket opens and then immediately crashes because of the page loader; we should only reset the socket reopen delay when the page loader is successful. |
I don't think it's an issue of any consequence, but it's fun to see that if you have a dynamic route I've tried to pass an option value that starts with a dash (e.g., Anyhow my hint is that I could not read the negative number from parseArgs, because it errors with import {parseArgs} from "node:util";
const {
values: {x}
} = parseArgs({
options: {x: {type: "string"}}
});
//const x = +process.argv[1 + [...process.argv].findIndex((d) => d === "--x")]; // this works but…
process.stdout.write(`# Hello ${JSON.stringify(process.argv)}
${JSON.stringify({x})}`); |
Okay, fixed by sending |
I've tried to break it some more, but I can't find how |
Co-authored-by: Philippe Rivière <[email protected]>
docs/page-loaders.md.js
Outdated
\`); | ||
~~~~ | ||
|
||
Framework’s [theme previews](./themes) are implemented as parameterized page loaders; see [their source](https://github.com/observablehq/framework/blob/main/docs/theme/%5Btheme%5D.md.ts) for a practical example. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Framework’s [theme previews](./themes) are implemented as parameterized page loaders; see [their source](https://github.com/observablehq/framework/blob/main/docs/theme/%5Btheme%5D.md.ts) for a practical example. | |
Within this documentation, the [theme previews](./themes) are implemented as parameterized page loaders; see [their source](https://github.com/observablehq/framework/blob/main/docs/theme/%5Btheme%5D.md.ts) for a practical example. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it’s clear that “theme previews” are part of documentation, no?
That's all I have! |
Co-authored-by: Philippe Rivière <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've disabled auto-merge to give you a chance to see the remaining suggestion.
This is a HUGE step forward.
I’m going to move the parameterized page loader docs into the parameterized routes docs so that it isn’t duplicated in both places. That makes the page loader docs pretty short, but that’s not too surprising since they’re very closely related to data loaders. I also want to expand the examples a bit and make them slightly more realistic. |
This introduces two concepts: page loaders allow server-side rendering of dynamic Markdown pages at build time (like data loaders, but for pages), while parameterized routes allow both page and data loaders to generate multiple outputs with varying parameter values.
Note: after this merges, or if you check out this branch locally, you may need to run
git clean -i
to remove the previous copies of theme pages.TODO
[param].csv.js
)[param]/data.csv.js
)prefix-[param].csv.js
)[param1]-[param2].csv.js
)--param=foo
)observable.params.param
)observable.params.param
)observable.params.param
)observable.params.param
FileAttachment
callspath/to/.csv
)[].csv.js
)Default paths should include page loaders, too (just not parameterized routes)Future workparamsdynamicPathsFixes #245.
Fixes #931.