Skip to content

Commit ad8eefd

Browse files
committed
fix: significantly reduce client bundle size (#547)
1 parent a645242 commit ad8eefd

7 files changed

+121
-181
lines changed

src/hooks.ts

+5
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,11 @@ export async function buildHook (nuxt: Nuxt, moduleOptions: ModuleConfiguration,
2828
mode: 'client',
2929
options: clientOptions,
3030
})
31+
addTemplate({
32+
src: resolve(templateDir, 'client.shared.js'),
33+
filename: 'sentry.client.shared.js',
34+
options: clientOptions,
35+
})
3136

3237
const pluginOptionServer = serverSentryEnabled(moduleOptions) ? 'server' : 'mocked'
3338
const serverOptions: ResolvedServerOptions = defu({ config: { release } }, await resolveServerOptions(nuxt, moduleOptions, logger))

src/module.ts

+9-1
Original file line numberDiff line numberDiff line change
@@ -85,10 +85,18 @@ export default defineNuxtModule<ModuleConfiguration>({
8585
}
8686

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

93101
if (serverSentryEnabled(options)) {
94102
/**

src/options.ts

+3-6
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ export async function resolveRelease (moduleOptions: ModuleConfiguration): Promi
8383
}
8484
}
8585

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

172172
const apiMethods = await getApiMethods('@sentry/vue')
173-
resolveLazyOptions(options, apiMethods, logger)
173+
resolveClientLazyOptions(options, apiMethods, logger)
174174
resolveTracingOptions(options, config)
175175

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

283283
const config = defu(defaultConfig, options.config, options.serverConfig, getRuntimeConfig(nuxt, options))
284-
285-
const apiMethods = await getApiMethods('@sentry/node')
286-
resolveLazyOptions(options, apiMethods, logger)
287284
resolveTracingOptions(options, options.config)
288285

289286
return {
290287
config,
291-
apiMethods,
288+
apiMethods: await getApiMethods('@sentry/node'),
292289
lazy: options.lazy,
293290
logMockCalls: options.logMockCalls, // for mocked only
294291
tracing: options.tracing,

src/templates/client.shared.js

+87
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
<%
2+
if (options.clientConfigPath) {%>import getClientConfig from '<%= options.clientConfigPath %>'
3+
<%}
4+
if (options.customClientIntegrations) {%>import getCustomIntegrations from '<%= options.customClientIntegrations %>'
5+
<%}%>
6+
import merge from '~lodash.mergewith'
7+
<%
8+
const browserIntegrations = options.BROWSER_INTEGRATIONS.filter(key => key in options.integrations)
9+
const vueImports = [
10+
'init',
11+
...(browserIntegrations.length ? ['Integrations'] : []),
12+
...(options.tracing ? ['vueRouterInstrumentation'] : [])
13+
]
14+
%>import { <%= vueImports.join(', ') %> } from '~@sentry/vue'
15+
import * as CoreSdk from '~@sentry/core'
16+
import * as BrowserSdk from '~@sentry/browser-sdk'
17+
<%
18+
if (options.tracing) {%>import { BrowserTracing } from '~@sentry/tracing'
19+
<%}
20+
let integrations = options.BROWSER_PLUGGABLE_INTEGRATIONS.filter(key => key in options.integrations)
21+
if (integrations.length) {%>import { <%= integrations.join(', ') %> } from '~@sentry/integrations'
22+
<%}%>
23+
24+
export { init }
25+
export const SentrySdk = { ...CoreSdk, ...BrowserSdk }
26+
27+
export<%= (options.clientConfigPath || options.customClientIntegrations) ? ' async' : '' %> function getConfig (ctx) {
28+
/* eslint-disable object-curly-spacing, quote-props, quotes, key-spacing, comma-spacing */
29+
const config = {
30+
<%= Object
31+
.entries(options.config)
32+
.map(([key, option]) => {
33+
const value = typeof option === 'function' ? serializeFunction(option) : serialize(option)
34+
return `${key}:${value}`
35+
})
36+
.join(',\n ') %>,
37+
}
38+
39+
<% if (browserIntegrations.length) {%>
40+
const { <%= browserIntegrations.join(', ') %> } = Integrations
41+
<%}%>
42+
config.integrations = [
43+
<%= Object
44+
.entries(options.integrations)
45+
.filter(([name]) => name !== 'Vue')
46+
.map(([name, integration]) => {
47+
const integrationOptions = Object
48+
.entries(integration)
49+
.map(([key, option]) => {
50+
const value = typeof option === 'function' ? serializeFunction(option) : serialize(option)
51+
return `${key}:${value}`
52+
})
53+
54+
return `new ${name}(${integrationOptions.length ? '{ ' + integrationOptions.join(',') + ' }' : ''})`
55+
})
56+
.join(',\n ') %>,
57+
]
58+
<% if (options.tracing) { %>
59+
const { browserTracing, vueOptions, ...tracingOptions } = <%= serialize(options.tracing) %>
60+
config.integrations.push(new BrowserTracing({
61+
...(ctx.app.router ? { routingInstrumentation: vueRouterInstrumentation(ctx.app.router) } : {}),
62+
...browserTracing,
63+
}))
64+
merge(config, vueOptions, tracingOptions)
65+
<% } %>
66+
67+
<% if (options.clientConfigPath) { %>
68+
const clientConfig = await getClientConfig(ctx)
69+
clientConfig ? merge(config, clientConfig) : console.error(`[@nuxtjs/sentry] Invalid value returned from the clientConfig plugin.`)
70+
<% } %>
71+
72+
<% if (options.customClientIntegrations) { %>
73+
const customIntegrations = await getCustomIntegrations(ctx)
74+
if (Array.isArray(customIntegrations)) {
75+
config.integrations.push(...customIntegrations)
76+
} else {
77+
console.error(`[@nuxtjs/sentry] Invalid value returned from customClientIntegrations plugin. Expected an array, got "${typeof customIntegrations}".`)
78+
}
79+
<% } %>
80+
81+
const runtimeConfigKey = <%= serialize(options.runtimeConfigKey) %>
82+
if (ctx.$config && runtimeConfigKey && ctx.$config[runtimeConfigKey]) {
83+
merge(config, ctx.$config[runtimeConfigKey].config, ctx.$config[runtimeConfigKey].clientConfig)
84+
}
85+
86+
return config
87+
}

src/templates/plugin.client.js

+5-81
Original file line numberDiff line numberDiff line change
@@ -1,85 +1,9 @@
1-
/* eslint-disable import/order */
21
import Vue from 'vue'
3-
import merge from '~lodash.mergewith'
4-
import * as Sentry from '~@sentry/vue'
5-
<%
6-
if (options.tracing) {
7-
%>import { BrowserTracing } from '~@sentry/tracing'
8-
import { vueRouterInstrumentation } from '~@sentry/vue'
9-
<%}
10-
let integrations = options.BROWSER_PLUGGABLE_INTEGRATIONS.filter(key => key in options.integrations)
11-
if (integrations.length) {%>import { <%= integrations.join(', ') %> } from '~@sentry/integrations'
12-
<%}
13-
if (options.clientConfigPath) {%>import getClientConfig from '<%= options.clientConfigPath %>'
14-
<%}
15-
if (options.customClientIntegrations) {%>import getCustomIntegrations from '<%= options.customClientIntegrations %>'
16-
<%}
17-
integrations = options.BROWSER_INTEGRATIONS.filter(key => key in options.integrations)
18-
if (integrations.length) {%>
19-
const { <%= integrations.join(', ') %> } = Sentry.Integrations
20-
<%}
21-
%>
2+
import { getConfig, init, SentrySdk } from './sentry.client.shared'
223

234
export default async function (ctx, inject) {
24-
/* eslint-disable object-curly-spacing, quote-props, quotes, key-spacing, comma-spacing */
25-
const config = {
26-
Vue,
27-
<%= Object
28-
.entries(options.config)
29-
.map(([key, option]) => {
30-
const value = typeof option === 'function' ? serializeFunction(option) : serialize(option)
31-
return `${key}:${value}`
32-
})
33-
.join(',\n ') %>,
34-
}
35-
36-
config.integrations = [
37-
<%= Object
38-
.entries(options.integrations)
39-
.filter(([name]) => name !== 'Vue')
40-
.map(([name, integration]) => {
41-
const integrationOptions = Object
42-
.entries(integration)
43-
.map(([key, option]) => {
44-
const value = typeof option === 'function' ? serializeFunction(option) : serialize(option)
45-
return `${key}:${value}`
46-
})
47-
48-
return `new ${name}(${integrationOptions.length ? '{ ' + integrationOptions.join(',') + ' }' : ''})`
49-
})
50-
.join(',\n ') %>,
51-
]
52-
<% if (options.tracing) { %>
53-
// eslint-disable-next-line prefer-regex-literals
54-
const { browserTracing, vueOptions, ...tracingOptions } = <%= serialize(options.tracing) %>
55-
config.integrations.push(new BrowserTracing({
56-
...(ctx.app.router ? { routingInstrumentation: vueRouterInstrumentation(ctx.app.router) } : {}),
57-
...browserTracing,
58-
}))
59-
merge(config, vueOptions, tracingOptions)
60-
<% } %>
61-
62-
<% if (options.clientConfigPath) { %>
63-
const clientConfig = await getClientConfig(ctx)
64-
clientConfig ? merge(config, clientConfig) : console.error(`[@nuxtjs/sentry] Invalid value returned from the clientConfig plugin.`)
65-
<% } %>
66-
67-
<% if (options.customClientIntegrations) { %>
68-
const customIntegrations = await getCustomIntegrations(ctx)
69-
if (Array.isArray(customIntegrations)) {
70-
config.integrations.push(...customIntegrations)
71-
} else {
72-
console.error(`[@nuxtjs/sentry] Invalid value returned from customClientIntegrations plugin. Expected an array, got "${typeof customIntegrations}".`)
73-
}
74-
<% } %>
75-
76-
const runtimeConfigKey = <%= serialize(options.runtimeConfigKey) %>
77-
if (ctx.$config && runtimeConfigKey && ctx.$config[runtimeConfigKey]) {
78-
merge(config, ctx.$config[runtimeConfigKey].config, ctx.$config[runtimeConfigKey].clientConfig)
79-
}
80-
81-
/* eslint-enable object-curly-spacing, quote-props, quotes, key-spacing, comma-spacing */
82-
Sentry.init(config)
83-
inject('sentry', Sentry)
84-
ctx.$sentry = Sentry
5+
const config = await getConfig(ctx)
6+
init({ Vue, ...config })
7+
inject('sentry', SentrySdk)
8+
ctx.$sentry = SentrySdk
859
}

src/templates/plugin.lazy.js

+11-93
Original file line numberDiff line numberDiff line change
@@ -83,13 +83,10 @@ async function attemptLoadSentry (ctx, inject) {
8383
if (!window.<%= globals.nuxt %>) {
8484
<% if (options.dev) { %>
8585
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?')
86-
<% } %>
87-
<% if (options.lazy.injectLoadHook) { %>
88-
window.<%= globals.readyCallback %>(() => loadSentry(ctx, inject))
89-
<% } else { %>
90-
// Wait for onNuxtReady hook to trigger.
91-
<% } %>
92-
return
86+
<% }
87+
if (options.lazy.injectLoadHook) { %>window.<%= globals.readyCallback %>(() => loadSentry(ctx, inject))
88+
<% } else { %>// Wait for onNuxtReady hook to trigger.
89+
<% } %>return
9390
}
9491

9592
await loadSentry(ctx, inject)
@@ -109,86 +106,10 @@ async function loadSentry (ctx, inject) {
109106
magicComments.push('webpackPreload: true')
110107
}
111108
%>
112-
const Sentry = await import(/* <%= magicComments.join(', ') %> */ '~@sentry/vue')
113-
<% if (options.tracing) { %>const { BrowserTracing } = await import(/* <%= magicComments.join(', ') %> */ '~@sentry/tracing')<% } %>
114-
<%
115-
if (options.initialize) {
116-
let integrations = options.BROWSER_PLUGGABLE_INTEGRATIONS.filter(key => key in options.integrations)
117-
if (integrations.length) {%>const { <%= integrations.join(', ') %> } = await import(/* <%= magicComments.join(', ') %> */ '~@sentry/integrations')
118-
<% }
119-
integrations = options.BROWSER_INTEGRATIONS.filter(key => key in options.integrations)
120-
if (integrations.length) {%> const { <%= integrations.join(', ') %> } = Sentry.Integrations
121-
<%}
122-
123-
const serializedConfig = Object
124-
.entries({
125-
...options.config,
126-
...(options.tracing ? options.tracing.vueOptions : {}),
127-
})
128-
.map(([key, option]) => {
129-
const value = typeof option === 'function' ? serializeFunction(option) : serialize(option)
130-
return`${key}: ${value}`
131-
})
132-
.join(',\n ')
133-
%>
134-
/* eslint-disable quotes, key-spacing */
135-
const config = {
136-
Vue,
137-
<%= serializedConfig %>,
138-
}
139-
140-
const runtimeConfigKey = <%= serialize(options.runtimeConfigKey) %>
141-
if (ctx.$config && runtimeConfigKey && ctx.$config[runtimeConfigKey]) {
142-
const { default: merge } = await import(/* <%= magicComments.join(', ') %> */ '~lodash.mergewith')
143-
merge(config, ctx.$config[runtimeConfigKey].config, ctx.$config[runtimeConfigKey].clientConfig)
144-
}
145-
146-
config.integrations = [
147-
<%= Object
148-
.entries(options.integrations)
149-
.filter(([name]) => name !== 'Vue')
150-
.map(([name, integration]) => {
151-
const integrationOptions = Object
152-
.entries(integration)
153-
.map(([key, option]) => {
154-
const value = typeof option === 'function' ? serializeFunction(option) : serialize(option)
155-
return `${key}:${value}`
156-
})
157-
158-
return `new ${name}(${integrationOptions.length ? '{ ' + integrationOptions.join(',') + ' }' : ''})`
159-
}).join(',\n ')
160-
%>,
161-
]
162-
<% if (options.tracing) {
163-
const serializedTracingConfig = Object
164-
.entries(options.tracing.browserTracing)
165-
.map(([key, option]) => {
166-
const value = typeof option === 'function' ? serializeFunction(option) : serialize(option)
167-
return`${key}: ${value}`
168-
})
169-
.join(',\n ')
170-
%>
171-
config.integrations.push(new BrowserTracing({
172-
...(ctx.app.router ? { routingInstrumentation: Sentry.vueRouterInstrumentation(ctx.app.router) } : {}),
173-
<%= serializedTracingConfig %>
174-
}))
175-
<% } %>
176-
177-
<% if (options.clientConfigPath) { %>
178-
const clientConfig = (await import(/* <%= magicComments.join(', ') %> */ '<%= options.clientConfigPath %>').then(m => m.default || m))(ctx)
179-
const { default: merge } = await import(/* <%= magicComments.join(', ') %> */ '~lodash.mergewith')
180-
clientConfig ? merge(config, clientConfig) : console.error(`[@nuxtjs/sentry] Invalid value returned from the clientConfig plugin.`)
181-
<% } %>
182-
183-
<%if (options.customClientIntegrations) {%>
184-
const customIntegrations = (await import(/* <%= magicComments.join(', ') %> */ '<%= options.customClientIntegrations %>').then(m => m.default || m))(ctx)
185-
if (Array.isArray(customIntegrations)) {
186-
config.integrations.push(...customIntegrations)
187-
} else {
188-
console.error(`[@nuxtjs/sentry] Invalid value returned from customClientIntegrations plugin. Expected an array, got "${typeof customIntegrations}".`)
189-
}
190-
<% } %>
191-
Sentry.init(config)
109+
const { getConfig, init, SentrySdk } = await import(/* <%= magicComments.join(', ') %> */ './sentry.client.shared')
110+
<% if (options.initialize) {%>
111+
const config = await getConfig(ctx)
112+
init({ Vue, ...config })
192113
<% } %>
193114

194115
loadCompleted = true
@@ -213,12 +134,10 @@ async function loadSentry (ctx, inject) {
213134
}
214135
delayedUnhandledRejections = []
215136
}
216-
delayedCalls.forEach(([methodName, args]) => Sentry[methodName].apply(Sentry, args))
137+
delayedCalls.forEach(([methodName, args]) => SentrySdk[methodName].apply(SentrySdk, args))
217138
<% } %>
218-
219-
forceInject(ctx, inject, 'sentry', Sentry)
220-
221-
sentryReadyResolve(Sentry)
139+
forceInject(ctx, inject, 'sentry', SentrySdk)
140+
sentryReadyResolve(SentrySdk)
222141

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

237-
238156
// Custom inject function that is able to overwrite previously injected values,
239157
// which original inject doesn't allow to do.
240158
// This method is adapted from the inject method in nuxt/vue-app/template/index.js

tsconfig.json

+1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
"exclude": [
2626
"./dist",
2727
"./node_modules/",
28+
"./src/templates/client.*.js",
2829
"./src/templates/plugin.*.js",
2930
]
3031
}

0 commit comments

Comments
 (0)