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

Plugin breaks Sentry #35

Closed
1 task done
IGassmann opened this issue Oct 6, 2022 · 21 comments · Fixed by #41
Closed
1 task done

Plugin breaks Sentry #35

IGassmann opened this issue Oct 6, 2022 · 21 comments · Fixed by #41
Labels
bug Something isn't working
Milestone

Comments

@IGassmann
Copy link

IGassmann commented Oct 6, 2022

Verify Next.js canary release

  • I verified that the issue exists in the latest Next.js canary release

Describe the bug

When using next-supersjon-plugin alongside @sentry/nextjs with auto server instrumentation — which is now part of the default Sentry configuration for Next.js —, the app doesn't build anymore when running yarn build:

info  - Collecting page data .Error: You can not use getInitialProps with getServerSideProps. Please remove getInitialProps.
    at /Users/igassmann/Repositories/IGassmann/error-page-autoinstrumentation/node_modules/next/dist/build/utils.js:872:19
    at processTicksAndRejections (node:internal/process/task_queues:96:5)
    at async Span.traceAsyncFn (/Users/igassmann/Repositories/IGassmann/error-page-autoinstrumentation/node_modules/next/dist/trace/trace.js:79:20)

> Build error occurred
Error: Failed to collect page data for /_error
    at /Users/igassmann/Repositories/IGassmann/error-page-autoinstrumentation/node_modules/next/dist/build/utils.js:916:15
    at processTicksAndRejections (node:internal/process/task_queues:96:5) {
  type: 'Error'
}
error Command failed with exit code 1.

Expected behavior

next-supersjon-plugin shouldn't break the build.

Reproduction link

https://github.com/IGassmann/error-page-autoinstrumentation

Version

0.4.2

Config

// This file sets a custom webpack configuration to use your Next.js app
// with Sentry.
// https://nextjs.org/docs/api-reference/next.config.js/introduction
// https://docs.sentry.io/platforms/javascript/guides/nextjs/

const { withSentryConfig } = require('@sentry/nextjs');

const moduleExports = {
  experimental: {
    /**
     * Enable `superjson` experimental support for swc.
     */
    swcPlugins: [
      [
        "next-superjson-plugin", {},
      ],
    ],
  },
  reactStrictMode: true,
  swcMinify: true,
  sentry: {
    // Use `hidden-source-map` rather than `source-map` as the Webpack `devtool`
    // for client-side builds. (This will be the default starting in
    // `@sentry/nextjs` version 8.0.0.) See
    // https://webpack.js.org/configuration/devtool/ and
    // https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/#use-hidden-source-map
    // for more information.
    hideSourceMaps: true,
    widenClientFileUpload: true,
    autoInstrumentServerFunctions: true,

    // Disable SentryWebpackPlugin so that the project can build without Sentry CLI token
    disableClientWebpackPlugin: true,
    disableServerWebpackPlugin: true,
  },
};

// Make sure adding Sentry options is the last code to run before exporting, to
// ensure that your source maps include changes from all other Webpack plugins
module.exports = withSentryConfig(moduleExports);

Additional context

The issue is also reported on Sentry Javascript repository: getsentry/sentry-javascript#5904

@orionmiz
Copy link
Collaborator

orionmiz commented Oct 6, 2022

I cloned the project above and tried to build it, but failed to reproduce this bug in my local environment.

If you have _error.js in the build artifacts (maybe located in .next/server/pages), please upload it.

@orionmiz orionmiz added this to the v0.4.3 milestone Oct 6, 2022
@IGassmann
Copy link
Author

@orionmiz I've fixed the project. Try once more, please.

@IGassmann
Copy link
Author

IGassmann commented Oct 6, 2022

To be clear, you need to do the following steps to reproduce:

  1. Clone IGassmann/error-page-autoinstrumentation
  2. Run yarn install
  3. Run yarn build

@lobsterkatie
Copy link

lobsterkatie commented Oct 7, 2022

Hi, all. @sentry/nextjs maintainer here.

I've looked into this, and I believe I've found the problem.

At a high level, the issue is that both we and the superJSON plugin are wrapping user code, and doing it in ways which are incompatible. We do it using a webpack loader which creates a proxy module in front of the user's page file. The superJSON plugin does it by parsing the user code into an AST and manipulating whatever relevant nodes it finds. (When building our wrapping feature, AST manipulation was actually our first approach as well, but the insane amount of edge-case-checking code we built up by the end made us reconsider.)

In any case, when using an AST approach, both the superJSON plugin and our original implementation worked on a "if specific value x is there, wrap it; if it's not, do nothing" system. (x in this case refers to something like an exported getServerSideProps function.) Our proxy loader approach works on a "proxy everything in such a way that whether it's there or not, nothing breaks" system. This is necessary because JS doesn't allow export statements to be wrapped in a conditional, so we have to move the conditional logic into the calculation of the exported value. (In other words, since we can't do (in pseudocode) if (x) export sentryWrapper(x), we have to do export x ? sentryWrapper(x) : undefined.)

The problem comes because our loader runs before the superJSON plugin, and our export x ? sentryWrapper(x) : undefined tricks the plugin into thinking that x is definitely there to be wrapped. As a result, functions which can't coexist if both defined (like getServerSideProps and getInitialProps) both end up with defined values of the form superJSONWrapper(x ? sentryWrapper(x) : undefined), and the build crashes.

One solution would be to force our loader to run last rather than first, but that has the disadvantage that it means our proxy module code won't go through the same processing as the rest of the user's code. In simple cases (maybe all cases?) that's probably fine - we use a pre-vanilla-fied version of our template so in theory no transpilation should be needed - but it's hard to feel totally confident nothing could go wrong that way. Another (seemingly more foolproof) solution would be for the superJSON plugin to specifically handle the export x ? sentryWrapper(x) : undefined case, so that instead of export superJSONWrapper(x ? sentryWrapper(x) : undefined) it becomes export x ? superJSONWrapper(sentryWrapper(x)) : undefined.

@orionmiz, how would you feel about that second solution? I know it's work on your part, but on our end I haven't yet been able to think of a (safe) way to fix it, without diving back into the AST madness we specifically moved away from. If you have other ideas, I'm definitely open to hearing them as well. (Also cc @lforst on that score.) Thoughts?

@lobsterkatie
Copy link

To be clear, you need to do the following steps to reproduce:

Clone IGassmann/error-page-autoinstrumentation
Run yarn install
Run yarn build

@IGassmann, I also had to add dryRun: true to sentryWebpackPluginOptions in next.config.js to prevent it from trying to upload sourcemaps to your org and then crashing in the absence of your auth token.

@IGassmann
Copy link
Author

@lobsterkatie, thanks a lot for investigating this so promptly and also for sharing all your findings. Learned a lot from this.

@lobsterkatie
Copy link

lobsterkatie commented Oct 7, 2022

Thought about this a bit more while walking the dogs, and another thing which occurred to me is that the problem is actually slightly worse (or at least more complicated) than I realized, because if we continue to go before the superJSON plugin, it will end up wrapping both our proxy module and the original page code, so in fact it's going to end up being something along the lines of export superJSONWrapper(superJSONWrapper(x) ? sentryWrapper(superJSONWrapper(x)) : undefined). Even with my proposed second option, it would still be export superJSONWrapper(x) ? superJSONWrapper(sentryWrapper(superJSONWrapper(x))) : undefined. And even the simpler case, that of the page component itself, would end up being export default superJSONWrapper(sentryWrapper(superJSONWrapper(origPageComponent))). So maybe we do need to figure out some way to safely go last...

@orionmiz
Copy link
Collaborator

orionmiz commented Oct 7, 2022

@lobsterkatie Thanks for finding the cause of this bug and also giving solutions for the plugin instead of me.

But seriously, suggested solution for the plugin may not be the ideal way to fix the bug.
Because if another plugin tries to wrap expressions in export statements, same kind of bugs would occurs again.

So I recommend to use other solutions, let's look at those.

Most simple way: Switching the order of loader
But you worried about rest of the user's code.

Most safe way: Using AST for transforming
It's tricky enough, but not impossible.
While developing this plugin, It was also the challenging parts for me to check edge cases as you said above.
So I can share possible cases and solutions to treat them if you want to fix the bug in this way.

How do you think?

@IGassmann
Copy link
Author

IGassmann commented Oct 12, 2022

@orionmiz another simple solution for this would be to allow the user to opt out of next-superjson-plugin for certain files by adding a configuration property excludedFiles, or/and by excluding by default the following files:

  • /_app
  • /_document
  • /_error

For instance, @sentry/nextjs already handle those files separately.

@lforst
Copy link

lforst commented Oct 12, 2022

Hi, another @sentry/nextjs maintainer here. I am also gonna leave some thoughts. And just as a side note, I believe @lobsterkatie and I aren't totally of the same opinion here - even though it is also in my interest that we're being "good citizens" with our SDK.

As @lobsterkatie already mentioned, we evaluated (and even implemented as part of an early iteration) a very similar approach to yours where we modify the AST. However, one of the big reasons we dropped this approach is because of the exact problem you're running into right now. It is really fragile when users do something slightly out of the ordinary. Think about it this way: The Sentry SDK is doing nothing to user code that the user couldn't do themselves.

My recommendation is as follows:

  • Either switch from the AST modification to something similar we do - which is injecting a proxy file that wraps data fetchers and re-exports them. Rollup has a pretty nice example (code example in resolveId section).
  • Make your AST modifications more robust.

Either way, I recommend that you make your wrapping method idempotent so any double wrapping won't break.

@orionmiz orionmiz removed this from the v0.4.3 milestone Oct 12, 2022
@orionmiz
Copy link
Collaborator

orionmiz commented Oct 13, 2022

@IGassmann First of all, I still cannot reproduce this issue.
But suggested option could be considered as a new feature regardless of the issue.

Assuming the collision between sentry loader and the plugin exists,
I came up with a pretty simple solution:

The only thing I concerned about is the order of export statements.
Plugin considers that each page has only one props function. (wrap the first thing found from the top)
If Webpack locates the dummy export after the real export, that workaround is valid.
Otherwise, plugin cannot even reach the real export.
In this case, the error seems to be gone, but plugin wouldn't work as intended.

@lobsterkatie @lforst Did I get it right? I want to hear your opinions.

  • Sorry, we cannot afford switching from the AST modification to the loader by the performance issue.
    Rather than that, maybe I should make the plugin more robust.

@orionmiz orionmiz modified the milestones: v0.4.3, Planned Oct 13, 2022
@IGassmann
Copy link
Author

@orionmiz did you freshly clone the repository? I've tested the repository once again, and I was able to reproduce it. Here are the exact steps I used:

  1. git clone [email protected]:IGassmann/error-page-autoinstrumentation.git
  2. cd error-page-autoinstrumentation/
  3. yarn install
  4. yarn build

@orionmiz
Copy link
Collaborator

orionmiz commented Oct 13, 2022

@IGassmann https://github.com/orionmiz/error-page-autoinstrumentation/actions/runs/3239954704/jobs/5309976851

Just found why I can't reproduce this issue in my local machine.. We need some investigations.

@IGassmann
Copy link
Author

@orionmiz, I assume you are on Windows? Interesting that it doesn't fail on it.

@lforst
Copy link

lforst commented Oct 13, 2022

The only thing I concerned about is the order of export statements.
Plugin considers that each page has only one props function. (wrap the first thing found from the top)
If Webpack locates the dummy export after the real export, that workaround is valid.
Otherwise, plugin cannot even reach the real export.
In this case, the error seems to be gone, but plugin wouldn't work as intended.
@lobsterkatie @lforst Did I get it right? I want to hear your opinions.

Hmm, I guess it would even be safe to just wrap all of the props functions, even if there are multiple ones. You just have to do a simple const wrappedFunc = func !== undefined ? wrap(func) : undefined. The reason is, that Next.js will throw a build error anyways when there are multiple props functions on a page.

@orionmiz
Copy link
Collaborator

orionmiz commented Oct 13, 2022

[REDACTED]

@lobsterkatie @lforst

By the way, @sentry/next doesn't seem to be working on Windows.

It's not our problem. because there has been a relevant issue before:

I noticed that from above:

@IGassmann https://github.com/orionmiz/error-page-autoinstrumentation/actions/runs/3239954704/jobs/5309976851

Just found why I can't reproduce this issue in my local machine.. We need some investigations.

Likewise, you can notice that the size of Window's build artifact is smaller than the Ubuntu, macOS's one from here:
https://github.com/orionmiz/error-page-autoinstrumentation/actions/runs/3241414830

Maybe pageProxyloader template doesn't seem to be attached to the codes properly in Windows as I see.

Hope this clue could help fix the bug. Good luck.

@orionmiz orionmiz modified the milestone: Planned Oct 13, 2022
@IGassmann
Copy link
Author

@orionmiz the new version resolved the previous error, but it's now causing a new error that wasn't occurring before:

Error occurred prerendering page "/404". Read more: https://nextjs.org/docs/messages/prerender-error
TypeError: Cannot read properties of undefined (reading '_superjson')
    at deserializeProps (.../next-superjson-plugin/tools.js:151:38)
    at WithSuperJSON (.../node_modules/next-superjson-plugin/tools.js:157:55)
    at Object.documentElement (.../node_modules/next/dist/server/render.js:745:42)
    at renderToHTML (.../node_modules/next/dist/server/render.js:893:23)
    at async .../node_modules/next/dist/export/worker.js:304:36
    at async Span.traceAsyncFn (.../node_modules/next/dist/trace/trace.js:79:20)

Error occurred prerendering page "/some-page/path". Read more: https://nextjs.org/docs/messages/prerender-error
TypeError: Cannot read properties of undefined (reading '_superjson')
    at deserializeProps (.../node_modules/next-superjson-plugin/tools.js:151:38)
    at WithSuperJSON (.../node_modules/next-superjson-plugin/tools.js:157:55)
    at Object.documentElement (.../node_modules/next/dist/server/render.js:745:42)
    at renderToHTML (.../node_modules/next/dist/server/render.js:893:23)
    at async .../node_modules/next/dist/export/worker.js:304:36
    at async Span.traceAsyncFn (.../node_modules/next/dist/trace/trace.js:79:20)

Error occurred prerendering page "/some-other-page/path". Read more: https://nextjs.org/docs/messages/prerender-error
TypeError: Cannot read properties of undefined (reading '_superjson')
    at deserializeProps (.../node_modules/next-superjson-plugin/tools.js:151:38)
    at WithSuperJSON (.../node_modules/next-superjson-plugin/tools.js:157:55)
    at Object.documentElement (.../node_modules/next/dist/server/render.js:745:42)
    at renderToHTML (.../node_modules/next/dist/server/render.js:893:23)
    at async .../node_modules/next/dist/export/worker.js:304:36
    at async Span.traceAsyncFn (.../node_modules/next/dist/trace/trace.js:79:20)
    
...

@orionmiz
Copy link
Collaborator

orionmiz commented Oct 15, 2022

@IGassmann This bug is fixed in v0.4.7 with next@canary. Please try it.

@IGassmann
Copy link
Author

@orionmiz I'm once again getting some new other errors:

thread '<unnamed>' panicked at 'failed to invoke plugin: failed to invoke plugin on 'Some(".../sentry.server.config.js")'

Caused by:
    0: failed to deserialize `swc_ecma_ast::module::Program`
    1: duplicate shared pointer: 0x360b6937978

Stack backtrace:
   0: _wasmer_vm_raise_trap
   1: _napi_register_module_v1
   2: _napi_register_module_v1
   3: _napi_register_module_v1
   4: _napi_register_module_v1
   5: <unknown>
   6: <unknown>
   7: <unknown>
   8: <unknown>
   9: <unknown>
  10: <unknown>
  11: <unknown>
  12: <unknown>
  13: <unknown>
  14: <unknown>
  15: _worker
  16: __pthread_deallocate', /Users/runner/.cargo/registry/src/github.com-1ecc6299db9ec823/swc-0.226.26/src/plugin.rs:216:14
stack backtrace:
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.
thread '<unnamed>' panicked at 'called `Result::unwrap()` on an `Err` value: LayoutError', /home/runner/.cargo/registry/src/github.com-1ecc6299db9ec823/rkyv-0.7.37/src/impls/core/mod.rs:265:67
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
thread '<unnamed>' panicked at 'failed to invoke plugin: failed to invoke plugin on 'Some(".../pages/404.tsx")'

Caused by:
    0: failed to invoke `.../node_modules/next-superjson-plugin/next_superjson.wasm` as js transform plugin at .../node_modules/next-superjson-plugin/next_superjson.wasm
    1: RuntimeError: unreachable
    2: unreachable

Stack backtrace:
   0: _wasmer_vm_raise_trap
   1: _wasmer_vm_raise_trap
   2: _napi_register_module_v1
   3: _napi_register_module_v1
   4: _napi_register_module_v1
   5: <unknown>
   6: <unknown>
   7: <unknown>
   8: <unknown>
   9: <unknown>
  10: <unknown>
  11: <unknown>
  12: <unknown>
  13: <unknown>
  14: <unknown>
  15: _worker
  16: __pthread_deallocate', /Users/runner/.cargo/registry/src/github.com-1ecc6299db9ec823/swc-0.226.26/src/plugin.rs:216:14
stack backtrace:
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.
thread '<unnamed>' panicked at 'called `Result::unwrap()` on an `Err` value: LayoutError', /home/runner/.cargo/registry/src/github.com-1ecc6299db9ec823/rkyv-0.7.37/src/impls/core/mod.rs:265:67
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
thread '<unnamed>' panicked at 'failed to invoke plugin: failed to invoke plugin on 'Some(".../pages/_app.tsx")'

Caused by:
    0: failed to invoke `.../node_modules/next-superjson-plugin/next_superjson.wasm` as js transform plugin at .../node_modules/next-superjson-plugin/next_superjson.wasm
    1: RuntimeError: unreachable
    2: unreachable

Stack backtrace:
   0: _wasmer_vm_raise_trap
   1: _wasmer_vm_raise_trap
   2: _napi_register_module_v1
   3: _napi_register_module_v1
   4: _napi_register_module_v1
   5: <unknown>
   6: <unknown>
   7: <unknown>
   8: <unknown>
   9: <unknown>
  10: <unknown>
  11: <unknown>
  12: <unknown>
  13: <unknown>
  14: <unknown>
  15: _worker
  16: __pthread_deallocate', /Users/runner/.cargo/registry/src/github.com-1ecc6299db9ec823/swc-0.226.26/src/plugin.rs:216:14
stack backtrace:
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.
thread '<unnamed>' panicked at 'called `Result::unwrap()` on an `Err` value: LayoutError', /home/runner/.cargo/registry/src/github.com-1ecc6299db9ec823/rkyv-0.7.37/src/impls/core/mod.rs:265:67
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
thread '<unnamed>' panicked at 'failed to invoke plugin: failed to invoke plugin on 'Some(".../pages/500.tsx")'

Caused by:
    0: failed to invoke `.../node_modules/next-superjson-plugin/next_superjson.wasm` as js transform plugin at .../node_modules/next-superjson-plugin/next_superjson.wasm
    1: RuntimeError: unreachable
    2: unreachable

Stack backtrace:
   0: _wasmer_vm_raise_trap
   1: _wasmer_vm_raise_trap
   2: _napi_register_module_v1
   3: _napi_register_module_v1
   4: _napi_register_module_v1
   5: <unknown>
   6: <unknown>
   7: <unknown>
   8: <unknown>
   9: <unknown>
  10: <unknown>
  11: <unknown>
  12: <unknown>
  13: <unknown>
  14: <unknown>
  15: _worker
  16: __pthread_deallocate', /Users/runner/.cargo/registry/src/github.com-1ecc6299db9ec823/swc-0.226.26/src/plugin.rs:216:14
stack backtrace:
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.
thread '<unnamed>' panicked at 'called `Result::unwrap()` on an `Err` value: LayoutError', /home/runner/.cargo/registry/src/github.com-1ecc6299db9ec823/rkyv-0.7.37/src/impls/core/mod.rs:265:67
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
thread '<unnamed>' panicked at 'failed to invoke plugin: failed to invoke plugin on 'Some(".../pages/some-page-path/my-page.tsx")'

Caused by:
    0: failed to invoke `.../node_modules/next-superjson-plugin/next_superjson.wasm` as js transform plugin at .../node_modules/next-superjson-plugin/next_superjson.wasm
    1: RuntimeError: unreachable
    2: unreachable

Stack backtrace:
   0: _wasmer_vm_raise_trap
   1: _wasmer_vm_raise_trap
   2: _napi_register_module_v1
   3: _napi_register_module_v1
   4: _napi_register_module_v1
   5: <unknown>
   6: <unknown>
   7: <unknown>
   8: <unknown>
   9: <unknown>
  10: <unknown>
  11: <unknown>
  12: <unknown>
  13: <unknown>
  14: <unknown>
  15: _worker
  16: __pthread_deallocate', /Users/runner/.cargo/registry/src/github.com-1ecc6299db9ec823/swc-0.226.26/src/plugin.rs:216:14
stack backtrace:
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.

@orionmiz
Copy link
Collaborator

@IGassmann Please let me know these things:

  • Next.js version
  • Plugin version
  • Is build cache used

@IGassmann
Copy link
Author

@IGassmann Please let me know these things:

  • Next.js version
  • Plugin version
  • Is build cache used

My bad. The latest Next.js canary version wasn't installed due to mismatching peer dependencies' requirements from other packages. I had to force install the latest canary version to make it work.

I tested, and it worked with the following:

  • Next.js version: 12.3.2-canary.29
  • Plugin version: 0.4.8
  • Is build cache used: No

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants