@linglongos/vite-plugin-html 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.cjs ADDED
@@ -0,0 +1,380 @@
1
+ 'use strict';
2
+
3
+ const ejs = require('ejs');
4
+ const dotenvExpand = require('dotenv-expand');
5
+ const dotenv = require('dotenv');
6
+ const path = require('pathe');
7
+ const fse = require('fs-extra');
8
+ const vite = require('vite');
9
+ const nodeHtmlParser = require('node-html-parser');
10
+ const tinyglobby = require('tinyglobby');
11
+ const consola = require('consola');
12
+ const colorette = require('colorette');
13
+ const history = require('connect-history-api-fallback');
14
+ const htmlMinifierTerser = require('html-minifier-terser');
15
+ const pluginutils = require('@rollup/pluginutils');
16
+
17
+ function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; }
18
+
19
+ const dotenv__default = /*#__PURE__*/_interopDefaultCompat(dotenv);
20
+ const path__default = /*#__PURE__*/_interopDefaultCompat(path);
21
+ const fse__default = /*#__PURE__*/_interopDefaultCompat(fse);
22
+ const consola__default = /*#__PURE__*/_interopDefaultCompat(consola);
23
+ const history__default = /*#__PURE__*/_interopDefaultCompat(history);
24
+
25
+ function loadEnv(mode, envDir, prefix = "") {
26
+ if (mode === "local") {
27
+ throw new Error(
28
+ `"local" cannot be used as a mode name because it conflicts with the .local postfix for .env files.`
29
+ );
30
+ }
31
+ const env = {};
32
+ const envFiles = [
33
+ /** mode local file */
34
+ `.env.${mode}.local`,
35
+ /** mode file */
36
+ `.env.${mode}`,
37
+ /** local file */
38
+ `.env.local`,
39
+ /** default file */
40
+ `.env`
41
+ ];
42
+ for (const file of envFiles) {
43
+ const path = lookupFile(envDir, [file], true);
44
+ if (path) {
45
+ const parsed = dotenv__default.parse(fse__default.readFileSync(path));
46
+ dotenvExpand.expand({
47
+ parsed,
48
+ // prevent process.env mutation
49
+ ignoreProcessEnv: true
50
+ });
51
+ for (const [key, value] of Object.entries(parsed)) {
52
+ if (key.startsWith(prefix) && env[key] === void 0) {
53
+ env[key] = value;
54
+ } else if (key === "NODE_ENV") {
55
+ process.env.VITE_USER_NODE_ENV = value;
56
+ }
57
+ }
58
+ }
59
+ }
60
+ return env;
61
+ }
62
+ function lookupFile(dir, formats, pathOnly = false) {
63
+ for (const format of formats) {
64
+ const fullPath = path.join(dir, format);
65
+ if (fse__default.pathExistsSync(fullPath) && fse__default.statSync(fullPath).isFile()) {
66
+ return pathOnly ? fullPath : fse__default.readFileSync(fullPath, "utf-8");
67
+ }
68
+ }
69
+ const parentDir = path.dirname(dir);
70
+ if (parentDir !== dir) {
71
+ return lookupFile(parentDir, formats, pathOnly);
72
+ }
73
+ }
74
+ async function isDirEmpty(dir) {
75
+ return fse__default.readdir(dir).then((files) => {
76
+ return files.length === 0;
77
+ });
78
+ }
79
+
80
+ const DEFAULT_TEMPLATE = "index.html";
81
+ const ignoreDirs = [".", "", "/"];
82
+ const bodyInjectRE = /<\/body>/;
83
+ function createPlugin(userOptions = {}) {
84
+ const {
85
+ entry,
86
+ template = DEFAULT_TEMPLATE,
87
+ pages = [],
88
+ verbose = false
89
+ } = userOptions;
90
+ let viteConfig;
91
+ let env = {};
92
+ const transformIndexHtmlHandler = async (html, ctx) => {
93
+ const url = ctx.filename;
94
+ const base = viteConfig.base;
95
+ const excludeBaseUrl = url.replace(base, "/");
96
+ const htmlName = path__default.relative(process.cwd(), excludeBaseUrl);
97
+ const page = getPage(userOptions, htmlName, viteConfig);
98
+ const { injectOptions = {} } = page;
99
+ const _html = await renderHtml(html, {
100
+ injectOptions,
101
+ viteConfig,
102
+ env,
103
+ entry: page.entry || entry,
104
+ verbose
105
+ });
106
+ const { tags = [] } = injectOptions;
107
+ return {
108
+ html: _html,
109
+ tags
110
+ };
111
+ };
112
+ return {
113
+ name: "vite:html",
114
+ configResolved(resolvedConfig) {
115
+ viteConfig = resolvedConfig;
116
+ env = loadEnv(viteConfig.mode, viteConfig.root, "");
117
+ },
118
+ config(conf) {
119
+ const input = createInput(userOptions, conf);
120
+ if (input) {
121
+ return {
122
+ build: {
123
+ rollupOptions: {
124
+ input
125
+ }
126
+ }
127
+ };
128
+ }
129
+ },
130
+ configureServer(server) {
131
+ let _pages = [];
132
+ const rewrites = [];
133
+ if (!isMpa(viteConfig)) {
134
+ const template2 = userOptions.template || DEFAULT_TEMPLATE;
135
+ const filename = DEFAULT_TEMPLATE;
136
+ _pages.push({
137
+ filename,
138
+ template: template2
139
+ });
140
+ } else {
141
+ _pages = pages.map((page) => {
142
+ return {
143
+ filename: page.filename || DEFAULT_TEMPLATE,
144
+ template: page.template || DEFAULT_TEMPLATE
145
+ };
146
+ });
147
+ }
148
+ const proxy = viteConfig.server?.proxy ?? {};
149
+ const baseUrl = viteConfig.base ?? "/";
150
+ const keys = Object.keys(proxy);
151
+ let indexPage = null;
152
+ for (const page of _pages) {
153
+ if (page.filename !== "index.html") {
154
+ rewrites.push(createRewire(page.template, page, baseUrl, keys));
155
+ } else {
156
+ indexPage = page;
157
+ }
158
+ }
159
+ if (indexPage) {
160
+ rewrites.push(createRewire("", indexPage, baseUrl, keys));
161
+ }
162
+ server.middlewares.use(
163
+ history__default({
164
+ disableDotRule: void 0,
165
+ htmlAcceptHeaders: ["text/html", "application/xhtml+xml"],
166
+ rewrites
167
+ })
168
+ );
169
+ },
170
+ transformIndexHtml: {
171
+ order: "pre",
172
+ handler: transformIndexHtmlHandler
173
+ },
174
+ async closeBundle() {
175
+ const outputDirs = [];
176
+ if (isMpa(viteConfig) || pages.length) {
177
+ for (const page of pages) {
178
+ const dir = path__default.dirname(page.template);
179
+ if (!ignoreDirs.includes(dir)) {
180
+ outputDirs.push(dir);
181
+ }
182
+ }
183
+ } else {
184
+ const dir = path__default.dirname(template);
185
+ if (!ignoreDirs.includes(dir)) {
186
+ outputDirs.push(dir);
187
+ }
188
+ }
189
+ const cwd = path__default.resolve(viteConfig.root, viteConfig.build.outDir);
190
+ const htmlFiles = await tinyglobby.glob(
191
+ outputDirs.map((dir) => `${dir}/*.html`),
192
+ { cwd: path__default.resolve(cwd), absolute: true }
193
+ );
194
+ await Promise.all(
195
+ htmlFiles.map(
196
+ (file) => fse__default.move(file, path__default.resolve(cwd, path__default.basename(file)), {
197
+ overwrite: true
198
+ })
199
+ )
200
+ );
201
+ const htmlDirs = await tinyglobby.glob(
202
+ outputDirs.map((dir) => dir),
203
+ { cwd: path__default.resolve(cwd), type: "dir", absolute: true }
204
+ );
205
+ await Promise.all(
206
+ htmlDirs.map(async (item) => {
207
+ const isEmpty = await isDirEmpty(item);
208
+ if (isEmpty) {
209
+ return fse__default.remove(item);
210
+ }
211
+ })
212
+ );
213
+ }
214
+ };
215
+ }
216
+ function createInput({ pages = [], template = DEFAULT_TEMPLATE }, viteConfig) {
217
+ const input = {};
218
+ if (isMpa(viteConfig) || pages?.length) {
219
+ const templates = pages.map((page) => page.template);
220
+ templates.forEach((temp) => {
221
+ let dirName = path__default.dirname(temp);
222
+ const file = path__default.basename(temp);
223
+ dirName = dirName.replace(/\s+/g, "").replace(/\//g, "-");
224
+ const key = dirName === "." || dirName === "public" || !dirName ? file.replace(/\.html/, "") : dirName;
225
+ input[key] = path__default.resolve(viteConfig.root, temp);
226
+ });
227
+ return input;
228
+ } else {
229
+ const dir = path__default.dirname(template);
230
+ if (ignoreDirs.includes(dir)) {
231
+ return void 0;
232
+ } else {
233
+ const file = path__default.basename(template);
234
+ const key = file.replace(/\.html/, "");
235
+ return {
236
+ [key]: path__default.resolve(viteConfig.root, template)
237
+ };
238
+ }
239
+ }
240
+ }
241
+ async function renderHtml(html, config) {
242
+ const { injectOptions, viteConfig, env, entry, verbose } = config;
243
+ const { data, ejsOptions } = injectOptions;
244
+ const ejsData = {
245
+ ...viteConfig?.env ?? {},
246
+ ...viteConfig?.define ?? {},
247
+ ...env || {},
248
+ ...data
249
+ };
250
+ let result = await ejs.render(html, ejsData, ejsOptions);
251
+ if (entry) {
252
+ result = removeEntryScript(result, verbose);
253
+ result = result.replace(
254
+ bodyInjectRE,
255
+ `<script type="module" src="${vite.normalizePath(
256
+ `${entry}`
257
+ )}"><\/script>
258
+ </body>`
259
+ );
260
+ }
261
+ return result;
262
+ }
263
+ function getPage({ pages = [], entry, template = DEFAULT_TEMPLATE, inject = {} }, name, viteConfig) {
264
+ let page;
265
+ if (isMpa(viteConfig) || pages?.length) {
266
+ page = getPageConfig(name, pages, DEFAULT_TEMPLATE);
267
+ } else {
268
+ page = createSpaPage(entry, template, inject);
269
+ }
270
+ return page;
271
+ }
272
+ function isMpa(viteConfig) {
273
+ const input = viteConfig?.build?.rollupOptions?.input ?? void 0;
274
+ return typeof input !== "string" && Object.keys(input || {}).length > 1;
275
+ }
276
+ function removeEntryScript(html, verbose = false) {
277
+ if (!html) {
278
+ return html;
279
+ }
280
+ const root = nodeHtmlParser.parse(html);
281
+ const scriptNodes = root.querySelectorAll("script[type=module]") || [];
282
+ const removedNode = [];
283
+ scriptNodes.forEach((item) => {
284
+ removedNode.push(item.toString());
285
+ item.parentNode.removeChild(item);
286
+ });
287
+ verbose && removedNode.length && consola__default.warn(`vite-plugin-html: Since you have already configured entry, ${colorette.dim(
288
+ removedNode.toString()
289
+ )} is deleted. You may also delete it from the index.html.
290
+ `);
291
+ return root.toString();
292
+ }
293
+ function createSpaPage(entry, template, inject = {}) {
294
+ return {
295
+ entry,
296
+ filename: "index.html",
297
+ template,
298
+ injectOptions: inject
299
+ };
300
+ }
301
+ function getPageConfig(htmlName, pages, defaultPage) {
302
+ const defaultPageOption = {
303
+ filename: defaultPage,
304
+ template: `./${defaultPage}`
305
+ };
306
+ const page = pages.filter((page2) => {
307
+ return path__default.resolve("/" + page2.template) === path__default.resolve("/" + htmlName);
308
+ })?.[0];
309
+ return page ?? defaultPageOption ?? void 0;
310
+ }
311
+ function createRewire(reg, page, baseUrl, proxyUrlKeys) {
312
+ return {
313
+ from: new RegExp(`^/${reg}*`),
314
+ to({ parsedUrl }) {
315
+ const pathname = parsedUrl.path;
316
+ const excludeBaseUrl = pathname.replace(baseUrl, "/");
317
+ const template = path__default.resolve(baseUrl, page.template);
318
+ if (excludeBaseUrl.startsWith("/static")) {
319
+ return excludeBaseUrl;
320
+ }
321
+ if (excludeBaseUrl === "/") {
322
+ return template;
323
+ }
324
+ const isApiUrl = proxyUrlKeys.some(
325
+ (item) => pathname.startsWith(path__default.resolve(baseUrl, item))
326
+ );
327
+ return isApiUrl ? parsedUrl.path : template;
328
+ }
329
+ };
330
+ }
331
+
332
+ const htmlFilter = pluginutils.createFilter(["**/*.html"]);
333
+
334
+ function getOptions(minify) {
335
+ return {
336
+ collapseWhitespace: minify,
337
+ keepClosingSlash: minify,
338
+ removeComments: minify,
339
+ removeRedundantAttributes: minify,
340
+ removeScriptTypeAttributes: minify,
341
+ removeStyleLinkTypeAttributes: minify,
342
+ useShortDoctype: minify,
343
+ minifyCSS: minify
344
+ };
345
+ }
346
+ async function minifyHtml(html, minify) {
347
+ if (typeof minify === "boolean" && !minify) {
348
+ return html;
349
+ }
350
+ let minifyOptions = minify;
351
+ if (typeof minify === "boolean" && minify) {
352
+ minifyOptions = getOptions(minify);
353
+ }
354
+ return await htmlMinifierTerser.minify(html, minifyOptions);
355
+ }
356
+ function createMinifyHtmlPlugin({
357
+ minify = true
358
+ } = {}) {
359
+ return {
360
+ name: "vite:minify-html",
361
+ // apply: 'build',
362
+ enforce: "post",
363
+ async generateBundle(_, outBundle) {
364
+ if (minify) {
365
+ for (const bundle of Object.values(outBundle)) {
366
+ if (bundle.type === "asset" && htmlFilter(bundle.fileName) && typeof bundle.source === "string") {
367
+ bundle.source = await minifyHtml(bundle.source, minify);
368
+ }
369
+ }
370
+ }
371
+ }
372
+ };
373
+ }
374
+
375
+ consola__default.wrapConsole();
376
+ function createHtmlPlugin(userOptions = {}) {
377
+ return [createPlugin(userOptions), createMinifyHtmlPlugin(userOptions)];
378
+ }
379
+
380
+ exports.createHtmlPlugin = createHtmlPlugin;
@@ -0,0 +1,53 @@
1
+ import { HtmlTagDescriptor, PluginOption } from 'vite';
2
+ import { Options } from 'ejs';
3
+ import { Options as Options$1 } from 'html-minifier-terser';
4
+
5
+ interface InjectOptions {
6
+ /**
7
+ * @description Data injected into the html template
8
+ */
9
+ data?: Record<string, any>;
10
+ tags?: HtmlTagDescriptor[];
11
+ /**
12
+ * @description ejs options configuration
13
+ */
14
+ ejsOptions?: Options;
15
+ }
16
+ interface PageOption {
17
+ filename: string;
18
+ template: string;
19
+ entry?: string;
20
+ injectOptions?: InjectOptions;
21
+ }
22
+ type Pages = PageOption[];
23
+ interface UserOptions {
24
+ /**
25
+ * @description Page options
26
+ */
27
+ pages?: Pages;
28
+ /**
29
+ * @description Minimize options
30
+ */
31
+ minify?: Options$1 | boolean;
32
+ /**
33
+ * page entry
34
+ */
35
+ entry?: string;
36
+ /**
37
+ * template path
38
+ */
39
+ template?: string;
40
+ /**
41
+ * @description inject options
42
+ */
43
+ inject?: InjectOptions;
44
+ /**
45
+ * output warning log
46
+ * @default false
47
+ */
48
+ verbose?: boolean;
49
+ }
50
+
51
+ declare function createHtmlPlugin(userOptions?: UserOptions): PluginOption[];
52
+
53
+ export { createHtmlPlugin };
@@ -0,0 +1,53 @@
1
+ import { HtmlTagDescriptor, PluginOption } from 'vite';
2
+ import { Options } from 'ejs';
3
+ import { Options as Options$1 } from 'html-minifier-terser';
4
+
5
+ interface InjectOptions {
6
+ /**
7
+ * @description Data injected into the html template
8
+ */
9
+ data?: Record<string, any>;
10
+ tags?: HtmlTagDescriptor[];
11
+ /**
12
+ * @description ejs options configuration
13
+ */
14
+ ejsOptions?: Options;
15
+ }
16
+ interface PageOption {
17
+ filename: string;
18
+ template: string;
19
+ entry?: string;
20
+ injectOptions?: InjectOptions;
21
+ }
22
+ type Pages = PageOption[];
23
+ interface UserOptions {
24
+ /**
25
+ * @description Page options
26
+ */
27
+ pages?: Pages;
28
+ /**
29
+ * @description Minimize options
30
+ */
31
+ minify?: Options$1 | boolean;
32
+ /**
33
+ * page entry
34
+ */
35
+ entry?: string;
36
+ /**
37
+ * template path
38
+ */
39
+ template?: string;
40
+ /**
41
+ * @description inject options
42
+ */
43
+ inject?: InjectOptions;
44
+ /**
45
+ * output warning log
46
+ * @default false
47
+ */
48
+ verbose?: boolean;
49
+ }
50
+
51
+ declare function createHtmlPlugin(userOptions?: UserOptions): PluginOption[];
52
+
53
+ export { createHtmlPlugin };
@@ -0,0 +1,53 @@
1
+ import { HtmlTagDescriptor, PluginOption } from 'vite';
2
+ import { Options } from 'ejs';
3
+ import { Options as Options$1 } from 'html-minifier-terser';
4
+
5
+ interface InjectOptions {
6
+ /**
7
+ * @description Data injected into the html template
8
+ */
9
+ data?: Record<string, any>;
10
+ tags?: HtmlTagDescriptor[];
11
+ /**
12
+ * @description ejs options configuration
13
+ */
14
+ ejsOptions?: Options;
15
+ }
16
+ interface PageOption {
17
+ filename: string;
18
+ template: string;
19
+ entry?: string;
20
+ injectOptions?: InjectOptions;
21
+ }
22
+ type Pages = PageOption[];
23
+ interface UserOptions {
24
+ /**
25
+ * @description Page options
26
+ */
27
+ pages?: Pages;
28
+ /**
29
+ * @description Minimize options
30
+ */
31
+ minify?: Options$1 | boolean;
32
+ /**
33
+ * page entry
34
+ */
35
+ entry?: string;
36
+ /**
37
+ * template path
38
+ */
39
+ template?: string;
40
+ /**
41
+ * @description inject options
42
+ */
43
+ inject?: InjectOptions;
44
+ /**
45
+ * output warning log
46
+ * @default false
47
+ */
48
+ verbose?: boolean;
49
+ }
50
+
51
+ declare function createHtmlPlugin(userOptions?: UserOptions): PluginOption[];
52
+
53
+ export { createHtmlPlugin };
package/dist/index.mjs ADDED
@@ -0,0 +1,370 @@
1
+ import { render } from 'ejs';
2
+ import { expand } from 'dotenv-expand';
3
+ import dotenv from 'dotenv';
4
+ import path, { join, dirname } from 'pathe';
5
+ import fse from 'fs-extra';
6
+ import { normalizePath } from 'vite';
7
+ import { parse } from 'node-html-parser';
8
+ import { glob } from 'tinyglobby';
9
+ import consola from 'consola';
10
+ import { dim } from 'colorette';
11
+ import history from 'connect-history-api-fallback';
12
+ import { minify } from 'html-minifier-terser';
13
+ import { createFilter } from '@rollup/pluginutils';
14
+
15
+ function loadEnv(mode, envDir, prefix = "") {
16
+ if (mode === "local") {
17
+ throw new Error(
18
+ `"local" cannot be used as a mode name because it conflicts with the .local postfix for .env files.`
19
+ );
20
+ }
21
+ const env = {};
22
+ const envFiles = [
23
+ /** mode local file */
24
+ `.env.${mode}.local`,
25
+ /** mode file */
26
+ `.env.${mode}`,
27
+ /** local file */
28
+ `.env.local`,
29
+ /** default file */
30
+ `.env`
31
+ ];
32
+ for (const file of envFiles) {
33
+ const path = lookupFile(envDir, [file], true);
34
+ if (path) {
35
+ const parsed = dotenv.parse(fse.readFileSync(path));
36
+ expand({
37
+ parsed,
38
+ // prevent process.env mutation
39
+ ignoreProcessEnv: true
40
+ });
41
+ for (const [key, value] of Object.entries(parsed)) {
42
+ if (key.startsWith(prefix) && env[key] === void 0) {
43
+ env[key] = value;
44
+ } else if (key === "NODE_ENV") {
45
+ process.env.VITE_USER_NODE_ENV = value;
46
+ }
47
+ }
48
+ }
49
+ }
50
+ return env;
51
+ }
52
+ function lookupFile(dir, formats, pathOnly = false) {
53
+ for (const format of formats) {
54
+ const fullPath = join(dir, format);
55
+ if (fse.pathExistsSync(fullPath) && fse.statSync(fullPath).isFile()) {
56
+ return pathOnly ? fullPath : fse.readFileSync(fullPath, "utf-8");
57
+ }
58
+ }
59
+ const parentDir = dirname(dir);
60
+ if (parentDir !== dir) {
61
+ return lookupFile(parentDir, formats, pathOnly);
62
+ }
63
+ }
64
+ async function isDirEmpty(dir) {
65
+ return fse.readdir(dir).then((files) => {
66
+ return files.length === 0;
67
+ });
68
+ }
69
+
70
+ const DEFAULT_TEMPLATE = "index.html";
71
+ const ignoreDirs = [".", "", "/"];
72
+ const bodyInjectRE = /<\/body>/;
73
+ function createPlugin(userOptions = {}) {
74
+ const {
75
+ entry,
76
+ template = DEFAULT_TEMPLATE,
77
+ pages = [],
78
+ verbose = false
79
+ } = userOptions;
80
+ let viteConfig;
81
+ let env = {};
82
+ const transformIndexHtmlHandler = async (html, ctx) => {
83
+ const url = ctx.filename;
84
+ const base = viteConfig.base;
85
+ const excludeBaseUrl = url.replace(base, "/");
86
+ const htmlName = path.relative(process.cwd(), excludeBaseUrl);
87
+ const page = getPage(userOptions, htmlName, viteConfig);
88
+ const { injectOptions = {} } = page;
89
+ const _html = await renderHtml(html, {
90
+ injectOptions,
91
+ viteConfig,
92
+ env,
93
+ entry: page.entry || entry,
94
+ verbose
95
+ });
96
+ const { tags = [] } = injectOptions;
97
+ return {
98
+ html: _html,
99
+ tags
100
+ };
101
+ };
102
+ return {
103
+ name: "vite:html",
104
+ configResolved(resolvedConfig) {
105
+ viteConfig = resolvedConfig;
106
+ env = loadEnv(viteConfig.mode, viteConfig.root, "");
107
+ },
108
+ config(conf) {
109
+ const input = createInput(userOptions, conf);
110
+ if (input) {
111
+ return {
112
+ build: {
113
+ rollupOptions: {
114
+ input
115
+ }
116
+ }
117
+ };
118
+ }
119
+ },
120
+ configureServer(server) {
121
+ let _pages = [];
122
+ const rewrites = [];
123
+ if (!isMpa(viteConfig)) {
124
+ const template2 = userOptions.template || DEFAULT_TEMPLATE;
125
+ const filename = DEFAULT_TEMPLATE;
126
+ _pages.push({
127
+ filename,
128
+ template: template2
129
+ });
130
+ } else {
131
+ _pages = pages.map((page) => {
132
+ return {
133
+ filename: page.filename || DEFAULT_TEMPLATE,
134
+ template: page.template || DEFAULT_TEMPLATE
135
+ };
136
+ });
137
+ }
138
+ const proxy = viteConfig.server?.proxy ?? {};
139
+ const baseUrl = viteConfig.base ?? "/";
140
+ const keys = Object.keys(proxy);
141
+ let indexPage = null;
142
+ for (const page of _pages) {
143
+ if (page.filename !== "index.html") {
144
+ rewrites.push(createRewire(page.template, page, baseUrl, keys));
145
+ } else {
146
+ indexPage = page;
147
+ }
148
+ }
149
+ if (indexPage) {
150
+ rewrites.push(createRewire("", indexPage, baseUrl, keys));
151
+ }
152
+ server.middlewares.use(
153
+ history({
154
+ disableDotRule: void 0,
155
+ htmlAcceptHeaders: ["text/html", "application/xhtml+xml"],
156
+ rewrites
157
+ })
158
+ );
159
+ },
160
+ transformIndexHtml: {
161
+ order: "pre",
162
+ handler: transformIndexHtmlHandler
163
+ },
164
+ async closeBundle() {
165
+ const outputDirs = [];
166
+ if (isMpa(viteConfig) || pages.length) {
167
+ for (const page of pages) {
168
+ const dir = path.dirname(page.template);
169
+ if (!ignoreDirs.includes(dir)) {
170
+ outputDirs.push(dir);
171
+ }
172
+ }
173
+ } else {
174
+ const dir = path.dirname(template);
175
+ if (!ignoreDirs.includes(dir)) {
176
+ outputDirs.push(dir);
177
+ }
178
+ }
179
+ const cwd = path.resolve(viteConfig.root, viteConfig.build.outDir);
180
+ const htmlFiles = await glob(
181
+ outputDirs.map((dir) => `${dir}/*.html`),
182
+ { cwd: path.resolve(cwd), absolute: true }
183
+ );
184
+ await Promise.all(
185
+ htmlFiles.map(
186
+ (file) => fse.move(file, path.resolve(cwd, path.basename(file)), {
187
+ overwrite: true
188
+ })
189
+ )
190
+ );
191
+ const htmlDirs = await glob(
192
+ outputDirs.map((dir) => dir),
193
+ { cwd: path.resolve(cwd), type: "dir", absolute: true }
194
+ );
195
+ await Promise.all(
196
+ htmlDirs.map(async (item) => {
197
+ const isEmpty = await isDirEmpty(item);
198
+ if (isEmpty) {
199
+ return fse.remove(item);
200
+ }
201
+ })
202
+ );
203
+ }
204
+ };
205
+ }
206
+ function createInput({ pages = [], template = DEFAULT_TEMPLATE }, viteConfig) {
207
+ const input = {};
208
+ if (isMpa(viteConfig) || pages?.length) {
209
+ const templates = pages.map((page) => page.template);
210
+ templates.forEach((temp) => {
211
+ let dirName = path.dirname(temp);
212
+ const file = path.basename(temp);
213
+ dirName = dirName.replace(/\s+/g, "").replace(/\//g, "-");
214
+ const key = dirName === "." || dirName === "public" || !dirName ? file.replace(/\.html/, "") : dirName;
215
+ input[key] = path.resolve(viteConfig.root, temp);
216
+ });
217
+ return input;
218
+ } else {
219
+ const dir = path.dirname(template);
220
+ if (ignoreDirs.includes(dir)) {
221
+ return void 0;
222
+ } else {
223
+ const file = path.basename(template);
224
+ const key = file.replace(/\.html/, "");
225
+ return {
226
+ [key]: path.resolve(viteConfig.root, template)
227
+ };
228
+ }
229
+ }
230
+ }
231
+ async function renderHtml(html, config) {
232
+ const { injectOptions, viteConfig, env, entry, verbose } = config;
233
+ const { data, ejsOptions } = injectOptions;
234
+ const ejsData = {
235
+ ...viteConfig?.env ?? {},
236
+ ...viteConfig?.define ?? {},
237
+ ...env || {},
238
+ ...data
239
+ };
240
+ let result = await render(html, ejsData, ejsOptions);
241
+ if (entry) {
242
+ result = removeEntryScript(result, verbose);
243
+ result = result.replace(
244
+ bodyInjectRE,
245
+ `<script type="module" src="${normalizePath(
246
+ `${entry}`
247
+ )}"><\/script>
248
+ </body>`
249
+ );
250
+ }
251
+ return result;
252
+ }
253
+ function getPage({ pages = [], entry, template = DEFAULT_TEMPLATE, inject = {} }, name, viteConfig) {
254
+ let page;
255
+ if (isMpa(viteConfig) || pages?.length) {
256
+ page = getPageConfig(name, pages, DEFAULT_TEMPLATE);
257
+ } else {
258
+ page = createSpaPage(entry, template, inject);
259
+ }
260
+ return page;
261
+ }
262
+ function isMpa(viteConfig) {
263
+ const input = viteConfig?.build?.rollupOptions?.input ?? void 0;
264
+ return typeof input !== "string" && Object.keys(input || {}).length > 1;
265
+ }
266
+ function removeEntryScript(html, verbose = false) {
267
+ if (!html) {
268
+ return html;
269
+ }
270
+ const root = parse(html);
271
+ const scriptNodes = root.querySelectorAll("script[type=module]") || [];
272
+ const removedNode = [];
273
+ scriptNodes.forEach((item) => {
274
+ removedNode.push(item.toString());
275
+ item.parentNode.removeChild(item);
276
+ });
277
+ verbose && removedNode.length && consola.warn(`vite-plugin-html: Since you have already configured entry, ${dim(
278
+ removedNode.toString()
279
+ )} is deleted. You may also delete it from the index.html.
280
+ `);
281
+ return root.toString();
282
+ }
283
+ function createSpaPage(entry, template, inject = {}) {
284
+ return {
285
+ entry,
286
+ filename: "index.html",
287
+ template,
288
+ injectOptions: inject
289
+ };
290
+ }
291
+ function getPageConfig(htmlName, pages, defaultPage) {
292
+ const defaultPageOption = {
293
+ filename: defaultPage,
294
+ template: `./${defaultPage}`
295
+ };
296
+ const page = pages.filter((page2) => {
297
+ return path.resolve("/" + page2.template) === path.resolve("/" + htmlName);
298
+ })?.[0];
299
+ return page ?? defaultPageOption ?? void 0;
300
+ }
301
+ function createRewire(reg, page, baseUrl, proxyUrlKeys) {
302
+ return {
303
+ from: new RegExp(`^/${reg}*`),
304
+ to({ parsedUrl }) {
305
+ const pathname = parsedUrl.path;
306
+ const excludeBaseUrl = pathname.replace(baseUrl, "/");
307
+ const template = path.resolve(baseUrl, page.template);
308
+ if (excludeBaseUrl.startsWith("/static")) {
309
+ return excludeBaseUrl;
310
+ }
311
+ if (excludeBaseUrl === "/") {
312
+ return template;
313
+ }
314
+ const isApiUrl = proxyUrlKeys.some(
315
+ (item) => pathname.startsWith(path.resolve(baseUrl, item))
316
+ );
317
+ return isApiUrl ? parsedUrl.path : template;
318
+ }
319
+ };
320
+ }
321
+
322
+ const htmlFilter = createFilter(["**/*.html"]);
323
+
324
+ function getOptions(minify) {
325
+ return {
326
+ collapseWhitespace: minify,
327
+ keepClosingSlash: minify,
328
+ removeComments: minify,
329
+ removeRedundantAttributes: minify,
330
+ removeScriptTypeAttributes: minify,
331
+ removeStyleLinkTypeAttributes: minify,
332
+ useShortDoctype: minify,
333
+ minifyCSS: minify
334
+ };
335
+ }
336
+ async function minifyHtml(html, minify$1) {
337
+ if (typeof minify$1 === "boolean" && !minify$1) {
338
+ return html;
339
+ }
340
+ let minifyOptions = minify$1;
341
+ if (typeof minify$1 === "boolean" && minify$1) {
342
+ minifyOptions = getOptions(minify$1);
343
+ }
344
+ return await minify(html, minifyOptions);
345
+ }
346
+ function createMinifyHtmlPlugin({
347
+ minify = true
348
+ } = {}) {
349
+ return {
350
+ name: "vite:minify-html",
351
+ // apply: 'build',
352
+ enforce: "post",
353
+ async generateBundle(_, outBundle) {
354
+ if (minify) {
355
+ for (const bundle of Object.values(outBundle)) {
356
+ if (bundle.type === "asset" && htmlFilter(bundle.fileName) && typeof bundle.source === "string") {
357
+ bundle.source = await minifyHtml(bundle.source, minify);
358
+ }
359
+ }
360
+ }
361
+ }
362
+ };
363
+ }
364
+
365
+ consola.wrapConsole();
366
+ function createHtmlPlugin(userOptions = {}) {
367
+ return [createPlugin(userOptions), createMinifyHtmlPlugin(userOptions)];
368
+ }
369
+
370
+ export { createHtmlPlugin };
package/package.json ADDED
@@ -0,0 +1,70 @@
1
+ {
2
+ "name": "@linglongos/vite-plugin-html",
3
+ "version": "1.0.0",
4
+ "description": "Fork of vite-plugin-html with Vite 8 support, EJS template and HTML minification for index.html",
5
+ "main": "dist/index.cjs",
6
+ "module": "dist/index.mjs",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "require": "./dist/index.cjs",
11
+ "import": "./dist/index.mjs",
12
+ "types": "./dist/index.d.ts"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist",
17
+ "CHANGELOG.md",
18
+ "README.md",
19
+ "README.zh_CN.md"
20
+ ],
21
+ "scripts": {
22
+ "dev": "pnpm unbuild --stub",
23
+ "build": "pnpm unbuild",
24
+ "prepublishOnly": "npm run build",
25
+ "prepack": "pnpm unbuild"
26
+ },
27
+ "keywords": [
28
+ "vite",
29
+ "html",
30
+ "minify",
31
+ "vite-plugin"
32
+ ],
33
+ "author": "Vben",
34
+ "license": "MIT",
35
+ "repository": {
36
+ "type": "git",
37
+ "url": "https://github.com/vbenjs/vite-plugin-html",
38
+ "directory": "packages/core"
39
+ },
40
+ "bugs": {
41
+ "url": "https://github.com/vbenjs/vite-plugin-html/issues"
42
+ },
43
+ "homepage": "https://github.com/vbenjs/vite-plugin-html/tree/master/#readme",
44
+ "dependencies": {
45
+ "@rollup/pluginutils": "^4.2.0",
46
+ "colorette": "^2.0.16",
47
+ "connect-history-api-fallback": "^2.0.0",
48
+ "consola": "^2.15.3",
49
+ "dotenv": "^16.0.0",
50
+ "dotenv-expand": "^8.0.2",
51
+ "ejs": "^3.1.6",
52
+ "tinyglobby": "^0.2.9",
53
+ "fs-extra": "^10.0.1",
54
+ "html-minifier-terser": "^6.1.0",
55
+ "node-html-parser": "^5.3.3",
56
+ "pathe": "^0.2.0"
57
+ },
58
+ "peerDependencies": {
59
+ "vite": ">=2.0.0"
60
+ },
61
+ "devDependencies": {
62
+ "@babel/types": "^7.17.0",
63
+ "@types/ejs": "^3.1.0",
64
+ "@types/fs-extra": "^9.0.13",
65
+ "@types/html-minifier-terser": "^6.1.0",
66
+ "@types/node": "^22.0.0",
67
+ "typescript": "^4.6.2",
68
+ "vite": "^8.0.0"
69
+ }
70
+ }