-
Notifications
You must be signed in to change notification settings - Fork 46
Expand file tree
/
Copy pathbundle.mjs
More file actions
151 lines (126 loc) · 5.39 KB
/
bundle.mjs
File metadata and controls
151 lines (126 loc) · 5.39 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
import { resolve } from 'node:path';
import virtual from '@rollup/plugin-virtual';
import { build } from 'rolldown';
import cssLoader from './css.mjs';
import getStaticData from './data.mjs';
import getConfig from '../../../utils/configuration/index.mjs';
import { lazy } from '../../../utils/misc.mjs';
// Resolve node_modules relative to this package (doc-kit), not cwd.
// We do this by finding where one of our dependencies (preact) is stored,
// and using it's NODE_MODULES
//
// FIXME(@avivkeller): When our CI (in Node.js Core) supports v22.x,
// this lazy-loading solution can be replaced by a simple import. For that
// matter, glob can also be replaced in other files throughout this repo.
const getNodeModules = lazy(async () => {
const { findPackageJSON } = await import('node:module');
return resolve(
findPackageJSON(new URL(import.meta.resolve('preact'))),
'../..'
);
});
/**
* Asynchronously bundles JavaScript source code (and its CSS imports),
* targeting either browser (client) or server (Node.js) environments.
*
* @param {Map<string, string>} codeMap - Map of {fileName: code} for all builds.
* @param {Object} [options] - Build configuration object.
* @param {boolean} [options.server=false] - Whether this is a server-side build.
*/
export default async function bundleCode(codeMap, { server = false } = {}) {
const config = getConfig('web');
const result = await build({
// 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,
},
checks: {
// Disable plugin timing logs for cleaner output. This can be re-enabled for debugging performance issues.
pluginTimings: false,
},
// Output configuration
output: {
// Output module format:
// - "cjs" for CommonJS (used in Node.js environments)
// - "esm" for browser environments (Using Chunk Code-Splitting)
format: server ? 'cjs' : 'esm',
// Minify output only for browser builds to optimize file size.
// Server builds are usually not minified to preserve stack traces and debuggability.
minify: !server,
},
// 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',
// 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']
: [],
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: {
// Static data injected directly into the bundle (as a literal or serialized JSON).
__STATIC_DATA__: getStaticData(),
// Boolean flags used for conditional logic in source code:
// Example: `if (SERVER) {...}` or `if (CLIENT) {...}`
// These flags help split logic for server/client environments.
// Unused branches will be removed via tree-shaking.
SERVER: String(server),
CLIENT: String(!server),
},
// 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',
},
// Module resolution configuration.
resolve: {
// exports condition to use
conditionNames: ['rolldown'],
// 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',
...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.
plugins: [
// Virtual plugin: provides in-memory modules from codeMap
virtual(Object.fromEntries(codeMap)),
// 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,
// Return chunks in memory instead of writing to disk
write: false,
});
// Separate CSS assets from JavaScript chunks
const assets = result.output.filter(c => c.type === 'asset');
const chunks = result.output.filter(c => c.type === 'chunk');
const importMap = assets.find(c => c.fileName === 'importmap.json');
return {
css: assets
.filter(c => c.fileName.endsWith('.css'))
.map(f => f.source)
.join(''),
chunks: chunks.map(({ fileName, code, isEntry }) => ({
fileName: fileName.replace('_virtual_', ''),
isEntry,
code,
})),
importMap: importMap?.source.toString(),
};
}