@monocle.sh/vite-plugin 0.2.0 → 0.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 +22 -8
- package/dist/vite.d.mts +1 -0
- package/dist/vite.mjs +417 -4
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -70,14 +70,28 @@ unless it is one of the placeholders above.
|
|
|
70
70
|
|
|
71
71
|
## How it works
|
|
72
72
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
73
|
+
When every entry of `vite build` is an HTML page (the default `index.html`
|
|
74
|
+
setup), the plugin writes the release into the page as
|
|
75
|
+
`<meta name="monocle-build" content="{…}">`, which the SDK reads in
|
|
76
|
+
`initMonocle()`. Your JavaScript chunks never contain the release, so two builds
|
|
77
|
+
of the same code produce identical chunks, file names and debug IDs: a deploy
|
|
78
|
+
only uploads the source maps of the chunks that changed. A `<meta>` tag is not
|
|
79
|
+
affected by Content Security Policies. This needs `@monocle.sh/browser-sdk`
|
|
80
|
+
0.13.0 or later; with an older SDK the plugin warns and falls back to the
|
|
81
|
+
bundle.
|
|
82
|
+
|
|
83
|
+
Otherwise (dev server, SSR, library builds, frameworks rendering the HTML
|
|
84
|
+
themselves), the plugin prepends `globalThis.__MONOCLE_BUILD__ = {…}` to each of
|
|
85
|
+
your modules that imports `@monocle.sh/browser-sdk`. This targets application
|
|
86
|
+
code rather than relying on `define`, which Vite does not apply to pre-bundled
|
|
87
|
+
dependencies. During SSR, the global is also set on the server, where the
|
|
88
|
+
Browser SDK stays inactive.
|
|
89
|
+
|
|
90
|
+
Vite and Rolldown also put the hashed file name of every imported chunk into
|
|
91
|
+
the importing chunk, so changing one chunk renames the chunks that import it,
|
|
92
|
+
up to the entry. To keep those unchanged too, enable Vite's
|
|
93
|
+
[`build.chunkImportMap`](https://vite.dev/config/build-options) (Vite 8,
|
|
94
|
+
experimental).
|
|
81
95
|
|
|
82
96
|
## Source maps
|
|
83
97
|
|
package/dist/vite.d.mts
CHANGED
package/dist/vite.mjs
CHANGED
|
@@ -1,8 +1,348 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
import { dirname, join, resolve, sep } from "node:path";
|
|
3
|
+
import { readFileSync, realpathSync, statSync } from "node:fs";
|
|
3
4
|
import MagicString from "magic-string";
|
|
4
5
|
import { rm } from "node:fs/promises";
|
|
5
6
|
import { injectDebugIds, uploadSourcemaps } from "@monocle.sh/sourcemaps";
|
|
7
|
+
//#region src/core/browser_sdk.ts
|
|
8
|
+
/**
|
|
9
|
+
* What the Browser SDK the application resolves can read.
|
|
10
|
+
*/
|
|
11
|
+
var BrowserSdk = class {
|
|
12
|
+
/**
|
|
13
|
+
* First `@monocle.sh/browser-sdk` reading `<meta name="monocle-build">`.
|
|
14
|
+
*/
|
|
15
|
+
static pageReleaseSince = "0.13.0";
|
|
16
|
+
static #atLeast(version, minimum) {
|
|
17
|
+
const parse = (value) => value.split(/[-+]/)[0].split(".").map(Number);
|
|
18
|
+
const [actual, expected] = [parse(version), parse(minimum)];
|
|
19
|
+
for (let index = 0; index < 3; index++) {
|
|
20
|
+
const difference = (actual[index] ?? 0) - (expected[index] ?? 0);
|
|
21
|
+
if (Number.isNaN(difference)) return false;
|
|
22
|
+
if (difference !== 0) return difference > 0;
|
|
23
|
+
}
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Where the installed SDK reads the release: `page` from the HTML, `bundle` only from the
|
|
28
|
+
* global, `missing` when the application does not resolve it, so nothing reads a page tag. A
|
|
29
|
+
* linked local copy (workspace) is assumed current; one that cannot be inspected (Yarn PnP
|
|
30
|
+
* archives…) reads the bundle.
|
|
31
|
+
*/
|
|
32
|
+
static releaseSource(root) {
|
|
33
|
+
let path;
|
|
34
|
+
try {
|
|
35
|
+
path = createRequire(join(root, "package.json")).resolve("@monocle.sh/browser-sdk/package.json");
|
|
36
|
+
} catch {
|
|
37
|
+
return "missing";
|
|
38
|
+
}
|
|
39
|
+
try {
|
|
40
|
+
if (!realpathSync(path).includes(`${sep}node_modules${sep}`)) return "page";
|
|
41
|
+
const { version } = JSON.parse(readFileSync(path, "utf8"));
|
|
42
|
+
return typeof version === "string" && this.#atLeast(version, this.pageReleaseSince) ? "page" : "bundle";
|
|
43
|
+
} catch {
|
|
44
|
+
return "bundle";
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
//#endregion
|
|
49
|
+
//#region ../otel-utils/dist/service-version.mjs
|
|
50
|
+
var ServiceVersionDetector = class {
|
|
51
|
+
/**
|
|
52
|
+
* Ordered platform revision variables, before CI and the local checkout.
|
|
53
|
+
*/
|
|
54
|
+
static #platformSources = [
|
|
55
|
+
["VERCEL_GIT_COMMIT_SHA", "platform:vercel"],
|
|
56
|
+
["RENDER_GIT_COMMIT", "platform:render"],
|
|
57
|
+
["RAILWAY_GIT_COMMIT_SHA", "platform:railway"],
|
|
58
|
+
["HEROKU_BUILD_COMMIT", "platform:heroku"],
|
|
59
|
+
["HEROKU_SLUG_COMMIT", "platform:heroku"],
|
|
60
|
+
["SOURCE_COMMIT", "platform:coolify"],
|
|
61
|
+
["GIT_REV", "platform:dokku"],
|
|
62
|
+
["K_REVISION", "platform:cloud-run"]
|
|
63
|
+
];
|
|
64
|
+
/**
|
|
65
|
+
* CI detection adapted from env-ci 11.2.0 (MIT): https://github.com/semantic-release/env-ci
|
|
66
|
+
* See LICENSE.env-ci. Git subprocess and Java properties adapters are excluded.
|
|
67
|
+
*/
|
|
68
|
+
static #ciSources = {
|
|
69
|
+
github: {
|
|
70
|
+
detect: (env) => Boolean(env.GITHUB_ACTIONS),
|
|
71
|
+
revisions: ["GITHUB_SHA"],
|
|
72
|
+
repository: (env) => this.#repositoryFromSlug({
|
|
73
|
+
host: env.GITHUB_SERVER_URL,
|
|
74
|
+
slug: env.GITHUB_REPOSITORY
|
|
75
|
+
})
|
|
76
|
+
},
|
|
77
|
+
gitlab: {
|
|
78
|
+
detect: (env) => Boolean(env.GITLAB_CI),
|
|
79
|
+
revisions: ["CI_COMMIT_SHA"],
|
|
80
|
+
repository: (env) => this.#normalizeRepositoryUrl(env.CI_PROJECT_URL) ?? this.#repositoryFromSlug({
|
|
81
|
+
host: env.CI_SERVER_URL,
|
|
82
|
+
slug: env.CI_PROJECT_PATH
|
|
83
|
+
})
|
|
84
|
+
},
|
|
85
|
+
woodpecker: {
|
|
86
|
+
detect: (env) => env.CI === "woodpecker",
|
|
87
|
+
revisions: ["CI_COMMIT_SHA"],
|
|
88
|
+
repository: (env) => env.CI_REPO_CLONE_URL
|
|
89
|
+
},
|
|
90
|
+
circleci: {
|
|
91
|
+
detect: (env) => Boolean(env.CIRCLECI),
|
|
92
|
+
revisions: ["CIRCLE_SHA1"],
|
|
93
|
+
repository: (env) => env.CIRCLE_REPOSITORY_URL
|
|
94
|
+
},
|
|
95
|
+
bitbucket: {
|
|
96
|
+
detect: (env) => Boolean(env.BITBUCKET_BUILD_NUMBER),
|
|
97
|
+
revisions: ["BITBUCKET_COMMIT"],
|
|
98
|
+
repository: (env) => this.#repositoryFromSlug({
|
|
99
|
+
host: "https://bitbucket.org",
|
|
100
|
+
slug: env.BITBUCKET_REPO_FULL_NAME
|
|
101
|
+
})
|
|
102
|
+
},
|
|
103
|
+
buildkite: {
|
|
104
|
+
detect: (env) => Boolean(env.BUILDKITE),
|
|
105
|
+
revisions: ["BUILDKITE_COMMIT"],
|
|
106
|
+
repository: (env) => env.BUILDKITE_REPO
|
|
107
|
+
},
|
|
108
|
+
jenkins: {
|
|
109
|
+
detect: (env) => Boolean(env.JENKINS_URL),
|
|
110
|
+
revisions: ["ghprbActualCommit", "GIT_COMMIT"],
|
|
111
|
+
repository: (env) => env.GIT_URL
|
|
112
|
+
},
|
|
113
|
+
"azure-pipelines": {
|
|
114
|
+
detect: (env) => Boolean(env.BUILD_BUILDURI),
|
|
115
|
+
revisions: ["BUILD_SOURCEVERSION"],
|
|
116
|
+
repository: (env) => env.BUILD_REPOSITORY_URI
|
|
117
|
+
},
|
|
118
|
+
travis: {
|
|
119
|
+
detect: (env) => Boolean(env.TRAVIS),
|
|
120
|
+
revisions: ["TRAVIS_COMMIT"]
|
|
121
|
+
},
|
|
122
|
+
netlify: {
|
|
123
|
+
detect: (env) => env.NETLIFY === "true",
|
|
124
|
+
revisions: ["COMMIT_REF"],
|
|
125
|
+
repository: (env) => env.REPOSITORY_URL
|
|
126
|
+
},
|
|
127
|
+
"cloudflare-pages": {
|
|
128
|
+
detect: (env) => env.CF_PAGES === "1",
|
|
129
|
+
revisions: ["CF_PAGES_COMMIT_SHA"]
|
|
130
|
+
},
|
|
131
|
+
drone: {
|
|
132
|
+
detect: (env) => Boolean(env.DRONE),
|
|
133
|
+
revisions: ["DRONE_COMMIT_SHA"],
|
|
134
|
+
repository: (env) => env.DRONE_REMOTE_URL
|
|
135
|
+
},
|
|
136
|
+
bitrise: {
|
|
137
|
+
detect: (env) => Boolean(env.BITRISE_IO),
|
|
138
|
+
revisions: ["BITRISE_GIT_COMMIT"]
|
|
139
|
+
},
|
|
140
|
+
appveyor: {
|
|
141
|
+
detect: (env) => Boolean(env.APPVEYOR),
|
|
142
|
+
revisions: ["APPVEYOR_REPO_COMMIT"]
|
|
143
|
+
},
|
|
144
|
+
semaphore: {
|
|
145
|
+
detect: (env) => Boolean(env.SEMAPHORE),
|
|
146
|
+
revisions: ["SEMAPHORE_GIT_SHA"]
|
|
147
|
+
},
|
|
148
|
+
codefresh: {
|
|
149
|
+
detect: (env) => Boolean(env.CF_BUILD_ID),
|
|
150
|
+
revisions: ["CF_REVISION"]
|
|
151
|
+
},
|
|
152
|
+
buddy: {
|
|
153
|
+
detect: (env) => Boolean(env.BUDDY_WORKSPACE_ID),
|
|
154
|
+
revisions: ["BUDDY_EXECUTION_REVISION"]
|
|
155
|
+
},
|
|
156
|
+
cirrus: {
|
|
157
|
+
detect: (env) => Boolean(env.CIRRUS_CI),
|
|
158
|
+
revisions: ["CIRRUS_CHANGE_IN_REPO"]
|
|
159
|
+
},
|
|
160
|
+
vela: {
|
|
161
|
+
detect: (env) => Boolean(env.VELA),
|
|
162
|
+
revisions: ["VELA_BUILD_COMMIT"]
|
|
163
|
+
},
|
|
164
|
+
screwdriver: {
|
|
165
|
+
detect: (env) => Boolean(env.SCREWDRIVER),
|
|
166
|
+
revisions: ["SD_BUILD_SHA"]
|
|
167
|
+
},
|
|
168
|
+
bamboo: {
|
|
169
|
+
detect: (env) => Boolean(env.bamboo_agentId),
|
|
170
|
+
revisions: ["bamboo_planRepository_1_revision"]
|
|
171
|
+
},
|
|
172
|
+
codeship: {
|
|
173
|
+
detect: (env) => env.CI_NAME === "codeship",
|
|
174
|
+
revisions: ["CI_COMMIT_ID"]
|
|
175
|
+
},
|
|
176
|
+
"jetbrains-space": {
|
|
177
|
+
detect: (env) => Boolean(env.JB_SPACE_EXECUTION_NUMBER),
|
|
178
|
+
revisions: ["JB_SPACE_GIT_REVISION"]
|
|
179
|
+
},
|
|
180
|
+
scrutinizer: {
|
|
181
|
+
detect: (env) => Boolean(env.SCRUTINIZER),
|
|
182
|
+
revisions: ["SCRUTINIZER_SHA1"]
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
static #value(value) {
|
|
186
|
+
const trimmed = value?.trim();
|
|
187
|
+
if (!trimmed || [
|
|
188
|
+
"0.0.0",
|
|
189
|
+
"0.0.1",
|
|
190
|
+
"1.0.0",
|
|
191
|
+
"unknown",
|
|
192
|
+
"dev",
|
|
193
|
+
"latest"
|
|
194
|
+
].includes(trimmed.toLowerCase())) return;
|
|
195
|
+
return trimmed;
|
|
196
|
+
}
|
|
197
|
+
static #sha(value) {
|
|
198
|
+
if (value && /^(?:[a-f0-9]{40}|[a-f0-9]{64})$/i.test(value)) return value;
|
|
199
|
+
}
|
|
200
|
+
static #read(path) {
|
|
201
|
+
try {
|
|
202
|
+
return readFileSync(path, "utf8").trim();
|
|
203
|
+
} catch {
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
static #gitDirectory(cwd) {
|
|
208
|
+
let directory = resolve(cwd);
|
|
209
|
+
while (true) {
|
|
210
|
+
const path = join(directory, ".git");
|
|
211
|
+
try {
|
|
212
|
+
if (statSync(path).isDirectory()) return path;
|
|
213
|
+
const target = this.#read(path)?.match(/^gitdir:\s*(.+)$/)?.[1];
|
|
214
|
+
return target ? resolve(directory, target) : void 0;
|
|
215
|
+
} catch {
|
|
216
|
+
const parent = dirname(directory);
|
|
217
|
+
if (parent === directory) return;
|
|
218
|
+
directory = parent;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
static #gitRevision(cwd) {
|
|
223
|
+
const gitDirectory = this.#gitDirectory(cwd);
|
|
224
|
+
if (!gitDirectory) return;
|
|
225
|
+
const head = this.#read(join(gitDirectory, "HEAD"));
|
|
226
|
+
const detached = this.#sha(head);
|
|
227
|
+
if (detached) return detached;
|
|
228
|
+
const ref = head?.match(/^ref:\s*(refs\/[^\s]+)$/)?.[1];
|
|
229
|
+
if (!ref || ref.split("/").includes("..")) return;
|
|
230
|
+
const common = this.#read(join(gitDirectory, "commondir"));
|
|
231
|
+
const directories = common ? [gitDirectory, resolve(gitDirectory, common)] : [gitDirectory];
|
|
232
|
+
for (const directory of directories) {
|
|
233
|
+
const direct = this.#sha(this.#read(join(directory, ref)));
|
|
234
|
+
if (direct) return direct;
|
|
235
|
+
const packed = this.#read(join(directory, "packed-refs"));
|
|
236
|
+
for (const line of packed?.split("\n") ?? []) {
|
|
237
|
+
const [sha, name] = line.trim().split(/\s+/);
|
|
238
|
+
if (name === ref && this.#sha(sha)) return sha;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
static #githubRevision(env) {
|
|
243
|
+
const fallback = this.#value(env.GITHUB_SHA);
|
|
244
|
+
if (env.GITHUB_EVENT_NAME !== "pull_request") return fallback;
|
|
245
|
+
if (!env.GITHUB_EVENT_PATH) return fallback;
|
|
246
|
+
try {
|
|
247
|
+
const sha = JSON.parse(this.#read(env.GITHUB_EVENT_PATH) ?? "")?.pull_request?.head?.sha;
|
|
248
|
+
return typeof sha === "string" ? this.#sha(sha) ?? fallback : fallback;
|
|
249
|
+
} catch {
|
|
250
|
+
return fallback;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
static #repositoryFromSlug(options) {
|
|
254
|
+
const host = this.#normalizeRepositoryUrl(options.host);
|
|
255
|
+
const slug = this.#value(options.slug);
|
|
256
|
+
if (!host || !slug) return;
|
|
257
|
+
return `${host}/${slug.replace(/^\/+/, "")}`;
|
|
258
|
+
}
|
|
259
|
+
static #vercelRepository(env) {
|
|
260
|
+
const hosts = new Map([
|
|
261
|
+
["github", "https://github.com"],
|
|
262
|
+
["gitlab", "https://gitlab.com"],
|
|
263
|
+
["bitbucket", "https://bitbucket.org"]
|
|
264
|
+
]);
|
|
265
|
+
const owner = this.#value(env.VERCEL_GIT_REPO_OWNER);
|
|
266
|
+
const slug = this.#value(env.VERCEL_GIT_REPO_SLUG);
|
|
267
|
+
if (!owner || !slug) return;
|
|
268
|
+
return this.#repositoryFromSlug({
|
|
269
|
+
host: hosts.get(env.VERCEL_GIT_PROVIDER ?? ""),
|
|
270
|
+
slug: `${owner}/${slug}`
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
static #repositoryUrl(env) {
|
|
274
|
+
return [
|
|
275
|
+
env.VERCEL || env.NOW_GITHUB_DEPLOYMENT ? this.#vercelRepository(env) : void 0,
|
|
276
|
+
env.RENDER ? env.RENDER_GIT_REPO_URL : void 0,
|
|
277
|
+
env.RAILWAY_PROJECT_ID ? env.RAILWAY_GIT_REPO_URL : void 0,
|
|
278
|
+
...Object.values(this.#ciSources).filter((provider) => provider.detect(env)).map((provider) => provider.repository?.(env))
|
|
279
|
+
].map((candidate) => this.#normalizeRepositoryUrl(candidate)).find(Boolean);
|
|
280
|
+
}
|
|
281
|
+
static #normalizeRepositoryUrl(candidate) {
|
|
282
|
+
const value = this.#value(candidate);
|
|
283
|
+
if (!value) return;
|
|
284
|
+
try {
|
|
285
|
+
const url = new URL(value.replace(/^git@([^:/]+):/, "ssh://git@$1/"));
|
|
286
|
+
if (!url.host || ![
|
|
287
|
+
"https:",
|
|
288
|
+
"http:",
|
|
289
|
+
"ssh:"
|
|
290
|
+
].includes(url.protocol)) return;
|
|
291
|
+
const path = url.pathname.replace(/\/+$/, "").replace(/\.git$/, "").replace(/\/+$/, "");
|
|
292
|
+
return `https://${url.protocol === "ssh:" ? url.hostname : url.host}${path}`;
|
|
293
|
+
} catch {
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
static detect(options = {}) {
|
|
298
|
+
const env = options.env ?? process.env;
|
|
299
|
+
const candidates = [
|
|
300
|
+
{
|
|
301
|
+
version: this.#value(options.serviceVersion),
|
|
302
|
+
source: "config"
|
|
303
|
+
},
|
|
304
|
+
{
|
|
305
|
+
version: this.#value(env.MONOCLE_VERSION),
|
|
306
|
+
source: "env"
|
|
307
|
+
},
|
|
308
|
+
{
|
|
309
|
+
version: this.#value(env.APP_VERSION),
|
|
310
|
+
source: "env"
|
|
311
|
+
},
|
|
312
|
+
...this.#platformSources.map(([key, source]) => ({
|
|
313
|
+
version: this.#value(env[key]),
|
|
314
|
+
source
|
|
315
|
+
})),
|
|
316
|
+
...Object.entries(this.#ciSources).filter(([, provider]) => provider.detect(env)).flatMap(([name, provider]) => provider.revisions.map((key) => ({
|
|
317
|
+
version: name === "github" ? this.#githubRevision(env) : this.#value(env[key]),
|
|
318
|
+
source: `ci:${name}`
|
|
319
|
+
})))
|
|
320
|
+
];
|
|
321
|
+
const selected = candidates.find(({ version }) => version);
|
|
322
|
+
const knownSha = candidates.map(({ version }) => this.#sha(version)).find(Boolean);
|
|
323
|
+
const gitSha = knownSha ? void 0 : this.#gitRevision(options.cwd ?? process.cwd());
|
|
324
|
+
return {
|
|
325
|
+
version: selected?.version ?? gitSha,
|
|
326
|
+
source: selected?.source ?? (gitSha ? "git" : "none"),
|
|
327
|
+
commitSha: knownSha ?? gitSha,
|
|
328
|
+
repositoryUrl: this.#repositoryUrl(env),
|
|
329
|
+
deploymentId: [
|
|
330
|
+
env.RAILWAY_DEPLOYMENT_ID,
|
|
331
|
+
env.VERCEL_DEPLOYMENT_ID,
|
|
332
|
+
env.RENDER_DEPLOY_ID
|
|
333
|
+
].map((value) => this.#value(value)).find(Boolean)
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
static resourceAttributes(result) {
|
|
337
|
+
const attributes = { "monocle.service.version.source": result.source };
|
|
338
|
+
if (result.version) attributes["service.version"] = result.version;
|
|
339
|
+
if (result.commitSha) attributes["vcs.ref.head.revision"] = result.commitSha;
|
|
340
|
+
if (result.repositoryUrl) attributes["vcs.repository.url.full"] = result.repositoryUrl;
|
|
341
|
+
if (result.deploymentId) attributes["deployment.id"] = result.deploymentId;
|
|
342
|
+
return attributes;
|
|
343
|
+
}
|
|
344
|
+
};
|
|
345
|
+
//#endregion
|
|
6
346
|
//#region src/core/build_metadata.ts
|
|
7
347
|
/**
|
|
8
348
|
* Resolve the release with the same priority chain as the server agents, without spawning Git
|
|
@@ -30,7 +370,10 @@ var BuildMetadataResolver = class {
|
|
|
30
370
|
//#endregion
|
|
31
371
|
//#region src/core/release_injection.ts
|
|
32
372
|
/**
|
|
33
|
-
*
|
|
373
|
+
* Expose the release to the Browser SDK. Builds with an HTML page write it into a
|
|
374
|
+
* `<meta name="monocle-build">` tag: JavaScript chunks then stay byte-identical between builds of
|
|
375
|
+
* the same code, so their hashes and debug IDs do too. Other builds (SSR, library, dev server)
|
|
376
|
+
* prepend a global assignment to every module importing the Browser SDK.
|
|
34
377
|
*
|
|
35
378
|
* Import declarations are hoisted, so the SDK module is evaluated first, but it only reads the
|
|
36
379
|
* global when `initMonocle()` runs, which always happens after the importing module starts
|
|
@@ -42,6 +385,10 @@ var ReleaseInjection = class {
|
|
|
42
385
|
* Read by `@monocle.sh/browser-sdk`. Keep both sides in sync.
|
|
43
386
|
*/
|
|
44
387
|
static globalName = "__MONOCLE_BUILD__";
|
|
388
|
+
/**
|
|
389
|
+
* Name of the `<meta>` tag the Browser SDK reads when the global is not set.
|
|
390
|
+
*/
|
|
391
|
+
static metaName = "monocle-build";
|
|
45
392
|
static sdkImport = /["']@monocle\.sh\/browser-sdk(?:\/[^"'\s]*)?["']/;
|
|
46
393
|
/**
|
|
47
394
|
* Stylesheets, markup and data can mention the package without being JavaScript. Markup is
|
|
@@ -56,6 +403,15 @@ var ReleaseInjection = class {
|
|
|
56
403
|
static snippet(metadata) {
|
|
57
404
|
return `globalThis.${this.globalName}=${JSON.stringify(metadata)};`;
|
|
58
405
|
}
|
|
406
|
+
/**
|
|
407
|
+
* URI-encoded JSON: without quotes, it survives Vite 5, which does not escape attributes.
|
|
408
|
+
*/
|
|
409
|
+
static meta(metadata) {
|
|
410
|
+
return {
|
|
411
|
+
name: this.metaName,
|
|
412
|
+
content: encodeURIComponent(JSON.stringify(metadata))
|
|
413
|
+
};
|
|
414
|
+
}
|
|
59
415
|
static shouldInject(options) {
|
|
60
416
|
return !this.nonScriptIds.test(options.id) && this.sdkImport.test(options.code);
|
|
61
417
|
}
|
|
@@ -112,6 +468,13 @@ var MonocleBuild = class {
|
|
|
112
468
|
this.#log(value);
|
|
113
469
|
return value;
|
|
114
470
|
}
|
|
471
|
+
/**
|
|
472
|
+
* The release for the `<meta>` tag of an HTML page, when injection is on.
|
|
473
|
+
*/
|
|
474
|
+
pageMetadata() {
|
|
475
|
+
if (!this.enabled || this.#options.options.inject === false) return;
|
|
476
|
+
return this.resolve();
|
|
477
|
+
}
|
|
115
478
|
transform(options) {
|
|
116
479
|
if (!this.enabled || this.#options.options.inject === false) return;
|
|
117
480
|
if (!ReleaseInjection.shouldInject(options)) return;
|
|
@@ -232,12 +595,47 @@ var SourcemapsStep = class {
|
|
|
232
595
|
/**
|
|
233
596
|
* Translate Vite hooks to the bundler-agnostic Monocle build.
|
|
234
597
|
*/
|
|
235
|
-
var MonocleVitePlugin = class {
|
|
598
|
+
var MonocleVitePlugin = class MonocleVitePlugin {
|
|
599
|
+
/**
|
|
600
|
+
* Client builds whose every entry is an HTML page, the only ones where the release can live
|
|
601
|
+
* in the page instead of the chunks. Vite builds `index.html` without an explicit input.
|
|
602
|
+
* Frameworks rendering the HTML themselves set another input, per environment since Vite 6.
|
|
603
|
+
*/
|
|
604
|
+
static #htmlBuild(config) {
|
|
605
|
+
const { build } = config;
|
|
606
|
+
if (config.consumer === "server" || build.ssr || build.lib) return false;
|
|
607
|
+
const input = (build.rolldownOptions ?? build.rollupOptions)?.input;
|
|
608
|
+
if (input === void 0) return true;
|
|
609
|
+
const entries = typeof input === "string" ? [input] : Object.values(input);
|
|
610
|
+
return entries.length > 0 && entries.every((entry) => /\.html?$/.test(entry));
|
|
611
|
+
}
|
|
236
612
|
static create(options = {}) {
|
|
237
613
|
const plugin = { name: "@monocle.sh/vite-plugin" };
|
|
238
614
|
if (options.enabled === false) return plugin;
|
|
239
615
|
let build;
|
|
240
616
|
let resolved;
|
|
617
|
+
let sdkReadsPage;
|
|
618
|
+
/**
|
|
619
|
+
* Whether the release can go into the HTML pages of this build: a build with injection on,
|
|
620
|
+
* for an installed Browser SDK reading the page. Detected once, with a warning for older
|
|
621
|
+
* SDKs. Without the SDK, nothing reads a page tag: none is written.
|
|
622
|
+
*/
|
|
623
|
+
function pageSupported() {
|
|
624
|
+
if (!resolved || resolved.command !== "build" || options.inject === false) return false;
|
|
625
|
+
if (sdkReadsPage === void 0) {
|
|
626
|
+
const source = BrowserSdk.releaseSource(resolved.root);
|
|
627
|
+
sdkReadsPage = source === "page";
|
|
628
|
+
if (source === "bundle") resolved.logger.warn(`[monocle] @monocle.sh/browser-sdk < ${BrowserSdk.pageReleaseSince} reads the release from the bundle only, so every build changes all chunk hashes. Upgrade it to keep unchanged chunks identical.`);
|
|
629
|
+
}
|
|
630
|
+
return sdkReadsPage;
|
|
631
|
+
}
|
|
632
|
+
/**
|
|
633
|
+
* Whether modules of this environment leave the release to its HTML page. Vite 5 has no
|
|
634
|
+
* environments: the top-level configuration applies.
|
|
635
|
+
*/
|
|
636
|
+
function releaseInPage(environment) {
|
|
637
|
+
return !!resolved && MonocleVitePlugin.#htmlBuild(environment?.config ?? resolved) && pageSupported();
|
|
638
|
+
}
|
|
241
639
|
const sourcemaps = new SourcemapsStep({
|
|
242
640
|
options,
|
|
243
641
|
env: process.env
|
|
@@ -279,6 +677,7 @@ var MonocleVitePlugin = class {
|
|
|
279
677
|
code: ReleaseInjection.sdkImport
|
|
280
678
|
},
|
|
281
679
|
handler(code, id) {
|
|
680
|
+
if (releaseInPage(this.environment)) return;
|
|
282
681
|
return build?.transform({
|
|
283
682
|
code,
|
|
284
683
|
id
|
|
@@ -286,6 +685,20 @@ var MonocleVitePlugin = class {
|
|
|
286
685
|
}
|
|
287
686
|
},
|
|
288
687
|
/**
|
|
688
|
+
* Builds only call it for HTML entries. When some modules still got the global (mixed
|
|
689
|
+
* HTML and script entries), the SDK prefers the global, so the tag is harmless.
|
|
690
|
+
*/
|
|
691
|
+
transformIndexHtml() {
|
|
692
|
+
if (!pageSupported()) return;
|
|
693
|
+
const metadata = build?.pageMetadata();
|
|
694
|
+
if (!metadata) return;
|
|
695
|
+
return [{
|
|
696
|
+
tag: "meta",
|
|
697
|
+
attrs: ReleaseInjection.meta(metadata),
|
|
698
|
+
injectTo: "head-prepend"
|
|
699
|
+
}];
|
|
700
|
+
},
|
|
701
|
+
/**
|
|
289
702
|
* Sequential and last, so compression or copy plugins see the injected files.
|
|
290
703
|
*/
|
|
291
704
|
writeBundle: {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@monocle.sh/vite-plugin",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Vite plugin that injects your release into the Monocle Browser SDK and uploads source maps at build time",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"monocle",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
41
|
"magic-string": "^0.30.21",
|
|
42
|
-
"@monocle.sh/sourcemaps": "^0.
|
|
42
|
+
"@monocle.sh/sourcemaps": "^0.2.0"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
45
|
"@playwright/test": "^1.63.0",
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
"tsdown": "^0.21.10",
|
|
50
50
|
"vite": "^8.3.4",
|
|
51
51
|
"vue": "^3.5.44",
|
|
52
|
-
"@monocle.sh/browser-sdk": "0.
|
|
52
|
+
"@monocle.sh/browser-sdk": "0.13.0",
|
|
53
53
|
"@monocle.sh/otel-utils": "1.9.0"
|
|
54
54
|
},
|
|
55
55
|
"peerDependencies": {
|