Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 39 additions & 2 deletions packages/remix/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,31 @@
"import": {
"default": "./build/import-hook.mjs"
}
},
"./v3": {
"import": {
"types": "./build/types/v3/index.server.d.ts",
"default": "./build/esm/v3/index.server.js"
}
},
"./v3/client": {
"import": {
"types": "./build/types/v3/index.client.d.ts",
"default": "./build/esm/v3/index.client.js"
}
},
"./v3/node": {
"import": {
"default": "./build/v3-node.mjs"
}
}
},
"publishConfig": {
"access": "public"
},
"dependencies": {
"@remix-run/router": "^1.23.4",
"@sentry/browser": "11.0.0",
"@sentry/bundler-plugins": "11.0.0",
"@sentry/conventions": "^0.24.0",
"@sentry/core": "11.0.0",
Expand All @@ -81,7 +99,8 @@
"@remix-run/node": "2.x",
"@remix-run/react": "2.x",
"@remix-run/server-runtime": "2.x",
"react": "18.x"
"react": "18.x",
"remix": ">=3.0.0-rc.1 <4"
},
"scripts": {
"build": "run-p build:transpile build:types",
Expand Down Expand Up @@ -115,9 +134,27 @@
"{projectRoot}/build/cjs",
"{projectRoot}/build/npm/esm",
"{projectRoot}/build/npm/cjs",
"{projectRoot}/build/import-hook.mjs"
"{projectRoot}/build/import-hook.mjs",
"{projectRoot}/build/v3-node.mjs"
]
}
}
},
Comment thread
cursor[bot] marked this conversation as resolved.
"peerDependenciesMeta": {
"@remix-run/node": {
"optional": true
},
"@remix-run/react": {
"optional": true
},
"@remix-run/server-runtime": {
"optional": true
},
"react": {
"optional": true
},
"remix": {
"optional": true
}
}
}
14 changes: 14 additions & 0 deletions packages/remix/rollup.npm.config.mjs
Original file line number Diff line number Diff line change
@@ -1,10 +1,19 @@
import { defineConfig } from 'rollup';
import { makeBaseNPMConfig, makeNPMConfigVariants, makeOrchestrionLoader } from '@sentry-internal/rollup-utils';

// `external: /.*/` keeps the `remix` import a runtime resolution against the installed package.
const v3NodeEntry = defineConfig({
input: 'src/v3/node.mjs',
external: /.*/,
output: { format: 'esm', file: 'build/v3-node.mjs' },
});

// We rely on esbuild's defaults for JSX (`jsx: 'transform'` = classic runtime, no
// __self/__source attributes). React 19 prefers the new automatic transform, but switching
// to it would break React 17 support — so we intentionally stay on classic for now.
// https://legacy.reactjs.org/blog/2020/09/22/introducing-the-new-jsx-transform.html
export default [
v3NodeEntry,
...makeNPMConfigVariants(
makeBaseNPMConfig({
entrypoints: [
Expand All @@ -14,12 +23,17 @@ export default [
'src/server/index.ts',
'src/cloudflare/index.ts',
'src/vite/index.ts',
'src/v3/index.server.ts',
'src/v3/index.client.ts',
],
packageSpecificConfig: {
external: ['react-router', 'react-router-dom', 'react', 'react/jsx-runtime'],
output: {
// make it so Rollup calms down about the fact that we're combining default and named exports
exports: 'named',
// Without this, Rollup infers a common root from the whole module graph, which here reaches
// into sibling workspace packages, and the v3 entries land outside `build/esm/v3/`.
preserveModulesRoot: 'src',
},
},
}),
Expand Down
8 changes: 8 additions & 0 deletions packages/remix/src/v3/index.client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// A named list rather than `export * from '@sentry/browser'`: Remix 3 has no bundler, so a wildcard
// makes every export reachable and nothing can be tree shaken out of the served module graph.
//
// `init` is withheld until the Remix 3 browser SDK lands. Re-exporting `@sentry/browser`'s would give
// an app History based tracing, which yields no navigation spans in Remix 3, so it would look
// configured while reporting nothing.
export { captureException, captureMessage } from '@sentry/browser';
export type { BrowserOptions } from '@sentry/browser';
Comment thread
cursor[bot] marked this conversation as resolved.
4 changes: 4 additions & 0 deletions packages/remix/src/v3/index.server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// Placeholder until the Remix 3 server instrumentation lands. `@sentry/node`'s `init` already emits
// `http.server` spans, so this is useful on its own; route parameterisation and router error capture
// are what is still missing.
export * from '@sentry/node';
3 changes: 3 additions & 0 deletions packages/remix/src/v3/node.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// Replaces `--import remix/node-tsx` rather than adding a second flag. Sentry's module hook is
// registered here once the server instrumentation lands, so for now nothing is instrumented.
await import('remix/node-tsx');
66 changes: 66 additions & 0 deletions packages/remix/test/v3-exports.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import * as fs from 'node:fs';
import * as path from 'node:path';
import * as semver from 'semver';
import { describe, expect, it } from 'vitest';

import packageJson from '../package.json';

const packageRoot = path.resolve(__dirname, '..');

// `exports` mixes shapes (`"./package.json"` is a bare string), so it does not fit one index signature.
const exportsMap = packageJson.exports as unknown as Record<string, { import?: Record<string, string> }>;

/**
* A mismatch between the `exports` map and the build output is invisible: the build and lint both
* pass, and the package is broken only for whoever installs it. Rollup infers its output layout from
* the module graph, so an unrelated change can relocate these files.
*/
describe('Remix 3 exports', () => {
const subpaths = ['./v3', './v3/client', './v3/node'];

it.each(subpaths)('%s is declared in the exports map', subpath => {
expect(packageJson.exports).toHaveProperty([subpath]);
});

it.each(subpaths)('%s only exposes an import condition', subpath => {
// Remix 3's asset server rejects CommonJS outright, so a `require` condition could never load.
expect(Object.keys(exportsMap[subpath] ?? {})).toEqual(['import']);
});

it.each(subpaths)('%s points at files that exist in the build output', subpath => {
const targets = Object.values(exportsMap[subpath]?.import ?? {});

expect(targets.length).toBeGreaterThan(0);

for (const target of targets) {
expect(fs.existsSync(path.join(packageRoot, target)), `${subpath} -> ${target} is missing`).toBe(true);
}
});

describe('the Remix 3 peer range', () => {
const range = packageJson.peerDependencies.remix;

// Asserted by behaviour, so the range can change after Remix 3 reaches GA without failing here.
it('accepts release candidates', () => {
// `3.x` does not match `3.0.0-rc.1`: semver ranges exclude prereleases unless they name one.
expect(semver.satisfies('3.0.0-rc.1', range)).toBe(true);
expect(semver.satisfies('3.0.0-rc.2', range)).toBe(true);
});

it('accepts stable 3.x', () => {
expect(semver.satisfies('3.0.0', range)).toBe(true);
expect(semver.satisfies('3.4.2', range)).toBe(true);
});

it('rejects the next major', () => {
expect(semver.satisfies('4.0.0', range)).toBe(false);
});
});

it('marks every peer dependency optional', () => {
// A Remix 3 app must not have the Remix 2 packages or React installed on its behalf.
for (const name of Object.keys(packageJson.peerDependencies)) {
expect(packageJson.peerDependenciesMeta, `${name} should be optional`).toHaveProperty([name, 'optional'], true);
}
});
});
Loading