vite-multi-spa 0.0.0 → 1.0.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/index.d.ts +86 -0
- package/dist/index.js +125 -0
- package/package.json +36 -9
- package/readme.md +32 -1
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { type Connect, type IndexHtmlTransformHook } from 'vite';
|
|
2
|
+
export type ViteMultiSpaOptions = {
|
|
3
|
+
/**
|
|
4
|
+
* Where your pages are located. Only `.html` files are recognized.
|
|
5
|
+
* @default 'src/pages'
|
|
6
|
+
*/
|
|
7
|
+
pagesRoot?: string;
|
|
8
|
+
/**
|
|
9
|
+
* Transform the HTML of your pages.
|
|
10
|
+
* @see https://vite.dev/guide/api-plugin#transformindexhtml
|
|
11
|
+
*/
|
|
12
|
+
transformPageHtml?: IndexHtmlTransformHook | readonly IndexHtmlTransformHook[];
|
|
13
|
+
};
|
|
14
|
+
export default function viteMultiSpa(options?: ViteMultiSpaOptions): ({
|
|
15
|
+
readonly name: "vite-multi-spa";
|
|
16
|
+
readonly configResolved: (config: {
|
|
17
|
+
root: string;
|
|
18
|
+
}) => void;
|
|
19
|
+
readonly configureServer: {
|
|
20
|
+
readonly order: "pre";
|
|
21
|
+
readonly handler: (server: {
|
|
22
|
+
middlewares: Connect.Server;
|
|
23
|
+
}) => void;
|
|
24
|
+
};
|
|
25
|
+
readonly transformIndexHtml: {
|
|
26
|
+
order: "pre";
|
|
27
|
+
handler: IndexHtmlTransformHook;
|
|
28
|
+
} | undefined;
|
|
29
|
+
readonly buildStart: (this: {
|
|
30
|
+
environment: {
|
|
31
|
+
name: string;
|
|
32
|
+
};
|
|
33
|
+
} & {
|
|
34
|
+
emitFile: (file: {
|
|
35
|
+
type: "chunk";
|
|
36
|
+
id: string;
|
|
37
|
+
}) => void;
|
|
38
|
+
}) => void;
|
|
39
|
+
readonly generateBundle: {
|
|
40
|
+
order: "post";
|
|
41
|
+
handler(this: {
|
|
42
|
+
environment: {
|
|
43
|
+
name: string;
|
|
44
|
+
};
|
|
45
|
+
}, _options: import("rollup").NormalizedOutputOptions, bundle: import("rollup").OutputBundle): void;
|
|
46
|
+
};
|
|
47
|
+
} | {
|
|
48
|
+
name: string;
|
|
49
|
+
transformIndexHtml: {
|
|
50
|
+
order: "pre";
|
|
51
|
+
handler: IndexHtmlTransformHook;
|
|
52
|
+
};
|
|
53
|
+
})[] | {
|
|
54
|
+
readonly name: "vite-multi-spa";
|
|
55
|
+
readonly configResolved: (config: {
|
|
56
|
+
root: string;
|
|
57
|
+
}) => void;
|
|
58
|
+
readonly configureServer: {
|
|
59
|
+
readonly order: "pre";
|
|
60
|
+
readonly handler: (server: {
|
|
61
|
+
middlewares: Connect.Server;
|
|
62
|
+
}) => void;
|
|
63
|
+
};
|
|
64
|
+
readonly transformIndexHtml: {
|
|
65
|
+
order: "pre";
|
|
66
|
+
handler: IndexHtmlTransformHook;
|
|
67
|
+
} | undefined;
|
|
68
|
+
readonly buildStart: (this: {
|
|
69
|
+
environment: {
|
|
70
|
+
name: string;
|
|
71
|
+
};
|
|
72
|
+
} & {
|
|
73
|
+
emitFile: (file: {
|
|
74
|
+
type: "chunk";
|
|
75
|
+
id: string;
|
|
76
|
+
}) => void;
|
|
77
|
+
}) => void;
|
|
78
|
+
readonly generateBundle: {
|
|
79
|
+
order: "post";
|
|
80
|
+
handler(this: {
|
|
81
|
+
environment: {
|
|
82
|
+
name: string;
|
|
83
|
+
};
|
|
84
|
+
}, _options: import("rollup").NormalizedOutputOptions, bundle: import("rollup").OutputBundle): void;
|
|
85
|
+
};
|
|
86
|
+
};
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { normalizePath, } from 'vite';
|
|
4
|
+
export default function viteMultiSpa(options = {}) {
|
|
5
|
+
let root;
|
|
6
|
+
const pagesRoot = options.pagesRoot
|
|
7
|
+
? normalizePath(options.pagesRoot).replace(/(^\/|\/$)/g, '')
|
|
8
|
+
: 'src/pages';
|
|
9
|
+
// Given a request URL, return the corresponding page URL. This is used to
|
|
10
|
+
// serve documents (and their relative assets) from the pages root.
|
|
11
|
+
const resolvePageUrl = (url) => {
|
|
12
|
+
const pageURL = '/' + pagesRoot + url;
|
|
13
|
+
if (pageURL.endsWith('/')) {
|
|
14
|
+
const pageFile = path.join(root, pageURL, 'index.html');
|
|
15
|
+
if (fs.existsSync(pageFile)) {
|
|
16
|
+
return pageURL;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
const pageFile = path.join(root, pageURL + '.html');
|
|
21
|
+
if (fs.existsSync(pageFile)) {
|
|
22
|
+
return pageURL;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
const transforms = Array.isArray(options.transformPageHtml)
|
|
27
|
+
? options.transformPageHtml
|
|
28
|
+
: options.transformPageHtml
|
|
29
|
+
? [options.transformPageHtml]
|
|
30
|
+
: [];
|
|
31
|
+
// In case another plugin emits HTML files, wrap custom transforms so they
|
|
32
|
+
// only run on /index.html and those in the pages root.
|
|
33
|
+
const transformPagesOnly = (transform) => function (html, ctx) {
|
|
34
|
+
return ctx.path.startsWith('/' + pagesRoot + '/') ||
|
|
35
|
+
ctx.path === '/index.html'
|
|
36
|
+
? transform.call(this, html, ctx)
|
|
37
|
+
: undefined;
|
|
38
|
+
};
|
|
39
|
+
// The main plugin doesn't use the Plugin type directly, as that more easily
|
|
40
|
+
// leads to assignability errors as the Vite API evolves.
|
|
41
|
+
const mainPlugin = {
|
|
42
|
+
name: 'vite-multi-spa',
|
|
43
|
+
configResolved(config) {
|
|
44
|
+
root = config.root;
|
|
45
|
+
},
|
|
46
|
+
configureServer: {
|
|
47
|
+
order: 'pre',
|
|
48
|
+
handler(server) {
|
|
49
|
+
server.middlewares.use((req, _res, next) => {
|
|
50
|
+
const url = req.url;
|
|
51
|
+
const secFetchDest = req.headers['sec-fetch-dest'];
|
|
52
|
+
if (secFetchDest === 'document') {
|
|
53
|
+
// Serve documents from the pages root.
|
|
54
|
+
const pageUrl = resolvePageUrl(url);
|
|
55
|
+
if (pageUrl) {
|
|
56
|
+
req.url = pageUrl;
|
|
57
|
+
delete req.originalUrl;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
else if (secFetchDest && req.headers['referer']) {
|
|
61
|
+
if (url.startsWith('/@vite/')) {
|
|
62
|
+
return next();
|
|
63
|
+
}
|
|
64
|
+
const referer = new URL(req.headers['referer']);
|
|
65
|
+
const pageUrl = resolvePageUrl(referer.pathname);
|
|
66
|
+
if (pageUrl && !fs.existsSync(path.join(root, url))) {
|
|
67
|
+
// Serve assets relative to the document's filesystem location.
|
|
68
|
+
const assetUrl = pageUrl.slice(0, pageUrl.lastIndexOf('/')) + url;
|
|
69
|
+
if (fs.existsSync(path.join(root, assetUrl))) {
|
|
70
|
+
req.url = assetUrl;
|
|
71
|
+
delete req.originalUrl;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
next();
|
|
76
|
+
});
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
transformIndexHtml: transforms.length > 0
|
|
80
|
+
? { order: 'pre', handler: transformPagesOnly(transforms[0]) }
|
|
81
|
+
: undefined,
|
|
82
|
+
buildStart() {
|
|
83
|
+
if (this.environment.name !== 'client') {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
fs.globSync(path.join(root, pagesRoot + '/**/*.html')).forEach(file => {
|
|
87
|
+
this.emitFile({
|
|
88
|
+
type: 'chunk',
|
|
89
|
+
id: path.relative(root, file),
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
},
|
|
93
|
+
generateBundle: {
|
|
94
|
+
order: 'post',
|
|
95
|
+
handler(_options, bundle) {
|
|
96
|
+
if (this.environment.name !== 'client') {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
// Flatten the pages root into the root.
|
|
100
|
+
for (const filename in bundle) {
|
|
101
|
+
const file = bundle[filename];
|
|
102
|
+
if (file.type === 'asset' &&
|
|
103
|
+
file.fileName.startsWith(pagesRoot + '/')) {
|
|
104
|
+
file.fileName = file.fileName.slice(pagesRoot.length + 1);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
// Is the plugin adherent to Vite's plugin API?
|
|
111
|
+
mainPlugin;
|
|
112
|
+
if (transforms.length > 1) {
|
|
113
|
+
return [
|
|
114
|
+
mainPlugin,
|
|
115
|
+
...transforms.slice(1).map(transform => ({
|
|
116
|
+
name: 'vite-multi-spa-transform',
|
|
117
|
+
transformIndexHtml: {
|
|
118
|
+
order: 'pre',
|
|
119
|
+
handler: transformPagesOnly(transform),
|
|
120
|
+
},
|
|
121
|
+
})),
|
|
122
|
+
];
|
|
123
|
+
}
|
|
124
|
+
return mainPlugin;
|
|
125
|
+
}
|
package/package.json
CHANGED
|
@@ -1,13 +1,40 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vite-multi-spa",
|
|
3
|
-
"
|
|
4
|
-
"
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
"test": "echo \"Error: no test specified\" && exit 1"
|
|
3
|
+
"type": "module",
|
|
4
|
+
"exports": {
|
|
5
|
+
"types": "./dist/index.d.ts",
|
|
6
|
+
"import": "./dist/index.js"
|
|
8
7
|
},
|
|
9
|
-
"
|
|
10
|
-
"
|
|
8
|
+
"version": "1.0.0",
|
|
9
|
+
"description": "A Vite plugin for building multi-spa applications",
|
|
11
10
|
"license": "MIT",
|
|
12
|
-
"
|
|
13
|
-
|
|
11
|
+
"keywords": [
|
|
12
|
+
"vite",
|
|
13
|
+
"multi-spa",
|
|
14
|
+
"spa",
|
|
15
|
+
"multi-page",
|
|
16
|
+
"mpa",
|
|
17
|
+
"html",
|
|
18
|
+
"plugin"
|
|
19
|
+
],
|
|
20
|
+
"author": "Alec Larson",
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "https://github.com/aleclarson/vite-multi-spa.git"
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"@alloc/prettier-config": "^1.0.0",
|
|
27
|
+
"@types/node": "^25.0.1",
|
|
28
|
+
"@typescript/native-preview": "7.0.0-dev.20251212.1",
|
|
29
|
+
"prettier": "^3.7.4",
|
|
30
|
+
"vite": "^7.2.7"
|
|
31
|
+
},
|
|
32
|
+
"prettier": "@alloc/prettier-config",
|
|
33
|
+
"files": [
|
|
34
|
+
"dist",
|
|
35
|
+
"!*.tsbuildinfo"
|
|
36
|
+
],
|
|
37
|
+
"scripts": {
|
|
38
|
+
"build": "tsgo -b tsconfig.json"
|
|
39
|
+
}
|
|
40
|
+
}
|
package/readme.md
CHANGED
|
@@ -1 +1,32 @@
|
|
|
1
|
-
|
|
1
|
+
# vite-multi-spa
|
|
2
|
+
|
|
3
|
+
Vite plugin that lets every `.html` in `src/pages` behave like its own SPA entry during dev and build.
|
|
4
|
+
|
|
5
|
+
## Quick start
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
// vite.config.ts
|
|
9
|
+
import { defineConfig } from 'vite'
|
|
10
|
+
import viteMultiSpa from 'vite-multi-spa'
|
|
11
|
+
|
|
12
|
+
export default defineConfig({
|
|
13
|
+
plugins: [
|
|
14
|
+
viteMultiSpa({
|
|
15
|
+
pagesRoot: 'src/pages', // optional
|
|
16
|
+
// transformPageHtml: html => html, // optional, supports array
|
|
17
|
+
}),
|
|
18
|
+
],
|
|
19
|
+
})
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## What it does
|
|
23
|
+
|
|
24
|
+
- Serves documents from `pagesRoot` when the browser requests `/foo` or `/foo/`.
|
|
25
|
+
- Serves page-relative assets when requested from a page (based on the Referer header).
|
|
26
|
+
- Runs `transformPageHtml` only on `/index.html` and pages under `pagesRoot`.
|
|
27
|
+
- Emits every `${pagesRoot}/**/*.html` as a build chunk and flattens outputs to the root of `dist`.
|
|
28
|
+
|
|
29
|
+
## Options
|
|
30
|
+
|
|
31
|
+
- `pagesRoot` (`string`, default `src/pages`): Folder scanned for `.html` pages.
|
|
32
|
+
- `transformPageHtml` (`IndexHtmlTransformHook | IndexHtmlTransformHook[]`): Passed through to Vite and scoped to pages. See [Vite's API documentation](https://vite.dev/guide/api-plugin#transformindexhtml).
|