@typed/vite-plugin 0.17.1 → 1.0.0-beta.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.
Files changed (48) hide show
  1. package/README.md +96 -0
  2. package/dist/index.d.ts +77 -4
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +84 -5
  5. package/package.json +27 -29
  6. package/src/index.test.ts +97 -0
  7. package/src/index.ts +183 -4
  8. package/dist/cjs/constants.d.ts +0 -2
  9. package/dist/cjs/constants.d.ts.map +0 -1
  10. package/dist/cjs/constants.js +0 -5
  11. package/dist/cjs/constants.js.map +0 -1
  12. package/dist/cjs/index.d.ts +0 -5
  13. package/dist/cjs/index.d.ts.map +0 -1
  14. package/dist/cjs/index.js +0 -26
  15. package/dist/cjs/index.js.map +0 -1
  16. package/dist/cjs/resolveTypedConfig.d.ts +0 -20
  17. package/dist/cjs/resolveTypedConfig.d.ts.map +0 -1
  18. package/dist/cjs/resolveTypedConfig.js +0 -16
  19. package/dist/cjs/resolveTypedConfig.js.map +0 -1
  20. package/dist/cjs/vite-plugin.d.ts +0 -56
  21. package/dist/cjs/vite-plugin.d.ts.map +0 -1
  22. package/dist/cjs/vite-plugin.js +0 -212
  23. package/dist/cjs/vite-plugin.js.map +0 -1
  24. package/dist/constants.d.ts +0 -2
  25. package/dist/constants.d.ts.map +0 -1
  26. package/dist/constants.js +0 -2
  27. package/dist/constants.js.map +0 -1
  28. package/dist/index.js.map +0 -1
  29. package/dist/resolveTypedConfig.d.ts +0 -20
  30. package/dist/resolveTypedConfig.d.ts.map +0 -1
  31. package/dist/resolveTypedConfig.js +0 -12
  32. package/dist/resolveTypedConfig.js.map +0 -1
  33. package/dist/tsconfig.cjs.build.tsbuildinfo +0 -1
  34. package/dist/vite-plugin.d.ts +0 -56
  35. package/dist/vite-plugin.d.ts.map +0 -1
  36. package/dist/vite-plugin.js +0 -179
  37. package/dist/vite-plugin.js.map +0 -1
  38. package/eslintrc.json +0 -3
  39. package/project.json +0 -44
  40. package/readme.md +0 -3
  41. package/src/constants.ts +0 -1
  42. package/src/resolveTypedConfig.ts +0 -35
  43. package/src/vite-plugin.ts +0 -312
  44. package/tsconfig.build.json +0 -8
  45. package/tsconfig.build.tsbuildinfo +0 -1
  46. package/tsconfig.cjs.build.json +0 -13
  47. package/tsconfig.json +0 -18
  48. package/vite.config.js +0 -3
package/README.md ADDED
@@ -0,0 +1,96 @@
1
+ # @typed/vite-plugin
2
+
3
+ > **Beta:** This package is in beta; APIs may change.
4
+
5
+ `@typed/vite-plugin` provides a **one-stop Vite preset**: tsconfig path resolution, virtual modules (router + HttpApi from @typed/app), optional bundle analyzer, and Brotli compression. Use it as the main plugin array for typed-smol Vite projects.
6
+
7
+ ## Purpose
8
+
9
+ `typedVitePlugin` is the recommended way to configure Vite for typed-smol apps. One call gives you: path resolution from `tsconfig.json`, virtual `router:` and `api:` imports that generate typed route matchers and API clients from convention-based source, optional bundle analysis, and Brotli compression for build output.
10
+
11
+ ## Capabilities
12
+
13
+ | Area | What you get |
14
+ |------|--------------|
15
+ | **Router VM** | `router:./path` → typed Matcher from route files |
16
+ | **HttpApi VM** | `api:./path` → typed Api + Client + OpenAPI (when `apiVmOptions` set) |
17
+ | **TypeInfo** | Structural type-checking of route/endpoint contracts (when `createTypeInfoApiSession` provided) |
18
+ | **tsconfig paths** | Path alias resolution (default: on) |
19
+ | **Analyzer** | `dist/stats.html` treemap when `ANALYZE=1` |
20
+ | **Compression** | Brotli `.br` for build output (default: on) |
21
+
22
+ ## Architecture
23
+
24
+ ```mermaid
25
+ flowchart TB
26
+ subgraph plugins [Plugin array from typedVitePlugin]
27
+ Tsconfig[tsconfig paths]
28
+ VM[virtualModulesVitePlugin]
29
+ Viz[visualizer]
30
+ Comp[viteCompression]
31
+ end
32
+ subgraph vmResolver [VM resolver]
33
+ RouterVM[router VM]
34
+ ApiVM[HttpApi VM]
35
+ end
36
+ VM --> vmResolver
37
+ ```
38
+
39
+ ## Dependencies
40
+
41
+ - `@typed/app`
42
+ - `@typed/virtual-modules`
43
+ - `@typed/virtual-modules-vite`
44
+
45
+ Peer: `vite` (>=5).
46
+
47
+ ## Installation
48
+
49
+ ```bash
50
+ pnpm add @typed/vite-plugin @typed/app
51
+ ```
52
+
53
+ ## Usage
54
+
55
+ ```ts
56
+ import { defineConfig } from "vite";
57
+ import { typedVitePlugin } from "@typed/vite-plugin";
58
+
59
+ export default defineConfig({
60
+ plugins: typedVitePlugin(),
61
+ });
62
+ ```
63
+
64
+ For router VM type-checking, pass `createTypeInfoApiSession` (e.g. from a custom Vite plugin that builds a TypeScript program):
65
+
66
+ ```ts
67
+ import { typedVitePlugin } from "@typed/vite-plugin";
68
+ import { createTypeInfoApiSessionForApp } from "@typed/app";
69
+
70
+ export default defineConfig({
71
+ plugins: typedVitePlugin({
72
+ createTypeInfoApiSession: ({ ts, program }) =>
73
+ createTypeInfoApiSessionForApp({ ts, program }),
74
+ routerVmOptions: {},
75
+ apiVmOptions: {},
76
+ }),
77
+ });
78
+ ```
79
+
80
+ ## API overview
81
+
82
+ - **`typedVitePlugin(options)`** — Returns an array of Vite plugins.
83
+ - **`createTypedViteResolver(options)`** — Builds the virtual-module resolver (for tests or custom compositions).
84
+
85
+ ## Options
86
+
87
+ | Option | Type | Default | Description |
88
+ | ------ | ---- | ------- | ----------- |
89
+ | `routerVmOptions` | `RouterVirtualModulePluginOptions` | `{}` | Options for the router VM plugin. |
90
+ | `apiVmOptions` | `HttpApiVirtualModulePluginOptions` | — | When set, enables the HttpApi VM plugin. |
91
+ | `createTypeInfoApiSession` | `CreateTypeInfoApiSession` | — | Required for router VM type-checking in dev. |
92
+ | `tsconfigPaths` | `boolean \| object` | `true` | Enable tsconfig path resolution. |
93
+ | `analyze` | `boolean \| object` | `process.env.ANALYZE === '1'` | Enable bundle analyzer (dist/stats.html). |
94
+ | `warnOnError` | `boolean` | `true` | Log virtual module resolution errors. |
95
+ | `compression` | `boolean \| object` | `true` | Brotli compression for build output. |
96
+
package/dist/index.d.ts CHANGED
@@ -1,5 +1,78 @@
1
- import typed from './vite-plugin.js';
2
- export default typed;
3
- export * from './vite-plugin.js';
4
- export { resolveTypedConfig } from './resolveTypedConfig.js';
1
+ /**
2
+ * @typed/vite-plugin — One-stop Vite preset: tsconfig paths, bundle analyzer,
3
+ * Brotli compression, virtual-modules Vite plugin, and @typed/app VM plugins.
4
+ */
5
+ import type { Plugin } from "vite";
6
+ import type { CreateTypeInfoApiSession, VirtualModuleResolver } from "@typed/virtual-modules";
7
+ /**
8
+ * Options passed through to the HttpApi virtual-module plugin when enabled.
9
+ * Forwarded to createHttpApiVirtualModulePlugin from the package that provides it
10
+ * (e.g. @typed/app when the export is added). See httpapi-virtual-module-plugin spec.
11
+ */
12
+ export interface HttpApiVirtualModulePluginOptions {
13
+ readonly [key: string]: unknown;
14
+ }
15
+ /** Options for vite-plugin-compression when compression is enabled. */
16
+ export type TypedViteCompressionOptions = boolean | {
17
+ readonly algorithm?: "gzip" | "brotliCompress" | "deflate" | "deflateRaw";
18
+ readonly ext?: string;
19
+ readonly threshold?: number;
20
+ readonly [key: string]: unknown;
21
+ };
22
+ export interface TypedVitePluginOptions {
23
+ /**
24
+ * Options for the router VM plugin from @typed/app.
25
+ */
26
+ readonly routerVmOptions?: import("@typed/app").RouterVirtualModulePluginOptions;
27
+ /**
28
+ * Options for the HttpApi VM plugin from @typed/app. HttpApi VM plugin is always
29
+ * registered (router first, then HttpApi). Use this to customize its behavior.
30
+ */
31
+ readonly apiVmOptions?: HttpApiVirtualModulePluginOptions;
32
+ /**
33
+ * Session factory for TypeInfo API. When not provided, a Language Service-backed
34
+ * session is auto-created from the project's tsconfig (evolves as files change).
35
+ * Override for custom session setup.
36
+ */
37
+ readonly createTypeInfoApiSession?: CreateTypeInfoApiSession;
38
+ /**
39
+ * Enable tsconfig path resolution. Default true.
40
+ */
41
+ readonly tsconfigPaths?: boolean | Record<string, unknown>;
42
+ /**
43
+ * Enable bundle analyzer. Default: process.env.ANALYZE === '1'.
44
+ */
45
+ readonly analyze?: boolean | {
46
+ filename?: string;
47
+ open?: boolean;
48
+ template?: string;
49
+ };
50
+ /**
51
+ * When true, virtual module resolution errors are logged. Default true.
52
+ */
53
+ readonly warnOnError?: boolean;
54
+ /**
55
+ * Enable Brotli compression for build. Default true.
56
+ * Set false to disable, or pass options to customize (algorithm, ext, threshold).
57
+ */
58
+ readonly compression?: TypedViteCompressionOptions;
59
+ }
60
+ /** Optional dependency injection for createTypedViteResolver (e.g. for tests). */
61
+ export interface TypedViteResolverDependencies {
62
+ createHttpApiVirtualModulePlugin?: (opts: HttpApiVirtualModulePluginOptions) => import("@typed/virtual-modules").VirtualModulePlugin;
63
+ }
64
+ /**
65
+ * Invariant: ALL @typed/app VM plugins are always registered. There are no optional
66
+ * or conditional app plugins. When adding a new VM plugin to @typed/app, add it here.
67
+ */
68
+ export declare function createTypedViteResolver(options?: TypedVitePluginOptions, dependencies?: TypedViteResolverDependencies): VirtualModuleResolver;
69
+ /**
70
+ * Returns Vite plugins: tsconfig paths, virtual modules (@typed/app), and optional bundle analyzer.
71
+ * Use as: `defineConfig({ plugins: typedVitePlugin() })`.
72
+ *
73
+ * When createTypeInfoApiSession is not provided, the plugin automatically creates a
74
+ * Language Service-backed session from the project's tsconfig. The type program evolves
75
+ * as files change during dev. Pass createTypeInfoApiSession to override.
76
+ */
77
+ export declare function typedVitePlugin(options?: TypedVitePluginOptions): Plugin[];
5
78
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,kBAAkB,CAAA;AAEpC,eAAe,KAAK,CAAA;AAEpB,cAAc,kBAAkB,CAAA;AAEhC,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AACnC,OAAO,KAAK,EAAE,wBAAwB,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AAgB9F;;;;GAIG;AACH,MAAM,WAAW,iCAAiC;IAChD,QAAQ,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACjC;AAED,uEAAuE;AACvE,MAAM,MAAM,2BAA2B,GACnC,OAAO,GACP;IACE,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,gBAAgB,GAAG,SAAS,GAAG,YAAY,CAAC;IAC1E,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACjC,CAAC;AAEN,MAAM,WAAW,sBAAsB;IACrC;;OAEG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,OAAO,YAAY,EAAE,gCAAgC,CAAC;IAEjF;;;OAGG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,iCAAiC,CAAC;IAE1D;;;;OAIG;IACH,QAAQ,CAAC,wBAAwB,CAAC,EAAE,wBAAwB,CAAC;IAE7D;;OAEG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAE3D;;OAEG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,GAAG;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,OAAO,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAEtF;;OAEG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,OAAO,CAAC;IAE/B;;;OAGG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,2BAA2B,CAAC;CACpD;AAED,kFAAkF;AAClF,MAAM,WAAW,6BAA6B;IAC5C,gCAAgC,CAAC,EAAE,CACjC,IAAI,EAAE,iCAAiC,KACpC,OAAO,wBAAwB,EAAE,mBAAmB,CAAC;CAC3D;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CACrC,OAAO,GAAE,sBAA2B,EACpC,YAAY,CAAC,EAAE,6BAA6B,GAC3C,qBAAqB,CASvB;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,OAAO,GAAE,sBAA2B,GAAG,MAAM,EAAE,CAuE9E"}
package/dist/index.js CHANGED
@@ -1,5 +1,84 @@
1
- import typed from './vite-plugin.js';
2
- export default typed;
3
- export * from './vite-plugin.js';
4
- export { resolveTypedConfig } from './resolveTypedConfig.js';
5
- //# sourceMappingURL=index.js.map
1
+ import { collectTypeTargetSpecsFromPlugins, createLanguageServiceSessionFactory, PluginManager, } from "@typed/virtual-modules";
2
+ import { createHttpApiVirtualModulePlugin, createRouterVirtualModulePlugin, } from "@typed/app";
3
+ import { virtualModulesVitePlugin } from "@typed/virtual-modules-vite";
4
+ import ts from "typescript";
5
+ import tsconfigPaths from "vite-tsconfig-paths";
6
+ import { visualizer } from "rollup-plugin-visualizer";
7
+ import viteCompression from "vite-plugin-compression";
8
+ /**
9
+ * Invariant: ALL @typed/app VM plugins are always registered. There are no optional
10
+ * or conditional app plugins. When adding a new VM plugin to @typed/app, add it here.
11
+ */
12
+ export function createTypedViteResolver(options = {}, dependencies) {
13
+ const httpApiFactory = dependencies?.createHttpApiVirtualModulePlugin ??
14
+ createHttpApiVirtualModulePlugin;
15
+ const plugins = [
16
+ createRouterVirtualModulePlugin(options.routerVmOptions ?? {}),
17
+ httpApiFactory(options.apiVmOptions ?? {}),
18
+ ];
19
+ return new PluginManager(plugins);
20
+ }
21
+ /**
22
+ * Returns Vite plugins: tsconfig paths, virtual modules (@typed/app), and optional bundle analyzer.
23
+ * Use as: `defineConfig({ plugins: typedVitePlugin() })`.
24
+ *
25
+ * When createTypeInfoApiSession is not provided, the plugin automatically creates a
26
+ * Language Service-backed session from the project's tsconfig. The type program evolves
27
+ * as files change during dev. Pass createTypeInfoApiSession to override.
28
+ */
29
+ export function typedVitePlugin(options = {}) {
30
+ const resolver = createTypedViteResolver(options);
31
+ const analyze = options.analyze ?? (process.env.ANALYZE === "1" ? true : false);
32
+ let createTypeInfoApiSession = options.createTypeInfoApiSession;
33
+ if (createTypeInfoApiSession === undefined) {
34
+ try {
35
+ const manager = resolver;
36
+ const typeTargetSpecs = collectTypeTargetSpecsFromPlugins(manager.plugins);
37
+ createTypeInfoApiSession = createLanguageServiceSessionFactory({
38
+ ts,
39
+ projectRoot: process.cwd(),
40
+ typeTargetSpecs,
41
+ });
42
+ }
43
+ catch {
44
+ // Graceful degradation: no session, plugins get noop TypeInfoApi
45
+ }
46
+ }
47
+ const plugins = [];
48
+ if (options.tsconfigPaths !== false) {
49
+ const pathsOpts = typeof options.tsconfigPaths === "object" ? options.tsconfigPaths : {};
50
+ plugins.push(tsconfigPaths(pathsOpts));
51
+ }
52
+ plugins.push(virtualModulesVitePlugin({
53
+ resolver,
54
+ createTypeInfoApiSession,
55
+ warnOnError: options.warnOnError ?? true,
56
+ }));
57
+ if (analyze) {
58
+ const vizOpts = typeof analyze === "object"
59
+ ? analyze
60
+ : { filename: "dist/stats.html", template: "treemap" };
61
+ plugins.push(visualizer({
62
+ filename: vizOpts.filename ?? "dist/stats.html",
63
+ open: vizOpts.open ?? false,
64
+ template: vizOpts.template ?? "treemap",
65
+ }));
66
+ }
67
+ const compression = options.compression ?? true;
68
+ if (compression !== false) {
69
+ const compressionOpts = typeof compression === "object"
70
+ ? {
71
+ algorithm: "brotliCompress",
72
+ ext: ".br",
73
+ threshold: 1024,
74
+ ...compression,
75
+ }
76
+ : {
77
+ algorithm: "brotliCompress",
78
+ ext: ".br",
79
+ threshold: 1024,
80
+ };
81
+ plugins.push(viteCompression(compressionOpts));
82
+ }
83
+ return plugins;
84
+ }
package/package.json CHANGED
@@ -1,42 +1,40 @@
1
1
  {
2
2
  "name": "@typed/vite-plugin",
3
- "version": "0.17.1",
3
+ "version": "1.0.0-beta.1",
4
4
  "type": "module",
5
- "main": "dist/index.js",
6
- "module": "dist/index.js",
7
- "typings": "dist/index.d.ts",
8
- "types": "dist/index.d.ts",
9
5
  "exports": {
10
6
  ".": {
11
- "import": "./dist/index.js",
12
- "require": "./dist/cjs/index.js",
13
- "types": "./dist/index.d.ts"
14
- },
15
- "./*": {
16
- "import": "./dist/*.js",
17
- "require": "./dist/cjs/*.js",
18
- "types": "./dist/*.d.ts"
7
+ "types": "./dist/index.d.ts",
8
+ "import": "./dist/index.js"
19
9
  }
20
10
  },
11
+ "publishConfig": {
12
+ "access": "public"
13
+ },
14
+ "scripts": {
15
+ "build": "[ -d dist ] || rm -f tsconfig.tsbuildinfo; tsc",
16
+ "test": "vitest run --passWithNoTests"
17
+ },
21
18
  "dependencies": {
22
- "@effect/data": "^0.17.6",
23
- "@typed/virtual-module": "0.1.2",
24
- "fast-glob": "^3.3.1",
25
- "rollup-plugin-visualizer": "^5.9.2",
26
- "vavite": "^2.0.2",
27
- "vite": "^4.4.9",
19
+ "@typed/app": "workspace:*",
20
+ "@typed/virtual-modules": "workspace:*",
21
+ "@typed/virtual-modules-vite": "workspace:*",
22
+ "rollup-plugin-visualizer": "^5.12.0",
28
23
  "vite-plugin-compression": "^0.5.1",
29
- "vite-tsconfig-paths": "^4.2.0"
24
+ "vite-tsconfig-paths": "^6.1.0"
30
25
  },
31
- "peerDependencies": {
32
- "vite-node": "^0.28.3"
26
+ "devDependencies": {
27
+ "@types/node": "^25.3.0",
28
+ "typescript": "catalog:",
29
+ "vite": "^7.3.1",
30
+ "vitest": "catalog:"
33
31
  },
34
- "gitHead": "8cba441523534aa1bd327bd84eb0e71334bb914e",
35
- "publishConfig": {
36
- "access": "public"
32
+ "peerDependencies": {
33
+ "typescript": ">=5.0.0",
34
+ "vite": ">=5.0.0"
37
35
  },
38
- "sideEffects": false,
39
- "devDependencies": {
40
- "@types/node": "^20.5.6"
41
- }
36
+ "files": [
37
+ "dist",
38
+ "src"
39
+ ]
42
40
  }
@@ -0,0 +1,97 @@
1
+ /**
2
+ * Tests for typedVitePlugin and createTypedViteResolver: plugin order and options pass-through.
3
+ * See .docs/specs/httpapi-virtual-module-plugin/spec.md (Vite Plugin Integration Surface)
4
+ * and testing-strategy.md (typedVitePlugin registration order and option wiring).
5
+ */
6
+ import { describe, expect, it } from "vitest";
7
+ import { PluginManager } from "@typed/virtual-modules";
8
+ import type { VirtualModulePlugin } from "@typed/virtual-modules";
9
+ import {
10
+ createTypedViteResolver,
11
+ typedVitePlugin,
12
+ type HttpApiVirtualModulePluginOptions,
13
+ } from "./index.js";
14
+
15
+ function fakeHttpApiPlugin(opts: HttpApiVirtualModulePluginOptions): VirtualModulePlugin {
16
+ return {
17
+ name: "httpapi-virtual-module",
18
+ shouldResolve: () => false,
19
+ build: () => "",
20
+ _testOpts: opts,
21
+ } as VirtualModulePlugin & { _testOpts: HttpApiVirtualModulePluginOptions };
22
+ }
23
+
24
+ describe("createTypedViteResolver", () => {
25
+ it("always registers router and HttpApi VM plugins", () => {
26
+ const resolver = createTypedViteResolver({});
27
+ expect(resolver).toBeInstanceOf(PluginManager);
28
+ const manager = resolver as PluginManager;
29
+ expect(manager.plugins).toHaveLength(2);
30
+ expect(manager.plugins[0].name).toBe("router-virtual-module");
31
+ expect(manager.plugins[1].name).toBe("httpapi-virtual-module");
32
+ });
33
+
34
+ it("uses DI override for HttpApi plugin when provided", () => {
35
+ const resolver = createTypedViteResolver(
36
+ { apiVmOptions: { prefix: "api:" } },
37
+ { createHttpApiVirtualModulePlugin: fakeHttpApiPlugin },
38
+ );
39
+ const manager = resolver as PluginManager;
40
+ expect(manager.plugins).toHaveLength(2);
41
+ expect(manager.plugins[0].name).toBe("router-virtual-module");
42
+ expect(manager.plugins[1].name).toBe("httpapi-virtual-module");
43
+ const apiPlugin = manager.plugins[1] as VirtualModulePlugin & {
44
+ _testOpts: HttpApiVirtualModulePluginOptions;
45
+ };
46
+ expect(apiPlugin._testOpts).toEqual({ prefix: "api:" });
47
+ });
48
+
49
+ it("passes apiVmOptions through to createHttpApiVirtualModulePlugin", () => {
50
+ const opts: HttpApiVirtualModulePluginOptions = { custom: "value", count: 1 };
51
+ const resolver = createTypedViteResolver(
52
+ { apiVmOptions: opts },
53
+ { createHttpApiVirtualModulePlugin: fakeHttpApiPlugin },
54
+ );
55
+ const manager = resolver as PluginManager;
56
+ const apiPlugin = manager.plugins[1] as VirtualModulePlugin & {
57
+ _testOpts: HttpApiVirtualModulePluginOptions;
58
+ };
59
+ expect(apiPlugin._testOpts).toEqual(opts);
60
+ });
61
+
62
+ it("uses routerVmOptions for the router plugin", () => {
63
+ const resolver = createTypedViteResolver({
64
+ routerVmOptions: { prefix: "routes:", name: "custom-router" },
65
+ });
66
+ const manager = resolver as PluginManager;
67
+ expect(manager.plugins).toHaveLength(2);
68
+ expect(manager.plugins[0].name).toBe("custom-router");
69
+ expect(manager.plugins[1].name).toBe("httpapi-virtual-module");
70
+ });
71
+ });
72
+
73
+ describe("typedVitePlugin", () => {
74
+ it("returns a non-empty plugin array", () => {
75
+ const plugins = typedVitePlugin();
76
+ expect(Array.isArray(plugins)).toBe(true);
77
+ expect(plugins.length).toBeGreaterThan(0);
78
+ });
79
+
80
+ it("returns virtual-modules plugin with resolveId and load", () => {
81
+ const plugins = typedVitePlugin({ tsconfigPaths: false, compression: false });
82
+ const virtualPlugin = plugins.find(
83
+ (p) => p && typeof p === "object" && "name" in p && (p as { name?: string }).name === "virtual-modules",
84
+ );
85
+ expect(virtualPlugin).toBeDefined();
86
+ });
87
+
88
+ it("auto-creates LS-backed session when createTypeInfoApiSession is not provided", () => {
89
+ const plugins = typedVitePlugin({ tsconfigPaths: false, compression: false });
90
+ const virtualPlugin = plugins.find(
91
+ (p) => p && typeof p === "object" && "name" in p && (p as { name?: string }).name === "virtual-modules",
92
+ );
93
+ expect(virtualPlugin).toBeDefined();
94
+ expect(virtualPlugin).toHaveProperty("resolveId");
95
+ expect(virtualPlugin).toHaveProperty("load");
96
+ });
97
+ });
package/src/index.ts CHANGED
@@ -1,7 +1,186 @@
1
- import typed from './vite-plugin.js'
1
+ /**
2
+ * @typed/vite-plugin — One-stop Vite preset: tsconfig paths, bundle analyzer,
3
+ * Brotli compression, virtual-modules Vite plugin, and @typed/app VM plugins.
4
+ */
5
+ import type { Plugin } from "vite";
6
+ import type { CreateTypeInfoApiSession, VirtualModuleResolver } from "@typed/virtual-modules";
7
+ import {
8
+ collectTypeTargetSpecsFromPlugins,
9
+ createLanguageServiceSessionFactory,
10
+ PluginManager,
11
+ } from "@typed/virtual-modules";
12
+ import {
13
+ createHttpApiVirtualModulePlugin,
14
+ createRouterVirtualModulePlugin,
15
+ } from "@typed/app";
16
+ import { virtualModulesVitePlugin } from "@typed/virtual-modules-vite";
17
+ import ts from "typescript";
18
+ import tsconfigPaths from "vite-tsconfig-paths";
19
+ import { visualizer } from "rollup-plugin-visualizer";
20
+ import viteCompression from "vite-plugin-compression";
2
21
 
3
- export default typed
22
+ /**
23
+ * Options passed through to the HttpApi virtual-module plugin when enabled.
24
+ * Forwarded to createHttpApiVirtualModulePlugin from the package that provides it
25
+ * (e.g. @typed/app when the export is added). See httpapi-virtual-module-plugin spec.
26
+ */
27
+ export interface HttpApiVirtualModulePluginOptions {
28
+ readonly [key: string]: unknown;
29
+ }
4
30
 
5
- export * from './vite-plugin.js'
31
+ /** Options for vite-plugin-compression when compression is enabled. */
32
+ export type TypedViteCompressionOptions =
33
+ | boolean
34
+ | {
35
+ readonly algorithm?: "gzip" | "brotliCompress" | "deflate" | "deflateRaw";
36
+ readonly ext?: string;
37
+ readonly threshold?: number;
38
+ readonly [key: string]: unknown;
39
+ };
6
40
 
7
- export { resolveTypedConfig } from './resolveTypedConfig.js'
41
+ export interface TypedVitePluginOptions {
42
+ /**
43
+ * Options for the router VM plugin from @typed/app.
44
+ */
45
+ readonly routerVmOptions?: import("@typed/app").RouterVirtualModulePluginOptions;
46
+
47
+ /**
48
+ * Options for the HttpApi VM plugin from @typed/app. HttpApi VM plugin is always
49
+ * registered (router first, then HttpApi). Use this to customize its behavior.
50
+ */
51
+ readonly apiVmOptions?: HttpApiVirtualModulePluginOptions;
52
+
53
+ /**
54
+ * Session factory for TypeInfo API. When not provided, a Language Service-backed
55
+ * session is auto-created from the project's tsconfig (evolves as files change).
56
+ * Override for custom session setup.
57
+ */
58
+ readonly createTypeInfoApiSession?: CreateTypeInfoApiSession;
59
+
60
+ /**
61
+ * Enable tsconfig path resolution. Default true.
62
+ */
63
+ readonly tsconfigPaths?: boolean | Record<string, unknown>;
64
+
65
+ /**
66
+ * Enable bundle analyzer. Default: process.env.ANALYZE === '1'.
67
+ */
68
+ readonly analyze?: boolean | { filename?: string; open?: boolean; template?: string };
69
+
70
+ /**
71
+ * When true, virtual module resolution errors are logged. Default true.
72
+ */
73
+ readonly warnOnError?: boolean;
74
+
75
+ /**
76
+ * Enable Brotli compression for build. Default true.
77
+ * Set false to disable, or pass options to customize (algorithm, ext, threshold).
78
+ */
79
+ readonly compression?: TypedViteCompressionOptions;
80
+ }
81
+
82
+ /** Optional dependency injection for createTypedViteResolver (e.g. for tests). */
83
+ export interface TypedViteResolverDependencies {
84
+ createHttpApiVirtualModulePlugin?: (
85
+ opts: HttpApiVirtualModulePluginOptions,
86
+ ) => import("@typed/virtual-modules").VirtualModulePlugin;
87
+ }
88
+
89
+ /**
90
+ * Invariant: ALL @typed/app VM plugins are always registered. There are no optional
91
+ * or conditional app plugins. When adding a new VM plugin to @typed/app, add it here.
92
+ */
93
+ export function createTypedViteResolver(
94
+ options: TypedVitePluginOptions = {},
95
+ dependencies?: TypedViteResolverDependencies,
96
+ ): VirtualModuleResolver {
97
+ const httpApiFactory =
98
+ dependencies?.createHttpApiVirtualModulePlugin ??
99
+ createHttpApiVirtualModulePlugin;
100
+ const plugins: import("@typed/virtual-modules").VirtualModulePlugin[] = [
101
+ createRouterVirtualModulePlugin(options.routerVmOptions ?? {}),
102
+ httpApiFactory(options.apiVmOptions ?? {}),
103
+ ];
104
+ return new PluginManager(plugins);
105
+ }
106
+
107
+ /**
108
+ * Returns Vite plugins: tsconfig paths, virtual modules (@typed/app), and optional bundle analyzer.
109
+ * Use as: `defineConfig({ plugins: typedVitePlugin() })`.
110
+ *
111
+ * When createTypeInfoApiSession is not provided, the plugin automatically creates a
112
+ * Language Service-backed session from the project's tsconfig. The type program evolves
113
+ * as files change during dev. Pass createTypeInfoApiSession to override.
114
+ */
115
+ export function typedVitePlugin(options: TypedVitePluginOptions = {}): Plugin[] {
116
+ const resolver = createTypedViteResolver(options);
117
+ const analyze =
118
+ options.analyze ?? (process.env.ANALYZE === "1" ? true : false);
119
+
120
+ let createTypeInfoApiSession: CreateTypeInfoApiSession | undefined =
121
+ options.createTypeInfoApiSession;
122
+
123
+ if (createTypeInfoApiSession === undefined) {
124
+ try {
125
+ const manager = resolver as PluginManager;
126
+ const typeTargetSpecs = collectTypeTargetSpecsFromPlugins(manager.plugins);
127
+ createTypeInfoApiSession = createLanguageServiceSessionFactory({
128
+ ts,
129
+ projectRoot: process.cwd(),
130
+ typeTargetSpecs,
131
+ });
132
+ } catch {
133
+ // Graceful degradation: no session, plugins get noop TypeInfoApi
134
+ }
135
+ }
136
+
137
+ const plugins: Plugin[] = [];
138
+
139
+ if (options.tsconfigPaths !== false) {
140
+ const pathsOpts =
141
+ typeof options.tsconfigPaths === "object" ? options.tsconfigPaths : {};
142
+ plugins.push(tsconfigPaths(pathsOpts) as Plugin);
143
+ }
144
+
145
+ plugins.push(
146
+ virtualModulesVitePlugin({
147
+ resolver,
148
+ createTypeInfoApiSession,
149
+ warnOnError: options.warnOnError ?? true,
150
+ }),
151
+ );
152
+
153
+ if (analyze) {
154
+ const vizOpts =
155
+ typeof analyze === "object"
156
+ ? analyze
157
+ : { filename: "dist/stats.html", template: "treemap" as const };
158
+ plugins.push(
159
+ visualizer({
160
+ filename: vizOpts.filename ?? "dist/stats.html",
161
+ open: vizOpts.open ?? false,
162
+ template: (vizOpts.template as "treemap" | "sunburst" | "flamegraph" | "network") ?? "treemap",
163
+ }) as Plugin,
164
+ );
165
+ }
166
+
167
+ const compression = options.compression ?? true;
168
+ if (compression !== false) {
169
+ const compressionOpts =
170
+ typeof compression === "object"
171
+ ? {
172
+ algorithm: "brotliCompress" as const,
173
+ ext: ".br",
174
+ threshold: 1024,
175
+ ...compression,
176
+ }
177
+ : {
178
+ algorithm: "brotliCompress" as const,
179
+ ext: ".br",
180
+ threshold: 1024,
181
+ };
182
+ plugins.push(viteCompression(compressionOpts) as Plugin);
183
+ }
184
+
185
+ return plugins;
186
+ }
@@ -1,2 +0,0 @@
1
- export declare const PLUGIN_NAME = "@typed/vite-plugin";
2
- //# sourceMappingURL=constants.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../src/constants.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,WAAW,uBAAuB,CAAA"}
@@ -1,5 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.PLUGIN_NAME = void 0;
4
- exports.PLUGIN_NAME = '@typed/vite-plugin';
5
- //# sourceMappingURL=constants.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"constants.js","sourceRoot":"","sources":["../../src/constants.ts"],"names":[],"mappings":";;;AAAa,QAAA,WAAW,GAAG,oBAAoB,CAAA"}
@@ -1,5 +0,0 @@
1
- import typed from './vite-plugin.js';
2
- export default typed;
3
- export * from './vite-plugin.js';
4
- export { resolveTypedConfig } from './resolveTypedConfig.js';
5
- //# sourceMappingURL=index.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,kBAAkB,CAAA;AAEpC,eAAe,KAAK,CAAA;AAEpB,cAAc,kBAAkB,CAAA;AAEhC,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA"}