diff --git a/e2e/examples.json b/e2e/examples.json index 37d51d692..df36e71d2 100644 --- a/e2e/examples.json +++ b/e2e/examples.json @@ -194,8 +194,7 @@ "serveCmd": "preview", "profile": "standard", "mountPath": "/", - "status": "broken", - "brokenReason": "Dynamic SDK reads a bare `process` global (nextTick/versions/emit) and imports `buffer/index.js`, so it needs Node shims. vite-plugin-node-polyfills declares Vite 8 support but is broken under its rolldown resolver: it aliases its own shims by bare specifier, and its exports map still has legacy trailing-slash keys pointing at files, which rolldown rejects (\"Expecting folder to folder mapping\"). Fails on 0.26.0 and 0.28.0 alike, in build and in dep pre-bundling. Blocked on upstream davidmyersdev/vite-plugin-node-polyfills#158, #161 and #154. The @dynamic-labs v5 upgrade itself is fine — it type-checks and bundles." + "status": "active" }, { "name": "nuxt", diff --git a/e2e/tests/profiles/widget-smoke.spec.ts b/e2e/tests/profiles/widget-smoke.spec.ts index d2f67b4e9..e95763cd9 100644 --- a/e2e/tests/profiles/widget-smoke.spec.ts +++ b/e2e/tests/profiles/widget-smoke.spec.ts @@ -5,8 +5,9 @@ import { expect, test, waitForTokens } from '../fixtures/base.fixture.js' * Widget smoke profile — covers standard (widget at /) and routed (widget at custom path). * mountPath is read from project metadata so the same spec handles both profiles. * - * Standard (13): vite, connectkit, privy, privy-ethers, rainbowkit, reown, svelte, - * zustand-widget-config, vue, nextjs, nextjs15, remix, react-router + * Standard (14): vite, connectkit, privy, privy-ethers, rainbowkit, reown, svelte, + * zustand-widget-config, vue, nextjs, nextjs15, remix, react-router, + * dynamic * Routed (1): tanstack-router (mountPath: /widget) */ diff --git a/examples/dynamic/package.json b/examples/dynamic/package.json index af02f4279..69193cc32 100644 --- a/examples/dynamic/package.json +++ b/examples/dynamic/package.json @@ -36,7 +36,6 @@ "react": "^19.2.8", "react-dom": "^19.2.8", "viem": ">=2.52.0", - "vite-plugin-env-compatible": "^2.0.1", "wagmi": "^3.7.6" }, "devDependencies": { @@ -45,6 +44,7 @@ "@vitejs/plugin-react": "^6.0.5", "globals": "^17.11.0", "typescript": "^7.0.2", - "vite": "^8.2.1" + "vite": "^8.2.1", + "vite-plugin-node-polyfills": "^0.26.0" } } diff --git a/examples/dynamic/src/components/WalletHeader.tsx b/examples/dynamic/src/components/WalletHeader.tsx index f1e81f044..e03229d4e 100644 --- a/examples/dynamic/src/components/WalletHeader.tsx +++ b/examples/dynamic/src/components/WalletHeader.tsx @@ -4,17 +4,19 @@ import { Box, Typography } from '@mui/material' export function WalletHeader() { return ( - + Dynamic + LI.FI widget Example - + diff --git a/examples/dynamic/vite.config.ts b/examples/dynamic/vite.config.ts index 8aad5587f..a3e3bad07 100644 --- a/examples/dynamic/vite.config.ts +++ b/examples/dynamic/vite.config.ts @@ -1,10 +1,93 @@ +import { createRequire } from 'node:module' +import { dirname, join, resolve } from 'node:path' import react from '@vitejs/plugin-react' -import { defineConfig } from 'vite' -import EnvCompatible from 'vite-plugin-env-compatible' +import { defineConfig, type Plugin } from 'vite' +import { nodePolyfills } from 'vite-plugin-node-polyfills' + +// The Dynamic SDK reads a bare `process` global (nextTick/versions/emit) and its own +// polyfills.js imports `buffer/index.js`, so it needs real Node shims — build-time +// `process.env.*` substitution is not enough. +// +// nodePolyfills() supplies those shims, but it points at them by bare specifier, and +// its exports map still carries legacy trailing-slash keys that resolve to files. +// Rolldown (Vite 8) rejects that with "Expecting folder to folder mapping", which +// breaks both `vite build` and dev dependency pre-bundling. 0.28.0 ships an identical +// exports map and fails the same way, so bumping the plugin does not help — the +// upstream fixes are open but unreleased (davidmyersdev/vite-plugin-node-polyfills#161 +// and #154). +// +// So we resolve the shims to their real files, which keeps rolldown out of the exports +// map. Two places need it, because pre-bundling runs its own resolver: +// - the module graph, via resolveId (also covers subpaths like `buffer/index.js`) +// - the plugin's own returned config, patched before Vite merges it, which is where +// the pre-bundle alias maps and injected banner come from +// Both become deletable once upstream ships. +const require = createRequire(import.meta.url) +const pkgRoot = resolve( + dirname(require.resolve('vite-plugin-node-polyfills')), + '..' +) +const SHIM_PREFIX = 'vite-plugin-node-polyfills/shims/' +const SHIM_RE = + /^(?:node:)?(?:vite-plugin-node-polyfills\/shims\/)?(buffer|global|process)(?:\/.*)?$/ +const QUOTED_SHIM_RE = + /(['"])vite-plugin-node-polyfills\/shims\/(buffer|global|process)\1/g +// Alias values point at the shim *directory*: aliases substitute by prefix, so a +// directory keeps both `buffer` and `buffer/index.js` resolvable. Import specifiers +// (the injected banner) get the file itself. +const shimDir = (name: string) => join(pkgRoot, 'shims', name, 'dist') +const shimPath = (name: string) => join(shimDir(name), 'index.js') + +/** Rewrite every bare shim specifier in a value tree to an absolute path. */ +function absolutizeShims(value: T): T { + if (typeof value === 'string') { + const exact = value.startsWith(SHIM_PREFIX) + ? shimDir(value.slice(SHIM_PREFIX.length)) + : value.replace( + QUOTED_SHIM_RE, + (_, quote: string, name: string) => + `${quote}${shimPath(name)}${quote}` + ) + return exact as unknown as T + } + if (Array.isArray(value)) return value.map(absolutizeShims) as unknown as T + if (value && typeof value === 'object') { + for (const [key, inner] of Object.entries(value)) { + ;(value as Record)[key] = absolutizeShims(inner) + } + } + return value +} + +/** nodePolyfills() with every bare shim reference resolved to a real file. */ +function nodePolyfillsResolved(): Plugin[] { + const plugins = [nodePolyfills()].flat() as Plugin[] + for (const plugin of plugins) { + const original = plugin.config + if (typeof original !== 'function') continue + plugin.config = async function config( + this: ThisParameterType, + ...args: Parameters + ) { + return absolutizeShims(await original.apply(this, args)) + } + } + return [ + { + name: 'resolve-node-polyfill-shims', + enforce: 'pre', + resolveId(id) { + const match = SHIM_RE.exec(id) + return match ? shimPath(match[1]) : null + }, + }, + ...plugins, + ] +} // https://vitejs.dev/config/ export default defineConfig({ - plugins: [react(), EnvCompatible()], + plugins: [react(), nodePolyfillsResolved()], server: { port: 3000, open: true, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e78b7148a..658186658 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -302,9 +302,6 @@ importers: viem: specifier: '>=2.52.0' version: 2.55.19(bufferutil@4.1.0)(typescript@7.0.2)(utf-8-validate@6.0.6)(zod@4.4.3) - vite-plugin-env-compatible: - specifier: ^2.0.1 - version: 2.0.1 wagmi: specifier: ^3.7.6 version: 3.7.6(5b3fe36345e22f8c086849e354cafa04) @@ -327,6 +324,9 @@ importers: vite: specifier: ^8.2.1 version: 8.2.1(@types/node@26.2.0)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.50.0)(yaml@2.9.0) + vite-plugin-node-polyfills: + specifier: ^0.26.0 + version: 0.26.0(rollup@4.62.4)(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.50.0)(yaml@2.9.0)) examples/nextjs: dependencies: @@ -14934,18 +14934,6 @@ packages: wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - ws@7.5.13: - resolution: {integrity: sha512-rsKI6xDBFVf4r/x8XyChGK04QR/XHroxs/jUcoWvtEZM8TPU/X/uIY9B1CsSzYws9ZJb/6bbBu7dPhFW00CAoA==} - engines: {node: '>=8.3.0'} - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: 6.0.6 - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - ws@8.21.3: resolution: {integrity: sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==} engines: {node: '>=10.0.0'} @@ -19706,7 +19694,7 @@ snapshots: tsconfig-paths: 4.2.0 valibot: 1.4.2(typescript@7.0.2) vite-node: 3.2.4(@types/node@26.2.0)(jiti@2.7.0)(lightningcss@1.33.0)(supports-color@10.2.2)(terser@5.50.0)(yaml@2.9.0) - ws: 7.5.13(bufferutil@4.1.0)(utf-8-validate@6.0.6) + ws: 8.21.3(bufferutil@4.1.0)(utf-8-validate@6.0.6) optionalDependencies: '@remix-run/serve': 2.17.5(supports-color@10.2.2)(typescript@7.0.2) typescript: 7.0.2 @@ -28089,9 +28077,9 @@ snapshots: isomorphic-timers-promises@1.0.1: {} - isomorphic-ws@4.0.1(ws@7.5.13(bufferutil@4.1.0)(utf-8-validate@6.0.6)): + isomorphic-ws@4.0.1(ws@8.21.3(bufferutil@4.1.0)(utf-8-validate@6.0.6)): dependencies: - ws: 7.5.13(bufferutil@4.1.0)(utf-8-validate@6.0.6) + ws: 8.21.3(bufferutil@4.1.0)(utf-8-validate@6.0.6) isows@1.0.7(ws@8.21.3(bufferutil@4.1.0)(utf-8-validate@6.0.6)): dependencies: @@ -28120,11 +28108,11 @@ snapshots: delay: 5.0.0 es6-promisify: 5.0.0 eyes: 0.1.8 - isomorphic-ws: 4.0.1(ws@7.5.13(bufferutil@4.1.0)(utf-8-validate@6.0.6)) + isomorphic-ws: 4.0.1(ws@8.21.3(bufferutil@4.1.0)(utf-8-validate@6.0.6)) json-stringify-safe: 5.0.1 stream-json: 1.9.1 uuid: 8.3.2 - ws: 7.5.13(bufferutil@4.1.0)(utf-8-validate@6.0.6) + ws: 8.21.3(bufferutil@4.1.0)(utf-8-validate@6.0.6) transitivePeerDependencies: - bufferutil - utf-8-validate @@ -33167,11 +33155,6 @@ snapshots: wrappy@1.0.2: {} - ws@7.5.13(bufferutil@4.1.0)(utf-8-validate@6.0.6): - optionalDependencies: - bufferutil: 4.1.0 - utf-8-validate: 6.0.6 - ws@8.21.3(bufferutil@4.1.0)(utf-8-validate@6.0.6): optionalDependencies: bufferutil: 4.1.0