Skip to content
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

Detecting server imports on the client #2442

Open
wants to merge 18 commits into
base: main
Choose a base branch
from

Conversation

infomiho
Copy link
Contributor

@infomiho infomiho commented Jan 9, 2025

We want to detect imports from wasp/server/* in users' client code because we know it's incorrect. It's better to give users a nice error message than users receiving some (often hard to understand) runtime error message.

This is a draft initial implementation, I'll iterate on this a bit more.

Closes #2067

@infomiho infomiho marked this pull request as ready for review January 20, 2025 12:13
moduleName: string;
}

const importStatementRegex = /\s*import\s+(?:(?:[\w*\s{},]*)\s+from\s+)?(['"`])([^'"`]+)\1\s*/g;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't dive deep, just wanted to check, since this is regex, is there a chance of false positives? If so, in which situations, and are we ok with that? If not, can we do it better?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, could we maybe parse the import and do something with its AST (I'm not sure how powerful these plugins are, just throwing out ideas)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Vite doesn't expose AST information to the plugins. One the popular plugins unplugin-ast uses the Babel parser under the hood to parse files into AST.

I didn't want to go down this route due to potential performance issues, but I have to admit that I haven't timed anything. I just assumed that regexes will be good enough.

I went with a good enough approach because we are not catch all server imports anyways. We are only focusing on wasp/server/* modules (for which we know they shouldn't be imported on the client). But... we don't catch stuff like some-node-dep.

I will explore the AST approach and see how it works and report back. I'll also explore the JS imports syntax and see if our regex matches all the cases.

Copy link
Contributor

@sodic sodic left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I love the change, but have some reserverations about the implementation.

return {
name: 'wasp-detect-server-imports',
transform(code, filePath) {
const isInDotWaspFolder = filePath.includes("/.wasp/");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a more appropriate way to do this? For example, by saying "check only src"?

Comment on lines 38 to 39
importStatement: match[0].trim(),
moduleName: match[2],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's better to destructure the array and name each segment instead of using indexes.

}

function getServerImportErrorMessage(imp: Import, filePath: string): string {
return `Client module "${getRelativeFilePath(filePath)}" imports server code:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like a double space, don't forget to run a formatter :)

Comment on lines 75 to 76
-- Vite plugins
genFileCopy [relfile|vite/detectServerImports.ts|]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of writing a comment, we should scope the file under an appropriately named function/variable. For example:

genVitePlugins :: ...
genVitePlugins = ...

Or

genViteFiles :: ...
genViteFiles ...
  where config = ...
        plugins = ...

// user running the tests, which is different on different machines.
function getWaspProjectDirAbsPathFromCwd(): string {
const webAppDirAbsPath = process.cwd();
const waspProjectDirAbsPath = path.join(webAppDirAbsPath, "../../../");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We already defined this relative path somewhere, probably in multiple places.

I know it's defined in one of our JS files. I think even Haskell code defines a constant under Project.Common (but perhaps this wasn't merged)

In any case, this knowledge should definitely come from Haskell. If you're up for it, you could change it in those other places too.

Also, I don't understand the reason we couldn't pass waspProjectDir. Could we add some more details in the comment? Perhaps an example (what it is vs. what we need)?

@@ -6,5 +6,5 @@
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true
},
"include": ["vite.config.ts", "./src/ext-src/vite.config.ts"]
"include": ["vite.config.ts", "./src/ext-src/vite.config.ts", "./vite/detectServerImports.ts"]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, this is starting to feel like something we should pass in through the template.

Comment on lines 54 to 56
function getRelativeFilePath(filePath: string): string {
return filePath.replace(waspProjectDirAbsPath, "");
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we improve this function's name?

moduleName: string;
}

const importStatementRegex = /\s*import\s+(?:(?:[\w*\s{},]*)\s+from\s+)?(['"`])([^'"`]+)\1\s*/g;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, could we maybe parse the import and do something with its AST (I'm not sure how powerful these plugins are, just throwing out ideas)?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Detect server imports on the client
3 participants