Skip to content

Commit

Permalink
fix: significantly reduce client bundle size (#547)
Browse files Browse the repository at this point in the history
  • Loading branch information
rchl committed Apr 3, 2023
1 parent a645242 commit ad8eefd
Show file tree
Hide file tree
Showing 7 changed files with 121 additions and 181 deletions.
5 changes: 5 additions & 0 deletions src/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ export async function buildHook (nuxt: Nuxt, moduleOptions: ModuleConfiguration,
mode: 'client',
options: clientOptions,
})
addTemplate({
src: resolve(templateDir, 'client.shared.js'),
filename: 'sentry.client.shared.js',
options: clientOptions,
})

const pluginOptionServer = serverSentryEnabled(moduleOptions) ? 'server' : 'mocked'
const serverOptions: ResolvedServerOptions = defu({ config: { release } }, await resolveServerOptions(nuxt, moduleOptions, logger))
Expand Down
10 changes: 9 additions & 1 deletion src/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,10 +85,18 @@ export default defineNuxtModule<ModuleConfiguration>({
}

// Work-around issues with Nuxt not being able to resolve unhoisted dependencies that are imported in webpack context.
const aliasedDependencies = ['lodash.mergewith', '@sentry/integrations', '@sentry/utils', '@sentry/vue', ...(options.tracing ? ['@sentry/tracing'] : [])]
const aliasedDependencies = [
'lodash.mergewith',
'@sentry/core',
'@sentry/integrations',
'@sentry/utils',
'@sentry/vue',
...(options.tracing ? ['@sentry/tracing'] : []),
]
for (const dep of aliasedDependencies) {
nuxt.options.alias[`~${dep}`] = (await resolvePath(dep)).replace(/\/cjs\//, '/esm/')
}
nuxt.options.alias['~@sentry/browser-sdk'] = (await resolvePath('@sentry/browser/esm/sdk'))

if (serverSentryEnabled(options)) {
/**
Expand Down
9 changes: 3 additions & 6 deletions src/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ export async function resolveRelease (moduleOptions: ModuleConfiguration): Promi
}
}

function resolveLazyOptions (options: ModuleConfiguration, apiMethods: string[], logger: Consola) {
function resolveClientLazyOptions (options: ModuleConfiguration, apiMethods: string[], logger: Consola) {
if (options.lazy) {
const defaultLazyOptions = {
injectMock: true,
Expand Down Expand Up @@ -170,7 +170,7 @@ export async function resolveClientOptions (nuxt: Nuxt, moduleOptions: ModuleCon
}

const apiMethods = await getApiMethods('@sentry/vue')
resolveLazyOptions(options, apiMethods, logger)
resolveClientLazyOptions(options, apiMethods, logger)
resolveTracingOptions(options, config)

for (const name of getIntegrationsKeys(options.clientIntegrations)) {
Expand Down Expand Up @@ -281,14 +281,11 @@ export async function resolveServerOptions (nuxt: Nuxt, moduleOptions: ModuleCon
}

const config = defu(defaultConfig, options.config, options.serverConfig, getRuntimeConfig(nuxt, options))

const apiMethods = await getApiMethods('@sentry/node')
resolveLazyOptions(options, apiMethods, logger)
resolveTracingOptions(options, options.config)

return {
config,
apiMethods,
apiMethods: await getApiMethods('@sentry/node'),
lazy: options.lazy,
logMockCalls: options.logMockCalls, // for mocked only
tracing: options.tracing,
Expand Down
87 changes: 87 additions & 0 deletions src/templates/client.shared.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
<%
if (options.clientConfigPath) {%>import getClientConfig from '<%= options.clientConfigPath %>'
<%}
if (options.customClientIntegrations) {%>import getCustomIntegrations from '<%= options.customClientIntegrations %>'
<%}%>
import merge from '~lodash.mergewith'
<%
const browserIntegrations = options.BROWSER_INTEGRATIONS.filter(key => key in options.integrations)
const vueImports = [
'init',
...(browserIntegrations.length ? ['Integrations'] : []),
...(options.tracing ? ['vueRouterInstrumentation'] : [])
]
%>import { <%= vueImports.join(', ') %> } from '~@sentry/vue'
import * as CoreSdk from '~@sentry/core'
import * as BrowserSdk from '~@sentry/browser-sdk'
<%
if (options.tracing) {%>import { BrowserTracing } from '~@sentry/tracing'
<%}
let integrations = options.BROWSER_PLUGGABLE_INTEGRATIONS.filter(key => key in options.integrations)
if (integrations.length) {%>import { <%= integrations.join(', ') %> } from '~@sentry/integrations'
<%}%>

export { init }
export const SentrySdk = { ...CoreSdk, ...BrowserSdk }

export<%= (options.clientConfigPath || options.customClientIntegrations) ? ' async' : '' %> function getConfig (ctx) {
/* eslint-disable object-curly-spacing, quote-props, quotes, key-spacing, comma-spacing */
const config = {
<%= Object
.entries(options.config)
.map(([key, option]) => {
const value = typeof option === 'function' ? serializeFunction(option) : serialize(option)
return `${key}:${value}`
})
.join(',\n ') %>,
}

<% if (browserIntegrations.length) {%>
const { <%= browserIntegrations.join(', ') %> } = Integrations
<%}%>
config.integrations = [
<%= Object
.entries(options.integrations)
.filter(([name]) => name !== 'Vue')
.map(([name, integration]) => {
const integrationOptions = Object
.entries(integration)
.map(([key, option]) => {
const value = typeof option === 'function' ? serializeFunction(option) : serialize(option)
return `${key}:${value}`
})

return `new ${name}(${integrationOptions.length ? '{ ' + integrationOptions.join(',') + ' }' : ''})`
})
.join(',\n ') %>,
]
<% if (options.tracing) { %>
const { browserTracing, vueOptions, ...tracingOptions } = <%= serialize(options.tracing) %>
config.integrations.push(new BrowserTracing({
...(ctx.app.router ? { routingInstrumentation: vueRouterInstrumentation(ctx.app.router) } : {}),
...browserTracing,
}))
merge(config, vueOptions, tracingOptions)
<% } %>

<% if (options.clientConfigPath) { %>
const clientConfig = await getClientConfig(ctx)
clientConfig ? merge(config, clientConfig) : console.error(`[@nuxtjs/sentry] Invalid value returned from the clientConfig plugin.`)
<% } %>

<% if (options.customClientIntegrations) { %>
const customIntegrations = await getCustomIntegrations(ctx)
if (Array.isArray(customIntegrations)) {
config.integrations.push(...customIntegrations)
} else {
console.error(`[@nuxtjs/sentry] Invalid value returned from customClientIntegrations plugin. Expected an array, got "${typeof customIntegrations}".`)
}
<% } %>

const runtimeConfigKey = <%= serialize(options.runtimeConfigKey) %>
if (ctx.$config && runtimeConfigKey && ctx.$config[runtimeConfigKey]) {
merge(config, ctx.$config[runtimeConfigKey].config, ctx.$config[runtimeConfigKey].clientConfig)
}

return config
}
86 changes: 5 additions & 81 deletions src/templates/plugin.client.js
Original file line number Diff line number Diff line change
@@ -1,85 +1,9 @@
/* eslint-disable import/order */
import Vue from 'vue'
import merge from '~lodash.mergewith'
import * as Sentry from '~@sentry/vue'
<%
if (options.tracing) {
%>import { BrowserTracing } from '~@sentry/tracing'
import { vueRouterInstrumentation } from '~@sentry/vue'
<%}
let integrations = options.BROWSER_PLUGGABLE_INTEGRATIONS.filter(key => key in options.integrations)
if (integrations.length) {%>import { <%= integrations.join(', ') %> } from '~@sentry/integrations'
<%}
if (options.clientConfigPath) {%>import getClientConfig from '<%= options.clientConfigPath %>'
<%}
if (options.customClientIntegrations) {%>import getCustomIntegrations from '<%= options.customClientIntegrations %>'
<%}
integrations = options.BROWSER_INTEGRATIONS.filter(key => key in options.integrations)
if (integrations.length) {%>
const { <%= integrations.join(', ') %> } = Sentry.Integrations
<%}
%>
import { getConfig, init, SentrySdk } from './sentry.client.shared'

export default async function (ctx, inject) {
/* eslint-disable object-curly-spacing, quote-props, quotes, key-spacing, comma-spacing */
const config = {
Vue,
<%= Object
.entries(options.config)
.map(([key, option]) => {
const value = typeof option === 'function' ? serializeFunction(option) : serialize(option)
return `${key}:${value}`
})
.join(',\n ') %>,
}

config.integrations = [
<%= Object
.entries(options.integrations)
.filter(([name]) => name !== 'Vue')
.map(([name, integration]) => {
const integrationOptions = Object
.entries(integration)
.map(([key, option]) => {
const value = typeof option === 'function' ? serializeFunction(option) : serialize(option)
return `${key}:${value}`
})

return `new ${name}(${integrationOptions.length ? '{ ' + integrationOptions.join(',') + ' }' : ''})`
})
.join(',\n ') %>,
]
<% if (options.tracing) { %>
// eslint-disable-next-line prefer-regex-literals
const { browserTracing, vueOptions, ...tracingOptions } = <%= serialize(options.tracing) %>
config.integrations.push(new BrowserTracing({
...(ctx.app.router ? { routingInstrumentation: vueRouterInstrumentation(ctx.app.router) } : {}),
...browserTracing,
}))
merge(config, vueOptions, tracingOptions)
<% } %>

<% if (options.clientConfigPath) { %>
const clientConfig = await getClientConfig(ctx)
clientConfig ? merge(config, clientConfig) : console.error(`[@nuxtjs/sentry] Invalid value returned from the clientConfig plugin.`)
<% } %>

<% if (options.customClientIntegrations) { %>
const customIntegrations = await getCustomIntegrations(ctx)
if (Array.isArray(customIntegrations)) {
config.integrations.push(...customIntegrations)
} else {
console.error(`[@nuxtjs/sentry] Invalid value returned from customClientIntegrations plugin. Expected an array, got "${typeof customIntegrations}".`)
}
<% } %>

const runtimeConfigKey = <%= serialize(options.runtimeConfigKey) %>
if (ctx.$config && runtimeConfigKey && ctx.$config[runtimeConfigKey]) {
merge(config, ctx.$config[runtimeConfigKey].config, ctx.$config[runtimeConfigKey].clientConfig)
}

/* eslint-enable object-curly-spacing, quote-props, quotes, key-spacing, comma-spacing */
Sentry.init(config)
inject('sentry', Sentry)
ctx.$sentry = Sentry
const config = await getConfig(ctx)
init({ Vue, ...config })
inject('sentry', SentrySdk)
ctx.$sentry = SentrySdk
}
104 changes: 11 additions & 93 deletions src/templates/plugin.lazy.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,13 +83,10 @@ async function attemptLoadSentry (ctx, inject) {
if (!window.<%= globals.nuxt %>) {
<% if (options.dev) { %>
console.warn('$sentryLoad was called but window.<%= globals.nuxt %> is not available, delaying sentry loading until onNuxtReady callback. Do you really need to use lazy loading for Sentry?')
<% } %>
<% if (options.lazy.injectLoadHook) { %>
window.<%= globals.readyCallback %>(() => loadSentry(ctx, inject))
<% } else { %>
// Wait for onNuxtReady hook to trigger.
<% } %>
return
<% }
if (options.lazy.injectLoadHook) { %>window.<%= globals.readyCallback %>(() => loadSentry(ctx, inject))
<% } else { %>// Wait for onNuxtReady hook to trigger.
<% } %>return
}

await loadSentry(ctx, inject)
Expand All @@ -109,86 +106,10 @@ async function loadSentry (ctx, inject) {
magicComments.push('webpackPreload: true')
}
%>
const Sentry = await import(/* <%= magicComments.join(', ') %> */ '~@sentry/vue')
<% if (options.tracing) { %>const { BrowserTracing } = await import(/* <%= magicComments.join(', ') %> */ '~@sentry/tracing')<% } %>
<%
if (options.initialize) {
let integrations = options.BROWSER_PLUGGABLE_INTEGRATIONS.filter(key => key in options.integrations)
if (integrations.length) {%>const { <%= integrations.join(', ') %> } = await import(/* <%= magicComments.join(', ') %> */ '~@sentry/integrations')
<% }
integrations = options.BROWSER_INTEGRATIONS.filter(key => key in options.integrations)
if (integrations.length) {%> const { <%= integrations.join(', ') %> } = Sentry.Integrations
<%}

const serializedConfig = Object
.entries({
...options.config,
...(options.tracing ? options.tracing.vueOptions : {}),
})
.map(([key, option]) => {
const value = typeof option === 'function' ? serializeFunction(option) : serialize(option)
return`${key}: ${value}`
})
.join(',\n ')
%>
/* eslint-disable quotes, key-spacing */
const config = {
Vue,
<%= serializedConfig %>,
}

const runtimeConfigKey = <%= serialize(options.runtimeConfigKey) %>
if (ctx.$config && runtimeConfigKey && ctx.$config[runtimeConfigKey]) {
const { default: merge } = await import(/* <%= magicComments.join(', ') %> */ '~lodash.mergewith')
merge(config, ctx.$config[runtimeConfigKey].config, ctx.$config[runtimeConfigKey].clientConfig)
}

config.integrations = [
<%= Object
.entries(options.integrations)
.filter(([name]) => name !== 'Vue')
.map(([name, integration]) => {
const integrationOptions = Object
.entries(integration)
.map(([key, option]) => {
const value = typeof option === 'function' ? serializeFunction(option) : serialize(option)
return `${key}:${value}`
})

return `new ${name}(${integrationOptions.length ? '{ ' + integrationOptions.join(',') + ' }' : ''})`
}).join(',\n ')
%>,
]
<% if (options.tracing) {
const serializedTracingConfig = Object
.entries(options.tracing.browserTracing)
.map(([key, option]) => {
const value = typeof option === 'function' ? serializeFunction(option) : serialize(option)
return`${key}: ${value}`
})
.join(',\n ')
%>
config.integrations.push(new BrowserTracing({
...(ctx.app.router ? { routingInstrumentation: Sentry.vueRouterInstrumentation(ctx.app.router) } : {}),
<%= serializedTracingConfig %>
}))
<% } %>

<% if (options.clientConfigPath) { %>
const clientConfig = (await import(/* <%= magicComments.join(', ') %> */ '<%= options.clientConfigPath %>').then(m => m.default || m))(ctx)
const { default: merge } = await import(/* <%= magicComments.join(', ') %> */ '~lodash.mergewith')
clientConfig ? merge(config, clientConfig) : console.error(`[@nuxtjs/sentry] Invalid value returned from the clientConfig plugin.`)
<% } %>

<%if (options.customClientIntegrations) {%>
const customIntegrations = (await import(/* <%= magicComments.join(', ') %> */ '<%= options.customClientIntegrations %>').then(m => m.default || m))(ctx)
if (Array.isArray(customIntegrations)) {
config.integrations.push(...customIntegrations)
} else {
console.error(`[@nuxtjs/sentry] Invalid value returned from customClientIntegrations plugin. Expected an array, got "${typeof customIntegrations}".`)
}
<% } %>
Sentry.init(config)
const { getConfig, init, SentrySdk } = await import(/* <%= magicComments.join(', ') %> */ './sentry.client.shared')
<% if (options.initialize) {%>
const config = await getConfig(ctx)
init({ Vue, ...config })
<% } %>

loadCompleted = true
Expand All @@ -213,12 +134,10 @@ async function loadSentry (ctx, inject) {
}
delayedUnhandledRejections = []
}
delayedCalls.forEach(([methodName, args]) => Sentry[methodName].apply(Sentry, args))
delayedCalls.forEach(([methodName, args]) => SentrySdk[methodName].apply(SentrySdk, args))
<% } %>

forceInject(ctx, inject, 'sentry', Sentry)

sentryReadyResolve(Sentry)
forceInject(ctx, inject, 'sentry', SentrySdk)
sentryReadyResolve(SentrySdk)

// help gc
<% if (options.lazy.injectMock) { %>
Expand All @@ -234,7 +153,6 @@ async function loadSentry (ctx, inject) {
sentryReadyResolve = undefined
}


// Custom inject function that is able to overwrite previously injected values,
// which original inject doesn't allow to do.
// This method is adapted from the inject method in nuxt/vue-app/template/index.js
Expand Down
1 change: 1 addition & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"exclude": [
"./dist",
"./node_modules/",
"./src/templates/client.*.js",
"./src/templates/plugin.*.js",
]
}

0 comments on commit ad8eefd

Please sign in to comment.