@jskit-ai/kernel 0.1.210 → 0.1.212

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.
@@ -309,16 +309,11 @@ export { installedClientModules, bootInstalledClientModules };
309
309
  `;
310
310
  }
311
311
 
312
- // Vite-only: this Set-based source-type filter exists solely to drive optimizeDeps include/exclude decisions.
312
+ // Explicit package exclusions remain separate from mutable local source resolution.
313
313
  function resolveClientOptimizeExcludeSpecifiers(clientModules = []) {
314
314
  const modulePackageMetadataEntries = normalizeClientModulePackageMetadataEntries(clientModules);
315
315
  return sortStrings(
316
- [
317
- ...modulePackageMetadataEntries
318
- .filter((entry) => LOCAL_PACKAGE_SOURCE_TYPES.has(entry.sourceType))
319
- .flatMap((entry) => [entry.packageId, `${entry.packageId}/shared`, `${entry.packageId}/client`]),
320
- ...modulePackageMetadataEntries.flatMap((entry) => entry.packageMetadataClientOptimizeExcludeSpecifiers || [])
321
- ]
316
+ modulePackageMetadataEntries.flatMap((entry) => entry.packageMetadataClientOptimizeExcludeSpecifiers || [])
322
317
  );
323
318
  }
324
319
 
@@ -335,12 +330,6 @@ function resolveClientOptimizeIncludeSpecifiers(clientModules = [], excludeSpeci
335
330
  );
336
331
  }
337
332
 
338
- function resolveLocalScopeOptimizeExcludeSpecifiers(localScopePackageIds = []) {
339
- return sortStrings(
340
- localScopePackageIds.flatMap((packageId) => [packageId, `${packageId}/shared`, `${packageId}/client`])
341
- );
342
- }
343
-
344
333
  function resolveClientRuntimeDedupeSpecifiers(userResolveConfig = {}) {
345
334
  const resolveConfig = normalizeObject(userResolveConfig);
346
335
  const userDedupe = sortStrings(resolveConfig.dedupe);
@@ -352,6 +341,7 @@ function createJskitClientBootstrapPlugin({ proxyTarget = "" } = {}) {
352
341
  let hasMutableLocalPackages = false;
353
342
  let localPackages = Object.freeze([]);
354
343
  let resolvePackageSpecifier = null;
344
+ let clientScanSpecifiers = [];
355
345
 
356
346
  return {
357
347
  name: "jskit-client-bootstrap",
@@ -377,14 +367,12 @@ function createJskitClientBootstrapPlugin({ proxyTarget = "" } = {}) {
377
367
  installedPackages.some((entry) => LOCAL_PACKAGE_SOURCE_TYPES.has(entry.sourceType))
378
368
  );
379
369
  const clientExcludeSpecifiers = resolveClientOptimizeExcludeSpecifiers(clientModules);
380
- const localScopeExcludeSpecifiers = resolveLocalScopeOptimizeExcludeSpecifiers(localScopePackageIds);
381
370
  const userOptimizeDeps = normalizeObject(userConfig.optimizeDeps);
382
371
  const userExclude = sortStrings(userOptimizeDeps.exclude);
383
372
  const userInclude = sortStrings(userOptimizeDeps.include);
384
373
  const exclude = sortStrings([
385
374
  ...userExclude,
386
- ...clientExcludeSpecifiers,
387
- ...localScopeExcludeSpecifiers
375
+ ...clientExcludeSpecifiers
388
376
  ]);
389
377
  const clientIncludeSpecifiers = resolveClientOptimizeIncludeSpecifiers(clientModules, exclude);
390
378
  const include = sortStrings([
@@ -392,6 +380,11 @@ function createJskitClientBootstrapPlugin({ proxyTarget = "" } = {}) {
392
380
  ...clientIncludeSpecifiers,
393
381
  CLIENT_BOOTSTRAP_MODULE_SPECIFIER
394
382
  ].filter((specifier) => !exclude.includes(specifier)));
383
+ // Vite cannot crawl the virtual bootstrap. Local and explicitly excluded clients
384
+ // still need scanning so their npm imports are ready before the first page loads.
385
+ clientScanSpecifiers = clientModules
386
+ .map((entry) => `${entry.packageId}/client`)
387
+ .filter((specifier) => !include.includes(specifier));
395
388
  const dedupe = resolveClientRuntimeDedupeSpecifiers(userResolve);
396
389
  const userServer = normalizeObject(userConfig.server);
397
390
  const userProxyEntries = normalizeObject(userServer.proxy);
@@ -415,7 +408,7 @@ function createJskitClientBootstrapPlugin({ proxyTarget = "" } = {}) {
415
408
  }
416
409
  };
417
410
  },
418
- configResolved(resolvedConfig) {
411
+ async configResolved(resolvedConfig) {
419
412
  if (hasMutableLocalPackages && resolvedConfig.resolve?.preserveSymlinks === true) {
420
413
  throw new Error(
421
414
  "JSKIT mutable local packages require Vite real-path resolution. Remove resolve.preserveSymlinks: true."
@@ -426,6 +419,28 @@ function createJskitClientBootstrapPlugin({ proxyTarget = "" } = {}) {
426
419
  // its dependency ?v hash) before JSKIT sees the selected file. The config resolver applies the
427
420
  // app's aliases, exports conditions, and wildcard rules without registering an optimized dep.
428
421
  resolvePackageSpecifier = resolvedConfig.createResolver({ scan: true });
422
+ if (clientScanSpecifiers.length === 0) {
423
+ return;
424
+ }
425
+ const clientEntries = await Promise.all(clientScanSpecifiers.map(async (specifier) => {
426
+ const entry = await resolvePackageSpecifier(specifier, path.join(appRoot, "package.json"));
427
+ if (!entry) {
428
+ throw new Error(`Cannot resolve JSKIT client dependency scan entry ${specifier}.`);
429
+ }
430
+ const localPackage = resolveLocalPackageForSpecifier(specifier, localPackages);
431
+ return localPackage ? resolveCanonicalLocalPackageId(entry, localPackage) || entry : entry;
432
+ }));
433
+ const optimizeDeps = resolvedConfig.environments.client.optimizeDeps;
434
+ const input = resolvedConfig.environments.client.input ?? resolvedConfig.build.rolldownOptions.input;
435
+ const entries = optimizeDeps.entries ?? (input
436
+ ? (typeof input === "string" || Array.isArray(input) ? input : Object.values(input))
437
+ : ["**/*.html", "!**/__tests__/**", "!**/coverage/**"]);
438
+ const scanEntries = sortStrings([
439
+ ...(Array.isArray(entries) ? entries : [entries]),
440
+ ...clientEntries
441
+ ]);
442
+ optimizeDeps.entries = scanEntries;
443
+ resolvedConfig.optimizeDeps.entries = scanEntries;
429
444
  },
430
445
  async resolveId(source, importer) {
431
446
  if (source === CLIENT_BOOTSTRAP_VIRTUAL_ID) {
@@ -472,7 +487,6 @@ export {
472
487
  resolveCanonicalLocalPackageId,
473
488
  resolveLocalPackageForSpecifier,
474
489
  resolveLocalPackageSources,
475
- resolveLocalScopeOptimizeExcludeSpecifiers,
476
490
  resolveInstalledClientPackageIds,
477
491
  resolveLocalScopePackageIds,
478
492
  resolveInstalledClientModules,
@@ -11,7 +11,6 @@ import {
11
11
  resolveCanonicalLocalPackageId,
12
12
  resolveLocalPackageForSpecifier,
13
13
  resolveLocalPackageSources,
14
- resolveLocalScopeOptimizeExcludeSpecifiers,
15
14
  resolveClientOptimizeIncludeSpecifiers,
16
15
  resolveClientOptimizeExcludeSpecifiers,
17
16
  resolveLocalScopePackageIds,
@@ -164,7 +163,7 @@ test("createVirtualModuleSource renders deterministic client module imports", ()
164
163
  assert.match(source, /installedClientModules/);
165
164
  });
166
165
 
167
- test("resolveClientOptimizeExcludeSpecifiers excludes local/app-local package roots and client/shared subpaths", () => {
166
+ test("resolveClientOptimizeExcludeSpecifiers lets Vite scan local source imports", () => {
168
167
  const exclude = resolveClientOptimizeExcludeSpecifiers([
169
168
  {
170
169
  packageId: "@z/pkg",
@@ -188,14 +187,7 @@ test("resolveClientOptimizeExcludeSpecifiers excludes local/app-local package ro
188
187
  }
189
188
  ]);
190
189
 
191
- assert.deepEqual(exclude, [
192
- "@a/pkg",
193
- "@a/pkg/client",
194
- "@a/pkg/shared",
195
- "@b/pkg",
196
- "@b/pkg/client",
197
- "@b/pkg/shared"
198
- ]);
190
+ assert.deepEqual(exclude, []);
199
191
  });
200
192
 
201
193
  test("resolveClientOptimizeExcludeSpecifiers includes packageMetadata-declared excludes", () => {
@@ -215,9 +207,6 @@ test("resolveClientOptimizeExcludeSpecifiers includes packageMetadata-declared e
215
207
  ]);
216
208
 
217
209
  assert.deepEqual(exclude, [
218
- "@a/pkg",
219
- "@a/pkg/client",
220
- "@a/pkg/shared",
221
210
  "@z/pkg/client",
222
211
  "external-problem-dep"
223
212
  ]);
@@ -271,18 +260,6 @@ test("resolveClientOptimizeIncludeSpecifiers omits excluded package clients", ()
271
260
  assert.deepEqual(include, ["@c/pkg/client"]);
272
261
  });
273
262
 
274
- test("resolveLocalScopeOptimizeExcludeSpecifiers expands @local package ids to root/client/shared", () => {
275
- const exclude = resolveLocalScopeOptimizeExcludeSpecifiers(["@local/app", "@local/feature"]);
276
- assert.deepEqual(exclude, [
277
- "@local/app",
278
- "@local/app/client",
279
- "@local/app/shared",
280
- "@local/feature",
281
- "@local/feature/client",
282
- "@local/feature/shared"
283
- ]);
284
- });
285
-
286
263
  test("resolveInstalledClientPackageIds returns only installed packages with a client export", async () => {
287
264
  const tempRoot = await mkdtemp(path.join(os.tmpdir(), "jskit-client-bootstrap-"));
288
265
  const appRoot = tempRoot;
@@ -681,11 +658,14 @@ test("createJskitClientBootstrapPlugin resolves multiple local packages before V
681
658
  ["@example/local-utility", path.join(tempRoot, "node_modules", "@example", "local-utility", "browser", "utility.js")],
682
659
  ["@example/local-utility/shared", path.join(tempRoot, "node_modules", "@example", "local-utility", "browser", "utility-shared.js")]
683
660
  ]);
684
- plugin.configResolved({
661
+ await plugin.configResolved({
662
+ optimizeDeps: {},
663
+ environments: { client: { optimizeDeps: { entries: ["index.html"] } } },
664
+ build: { rolldownOptions: {} },
685
665
  createResolver(options) {
686
666
  assert.equal(options.scan, true);
687
667
  return async (source, importer) => {
688
- assert.equal(importer, undefined);
668
+ assert.ok(importer === undefined || importer === path.join(tempRoot, "package.json"));
689
669
  return viteResolvedIds.get(source);
690
670
  };
691
671
  }
@@ -812,7 +792,7 @@ test("createJskitClientBootstrapPlugin config lets packageMetadata excludes over
812
792
  }
813
793
  });
814
794
 
815
- test("createJskitClientBootstrapPlugin config excludes local package roots and client/shared subpaths", async () => {
795
+ test("createJskitClientBootstrapPlugin config keeps local packages as scannable source", async () => {
816
796
  const tempRoot = await mkdtemp(path.join(os.tmpdir(), "jskit-client-bootstrap-config-local-"));
817
797
  const previousCwd = process.cwd();
818
798
 
@@ -862,11 +842,7 @@ test("createJskitClientBootstrapPlugin config excludes local package roots and c
862
842
  }
863
843
  });
864
844
 
865
- assert.deepEqual(result.optimizeDeps.exclude, [
866
- "@example/local-client",
867
- "@example/local-client/client",
868
- "@example/local-client/shared"
869
- ]);
845
+ assert.deepEqual(result.optimizeDeps.exclude, []);
870
846
  assert.deepEqual(result.optimizeDeps.include, [
871
847
  "@example/remote-client/client",
872
848
  "@jskit-ai/kernel/client/moduleBootstrap",
@@ -910,7 +886,7 @@ test("createJskitClientBootstrapPlugin config preserves user resolve fields and
910
886
  }
911
887
  });
912
888
 
913
- test("createJskitClientBootstrapPlugin config excludes all @local scoped packages from package.json", async () => {
889
+ test("createJskitClientBootstrapPlugin config uses real paths for @local scoped packages", async () => {
914
890
  const tempRoot = await mkdtemp(path.join(os.tmpdir(), "jskit-client-bootstrap-local-scope-config-"));
915
891
  const previousCwd = process.cwd();
916
892
 
@@ -944,14 +920,7 @@ test("createJskitClientBootstrapPlugin config excludes all @local scoped package
944
920
  const plugin = createJskitClientBootstrapPlugin();
945
921
  const result = await plugin.config({});
946
922
 
947
- assert.deepEqual(result.optimizeDeps.exclude, [
948
- "@local/feature",
949
- "@local/feature/client",
950
- "@local/feature/shared",
951
- "@local/main",
952
- "@local/main/client",
953
- "@local/main/shared"
954
- ]);
923
+ assert.deepEqual(result.optimizeDeps.exclude, []);
955
924
  assert.deepEqual(result.optimizeDeps.include, [
956
925
  "@example/remote-client/client",
957
926
  "@jskit-ai/kernel/client/moduleBootstrap"
@@ -976,7 +945,7 @@ test("createJskitClientBootstrapPlugin rejects a later preserveSymlinks override
976
945
  const result = await plugin.config({});
977
946
 
978
947
  assert.equal(result.resolve.preserveSymlinks, false);
979
- assert.throws(
948
+ await assert.rejects(
980
949
  () => plugin.configResolved({
981
950
  resolve: {
982
951
  preserveSymlinks: true
@@ -0,0 +1,93 @@
1
+ import assert from "node:assert/strict";
2
+ import { mkdtemp, mkdir, rm, symlink, writeFile } from "node:fs/promises";
3
+ import os from "node:os";
4
+ import path from "node:path";
5
+ import test from "node:test";
6
+ import { optimizeDeps, resolveConfig } from "vite";
7
+ import { createJskitClientBootstrapPlugin } from "./clientBootstrapPlugin.js";
8
+
9
+ const scanCases = [
10
+ { name: "workspace with explicit entries", localDependency: "1.0.0", config: { optimizeDeps: { entries: ["src/main.js"] } } },
11
+ { name: "workspace with default HTML discovery", localDependency: "1.0.0", config: {} },
12
+ { name: "workspace with build inputs", localDependency: "1.0.0", config: { build: { rolldownOptions: { input: { app: "src/main.js" } } } } },
13
+ { name: "file dependency", localDependency: "file:packages/main", config: { optimizeDeps: { entries: ["src/main.js"] } } }
14
+ ];
15
+
16
+ for (const scanCase of scanCases) {
17
+ test(`cold scan discovers virtual client and local shared dependencies: ${scanCase.name}`, async () => {
18
+ const root = await mkdtemp(path.join(os.tmpdir(), "jskit-client-dependency-scan-"));
19
+ const previousCwd = process.cwd();
20
+ const write = async (name, source) => {
21
+ const target = path.join(root, name);
22
+ await mkdir(path.dirname(target), { recursive: true });
23
+ await writeFile(target, typeof source === "string" ? source : JSON.stringify(source));
24
+ };
25
+
26
+ try {
27
+ await write("package.json", {
28
+ name: "client-scan-fixture", type: "module", private: true,
29
+ workspaces: ["packages/*"],
30
+ dependencies: {
31
+ "@local/main": scanCase.localDependency,
32
+ "@local/utility": "1.0.0",
33
+ "@example/shell": "1.0.0"
34
+ }
35
+ });
36
+ await write("index.html", '<script type="module" src="/src/main.js"></script>');
37
+ await write("__tests__/ignored.html", '<script type="module">import "missing-test-only";</script>');
38
+ await write("src/main.js", 'import "virtual:jskit-client-bootstrap";');
39
+ await write("packages/main/package.json", {
40
+ name: "@local/main", version: "1.0.0", type: "module",
41
+ jskit: { kind: "runtime" },
42
+ exports: { "./client": { browser: "./browser.js", default: "./server.js" } }
43
+ });
44
+ await write("packages/main/browser.js", 'import "client-helper"; import "@local/utility/shared"; import("./Page.vue");');
45
+ await write("packages/main/server.js", 'import "server-only-missing";');
46
+ await write("packages/main/Page.vue", '<script setup>import "page-helper";</script><template>Page</template>');
47
+ await write("packages/utility/package.json", {
48
+ name: "@local/utility", version: "1.0.0", type: "module",
49
+ exports: { "./shared": "./shared.js" }
50
+ });
51
+ await write("packages/utility/shared.js", 'import "shared-helper";');
52
+ await mkdir(path.join(root, "node_modules/@local"), { recursive: true });
53
+ for (const name of ["main", "utility"]) {
54
+ await symlink(path.join(root, "packages", name), path.join(root, "node_modules/@local", name));
55
+ }
56
+ await write("node_modules/@example/shell/package.json", {
57
+ name: "@example/shell", version: "1.0.0", type: "module",
58
+ exports: { "./client": "./client.js" },
59
+ jskit: { metadata: { client: { optimizeDeps: { exclude: ["@example/shell/client"] } } } }
60
+ });
61
+ await write("node_modules/@example/shell/client.js", 'import "/src/shell-content.js";');
62
+ await write("src/shell-content.js", 'import "shell-helper";');
63
+ await write("node_modules/@jskit-ai/kernel/package.json", {
64
+ name: "@jskit-ai/kernel", version: "1.0.0", type: "module",
65
+ exports: { "./client/moduleBootstrap": "./bootstrap.js" }
66
+ });
67
+ await write("node_modules/@jskit-ai/kernel/bootstrap.js", "export const bootClientModules = () => {};");
68
+ for (const name of ["client-helper", "shared-helper", "page-helper", "shell-helper"]) {
69
+ await write(`node_modules/${name}/package.json`, {
70
+ name, version: "1.0.0", type: "module", exports: "./index.js"
71
+ });
72
+ await write(`node_modules/${name}/index.js`, "export const value = 1;");
73
+ }
74
+
75
+ process.chdir(root);
76
+ const config = await resolveConfig({
77
+ configFile: false,
78
+ logLevel: "silent",
79
+ plugins: [createJskitClientBootstrapPlugin()],
80
+ ...scanCase.config
81
+ }, "serve");
82
+ const metadata = await optimizeDeps(config, true);
83
+ for (const name of ["client-helper", "shared-helper", "page-helper", "shell-helper"]) {
84
+ assert.ok(metadata.optimized[name], `${name} must be ready before the browser loads a page`);
85
+ }
86
+ assert.equal(Object.keys(metadata.optimized).some((id) => id.startsWith("@local/")), false);
87
+ assert.equal(metadata.optimized["@example/shell/client"], undefined);
88
+ } finally {
89
+ process.chdir(previousCwd);
90
+ await rm(root, { recursive: true, force: true });
91
+ }
92
+ });
93
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jskit-ai/kernel",
3
- "version": "0.1.210",
3
+ "version": "0.1.212",
4
4
  "type": "module",
5
5
  "dependencies": {
6
6
  "json-rest-schema": "^1.0.17"