Skip to content

Commit 8a8ebf4

Browse files
committed
Split apps runtime into dev and prod and inject based on Vite command
1 parent 57382e6 commit 8a8ebf4

10 files changed

Lines changed: 99 additions & 123 deletions

File tree

packages/plugins/apps/package.json

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,14 @@
1515
"hideFromRootReadme": true
1616
},
1717
"toBuild": {
18-
"apps-runtime": {
19-
"entry": "./src/built/apps-runtime.ts",
18+
"apps-runtime-dev": {
19+
"entry": "./src/built/apps-runtime-dev.ts",
20+
"format": [
21+
"esm"
22+
]
23+
},
24+
"apps-runtime-prod": {
25+
"entry": "./src/built/apps-runtime-prod.ts",
2026
"format": [
2127
"esm"
2228
]

packages/plugins/apps/src/backend/client/execute-backend-function.ts

Lines changed: 0 additions & 54 deletions
This file was deleted.

packages/plugins/apps/src/backend/client/execute-backend-function.test.ts renamed to packages/plugins/apps/src/backend/client/transports/dev-server-transport.test.ts

Lines changed: 14 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,11 @@
99
// lives with the original tests in web-ui's @datadog/apps-function-query
1010
// until a DOM-enabled harness is introduced.
1111

12-
import { executeBackendFunction } from './execute-backend-function';
13-
import { BackendFunctionError } from './types';
12+
import { BackendFunctionError } from '../types';
1413

15-
describe('executeBackendFunction', () => {
14+
import { devServerTransport } from './dev-server-transport';
15+
16+
describe('devServerTransport', () => {
1617
let originalFetch: typeof fetch;
1718

1819
beforeEach(() => {
@@ -30,7 +31,7 @@ describe('executeBackendFunction', () => {
3031
json: async () => mockResponse,
3132
});
3233

33-
const result = await executeBackendFunction<{ sum: number }>('testWithImport', [5, 7]);
34+
const result = await devServerTransport<{ sum: number }>('testWithImport', [5, 7]);
3435

3536
expect(result).toEqual({ sum: 12 });
3637
expect(global.fetch).toHaveBeenCalledWith('/__dd/executeAction', {
@@ -48,11 +49,9 @@ describe('executeBackendFunction', () => {
4849
it('should throw BackendFunctionError on network error', async () => {
4950
global.fetch = jest.fn().mockRejectedValue(new Error('Network failed'));
5051

51-
await expect(executeBackendFunction('testFunction', [])).rejects.toThrow(
52-
BackendFunctionError,
53-
);
52+
await expect(devServerTransport('testFunction', [])).rejects.toThrow(BackendFunctionError);
5453

55-
await expect(executeBackendFunction('testFunction', [])).rejects.toThrow(
54+
await expect(devServerTransport('testFunction', [])).rejects.toThrow(
5655
'Network error while executing backend function "testFunction"',
5756
);
5857
});
@@ -64,11 +63,9 @@ describe('executeBackendFunction', () => {
6463
text: async () => 'Internal Server Error',
6564
});
6665

67-
await expect(executeBackendFunction('testFunction', [])).rejects.toThrow(
68-
BackendFunctionError,
69-
);
66+
await expect(devServerTransport('testFunction', [])).rejects.toThrow(BackendFunctionError);
7067

71-
await expect(executeBackendFunction('testFunction', [])).rejects.toThrow(
68+
await expect(devServerTransport('testFunction', [])).rejects.toThrow(
7269
'Backend function "testFunction" failed with status 500',
7370
);
7471
});
@@ -83,11 +80,9 @@ describe('executeBackendFunction', () => {
8380
json: async () => mockResponse,
8481
});
8582

86-
await expect(executeBackendFunction('testFunction', [])).rejects.toThrow(
87-
BackendFunctionError,
88-
);
83+
await expect(devServerTransport('testFunction', [])).rejects.toThrow(BackendFunctionError);
8984

90-
await expect(executeBackendFunction('testFunction', [])).rejects.toThrow(
85+
await expect(devServerTransport('testFunction', [])).rejects.toThrow(
9186
'Backend function "testFunction" returned an error',
9287
);
9388
});
@@ -101,11 +96,9 @@ describe('executeBackendFunction', () => {
10196
},
10297
});
10398

104-
await expect(executeBackendFunction('testFunction', [])).rejects.toThrow(
105-
BackendFunctionError,
106-
);
99+
await expect(devServerTransport('testFunction', [])).rejects.toThrow(BackendFunctionError);
107100

108-
await expect(executeBackendFunction('testFunction', [])).rejects.toThrow(
101+
await expect(devServerTransport('testFunction', [])).rejects.toThrow(
109102
'Failed to parse response from backend function',
110103
);
111104
});
@@ -117,7 +110,7 @@ describe('executeBackendFunction', () => {
117110
text: async () => 'Not Found',
118111
});
119112

120-
await expect(executeBackendFunction('testFunction', [])).rejects.toMatchObject({
113+
await expect(devServerTransport('testFunction', [])).rejects.toMatchObject({
121114
name: 'BackendFunctionError',
122115
functionName: 'testFunction',
123116
statusCode: 404,
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// Unless explicitly stated otherwise all files in this repository are licensed under the MIT License.
2+
// This product includes software developed at Datadog (https://www.datadoghq.com/).
3+
// Copyright 2019-Present Datadog, Inc.
4+
5+
/* eslint-env browser */
6+
/* global globalThis */
7+
import { devServerTransport } from '../backend/client/transports/dev-server-transport';
8+
9+
const globalAny: any = globalThis;
10+
globalAny.DD_APPS_RUNTIME = { executeBackendFunction: devServerTransport };
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// Unless explicitly stated otherwise all files in this repository are licensed under the MIT License.
2+
// This product includes software developed at Datadog (https://www.datadoghq.com/).
3+
// Copyright 2019-Present Datadog, Inc.
4+
5+
/* eslint-env browser */
6+
/* global globalThis */
7+
import { postMessageTransport } from '../backend/client/transports/post-message-transport/post-message-transport';
8+
9+
const globalAny: any = globalThis;
10+
globalAny.DD_APPS_RUNTIME = { executeBackendFunction: postMessageTransport };

packages/plugins/apps/src/built/apps-runtime.ts

Lines changed: 0 additions & 14 deletions
This file was deleted.

packages/plugins/apps/src/index.test.ts

Lines changed: 31 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -59,21 +59,37 @@ describe('Apps Plugin - getPlugins', () => {
5959
expect(getPlugins(getArgs())).toHaveLength(1);
6060
});
6161

62-
test('Should inject the apps runtime at the top of the user bundle when enabled', () => {
63-
const injectMock = jest.fn();
64-
getPlugins(
65-
getGetPluginsArg(
66-
{ apps: {} },
67-
{ bundler: { ...getMockBundler({ name: 'vite' }), outDir }, inject: injectMock },
68-
),
69-
);
70-
71-
expect(injectMock).toHaveBeenCalledWith({
72-
type: 'file',
73-
position: InjectPosition.MIDDLE,
74-
value: expect.stringContaining('apps-runtime.mjs'),
75-
});
76-
});
62+
const runtimeInjectionCases = [
63+
{ command: 'serve' as const, runtime: 'apps-runtime-dev.mjs' },
64+
{ command: 'build' as const, runtime: 'apps-runtime-prod.mjs' },
65+
];
66+
67+
test.each(runtimeInjectionCases)(
68+
'Should inject the $runtime runtime when vite command is "$command"',
69+
({ command, runtime }) => {
70+
const injectMock = jest.fn();
71+
const plugins = getPlugins(
72+
getGetPluginsArg(
73+
{ apps: {} },
74+
{
75+
bundler: { ...getMockBundler({ name: 'vite' }), outDir },
76+
inject: injectMock,
77+
},
78+
),
79+
);
80+
const configHook = plugins[0].vite!.config as (
81+
userConfig: object,
82+
env: { command: 'serve' | 'build' },
83+
) => void;
84+
configHook({}, { command });
85+
86+
expect(injectMock).toHaveBeenCalledWith({
87+
type: 'file',
88+
position: InjectPosition.MIDDLE,
89+
value: expect.stringContaining(runtime),
90+
});
91+
},
92+
);
7793

7894
test('Should not inject the runtime when disabled', () => {
7995
const injectMock = jest.fn();

packages/plugins/apps/src/index.ts

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44

55
import { rm } from '@dd/core/helpers/fs';
66
import type { GetPlugins } from '@dd/core/types';
7-
import { InjectPosition } from '@dd/core/types';
87
import chalk from 'chalk';
98
import path from 'path';
109

@@ -89,21 +88,6 @@ export const getPlugins: GetPlugins = ({ options, context, bundler }) => {
8988
return [];
9089
}
9190

92-
// Inject the runtime that `globalThis.DD_APPS_RUNTIME.executeBackendFunction`
93-
// is read from. The generated proxy modules (emitted by the transform hook
94-
// below) reference that global. NOTE: This file is built alongside the
95-
// bundler plugin via the `toBuild` entry in @dd/apps-plugin's package.json.
96-
//
97-
// Position MIDDLE is used instead of BEFORE so Vite's dev server injects
98-
// the runtime as a <script type="module"> via `transformIndexHtml` — BEFORE
99-
// is served via Rollup's `banner()` output hook which only fires at build
100-
// time, leaving the runtime undefined during `vite` (dev).
101-
context.inject({
102-
type: 'file',
103-
position: InjectPosition.MIDDLE,
104-
value: path.join(__dirname, './apps-runtime.mjs'),
105-
});
106-
10791
const { setBackendFunctions, getBackendFunctions } = createBackendFunctionRegistry();
10892

10993
const handleUpload = async (backendOutputs: Map<string, string>) => {
@@ -257,6 +241,8 @@ Either:
257241
handleUpload,
258242
log,
259243
auth: context.auth,
244+
inject: context.inject,
245+
pluginDir: __dirname,
260246
}),
261247
},
262248
];

packages/plugins/apps/src/vite/index.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ const defaultOptions = {
4141
handleUpload: mockHandleUpload,
4242
log,
4343
auth: { site: 'datadoghq.com' },
44+
inject: jest.fn(),
45+
pluginDir: '/plugin',
4446
};
4547

4648
describe('Backend Functions - getVitePlugin', () => {

packages/plugins/apps/src/vite/index.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
// Copyright 2019-Present Datadog, Inc.
44

55
import { rm } from '@dd/core/helpers/fs';
6-
import type { AuthOptionsWithDefaults, Logger, PluginOptions } from '@dd/core/types';
6+
import type { AuthOptionsWithDefaults, GlobalContext, Logger, PluginOptions } from '@dd/core/types';
7+
import { InjectPosition } from '@dd/core/types';
8+
import path from 'path';
79
import type { build } from 'vite';
810

911
import type { BackendFunction } from '../backend/discovery';
@@ -18,11 +20,17 @@ export interface VitePluginOptions {
1820
handleUpload: (backendOutputs: Map<string, string>) => Promise<void>;
1921
log: Logger;
2022
auth: AuthOptionsWithDefaults;
23+
inject: GlobalContext['inject'];
24+
pluginDir: string;
2125
}
2226

2327
/**
2428
* Returns the Vite-specific plugin hooks for the apps plugin.
2529
*
30+
* Config: injects either the dev-server or postMessage runtime depending on
31+
* whether Vite is running in `serve` (dev) or `build` (production) mode, so
32+
* each bundle ships only the transport it needs.
33+
*
2634
* Production (closeBundle): builds backend functions (if any) then uploads
2735
* all assets sequentially.
2836
*
@@ -36,7 +44,20 @@ export const getVitePlugin = ({
3644
handleUpload,
3745
log,
3846
auth,
47+
inject,
48+
pluginDir,
3949
}: VitePluginOptions): PluginOptions['vite'] => ({
50+
config(_userConfig, { command }) {
51+
// Position MIDDLE so the runtime is injected via Vite's
52+
// `transformIndexHtml` in dev — BEFORE goes through Rollup's
53+
// `banner()` which only fires at build time.
54+
const runtime = command === 'serve' ? 'apps-runtime-dev.mjs' : 'apps-runtime-prod.mjs';
55+
inject({
56+
type: 'file',
57+
position: InjectPosition.MIDDLE,
58+
value: path.join(pluginDir, runtime),
59+
});
60+
},
4061
async closeBundle() {
4162
let backendOutDir: string | undefined;
4263
let backendOutputs = new Map<string, string>();

0 commit comments

Comments
 (0)