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

feat: add detection + nice error message for npm import errors #195

Merged
merged 12 commits into from
Nov 17, 2022

Conversation

Skn0tt
Copy link
Contributor

@Skn0tt Skn0tt commented Nov 15, 2022

@Skn0tt Skn0tt requested a review from a team November 15, 2022 14:11
@Skn0tt Skn0tt added the type: feature code contributing to the implementation of a feature and/or user facing functionality label Nov 15, 2022
@Skn0tt Skn0tt self-assigned this Nov 15, 2022
expect(error).toBeInstanceOf(NPMImportError)
expect((error as NPMImportError).moduleName).toEqual('p-retry')
expect((error as NPMImportError).message).toEqual(
`It seems like you're trying to import an NPM module, did you mean to 'import ... from "npm:p-retry"'?`,
Copy link
Member

Choose a reason for hiding this comment

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

We shouldn't offer this suggestion because don't support it.


const checkNpmImportError = (input: unknown): NPMImportError | undefined => {
if (input instanceof Error) {
const match = input.message.match(/Relative import path "(.*)" not prefixed with/)
Copy link
Member

Choose a reason for hiding this comment

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

Matching the error message feels a bit brittle. As I suggested here, we could leverage the fact that we're in control of the module resolving (in production anyway, where we use ESZIP).

We should be able to look at a specifier and see whether it's a full URL, a relative path, or anything else. If it's anything else and the loading fails, I think we can safely infer that people are either trying to load an npm module or a specifier that should be in the import map but isn't.

I think doing that check in the loader would be more robust.

Copy link
Contributor Author

@Skn0tt Skn0tt Nov 16, 2022

Choose a reason for hiding this comment

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

I tried doing that check in the loader, but it doesn't seem like the loader is ever called with p-retry. It's called once with netlify:bootstrap-stage2, and once with file:///root/func1.ts, and then the resolution mechanism throw the error Relative import path ... not prefixed with .... So I don't see a way of detecting this within the loader. Maybe i'm missing something? Would be happy for your help on this.

Copy link
Member

Choose a reason for hiding this comment

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

Ah, interesting! I thought the ESZIP loader would be called even for paths that don't match a URL format or a path. In that case, ignore me!

@@ -52,6 +53,11 @@ const bundleESZIP = async ({
try {
await deno.run(['run', ...flags, bundler, JSON.stringify(payload)], { pipeOutput: true })
} catch (error: unknown) {
const npmImportError = checkNpmImportError(error)
if (npmImportError) {
throw npmImportError
Copy link
Member

Choose a reason for hiding this comment

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

wrapBundleError is responsible for setting the customErrorInfo property, which is then used to distinguish user errors from system errors. By not wrapping npmImportError, we'll start incorrectly treating these as system errors.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

that's good to know! fixed in d49211a and added a comment about that in fd38b1f

Comment on lines 55 to 57
} catch (_error: unknown) {
let error = _error
error = checkNpmImportError(error) ?? error
Copy link
Member

Choose a reason for hiding this comment

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

I find this sequence confusing, to be honest.

I think a method called checkNpmImportError that returns either an error or undefined isn't the most natural?

I would either:

  • Call it isNpmImportError and make it return a boolean — you'd then need to call the NPMImportError separately if that condition is true, or
  • Call it something like wrapNpmImportError which always returns an error — either the original error if it's not an npm import error, or an instance of NPMImportError if it is

What do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Agreed! looks like I write too much go :D changed in 67475dd

class NPMImportError extends Error {
constructor(originalError: Error, moduleName: string) {
super(
`It seems like you're trying to import an npm module. This is only supported in Deno via CDNs like esm.sh. Have you tried 'import mod from "https://esm.sh/${moduleName}"'?`,
Copy link
Member

Choose a reason for hiding this comment

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

Btw, extracting the module name with the regular expression and showing it here is a really nice touch! ✨

@Skn0tt
Copy link
Contributor Author

Skn0tt commented Nov 17, 2022

we should really install a pre-commit hook with prettier ...

@kodiakhq kodiakhq bot merged commit 1389373 into main Nov 17, 2022
@kodiakhq kodiakhq bot deleted the helpful-npm-import-errors branch November 17, 2022 14:34
Skn0tt added a commit to netlify/build that referenced this pull request Apr 23, 2024
…fy/edge-bundler#195)

* feat: add detection + nice error message for npm import errors

* fix: update recommendation

* fix: fmt

* fix: remove .only

* feat: also detect npm imports for eszip builds

* Update node/npm_import_error.ts

Co-authored-by: Eduardo Bouças <[email protected]>

* fix: test

* fix: wrap in BundleError

* chore: add comment about user-errors

* refactor: wrapImportError

* fix: prettier

Co-authored-by: Eduardo Bouças <[email protected]>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
automerge type: feature code contributing to the implementation of a feature and/or user facing functionality
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants