vite-plugin-astro-prerender 0.1.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +278 -0
- package/dist/client.d.ts +181 -0
- package/dist/client.js +2 -0
- package/dist/client.js.map +1 -0
- package/dist/index.d.ts +341 -0
- package/dist/index.js +19 -0
- package/dist/index.js.map +1 -0
- package/package.json +108 -0
package/README.md
ADDED
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
# vite-plugin-astro-prerender
|
|
2
|
+
|
|
3
|
+
A Vite plugin for Astro that prerenders components to static HTML and generates optimized CSS. Perfect for lazy-loading below-the-fold content to improve initial page load times.
|
|
4
|
+
|
|
5
|
+
> [!WARNING]
|
|
6
|
+
> **Alpha Release**: This plugin is currently in alpha. APIs are subject to change and features may be unstable. Please report any issues on GitHub.
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- ๐ **Prerender Astro components** to static HTML at build time
|
|
12
|
+
- ๐จ **Generate optimized CSS** with Tailwind tree-shaking (only used classes)
|
|
13
|
+
- ๐ฆ **Two rendering modes**: Parser-based (simple) and Container API (full features)
|
|
14
|
+
- ๐ **Hot reload support** in dev mode with file watching
|
|
15
|
+
- ๐พ **Smart caching** to skip unchanged components
|
|
16
|
+
- ๐ฏ **Component styles externalization** - extracts and includes `<style>` tags
|
|
17
|
+
- ๐งน **Clean HTML output** - removes scripts, dev attributes, and inline styles
|
|
18
|
+
- ๐ฑ **Client-side LazyLoader** - fetch and inject HTML when needed
|
|
19
|
+
|
|
20
|
+
## Installation
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npm install vite-plugin-astro-prerender
|
|
24
|
+
# or
|
|
25
|
+
pnpm add vite-plugin-astro-prerender
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### Peer Dependencies
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
npm install @astrojs/compiler astro
|
|
32
|
+
# Optional for Tailwind CSS support:
|
|
33
|
+
npm install tailwindcss postcss autoprefixer
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Usage
|
|
37
|
+
|
|
38
|
+
### As Astro Integration (Recommended)
|
|
39
|
+
|
|
40
|
+
```javascript
|
|
41
|
+
// astro.config.mjs
|
|
42
|
+
import { defineConfig } from 'astro/config';
|
|
43
|
+
import { astroPrerenderIntegration } from 'vite-plugin-astro-prerender';
|
|
44
|
+
|
|
45
|
+
export default defineConfig({
|
|
46
|
+
integrations: [
|
|
47
|
+
astroPrerenderIntegration({
|
|
48
|
+
componentsDir: 'src/components/Lazy',
|
|
49
|
+
outputDir: 'public/prerendered',
|
|
50
|
+
generateTailwindCSS: true,
|
|
51
|
+
renderer: 'parser',
|
|
52
|
+
}),
|
|
53
|
+
],
|
|
54
|
+
});
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### As Vite Plugin
|
|
58
|
+
|
|
59
|
+
```javascript
|
|
60
|
+
// astro.config.mjs
|
|
61
|
+
import { defineConfig } from 'astro/config';
|
|
62
|
+
import { astroPrerenderPlugin } from 'vite-plugin-astro-prerender';
|
|
63
|
+
|
|
64
|
+
export default defineConfig({
|
|
65
|
+
vite: {
|
|
66
|
+
plugins: [
|
|
67
|
+
astroPrerenderPlugin({
|
|
68
|
+
componentsDir: 'src/components/Lazy',
|
|
69
|
+
outputDir: 'public/prerendered',
|
|
70
|
+
}),
|
|
71
|
+
],
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Configuration Options
|
|
77
|
+
|
|
78
|
+
| Option | Type | Default | Description |
|
|
79
|
+
|--------|------|---------|-------------|
|
|
80
|
+
| `componentsDir` | `string` | `'src/components/Lazy'` | Directory containing components to prerender |
|
|
81
|
+
| `outputDir` | `string` | `'public/prerendered'` | Output directory for prerendered files |
|
|
82
|
+
| `generateTailwindCSS` | `boolean` | `true` | Generate Tailwind CSS file with tree-shaking |
|
|
83
|
+
| `tailwindConfigPath` | `string` | `'tailwind.config.mjs'` | Path to Tailwind config file |
|
|
84
|
+
| `renderer` | `'parser' \| 'container'` | `'parser'` | Rendering strategy to use |
|
|
85
|
+
| `minify` | `boolean` | `true` | Minify HTML output to reduce file size |
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
## Rendering Modes
|
|
89
|
+
|
|
90
|
+
### Parser Renderer (Default)
|
|
91
|
+
|
|
92
|
+
Uses `@astrojs/compiler` to parse and render components via AST.
|
|
93
|
+
|
|
94
|
+
**Best for:** Simple, self-contained components without imports
|
|
95
|
+
|
|
96
|
+
```javascript
|
|
97
|
+
astroPrerenderIntegration({ renderer: 'parser' })
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
| Pros | Cons |
|
|
101
|
+
|------|------|
|
|
102
|
+
| โ
Simpler and faster | โ Doesn't resolve component imports |
|
|
103
|
+
| โ
No Vite dependency | โ Limited to basic Astro features |
|
|
104
|
+
| โ
Works for self-contained components | |
|
|
105
|
+
|
|
106
|
+
### Container Renderer
|
|
107
|
+
|
|
108
|
+
Uses Astro's Container API with Vite SSR module loading.
|
|
109
|
+
|
|
110
|
+
**Best for:** Components with imports and complex logic
|
|
111
|
+
|
|
112
|
+
```javascript
|
|
113
|
+
astroPrerenderIntegration({ renderer: 'container' })
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
| Pros | Cons |
|
|
117
|
+
|------|------|
|
|
118
|
+
| โ
Full Astro feature support | โ Requires Vite server in dev mode |
|
|
119
|
+
| โ
Resolves component imports | โ Slightly slower |
|
|
120
|
+
| โ
Handles complex component trees | |
|
|
121
|
+
|
|
122
|
+
## Example Component
|
|
123
|
+
|
|
124
|
+
Create a component in `src/components/Lazy/LazyFooter.astro`:
|
|
125
|
+
|
|
126
|
+
```astro
|
|
127
|
+
---
|
|
128
|
+
const year = '2024';
|
|
129
|
+
const company = 'ACME Inc';
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
<footer class="bg-gray-800 text-white p-6">
|
|
133
|
+
<div class="container mx-auto">
|
|
134
|
+
<p class="text-center">ยฉ {year} {company}. All rights reserved.</p>
|
|
135
|
+
</div>
|
|
136
|
+
</footer>
|
|
137
|
+
|
|
138
|
+
<style>
|
|
139
|
+
footer {
|
|
140
|
+
margin-top: 2rem;
|
|
141
|
+
}
|
|
142
|
+
</style>
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## Client-Side Lazy Loading
|
|
146
|
+
|
|
147
|
+
Use the `LazyHTMLLoader` to load prerendered components at runtime:
|
|
148
|
+
|
|
149
|
+
### Basic Usage
|
|
150
|
+
|
|
151
|
+
```typescript
|
|
152
|
+
import { createLazyLoader } from 'vite-plugin-astro-prerender';
|
|
153
|
+
|
|
154
|
+
// Create a loader instance
|
|
155
|
+
const loader = createLazyLoader({
|
|
156
|
+
baseUrl: '/prerendered',
|
|
157
|
+
debug: true,
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
// Load HTML manually
|
|
161
|
+
const html = await loader.load('LazyFooter');
|
|
162
|
+
document.getElementById('footer')!.innerHTML = html;
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### Viewport-Based Loading (IntersectionObserver)
|
|
166
|
+
|
|
167
|
+
```typescript
|
|
168
|
+
import { lazyLoader } from 'vite-plugin-astro-prerender';
|
|
169
|
+
|
|
170
|
+
// Automatically load when element enters viewport
|
|
171
|
+
lazyLoader.observeAndLoad('LazyFooter', '#footer-container');
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### Advanced Configuration
|
|
175
|
+
|
|
176
|
+
```typescript
|
|
177
|
+
import { createLazyLoader } from 'vite-plugin-astro-prerender';
|
|
178
|
+
|
|
179
|
+
const loader = createLazyLoader({
|
|
180
|
+
baseUrl: '/prerendered',
|
|
181
|
+
cacheHTML: true,
|
|
182
|
+
enableRetry: true,
|
|
183
|
+
maxRetries: 3,
|
|
184
|
+
retryDelay: 1000,
|
|
185
|
+
debug: false,
|
|
186
|
+
|
|
187
|
+
// Callbacks
|
|
188
|
+
onLoad: (name, duration) => {
|
|
189
|
+
console.log(`Loaded ${name} in ${duration}ms`);
|
|
190
|
+
},
|
|
191
|
+
onError: (name, error) => {
|
|
192
|
+
console.error(`Failed to load ${name}:`, error);
|
|
193
|
+
},
|
|
194
|
+
onCSSLoad: (file, duration) => {
|
|
195
|
+
console.log(`CSS loaded: ${file}`);
|
|
196
|
+
},
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
// Preload multiple components
|
|
200
|
+
await loader.preloadBatch(['LazyHeader', 'LazyFooter', 'LazySidebar']);
|
|
201
|
+
|
|
202
|
+
// Get statistics
|
|
203
|
+
console.log(loader.getStats());
|
|
204
|
+
// { totalLoads: 3, cacheHits: 0, cacheMisses: 3, errors: 0, averageLoadTime: 45.2 }
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### LazyLoader API
|
|
208
|
+
|
|
209
|
+
| Method | Description |
|
|
210
|
+
|--------|-------------|
|
|
211
|
+
| `load(name)` | Load a component's HTML (returns Promise<string>) |
|
|
212
|
+
| `inject(name, selector)` | Load and inject into a DOM element |
|
|
213
|
+
| `observeAndLoad(name, selector, options?)` | Load when element enters viewport |
|
|
214
|
+
| `preload(name)` | Preload without injecting |
|
|
215
|
+
| `preloadBatch(names)` | Preload multiple components |
|
|
216
|
+
| `getStats()` | Get load statistics |
|
|
217
|
+
| `clearCache()` | Clear HTML cache |
|
|
218
|
+
| `clearCSSCache()` | Clear CSS cache |
|
|
219
|
+
| `disconnectAll()` | Disconnect all observers |
|
|
220
|
+
| `reset()` | Reset all state |
|
|
221
|
+
|
|
222
|
+
## Output Structure
|
|
223
|
+
|
|
224
|
+
```
|
|
225
|
+
public/prerendered/
|
|
226
|
+
โโโ LazyFooter.html # Clean HTML output
|
|
227
|
+
โโโ LazyHeader.html
|
|
228
|
+
โโโ lazy-components.css # Tree-shaken Tailwind + component styles
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
Astro automatically copies `public/` to your output directory during build.
|
|
232
|
+
|
|
233
|
+
## How It Works
|
|
234
|
+
|
|
235
|
+
1. **Discovery**: Finds all `.astro` files in `componentsDir`
|
|
236
|
+
2. **Caching**: Checks MD5 hash to skip unchanged components
|
|
237
|
+
3. **Rendering**: Uses selected renderer to generate HTML
|
|
238
|
+
4. **Style Extraction**: Extracts `<style>` tags from components
|
|
239
|
+
5. **Class Extraction**: Finds all Tailwind classes in rendered HTML
|
|
240
|
+
6. **CSS Generation**: Creates optimized CSS with only used classes
|
|
241
|
+
7. **HTML Cleaning**: Removes scripts, dev attributes, and inline styles
|
|
242
|
+
8. **Output**: Writes clean HTML and CSS to `outputDir`
|
|
243
|
+
|
|
244
|
+
## Development
|
|
245
|
+
|
|
246
|
+
The plugin includes hot reload support. Changes to components in `componentsDir` trigger automatic reprocessing.
|
|
247
|
+
|
|
248
|
+
## TypeScript Support
|
|
249
|
+
|
|
250
|
+
Full TypeScript support with exported types:
|
|
251
|
+
|
|
252
|
+
```typescript
|
|
253
|
+
import type {
|
|
254
|
+
PluginOptions,
|
|
255
|
+
LazyLoaderConfig,
|
|
256
|
+
LoadStats,
|
|
257
|
+
CSSModuleManifest,
|
|
258
|
+
Logger,
|
|
259
|
+
} from 'vite-plugin-astro-prerender';
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
## Limitations
|
|
263
|
+
|
|
264
|
+
While powerful, this plugin has some limitations to keep in mind:
|
|
265
|
+
|
|
266
|
+
- **Client Interaction**: Components are prerendered as static HTML. While you can include scripts, complex Astro client-side state (like `base` or shared Nanostores) might require careful manual setup in the target page.
|
|
267
|
+
- **Component Imports**: The default `parser` renderer cannot resolve component imports (e.g., `<Button />`). Use the `container` renderer for components with nested dependencies.
|
|
268
|
+
- **Vite Dependency**: The `container` renderer requires a running Vite dev server to perform SSR module loading.
|
|
269
|
+
- **Relative Assets**: Assets like local images or fonts within lazy components should use absolute paths or be placed in the `public/` directory for reliable loading.
|
|
270
|
+
- **Hydration**: This plugin does not perform full "selective hydration" of interactive React/Vue/Svelte components within the lazy-loaded fragment. It is best for static-majority content.
|
|
271
|
+
|
|
272
|
+
## Contributing
|
|
273
|
+
|
|
274
|
+
Contributions are welcome! If you're interested in helping improve the plugin, please check out our [Contributing Guidelines](CONTRIBUTING.md) for a detailed breakdown of the codebase and instructions on how to get started.
|
|
275
|
+
|
|
276
|
+
## License
|
|
277
|
+
|
|
278
|
+
MIT
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration options for the LazyHTMLLoader
|
|
3
|
+
*/
|
|
4
|
+
interface LazyLoaderConfig {
|
|
5
|
+
/** Base URL for prerendered files (default: '/prerendered') */
|
|
6
|
+
baseUrl?: string;
|
|
7
|
+
/** Enable CSS modules mode with per-component CSS (default: false) */
|
|
8
|
+
cssModules?: boolean;
|
|
9
|
+
/** URL to CSS modules manifest (default: '/prerendered/manifest.json') */
|
|
10
|
+
manifestUrl?: string;
|
|
11
|
+
/** URL to base CSS file in CSS modules mode (default: '/prerendered/base.css') */
|
|
12
|
+
baseCssUrl?: string;
|
|
13
|
+
/** URL to legacy single CSS file (default: '/prerendered/lazy-components.css') */
|
|
14
|
+
legacyCssUrl?: string;
|
|
15
|
+
/** Preload CSS files before they're needed (default: false) */
|
|
16
|
+
preloadCSS?: boolean;
|
|
17
|
+
/** Cache loaded HTML in memory (default: true) */
|
|
18
|
+
cacheHTML?: boolean;
|
|
19
|
+
/** Enable retry logic for failed fetches (default: true) */
|
|
20
|
+
enableRetry?: boolean;
|
|
21
|
+
/** Maximum number of retry attempts (default: 3) */
|
|
22
|
+
maxRetries?: number;
|
|
23
|
+
/** Delay between retries in milliseconds (default: 1000) */
|
|
24
|
+
retryDelay?: number;
|
|
25
|
+
/** Enable debug logging (default: false) */
|
|
26
|
+
debug?: boolean;
|
|
27
|
+
/** Callback when a component is loaded */
|
|
28
|
+
onLoad?: (componentName: string, stats: {
|
|
29
|
+
duration: number;
|
|
30
|
+
bytes: number;
|
|
31
|
+
fromCache: boolean;
|
|
32
|
+
secondaryAssets?: Array<{
|
|
33
|
+
url: string;
|
|
34
|
+
bytes: number;
|
|
35
|
+
duration: number;
|
|
36
|
+
type: string;
|
|
37
|
+
}>;
|
|
38
|
+
}) => void;
|
|
39
|
+
/** Callback when an error occurs */
|
|
40
|
+
onError?: (componentName: string, error: Error) => void;
|
|
41
|
+
/** Callback when CSS is loaded */
|
|
42
|
+
onCSSLoad?: (cssFile: string, duration: number) => void;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* CSS module manifest structure
|
|
46
|
+
*/
|
|
47
|
+
interface CSSModuleManifest {
|
|
48
|
+
/** Map of component names to their CSS file paths */
|
|
49
|
+
components: Record<string, string>;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Load statistics
|
|
53
|
+
*/
|
|
54
|
+
interface LoadStats {
|
|
55
|
+
/** Total number of successful loads */
|
|
56
|
+
totalLoads: number;
|
|
57
|
+
/** Number of cache hits */
|
|
58
|
+
cacheHits: number;
|
|
59
|
+
/** Number of cache misses */
|
|
60
|
+
cacheMisses: number;
|
|
61
|
+
/** Number of errors encountered */
|
|
62
|
+
errors: number;
|
|
63
|
+
/** Average load time in milliseconds */
|
|
64
|
+
averageLoadTime: number;
|
|
65
|
+
/** Total bytes transferred across all loads */
|
|
66
|
+
totalBytes: number;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Client-side utility for lazy loading prerendered HTML components
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* ```ts
|
|
73
|
+
* import { createLazyLoader } from 'vite-plugin-astro-prerender';
|
|
74
|
+
*
|
|
75
|
+
* const loader = createLazyLoader({ debug: true });
|
|
76
|
+
*
|
|
77
|
+
* // Load and inject when in viewport
|
|
78
|
+
* loader.observeAndLoad('LazyFooter', '#footer-container');
|
|
79
|
+
*
|
|
80
|
+
* // Or load manually
|
|
81
|
+
* const html = await loader.load('LazyHeader');
|
|
82
|
+
* document.getElementById('header')!.innerHTML = html;
|
|
83
|
+
* ```
|
|
84
|
+
*/
|
|
85
|
+
declare class LazyHTMLLoader {
|
|
86
|
+
private config;
|
|
87
|
+
private htmlCache;
|
|
88
|
+
private cssCache;
|
|
89
|
+
private observers;
|
|
90
|
+
private manifest;
|
|
91
|
+
private manifestLoaded;
|
|
92
|
+
private baseCssLoaded;
|
|
93
|
+
private legacyCssLoaded;
|
|
94
|
+
private stats;
|
|
95
|
+
private detailedStats;
|
|
96
|
+
constructor(config?: LazyLoaderConfig);
|
|
97
|
+
/**
|
|
98
|
+
* Internal logging method
|
|
99
|
+
*/
|
|
100
|
+
private log;
|
|
101
|
+
/**
|
|
102
|
+
* Fetch with retry logic
|
|
103
|
+
*/
|
|
104
|
+
private fetchWithRetry;
|
|
105
|
+
/**
|
|
106
|
+
* Load CSS manifest for CSS modules mode
|
|
107
|
+
*/
|
|
108
|
+
private loadManifest;
|
|
109
|
+
/**
|
|
110
|
+
* Load base CSS file (CSS modules mode)
|
|
111
|
+
*/
|
|
112
|
+
private loadBaseCSS;
|
|
113
|
+
/**
|
|
114
|
+
* Load component-specific CSS file (CSS modules mode)
|
|
115
|
+
*/
|
|
116
|
+
private loadComponentCSS;
|
|
117
|
+
/**
|
|
118
|
+
* Load legacy single CSS file
|
|
119
|
+
*/
|
|
120
|
+
private loadLegacyCSS;
|
|
121
|
+
/**
|
|
122
|
+
* Load HTML fragment from server
|
|
123
|
+
*/
|
|
124
|
+
load(componentName: string): Promise<string>;
|
|
125
|
+
/**
|
|
126
|
+
* Inject HTML fragment into target element
|
|
127
|
+
*/
|
|
128
|
+
inject(componentName: string, targetSelector: string): Promise<void>;
|
|
129
|
+
/**
|
|
130
|
+
* Load HTML fragment when target element enters viewport
|
|
131
|
+
*/
|
|
132
|
+
observeAndLoad(componentName: string, targetSelector: string, options?: IntersectionObserverInit): void;
|
|
133
|
+
/**
|
|
134
|
+
* Preload HTML fragment without injecting
|
|
135
|
+
*/
|
|
136
|
+
preload(componentName: string): Promise<void>;
|
|
137
|
+
/**
|
|
138
|
+
* Batch preload multiple components
|
|
139
|
+
*/
|
|
140
|
+
preloadBatch(componentNames: string[]): Promise<void>;
|
|
141
|
+
/**
|
|
142
|
+
* Get load statistics
|
|
143
|
+
*/
|
|
144
|
+
getStats(): LoadStats;
|
|
145
|
+
/**
|
|
146
|
+
* Get detailed history of all loads in this session
|
|
147
|
+
*/
|
|
148
|
+
getDetailedHistory(): Array<{
|
|
149
|
+
componentName: string;
|
|
150
|
+
duration: number;
|
|
151
|
+
bytes: number;
|
|
152
|
+
fromCache: boolean;
|
|
153
|
+
timestamp: number;
|
|
154
|
+
}>;
|
|
155
|
+
/**
|
|
156
|
+
* Clear HTML cache
|
|
157
|
+
*/
|
|
158
|
+
clearCache(): void;
|
|
159
|
+
/**
|
|
160
|
+
* Clear CSS cache (forces reload of CSS files)
|
|
161
|
+
*/
|
|
162
|
+
clearCSSCache(): void;
|
|
163
|
+
/**
|
|
164
|
+
* Disconnect all observers
|
|
165
|
+
*/
|
|
166
|
+
disconnectAll(): void;
|
|
167
|
+
/**
|
|
168
|
+
* Reset all state (cache, observers, stats)
|
|
169
|
+
*/
|
|
170
|
+
reset(): void;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Factory function to create a lazy loader instance
|
|
174
|
+
*/
|
|
175
|
+
declare function createLazyLoader(config?: LazyLoaderConfig): LazyHTMLLoader;
|
|
176
|
+
/**
|
|
177
|
+
* Default lazy loader instance with default configuration
|
|
178
|
+
*/
|
|
179
|
+
declare const lazyLoader: LazyHTMLLoader;
|
|
180
|
+
|
|
181
|
+
export { type CSSModuleManifest, LazyHTMLLoader, type LazyLoaderConfig, type LoadStats, createLazyLoader, lazyLoader };
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
var m=class{constructor(e={}){this.htmlCache=new Map;this.cssCache=new Set;this.observers=new Map;this.manifest=null;this.manifestLoaded=!1;this.baseCssLoaded=!1;this.legacyCssLoaded=!1;this.stats={totalLoads:0,cacheHits:0,cacheMisses:0,errors:0,totalLoadTime:0,totalBytes:0};this.detailedStats=new Map;var t,o,s,r,d,i,a,f,u,n,c,l,h,y;this.config={baseUrl:(t=e.baseUrl)!=null?t:"/prerendered",cssModules:(o=e.cssModules)!=null?o:!1,manifestUrl:(s=e.manifestUrl)!=null?s:"/prerendered/manifest.json",baseCssUrl:(r=e.baseCssUrl)!=null?r:"/prerendered/base.css",legacyCssUrl:(d=e.legacyCssUrl)!=null?d:"/prerendered/lazy-components.css",preloadCSS:(i=e.preloadCSS)!=null?i:!1,cacheHTML:(a=e.cacheHTML)!=null?a:!0,enableRetry:(f=e.enableRetry)!=null?f:!0,maxRetries:(u=e.maxRetries)!=null?u:3,retryDelay:(n=e.retryDelay)!=null?n:1e3,debug:(c=e.debug)!=null?c:!1,onLoad:(l=e.onLoad)!=null?l:(()=>{}),onError:(h=e.onError)!=null?h:(()=>{}),onCSSLoad:(y=e.onCSSLoad)!=null?y:(()=>{})},this.log("LazyHTMLLoader initialized",this.config)}log(e,...t){this.config.debug&&console.log(`[LazyHTMLLoader] ${e}`,...t)}async fetchWithRetry(e,t=this.config.maxRetries){for(let o=1;o<=t;o++){try{this.log(`Fetching ${e} (attempt ${o}/${t})`);let s=await fetch(e);if(s.ok)return s;if(o===t)throw new Error(`HTTP ${s.status}: ${s.statusText}`);if(s.status===404)throw new Error(`Not found: ${e}`);this.log(`Fetch failed with status ${s.status}, retrying...`)}catch(s){if(o===t)throw s;this.log(`Fetch error: ${s}, retrying...`)}o<t&&await new Promise(s=>setTimeout(s,this.config.retryDelay*o))}throw new Error(`Failed to fetch ${e} after ${t} attempts`)}async loadManifest(){if(this.manifestLoaded)return;let e=performance.now();try{let t=await(this.config.enableRetry?this.fetchWithRetry(this.config.manifestUrl):fetch(this.config.manifestUrl));if(!t.ok)throw new Error(`Failed to load manifest: ${t.statusText}`);this.manifest=await t.json(),this.manifestLoaded=!0;let o=performance.now()-e;this.log(`Manifest loaded in ${o.toFixed(2)}ms`,this.manifest),this.config.onCSSLoad(this.config.manifestUrl,o)}catch(t){console.error("Failed to load CSS manifest:",t),this.config.cssModules=!1,this.manifestLoaded=!0}}async loadBaseCSS(){if(this.baseCssLoaded||(await this.loadManifest(),!this.manifest))return;let e=performance.now();return new Promise((t,o)=>{let s=document.createElement("link");if(s.rel="stylesheet",s.href=this.config.baseCssUrl,s.onload=()=>{this.baseCssLoaded=!0;let r=performance.now()-e;this.log(`Base CSS loaded in ${r.toFixed(2)}ms`),this.config.onCSSLoad(this.config.baseCssUrl,r),t()},s.onerror=()=>{console.error("Failed to load base CSS"),o(new Error("Failed to load base CSS"))},this.config.preloadCSS){let r=document.createElement("link");r.rel="preload",r.as="style",r.href=this.config.baseCssUrl,document.head.appendChild(r)}document.head.appendChild(s)})}async loadComponentCSS(e){if(!this.config.cssModules||!this.manifest)return;let t=this.manifest.components[e];if(!t){this.log(`No CSS file for component: ${e}`);return}if(this.cssCache.has(t)){this.log(`CSS already loaded: ${t}`);return}let o=performance.now(),s=`${this.config.baseUrl}/${t}`;return new Promise((r,d)=>{let i=document.createElement("link");if(i.rel="stylesheet",i.href=s,i.onload=()=>{this.cssCache.add(t);let a=performance.now()-o;this.log(`Component CSS loaded in ${a.toFixed(2)}ms: ${t}`),this.config.onCSSLoad(t,a),r()},i.onerror=()=>{console.error(`Failed to load component CSS: ${t}`),d(new Error(`Failed to load CSS: ${t}`))},this.config.preloadCSS){let a=document.createElement("link");a.rel="preload",a.as="style",a.href=s,document.head.appendChild(a)}document.head.appendChild(i)})}async loadLegacyCSS(){if(this.legacyCssLoaded)return;let e=performance.now();return new Promise((t,o)=>{let s=document.createElement("link");if(s.rel="stylesheet",s.href=this.config.legacyCssUrl,s.onload=()=>{this.legacyCssLoaded=!0;let r=performance.now()-e;this.log(`Legacy CSS loaded in ${r.toFixed(2)}ms`),this.config.onCSSLoad(this.config.legacyCssUrl,r),t()},s.onerror=()=>{console.error("Failed to load legacy CSS"),o(new Error("Failed to load lazy components CSS"))},this.config.preloadCSS){let r=document.createElement("link");r.rel="preload",r.as="style",r.href=this.config.legacyCssUrl,document.head.appendChild(r)}document.head.appendChild(s)})}async load(e){let t=performance.now();try{if(this.config.cacheHTML&&this.htmlCache.has(e)){this.stats.cacheHits++,this.log(`Cache hit: ${e}`);let n=this.htmlCache.get(e),c=new Blob([n]).size,l=performance.now()-t;this.stats.totalLoads++,this.stats.totalLoadTime+=l;let h={duration:l,bytes:c,fromCache:!0,timestamp:Date.now()};return this.detailedStats.set(e,h),this.config.onLoad(e,{duration:l,bytes:c,fromCache:!0}),n}this.stats.cacheMisses++,this.config.cssModules?(await this.loadBaseCSS().catch(n=>{console.warn("Failed to load base CSS:",n)}),await this.loadComponentCSS(e).catch(n=>{console.warn(`Failed to load CSS for ${e}:`,n)})):await this.loadLegacyCSS().catch(n=>{console.warn("Failed to load legacy CSS:",n)});let o=`${this.config.baseUrl}/${e}.html`,s=await(this.config.enableRetry?this.fetchWithRetry(o):fetch(o));if(!s.ok)throw new Error(`HTTP ${s.status}: ${s.statusText}`);let r=await s.text(),d=new Blob([r]).size;this.config.cacheHTML&&this.htmlCache.set(e,r);let i=performance.now()-t;this.stats.totalLoads++,this.stats.totalLoadTime+=i,this.stats.totalBytes+=d,this.log(`Loaded ${e} in ${i.toFixed(2)}ms (${d} bytes)`);let a=[],f=new PerformanceObserver(n=>{n.getEntries().forEach(c=>{let l=c;if(l.startTime>=t-50){let h=l.transferSize||l.decodedBodySize||l.encodedBodySize||0;a.push({url:l.name,bytes:h,duration:l.duration,type:l.initiatorType}),this.stats.totalBytes+=h}})});try{f.observe({entryTypes:["resource"],buffered:!0}),setTimeout(()=>f.disconnect(),6e3)}catch(n){this.log("PerformanceObserver failed");try{f.observe({entryTypes:["resource"]})}catch(c){}}let u={duration:i,bytes:d,fromCache:!1,timestamp:Date.now(),secondaryAssets:a};return this.detailedStats.set(e,u),this.config.onLoad(e,{duration:i,bytes:d,fromCache:!1,secondaryAssets:a}),r}catch(o){this.stats.errors++;let s=o instanceof Error?o:new Error(String(o));throw this.log(`Error loading ${e}:`,s),this.config.onError(e,s),s}}async inject(e,t){let o=await this.load(e),s=document.querySelector(t);if(s)s.innerHTML=o,this.log(`Injected ${e} into ${t}`);else{let r=new Error(`Target element not found: ${t}`);throw this.config.onError(e,r),r}}observeAndLoad(e,t,o){let s=document.querySelector(t);if(!s){console.warn(`Target element not found: ${t}`);return}let r=new IntersectionObserver(d=>{d.forEach(i=>{i.isIntersecting&&(this.log(`Component ${e} entered viewport`),this.inject(e,t).catch(a=>{console.error(`Failed to inject ${e}:`,a)}),r.disconnect(),this.observers.delete(e))})},o||{rootMargin:"100px"});r.observe(s),this.observers.set(e,r),this.log(`Observing ${e} at ${t}`)}async preload(e){await this.load(e)}async preloadBatch(e){this.log(`Preloading ${e.length} components:`,e),await Promise.all(e.map(t=>this.preload(t)))}getStats(){return{totalLoads:this.stats.totalLoads,cacheHits:this.stats.cacheHits,cacheMisses:this.stats.cacheMisses,errors:this.stats.errors,averageLoadTime:this.stats.totalLoads>0?this.stats.totalLoadTime/this.stats.totalLoads:0,totalBytes:this.stats.totalBytes}}getDetailedHistory(){return Array.from(this.detailedStats.entries()).map(([e,t])=>({componentName:e,...t})).sort((e,t)=>t.timestamp-e.timestamp)}clearCache(){this.htmlCache.clear(),this.log("HTML cache cleared")}clearCSSCache(){this.cssCache.clear(),this.baseCssLoaded=!1,this.legacyCssLoaded=!1,this.manifestLoaded=!1,this.manifest=null,this.log("CSS cache cleared")}disconnectAll(){this.observers.forEach(e=>e.disconnect()),this.observers.clear(),this.log("All observers disconnected")}reset(){this.clearCache(),this.clearCSSCache(),this.disconnectAll(),this.stats={totalLoads:0,cacheHits:0,cacheMisses:0,errors:0,totalLoadTime:0,totalBytes:0},this.detailedStats.clear(),this.log("LazyHTMLLoader reset")}};function C(g){return new m(g)}var S=new m;export{m as LazyHTMLLoader,C as createLazyLoader,S as lazyLoader};
|
|
2
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/utils/LazyLoader.ts"],"sourcesContent":["// src/utils/LazyLoader.ts\n// Client-side utility for lazy loading prerendered HTML components\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Configuration options for the LazyHTMLLoader\n */\nexport interface LazyLoaderConfig {\n /** Base URL for prerendered files (default: '/prerendered') */\n baseUrl?: string;\n /** Enable CSS modules mode with per-component CSS (default: false) */\n cssModules?: boolean;\n /** URL to CSS modules manifest (default: '/prerendered/manifest.json') */\n manifestUrl?: string;\n /** URL to base CSS file in CSS modules mode (default: '/prerendered/base.css') */\n baseCssUrl?: string;\n /** URL to legacy single CSS file (default: '/prerendered/lazy-components.css') */\n legacyCssUrl?: string;\n /** Preload CSS files before they're needed (default: false) */\n preloadCSS?: boolean;\n /** Cache loaded HTML in memory (default: true) */\n cacheHTML?: boolean;\n /** Enable retry logic for failed fetches (default: true) */\n enableRetry?: boolean;\n /** Maximum number of retry attempts (default: 3) */\n maxRetries?: number;\n /** Delay between retries in milliseconds (default: 1000) */\n retryDelay?: number;\n /** Enable debug logging (default: false) */\n debug?: boolean;\n /** Callback when a component is loaded */\n onLoad?: (componentName: string, stats: {\n duration: number;\n bytes: number;\n fromCache: boolean;\n secondaryAssets?: Array<{ url: string; bytes: number; duration: number; type: string }>\n }) => void;\n /** Callback when an error occurs */\n onError?: (componentName: string, error: Error) => void;\n /** Callback when CSS is loaded */\n onCSSLoad?: (cssFile: string, duration: number) => void;\n}\n\n/**\n * CSS module manifest structure\n */\nexport interface CSSModuleManifest {\n /** Map of component names to their CSS file paths */\n components: Record<string, string>;\n}\n\n/**\n * Load statistics\n */\nexport interface LoadStats {\n /** Total number of successful loads */\n totalLoads: number;\n /** Number of cache hits */\n cacheHits: number;\n /** Number of cache misses */\n cacheMisses: number;\n /** Number of errors encountered */\n errors: number;\n /** Average load time in milliseconds */\n averageLoadTime: number;\n /** Total bytes transferred across all loads */\n totalBytes: number;\n}\n\n// ============================================================================\n// LazyHTMLLoader Class\n// ============================================================================\n\n/**\n * Client-side utility for lazy loading prerendered HTML components\n * \n * @example\n * ```ts\n * import { createLazyLoader } from 'vite-plugin-astro-prerender';\n * \n * const loader = createLazyLoader({ debug: true });\n * \n * // Load and inject when in viewport\n * loader.observeAndLoad('LazyFooter', '#footer-container');\n * \n * // Or load manually\n * const html = await loader.load('LazyHeader');\n * document.getElementById('header')!.innerHTML = html;\n * ```\n */\nexport class LazyHTMLLoader {\n private config: Required<LazyLoaderConfig>;\n private htmlCache = new Map<string, string>();\n private cssCache = new Set<string>();\n private observers = new Map<string, IntersectionObserver>();\n private manifest: CSSModuleManifest | null = null;\n private manifestLoaded = false;\n private baseCssLoaded = false;\n private legacyCssLoaded = false;\n private stats = {\n totalLoads: 0,\n cacheHits: 0,\n cacheMisses: 0,\n errors: 0,\n totalLoadTime: 0,\n totalBytes: 0,\n };\n private detailedStats = new Map<string, {\n duration: number;\n bytes: number;\n fromCache: boolean;\n timestamp: number;\n secondaryAssets?: Array<{ url: string; bytes: number; duration: number; type: string }>\n }>();\n\n constructor(config: LazyLoaderConfig = {}) {\n this.config = {\n baseUrl: config.baseUrl ?? '/prerendered',\n cssModules: config.cssModules ?? false,\n manifestUrl: config.manifestUrl ?? '/prerendered/manifest.json',\n baseCssUrl: config.baseCssUrl ?? '/prerendered/base.css',\n legacyCssUrl: config.legacyCssUrl ?? '/prerendered/lazy-components.css',\n preloadCSS: config.preloadCSS ?? false,\n cacheHTML: config.cacheHTML ?? true,\n enableRetry: config.enableRetry ?? true,\n maxRetries: config.maxRetries ?? 3,\n retryDelay: config.retryDelay ?? 1000,\n debug: config.debug ?? false,\n onLoad: config.onLoad ?? (() => { }),\n onError: config.onError ?? (() => { }),\n onCSSLoad: config.onCSSLoad ?? (() => { }),\n };\n\n this.log('LazyHTMLLoader initialized', this.config);\n }\n\n /**\n * Internal logging method\n */\n private log(message: string, ...args: unknown[]): void {\n if (this.config.debug) {\n console.log(`[LazyHTMLLoader] ${message}`, ...args);\n }\n }\n\n /**\n * Fetch with retry logic\n */\n private async fetchWithRetry(\n url: string,\n retries = this.config.maxRetries,\n ): Promise<Response> {\n for (let attempt = 1; attempt <= retries; attempt++) {\n try {\n this.log(`Fetching ${url} (attempt ${attempt}/${retries})`);\n const response = await fetch(url);\n\n if (response.ok) {\n return response;\n }\n\n if (attempt === retries) {\n throw new Error(`HTTP ${response.status}: ${response.statusText}`);\n }\n\n // Don't retry on 404\n if (response.status === 404) {\n throw new Error(`Not found: ${url}`);\n }\n\n this.log(`Fetch failed with status ${response.status}, retrying...`);\n } catch (error) {\n if (attempt === retries) {\n throw error;\n }\n this.log(`Fetch error: ${error}, retrying...`);\n }\n\n // Wait before retrying\n if (attempt < retries) {\n await new Promise((resolve) =>\n setTimeout(resolve, this.config.retryDelay * attempt),\n );\n }\n }\n\n throw new Error(`Failed to fetch ${url} after ${retries} attempts`);\n }\n\n /**\n * Load CSS manifest for CSS modules mode\n */\n private async loadManifest(): Promise<void> {\n if (this.manifestLoaded) {\n return;\n }\n\n const startTime = performance.now();\n\n try {\n const response = await (this.config.enableRetry\n ? this.fetchWithRetry(this.config.manifestUrl)\n : fetch(this.config.manifestUrl));\n\n if (!response.ok) {\n throw new Error(`Failed to load manifest: ${response.statusText}`);\n }\n\n this.manifest = await response.json();\n this.manifestLoaded = true;\n\n const duration = performance.now() - startTime;\n this.log(`Manifest loaded in ${duration.toFixed(2)}ms`, this.manifest);\n this.config.onCSSLoad(this.config.manifestUrl, duration);\n } catch (error) {\n console.error('Failed to load CSS manifest:', error);\n // Fallback to legacy mode\n this.config.cssModules = false;\n this.manifestLoaded = true;\n }\n }\n\n /**\n * Load base CSS file (CSS modules mode)\n */\n private async loadBaseCSS(): Promise<void> {\n if (this.baseCssLoaded) {\n return;\n }\n\n await this.loadManifest();\n\n if (!this.manifest) {\n return;\n }\n\n const startTime = performance.now();\n\n return new Promise((resolve, reject) => {\n const link = document.createElement('link');\n link.rel = 'stylesheet';\n link.href = this.config.baseCssUrl;\n\n link.onload = () => {\n this.baseCssLoaded = true;\n const duration = performance.now() - startTime;\n this.log(`Base CSS loaded in ${duration.toFixed(2)}ms`);\n this.config.onCSSLoad(this.config.baseCssUrl, duration);\n resolve();\n };\n\n link.onerror = () => {\n console.error('Failed to load base CSS');\n reject(new Error('Failed to load base CSS'));\n };\n\n if (this.config.preloadCSS) {\n const preload = document.createElement('link');\n preload.rel = 'preload';\n preload.as = 'style';\n preload.href = this.config.baseCssUrl;\n document.head.appendChild(preload);\n }\n\n document.head.appendChild(link);\n });\n }\n\n /**\n * Load component-specific CSS file (CSS modules mode)\n */\n private async loadComponentCSS(componentName: string): Promise<void> {\n if (!this.config.cssModules || !this.manifest) {\n return;\n }\n\n const cssFile = this.manifest.components[componentName];\n if (!cssFile) {\n this.log(`No CSS file for component: ${componentName}`);\n return;\n }\n\n if (this.cssCache.has(cssFile)) {\n this.log(`CSS already loaded: ${cssFile}`);\n return;\n }\n\n const startTime = performance.now();\n const cssUrl = `${this.config.baseUrl}/${cssFile}`;\n\n return new Promise((resolve, reject) => {\n const link = document.createElement('link');\n link.rel = 'stylesheet';\n link.href = cssUrl;\n\n link.onload = () => {\n this.cssCache.add(cssFile);\n const duration = performance.now() - startTime;\n this.log(\n `Component CSS loaded in ${duration.toFixed(2)}ms: ${cssFile}`,\n );\n this.config.onCSSLoad(cssFile, duration);\n resolve();\n };\n\n link.onerror = () => {\n console.error(`Failed to load component CSS: ${cssFile}`);\n reject(new Error(`Failed to load CSS: ${cssFile}`));\n };\n\n if (this.config.preloadCSS) {\n const preload = document.createElement('link');\n preload.rel = 'preload';\n preload.as = 'style';\n preload.href = cssUrl;\n document.head.appendChild(preload);\n }\n\n document.head.appendChild(link);\n });\n }\n\n /**\n * Load legacy single CSS file\n */\n private async loadLegacyCSS(): Promise<void> {\n if (this.legacyCssLoaded) {\n return;\n }\n\n const startTime = performance.now();\n\n return new Promise((resolve, reject) => {\n const link = document.createElement('link');\n link.rel = 'stylesheet';\n link.href = this.config.legacyCssUrl;\n\n link.onload = () => {\n this.legacyCssLoaded = true;\n const duration = performance.now() - startTime;\n this.log(`Legacy CSS loaded in ${duration.toFixed(2)}ms`);\n this.config.onCSSLoad(this.config.legacyCssUrl, duration);\n resolve();\n };\n\n link.onerror = () => {\n console.error('Failed to load legacy CSS');\n reject(new Error('Failed to load lazy components CSS'));\n };\n\n if (this.config.preloadCSS) {\n const preload = document.createElement('link');\n preload.rel = 'preload';\n preload.as = 'style';\n preload.href = this.config.legacyCssUrl;\n document.head.appendChild(preload);\n }\n\n document.head.appendChild(link);\n });\n }\n\n /**\n * Load HTML fragment from server\n */\n async load(componentName: string): Promise<string> {\n const startTime = performance.now();\n\n try {\n // Check cache\n if (this.config.cacheHTML && this.htmlCache.has(componentName)) {\n this.stats.cacheHits++;\n this.log(`Cache hit: ${componentName}`);\n const html = this.htmlCache.get(componentName)!;\n const bytes = new Blob([html]).size;\n const duration = performance.now() - startTime;\n\n this.stats.totalLoads++;\n this.stats.totalLoadTime += duration;\n\n const loadInfo = { duration, bytes, fromCache: true, timestamp: Date.now() };\n this.detailedStats.set(componentName, loadInfo);\n this.config.onLoad(componentName, { duration, bytes, fromCache: true });\n\n return html;\n }\n\n this.stats.cacheMisses++;\n\n // Load CSS\n if (this.config.cssModules) {\n await this.loadBaseCSS().catch((err) => {\n console.warn('Failed to load base CSS:', err);\n });\n await this.loadComponentCSS(componentName).catch((err) => {\n console.warn(`Failed to load CSS for ${componentName}:`, err);\n });\n } else {\n await this.loadLegacyCSS().catch((err) => {\n console.warn('Failed to load legacy CSS:', err);\n });\n }\n\n // Fetch HTML\n const url = `${this.config.baseUrl}/${componentName}.html`;\n const response = await (this.config.enableRetry\n ? this.fetchWithRetry(url)\n : fetch(url));\n\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}: ${response.statusText}`);\n }\n\n const html = await response.text();\n const bytes = new Blob([html]).size;\n\n // Cache HTML\n if (this.config.cacheHTML) {\n this.htmlCache.set(componentName, html);\n }\n\n // Update stats\n const duration = performance.now() - startTime;\n this.stats.totalLoads++;\n this.stats.totalLoadTime += duration;\n this.stats.totalBytes += bytes;\n\n this.log(`Loaded ${componentName} in ${duration.toFixed(2)}ms (${bytes} bytes)`);\n\n // Start tracking secondary assets (images, etc.) that might be triggered by this injection\n const secondaryAssets: Array<{ url: string; bytes: number; duration: number; type: string }> = [];\n\n const resourceObserver = new PerformanceObserver((list) => {\n list.getEntries().forEach((entry) => {\n const res = entry as PerformanceResourceTiming;\n // Filter for assets likely triggered by this component\n if (res.startTime >= (startTime - 50)) {\n const assetBytes = res.transferSize || res.decodedBodySize || res.encodedBodySize || 0;\n\n secondaryAssets.push({\n url: res.name,\n bytes: assetBytes,\n duration: res.duration,\n type: res.initiatorType\n });\n\n // Update total bytes and global stats with secondary assets\n this.stats.totalBytes += assetBytes;\n }\n });\n });\n\n try {\n // Use buffered: true to catch resources that might have started \n // between performance.now() and observe() call\n resourceObserver.observe({ entryTypes: ['resource'], buffered: true });\n\n // Stop observing after a reasonable time (longer for assets)\n setTimeout(() => resourceObserver.disconnect(), 6000);\n } catch (e) {\n this.log('PerformanceObserver failed');\n // Fallback attempt without buffering if that was the cause\n try { resourceObserver.observe({ entryTypes: ['resource'] }); } catch (err) { }\n }\n\n const loadInfo = {\n duration,\n bytes,\n fromCache: false,\n timestamp: Date.now(),\n secondaryAssets\n };\n this.detailedStats.set(componentName, loadInfo);\n this.config.onLoad(componentName, { duration, bytes, fromCache: false, secondaryAssets });\n\n return html;\n } catch (error) {\n this.stats.errors++;\n const err = error instanceof Error ? error : new Error(String(error));\n this.log(`Error loading ${componentName}:`, err);\n this.config.onError(componentName, err);\n throw err;\n }\n }\n\n /**\n * Inject HTML fragment into target element\n */\n async inject(componentName: string, targetSelector: string): Promise<void> {\n const html = await this.load(componentName);\n const target = document.querySelector(targetSelector);\n\n if (target) {\n target.innerHTML = html;\n this.log(`Injected ${componentName} into ${targetSelector}`);\n } else {\n const error = new Error(`Target element not found: ${targetSelector}`);\n this.config.onError(componentName, error);\n throw error;\n }\n }\n\n /**\n * Load HTML fragment when target element enters viewport\n */\n observeAndLoad(\n componentName: string,\n targetSelector: string,\n options?: IntersectionObserverInit,\n ): void {\n const target = document.querySelector(targetSelector);\n if (!target) {\n console.warn(`Target element not found: ${targetSelector}`);\n return;\n }\n\n const observer = new IntersectionObserver(\n (entries) => {\n entries.forEach((entry) => {\n if (entry.isIntersecting) {\n this.log(`Component ${componentName} entered viewport`);\n this.inject(componentName, targetSelector).catch((err) => {\n console.error(`Failed to inject ${componentName}:`, err);\n });\n observer.disconnect();\n this.observers.delete(componentName);\n }\n });\n },\n options || { rootMargin: '100px' },\n );\n\n observer.observe(target);\n this.observers.set(componentName, observer);\n this.log(`Observing ${componentName} at ${targetSelector}`);\n }\n\n /**\n * Preload HTML fragment without injecting\n */\n async preload(componentName: string): Promise<void> {\n await this.load(componentName);\n }\n\n /**\n * Batch preload multiple components\n */\n async preloadBatch(componentNames: string[]): Promise<void> {\n this.log(`Preloading ${componentNames.length} components:`, componentNames);\n await Promise.all(componentNames.map((name) => this.preload(name)));\n }\n\n /**\n * Get load statistics\n */\n getStats(): LoadStats {\n return {\n totalLoads: this.stats.totalLoads,\n cacheHits: this.stats.cacheHits,\n cacheMisses: this.stats.cacheMisses,\n errors: this.stats.errors,\n averageLoadTime:\n this.stats.totalLoads > 0\n ? this.stats.totalLoadTime / this.stats.totalLoads\n : 0,\n totalBytes: this.stats.totalBytes,\n };\n }\n\n /**\n * Get detailed history of all loads in this session\n */\n getDetailedHistory(): Array<{ componentName: string; duration: number; bytes: number; fromCache: boolean; timestamp: number }> {\n return Array.from(this.detailedStats.entries()).map(([name, info]) => ({\n componentName: name,\n ...info,\n })).sort((a, b) => b.timestamp - a.timestamp);\n }\n\n /**\n * Clear HTML cache\n */\n clearCache(): void {\n this.htmlCache.clear();\n this.log('HTML cache cleared');\n }\n\n /**\n * Clear CSS cache (forces reload of CSS files)\n */\n clearCSSCache(): void {\n this.cssCache.clear();\n this.baseCssLoaded = false;\n this.legacyCssLoaded = false;\n this.manifestLoaded = false;\n this.manifest = null;\n this.log('CSS cache cleared');\n }\n\n /**\n * Disconnect all observers\n */\n disconnectAll(): void {\n this.observers.forEach((observer) => observer.disconnect());\n this.observers.clear();\n this.log('All observers disconnected');\n }\n\n /**\n * Reset all state (cache, observers, stats)\n */\n reset(): void {\n this.clearCache();\n this.clearCSSCache();\n this.disconnectAll();\n this.stats = {\n totalLoads: 0,\n cacheHits: 0,\n cacheMisses: 0,\n errors: 0,\n totalLoadTime: 0,\n totalBytes: 0,\n };\n this.detailedStats.clear();\n this.log('LazyHTMLLoader reset');\n }\n}\n\n/**\n * Factory function to create a lazy loader instance\n */\nexport function createLazyLoader(config?: LazyLoaderConfig): LazyHTMLLoader {\n return new LazyHTMLLoader(config);\n}\n\n/**\n * Default lazy loader instance with default configuration\n */\nexport const lazyLoader = new LazyHTMLLoader();"],"mappings":"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","names":["LazyHTMLLoader","config","_a","_b","_c","_d","_e","_f","_g","_h","_i","_j","_k","_l","_m","_n","message","args","url","retries","attempt","response","error","resolve","startTime","duration","reject","link","preload","componentName","cssFile","cssUrl","html","bytes","loadInfo","err","secondaryAssets","resourceObserver","list","entry","res","assetBytes","e","targetSelector","target","options","observer","entries","componentNames","name","info","a","b","createLazyLoader","lazyLoader"]}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
import { AstroIntegration } from 'astro';
|
|
2
|
+
import { Plugin } from 'vite';
|
|
3
|
+
import { ConsolaInstance } from 'consola';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Manages cache for rendered components using flat-cache
|
|
7
|
+
* Uses file hashes to skip unchanged components
|
|
8
|
+
*/
|
|
9
|
+
declare class CacheManager {
|
|
10
|
+
private cache;
|
|
11
|
+
private loaded;
|
|
12
|
+
constructor(cacheDir: string);
|
|
13
|
+
/**
|
|
14
|
+
* Load cache from disk
|
|
15
|
+
*/
|
|
16
|
+
load(): Promise<void>;
|
|
17
|
+
/**
|
|
18
|
+
* Save cache to disk
|
|
19
|
+
*/
|
|
20
|
+
save(): Promise<void>;
|
|
21
|
+
/**
|
|
22
|
+
* Check if component is cached with same hash
|
|
23
|
+
*/
|
|
24
|
+
isCached(componentPath: string, hash: string): boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Set component hash in cache
|
|
27
|
+
*/
|
|
28
|
+
set(componentPath: string, hash: string): void;
|
|
29
|
+
/**
|
|
30
|
+
* Delete component from cache
|
|
31
|
+
*/
|
|
32
|
+
delete(componentPath: string): void;
|
|
33
|
+
/**
|
|
34
|
+
* Clear all cache
|
|
35
|
+
*/
|
|
36
|
+
clear(): void;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
type Logger = ConsolaInstance;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Generates CSS from Tailwind classes and component styles
|
|
43
|
+
* Tree-shakes Tailwind to only include used classes
|
|
44
|
+
*/
|
|
45
|
+
declare class CSSGenerator {
|
|
46
|
+
private tailwindConfigPath;
|
|
47
|
+
private logger;
|
|
48
|
+
private classes;
|
|
49
|
+
private componentStyles;
|
|
50
|
+
private generateTailwind;
|
|
51
|
+
constructor(tailwindConfigPath: string, logger: Logger, generateTailwind?: boolean);
|
|
52
|
+
/**
|
|
53
|
+
* Add Tailwind classes to generate CSS for
|
|
54
|
+
*/
|
|
55
|
+
addClasses(classes: string[]): void;
|
|
56
|
+
/**
|
|
57
|
+
* Add component styles to include in CSS
|
|
58
|
+
*/
|
|
59
|
+
addStyles(styles: string[]): void;
|
|
60
|
+
/**
|
|
61
|
+
* Clear all classes and styles
|
|
62
|
+
*/
|
|
63
|
+
clear(): void;
|
|
64
|
+
/**
|
|
65
|
+
* Generate CSS file with Tailwind and component styles
|
|
66
|
+
*/
|
|
67
|
+
generate(outputPath: string): Promise<void>;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
interface ViteDevServer {
|
|
71
|
+
ssrLoadModule(url: string): Promise<Record<string, unknown>>;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Renders components using Astro Container API
|
|
75
|
+
* Supports component imports and full Astro features
|
|
76
|
+
*/
|
|
77
|
+
declare class ContainerRenderer {
|
|
78
|
+
private logger;
|
|
79
|
+
private container;
|
|
80
|
+
private viteServer;
|
|
81
|
+
constructor(logger: Logger);
|
|
82
|
+
/**
|
|
83
|
+
* Set Vite server instance (required for dev mode)
|
|
84
|
+
*/
|
|
85
|
+
setViteServer(server: ViteDevServer): void;
|
|
86
|
+
/**
|
|
87
|
+
* Initialize the container
|
|
88
|
+
*/
|
|
89
|
+
private init;
|
|
90
|
+
/**
|
|
91
|
+
* Render a component to HTML
|
|
92
|
+
*/
|
|
93
|
+
render(componentPath: string): Promise<string>;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Renders components using Astro's AST parser
|
|
98
|
+
* Simpler approach but doesn't resolve component imports
|
|
99
|
+
*/
|
|
100
|
+
declare class ParserRenderer {
|
|
101
|
+
private logger;
|
|
102
|
+
/**
|
|
103
|
+
* Extract frontmatter variables from AST
|
|
104
|
+
*/
|
|
105
|
+
private extractFrontmatter;
|
|
106
|
+
/**
|
|
107
|
+
* Convert AST node to HTML string
|
|
108
|
+
*/
|
|
109
|
+
private nodeToHTML;
|
|
110
|
+
/**
|
|
111
|
+
* Replace frontmatter variable expressions in HTML
|
|
112
|
+
*/
|
|
113
|
+
private replaceFrontmatterVars;
|
|
114
|
+
/**
|
|
115
|
+
* Render a component to HTML
|
|
116
|
+
*/
|
|
117
|
+
render(filePath: string): Promise<string>;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Configuration options for the LazyHTMLLoader
|
|
122
|
+
*/
|
|
123
|
+
interface LazyLoaderConfig {
|
|
124
|
+
/** Base URL for prerendered files (default: '/prerendered') */
|
|
125
|
+
baseUrl?: string;
|
|
126
|
+
/** Enable CSS modules mode with per-component CSS (default: false) */
|
|
127
|
+
cssModules?: boolean;
|
|
128
|
+
/** URL to CSS modules manifest (default: '/prerendered/manifest.json') */
|
|
129
|
+
manifestUrl?: string;
|
|
130
|
+
/** URL to base CSS file in CSS modules mode (default: '/prerendered/base.css') */
|
|
131
|
+
baseCssUrl?: string;
|
|
132
|
+
/** URL to legacy single CSS file (default: '/prerendered/lazy-components.css') */
|
|
133
|
+
legacyCssUrl?: string;
|
|
134
|
+
/** Preload CSS files before they're needed (default: false) */
|
|
135
|
+
preloadCSS?: boolean;
|
|
136
|
+
/** Cache loaded HTML in memory (default: true) */
|
|
137
|
+
cacheHTML?: boolean;
|
|
138
|
+
/** Enable retry logic for failed fetches (default: true) */
|
|
139
|
+
enableRetry?: boolean;
|
|
140
|
+
/** Maximum number of retry attempts (default: 3) */
|
|
141
|
+
maxRetries?: number;
|
|
142
|
+
/** Delay between retries in milliseconds (default: 1000) */
|
|
143
|
+
retryDelay?: number;
|
|
144
|
+
/** Enable debug logging (default: false) */
|
|
145
|
+
debug?: boolean;
|
|
146
|
+
/** Callback when a component is loaded */
|
|
147
|
+
onLoad?: (componentName: string, stats: {
|
|
148
|
+
duration: number;
|
|
149
|
+
bytes: number;
|
|
150
|
+
fromCache: boolean;
|
|
151
|
+
secondaryAssets?: Array<{
|
|
152
|
+
url: string;
|
|
153
|
+
bytes: number;
|
|
154
|
+
duration: number;
|
|
155
|
+
type: string;
|
|
156
|
+
}>;
|
|
157
|
+
}) => void;
|
|
158
|
+
/** Callback when an error occurs */
|
|
159
|
+
onError?: (componentName: string, error: Error) => void;
|
|
160
|
+
/** Callback when CSS is loaded */
|
|
161
|
+
onCSSLoad?: (cssFile: string, duration: number) => void;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* CSS module manifest structure
|
|
165
|
+
*/
|
|
166
|
+
interface CSSModuleManifest {
|
|
167
|
+
/** Map of component names to their CSS file paths */
|
|
168
|
+
components: Record<string, string>;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Load statistics
|
|
172
|
+
*/
|
|
173
|
+
interface LoadStats {
|
|
174
|
+
/** Total number of successful loads */
|
|
175
|
+
totalLoads: number;
|
|
176
|
+
/** Number of cache hits */
|
|
177
|
+
cacheHits: number;
|
|
178
|
+
/** Number of cache misses */
|
|
179
|
+
cacheMisses: number;
|
|
180
|
+
/** Number of errors encountered */
|
|
181
|
+
errors: number;
|
|
182
|
+
/** Average load time in milliseconds */
|
|
183
|
+
averageLoadTime: number;
|
|
184
|
+
/** Total bytes transferred across all loads */
|
|
185
|
+
totalBytes: number;
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Client-side utility for lazy loading prerendered HTML components
|
|
189
|
+
*
|
|
190
|
+
* @example
|
|
191
|
+
* ```ts
|
|
192
|
+
* import { createLazyLoader } from 'vite-plugin-astro-prerender';
|
|
193
|
+
*
|
|
194
|
+
* const loader = createLazyLoader({ debug: true });
|
|
195
|
+
*
|
|
196
|
+
* // Load and inject when in viewport
|
|
197
|
+
* loader.observeAndLoad('LazyFooter', '#footer-container');
|
|
198
|
+
*
|
|
199
|
+
* // Or load manually
|
|
200
|
+
* const html = await loader.load('LazyHeader');
|
|
201
|
+
* document.getElementById('header')!.innerHTML = html;
|
|
202
|
+
* ```
|
|
203
|
+
*/
|
|
204
|
+
declare class LazyHTMLLoader {
|
|
205
|
+
private config;
|
|
206
|
+
private htmlCache;
|
|
207
|
+
private cssCache;
|
|
208
|
+
private observers;
|
|
209
|
+
private manifest;
|
|
210
|
+
private manifestLoaded;
|
|
211
|
+
private baseCssLoaded;
|
|
212
|
+
private legacyCssLoaded;
|
|
213
|
+
private stats;
|
|
214
|
+
private detailedStats;
|
|
215
|
+
constructor(config?: LazyLoaderConfig);
|
|
216
|
+
/**
|
|
217
|
+
* Internal logging method
|
|
218
|
+
*/
|
|
219
|
+
private log;
|
|
220
|
+
/**
|
|
221
|
+
* Fetch with retry logic
|
|
222
|
+
*/
|
|
223
|
+
private fetchWithRetry;
|
|
224
|
+
/**
|
|
225
|
+
* Load CSS manifest for CSS modules mode
|
|
226
|
+
*/
|
|
227
|
+
private loadManifest;
|
|
228
|
+
/**
|
|
229
|
+
* Load base CSS file (CSS modules mode)
|
|
230
|
+
*/
|
|
231
|
+
private loadBaseCSS;
|
|
232
|
+
/**
|
|
233
|
+
* Load component-specific CSS file (CSS modules mode)
|
|
234
|
+
*/
|
|
235
|
+
private loadComponentCSS;
|
|
236
|
+
/**
|
|
237
|
+
* Load legacy single CSS file
|
|
238
|
+
*/
|
|
239
|
+
private loadLegacyCSS;
|
|
240
|
+
/**
|
|
241
|
+
* Load HTML fragment from server
|
|
242
|
+
*/
|
|
243
|
+
load(componentName: string): Promise<string>;
|
|
244
|
+
/**
|
|
245
|
+
* Inject HTML fragment into target element
|
|
246
|
+
*/
|
|
247
|
+
inject(componentName: string, targetSelector: string): Promise<void>;
|
|
248
|
+
/**
|
|
249
|
+
* Load HTML fragment when target element enters viewport
|
|
250
|
+
*/
|
|
251
|
+
observeAndLoad(componentName: string, targetSelector: string, options?: IntersectionObserverInit): void;
|
|
252
|
+
/**
|
|
253
|
+
* Preload HTML fragment without injecting
|
|
254
|
+
*/
|
|
255
|
+
preload(componentName: string): Promise<void>;
|
|
256
|
+
/**
|
|
257
|
+
* Batch preload multiple components
|
|
258
|
+
*/
|
|
259
|
+
preloadBatch(componentNames: string[]): Promise<void>;
|
|
260
|
+
/**
|
|
261
|
+
* Get load statistics
|
|
262
|
+
*/
|
|
263
|
+
getStats(): LoadStats;
|
|
264
|
+
/**
|
|
265
|
+
* Get detailed history of all loads in this session
|
|
266
|
+
*/
|
|
267
|
+
getDetailedHistory(): Array<{
|
|
268
|
+
componentName: string;
|
|
269
|
+
duration: number;
|
|
270
|
+
bytes: number;
|
|
271
|
+
fromCache: boolean;
|
|
272
|
+
timestamp: number;
|
|
273
|
+
}>;
|
|
274
|
+
/**
|
|
275
|
+
* Clear HTML cache
|
|
276
|
+
*/
|
|
277
|
+
clearCache(): void;
|
|
278
|
+
/**
|
|
279
|
+
* Clear CSS cache (forces reload of CSS files)
|
|
280
|
+
*/
|
|
281
|
+
clearCSSCache(): void;
|
|
282
|
+
/**
|
|
283
|
+
* Disconnect all observers
|
|
284
|
+
*/
|
|
285
|
+
disconnectAll(): void;
|
|
286
|
+
/**
|
|
287
|
+
* Reset all state (cache, observers, stats)
|
|
288
|
+
*/
|
|
289
|
+
reset(): void;
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Factory function to create a lazy loader instance
|
|
293
|
+
*/
|
|
294
|
+
declare function createLazyLoader(config?: LazyLoaderConfig): LazyHTMLLoader;
|
|
295
|
+
/**
|
|
296
|
+
* Default lazy loader instance with default configuration
|
|
297
|
+
*/
|
|
298
|
+
declare const lazyLoader: LazyHTMLLoader;
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Plugin configuration options
|
|
302
|
+
*/
|
|
303
|
+
interface PluginOptions {
|
|
304
|
+
/** Directory containing components to prerender (default: 'src/components/Lazy') */
|
|
305
|
+
componentsDir?: string;
|
|
306
|
+
/** Output directory for prerendered files (default: 'public/prerendered') */
|
|
307
|
+
outputDir?: string;
|
|
308
|
+
/** Generate Tailwind CSS file with tree-shaking (default: true) */
|
|
309
|
+
generateTailwindCSS?: boolean;
|
|
310
|
+
/** Path to Tailwind config file (default: 'tailwind.config.mjs') */
|
|
311
|
+
tailwindConfigPath?: string;
|
|
312
|
+
/** Rendering strategy: 'parser' (simple) or 'container' (full features) */
|
|
313
|
+
renderer?: 'parser' | 'container';
|
|
314
|
+
/** Minify HTML output to reduce file size (default: true) */
|
|
315
|
+
minify?: boolean;
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* Astro Prerender Vite Plugin
|
|
319
|
+
* Prerenders Astro components to static HTML and generates optimized CSS
|
|
320
|
+
*/
|
|
321
|
+
declare function astroPrerenderPlugin({ componentsDir, outputDir, generateTailwindCSS, tailwindConfigPath, renderer, minify, }?: PluginOptions): Plugin;
|
|
322
|
+
/**
|
|
323
|
+
* Astro Integration wrapper for the prerender plugin
|
|
324
|
+
* Use this in your astro.config.mjs integrations array
|
|
325
|
+
*
|
|
326
|
+
* @example
|
|
327
|
+
* ```js
|
|
328
|
+
* import { astroPrerenderIntegration } from 'astro-prerender-plugin';
|
|
329
|
+
*
|
|
330
|
+
* export default defineConfig({
|
|
331
|
+
* integrations: [
|
|
332
|
+
* astroPrerenderIntegration({
|
|
333
|
+
* componentsDir: 'src/components/Lazy',
|
|
334
|
+
* }),
|
|
335
|
+
* ],
|
|
336
|
+
* });
|
|
337
|
+
* ```
|
|
338
|
+
*/
|
|
339
|
+
declare function astroPrerenderIntegration(options?: PluginOptions): AstroIntegration;
|
|
340
|
+
|
|
341
|
+
export { CSSGenerator, type CSSModuleManifest, CacheManager, ContainerRenderer, LazyHTMLLoader, type LazyLoaderConfig, type LoadStats, type Logger, ParserRenderer, type PluginOptions, astroPrerenderIntegration, astroPrerenderPlugin, createLazyLoader, lazyLoader };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import{watch as re}from"chokidar";import{join as w,relative as T,basename as se}from"path";import{createHash as W}from"crypto";import{readFile as q}from"fs/promises";import _ from"fast-glob";import{outputFile as G,ensureDir as he}from"fs-extra";import*as P from"cheerio";import{consola as D}from"consola";function k(l){return D.withTag(l)}async function E(l){let e=await q(l,"utf-8");return W("md5").update(e).digest("hex")}async function U(l){try{return await _("**/*.astro",{cwd:l,absolute:!0,ignore:["**/node_modules/**"]})}catch(e){return[]}}function z(l){let e=P.load(l),t=new Set;return e("[class]").each((s,o)=>{var i;(((i=e(o).attr("class"))==null?void 0:i.split(/\s+/))||[]).forEach(r=>{r&&t.add(r)})}),Array.from(t)}function j(l){let e=P.load(l),t=[];return e("style").each((s,o)=>{let n=e(o).html();n!=null&&n.trim()&&t.push(n)}),t}function B(l){let e=P.load(l);e("script").remove(),e("style").remove(),e("*").each((s,o)=>{let n=e(o),i=o.attribs||{};Object.keys(i).forEach(r=>{(r.startsWith("data-astro-source-")||r.startsWith("data-astro-cid-"))&&n.removeAttr(r)})});let t=e("body").html();return(t==null?void 0:t.trim())||e.html().trim()}async function I(l){try{let{minify:e}=await import("html-minifier-terser");return await e(l,{collapseWhitespace:!0,removeComments:!0,removeRedundantAttributes:!0,removeEmptyAttributes:!0,minifyCSS:!0,minifyJS:!0,conservativeCollapse:!0,preserveLineBreaks:!1})}catch(e){return console.warn("html-minifier-terser not available, skipping minification"),l}}async function b(l,e){await G(l,e,"utf-8")}import{FlatCache as K}from"flat-cache";var x=class{constructor(e){this.loaded=!1;this.cache=new K({cacheDir:e,cacheId:"prerender-cache"})}async load(){this.loaded||(this.cache.load(),this.loaded=!0)}async save(){this.cache.save()}isCached(e,t){return this.cache.getKey(e)===t}set(e,t){this.cache.setKey(e,t)}delete(e){this.cache.delete(e)}clear(){this.cache.clear()}};import{pathToFileURL as N}from"url";var M=class{constructor(e,t,s=!0){this.tailwindConfigPath=e,this.logger=t,this.classes=new Set,this.componentStyles=[],this.generateTailwind=s}addClasses(e){e.forEach(t=>this.classes.add(t))}addStyles(e){this.componentStyles.push(...e)}clear(){this.classes.clear(),this.componentStyles=[]}async generate(e){let t=Array.from(this.classes);if(this.logger.info(`CSS Generator state: ${t.length} classes, ${this.componentStyles.length} component styles`),t.length===0&&this.componentStyles.length===0){this.logger.warn("No classes or styles to generate CSS for");return}this.logger.info(`Generating CSS for ${t.length} classes and ${this.componentStyles.length} component styles...`);try{if(!this.generateTailwind&&this.componentStyles.length>0){let c=`/* Component Styles */
|
|
2
|
+
`+this.componentStyles.join(`
|
|
3
|
+
|
|
4
|
+
`);await b(e,c),this.logger.success(`CSS generated (component styles only): ${e}`);return}if(!this.generateTailwind&&t.length>0){if(this.logger.warn(`Found ${t.length} Tailwind classes but generateTailwindCSS is disabled. Enable it to include Tailwind styles.`),this.componentStyles.length>0){let c=`/* Component Styles */
|
|
5
|
+
`+this.componentStyles.join(`
|
|
6
|
+
|
|
7
|
+
`);await b(e,c),this.logger.success(`CSS generated (component styles only): ${e}`)}return}let[s,o,n]=await Promise.all([import("postcss"),import("tailwindcss"),import("autoprefixer")]),i=s.default,r=o.default,a=n.default,u={...(await import(N(this.tailwindConfigPath).href)).default,content:[{raw:t.map(c=>`<div class="${c}"></div>`).join(`
|
|
8
|
+
`)}],safelist:t},g=`
|
|
9
|
+
@tailwind base;
|
|
10
|
+
@tailwind components;
|
|
11
|
+
@tailwind utilities;
|
|
12
|
+
`;this.componentStyles.length>0&&(g+=`
|
|
13
|
+
/* Component Styles */
|
|
14
|
+
`,g+=this.componentStyles.join(`
|
|
15
|
+
|
|
16
|
+
`));let p=await i([r(u),a]).process(g,{from:void 0});await b(e,p.css),this.logger.success(`CSS generated: ${e}`)}catch(s){throw this.logger.error(`Failed to generate CSS: ${s}`),s}}};import{readFile as J}from"fs/promises";import"url";import{experimental_AstroContainer as Q}from"astro/container";var L=class{constructor(e){this.logger=e,this.container=null,this.viteServer=null}setViteServer(e){this.viteServer=e}async init(){this.container||(this.container=await Q.create())}async render(e){await this.init();try{let s=(await J(e,"utf-8")).match(/^---\s*\n([\s\S]*?)\n---/),o={};if(s){let d=s[1].matchAll(/(?:const|let|var)\s+(\w+)\s*=\s*["']([^"']+)["']/g);for(let f of d)o[f[1]]=f[2]}let n;if(!this.viteServer)return this.logger.warn("Container renderer requires Vite dev server - use Parser renderer for builds"),null;try{if(typeof this.viteServer.ssrLoadModule!="function")return this.logger.warn("Vite server does not support SSR module loading"),null;n=await this.viteServer.ssrLoadModule(e)}catch(a){let d=a;return this.logger.warn(`Failed to load via Vite SSR: ${d.message}`),null}let r=await this.container.renderToString(n.default,{props:o});for(let[a,d]of Object.entries(o)){let f=new RegExp(`\\{${a}\\}`,"g");r=r.replace(f,d)}return r}catch(t){let s=t;throw this.logger.error(`Failed to render ${e}: ${s.message}`),t}}};import{parse as X}from"@astrojs/compiler";import{readFile as Y}from"fs/promises";import{consola as Z}from"consola";var $=class{constructor(){this.logger=Z.withTag("ParserRenderer")}extractFrontmatter(e){let t=e.frontmatter;if(!t||typeof t.value!="string")return{};let s={},n=t.value.matchAll(/(?:const|let|var)\s+(\w+)\s*=\s*["']([^"']+)["']/g);for(let i of n)i[1]&&i[2]&&(s[i[1]]=i[2]);return s}nodeToHTML(e,t=""){if(e.type==="text")return e.value;if(e.type==="element"){let s=e,o=s.attributes.map(r=>r.kind==="quoted"&&r.name&&r.value?`${r.name}="${r.value}"`:r.kind==="empty"&&r.name?r.name:"").filter(Boolean).join(" "),n=o?`<${s.name} ${o}>`:`<${s.name}>`;if(!s.children||s.children.length===0)return["img","br","hr","input","meta","link"].includes(s.name)?`${n.replace(">"," />")}`:`${n}</${s.name}>`;let i=s.children.map(r=>this.nodeToHTML(r,t+" ")).join(`
|
|
17
|
+
`);return`${n}${i}</${s.name}>`}else if(e.type==="component"){let s=e;return this.logger.warn(`Component <${s.name} /> found but won't be resolved (use container renderer for imports)`),`<!-- Component: ${s.name} -->`}else{if(e.type==="expression")return`{${String(e)}}`;if(e.type==="frontmatter")return"";if(e.type==="style"){let s=e,o=(s.attributes||[]).map(a=>a.kind==="quoted"&&a.name&&a.value?`${a.name}="${a.value}"`:a.kind==="empty"&&a.name?a.name:"").filter(Boolean).join(" "),n=o?`<style ${o}>`:"<style>",i=(s.children||[]).map(a=>a.type==="text"?a.value:"").join(""),r=e.content||"";return`${n}${i||r}</style>`}}return""}replaceFrontmatterVars(e,t){let s=e;for(let[o,n]of Object.entries(t)){let i=o.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),r=new RegExp(`\\{${i}\\}`,"g");s=s.replace(r,n)}return s}async render(e){this.logger.info(`Rendering component: ${e}`);try{let t=await Y(e,"utf-8"),o=(await X(t)).ast,n=this.extractFrontmatter(o);this.logger.debug(`Extracted ${Object.keys(n).length} frontmatter variables`);let i="";return o.children&&(i=o.children.map(r=>this.nodeToHTML(r)).join(`
|
|
18
|
+
`)),i=this.replaceFrontmatterVars(i,n),this.logger.success(`Rendered ${e} (${i.length} chars)`),i}catch(t){let s=t;throw this.logger.error(`Failed to render ${e}: ${s.message}`),t}}};var F=class{constructor(e={}){this.htmlCache=new Map;this.cssCache=new Set;this.observers=new Map;this.manifest=null;this.manifestLoaded=!1;this.baseCssLoaded=!1;this.legacyCssLoaded=!1;this.stats={totalLoads:0,cacheHits:0,cacheMisses:0,errors:0,totalLoadTime:0,totalBytes:0};this.detailedStats=new Map;var t,s,o,n,i,r,a,d,f,m,u,g,p,c;this.config={baseUrl:(t=e.baseUrl)!=null?t:"/prerendered",cssModules:(s=e.cssModules)!=null?s:!1,manifestUrl:(o=e.manifestUrl)!=null?o:"/prerendered/manifest.json",baseCssUrl:(n=e.baseCssUrl)!=null?n:"/prerendered/base.css",legacyCssUrl:(i=e.legacyCssUrl)!=null?i:"/prerendered/lazy-components.css",preloadCSS:(r=e.preloadCSS)!=null?r:!1,cacheHTML:(a=e.cacheHTML)!=null?a:!0,enableRetry:(d=e.enableRetry)!=null?d:!0,maxRetries:(f=e.maxRetries)!=null?f:3,retryDelay:(m=e.retryDelay)!=null?m:1e3,debug:(u=e.debug)!=null?u:!1,onLoad:(g=e.onLoad)!=null?g:(()=>{}),onError:(p=e.onError)!=null?p:(()=>{}),onCSSLoad:(c=e.onCSSLoad)!=null?c:(()=>{})},this.log("LazyHTMLLoader initialized",this.config)}log(e,...t){this.config.debug&&console.log(`[LazyHTMLLoader] ${e}`,...t)}async fetchWithRetry(e,t=this.config.maxRetries){for(let s=1;s<=t;s++){try{this.log(`Fetching ${e} (attempt ${s}/${t})`);let o=await fetch(e);if(o.ok)return o;if(s===t)throw new Error(`HTTP ${o.status}: ${o.statusText}`);if(o.status===404)throw new Error(`Not found: ${e}`);this.log(`Fetch failed with status ${o.status}, retrying...`)}catch(o){if(s===t)throw o;this.log(`Fetch error: ${o}, retrying...`)}s<t&&await new Promise(o=>setTimeout(o,this.config.retryDelay*s))}throw new Error(`Failed to fetch ${e} after ${t} attempts`)}async loadManifest(){if(this.manifestLoaded)return;let e=performance.now();try{let t=await(this.config.enableRetry?this.fetchWithRetry(this.config.manifestUrl):fetch(this.config.manifestUrl));if(!t.ok)throw new Error(`Failed to load manifest: ${t.statusText}`);this.manifest=await t.json(),this.manifestLoaded=!0;let s=performance.now()-e;this.log(`Manifest loaded in ${s.toFixed(2)}ms`,this.manifest),this.config.onCSSLoad(this.config.manifestUrl,s)}catch(t){console.error("Failed to load CSS manifest:",t),this.config.cssModules=!1,this.manifestLoaded=!0}}async loadBaseCSS(){if(this.baseCssLoaded||(await this.loadManifest(),!this.manifest))return;let e=performance.now();return new Promise((t,s)=>{let o=document.createElement("link");if(o.rel="stylesheet",o.href=this.config.baseCssUrl,o.onload=()=>{this.baseCssLoaded=!0;let n=performance.now()-e;this.log(`Base CSS loaded in ${n.toFixed(2)}ms`),this.config.onCSSLoad(this.config.baseCssUrl,n),t()},o.onerror=()=>{console.error("Failed to load base CSS"),s(new Error("Failed to load base CSS"))},this.config.preloadCSS){let n=document.createElement("link");n.rel="preload",n.as="style",n.href=this.config.baseCssUrl,document.head.appendChild(n)}document.head.appendChild(o)})}async loadComponentCSS(e){if(!this.config.cssModules||!this.manifest)return;let t=this.manifest.components[e];if(!t){this.log(`No CSS file for component: ${e}`);return}if(this.cssCache.has(t)){this.log(`CSS already loaded: ${t}`);return}let s=performance.now(),o=`${this.config.baseUrl}/${t}`;return new Promise((n,i)=>{let r=document.createElement("link");if(r.rel="stylesheet",r.href=o,r.onload=()=>{this.cssCache.add(t);let a=performance.now()-s;this.log(`Component CSS loaded in ${a.toFixed(2)}ms: ${t}`),this.config.onCSSLoad(t,a),n()},r.onerror=()=>{console.error(`Failed to load component CSS: ${t}`),i(new Error(`Failed to load CSS: ${t}`))},this.config.preloadCSS){let a=document.createElement("link");a.rel="preload",a.as="style",a.href=o,document.head.appendChild(a)}document.head.appendChild(r)})}async loadLegacyCSS(){if(this.legacyCssLoaded)return;let e=performance.now();return new Promise((t,s)=>{let o=document.createElement("link");if(o.rel="stylesheet",o.href=this.config.legacyCssUrl,o.onload=()=>{this.legacyCssLoaded=!0;let n=performance.now()-e;this.log(`Legacy CSS loaded in ${n.toFixed(2)}ms`),this.config.onCSSLoad(this.config.legacyCssUrl,n),t()},o.onerror=()=>{console.error("Failed to load legacy CSS"),s(new Error("Failed to load lazy components CSS"))},this.config.preloadCSS){let n=document.createElement("link");n.rel="preload",n.as="style",n.href=this.config.legacyCssUrl,document.head.appendChild(n)}document.head.appendChild(o)})}async load(e){let t=performance.now();try{if(this.config.cacheHTML&&this.htmlCache.has(e)){this.stats.cacheHits++,this.log(`Cache hit: ${e}`);let m=this.htmlCache.get(e),u=new Blob([m]).size,g=performance.now()-t;this.stats.totalLoads++,this.stats.totalLoadTime+=g;let p={duration:g,bytes:u,fromCache:!0,timestamp:Date.now()};return this.detailedStats.set(e,p),this.config.onLoad(e,{duration:g,bytes:u,fromCache:!0}),m}this.stats.cacheMisses++,this.config.cssModules?(await this.loadBaseCSS().catch(m=>{console.warn("Failed to load base CSS:",m)}),await this.loadComponentCSS(e).catch(m=>{console.warn(`Failed to load CSS for ${e}:`,m)})):await this.loadLegacyCSS().catch(m=>{console.warn("Failed to load legacy CSS:",m)});let s=`${this.config.baseUrl}/${e}.html`,o=await(this.config.enableRetry?this.fetchWithRetry(s):fetch(s));if(!o.ok)throw new Error(`HTTP ${o.status}: ${o.statusText}`);let n=await o.text(),i=new Blob([n]).size;this.config.cacheHTML&&this.htmlCache.set(e,n);let r=performance.now()-t;this.stats.totalLoads++,this.stats.totalLoadTime+=r,this.stats.totalBytes+=i,this.log(`Loaded ${e} in ${r.toFixed(2)}ms (${i} bytes)`);let a=[],d=new PerformanceObserver(m=>{m.getEntries().forEach(u=>{let g=u;if(g.startTime>=t-50){let p=g.transferSize||g.decodedBodySize||g.encodedBodySize||0;a.push({url:g.name,bytes:p,duration:g.duration,type:g.initiatorType}),this.stats.totalBytes+=p}})});try{d.observe({entryTypes:["resource"],buffered:!0}),setTimeout(()=>d.disconnect(),6e3)}catch(m){this.log("PerformanceObserver failed");try{d.observe({entryTypes:["resource"]})}catch(u){}}let f={duration:r,bytes:i,fromCache:!1,timestamp:Date.now(),secondaryAssets:a};return this.detailedStats.set(e,f),this.config.onLoad(e,{duration:r,bytes:i,fromCache:!1,secondaryAssets:a}),n}catch(s){this.stats.errors++;let o=s instanceof Error?s:new Error(String(s));throw this.log(`Error loading ${e}:`,o),this.config.onError(e,o),o}}async inject(e,t){let s=await this.load(e),o=document.querySelector(t);if(o)o.innerHTML=s,this.log(`Injected ${e} into ${t}`);else{let n=new Error(`Target element not found: ${t}`);throw this.config.onError(e,n),n}}observeAndLoad(e,t,s){let o=document.querySelector(t);if(!o){console.warn(`Target element not found: ${t}`);return}let n=new IntersectionObserver(i=>{i.forEach(r=>{r.isIntersecting&&(this.log(`Component ${e} entered viewport`),this.inject(e,t).catch(a=>{console.error(`Failed to inject ${e}:`,a)}),n.disconnect(),this.observers.delete(e))})},s||{rootMargin:"100px"});n.observe(o),this.observers.set(e,n),this.log(`Observing ${e} at ${t}`)}async preload(e){await this.load(e)}async preloadBatch(e){this.log(`Preloading ${e.length} components:`,e),await Promise.all(e.map(t=>this.preload(t)))}getStats(){return{totalLoads:this.stats.totalLoads,cacheHits:this.stats.cacheHits,cacheMisses:this.stats.cacheMisses,errors:this.stats.errors,averageLoadTime:this.stats.totalLoads>0?this.stats.totalLoadTime/this.stats.totalLoads:0,totalBytes:this.stats.totalBytes}}getDetailedHistory(){return Array.from(this.detailedStats.entries()).map(([e,t])=>({componentName:e,...t})).sort((e,t)=>t.timestamp-e.timestamp)}clearCache(){this.htmlCache.clear(),this.log("HTML cache cleared")}clearCSSCache(){this.cssCache.clear(),this.baseCssLoaded=!1,this.legacyCssLoaded=!1,this.manifestLoaded=!1,this.manifest=null,this.log("CSS cache cleared")}disconnectAll(){this.observers.forEach(e=>e.disconnect()),this.observers.clear(),this.log("All observers disconnected")}reset(){this.clearCache(),this.clearCSSCache(),this.disconnectAll(),this.stats={totalLoads:0,cacheHits:0,cacheMisses:0,errors:0,totalLoadTime:0,totalBytes:0},this.detailedStats.clear(),this.log("LazyHTMLLoader reset")}};function ee(l){return new F(l)}var te=new F;function oe({componentsDir:l="src/components/Lazy",outputDir:e="public/prerendered",generateTailwindCSS:t=!0,tailwindConfigPath:s="tailwind.config.mjs",renderer:o="parser",minify:n=!0}={}){let i=k("astro-prerender"),r=null,a=null,d=null,f=null,m=null;async function u(){if(!(r!=null&&r.root))return;let c=r.root,y=w(c,l),S=w(c,e);i.info(`Processing components from ${l}...`),a&&await a.load(),d&&d.clear();let h=await U(y);if(h.length===0){i.warn(`No .astro files found in ${l}`);return}for(let R of h)await g(R);await p(),a&&await a.save(),i.success(`Processed ${h.length} component(s)`)}async function g(c){if(!(r!=null&&r.root))return;let y=r.root,S=w(y,e);try{if(a){let h=await E(c);if(a.isCached(c,h)){i.info(`Skipping cached: ${T(y,c)}`);return}}if(f){let h=await f.render(c);if(!h&&m&&(i.info(`Falling back to parser for: ${T(y,c)}`),h=await m.render(c)),h){let R=j(h);d&&R.length>0&&d.addStyles(R);let C=B(h);if(d){let v=z(C);d.addClasses(v)}if(n){let v=C.length;C=await I(C);let H=C.length,V=Math.round((1-H/v)*100);i.info(`Minified: ${v} \u2192 ${H} bytes (${V}% smaller)`)}let A=se(c,".astro"),O=w(S,`${A}.html`);if(await b(O,C),a){let v=await E(c);a.set(c,v)}i.success(`${A}.html (${C.length} chars)`)}}}catch(h){i.error(`Failed to process ${T(y,c)}: ${h instanceof Error?h.message:"Unknown error"}`)}}async function p(){if(!d||!(r!=null&&r.root))return;let c=r.root,y=w(c,e),S=w(y,"lazy-components.css");await d.generate(S)}return{name:"astro-prerender-plugin",configResolved(c){r=c;let y=r.root;a=new x(w(y,e)),d=new M(w(y,s),i,t),o==="container"?(f=new L(i),m=new $,i.info("Using Container API renderer (supports imports)")):(f=new $,i.info("Using Parser renderer (simpler, no imports)"))},async buildStart(){(r==null?void 0:r.command)==="build"&&await u()},async configureServer(c){f instanceof L&&f.setViteServer(c),await u();let y=w((r==null?void 0:r.root)||"",l),S=re(y,{ignored:/node_modules/,persistent:!0});S.on("change",async h=>{r!=null&&r.root&&(i.info(`Component changed: ${T(r.root,h)}`),await g(h),await p())}),S.on("add",async h=>{r!=null&&r.root&&(i.info(`Component added: ${T(r.root,h)}`),await g(h),await p())}),S.on("unlink",h=>{r!=null&&r.root&&(i.info(`Component removed: ${T(r.root,h)}`),a==null||a.delete(h))})}}}function ze(l={}){return{name:"astro-prerender-integration",hooks:{"astro:config:setup":({updateConfig:e})=>{e({vite:{plugins:[oe(l)]}})}}}}export{M as CSSGenerator,x as CacheManager,L as ContainerRenderer,F as LazyHTMLLoader,$ as ParserRenderer,ze as astroPrerenderIntegration,oe as astroPrerenderPlugin,ee as createLazyLoader,te as lazyLoader};
|
|
19
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/utils/index.ts","../src/utils/logger.ts","../src/utils/cache.ts","../src/utils/css-generator.ts","../src/utils/container-renderer.ts","../src/utils/parser-renderer.ts","../src/utils/LazyLoader.ts"],"sourcesContent":["// src/index.ts\nimport { watch } from 'chokidar';\nimport { join, relative, basename } from 'node:path';\nimport {\n createLogger,\n findComponents,\n extractClasses,\n extractStyles,\n cleanHTML,\n minifyHTML,\n getFileHash,\n writeFileWithDir,\n} from './utils';\nimport { CacheManager } from './utils/cache';\nimport { CSSGenerator } from './utils/css-generator';\nimport { ContainerRenderer } from './utils/container-renderer';\nimport { ParserRenderer } from './utils/parser-renderer';\nimport type { AstroIntegration } from 'astro';\nimport type { Plugin as VitePlugin, ResolvedConfig, ViteDevServer } from 'vite';\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Plugin configuration options\n */\nexport interface PluginOptions {\n /** Directory containing components to prerender (default: 'src/components/Lazy') */\n componentsDir?: string;\n /** Output directory for prerendered files (default: 'public/prerendered') */\n outputDir?: string;\n /** Generate Tailwind CSS file with tree-shaking (default: true) */\n generateTailwindCSS?: boolean;\n /** Path to Tailwind config file (default: 'tailwind.config.mjs') */\n tailwindConfigPath?: string;\n /** Rendering strategy: 'parser' (simple) or 'container' (full features) */\n renderer?: 'parser' | 'container';\n /** Minify HTML output to reduce file size (default: true) */\n minify?: boolean;\n}\n\n// ============================================================================\n// Vite Plugin\n// ============================================================================\n\n/**\n * Astro Prerender Vite Plugin\n * Prerenders Astro components to static HTML and generates optimized CSS\n */\nexport function astroPrerenderPlugin({\n componentsDir = 'src/components/Lazy',\n outputDir = 'public/prerendered',\n generateTailwindCSS = true,\n tailwindConfigPath = 'tailwind.config.mjs',\n renderer = 'parser',\n minify = true,\n}: PluginOptions = {}): VitePlugin {\n const logger = createLogger('astro-prerender');\n let config: ResolvedConfig | null = null;\n let cacheManager: CacheManager | null = null;\n let cssGenerator: CSSGenerator | null = null;\n let componentRenderer: ContainerRenderer | ParserRenderer | null = null;\n let parserFallback: ParserRenderer | null = null;\n\n /**\n * Process all components\n */\n async function processAll(): Promise<void> {\n if (!config?.root) return;\n\n const root = config.root;\n const componentsPath = join(root, componentsDir);\n const outputPath = join(root, outputDir);\n\n logger.info(`Processing components from ${componentsDir}...`);\n\n // Load cache\n if (cacheManager) {\n await cacheManager.load();\n }\n\n // Clear CSS generator\n if (cssGenerator) {\n cssGenerator.clear();\n }\n\n // Find all components\n const componentFiles = await findComponents(componentsPath);\n\n if (componentFiles.length === 0) {\n logger.warn(`No .astro files found in ${componentsDir}`);\n return;\n }\n\n // Process each component\n for (const componentPath of componentFiles) {\n await processComponent(componentPath);\n }\n\n // Generate CSS\n await generateCSS();\n\n // Save cache\n if (cacheManager) {\n await cacheManager.save();\n }\n\n logger.success(`Processed ${componentFiles.length} component(s)`);\n }\n\n /**\n * Process a single component\n */\n async function processComponent(componentPath: string): Promise<void> {\n if (!config?.root) return;\n\n const root = config.root;\n const outputPath = join(root, outputDir);\n\n try {\n // Check cache\n if (cacheManager) {\n const hash = await getFileHash(componentPath);\n if (cacheManager.isCached(componentPath, hash)) {\n logger.info(`Skipping cached: ${relative(root, componentPath)}`);\n return;\n }\n }\n\n // Render component\n if (componentRenderer) {\n let html = await componentRenderer.render(componentPath);\n\n // If container renderer returned null, fall back to parser\n if (!html && parserFallback) {\n logger.info(`Falling back to parser for: ${relative(root, componentPath)}`);\n html = await parserFallback.render(componentPath);\n }\n\n if (html) {\n // Extract styles before cleaning HTML\n const styles = extractStyles(html);\n\n if (cssGenerator && styles.length > 0) {\n cssGenerator.addStyles(styles);\n }\n\n // Clean HTML\n let cleanedHTML = cleanHTML(html);\n\n // Extract Tailwind classes\n if (cssGenerator) {\n const classes = extractClasses(cleanedHTML);\n cssGenerator.addClasses(classes);\n }\n\n // Minify HTML if enabled\n if (minify) {\n const originalSize = cleanedHTML.length;\n cleanedHTML = await minifyHTML(cleanedHTML);\n const minifiedSize = cleanedHTML.length;\n const savings = Math.round((1 - minifiedSize / originalSize) * 100);\n logger.info(`Minified: ${originalSize} โ ${minifiedSize} bytes (${savings}% smaller)`);\n }\n\n // Write HTML file\n const componentName = basename(componentPath, '.astro');\n const htmlOutputPath = join(outputPath, `${componentName}.html`);\n\n await writeFileWithDir(htmlOutputPath, cleanedHTML);\n\n // Update cache\n if (cacheManager) {\n const hash = await getFileHash(componentPath);\n cacheManager.set(componentPath, hash);\n }\n\n logger.success(`${componentName}.html (${cleanedHTML.length} chars)`);\n }\n }\n } catch (error) {\n logger.error(\n `Failed to process ${relative(root, componentPath)}: ${error instanceof Error ? error.message : 'Unknown error'\n }`\n );\n }\n }\n\n /**\n * Generate CSS file\n */\n async function generateCSS(): Promise<void> {\n if (!cssGenerator || !config?.root) return;\n\n const root = config.root;\n const outputPath = join(root, outputDir);\n const cssOutputPath = join(outputPath, 'lazy-components.css');\n\n await cssGenerator.generate(cssOutputPath);\n }\n\n return {\n name: 'astro-prerender-plugin',\n\n configResolved(resolvedConfig: ResolvedConfig) {\n config = resolvedConfig;\n const root = config.root;\n\n // Initialize cache manager\n cacheManager = new CacheManager(join(root, outputDir));\n\n // Always initialize CSS generator (it will handle component styles even without Tailwind)\n cssGenerator = new CSSGenerator(\n join(root, tailwindConfigPath),\n logger,\n generateTailwindCSS\n );\n\n // Initialize renderer based on config\n if (renderer === 'container') {\n componentRenderer = new ContainerRenderer(logger);\n parserFallback = new ParserRenderer(); // Fallback for when container fails\n logger.info('Using Container API renderer (supports imports)');\n } else {\n componentRenderer = new ParserRenderer();\n logger.info('Using Parser renderer (simpler, no imports)');\n }\n },\n\n async buildStart() {\n if (config?.command === 'build') {\n await processAll();\n }\n },\n\n async configureServer(server: ViteDevServer) {\n // Set Vite server for Container renderer\n if (componentRenderer instanceof ContainerRenderer) {\n componentRenderer.setViteServer(server);\n }\n\n // Initial processing\n await processAll();\n\n // Watch for changes in components directory\n const componentsPath = join(config?.root || '', componentsDir);\n const watcher = watch(componentsPath, {\n ignored: /node_modules/,\n persistent: true,\n });\n\n watcher.on('change', async (filePath: string) => {\n if (!config?.root) return;\n\n logger.info(`Component changed: ${relative(config.root, filePath)}`);\n await processComponent(filePath);\n await generateCSS();\n });\n\n watcher.on('add', async (filePath: string) => {\n if (!config?.root) return;\n\n logger.info(`Component added: ${relative(config.root, filePath)}`);\n await processComponent(filePath);\n await generateCSS();\n });\n\n watcher.on('unlink', (filePath: string) => {\n if (!config?.root) return;\n\n logger.info(`Component removed: ${relative(config.root, filePath)}`);\n cacheManager?.delete(filePath);\n });\n },\n };\n}\n\n// ============================================================================\n// Astro Integration\n// ============================================================================\n\n/**\n * Astro Integration wrapper for the prerender plugin\n * Use this in your astro.config.mjs integrations array\n * \n * @example\n * ```js\n * import { astroPrerenderIntegration } from 'astro-prerender-plugin';\n * \n * export default defineConfig({\n * integrations: [\n * astroPrerenderIntegration({\n * componentsDir: 'src/components/Lazy',\n * }),\n * ],\n * });\n * ```\n */\nexport function astroPrerenderIntegration(options: PluginOptions = {}): AstroIntegration {\n return {\n name: 'astro-prerender-integration',\n hooks: {\n 'astro:config:setup': ({ updateConfig }) => {\n updateConfig({\n vite: {\n plugins: [astroPrerenderPlugin(options)],\n },\n });\n },\n },\n };\n}\n\n// ============================================================================\n// Re-exports\n// ============================================================================\n\n// Core utilities\nexport { CacheManager } from './utils/cache';\nexport { CSSGenerator } from './utils/css-generator';\nexport { ContainerRenderer } from './utils/container-renderer';\nexport { ParserRenderer } from './utils/parser-renderer';\nexport type { Logger } from './utils/logger';\n\n// Lazy loader (client-side utility)\nexport {\n LazyHTMLLoader,\n createLazyLoader,\n lazyLoader,\n type LazyLoaderConfig,\n type CSSModuleManifest,\n type LoadStats,\n} from './utils/LazyLoader';","// src/utils/index.ts\nimport { createHash } from 'node:crypto';\nimport { readFile } from 'node:fs/promises';\nimport fg from 'fast-glob';\nimport { outputFile, ensureDir as fsEnsureDir } from 'fs-extra';\nimport * as cheerio from 'cheerio';\n\n// Re-export logger\nexport { createLogger, type Logger } from './logger';\n\n/**\n * Get MD5 hash of file content\n */\nexport async function getFileHash(filePath: string): Promise<string> {\n const content = await readFile(filePath, 'utf-8');\n return createHash('md5').update(content).digest('hex');\n}\n\n/**\n * Find all .astro files recursively in a directory using fast-glob\n */\nexport async function findComponents(dir: string): Promise<string[]> {\n try {\n return await fg('**/*.astro', {\n cwd: dir,\n absolute: true,\n ignore: ['**/node_modules/**'],\n });\n } catch {\n // Directory doesn't exist or is not accessible\n return [];\n }\n}\n\n/**\n * Extract Tailwind classes from HTML using Cheerio\n */\nexport function extractClasses(html: string): string[] {\n const $ = cheerio.load(html);\n const classes = new Set<string>();\n\n $('[class]').each((_, el) => {\n const classList = $(el).attr('class')?.split(/\\s+/) || [];\n classList.forEach((cls) => {\n if (cls) classes.add(cls);\n });\n });\n\n return Array.from(classes);\n}\n\n/**\n * Extract style tags from HTML using Cheerio\n */\nexport function extractStyles(html: string): string[] {\n const $ = cheerio.load(html);\n const styles: string[] = [];\n\n $('style').each((_, el) => {\n const content = $(el).html();\n if (content?.trim()) {\n styles.push(content);\n }\n });\n\n return styles;\n}\n\n/**\n * Clean HTML output by removing unnecessary elements using Cheerio\n */\nexport function cleanHTML(html: string): string {\n const $ = cheerio.load(html);\n\n // Remove script tags\n $('script').remove();\n\n // Remove style tags (they're externalized)\n $('style').remove();\n\n // Remove data-astro-* attributes\n $('*').each((_, el) => {\n const $el = $(el);\n const attrs = (el as unknown as { attribs?: Record<string, string> }).attribs || {};\n\n Object.keys(attrs).forEach((attr) => {\n if (attr.startsWith('data-astro-source-') || attr.startsWith('data-astro-cid-')) {\n $el.removeAttr(attr);\n }\n });\n });\n\n // Get the body content (Cheerio wraps in html/head/body)\n const body = $('body').html();\n return body?.trim() || $.html().trim();\n}\n\n/**\n * Minify HTML using html-minifier-terser\n */\nexport async function minifyHTML(html: string): Promise<string> {\n try {\n const { minify } = await import('html-minifier-terser');\n\n return await minify(html, {\n collapseWhitespace: true,\n removeComments: true,\n removeRedundantAttributes: true,\n removeEmptyAttributes: true,\n minifyCSS: true,\n minifyJS: true,\n conservativeCollapse: true,\n preserveLineBreaks: false,\n });\n } catch (error) {\n console.warn('html-minifier-terser not available, skipping minification');\n return html;\n }\n}\n\n/**\n * Ensure directory exists (using fs-extra)\n */\nexport { fsEnsureDir as ensureDir };\n\n/**\n * Write file with directory creation (using fs-extra)\n */\nexport async function writeFileWithDir(filePath: string, content: string): Promise<void> {\n await outputFile(filePath, content, 'utf-8');\n}","// src/utils/logger.ts\nimport { consola, type ConsolaInstance } from 'consola';\n\n/**\n * Create a logger instance with a specific tag\n * Uses Consola for beautiful, structured logging\n */\nexport function createLogger(tag: string): ConsolaInstance {\n return consola.withTag(tag);\n}\n\nexport type Logger = ConsolaInstance;\n","// src/utils/cache.ts\nimport { FlatCache } from 'flat-cache';\n\n/**\n * Manages cache for rendered components using flat-cache\n * Uses file hashes to skip unchanged components\n */\nexport class CacheManager {\n private cache: FlatCache;\n private loaded: boolean = false;\n\n constructor(cacheDir: string) {\n this.cache = new FlatCache({\n cacheDir,\n cacheId: 'prerender-cache',\n });\n }\n\n /**\n * Load cache from disk\n */\n async load(): Promise<void> {\n if (!this.loaded) {\n this.cache.load();\n this.loaded = true;\n }\n }\n\n /**\n * Save cache to disk\n */\n async save(): Promise<void> {\n this.cache.save();\n }\n\n /**\n * Check if component is cached with same hash\n */\n isCached(componentPath: string, hash: string): boolean {\n return this.cache.getKey(componentPath) === hash;\n }\n\n /**\n * Set component hash in cache\n */\n set(componentPath: string, hash: string): void {\n this.cache.setKey(componentPath, hash);\n }\n\n /**\n * Delete component from cache\n */\n delete(componentPath: string): void {\n this.cache.delete(componentPath);\n }\n\n /**\n * Clear all cache\n */\n clear(): void {\n this.cache.clear();\n }\n}","// src/utils/css-generator.ts\nimport { pathToFileURL } from 'node:url';\nimport { writeFileWithDir } from './index';\nimport type { Logger } from './logger';\nimport type { AcceptedPlugin } from 'postcss';\n\n/**\n * Generates CSS from Tailwind classes and component styles\n * Tree-shakes Tailwind to only include used classes\n */\nexport class CSSGenerator {\n private tailwindConfigPath: string;\n private logger: Logger;\n private classes: Set<string>;\n private componentStyles: string[];\n private generateTailwind: boolean;\n\n constructor(tailwindConfigPath: string, logger: Logger, generateTailwind: boolean = true) {\n this.tailwindConfigPath = tailwindConfigPath;\n this.logger = logger;\n this.classes = new Set();\n this.componentStyles = [];\n this.generateTailwind = generateTailwind;\n }\n\n /**\n * Add Tailwind classes to generate CSS for\n */\n addClasses(classes: string[]): void {\n classes.forEach((cls) => this.classes.add(cls));\n }\n\n /**\n * Add component styles to include in CSS\n */\n addStyles(styles: string[]): void {\n this.componentStyles.push(...styles);\n }\n\n /**\n * Clear all classes and styles\n */\n clear(): void {\n this.classes.clear();\n this.componentStyles = [];\n }\n\n /**\n * Generate CSS file with Tailwind and component styles\n */\n async generate(outputPath: string): Promise<void> {\n const classArray = Array.from(this.classes);\n\n this.logger.info(\n `CSS Generator state: ${classArray.length} classes, ${this.componentStyles.length} component styles`,\n );\n\n if (classArray.length === 0 && this.componentStyles.length === 0) {\n this.logger.warn('No classes or styles to generate CSS for');\n return;\n }\n\n this.logger.info(\n `Generating CSS for ${classArray.length} classes and ${this.componentStyles.length} component styles...`,\n );\n\n try {\n // If Tailwind is disabled and we only have component styles, just write them directly\n if (!this.generateTailwind && this.componentStyles.length > 0) {\n const cssContent = '/* Component Styles */\\n' + this.componentStyles.join('\\n\\n');\n await writeFileWithDir(outputPath, cssContent);\n this.logger.success(`CSS generated (component styles only): ${outputPath}`);\n return;\n }\n\n // If Tailwind is disabled but we have classes, warn the user\n if (!this.generateTailwind && classArray.length > 0) {\n this.logger.warn(\n `Found ${classArray.length} Tailwind classes but generateTailwindCSS is disabled. Enable it to include Tailwind styles.`,\n );\n // Still output component styles if we have them\n if (this.componentStyles.length > 0) {\n const cssContent = '/* Component Styles */\\n' + this.componentStyles.join('\\n\\n');\n await writeFileWithDir(outputPath, cssContent);\n this.logger.success(`CSS generated (component styles only): ${outputPath}`);\n }\n return;\n }\n\n // Full Tailwind + component styles processing\n // Dynamically import dependencies (they're optional peer deps)\n const [postcssModule, tailwindModule, autoprefixerModule] = await Promise.all([\n import('postcss'),\n import('tailwindcss'),\n import('autoprefixer'),\n ]);\n\n const postcss = postcssModule.default;\n const tailwindcss = tailwindModule.default;\n const autoprefixer = autoprefixerModule.default;\n\n // Load the Tailwind config\n const configUrl = pathToFileURL(this.tailwindConfigPath).href;\n const configModule = await import(configUrl);\n const tailwindConfig = configModule.default;\n\n // Override the content to only process the extracted classes\n const modifiedConfig = {\n ...tailwindConfig,\n content: [\n {\n raw: classArray\n .map((cls) => `<div class=\"${cls}\"></div>`)\n .join('\\n'),\n },\n ],\n safelist: classArray,\n };\n\n // Create CSS content with Tailwind directives\n let cssContent = `\n@tailwind base;\n@tailwind components;\n@tailwind utilities;\n`;\n\n // Add component styles if any\n if (this.componentStyles.length > 0) {\n cssContent += '\\n/* Component Styles */\\n';\n cssContent += this.componentStyles.join('\\n\\n');\n }\n\n // Process with PostCSS\n const result = await postcss([\n tailwindcss(modifiedConfig) as AcceptedPlugin,\n autoprefixer as AcceptedPlugin,\n ]).process(cssContent, {\n from: undefined,\n });\n\n // Write the generated CSS\n await writeFileWithDir(outputPath, result.css);\n\n this.logger.success(`CSS generated: ${outputPath}`);\n } catch (error) {\n this.logger.error(`Failed to generate CSS: ${error}`);\n throw error;\n }\n }\n}","// src/utils/container-renderer.ts\nimport { readFile } from 'node:fs/promises';\nimport { pathToFileURL } from 'node:url';\nimport { experimental_AstroContainer as AstroContainer } from 'astro/container';\nimport type { Logger } from './logger';\n\ninterface ViteDevServer {\n ssrLoadModule(url: string): Promise<Record<string, unknown>>;\n}\n\n/**\n * Renders components using Astro Container API\n * Supports component imports and full Astro features\n */\nexport class ContainerRenderer {\n private logger: Logger;\n private container: Awaited<ReturnType<typeof AstroContainer.create>> | null;\n private viteServer: ViteDevServer | null;\n\n constructor(logger: Logger) {\n this.logger = logger;\n this.container = null;\n this.viteServer = null;\n }\n\n /**\n * Set Vite server instance (required for dev mode)\n */\n setViteServer(server: ViteDevServer): void {\n this.viteServer = server;\n }\n\n /**\n * Initialize the container\n */\n private async init(): Promise<void> {\n if (!this.container) {\n this.container = await AstroContainer.create();\n }\n }\n\n /**\n * Render a component to HTML\n */\n async render(componentPath: string): Promise<string> {\n await this.init();\n\n try {\n const fileContent = await readFile(componentPath, 'utf-8');\n\n // Extract frontmatter to get variable values\n const frontmatterMatch = fileContent.match(/^---\\s*\\n([\\s\\S]*?)\\n---/);\n const frontmatterVars: Record<string, string> = {};\n\n if (frontmatterMatch) {\n const frontmatterCode = frontmatterMatch[1];\n // Simple extraction of const/let/var declarations\n const varMatches = frontmatterCode.matchAll(\n /(?:const|let|var)\\s+(\\w+)\\s*=\\s*[\"']([^\"']+)[\"']/g,\n );\n for (const match of varMatches) {\n frontmatterVars[match[1]] = match[2];\n }\n }\n\n let ComponentModule: Record<string, unknown>;\n\n // Container renderer requires Vite SSR to work with .astro files\n if (!this.viteServer) {\n this.logger.warn('Container renderer requires Vite dev server - use Parser renderer for builds');\n return null as unknown as string;\n }\n\n try {\n // Ensure the server has the ssrLoadModule method\n if (typeof this.viteServer.ssrLoadModule !== 'function') {\n this.logger.warn('Vite server does not support SSR module loading');\n return null as unknown as string;\n }\n\n ComponentModule = await this.viteServer.ssrLoadModule(componentPath);\n } catch (error: unknown) {\n const err = error as Error;\n this.logger.warn(`Failed to load via Vite SSR: ${err.message}`);\n // Return null to signal fallback to parser renderer\n return null as unknown as string;\n }\n\n // Render the component with props from frontmatter\n const result = await this.container!.renderToString(\n ComponentModule.default as Awaited<ReturnType<typeof AstroContainer.create>> extends { renderToString: (c: infer T, ...args: unknown[]) => unknown } ? T : never,\n {\n props: frontmatterVars,\n },\n );\n\n // Replace {varName} expressions with actual values\n let html = result;\n for (const [key, value] of Object.entries(frontmatterVars)) {\n const regex = new RegExp(`\\\\{${key}\\\\}`, 'g');\n html = html.replace(regex, value);\n }\n\n return html;\n } catch (error: unknown) {\n const err = error as Error;\n this.logger.error(`Failed to render ${componentPath}: ${err.message}`);\n throw error;\n }\n }\n}","// src/utils/parser-renderer.ts\nimport { parse } from '@astrojs/compiler';\nimport { readFile } from 'node:fs/promises';\nimport { consola } from 'consola';\nimport type { RootNode, Node, ElementNode, AttributeNode } from '@astrojs/compiler/types';\n\n/**\n * Renders components using Astro's AST parser\n * Simpler approach but doesn't resolve component imports\n */\nexport class ParserRenderer {\n private logger = consola.withTag('ParserRenderer');\n\n /**\n * Extract frontmatter variables from AST\n */\n private extractFrontmatter(ast: RootNode): Record<string, string> {\n const frontmatter = (ast as unknown as { frontmatter?: { value?: string } }).frontmatter;\n if (!frontmatter || typeof frontmatter.value !== 'string') return {};\n\n const vars: Record<string, string> = {};\n const code = frontmatter.value;\n\n // Safe regex with limits\n const varMatches = code.matchAll(\n /(?:const|let|var)\\s+(\\w+)\\s*=\\s*[\"']([^\"']+)[\"']/g\n );\n\n for (const match of varMatches) {\n if (match[1] && match[2]) {\n vars[match[1]] = match[2];\n }\n }\n\n return vars;\n }\n\n /**\n * Convert AST node to HTML string\n */\n private nodeToHTML(node: Node, indent = ''): string {\n if (node.type === 'text') {\n return (node as { type: 'text'; value: string }).value;\n } else if (node.type === 'element') {\n const elementNode = node as ElementNode;\n const attrs = elementNode.attributes\n .map((attr: AttributeNode) => {\n if (attr.kind === 'quoted' && attr.name && attr.value) {\n return `${attr.name}=\"${attr.value}\"`;\n } else if (attr.kind === 'empty' && attr.name) {\n return attr.name;\n }\n return '';\n })\n .filter(Boolean)\n .join(' ');\n\n const openTag = attrs ? `<${elementNode.name} ${attrs}>` : `<${elementNode.name}>`;\n\n if (!elementNode.children || elementNode.children.length === 0) {\n // Self-closing tags\n if (['img', 'br', 'hr', 'input', 'meta', 'link'].includes(elementNode.name)) {\n return `${openTag.replace('>', ' />')}`;\n }\n return `${openTag}</${elementNode.name}>`;\n }\n\n const children = elementNode.children\n .map((child: Node) => this.nodeToHTML(child, indent + ' '))\n .join('\\n');\n\n return `${openTag}${children}</${elementNode.name}>`;\n } else if (node.type === 'component') {\n const componentNode = node as { type: 'component'; name: string };\n // Warning for unmatched components\n this.logger.warn(\n `Component <${componentNode.name} /> found but won't be resolved (use container renderer for imports)`,\n );\n\n return `<!-- Component: ${componentNode.name} -->`;\n } else if (node.type === 'expression') {\n return `{${String(node)}}`;\n } else if (node.type === 'frontmatter') {\n // Skip frontmatter nodes\n return '';\n } else if ((node as { type: string }).type === 'style') {\n // Handle style nodes - include them so they get extracted\n const styleNode = node as unknown as { type: 'style'; attributes: AttributeNode[]; children: Node[] };\n const attrs = (styleNode.attributes || [])\n .map((attr: AttributeNode) => {\n if (attr.kind === 'quoted' && attr.name && attr.value) {\n return `${attr.name}=\"${attr.value}\"`;\n } else if (attr.kind === 'empty' && attr.name) {\n return attr.name;\n }\n return '';\n })\n .filter(Boolean)\n .join(' ');\n\n const openTag = attrs ? `<style ${attrs}>` : '<style>';\n\n // Try to get content from children\n const content = (styleNode.children || [])\n .map((child: Node) => {\n if (child.type === 'text') {\n return (child as { type: 'text'; value: string }).value;\n }\n return '';\n })\n .join('');\n\n // If no children, try to get content directly\n const directContent = (node as { content?: string }).content || '';\n\n return `${openTag}${content || directContent}</style>`;\n }\n\n return '';\n }\n\n /**\n * Replace frontmatter variable expressions in HTML\n */\n private replaceFrontmatterVars(html: string, vars: Record<string, string>): string {\n let result = html;\n for (const [key, value] of Object.entries(vars)) {\n // Safe regex replacement\n const escapedKey = key.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n const regex = new RegExp(`\\\\{${escapedKey}\\\\}`, 'g');\n result = result.replace(regex, value);\n }\n return result;\n }\n\n /**\n * Render a component to HTML\n */\n async render(filePath: string): Promise<string> {\n this.logger.info(`Rendering component: ${filePath}`);\n\n try {\n // Read and parse component\n const fileContent = await readFile(filePath, 'utf-8');\n\n // Parse Astro file\n const result = await parse(fileContent);\n const ast = result.ast;\n\n // Extract and log frontmatter variables\n const vars = this.extractFrontmatter(ast);\n this.logger.debug(`Extracted ${Object.keys(vars).length} frontmatter variables`);\n\n // Convert AST to HTML\n let html = '';\n if (ast.children) {\n html = ast.children.map((child: Node) => this.nodeToHTML(child)).join('\\n');\n }\n\n // Replace variables\n html = this.replaceFrontmatterVars(html, vars);\n\n // Success logging\n this.logger.success(`Rendered ${filePath} (${html.length} chars)`);\n\n return html;\n } catch (error: unknown) {\n const err = error as Error;\n this.logger.error(`Failed to render ${filePath}: ${err.message}`);\n throw error;\n }\n }\n}","// src/utils/LazyLoader.ts\n// Client-side utility for lazy loading prerendered HTML components\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Configuration options for the LazyHTMLLoader\n */\nexport interface LazyLoaderConfig {\n /** Base URL for prerendered files (default: '/prerendered') */\n baseUrl?: string;\n /** Enable CSS modules mode with per-component CSS (default: false) */\n cssModules?: boolean;\n /** URL to CSS modules manifest (default: '/prerendered/manifest.json') */\n manifestUrl?: string;\n /** URL to base CSS file in CSS modules mode (default: '/prerendered/base.css') */\n baseCssUrl?: string;\n /** URL to legacy single CSS file (default: '/prerendered/lazy-components.css') */\n legacyCssUrl?: string;\n /** Preload CSS files before they're needed (default: false) */\n preloadCSS?: boolean;\n /** Cache loaded HTML in memory (default: true) */\n cacheHTML?: boolean;\n /** Enable retry logic for failed fetches (default: true) */\n enableRetry?: boolean;\n /** Maximum number of retry attempts (default: 3) */\n maxRetries?: number;\n /** Delay between retries in milliseconds (default: 1000) */\n retryDelay?: number;\n /** Enable debug logging (default: false) */\n debug?: boolean;\n /** Callback when a component is loaded */\n onLoad?: (componentName: string, stats: {\n duration: number;\n bytes: number;\n fromCache: boolean;\n secondaryAssets?: Array<{ url: string; bytes: number; duration: number; type: string }>\n }) => void;\n /** Callback when an error occurs */\n onError?: (componentName: string, error: Error) => void;\n /** Callback when CSS is loaded */\n onCSSLoad?: (cssFile: string, duration: number) => void;\n}\n\n/**\n * CSS module manifest structure\n */\nexport interface CSSModuleManifest {\n /** Map of component names to their CSS file paths */\n components: Record<string, string>;\n}\n\n/**\n * Load statistics\n */\nexport interface LoadStats {\n /** Total number of successful loads */\n totalLoads: number;\n /** Number of cache hits */\n cacheHits: number;\n /** Number of cache misses */\n cacheMisses: number;\n /** Number of errors encountered */\n errors: number;\n /** Average load time in milliseconds */\n averageLoadTime: number;\n /** Total bytes transferred across all loads */\n totalBytes: number;\n}\n\n// ============================================================================\n// LazyHTMLLoader Class\n// ============================================================================\n\n/**\n * Client-side utility for lazy loading prerendered HTML components\n * \n * @example\n * ```ts\n * import { createLazyLoader } from 'vite-plugin-astro-prerender';\n * \n * const loader = createLazyLoader({ debug: true });\n * \n * // Load and inject when in viewport\n * loader.observeAndLoad('LazyFooter', '#footer-container');\n * \n * // Or load manually\n * const html = await loader.load('LazyHeader');\n * document.getElementById('header')!.innerHTML = html;\n * ```\n */\nexport class LazyHTMLLoader {\n private config: Required<LazyLoaderConfig>;\n private htmlCache = new Map<string, string>();\n private cssCache = new Set<string>();\n private observers = new Map<string, IntersectionObserver>();\n private manifest: CSSModuleManifest | null = null;\n private manifestLoaded = false;\n private baseCssLoaded = false;\n private legacyCssLoaded = false;\n private stats = {\n totalLoads: 0,\n cacheHits: 0,\n cacheMisses: 0,\n errors: 0,\n totalLoadTime: 0,\n totalBytes: 0,\n };\n private detailedStats = new Map<string, {\n duration: number;\n bytes: number;\n fromCache: boolean;\n timestamp: number;\n secondaryAssets?: Array<{ url: string; bytes: number; duration: number; type: string }>\n }>();\n\n constructor(config: LazyLoaderConfig = {}) {\n this.config = {\n baseUrl: config.baseUrl ?? '/prerendered',\n cssModules: config.cssModules ?? false,\n manifestUrl: config.manifestUrl ?? '/prerendered/manifest.json',\n baseCssUrl: config.baseCssUrl ?? '/prerendered/base.css',\n legacyCssUrl: config.legacyCssUrl ?? '/prerendered/lazy-components.css',\n preloadCSS: config.preloadCSS ?? false,\n cacheHTML: config.cacheHTML ?? true,\n enableRetry: config.enableRetry ?? true,\n maxRetries: config.maxRetries ?? 3,\n retryDelay: config.retryDelay ?? 1000,\n debug: config.debug ?? false,\n onLoad: config.onLoad ?? (() => { }),\n onError: config.onError ?? (() => { }),\n onCSSLoad: config.onCSSLoad ?? (() => { }),\n };\n\n this.log('LazyHTMLLoader initialized', this.config);\n }\n\n /**\n * Internal logging method\n */\n private log(message: string, ...args: unknown[]): void {\n if (this.config.debug) {\n console.log(`[LazyHTMLLoader] ${message}`, ...args);\n }\n }\n\n /**\n * Fetch with retry logic\n */\n private async fetchWithRetry(\n url: string,\n retries = this.config.maxRetries,\n ): Promise<Response> {\n for (let attempt = 1; attempt <= retries; attempt++) {\n try {\n this.log(`Fetching ${url} (attempt ${attempt}/${retries})`);\n const response = await fetch(url);\n\n if (response.ok) {\n return response;\n }\n\n if (attempt === retries) {\n throw new Error(`HTTP ${response.status}: ${response.statusText}`);\n }\n\n // Don't retry on 404\n if (response.status === 404) {\n throw new Error(`Not found: ${url}`);\n }\n\n this.log(`Fetch failed with status ${response.status}, retrying...`);\n } catch (error) {\n if (attempt === retries) {\n throw error;\n }\n this.log(`Fetch error: ${error}, retrying...`);\n }\n\n // Wait before retrying\n if (attempt < retries) {\n await new Promise((resolve) =>\n setTimeout(resolve, this.config.retryDelay * attempt),\n );\n }\n }\n\n throw new Error(`Failed to fetch ${url} after ${retries} attempts`);\n }\n\n /**\n * Load CSS manifest for CSS modules mode\n */\n private async loadManifest(): Promise<void> {\n if (this.manifestLoaded) {\n return;\n }\n\n const startTime = performance.now();\n\n try {\n const response = await (this.config.enableRetry\n ? this.fetchWithRetry(this.config.manifestUrl)\n : fetch(this.config.manifestUrl));\n\n if (!response.ok) {\n throw new Error(`Failed to load manifest: ${response.statusText}`);\n }\n\n this.manifest = await response.json();\n this.manifestLoaded = true;\n\n const duration = performance.now() - startTime;\n this.log(`Manifest loaded in ${duration.toFixed(2)}ms`, this.manifest);\n this.config.onCSSLoad(this.config.manifestUrl, duration);\n } catch (error) {\n console.error('Failed to load CSS manifest:', error);\n // Fallback to legacy mode\n this.config.cssModules = false;\n this.manifestLoaded = true;\n }\n }\n\n /**\n * Load base CSS file (CSS modules mode)\n */\n private async loadBaseCSS(): Promise<void> {\n if (this.baseCssLoaded) {\n return;\n }\n\n await this.loadManifest();\n\n if (!this.manifest) {\n return;\n }\n\n const startTime = performance.now();\n\n return new Promise((resolve, reject) => {\n const link = document.createElement('link');\n link.rel = 'stylesheet';\n link.href = this.config.baseCssUrl;\n\n link.onload = () => {\n this.baseCssLoaded = true;\n const duration = performance.now() - startTime;\n this.log(`Base CSS loaded in ${duration.toFixed(2)}ms`);\n this.config.onCSSLoad(this.config.baseCssUrl, duration);\n resolve();\n };\n\n link.onerror = () => {\n console.error('Failed to load base CSS');\n reject(new Error('Failed to load base CSS'));\n };\n\n if (this.config.preloadCSS) {\n const preload = document.createElement('link');\n preload.rel = 'preload';\n preload.as = 'style';\n preload.href = this.config.baseCssUrl;\n document.head.appendChild(preload);\n }\n\n document.head.appendChild(link);\n });\n }\n\n /**\n * Load component-specific CSS file (CSS modules mode)\n */\n private async loadComponentCSS(componentName: string): Promise<void> {\n if (!this.config.cssModules || !this.manifest) {\n return;\n }\n\n const cssFile = this.manifest.components[componentName];\n if (!cssFile) {\n this.log(`No CSS file for component: ${componentName}`);\n return;\n }\n\n if (this.cssCache.has(cssFile)) {\n this.log(`CSS already loaded: ${cssFile}`);\n return;\n }\n\n const startTime = performance.now();\n const cssUrl = `${this.config.baseUrl}/${cssFile}`;\n\n return new Promise((resolve, reject) => {\n const link = document.createElement('link');\n link.rel = 'stylesheet';\n link.href = cssUrl;\n\n link.onload = () => {\n this.cssCache.add(cssFile);\n const duration = performance.now() - startTime;\n this.log(\n `Component CSS loaded in ${duration.toFixed(2)}ms: ${cssFile}`,\n );\n this.config.onCSSLoad(cssFile, duration);\n resolve();\n };\n\n link.onerror = () => {\n console.error(`Failed to load component CSS: ${cssFile}`);\n reject(new Error(`Failed to load CSS: ${cssFile}`));\n };\n\n if (this.config.preloadCSS) {\n const preload = document.createElement('link');\n preload.rel = 'preload';\n preload.as = 'style';\n preload.href = cssUrl;\n document.head.appendChild(preload);\n }\n\n document.head.appendChild(link);\n });\n }\n\n /**\n * Load legacy single CSS file\n */\n private async loadLegacyCSS(): Promise<void> {\n if (this.legacyCssLoaded) {\n return;\n }\n\n const startTime = performance.now();\n\n return new Promise((resolve, reject) => {\n const link = document.createElement('link');\n link.rel = 'stylesheet';\n link.href = this.config.legacyCssUrl;\n\n link.onload = () => {\n this.legacyCssLoaded = true;\n const duration = performance.now() - startTime;\n this.log(`Legacy CSS loaded in ${duration.toFixed(2)}ms`);\n this.config.onCSSLoad(this.config.legacyCssUrl, duration);\n resolve();\n };\n\n link.onerror = () => {\n console.error('Failed to load legacy CSS');\n reject(new Error('Failed to load lazy components CSS'));\n };\n\n if (this.config.preloadCSS) {\n const preload = document.createElement('link');\n preload.rel = 'preload';\n preload.as = 'style';\n preload.href = this.config.legacyCssUrl;\n document.head.appendChild(preload);\n }\n\n document.head.appendChild(link);\n });\n }\n\n /**\n * Load HTML fragment from server\n */\n async load(componentName: string): Promise<string> {\n const startTime = performance.now();\n\n try {\n // Check cache\n if (this.config.cacheHTML && this.htmlCache.has(componentName)) {\n this.stats.cacheHits++;\n this.log(`Cache hit: ${componentName}`);\n const html = this.htmlCache.get(componentName)!;\n const bytes = new Blob([html]).size;\n const duration = performance.now() - startTime;\n\n this.stats.totalLoads++;\n this.stats.totalLoadTime += duration;\n\n const loadInfo = { duration, bytes, fromCache: true, timestamp: Date.now() };\n this.detailedStats.set(componentName, loadInfo);\n this.config.onLoad(componentName, { duration, bytes, fromCache: true });\n\n return html;\n }\n\n this.stats.cacheMisses++;\n\n // Load CSS\n if (this.config.cssModules) {\n await this.loadBaseCSS().catch((err) => {\n console.warn('Failed to load base CSS:', err);\n });\n await this.loadComponentCSS(componentName).catch((err) => {\n console.warn(`Failed to load CSS for ${componentName}:`, err);\n });\n } else {\n await this.loadLegacyCSS().catch((err) => {\n console.warn('Failed to load legacy CSS:', err);\n });\n }\n\n // Fetch HTML\n const url = `${this.config.baseUrl}/${componentName}.html`;\n const response = await (this.config.enableRetry\n ? this.fetchWithRetry(url)\n : fetch(url));\n\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}: ${response.statusText}`);\n }\n\n const html = await response.text();\n const bytes = new Blob([html]).size;\n\n // Cache HTML\n if (this.config.cacheHTML) {\n this.htmlCache.set(componentName, html);\n }\n\n // Update stats\n const duration = performance.now() - startTime;\n this.stats.totalLoads++;\n this.stats.totalLoadTime += duration;\n this.stats.totalBytes += bytes;\n\n this.log(`Loaded ${componentName} in ${duration.toFixed(2)}ms (${bytes} bytes)`);\n\n // Start tracking secondary assets (images, etc.) that might be triggered by this injection\n const secondaryAssets: Array<{ url: string; bytes: number; duration: number; type: string }> = [];\n\n const resourceObserver = new PerformanceObserver((list) => {\n list.getEntries().forEach((entry) => {\n const res = entry as PerformanceResourceTiming;\n // Filter for assets likely triggered by this component\n if (res.startTime >= (startTime - 50)) {\n const assetBytes = res.transferSize || res.decodedBodySize || res.encodedBodySize || 0;\n\n secondaryAssets.push({\n url: res.name,\n bytes: assetBytes,\n duration: res.duration,\n type: res.initiatorType\n });\n\n // Update total bytes and global stats with secondary assets\n this.stats.totalBytes += assetBytes;\n }\n });\n });\n\n try {\n // Use buffered: true to catch resources that might have started \n // between performance.now() and observe() call\n resourceObserver.observe({ entryTypes: ['resource'], buffered: true });\n\n // Stop observing after a reasonable time (longer for assets)\n setTimeout(() => resourceObserver.disconnect(), 6000);\n } catch (e) {\n this.log('PerformanceObserver failed');\n // Fallback attempt without buffering if that was the cause\n try { resourceObserver.observe({ entryTypes: ['resource'] }); } catch (err) { }\n }\n\n const loadInfo = {\n duration,\n bytes,\n fromCache: false,\n timestamp: Date.now(),\n secondaryAssets\n };\n this.detailedStats.set(componentName, loadInfo);\n this.config.onLoad(componentName, { duration, bytes, fromCache: false, secondaryAssets });\n\n return html;\n } catch (error) {\n this.stats.errors++;\n const err = error instanceof Error ? error : new Error(String(error));\n this.log(`Error loading ${componentName}:`, err);\n this.config.onError(componentName, err);\n throw err;\n }\n }\n\n /**\n * Inject HTML fragment into target element\n */\n async inject(componentName: string, targetSelector: string): Promise<void> {\n const html = await this.load(componentName);\n const target = document.querySelector(targetSelector);\n\n if (target) {\n target.innerHTML = html;\n this.log(`Injected ${componentName} into ${targetSelector}`);\n } else {\n const error = new Error(`Target element not found: ${targetSelector}`);\n this.config.onError(componentName, error);\n throw error;\n }\n }\n\n /**\n * Load HTML fragment when target element enters viewport\n */\n observeAndLoad(\n componentName: string,\n targetSelector: string,\n options?: IntersectionObserverInit,\n ): void {\n const target = document.querySelector(targetSelector);\n if (!target) {\n console.warn(`Target element not found: ${targetSelector}`);\n return;\n }\n\n const observer = new IntersectionObserver(\n (entries) => {\n entries.forEach((entry) => {\n if (entry.isIntersecting) {\n this.log(`Component ${componentName} entered viewport`);\n this.inject(componentName, targetSelector).catch((err) => {\n console.error(`Failed to inject ${componentName}:`, err);\n });\n observer.disconnect();\n this.observers.delete(componentName);\n }\n });\n },\n options || { rootMargin: '100px' },\n );\n\n observer.observe(target);\n this.observers.set(componentName, observer);\n this.log(`Observing ${componentName} at ${targetSelector}`);\n }\n\n /**\n * Preload HTML fragment without injecting\n */\n async preload(componentName: string): Promise<void> {\n await this.load(componentName);\n }\n\n /**\n * Batch preload multiple components\n */\n async preloadBatch(componentNames: string[]): Promise<void> {\n this.log(`Preloading ${componentNames.length} components:`, componentNames);\n await Promise.all(componentNames.map((name) => this.preload(name)));\n }\n\n /**\n * Get load statistics\n */\n getStats(): LoadStats {\n return {\n totalLoads: this.stats.totalLoads,\n cacheHits: this.stats.cacheHits,\n cacheMisses: this.stats.cacheMisses,\n errors: this.stats.errors,\n averageLoadTime:\n this.stats.totalLoads > 0\n ? this.stats.totalLoadTime / this.stats.totalLoads\n : 0,\n totalBytes: this.stats.totalBytes,\n };\n }\n\n /**\n * Get detailed history of all loads in this session\n */\n getDetailedHistory(): Array<{ componentName: string; duration: number; bytes: number; fromCache: boolean; timestamp: number }> {\n return Array.from(this.detailedStats.entries()).map(([name, info]) => ({\n componentName: name,\n ...info,\n })).sort((a, b) => b.timestamp - a.timestamp);\n }\n\n /**\n * Clear HTML cache\n */\n clearCache(): void {\n this.htmlCache.clear();\n this.log('HTML cache cleared');\n }\n\n /**\n * Clear CSS cache (forces reload of CSS files)\n */\n clearCSSCache(): void {\n this.cssCache.clear();\n this.baseCssLoaded = false;\n this.legacyCssLoaded = false;\n this.manifestLoaded = false;\n this.manifest = null;\n this.log('CSS cache cleared');\n }\n\n /**\n * Disconnect all observers\n */\n disconnectAll(): void {\n this.observers.forEach((observer) => observer.disconnect());\n this.observers.clear();\n this.log('All observers disconnected');\n }\n\n /**\n * Reset all state (cache, observers, stats)\n */\n reset(): void {\n this.clearCache();\n this.clearCSSCache();\n this.disconnectAll();\n this.stats = {\n totalLoads: 0,\n cacheHits: 0,\n cacheMisses: 0,\n errors: 0,\n totalLoadTime: 0,\n totalBytes: 0,\n };\n this.detailedStats.clear();\n this.log('LazyHTMLLoader reset');\n }\n}\n\n/**\n * Factory function to create a lazy loader instance\n */\nexport function createLazyLoader(config?: LazyLoaderConfig): LazyHTMLLoader {\n return new LazyHTMLLoader(config);\n}\n\n/**\n * Default lazy loader instance with default configuration\n */\nexport const lazyLoader = new LazyHTMLLoader();"],"mappings":"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","names":["watch","join","relative","basename","createHash","readFile","fg","outputFile","fsEnsureDir","cheerio","consola","createLogger","tag","getFileHash","filePath","content","readFile","createHash","findComponents","dir","fg","extractClasses","html","$","classes","_","el","_a","cls","extractStyles","styles","cleanHTML","$el","attrs","attr","body","minifyHTML","minify","error","writeFileWithDir","filePath","content","outputFile","FlatCache","CacheManager","cacheDir","componentPath","hash","pathToFileURL","CSSGenerator","tailwindConfigPath","logger","generateTailwind","classes","cls","styles","outputPath","classArray","cssContent","writeFileWithDir","postcssModule","tailwindModule","autoprefixerModule","postcss","tailwindcss","autoprefixer","modifiedConfig","pathToFileURL","result","error","readFile","AstroContainer","ContainerRenderer","logger","server","componentPath","frontmatterMatch","frontmatterVars","varMatches","match","ComponentModule","error","err","html","key","value","regex","parse","readFile","consola","ParserRenderer","ast","frontmatter","vars","varMatches","match","node","indent","elementNode","attrs","attr","openTag","children","child","componentNode","styleNode","content","directContent","html","result","key","value","escapedKey","regex","filePath","fileContent","error","err","LazyHTMLLoader","config","_a","_b","_c","_d","_e","_f","_g","_h","_i","_j","_k","_l","_m","_n","message","args","url","retries","attempt","response","error","resolve","startTime","duration","reject","link","preload","componentName","cssFile","cssUrl","html","bytes","loadInfo","err","secondaryAssets","resourceObserver","list","entry","res","assetBytes","e","targetSelector","target","options","observer","entries","componentNames","name","info","a","b","createLazyLoader","lazyLoader","astroPrerenderPlugin","componentsDir","outputDir","generateTailwindCSS","tailwindConfigPath","renderer","minify","logger","createLogger","config","cacheManager","cssGenerator","componentRenderer","parserFallback","processAll","root","componentsPath","join","outputPath","componentFiles","findComponents","componentPath","processComponent","generateCSS","hash","getFileHash","relative","html","styles","extractStyles","cleanedHTML","cleanHTML","classes","extractClasses","originalSize","minifyHTML","minifiedSize","savings","componentName","basename","htmlOutputPath","writeFileWithDir","error","cssOutputPath","resolvedConfig","CacheManager","CSSGenerator","ContainerRenderer","ParserRenderer","server","watcher","watch","filePath","astroPrerenderIntegration","options","updateConfig"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "vite-plugin-astro-prerender",
|
|
3
|
+
"version": "0.1.22",
|
|
4
|
+
"description": "A Vite plugin for Astro that prerenders components to static HTML and generates optimized CSS with Tailwind tree-shaking for lazy-loading below-the-fold content",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "Ishan Parlikar"
|
|
7
|
+
},
|
|
8
|
+
"license": "MIT",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/ishaanparlikar/vite-plugin-astro-prerender.git"
|
|
12
|
+
},
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/ishaanparlikar/vite-plugin-astro-prerender/issues"
|
|
15
|
+
},
|
|
16
|
+
"homepage": "https://github.com/ishaanparlikar/vite-plugin-astro-prerender#readme",
|
|
17
|
+
"keywords": [
|
|
18
|
+
"vite",
|
|
19
|
+
"vite-plugin",
|
|
20
|
+
"astro",
|
|
21
|
+
"astro-integration",
|
|
22
|
+
"prerender",
|
|
23
|
+
"static-html",
|
|
24
|
+
"lazy-loading",
|
|
25
|
+
"lazy-load",
|
|
26
|
+
"performance",
|
|
27
|
+
"optimization",
|
|
28
|
+
"tailwind",
|
|
29
|
+
"tailwindcss",
|
|
30
|
+
"css",
|
|
31
|
+
"tree-shaking",
|
|
32
|
+
"ssr",
|
|
33
|
+
"ssg",
|
|
34
|
+
"components",
|
|
35
|
+
"html-fragments"
|
|
36
|
+
],
|
|
37
|
+
"type": "module",
|
|
38
|
+
"main": "./dist/index.js",
|
|
39
|
+
"module": "./dist/index.js",
|
|
40
|
+
"types": "./dist/index.d.ts",
|
|
41
|
+
"exports": {
|
|
42
|
+
".": {
|
|
43
|
+
"types": "./dist/index.d.ts",
|
|
44
|
+
"import": "./dist/index.js",
|
|
45
|
+
"default": "./dist/index.js"
|
|
46
|
+
},
|
|
47
|
+
"./client": {
|
|
48
|
+
"types": "./dist/client.d.ts",
|
|
49
|
+
"import": "./dist/client.js",
|
|
50
|
+
"default": "./dist/client.js"
|
|
51
|
+
},
|
|
52
|
+
"./package.json": "./package.json"
|
|
53
|
+
},
|
|
54
|
+
"files": [
|
|
55
|
+
"dist",
|
|
56
|
+
"README.md",
|
|
57
|
+
"LICENSE"
|
|
58
|
+
],
|
|
59
|
+
"sideEffects": false,
|
|
60
|
+
"engines": {
|
|
61
|
+
"node": ">=18.0.0"
|
|
62
|
+
},
|
|
63
|
+
"peerDependencies": {
|
|
64
|
+
"@astrojs/compiler": "^2.0.0",
|
|
65
|
+
"astro": "^5.0.0",
|
|
66
|
+
"autoprefixer": "^10.0.0",
|
|
67
|
+
"postcss": "^8.0.0",
|
|
68
|
+
"tailwindcss": "^3.0.0 || ^4.0.0"
|
|
69
|
+
},
|
|
70
|
+
"peerDependenciesMeta": {
|
|
71
|
+
"tailwindcss": {
|
|
72
|
+
"optional": true
|
|
73
|
+
},
|
|
74
|
+
"autoprefixer": {
|
|
75
|
+
"optional": true
|
|
76
|
+
},
|
|
77
|
+
"postcss": {
|
|
78
|
+
"optional": true
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
"dependencies": {
|
|
82
|
+
"cheerio": "^1.1.2",
|
|
83
|
+
"chokidar": "^5.0.0",
|
|
84
|
+
"consola": "^3.4.2",
|
|
85
|
+
"fast-glob": "^3.3.3",
|
|
86
|
+
"flat-cache": "^6.1.19",
|
|
87
|
+
"fs-extra": "^11.3.3",
|
|
88
|
+
"html-minifier-terser": "^7.2.0"
|
|
89
|
+
},
|
|
90
|
+
"devDependencies": {
|
|
91
|
+
"@astrojs/compiler": "^2.13.0",
|
|
92
|
+
"@types/flat-cache": "^2.0.2",
|
|
93
|
+
"@types/fs-extra": "^11.0.4",
|
|
94
|
+
"@types/html-minifier-terser": "^7.0.2",
|
|
95
|
+
"@types/node": "^25.0.6",
|
|
96
|
+
"astro": "^5.16.8",
|
|
97
|
+
"autoprefixer": "^10.4.23",
|
|
98
|
+
"postcss": "^8.5.6",
|
|
99
|
+
"tailwindcss": "^3.4.19",
|
|
100
|
+
"tsup": "^8.5.1",
|
|
101
|
+
"typescript": "^5.9.3",
|
|
102
|
+
"vite": "^6.4.1"
|
|
103
|
+
},
|
|
104
|
+
"scripts": {
|
|
105
|
+
"build": "tsup",
|
|
106
|
+
"dev": "tsup --watch"
|
|
107
|
+
}
|
|
108
|
+
}
|