@catfyrr/vite-plugin-ssi 0.0.3 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/dev-server.d.ts +25 -0
- package/dist/file-types.d.ts +19 -0
- package/dist/{index.js → index.cjs} +31 -12
- package/dist/index.d.ts +86 -0
- package/dist/index.mjs +338 -0
- package/dist/ssi.d.ts +19 -0
- package/package.json +8 -7
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { ViteDevServer, PreviewServer } from 'vite';
|
|
2
|
+
export interface DevServerOptions {
|
|
3
|
+
maxDepth: number;
|
|
4
|
+
includeFileTypes?: string[];
|
|
5
|
+
fileTypeMap?: import('./file-types').FileTypeMap;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Sets up preview server middleware to process SSI on-the-fly
|
|
9
|
+
*/
|
|
10
|
+
export declare function setupPreviewServer(previewServer: PreviewServer, options: DevServerOptions): void;
|
|
11
|
+
/**
|
|
12
|
+
* Handles HMR updates when SSI dependency files change
|
|
13
|
+
*/
|
|
14
|
+
export declare function handleHotUpdate(ctx: {
|
|
15
|
+
file: string;
|
|
16
|
+
}, server: ViteDevServer, reverseDependencyMap: Map<string, Set<string>>): Array<import('vite').ModuleNode> | void;
|
|
17
|
+
/**
|
|
18
|
+
* Transforms index HTML with SSI processing
|
|
19
|
+
*/
|
|
20
|
+
export declare function transformIndexHtml(html: string, ctx: {
|
|
21
|
+
filename?: string;
|
|
22
|
+
server?: ViteDevServer;
|
|
23
|
+
}, options: DevServerOptions & {
|
|
24
|
+
root?: string;
|
|
25
|
+
}): Promise<string>;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File type to extension mappings for intelligent SSI processing
|
|
3
|
+
*/
|
|
4
|
+
export interface FileTypeMap {
|
|
5
|
+
[key: string]: string[];
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Default file type mappings
|
|
9
|
+
* Maps file type names to their associated extensions
|
|
10
|
+
*/
|
|
11
|
+
export declare const DEFAULT_FILE_TYPE_MAP: FileTypeMap;
|
|
12
|
+
/**
|
|
13
|
+
* Checks if a file path matches any of the specified file types
|
|
14
|
+
*/
|
|
15
|
+
export declare function matchesFileType(filePath: string, fileTypes: string[], fileTypeMap: FileTypeMap): boolean;
|
|
16
|
+
/**
|
|
17
|
+
* Checks if a file path matches HTML file extensions (for top-level processing)
|
|
18
|
+
*/
|
|
19
|
+
export declare function isHtmlFile(filePath: string): boolean;
|
|
@@ -1,42 +1,62 @@
|
|
|
1
|
-
|
|
2
|
-
(function(exports, require, module, __filename, __dirname) {var __create = Object.create;
|
|
1
|
+
var __create = Object.create;
|
|
3
2
|
var __getProtoOf = Object.getPrototypeOf;
|
|
4
3
|
var __defProp = Object.defineProperty;
|
|
5
4
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
5
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
7
6
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
function __accessProp(key) {
|
|
8
|
+
return this[key];
|
|
9
|
+
}
|
|
10
|
+
var __toESMCache_node;
|
|
11
|
+
var __toESMCache_esm;
|
|
8
12
|
var __toESM = (mod, isNodeMode, target) => {
|
|
13
|
+
var canCache = mod != null && typeof mod === "object";
|
|
14
|
+
if (canCache) {
|
|
15
|
+
var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
|
|
16
|
+
var cached = cache.get(mod);
|
|
17
|
+
if (cached)
|
|
18
|
+
return cached;
|
|
19
|
+
}
|
|
9
20
|
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
10
21
|
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
11
22
|
for (let key of __getOwnPropNames(mod))
|
|
12
23
|
if (!__hasOwnProp.call(to, key))
|
|
13
24
|
__defProp(to, key, {
|
|
14
|
-
get: (
|
|
25
|
+
get: __accessProp.bind(mod, key),
|
|
15
26
|
enumerable: true
|
|
16
27
|
});
|
|
28
|
+
if (canCache)
|
|
29
|
+
cache.set(mod, to);
|
|
17
30
|
return to;
|
|
18
31
|
};
|
|
19
|
-
var __moduleCache = /* @__PURE__ */ new WeakMap;
|
|
20
32
|
var __toCommonJS = (from) => {
|
|
21
|
-
var entry = __moduleCache.get(from), desc;
|
|
33
|
+
var entry = (__moduleCache ??= new WeakMap).get(from), desc;
|
|
22
34
|
if (entry)
|
|
23
35
|
return entry;
|
|
24
36
|
entry = __defProp({}, "__esModule", { value: true });
|
|
25
|
-
if (from && typeof from === "object" || typeof from === "function")
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
37
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
38
|
+
for (var key of __getOwnPropNames(from))
|
|
39
|
+
if (!__hasOwnProp.call(entry, key))
|
|
40
|
+
__defProp(entry, key, {
|
|
41
|
+
get: __accessProp.bind(from, key),
|
|
42
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
43
|
+
});
|
|
44
|
+
}
|
|
30
45
|
__moduleCache.set(from, entry);
|
|
31
46
|
return entry;
|
|
32
47
|
};
|
|
48
|
+
var __moduleCache;
|
|
49
|
+
var __returnValue = (v) => v;
|
|
50
|
+
function __exportSetter(name, newValue) {
|
|
51
|
+
this[name] = __returnValue.bind(null, newValue);
|
|
52
|
+
}
|
|
33
53
|
var __export = (target, all) => {
|
|
34
54
|
for (var name in all)
|
|
35
55
|
__defProp(target, name, {
|
|
36
56
|
get: all[name],
|
|
37
57
|
enumerable: true,
|
|
38
58
|
configurable: true,
|
|
39
|
-
set: (
|
|
59
|
+
set: __exportSetter.bind(all, name)
|
|
40
60
|
});
|
|
41
61
|
};
|
|
42
62
|
|
|
@@ -380,4 +400,3 @@ function vitePluginSsi(options = {}) {
|
|
|
380
400
|
}
|
|
381
401
|
};
|
|
382
402
|
}
|
|
383
|
-
})
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import type { Plugin } from 'vite';
|
|
2
|
+
import { type FileTypeMap } from './file-types';
|
|
3
|
+
/**
|
|
4
|
+
* File type to extension mappings for intelligent SSI processing.
|
|
5
|
+
* Maps file type names (e.g., 'js', 'css', 'html') to arrays of file extensions.
|
|
6
|
+
* Used to determine which file types should be processed when included via SSI.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```ts
|
|
10
|
+
* const fileTypeMap: FileTypeMap = {
|
|
11
|
+
* js: ['.js', '.ts', '.jsx'],
|
|
12
|
+
* css: ['.css', '.scss']
|
|
13
|
+
* };
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
export type { FileTypeMap } from './file-types';
|
|
17
|
+
/**
|
|
18
|
+
* Configuration options for the Vite SSI plugin.
|
|
19
|
+
*
|
|
20
|
+
* All options are optional and have sensible defaults.
|
|
21
|
+
*/
|
|
22
|
+
export interface VitePluginSsiOptions {
|
|
23
|
+
/**
|
|
24
|
+
* Maximum depth for recursive includes
|
|
25
|
+
* @default 10
|
|
26
|
+
*/
|
|
27
|
+
maxDepth?: number;
|
|
28
|
+
/**
|
|
29
|
+
* When to run this plugin in the pipeline
|
|
30
|
+
* @default 'pre'
|
|
31
|
+
*/
|
|
32
|
+
enforce?: 'pre' | 'post';
|
|
33
|
+
/**
|
|
34
|
+
* Apply plugin only in specific environments
|
|
35
|
+
* @default undefined (applies to all environments)
|
|
36
|
+
*/
|
|
37
|
+
apply?: 'serve' | 'build' | 'preview' | {
|
|
38
|
+
serve?: boolean;
|
|
39
|
+
build?: boolean;
|
|
40
|
+
preview?: boolean;
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* File types to apply SSI processing to for included files (at any depth)
|
|
44
|
+
* SSI always applies to top-level HTML files (.html, .htm, .shtml)
|
|
45
|
+
* When files are included via SSI, they will also be processed if they match these types
|
|
46
|
+
* Examples: ['js', 'html', 'css'] - will process .js/.ts/.mjs/etc., .html/.htm/.shtml, .css/.scss/etc.
|
|
47
|
+
* @default [] (only process top-level HTML files)
|
|
48
|
+
*/
|
|
49
|
+
includeFileTypes?: string[];
|
|
50
|
+
/**
|
|
51
|
+
* Custom file type to extension mappings
|
|
52
|
+
* Allows overriding or extending the default mappings
|
|
53
|
+
*/
|
|
54
|
+
fileTypeMap?: FileTypeMap;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Vite plugin for Server-Side Includes (SSI) processing.
|
|
58
|
+
*
|
|
59
|
+
* This plugin processes Apache/Nginx-compatible SSI directives in HTML files,
|
|
60
|
+
* including support for `<!--#include virtual="..." -->` and `<!--#include file="..." -->`.
|
|
61
|
+
*
|
|
62
|
+
* Features:
|
|
63
|
+
* - Supports recursive includes with configurable depth limits
|
|
64
|
+
* - Hot Module Replacement (HMR) support for included files
|
|
65
|
+
* - Works in dev, build, and preview modes
|
|
66
|
+
* - Configurable file type processing
|
|
67
|
+
* - Custom file type mappings
|
|
68
|
+
*
|
|
69
|
+
* @param options - Configuration options for the SSI plugin
|
|
70
|
+
* @returns A Vite plugin instance
|
|
71
|
+
*
|
|
72
|
+
* @example
|
|
73
|
+
* ```ts
|
|
74
|
+
* import vitePluginSsi from '@catfyrr/vite-plugin-ssi';
|
|
75
|
+
*
|
|
76
|
+
* export default defineConfig({
|
|
77
|
+
* plugins: [
|
|
78
|
+
* vitePluginSsi({
|
|
79
|
+
* maxDepth: 5,
|
|
80
|
+
* includeFileTypes: ['html', 'js']
|
|
81
|
+
* })
|
|
82
|
+
* ]
|
|
83
|
+
* });
|
|
84
|
+
* ```
|
|
85
|
+
*/
|
|
86
|
+
export default function vitePluginSsi(options?: VitePluginSsiOptions): Plugin;
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import * as path3 from "path";
|
|
3
|
+
|
|
4
|
+
// src/file-types.ts
|
|
5
|
+
var DEFAULT_FILE_TYPE_MAP = {
|
|
6
|
+
html: [".html", ".htm", ".shtml"],
|
|
7
|
+
js: [".js", ".mjs", ".cjs", ".ts", ".tsx", ".jsx", ".mts", ".cts"],
|
|
8
|
+
css: [".css", ".scss", ".sass", ".less", ".styl"],
|
|
9
|
+
json: [".json", ".jsonc"],
|
|
10
|
+
xml: [".xml", ".xhtml"],
|
|
11
|
+
text: [".txt", ".md", ".markdown"]
|
|
12
|
+
};
|
|
13
|
+
function matchesFileType(filePath, fileTypes, fileTypeMap) {
|
|
14
|
+
const ext = getFileExtension(filePath);
|
|
15
|
+
if (!ext)
|
|
16
|
+
return false;
|
|
17
|
+
for (const fileType of fileTypes) {
|
|
18
|
+
const extensions = fileTypeMap[fileType.toLowerCase()] || [];
|
|
19
|
+
if (extensions.includes(ext)) {
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
function getFileExtension(filePath) {
|
|
26
|
+
const lastDot = filePath.lastIndexOf(".");
|
|
27
|
+
if (lastDot === -1)
|
|
28
|
+
return null;
|
|
29
|
+
const ext = filePath.substring(lastDot);
|
|
30
|
+
return ext && ext.length > 1 ? ext.toLowerCase() : null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// src/ssi.ts
|
|
34
|
+
import { promises as fs } from "fs";
|
|
35
|
+
import * as path from "path";
|
|
36
|
+
function resolveIncludePath(virtualPath, includingFile, root) {
|
|
37
|
+
if (virtualPath.startsWith("/")) {
|
|
38
|
+
return path.join(root, virtualPath.slice(1));
|
|
39
|
+
}
|
|
40
|
+
const includingDir = path.dirname(includingFile);
|
|
41
|
+
return path.resolve(includingDir, virtualPath);
|
|
42
|
+
}
|
|
43
|
+
function normalizePath(filePath) {
|
|
44
|
+
return path.resolve(filePath).replace(/\\/g, "/");
|
|
45
|
+
}
|
|
46
|
+
async function processSsi(filePath, content, options) {
|
|
47
|
+
const { root, maxDepth, includeFileTypes = [], fileTypeMap = DEFAULT_FILE_TYPE_MAP } = options;
|
|
48
|
+
return processSsiRecursive(filePath, content, root, new Set, 0, maxDepth, includeFileTypes, fileTypeMap);
|
|
49
|
+
}
|
|
50
|
+
async function processSsiRecursive(filePath, content, root, seen, depth, maxDepth, includeFileTypes, fileTypeMap) {
|
|
51
|
+
const normalizedPath = normalizePath(filePath);
|
|
52
|
+
const deps = new Set;
|
|
53
|
+
if (seen.has(normalizedPath)) {
|
|
54
|
+
const seenArray = Array.from(seen);
|
|
55
|
+
const cycleStart = seenArray.indexOf(normalizedPath);
|
|
56
|
+
const cycle = seenArray.slice(cycleStart).concat(normalizedPath).join(" -> ");
|
|
57
|
+
return {
|
|
58
|
+
code: `<!-- SSI Error: Circular include detected: ${cycle} -->`,
|
|
59
|
+
deps
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
if (depth >= maxDepth) {
|
|
63
|
+
return {
|
|
64
|
+
code: `<!-- SSI Error: Maximum include depth (${maxDepth}) exceeded -->`,
|
|
65
|
+
deps
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
seen.add(normalizedPath);
|
|
69
|
+
const includeRegex = /<!--#include\s+virtual\s*=\s*"([^"]+)"\s*-->/g;
|
|
70
|
+
let match;
|
|
71
|
+
let result = content;
|
|
72
|
+
const replacements = [];
|
|
73
|
+
const matches = [];
|
|
74
|
+
while ((match = includeRegex.exec(content)) !== null) {
|
|
75
|
+
matches.push({
|
|
76
|
+
index: match.index,
|
|
77
|
+
length: match[0].length,
|
|
78
|
+
virtualPath: match[1]
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
for (let i = matches.length - 1;i >= 0; i--) {
|
|
82
|
+
const { index, length, virtualPath } = matches[i];
|
|
83
|
+
const matchEnd = index + length;
|
|
84
|
+
const resolvedPath = resolveIncludePath(virtualPath, filePath, root);
|
|
85
|
+
const normalizedResolvedPath = normalizePath(resolvedPath);
|
|
86
|
+
deps.add(normalizedResolvedPath);
|
|
87
|
+
try {
|
|
88
|
+
await fs.access(resolvedPath);
|
|
89
|
+
const includedContent = await fs.readFile(resolvedPath, "utf-8");
|
|
90
|
+
const shouldProcessSsi = includeFileTypes.length === 0 ? false : matchesFileType(resolvedPath, includeFileTypes, fileTypeMap);
|
|
91
|
+
let processed;
|
|
92
|
+
if (shouldProcessSsi) {
|
|
93
|
+
processed = await processSsiRecursive(resolvedPath, includedContent, root, new Set(seen), depth + 1, maxDepth, includeFileTypes, fileTypeMap);
|
|
94
|
+
} else {
|
|
95
|
+
processed = {
|
|
96
|
+
code: includedContent,
|
|
97
|
+
deps: new Set
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
processed.deps.forEach((dep) => deps.add(dep));
|
|
101
|
+
replacements.push({
|
|
102
|
+
start: index,
|
|
103
|
+
end: matchEnd,
|
|
104
|
+
replacement: processed.code
|
|
105
|
+
});
|
|
106
|
+
} catch (error) {
|
|
107
|
+
replacements.push({
|
|
108
|
+
start: index,
|
|
109
|
+
end: matchEnd,
|
|
110
|
+
replacement: `<!-- SSI Error: File not found: ${normalizedResolvedPath} -->`
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
for (const { start, end, replacement } of replacements) {
|
|
115
|
+
result = result.slice(0, start) + replacement + result.slice(end);
|
|
116
|
+
}
|
|
117
|
+
return { code: result, deps };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// src/dev-server.ts
|
|
121
|
+
import { promises as fs2 } from "fs";
|
|
122
|
+
import * as path2 from "path";
|
|
123
|
+
function setupPreviewServer(previewServer, options) {
|
|
124
|
+
const outDir = previewServer.config.build.outDir || "dist";
|
|
125
|
+
const projectRoot = previewServer.config.root;
|
|
126
|
+
const distRoot = path2.resolve(projectRoot, outDir);
|
|
127
|
+
const processOptions = {
|
|
128
|
+
root: projectRoot,
|
|
129
|
+
maxDepth: options.maxDepth,
|
|
130
|
+
includeFileTypes: options.includeFileTypes,
|
|
131
|
+
fileTypeMap: options.fileTypeMap
|
|
132
|
+
};
|
|
133
|
+
previewServer.middlewares.use(async (req, res, next) => {
|
|
134
|
+
try {
|
|
135
|
+
if (!req.url || req.method !== "GET")
|
|
136
|
+
return next();
|
|
137
|
+
let reqPath = req.url.split("?")[0];
|
|
138
|
+
if (reqPath === "/" || reqPath === "") {
|
|
139
|
+
reqPath = "/index.html";
|
|
140
|
+
}
|
|
141
|
+
if (!reqPath.endsWith(".html"))
|
|
142
|
+
return next();
|
|
143
|
+
const filePath = path2.join(distRoot, reqPath.startsWith("/") ? reqPath.slice(1) : reqPath);
|
|
144
|
+
const exists = await fs2.access(filePath).then(() => true).catch(() => false);
|
|
145
|
+
if (!exists)
|
|
146
|
+
return next();
|
|
147
|
+
const raw = await fs2.readFile(filePath, "utf-8");
|
|
148
|
+
const relativePath = path2.relative(distRoot, filePath);
|
|
149
|
+
const sourceFilePath = path2.join(projectRoot, relativePath);
|
|
150
|
+
const result = await processSsi(sourceFilePath, raw, processOptions);
|
|
151
|
+
res.setHeader("Content-Type", "text/html; charset=utf-8");
|
|
152
|
+
res.end(result.code);
|
|
153
|
+
return;
|
|
154
|
+
} catch (err) {
|
|
155
|
+
return next();
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
function handleHotUpdate(ctx, server, reverseDependencyMap) {
|
|
160
|
+
const changedFile = normalizePath(ctx.file);
|
|
161
|
+
const affectedHtmlFiles = reverseDependencyMap.get(changedFile);
|
|
162
|
+
if (!affectedHtmlFiles || affectedHtmlFiles.size === 0) {
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
const affectedModules = [];
|
|
166
|
+
for (const htmlFile of affectedHtmlFiles) {
|
|
167
|
+
const modulesByFile = server.moduleGraph.getModulesByFile(htmlFile);
|
|
168
|
+
if (modulesByFile && modulesByFile.size > 0) {
|
|
169
|
+
modulesByFile.forEach((module) => {
|
|
170
|
+
affectedModules.push(module);
|
|
171
|
+
});
|
|
172
|
+
} else {
|
|
173
|
+
try {
|
|
174
|
+
const relativePath = path2.relative(server.config.root, htmlFile);
|
|
175
|
+
const url = `/${relativePath.replace(/\\/g, "/")}`;
|
|
176
|
+
const urlModule = server.moduleGraph.urlToModuleMap.get(url);
|
|
177
|
+
if (urlModule) {
|
|
178
|
+
affectedModules.push(urlModule);
|
|
179
|
+
}
|
|
180
|
+
} catch {}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
if (affectedModules.length > 0) {
|
|
184
|
+
affectedModules.forEach((module) => {
|
|
185
|
+
try {
|
|
186
|
+
server.moduleGraph.invalidateModule(module);
|
|
187
|
+
if ("reloadModule" in server && typeof server.reloadModule === "function") {
|
|
188
|
+
server.reloadModule(module);
|
|
189
|
+
}
|
|
190
|
+
} catch {
|
|
191
|
+
server.ws.send({ type: "full-reload" });
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
return affectedModules;
|
|
195
|
+
}
|
|
196
|
+
if (affectedHtmlFiles.size > 0) {
|
|
197
|
+
server.ws.send({ type: "full-reload" });
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
async function transformIndexHtml(html, ctx, options) {
|
|
201
|
+
const root = ctx.server?.config.root || options.root || process.cwd();
|
|
202
|
+
const filename = ctx.filename || "index.html";
|
|
203
|
+
const filePath = path2.isAbsolute(filename) ? filename : path2.resolve(root, filename);
|
|
204
|
+
try {
|
|
205
|
+
const processOptions = {
|
|
206
|
+
root,
|
|
207
|
+
maxDepth: options.maxDepth,
|
|
208
|
+
includeFileTypes: options.includeFileTypes,
|
|
209
|
+
fileTypeMap: options.fileTypeMap
|
|
210
|
+
};
|
|
211
|
+
const result = await processSsi(filePath, html, processOptions);
|
|
212
|
+
return result.code;
|
|
213
|
+
} catch (error) {
|
|
214
|
+
return `<!-- SSI Error: ${error instanceof Error ? error.message : String(error)} -->
|
|
215
|
+
${html}`;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// src/index.ts
|
|
220
|
+
function normalizeApplyOption(apply) {
|
|
221
|
+
if (!apply) {
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
if (typeof apply === "string") {
|
|
225
|
+
if (apply === "preview") {
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
return apply;
|
|
229
|
+
}
|
|
230
|
+
if (apply.serve === true && !apply.build) {
|
|
231
|
+
return "serve";
|
|
232
|
+
}
|
|
233
|
+
if (apply.build === true && !apply.serve) {
|
|
234
|
+
return "build";
|
|
235
|
+
}
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
function shouldApplyInEnvironment(apply, command) {
|
|
239
|
+
if (!apply) {
|
|
240
|
+
return true;
|
|
241
|
+
}
|
|
242
|
+
if (typeof apply === "string") {
|
|
243
|
+
return apply === command;
|
|
244
|
+
}
|
|
245
|
+
if (command === "serve") {
|
|
246
|
+
return apply.serve !== false;
|
|
247
|
+
}
|
|
248
|
+
if (command === "build") {
|
|
249
|
+
return apply.build !== false;
|
|
250
|
+
}
|
|
251
|
+
if (command === "preview") {
|
|
252
|
+
return apply.preview !== false;
|
|
253
|
+
}
|
|
254
|
+
return true;
|
|
255
|
+
}
|
|
256
|
+
function vitePluginSsi(options = {}) {
|
|
257
|
+
const {
|
|
258
|
+
maxDepth = 10,
|
|
259
|
+
enforce = "pre",
|
|
260
|
+
apply: applyOption,
|
|
261
|
+
includeFileTypes = [],
|
|
262
|
+
fileTypeMap
|
|
263
|
+
} = options;
|
|
264
|
+
const mergedFileTypeMap = {
|
|
265
|
+
...DEFAULT_FILE_TYPE_MAP,
|
|
266
|
+
...fileTypeMap
|
|
267
|
+
};
|
|
268
|
+
const dependencyGraph = new Map;
|
|
269
|
+
const reverseDependencyMap = new Map;
|
|
270
|
+
let server;
|
|
271
|
+
let command = "serve";
|
|
272
|
+
let resolvedRoot;
|
|
273
|
+
return {
|
|
274
|
+
name: "vite-plugin-ssi",
|
|
275
|
+
enforce,
|
|
276
|
+
apply: normalizeApplyOption(applyOption),
|
|
277
|
+
configResolved(config) {
|
|
278
|
+
command = config.command || "serve";
|
|
279
|
+
resolvedRoot = config.root;
|
|
280
|
+
},
|
|
281
|
+
configureServer(_server) {
|
|
282
|
+
server = _server;
|
|
283
|
+
},
|
|
284
|
+
configurePreviewServer(previewServer) {
|
|
285
|
+
if (!shouldApplyInEnvironment(applyOption, "preview")) {
|
|
286
|
+
return () => {};
|
|
287
|
+
}
|
|
288
|
+
setupPreviewServer(previewServer, {
|
|
289
|
+
maxDepth,
|
|
290
|
+
includeFileTypes,
|
|
291
|
+
fileTypeMap: mergedFileTypeMap
|
|
292
|
+
});
|
|
293
|
+
},
|
|
294
|
+
async transformIndexHtml(html, ctx) {
|
|
295
|
+
const currentCommand = ctx.server ? ctx.server.config.command || "serve" : command;
|
|
296
|
+
if (!shouldApplyInEnvironment(applyOption, currentCommand)) {
|
|
297
|
+
return html;
|
|
298
|
+
}
|
|
299
|
+
const root = ctx.server?.config.root || resolvedRoot || process.cwd();
|
|
300
|
+
const result = await transformIndexHtml(html, ctx, {
|
|
301
|
+
root,
|
|
302
|
+
maxDepth,
|
|
303
|
+
includeFileTypes,
|
|
304
|
+
fileTypeMap: mergedFileTypeMap
|
|
305
|
+
});
|
|
306
|
+
const filename = ctx.filename || "index.html";
|
|
307
|
+
const filePath = path3.isAbsolute(filename) ? filename : path3.resolve(root, filename);
|
|
308
|
+
const normalizedFilePath = normalizePath(filePath);
|
|
309
|
+
const processOptions = {
|
|
310
|
+
root,
|
|
311
|
+
maxDepth,
|
|
312
|
+
includeFileTypes,
|
|
313
|
+
fileTypeMap: mergedFileTypeMap
|
|
314
|
+
};
|
|
315
|
+
const depsResult = await processSsi(filePath, html, processOptions);
|
|
316
|
+
dependencyGraph.set(normalizedFilePath, depsResult.deps);
|
|
317
|
+
depsResult.deps.forEach((dep) => {
|
|
318
|
+
if (!reverseDependencyMap.has(dep)) {
|
|
319
|
+
reverseDependencyMap.set(dep, new Set);
|
|
320
|
+
}
|
|
321
|
+
reverseDependencyMap.get(dep).add(normalizedFilePath);
|
|
322
|
+
});
|
|
323
|
+
return result;
|
|
324
|
+
},
|
|
325
|
+
handleHotUpdate(ctx) {
|
|
326
|
+
if (!shouldApplyInEnvironment(applyOption, command)) {
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
if (!server) {
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
return handleHotUpdate(ctx, server, reverseDependencyMap);
|
|
333
|
+
}
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
export {
|
|
337
|
+
vitePluginSsi as default
|
|
338
|
+
};
|
package/dist/ssi.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type FileTypeMap } from './file-types';
|
|
2
|
+
export interface ProcessResult {
|
|
3
|
+
code: string;
|
|
4
|
+
deps: Set<string>;
|
|
5
|
+
}
|
|
6
|
+
export interface ProcessSsiOptions {
|
|
7
|
+
root: string;
|
|
8
|
+
maxDepth: number;
|
|
9
|
+
includeFileTypes?: string[];
|
|
10
|
+
fileTypeMap?: FileTypeMap;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Normalizes a file path to absolute path for consistent comparison
|
|
14
|
+
*/
|
|
15
|
+
export declare function normalizePath(filePath: string): string;
|
|
16
|
+
/**
|
|
17
|
+
* Processes SSI includes recursively
|
|
18
|
+
*/
|
|
19
|
+
export declare function processSsi(filePath: string, content: string, options: ProcessSsiOptions): Promise<ProcessResult>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@catfyrr/vite-plugin-ssi",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.1.0",
|
|
4
4
|
"description": "Fully Apache/Nginx compatible Server-Side Includes (SSI) Vite plugin",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -18,9 +18,10 @@
|
|
|
18
18
|
"node": ">=18.0.0"
|
|
19
19
|
},
|
|
20
20
|
"scripts": {
|
|
21
|
-
"build": "bun run build:esm && bun run build:cjs",
|
|
22
|
-
"build:esm": "bun build ./src/index.ts --
|
|
23
|
-
"build:cjs": "bun build ./src/index.ts --
|
|
21
|
+
"build": "bun run build:esm && bun run build:cjs && bun run build:types",
|
|
22
|
+
"build:esm": "bun build ./src/index.ts --outfile dist/index.mjs --target node --format esm",
|
|
23
|
+
"build:cjs": "bun build ./src/index.ts --outfile dist/index.cjs --target node --format cjs",
|
|
24
|
+
"build:types": "tsc --emitDeclarationOnly --declaration --outDir dist",
|
|
24
25
|
"test": "bun test",
|
|
25
26
|
"test:watch": "bun test --watch",
|
|
26
27
|
"lint": "eslint src tests",
|
|
@@ -38,14 +39,14 @@
|
|
|
38
39
|
"license": "MIT",
|
|
39
40
|
"repository": {
|
|
40
41
|
"type": "git",
|
|
41
|
-
"url": "https://github.com/catFurr/vite-plugin-ssi.git"
|
|
42
|
+
"url": "git+https://github.com/catFurr/vite-plugin-ssi.git"
|
|
42
43
|
},
|
|
43
44
|
"bugs": {
|
|
44
45
|
"url": "https://github.com/catFurr/vite-plugin-ssi/issues"
|
|
45
46
|
},
|
|
46
47
|
"homepage": "https://github.com/catFurr/vite-plugin-ssi#readme",
|
|
47
48
|
"peerDependencies": {
|
|
48
|
-
"vite": "^5.0.0"
|
|
49
|
+
"vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0"
|
|
49
50
|
},
|
|
50
51
|
"devDependencies": {
|
|
51
52
|
"@types/node": "^20.10.0",
|
|
@@ -58,6 +59,6 @@
|
|
|
58
59
|
"husky": "^8.0.3",
|
|
59
60
|
"prettier": "^3.1.0",
|
|
60
61
|
"typescript": "^5.3.2",
|
|
61
|
-
"vite": "^
|
|
62
|
+
"vite": "^6.0.0"
|
|
62
63
|
}
|
|
63
64
|
}
|