@catfyrr/vite-plugin-ssi 0.0.1 → 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +283 -9
  2. package/dist/index.js +341 -11
  3. package/package.json +2 -3
package/README.md CHANGED
@@ -1,12 +1,15 @@
1
1
  # Vite SSI Plugin
2
2
 
3
- A fully Apache/Nginx compatible Server-Side Includes (SSI) plugin for Vite.
3
+ A fully Apache/Nginx compatible Server-Side Includes (SSI) plugin for Vite. This plugin processes SSI directives in your HTML files during development, build, and preview modes, enabling server-side includes without needing a traditional web server.
4
4
 
5
5
  ## Features
6
6
 
7
- - 🌐 Apache/Nginx SSI compatibility
8
- - 🚀 Vite integration
9
- - 🔧 Customizable include processing
7
+ - 🌐 **SSI Directive Support** - Process Server-Side Includes in your Vite projects
8
+ - 🚀 **Full Vite Integration** - Works seamlessly in dev, build, and preview modes
9
+ - 🔧 **Configurable File Types** - Intelligently process different file types with SSI
10
+ - 🔄 **Hot Module Replacement (HMR)** - Automatic reloading when SSI dependencies change
11
+ - 📦 **Zero Configuration** - Works out of the box with sensible defaults
12
+ - 🛡️ **Safe by Default** - Circular dependency detection and depth limiting
10
13
 
11
14
  ## Installation
12
15
 
@@ -18,7 +21,9 @@ npm install @catfyrr/vite-plugin-ssi
18
21
  npx jsr install @catfyrr/vite-plugin-ssi
19
22
  ```
20
23
 
21
- ## Usage
24
+ ## Quick Start
25
+
26
+ Add the plugin to your `vite.config.ts`:
22
27
 
23
28
  ```typescript
24
29
  import { defineConfig } from 'vite';
@@ -26,13 +31,282 @@ import vitePluginSsi from '@catfyrr/vite-plugin-ssi';
26
31
 
27
32
  export default defineConfig({
28
33
  plugins: [
29
- vitePluginSsi({
30
- include: ['.html', '.shtml'],
31
- })
32
- ]
34
+ vitePluginSsi(),
35
+ ],
33
36
  });
34
37
  ```
35
38
 
39
+ Now you can use SSI directives in your HTML files:
40
+
41
+ ```html
42
+ <!DOCTYPE html>
43
+ <html>
44
+ <head>
45
+ <!--#include virtual="/common/head.html" -->
46
+ </head>
47
+ <body>
48
+ <!--#include virtual="/components/header.html" -->
49
+ <main>
50
+ <h1>Welcome</h1>
51
+ </main>
52
+ <!--#include virtual="/components/footer.html" -->
53
+ </body>
54
+ </html>
55
+ ```
56
+
57
+ ## Configuration Options
58
+
59
+ ### `maxDepth`
60
+
61
+ Maximum depth for recursive includes.
62
+
63
+ - **Type:** `number`
64
+ - **Default:** `10`
65
+ - **Description:** Prevents infinite recursion and circular dependencies
66
+
67
+ ```typescript
68
+ vitePluginSsi({
69
+ maxDepth: 15, // Allow deeper nesting
70
+ })
71
+ ```
72
+
73
+ ### `enforce`
74
+
75
+ When to run this plugin in the Vite pipeline.
76
+
77
+ - **Type:** `'pre' | 'post'`
78
+ - **Default:** `'pre'`
79
+ - **Description:** Controls the execution order relative to other plugins
80
+
81
+ ```typescript
82
+ vitePluginSsi({
83
+ enforce: 'pre', // Run before other plugins
84
+ })
85
+ ```
86
+
87
+ ### `apply`
88
+
89
+ Apply plugin only in specific environments.
90
+
91
+ - **Type:** `'serve' | 'build' | 'preview' | { serve?: boolean; build?: boolean; preview?: boolean }`
92
+ - **Default:** `undefined` (applies to all environments)
93
+ - **Description:** Control which Vite commands should process SSI
94
+
95
+ ```typescript
96
+ // Apply only in dev server
97
+ vitePluginSsi({
98
+ apply: 'serve',
99
+ })
100
+
101
+ // Apply only in build
102
+ vitePluginSsi({
103
+ apply: 'build',
104
+ })
105
+
106
+ // Apply to specific environments using object form
107
+ vitePluginSsi({
108
+ apply: {
109
+ serve: true,
110
+ build: true,
111
+ preview: false,
112
+ },
113
+ })
114
+ ```
115
+
116
+ ### `includeFileTypes`
117
+
118
+ File types to apply SSI processing to for included files (at any depth).
119
+
120
+ - **Type:** `string[]`
121
+ - **Default:** `[]` (only process top-level HTML files)
122
+ - **Description:** When files are included via SSI, they will also be processed if they match these types. SSI always applies to top-level HTML files regardless of this setting.
123
+
124
+ ```typescript
125
+ // Process JS/TS files when included
126
+ vitePluginSsi({
127
+ includeFileTypes: ['js', 'html'], // Processes .js, .mjs, .ts, .tsx, .jsx, etc.
128
+ })
129
+ ```
130
+
131
+ The plugin intelligently maps file types to extensions:
132
+ - `'js'` → `.js`, `.mjs`, `.cjs`, `.ts`, `.tsx`, `.jsx`, `.mts`, `.cts`
133
+ - `'html'` → `.html`, `.htm`, `.shtml`
134
+ - `'css'` → `.css`, `.scss`, `.sass`, `.less`, `.styl`
135
+ - `'json'` → `.json`, `.jsonc`
136
+ - `'xml'` → `.xml`, `.xhtml`
137
+ - `'text'` → `.txt`, `.md`, `.markdown`
138
+
139
+ ### `fileTypeMap`
140
+
141
+ Custom file type to extension mappings.
142
+
143
+ - **Type:** `FileTypeMap`
144
+ - **Default:** See [file-types.ts](./src/file-types.ts) for defaults
145
+ - **Description:** Override or extend the default file type mappings
146
+
147
+ ```typescript
148
+ import type { FileTypeMap } from '@catfyrr/vite-plugin-ssi';
149
+
150
+ const customFileTypes: FileTypeMap = {
151
+ html: ['.html', '.htm', '.shtml'],
152
+ js: ['.js', '.mjs', '.ts', '.jsx', '.tsx'],
153
+ // Add custom types
154
+ vue: ['.vue'],
155
+ svelte: ['.svelte'],
156
+ };
157
+
158
+ vitePluginSsi({
159
+ includeFileTypes: ['js', 'vue'],
160
+ fileTypeMap: customFileTypes,
161
+ })
162
+ ```
163
+
164
+ ## Usage Examples
165
+
166
+ ### Basic Include
167
+
168
+ ```html
169
+ <!-- index.html -->
170
+ <!DOCTYPE html>
171
+ <html>
172
+ <head>
173
+ <title>My Site</title>
174
+ </head>
175
+ <body>
176
+ <!--#include virtual="/header.html" -->
177
+ <main>Content</main>
178
+ <!--#include virtual="/footer.html" -->
179
+ </body>
180
+ </html>
181
+ ```
182
+
183
+ ### Absolute and Relative Paths
184
+
185
+ ```html
186
+ <!-- Absolute paths from project root -->
187
+ <!--#include virtual="/components/nav.html" -->
188
+
189
+ <!-- Relative paths from current file -->
190
+ <!--#include virtual="../common/sidebar.html" -->
191
+ ```
192
+
193
+ ### Processing Non-HTML Files
194
+
195
+ When you want SSI to process included JavaScript/TypeScript files:
196
+
197
+ ```typescript
198
+ vitePluginSsi({
199
+ includeFileTypes: ['js', 'html'],
200
+ })
201
+ ```
202
+
203
+ ```html
204
+ <!-- index.html -->
205
+ <!--#include virtual="/scripts/utils.ts" -->
206
+ ```
207
+
208
+ The included `.ts` file will also be processed for SSI directives if it contains them.
209
+
210
+ ### Environment-Specific Configuration
211
+
212
+ ```typescript
213
+ import { defineConfig } from 'vite';
214
+ import vitePluginSsi from '@catfyrr/vite-plugin-ssi';
215
+
216
+ export default defineConfig({
217
+ plugins: [
218
+ vitePluginSsi({
219
+ // Only process SSI during build (for static site generation)
220
+ apply: 'build',
221
+ includeFileTypes: ['html'],
222
+ maxDepth: 5,
223
+ }),
224
+ ],
225
+ })
226
+ ```
227
+
228
+ ## How It Works
229
+
230
+ 1. **Development Mode (`vite dev`)**: SSI directives are processed on-the-fly when HTML files are served. Changes to included files trigger HMR automatically.
231
+
232
+ 2. **Build Mode (`vite build`)**: SSI directives are processed during the build phase, and the final HTML output contains the resolved includes.
233
+
234
+ 3. **Preview Mode (`vite preview`)**: SSI directives are processed when serving the built files, allowing you to test the built output with SSI processing.
235
+
236
+ ## Error Handling
237
+
238
+ The plugin provides clear error messages for common issues:
239
+
240
+ - **Circular Dependencies**: Detected and reported with the dependency chain
241
+ - **Missing Files**: Included files that don't exist show an error comment
242
+ - **Max Depth Exceeded**: Recursion limits are enforced and reported
243
+
244
+ ```html
245
+ <!-- If header.html doesn't exist -->
246
+ <!-- SSI Error: File not found: /path/to/header.html -->
247
+
248
+ <!-- If circular dependency detected -->
249
+ <!-- SSI Error: Circular include detected: file-a.html -> file-b.html -> file-a.html -->
250
+ ```
251
+
252
+ ## Compatibility
253
+
254
+ This plugin currently supports:
255
+
256
+ ✅ **Implemented:**
257
+ - `<!--#include virtual="..." -->` - File inclusion with absolute and relative paths
258
+ - Recursive includes with depth limiting
259
+ - Circular dependency detection
260
+ - HMR for included file changes
261
+ - Configurable file type processing
262
+
263
+ 📋 **Roadmap:** See [COMPATIBILITY.md](./COMPATIBILITY.md) for full compatibility tracking with Apache and Nginx SSI modules.
264
+
265
+ ## TypeScript Support
266
+
267
+ The plugin includes full TypeScript definitions:
268
+
269
+ ```typescript
270
+ import vitePluginSsi, { type VitePluginSsiOptions, type FileTypeMap } from '@catfyrr/vite-plugin-ssi';
271
+ ```
272
+
273
+ ## Troubleshooting
274
+
275
+ ### SSI directives not being processed
276
+
277
+ 1. Ensure your HTML file has the correct extension (`.html`, `.htm`, `.shtml`)
278
+ 2. Check that the plugin is added to your `vite.config.ts`
279
+ 3. Verify file paths are correct (use absolute paths from project root or relative paths)
280
+
281
+ ### HMR not working for included files
282
+
283
+ - Make sure you're using the dev server (`vite dev`)
284
+ - Check that the included file is being tracked as a dependency
285
+ - Verify the included file path matches exactly (case-sensitive on some systems)
286
+
287
+ ### Circular dependency errors
288
+
289
+ - Review your include structure to identify the cycle
290
+ - Consider using a shared partial file instead of circular includes
291
+ - Adjust `maxDepth` if needed, but beware of infinite loops
292
+
293
+ ## Security Considerations
294
+
295
+ - The plugin only processes SSI directives, it does not execute shell commands or arbitrary code
296
+ - File access is limited to the project directory
297
+ - Circular dependencies are detected and prevented
298
+ - Maximum include depth limits prevent excessive recursion
299
+
300
+ ## Contributing
301
+
302
+ Contributions are welcome! Please see [CONTRIBUTING.md](./CONTRIBUTING.md) for guidelines.
303
+
36
304
  ## License
37
305
 
38
306
  MIT
307
+
308
+ ## References
309
+
310
+ - [Apache SSI Documentation](https://httpd.apache.org/docs/current/howto/ssi.html)
311
+ - [Nginx SSI Module Documentation](https://nginx.org/en/docs/http/ngx_http_ssi_module.html)
312
+ - [Apache mod_include Documentation](https://httpd.apache.org/docs/current/mod/mod_include.html)
package/dist/index.js CHANGED
@@ -1,8 +1,21 @@
1
1
  // @bun @bun-cjs
2
- (function(exports, require, module, __filename, __dirname) {var __defProp = Object.defineProperty;
2
+ (function(exports, require, module, __filename, __dirname) {var __create = Object.create;
3
+ var __getProtoOf = Object.getPrototypeOf;
4
+ var __defProp = Object.defineProperty;
3
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
4
6
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __toESM = (mod, isNodeMode, target) => {
9
+ target = mod != null ? __create(__getProtoOf(mod)) : {};
10
+ const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
11
+ for (let key of __getOwnPropNames(mod))
12
+ if (!__hasOwnProp.call(to, key))
13
+ __defProp(to, key, {
14
+ get: () => mod[key],
15
+ enumerable: true
16
+ });
17
+ return to;
18
+ };
6
19
  var __moduleCache = /* @__PURE__ */ new WeakMap;
7
20
  var __toCommonJS = (from) => {
8
21
  var entry = __moduleCache.get(from), desc;
@@ -33,21 +46,338 @@ __export(exports_src, {
33
46
  default: () => vitePluginSsi
34
47
  });
35
48
  module.exports = __toCommonJS(exports_src);
49
+ var path3 = __toESM(require("path"));
50
+
51
+ // src/file-types.ts
52
+ var DEFAULT_FILE_TYPE_MAP = {
53
+ html: [".html", ".htm", ".shtml"],
54
+ js: [".js", ".mjs", ".cjs", ".ts", ".tsx", ".jsx", ".mts", ".cts"],
55
+ css: [".css", ".scss", ".sass", ".less", ".styl"],
56
+ json: [".json", ".jsonc"],
57
+ xml: [".xml", ".xhtml"],
58
+ text: [".txt", ".md", ".markdown"]
59
+ };
60
+ function matchesFileType(filePath, fileTypes, fileTypeMap) {
61
+ const ext = getFileExtension(filePath);
62
+ if (!ext)
63
+ return false;
64
+ for (const fileType of fileTypes) {
65
+ const extensions = fileTypeMap[fileType.toLowerCase()] || [];
66
+ if (extensions.includes(ext)) {
67
+ return true;
68
+ }
69
+ }
70
+ return false;
71
+ }
72
+ function getFileExtension(filePath) {
73
+ const lastDot = filePath.lastIndexOf(".");
74
+ if (lastDot === -1)
75
+ return null;
76
+ const ext = filePath.substring(lastDot);
77
+ return ext && ext.length > 1 ? ext.toLowerCase() : null;
78
+ }
79
+
80
+ // src/ssi.ts
81
+ var import_fs = require("fs");
82
+ var path = __toESM(require("path"));
83
+ function resolveIncludePath(virtualPath, includingFile, root) {
84
+ if (virtualPath.startsWith("/")) {
85
+ return path.join(root, virtualPath.slice(1));
86
+ }
87
+ const includingDir = path.dirname(includingFile);
88
+ return path.resolve(includingDir, virtualPath);
89
+ }
90
+ function normalizePath(filePath) {
91
+ return path.resolve(filePath).replace(/\\/g, "/");
92
+ }
93
+ async function processSsi(filePath, content, options) {
94
+ const { root, maxDepth, includeFileTypes = [], fileTypeMap = DEFAULT_FILE_TYPE_MAP } = options;
95
+ return processSsiRecursive(filePath, content, root, new Set, 0, maxDepth, includeFileTypes, fileTypeMap);
96
+ }
97
+ async function processSsiRecursive(filePath, content, root, seen, depth, maxDepth, includeFileTypes, fileTypeMap) {
98
+ const normalizedPath = normalizePath(filePath);
99
+ const deps = new Set;
100
+ if (seen.has(normalizedPath)) {
101
+ const seenArray = Array.from(seen);
102
+ const cycleStart = seenArray.indexOf(normalizedPath);
103
+ const cycle = seenArray.slice(cycleStart).concat(normalizedPath).join(" -> ");
104
+ return {
105
+ code: `<!-- SSI Error: Circular include detected: ${cycle} -->`,
106
+ deps
107
+ };
108
+ }
109
+ if (depth >= maxDepth) {
110
+ return {
111
+ code: `<!-- SSI Error: Maximum include depth (${maxDepth}) exceeded -->`,
112
+ deps
113
+ };
114
+ }
115
+ seen.add(normalizedPath);
116
+ const includeRegex = /<!--#include\s+virtual\s*=\s*"([^"]+)"\s*-->/g;
117
+ let match;
118
+ let result = content;
119
+ const replacements = [];
120
+ const matches = [];
121
+ while ((match = includeRegex.exec(content)) !== null) {
122
+ matches.push({
123
+ index: match.index,
124
+ length: match[0].length,
125
+ virtualPath: match[1]
126
+ });
127
+ }
128
+ for (let i = matches.length - 1;i >= 0; i--) {
129
+ const { index, length, virtualPath } = matches[i];
130
+ const matchEnd = index + length;
131
+ const resolvedPath = resolveIncludePath(virtualPath, filePath, root);
132
+ const normalizedResolvedPath = normalizePath(resolvedPath);
133
+ deps.add(normalizedResolvedPath);
134
+ try {
135
+ await import_fs.promises.access(resolvedPath);
136
+ const includedContent = await import_fs.promises.readFile(resolvedPath, "utf-8");
137
+ const shouldProcessSsi = includeFileTypes.length === 0 ? false : matchesFileType(resolvedPath, includeFileTypes, fileTypeMap);
138
+ let processed;
139
+ if (shouldProcessSsi) {
140
+ processed = await processSsiRecursive(resolvedPath, includedContent, root, new Set(seen), depth + 1, maxDepth, includeFileTypes, fileTypeMap);
141
+ } else {
142
+ processed = {
143
+ code: includedContent,
144
+ deps: new Set
145
+ };
146
+ }
147
+ processed.deps.forEach((dep) => deps.add(dep));
148
+ replacements.push({
149
+ start: index,
150
+ end: matchEnd,
151
+ replacement: processed.code
152
+ });
153
+ } catch (error) {
154
+ replacements.push({
155
+ start: index,
156
+ end: matchEnd,
157
+ replacement: `<!-- SSI Error: File not found: ${normalizedResolvedPath} -->`
158
+ });
159
+ }
160
+ }
161
+ for (const { start, end, replacement } of replacements) {
162
+ result = result.slice(0, start) + replacement + result.slice(end);
163
+ }
164
+ return { code: result, deps };
165
+ }
166
+
167
+ // src/dev-server.ts
168
+ var import_fs2 = require("fs");
169
+ var path2 = __toESM(require("path"));
170
+ function setupPreviewServer(previewServer, options) {
171
+ const outDir = previewServer.config.build.outDir || "dist";
172
+ const projectRoot = previewServer.config.root;
173
+ const distRoot = path2.resolve(projectRoot, outDir);
174
+ const processOptions = {
175
+ root: projectRoot,
176
+ maxDepth: options.maxDepth,
177
+ includeFileTypes: options.includeFileTypes,
178
+ fileTypeMap: options.fileTypeMap
179
+ };
180
+ previewServer.middlewares.use(async (req, res, next) => {
181
+ try {
182
+ if (!req.url || req.method !== "GET")
183
+ return next();
184
+ let reqPath = req.url.split("?")[0];
185
+ if (reqPath === "/" || reqPath === "") {
186
+ reqPath = "/index.html";
187
+ }
188
+ if (!reqPath.endsWith(".html"))
189
+ return next();
190
+ const filePath = path2.join(distRoot, reqPath.startsWith("/") ? reqPath.slice(1) : reqPath);
191
+ const exists = await import_fs2.promises.access(filePath).then(() => true).catch(() => false);
192
+ if (!exists)
193
+ return next();
194
+ const raw = await import_fs2.promises.readFile(filePath, "utf-8");
195
+ const relativePath = path2.relative(distRoot, filePath);
196
+ const sourceFilePath = path2.join(projectRoot, relativePath);
197
+ const result = await processSsi(sourceFilePath, raw, processOptions);
198
+ res.setHeader("Content-Type", "text/html; charset=utf-8");
199
+ res.end(result.code);
200
+ return;
201
+ } catch (err) {
202
+ return next();
203
+ }
204
+ });
205
+ }
206
+ function handleHotUpdate(ctx, server, reverseDependencyMap) {
207
+ const changedFile = normalizePath(ctx.file);
208
+ const affectedHtmlFiles = reverseDependencyMap.get(changedFile);
209
+ if (!affectedHtmlFiles || affectedHtmlFiles.size === 0) {
210
+ return;
211
+ }
212
+ const affectedModules = [];
213
+ for (const htmlFile of affectedHtmlFiles) {
214
+ const modulesByFile = server.moduleGraph.getModulesByFile(htmlFile);
215
+ if (modulesByFile && modulesByFile.size > 0) {
216
+ modulesByFile.forEach((module2) => {
217
+ affectedModules.push(module2);
218
+ });
219
+ } else {
220
+ try {
221
+ const relativePath = path2.relative(server.config.root, htmlFile);
222
+ const url = `/${relativePath.replace(/\\/g, "/")}`;
223
+ const urlModule = server.moduleGraph.urlToModuleMap.get(url);
224
+ if (urlModule) {
225
+ affectedModules.push(urlModule);
226
+ }
227
+ } catch {}
228
+ }
229
+ }
230
+ if (affectedModules.length > 0) {
231
+ affectedModules.forEach((module2) => {
232
+ try {
233
+ server.moduleGraph.invalidateModule(module2);
234
+ if ("reloadModule" in server && typeof server.reloadModule === "function") {
235
+ server.reloadModule(module2);
236
+ }
237
+ } catch {
238
+ server.ws.send({ type: "full-reload" });
239
+ }
240
+ });
241
+ return affectedModules;
242
+ }
243
+ if (affectedHtmlFiles.size > 0) {
244
+ server.ws.send({ type: "full-reload" });
245
+ }
246
+ }
247
+ async function transformIndexHtml(html, ctx, options) {
248
+ const root = ctx.server?.config.root || options.root || process.cwd();
249
+ const filename = ctx.filename || "index.html";
250
+ const filePath = path2.isAbsolute(filename) ? filename : path2.resolve(root, filename);
251
+ try {
252
+ const processOptions = {
253
+ root,
254
+ maxDepth: options.maxDepth,
255
+ includeFileTypes: options.includeFileTypes,
256
+ fileTypeMap: options.fileTypeMap
257
+ };
258
+ const result = await processSsi(filePath, html, processOptions);
259
+ return result.code;
260
+ } catch (error) {
261
+ return `<!-- SSI Error: ${error instanceof Error ? error.message : String(error)} -->
262
+ ${html}`;
263
+ }
264
+ }
265
+
266
+ // src/index.ts
267
+ function normalizeApplyOption(apply) {
268
+ if (!apply) {
269
+ return;
270
+ }
271
+ if (typeof apply === "string") {
272
+ if (apply === "preview") {
273
+ return;
274
+ }
275
+ return apply;
276
+ }
277
+ if (apply.serve === true && !apply.build) {
278
+ return "serve";
279
+ }
280
+ if (apply.build === true && !apply.serve) {
281
+ return "build";
282
+ }
283
+ return;
284
+ }
285
+ function shouldApplyInEnvironment(apply, command) {
286
+ if (!apply) {
287
+ return true;
288
+ }
289
+ if (typeof apply === "string") {
290
+ return apply === command;
291
+ }
292
+ if (command === "serve") {
293
+ return apply.serve !== false;
294
+ }
295
+ if (command === "build") {
296
+ return apply.build !== false;
297
+ }
298
+ if (command === "preview") {
299
+ return apply.preview !== false;
300
+ }
301
+ return true;
302
+ }
36
303
  function vitePluginSsi(options = {}) {
37
- const { include = [".html"], processIncludes = defaultProcessIncludes } = options;
304
+ const {
305
+ maxDepth = 10,
306
+ enforce = "pre",
307
+ apply: applyOption,
308
+ includeFileTypes = [],
309
+ fileTypeMap
310
+ } = options;
311
+ const mergedFileTypeMap = {
312
+ ...DEFAULT_FILE_TYPE_MAP,
313
+ ...fileTypeMap
314
+ };
315
+ const dependencyGraph = new Map;
316
+ const reverseDependencyMap = new Map;
317
+ let server;
318
+ let command = "serve";
319
+ let resolvedRoot;
38
320
  return {
39
321
  name: "vite-plugin-ssi",
40
- transform(code, id) {
41
- if (!include.some((ext) => id.endsWith(ext))) {
42
- return null;
322
+ enforce,
323
+ apply: normalizeApplyOption(applyOption),
324
+ configResolved(config) {
325
+ command = config.command || "serve";
326
+ resolvedRoot = config.root;
327
+ },
328
+ configureServer(_server) {
329
+ server = _server;
330
+ },
331
+ configurePreviewServer(previewServer) {
332
+ if (!shouldApplyInEnvironment(applyOption, "preview")) {
333
+ return () => {};
334
+ }
335
+ setupPreviewServer(previewServer, {
336
+ maxDepth,
337
+ includeFileTypes,
338
+ fileTypeMap: mergedFileTypeMap
339
+ });
340
+ },
341
+ async transformIndexHtml(html, ctx) {
342
+ const currentCommand = ctx.server ? ctx.server.config.command || "serve" : command;
343
+ if (!shouldApplyInEnvironment(applyOption, currentCommand)) {
344
+ return html;
43
345
  }
44
- return processIncludes(code);
346
+ const root = ctx.server?.config.root || resolvedRoot || process.cwd();
347
+ const result = await transformIndexHtml(html, ctx, {
348
+ root,
349
+ maxDepth,
350
+ includeFileTypes,
351
+ fileTypeMap: mergedFileTypeMap
352
+ });
353
+ const filename = ctx.filename || "index.html";
354
+ const filePath = path3.isAbsolute(filename) ? filename : path3.resolve(root, filename);
355
+ const normalizedFilePath = normalizePath(filePath);
356
+ const processOptions = {
357
+ root,
358
+ maxDepth,
359
+ includeFileTypes,
360
+ fileTypeMap: mergedFileTypeMap
361
+ };
362
+ const depsResult = await processSsi(filePath, html, processOptions);
363
+ dependencyGraph.set(normalizedFilePath, depsResult.deps);
364
+ depsResult.deps.forEach((dep) => {
365
+ if (!reverseDependencyMap.has(dep)) {
366
+ reverseDependencyMap.set(dep, new Set);
367
+ }
368
+ reverseDependencyMap.get(dep).add(normalizedFilePath);
369
+ });
370
+ return result;
371
+ },
372
+ handleHotUpdate(ctx) {
373
+ if (!shouldApplyInEnvironment(applyOption, command)) {
374
+ return;
375
+ }
376
+ if (!server) {
377
+ return;
378
+ }
379
+ return handleHotUpdate(ctx, server, reverseDependencyMap);
45
380
  }
46
381
  };
47
382
  }
48
- function defaultProcessIncludes(content) {
49
- return content.replace(/<!--\s*#include\s+(virtual|file)="([^"]+)"\s*-->/g, (match, type, path) => {
50
- return `<!-- Unimplemented SSI include: ${type} - ${path} -->`;
51
- });
52
- }
53
383
  })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@catfyrr/vite-plugin-ssi",
3
- "version": "0.0.1",
3
+ "version": "0.0.2",
4
4
  "description": "Fully Apache/Nginx compatible Server-Side Includes (SSI) Vite plugin",
5
5
  "type": "module",
6
6
  "exports": {
@@ -25,8 +25,7 @@
25
25
  "test:watch": "bun test --watch",
26
26
  "lint": "eslint src tests",
27
27
  "lint:fix": "eslint src tests --fix",
28
- "prepublishOnly": "bun run build",
29
- "prepare": "husky install"
28
+ "prepublishOnly": "bun run build"
30
29
  },
31
30
  "keywords": [
32
31
  "vite",