-
-
Notifications
You must be signed in to change notification settings - Fork 125
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
fix: router typegen variable naming #925
fix: router typegen variable naming #925
Conversation
The latest updates on your projects. Learn more about Vercel for Git ↗︎
|
This pull request is automatically built and testable in CodeSandbox. To see build info of the built libraries, click here or the icon next to each commit SHA. |
I'm getting 404s with this... something must be off with generated |
This PR works fine on my cloudflare project... looking into the 404s in my aws lambda project... might be unrelated. |
OK. My 404 on AWS was due to test code I had for the I'm having a lot of trouble testing the latest main due trouble linking the @swc/core dependency. It would be helpful to get a beta (or alpha) build of waku on npm. Thank you! |
0891170
to
00d35d4
Compare
@rmarscher Can you summarize what path issues in specific you were seeing as wrong and what the key changes were? From looking at the code changed, it looks like just most of the code for the file path collection was changed. I'm not opposed to changing it at all, but it's hard to see what the precise bug and solution is for what you were seeing. Also, Referring back to your log of apologies for this being so long 😅 |
OK. I'll create a reproduction. |
There are two main issues that this PR addresses:
Reproducing is pretty easy. Delete the entries.tsx and main.tsx from For me, that causes this error:
I added some debug statements to const updateGeneratedFile = async () => {
if (!pagesDir || !outputFile) return;
const files = await collectFiles(pagesDir);
console.log('collected files', files);
const formatted = await formatter(generateFile(files));
console.log('entries.gen.tsx', formatted);
await writeFile(outputFile, formatted, 'utf-8');
}; which outputs:
before the error. The error happens when calling I can temporarily get past the error by changing that line to Here is the entries.gen.tsx that it's trying to make: import { createPages } from 'waku';
import type { PathsForPages } from 'waku/router';
import _Layout from './pages/_layout';
import Bar from './pages/bar';
import Index from './pages/index';
import SlugName from './pages/[name]';
import Index from './pages/index';
const _pages = createPages(async (pagesFns) => [
pagesFns.createLayout({ path: '/', component: _Layout, render: 'static' }),
pagesFns.createPage({ path: '/bar', component: Bar, render: 'dynamic' }),
pagesFns.createPage({ path: '/', component: Index, render: 'dynamic' }),
pagesFns.createPage({
path: '/[name]',
component: SlugName,
render: 'dynamic',
}),
pagesFns.createPage({ path: '/', component: Index, render: 'dynamic' }),
]);
declare module 'waku/router' {
interface RouteConfig {
paths: PathsForPages<typeof _pages>;
}
}
export default _pages; If you rename
I think it hits that error running the formatter before it saves the entries.gen.tsx. When using the code from this PR, all is good. It looks like this:
I guess technically someone could still do something to mess it up. A file named |
Unit tests for some of these vite-plugin-fs-router-typegen.ts functions might be useful. |
That doesn't make sense to me. I might be missing something. Going off the 10 fs router example, create a
Several of those paths two not resolve and there are duplicate names. After the changes in this PR, those become:
|
This is what I initially was aiming for, so thanks for the fix. |
agreed, would you mind adding some given that you've run into a few of the things we should test for here? |
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.
Thanks for digging into this
@@ -124,14 +110,14 @@ export const fsRouterTypegenPlugin = (opts: { srcDir: string }): Plugin => { | |||
const src = filePath.slice(1); | |||
const hasGetConfig = fileExportsGetConfig(filePath); | |||
|
|||
if (filePath === '/_layout.tsx') { | |||
if (filePath.endsWith('/_layout.tsx')) { |
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.
Why is this needed?
same comment for both endsWith
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.
It's needed for nested index and layout files.
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.
ahh the filepaths are all relative to pages dir now, that makes sense
for (const file of files) { | ||
if (file.name.endsWith('.tsx')) { | ||
results.push('/' + file.name); | ||
const collectFiles = async ( |
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.
@dai-shi this is going back to the manual recursive collection of files since the recursive flag seems to not be supported on node 18 if I followed correctly.
@rmarscher can you add a todo to switch this back whenever we have support for recursive: true
as stable?
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.
Done.
@@ -6,49 +6,29 @@ import { joinPath } from '../utils/path.js'; | |||
|
|||
const SRC_PAGES = 'pages'; | |||
|
|||
const invalidCharRegex = /[^0-9a-zA-Z_$]/g; |
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.
can you add a comment for this explaining which characters are invalid for the filenames and a brief bit about why please?
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.
To be correct... it looks like unicode chars might be allowed. And there are reserved words that need to be replaced too if they match exactly. https://tc39.es/ecma262/multipage/ecmascript-language-lexical-grammar.html#sec-names-and-keywords
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.
sure, thanks! I meant a comment in the code with the regex though 😅 - just for anyone looking later without the PR context handy
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.
Right. It's a good suggestion. I agree we should document it. I just wanted to chat about that first before making revisions.
I found this lib - https://github.com/eemeli/safe-identifier. It references a blog article that doesn't exist anymore that suggests appending hash of the original var name to the sanitized name to ensure that it will be unique. https://web.archive.org/web/20110703045355/https://werxltd.com/wp/2010/05/13/javascript-implementation-of-javas-string-hashcode-method/
I think I'll update it to be more correct by using a technique like that.
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 also found this reference.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#identifiers
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#reserved_words
We reserved words are not an issue because we uppercase the first char.
files: string[] = [], | ||
): Promise<string[]> => { | ||
if (!cwd) { | ||
cwd = dir; |
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.
Is cwd needed? each entry from readDir has it's parent path
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.
dir is the starting dir and cwd is the current dir as it recurses. It needs the starting dir to create the relative path. It probably could use pagesDir
from the outer scope though instead of passing around in the function.
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.
pagesDir seems good to re-use. and I am seeing now that entry.parentPath is only available in node 20, so nvm about that.
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.
Yeah, they started with parentPath
and then changed it to just path
. I tried to use the readdir recursive option when generating the _routes.json file for the Cloudflare Pages build. I found those same issues with it and pivoted to a recursive function - so it was easy for me to apply that here.
} | ||
} | ||
return results; | ||
return files; | ||
}; | ||
|
||
const fileExportsGetConfig = (filePath: string) => { |
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.
Should filePath be renamed? or is this still right?
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.
filePath seems right.
We could create a fixture for an e2e test with various pages and layout paths and names to test. |
I'm not following all the details, but it feels like unit tests are more appropriate than e2e tests. I mean unit tests for more coverage and e2e tests for some typical use cases would be good. General idea: e2e tests are very good and necessary to tests React/RSC framework capabilities, but having more e2e tests slows down our CI, which results in bad DX. So, we prefer unit tests if possible and we are adding more (but not enough) unit tests lately. |
Great. That sounds good. I think there are two issues - needing to use the relative fs route path when creating a list of entries and needing to convert that path into a valid javascript identifier to use as a variable name in the generated file. Tests for those two functions would be helpful. I will add vitest tests. |
@rmarscher wanna make sure you saw this: #925 (comment) It would probably be good to add a check for undefined in the unit tests for the identifiers as well |
Co-authored-by: Tyler <26290074+thegitduck@users.noreply.github.com>
cd61d9d
to
862e240
Compare
// from waku/router/common | ||
export function getInputString(path: string): string { |
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.
Can we simply import it from there? We might be changing the implementation in the future.
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.
When I tried adding import { getInputString } from 'waku/router/common';
to the plugin, I got an error Missing "./router/common" specifier in "waku" package
.
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.
Try to use a relative path.
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.
Try to use a relative path.
Ah right. Thanks.
I'm almost there with a new test that can catch this issue. I pushed a WIP commit but it's still failing. |
1d463ba
to
676ba0e
Compare
…/paths-and-var-names
OK. This is ready again. Test coverage is much better now. I'm mocking the writeFile module to capture the write of a generated file and then checking the output contains expected text. |
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.
LGTM
Can @tylersayshi review?
Looks great to me! thanks again :) |
Thanks team!! 🙌 🙌 |
Fixes #924