diff --git a/package-lock.json b/package-lock.json index 86920549..6888777e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@node-core/doc-kit", - "version": "1.3.8", + "version": "1.3.9", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@node-core/doc-kit", - "version": "1.3.8", + "version": "1.3.9", "dependencies": { "@actions/core": "^3.0.0", "@heroicons/react": "^2.2.0", diff --git a/package.json b/package.json index d6e7fc22..0964696b 100644 --- a/package.json +++ b/package.json @@ -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" diff --git a/src/generators/web/README.md b/src/generators/web/README.md index 428c4f4e..ff0ce1e6 100644 --- a/src/generators/web/README.md +++ b/src/generators/web/README.md @@ -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 ``, ``, 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 ``, ``, 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` @@ -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 | diff --git a/src/generators/web/index.mjs b/src/generators/web/index.mjs index 0d53c592..3695c864 100644 --- a/src/generators/web/index.mjs +++ b/src/generators/web/index.mjs @@ -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: {}, }, }); diff --git a/src/generators/web/types.d.ts b/src/generators/web/types.d.ts index ec8684cb..2fb239e9 100644 --- a/src/generators/web/types.d.ts +++ b/src/generators/web/types.d.ts @@ -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'; @@ -33,6 +34,9 @@ export type Configuration = { >; imports: Record; virtualImports: Record; + // Options merged into the Rolldown build for the client and server bundles. + // See the web generator README for the merge semantics. + rolldown: Partial; }; export type Generator = GeneratorMetadata< diff --git a/src/generators/web/utils/bundle.mjs b/src/generators/web/utils/bundle.mjs index f084474e..482d176d 100644 --- a/src/generators/web/utils/bundle.mjs +++ b/src/generators/web/utils/bundle.mjs @@ -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 @@ -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. @@ -93,7 +107,7 @@ 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. @@ -101,17 +115,20 @@ export default async function bundleCode( // 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, ...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. @@ -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, diff --git a/src/generators/web/utils/css.mjs b/src/generators/web/utils/css.mjs index 2f2ca86b..ad57a04c 100644 --- a/src/generators/web/utils/css.mjs +++ b/src/generators/web/utils/css.mjs @@ -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({