@crxjs/vite-plugin 2.0.0-beta.11 → 2.0.0-beta.13

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.
Files changed (2) hide show
  1. package/dist/index.mjs +44 -8
  2. package/package.json +4 -4
package/dist/index.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import { simple } from 'acorn-walk';
2
2
  import { createHash } from 'crypto';
3
- import debug$2 from 'debug';
3
+ import debug$3 from 'debug';
4
4
  import v8 from 'v8';
5
5
  import { posix } from 'path';
6
6
  import { Subject, filter, ReplaySubject, switchMap, of, startWith, map, mergeMap, firstValueFrom, takeUntil, first, toArray, retry, concatWith, Subscription, buffer } from 'rxjs';
@@ -24,8 +24,10 @@ const pluginOptionsProvider = (options) => {
24
24
  name: pluginName$1,
25
25
  api: {
26
26
  crx: {
27
+ // during testing this can be null, we don't provide options through the test config
27
28
  options
28
29
  }
30
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
29
31
  }
30
32
  };
31
33
  };
@@ -58,7 +60,7 @@ function isCrxPlugin(p) {
58
60
 
59
61
  var workerHmrClient = "const ownOrigin = new URL(chrome.runtime.getURL(\"/\")).origin;\nself.addEventListener(\"fetch\", (fetchEvent) => {\n const url = new URL(fetchEvent.request.url);\n if (url.origin === ownOrigin) {\n fetchEvent.respondWith(sendToServer(url));\n }\n});\nasync function sendToServer(url) {\n url.protocol = \"http:\";\n url.host = \"localhost\";\n url.port = __SERVER_PORT__;\n url.searchParams.set(\"t\", Date.now().toString());\n const response = await fetch(url.href.replace(/=$|=(?=&)/g, \"\"));\n return new Response(response.body, {\n headers: {\n \"Content-Type\": response.headers.get(\"Content-Type\") ?? \"text/javascript\"\n }\n });\n}\nconst ports = /* @__PURE__ */ new Set();\nchrome.runtime.onConnect.addListener((port) => {\n if (port.name === \"@crx/client\") {\n ports.add(port);\n port.onDisconnect.addListener((port2) => ports.delete(port2));\n port.onMessage.addListener((message) => {\n });\n port.postMessage({ data: JSON.stringify({ type: \"connected\" }) });\n }\n});\nfunction notifyContentScripts(payload) {\n const data = JSON.stringify(payload);\n for (const port of ports)\n port.postMessage({ data });\n}\nconsole.log(\"[vite] connecting...\");\nconst socketProtocol = __HMR_PROTOCOL__ || (location.protocol === \"https:\" ? \"wss\" : \"ws\");\nconst socketHost = `${__HMR_HOSTNAME__ || location.hostname}:${__HMR_PORT__}`;\nconst socket = new WebSocket(`${socketProtocol}://${socketHost}`, \"vite-hmr\");\nconst base = __BASE__ || \"/\";\nsocket.addEventListener(\"message\", async ({ data }) => {\n handleSocketMessage(JSON.parse(data));\n});\nfunction isCrxHmrPayload(x) {\n return x.type === \"custom\" && x.event.startsWith(\"crx:\");\n}\nfunction handleSocketMessage(payload) {\n if (isCrxHmrPayload(payload)) {\n handleCrxHmrPayload(payload);\n } else if (payload.type === \"connected\") {\n console.log(`[vite] connected.`);\n const interval = setInterval(() => socket.send(\"ping\"), __HMR_TIMEOUT__);\n socket.addEventListener(\"close\", () => clearInterval(interval));\n }\n}\nfunction handleCrxHmrPayload(payload) {\n notifyContentScripts(payload);\n switch (payload.event) {\n case \"crx:runtime-reload\":\n console.log(\"[crx] runtime reload\");\n chrome.runtime.reload();\n break;\n }\n}\nasync function waitForSuccessfulPing(ms = 1e3) {\n while (true) {\n try {\n await fetch(`${base}__vite_ping`);\n break;\n } catch (e) {\n await new Promise((resolve) => setTimeout(resolve, ms));\n }\n }\n}\nsocket.addEventListener(\"close\", async ({ wasClean }) => {\n if (wasClean)\n return;\n console.log(`[vite] server connection lost. polling for restart...`);\n await waitForSuccessfulPing();\n handleCrxHmrPayload({\n type: \"custom\",\n event: \"crx:runtime-reload\"\n });\n});\n";
60
62
 
61
- const _debug = (id) => debug$2("crx").extend(id);
63
+ const _debug = (id) => debug$3("crx").extend(id);
62
64
  const structuredClone = (obj) => {
63
65
  return v8.deserialize(v8.serialize(obj));
64
66
  };
@@ -204,7 +206,7 @@ function formatFileData(script) {
204
206
  return script;
205
207
  }
206
208
  function getFileName({ type, id }) {
207
- let fileName = id.replace(/t=\d+&/, "").replace(/^\//, "").replace(/\?/g, "__").replace(/&/g, "_").replace(/=/g, "--");
209
+ let fileName = id.replace(/t=\d+&/, "").replace(/\?t=\d+$/, "").replace(/^\//, "").replace(/\?/g, "__").replace(/&/g, "_").replace(/=/g, "--");
208
210
  if (fileName.includes("node_modules/")) {
209
211
  fileName = `vendor/${fileName.split("node_modules/").pop().replace(/\//g, "-")}`;
210
212
  } else if (fileName.startsWith("@")) {
@@ -292,6 +294,7 @@ const pluginBackground = () => {
292
294
  },
293
295
  {
294
296
  name: "crx:background-loader-file",
297
+ // this should happen after other plugins; the loader file is an implementation detail
295
298
  enforce: "post",
296
299
  configResolved(_config) {
297
300
  config = _config;
@@ -318,6 +321,7 @@ const pluginBackground = () => {
318
321
  }
319
322
  const refId = this.emitFile({
320
323
  type: "asset",
324
+ // fileName b/c service worker must be at root of crx
321
325
  fileName: getFileName({ type: "loader", id: "service-worker" }),
322
326
  source: loader
323
327
  });
@@ -426,6 +430,7 @@ function prepAsset(fileName, { id, source }) {
426
430
  }
427
431
  function prepScript(fileName, script) {
428
432
  return ($) => $.pipe(
433
+ // get script contents from dev server
429
434
  mergeMap(async ({ server }) => {
430
435
  const target = getOutputPath(server, fileName);
431
436
  const viteUrl = getViteUrl(script);
@@ -435,7 +440,9 @@ function prepScript(fileName, script) {
435
440
  const { code, deps = [], dynamicDeps = [] } = transformResult;
436
441
  return { target, code, deps: [...deps, ...dynamicDeps].flat(), server };
437
442
  }),
443
+ // retry in case of dependency rebundle
438
444
  retry({ count: 10, delay: 100 }),
445
+ // patch content scripts
439
446
  mergeMap(async ({ target, server, ...rest }) => {
440
447
  const plugins = server.config.plugins;
441
448
  let { code, deps } = rest;
@@ -451,10 +458,14 @@ function prepScript(fileName, script) {
451
458
  const [imports] = lexer.parse(code, fileName);
452
459
  const depSet = new Set(deps);
453
460
  const magic = new MagicString(code);
461
+ const now = Date.now();
454
462
  for (const i of imports)
455
463
  if (i.n) {
456
464
  depSet.add(i.n);
457
- const fileName2 = getFileName({ type: "module", id: i.n });
465
+ let fileName2 = getFileName({ type: "module", id: i.n });
466
+ if (!fileName2.startsWith("vendor")) {
467
+ fileName2 = `${fileName2}?t=${now}`;
468
+ }
458
469
  const fullImport = code.substring(i.s, i.e);
459
470
  magic.overwrite(i.s, i.e, fullImport.replace(i.n, `/${fileName2}`));
460
471
  }
@@ -466,7 +477,7 @@ async function allFilesReady() {
466
477
  await firstValueFrom(allFilesReady$);
467
478
  }
468
479
 
469
- const { outputFile } = fsx;
480
+ const { outputFile, remove } = fsx;
470
481
  _debug("file-writer");
471
482
  async function start({
472
483
  server
@@ -487,6 +498,8 @@ async function start({
487
498
  dir: outDir,
488
499
  format: "es"
489
500
  };
501
+ if (server.config.build.emptyOutDir)
502
+ await remove(server.config.build.outDir);
490
503
  fileWriterEvent$.next({ type: "build_start" });
491
504
  const build = await rollup(inputOptions);
492
505
  await build.write(outputOptions);
@@ -527,8 +540,11 @@ function update(_id) {
527
540
  async function write(fileId) {
528
541
  const start2 = performance.now();
529
542
  const deps = await firstValueFrom(
543
+ // wait for start event
530
544
  start$.pipe(
545
+ // prepare either asset or script contents
531
546
  prepFileData(fileId),
547
+ // output file and add dependencies to file writer
532
548
  mergeMap(async ({ target, source, deps: deps2 }) => {
533
549
  const files = deps2.map((id) => {
534
550
  const r = [add({ id, type: "module" })];
@@ -544,6 +560,7 @@ async function write(fileId) {
544
560
  await outputFile(target, source, { encoding: "utf8" });
545
561
  return files;
546
562
  }),
563
+ // abort write operation on close event
547
564
  takeUntil(close$),
548
565
  concatWith(of([]))
549
566
  )
@@ -816,6 +833,12 @@ const pluginDynamicContentScripts = () => {
816
833
  {
817
834
  name: "crx:dynamic-content-scripts-build",
818
835
  apply: "build",
836
+ /**
837
+ * Replace dynamic script placeholders during build.
838
+ *
839
+ * Can't use `renderChunk` b/c pre plugin crx:content-scripts uses
840
+ * `generateBundle` to emit loaders. Must come after "enforce: pre".
841
+ */
819
842
  generateBundle(options, bundle) {
820
843
  for (const chunk of Object.values(bundle))
821
844
  if (chunk.type === "chunk") {
@@ -982,11 +1005,13 @@ const pluginFileWriterPublic = () => {
982
1005
  };
983
1006
  };
984
1007
 
985
- _debug("file-writer").extend("hmr");
986
- const isCrxHMRPayload = (p) => p.type === "custom" && p.event.startsWith("crx:");
1008
+ const debug$2 = _debug("file-writer").extend("hmr");
1009
+ const isCustomPayload = (p) => {
1010
+ return p.type === "custom";
1011
+ };
987
1012
  const hmrPayload$ = new Subject();
988
1013
  const crxHMRPayload$ = hmrPayload$.pipe(
989
- filter((p) => !isCrxHMRPayload(p)),
1014
+ filter((p) => !isCustomPayload(p)),
990
1015
  buffer(allFilesReady$),
991
1016
  mergeMap((pps) => {
992
1017
  let fullReload;
@@ -1045,6 +1070,7 @@ const crxHMRPayload$ = hmrPayload$.pipe(
1045
1070
  }
1046
1071
  }),
1047
1072
  map((data) => {
1073
+ debug$2(`hmr payload`, data);
1048
1074
  return {
1049
1075
  type: "custom",
1050
1076
  event: "crx:content-script-payload",
@@ -1084,6 +1110,7 @@ const pluginHMR = () => {
1084
1110
  name: "crx:hmr",
1085
1111
  apply: "serve",
1086
1112
  enforce: "pre",
1113
+ // server hmr host should be localhost
1087
1114
  async config({ server = {}, ...config2 }) {
1088
1115
  if (server.hmr === false)
1089
1116
  return;
@@ -1093,6 +1120,7 @@ const pluginHMR = () => {
1093
1120
  server.hmr.host = "localhost";
1094
1121
  return { server, ...config2 };
1095
1122
  },
1123
+ // server should ignore outdir
1096
1124
  configResolved(_config) {
1097
1125
  config = _config;
1098
1126
  const { watch = {} } = config.server;
@@ -1130,6 +1158,7 @@ const pluginHMR = () => {
1130
1158
  closeBundle() {
1131
1159
  subs.unsubscribe();
1132
1160
  },
1161
+ // background changes require a full extension reload
1133
1162
  handleHotUpdate({ modules, server }) {
1134
1163
  const { root } = server.config;
1135
1164
  const relFiles = /* @__PURE__ */ new Set();
@@ -1157,6 +1186,7 @@ const pluginHMR = () => {
1157
1186
  name: "crx:hmr",
1158
1187
  apply: "serve",
1159
1188
  enforce: "post",
1189
+ // get final output manifest for handleHotUpdate 👆
1160
1190
  async transformCrxManifest(manifest) {
1161
1191
  inputManifestFiles = await manifestFiles(manifest, { cwd: config.root });
1162
1192
  return null;
@@ -1247,6 +1277,7 @@ const pluginHtmlInlineScripts = () => {
1247
1277
  };
1248
1278
  const postPlugin = {
1249
1279
  name: "crx:html-auditor-post",
1280
+ // this hook isn't audited b/c we add it after we set up the auditors
1250
1281
  transformIndexHtml(html, ctx) {
1251
1282
  const key = toKey(ctx);
1252
1283
  const p = pages.get(key);
@@ -1591,6 +1622,7 @@ Public dir: "${config.publicDir}"`
1591
1622
  this.emitFile({
1592
1623
  type: "asset",
1593
1624
  fileName: f,
1625
+ // TODO: cache source buffer
1594
1626
  source: await readFile(filename)
1595
1627
  });
1596
1628
  }
@@ -1703,8 +1735,11 @@ const pluginWebAccessibleResources = () => {
1703
1735
  ...rest
1704
1736
  })).filter(({ resources }) => resources.length);
1705
1737
  manifest.web_accessible_resources.push({
1738
+ // change the extension origin on every reload
1706
1739
  use_dynamic_url: true,
1740
+ // all web origins can access
1707
1741
  matches: ["<all_urls>"],
1742
+ // all resources are web accessible
1708
1743
  resources: ["**/*", "*"]
1709
1744
  });
1710
1745
  return manifest;
@@ -1834,6 +1869,7 @@ const pluginWebAccessibleResources = () => {
1834
1869
  };
1835
1870
 
1836
1871
  const crx = (options) => {
1872
+ contentScripts.clear();
1837
1873
  return [
1838
1874
  pluginOptionsProvider(options),
1839
1875
  pluginBackground(),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crxjs/vite-plugin",
3
- "version": "2.0.0-beta.11",
3
+ "version": "2.0.0-beta.13",
4
4
  "description": "Build Chrome Extensions with this Vite plugin.",
5
5
  "keywords": [
6
6
  "rollup-plugin",
@@ -101,7 +101,7 @@
101
101
  "@vitejs/plugin-react": "^2.1.0",
102
102
  "@vitejs/plugin-vue": "3.2.0",
103
103
  "chokidar": "^3.5.3",
104
- "esbuild": "0.15.16",
104
+ "esbuild": "0.17.4",
105
105
  "esbuild-runner": "2.2.2",
106
106
  "eslint": "8.26.0",
107
107
  "eslint-plugin-react": "^7.29.4",
@@ -116,8 +116,8 @@
116
116
  "svelte": "^3.48.0",
117
117
  "typescript": "^4.6.4",
118
118
  "vite": "^3.1.7",
119
- "vite-plugin-inspect": "0.7.5",
120
- "vitest": "0.24.3",
119
+ "vite-plugin-inspect": "0.7.14",
120
+ "vitest": "0.28.5",
121
121
  "vue": "3.2.41"
122
122
  }
123
123
  }