@reticlehq/vite-plugin 3.1.0 → 3.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -0
- package/dist/connect-args.d.ts +10 -0
- package/dist/connect-args.js +71 -0
- package/dist/disabled-browser-stub.d.ts +3 -0
- package/dist/disabled-browser-stub.js +185 -0
- package/dist/index.cjs +467 -161
- package/dist/index.d.cts +22 -11
- package/dist/index.d.ts +22 -11
- package/dist/index.js +33 -107
- package/dist/stamping.d.ts +36 -0
- package/dist/stamping.js +99 -0
- package/package.json +4 -4
package/dist/index.d.cts
CHANGED
|
@@ -72,9 +72,8 @@ export interface ReticleVitePluginOptions {
|
|
|
72
72
|
* This build is an Electron/Tauri renderer. Changes two things a desktop shell needs and a web app
|
|
73
73
|
* must not get:
|
|
74
74
|
*
|
|
75
|
-
* -
|
|
76
|
-
*
|
|
77
|
-
* `apply: 'serve'` drops the plugin entirely and the app ships with no `connect()` at all.
|
|
75
|
+
* - A packaged desktop renderer IS a production build with no dev server, so it needs the real
|
|
76
|
+
* `connect()` wiring during `vite build` — the web default's stub-swap must not apply to it.
|
|
78
77
|
* - `connect()` is called with `allowInProduction`, because that same renderer reports
|
|
79
78
|
* NODE_ENV=production and the SDK's prod backstop would otherwise refuse to start.
|
|
80
79
|
*
|
|
@@ -95,6 +94,20 @@ export interface ReticleVitePluginOptions {
|
|
|
95
94
|
* Also settable as `VITE_RETICLE_CAPTURE_BODIES=1`.
|
|
96
95
|
*/
|
|
97
96
|
captureNetworkBodies?: boolean;
|
|
97
|
+
/**
|
|
98
|
+
* Per-body character cap for captured bodies. Default 8192; clamped to [256, 262144].
|
|
99
|
+
*
|
|
100
|
+
* Reachable here for the reason `captureNetworkBodies` is: the plugin is the only `connect()`
|
|
101
|
+
* most apps ever have, so an SDK option the plugin cannot pass is an option that does not exist.
|
|
102
|
+
*
|
|
103
|
+
* Raise it to make a NEGATIVE `bodyContains` decidable -- a negation is checked over the whole
|
|
104
|
+
* payload, so on a list endpoint bigger than the cap it is permanently undecidable, and that is
|
|
105
|
+
* the class that proves "this dangerous field is absent from every row" (#799).
|
|
106
|
+
*
|
|
107
|
+
* Also settable as `VITE_RETICLE_BODY_MAX_CHARS=65536`, so one run can raise it without editing
|
|
108
|
+
* vite.config.
|
|
109
|
+
*/
|
|
110
|
+
networkBodyMaxChars?: number;
|
|
98
111
|
/**
|
|
99
112
|
* Retain a FAILED request's response body even with `captureNetworkBodies` off. Default true.
|
|
100
113
|
*
|
|
@@ -195,8 +208,6 @@ export interface ReticleVitePlugin {
|
|
|
195
208
|
};
|
|
196
209
|
};
|
|
197
210
|
};
|
|
198
|
-
/** Absent in desktop mode, where the plugin must also run for `vite build`. */
|
|
199
|
-
apply?: 'serve';
|
|
200
211
|
enforce: 'pre';
|
|
201
212
|
transform: (code: string, id: string) => {
|
|
202
213
|
code: string;
|
|
@@ -313,14 +324,14 @@ export declare function connectModuleSource(options: ReticleVitePluginOptions, d
|
|
|
313
324
|
* import { reticle } from '@reticlehq/vite-plugin';
|
|
314
325
|
* export default defineConfig({ plugins: [react(), reticle()] });
|
|
315
326
|
*
|
|
316
|
-
*
|
|
317
|
-
* bundle
|
|
327
|
+
* The web plugin also builds, replacing the browser SDK with an inert stub so a default
|
|
328
|
+
* production bundle ships no Reticle runtime code at all.
|
|
318
329
|
*
|
|
319
330
|
* `desktop: true` is the ONE documented exception, and it inverts that guarantee deliberately: a
|
|
320
|
-
* packaged Electron/Tauri renderer IS a production build with no dev server, so
|
|
321
|
-
* would ship an app with no connect() at all. The cost is that the flag hands
|
|
322
|
-
* caller — keep it behind your own dev-only build target so an instrumented
|
|
323
|
-
* a release binary.
|
|
331
|
+
* packaged Electron/Tauri renderer IS a production build with no dev server, so the web default's
|
|
332
|
+
* stub-replacement would ship an app with no connect() at all. The cost is that the flag hands
|
|
333
|
+
* gating back to the caller — keep it behind your own dev-only build target so an instrumented
|
|
334
|
+
* bundle can never reach a release binary.
|
|
324
335
|
*/
|
|
325
336
|
/**
|
|
326
337
|
* The daemon's journal directory, as a matcher every chokidar major honours.
|
package/dist/index.d.ts
CHANGED
|
@@ -72,9 +72,8 @@ export interface ReticleVitePluginOptions {
|
|
|
72
72
|
* This build is an Electron/Tauri renderer. Changes two things a desktop shell needs and a web app
|
|
73
73
|
* must not get:
|
|
74
74
|
*
|
|
75
|
-
* -
|
|
76
|
-
*
|
|
77
|
-
* `apply: 'serve'` drops the plugin entirely and the app ships with no `connect()` at all.
|
|
75
|
+
* - A packaged desktop renderer IS a production build with no dev server, so it needs the real
|
|
76
|
+
* `connect()` wiring during `vite build` — the web default's stub-swap must not apply to it.
|
|
78
77
|
* - `connect()` is called with `allowInProduction`, because that same renderer reports
|
|
79
78
|
* NODE_ENV=production and the SDK's prod backstop would otherwise refuse to start.
|
|
80
79
|
*
|
|
@@ -95,6 +94,20 @@ export interface ReticleVitePluginOptions {
|
|
|
95
94
|
* Also settable as `VITE_RETICLE_CAPTURE_BODIES=1`.
|
|
96
95
|
*/
|
|
97
96
|
captureNetworkBodies?: boolean;
|
|
97
|
+
/**
|
|
98
|
+
* Per-body character cap for captured bodies. Default 8192; clamped to [256, 262144].
|
|
99
|
+
*
|
|
100
|
+
* Reachable here for the reason `captureNetworkBodies` is: the plugin is the only `connect()`
|
|
101
|
+
* most apps ever have, so an SDK option the plugin cannot pass is an option that does not exist.
|
|
102
|
+
*
|
|
103
|
+
* Raise it to make a NEGATIVE `bodyContains` decidable -- a negation is checked over the whole
|
|
104
|
+
* payload, so on a list endpoint bigger than the cap it is permanently undecidable, and that is
|
|
105
|
+
* the class that proves "this dangerous field is absent from every row" (#799).
|
|
106
|
+
*
|
|
107
|
+
* Also settable as `VITE_RETICLE_BODY_MAX_CHARS=65536`, so one run can raise it without editing
|
|
108
|
+
* vite.config.
|
|
109
|
+
*/
|
|
110
|
+
networkBodyMaxChars?: number;
|
|
98
111
|
/**
|
|
99
112
|
* Retain a FAILED request's response body even with `captureNetworkBodies` off. Default true.
|
|
100
113
|
*
|
|
@@ -195,8 +208,6 @@ export interface ReticleVitePlugin {
|
|
|
195
208
|
};
|
|
196
209
|
};
|
|
197
210
|
};
|
|
198
|
-
/** Absent in desktop mode, where the plugin must also run for `vite build`. */
|
|
199
|
-
apply?: 'serve';
|
|
200
211
|
enforce: 'pre';
|
|
201
212
|
transform: (code: string, id: string) => {
|
|
202
213
|
code: string;
|
|
@@ -313,14 +324,14 @@ export declare function connectModuleSource(options: ReticleVitePluginOptions, d
|
|
|
313
324
|
* import { reticle } from '@reticlehq/vite-plugin';
|
|
314
325
|
* export default defineConfig({ plugins: [react(), reticle()] });
|
|
315
326
|
*
|
|
316
|
-
*
|
|
317
|
-
* bundle
|
|
327
|
+
* The web plugin also builds, replacing the browser SDK with an inert stub so a default
|
|
328
|
+
* production bundle ships no Reticle runtime code at all.
|
|
318
329
|
*
|
|
319
330
|
* `desktop: true` is the ONE documented exception, and it inverts that guarantee deliberately: a
|
|
320
|
-
* packaged Electron/Tauri renderer IS a production build with no dev server, so
|
|
321
|
-
* would ship an app with no connect() at all. The cost is that the flag hands
|
|
322
|
-
* caller — keep it behind your own dev-only build target so an instrumented
|
|
323
|
-
* a release binary.
|
|
331
|
+
* packaged Electron/Tauri renderer IS a production build with no dev server, so the web default's
|
|
332
|
+
* stub-replacement would ship an app with no connect() at all. The cost is that the flag hands
|
|
333
|
+
* gating back to the caller — keep it behind your own dev-only build target so an instrumented
|
|
334
|
+
* bundle can never reach a release binary.
|
|
324
335
|
*/
|
|
325
336
|
/**
|
|
326
337
|
* The daemon's journal directory, as a matcher every chokidar major honours.
|
package/dist/index.js
CHANGED
|
@@ -3,14 +3,15 @@ import { missingTokenWarning } from './token/missing-token.js';
|
|
|
3
3
|
import { ensurePairingToken } from './token/ensure-token.js';
|
|
4
4
|
import { homedir } from 'node:os';
|
|
5
5
|
import { join } from 'node:path';
|
|
6
|
-
import {
|
|
7
|
-
import reticleSource from '@reticlehq/babel-plugin';
|
|
8
|
-
import { RETICLE_DEFAULT_PORT, RETICLE_RENDER_PREHOOK, bridgeWsUrl, ReticleDir, ReticleEnv, RETICLE_ROOT_GLOBAL, RETICLE_SDK_VERSION_GLOBAL, } from '@reticlehq/core';
|
|
6
|
+
import { RETICLE_RENDER_PREHOOK, ReticleDir, ReticleEnv, RETICLE_ROOT_GLOBAL, RETICLE_SDK_VERSION_GLOBAL, } from '@reticlehq/core';
|
|
9
7
|
import { resolveProjectId } from './project-id.js';
|
|
10
8
|
import { discoverDaemonPort } from './discover-port.js';
|
|
11
9
|
import { announceDevServer } from './announce.js';
|
|
12
|
-
import {
|
|
10
|
+
import { stampSvelte } from './svelte-source.js';
|
|
11
|
+
import { ignoredFileNotice, optsOutOfStamping, shouldStamp, shouldStampSvelte, stamp, } from './stamping.js';
|
|
13
12
|
import { resolvableChain, sdkPackageVersion, sdkBuildFingerprint, viteMajor, optimizerOptionsKey, optimizerOptions, } from './installed.js';
|
|
13
|
+
import { RETICLE_DISABLED_STUB_CODE, RETICLE_DISABLED_STUB, isReticleDisabledWebBuild, } from './disabled-browser-stub.js';
|
|
14
|
+
import { connectArgs } from './connect-args.js';
|
|
14
15
|
import { createInjectionWatch, notInjectedMessage, } from './injection-postcondition.js';
|
|
15
16
|
import { RETICLE_VITE_PLUGIN_NAME } from './plugin-name.js';
|
|
16
17
|
export { RETICLE_VITE_PLUGIN_NAME } from './plugin-name.js';
|
|
@@ -26,11 +27,6 @@ const RETICLE_SENSOR = '@reticlehq/browser';
|
|
|
26
27
|
* file it lives in.
|
|
27
28
|
*/
|
|
28
29
|
export const RETICLE_TOKEN_GLOBAL = '__RETICLE_TOKEN__';
|
|
29
|
-
/** Files we stamp with source info — JSX/TSX only. */
|
|
30
|
-
const JSX_FILE = /\.[jt]sx$/;
|
|
31
|
-
/** Rollup virtual-module ids start with a NUL byte; never transform those. */
|
|
32
|
-
const VIRTUAL_PREFIX = '\0';
|
|
33
|
-
const NODE_MODULES = 'node_modules';
|
|
34
30
|
/**
|
|
35
31
|
* The connect code is served as a real module (not an inline <script>) so that Vite's import
|
|
36
32
|
* pipeline resolves the bare `@reticlehq/react` specifier. An inline injected script is NOT run through
|
|
@@ -132,40 +128,6 @@ function isHtmlEntry(id, specifier, root) {
|
|
|
132
128
|
// buildEnd post-condition means a wrong match cannot pass unnoticed as "nothing happened".
|
|
133
129
|
return candidate.endsWith(target.startsWith('/') ? target : `/${target}`);
|
|
134
130
|
}
|
|
135
|
-
/** A module id we may stamp at all: not virtual, not a dependency. Extension decides which stamper. */
|
|
136
|
-
function stampableId(id) {
|
|
137
|
-
if (id.startsWith(VIRTUAL_PREFIX))
|
|
138
|
-
return null;
|
|
139
|
-
if (id.includes(NODE_MODULES))
|
|
140
|
-
return null;
|
|
141
|
-
// Strip any query suffix (?worker, ?raw,...) before matching the extension.
|
|
142
|
-
return id.split('?')[0] ?? id;
|
|
143
|
-
}
|
|
144
|
-
function shouldStamp(id) {
|
|
145
|
-
const clean = stampableId(id);
|
|
146
|
-
return clean !== null && JSX_FILE.test(clean);
|
|
147
|
-
}
|
|
148
|
-
/** A `.svelte` single-file component, which needs the Svelte stamper rather than Babel. */
|
|
149
|
-
function shouldStampSvelte(id) {
|
|
150
|
-
const clean = stampableId(id);
|
|
151
|
-
return clean !== null && SVELTE_FILE.test(clean);
|
|
152
|
-
}
|
|
153
|
-
function stamp(code, id) {
|
|
154
|
-
const out = transformSync(code, {
|
|
155
|
-
filename: id,
|
|
156
|
-
plugins: [reticleSource],
|
|
157
|
-
parserOpts: { plugins: ['jsx', 'typescript'] },
|
|
158
|
-
sourceMaps: true,
|
|
159
|
-
configFile: false,
|
|
160
|
-
babelrc: false,
|
|
161
|
-
});
|
|
162
|
-
if (out?.code === undefined || null === out.code)
|
|
163
|
-
return null;
|
|
164
|
-
return {
|
|
165
|
-
code: out.code,
|
|
166
|
-
map: out.map === undefined || null === out.map ? null : JSON.stringify(out.map),
|
|
167
|
-
};
|
|
168
|
-
}
|
|
169
131
|
/**
|
|
170
132
|
* Read the daemon's auto-provisioned pairing token (~/.reticle/pairing-token, or the
|
|
171
133
|
* RETICLE_PAIRING_TOKEN_DIR override) so the served app can present it. Node-side only — a browser
|
|
@@ -196,60 +158,6 @@ function warnIfTokenMissing(token) {
|
|
|
196
158
|
}
|
|
197
159
|
return token;
|
|
198
160
|
}
|
|
199
|
-
/** Build the `reticle.connect` argument literal — only includes keys the user set. */
|
|
200
|
-
function connectArgs(options) {
|
|
201
|
-
const args = {};
|
|
202
|
-
const port = options.port ?? RETICLE_DEFAULT_PORT;
|
|
203
|
-
if (port !== RETICLE_DEFAULT_PORT)
|
|
204
|
-
args['url'] = bridgeWsUrl(port);
|
|
205
|
-
if (options.session !== undefined)
|
|
206
|
-
args['session'] = options.session;
|
|
207
|
-
if (options.projectId !== undefined)
|
|
208
|
-
args['projectId'] = options.projectId;
|
|
209
|
-
if (options.token !== undefined)
|
|
210
|
-
args['token'] = options.token;
|
|
211
|
-
// Passed as connect ARGUMENTS, not as a `define`. A define substitutes a bare identifier in the
|
|
212
|
-
// source it transforms; the SDK reads these as `globalThis[NAME]`, a dynamic lookup no define can
|
|
213
|
-
// ever reach — so defining them looked right, shipped, and did nothing. Baking them into the
|
|
214
|
-
// generated connect call is a literal in generated source: no bundler subtleties, works the same
|
|
215
|
-
// in dev and in a desktop build.
|
|
216
|
-
if (options.root !== undefined && options.root.length > 0)
|
|
217
|
-
args['root'] = options.root;
|
|
218
|
-
if (options.sdkVersion !== undefined && options.sdkVersion.length > 0) {
|
|
219
|
-
args['sdkVersion'] = options.sdkVersion;
|
|
220
|
-
}
|
|
221
|
-
// A desktop renderer is a production build by construction; without this the SDK's prod backstop
|
|
222
|
-
// refuses to connect and the app is silently uninstrumented.
|
|
223
|
-
if (true === options.desktop)
|
|
224
|
-
args['allowInProduction'] = true;
|
|
225
|
-
// Env wins nothing — it only turns the flag ON, so a config that never set it can still be
|
|
226
|
-
// switched on for one debugging session without editing vite.config and restarting the mental
|
|
227
|
-
// model with it.
|
|
228
|
-
if (true === options.captureNetworkBodies || '1' === process.env['VITE_RETICLE_CAPTURE_BODIES']) {
|
|
229
|
-
args['captureNetworkBodies'] = true;
|
|
230
|
-
}
|
|
231
|
-
// Only the OPT-OUT is announced, never the default. The daemon reads absence as "unknown", so
|
|
232
|
-
// sending `true` here would say nothing it does not already assume — while sending `false` is the
|
|
233
|
-
// one fact a red verdict needs to stop prescribing a plugin this project has deliberately muted.
|
|
234
|
-
if (false === options.sourceMapping)
|
|
235
|
-
args['sourceMapping'] = false;
|
|
236
|
-
// The one option that defaults ON, so the env var and the config flag both DISABLE rather than
|
|
237
|
-
// enable. Emitted only when switched off; the default stays implicit in the SDK.
|
|
238
|
-
if (false === options.captureErrorBodies || '1' === process.env['VITE_RETICLE_NO_ERROR_BODIES']) {
|
|
239
|
-
args['captureErrorBodies'] = false;
|
|
240
|
-
}
|
|
241
|
-
// Same shape, same reason. Off unless asked for, in a config or for one session.
|
|
242
|
-
if (true === options.exposePresenter || '1' === process.env['VITE_RETICLE_EXPOSE_PRESENTER']) {
|
|
243
|
-
args['exposePresenter'] = true;
|
|
244
|
-
}
|
|
245
|
-
// Same shape, same reason: without it an app that cannot be served on localhost has no way to
|
|
246
|
-
// reach the SDK option at all. The pairing token still applies — see the option's docstring.
|
|
247
|
-
if (true === options.allowNonLocalhost ||
|
|
248
|
-
'1' === process.env['VITE_RETICLE_ALLOW_NON_LOCALHOST']) {
|
|
249
|
-
args['allowNonLocalhost'] = true;
|
|
250
|
-
}
|
|
251
|
-
return Object.keys(args).length > 0 ? JSON.stringify(args) : '';
|
|
252
|
-
}
|
|
253
161
|
/** The body of the connect module — real imports, resolved by Vite when the module is served. */
|
|
254
162
|
/**
|
|
255
163
|
* The conventional app-side dev module: `registerStore` / `registerCapabilities` live here.
|
|
@@ -333,14 +241,14 @@ export function connectModuleSource(options, devModule = null) {
|
|
|
333
241
|
* import { reticle } from '@reticlehq/vite-plugin';
|
|
334
242
|
* export default defineConfig({ plugins: [react(), reticle()] });
|
|
335
243
|
*
|
|
336
|
-
*
|
|
337
|
-
* bundle
|
|
244
|
+
* The web plugin also builds, replacing the browser SDK with an inert stub so a default
|
|
245
|
+
* production bundle ships no Reticle runtime code at all.
|
|
338
246
|
*
|
|
339
247
|
* `desktop: true` is the ONE documented exception, and it inverts that guarantee deliberately: a
|
|
340
|
-
* packaged Electron/Tauri renderer IS a production build with no dev server, so
|
|
341
|
-
* would ship an app with no connect() at all. The cost is that the flag hands
|
|
342
|
-
* caller — keep it behind your own dev-only build target so an instrumented
|
|
343
|
-
* a release binary.
|
|
248
|
+
* packaged Electron/Tauri renderer IS a production build with no dev server, so the web default's
|
|
249
|
+
* stub-replacement would ship an app with no connect() at all. The cost is that the flag hands
|
|
250
|
+
* gating back to the caller — keep it behind your own dev-only build target so an instrumented
|
|
251
|
+
* bundle can never reach a release binary.
|
|
344
252
|
*/
|
|
345
253
|
/**
|
|
346
254
|
* The daemon's journal directory, as a matcher every chokidar major honours.
|
|
@@ -378,6 +286,8 @@ export function reticle(options = {}) {
|
|
|
378
286
|
const warn = options.onWarn ?? ((message) => globalThis.console.warn(message));
|
|
379
287
|
/** Whether connect() actually reached a module — asserted at buildEnd, never assumed. */
|
|
380
288
|
let injected = false;
|
|
289
|
+
/** Files already announced as opted out of stamping: one line each, for the life of the server. */
|
|
290
|
+
const announcedIgnored = new Set();
|
|
381
291
|
/**
|
|
382
292
|
* Whether Vite ever asked us to transform the app's HTML.
|
|
383
293
|
*
|
|
@@ -426,10 +336,6 @@ export function reticle(options = {}) {
|
|
|
426
336
|
});
|
|
427
337
|
return {
|
|
428
338
|
name: RETICLE_VITE_PLUGIN_NAME,
|
|
429
|
-
// Web: serve-only, so a production bundle can never carry the SDK — gating is the tool's job.
|
|
430
|
-
// Desktop: a packaged renderer IS a production build with no dev server, so the plugin must also
|
|
431
|
-
// run for `vite build` or the shipped app has no connect() at all.
|
|
432
|
-
...(true === options.desktop ? {} : { apply: 'serve' }),
|
|
433
339
|
enforce: 'pre',
|
|
434
340
|
/**
|
|
435
341
|
* Declare the SDK itself and the optimizer cache fingerprint.
|
|
@@ -511,6 +417,8 @@ export function reticle(options = {}) {
|
|
|
511
417
|
};
|
|
512
418
|
},
|
|
513
419
|
transform(code, id) {
|
|
420
|
+
if (isReticleDisabledWebBuild(desktop, command))
|
|
421
|
+
return null;
|
|
514
422
|
// Desktop injection: prepend connect() to the HTML's own entry module. It is a REAL module, so
|
|
515
423
|
// its bare `@reticlehq/react` import resolves through the normal pipeline in both dev and
|
|
516
424
|
// build — which a virtual <script src> only ever did in dev.
|
|
@@ -522,6 +430,16 @@ export function reticle(options = {}) {
|
|
|
522
430
|
}
|
|
523
431
|
if (!sourceMapping)
|
|
524
432
|
return null;
|
|
433
|
+
// Decided HERE, ahead of both stampers, so the Svelte path honours the marker too and so the
|
|
434
|
+
// opt-out is announced once regardless of which stamper would have run. The Babel plugin reads
|
|
435
|
+
// the same marker on its own for the callers that reach it without Vite (#853).
|
|
436
|
+
if (optsOutOfStamping(code, id)) {
|
|
437
|
+
if (!announcedIgnored.has(id)) {
|
|
438
|
+
announcedIgnored.add(id);
|
|
439
|
+
warn(ignoredFileNotice(id));
|
|
440
|
+
}
|
|
441
|
+
return null;
|
|
442
|
+
}
|
|
525
443
|
// `.svelte` runs on the RAW component source, which is only still markup because this plugin
|
|
526
444
|
// declares `enforce: 'pre'` and therefore transforms before @sveltejs/vite-plugin-svelte. No
|
|
527
445
|
// map: the insertions are within a line and never move one, and a wrong map is worse than none.
|
|
@@ -534,6 +452,9 @@ export function reticle(options = {}) {
|
|
|
534
452
|
return stamp(code, id);
|
|
535
453
|
},
|
|
536
454
|
resolveId(id, importer) {
|
|
455
|
+
if (isReticleDisabledWebBuild(desktop, command) && id === RETICLE_SENSOR) {
|
|
456
|
+
return RETICLE_DISABLED_STUB;
|
|
457
|
+
}
|
|
537
458
|
// Desktop: remember the module the HTML points at, so `transform` can prepend connect() into
|
|
538
459
|
// it. A packaged build has no dev server, so the serve-time trick below — a <script src> at a
|
|
539
460
|
// virtual URL — would emit a tag pointing at a file that does not exist. That shipped an app
|
|
@@ -549,6 +470,9 @@ export function reticle(options = {}) {
|
|
|
549
470
|
return inject && id === RETICLE_CONNECT_MODULE ? RETICLE_CONNECT_MODULE : null;
|
|
550
471
|
},
|
|
551
472
|
load(id) {
|
|
473
|
+
if (isReticleDisabledWebBuild(desktop, command) && id === RETICLE_DISABLED_STUB) {
|
|
474
|
+
return RETICLE_DISABLED_STUB_CODE;
|
|
475
|
+
}
|
|
552
476
|
if (!inject || id !== RETICLE_CONNECT_MODULE)
|
|
553
477
|
return null;
|
|
554
478
|
const source = currentConnectSource();
|
|
@@ -672,6 +596,8 @@ export function reticle(options = {}) {
|
|
|
672
596
|
checkHtmlHookForTest: watch.checkHtmlHookRan,
|
|
673
597
|
injectionWatchForTest: watch,
|
|
674
598
|
transformIndexHtml() {
|
|
599
|
+
if (isReticleDisabledWebBuild(desktop, command))
|
|
600
|
+
return [];
|
|
675
601
|
htmlTransformed = true;
|
|
676
602
|
if (desktop && inject && 'serve' === command)
|
|
677
603
|
watch.armDesktopCheck();
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which files get a `data-reticle-source` stamp, and how.
|
|
3
|
+
*
|
|
4
|
+
* Split out of index.ts when that file crossed the line cap while gaining the per-file opt-out.
|
|
5
|
+
* This is one concern — a module id comes in, and either a stamper runs on it, nothing does, or the
|
|
6
|
+
* file has asked to be left alone — and it changes for one reason (the source-pointer feature)
|
|
7
|
+
* rather than for the dozen the plugin's lifecycle does. The Svelte stamper is its sibling module;
|
|
8
|
+
* the JSX one is `@reticlehq/babel-plugin`, which also reads the opt-out on its own for the callers
|
|
9
|
+
* that reach it without Vite.
|
|
10
|
+
*/
|
|
11
|
+
/** A module id we may stamp at all: not virtual, not a dependency. Extension decides which stamper. */
|
|
12
|
+
export declare function stampableId(id: string): string | null;
|
|
13
|
+
export declare function shouldStamp(id: string): boolean;
|
|
14
|
+
/** A `.svelte` single-file component, which needs the Svelte stamper rather than Babel. */
|
|
15
|
+
export declare function shouldStampSvelte(id: string): boolean;
|
|
16
|
+
export declare function stamp(code: string, id: string): {
|
|
17
|
+
code: string;
|
|
18
|
+
map: string | null;
|
|
19
|
+
} | null;
|
|
20
|
+
/**
|
|
21
|
+
* Has this file asked not to be stamped?
|
|
22
|
+
*
|
|
23
|
+
* Decided ahead of BOTH stampers, so the Svelte path honours the marker too and so the opt-out is
|
|
24
|
+
* announced once regardless of which stamper would have run. Only a file one of them would have
|
|
25
|
+
* touched can opt out — an id that was never stampable has nothing to announce (#853).
|
|
26
|
+
*/
|
|
27
|
+
export declare function optsOutOfStamping(code: string, id: string): boolean;
|
|
28
|
+
/**
|
|
29
|
+
* The one line said when a file opts out of source stamping with `@reticle-ignore`.
|
|
30
|
+
*
|
|
31
|
+
* Not silent, on purpose. An opt-out nobody is told about is an opt-out somebody forgets, and the
|
|
32
|
+
* `file:line` it would have produced quietly stops resolving months later with nothing to point at
|
|
33
|
+
* the comment. Said once per file, in the dev-server output where the person who added the comment
|
|
34
|
+
* will see it. Exported for the test that pins the wording.
|
|
35
|
+
*/
|
|
36
|
+
export declare function ignoredFileNotice(id: string): string;
|
package/dist/stamping.js
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which files get a `data-reticle-source` stamp, and how.
|
|
3
|
+
*
|
|
4
|
+
* Split out of index.ts when that file crossed the line cap while gaining the per-file opt-out.
|
|
5
|
+
* This is one concern — a module id comes in, and either a stamper runs on it, nothing does, or the
|
|
6
|
+
* file has asked to be left alone — and it changes for one reason (the source-pointer feature)
|
|
7
|
+
* rather than for the dozen the plugin's lifecycle does. The Svelte stamper is its sibling module;
|
|
8
|
+
* the JSX one is `@reticlehq/babel-plugin`, which also reads the opt-out on its own for the callers
|
|
9
|
+
* that reach it without Vite.
|
|
10
|
+
*/
|
|
11
|
+
import { transformSync } from '@babel/core';
|
|
12
|
+
import reticleSource from '@reticlehq/babel-plugin';
|
|
13
|
+
import { isSourceStampingIgnored } from '@reticlehq/babel-plugin/ignore';
|
|
14
|
+
import { RETICLE_IGNORE_MARKER } from '@reticlehq/core';
|
|
15
|
+
import { SVELTE_FILE } from './svelte-source.js';
|
|
16
|
+
/** Files we stamp with source info — JSX/TSX only. */
|
|
17
|
+
const JSX_FILE = /\.[jt]sx$/;
|
|
18
|
+
/** Rollup virtual-module ids start with a NUL byte; never transform those. */
|
|
19
|
+
const VIRTUAL_PREFIX = '\0';
|
|
20
|
+
const NODE_MODULES = 'node_modules';
|
|
21
|
+
/** A module id we may stamp at all: not virtual, not a dependency. Extension decides which stamper. */
|
|
22
|
+
export function stampableId(id) {
|
|
23
|
+
if (id.startsWith(VIRTUAL_PREFIX))
|
|
24
|
+
return null;
|
|
25
|
+
if (id.includes(NODE_MODULES))
|
|
26
|
+
return null;
|
|
27
|
+
// Strip any query suffix (?worker, ?raw,...) before matching the extension.
|
|
28
|
+
return id.split('?')[0] ?? id;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* A test file, by the names every JS test runner agrees on.
|
|
32
|
+
*
|
|
33
|
+
* `.test.`/`.spec.` before the extension, or anywhere under a `__tests__` directory. Anchored to a
|
|
34
|
+
* path SEGMENT and a dot so `TestBanner.tsx`, `latest.tsx` and `contest.tsx` stay ordinary
|
|
35
|
+
* components — they are somebody's app, and skipping them would silently cost source mapping.
|
|
36
|
+
*/
|
|
37
|
+
const TEST_FILE = /(?:\.(?:test|spec)\.[^./]+$)|(?:^|\/)__tests__\//;
|
|
38
|
+
export function shouldStamp(id) {
|
|
39
|
+
const clean = stampableId(id);
|
|
40
|
+
if (null === clean)
|
|
41
|
+
return false;
|
|
42
|
+
/*
|
|
43
|
+
* A test file is not the app under test.
|
|
44
|
+
*
|
|
45
|
+
* Nothing reads `data-reticle-source` on one — an agent inspects the app's DOM, never a test's —
|
|
46
|
+
* so stamping them is pure cost, charged per file on every run, and reported from the field as
|
|
47
|
+
* nearly doubling a large jsdom suite and failing a test that passed without instrumentation:
|
|
48
|
+
* inserting attributes into JSX is exactly what an assertion on rendered output notices (#995).
|
|
49
|
+
*
|
|
50
|
+
* Cut on the NAME rather than on "are we under Vitest": `vitest-browser.ts` records what happened
|
|
51
|
+
* the last time that was cut on the `VITEST` env var, which also reads true when a Vitest suite
|
|
52
|
+
* boots an app in order to test it. A name check cannot make that mistake.
|
|
53
|
+
*/
|
|
54
|
+
if (TEST_FILE.test(clean))
|
|
55
|
+
return false;
|
|
56
|
+
return JSX_FILE.test(clean);
|
|
57
|
+
}
|
|
58
|
+
/** A `.svelte` single-file component, which needs the Svelte stamper rather than Babel. */
|
|
59
|
+
export function shouldStampSvelte(id) {
|
|
60
|
+
const clean = stampableId(id);
|
|
61
|
+
return clean !== null && SVELTE_FILE.test(clean);
|
|
62
|
+
}
|
|
63
|
+
export function stamp(code, id) {
|
|
64
|
+
const out = transformSync(code, {
|
|
65
|
+
filename: id,
|
|
66
|
+
plugins: [reticleSource],
|
|
67
|
+
parserOpts: { plugins: ['jsx', 'typescript'] },
|
|
68
|
+
sourceMaps: true,
|
|
69
|
+
configFile: false,
|
|
70
|
+
babelrc: false,
|
|
71
|
+
});
|
|
72
|
+
if (out?.code === undefined || null === out.code)
|
|
73
|
+
return null;
|
|
74
|
+
return {
|
|
75
|
+
code: out.code,
|
|
76
|
+
map: out.map === undefined || null === out.map ? null : JSON.stringify(out.map),
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Has this file asked not to be stamped?
|
|
81
|
+
*
|
|
82
|
+
* Decided ahead of BOTH stampers, so the Svelte path honours the marker too and so the opt-out is
|
|
83
|
+
* announced once regardless of which stamper would have run. Only a file one of them would have
|
|
84
|
+
* touched can opt out — an id that was never stampable has nothing to announce (#853).
|
|
85
|
+
*/
|
|
86
|
+
export function optsOutOfStamping(code, id) {
|
|
87
|
+
return (shouldStamp(id) || shouldStampSvelte(id)) && isSourceStampingIgnored(code);
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* The one line said when a file opts out of source stamping with `@reticle-ignore`.
|
|
91
|
+
*
|
|
92
|
+
* Not silent, on purpose. An opt-out nobody is told about is an opt-out somebody forgets, and the
|
|
93
|
+
* `file:line` it would have produced quietly stops resolving months later with nothing to point at
|
|
94
|
+
* the comment. Said once per file, in the dev-server output where the person who added the comment
|
|
95
|
+
* will see it. Exported for the test that pins the wording.
|
|
96
|
+
*/
|
|
97
|
+
export function ignoredFileNotice(id) {
|
|
98
|
+
return `[reticle] ${id} opts out of source stamping (${RETICLE_IGNORE_MARKER} on its first line) — elements in it will have no file:line.`;
|
|
99
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reticlehq/vite-plugin",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.3.0",
|
|
4
4
|
"description": "Vite plugin for Reticle: dev-only source-map stamping plus auto-injected reticle.connect(). apply:'serve' guarantees it never ships to production.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -41,15 +41,15 @@
|
|
|
41
41
|
],
|
|
42
42
|
"dependencies": {
|
|
43
43
|
"@babel/core": "^7.26.0",
|
|
44
|
-
"@reticlehq/babel-plugin": "3.
|
|
45
|
-
"@reticlehq/core": "3.
|
|
44
|
+
"@reticlehq/babel-plugin": "3.3.0",
|
|
45
|
+
"@reticlehq/core": "3.3.0"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
48
|
"@types/babel__core": "^7.20.5",
|
|
49
49
|
"esbuild": "^0.28.2",
|
|
50
50
|
"svelte": "^5.56.10",
|
|
51
51
|
"vite": "^8",
|
|
52
|
-
"@reticlehq/react": "3.
|
|
52
|
+
"@reticlehq/react": "3.3.0"
|
|
53
53
|
},
|
|
54
54
|
"peerDependencies": {
|
|
55
55
|
"vite": ">=4"
|