@foldkit/vite-plugin 0.26.0 → 0.26.1

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 CHANGED
@@ -65,6 +65,25 @@ With this set, the dev server converts HTML page requests to Web `Request` value
65
65
 
66
66
  Vite retains ownership of configured proxy routes before Foldkit handles application requests. Vite's `server.cors` option applies to Vite-owned source modules, assets, and HMR. It does not add headers to application responses or answer their preflights. Preflight ownership follows `Access-Control-Request-Method`, so a preflight for an application `POST` reaches `renderPage` even when its path looks like an asset. An `OPTIONS` request without both `Origin` and `Access-Control-Request-Method` is not a preflight and also reaches `renderPage`. Define application CORS in `renderPage`, where development and the deployed host share one policy. Vite's `allowedHosts` check runs before proxy and application handling, including `OPTIONS` and methods the Web `Request` API cannot represent.
67
67
 
68
+ ## Foldkit package resolution
69
+
70
+ Each module graph must load one Foldkit copy. The plugin configures Vite for that:
71
+
72
+ - `resolve.dedupe` lists `foldkit`, `@foldkit/ui`, and `@foldkit/devtools`, each only when it resolves from the application root.
73
+ - Server builds and the dev server's server render bundle those three packages, plus every installed package whose `dependencies` or `peerDependencies` include `foldkit` or an `@foldkit/*` package, such as `@foldkit/markdown`. In the dev server, these `ssr.noExternal` packages run through Vite's module runner instead of Node's own import. An explicit `ssr.external` entry still keeps a package external.
74
+ - The plugin finds these packages by crawling from the application's `package.json`. It follows the application's `dependencies` and `devDependencies`, then the `dependencies` of each package it bundles, plus the `devDependencies` of a bundled package that is a private workspace package.
75
+
76
+ A package the crawl does not reach stays external. For example: a peer the application does not declare, or a package reached only through a package that does not depend on Foldkit. Such a package loads a second Foldkit copy from `node_modules` at runtime. Declare it in the application's `package.json`, or add it to `ssr.noExternal`:
77
+
78
+ ```typescript
79
+ export default defineConfig({
80
+ plugins: [foldkit()],
81
+ ssr: { noExternal: ['foldkit-component-library'] },
82
+ })
83
+ ```
84
+
85
+ Vitest copies SSR `noExternal` into `server.deps.inline`. A Vitest config that includes `foldkit()` therefore also inlines the crawled packages in tests.
86
+
68
87
  ## Completed build metadata
69
88
 
70
89
  Deployment tools that run Vite in process can read `foldkit:build` through Vite's standard plugin `api` field. Await the full application build before reading:
@@ -1,12 +1,17 @@
1
+ import { type UserConfig } from 'vite';
1
2
  /** Tests whether an import refers to a package that must share Foldkit's
2
3
  * runtime instance.
3
4
  *
4
5
  * @internal
5
6
  */
6
7
  export declare const isFoldkitSingletonPackageSpecifier: (specifier: string) => boolean;
7
- /** Resolves the installed packages that must share Foldkit's runtime instance.
8
+ /** Finds the installed packages an application's server render must bundle
9
+ * and the Foldkit packages Vite must deduplicate.
8
10
  *
9
11
  * @internal
10
12
  */
11
- export declare const resolveInstalledFoldkitPackages: (root: string) => Array<string>;
13
+ export declare const crawlFoldkitPackages: (root: string, isBuild: boolean, viteUserConfig: UserConfig) => Promise<Readonly<{
14
+ dedupe: Array<string>;
15
+ ssrNoExternal: Array<string>;
16
+ }>>;
12
17
  //# sourceMappingURL=foldkitPackages.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"foldkitPackages.d.ts","sourceRoot":"","sources":["../src/foldkitPackages.ts"],"names":[],"mappings":"AAUA;;;;GAIG;AACH,eAAO,MAAM,kCAAkC,cAClC,MAAM,KAChB,OAKA,CAAA;AAEH;;;GAGG;AACH,eAAO,MAAM,+BAA+B,SACpC,MAAM,KACX,KAAK,CAAC,MAAM,CAmBd,CAAA"}
1
+ {"version":3,"file":"foldkitPackages.d.ts","sourceRoot":"","sources":["../src/foldkitPackages.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,UAAU,EAA0B,MAAM,MAAM,CAAA;AAS9D;;;;GAIG;AACH,eAAO,MAAM,kCAAkC,cAClC,MAAM,KAChB,OAKA,CAAA;AA8CH;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,SACzB,MAAM,WACH,OAAO,kBACA,UAAU,KACzB,OAAO,CACR,QAAQ,CAAC;IAAE,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAAC,aAAa,EAAE,KAAK,CAAC,MAAM,CAAC,CAAA;CAAE,CAAC,CAqClE,CAAA"}
@@ -1,6 +1,8 @@
1
- import { Array, Predicate } from 'effect';
2
- import { createRequire } from 'node:module';
1
+ import { Array, Option, Record, Schema, pipe } from 'effect';
2
+ import { realpathSync } from 'node:fs';
3
3
  import { resolve } from 'node:path';
4
+ import { searchForWorkspaceRoot } from 'vite';
5
+ import { crawlFrameworkPkgs, findDepPkgJsonPath } from 'vitefu';
4
6
  const FOLDKIT_SINGLETON_PACKAGES = [
5
7
  'foldkit',
6
8
  '@foldkit/ui',
@@ -12,26 +14,55 @@ const FOLDKIT_SINGLETON_PACKAGES = [
12
14
  * @internal
13
15
  */
14
16
  export const isFoldkitSingletonPackageSpecifier = (specifier) => Array.some(FOLDKIT_SINGLETON_PACKAGES, packageName => specifier === packageName || specifier.startsWith(`${packageName}/`));
15
- /** Resolves the installed packages that must share Foldkit's runtime instance.
17
+ const isFoldkitPackageName = (name) => name === 'foldkit' || name.startsWith('@foldkit/');
18
+ const decodeDependencyRecord = Schema.decodeUnknownOption(Schema.Record(Schema.String, Schema.Unknown));
19
+ const dependencyNames = (field) => Option.match(decodeDependencyRecord(field), {
20
+ onNone: () => [],
21
+ onSome: Record.keys,
22
+ });
23
+ const dependsOnFoldkit = (packageJson) => pipe([packageJson['dependencies'], packageJson['peerDependencies']], Array.flatMap(dependencyNames), Array.some(isFoldkitPackageName));
24
+ const toRealPath = (path) => {
25
+ try {
26
+ return realpathSync(path);
27
+ }
28
+ catch {
29
+ return path;
30
+ }
31
+ };
32
+ const toCrawlRoot = (root, isPreservingSymlinks) => {
33
+ const absoluteRoot = resolve(root);
34
+ // NOTE: The crawl and the dedupe lookup must start from the root Vite
35
+ // resolves from: the path as given under resolve.preserveSymlinks, and its
36
+ // realpath otherwise. Any other root makes them walk a different directory
37
+ // chain than Vite's resolver.
38
+ if (isPreservingSymlinks) {
39
+ return absoluteRoot;
40
+ }
41
+ return toRealPath(absoluteRoot);
42
+ };
43
+ /** Finds the installed packages an application's server render must bundle
44
+ * and the Foldkit packages Vite must deduplicate.
16
45
  *
17
46
  * @internal
18
47
  */
19
- export const resolveInstalledFoldkitPackages = (root) => {
20
- // NOTE: Vite can supply a relative root before it resolves the config, while
21
- // createRequire requires an absolute path.
22
- const requireFromRoot = createRequire(resolve(root, 'noop.js'));
23
- return Array.filter(FOLDKIT_SINGLETON_PACKAGES, packageName => {
24
- try {
25
- requireFromRoot.resolve(packageName);
26
- return true;
27
- }
28
- catch (error) {
29
- // NOTE: optional ESM-only packages can throw
30
- // ERR_PACKAGE_PATH_NOT_EXPORTED even when installed. Only
31
- // MODULE_NOT_FOUND proves this consumer does not have the package.
32
- return !(error instanceof Error &&
33
- Predicate.hasProperty(error, 'code') &&
34
- error.code === 'MODULE_NOT_FOUND');
35
- }
48
+ export const crawlFoldkitPackages = async (root, isBuild, viteUserConfig) => {
49
+ const crawlRoot = toCrawlRoot(root, viteUserConfig.resolve?.preserveSymlinks ?? false);
50
+ // NOTE: vitefu realpaths each dependency's package.json before comparing it
51
+ // with workspaceRoot, including when Vite preserves symlinks. The workspace
52
+ // boundary must use the same form so private workspace packages are found.
53
+ const workspaceRoot = toRealPath(searchForWorkspaceRoot(crawlRoot));
54
+ const crawl = await crawlFrameworkPkgs({
55
+ root: crawlRoot,
56
+ workspaceRoot,
57
+ isBuild,
58
+ viteUserConfig,
59
+ isSemiFrameworkPkgByJson: dependsOnFoldkit,
36
60
  });
61
+ const ssrNoExternal = Array.dedupe([
62
+ ...FOLDKIT_SINGLETON_PACKAGES,
63
+ ...crawl.ssr.noExternal,
64
+ ]);
65
+ const maybeResolvableSingletons = await Promise.all(Array.map(FOLDKIT_SINGLETON_PACKAGES, async (packageName) => pipe(await findDepPkgJsonPath(packageName, crawlRoot), Option.fromUndefinedOr, Option.as(packageName))));
66
+ const dedupe = Array.getSomes(maybeResolvableSingletons);
67
+ return { dedupe, ssrNoExternal };
37
68
  };
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AA6CA,OAAO,KAAK,EAEV,MAAM,EAIP,MAAM,MAAM,CAAA;AAOb,OAAO,EAAE,KAAK,mBAAmB,EAAgB,MAAM,YAAY,CAAA;AAKnE,OAAO,EAAE,KAAK,iBAAiB,EAAc,MAAM,UAAU,CAAA;AAG7D,OAAO,EAAE,KAAK,eAAe,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAA;AACzE,OAAO,EACL,uBAAuB,EACvB,oBAAoB,EACpB,oBAAoB,EACpB,KAAK,eAAe,EACpB,KAAK,mBAAmB,EACxB,KAAK,uBAAuB,EAC5B,YAAY,GACb,MAAM,YAAY,CAAA;AACnB,OAAO,EAAE,KAAK,iBAAiB,EAAE,UAAU,EAAE,MAAM,UAAU,CAAA;AAC7D,OAAO,EACL,KAAK,2BAA2B,EAChC,mBAAmB,EACnB,qBAAqB,GACtB,MAAM,mBAAmB,CAAA;AAE1B,6CAA6C;AAC7C,MAAM,MAAM,oBAAoB,GAAG,QAAQ,CAAC;IAC1C;;;;;;;;OAQG;IACH,eAAe,CAAC,EAAE,MAAM,GAAG,KAAK,CAAA;IAChC;;;;;;;OAOG;IACH,GAAG,CAAC,EAAE,IAAI,CAAC,iBAAiB,EAAE,SAAS,GAAG,gBAAgB,CAAC,GACzD,QAAQ,CAAC;QACP;;;;;;;;WAQG;QACH,KAAK,CAAC,EAAE,OAAO,GAAG,mBAAmB,CAAA;KACtC,CAAC,CAAA;IACJ;;;;;;;;;;;;;;;;OAgBG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB,CAAC,CAAA;AAs6BF,eAAO,MAAM,OAAO,aAAa,oBAAoB,KAAQ,KAAK,CAAC,MAAM,CAuHxE,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AA6CA,OAAO,KAAK,EAEV,MAAM,EAIP,MAAM,MAAM,CAAA;AAOb,OAAO,EAAE,KAAK,mBAAmB,EAAgB,MAAM,YAAY,CAAA;AAKnE,OAAO,EAAE,KAAK,iBAAiB,EAAc,MAAM,UAAU,CAAA;AAG7D,OAAO,EAAE,KAAK,eAAe,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAA;AACzE,OAAO,EACL,uBAAuB,EACvB,oBAAoB,EACpB,oBAAoB,EACpB,KAAK,eAAe,EACpB,KAAK,mBAAmB,EACxB,KAAK,uBAAuB,EAC5B,YAAY,GACb,MAAM,YAAY,CAAA;AACnB,OAAO,EAAE,KAAK,iBAAiB,EAAE,UAAU,EAAE,MAAM,UAAU,CAAA;AAC7D,OAAO,EACL,KAAK,2BAA2B,EAChC,mBAAmB,EACnB,qBAAqB,GACtB,MAAM,mBAAmB,CAAA;AAE1B,6CAA6C;AAC7C,MAAM,MAAM,oBAAoB,GAAG,QAAQ,CAAC;IAC1C;;;;;;;;OAQG;IACH,eAAe,CAAC,EAAE,MAAM,GAAG,KAAK,CAAA;IAChC;;;;;;;OAOG;IACH,GAAG,CAAC,EAAE,IAAI,CAAC,iBAAiB,EAAE,SAAS,GAAG,gBAAgB,CAAC,GACzD,QAAQ,CAAC;QACP;;;;;;;;WAQG;QACH,KAAK,CAAC,EAAE,OAAO,GAAG,mBAAmB,CAAA;KACtC,CAAC,CAAA;IACJ;;;;;;;;;;;;;;;;OAgBG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB,CAAC,CAAA;AAs6BF,eAAO,MAAM,OAAO,aAAa,oBAAoB,KAAQ,KAAK,CAAC,MAAM,CAyHxE,CAAA"}
package/dist/index.js CHANGED
@@ -10,7 +10,7 @@ import * as NodePath from '@effect/platform-node/NodePath';
10
10
  import { foldkitBuild } from './build.js';
11
11
  import { foldkitBuildToken } from './buildToken.js';
12
12
  import { devToolsOverlayPlugin } from './devToolsOverlay.js';
13
- import { resolveInstalledFoldkitPackages } from './foldkitPackages.js';
13
+ import { crawlFoldkitPackages } from './foldkitPackages.js';
14
14
  import { publishRelayRecord, retireRelayRecord } from './relayRegistry.js';
15
15
  import { foldkitSsr } from './ssr.js';
16
16
  import { foldkitViewIdentity } from './viewIdentity.js';
@@ -18,16 +18,15 @@ export { brandDistDirectory } from './brandDist.js';
18
18
  export { FOLDKIT_FETCH_MODULE_ID, FoldkitBuildManifest, FoldkitBuildMetadata, foldkitBuild, } from './build.js';
19
19
  export { foldkitSsr } from './ssr.js';
20
20
  export { foldkitViewIdentity, transformViewIdentity, } from './viewIdentity.js';
21
- // NOTE: Vite's dep optimizer scans the consumer's source for `effect`
22
- // imports and pre-bundles only those exports into a single `effect.js`
23
- // blob. It does not follow imports through workspace/node_modules
24
- // packages, so any `effect` namespace foldkit's compiled dist references
25
- // that the consumer does not mention by name is missing from the blob
26
- // and crashes at runtime in dev. The list below covers every top-level
27
- // namespace foldkit imports from bare `'effect'`. Over-inclusion is
28
- // harmless; under-inclusion is the bug. Kept in sync with foldkit's
29
- // source by `scripts/check-effect-prebundle.ts` (runs in `pnpm check`).
30
- const FORCE_INCLUDED_EFFECT_NAMESPACES = [
21
+ // NOTE: Vite does not scan imports through `foldkit` because the plugin
22
+ // excludes the package from optimization. A consumer can import only Effect
23
+ // subpaths while Foldkit's compiled distribution imports the bare barrel, so
24
+ // include both the barrel and every top-level namespace Foldkit imports. This
25
+ // keeps them in one optimized dependency graph. Over-inclusion is harmless;
26
+ // under-inclusion is the bug. `scripts/check-effect-prebundle.ts` keeps the
27
+ // entries in sync with Foldkit's source and runs in `pnpm check`.
28
+ const FORCE_INCLUDED_EFFECT_ENTRIES = [
29
+ 'effect',
31
30
  'effect/Array',
32
31
  'effect/Boolean',
33
32
  'effect/Cause',
@@ -55,6 +54,7 @@ const FORCE_INCLUDED_EFFECT_NAMESPACES = [
55
54
  'effect/PubSub',
56
55
  'effect/Queue',
57
56
  'effect/Record',
57
+ 'effect/Redacted',
58
58
  'effect/Ref',
59
59
  'effect/Result',
60
60
  'effect/Runtime',
@@ -515,7 +515,7 @@ export const foldkit = (options = {}) => {
515
515
  apply: 'serve',
516
516
  config: () => ({
517
517
  optimizeDeps: {
518
- include: [...FORCE_INCLUDED_EFFECT_NAMESPACES],
518
+ include: [...FORCE_INCLUDED_EFFECT_ENTRIES],
519
519
  },
520
520
  }),
521
521
  configureServer: server => {
@@ -544,22 +544,22 @@ export const foldkit = (options = {}) => {
544
544
  };
545
545
  const resolutionPlugin = {
546
546
  name: 'foldkit:resolution',
547
- config: userConfig => {
548
- const singletonPackages = resolveInstalledFoldkitPackages(userConfig.root ?? process.cwd());
547
+ config: async (userConfig, { command }) => {
548
+ const foldkitPackages = await crawlFoldkitPackages(userConfig.root ?? process.cwd(), command === 'build', userConfig);
549
549
  return {
550
550
  optimizeDeps: {
551
551
  exclude: ['foldkit'],
552
552
  },
553
553
  resolve: {
554
- dedupe: singletonPackages,
554
+ dedupe: foldkitPackages.dedupe,
555
555
  },
556
556
  ssr: {
557
- noExternal: singletonPackages,
557
+ noExternal: foldkitPackages.ssrNoExternal,
558
558
  },
559
559
  environments: {
560
560
  ssr: {
561
561
  resolve: {
562
- noExternal: singletonPackages,
562
+ noExternal: foldkitPackages.ssrNoExternal,
563
563
  },
564
564
  },
565
565
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@foldkit/vite-plugin",
3
- "version": "0.26.0",
3
+ "version": "0.26.1",
4
4
  "description": "Vite plugin for Foldkit with state-preserving live reload",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -24,6 +24,7 @@
24
24
  "@effect/platform-node": "4.0.0",
25
25
  "@effect/platform-node-shared": "4.0.0",
26
26
  "magic-string": "^1.4.2",
27
+ "vitefu": "^1.1.3",
27
28
  "ws": "^8.21.3"
28
29
  },
29
30
  "devDependencies": {
@@ -37,7 +38,7 @@
37
38
  "vite": "^8.3.1",
38
39
  "vite-host": "npm:vite@8.2.1",
39
40
  "vitest": "^4.1.11",
40
- "foldkit": "0.165.0"
41
+ "foldkit": "0.166.0"
41
42
  },
42
43
  "keywords": [
43
44
  "vite",