├── .gitignore ├── .eslintignore ├── packages ├── playground │ ├── mpa │ │ ├── .env │ │ ├── src │ │ │ ├── test.vue │ │ │ ├── App.vue │ │ │ ├── other-app.vue │ │ │ ├── other-main.ts │ │ │ └── main.ts │ │ ├── public │ │ │ ├── inject.js │ │ │ └── favicon.ico │ │ ├── other.html │ │ ├── index.html │ │ ├── package.json │ │ └── vite.config.ts │ ├── basic │ │ ├── .env │ │ ├── src │ │ │ ├── test.vue │ │ │ ├── App.vue │ │ │ └── main.ts │ │ ├── public │ │ │ ├── inject.js │ │ │ └── favicon.ico │ │ ├── package.json │ │ ├── index.html │ │ ├── basic.html │ │ ├── vite.config.ts │ │ └── server.js │ └── custom-entry │ │ ├── .env │ │ ├── src │ │ ├── test.vue │ │ ├── App.vue │ │ └── main.ts │ │ ├── public │ │ ├── inject.js │ │ └── favicon.ico │ │ ├── package.json │ │ ├── static │ │ └── index.html │ │ └── vite.config.ts └── core │ ├── src │ ├── utils │ │ ├── createHtmlFilter.ts │ │ └── index.ts │ ├── index.ts │ ├── __tests__ │ │ ├── utils.spec.ts │ │ ├── html.spec.ts │ │ └── minify.spec.ts │ ├── typing.ts │ ├── minifyHtml.ts │ └── htmlPlugin.ts │ ├── build.config.ts │ └── package.json ├── .npmrc ├── .commitlintrc.json ├── pnpm-workspace.yaml ├── vitest.config.ts ├── .prettierrc.json ├── .husky ├── commit-msg ├── common.sh └── pre-commit ├── .vscode ├── extensions.json └── settings.json ├── .editorconfig ├── tsconfig.json ├── .eslintrc.json ├── .github └── workflows │ ├── release.yml │ ├── test.yml │ └── publish.yml ├── LICENSE ├── package.json ├── README.zh_CN.md ├── CHANGELOG.md └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | -------------------------------------------------------------------------------- /packages/playground/mpa/.env: -------------------------------------------------------------------------------- 1 | NODE_ENV=dev 2 | ENV=env 3 | -------------------------------------------------------------------------------- /packages/playground/basic/.env: -------------------------------------------------------------------------------- 1 | NODE_ENV=dev 2 | ENV=env 3 | -------------------------------------------------------------------------------- /packages/playground/basic/src/test.vue: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /packages/playground/custom-entry/.env: -------------------------------------------------------------------------------- 1 | NODE_ENV=dev 2 | ENV=env 3 | -------------------------------------------------------------------------------- /packages/playground/mpa/src/test.vue: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | ignore-workspace-root-check=true 2 | public-hoist-pattern[]=* 3 | -------------------------------------------------------------------------------- /.commitlintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["@commitlint/config-conventional"] 3 | } 4 | -------------------------------------------------------------------------------- /packages/playground/basic/public/inject.js: -------------------------------------------------------------------------------- 1 | console.log('inject successfully!') 2 | -------------------------------------------------------------------------------- /packages/playground/mpa/public/inject.js: -------------------------------------------------------------------------------- 1 | console.log('inject successfully!') 2 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - packages/* 3 | - packages/playground/* 4 | -------------------------------------------------------------------------------- /packages/playground/custom-entry/src/test.vue: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /packages/playground/custom-entry/public/inject.js: -------------------------------------------------------------------------------- 1 | console.log('inject successfully!') 2 | -------------------------------------------------------------------------------- /packages/playground/mpa/src/App.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /packages/playground/mpa/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbenjs/vite-plugin-html/HEAD/packages/playground/mpa/public/favicon.ico -------------------------------------------------------------------------------- /vitest.config.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | import { defineConfig } from 'vite' 4 | 5 | export default defineConfig({}) 6 | -------------------------------------------------------------------------------- /packages/playground/basic/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbenjs/vite-plugin-html/HEAD/packages/playground/basic/public/favicon.ico -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "tabWidth": 2, 4 | "singleQuote": true, 5 | "printWidth": 80, 6 | "trailingComma": "all" 7 | } 8 | -------------------------------------------------------------------------------- /packages/playground/basic/src/App.vue: -------------------------------------------------------------------------------- 1 | 7 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # shellcheck source=./_/husky.sh 4 | . "$(dirname "$0")/_/husky.sh" 5 | 6 | npx --no-install commitlint --edit "$1" 7 | -------------------------------------------------------------------------------- /packages/playground/custom-entry/src/App.vue: -------------------------------------------------------------------------------- 1 | 7 | -------------------------------------------------------------------------------- /packages/core/src/utils/createHtmlFilter.ts: -------------------------------------------------------------------------------- 1 | import { createFilter } from '@rollup/pluginutils' 2 | 3 | export const htmlFilter = createFilter(['**/*.html']) 4 | -------------------------------------------------------------------------------- /packages/playground/custom-entry/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbenjs/vite-plugin-html/HEAD/packages/playground/custom-entry/public/favicon.ico -------------------------------------------------------------------------------- /packages/playground/mpa/src/other-app.vue: -------------------------------------------------------------------------------- 1 | 7 | -------------------------------------------------------------------------------- /.husky/common.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | command_exists () { 3 | command -v "$1" >/dev/null 2>&1 4 | } 5 | 6 | # Workaround for Windows 10, Git Bash and Yarn 7 | if command_exists winpty && test -t 1; then 8 | exec < /dev/tty 9 | fi 10 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | . "$(dirname "$0")/common.sh" 4 | 5 | [ -n "$CI" ] && exit 0 6 | 7 | # Format and submit code according to lintstagedrc.js configuration 8 | pnpm exec lint-staged --concurrent false 9 | -------------------------------------------------------------------------------- /packages/core/build.config.ts: -------------------------------------------------------------------------------- 1 | import { defineBuildConfig } from 'unbuild' 2 | 3 | export default defineBuildConfig({ 4 | clean: true, 5 | entries: ['./src/index'], 6 | declaration: true, 7 | rollup: { 8 | emitCJS: true, 9 | }, 10 | }) 11 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "octref.vetur", 4 | "dbaeumer.vscode-eslint", 5 | "stylelint.vscode-stylelint", 6 | "esbenp.prettier-vscode", 7 | "mrmlnc.vscode-less", 8 | "lokalise.i18n-ally", 9 | "antfu.iconify", 10 | "mikestead.dotenv", 11 | "heybourn.headwind" 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | trim_trailing_whitespace = true 7 | insert_final_newline = true 8 | indent_size = 2 9 | indent_style = space 10 | max_line_length = 100 11 | quote_type = single 12 | 13 | [*.md] 14 | indent_size = 2 15 | insert_final_newline = false 16 | trim_trailing_whitespace = false 17 | 18 | [*.py] 19 | indent_size = 4 20 | 21 | [Makefile] 22 | indent_style = tab 23 | -------------------------------------------------------------------------------- /packages/playground/mpa/src/other-main.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue' 2 | import App from './other-app.vue' 3 | import { createRouter, createWebHashHistory } from 'vue-router' 4 | 5 | const router = createRouter({ 6 | history: createWebHashHistory(), 7 | routes: [ 8 | { 9 | path: '/test', 10 | component: () => import('./test.vue'), 11 | }, 12 | ], 13 | }) 14 | 15 | createApp(App).use(router).mount('#app') 16 | -------------------------------------------------------------------------------- /packages/playground/mpa/other.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | <%- title %> 8 | 9 | 10 | 11 |
<%- ENV %>
12 |
<%- NODE_ENV %>
13 |
14 |
other page
15 | 16 | 17 | -------------------------------------------------------------------------------- /packages/playground/mpa/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | test-<%- title %> 8 | 9 | 10 | 11 |
12 |
index app
13 |
<%- ENV %>
14 |
<%- NODE_ENV %>
15 | 16 | 17 | -------------------------------------------------------------------------------- /packages/core/src/index.ts: -------------------------------------------------------------------------------- 1 | import type { PluginOption } from 'vite' 2 | import type { UserOptions } from './typing' 3 | import { createPlugin } from './htmlPlugin' 4 | import { createMinifyHtmlPlugin } from './minifyHtml' 5 | import consola from 'consola' 6 | 7 | consola.wrapConsole() 8 | 9 | export function createHtmlPlugin( 10 | userOptions: UserOptions = {}, 11 | ): PluginOption[] { 12 | return [createPlugin(userOptions), createMinifyHtmlPlugin(userOptions)] 13 | } 14 | -------------------------------------------------------------------------------- /packages/playground/custom-entry/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "playground-entry", 3 | "version": "0.0.1", 4 | "scripts": { 5 | "dev": "vite", 6 | "build": "vite build" 7 | }, 8 | "dependencies": { 9 | "vite-plugin-html": "workspace:*", 10 | "vue": "^3.2.31", 11 | "vue-router": "^4.0.14" 12 | }, 13 | "devDependencies": { 14 | "@vitejs/plugin-vue": "^2.2.4", 15 | "@vue/compiler-sfc": "^3.2.31", 16 | "vite": "^2.8.6" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/playground/custom-entry/static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | test-<%- title %> 8 | 9 | 10 | 11 |
<%- ENV %>
12 |
<%- NODE_ENV %>
13 |
14 | <%- injectScript %> 15 | 16 | 17 | -------------------------------------------------------------------------------- /packages/playground/mpa/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "playground-map", 3 | "version": "0.0.1", 4 | "private": true, 5 | "scripts": { 6 | "dev": "vite", 7 | "build": "vite build" 8 | }, 9 | "dependencies": { 10 | "vite-plugin-html": "workspace:*", 11 | "vue": "^3.2.31", 12 | "vue-router": "^4.0.14" 13 | }, 14 | "devDependencies": { 15 | "@vitejs/plugin-vue": "^2.2.4", 16 | "@vue/compiler-sfc": "^3.2.31", 17 | "vite": "^2.8.6" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/playground/mpa/src/main.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue' 2 | import App from './App.vue' 3 | import { createRouter, createWebHistory } from 'vue-router' 4 | // import { createRouter, createWebHashHistory } from 'vue-router' 5 | 6 | const router = createRouter({ 7 | history: createWebHistory(), 8 | routes: [ 9 | { 10 | path: '/test', 11 | component: () => import('./test.vue'), 12 | }, 13 | ], 14 | }) 15 | 16 | createApp(App).use(router).mount('#app') 17 | -------------------------------------------------------------------------------- /packages/playground/basic/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "playground-basic", 3 | "version": "0.0.1", 4 | "scripts": { 5 | "dev": "vite", 6 | "build": "vite build" 7 | }, 8 | "dependencies": { 9 | "axios": "^0.26.1", 10 | "vite-plugin-html": "workspace:*", 11 | "vue": "^3.2.31", 12 | "vue-router": "^4.0.14" 13 | }, 14 | "devDependencies": { 15 | "@vitejs/plugin-vue": "^2.2.4", 16 | "@vue/compiler-sfc": "^3.2.31", 17 | "vite": "^2.8.6" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/playground/custom-entry/src/main.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue' 2 | import App from './App.vue' 3 | import { createRouter, createWebHashHistory } from 'vue-router' 4 | // import { createRouter, createWebHistory } from 'vue-router' 5 | 6 | const router = createRouter({ 7 | history: createWebHashHistory(), 8 | routes: [ 9 | { 10 | path: '/test', 11 | component: () => import('./test.vue'), 12 | }, 13 | ], 14 | }) 15 | createApp(App).use(router).mount('#app') 16 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "ESNext", 4 | "target": "es2017", 5 | "moduleResolution": "node", 6 | "strict": true, 7 | "declaration": true, 8 | "noUnusedLocals": true, 9 | "esModuleInterop": true, 10 | "outDir": "dist", 11 | "lib": ["ESNext"], 12 | "sourceMap": false, 13 | "noEmitOnError": true, 14 | "noImplicitAny": false 15 | }, 16 | "include": ["./packages"], 17 | "exclude": ["**/dist", "**/node_modules", "**/test"] 18 | } 19 | -------------------------------------------------------------------------------- /packages/playground/basic/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | test-<%- title %> 8 | 9 | 10 | 11 |
<%- ENV %>
12 |
<%- NODE_ENV %>
13 |
14 | 15 | <%- injectScript %> 16 | 17 | 18 | -------------------------------------------------------------------------------- /packages/playground/basic/basic.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | test-<%- title %> 8 | 9 | 10 | 11 |
<%- ENV %>
12 |
<%- NODE_ENV %>
13 |
14 | basic-html 15 | 16 | <%- injectScript %> 17 | 18 | 19 | -------------------------------------------------------------------------------- /packages/playground/basic/src/main.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue' 2 | import App from './App.vue' 3 | import { createRouter, createWebHashHistory } from 'vue-router' 4 | // import { createRouter, createWebHistory } from 'vue-router' 5 | import axios from 'axios' 6 | 7 | const router = createRouter({ 8 | history: createWebHashHistory(), 9 | routes: [ 10 | { 11 | path: '/test', 12 | component: () => import('./test.vue'), 13 | }, 14 | ], 15 | }) 16 | 17 | createApp(App).use(router).mount('#app') 18 | 19 | axios.get('/api/users').then((res) => { 20 | console.log(res) 21 | }) 22 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "env": { 4 | "browser": true, 5 | "es2021": true, 6 | "es6": true, 7 | "node": true 8 | }, 9 | "extends": [ 10 | "eslint:recommended", 11 | "plugin:@typescript-eslint/recommended", 12 | "prettier" 13 | ], 14 | "parser": "@typescript-eslint/parser", 15 | "parserOptions": { 16 | "ecmaVersion": 12, 17 | "sourceType": "module" 18 | }, 19 | "rules": { 20 | "no-console": 1, 21 | "@typescript-eslint/no-non-null-assertion": "off", 22 | "@typescript-eslint/ban-ts-comment": "off", 23 | "@typescript-eslint/no-explicit-any": "off" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /packages/playground/custom-entry/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import vue from '@vitejs/plugin-vue' 3 | import { createHtmlPlugin } from 'vite-plugin-html' 4 | 5 | export default defineConfig({ 6 | server: { 7 | proxy: { 8 | '/api': 'http://localhost:8080', 9 | }, 10 | }, 11 | plugins: [ 12 | vue(), 13 | createHtmlPlugin({ 14 | minify: true, 15 | entry: 'src/main.ts', 16 | /** 17 | * @default index.html 18 | */ 19 | template: 'static/index.html', 20 | inject: { 21 | data: { 22 | title: 'index', 23 | injectScript: ``, 24 | }, 25 | }, 26 | }), 27 | ], 28 | }) 29 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Create Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - v* 7 | 8 | jobs: 9 | build: 10 | name: Create Release 11 | runs-on: ${{matrix.os}} 12 | 13 | strategy: 14 | matrix: 15 | os: [ubuntu-latest] 16 | fail-fast: false 17 | 18 | steps: 19 | - name: Checkout code 20 | uses: actions/checkout@master 21 | 22 | - name: Create Release for Tag 23 | id: release_tag 24 | uses: yyx990803/release-tag@master 25 | env: 26 | GITHUB_TOKEN: ${{ secrets.OPER_TOKEN }} 27 | with: 28 | tag_name: ${{ github.ref }} 29 | body: | 30 | Please refer to [CHANGELOG.md](https://github.com/anncwb/vite-plugin-html/blob/main/CHANGELOG.md) for details. 31 | -------------------------------------------------------------------------------- /packages/playground/basic/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import vue from '@vitejs/plugin-vue' 3 | import { createHtmlPlugin } from 'vite-plugin-html' 4 | 5 | export default defineConfig({ 6 | // base: '/aaa/', 7 | server: { 8 | proxy: { 9 | '/api': 'http://localhost:8080', 10 | }, 11 | }, 12 | plugins: [ 13 | vue(), 14 | createHtmlPlugin({ 15 | minify: true, 16 | inject: { 17 | data: { 18 | title: 'index', 19 | injectScript: ``, 20 | }, 21 | tags: [ 22 | { 23 | tag: 'div', 24 | attrs: { id: 'ddd' }, 25 | injectTo: 'body-prepend', 26 | }, 27 | ], 28 | }, 29 | }), 30 | ], 31 | }) 32 | -------------------------------------------------------------------------------- /packages/core/src/__tests__/utils.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, test, expect } from 'vitest' 2 | import { htmlFilter } from '../utils/createHtmlFilter' 3 | 4 | describe('utils test.', () => { 5 | test('createHtmlFilter > htmlFilter.', async () => { 6 | expect(htmlFilter('index.html')).toBe(true) 7 | expect(htmlFilter('index.html.html')).toBe(true) 8 | expect(htmlFilter('/index.html')).toBe(true) 9 | expect(htmlFilter('/index.html/index.html')).toBe(true) 10 | expect(htmlFilter('users/index.html')).toBe(true) 11 | 12 | expect(htmlFilter('index.htm')).toBe(false) 13 | expect(htmlFilter('index.css')).toBe(false) 14 | expect(htmlFilter('index.js')).toBe(false) 15 | expect(htmlFilter('index.ts')).toBe(false) 16 | expect(htmlFilter('./index.html')).toBe(false) 17 | }) 18 | }) 19 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | pull_request: 9 | branches: 10 | - main 11 | 12 | jobs: 13 | build: 14 | runs-on: ${{ matrix.os }} 15 | 16 | strategy: 17 | matrix: 18 | node-version: [16.x] 19 | os: [ubuntu-latest] 20 | fail-fast: false 21 | 22 | steps: 23 | - uses: actions/checkout@v2 24 | 25 | - name: Install pnpm 26 | uses: pnpm/action-setup@v2.0.1 27 | with: 28 | version: 6.28.0 29 | 30 | - name: Use Node.js ${{ matrix.node-version }} 31 | uses: actions/setup-node@v2 32 | with: 33 | node-version: ${{ matrix.node-version }} 34 | registry-url: https://registry.npmjs.org/ 35 | cache: 'pnpm' 36 | 37 | - run: pnpm install 38 | 39 | - name: Test 40 | run: pnpm run test 41 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Npm Publish 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | publish-npm: 10 | if: "contains(github.event.head_commit.message, 'release')" 11 | runs-on: ${{matrix.os}} 12 | 13 | strategy: 14 | matrix: 15 | os: [ubuntu-latest] 16 | node-version: [16.x] 17 | fail-fast: false 18 | 19 | steps: 20 | - name: Checkout 21 | uses: actions/checkout@v2 22 | 23 | - name: Install pnpm 24 | uses: pnpm/action-setup@v2.0.1 25 | with: 26 | version: 6.31.0 27 | 28 | - name: Use Node.js ${{ matrix.node-version }} 29 | uses: actions/setup-node@v2 30 | with: 31 | node-version: ${{ matrix.node-version }} 32 | registry-url: https://registry.npmjs.org/ 33 | cache: 'pnpm' 34 | 35 | - name: Install Dependencies 36 | run: pnpm install 37 | 38 | - name: Publish to NPM 39 | run: pnpm -r publish --access public --no-git-checks 40 | env: 41 | NODE_AUTH_TOKEN: ${{secrets.npm_token}} 42 | -------------------------------------------------------------------------------- /packages/playground/basic/server.js: -------------------------------------------------------------------------------- 1 | const http = require('http') 2 | const { URL } = require('url') 3 | 4 | const port = 8080 5 | 6 | const server = http.createServer((req, res) => { 7 | const { pathname } = new URL(req.url) 8 | 9 | // api开头的是API请求 10 | if (pathname.startsWith('/api')) { 11 | // 再判断路由 12 | if (pathname === '/api/users') { 13 | // 获取HTTP动词 14 | const method = req.method 15 | if (method === 'GET') { 16 | // 写一个假数据 17 | const resData = [ 18 | { 19 | id: 1, 20 | name: '小明', 21 | age: 18, 22 | }, 23 | { 24 | id: 2, 25 | name: '小红', 26 | age: 19, 27 | }, 28 | ] 29 | res.setHeader('Content-Type', 'application/json') 30 | res.end(JSON.stringify(resData)) 31 | return 32 | } 33 | } 34 | } 35 | res.statusCode = 200 36 | res.setHeader('Content-Type', 'text/plain') 37 | res.end('Hello World') 38 | }) 39 | 40 | server.listen(port, () => { 41 | console.log(`Server is running on http://127.0.0.1:${port}/`) 42 | }) 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020-present, Vben 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/core/src/typing.ts: -------------------------------------------------------------------------------- 1 | import type { Options as EJSOptions } from 'ejs' 2 | import type { Options as MinifyOptions } from 'html-minifier-terser' 3 | import type { HtmlTagDescriptor } from 'vite' 4 | 5 | export type Entry = string | Record 6 | 7 | export interface InjectOptions { 8 | /** 9 | * @description Data injected into the html template 10 | */ 11 | data?: Record 12 | 13 | tags?: HtmlTagDescriptor[] 14 | 15 | /** 16 | * @description ejs options configuration 17 | */ 18 | ejsOptions?: EJSOptions 19 | } 20 | 21 | export interface PageOption { 22 | filename: string 23 | template: string 24 | entry?: string 25 | injectOptions?: InjectOptions 26 | } 27 | 28 | export type Pages = PageOption[] 29 | 30 | export interface UserOptions { 31 | /** 32 | * @description Page options 33 | */ 34 | pages?: Pages 35 | 36 | /** 37 | * @description Minimize options 38 | */ 39 | minify?: MinifyOptions | boolean 40 | 41 | /** 42 | * page entry 43 | */ 44 | entry?: string 45 | 46 | /** 47 | * template path 48 | */ 49 | template?: string 50 | 51 | /** 52 | * @description inject options 53 | */ 54 | inject?: InjectOptions 55 | 56 | /** 57 | * output warning log 58 | * @default false 59 | */ 60 | verbose?: boolean 61 | } 62 | -------------------------------------------------------------------------------- /packages/core/src/__tests__/html.spec.ts: -------------------------------------------------------------------------------- 1 | import { createPlugin, createSpaPage, renderHtml } from '../htmlPlugin' 2 | import { describe, test, expect } from 'vitest' 3 | 4 | const createVitePlugin = () => { 5 | const { name } = createPlugin() 6 | return { name } 7 | } 8 | 9 | describe('html plugin test.', () => { 10 | test('make sure name.', async () => { 11 | const { name } = await createVitePlugin() 12 | expect(name).toEqual('vite:html') 13 | }) 14 | }) 15 | 16 | describe('function test.', () => { 17 | test('createSpaPage function test.', async () => { 18 | const page = createSpaPage('main.ts', 'public/index.html', { 19 | data: { a: '1' }, 20 | ejsOptions: {}, 21 | }) 22 | expect(page).toEqual({ 23 | filename: 'index.html', 24 | template: 'public/index.html', 25 | entry: 'main.ts', 26 | injectOptions: { data: { a: '1' }, ejsOptions: {} }, 27 | }) 28 | }) 29 | 30 | test('renderHtml function test.', async () => { 31 | const content = await renderHtml( 32 | ` 33 | 34 | 35 | 36 | <%- title %> 37 | <%- ENV_TITLE %> 38 | 39 | 40 | `, 41 | { 42 | injectOptions: { 43 | data: { title: 'test-title' }, 44 | }, 45 | viteConfig: {} as any, 46 | env: { 47 | ENV_TITLE: 'env-title', 48 | }, 49 | }, 50 | ) 51 | expect(content).toEqual(` 52 | 53 | 54 | 55 | test-title 56 | env-title 57 | 58 | 59 | `) 60 | }) 61 | }) 62 | -------------------------------------------------------------------------------- /packages/playground/mpa/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import vue from '@vitejs/plugin-vue' 3 | import { createHtmlPlugin } from 'vite-plugin-html' 4 | 5 | export default defineConfig({ 6 | server: { 7 | proxy: { 8 | '/api': 'http://localhost:8080', 9 | }, 10 | }, 11 | plugins: [ 12 | vue(), 13 | createHtmlPlugin({ 14 | minify: true, 15 | pages: [ 16 | { 17 | entry: 'src/main.ts', 18 | filename: 'index.html', 19 | template: 'index.html', 20 | injectOptions: { 21 | data: { 22 | title: 'index', 23 | injectScript: ``, 24 | }, 25 | tags: [ 26 | { 27 | injectTo: 'body-prepend', 28 | tag: 'div', 29 | attrs: { 30 | id: 'tag1', 31 | }, 32 | }, 33 | ], 34 | }, 35 | }, 36 | { 37 | entry: 'src/other-main.ts', 38 | filename: 'other.html', 39 | template: 'other.html', 40 | injectOptions: { 41 | data: { 42 | title: 'other page', 43 | injectScript: ``, 44 | }, 45 | tags: [ 46 | { 47 | injectTo: 'body-prepend', 48 | tag: 'div', 49 | attrs: { 50 | id: 'tag2', 51 | }, 52 | }, 53 | ], 54 | }, 55 | }, 56 | ], 57 | }), 58 | ], 59 | }) 60 | -------------------------------------------------------------------------------- /packages/core/src/minifyHtml.ts: -------------------------------------------------------------------------------- 1 | import type { PluginOption } from 'vite' 2 | import type { UserOptions } from './typing' 3 | import type { Options as MinifyOptions } from 'html-minifier-terser' 4 | import { minify as minifyFn } from 'html-minifier-terser' 5 | import { htmlFilter } from './utils/createHtmlFilter' 6 | 7 | function getOptions(minify: boolean): MinifyOptions { 8 | return { 9 | collapseWhitespace: minify, 10 | keepClosingSlash: minify, 11 | removeComments: minify, 12 | removeRedundantAttributes: minify, 13 | removeScriptTypeAttributes: minify, 14 | removeStyleLinkTypeAttributes: minify, 15 | useShortDoctype: minify, 16 | minifyCSS: minify, 17 | } 18 | } 19 | 20 | export async function minifyHtml( 21 | html: string, 22 | minify: boolean | MinifyOptions, 23 | ) { 24 | if (typeof minify === 'boolean' && !minify) { 25 | return html 26 | } 27 | 28 | let minifyOptions: boolean | MinifyOptions = minify 29 | 30 | if (typeof minify === 'boolean' && minify) { 31 | minifyOptions = getOptions(minify) 32 | } 33 | 34 | return await minifyFn(html, minifyOptions as MinifyOptions) 35 | } 36 | 37 | export function createMinifyHtmlPlugin({ 38 | minify = true, 39 | }: UserOptions = {}): PluginOption { 40 | return { 41 | name: 'vite:minify-html', 42 | // apply: 'build', 43 | enforce: 'post', 44 | async generateBundle(_, outBundle) { 45 | if (minify) { 46 | for (const bundle of Object.values(outBundle)) { 47 | if ( 48 | bundle.type === 'asset' && 49 | htmlFilter(bundle.fileName) && 50 | typeof bundle.source === 'string' 51 | ) { 52 | bundle.source = await minifyHtml(bundle.source, minify) 53 | } 54 | } 55 | } 56 | }, 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vite-plugin-html-monorepo", 3 | "version": "3.2.1", 4 | "private": true, 5 | "scripts": { 6 | "stub": "pnpm run prepack --filter ./packages -- --stub", 7 | "postinstall": "pnpm run stub", 8 | "log": "conventional-changelog -p angular -i CHANGELOG.md -s", 9 | "lint:pretty": "pretty-quick --staged", 10 | "lint:eslint": "eslint \"packages/**/*.{ts,tsx}\" --fix", 11 | "prepare": "husky install", 12 | "preinstall": "npx only-allow pnpm", 13 | "test": "vitest" 14 | }, 15 | "author": "Vben", 16 | "license": "MIT", 17 | "repository": { 18 | "type": "git", 19 | "url": "https://github.com/vbenjs/vite-plugin-html" 20 | }, 21 | "bugs": { 22 | "url": "https://github.com/vbenjs/vite-plugin-html/issues" 23 | }, 24 | "homepage": "https://github.com/vbenjs/vite-plugin-html/tree/master/#readme", 25 | "devDependencies": { 26 | "@commitlint/cli": "^16.2.1", 27 | "@commitlint/config-conventional": "^16.2.1", 28 | "@types/html-minifier-terser": "^6.1.0", 29 | "@types/jsdom": "^16.2.14", 30 | "@types/node": "^17.0.21", 31 | "@typescript-eslint/eslint-plugin": "^5.14.0", 32 | "@typescript-eslint/parser": "^5.14.0", 33 | "commitizen": "^4.2.4", 34 | "conventional-changelog-cli": "^2.2.2", 35 | "cross-env": "^7.0.3", 36 | "eslint": "^8.11.0", 37 | "eslint-config-prettier": "^8.5.0", 38 | "eslint-plugin-html": "^6.2.0", 39 | "husky": "^7.0.4", 40 | "lint-staged": "^12.3.5", 41 | "prettier": "^2.5.1", 42 | "rimraf": "^3.0.2", 43 | "tsup": "^5.12.1", 44 | "typescript": "^4.6.2", 45 | "unbuild": "^0.7.0", 46 | "vite": "^2.8.6", 47 | "vitest": "^0.6.1" 48 | }, 49 | "lint-staged": { 50 | "*": [ 51 | "prettier --write --ignore-unknown" 52 | ], 53 | "packages/*/{src,types}/**/*.ts": [ 54 | "eslint --ext .ts" 55 | ], 56 | "packages/**/*.d.ts": [ 57 | "eslint --ext .ts" 58 | ] 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /packages/core/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vite-plugin-html", 3 | "version": "3.2.2", 4 | "description": "A plugin for vite to Minimize index.html and use lodash.template template syntax in 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": "^1.6.0", 48 | "consola": "^2.15.3", 49 | "dotenv": "^16.0.0", 50 | "dotenv-expand": "^8.0.2", 51 | "ejs": "^3.1.6", 52 | "fast-glob": "^3.2.11", 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": "^17.0.21", 67 | "typescript": "^4.6.2", 68 | "vite": "^2.8.6" 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /packages/core/src/utils/index.ts: -------------------------------------------------------------------------------- 1 | import { expand } from 'dotenv-expand' 2 | import dotenv from 'dotenv' 3 | import { join, dirname } from 'pathe' 4 | import fse from 'fs-extra' 5 | 6 | export function loadEnv( 7 | mode: string, 8 | envDir: string, 9 | prefix = '', 10 | ): Record { 11 | if (mode === 'local') { 12 | throw new Error( 13 | `"local" cannot be used as a mode name because it conflicts with ` + 14 | `the .local postfix for .env files.`, 15 | ) 16 | } 17 | 18 | const env: Record = {} 19 | const envFiles = [ 20 | /** mode local file */ `.env.${mode}.local`, 21 | /** mode file */ `.env.${mode}`, 22 | /** local file */ `.env.local`, 23 | /** default file */ `.env`, 24 | ] 25 | 26 | for (const file of envFiles) { 27 | const path = lookupFile(envDir, [file], true) 28 | if (path) { 29 | const parsed = dotenv.parse(fse.readFileSync(path)) 30 | 31 | // let environment variables use each other 32 | expand({ 33 | parsed, 34 | // prevent process.env mutation 35 | ignoreProcessEnv: true, 36 | }) 37 | 38 | // only keys that start with prefix are exposed to client 39 | for (const [key, value] of Object.entries(parsed)) { 40 | if (key.startsWith(prefix) && env[key] === undefined) { 41 | env[key] = value 42 | } else if (key === 'NODE_ENV') { 43 | // NODE_ENV override in .env file 44 | process.env.VITE_USER_NODE_ENV = value 45 | } 46 | } 47 | } 48 | } 49 | 50 | return env 51 | } 52 | 53 | export function lookupFile( 54 | dir: string, 55 | formats: string[], 56 | pathOnly = false, 57 | ): string | undefined { 58 | for (const format of formats) { 59 | const fullPath = join(dir, format) 60 | if (fse.pathExistsSync(fullPath) && fse.statSync(fullPath).isFile()) { 61 | return pathOnly ? fullPath : fse.readFileSync(fullPath, 'utf-8') 62 | } 63 | } 64 | const parentDir = dirname(dir) 65 | if (parentDir !== dir) { 66 | return lookupFile(parentDir, formats, pathOnly) 67 | } 68 | } 69 | 70 | export async function isDirEmpty(dir: string) { 71 | return fse.readdir(dir).then((files) => { 72 | return files.length === 0 73 | }) 74 | } 75 | -------------------------------------------------------------------------------- /packages/core/src/__tests__/minify.spec.ts: -------------------------------------------------------------------------------- 1 | import { createMinifyHtmlPlugin, minifyHtml } from '../minifyHtml' 2 | import { describe, test, expect } from 'vitest' 3 | 4 | const createVitePlugin = () => { 5 | const { name, generateBundle } = createMinifyHtmlPlugin() 6 | return { name, generateBundle } 7 | } 8 | 9 | describe('minify html plugin test.', () => { 10 | test('make sure name.', async () => { 11 | const { name } = await createVitePlugin() 12 | expect(name).toEqual('vite:minify-html') 13 | }) 14 | 15 | test('make sure generateBundle.', async () => { 16 | const { generateBundle } = await createVitePlugin() 17 | const generate: any = generateBundle 18 | const testBundle = { 19 | test: { 20 | type: 'asset', 21 | fileName: 'index.html', 22 | source: ` 23 | 24 | 25 | 30 | 31 | `, 32 | }, 33 | } 34 | await generate(null, testBundle, false) 35 | expect(testBundle.test.source).toEqual( 36 | ``, 37 | ) 38 | }) 39 | 40 | test('minify is true.', async () => { 41 | const ret = await minifyHtml( 42 | ` 43 | 44 | 45 | `, 46 | true, 47 | ) 48 | expect(ret).toEqual(``) 49 | }) 50 | 51 | test('minify is false.', async () => { 52 | const ret = await minifyHtml( 53 | ` 54 | 55 | 56 | `, 57 | false, 58 | ) 59 | expect(ret).toEqual( 60 | ` 61 | 62 | 63 | `, 64 | ) 65 | }) 66 | 67 | test('minify css.', async () => { 68 | const ret = await minifyHtml( 69 | ` 70 | 71 | 76 | 77 | `, 78 | true, 79 | ) 80 | expect(ret).toEqual( 81 | ``, 82 | ) 83 | }) 84 | 85 | test('custom minify options.', async () => { 86 | const ret = await minifyHtml( 87 | ` 88 | 89 | 94 | 95 | `, 96 | { minifyCSS: true }, 97 | ) 98 | expect(ret).toEqual(` 99 | 100 | 101 | 102 | `) 103 | }) 104 | }) 105 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsdk": "./node_modules/typescript/lib", 3 | "typescript.enablePromptUseWorkspaceTsdk": true, 4 | //=========================================== 5 | //============= Editor ====================== 6 | //=========================================== 7 | "explorer.openEditors.visible": 0, 8 | "editor.tabSize": 2, 9 | "editor.defaultFormatter": "esbenp.prettier-vscode", 10 | "diffEditor.ignoreTrimWhitespace": false, 11 | //=========================================== 12 | //============= Other ======================= 13 | //=========================================== 14 | "breadcrumbs.enabled": true, 15 | "open-in-browser.default": "chrome", 16 | //=========================================== 17 | //============= files ======================= 18 | //=========================================== 19 | "files.eol": "\n", 20 | "search.exclude": { 21 | "**/node_modules": true, 22 | "**/*.log": true, 23 | "**/*.log*": true, 24 | "**/bower_components": true, 25 | "**/dist": true, 26 | "**/elehukouben": true, 27 | "**/.git": true, 28 | "**/.gitignore": true, 29 | "**/.svn": true, 30 | "**/.DS_Store": true, 31 | "**/.idea": true, 32 | "**/.vscode": false, 33 | "**/yarn.lock": true, 34 | "**/tmp": true, 35 | "out": true, 36 | "dist": true, 37 | "node_modules": true, 38 | "CHANGELOG.md": true, 39 | "examples": true, 40 | "res": true, 41 | "screenshots": true, 42 | "yarn-error.log": true, 43 | "**/pnpm-lock.yaml": true, 44 | "**/.yarn": true 45 | }, 46 | "files.exclude": { 47 | "**/.cache": true, 48 | "**/.editorconfig": true, 49 | "**/.eslintcache": true, 50 | "**/bower_components": true, 51 | "**/.idea": true, 52 | "**/tmp": true, 53 | "**/.git": true, 54 | "**/.svn": true, 55 | "**/.hg": true, 56 | "**/CVS": true, 57 | "**/.DS_Store": true 58 | }, 59 | "files.watcherExclude": { 60 | "**/.git/objects/**": true, 61 | "**/.git/subtree-cache/**": true, 62 | "**/.vscode/**": true, 63 | "**/node_modules/**": true, 64 | "**/tmp/**": true, 65 | "**/bower_components/**": true, 66 | "**/dist/**": true, 67 | "**/yarn.lock": true 68 | }, 69 | "stylelint.enable": true, 70 | "stylelint.packageManager": "yarn", 71 | "liveServer.settings.donotShowInfoMsg": true, 72 | "workbench.settings.enableNaturalLanguageSearch": false, 73 | "prettier.requireConfig": true, 74 | "workbench.sideBar.location": "left", 75 | "cSpell.words": [ 76 | "vben", 77 | "windi", 78 | "browserslist", 79 | "tailwindcss", 80 | "esnext", 81 | "antv", 82 | "tinymce", 83 | "qrcode", 84 | "sider", 85 | "pinia", 86 | "sider", 87 | "nprogress", 88 | "INTLIFY", 89 | "stylelint", 90 | "esno", 91 | "vitejs", 92 | "sortablejs", 93 | "mockjs", 94 | "codemirror", 95 | "iconify", 96 | "commitlint", 97 | "vditor", 98 | "echarts", 99 | "cropperjs", 100 | "logicflow", 101 | "vueuse", 102 | "zxcvbn", 103 | "lintstagedrc", 104 | "brotli", 105 | "tailwindcss", 106 | "sider" 107 | ] 108 | } 109 | -------------------------------------------------------------------------------- /README.zh_CN.md: -------------------------------------------------------------------------------- 1 | # vite-plugin-html 2 | 3 | **中文** | [English](./README.md) 4 | 5 | [![npm][npm-img]][npm-url] [![node][node-img]][node-url] 6 | 7 | ## 功能 8 | 9 | - HTML 压缩能力 10 | - EJS 模版能力 11 | - 多页应用支持 12 | - 支持自定义`entry` 13 | - 支持自定义`template` 14 | 15 | ## 安装 (yarn or npm) 16 | 17 | **node version:** >=12.0.0 18 | 19 | **vite version:** >=2.0.0 20 | 21 | ```bash 22 | yarn add vite-plugin-html -D 23 | ``` 24 | 25 | 或 26 | 27 | ```bash 28 | npm i vite-plugin-html -D 29 | ``` 30 | 31 | ## 使用 32 | 33 | - 在 `index.html` 中增加 EJS 标签,例如 34 | 35 | ```html 36 | 37 | 38 | 39 | 40 | <%- title %> 41 | <%- injectScript %> 42 | 43 | ``` 44 | 45 | - 在 `vite.config.ts` 中配置,该方式可以按需引入需要的功能即可 46 | 47 | ```ts 48 | import { defineConfig, Plugin } from 'vite' 49 | import vue from '@vitejs/plugin-vue' 50 | 51 | import { createHtmlPlugin } from 'vite-plugin-html' 52 | 53 | export default defineConfig({ 54 | plugins: [ 55 | vue(), 56 | createHtmlPlugin({ 57 | minify: true, 58 | /** 59 | * 在这里写entry后,你将不需要在`index.html`内添加 script 标签,原有标签需要删除 60 | * @default src/main.ts 61 | */ 62 | entry: 'src/main.ts', 63 | /** 64 | * 如果你想将 `index.html`存放在指定文件夹,可以修改它,否则不需要配置 65 | * @default index.html 66 | */ 67 | template: 'public/index.html', 68 | 69 | /** 70 | * 需要注入 index.html ejs 模版的数据 71 | */ 72 | inject: { 73 | data: { 74 | title: 'index', 75 | injectScript: ``, 76 | }, 77 | tags: [ 78 | { 79 | injectTo: 'body-prepend', 80 | tag: 'div', 81 | attrs: { 82 | id: 'tag', 83 | }, 84 | }, 85 | ], 86 | }, 87 | }), 88 | ], 89 | }) 90 | ``` 91 | 92 | 多页应用配置 93 | 94 | ```ts 95 | import { defineConfig } from 'vite' 96 | import { createHtmlPlugin } from 'vite-plugin-html' 97 | 98 | export default defineConfig({ 99 | plugins: [ 100 | createHtmlPlugin({ 101 | minify: true, 102 | pages: [ 103 | { 104 | entry: 'src/main.ts', 105 | filename: 'index.html', 106 | template: 'public/index.html', 107 | injectOptions: { 108 | data: { 109 | title: 'index', 110 | injectScript: ``, 111 | }, 112 | tags: [ 113 | { 114 | injectTo: 'body-prepend', 115 | tag: 'div', 116 | attrs: { 117 | id: 'tag1', 118 | }, 119 | }, 120 | ], 121 | }, 122 | }, 123 | { 124 | entry: 'src/other-main.ts', 125 | filename: 'other.html', 126 | template: 'public/other.html', 127 | injectOptions: { 128 | data: { 129 | title: 'other page', 130 | injectScript: ``, 131 | }, 132 | tags: [ 133 | { 134 | injectTo: 'body-prepend', 135 | tag: 'div', 136 | attrs: { 137 | id: 'tag2', 138 | }, 139 | }, 140 | ], 141 | }, 142 | }, 143 | ], 144 | }), 145 | ], 146 | }) 147 | ``` 148 | 149 | ## 参数说明 150 | 151 | `createHtmlPlugin(options: UserOptions)` 152 | 153 | ### UserOptions 154 | 155 | | 参数 | 类型 | 默认值 | 说明 | 156 | | -------- | ------------------------ | ------------- | ------------------------------- | 157 | | entry | `string` | `src/main.ts` | 入口文件 | 158 | | template | `string` | `index.html` | 模板的相对路径 | 159 | | inject | `InjectOptions` | - | 注入 HTML 的数据 | 160 | | minify | `boolean|MinifyOptions` | - | 是否压缩 html | 161 | | pages | `PageOption` | - | 多页配置 | 162 | 163 | ### InjectOptions 164 | 165 | | 参数 | 类型 | 默认值 | 说明 | 166 | | ---------- | --------------------- | ------ | ---------------------------------------------------------- | 167 | | data | `Record` | - | 注入的数据 | 168 | | ejsOptions | `EJSOptions` | - | ejs 配置项[EJSOptions](https://github.com/mde/ejs#options) | 169 | | tags | `HtmlTagDescriptor` | - | 需要注入的标签列表 | 170 | 171 | `data` 可以在 `html` 中使用 `ejs` 模版语法获取 172 | 173 | #### env 注入 174 | 175 | 默认会向 index.html 注入 `.env` 文件的内容,类似 vite 的 `loadEnv`函数 176 | 177 | ### PageOption 178 | 179 | | 参数 | 类型 | 默认值 | 说明 | 180 | | ------------- | --------------- | ------------- | ---------------- | 181 | | filename | `string` | - | html 文件名 | 182 | | template | `string` | `index.html` | 模板的相对路径 | 183 | | entry | `string` | `src/main.ts` | 入口文件 | 184 | | injectOptions | `InjectOptions` | - | 注入 HTML 的数据 | 185 | 186 | ### MinifyOptions 187 | 188 | 默认压缩配置 189 | 190 | ```ts 191 | collapseWhitespace: true, 192 | keepClosingSlash: true, 193 | removeComments: true, 194 | removeRedundantAttributes: true, 195 | removeScriptTypeAttributes: true, 196 | removeStyleLinkTypeAttributes: true, 197 | useShortDoctype: true, 198 | minifyCSS: true, 199 | ``` 200 | 201 | ### 运行示例 202 | 203 | ```bash 204 | pnpm install 205 | 206 | # spa 207 | cd ./packages/playground/basic 208 | 209 | pnpm run dev 210 | 211 | # map 212 | cd ./packages/playground/mpa 213 | 214 | pnpm run dev 215 | 216 | ``` 217 | 218 | ## 示例项目 219 | 220 | [Vben Admin](https://github.com/anncwb/vue-vben-admin) 221 | 222 | ## License 223 | 224 | MIT 225 | 226 | [npm-img]: https://img.shields.io/npm/v/vite-plugin-html.svg 227 | [npm-url]: https://npmjs.com/package/vite-plugin-html 228 | [node-img]: https://img.shields.io/node/v/vite-plugin-html.svg 229 | [node-url]: https://nodejs.org/en/about/releases/ 230 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # [3.2.1](https://github.com/vbenjs/vite-plugin-html/compare/v3.0.6...v3.2.1) (2023-12-26) 2 | 3 | ### Features 4 | 5 | - support vite5.0 6 | 7 | # [3.2.0](https://github.com/vbenjs/vite-plugin-html/compare/v3.0.6...v3.2.0) (2022-03-15) 8 | 9 | ### Bug Fixes 10 | 11 | - improve middleware logic ([36e143a](https://github.com/vbenjs/vite-plugin-html/commit/36e143a55b62710c7435ed0ca5ed4b035930c3af)) 12 | 13 | ### Features 14 | 15 | - support tags ([d07c9db](https://github.com/vbenjs/vite-plugin-html/commit/d07c9db4541432b94576e1fd4dce1b17098a60d0)) 16 | 17 | ## [3.0.6](https://github.com/vbenjs/vite-plugin-html/compare/v3.0.0...v3.0.6) (2022-02-10) 18 | 19 | ### Bug Fixes 20 | 21 | - fix base configuration causing local development errors ([6deeead](https://github.com/vbenjs/vite-plugin-html/commit/6deeead53f02007effd42b013d0eb03390f0a9a2)) 22 | - fs-extra no longer exports existsSync ([#25](https://github.com/vbenjs/vite-plugin-html/issues/25)) ([d6614da](https://github.com/vbenjs/vite-plugin-html/commit/d6614dae2ab5d2f53d54ec480e1212613819186b)) 23 | - history mode support ([20a7c69](https://github.com/vbenjs/vite-plugin-html/commit/20a7c69ed7f8f355bda923dd9f84717727276c67)) 24 | - make sure template defaults are correct ([697626c](https://github.com/vbenjs/vite-plugin-html/commit/697626cb62db42c1853788ac4019a834822b19e5)) 25 | 26 | ## [3.0.5](https://github.com/vbenjs/vite-plugin-html/compare/v3.0.0...v3.0.5) (2022-02-09) 27 | 28 | ### Bug Fixes 29 | 30 | - fix base configuration causing local development errors ([6deeead](https://github.com/vbenjs/vite-plugin-html/commit/6deeead53f02007effd42b013d0eb03390f0a9a2)) 31 | - fs-extra no longer exports existsSync ([#25](https://github.com/vbenjs/vite-plugin-html/issues/25)) ([d6614da](https://github.com/vbenjs/vite-plugin-html/commit/d6614dae2ab5d2f53d54ec480e1212613819186b)) 32 | - history mode support ([20a7c69](https://github.com/vbenjs/vite-plugin-html/commit/20a7c69ed7f8f355bda923dd9f84717727276c67)) 33 | - make sure template defaults are correct ([697626c](https://github.com/vbenjs/vite-plugin-html/commit/697626cb62db42c1853788ac4019a834822b19e5)) 34 | 35 | ## [3.0.4](https://github.com/vbenjs/vite-plugin-html/compare/v3.0.0...v3.0.4) (2022-02-07) 36 | 37 | ### Bug Fixes 38 | 39 | - fix base configuration causing local development errors ([6deeead](https://github.com/vbenjs/vite-plugin-html/commit/6deeead53f02007effd42b013d0eb03390f0a9a2)) 40 | - fs-extra no longer exports existsSync ([#25](https://github.com/vbenjs/vite-plugin-html/issues/25)) ([d6614da](https://github.com/vbenjs/vite-plugin-html/commit/d6614dae2ab5d2f53d54ec480e1212613819186b)) 41 | - history mode support ([4b0a54f](https://github.com/vbenjs/vite-plugin-html/commit/4b0a54fd08dd3e065b239ef0587dc683263db343)) 42 | - make sure template defaults are correct ([697626c](https://github.com/vbenjs/vite-plugin-html/commit/697626cb62db42c1853788ac4019a834822b19e5)) 43 | 44 | ## [3.0.2](https://github.com/vbenjs/vite-plugin-html/compare/v3.0.0...v3.0.2) (2022-01-28) 45 | 46 | ### Bug Fixes 47 | 48 | - fix base configuration causing local development errors ([6deeead](https://github.com/vbenjs/vite-plugin-html/commit/6deeead53f02007effd42b013d0eb03390f0a9a2)) 49 | - fs-extra no longer exports existsSync ([#25](https://github.com/vbenjs/vite-plugin-html/issues/25)) ([d6614da](https://github.com/vbenjs/vite-plugin-html/commit/d6614dae2ab5d2f53d54ec480e1212613819186b)) 50 | - make sure template defaults are correct ([697626c](https://github.com/vbenjs/vite-plugin-html/commit/697626cb62db42c1853788ac4019a834822b19e5)) 51 | 52 | ## [3.0.2](https://github.com/vbenjs/vite-plugin-html/compare/v3.0.0...v3.0.2) (2022-01-27) 53 | 54 | ### Bug Fixes 55 | 56 | - fs-extra no longer exports existsSync ([#25](https://github.com/vbenjs/vite-plugin-html/issues/25)) ([d6614da](https://github.com/vbenjs/vite-plugin-html/commit/d6614dae2ab5d2f53d54ec480e1212613819186b)) 57 | - make sure template defaults are correct ([697626c](https://github.com/vbenjs/vite-plugin-html/commit/697626cb62db42c1853788ac4019a834822b19e5)) 58 | 59 | ## [3.0.1](https://github.com/vbenjs/vite-plugin-html/compare/v3.0.0...v3.0.1) (2022-01-27) 60 | 61 | ### Bug Fixes 62 | 63 | - make sure template defaults are correct ([697626c](https://github.com/vbenjs/vite-plugin-html/commit/697626cb62db42c1853788ac4019a834822b19e5)) 64 | 65 | # [3.0.0-beta.1](https://github.com/vbenjs/vite-plugin-html/compare/v3.0.0...v3.0.0-beta.1) (2022-01-27) 66 | 67 | ### Bug Fixes 68 | 69 | - make sure template defaults are correct ([697626c](https://github.com/vbenjs/vite-plugin-html/commit/697626cb62db42c1853788ac4019a834822b19e5)) 70 | 71 | ## [2.1.2](https://github.com/vbenjs/vite-plugin-html/compare/v2.0.6...v2.1.2) (2021-12-27) 72 | 73 | ### Bug Fixes 74 | 75 | - ssr error,close [#18](https://github.com/vbenjs/vite-plugin-html/issues/18) ([f799d98](https://github.com/vbenjs/vite-plugin-html/commit/f799d9821ec9b22bbbfd8b92ddcb4d25cc18219e)) 76 | 77 | ### Features 78 | 79 | - expose minifyFn ([c6409dc](https://github.com/vbenjs/vite-plugin-html/commit/c6409dc25e118b47adff250ab4dd0a239803258b)) 80 | 81 | ## [2.1.1](https://github.com/vbenjs/vite-plugin-html/compare/v2.0.6...v2.1.1) (2021-09-27) 82 | 83 | ### Features 84 | 85 | - expose minifyFn ([c6409dc](https://github.com/vbenjs/vite-plugin-html/commit/c6409dc25e118b47adff250ab4dd0a239803258b)) 86 | 87 | # [2.1.0](https://github.com/vbenjs/vite-plugin-html/compare/v2.0.6...v2.1.0) (2021-08-20) 88 | 89 | ### Features 90 | 91 | - expose minifyFn ([c6409dc](https://github.com/vbenjs/vite-plugin-html/commit/c6409dc25e118b47adff250ab4dd0a239803258b)) 92 | - **inject:** inject the contents of the .env file into index.html ([5b52d7e](https://github.com/vbenjs/vite-plugin-html/commit/5b52d7e654c1056f6a368f4c7df0de8a63b61874)) 93 | 94 | ## [2.0.7](https://github.com/vbenjs/vite-plugin-html/compare/v2.0.6...v2.0.7) (2021-04-16) 95 | 96 | ### Features 97 | 98 | - expose minifyFn ([c6409dc](https://github.com/vbenjs/vite-plugin-html/commit/c6409dc25e118b47adff250ab4dd0a239803258b)) 99 | 100 | ## [2.0.4](https://github.com/vbenjs/vite-plugin-html/compare/v2.0.2...v2.0.4) (2021-04-05) 101 | 102 | ## [2.0.3](https://github.com/vbenjs/vite-plugin-html/compare/v2.0.2...v2.0.3) (2021-03-02) 103 | 104 | ## [2.0.2](https://github.com/vbenjs/vite-plugin-html/compare/v2.0.1...v2.0.2) (2021-02-23) 105 | 106 | ### Features 107 | 108 | - add gihub action ([3569c1c](https://github.com/vbenjs/vite-plugin-html/commit/3569c1c097be457fe91b5bb39c2bd56e61753fc9)) 109 | 110 | # [2.0.0-rc.1](https://github.com/vbenjs/vite-plugin-html/compare/v2.0.0-beta.2...v2.0.0-rc.1) (2021-01-29) 111 | 112 | ### Bug Fixes 113 | 114 | - css build error ([12cd218](https://github.com/vbenjs/vite-plugin-html/commit/12cd218c3f02267022eed06eea18c8e67d4119ff)) 115 | - fix css compression failure [#1](https://github.com/vbenjs/vite-plugin-html/issues/1) ([b62e99c](https://github.com/vbenjs/vite-plugin-html/commit/b62e99cd809a0a581cbd1e1dae9260d0b35e9abb)) 116 | 117 | ### Features 118 | 119 | - inject title to viteHtmlPluginOptions ([3b34151](https://github.com/vbenjs/vite-plugin-html/commit/3b341516cc78c83619d672ab1c5316a4339a92ac)) 120 | 121 | # 2.0.0-beta.2 (2021-01-03) 122 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vite-plugin-html 2 | 3 | **English** | [中文](./README.zh_CN.md) 4 | 5 | ## Features 6 | 7 | - HTML compression capability 8 | - EJS template capability 9 | - Multi-page application support 10 | - Support custom `entry` 11 | - Support custom `template` 12 | 13 | ## Install (yarn or npm) 14 | 15 | **node version:** >=12.0.0 16 | 17 | **vite version:** >=2.0.0 18 | 19 | ```bash 20 | yarn add vite-plugin-html -D 21 | ``` 22 | 23 | 或 24 | 25 | ```bash 26 | npm i vite-plugin-html -D 27 | ``` 28 | 29 | ## Usage 30 | 31 | - Add EJS tags to `index.html`, e.g. 32 | 33 | ```html 34 | 35 | 36 | 37 | 38 | <%- title %> 39 | <%- injectScript %> 40 | 41 | ``` 42 | 43 | - Configure in `vite.config.ts`, this method can introduce the required functions as needed 44 | 45 | ```ts 46 | import { defineConfig, Plugin } from 'vite' 47 | import vue from '@vitejs/plugin-vue' 48 | 49 | import { createHtmlPlugin } from 'vite-plugin-html' 50 | 51 | export default defineConfig({ 52 | plugins: [ 53 | vue(), 54 | createHtmlPlugin({ 55 | minify: true, 56 | /** 57 | * After writing entry here, you will not need to add script tags in `index.html`, the original tags need to be deleted 58 | * @default src/main.ts 59 | */ 60 | entry: 'src/main.ts', 61 | /** 62 | * If you want to store `index.html` in the specified folder, you can modify it, otherwise no configuration is required 63 | * @default index.html 64 | */ 65 | template: 'public/index.html', 66 | 67 | /** 68 | * Data that needs to be injected into the index.html ejs template 69 | */ 70 | inject: { 71 | data: { 72 | title: 'index', 73 | injectScript: ``, 74 | }, 75 | tags: [ 76 | { 77 | injectTo: 'body-prepend', 78 | tag: 'div', 79 | attrs: { 80 | id: 'tag', 81 | }, 82 | }, 83 | ], 84 | }, 85 | }), 86 | ], 87 | }) 88 | ``` 89 | 90 | Multi-page application configuration 91 | 92 | ```ts 93 | import { defineConfig } from 'vite' 94 | import { createHtmlPlugin } from 'vite-plugin-html' 95 | 96 | export default defineConfig({ 97 | plugins: [ 98 | createHtmlPlugin({ 99 | minify: true, 100 | pages: [ 101 | { 102 | entry: 'src/main.ts', 103 | filename: 'index.html', 104 | template: 'public/index.html', 105 | injectOptions: { 106 | data: { 107 | title: 'index', 108 | injectScript: ``, 109 | }, 110 | tags: [ 111 | { 112 | injectTo: 'body-prepend', 113 | tag: 'div', 114 | attrs: { 115 | id: 'tag1', 116 | }, 117 | }, 118 | ], 119 | }, 120 | }, 121 | { 122 | entry: 'src/other-main.ts', 123 | filename: 'other.html', 124 | template: 'public/other.html', 125 | injectOptions: { 126 | data: { 127 | title: 'other page', 128 | injectScript: ``, 129 | }, 130 | tags: [ 131 | { 132 | injectTo: 'body-prepend', 133 | tag: 'div', 134 | attrs: { 135 | id: 'tag2', 136 | }, 137 | }, 138 | ], 139 | }, 140 | }, 141 | ], 142 | }), 143 | ], 144 | }) 145 | ``` 146 | 147 | ## Parameter Description 148 | 149 | `createHtmlPlugin(options: UserOptions)` 150 | 151 | ### UserOptions 152 | 153 | | Parameter | Types | Default | Description | 154 | | --------- | ------------------------ | ------------- | ------------------------------------------------- | 155 | | entry | `string` | `src/main.ts` | entry file path | 156 | | template | `string` | `index.html` | relative path to the template | 157 | | inject | `InjectOptions` | - | Data injected into HTML | 158 | | minify | `boolean|MinifyOptions` | - | whether to compress html | 159 | | pages | `PageOption` | - | Multi-page configuration | 160 | 161 | ### InjectOptions 162 | 163 | | Parameter | Types | Default | Description | 164 | | ---------- | --------------------- | ------- | ------------------------------------------------------------------------- | 165 | | data | `Record` | - | injected data | 166 | | ejsOptions | `EJSOptions` | - | ejs configuration Options[EJSOptions](https://github.com/mde/ejs#options) | 167 | | tags | `HtmlTagDescriptor` | - | List of tags to inject | 168 | 169 | `data` can be accessed in `html` using the `ejs` template syntax 170 | 171 | #### Env inject 172 | 173 | By default, the contents of the `.env` file will be injected into index.html, similar to vite's `loadEnv` function 174 | 175 | ### PageOption 176 | 177 | | Parameter | Types | Default | Description | 178 | | ------------- | --------------- | ------------- | ----------------------------- | 179 | | filename | `string` | - | html file name | 180 | | template | `string` | `index.html` | relative path to the template | 181 | | entry | `string` | `src/main.ts` | entry file path | 182 | | injectOptions | `InjectOptions` | - | Data injected into HTML | 183 | 184 | ### MinifyOptions 185 | 186 | Default compression configuration 187 | 188 | ```ts 189 | collapseWhitespace: true, 190 | keepClosingSlash: true, 191 | removeComments: true, 192 | removeRedundantAttributes: true, 193 | removeScriptTypeAttributes: true, 194 | removeStyleLinkTypeAttributes: true, 195 | useShortDoctype: true, 196 | minifyCSS: true, 197 | ``` 198 | 199 | ### Run the playground 200 | 201 | ```bash 202 | pnpm install 203 | 204 | # spa 205 | cd ./packages/playground/basic 206 | 207 | pnpm run dev 208 | 209 | # map 210 | cd ./packages/playground/mpa 211 | 212 | pnpm run dev 213 | 214 | ``` 215 | 216 | ## Example project 217 | 218 | [Vben Admin](https://github.com/anncwb/vue-vben-admin) 219 | 220 | ## License 221 | 222 | MIT 223 | 224 | [npm-img]: https://img.shields.io/npm/v/vite-plugin-html.svg 225 | [npm-url]: https://npmjs.com/package/vite-plugin-html 226 | [node-img]: https://img.shields.io/node/v/vite-plugin-html.svg 227 | [node-url]: https://nodejs.org/en/about/releases/ 228 | -------------------------------------------------------------------------------- /packages/core/src/htmlPlugin.ts: -------------------------------------------------------------------------------- 1 | import type { ResolvedConfig, PluginOption } from 'vite' 2 | import type { InjectOptions, PageOption, Pages, UserOptions } from './typing' 3 | import { render } from 'ejs' 4 | import { isDirEmpty, loadEnv } from './utils' 5 | import { normalizePath } from 'vite' 6 | import { parse } from 'node-html-parser' 7 | import fs from 'fs-extra' 8 | import path from 'pathe' 9 | import fg from 'fast-glob' 10 | import consola from 'consola' 11 | import { dim } from 'colorette' 12 | import history from 'connect-history-api-fallback' 13 | import * as vite from 'vite' 14 | 15 | const DEFAULT_TEMPLATE = 'index.html' 16 | const ignoreDirs = ['.', '', '/'] 17 | 18 | const bodyInjectRE = /<\/body>/ 19 | 20 | function getViteMajorVersion() { 21 | return vite?.version ? Number(vite.version.split('.')[0]) : 2 22 | } 23 | 24 | export function createPlugin(userOptions: UserOptions = {}): PluginOption { 25 | const { 26 | entry, 27 | template = DEFAULT_TEMPLATE, 28 | pages = [], 29 | verbose = false, 30 | } = userOptions 31 | 32 | let viteConfig: ResolvedConfig 33 | let env: Record = {} 34 | const transformIndexHtmlHandler = async (html, ctx) => { 35 | const url = ctx.filename 36 | const base = viteConfig.base 37 | const excludeBaseUrl = url.replace(base, '/') 38 | const htmlName = path.relative(process.cwd(), excludeBaseUrl) 39 | 40 | const page = getPage(userOptions, htmlName, viteConfig) 41 | const { injectOptions = {} } = page 42 | const _html = await renderHtml(html, { 43 | injectOptions, 44 | viteConfig, 45 | env, 46 | entry: page.entry || entry, 47 | verbose, 48 | }) 49 | const { tags = [] } = injectOptions 50 | return { 51 | html: _html, 52 | tags: tags, 53 | } 54 | } 55 | 56 | return { 57 | name: 'vite:html', 58 | enforce: 'pre', 59 | configResolved(resolvedConfig) { 60 | viteConfig = resolvedConfig 61 | env = loadEnv(viteConfig.mode, viteConfig.root, '') 62 | }, 63 | config(conf) { 64 | const input = createInput(userOptions, conf as unknown as ResolvedConfig) 65 | 66 | if (input) { 67 | return { 68 | build: { 69 | rollupOptions: { 70 | input, 71 | }, 72 | }, 73 | } 74 | } 75 | }, 76 | 77 | configureServer(server) { 78 | let _pages: { filename: string; template: string }[] = [] 79 | const rewrites: { from: RegExp; to: any }[] = [] 80 | if (!isMpa(viteConfig)) { 81 | const template = userOptions.template || DEFAULT_TEMPLATE 82 | const filename = DEFAULT_TEMPLATE 83 | _pages.push({ 84 | filename, 85 | template, 86 | }) 87 | } else { 88 | _pages = pages.map((page) => { 89 | return { 90 | filename: page.filename || DEFAULT_TEMPLATE, 91 | template: page.template || DEFAULT_TEMPLATE, 92 | } 93 | }) 94 | } 95 | const proxy = viteConfig.server?.proxy ?? {} 96 | const baseUrl = viteConfig.base ?? '/' 97 | const keys = Object.keys(proxy) 98 | 99 | let indexPage: any = null 100 | for (const page of _pages) { 101 | if (page.filename !== 'index.html') { 102 | rewrites.push(createRewire(page.template, page, baseUrl, keys)) 103 | } else { 104 | indexPage = page 105 | } 106 | } 107 | 108 | // ensure order 109 | if (indexPage) { 110 | rewrites.push(createRewire('', indexPage, baseUrl, keys)) 111 | } 112 | 113 | server.middlewares.use( 114 | history({ 115 | disableDotRule: undefined, 116 | htmlAcceptHeaders: ['text/html', 'application/xhtml+xml'], 117 | rewrites: rewrites, 118 | }), 119 | ) 120 | }, 121 | 122 | transformIndexHtml: 123 | getViteMajorVersion() >= 5 124 | ? { 125 | // @ts-ignore 126 | order: 'pre', 127 | handler: transformIndexHtmlHandler, 128 | } 129 | : { 130 | enforce: 'pre', 131 | transform: transformIndexHtmlHandler, 132 | }, 133 | async closeBundle() { 134 | const outputDirs: string[] = [] 135 | 136 | if (isMpa(viteConfig) || pages.length) { 137 | for (const page of pages) { 138 | const dir = path.dirname(page.template) 139 | if (!ignoreDirs.includes(dir)) { 140 | outputDirs.push(dir) 141 | } 142 | } 143 | } else { 144 | const dir = path.dirname(template) 145 | if (!ignoreDirs.includes(dir)) { 146 | outputDirs.push(dir) 147 | } 148 | } 149 | const cwd = path.resolve(viteConfig.root, viteConfig.build.outDir) 150 | const htmlFiles = await fg( 151 | outputDirs.map((dir) => `${dir}/*.html`), 152 | { cwd: path.resolve(cwd), absolute: true }, 153 | ) 154 | 155 | await Promise.all( 156 | htmlFiles.map((file) => 157 | fs.move(file, path.resolve(cwd, path.basename(file)), { 158 | overwrite: true, 159 | }), 160 | ), 161 | ) 162 | 163 | const htmlDirs = await fg( 164 | outputDirs.map((dir) => dir), 165 | { cwd: path.resolve(cwd), onlyDirectories: true, absolute: true }, 166 | ) 167 | await Promise.all( 168 | htmlDirs.map(async (item) => { 169 | const isEmpty = await isDirEmpty(item) 170 | if (isEmpty) { 171 | return fs.remove(item) 172 | } 173 | }), 174 | ) 175 | }, 176 | } 177 | } 178 | 179 | export function createInput( 180 | { pages = [], template = DEFAULT_TEMPLATE }: UserOptions, 181 | viteConfig: ResolvedConfig, 182 | ) { 183 | const input: Record = {} 184 | if (isMpa(viteConfig) || pages?.length) { 185 | const templates = pages.map((page) => page.template) 186 | templates.forEach((temp) => { 187 | let dirName = path.dirname(temp) 188 | const file = path.basename(temp) 189 | 190 | dirName = dirName.replace(/\s+/g, '').replace(/\//g, '-') 191 | 192 | const key = 193 | dirName === '.' || dirName === 'public' || !dirName 194 | ? file.replace(/\.html/, '') 195 | : dirName 196 | input[key] = path.resolve(viteConfig.root, temp) 197 | }) 198 | 199 | return input 200 | } else { 201 | const dir = path.dirname(template) 202 | if (ignoreDirs.includes(dir)) { 203 | return undefined 204 | } else { 205 | const file = path.basename(template) 206 | const key = file.replace(/\.html/, '') 207 | return { 208 | [key]: path.resolve(viteConfig.root, template), 209 | } 210 | } 211 | } 212 | } 213 | 214 | export async function renderHtml( 215 | html: string, 216 | config: { 217 | injectOptions: InjectOptions 218 | viteConfig: ResolvedConfig 219 | env: Record 220 | entry?: string 221 | verbose?: boolean 222 | }, 223 | ) { 224 | const { injectOptions, viteConfig, env, entry, verbose } = config 225 | const { data, ejsOptions } = injectOptions 226 | 227 | const ejsData: Record = { 228 | ...(viteConfig?.env ?? {}), 229 | ...(viteConfig?.define ?? {}), 230 | ...(env || {}), 231 | ...data, 232 | } 233 | let result = await render(html, ejsData, ejsOptions) 234 | 235 | if (entry) { 236 | result = removeEntryScript(result, verbose) 237 | result = result.replace( 238 | bodyInjectRE, 239 | `\n`, 242 | ) 243 | } 244 | return result 245 | } 246 | 247 | export function getPage( 248 | { pages = [], entry, template = DEFAULT_TEMPLATE, inject = {} }: UserOptions, 249 | name: string, 250 | viteConfig: ResolvedConfig, 251 | ) { 252 | let page: PageOption 253 | if (isMpa(viteConfig) || pages?.length) { 254 | page = getPageConfig(name, pages, DEFAULT_TEMPLATE) 255 | } else { 256 | page = createSpaPage(entry, template, inject) 257 | } 258 | return page 259 | } 260 | 261 | function isMpa(viteConfig: ResolvedConfig) { 262 | const input = viteConfig?.build?.rollupOptions?.input ?? undefined 263 | return typeof input !== 'string' && Object.keys(input || {}).length > 1 264 | } 265 | 266 | export function removeEntryScript(html: string, verbose = false) { 267 | if (!html) { 268 | return html 269 | } 270 | 271 | const root = parse(html) 272 | const scriptNodes = root.querySelectorAll('script[type=module]') || [] 273 | const removedNode: string[] = [] 274 | scriptNodes.forEach((item) => { 275 | removedNode.push(item.toString()) 276 | item.parentNode.removeChild(item) 277 | }) 278 | verbose && 279 | removedNode.length && 280 | consola.warn(`vite-plugin-html: Since you have already configured entry, ${dim( 281 | removedNode.toString(), 282 | )} is deleted. You may also delete it from the index.html. 283 | `) 284 | return root.toString() 285 | } 286 | 287 | export function createSpaPage( 288 | entry: string | undefined, 289 | template: string, 290 | inject: InjectOptions = {}, 291 | ): PageOption { 292 | return { 293 | entry, 294 | filename: 'index.html', 295 | template: template, 296 | injectOptions: inject, 297 | } 298 | } 299 | 300 | export function getPageConfig( 301 | htmlName: string, 302 | pages: Pages, 303 | defaultPage: string, 304 | ): PageOption { 305 | const defaultPageOption: PageOption = { 306 | filename: defaultPage, 307 | template: `./${defaultPage}`, 308 | } 309 | 310 | const page = pages.filter((page) => { 311 | return path.resolve('/' + page.template) === path.resolve('/' + htmlName) 312 | })?.[0] 313 | return page ?? defaultPageOption ?? undefined 314 | } 315 | 316 | export function getHtmlInPages(page: PageOption, root: string) { 317 | const htmlPath = getHtmlPath(page, root) 318 | 319 | return readHtml(htmlPath) 320 | } 321 | 322 | export function getHtmlPath(page: PageOption, root: string) { 323 | const { template } = page 324 | const templatePath = template.startsWith('.') ? template : `./${template}` 325 | return path.resolve(root, templatePath) 326 | } 327 | 328 | export async function readHtml(path: string) { 329 | if (!fs.pathExistsSync(path)) { 330 | throw new Error(`html is not exist in ${path}`) 331 | } 332 | return await fs.readFile(path).then((buffer) => buffer.toString()) 333 | } 334 | 335 | function createRewire( 336 | reg: string, 337 | page: any, 338 | baseUrl: string, 339 | proxyUrlKeys: string[], 340 | ) { 341 | return { 342 | from: new RegExp(`^/${reg}*`), 343 | to({ parsedUrl }: any) { 344 | const pathname: string = parsedUrl.path 345 | 346 | const excludeBaseUrl = pathname.replace(baseUrl, '/') 347 | 348 | const template = path.resolve(baseUrl, page.template) 349 | 350 | if (excludeBaseUrl.startsWith("/static")) { 351 | return excludeBaseUrl; 352 | } 353 | 354 | if (excludeBaseUrl === '/') { 355 | return template 356 | } 357 | const isApiUrl = proxyUrlKeys.some((item) => 358 | pathname.startsWith(path.resolve(baseUrl, item)), 359 | ) 360 | return isApiUrl ? parsedUrl.path : template 361 | }, 362 | } 363 | } 364 | --------------------------------------------------------------------------------