From 4ec5863acacc8adbc5cf4e0a1ba13810adcfc6c9 Mon Sep 17 00:00:00 2001 From: Katsuhiro Ueno Date: Sat, 9 Dec 2023 15:07:25 +0900 Subject: [PATCH] move README.md to packages/vite-plugin-minissg --- README.md | 1407 +---------------------- packages/vite-plugin-minissg/README.md | 1416 ++++++++++++++++++++++++ 2 files changed, 1422 insertions(+), 1401 deletions(-) create mode 100644 packages/vite-plugin-minissg/README.md diff --git a/README.md b/README.md index 60b9b1c..1aa6b2a 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # vite-plugin-minissg -[Minissg] (pronounce it as "missing" 😄) is a minimum-sized, +Minissg (pronounce it as "missing" 😄) is a minimum-sized, configurable, and zero-JS static site generator, provided as a Vite plugin. @@ -12,1405 +12,10 @@ framework, so that it does not hide anything in JS and Vite from the users. With Minissg, any decision and convention for static site generation are up to you; for example, you can write your static webpages with -any of your favorite technologies including [React], [Preact], -[Solid], [Svelte], [Vue], [Markdown], [MDX], and even any combination -of them, in exchange of a little effort to write some JS code and some -configurations in `vite.config.js`. +any combination of your favorite JS technologies in exchange of a +little effort to write some JS code and some configurations in +`vite.config.js`. -The core part of Minissg consists only about 1,100 lines of code in -TypeScript (actually, this README has more lines than Minissg). -This small codebase allows you to easily understand what Minissg does -and does not. -Including this point, Minissg aims to be not an opinionated framework, -but a _transparent atmosphere_, which does not lead you to anything -but maximum freedom of static site programming. +See [packages/vite-plugin-minissg/README.md] for details. -## Getting Started - -Template projects using Minissg with Preact, React, Solid, Svelte, Vue, -and MDX are available in this repository. -See [template directory](./template/) for the full list of templates. -You can start your project with one of these templates by downloading -it. -For example, by using [tiged]: - -```bash -tiged uenob/vite-plugin-minissg/template/preact my_project -``` - -After that, change directory to the new directory and install all -dependencies by the `npm` command: - -```bash -cd my_project -npm install -``` - -The following scripts are initially available: -* `npm run build` for static site generation for production. -* `npm run serve` for preview of the build result. -* `npm run dev` for starting Vite's dev server. - -## Getting Started from Scratch - -### Hello, World - -To start a Minissg project without any template, -install Vite and Minissg by your favorite package manager: - -```bash -npm install vite vite-plugin-minissg -``` - -And then, put Minissg in the `plugins` list of `vite.config.js` -and specify at least one entry script in `build.rollupOptions.input`. - -```js -import { defineConfig } from "vite" -import minissg from "vite-plugin-minissg" - -export default defineConfig({ - build: { - rollupOptions: { - input: "index.html.js" // put a script file here - } - }, - plugins: [ - minissg({ - // put your configuration here - }) - ] -}) -``` - -Write the following code and save it in `index.html.js`: - -```js -export default ` - - My first Minissg site -

Hello, Vite + Minissg!

-`; -``` - -We are now ready to build a site. -Run `vite build` by your favorite package manager: - -```bash -npx vite build -``` - -Vite runs twice automatically with the following message and generates -an `index.html` file in `dist` directory: - -``` -vite v4.4.4 building SSR bundle for production... -✓ 2 modules transformed. -dist/index.html.mjs 0.18 kB -dist/assets/lib-d99b01dc.mjs 0.33 kB -✓ built in 146ms -vite v4.4.4 building for production... -✓ 1 modules transformed. -dist/index.html 0.12 kB │ gzip: 0.11 kB -✓ built in 16ms -``` - -The content of `dist/index.html` should be something like the -following, which is exactly same as the string literal written in -`index.html.js`: - -```html - - - My first Minissg site -

Hello, Vite + Minissg!

- -``` - -### Previewing and Dev Server - -You can use Vite's dev server and preview server for the website -development. -To view your site with live reloading, run Vite's Dev Server -by the following command: - -```bash -npx vite -``` - -To preview the production site generated by `vite build`, execute the -following command: - -```bash -npx vite preview -``` - -See Vite's manual for details of these commands. - -### Multiple Page Generation - -To generate multiple pages, do one of the following: - -1. Write a new file, say `hello.txt.js`, - - ```js - export default "Hello\n"; - ``` - - and add it to `build.rollupOptions.input`. - - ```js - import { defineConfig } from "vite" - import minissg from "vite-plugin-minissg" - - export default defineConfig({ - build: { - rollupOptions: { - input: ["index.html.js", "hello.txt.js"] - } - }, - plugins: [minissg()] - }) - ``` - -2. Define `entries` function in `index.html.js` instead of the `default` - export and indicate multiple routes in it. - - ```js - export const entries = () => ({ - 'index.html': - { - default: ` - - My first Minissg site -

Hello, Vite + Minissg!

- ` - }, - 'hello.txt': { default: "Hello\n" } - }); - ``` - -### Using a Component Library - -The page construction can be done with a component library. -For your convenience, Minissg provides _renderers_ that -serialize components into HTML file contents. -Here, we use [React] to do the same thing as the above. -First of all, install React and its Vite plugin by the following -command: - -```bash -npm install react react-dom @vitejs/plugin-react @minissg/render-react -``` - -Write `vite.config.js` as follows: - -```js -import { defineConfig } from "vite" -import minissg from "vite-plugin-minissg" -import react from "@vitejs/plugin-react" // ADDED -import minissgReact from "@minissg/render-react" // ADDED - -export default defineConfig({ - build: { - rollupOptions: { - input: "./index.html.jsx?render" // MODIFIED. NOTE: "./" is mandatory - } - }, - plugins: [ - minissg({ - render: { - "**/*.jsx": minissgReact() // ADDED - }, - plugins: () => [react()] // ADDED - }) - ] -}) -``` - -The above config includes three important changes from the previous -one: -1. Specify a `*.jsx` file in `build.rollupOptions.input` with - `?render` query. - The `?render` query indicates that the component exported by this - file must be serialized by the renderer specified in Minissg's - `render` option. -2. Associate `*.jsx` files to the renderer of React components - (`minissgReact`) by setting Minissg's `render` option. -3. Add the React plugin to Minissg's `plugins` option of the above - form, not in that of Vite's config. - The plugins put here are included in both the server-side and - client-side run of Vite, whereas plugins specified in Vite's config - are used only in the server-side run. - -Write `index.html.jsx` like this: - -```jsx -export default function() { - return ( - - My first Minissg site -

Hello, Vite + Minissg + React!

- - ) -}; -``` - -Run `vite build` and find `dist/index.html` created from the JSX file. - -### Authoring with Markdown - -Minissg does not provide any capability to deal with Markdown files, -but you can combine it with your favorite Markdown libraries. -Say [@mdx-js/rollup] for such a Markdown processor. -The Vite config must be extended as follows: - -```js -import { defineConfig } from "vite" -import react from "@vitejs/plugin-react" -import mdx from "@mdx-js/rollup" // ADDED -import minissg from "vite-plugin-minissg" -import minissgReact from "@minissg/render-react" - -export default defineConfig({ - build: { - rollupOptions: { - input: "./index.html.md?render" // MODIFIED - } - }, - plugins: [ - minissg({ - render: { - "**/*.{jsx,md}": minissgReact() // MODIFIED - }, - plugins: () => [ - react(), - mdx() // ADDED - ] - }) - ] -}) -``` - -Then, write a markdown file named `index.html.md` with the following -content: - -```markdown -# My first Minissg site -Hello, Vite + Minissg + MDX! -``` - -And execute `vite build` to generate the page. - -## How It Works - -### Vite Runs Twice - -Minissg actually does the following: - -1. Run Vite in SSR mode and bundle all the files specified in - `build.rollupOptions.input` into a single JavaScript program. -2. Traverse file dependencies in the generated program and determine - the set of client-side codes and style sheets for each page to be - generated. -3. Execute the generated program and obtain the list of pages and - their contents to be generated. -4. Run Vite again to generate client-side codes, style sheets, and - assets. - -In what follows, we refer to the first and second run of Vite as -_server-side run_ and _client-side run_, respectively. -We also refer to the program generated by or given to the first run -as _server-side code_. - -For integrity between the two runs, Minissg loads server-side code -not only in server-side run but also client-side run. -Loading server-side code in client-side run is needed to yield assets -that are referred only from server-side code or are generated by some -server-side plugins and used in client-side code. -Assets generated in server-side run are all discarded for integrity. -Only the files generated by client-side run are left in the -destination site. - -### Module Tree - -Server-side code consists of a collection of _modules_, which -constitute the hierarchy of files in the website to be generated. -The variation of a module is defined as follows in TypeScript: - -```typescript -type Module = - | { entries: (arg: EntriesArg) => Module | PromiseLike } - | { default: Content | PromiseLike } - | Record> - | Iterable<[string, Module | PromiseLike]>; - -export type Content = - | string - | ArrayBufferLike - | ArrayBufferView - | Blob - | null - | undefined -``` - -The definition of `EntriesArg` will be given later in Contextual -Information of Modules section. - -The `Module` type is the type of modules expected by Minissg. -Intuitively: - -1. A module may have `entries` function that returns another module. - This allows the module to delegate file generation to another - module. -2. A module may also have `default` value that gives Minissg the - content of a destination file. - The content can be given in several forms including string, - Uint8Array, ArrayBuffer, and Blob. - Except for nullish values, `Content` can be included in an argument - of `Blob` constructor. - This allows modules to generate any kind of files from various - sources; for example, a module can download something by fetch - API and pass its response as a Blob to Minissg. - The `null` or `undefined` content means "not found." - If a module have both `entries` and `default`, `entries` has precedence - and `default` is ignored. -3. A module may also be an Iterable object that enumerates multiple - routing. - In this case, each item in the Iterable object must be a pair - (array with two elements) of a string and module, where the string - is a relative path that will be joined with the name of currently - requested file. - See Static Routing section below for the details of name - concatination. - -Empty module is regarded as `{ default: null }`. - -Through the `entries` functions and mapping objects, the collection of -modules constitute a tree structure. -The root of this tree is the _top-level module_, which is a virtual -module generated in accordance with Vite's `build.rollupOptions.input` -config. -The top-level module is constructed depending on the structure of -`build.rollupOptions.input` by the following rules: - -1. If `build.rollupOptions.input` is a single string, the top-level - module is a singleton mapping from the name of the given file - to the module provided by the file. - For example, - ```js - { build: { rollupOptions: { input: "index.html.js" } } } - ``` - means that `index.html.js` is the module providing the content of - `index.html` file. - This is equivalent to the following module: - ```js - { "index.html": { entries: () => import("./index.html.js") } } - ``` -2. If it is an array of strings, the top-level modules is a mapping - from their names to their modules. - For example, - ```js - { build: { rollupOptions: { input: ["index.html.js", "hello.txt.js"] } } } - ``` - means the following module: - ```js - { - "index.html": { entries: () => import("./index.html.js") }, - "hello.txt": { entries: () => import("./hello.txt") } - } - ``` -3. If it is an object, Minissg uses it as the mapping from names to - modules. - For example, if the following config is given, - ```js - { build: { rollupOptions: { input: { "index.html": "index.js" } } } } - ``` - Minissg uses `index.js` to generate `index.html`, i.e., the - top-level module should look like the following: - ```js - { "index.html": { entries: () => import("./index.js") } } - ``` - -### Static Routing - -Each module is uniquely associated to a name of a destination file. -In what follows, we refer to such a name as the _name_ of a module. - -The name of the top-level module is always `index.html`. -For other modules, which must be child modules of a module, -their names are computed from the name of their parent module by the -following rule: - -1. The name of the module returned by `entries` function is same - as that of the module owning the `entries` function. -2. The name of a module associated to a relative path in a parent - module is obtained by appending the relative path to the end of the - name of the parent module. - The appending is done with equating path fragment `index.html` with - empty fragment. - Detailed procedure of this appending is the following: - 1. If the name of the parent module ends with `index.html` fragment, - eliminate it. - 2. If the relative path includes `.` fragment, eliminate all of - them. - 2. If the name of the parent module does not end with `/` - and the relative path is not empty, add `/` to the beginning of - the relative path. - 4. Concatenate the output of the parent module and the relative - path in this order. - 5. If the concatenation result ends with `/`, append `index.html` - to the end of it. - -For static site generation, Minissg visits all of the modules -reachable from the top-level module. -During this traversal, Minissg calls all `entries` functions -to determine the entire set of modules. -After that, for each module that have effective `default` value, -Minissg generates a file whose name is the name of the module and -whose content is the `default` value. - -Note that the name of a module is not always unique. -If two modules has the same name and effective `default` values, the -first-visited one precedes another. -Strictly speaking, the precedence is determined by the pre-order of -the entire module tree. -Intuitively, the order of precedence is the order of modules appearing -in a parent module. - -A simple but powerful way to organize multiple modules is to use -Vite's `import.meta.glob` feature. -The following example defines the `entries` function that -includes all of the `*.md` files in the `pages` directory to -generate `*/index.html` files from them. - -```js -const mdFiles = import.meta.glob("./**/*.md", { query: { render: "" } }); - -// transform filenames *.md to */ and import functions to entries functions -const modules = Object.entries(mdFiles).map(([filename, entries]) => { - return [filename.replace(/\.md$/, "/"), { entries }] -}); - -export const entries = () => modules; -``` - -By exploiting the fact that `index.html` and `./` fragments in a -relative path are ignored, you can overlay a module tree with other -trees. -This is useful to separate files for each concern regardless of the -hierarchy of destination files. -A typical example is, as shown below, to separate the top page from -other pages that are generated from Markdown files. - -```js -export const entries = () => ({ - // Only the top page has a special construction. - 'index.html': { entries: () => import("./index.jsx") }, - // But others have the same layout and generated in the same way. - '.': { entries: () => import("./pages.js") } -}); -``` - -### Renderers - -Renderer is a Minissg's feature that transforms the default export -of a file to a content of a generated file. -To apply a renderer to a file, add `?render` query to the import -referring to the file. - -For example, suppose that we are using Minissg with React. -We usually write a React component as a separate file, say -`Count.jsx`, like the following: - -```jsx -import { useState } from "react"; - -export default function Count({ init }) { - const [count, setCount] = useState(init ?? 0); - return ( - - ); -}; -``` - -The default export of this file is a React component, which is a -function, not of type `Content` given in the above type -definitions. -Therefore, without any additional conversion, Minissg cannot generate -a file from this module. -The purpose of renderers is to provide this kind of conversion. -By applying the renderer for React components to this file, we -obtain the serialized string of this component. - -To apply a renderer to a file, add `?render` query to its import like -this: - -```js -import content from "./Count.jsx?render"; -``` - -The `content` variable contains a PromiseLike object of a string -that can be accepted by Minissg as a file content. - -The association between files and their renderers is given in -Minissg's `render` option in `vite.config.js`. -The `render` option associates a glob pattern to a renderer. -Minissg searches for a renderer of a particular file in accordance with -this association. -See Plugin Options section for details of the `render` option. - -Minissg provides several renderers for popular component systems -in separate packages. -To use renderers, install the following packages and import them in -`vite.config.js`: - -* `@minissg/render-preact` for [Preact] components. -* `@minissg/render-react` for [React] components. -* `@minissg/render-solid` for [Solid] components. -* `@minissg/render-svelte` for [Svelte] components. -* `@minissg/render-vue` for [Vue] components. - -Note that using renderers is not essential; -you can avoid it by writing your own serializer by your hand and -include it in server-side code. -You can even write custom renderers and give it to Minissg through -Minissg's `render` option. -See User-defined Renderer and Hydration section for details. - -### Style Sheets - -Each generated HTML file may have static links to CSS files. -The set of style sheets of each page is the set of all of the `*.css` -files (or other style sheet files supported by Vite) imported in -server-side code in the middle of loading the module corresponding to -the file. - -While this looks similar to Vite's default handing of style sheets, -Minissg provides more; -if a dynamic import occurs in the middle of the path to a module, -dynamically imported `*.css` files are also included in the set of -style sheets of that module. -The set of style sheets is computed independently for each module. -For example, suppose the following three files: - -1. `index.html.js` - ```js - import "./index.css"; - export const entries = () => ({ - "foo.html" => { entries: () => import("./foo.html.js") }, - "bar.html" => { entries: () => import("./bar.html.js") } - }); - ``` -2. `foo.html.js` - ```js - import "./foo.css"; - export default " ... "; - ``` -3. `bar.html.js` - ```js - import "./bar.css"; - export default " ... "; - ``` - -The `index.html.js` file provides the route to two modules -`foo.html` and `bar.html` provided by `foo.html.js` and `bar.html.js`, -respectively. -The `foo.html.js` and `bar.html.js` have `default` export and -consequently `foo.html` and `bar.html` are generated as a result. -The set of style sheets of these two HTML files depends on the path of -execution to these modules as follows. - -1. Regardless of the two modules, `index.html.js` must be executed - and therefore `index.css` is inevitably imported. - Consequently, a link to `index.css` is included in both `foo.html` - and `bar.html`. -2. `foo.css` is imported only when `foo.html.js` is dynamically - imported. - Hence, `foo.css` is additionally included in `foo.html`. - Since dynamic import of `foo.html.js` is not relevant to - `bar.html`, `foo.css` is not included in `bar.html`. -3. Similarly, `bar.css` is included only in `bar.html`. - -As a result, the following two links are included in `foo.html`: - -```html - - -``` - -and are the following in `bar.html`: - -```html - - -``` - -Note that these tags are not what is included exactly in the final -output. -Vite transforms `*.css` files, bundles them appropriately, -and then injects the optimized result in the generated HTML files. - -### Client-side Code - -All the modules in server-side code are just for server-side file -generation and therefore are not left in the output. -To include some code in the generated site in order to execute it -on client side, import it in server-side code with `?client` query. - -For example, suppose the following two files: - -1. `index.html.js` - ```js - import "./foo.js?client"; - export default ` - Hello`; - ``` -2. `foo.js` - ```js - document.body.appendChild(document.createTextNode('Hi!')); - ``` - -By building the site up with `index.html.js`, we find `dist/index.html` -that refers a script like the following: - -```html - - - - Hello - - - - -``` - -The set of scripts to be included in each page is determined by the -same manner as style sheets; -all the imports with `?client` query executed during loading a module -is included in the output of that module. - -The client-side code is processed by Vite as well as server-side code. -This means that you can write client-side code in any language -that Vite (or one of its plugin) supports. -Vite translates client-side code and minifies all its chunks as usual. -Minissg does not throw anything more at Vite, but does not take -anything more from Vite. - -### Partial Hydration - -Minissg provides a simple partial hydration mechanism -in the sense of [island architecture] for several component systems. -To enable partial hydration for a component, import the component -with `?hydrate` query. -The code imported with `?hydrate` query is included in both -server-side and client-side code to embed its initial view in the -static file and to hydrate the view in the web browsers. - -As an example, here we attempt to use the `Count.jsx` presented in -Renderer section. -At first, To enable partial hydration feature for `*.jsx` files, -associate `**/*.jsx` to React renderer in Minissg plugin's `render` -option. - -By importing `Count.jsx` with `?hydrate` query as follows, we turn on -the hydration of this component: - -```jsx -import Count from "./Count.jsx?hydrate"; - -export default function() { - return ; -}; -``` - -This results in the following output: - -```html - - - - - -
- -
- - -``` - -The `/assets/hydrate-7c5f4dec.js` file is the top-level client-side -code that hydrates the `Count` component. -It searches for the element with `data-hydrate="g_zZoKsE"` attribute -for the target of hydration, where `"g_zZoKsE"` is the unique -identifier of the `Count` component. -The `data-hydrate-args` attribute holds the serialized argument of -`Count` component, which is passed to the component for hydration. - -Note that this is not the only way for hydration in Minissg. -You can hydrate a component in a different way than `?hydrate` by -writing it in your program. -Minissg does not force you anything. - -## Advanced Features - -### Configuring Client-side Run - -As described above, Vite runs twice for static site generation. -The two runs are for different purposes and therefore may have -different settings. -The top-level config of `vite.config.js` is used only in server-side -run, not in client-side run. -The config of client-side run is automatically generated by Missing -based on the top-level config and the result of server-side run. -To tweak the config of client-side run, Minissg provides `config` -option that will be merged into the config of client-side run. -For example, to turn on the minify option only in client-side run, -write it in the `config` option as follows: - -```js -import { defineConfig } from "vite" - -export default defineConfig({ - build: { - rollupOptions: { - input: "./index.html.js" - } - }, - plugins: [ - minissg({ - config: { - build: { - minify: true // enable minify only on client-side run - } - } - }) - ] -}) -``` - -Minissg computes the config of client-side run from the following -three sources: the top-level config, the results of server-side run, -and the `config` option of the Minissg plugin. -Almost all the settings in the top-level config are inherited to -client-side run except for the following: - -* `plugins` are never shared between the two runs. - Plugins must be instantiated for each run. - To enable the same set of plugins in both runs, which is a usual - requirement, duplicate it in both the top-level config and Minissg's - `config`, as seen in the following example: - ```js - import { defineConfig } from "vite" - import react from "@vitejs/plugin-react" - - export default defineConfig({ - ... - plugins: [ - react(), // plugin for server-side run - minissg({ - ... - config: { - plugins: [react()] // plugin for client-side run - } - }) - ] - }) - ``` - As a shorthand of this, Minissg provides `plugin` option for - convenience. - Set it to a function returning an array of plugins and the function - will call twice to instantiate plugins for each runs. - The following example is equivalent to the above: - ```js - import { defineConfig } from "vite" - import react from "@vitejs/plugin-react" - - export default defineConfig({ - ... - plugins: [ - minissg({ - ... - plugins: () => [react()] // plugin for both runs - }) - ] - }) - ``` -* The following options of the top-level config are overwritten in - client-side run with the result of server-side run: `root`, `base`, - `mode`, `build.emptyOutDir`, and `build.rollupOptions.input`. - You can overwrite them again by setting them in the `config` option, - but it usually breaks the integrity of two runs; do it at your own - risk. - -### Generating Data for Client-side Code - -Minissg allows server-side code to generate specific data for each -client-side code. -For this purpose, importing with `?client` query provides us with a -mutable object that holds data to be passed to the client-side code. -Client-side code can refer to such data by importing a virtual module -named `virtual:minissg/client`. - -Suppose that we have a client-side code `foo.js` and attempt to pass -some data to it from server-side code. -This can be done by manipulating the default export of -`foo.js?client`. -For example, the following code - -```js -import data from "./foo.js?client"; - -data["bar"] = "baz"; -``` - -puts a string `"baz"` with the key `"bar"` in the object to be passed -to `foo.js`. -Minissg serialize this object in JSON and generate a client-side -module that provides the JSON object. -Because of this implementation, the values put in this object must be -serializable in JSON. - -In client-side code, import `virtual:minissg/client` in `foo.js` to -have an access to the object passed from the server-side code. -For example, in `foo.js`, the following client-side code - -```js -import data from "virtual:minissg/client"; - -console.log(data["bar"]); -``` - -will print `baz` in the web browser's console. - -The object imported by `?client` query has `id` property as its -initial content. -The value of the `id` property is a string of a unique identifier of -this client-side module. -You can overwrite or delete it if it is not needed. -A supposed usage of this identifier is to find particular HTML elements -generated by server-side code from client-side code. - -### Renderer for a Module Itself - -Sometimes it is convenient if a module can obtain the renderer -associated to itself. -A special module named `virtual:minissg/render` provides it. -The default export of `virtual:minissg/render` is the rendering -function of the importer. -The actual type of the rendering function depends on its definition. - -A typical example of using this feature is that a module has multiple -children of the same type and renders them in the same way. -The following is an example of JSX file that aggregates MDX files: - -```jsx -import render from "virtual:minissg/render"; - -// common layout for all MDX files -const Layout = ({ children }) => { - return {children}; -}; - -const pages = import.meta.glob("./**/*.mdx"); -const modules = Object.entries(pages).map(([filename, load]) => { - const entries = async () => { - // load an MDX file and compose it with Layout. - const Component = await load(); - const Page = () => ; - // serialize the composed component. - return render(Page); - }; - return [filename.replace(/mdx$/, "html"), { entries }] -}); - -export const entries = () => modules; -``` - -### User-defined Renderers and Hydration - -A renderer is actually an object representing a collection of -functions that return code in a string. -Minissg calls one of these function in accordance with `?render` -and/or `?hydrate` queries to obtain the code that implements the -feature specified by those queries. -The type of renderers is given below in TypeScript: - -```typescript -type Renderer = { - render?: { - server?: (arg: { parameter: string }) => string | PromiseLike; - client?: (arg: { parameter: string }) => string | PromiseLike; - }; - hydrate?: { - server: (arg: HydrateArg) => string | PromiseLike; - client: (arg: HydrateArg) => string | PromiseLike; - }; -}; - -type HydrateArg = { - id: string; - moduleId: string; - parameter: string; -}; -``` - -A renderer can provide two properties `render` and `hydrate`, which -are associated to `?render` and `?hydrate` queries, respectively. -Both of them may have two variants: `server` for server-side code -generation, and `client` for client-side. -All of the functions in a renderer must return a string of the source -code of an ES6 module. -Except for `hydrate.server`, the code must be written in vanilla -JavaScript. -The code returned by `hydrate.client` must be written in the same -language as the file that `?hydrate` query is given. - -Each function in `render` must return a string of the source code of -an ES6 module whose default export is the rendering function, which -serializes the given data or component into a format specified in -the `Content` type described in Module Tree section. -The `render` functions takes one argument, namely `parameter`, which -holds the value of `?render` query. - -The code generated by `hydrate.server` must be a component that wraps -the original component with hydration target container. -The `hydrate.server` function is given an argument of `HydrateArg` -type, which has three properties: -`id` for a unique identifier of this component, -`moduleId` for the identifier of the original file, and -`parameter` for the value of `?hydrate` query. -Both `hydrate.server` and `hydrate.client` functions are called with -the identical argument for each import. - -The code generated by `hydrate.client` must be a JS code that can be -embedded in an ` + + + +``` + +The set of scripts to be included in each page is determined by the +same manner as style sheets; +all the imports with `?client` query executed during loading a module +is included in the output of that module. + +The client-side code is processed by Vite as well as server-side code. +This means that you can write client-side code in any language +that Vite (or one of its plugin) supports. +Vite translates client-side code and minifies all its chunks as usual. +Minissg does not throw anything more at Vite, but does not take +anything more from Vite. + +### Partial Hydration + +Minissg provides a simple partial hydration mechanism +in the sense of [island architecture] for several component systems. +To enable partial hydration for a component, import the component +with `?hydrate` query. +The code imported with `?hydrate` query is included in both +server-side and client-side code to embed its initial view in the +static file and to hydrate the view in the web browsers. + +As an example, here we attempt to use the `Count.jsx` presented in +Renderer section. +At first, To enable partial hydration feature for `*.jsx` files, +associate `**/*.jsx` to React renderer in Minissg plugin's `render` +option. + +By importing `Count.jsx` with `?hydrate` query as follows, we turn on +the hydration of this component: + +```jsx +import Count from "./Count.jsx?hydrate"; + +export default function() { + return ; +}; +``` + +This results in the following output: + +```html + + + + + +
+ +
+ + +``` + +The `/assets/hydrate-7c5f4dec.js` file is the top-level client-side +code that hydrates the `Count` component. +It searches for the element with `data-hydrate="g_zZoKsE"` attribute +for the target of hydration, where `"g_zZoKsE"` is the unique +identifier of the `Count` component. +The `data-hydrate-args` attribute holds the serialized argument of +`Count` component, which is passed to the component for hydration. + +Note that this is not the only way for hydration in Minissg. +You can hydrate a component in a different way than `?hydrate` by +writing it in your program. +Minissg does not force you anything. + +## Advanced Features + +### Configuring Client-side Run + +As described above, Vite runs twice for static site generation. +The two runs are for different purposes and therefore may have +different settings. +The top-level config of `vite.config.js` is used only in server-side +run, not in client-side run. +The config of client-side run is automatically generated by Missing +based on the top-level config and the result of server-side run. +To tweak the config of client-side run, Minissg provides `config` +option that will be merged into the config of client-side run. +For example, to turn on the minify option only in client-side run, +write it in the `config` option as follows: + +```js +import { defineConfig } from "vite" + +export default defineConfig({ + build: { + rollupOptions: { + input: "./index.html.js" + } + }, + plugins: [ + minissg({ + config: { + build: { + minify: true // enable minify only on client-side run + } + } + }) + ] +}) +``` + +Minissg computes the config of client-side run from the following +three sources: the top-level config, the results of server-side run, +and the `config` option of the Minissg plugin. +Almost all the settings in the top-level config are inherited to +client-side run except for the following: + +* `plugins` are never shared between the two runs. + Plugins must be instantiated for each run. + To enable the same set of plugins in both runs, which is a usual + requirement, duplicate it in both the top-level config and Minissg's + `config`, as seen in the following example: + ```js + import { defineConfig } from "vite" + import react from "@vitejs/plugin-react" + + export default defineConfig({ + ... + plugins: [ + react(), // plugin for server-side run + minissg({ + ... + config: { + plugins: [react()] // plugin for client-side run + } + }) + ] + }) + ``` + As a shorthand of this, Minissg provides `plugin` option for + convenience. + Set it to a function returning an array of plugins and the function + will call twice to instantiate plugins for each runs. + The following example is equivalent to the above: + ```js + import { defineConfig } from "vite" + import react from "@vitejs/plugin-react" + + export default defineConfig({ + ... + plugins: [ + minissg({ + ... + plugins: () => [react()] // plugin for both runs + }) + ] + }) + ``` +* The following options of the top-level config are overwritten in + client-side run with the result of server-side run: `root`, `base`, + `mode`, `build.emptyOutDir`, and `build.rollupOptions.input`. + You can overwrite them again by setting them in the `config` option, + but it usually breaks the integrity of two runs; do it at your own + risk. + +### Generating Data for Client-side Code + +Minissg allows server-side code to generate specific data for each +client-side code. +For this purpose, importing with `?client` query provides us with a +mutable object that holds data to be passed to the client-side code. +Client-side code can refer to such data by importing a virtual module +named `virtual:minissg/client`. + +Suppose that we have a client-side code `foo.js` and attempt to pass +some data to it from server-side code. +This can be done by manipulating the default export of +`foo.js?client`. +For example, the following code + +```js +import data from "./foo.js?client"; + +data["bar"] = "baz"; +``` + +puts a string `"baz"` with the key `"bar"` in the object to be passed +to `foo.js`. +Minissg serialize this object in JSON and generate a client-side +module that provides the JSON object. +Because of this implementation, the values put in this object must be +serializable in JSON. + +In client-side code, import `virtual:minissg/client` in `foo.js` to +have an access to the object passed from the server-side code. +For example, in `foo.js`, the following client-side code + +```js +import data from "virtual:minissg/client"; + +console.log(data["bar"]); +``` + +will print `baz` in the web browser's console. + +The object imported by `?client` query has `id` property as its +initial content. +The value of the `id` property is a string of a unique identifier of +this client-side module. +You can overwrite or delete it if it is not needed. +A supposed usage of this identifier is to find particular HTML elements +generated by server-side code from client-side code. + +### Renderer for a Module Itself + +Sometimes it is convenient if a module can obtain the renderer +associated to itself. +A special module named `virtual:minissg/render` provides it. +The default export of `virtual:minissg/render` is the rendering +function of the importer. +The actual type of the rendering function depends on its definition. + +A typical example of using this feature is that a module has multiple +children of the same type and renders them in the same way. +The following is an example of JSX file that aggregates MDX files: + +```jsx +import render from "virtual:minissg/render"; + +// common layout for all MDX files +const Layout = ({ children }) => { + return {children}; +}; + +const pages = import.meta.glob("./**/*.mdx"); +const modules = Object.entries(pages).map(([filename, load]) => { + const entries = async () => { + // load an MDX file and compose it with Layout. + const Component = await load(); + const Page = () => ; + // serialize the composed component. + return render(Page); + }; + return [filename.replace(/mdx$/, "html"), { entries }] +}); + +export const entries = () => modules; +``` + +### User-defined Renderers and Hydration + +A renderer is actually an object representing a collection of +functions that return code in a string. +Minissg calls one of these function in accordance with `?render` +and/or `?hydrate` queries to obtain the code that implements the +feature specified by those queries. +The type of renderers is given below in TypeScript: + +```typescript +type Renderer = { + render?: { + server?: (arg: { parameter: string }) => string | PromiseLike; + client?: (arg: { parameter: string }) => string | PromiseLike; + }; + hydrate?: { + server: (arg: HydrateArg) => string | PromiseLike; + client: (arg: HydrateArg) => string | PromiseLike; + }; +}; + +type HydrateArg = { + id: string; + moduleId: string; + parameter: string; +}; +``` + +A renderer can provide two properties `render` and `hydrate`, which +are associated to `?render` and `?hydrate` queries, respectively. +Both of them may have two variants: `server` for server-side code +generation, and `client` for client-side. +All of the functions in a renderer must return a string of the source +code of an ES6 module. +Except for `hydrate.server`, the code must be written in vanilla +JavaScript. +The code returned by `hydrate.client` must be written in the same +language as the file that `?hydrate` query is given. + +Each function in `render` must return a string of the source code of +an ES6 module whose default export is the rendering function, which +serializes the given data or component into a format specified in +the `Content` type described in Module Tree section. +The `render` functions takes one argument, namely `parameter`, which +holds the value of `?render` query. + +The code generated by `hydrate.server` must be a component that wraps +the original component with hydration target container. +The `hydrate.server` function is given an argument of `HydrateArg` +type, which has three properties: +`id` for a unique identifier of this component, +`moduleId` for the identifier of the original file, and +`parameter` for the value of `?hydrate` query. +Both `hydrate.server` and `hydrate.client` functions are called with +the identical argument for each import. + +The code generated by `hydrate.client` must be a JS code that can be +embedded in an `