-
-
Notifications
You must be signed in to change notification settings - Fork 1.8k
fix(nextjs): Add orchestrion bundling regression tests and import.meta.url shim #23935
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
Merged
Merged
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
70 changes: 70 additions & 0 deletions
70
dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/worker-bundle.test.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,70 @@ | ||
| import { expect, test } from '@playwright/test'; | ||
| import * as fs from 'fs'; | ||
| import { createRequire } from 'module'; | ||
| import * as path from 'path'; | ||
| import { isDevMode } from './isDevMode'; | ||
|
|
||
| /** | ||
| * The orchestrion bundler plugins are build-time-only, and their module-scope side effects break | ||
| * on Workers (an unawaited `WebAssembly.compile()` crashed every cold start, issue #22794). The | ||
| * worker bundle OpenNext produces must therefore never contain them: importing `@sentry/nextjs` | ||
| * on the server has to keep the plugin graph out of the deployed artifact. | ||
| */ | ||
| test('worker bundle does not contain the orchestrion bundler plugins', () => { | ||
| test.skip(isDevMode, 'requires the production worker build'); | ||
|
|
||
| const openNextDir = path.resolve(__dirname, '..', '.open-next'); | ||
| expect(fs.existsSync(path.join(openNextDir, 'worker.js'))).toBe(true); | ||
|
|
||
| // `assets` holds the static client files; everything else is code the worker can run. | ||
| const serverFiles = collectJsFiles(openNextDir).filter( | ||
| filePath => !filePath.startsWith(path.join(openNextDir, 'assets')), | ||
| ); | ||
| expect(serverFiles.length).toBeGreaterThan(0); | ||
|
|
||
| const markers = ['code-transformer-bundler-plugins', '__codeTransformerWebpackDiagnostics']; | ||
|
|
||
| // The markers must still exist in the installed plugin build. | ||
| // If upstream renames them, this fails instead of letting the leak check below pass. | ||
| const pluginGraphSources = readOrchestrionPluginGraphSources(); | ||
| for (const marker of markers) { | ||
| expect( | ||
| pluginGraphSources.some(source => source.includes(marker)), | ||
| `marker "${marker}" is gone from the @sentry/server-utils plugin build — update the markers`, | ||
| ).toBe(true); | ||
| } | ||
|
|
||
| const leaks = serverFiles.filter(filePath => { | ||
| const content = fs.readFileSync(filePath, 'utf8'); | ||
| return markers.some(marker => content.includes(marker)); | ||
| }); | ||
|
|
||
| expect(leaks.map(filePath => path.relative(openNextDir, filePath))).toEqual([]); | ||
| }); | ||
|
|
||
| /** | ||
| * Reads the source of the installed `@sentry/server-utils` webpack plugin entry plus the files it | ||
| * requires relatively — the graph a leak would drag into the worker bundle. `createRequire` takes | ||
| * the `require` export condition, so this resolves the CJS build, whose `require('./…')` calls the | ||
| * regex below picks up. | ||
| */ | ||
| function readOrchestrionPluginGraphSources(): string[] { | ||
| const pluginEntry = createRequire(__filename).resolve('@sentry/server-utils/orchestrion/webpack'); | ||
| const entrySource = fs.readFileSync(pluginEntry, 'utf8'); | ||
| return [ | ||
| entrySource, | ||
| ...[...entrySource.matchAll(/require\('(\.\.?\/[^']+)'\)/g)].map(([, specifier]) => | ||
| fs.readFileSync(path.resolve(path.dirname(pluginEntry), specifier), 'utf8'), | ||
| ), | ||
|
s1gr1d marked this conversation as resolved.
|
||
| ]; | ||
| } | ||
|
|
||
| function collectJsFiles(dir: string): string[] { | ||
| return fs.readdirSync(dir, { withFileTypes: true }).flatMap(entry => { | ||
| const fullPath = path.join(dir, entry.name); | ||
| if (entry.isDirectory()) { | ||
| return collectJsFiles(fullPath); | ||
| } | ||
| return /\.(js|mjs|cjs)$/.test(entry.name) ? [fullPath] : []; | ||
| }); | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| import { spawnSync } from 'node:child_process'; | ||
| import { resolve } from 'node:path'; | ||
| import { describe, expect, it } from 'vitest'; | ||
|
|
||
| /** | ||
| * Importing the SDK server entry must not load the orchestrion bundler plugins. They are | ||
| * build-time-only, and their module-scope side effects break runtimes the build never sees, | ||
| * like jsdom/happy-dom test runs (issue #23789) and Cloudflare Workers cold starts (issue #22794). | ||
| * Runs in a child process for a clean module cache and real Node resolution. | ||
| */ | ||
| describe('built CJS server entry', () => { | ||
| const serverEntry = resolve(__dirname, '../build/cjs/index.server.js'); | ||
|
|
||
| it('loads under a DOM test environment without pulling in the orchestrion bundler graph', () => { | ||
| const script = ` | ||
| globalThis.document = { baseURI: 'http://localhost:3000/' }; | ||
| require(${JSON.stringify(serverEntry)}); | ||
| const toPosix = modulePath => modulePath.split(require('path').sep).join('/'); | ||
| const bundlerModules = Object.keys(require.cache).map(toPosix).filter( | ||
| modulePath => modulePath.includes('code-transformer-bundler-plugins') || modulePath.includes('orchestrion/bundler'), | ||
| ); | ||
| if (bundlerModules.length > 0) { | ||
| console.error('Bundler-plugin modules loaded at import time:\\n' + bundlerModules.join('\\n')); | ||
| process.exit(1); | ||
| } | ||
| `; | ||
|
|
||
| // On failure, stderr carries either the leaked module list or the import crash itself. | ||
| const result = spawnSync(process.execPath, ['-e', script], { encoding: 'utf8' }); | ||
| expect(result.status, result.stderr).toBe(0); | ||
| }); | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
38 changes: 38 additions & 0 deletions
38
packages/server-utils/test/orchestrion/bundlerBuildOutput.test.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| import { createRequire } from 'node:module'; | ||
| import { resolve } from 'node:path'; | ||
| import { afterEach, describe, expect, it } from 'vitest'; | ||
|
|
||
| const nodeRequire = createRequire(import.meta.url); | ||
| const BUILD_CJS_DIR = resolve(__dirname, '../../build/cjs'); | ||
|
s1gr1d marked this conversation as resolved.
|
||
|
|
||
| // The five entries share vendored chunks, and the require cache would keep a chunk's module scope | ||
| // from running again after the first test. Drop everything under `build/cjs` first, so each test | ||
| // really executes the code it claims to. | ||
| function requireFresh(entry: string): unknown { | ||
| for (const key of Object.keys(nodeRequire.cache)) { | ||
| if (key.startsWith(BUILD_CJS_DIR)) { | ||
| Reflect.deleteProperty(nodeRequire.cache, key); | ||
| } | ||
| } | ||
| return nodeRequire(resolve(BUILD_CJS_DIR, 'orchestrion/bundler', `${entry}.js`)); | ||
| } | ||
|
|
||
| /** | ||
| * The bundler entries must load in Node even when a `document` global exists, which is the case | ||
| * under jsdom/happy-dom: the vendored code must never treat `document` as proof of a browser. | ||
| * Runs against `build/cjs` because that guard lives in the emitted code, not the sources. | ||
| * Reference Issue: https://github.com/getsentry/sentry-javascript/issues/23789 | ||
| */ | ||
| describe('built CJS bundler entries load under DOM test environments', () => { | ||
| afterEach(() => { | ||
| delete (globalThis as { document?: unknown }).document; | ||
| }); | ||
|
|
||
| it.each(['webpack', 'webpack-loader', 'esbuild', 'vite', 'rollup'])( | ||
| 'build/cjs/orchestrion/bundler/%s.js loads while a `document` global is defined', | ||
| entry => { | ||
| (globalThis as { document?: unknown }).document = { baseURI: 'http://localhost:3000/' }; | ||
| expect(() => requireFresh(entry)).not.toThrow(); | ||
| }, | ||
| ); | ||
| }); | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
l: did we verify this still is the same here on v11? Just do make sure, because we changed a bunch of stuff around these I believe.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Verified on v11: both are still in the build output.
code-transformer-bundler-pluginsis the vendored directory name and appears in the require paths insidebuild/cjs/orchestrion/bundler/webpack.js__codeTransformerWebpackDiagnosticsis still in the vendored webpack pluginBut I'm going to add a check to make sure this fails when this is renamed.