@flight-framework/core 0.5.0 → 0.6.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.
@@ -0,0 +1,155 @@
1
+ import { Plugin } from 'vite';
2
+ import { FlightEnvPluginOptions } from './env-plugin.js';
3
+ export { flightEnvPlugin } from './env-plugin.js';
4
+ import { ServerBoundaryPluginOptions } from './server-boundary-plugin.js';
5
+ export { serverBoundaryPlugin } from './server-boundary-plugin.js';
6
+
7
+ /**
8
+ * @flight-framework/core - Critical CSS Plugin
9
+ *
10
+ * Extract and inline critical CSS for improved Core Web Vitals.
11
+ * Uses Critters under the hood for CSS extraction.
12
+ *
13
+ * This is an OPTIONAL plugin. Install 'critters' only if you use it.
14
+ *
15
+ * @example
16
+ * ```typescript
17
+ * // vite.config.ts
18
+ * import { criticalCSS } from '@flight-framework/core/plugins';
19
+ *
20
+ * export default defineConfig({
21
+ * plugins: [
22
+ * criticalCSS({
23
+ * preload: 'swap',
24
+ * pruneSource: false,
25
+ * }),
26
+ * ],
27
+ * });
28
+ * ```
29
+ */
30
+
31
+ /**
32
+ * Preload strategy for non-critical CSS.
33
+ *
34
+ * - 'body': Move stylesheet links to end of body
35
+ * - 'media': Use media="print" and swap to "all" on load
36
+ * - 'swap': Use link rel="preload" and swap to stylesheet
37
+ * - 'swap-high': Like swap, with fetchpriority="high"
38
+ * - 'js': Load via JavaScript
39
+ * - 'js-lazy': Load via JavaScript when idle
40
+ * - false: Don't preload
41
+ */
42
+ type PreloadStrategy = 'body' | 'media' | 'swap' | 'swap-high' | 'js' | 'js-lazy' | false;
43
+ /**
44
+ * Critical CSS plugin options.
45
+ */
46
+ interface CriticalCSSOptions {
47
+ /**
48
+ * Enable critical CSS extraction.
49
+ * @default true in production
50
+ */
51
+ enabled?: boolean;
52
+ /**
53
+ * Strategy for loading non-critical CSS.
54
+ * @default 'swap'
55
+ */
56
+ preload?: PreloadStrategy;
57
+ /**
58
+ * Remove inlined CSS rules from the source stylesheet.
59
+ * Reduces duplicate CSS but may cause issues with JS frameworks.
60
+ * @default false
61
+ */
62
+ pruneSource?: boolean;
63
+ /**
64
+ * Remove unused CSS selectors from critical CSS.
65
+ * @default true
66
+ */
67
+ reduceInlineStyles?: boolean;
68
+ /**
69
+ * Merge adjacent inline <style> tags.
70
+ * @default true
71
+ */
72
+ mergeStylesheets?: boolean;
73
+ /**
74
+ * Additional CSS selectors to always include as critical.
75
+ */
76
+ additionalStylesheets?: string[];
77
+ /**
78
+ * CSS selectors to exclude from critical extraction.
79
+ */
80
+ excludeSelectors?: string[];
81
+ /**
82
+ * Routes to include (glob patterns).
83
+ * @default ['**\/*.html']
84
+ */
85
+ include?: string[];
86
+ /**
87
+ * Routes to exclude (glob patterns).
88
+ * @default ['/api/**']
89
+ */
90
+ exclude?: string[];
91
+ /**
92
+ * Inline external fonts.
93
+ * @default false
94
+ */
95
+ inlineFonts?: boolean;
96
+ /**
97
+ * Preload external fonts.
98
+ * @default true
99
+ */
100
+ preloadFonts?: boolean;
101
+ /**
102
+ * Maximum size of inlined CSS (in bytes).
103
+ * Larger stylesheets will be loaded normally.
104
+ * @default 100000 (100KB)
105
+ */
106
+ maxInlinedSize?: number;
107
+ /**
108
+ * Log extraction stats.
109
+ * @default true
110
+ */
111
+ verbose?: boolean;
112
+ }
113
+ /**
114
+ * Vite plugin for critical CSS extraction.
115
+ *
116
+ * This plugin extracts critical (above-the-fold) CSS and inlines it
117
+ * in the HTML, deferring non-critical CSS for better performance.
118
+ *
119
+ * Requires 'critters' as a peer dependency:
120
+ * ```bash
121
+ * npm install critters
122
+ * ```
123
+ *
124
+ * @param options - Plugin configuration
125
+ * @returns Vite plugin
126
+ */
127
+ declare function criticalCSS(options?: CriticalCSSOptions): Plugin;
128
+
129
+ /**
130
+ * @flight-framework/core - Build Plugins
131
+ *
132
+ * Optional Vite plugins for advanced optimizations.
133
+ * These plugins are opt-in and require explicit configuration.
134
+ *
135
+ * Flight doesn't force you to use any of these - use them if you want.
136
+ */
137
+
138
+ /**
139
+ * Recommended plugins for most apps.
140
+ * Provides sensible defaults with warnings for violations.
141
+ */
142
+ declare function flightRecommendedPlugins(options?: {
143
+ env?: FlightEnvPluginOptions;
144
+ serverBoundary?: ServerBoundaryPluginOptions;
145
+ }): Plugin[];
146
+ /**
147
+ * Strict mode plugins - errors instead of warnings.
148
+ * Recommended for production builds.
149
+ */
150
+ declare function flightStrictPlugins(options?: {
151
+ env?: FlightEnvPluginOptions;
152
+ serverBoundary?: ServerBoundaryPluginOptions;
153
+ }): Plugin[];
154
+
155
+ export { type CriticalCSSOptions, FlightEnvPluginOptions, type PreloadStrategy, ServerBoundaryPluginOptions, criticalCSS, flightRecommendedPlugins, flightStrictPlugins };
@@ -0,0 +1,150 @@
1
+ import { serverBoundaryPlugin } from '../chunk-56ZZNOJD.js';
2
+ export { serverBoundaryPlugin } from '../chunk-56ZZNOJD.js';
3
+ import { flightEnvPlugin } from '../chunk-RFTE6JVG.js';
4
+ export { flightEnvPlugin } from '../chunk-RFTE6JVG.js';
5
+
6
+ // src/plugins/critical-css.ts
7
+ function criticalCSS(options = {}) {
8
+ const {
9
+ preload = "swap",
10
+ pruneSource = false,
11
+ reduceInlineStyles = true,
12
+ mergeStylesheets = true,
13
+ additionalStylesheets = [],
14
+ inlineFonts = false,
15
+ preloadFonts = true,
16
+ maxInlinedSize = 1e5,
17
+ include = ["**/*.html"],
18
+ exclude = ["/api/**"],
19
+ verbose = true
20
+ } = options;
21
+ let config;
22
+ let isProduction = false;
23
+ let enabled = options.enabled;
24
+ let critters = null;
25
+ return {
26
+ name: "flight-critical-css",
27
+ enforce: "post",
28
+ apply: "build",
29
+ configResolved(resolvedConfig) {
30
+ config = resolvedConfig;
31
+ isProduction = resolvedConfig.command === "build";
32
+ if (enabled === void 0) {
33
+ enabled = isProduction;
34
+ }
35
+ },
36
+ async buildStart() {
37
+ if (!enabled) return;
38
+ try {
39
+ const CrittersModule = await import('critters');
40
+ const Critters = CrittersModule.default || CrittersModule;
41
+ const crittersOptions = {
42
+ preload,
43
+ pruneSource,
44
+ reduceInlineStyles,
45
+ mergeStylesheets,
46
+ inlineFonts,
47
+ preloadFonts,
48
+ additionalStylesheets,
49
+ external: true,
50
+ noscriptFallback: true,
51
+ path: config.build.outDir,
52
+ publicPath: config.base || "/",
53
+ logLevel: verbose ? "info" : "silent"
54
+ };
55
+ critters = new Critters(crittersOptions);
56
+ if (verbose) {
57
+ console.log("[flight-critical-css] Critical CSS extraction enabled");
58
+ }
59
+ } catch (error) {
60
+ console.warn(
61
+ "[flight-critical-css] Critters not installed. Install with: npm install critters"
62
+ );
63
+ critters = null;
64
+ }
65
+ },
66
+ async transformIndexHtml(html, ctx) {
67
+ if (!enabled || !critters) {
68
+ return html;
69
+ }
70
+ const path = ctx.path || "";
71
+ for (const pattern of exclude) {
72
+ if (matchesPattern(path, pattern)) {
73
+ return html;
74
+ }
75
+ }
76
+ let shouldProcess = false;
77
+ for (const pattern of include) {
78
+ if (matchesPattern(path, pattern)) {
79
+ shouldProcess = true;
80
+ break;
81
+ }
82
+ }
83
+ if (!shouldProcess) {
84
+ return html;
85
+ }
86
+ try {
87
+ const processedHtml = await critters.process(html);
88
+ const inlineStyleMatch = processedHtml.match(/<style[^>]*>([\s\S]*?)<\/style>/gi);
89
+ if (inlineStyleMatch) {
90
+ const totalInlineSize = inlineStyleMatch.reduce(
91
+ (sum, style) => sum + style.length,
92
+ 0
93
+ );
94
+ if (totalInlineSize > maxInlinedSize) {
95
+ console.warn(
96
+ `[flight-critical-css] Inline CSS (${totalInlineSize} bytes) exceeds max (${maxInlinedSize} bytes) for ${path}`
97
+ );
98
+ }
99
+ }
100
+ if (verbose) {
101
+ console.log(`[flight-critical-css] Processed: ${path}`);
102
+ }
103
+ return processedHtml;
104
+ } catch (error) {
105
+ console.error(`[flight-critical-css] Error processing ${path}:`, error);
106
+ return html;
107
+ }
108
+ },
109
+ async closeBundle() {
110
+ if (verbose && critters) {
111
+ console.log("[flight-critical-css] Critical CSS extraction complete");
112
+ }
113
+ }
114
+ };
115
+ }
116
+ function matchesPattern(path, pattern) {
117
+ const regexPattern = pattern.replace(/\*\*/g, "{{DOUBLE_STAR}}").replace(/\*/g, "[^/]*").replace(/\{\{DOUBLE_STAR\}\}/g, ".*").replace(/\?/g, ".");
118
+ const regex = new RegExp(`^${regexPattern}$`);
119
+ return regex.test(path);
120
+ }
121
+
122
+ // src/plugins/index.ts
123
+ function flightRecommendedPlugins(options) {
124
+ return [
125
+ flightEnvPlugin({
126
+ privateVarBehavior: "warn",
127
+ ...options?.env
128
+ }),
129
+ serverBoundaryPlugin({
130
+ violationBehavior: "warn",
131
+ ...options?.serverBoundary
132
+ })
133
+ ];
134
+ }
135
+ function flightStrictPlugins(options) {
136
+ return [
137
+ flightEnvPlugin({
138
+ privateVarBehavior: "error",
139
+ ...options?.env
140
+ }),
141
+ serverBoundaryPlugin({
142
+ violationBehavior: "error",
143
+ ...options?.serverBoundary
144
+ })
145
+ ];
146
+ }
147
+
148
+ export { criticalCSS, flightRecommendedPlugins, flightStrictPlugins };
149
+ //# sourceMappingURL=index.js.map
150
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/plugins/critical-css.ts","../../src/plugins/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\r\n * @flight-framework/core - Critical CSS Plugin\r\n *\r\n * Extract and inline critical CSS for improved Core Web Vitals.\r\n * Uses Critters under the hood for CSS extraction.\r\n *\r\n * This is an OPTIONAL plugin. Install 'critters' only if you use it.\r\n *\r\n * @example\r\n * ```typescript\r\n * // vite.config.ts\r\n * import { criticalCSS } from '@flight-framework/core/plugins';\r\n *\r\n * export default defineConfig({\r\n * plugins: [\r\n * criticalCSS({\r\n * preload: 'swap',\r\n * pruneSource: false,\r\n * }),\r\n * ],\r\n * });\r\n * ```\r\n */\r\n\r\nimport type { Plugin, ResolvedConfig } from 'vite';\r\n\r\n// ============================================================================\r\n// Types\r\n// ============================================================================\r\n\r\n/**\r\n * Preload strategy for non-critical CSS.\r\n *\r\n * - 'body': Move stylesheet links to end of body\r\n * - 'media': Use media=\"print\" and swap to \"all\" on load\r\n * - 'swap': Use link rel=\"preload\" and swap to stylesheet\r\n * - 'swap-high': Like swap, with fetchpriority=\"high\"\r\n * - 'js': Load via JavaScript\r\n * - 'js-lazy': Load via JavaScript when idle\r\n * - false: Don't preload\r\n */\r\nexport type PreloadStrategy =\r\n | 'body'\r\n | 'media'\r\n | 'swap'\r\n | 'swap-high'\r\n | 'js'\r\n | 'js-lazy'\r\n | false;\r\n\r\n/**\r\n * Critical CSS plugin options.\r\n */\r\nexport interface CriticalCSSOptions {\r\n /**\r\n * Enable critical CSS extraction.\r\n * @default true in production\r\n */\r\n enabled?: boolean;\r\n\r\n /**\r\n * Strategy for loading non-critical CSS.\r\n * @default 'swap'\r\n */\r\n preload?: PreloadStrategy;\r\n\r\n /**\r\n * Remove inlined CSS rules from the source stylesheet.\r\n * Reduces duplicate CSS but may cause issues with JS frameworks.\r\n * @default false\r\n */\r\n pruneSource?: boolean;\r\n\r\n /**\r\n * Remove unused CSS selectors from critical CSS.\r\n * @default true\r\n */\r\n reduceInlineStyles?: boolean;\r\n\r\n /**\r\n * Merge adjacent inline <style> tags.\r\n * @default true\r\n */\r\n mergeStylesheets?: boolean;\r\n\r\n /**\r\n * Additional CSS selectors to always include as critical.\r\n */\r\n additionalStylesheets?: string[];\r\n\r\n /**\r\n * CSS selectors to exclude from critical extraction.\r\n */\r\n excludeSelectors?: string[];\r\n\r\n /**\r\n * Routes to include (glob patterns).\r\n * @default ['**\\/*.html']\r\n */\r\n include?: string[];\r\n\r\n /**\r\n * Routes to exclude (glob patterns).\r\n * @default ['/api/**']\r\n */\r\n exclude?: string[];\r\n\r\n /**\r\n * Inline external fonts.\r\n * @default false\r\n */\r\n inlineFonts?: boolean;\r\n\r\n /**\r\n * Preload external fonts.\r\n * @default true\r\n */\r\n preloadFonts?: boolean;\r\n\r\n /**\r\n * Maximum size of inlined CSS (in bytes).\r\n * Larger stylesheets will be loaded normally.\r\n * @default 100000 (100KB)\r\n */\r\n maxInlinedSize?: number;\r\n\r\n /**\r\n * Log extraction stats.\r\n * @default true\r\n */\r\n verbose?: boolean;\r\n}\r\n\r\n// ============================================================================\r\n// Critters Integration\r\n// ============================================================================\r\n\r\n/**\r\n * Critters options interface (subset of full Critters options).\r\n * Full options available at: https://github.com/GoogleChromeLabs/critters\r\n */\r\ninterface CrittersOptions {\r\n preload?: PreloadStrategy;\r\n pruneSource?: boolean;\r\n reduceInlineStyles?: boolean;\r\n mergeStylesheets?: boolean;\r\n external?: boolean;\r\n inlineFonts?: boolean;\r\n preloadFonts?: boolean;\r\n additionalStylesheets?: string[];\r\n noscriptFallback?: boolean;\r\n path?: string;\r\n publicPath?: string;\r\n logLevel?: 'info' | 'warn' | 'error' | 'trace' | 'debug' | 'silent';\r\n}\r\n\r\n// ============================================================================\r\n// Vite Plugin\r\n// ============================================================================\r\n\r\n/**\r\n * Vite plugin for critical CSS extraction.\r\n *\r\n * This plugin extracts critical (above-the-fold) CSS and inlines it\r\n * in the HTML, deferring non-critical CSS for better performance.\r\n *\r\n * Requires 'critters' as a peer dependency:\r\n * ```bash\r\n * npm install critters\r\n * ```\r\n *\r\n * @param options - Plugin configuration\r\n * @returns Vite plugin\r\n */\r\nexport function criticalCSS(options: CriticalCSSOptions = {}): Plugin {\r\n const {\r\n preload = 'swap',\r\n pruneSource = false,\r\n reduceInlineStyles = true,\r\n mergeStylesheets = true,\r\n additionalStylesheets = [],\r\n inlineFonts = false,\r\n preloadFonts = true,\r\n maxInlinedSize = 100000,\r\n include = ['**/*.html'],\r\n exclude = ['/api/**'],\r\n verbose = true,\r\n } = options;\r\n\r\n let config: ResolvedConfig;\r\n let isProduction = false;\r\n let enabled = options.enabled;\r\n let critters: {\r\n process: (html: string) => Promise<string>;\r\n } | null = null;\r\n\r\n return {\r\n name: 'flight-critical-css',\r\n enforce: 'post',\r\n apply: 'build',\r\n\r\n configResolved(resolvedConfig) {\r\n config = resolvedConfig;\r\n isProduction = resolvedConfig.command === 'build';\r\n\r\n // Enable by default only in production\r\n if (enabled === undefined) {\r\n enabled = isProduction;\r\n }\r\n },\r\n\r\n async buildStart() {\r\n if (!enabled) return;\r\n\r\n try {\r\n // Dynamic import of Critters (optional peer dependency)\r\n // @ts-expect-error - critters has broken type exports in its package.json\r\n const CrittersModule = await import('critters');\r\n const Critters = CrittersModule.default || CrittersModule;\r\n\r\n const crittersOptions: CrittersOptions = {\r\n preload,\r\n pruneSource,\r\n reduceInlineStyles,\r\n mergeStylesheets,\r\n inlineFonts,\r\n preloadFonts,\r\n additionalStylesheets,\r\n external: true,\r\n noscriptFallback: true,\r\n path: config.build.outDir,\r\n publicPath: config.base || '/',\r\n logLevel: verbose ? 'info' : 'silent',\r\n };\r\n\r\n critters = new Critters(crittersOptions);\r\n\r\n if (verbose) {\r\n console.log('[flight-critical-css] Critical CSS extraction enabled');\r\n }\r\n } catch (error) {\r\n console.warn(\r\n '[flight-critical-css] Critters not installed. Install with: npm install critters'\r\n );\r\n critters = null;\r\n }\r\n },\r\n\r\n async transformIndexHtml(html, ctx) {\r\n if (!enabled || !critters) {\r\n return html;\r\n }\r\n\r\n // Check if this route should be processed\r\n const path = ctx.path || '';\r\n\r\n // Check exclusions\r\n for (const pattern of exclude) {\r\n if (matchesPattern(path, pattern)) {\r\n return html;\r\n }\r\n }\r\n\r\n // Check inclusions\r\n let shouldProcess = false;\r\n for (const pattern of include) {\r\n if (matchesPattern(path, pattern)) {\r\n shouldProcess = true;\r\n break;\r\n }\r\n }\r\n\r\n if (!shouldProcess) {\r\n return html;\r\n }\r\n\r\n try {\r\n const processedHtml = await critters.process(html);\r\n\r\n // Check if critical CSS is too large\r\n const inlineStyleMatch = processedHtml.match(/<style[^>]*>([\\s\\S]*?)<\\/style>/gi);\r\n if (inlineStyleMatch) {\r\n const totalInlineSize = inlineStyleMatch.reduce(\r\n (sum, style) => sum + style.length, 0\r\n );\r\n\r\n if (totalInlineSize > maxInlinedSize) {\r\n console.warn(\r\n `[flight-critical-css] Inline CSS (${totalInlineSize} bytes) exceeds max (${maxInlinedSize} bytes) for ${path}`\r\n );\r\n }\r\n }\r\n\r\n if (verbose) {\r\n console.log(`[flight-critical-css] Processed: ${path}`);\r\n }\r\n\r\n return processedHtml;\r\n } catch (error) {\r\n console.error(`[flight-critical-css] Error processing ${path}:`, error);\r\n return html;\r\n }\r\n },\r\n\r\n async closeBundle() {\r\n if (verbose && critters) {\r\n console.log('[flight-critical-css] Critical CSS extraction complete');\r\n }\r\n },\r\n };\r\n}\r\n\r\n// ============================================================================\r\n// Helper Functions\r\n// ============================================================================\r\n\r\n/**\r\n * Simple glob pattern matching.\r\n */\r\nfunction matchesPattern(path: string, pattern: string): boolean {\r\n // Convert glob to regex\r\n const regexPattern = pattern\r\n .replace(/\\*\\*/g, '{{DOUBLE_STAR}}')\r\n .replace(/\\*/g, '[^/]*')\r\n .replace(/\\{\\{DOUBLE_STAR\\}\\}/g, '.*')\r\n .replace(/\\?/g, '.');\r\n\r\n const regex = new RegExp(`^${regexPattern}$`);\r\n return regex.test(path);\r\n}\r\n\r\n// ============================================================================\r\n// CSS Optimization Utilities\r\n// ============================================================================\r\n\r\n/**\r\n * Extract inline styles from HTML.\r\n * Utility for manual critical CSS handling.\r\n */\r\nexport function extractInlineStyles(html: string): {\r\n html: string;\r\n styles: string[];\r\n} {\r\n const styles: string[] = [];\r\n const cleanedHtml = html.replace(\r\n /<style[^>]*>([\\s\\S]*?)<\\/style>/gi,\r\n (match, content) => {\r\n styles.push(content);\r\n return '';\r\n }\r\n );\r\n\r\n return { html: cleanedHtml, styles };\r\n}\r\n\r\n/**\r\n * Merge multiple CSS strings into one.\r\n */\r\nexport function mergeCSS(styles: string[]): string {\r\n return styles\r\n .map(s => s.trim())\r\n .filter(Boolean)\r\n .join('\\n');\r\n}\r\n\r\n/**\r\n * Generate a preload link for a stylesheet.\r\n */\r\nexport function generatePreloadLink(\r\n href: string,\r\n strategy: PreloadStrategy\r\n): string {\r\n switch (strategy) {\r\n case 'swap':\r\n return `<link rel=\"preload\" href=\"${href}\" as=\"style\" onload=\"this.onload=null;this.rel='stylesheet'\">`;\r\n case 'swap-high':\r\n return `<link rel=\"preload\" href=\"${href}\" as=\"style\" fetchpriority=\"high\" onload=\"this.onload=null;this.rel='stylesheet'\">`;\r\n case 'media':\r\n return `<link rel=\"stylesheet\" href=\"${href}\" media=\"print\" onload=\"this.media='all'\">`;\r\n case 'js':\r\n return `<script>\r\n (function(){var l=document.createElement('link');l.rel='stylesheet';l.href='${href}';document.head.appendChild(l);})();\r\n </script>`;\r\n case 'js-lazy':\r\n return `<script>\r\n requestIdleCallback(function(){var l=document.createElement('link');l.rel='stylesheet';l.href='${href}';document.head.appendChild(l);});\r\n </script>`;\r\n default:\r\n return '';\r\n }\r\n}\r\n\r\n/**\r\n * Generate noscript fallback for preloaded stylesheet.\r\n */\r\nexport function generateNoscriptFallback(href: string): string {\r\n return `<noscript><link rel=\"stylesheet\" href=\"${href}\"></noscript>`;\r\n}\r\n\r\n// ============================================================================\r\n// Exports\r\n// ============================================================================\r\n\r\nexport default criticalCSS;\r\n","/**\r\n * @flight-framework/core - Build Plugins\r\n *\r\n * Optional Vite plugins for advanced optimizations.\r\n * These plugins are opt-in and require explicit configuration.\r\n * \r\n * Flight doesn't force you to use any of these - use them if you want.\r\n */\r\n\r\n// Critical CSS Plugin (existing)\r\nexport { criticalCSS } from './critical-css.js';\r\nexport type {\r\n CriticalCSSOptions,\r\n PreloadStrategy,\r\n} from './critical-css.js';\r\n\r\n// Environment Variables Plugin (NEW)\r\nexport {\r\n flightEnvPlugin,\r\n type FlightEnvPluginOptions,\r\n} from './env-plugin.js';\r\n\r\n// Server Boundary Plugin (NEW)\r\nexport {\r\n serverBoundaryPlugin,\r\n type ServerBoundaryPluginOptions,\r\n} from './server-boundary-plugin.js';\r\n\r\n// ============================================================================\r\n// Convenience Presets\r\n// ============================================================================\r\n\r\nimport type { Plugin } from 'vite';\r\nimport { flightEnvPlugin, type FlightEnvPluginOptions } from './env-plugin.js';\r\nimport { serverBoundaryPlugin, type ServerBoundaryPluginOptions } from './server-boundary-plugin.js';\r\n\r\n/**\r\n * Recommended plugins for most apps.\r\n * Provides sensible defaults with warnings for violations.\r\n */\r\nexport function flightRecommendedPlugins(options?: {\r\n env?: FlightEnvPluginOptions;\r\n serverBoundary?: ServerBoundaryPluginOptions;\r\n}): Plugin[] {\r\n return [\r\n flightEnvPlugin({\r\n privateVarBehavior: 'warn',\r\n ...options?.env,\r\n }),\r\n serverBoundaryPlugin({\r\n violationBehavior: 'warn',\r\n ...options?.serverBoundary,\r\n }),\r\n ];\r\n}\r\n\r\n/**\r\n * Strict mode plugins - errors instead of warnings.\r\n * Recommended for production builds.\r\n */\r\nexport function flightStrictPlugins(options?: {\r\n env?: FlightEnvPluginOptions;\r\n serverBoundary?: ServerBoundaryPluginOptions;\r\n}): Plugin[] {\r\n return [\r\n flightEnvPlugin({\r\n privateVarBehavior: 'error',\r\n ...options?.env,\r\n }),\r\n serverBoundaryPlugin({\r\n violationBehavior: 'error',\r\n ...options?.serverBoundary,\r\n }),\r\n ];\r\n}\r\n"]}
@@ -0,0 +1,67 @@
1
+ import { Plugin } from 'vite';
2
+
3
+ /**
4
+ * @flight-framework/core - Server Boundary Plugin
5
+ *
6
+ * Vite plugin to detect server-only code imported in client bundles.
7
+ * OPTIONAL - use if you want build-time protection.
8
+ *
9
+ * Features:
10
+ * - Detects *.server.ts files imported in client code
11
+ * - Detects /server/ and /api/ directory imports
12
+ * - Configurable behavior (warn, error)
13
+ * - Clear error messages with solutions
14
+ *
15
+ * @example
16
+ * ```typescript
17
+ * // vite.config.ts
18
+ * import { serverBoundaryPlugin } from '@flight-framework/core/plugins';
19
+ *
20
+ * export default {
21
+ * plugins: [serverBoundaryPlugin()],
22
+ * };
23
+ * ```
24
+ */
25
+
26
+ interface ServerBoundaryPluginOptions {
27
+ /**
28
+ * File patterns that are server-only.
29
+ * @default ['.server.ts', '.server.tsx', '.server.js', '.server.jsx']
30
+ */
31
+ serverFilePatterns?: string[];
32
+ /**
33
+ * Directory patterns that are server-only.
34
+ * @default ['/server/', '/api/']
35
+ */
36
+ serverDirPatterns?: string[];
37
+ /**
38
+ * How to handle violations.
39
+ * - 'error': Build error (recommended for production)
40
+ * - 'warn': Warning only (for migration)
41
+ * @default 'error'
42
+ */
43
+ violationBehavior?: 'error' | 'warn';
44
+ /**
45
+ * Custom error message template.
46
+ */
47
+ customErrorMessage?: (module: string, importer: string) => string;
48
+ /**
49
+ * Files to exclude from checking (e.g., tests).
50
+ * @default ['**\/*.test.*', '**\/*.spec.*']
51
+ */
52
+ exclude?: string[];
53
+ }
54
+ /**
55
+ * Vite plugin to detect server-only imports in client code.
56
+ *
57
+ * This plugin is OPTIONAL. Use it if you want build-time
58
+ * detection of server code leaking to the client.
59
+ *
60
+ * By default, it will:
61
+ * - Block imports of *.server.ts files in client code
62
+ * - Block imports from /server/ and /api/ directories
63
+ * - Throw build errors with clear solutions
64
+ */
65
+ declare function serverBoundaryPlugin(options?: ServerBoundaryPluginOptions): Plugin;
66
+
67
+ export { type ServerBoundaryPluginOptions, serverBoundaryPlugin };
@@ -0,0 +1,3 @@
1
+ export { serverBoundaryPlugin } from '../chunk-56ZZNOJD.js';
2
+ //# sourceMappingURL=server-boundary-plugin.js.map
3
+ //# sourceMappingURL=server-boundary-plugin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"server-boundary-plugin.js"}
@@ -1,42 +1,97 @@
1
+ export { g as getEnvironment, d as isBrowser, a as isDevelopment, i as isProduction, c as isServer, b as isTest } from '../env-9do2c6yn.js';
2
+
1
3
  /**
2
- * @flight-framework/core - Environment Utilities
4
+ * @flight-framework/core - Server/Client Boundaries
3
5
  *
4
- * Environment detection utilities that work across Node.js, browsers, and edge runtimes.
5
- * No hardcoded values - detects environment dynamically.
6
- */
7
- /**
8
- * Check if running in production environment.
6
+ * Runtime utilities for server/client code separation.
7
+ * These are OPTIONAL - use them if you want safety guards.
8
+ * Flight doesn't force you to use them.
9
9
  *
10
- * Detection order:
11
- * 1. Node.js process.env.NODE_ENV
12
- * 2. Vite/modern bundlers import.meta.env.MODE or import.meta.env.PROD
13
- * 3. Default: false (not production)
14
- */
15
- declare function isProduction(): boolean;
16
- /**
17
- * Check if running in development environment.
10
+ * @example
11
+ * ```typescript
12
+ * import { assertServer, getEnv } from '@flight-framework/core';
18
13
  *
19
- * Detection order:
20
- * 1. Node.js process.env.NODE_ENV
21
- * 2. Vite/modern bundlers import.meta.env.MODE or import.meta.env.DEV
22
- * 3. Default: true (assume development if unknown)
14
+ * export async function fetchFromDatabase() {
15
+ * assertServer('fetchFromDatabase'); // Throws if called on client
16
+ * const dbUrl = getEnv('DATABASE_URL'); // Returns undefined on client
17
+ * // ... your code
18
+ * }
19
+ * ```
23
20
  */
24
- declare function isDevelopment(): boolean;
25
21
  /**
26
- * Check if running in test environment.
22
+ * Assert that code is running on the server.
23
+ * Throws an error if called from the client (browser).
24
+ *
25
+ * Use this to protect server-only code from being accidentally
26
+ * called on the client.
27
+ *
28
+ * @param context - Optional context string for the error message
29
+ * @throws Error if called on client
30
+ *
31
+ * @example
32
+ * ```typescript
33
+ * export async function queryDatabase() {
34
+ * assertServer('queryDatabase');
35
+ * // Safe to use process.env, database connections, etc.
36
+ * }
37
+ * ```
27
38
  */
28
- declare function isTest(): boolean;
39
+ declare function assertServer(context?: string): void;
29
40
  /**
30
- * Check if running on the server (not browser).
41
+ * Assert that code is running on the client.
42
+ * Throws an error if called from the server.
43
+ *
44
+ * Use this to protect client-only code (e.g., DOM manipulation,
45
+ * browser APIs) from being accidentally called during SSR.
46
+ *
47
+ * @param context - Optional context string for the error message
48
+ * @throws Error if called on server
49
+ *
50
+ * @example
51
+ * ```typescript
52
+ * export function scrollToTop() {
53
+ * assertClient('scrollToTop');
54
+ * window.scrollTo(0, 0);
55
+ * }
56
+ * ```
31
57
  */
32
- declare function isServer(): boolean;
58
+ declare function assertClient(context?: string): void;
33
59
  /**
34
- * Check if running in the browser.
60
+ * Get an environment variable safely.
61
+ *
62
+ * On the server: Returns the variable value.
63
+ * On the client: Returns undefined for non-public vars.
64
+ *
65
+ * Public variables (prefixed with FLIGHT_PUBLIC_) are always accessible.
66
+ *
67
+ * @param key - Environment variable name
68
+ * @returns The value or undefined
69
+ *
70
+ * @example
71
+ * ```typescript
72
+ * // On server: returns actual value
73
+ * // On client: returns undefined (safe!)
74
+ * const dbUrl = getEnv('DATABASE_URL');
75
+ *
76
+ * // Works on both server and client
77
+ * const apiUrl = getEnv('FLIGHT_PUBLIC_API_URL');
78
+ * ```
35
79
  */
36
- declare function isBrowser(): boolean;
80
+ declare function getEnv(key: string): string | undefined;
37
81
  /**
38
- * Get the current environment name.
82
+ * Get an environment variable, throwing if not found.
83
+ * Use this when a variable is required.
84
+ *
85
+ * @param key - Environment variable name
86
+ * @returns The value
87
+ * @throws Error if variable is not set or inaccessible
88
+ *
89
+ * @example
90
+ * ```typescript
91
+ * // Throws if DATABASE_URL is not set
92
+ * const dbUrl = requireEnv('DATABASE_URL');
93
+ * ```
39
94
  */
40
- declare function getEnvironment(): 'production' | 'development' | 'test' | 'unknown';
95
+ declare function requireEnv(key: string): string;
41
96
 
42
- export { getEnvironment, isBrowser, isDevelopment, isProduction, isServer, isTest };
97
+ export { assertClient, assertServer, getEnv, requireEnv };
@@ -1,4 +1,4 @@
1
- import '../chunk-PL37KFRJ.js';
1
+ export { assertClient, assertServer, getEnv, requireEnv } from '../chunk-WF46NESN.js';
2
2
  export { getEnvironment, isBrowser, isDevelopment, isProduction, isServer, isTest } from '../chunk-YHEVHRLH.js';
3
3
  //# sourceMappingURL=index.js.map
4
4
  //# sourceMappingURL=index.js.map
package/package.json CHANGED
@@ -1,13 +1,15 @@
1
1
  {
2
2
  "name": "@flight-framework/core",
3
- "version": "0.5.0",
4
- "description": "Core primitives for Flight Framework - routing, rendering, caching",
3
+ "version": "0.6.1",
4
+ "description": "Core primitives for Flight Framework - routing, rendering, caching, server/client boundaries",
5
5
  "keywords": [
6
6
  "flight",
7
7
  "framework",
8
8
  "core",
9
9
  "ssr",
10
- "routing"
10
+ "routing",
11
+ "server-components",
12
+ "boundaries"
11
13
  ],
12
14
  "license": "MIT",
13
15
  "author": "Flight Contributors",
@@ -182,6 +184,14 @@
182
184
  "./plugins/critical-css": {
183
185
  "types": "./dist/plugins/critical-css.d.ts",
184
186
  "import": "./dist/plugins/critical-css.js"
187
+ },
188
+ "./plugins/env": {
189
+ "types": "./dist/plugins/env-plugin.d.ts",
190
+ "import": "./dist/plugins/env-plugin.js"
191
+ },
192
+ "./plugins/server-boundary": {
193
+ "types": "./dist/plugins/server-boundary-plugin.d.ts",
194
+ "import": "./dist/plugins/server-boundary-plugin.js"
185
195
  }
186
196
  },
187
197
  "main": "./dist/index.js",