# Vite Plugin Generate HTML

A Vite plugin that writes generated `<script>` and `<link>` tags to separate files during the build.

The plugin reads Vite/Rollup output entry chunks, uses their final hashed asset filenames, and writes:

- one file containing `<script>` tags for JavaScript entry chunks
- one file containing `<link>` tags for CSS imported by those entry chunks

This is useful when your application shell is rendered outside Vite, for example in `.html`, `.cshtml` (Razor), `.php`, or another server-rendered template, but you still need references to Vite's hashed build assets.

It is inspired by Webpack's `HtmlWebpackPlugin` and is intended for similar use cases in Vite projects where generated asset tags need to be written into external template files.

---

## Installation

```bash
npm install --save-dev vite-plugin-generate-html
```

## Requirements

- Node.js 18 or newer
- npm 8 or newer

---

## Usage

```ts
// vite.config.ts
import { resolve } from "node:path";
import { defineConfig } from "vite";
import generateHtmlFiles from "vite-plugin-generate-html";

export default defineConfig({
  build: {
    rollupOptions: {
      input: {
        app: resolve(__dirname, "src/main.ts"),
      },
    },
  },
  plugins: [
    generateHtmlFiles({
      publicDir: "/dist/",
      jsEntryFile: resolve(__dirname, "../templates/javascript.html"),
      cssEntryFile: resolve(__dirname, "../templates/css.html"),
    }),
  ],
});
```

If `src/main.ts` imports CSS:

```ts
import "./styles.css";
```

the generated files can look like this after a Vite build.

`javascript.html`:

```html
<script type="module" src="/dist/app.abc123.js"></script>
```

`css.html`:

```html
<link href="/dist/app.abc123.css" rel="stylesheet" media="all" />
```

The plugin overwrites both configured output files on each build.

---

## Options

```ts
interface VitePluginGenerateHtmlOptions {
  /**
   * URL prefix added before generated JavaScript and CSS filenames.
   *
   * @default "/dist/"
   */
  publicDir?: string;

  /**
   * File path where generated `<script>` tags are written.
   */
  jsEntryFile: string;

  /**
   * File path where generated `<link>` tags are written.
   */
  cssEntryFile: string;

  /**
   * Custom attributes for generated tags by entry chunk name.
   */
  output?: Array<
    Record<
      string,
      {
        attrs: string[];
        linkAttrs: string[];
      }
    >
  >;

  /**
   * Limit generation to specific entry chunk names.
   * By default, all entry chunks are handled.
   */
  chunks?: string[];
}
```

### `publicDir`

`publicDir` is used as a URL prefix for generated asset references. It is not read from disk.

For example, with `publicDir: "/assets/"` and an output chunk named `app.abc123.js`, the generated script tag uses:

```html
<script type="module" src="/assets/app.abc123.js"></script>
```

### `jsEntryFile`

Required. The file path where generated `<script>` tags are written.

### `cssEntryFile`

Required. The file path where generated `<link>` tags are written.

Only CSS imported by handled Vite entry chunks is included. If a handled entry chunk has no imported CSS, no link tag is generated for that chunk.

### `output`

Use `output` to customize attributes for specific entry chunk names.

```ts
generateHtmlFiles({
  publicDir: "/dist/",
  jsEntryFile: resolve(__dirname, "../templates/javascript.html"),
  cssEntryFile: resolve(__dirname, "../templates/css.html"),
  output: [
    {
      app: {
        attrs: ['type="module"', "defer", 'data-entry="app"'],
        linkAttrs: ['rel="stylesheet"', 'media="all"'],
      },
    },
  ],
});
```

Generated output:

```html
<script type="module" defer data-entry="app" src="/dist/app.abc123.js"></script>
```

```html
<link href="/dist/app.abc123.css" rel="stylesheet" media="all" />
```

When `output` is provided, each handled entry chunk must have a matching key for script generation. If you only want to configure some entries, combine `output` with `chunks`.

Default attributes are used when `output` is omitted:

- `<script>`: `type="module"`
- `<link>`: `rel="stylesheet" media="all"`

### `chunks`

Use `chunks` to generate files for only specific entry chunk names.

```ts
generateHtmlFiles({
  publicDir: "/dist/",
  jsEntryFile: resolve(__dirname, "../templates/admin-javascript.html"),
  cssEntryFile: resolve(__dirname, "../templates/admin-css.html"),
  chunks: ["admin"],
});
```

This is helpful when a Vite build has multiple entry points and you want separate generated files for different pages or templates.

---

## Multiple Entry Points

You can use the plugin more than once to write separate files for different entry chunks.

```ts
plugins: [
  generateHtmlFiles({
    publicDir: "/dist/",
    jsEntryFile: resolve(__dirname, "../templates/app-js.html"),
    cssEntryFile: resolve(__dirname, "../templates/app-css.html"),
    chunks: ["app"],
  }),
  generateHtmlFiles({
    publicDir: "/dist/",
    jsEntryFile: resolve(__dirname, "../templates/admin-js.html"),
    cssEntryFile: resolve(__dirname, "../templates/admin-css.html"),
    chunks: ["admin"],
    output: [
      {
        admin: {
          attrs: ['type="module"', 'data-entry="admin"'],
          linkAttrs: ['rel="stylesheet"', 'media="all"'],
        },
      },
    ],
  }),
];
```

---

## Errors

The plugin throws an error when:

- `jsEntryFile` is missing
- `cssEntryFile` is missing
- `output` is not an array
- no handled entry chunks are found
- `output` is provided but a handled entry chunk has no matching configuration
- `attrs` or `linkAttrs` is not an array for a matching entry

---

## License

This project is licensed under the [MIT License](./LICENSE).
