@flight-framework/core 0.6.4 → 0.6.5
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/dist/plugins/index.d.ts
CHANGED
|
@@ -61,7 +61,7 @@ interface ServerOnlyPluginOptions {
|
|
|
61
61
|
* - SSR/Server: Import succeeds (replaced with empty module)
|
|
62
62
|
* - Client: Build fails with clear error message
|
|
63
63
|
*/
|
|
64
|
-
declare function serverOnlyPlugin(
|
|
64
|
+
declare function serverOnlyPlugin(pluginOptions?: ServerOnlyPluginOptions): Plugin;
|
|
65
65
|
|
|
66
66
|
/**
|
|
67
67
|
* @flight-framework/core - Critical CSS Plugin
|
package/dist/plugins/index.js
CHANGED
|
@@ -4,10 +4,11 @@ export { flightEnvPlugin } from '../chunk-MFUJN7RV.js';
|
|
|
4
4
|
|
|
5
5
|
// src/plugins/server-only-plugin.ts
|
|
6
6
|
var VIRTUAL_SERVER_ONLY = "\0server-only-noop";
|
|
7
|
-
function serverOnlyPlugin(
|
|
7
|
+
function serverOnlyPlugin(pluginOptions = {}) {
|
|
8
8
|
const {
|
|
9
|
-
additionalPackages = []
|
|
10
|
-
|
|
9
|
+
additionalPackages = [],
|
|
10
|
+
violationBehavior
|
|
11
|
+
} = pluginOptions;
|
|
11
12
|
const serverOnlyPackages = /* @__PURE__ */ new Set([
|
|
12
13
|
"server-only",
|
|
13
14
|
...additionalPackages
|
|
@@ -22,16 +23,19 @@ function serverOnlyPlugin(options = {}) {
|
|
|
22
23
|
config = resolvedConfig;
|
|
23
24
|
isDev = config.command === "serve";
|
|
24
25
|
},
|
|
25
|
-
resolveId(id) {
|
|
26
|
+
resolveId(id, _importer, hookOptions) {
|
|
26
27
|
if (!serverOnlyPackages.has(id)) {
|
|
27
28
|
return null;
|
|
28
29
|
}
|
|
29
|
-
const isServer =
|
|
30
|
-
|
|
30
|
+
const isServer = (
|
|
31
|
+
// Vite 6+ Environment API
|
|
32
|
+
this.environment?.config?.consumer === "server" || this.environment?.name === "ssr" || // Vite 5 fallback: options.ssr parameter in hooks
|
|
33
|
+
hookOptions?.ssr === true
|
|
34
|
+
);
|
|
31
35
|
if (isServer) {
|
|
32
36
|
return VIRTUAL_SERVER_ONLY;
|
|
33
37
|
}
|
|
34
|
-
const behavior =
|
|
38
|
+
const behavior = violationBehavior ?? (isDev ? "warn" : "error");
|
|
35
39
|
const message = `[Flight] Cannot import '${id}' in client bundle. This module is marked as server-only and should not be used in client code. Check your import chain to ensure server-only code is not imported from client components.`;
|
|
36
40
|
if (behavior === "error") {
|
|
37
41
|
this.error(message);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/plugins/server-only-plugin.ts","../../src/plugins/critical-css.ts","../../src/plugins/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\r\n * @flight-framework/core - Server Only Plugin\r\n * \r\n * Vite plugin to properly handle the `server-only` npm package.\r\n * \r\n * The `server-only` package is designed for Next.js and throws an error\r\n * when imported. In Next.js, the bundler has special handling to:\r\n * - Allow the import in server context (no error)\r\n * - Block the import in client bundles (build error)\r\n * \r\n * This plugin provides the same behavior for Vite/Flight:\r\n * - In SSR/Server environment: Replace with empty module (no-op)\r\n * - In Client environment: Let the build fail (or warn in dev)\r\n * \r\n * @example\r\n * ```typescript\r\n * // vite.config.ts\r\n * import { serverOnlyPlugin } from '@flight-framework/core/plugins';\r\n * \r\n * export default {\r\n * plugins: [serverOnlyPlugin()],\r\n * };\r\n * ```\r\n * \r\n * @example\r\n * ```typescript\r\n * // In your server-only file:\r\n * import 'server-only';\r\n * \r\n * export async function getSecretData() {\r\n * return process.env.SECRET_KEY;\r\n * }\r\n * ```\r\n */\r\n\r\nimport type { Plugin, ResolvedConfig } from 'vite';\r\n\r\nexport interface ServerOnlyPluginOptions {\r\n /**\r\n * What to do when server-only is imported in client context.\r\n * - 'error': Throw build error (production)\r\n * - 'warn': Log warning but continue (development)\r\n * @default 'error' in build, 'warn' in dev\r\n */\r\n violationBehavior?: 'error' | 'warn';\r\n\r\n /**\r\n * Additional packages to treat as server-only.\r\n * These will be replaced with empty modules in server context\r\n * and blocked in client context.\r\n * @default []\r\n */\r\n additionalPackages?: string[];\r\n}\r\n\r\n// Virtual module ID for the empty replacement\r\nconst VIRTUAL_SERVER_ONLY = '\\0server-only-noop';\r\n\r\n/**\r\n * Vite plugin to handle `server-only` package for Flight/Vite applications.\r\n * \r\n * This replicates Next.js behavior:\r\n * - SSR/Server: Import succeeds (replaced with empty module)\r\n * - Client: Build fails with clear error message\r\n */\r\nexport function serverOnlyPlugin(options: ServerOnlyPluginOptions = {}): Plugin {\r\n const {\r\n additionalPackages = [],\r\n } = options;\r\n\r\n // Packages to intercept\r\n const serverOnlyPackages = new Set([\r\n 'server-only',\r\n ...additionalPackages,\r\n ]);\r\n\r\n let config: ResolvedConfig;\r\n let isDev = false;\r\n\r\n return {\r\n name: 'flight:server-only',\r\n enforce: 'pre', // Run before other plugins\r\n\r\n configResolved(resolvedConfig) {\r\n config = resolvedConfig;\r\n isDev = config.command === 'serve';\r\n },\r\n\r\n resolveId(id) {\r\n // Check if this is a server-only package\r\n if (!serverOnlyPackages.has(id)) {\r\n return null;\r\n }\r\n\r\n // Determine if we're in server or client context\r\n // Vite 6 uses this.environment, fallback to config.build.ssr for older versions\r\n const isServer =\r\n this.environment?.config?.consumer === 'server' ||\r\n this.environment?.name === 'ssr' ||\r\n // @ts-ignore - Fallback for Vite 5 compatibility\r\n config.build?.ssr === true;\r\n\r\n if (isServer) {\r\n // Server context: Replace with empty virtual module\r\n return VIRTUAL_SERVER_ONLY;\r\n }\r\n\r\n // Client context: This is a violation!\r\n const behavior = options.violationBehavior ?? (isDev ? 'warn' : 'error');\r\n const message =\r\n `[Flight] Cannot import '${id}' in client bundle. ` +\r\n `This module is marked as server-only and should not be used in client code. ` +\r\n `Check your import chain to ensure server-only code is not imported from client components.`;\r\n\r\n if (behavior === 'error') {\r\n this.error(message);\r\n } else {\r\n console.warn(`\\n⚠️ ${message}\\n`);\r\n // In dev/warn mode, still return the virtual module to avoid crashes\r\n return VIRTUAL_SERVER_ONLY;\r\n }\r\n\r\n return null;\r\n },\r\n\r\n load(id) {\r\n // Serve the empty module for server-only\r\n if (id === VIRTUAL_SERVER_ONLY) {\r\n return {\r\n code: '/* server-only: no-op in server context */',\r\n map: null,\r\n };\r\n }\r\n return null;\r\n },\r\n };\r\n}\r\n","/**\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 - Vite Plugins\r\n * \r\n * Optional Vite plugins for Flight Framework.\r\n * All plugins are opt-in - use only what you need.\r\n */\r\n\r\n// Environment Variables Plugin\r\nexport { flightEnvPlugin, type FlightEnvPluginOptions } from './env-plugin.js';\r\n\r\n// Server Only Plugin (handles `server-only` npm package)\r\nexport { serverOnlyPlugin, type ServerOnlyPluginOptions } from './server-only-plugin.js';\r\n\r\n// Server Boundary Plugin (DEPRECATED - use serverOnlyPlugin instead)\r\nexport {\r\n serverBoundaryPlugin,\r\n type ServerBoundaryPluginOptions\r\n} from './server-boundary-plugin.js';\r\n\r\n// Critical CSS (existing)\r\nexport { criticalCSS } from './critical-css.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 { serverOnlyPlugin, type ServerOnlyPluginOptions } from './server-only-plugin.js';\r\n\r\nexport interface FlightPluginsOptions {\r\n env?: FlightEnvPluginOptions;\r\n serverOnly?: ServerOnlyPluginOptions;\r\n}\r\n\r\n/**\r\n * Recommended Flight plugins for most projects.\r\n * Includes:\r\n * - flightEnvPlugin: Configures FLIGHT_PUBLIC_* env prefix\r\n * - serverOnlyPlugin: Properly handles `server-only` npm package\r\n * \r\n * @example\r\n * ```typescript\r\n * // vite.config.ts\r\n * import { flightRecommendedPlugins } from '@flight-framework/core/plugins';\r\n * \r\n * export default {\r\n * plugins: [...flightRecommendedPlugins()],\r\n * };\r\n * ```\r\n */\r\nexport function flightRecommendedPlugins(options: FlightPluginsOptions = {}): Plugin[] {\r\n return [\r\n flightEnvPlugin(options.env),\r\n serverOnlyPlugin(options.serverOnly),\r\n ];\r\n}\r\n\r\n/**\r\n * @deprecated Use flightRecommendedPlugins instead.\r\n */\r\nexport function flightStrictPlugins(options: FlightPluginsOptions = {}): Plugin[] {\r\n console.warn('[Flight] flightStrictPlugins is deprecated. Use flightRecommendedPlugins instead.');\r\n return flightRecommendedPlugins(options);\r\n}\r\n\r\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/plugins/server-only-plugin.ts","../../src/plugins/critical-css.ts","../../src/plugins/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\r\n * @flight-framework/core - Server Only Plugin\r\n * \r\n * Vite plugin to properly handle the `server-only` npm package.\r\n * \r\n * The `server-only` package is designed for Next.js and throws an error\r\n * when imported. In Next.js, the bundler has special handling to:\r\n * - Allow the import in server context (no error)\r\n * - Block the import in client bundles (build error)\r\n * \r\n * This plugin provides the same behavior for Vite/Flight:\r\n * - In SSR/Server environment: Replace with empty module (no-op)\r\n * - In Client environment: Let the build fail (or warn in dev)\r\n * \r\n * @example\r\n * ```typescript\r\n * // vite.config.ts\r\n * import { serverOnlyPlugin } from '@flight-framework/core/plugins';\r\n * \r\n * export default {\r\n * plugins: [serverOnlyPlugin()],\r\n * };\r\n * ```\r\n * \r\n * @example\r\n * ```typescript\r\n * // In your server-only file:\r\n * import 'server-only';\r\n * \r\n * export async function getSecretData() {\r\n * return process.env.SECRET_KEY;\r\n * }\r\n * ```\r\n */\r\n\r\nimport type { Plugin, ResolvedConfig } from 'vite';\r\n\r\nexport interface ServerOnlyPluginOptions {\r\n /**\r\n * What to do when server-only is imported in client context.\r\n * - 'error': Throw build error (production)\r\n * - 'warn': Log warning but continue (development)\r\n * @default 'error' in build, 'warn' in dev\r\n */\r\n violationBehavior?: 'error' | 'warn';\r\n\r\n /**\r\n * Additional packages to treat as server-only.\r\n * These will be replaced with empty modules in server context\r\n * and blocked in client context.\r\n * @default []\r\n */\r\n additionalPackages?: string[];\r\n}\r\n\r\n// Virtual module ID for the empty replacement\r\nconst VIRTUAL_SERVER_ONLY = '\\0server-only-noop';\r\n\r\n/**\r\n * Vite plugin to handle `server-only` package for Flight/Vite applications.\r\n * \r\n * This replicates Next.js behavior:\r\n * - SSR/Server: Import succeeds (replaced with empty module)\r\n * - Client: Build fails with clear error message\r\n */\r\nexport function serverOnlyPlugin(pluginOptions: ServerOnlyPluginOptions = {}): Plugin {\r\n const {\r\n additionalPackages = [],\r\n violationBehavior,\r\n } = pluginOptions;\r\n\r\n // Packages to intercept\r\n const serverOnlyPackages = new Set([\r\n 'server-only',\r\n ...additionalPackages,\r\n ]);\r\n\r\n let config: ResolvedConfig;\r\n let isDev = false;\r\n\r\n return {\r\n name: 'flight:server-only',\r\n enforce: 'pre', // Run before other plugins\r\n\r\n configResolved(resolvedConfig) {\r\n config = resolvedConfig;\r\n isDev = config.command === 'serve';\r\n },\r\n\r\n resolveId(id, _importer, hookOptions) {\r\n // Check if this is a server-only package\r\n if (!serverOnlyPackages.has(id)) {\r\n return null;\r\n }\r\n\r\n // Determine if we're in server or client context\r\n // Vite 6: Use this.environment API\r\n // Vite 5: Fallback to options.ssr parameter\r\n const isServer =\r\n // Vite 6+ Environment API\r\n this.environment?.config?.consumer === 'server' ||\r\n this.environment?.name === 'ssr' ||\r\n // Vite 5 fallback: options.ssr parameter in hooks\r\n hookOptions?.ssr === true;\r\n\r\n if (isServer) {\r\n // Server context: Replace with empty virtual module\r\n return VIRTUAL_SERVER_ONLY;\r\n }\r\n\r\n // Client context: This is a violation!\r\n const behavior = violationBehavior ?? (isDev ? 'warn' : 'error');\r\n const message =\r\n `[Flight] Cannot import '${id}' in client bundle. ` +\r\n `This module is marked as server-only and should not be used in client code. ` +\r\n `Check your import chain to ensure server-only code is not imported from client components.`;\r\n\r\n if (behavior === 'error') {\r\n this.error(message);\r\n } else {\r\n console.warn(`\\n⚠️ ${message}\\n`);\r\n // In dev/warn mode, still return the virtual module to avoid crashes\r\n return VIRTUAL_SERVER_ONLY;\r\n }\r\n\r\n return null;\r\n },\r\n\r\n load(id) {\r\n // Serve the empty module for server-only\r\n if (id === VIRTUAL_SERVER_ONLY) {\r\n return {\r\n code: '/* server-only: no-op in server context */',\r\n map: null,\r\n };\r\n }\r\n return null;\r\n },\r\n };\r\n}\r\n","/**\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 - Vite Plugins\r\n * \r\n * Optional Vite plugins for Flight Framework.\r\n * All plugins are opt-in - use only what you need.\r\n */\r\n\r\n// Environment Variables Plugin\r\nexport { flightEnvPlugin, type FlightEnvPluginOptions } from './env-plugin.js';\r\n\r\n// Server Only Plugin (handles `server-only` npm package)\r\nexport { serverOnlyPlugin, type ServerOnlyPluginOptions } from './server-only-plugin.js';\r\n\r\n// Server Boundary Plugin (DEPRECATED - use serverOnlyPlugin instead)\r\nexport {\r\n serverBoundaryPlugin,\r\n type ServerBoundaryPluginOptions\r\n} from './server-boundary-plugin.js';\r\n\r\n// Critical CSS (existing)\r\nexport { criticalCSS } from './critical-css.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 { serverOnlyPlugin, type ServerOnlyPluginOptions } from './server-only-plugin.js';\r\n\r\nexport interface FlightPluginsOptions {\r\n env?: FlightEnvPluginOptions;\r\n serverOnly?: ServerOnlyPluginOptions;\r\n}\r\n\r\n/**\r\n * Recommended Flight plugins for most projects.\r\n * Includes:\r\n * - flightEnvPlugin: Configures FLIGHT_PUBLIC_* env prefix\r\n * - serverOnlyPlugin: Properly handles `server-only` npm package\r\n * \r\n * @example\r\n * ```typescript\r\n * // vite.config.ts\r\n * import { flightRecommendedPlugins } from '@flight-framework/core/plugins';\r\n * \r\n * export default {\r\n * plugins: [...flightRecommendedPlugins()],\r\n * };\r\n * ```\r\n */\r\nexport function flightRecommendedPlugins(options: FlightPluginsOptions = {}): Plugin[] {\r\n return [\r\n flightEnvPlugin(options.env),\r\n serverOnlyPlugin(options.serverOnly),\r\n ];\r\n}\r\n\r\n/**\r\n * @deprecated Use flightRecommendedPlugins instead.\r\n */\r\nexport function flightStrictPlugins(options: FlightPluginsOptions = {}): Plugin[] {\r\n console.warn('[Flight] flightStrictPlugins is deprecated. Use flightRecommendedPlugins instead.');\r\n return flightRecommendedPlugins(options);\r\n}\r\n\r\n"]}
|