[APPS] Add vite dev server middleware for backend functions#291
[APPS] Add vite dev server middleware for backend functions#291sdkennedy2 wants to merge 2 commits intosdkennedy2/apps-vite-backend-buildfrom
Conversation
|
Warning This pull request is not mergeable via GitHub because a downstack PR is open. Once all requirements are satisfied, merge this PR as a stack on Graphite.
This stack of pull requests is managed by Graphite. Learn more about stacking. |
a1630ef to
4f85ed9
Compare
264286b to
2f9f167
Compare
7e20d87 to
e465412
Compare
2f9f167 to
d682d52
Compare
sarenji
left a comment
There was a problem hiding this comment.
Some initial thoughts on this
| // Dynamic import — vite is guaranteed available when bundler is vite. | ||
| // Use a variable to prevent Rollup from statically analyzing and bundling vite. |
There was a problem hiding this comment.
Could we do dependency injection somehow, by passing a vite build function to this middleware, avoiding the hassle of importing Vite within createDevServerMiddleware?
We could also externalize vite if necessary.
| log: Logger, | ||
| ): Promise<unknown> { | ||
| const endpoint = `https://${auth.site}/api/v2/app-builder/queries/execution-long-polling/${receiptId}`; | ||
| // Each long-poll request waits server-side (~30s). Max retries provides a safety net. |
There was a problem hiding this comment.
Consider using exponential backoff, plus a jitter, for cases where the API has gone down and is trying to get back up
I also see async-retry usage here, and maybe doRequest in general is a method we can reuse.
| const res = createMockResponse(); | ||
|
|
||
| middleware(req, res, jest.fn()); | ||
| await new Promise((resolve) => setTimeout(resolve, 100)); |
There was a problem hiding this comment.
This seems like a potential source of flakiness and test slowdown. Can we be more exact on what we are waiting for?
| const code = await bundleBackendFunction(func, args, projectRoot, log); | ||
|
|
||
| res.statusCode = 200; | ||
| res.setHeader('Content-Type', 'text/plain'); | ||
| res.end(code); |
There was a problem hiding this comment.
Fundamentally I do not understand this because Connect middleware is not async so it's confusing to me how this is working.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
95203d8 to
a0932f3
Compare

Motivation
Developers building high code apps with backend functions need a way to test those functions locally during
vite devwithout deploying. This PR adds a Vite dev server middleware that bundles and executes backend functions on-the-fly, using the samevite.build()approach established in #292.Changes
Added a Vite dev server middleware that registers via
configureServerand intercepts two endpoints:POST /__dd/debugBundle— returns the bundled script for inspectionPOST /__dd/executeAction— bundles the function, sends it to Datadog's query preview-async API, polls for the result, and returns itWhy
vite.build()instead of the dev server's transform pipeline?Vite's dev server (
transformRequest,ssrLoadModule) is designed for serving individual modules on-demand — it doesn't produce bundled output. But Datadog's execution API needs a complete, self-contained script with all dependencies resolved and inlined into one file.vite.build({ write: false })runs a mini Rollup build in memory, producing exactly that.Why the hacky
import('vite')?The dev server middleware needs to call
vite.build(), but we can't addviteas a dependency of the apps plugin. The apps plugin is bundler-agnostic — it's compiled into@datadog/webpack-plugin,@datadog/esbuild-plugin, etc. Addingviteas a dependency would force every bundler's published package to pull in vite.Instead, we use a variable indirection to make the import opaque to Rollup's static analysis:
This is safe because: the code only runs inside
configureServer, which is a Vite-only hook — vite is guaranteed to be installed.Key implementation details:
configFile: falseprevents recursion (user's vite.config.ts includes the Datadog plugin)treeshake: false+inlineDynamicImports: truekeeps action-catalog bridges and argument passing fully intactgenerateDevVirtualEntryContent()inlines args as JSON (vs the production template expression${backendFunctionArgs})New files:
backend/vite/dev-server.ts— middleware, Vite-based bundling, Datadog API integrationbackend/vite/dev-server.test.ts— unit testsModified files:
backend/virtual-entry.ts— addedgenerateDevVirtualEntryContent()for dev modebackend/vite/index.ts— registers middleware viaconfigureServerhookbackend/index.ts— addedauthtoBackendPluginContextsrc/index.ts— passes auth credentials to the backend pluginQA Instructions
backend/directory containing a functionyarn devin build-plugins, then start the app:NODE_TLS_REJECT_UNAUTHORIZED=0 dd-auth --domain="dd.datad0g.com" --actions-api -- npx vitecurl -X POST http://localhost:5173/__dd/debugBundle -H "Content-Type: application/json" -d '{"functionName": "yourFunction", "args": []}'curl -X POST http://localhost:5173/__dd/executeAction -H "Content-Type: application/json" -d '{"functionName": "yourFunction", "args": ["test"]}'Blast Radius
apps.backendDiris configured and backend functions are discoveredDocumentation