-
-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
32 changed files
with
1,940 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
{ | ||
"$schema": "https://json.schemastore.org/tsconfig", | ||
"include": [], | ||
"compilerOptions": { | ||
"target": "ES2022", | ||
"lib": [ | ||
"esnext" | ||
], | ||
"types": [ | ||
"node" | ||
], | ||
"module": "nodenext", | ||
"skipLibCheck": true, | ||
"moduleResolution": "NodeNext", | ||
"resolveJsonModule": true, | ||
"importHelpers": true, | ||
/* Linting */ | ||
"strict": true, | ||
"noUnusedLocals": true, | ||
"noUnusedParameters": false, | ||
"noFallthroughCasesInSwitch": true, | ||
"exactOptionalPropertyTypes": false, | ||
"verbatimModuleSyntax": true, | ||
"checkJs": true, | ||
"allowUnusedLabels": false, | ||
"allowUnreachableCode": false, | ||
"noImplicitOverride": true, | ||
"noImplicitReturns": true, | ||
"noPropertyAccessFromIndexSignature": true, | ||
"noUncheckedIndexedAccess": true, | ||
/* Source maps */ | ||
"sourceMap": true, | ||
/* project references */ | ||
"declarationMap": true | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,139 @@ | ||
# @wroud/vite-plugin-ssg | ||
|
||
[![ESM-only package][package]][package-url] | ||
[![NPM version][npm]][npm-url] | ||
|
||
[package]: https://img.shields.io/badge/package-ESM--only-ffe536.svg | ||
[package-url]: https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c | ||
[npm]: https://img.shields.io/npm/v/@wroud/vite-plugin-ssg.svg | ||
[npm-url]: https://npmjs.com/package/@wroud/vite-plugin-ssg | ||
|
||
`@wroud/vite-plugin-ssg` is a Vite plugin for rendering React applications statically to HTML. It allows for generating pre-rendered HTML files, improving SEO and initial loading times for client-side rendered apps. | ||
|
||
## Features | ||
|
||
- **Static Site Generation (SSG)**: Renders your React application to static HTML at build time. | ||
- **Configurable Render Timeout**: Customize the render timeout for flexible rendering behavior. | ||
- **React Refresh**: Supports React Refresh for a seamless development experience. | ||
- **Hot Module Reload (HMR)**: Enables Hot Module Reload for faster iterative development. | ||
|
||
## Requirements | ||
|
||
- **React** 19 or higher | ||
- **Vite** 6 or higher | ||
|
||
## Installation | ||
|
||
Install via npm: | ||
|
||
```sh | ||
npm install @wroud/vite-plugin-ssg | ||
``` | ||
|
||
Install via yarn: | ||
|
||
```sh | ||
yarn add @wroud/vite-plugin-ssg | ||
``` | ||
|
||
## Usage | ||
|
||
### Options | ||
|
||
`@wroud/vite-plugin-ssg` provides a configurable option: | ||
|
||
```ts | ||
interface SSGOptions { | ||
renderTimeout?: number; | ||
} | ||
``` | ||
|
||
### Example Configuration | ||
|
||
To add `@wroud/vite-plugin-ssg` to your Vite application, follow these steps: | ||
|
||
1. Import the plugin: | ||
|
||
```ts | ||
import { ssgPlugin } from "@wroud/vite-plugin-ssg"; | ||
``` | ||
|
||
2. Add the plugin and configure the build entry point using `rollupOptions`: | ||
|
||
```ts | ||
import path from "path"; | ||
export default defineConfig({ | ||
build: { | ||
rollupOptions: { | ||
input: { | ||
app: path.resolve("src/index.tsx") + "?ssg", | ||
}, | ||
}, | ||
}, | ||
plugins: [ssgPlugin()], | ||
}); | ||
``` | ||
|
||
Here, we specify the entry point with a `?ssg` query to signal SSG processing. | ||
|
||
3. In `vite-env.d.ts` add: | ||
|
||
```ts | ||
/// <reference types="vite/client" /> | ||
/// <reference types="@wroud/vite-plugin-ssg/resolvers" /> | ||
``` | ||
|
||
4. Create your `index.tsx` file with a default export for the `Index` component: | ||
|
||
```tsx | ||
import type { IndexComponentProps } from "@wroud/vite-plugin-ssg"; | ||
import { Body, Head } from "@wroud/vite-plugin-ssg/react/components"; | ||
import main from "./index.tsx?ssg-main"; | ||
import indexStyles from "./index.css?url"; | ||
import { App } from "./App.js"; | ||
export default function Index(props: IndexComponentProps) { | ||
return ( | ||
<html lang="en"> | ||
<Head {...props}> | ||
<meta charSet="utf-8" /> | ||
<meta | ||
name="viewport" | ||
content="width=device-width, initial-scale=1" | ||
/> | ||
<title>Grid</title> | ||
<link rel="stylesheet" href={indexStyles} /> | ||
</Head> | ||
<Body {...props} after={<script type="module" src={main} />}> | ||
<App /> | ||
</Body> | ||
</html> | ||
); | ||
} | ||
``` | ||
|
||
- Use a default export for the `Index` component. | ||
- Import styles with `?url` and add them as a `<link>` element. | ||
- Import the main file (e.g., `index.tsx`) with the `?ssg-main` query and include it as a script element for client-side bootstrapping. | ||
|
||
### Generated Output | ||
|
||
When building the project, the following files will be generated: | ||
|
||
- `app/index.js`: Contains the exported `render` function for SSG, which can be manually used to generate HTML. | ||
- `app/index.html`: The statically generated HTML page. | ||
- `assets/index-[hash].css`: The CSS file with styles. | ||
- `assets/app/index-[hash].js`: The client bootstrap script. | ||
|
||
## Documentation | ||
|
||
For detailed usage and API reference, visit the [documentation site](https://wroud.dev). | ||
|
||
## Changelog | ||
|
||
All notable changes to this project will be documented in the [CHANGELOG](./CHANGELOG.md) file. | ||
|
||
## License | ||
|
||
This project is licensed under the MIT License. See the [LICENSE](./LICENSE) file for details. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
{ | ||
"name": "@wroud/vite-plugin-ssg", | ||
"description": "A Vite plugin for static site generation (SSG) with React. Renders React applications to static HTML for faster load times and improved SEO.", | ||
"version": "0.0.0", | ||
"type": "module", | ||
"sideEffects": [], | ||
"exports": { | ||
".": "./lib/ssgPlugin.js", | ||
"./*": "./lib/*.js", | ||
"./resolvers": { | ||
"types": "./resolvers.d.ts" | ||
} | ||
}, | ||
"scripts": { | ||
"ci:release": "yarn ci release --prefix vite-plugin-ssg-v", | ||
"ci:git-tag": "yarn ci git-tag --prefix vite-plugin-ssg-v", | ||
"ci:release-github": "yarn ci release-github --prefix vite-plugin-ssg-v", | ||
"build": "tsc -b", | ||
"watch:tsc": "tsc -b -w", | ||
"clear": "rimraf lib" | ||
}, | ||
"files": [ | ||
"package.json", | ||
"LICENSE", | ||
"README.md", | ||
"CHANGELOG.md", | ||
"lib", | ||
"!lib/**/*.d.ts.map", | ||
"!lib/**/*.test.js", | ||
"!lib/**/*.test.d.ts", | ||
"!lib/**/*.test.d.ts.map", | ||
"!lib/**/*.test.js.map", | ||
"!lib/tests", | ||
"!.tsbuildinfo" | ||
], | ||
"packageManager": "yarn@4.5.0", | ||
"devDependencies": { | ||
"@types/node": "^20", | ||
"@types/react": "npm:types-react@rc", | ||
"@types/react-dom": "npm:types-react-dom@rc", | ||
"@wroud/tsconfig": "workspace:^", | ||
"rimraf": "^6", | ||
"rollup": "^4", | ||
"typescript": "^5", | ||
"vite": ">=6.0.0-beta" | ||
}, | ||
"dependencies": { | ||
"change-case": "^5", | ||
"magic-string": "^0", | ||
"react": ">=19.0.0-rc", | ||
"react-dom": ">=19.0.0-rc", | ||
"style-to-object": "^1" | ||
}, | ||
"peerDependencies": { | ||
"@types/react": "*", | ||
"@types/react-dom": "*", | ||
"vite": ">=6.0.0-beta" | ||
}, | ||
"overrides": { | ||
"@types/react": "npm:types-react@rc", | ||
"@types/react-dom": "npm:types-react-dom@rc" | ||
}, | ||
"keywords": [ | ||
"vite", | ||
"vite-plugin", | ||
"static-site-generation", | ||
"ssg", | ||
"react", | ||
"static-rendering", | ||
"html-generation", | ||
"vite-ssg", | ||
"pre-rendering", | ||
"SEO" | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
declare module "*?ssg-main" { | ||
const src: string; | ||
export default src; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
import type { HtmlTagDescriptor } from "vite"; | ||
|
||
export interface IEntryDescriptor { | ||
chunk: string; | ||
entry: string; | ||
main?: IEntryDescriptor; | ||
htmlTags: HtmlTagDescriptor[]; | ||
} |
50 changes: 50 additions & 0 deletions
50
packages/@wroud/vite-plugin-ssg/src/isTagDescriptorEquals.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
import type { HtmlTagDescriptor } from "vite"; | ||
|
||
export function isTagDescriptorEquals( | ||
a: HtmlTagDescriptor, | ||
b: HtmlTagDescriptor, | ||
) { | ||
if (a.tag !== b.tag) { | ||
return false; | ||
} | ||
|
||
if ((a.injectTo || "head-prepend") !== (b.injectTo || "head-prepend")) { | ||
return false; | ||
} | ||
|
||
const aKeys = Object.keys(a.attrs || {}); | ||
const bKeys = Object.keys(b.attrs || {}); | ||
|
||
if (aKeys.length !== bKeys.length) { | ||
return false; | ||
} | ||
|
||
for (const key of aKeys) { | ||
if (a.attrs?.[key] !== b.attrs?.[key]) { | ||
return false; | ||
} | ||
} | ||
|
||
if (a.children !== b.children) { | ||
if (typeof a.children !== typeof b.children) { | ||
return false; | ||
} | ||
if (typeof a.children === "string" && a.children !== b.children) { | ||
return false; | ||
} | ||
|
||
if (Array.isArray(a.children) && Array.isArray(b.children)) { | ||
if (a.children.length !== b.children.length) { | ||
return false; | ||
} | ||
|
||
for (let i = 0; i < a.children.length; i++) { | ||
if (!isTagDescriptorEquals(a.children[i]!, b.children[i]!)) { | ||
return false; | ||
} | ||
} | ||
} | ||
} | ||
|
||
return true; | ||
} |
35 changes: 35 additions & 0 deletions
35
packages/@wroud/vite-plugin-ssg/src/parseHtmlTagsFromHtml.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
import type { HtmlTagDescriptor } from "vite"; | ||
import { unescapeHtml } from "./utils/unescapeHtml.js"; | ||
|
||
// Main regex to match tag, attributes, and content | ||
const mainRegex = /<(\w+)(\s+[^>]*?)?>([\s\S]*?)<\/\1>|<(\w+)(\s+[^>]*?)?\/?>/g; | ||
// Secondary regex to capture each attribute within the attributes string | ||
const attributeRegex = /(\w+)(?:="([^"]*)")?/g; | ||
|
||
export function parseHtmlTagsFromHtml(html: string): HtmlTagDescriptor[] { | ||
const matches = [...html.matchAll(mainRegex)]; | ||
|
||
const tags = matches.map<HtmlTagDescriptor>((match) => { | ||
const tagName = match[1] || match[4]; // Tag name | ||
const attributesString = match[2] || match[5] || ""; // Attributes as string | ||
const content = match[3] || ""; // Content | ||
|
||
// Extract individual attributes as an array of { name, value } pairs | ||
const attrs = [...attributesString.matchAll(attributeRegex)].reduce( | ||
(acc, [, name, value]) => ({ | ||
...acc, | ||
[name!]: unescapeHtml(value), | ||
}), | ||
{}, | ||
); | ||
|
||
return { | ||
tag: tagName, | ||
attrs, | ||
children: content, | ||
injectTo: "head-prepend", | ||
} as HtmlTagDescriptor; | ||
}); | ||
|
||
return tags; | ||
} |
15 changes: 15 additions & 0 deletions
15
packages/@wroud/vite-plugin-ssg/src/react/IndexComponent.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import type React from "react"; | ||
|
||
export interface IndexComponentContext { | ||
cspNonce?: string; | ||
base?: string; | ||
} | ||
|
||
export interface IndexComponentProps { | ||
renderTags: ( | ||
injectTo?: "head" | "body" | "head-prepend" | "body-prepend", | ||
) => React.ReactElement; | ||
context: IndexComponentContext; | ||
} | ||
|
||
export type IndexComponent = React.FC<IndexComponentProps>; |
Oops, something went wrong.