Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@node-core/doc-kit",
"type": "module",
"version": "1.3.8",
"version": "1.3.9",
"repository": {
"type": "git",
"url": "git+https://github.com/nodejs/doc-kit.git"
Expand Down
84 changes: 71 additions & 13 deletions src/generators/web/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,20 @@ The `web` generator transforms JSX AST entries into complete web bundles, produc

The `web` generator accepts the following configuration options:

| Name | Type | Default | Description |
| ----------------- | --------- | --------------------------------------------- | --------------------------------------------------------------------- |
| `output` | `string` | - | The directory where HTML, JavaScript, and CSS files will be written |
| `templatePath` | `string` | `'template.html'` | Path to the HTML template file |
| `project` | `string` | `'Node.js'` | Project name used in page titles and the version selector |
| `title` | `string` | `'{project} v{version} Documentation'` | Title template for HTML pages (supports `{project}`, `{version}`) |
| `useAbsoluteURLs` | `boolean` | `false` | When `true`, all internal links use absolute URLs based on `baseURL` |
| `editURL` | `string` | `'${GITHUB_EDIT_URL}/doc/api{path}.md'` | URL template for "edit this page" links |
| `pageURL` | `string` | `'{baseURL}/latest-{version}/api{path}.html'` | URL template for documentation page links |
| `head` | `object` | See below | Configurable `<meta>`, `<link>`, and raw markup for the document head |
| `lightningcss` | `object` | `{}` | Options spread into LightningCSS while bundling CSS (see below) |
| `imports` | `object` | See below | Object mapping `#theme/` aliases to component paths for customization |
| `virtualImports` | `object` | `{}` | Additional virtual module mappings merged into the build |
| Name | Type | Default | Description |
| ----------------- | --------- | --------------------------------------------- | ------------------------------------------------------------------------ |
| `output` | `string` | - | The directory where HTML, JavaScript, and CSS files will be written |
| `templatePath` | `string` | `'template.html'` | Path to the HTML template file |
| `project` | `string` | `'Node.js'` | Project name used in page titles and the version selector |
| `title` | `string` | `'{project} v{version} Documentation'` | Title template for HTML pages (supports `{project}`, `{version}`) |
| `useAbsoluteURLs` | `boolean` | `false` | When `true`, all internal links use absolute URLs based on `baseURL` |
| `editURL` | `string` | `'${GITHUB_EDIT_URL}/doc/api{path}.md'` | URL template for "edit this page" links |
| `pageURL` | `string` | `'{baseURL}/latest-{version}/api{path}.html'` | URL template for documentation page links |
| `head` | `object` | See below | Configurable `<meta>`, `<link>`, and raw markup for the document head |
| `lightningcss` | `object` | `{}` | Options spread into LightningCSS while bundling CSS (see below) |
| `imports` | `object` | See below | Object mapping `#theme/` aliases to component paths for customization |
| `virtualImports` | `object` | `{}` | Additional virtual module mappings merged into the build |
| `rolldown` | `object` | `{}` | Options merged into the Rolldown build — extra plugins, etc. (see below) |

#### `head`

Expand Down Expand Up @@ -95,6 +96,63 @@ To apply more than one visitor, compose them with LightningCSS's

[lightningcss]: https://lightningcss.dev/transforms.html

#### Custom Rolldown options

The `rolldown` object is merged into the [Rolldown][rolldown] `build` call used
for **both** the client and server bundles, so you can register extra plugins,
inject compile-time constants, add module aliases, or set any other Rolldown
option. The same config is applied to both builds — branch inside a plugin (or
read the `SERVER`/`CLIENT` defines) if you need per-target behavior.

The merge follows these rules:

- **`plugins`** are registered after the built-in virtual-module plugin (so
they see the in-memory entry modules) and before the CSS loader.
- **`transform.define`** and **`resolve.alias`** are merged key-by-key, so you
can add entries without dropping the generator's. The built-in `SERVER`/
`CLIENT` defines and the `react`/`react-dom` → `preact/compat` aliases
(plus anything from `imports`) always win.
- **All other options** (e.g. `output.minify`, `treeshake`, `external`,
`platform`, `logLevel`) override the generator's defaults.
- **`input`** and the built-in plugins are managed by the generator and cannot
be overridden.

> Functions (plugins, hooks, alias resolvers, etc.) are allowed here because the
> `web` generator runs entirely on the main thread. Unlike the chunked
> generators, its config is never serialized to a worker, so values that can't
> be structured-cloned are safe. Keep this in mind if `web` ever gains parallel
> processing: function-valued options would then need a serializable form.

```js
// doc-kit.config.mjs
import myRolldownPlugin from './my-rolldown-plugin.mjs';

export default {
web: {
rolldown: {
// Register additional plugins (run after the built-in ones).
plugins: [myRolldownPlugin()],

// Inject extra compile-time constants (merged with SERVER/CLIENT).
transform: {
define: {
'process.env.ANALYTICS_ID': JSON.stringify('UA-XXXXX'),
},
},

// Extend module resolution with custom aliases.
resolve: {
alias: {
'@components': './src/components',
},
},
},
},
};
```

[rolldown]: https://rolldown.rs/

#### Default `imports`

| Alias | Default | Description |
Expand Down
4 changes: 4 additions & 0 deletions src/generators/web/index.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -86,5 +86,9 @@ export default createLazyGenerator({
'#theme/Layout': join(import.meta.dirname, './ui/components/Layout'),
},
virtualImports: {},

// Options merged into the Rolldown build (client and server), e.g. extra
// `plugins`. See the README for the merge semantics.
rolldown: {},
},
});
4 changes: 4 additions & 0 deletions src/generators/web/types.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { BundleAsyncOptions, CustomAtRules } from 'lightningcss-wasm';
import type { BuildOptions } from 'rolldown';

import type { JSXContent } from '../jsx-ast/utils/buildContent.mjs';

Expand Down Expand Up @@ -33,6 +34,9 @@ export type Configuration = {
>;
imports: Record<string, string>;
virtualImports: Record<string, string>;
// Options merged into the Rolldown build for the client and server bundles.
// See the web generator README for the merge semantics.
rolldown: Partial<BuildOptions>;
};

export type Generator = GeneratorMetadata<
Expand Down
39 changes: 29 additions & 10 deletions src/generators/web/utils/bundle.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -39,18 +39,24 @@ export default async function bundleCode(
) {
const config = getConfig('web');

const { rolldown = {} } = config;

const result = await build({
...rolldown,

// Entry points: array of virtual module names that the virtual plugin provides
input: Array.from(codeMap.keys()),

// Experimental features: import maps for client, none for server
experimental: {
chunkImportMap: !server,
...rolldown.experimental,
},

checks: {
// Disable plugin timing logs for cleaner output. This can be re-enabled for debugging performance issues.
pluginTimings: false,
...rolldown.checks,
},

// Output configuration
Expand All @@ -63,25 +69,33 @@ export default async function bundleCode(
// Minify output only for browser builds to optimize file size.
// Server builds are usually not minified to preserve stack traces and debuggability.
minify: !server,

...rolldown.output,
},

// Platform informs Rolldown of the environment-specific code behavior:
// - 'node' enables things like `require`, and skips polyfills.
// - 'browser' enables inlining of polyfills and uses native browser features.
platform: server ? 'node' : 'browser',
platform: rolldown.platform ?? (server ? 'node' : 'browser'),

// External dependencies to exclude from bundling.
// These are expected to be available at runtime in the server environment.
// This reduces bundle size and avoids bundling shared server libs.
external: server
? ['preact', 'preact-render-to-string', '@node-core/ui-components']
: [],
external:
rolldown.external ??
(server
? ['preact', 'preact-render-to-string', '@node-core/ui-components']
: []),

transform: {
...rolldown.transform,

// Inject global compile-time constants that will be replaced in code.
// These are useful for tree-shaking and conditional branching.
// Be sure to update type declarations (`types.d.ts`) if these change.
define: {
...rolldown.transform?.define,

// Boolean flags used for conditional logic in source code:
// Example: `if (SERVER) {...}` or `if (CLIENT) {...}`
// These flags help split logic for server/client environments.
Expand All @@ -93,25 +107,28 @@ export default async function bundleCode(
// JSX transformation configuration.
// `'react-jsx'` enables the automatic JSX runtime, which doesn't require `import React`.
// Since we're using Preact via aliasing, this setting works well with `preact/compat`.
jsx: 'react-jsx',
jsx: rolldown.transform?.jsx ?? 'react-jsx',
},

// Module resolution configuration.
resolve: {
// exports condition to use
conditionNames: ['rolldown'],

// Tell the bundler where to find node_modules.
// We use our custom `NODE_MODULES`, and then the cwd's `node_modules`.
modules: [await getNodeModules(), 'node_modules'],

...rolldown.resolve,

// Alias react imports to preact/compat for smaller bundle sizes.
// Explicit jsx-runtime aliases are required for the automatic JSX transform.
alias: {
react: 'preact/compat',
'react-dom': 'preact/compat',
...rolldown.resolve?.alias,
Comment thread
avivkeller marked this conversation as resolved.
...config.imports,
},

// Tell the bundler where to find node_modules.
// We use our custom `NODE_MODULES`, and then the cwd's `node_modules`.
modules: [await getNodeModules(), 'node_modules'],
},

// Array of plugins to apply during the build.
Expand All @@ -123,13 +140,15 @@ export default async function bundleCode(
...virtualImports,
}),

...(rolldown.plugins ?? []),

// Load CSS imports via the custom plugin.
// This plugin will collect imported CSS files and return them as `source` chunks.
cssLoader(),
],

// Enable tree-shaking to remove unused code
treeshake: true,
treeshake: rolldown.treeshake ?? true,

// Return chunks in memory instead of writing to disk
write: false,
Expand Down
5 changes: 4 additions & 1 deletion src/generators/web/utils/css.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,10 @@ export default () => {
}

// Read the raw CSS file from disk
const source = await readFile(id, 'utf8');
const source = await (lightningcss.resolver?.read ?? readFile)(
id,
'utf8'
);

// Use Lightning CSS to compile the file with CSS Modules enabled
const { code, exports } = await bundleAsync({
Expand Down
Loading