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(sveltekit): Auto-wrap load functions with proxy module #7994

Merged
merged 4 commits into from
May 4, 2023

Conversation

Lms24
Copy link
Member

@Lms24 Lms24 commented Apr 28, 2023

Finally something somewhat robust (at least more than modifying the AST):

This PR introduces auto-wrapping of load functions in

  • +(page|layout).(ts|js) files (universal loads)
  • +(page|layout).server.(ts|js) files (server-only loads)

API

API wise, this is not a breaking change for users, as internally, we just add an additional plugin which is returned by the sentrySvelteKit plugin factory function. However, users can add new options to the factory functions, to control auto-wrapping:

sentrySvelteKit({
  autoInstrument: true, // false to disable entirely
  // or
  autoInstrument: {
    load: true,
    serverLoad: false,
  },
  // other options...
  autoUploadSourceMaps: false,
  debug: true,
})

Methodology

(Almost) everything auto-wrapping related happens in a new plugin.
The plugin works by returning wrapping code in the load function whenever a file is encountered in which a load function should be wrapped. The wrapper imports the user's module but appends a query string to the import path of the original module. This makes the next load call to not return the wrapper but the actual user code.

Unfortunately, the query string makes it to the final source map, meaning that we need to clean up the source map when building. We already flatten the source map anyway, so IMO it's fine to also just remove the string at that time.

This works very well for prod builds. In dev mode, client-side loads are wrapped correctly but unfortunately, server-loads are not wrapped. I didn't figure out why this happens but i vote we go with this approach anyway as it's still much safer than AST manipulation.

closes #7940

@github-actions
Copy link
Contributor

github-actions bot commented Apr 28, 2023

size-limit report 📦

Path Size
@sentry/browser - ES5 CDN Bundle (gzipped + minified) 21.01 KB (-0.01% 🔽)
@sentry/browser - ES5 CDN Bundle (minified) 65.62 KB (0%)
@sentry/browser - ES6 CDN Bundle (gzipped + minified) 19.55 KB (-0.02% 🔽)
@sentry/browser - ES6 CDN Bundle (minified) 58.08 KB (0%)
@sentry/browser - Webpack (gzipped + minified) 21.16 KB (0%)
@sentry/browser - Webpack (minified) 69.03 KB (0%)
@sentry/react - Webpack (gzipped + minified) 21.18 KB (0%)
@sentry/nextjs Client - Webpack (gzipped + minified) 49.09 KB (0%)
@sentry/browser + @sentry/tracing - ES5 CDN Bundle (gzipped + minified) 28.64 KB (-0.01% 🔽)
@sentry/browser + @sentry/tracing - ES6 CDN Bundle (gzipped + minified) 26.86 KB (-0.01% 🔽)
@sentry/replay ES6 CDN Bundle (gzipped + minified) 46.86 KB (-0.01% 🔽)
@sentry/replay - Webpack (gzipped + minified) 40.67 KB (0%)
@sentry/browser + @sentry/tracing + @sentry/replay - ES6 CDN Bundle (gzipped + minified) 65.74 KB (-0.01% 🔽)
@sentry/browser + @sentry/replay - ES6 CDN Bundle (gzipped + minified) 58.63 KB (-0.01% 🔽)

Copy link
Member

@lforst lforst left a comment

Choose a reason for hiding this comment

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

nice job man 💪

Comment on lines +113 to +123
function getWrapperCode(
wrapperFunction: 'wrapLoadWithSentry' | 'wrapServerLoadWithSentry',
idWithSuffix: string,
): string {
return (
`import { ${wrapperFunction} } from "@sentry/sveltekit";` +
`import * as userModule from ${JSON.stringify(idWithSuffix)};` +
`export const load = userModule.load ? ${wrapperFunction}(userModule.load) : undefined;` +
`export * from ${JSON.stringify(idWithSuffix)};`
);
}
Copy link
Member

Choose a reason for hiding this comment

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

Honestly I really like that we just do template strings for this instead of typescript templates. Makes this so much simpler.

*
* @returns `true` if we can wrap the given file, `false` otherwise
*/
export async function canWrapLoad(id: string, debug: boolean): Promise<boolean> {
Copy link
Member

Choose a reason for hiding this comment

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

I am very curious how this will work out. I think this is a very sane approach 👍

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah me too, I guess we'll learn it soon enough 😅


const DEFAULT_PLUGIN_OPTIONS: SentrySvelteKitPluginOptions = {
autoUploadSourceMaps: true,
autoInstrument: true,
Copy link
Member

Choose a reason for hiding this comment

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

Doesn't have to be now but we should think about adding an escape hatch for not auto instrumenting particular routes. I've seen users in nexts that found this useful (eg some people didn't want to instrument their auth routes).

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, good point. As discussed, I'll do this in a follow-up PR but I agree that it's also a great way of giving people a quick way to opt out of auto instrumenting in case they get an error.

Copy link
Member

@AbhiPrasad AbhiPrasad left a comment

Choose a reason for hiding this comment

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

nice tests!

debug && console.log(`Skipping wrapping ${id} because it already contains Sentry code`);
}

const hasLoadDeclaration = /(const|let|var|function)\s+load\s*(=|\()/gm.test(codeWithoutComments);
Copy link
Member

Choose a reason for hiding this comment

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

l: What about the case where the function declaration is not load? I think it's fine, but maybe we leave a note in the docs about it (or even add a check).

const someThingElse = () => {

};

export {
  load: someThingElse
}

Copy link
Member Author

@Lms24 Lms24 May 3, 2023

Choose a reason for hiding this comment

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

I thought about this, too but I think this isn't valid syntax: https://developer.mozilla.org/en-US/docs/web/javascript/reference/statements/export#syntax (🤞 )

The closest thing to this example would be

const load = () => {};

export {
  load
}

which should be covered by the regex

Copy link
Member

Choose a reason for hiding this comment

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

Ah yeah sorry missed something in the snippet - you gotta use as for esm, but this still applies if I'm thinking about this correctly.

export {
  someThingElse as load
}

Copy link
Member Author

Choose a reason for hiding this comment

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

Ahh damnit, I missed this case. I think we could adjust the regex to test for /as\s+load/ but I guess this has the potential to interfere with TypeScript's type assertions. However, in the worst case, this would produce a build warning so I think it's fine.

packages/sveltekit/src/vite/autoInstrument.ts Outdated Show resolved Hide resolved
packages/sveltekit/test/vite/autoInstrument.test.ts Outdated Show resolved Hide resolved
@Lms24 Lms24 merged commit 43d9fa9 into develop May 4, 2023
@Lms24 Lms24 deleted the lms/autowrap-load-proxy-module branch May 4, 2023 10:24
billyvg pushed a commit that referenced this pull request May 5, 2023
Introduce auto-wrapping of `load` functions in 
* `+(page|layout).(ts|js)` files (universal loads)
* `+(page|layout).server.(ts|js)` files (server-only loads)

See PR description for further details on API and methodology
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.

Auto-instrument SvelteKit load functions
3 participants