@crxjs/vite-plugin 1.0.3 → 1.0.6

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/CHANGELOG.md CHANGED
@@ -1,5 +1,34 @@
1
1
  # @crxjs/vite-plugin
2
2
 
3
+ ## 1.0.6
4
+
5
+ ### Patch Changes
6
+
7
+ - 63d102f: Automatically ignores `build.outDir` for server HMR, so the file
8
+ writer doesn't trigger a full reload.
9
+
10
+ Fixes flaky HMR updates for content scripts; Tailwind should work fine now 🥳
11
+
12
+ - a1e2728: Fix isImporter recursion
13
+
14
+ ## 1.0.5
15
+
16
+ ### Patch Changes
17
+
18
+ - 86adbec: Sometimes during development, an extension page may open before the
19
+ service worker has a chance to control fetch. The HTML file will load from the
20
+ file system, but the script tag might load from the dev server. This PR adds a
21
+ precontroller loader plugin to the dev server so that the extension page will
22
+ reload and the fetch handler will get the real HTML file from the server.
23
+
24
+ ## 1.0.4
25
+
26
+ ### Patch Changes
27
+
28
+ - b83a4bd: Check for manifest assets first in the project root, then check in
29
+ the public dir. Throw an informative error if the file does not exist in
30
+ either dir.
31
+
3
32
  ## 1.0.3
4
33
 
5
34
  ### Patch Changes
package/dist/index.cjs CHANGED
@@ -17,6 +17,7 @@ var vite = require('vite');
17
17
  var module$1 = require('module');
18
18
  var cheerio = require('cheerio');
19
19
  var jsesc = require('jsesc');
20
+ var fs = require('fs');
20
21
  var injector = require('connect-injector');
21
22
 
22
23
  function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
@@ -184,6 +185,7 @@ const setOutputMeta = ({
184
185
  outputByOwner.set(ownerName, output);
185
186
  outputById.set(id, output);
186
187
  };
188
+ const transformResultByOwner = /* @__PURE__ */ new Map();
187
189
 
188
190
  const {
189
191
  basename,
@@ -230,17 +232,12 @@ function sourceToUrlMeta(source) {
230
232
  }
231
233
  const pluginFileWriterChunks = () => {
232
234
  let server;
233
- const ownerToTransformResultMap = /* @__PURE__ */ new Map();
234
235
  return {
235
236
  name: "crx:file-writer-chunks",
236
237
  apply: "build",
237
238
  fileWriterStart(_server) {
238
239
  server = _server;
239
240
  },
240
- watchChange(fileName) {
241
- for (const owner of ownersByFile.get(fileName) ?? /* @__PURE__ */ new Set())
242
- ownerToTransformResultMap.delete(owner);
243
- },
244
241
  async resolveId(source, importer) {
245
242
  if (this.meta.watchMode) {
246
243
  if (idBySource.has(source)) {
@@ -276,12 +273,12 @@ const pluginFileWriterChunks = () => {
276
273
  if (!serverModule)
277
274
  throw new Error(`Unable to load "${url}" from server.`);
278
275
  const { file, url: owner } = serverModule;
279
- transformResult = transformResult ?? ownerToTransformResultMap.get(owner) ?? serverModule.transformResult;
276
+ transformResult = transformResult ?? transformResultByOwner.get(owner) ?? serverModule.transformResult;
280
277
  if (!transformResult)
281
278
  transformResult = await server.transformRequest(url);
282
279
  if (!transformResult)
283
280
  throw new TypeError(`Unable to load "${url}" from server.`);
284
- ownerToTransformResultMap.set(owner, transformResult);
281
+ transformResultByOwner.set(owner, transformResult);
285
282
  if (file) {
286
283
  setFileMeta({ id, file });
287
284
  this.addWatchFile(file);
@@ -368,7 +365,7 @@ const pluginFileWriterChunks = () => {
368
365
  };
369
366
  };
370
367
 
371
- /*! *****************************************************************************
368
+ /******************************************************************************
372
369
  Copyright (c) Microsoft Corporation.
373
370
 
374
371
  Permission to use, copy, modify, and/or distribute this software for any
@@ -1480,6 +1477,12 @@ function last(arr) {
1480
1477
  function popResultSelector(args) {
1481
1478
  return isFunction(last(args)) ? args.pop() : undefined;
1482
1479
  }
1480
+ function popScheduler(args) {
1481
+ return isScheduler(last(args)) ? args.pop() : undefined;
1482
+ }
1483
+ function popNumber(args, defaultValue) {
1484
+ return typeof last(args) === 'number' ? args.pop() : defaultValue;
1485
+ }
1483
1486
 
1484
1487
  var isArrayLike = (function (x) { return x && typeof x.length === 'number' && typeof x !== 'function'; });
1485
1488
 
@@ -1696,6 +1699,126 @@ function executeSchedule(parentSubscription, scheduler, work, delay, repeat) {
1696
1699
  }
1697
1700
  }
1698
1701
 
1702
+ function observeOn(scheduler, delay) {
1703
+ if (delay === void 0) { delay = 0; }
1704
+ return operate(function (source, subscriber) {
1705
+ source.subscribe(createOperatorSubscriber(subscriber, function (value) { return executeSchedule(subscriber, scheduler, function () { return subscriber.next(value); }, delay); }, function () { return executeSchedule(subscriber, scheduler, function () { return subscriber.complete(); }, delay); }, function (err) { return executeSchedule(subscriber, scheduler, function () { return subscriber.error(err); }, delay); }));
1706
+ });
1707
+ }
1708
+
1709
+ function subscribeOn(scheduler, delay) {
1710
+ if (delay === void 0) { delay = 0; }
1711
+ return operate(function (source, subscriber) {
1712
+ subscriber.add(scheduler.schedule(function () { return source.subscribe(subscriber); }, delay));
1713
+ });
1714
+ }
1715
+
1716
+ function scheduleObservable(input, scheduler) {
1717
+ return innerFrom(input).pipe(subscribeOn(scheduler), observeOn(scheduler));
1718
+ }
1719
+
1720
+ function schedulePromise(input, scheduler) {
1721
+ return innerFrom(input).pipe(subscribeOn(scheduler), observeOn(scheduler));
1722
+ }
1723
+
1724
+ function scheduleArray(input, scheduler) {
1725
+ return new Observable(function (subscriber) {
1726
+ var i = 0;
1727
+ return scheduler.schedule(function () {
1728
+ if (i === input.length) {
1729
+ subscriber.complete();
1730
+ }
1731
+ else {
1732
+ subscriber.next(input[i++]);
1733
+ if (!subscriber.closed) {
1734
+ this.schedule();
1735
+ }
1736
+ }
1737
+ });
1738
+ });
1739
+ }
1740
+
1741
+ function scheduleIterable(input, scheduler) {
1742
+ return new Observable(function (subscriber) {
1743
+ var iterator$1;
1744
+ executeSchedule(subscriber, scheduler, function () {
1745
+ iterator$1 = input[iterator]();
1746
+ executeSchedule(subscriber, scheduler, function () {
1747
+ var _a;
1748
+ var value;
1749
+ var done;
1750
+ try {
1751
+ (_a = iterator$1.next(), value = _a.value, done = _a.done);
1752
+ }
1753
+ catch (err) {
1754
+ subscriber.error(err);
1755
+ return;
1756
+ }
1757
+ if (done) {
1758
+ subscriber.complete();
1759
+ }
1760
+ else {
1761
+ subscriber.next(value);
1762
+ }
1763
+ }, 0, true);
1764
+ });
1765
+ return function () { return isFunction(iterator$1 === null || iterator$1 === void 0 ? void 0 : iterator$1.return) && iterator$1.return(); };
1766
+ });
1767
+ }
1768
+
1769
+ function scheduleAsyncIterable(input, scheduler) {
1770
+ if (!input) {
1771
+ throw new Error('Iterable cannot be null');
1772
+ }
1773
+ return new Observable(function (subscriber) {
1774
+ executeSchedule(subscriber, scheduler, function () {
1775
+ var iterator = input[Symbol.asyncIterator]();
1776
+ executeSchedule(subscriber, scheduler, function () {
1777
+ iterator.next().then(function (result) {
1778
+ if (result.done) {
1779
+ subscriber.complete();
1780
+ }
1781
+ else {
1782
+ subscriber.next(result.value);
1783
+ }
1784
+ });
1785
+ }, 0, true);
1786
+ });
1787
+ });
1788
+ }
1789
+
1790
+ function scheduleReadableStreamLike(input, scheduler) {
1791
+ return scheduleAsyncIterable(readableStreamLikeToAsyncGenerator(input), scheduler);
1792
+ }
1793
+
1794
+ function scheduled(input, scheduler) {
1795
+ if (input != null) {
1796
+ if (isInteropObservable(input)) {
1797
+ return scheduleObservable(input, scheduler);
1798
+ }
1799
+ if (isArrayLike(input)) {
1800
+ return scheduleArray(input, scheduler);
1801
+ }
1802
+ if (isPromise(input)) {
1803
+ return schedulePromise(input, scheduler);
1804
+ }
1805
+ if (isAsyncIterable(input)) {
1806
+ return scheduleAsyncIterable(input, scheduler);
1807
+ }
1808
+ if (isIterable(input)) {
1809
+ return scheduleIterable(input, scheduler);
1810
+ }
1811
+ if (isReadableStreamLike(input)) {
1812
+ return scheduleReadableStreamLike(input, scheduler);
1813
+ }
1814
+ }
1815
+ throw createInvalidObservableTypeError(input);
1816
+ }
1817
+
1818
+ function from(input, scheduler) {
1819
+ return scheduler ? scheduled(input, scheduler) : innerFrom(input);
1820
+ }
1821
+
1699
1822
  var EmptyError = createErrorClass(function (_super) { return function EmptyErrorImpl() {
1700
1823
  _super(this);
1701
1824
  this.name = 'EmptyError';
@@ -1806,6 +1929,11 @@ function mergeMap(project, resultSelector, concurrent) {
1806
1929
  return operate(function (source, subscriber) { return mergeInternals(source, subscriber, project, concurrent); });
1807
1930
  }
1808
1931
 
1932
+ function mergeAll(concurrent) {
1933
+ if (concurrent === void 0) { concurrent = Infinity; }
1934
+ return mergeMap(identity, concurrent);
1935
+ }
1936
+
1809
1937
  function timer(dueTime, intervalOrScheduler, scheduler) {
1810
1938
  if (dueTime === void 0) { dueTime = 0; }
1811
1939
  if (scheduler === void 0) { scheduler = async; }
@@ -1838,13 +1966,22 @@ function timer(dueTime, intervalOrScheduler, scheduler) {
1838
1966
  });
1839
1967
  }
1840
1968
 
1841
- function interval(period, scheduler) {
1842
- if (period === void 0) { period = 0; }
1843
- if (scheduler === void 0) { scheduler = asyncScheduler; }
1844
- if (period < 0) {
1845
- period = 0;
1969
+ function merge() {
1970
+ var args = [];
1971
+ for (var _i = 0; _i < arguments.length; _i++) {
1972
+ args[_i] = arguments[_i];
1846
1973
  }
1847
- return timer(period, period, scheduler);
1974
+ var scheduler = popScheduler(args);
1975
+ var concurrent = popNumber(args, Infinity);
1976
+ var sources = args;
1977
+ return !sources.length
1978
+ ?
1979
+ EMPTY
1980
+ : sources.length === 1
1981
+ ?
1982
+ innerFrom(sources[0])
1983
+ :
1984
+ mergeAll(concurrent)(from(sources, scheduler));
1848
1985
  }
1849
1986
 
1850
1987
  function filter(predicate, thisArg) {
@@ -1872,6 +2009,36 @@ function buffer(closingNotifier) {
1872
2009
  });
1873
2010
  }
1874
2011
 
2012
+ function debounce(durationSelector) {
2013
+ return operate(function (source, subscriber) {
2014
+ var hasValue = false;
2015
+ var lastValue = null;
2016
+ var durationSubscriber = null;
2017
+ var emit = function () {
2018
+ durationSubscriber === null || durationSubscriber === void 0 ? void 0 : durationSubscriber.unsubscribe();
2019
+ durationSubscriber = null;
2020
+ if (hasValue) {
2021
+ hasValue = false;
2022
+ var value = lastValue;
2023
+ lastValue = null;
2024
+ subscriber.next(value);
2025
+ }
2026
+ };
2027
+ source.subscribe(createOperatorSubscriber(subscriber, function (value) {
2028
+ durationSubscriber === null || durationSubscriber === void 0 ? void 0 : durationSubscriber.unsubscribe();
2029
+ hasValue = true;
2030
+ lastValue = value;
2031
+ durationSubscriber = createOperatorSubscriber(subscriber, emit, noop);
2032
+ innerFrom(durationSelector(value)).subscribe(durationSubscriber);
2033
+ }, function () {
2034
+ emit();
2035
+ subscriber.complete();
2036
+ }, undefined, function () {
2037
+ lastValue = durationSubscriber = null;
2038
+ }));
2039
+ });
2040
+ }
2041
+
1875
2042
  function defaultIfEmpty(defaultValue) {
1876
2043
  return operate(function (source, subscriber) {
1877
2044
  var hasValue = false;
@@ -1904,10 +2071,6 @@ function take(count) {
1904
2071
  });
1905
2072
  }
1906
2073
 
1907
- function mapTo(value) {
1908
- return map(function () { return value; });
1909
- }
1910
-
1911
2074
  function throwIfEmpty(errorFactory) {
1912
2075
  if (errorFactory === void 0) { errorFactory = defaultErrorFactory; }
1913
2076
  return operate(function (source, subscriber) {
@@ -2002,7 +2165,7 @@ const filesStart$ = writerEvent$.pipe(filter((x) => {
2002
2165
  const filesStart = () => firstValueFrom(filesStart$);
2003
2166
  const filesReady$ = writerEvent$.pipe(filter((x) => {
2004
2167
  return x.type === "writeBundle";
2005
- }), switchMap((event) => interval(100).pipe(mapTo(event), first(({ bundle, options, timestamp }) => {
2168
+ }), switchMap((event) => timer(0, 100).pipe(map(() => event), first(({ bundle, options, timestamp }) => {
2006
2169
  const result = Object.keys(bundle).every((p) => {
2007
2170
  const stats = statSync(join(options.dir, p));
2008
2171
  return stats.mtimeMs > timestamp;
@@ -2093,27 +2256,40 @@ const pluginFileWriterEvents = () => {
2093
2256
  };
2094
2257
  };
2095
2258
 
2096
- var preControllerScript = "const id = setInterval(() => location.reload(), 100);\nsetTimeout(() => clearInterval(id), 5e3);\n";
2259
+ var precontrollerScript = "const id = setInterval(() => location.reload(), 100);\nsetTimeout(() => clearInterval(id), 5e3);\n";
2097
2260
 
2098
- var preControllerHtml = "<!DOCTYPE html>\n<html lang=\"en\">\n <head>\n <title>Waiting for the extension service worker...</title>\n <script src=\"%PATH%\"></script>\n </head>\n <body>\n <h1>Waiting for service worker</h1>\n\n <p>\n If you see this message, it means the service worker has not loaded fully.\n </p>\n\n <p>\n During development, the service worker reroutes HTML requests to the dev\n server, so this file isn't used unless the extension service worker opens\n a page immediately after a full extension reload, and before the service\n worker takes control of fetch (e.g., in the onInstalled event). In that\n case, this page will continuously reload until the service worker is\n ready, always less than 100 ms.\n </p>\n\n <p>This page is never added in production.</p>\n </body>\n</html>\n";
2261
+ var precontrollerHtml = "<!DOCTYPE html>\n<html lang=\"en\">\n <head>\n <title>Waiting for the extension service worker...</title>\n <script src=\"%PATH%\"></script>\n </head>\n <body>\n <h1>Waiting for service worker</h1>\n\n <p>\n If you see this message, it means the service worker has not loaded fully.\n </p>\n\n <p>This page is never added in production.</p>\n </body>\n</html>\n";
2099
2262
 
2100
2263
  const pluginFileWriterHtml = () => {
2264
+ let precontrollerName;
2101
2265
  return {
2102
2266
  name: "crx:file-writer-html",
2103
2267
  apply: "build",
2268
+ fileWriterStart(server) {
2269
+ const plugins = server.config.plugins;
2270
+ const i = plugins.findIndex(({ name }) => name === "alias");
2271
+ plugins.splice(i, 0, {
2272
+ name: "crx:load-precontroller",
2273
+ apply: "serve",
2274
+ load(id) {
2275
+ if (id === `/${precontrollerName}`)
2276
+ return "location.reload();";
2277
+ }
2278
+ });
2279
+ },
2104
2280
  renderCrxManifest(manifest) {
2105
2281
  if (this.meta.watchMode) {
2106
2282
  const refId = this.emitFile({
2107
2283
  type: "asset",
2108
2284
  name: "precontroller.js",
2109
- source: preControllerScript
2285
+ source: precontrollerScript
2110
2286
  });
2111
- const name = this.getFileName(refId);
2287
+ precontrollerName = this.getFileName(refId);
2112
2288
  for (const fileName of htmlFiles(manifest)) {
2113
2289
  this.emitFile({
2114
2290
  type: "asset",
2115
2291
  fileName,
2116
- source: preControllerHtml.replace("%PATH%", `/${name}`)
2292
+ source: precontrollerHtml.replace("%PATH%", `/${precontrollerName}`)
2117
2293
  });
2118
2294
  }
2119
2295
  }
@@ -2138,7 +2314,6 @@ const pluginFileWriterPublic = () => {
2138
2314
  const source = await fsExtra.readFile(file);
2139
2315
  const fileName = relative(config.publicDir, file);
2140
2316
  this.emitFile({ type: "asset", fileName, source });
2141
- this.addWatchFile(file);
2142
2317
  }
2143
2318
  }
2144
2319
  }
@@ -2173,6 +2348,94 @@ const pluginFileWriterPolyfill = () => {
2173
2348
  };
2174
2349
  };
2175
2350
 
2351
+ function isUpdatePayload(p) {
2352
+ return p.type === "update";
2353
+ }
2354
+ function isFullReloadPayload(p) {
2355
+ return p.type === "full-reload";
2356
+ }
2357
+ function isPrunePayload(p) {
2358
+ return p.type === "prune";
2359
+ }
2360
+ function isCrxHMRPayload(x) {
2361
+ return x.type === "custom" && x.event.startsWith("crx:");
2362
+ }
2363
+ const hmrPayload$ = new Subject();
2364
+ const hmrPrune$ = hmrPayload$.pipe(filter(isPrunePayload));
2365
+ const hmrFullReload$ = hmrPayload$.pipe(filter(isFullReloadPayload));
2366
+ const hmrUpdate$ = hmrPayload$.pipe(filter(isUpdatePayload));
2367
+ const payload$ = merge(hmrFullReload$, hmrPrune$, hmrUpdate$);
2368
+ const rebuildSignal$ = payload$.pipe(buffer(payload$.pipe(debounce(() => filesReady$))), map((payloads) => {
2369
+ if (payloads.every(isUpdatePayload)) {
2370
+ const owners = /* @__PURE__ */ new Set();
2371
+ for (const { updates } of payloads)
2372
+ for (const { path } of updates)
2373
+ if (transformResultByOwner.has(path))
2374
+ owners.add(path);
2375
+ return { type: "partial", owners };
2376
+ }
2377
+ return { type: "full" };
2378
+ }), filter((rebuild) => rebuild.type === "partial" ? rebuild.owners.size > 0 : true));
2379
+ const crxHmrPayload$ = hmrPayload$.pipe(filter((p) => !isCrxHMRPayload(p)), buffer(filesReady$), mergeMap((pps) => {
2380
+ let fullReload;
2381
+ const payloads = [];
2382
+ for (const p of pps.slice(-50))
2383
+ if (p.type === "full-reload") {
2384
+ fullReload = p;
2385
+ } else {
2386
+ payloads.push(p);
2387
+ }
2388
+ if (fullReload)
2389
+ payloads.push(fullReload);
2390
+ return payloads;
2391
+ }), map((p) => {
2392
+ switch (p.type) {
2393
+ case "full-reload": {
2394
+ const path = p.path && outputByOwner.get(p.path);
2395
+ const fullReload = {
2396
+ type: "full-reload",
2397
+ path
2398
+ };
2399
+ return fullReload;
2400
+ }
2401
+ case "prune": {
2402
+ const paths = [];
2403
+ for (const owner of p.paths)
2404
+ if (outputByOwner.has(owner))
2405
+ paths.push(outputByOwner.get(owner));
2406
+ return { type: "prune", paths };
2407
+ }
2408
+ case "update": {
2409
+ const updates = [];
2410
+ for (const { acceptedPath, path, ...rest } of p.updates)
2411
+ if (outputByOwner.has(acceptedPath) && outputByOwner.has(path))
2412
+ updates.push({
2413
+ ...rest,
2414
+ acceptedPath: outputByOwner.get(acceptedPath),
2415
+ path: outputByOwner.get(path)
2416
+ });
2417
+ return { type: "update", updates };
2418
+ }
2419
+ default:
2420
+ return p;
2421
+ }
2422
+ }), withLatestFrom(filesReady$), filter(([p, { bundle }]) => {
2423
+ switch (p.type) {
2424
+ case "full-reload":
2425
+ return typeof p.path === "undefined" || p.path in bundle;
2426
+ case "prune":
2427
+ return p.paths.length > 0;
2428
+ case "update":
2429
+ return p.updates.length > 0;
2430
+ default:
2431
+ return true;
2432
+ }
2433
+ }), map(([p]) => ({
2434
+ type: "custom",
2435
+ event: "crx:content-script-payload",
2436
+ data: p
2437
+ })));
2438
+
2176
2439
  function sortPlugins(plugins, command) {
2177
2440
  const pre = [];
2178
2441
  const mid = [];
@@ -2269,6 +2532,18 @@ const pluginFileWriter = (crxPlugins) => (options) => {
2269
2532
  writerEvent$.next({ type: "error", error, code, frame });
2270
2533
  }
2271
2534
  });
2535
+ const rebuildSub = rebuildSignal$.subscribe((rebuild) => {
2536
+ if (rebuild.type === "partial") {
2537
+ for (const owner of rebuild.owners)
2538
+ transformResultByOwner.delete(owner);
2539
+ } else {
2540
+ transformResultByOwner.clear();
2541
+ }
2542
+ rebuildFiles();
2543
+ });
2544
+ watcher.on("close", () => {
2545
+ rebuildSub.unsubscribe();
2546
+ });
2272
2547
  });
2273
2548
  },
2274
2549
  closeBundle() {
@@ -2277,87 +2552,30 @@ const pluginFileWriter = (crxPlugins) => (options) => {
2277
2552
  };
2278
2553
  };
2279
2554
 
2280
- const debug$2 = _debug("hmr");
2281
2555
  function isImporter(file) {
2282
- const pred = (node) => {
2283
- if (node.file === file)
2556
+ const seen = /* @__PURE__ */ new Set();
2557
+ const pred = (changedNode) => {
2558
+ seen.add(changedNode);
2559
+ if (changedNode.file === file)
2284
2560
  return true;
2285
- for (const node2 of node.importers)
2286
- if (pred(node2))
2561
+ for (const parentNode of changedNode.importers) {
2562
+ const unseen = !seen.has(parentNode);
2563
+ if (unseen && pred(parentNode))
2287
2564
  return true;
2565
+ }
2566
+ return false;
2288
2567
  };
2289
2568
  return pred;
2290
2569
  }
2291
- function isCrxHMRPayload(x) {
2292
- return x.type === "custom" && x.event.startsWith("crx:");
2293
- }
2294
- const hmrPayload$ = new Subject();
2295
- const crxHmrPayload$ = hmrPayload$.pipe(filter((p) => !isCrxHMRPayload(p)), buffer(filesReady$), mergeMap((pps) => {
2296
- let fullReload;
2297
- const payloads = [];
2298
- for (const p of pps.slice(-50))
2299
- if (p.type === "full-reload") {
2300
- fullReload = p;
2301
- } else {
2302
- payloads.push(p);
2303
- }
2304
- if (fullReload)
2305
- payloads.push(fullReload);
2306
- return payloads;
2307
- }), map((p) => {
2308
- switch (p.type) {
2309
- case "full-reload": {
2310
- const path = p.path && outputByOwner.get(p.path);
2311
- const fullReload = {
2312
- type: "full-reload",
2313
- path
2314
- };
2315
- return fullReload;
2316
- }
2317
- case "prune": {
2318
- const paths = [];
2319
- for (const owner of p.paths)
2320
- if (outputByOwner.has(owner))
2321
- paths.push(outputByOwner.get(owner));
2322
- return { type: "prune", paths };
2323
- }
2324
- case "update": {
2325
- const updates = [];
2326
- for (const { acceptedPath, path, ...rest } of p.updates)
2327
- if (outputByOwner.has(acceptedPath) && outputByOwner.has(path))
2328
- updates.push({
2329
- ...rest,
2330
- acceptedPath: outputByOwner.get(acceptedPath),
2331
- path: outputByOwner.get(path)
2332
- });
2333
- return { type: "update", updates };
2334
- }
2335
- default:
2336
- return p;
2337
- }
2338
- }), withLatestFrom(filesReady$), filter(([p, { bundle }]) => {
2339
- switch (p.type) {
2340
- case "full-reload":
2341
- return typeof p.path === "undefined" || p.path in bundle;
2342
- case "prune":
2343
- return p.paths.length > 0;
2344
- case "update":
2345
- return p.updates.length > 0;
2346
- default:
2347
- return true;
2348
- }
2349
- }), map(([p]) => ({
2350
- type: "custom",
2351
- event: "crx:content-script-payload",
2352
- data: p
2353
- })));
2570
+
2571
+ const debug$2 = _debug("hmr");
2354
2572
  const crxRuntimeReload = {
2355
2573
  type: "custom",
2356
2574
  event: "crx:runtime-reload"
2357
2575
  };
2358
2576
  const pluginHMR = () => {
2359
2577
  let files;
2360
- let server;
2578
+ let decoratedSend;
2361
2579
  return [
2362
2580
  {
2363
2581
  name: "crx:hmr",
@@ -2374,38 +2592,41 @@ const pluginHMR = () => {
2374
2592
  name: "crx:hmr",
2375
2593
  apply: "serve",
2376
2594
  enforce: "pre",
2377
- config({ server: server2 = {}, ...config }) {
2378
- if (server2.hmr === false)
2595
+ config({ server = {}, ...config }) {
2596
+ if (server.hmr === false)
2379
2597
  return;
2380
- if (server2.hmr === true)
2381
- server2.hmr = {};
2382
- server2.hmr = server2.hmr ?? {};
2383
- server2.hmr.host = "localhost";
2384
- return { server: server2, ...config };
2598
+ if (server.hmr === true)
2599
+ server.hmr = {};
2600
+ server.hmr = server.hmr ?? {};
2601
+ server.hmr.host = "localhost";
2602
+ return { server, ...config };
2385
2603
  },
2386
2604
  configResolved(config) {
2387
2605
  const { watch = {} } = config.server;
2388
2606
  config.server.watch = watch;
2389
2607
  watch.ignored = watch.ignored ? [...new Set([watch.ignored].flat())] : [];
2390
- watch.ignored.push(config.build.outDir);
2608
+ const outDir = isAbsolute(config.build.outDir) ? config.build.outDir : join(config.root, config.build.outDir, "**/*");
2609
+ watch.ignored.push(outDir);
2391
2610
  },
2392
- configureServer(_server) {
2393
- server = _server;
2394
- const { send } = server.ws;
2395
- server.ws.send = (payload) => {
2396
- hmrPayload$.next(payload);
2397
- send(payload);
2398
- };
2399
- crxHmrPayload$.subscribe((payload) => {
2400
- send(payload);
2401
- });
2611
+ configureServer(server) {
2612
+ if (server.ws.send !== decoratedSend) {
2613
+ const { send } = server.ws;
2614
+ decoratedSend = (payload) => {
2615
+ hmrPayload$.next(payload);
2616
+ send(payload);
2617
+ };
2618
+ server.ws.send = decoratedSend;
2619
+ crxHmrPayload$.subscribe((payload) => {
2620
+ send(payload);
2621
+ });
2622
+ }
2402
2623
  },
2403
- handleHotUpdate({ file, modules, server: server2 }) {
2404
- const background = files.background[0] && join(server2.config.root, files.background[0]);
2624
+ handleHotUpdate({ file, modules, server }) {
2625
+ const background = files.background[0] && join(server.config.root, files.background[0]);
2405
2626
  if (background) {
2406
2627
  if (file === background || modules.some(isImporter(background))) {
2407
2628
  debug$2("sending runtime reload");
2408
- server2.ws.send(crxRuntimeReload);
2629
+ server.ws.send(crxRuntimeReload);
2409
2630
  return [];
2410
2631
  }
2411
2632
  }
@@ -2731,7 +2952,14 @@ const pluginManifest = (_manifest) => () => {
2731
2952
  const files = await manifestFiles(manifest2);
2732
2953
  await Promise.all(assetTypes.map((k) => files[k]).flat().map(async (f) => {
2733
2954
  if (typeof bundle[f] === "undefined") {
2734
- const filename = join(config.root, f);
2955
+ let filename = join(config.root, f);
2956
+ if (!fs.existsSync(filename))
2957
+ filename = join(config.publicDir, f);
2958
+ if (!fs.existsSync(filename))
2959
+ throw new Error(`ENOENT: Could not load manifest asset "${f}".
2960
+ Manifest assets must exist in one of these directories:
2961
+ Project root: "${config.root}"
2962
+ Public dir: "${config.publicDir}"`);
2735
2963
  this.emitFile({
2736
2964
  type: "asset",
2737
2965
  fileName: f,
package/dist/index.mjs CHANGED
@@ -13,6 +13,7 @@ import { createLogger } from 'vite';
13
13
  import { createRequire } from 'module';
14
14
  import { load } from 'cheerio';
15
15
  import jsesc from 'jsesc';
16
+ import { existsSync } from 'fs';
16
17
  import injector from 'connect-injector';
17
18
 
18
19
  const _debug = (id) => debug$5("crx").extend(id);
@@ -151,6 +152,7 @@ const setOutputMeta = ({
151
152
  outputByOwner.set(ownerName, output);
152
153
  outputById.set(id, output);
153
154
  };
155
+ const transformResultByOwner = /* @__PURE__ */ new Map();
154
156
 
155
157
  const {
156
158
  basename,
@@ -197,17 +199,12 @@ function sourceToUrlMeta(source) {
197
199
  }
198
200
  const pluginFileWriterChunks = () => {
199
201
  let server;
200
- const ownerToTransformResultMap = /* @__PURE__ */ new Map();
201
202
  return {
202
203
  name: "crx:file-writer-chunks",
203
204
  apply: "build",
204
205
  fileWriterStart(_server) {
205
206
  server = _server;
206
207
  },
207
- watchChange(fileName) {
208
- for (const owner of ownersByFile.get(fileName) ?? /* @__PURE__ */ new Set())
209
- ownerToTransformResultMap.delete(owner);
210
- },
211
208
  async resolveId(source, importer) {
212
209
  if (this.meta.watchMode) {
213
210
  if (idBySource.has(source)) {
@@ -243,12 +240,12 @@ const pluginFileWriterChunks = () => {
243
240
  if (!serverModule)
244
241
  throw new Error(`Unable to load "${url}" from server.`);
245
242
  const { file, url: owner } = serverModule;
246
- transformResult = transformResult ?? ownerToTransformResultMap.get(owner) ?? serverModule.transformResult;
243
+ transformResult = transformResult ?? transformResultByOwner.get(owner) ?? serverModule.transformResult;
247
244
  if (!transformResult)
248
245
  transformResult = await server.transformRequest(url);
249
246
  if (!transformResult)
250
247
  throw new TypeError(`Unable to load "${url}" from server.`);
251
- ownerToTransformResultMap.set(owner, transformResult);
248
+ transformResultByOwner.set(owner, transformResult);
252
249
  if (file) {
253
250
  setFileMeta({ id, file });
254
251
  this.addWatchFile(file);
@@ -335,7 +332,7 @@ const pluginFileWriterChunks = () => {
335
332
  };
336
333
  };
337
334
 
338
- /*! *****************************************************************************
335
+ /******************************************************************************
339
336
  Copyright (c) Microsoft Corporation.
340
337
 
341
338
  Permission to use, copy, modify, and/or distribute this software for any
@@ -1447,6 +1444,12 @@ function last(arr) {
1447
1444
  function popResultSelector(args) {
1448
1445
  return isFunction(last(args)) ? args.pop() : undefined;
1449
1446
  }
1447
+ function popScheduler(args) {
1448
+ return isScheduler(last(args)) ? args.pop() : undefined;
1449
+ }
1450
+ function popNumber(args, defaultValue) {
1451
+ return typeof last(args) === 'number' ? args.pop() : defaultValue;
1452
+ }
1450
1453
 
1451
1454
  var isArrayLike = (function (x) { return x && typeof x.length === 'number' && typeof x !== 'function'; });
1452
1455
 
@@ -1663,6 +1666,126 @@ function executeSchedule(parentSubscription, scheduler, work, delay, repeat) {
1663
1666
  }
1664
1667
  }
1665
1668
 
1669
+ function observeOn(scheduler, delay) {
1670
+ if (delay === void 0) { delay = 0; }
1671
+ return operate(function (source, subscriber) {
1672
+ source.subscribe(createOperatorSubscriber(subscriber, function (value) { return executeSchedule(subscriber, scheduler, function () { return subscriber.next(value); }, delay); }, function () { return executeSchedule(subscriber, scheduler, function () { return subscriber.complete(); }, delay); }, function (err) { return executeSchedule(subscriber, scheduler, function () { return subscriber.error(err); }, delay); }));
1673
+ });
1674
+ }
1675
+
1676
+ function subscribeOn(scheduler, delay) {
1677
+ if (delay === void 0) { delay = 0; }
1678
+ return operate(function (source, subscriber) {
1679
+ subscriber.add(scheduler.schedule(function () { return source.subscribe(subscriber); }, delay));
1680
+ });
1681
+ }
1682
+
1683
+ function scheduleObservable(input, scheduler) {
1684
+ return innerFrom(input).pipe(subscribeOn(scheduler), observeOn(scheduler));
1685
+ }
1686
+
1687
+ function schedulePromise(input, scheduler) {
1688
+ return innerFrom(input).pipe(subscribeOn(scheduler), observeOn(scheduler));
1689
+ }
1690
+
1691
+ function scheduleArray(input, scheduler) {
1692
+ return new Observable(function (subscriber) {
1693
+ var i = 0;
1694
+ return scheduler.schedule(function () {
1695
+ if (i === input.length) {
1696
+ subscriber.complete();
1697
+ }
1698
+ else {
1699
+ subscriber.next(input[i++]);
1700
+ if (!subscriber.closed) {
1701
+ this.schedule();
1702
+ }
1703
+ }
1704
+ });
1705
+ });
1706
+ }
1707
+
1708
+ function scheduleIterable(input, scheduler) {
1709
+ return new Observable(function (subscriber) {
1710
+ var iterator$1;
1711
+ executeSchedule(subscriber, scheduler, function () {
1712
+ iterator$1 = input[iterator]();
1713
+ executeSchedule(subscriber, scheduler, function () {
1714
+ var _a;
1715
+ var value;
1716
+ var done;
1717
+ try {
1718
+ (_a = iterator$1.next(), value = _a.value, done = _a.done);
1719
+ }
1720
+ catch (err) {
1721
+ subscriber.error(err);
1722
+ return;
1723
+ }
1724
+ if (done) {
1725
+ subscriber.complete();
1726
+ }
1727
+ else {
1728
+ subscriber.next(value);
1729
+ }
1730
+ }, 0, true);
1731
+ });
1732
+ return function () { return isFunction(iterator$1 === null || iterator$1 === void 0 ? void 0 : iterator$1.return) && iterator$1.return(); };
1733
+ });
1734
+ }
1735
+
1736
+ function scheduleAsyncIterable(input, scheduler) {
1737
+ if (!input) {
1738
+ throw new Error('Iterable cannot be null');
1739
+ }
1740
+ return new Observable(function (subscriber) {
1741
+ executeSchedule(subscriber, scheduler, function () {
1742
+ var iterator = input[Symbol.asyncIterator]();
1743
+ executeSchedule(subscriber, scheduler, function () {
1744
+ iterator.next().then(function (result) {
1745
+ if (result.done) {
1746
+ subscriber.complete();
1747
+ }
1748
+ else {
1749
+ subscriber.next(result.value);
1750
+ }
1751
+ });
1752
+ }, 0, true);
1753
+ });
1754
+ });
1755
+ }
1756
+
1757
+ function scheduleReadableStreamLike(input, scheduler) {
1758
+ return scheduleAsyncIterable(readableStreamLikeToAsyncGenerator(input), scheduler);
1759
+ }
1760
+
1761
+ function scheduled(input, scheduler) {
1762
+ if (input != null) {
1763
+ if (isInteropObservable(input)) {
1764
+ return scheduleObservable(input, scheduler);
1765
+ }
1766
+ if (isArrayLike(input)) {
1767
+ return scheduleArray(input, scheduler);
1768
+ }
1769
+ if (isPromise(input)) {
1770
+ return schedulePromise(input, scheduler);
1771
+ }
1772
+ if (isAsyncIterable(input)) {
1773
+ return scheduleAsyncIterable(input, scheduler);
1774
+ }
1775
+ if (isIterable(input)) {
1776
+ return scheduleIterable(input, scheduler);
1777
+ }
1778
+ if (isReadableStreamLike(input)) {
1779
+ return scheduleReadableStreamLike(input, scheduler);
1780
+ }
1781
+ }
1782
+ throw createInvalidObservableTypeError(input);
1783
+ }
1784
+
1785
+ function from(input, scheduler) {
1786
+ return scheduler ? scheduled(input, scheduler) : innerFrom(input);
1787
+ }
1788
+
1666
1789
  var EmptyError = createErrorClass(function (_super) { return function EmptyErrorImpl() {
1667
1790
  _super(this);
1668
1791
  this.name = 'EmptyError';
@@ -1773,6 +1896,11 @@ function mergeMap(project, resultSelector, concurrent) {
1773
1896
  return operate(function (source, subscriber) { return mergeInternals(source, subscriber, project, concurrent); });
1774
1897
  }
1775
1898
 
1899
+ function mergeAll(concurrent) {
1900
+ if (concurrent === void 0) { concurrent = Infinity; }
1901
+ return mergeMap(identity, concurrent);
1902
+ }
1903
+
1776
1904
  function timer(dueTime, intervalOrScheduler, scheduler) {
1777
1905
  if (dueTime === void 0) { dueTime = 0; }
1778
1906
  if (scheduler === void 0) { scheduler = async; }
@@ -1805,13 +1933,22 @@ function timer(dueTime, intervalOrScheduler, scheduler) {
1805
1933
  });
1806
1934
  }
1807
1935
 
1808
- function interval(period, scheduler) {
1809
- if (period === void 0) { period = 0; }
1810
- if (scheduler === void 0) { scheduler = asyncScheduler; }
1811
- if (period < 0) {
1812
- period = 0;
1936
+ function merge() {
1937
+ var args = [];
1938
+ for (var _i = 0; _i < arguments.length; _i++) {
1939
+ args[_i] = arguments[_i];
1813
1940
  }
1814
- return timer(period, period, scheduler);
1941
+ var scheduler = popScheduler(args);
1942
+ var concurrent = popNumber(args, Infinity);
1943
+ var sources = args;
1944
+ return !sources.length
1945
+ ?
1946
+ EMPTY
1947
+ : sources.length === 1
1948
+ ?
1949
+ innerFrom(sources[0])
1950
+ :
1951
+ mergeAll(concurrent)(from(sources, scheduler));
1815
1952
  }
1816
1953
 
1817
1954
  function filter(predicate, thisArg) {
@@ -1839,6 +1976,36 @@ function buffer(closingNotifier) {
1839
1976
  });
1840
1977
  }
1841
1978
 
1979
+ function debounce(durationSelector) {
1980
+ return operate(function (source, subscriber) {
1981
+ var hasValue = false;
1982
+ var lastValue = null;
1983
+ var durationSubscriber = null;
1984
+ var emit = function () {
1985
+ durationSubscriber === null || durationSubscriber === void 0 ? void 0 : durationSubscriber.unsubscribe();
1986
+ durationSubscriber = null;
1987
+ if (hasValue) {
1988
+ hasValue = false;
1989
+ var value = lastValue;
1990
+ lastValue = null;
1991
+ subscriber.next(value);
1992
+ }
1993
+ };
1994
+ source.subscribe(createOperatorSubscriber(subscriber, function (value) {
1995
+ durationSubscriber === null || durationSubscriber === void 0 ? void 0 : durationSubscriber.unsubscribe();
1996
+ hasValue = true;
1997
+ lastValue = value;
1998
+ durationSubscriber = createOperatorSubscriber(subscriber, emit, noop);
1999
+ innerFrom(durationSelector(value)).subscribe(durationSubscriber);
2000
+ }, function () {
2001
+ emit();
2002
+ subscriber.complete();
2003
+ }, undefined, function () {
2004
+ lastValue = durationSubscriber = null;
2005
+ }));
2006
+ });
2007
+ }
2008
+
1842
2009
  function defaultIfEmpty(defaultValue) {
1843
2010
  return operate(function (source, subscriber) {
1844
2011
  var hasValue = false;
@@ -1871,10 +2038,6 @@ function take(count) {
1871
2038
  });
1872
2039
  }
1873
2040
 
1874
- function mapTo(value) {
1875
- return map(function () { return value; });
1876
- }
1877
-
1878
2041
  function throwIfEmpty(errorFactory) {
1879
2042
  if (errorFactory === void 0) { errorFactory = defaultErrorFactory; }
1880
2043
  return operate(function (source, subscriber) {
@@ -1969,7 +2132,7 @@ const filesStart$ = writerEvent$.pipe(filter((x) => {
1969
2132
  const filesStart = () => firstValueFrom(filesStart$);
1970
2133
  const filesReady$ = writerEvent$.pipe(filter((x) => {
1971
2134
  return x.type === "writeBundle";
1972
- }), switchMap((event) => interval(100).pipe(mapTo(event), first(({ bundle, options, timestamp }) => {
2135
+ }), switchMap((event) => timer(0, 100).pipe(map(() => event), first(({ bundle, options, timestamp }) => {
1973
2136
  const result = Object.keys(bundle).every((p) => {
1974
2137
  const stats = statSync(join(options.dir, p));
1975
2138
  return stats.mtimeMs > timestamp;
@@ -2060,27 +2223,40 @@ const pluginFileWriterEvents = () => {
2060
2223
  };
2061
2224
  };
2062
2225
 
2063
- var preControllerScript = "const id = setInterval(() => location.reload(), 100);\nsetTimeout(() => clearInterval(id), 5e3);\n";
2226
+ var precontrollerScript = "const id = setInterval(() => location.reload(), 100);\nsetTimeout(() => clearInterval(id), 5e3);\n";
2064
2227
 
2065
- var preControllerHtml = "<!DOCTYPE html>\n<html lang=\"en\">\n <head>\n <title>Waiting for the extension service worker...</title>\n <script src=\"%PATH%\"></script>\n </head>\n <body>\n <h1>Waiting for service worker</h1>\n\n <p>\n If you see this message, it means the service worker has not loaded fully.\n </p>\n\n <p>\n During development, the service worker reroutes HTML requests to the dev\n server, so this file isn't used unless the extension service worker opens\n a page immediately after a full extension reload, and before the service\n worker takes control of fetch (e.g., in the onInstalled event). In that\n case, this page will continuously reload until the service worker is\n ready, always less than 100 ms.\n </p>\n\n <p>This page is never added in production.</p>\n </body>\n</html>\n";
2228
+ var precontrollerHtml = "<!DOCTYPE html>\n<html lang=\"en\">\n <head>\n <title>Waiting for the extension service worker...</title>\n <script src=\"%PATH%\"></script>\n </head>\n <body>\n <h1>Waiting for service worker</h1>\n\n <p>\n If you see this message, it means the service worker has not loaded fully.\n </p>\n\n <p>This page is never added in production.</p>\n </body>\n</html>\n";
2066
2229
 
2067
2230
  const pluginFileWriterHtml = () => {
2231
+ let precontrollerName;
2068
2232
  return {
2069
2233
  name: "crx:file-writer-html",
2070
2234
  apply: "build",
2235
+ fileWriterStart(server) {
2236
+ const plugins = server.config.plugins;
2237
+ const i = plugins.findIndex(({ name }) => name === "alias");
2238
+ plugins.splice(i, 0, {
2239
+ name: "crx:load-precontroller",
2240
+ apply: "serve",
2241
+ load(id) {
2242
+ if (id === `/${precontrollerName}`)
2243
+ return "location.reload();";
2244
+ }
2245
+ });
2246
+ },
2071
2247
  renderCrxManifest(manifest) {
2072
2248
  if (this.meta.watchMode) {
2073
2249
  const refId = this.emitFile({
2074
2250
  type: "asset",
2075
2251
  name: "precontroller.js",
2076
- source: preControllerScript
2252
+ source: precontrollerScript
2077
2253
  });
2078
- const name = this.getFileName(refId);
2254
+ precontrollerName = this.getFileName(refId);
2079
2255
  for (const fileName of htmlFiles(manifest)) {
2080
2256
  this.emitFile({
2081
2257
  type: "asset",
2082
2258
  fileName,
2083
- source: preControllerHtml.replace("%PATH%", `/${name}`)
2259
+ source: precontrollerHtml.replace("%PATH%", `/${precontrollerName}`)
2084
2260
  });
2085
2261
  }
2086
2262
  }
@@ -2105,7 +2281,6 @@ const pluginFileWriterPublic = () => {
2105
2281
  const source = await readFile(file);
2106
2282
  const fileName = relative(config.publicDir, file);
2107
2283
  this.emitFile({ type: "asset", fileName, source });
2108
- this.addWatchFile(file);
2109
2284
  }
2110
2285
  }
2111
2286
  }
@@ -2140,6 +2315,94 @@ const pluginFileWriterPolyfill = () => {
2140
2315
  };
2141
2316
  };
2142
2317
 
2318
+ function isUpdatePayload(p) {
2319
+ return p.type === "update";
2320
+ }
2321
+ function isFullReloadPayload(p) {
2322
+ return p.type === "full-reload";
2323
+ }
2324
+ function isPrunePayload(p) {
2325
+ return p.type === "prune";
2326
+ }
2327
+ function isCrxHMRPayload(x) {
2328
+ return x.type === "custom" && x.event.startsWith("crx:");
2329
+ }
2330
+ const hmrPayload$ = new Subject();
2331
+ const hmrPrune$ = hmrPayload$.pipe(filter(isPrunePayload));
2332
+ const hmrFullReload$ = hmrPayload$.pipe(filter(isFullReloadPayload));
2333
+ const hmrUpdate$ = hmrPayload$.pipe(filter(isUpdatePayload));
2334
+ const payload$ = merge(hmrFullReload$, hmrPrune$, hmrUpdate$);
2335
+ const rebuildSignal$ = payload$.pipe(buffer(payload$.pipe(debounce(() => filesReady$))), map((payloads) => {
2336
+ if (payloads.every(isUpdatePayload)) {
2337
+ const owners = /* @__PURE__ */ new Set();
2338
+ for (const { updates } of payloads)
2339
+ for (const { path } of updates)
2340
+ if (transformResultByOwner.has(path))
2341
+ owners.add(path);
2342
+ return { type: "partial", owners };
2343
+ }
2344
+ return { type: "full" };
2345
+ }), filter((rebuild) => rebuild.type === "partial" ? rebuild.owners.size > 0 : true));
2346
+ const crxHmrPayload$ = hmrPayload$.pipe(filter((p) => !isCrxHMRPayload(p)), buffer(filesReady$), mergeMap((pps) => {
2347
+ let fullReload;
2348
+ const payloads = [];
2349
+ for (const p of pps.slice(-50))
2350
+ if (p.type === "full-reload") {
2351
+ fullReload = p;
2352
+ } else {
2353
+ payloads.push(p);
2354
+ }
2355
+ if (fullReload)
2356
+ payloads.push(fullReload);
2357
+ return payloads;
2358
+ }), map((p) => {
2359
+ switch (p.type) {
2360
+ case "full-reload": {
2361
+ const path = p.path && outputByOwner.get(p.path);
2362
+ const fullReload = {
2363
+ type: "full-reload",
2364
+ path
2365
+ };
2366
+ return fullReload;
2367
+ }
2368
+ case "prune": {
2369
+ const paths = [];
2370
+ for (const owner of p.paths)
2371
+ if (outputByOwner.has(owner))
2372
+ paths.push(outputByOwner.get(owner));
2373
+ return { type: "prune", paths };
2374
+ }
2375
+ case "update": {
2376
+ const updates = [];
2377
+ for (const { acceptedPath, path, ...rest } of p.updates)
2378
+ if (outputByOwner.has(acceptedPath) && outputByOwner.has(path))
2379
+ updates.push({
2380
+ ...rest,
2381
+ acceptedPath: outputByOwner.get(acceptedPath),
2382
+ path: outputByOwner.get(path)
2383
+ });
2384
+ return { type: "update", updates };
2385
+ }
2386
+ default:
2387
+ return p;
2388
+ }
2389
+ }), withLatestFrom(filesReady$), filter(([p, { bundle }]) => {
2390
+ switch (p.type) {
2391
+ case "full-reload":
2392
+ return typeof p.path === "undefined" || p.path in bundle;
2393
+ case "prune":
2394
+ return p.paths.length > 0;
2395
+ case "update":
2396
+ return p.updates.length > 0;
2397
+ default:
2398
+ return true;
2399
+ }
2400
+ }), map(([p]) => ({
2401
+ type: "custom",
2402
+ event: "crx:content-script-payload",
2403
+ data: p
2404
+ })));
2405
+
2143
2406
  function sortPlugins(plugins, command) {
2144
2407
  const pre = [];
2145
2408
  const mid = [];
@@ -2236,6 +2499,18 @@ const pluginFileWriter = (crxPlugins) => (options) => {
2236
2499
  writerEvent$.next({ type: "error", error, code, frame });
2237
2500
  }
2238
2501
  });
2502
+ const rebuildSub = rebuildSignal$.subscribe((rebuild) => {
2503
+ if (rebuild.type === "partial") {
2504
+ for (const owner of rebuild.owners)
2505
+ transformResultByOwner.delete(owner);
2506
+ } else {
2507
+ transformResultByOwner.clear();
2508
+ }
2509
+ rebuildFiles();
2510
+ });
2511
+ watcher.on("close", () => {
2512
+ rebuildSub.unsubscribe();
2513
+ });
2239
2514
  });
2240
2515
  },
2241
2516
  closeBundle() {
@@ -2244,87 +2519,30 @@ const pluginFileWriter = (crxPlugins) => (options) => {
2244
2519
  };
2245
2520
  };
2246
2521
 
2247
- const debug$2 = _debug("hmr");
2248
2522
  function isImporter(file) {
2249
- const pred = (node) => {
2250
- if (node.file === file)
2523
+ const seen = /* @__PURE__ */ new Set();
2524
+ const pred = (changedNode) => {
2525
+ seen.add(changedNode);
2526
+ if (changedNode.file === file)
2251
2527
  return true;
2252
- for (const node2 of node.importers)
2253
- if (pred(node2))
2528
+ for (const parentNode of changedNode.importers) {
2529
+ const unseen = !seen.has(parentNode);
2530
+ if (unseen && pred(parentNode))
2254
2531
  return true;
2532
+ }
2533
+ return false;
2255
2534
  };
2256
2535
  return pred;
2257
2536
  }
2258
- function isCrxHMRPayload(x) {
2259
- return x.type === "custom" && x.event.startsWith("crx:");
2260
- }
2261
- const hmrPayload$ = new Subject();
2262
- const crxHmrPayload$ = hmrPayload$.pipe(filter((p) => !isCrxHMRPayload(p)), buffer(filesReady$), mergeMap((pps) => {
2263
- let fullReload;
2264
- const payloads = [];
2265
- for (const p of pps.slice(-50))
2266
- if (p.type === "full-reload") {
2267
- fullReload = p;
2268
- } else {
2269
- payloads.push(p);
2270
- }
2271
- if (fullReload)
2272
- payloads.push(fullReload);
2273
- return payloads;
2274
- }), map((p) => {
2275
- switch (p.type) {
2276
- case "full-reload": {
2277
- const path = p.path && outputByOwner.get(p.path);
2278
- const fullReload = {
2279
- type: "full-reload",
2280
- path
2281
- };
2282
- return fullReload;
2283
- }
2284
- case "prune": {
2285
- const paths = [];
2286
- for (const owner of p.paths)
2287
- if (outputByOwner.has(owner))
2288
- paths.push(outputByOwner.get(owner));
2289
- return { type: "prune", paths };
2290
- }
2291
- case "update": {
2292
- const updates = [];
2293
- for (const { acceptedPath, path, ...rest } of p.updates)
2294
- if (outputByOwner.has(acceptedPath) && outputByOwner.has(path))
2295
- updates.push({
2296
- ...rest,
2297
- acceptedPath: outputByOwner.get(acceptedPath),
2298
- path: outputByOwner.get(path)
2299
- });
2300
- return { type: "update", updates };
2301
- }
2302
- default:
2303
- return p;
2304
- }
2305
- }), withLatestFrom(filesReady$), filter(([p, { bundle }]) => {
2306
- switch (p.type) {
2307
- case "full-reload":
2308
- return typeof p.path === "undefined" || p.path in bundle;
2309
- case "prune":
2310
- return p.paths.length > 0;
2311
- case "update":
2312
- return p.updates.length > 0;
2313
- default:
2314
- return true;
2315
- }
2316
- }), map(([p]) => ({
2317
- type: "custom",
2318
- event: "crx:content-script-payload",
2319
- data: p
2320
- })));
2537
+
2538
+ const debug$2 = _debug("hmr");
2321
2539
  const crxRuntimeReload = {
2322
2540
  type: "custom",
2323
2541
  event: "crx:runtime-reload"
2324
2542
  };
2325
2543
  const pluginHMR = () => {
2326
2544
  let files;
2327
- let server;
2545
+ let decoratedSend;
2328
2546
  return [
2329
2547
  {
2330
2548
  name: "crx:hmr",
@@ -2341,38 +2559,41 @@ const pluginHMR = () => {
2341
2559
  name: "crx:hmr",
2342
2560
  apply: "serve",
2343
2561
  enforce: "pre",
2344
- config({ server: server2 = {}, ...config }) {
2345
- if (server2.hmr === false)
2562
+ config({ server = {}, ...config }) {
2563
+ if (server.hmr === false)
2346
2564
  return;
2347
- if (server2.hmr === true)
2348
- server2.hmr = {};
2349
- server2.hmr = server2.hmr ?? {};
2350
- server2.hmr.host = "localhost";
2351
- return { server: server2, ...config };
2565
+ if (server.hmr === true)
2566
+ server.hmr = {};
2567
+ server.hmr = server.hmr ?? {};
2568
+ server.hmr.host = "localhost";
2569
+ return { server, ...config };
2352
2570
  },
2353
2571
  configResolved(config) {
2354
2572
  const { watch = {} } = config.server;
2355
2573
  config.server.watch = watch;
2356
2574
  watch.ignored = watch.ignored ? [...new Set([watch.ignored].flat())] : [];
2357
- watch.ignored.push(config.build.outDir);
2575
+ const outDir = isAbsolute(config.build.outDir) ? config.build.outDir : join(config.root, config.build.outDir, "**/*");
2576
+ watch.ignored.push(outDir);
2358
2577
  },
2359
- configureServer(_server) {
2360
- server = _server;
2361
- const { send } = server.ws;
2362
- server.ws.send = (payload) => {
2363
- hmrPayload$.next(payload);
2364
- send(payload);
2365
- };
2366
- crxHmrPayload$.subscribe((payload) => {
2367
- send(payload);
2368
- });
2578
+ configureServer(server) {
2579
+ if (server.ws.send !== decoratedSend) {
2580
+ const { send } = server.ws;
2581
+ decoratedSend = (payload) => {
2582
+ hmrPayload$.next(payload);
2583
+ send(payload);
2584
+ };
2585
+ server.ws.send = decoratedSend;
2586
+ crxHmrPayload$.subscribe((payload) => {
2587
+ send(payload);
2588
+ });
2589
+ }
2369
2590
  },
2370
- handleHotUpdate({ file, modules, server: server2 }) {
2371
- const background = files.background[0] && join(server2.config.root, files.background[0]);
2591
+ handleHotUpdate({ file, modules, server }) {
2592
+ const background = files.background[0] && join(server.config.root, files.background[0]);
2372
2593
  if (background) {
2373
2594
  if (file === background || modules.some(isImporter(background))) {
2374
2595
  debug$2("sending runtime reload");
2375
- server2.ws.send(crxRuntimeReload);
2596
+ server.ws.send(crxRuntimeReload);
2376
2597
  return [];
2377
2598
  }
2378
2599
  }
@@ -2698,7 +2919,14 @@ const pluginManifest = (_manifest) => () => {
2698
2919
  const files = await manifestFiles(manifest2);
2699
2920
  await Promise.all(assetTypes.map((k) => files[k]).flat().map(async (f) => {
2700
2921
  if (typeof bundle[f] === "undefined") {
2701
- const filename = join(config.root, f);
2922
+ let filename = join(config.root, f);
2923
+ if (!existsSync(filename))
2924
+ filename = join(config.publicDir, f);
2925
+ if (!existsSync(filename))
2926
+ throw new Error(`ENOENT: Could not load manifest asset "${f}".
2927
+ Manifest assets must exist in one of these directories:
2928
+ Project root: "${config.root}"
2929
+ Public dir: "${config.publicDir}"`);
2702
2930
  this.emitFile({
2703
2931
  type: "asset",
2704
2932
  fileName: f,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crxjs/vite-plugin",
3
- "version": "1.0.3",
3
+ "version": "1.0.6",
4
4
  "description": "Build Chrome Extensions with this Vite plugin.",
5
5
  "keywords": [
6
6
  "rollup-plugin",
@@ -50,12 +50,13 @@
50
50
  "lint": "run-s lint:eslint lint:types",
51
51
  "lint:eslint": "eslint \"{src,test}/**/*.ts\"",
52
52
  "lint:types": "tsc --noEmit",
53
- "test": "run-s test:update:*",
53
+ "test": "run-s test:units test:update:*",
54
54
  "test:ci:e2e:build": "node --expose-gc ./node_modules/jest/bin/jest.js --logHeapUsage --runInBand --forceExit --testPathIgnorePatterns=\\(/node_modules/ /templates/ /mv3/\\) -t build",
55
55
  "test:ci:e2e:serve": "npm run test:ci:e2e:serve:cmd || npm run test:ci:e2e:serve:cmd",
56
56
  "test:ci:e2e:serve:cmd": "node --expose-gc ./node_modules/jest/bin/jest.js --logHeapUsage --runInBand --forceExit --testPathIgnorePatterns=\\(/node_modules/ /templates/ /mv3/\\) -t serve",
57
57
  "test:ci:mv3:build": "node --expose-gc ./node_modules/jest/bin/jest.js --logHeapUsage --runInBand --forceExit --testPathIgnorePatterns=\\(/node_modules/ /templates/ /e2e/\\) --updateSnapshot build",
58
58
  "test:ci:mv3:serve": "node --expose-gc ./node_modules/jest/bin/jest.js --logHeapUsage --runInBand --forceExit --testPathIgnorePatterns=\\(/node_modules/ /templates/ /e2e/\\) --updateSnapshot serve",
59
+ "test:units": "jest src",
59
60
  "test:update:build": "jest build -u",
60
61
  "test:update:serve": "jest serve -u"
61
62
  },
@@ -82,22 +83,22 @@
82
83
  "@rollup/plugin-commonjs": "21.1.0",
83
84
  "@rollup/plugin-node-resolve": "13.2.0",
84
85
  "@types/acorn": "4.0.6",
85
- "@types/chrome": "0.0.183",
86
+ "@types/chrome": "0.0.184",
86
87
  "@types/debug": "4.1.7",
87
88
  "@types/fs-extra": "9.0.13",
88
- "@types/jest": "27.4.1",
89
+ "@types/jest": "27.5.0",
89
90
  "@types/jest-image-snapshot": "4.3.1",
90
91
  "@types/jsesc": "3.0.1",
91
92
  "@types/node": "17.0.18",
92
93
  "@types/react": "17.0.44",
93
94
  "@types/react-dom": "17.0.16",
94
- "@typescript-eslint/eslint-plugin": "5.21.0",
95
- "@typescript-eslint/parser": "5.21.0",
96
- "@vitejs/plugin-react": "1.3.1",
97
- "@vitejs/plugin-vue": "2.3.1",
95
+ "@typescript-eslint/eslint-plugin": "5.23.0",
96
+ "@typescript-eslint/parser": "5.23.0",
97
+ "@vitejs/plugin-react": "1.3.2",
98
+ "@vitejs/plugin-vue": "2.3.2",
98
99
  "esbuild": "0.14.38",
99
100
  "esbuild-runner": "2.2.1",
100
- "eslint": "^8.14.0",
101
+ "eslint": "8.15.0",
101
102
  "jest": "27.5.1",
102
103
  "jest-image-snapshot": "4.5.1",
103
104
  "playwright-chromium": "1.21.1",
@@ -107,6 +108,7 @@
107
108
  "rollup-plugin-dts": "^4.2.0",
108
109
  "rollup-plugin-esbuild": "4.9.1",
109
110
  "rxjs": "7.5.5",
111
+ "typescript": "^4.6.4",
110
112
  "vite": "^2.9.5",
111
113
  "vite-plugin-inspect": "0.5.0",
112
114
  "vue": "3.2.33"