Skip to content

Commit a8298b4

Browse files
committed
Try new build
1 parent 869968b commit a8298b4

File tree

4 files changed

+280
-13
lines changed

4 files changed

+280
-13
lines changed

CONTRIBUTING.md

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -97,11 +97,9 @@ The short summary is:
9797

9898
Scripts can be executed via `npm run [script]` or `yarn [script]` respectively.
9999

100-
- `build` - compiles all packages ready for publishing to npm
101-
- `build:core` - builds just Preact itself
102-
- `build:debug` - builds the debug addon only
103-
- `build:hooks` - builds the hook addon only
104-
- `build:test-utils` - builds the test-utils addon only
100+
- `build` - compiles all packages (core + addons) via unified script (`scripts/build-packages.cjs`)
101+
- `build:core` - builds just Preact itself (alias for `node scripts/build-packages.cjs core`)
102+
- (Deprecated) Previous per-package commands like `build:hooks`, `build:debug`, etc. now proxy to the unified build and will be removed in a future major.
105103
- `test:ts` - Run all tests for TypeScript definitions
106104
- `test:karma` - Run all unit/integration tests.
107105
- `test:karma:watch` - Same as above, but it will automatically re-run the test suite if a code change was detected.

package.json

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -114,14 +114,14 @@
114114
"types": "src/index.d.ts",
115115
"scripts": {
116116
"prepare": "husky && npm run test:install && run-s build",
117-
"build": "npm-run-all --parallel 'build:*'",
118-
"build:core": "microbundle build --raw --no-generateTypes -f cjs,esm,umd",
119-
"build:debug": "microbundle build --raw --no-generateTypes -f cjs,esm,umd --cwd debug",
120-
"build:devtools": "microbundle build --raw --no-generateTypes -f cjs,esm,umd --cwd devtools",
121-
"build:hooks": "microbundle build --raw --no-generateTypes -f cjs,esm,umd --cwd hooks",
122-
"build:test-utils": "microbundle build --raw --no-generateTypes -f cjs,esm,umd --cwd test-utils",
123-
"build:compat": "microbundle build --raw --no-generateTypes -f cjs,esm,umd --cwd compat --globals 'preact/hooks=preactHooks'",
124-
"build:jsx": "microbundle build --raw --no-generateTypes -f cjs,esm,umd --cwd jsx-runtime",
117+
"build": "node scripts/build-packages.cjs all",
118+
"build:core:legacy": "microbundle build --raw --no-generateTypes -f cjs,esm,umd",
119+
"build:core": "node scripts/build-packages.cjs core",
120+
"build:devtools": "echo 'Use build script: npm run build (devtools included)'",
121+
"build:hooks": "echo 'Use build script: npm run build (hooks included)'",
122+
"build:test-utils": "echo 'Use build script: npm run build (test-utils included)'",
123+
"build:compat": "echo 'Use build script: npm run build (compat included)'",
124+
"build:jsx": "echo 'Use build script: npm run build (jsx-runtime included)'",
125125
"postbuild": "node ./config/compat-entries.js",
126126
"dev": "microbundle watch --raw --no-generateTypes --format cjs",
127127
"dev:hooks": "microbundle watch --raw --no-generateTypes --format cjs --cwd hooks",

scripts/build-packages.cjs

Lines changed: 267 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,267 @@
1+
#!/usr/bin/env node
2+
/**
3+
* Generalized build script replacing former microbundle-based per-package builds.
4+
*
5+
* Usage:
6+
* npm run build # build all packages
7+
* node scripts/build-packages.cjs core hooks # build subset
8+
*
9+
* Pipeline per package:
10+
* esbuild (ESM) -> manual CJS transform -> esbuild IIFE (UMD) -> terser minification (in-place)
11+
* Property rename (internal) via babel-plugin-transform-rename-properties using root mangle.json
12+
*/
13+
const path = require('node:path');
14+
const fs = require('node:fs/promises');
15+
const { build } = require('esbuild');
16+
const babel = require('@babel/core');
17+
const { minify } = require('terser');
18+
const zlib = require('node:zlib');
19+
20+
async function main() {
21+
const root = path.join(__dirname, '..');
22+
const pkgRoot = JSON.parse(
23+
String(await fs.readFile(path.join(root, 'package.json')))
24+
);
25+
const mangleConfig = JSON.parse(
26+
String(await fs.readFile(path.join(root, 'mangle.json')))
27+
);
28+
29+
const packages = [
30+
{
31+
id: 'core',
32+
dir: '.',
33+
entry: 'src/index.js',
34+
base: 'preact',
35+
globalName: pkgRoot.amdName || 'preact'
36+
},
37+
{
38+
id: 'hooks',
39+
dir: 'hooks',
40+
entry: 'src/index.js',
41+
base: 'hooks',
42+
globalName: 'preactHooks'
43+
},
44+
{
45+
id: 'compat',
46+
dir: 'compat',
47+
entry: 'src/index.js',
48+
base: 'compat',
49+
globalName: 'preactCompat'
50+
},
51+
{
52+
id: 'debug',
53+
dir: 'debug',
54+
entry: 'src/index.js',
55+
base: 'debug',
56+
globalName: 'preactDebug'
57+
},
58+
{
59+
id: 'devtools',
60+
dir: 'devtools',
61+
entry: 'src/index.js',
62+
base: 'devtools',
63+
globalName: 'preactDevtools'
64+
},
65+
{
66+
id: 'test-utils',
67+
dir: 'test-utils',
68+
entry: 'src/index.js',
69+
base: 'testUtils',
70+
globalName: 'preactTestUtils'
71+
},
72+
{
73+
id: 'jsx-runtime',
74+
dir: 'jsx-runtime',
75+
entry: 'src/index.js',
76+
base: 'jsxRuntime',
77+
globalName: 'preactJsxRuntime'
78+
}
79+
];
80+
81+
const args = process.argv.slice(2).filter(Boolean);
82+
let selected = packages;
83+
if (args.length && !args.includes('all')) {
84+
const wanted = new Set(args);
85+
selected = packages.filter(p => wanted.has(p.id));
86+
const missing = Array.from(wanted).filter(
87+
x => !selected.some(p => p.id === x)
88+
);
89+
if (missing.length) {
90+
console.error('[build] Unknown package id(s):', missing.join(', '));
91+
process.exit(1);
92+
}
93+
}
94+
95+
const rename = {};
96+
for (const original in mangleConfig.props.props) {
97+
let name = original;
98+
if (name.startsWith('$')) name = name.slice(1);
99+
rename[name] = mangleConfig.props.props[original];
100+
}
101+
102+
const babelCache = new Map();
103+
function babelRenamePlugin() {
104+
return {
105+
name: 'babel-rename-properties',
106+
setup(buildApi) {
107+
buildApi.onLoad({ filter: /\/src\/.*\.js$/ }, async args => {
108+
const code = String(await fs.readFile(args.path));
109+
const cacheKey = args.path + '::' + code;
110+
const cached = babelCache.get(cacheKey);
111+
if (cached) return cached;
112+
const result = await babel.transformAsync(code, {
113+
filename: args.path,
114+
babelrc: false,
115+
configFile: false,
116+
presets: [],
117+
plugins: [['babel-plugin-transform-rename-properties', { rename }]]
118+
});
119+
const out = { contents: result.code, loader: 'js' };
120+
babelCache.set(cacheKey, out);
121+
return out;
122+
});
123+
}
124+
};
125+
}
126+
127+
const reserved = mangleConfig.minify.mangle.properties.reserved;
128+
Object.values(mangleConfig.props.props).forEach(n => reserved.push(n));
129+
const terserBase = {
130+
toplevel: true,
131+
compress: {
132+
...mangleConfig.minify.compress,
133+
unsafe: true,
134+
pure_getters: true,
135+
keep_infinity: true,
136+
unsafe_proto: true,
137+
passes: 10,
138+
toplevel: true
139+
},
140+
mangle: {
141+
toplevel: true,
142+
properties: { ...mangleConfig.minify.mangle.properties, reserved }
143+
},
144+
format: {
145+
shebang: true,
146+
shorthand: true,
147+
wrap_func_args: false,
148+
comments: /^\s*([@#]__[A-Z]+__\s*$|@cc_on)/,
149+
preserve_annotations: true
150+
},
151+
module: true,
152+
sourceMap: true
153+
};
154+
155+
async function minifyFile(inputPath, outputPath, { module }) {
156+
const code = String(await fs.readFile(inputPath));
157+
const result = await minify(code, {
158+
...terserBase,
159+
module,
160+
sourceMap: {
161+
filename: path.basename(outputPath),
162+
url: path.basename(outputPath) + '.map'
163+
}
164+
});
165+
await fs.writeFile(outputPath, result.code + '\n');
166+
if (result.map)
167+
await fs.writeFile(
168+
outputPath + '.map',
169+
typeof result.map === 'string' ? result.map : JSON.stringify(result.map)
170+
);
171+
}
172+
173+
const sizeRows = [];
174+
175+
for (const pkg of selected) {
176+
const absDir = path.join(root, pkg.dir);
177+
const distDir = path.join(absDir, 'dist');
178+
const entryAbs = path.join(absDir, pkg.entry);
179+
await fs.mkdir(distDir, { recursive: true });
180+
181+
const shared = {
182+
entryPoints: [entryAbs],
183+
external: [
184+
'preact',
185+
'preact/jsx-runtime',
186+
'preact/compat',
187+
'preact/hooks',
188+
'preact/debug',
189+
'preact/test-utils',
190+
'preact/devtools',
191+
'preact-render-to-string'
192+
],
193+
bundle: true,
194+
sourcemap: true,
195+
sourcesContent: true,
196+
plugins: [babelRenamePlugin()],
197+
target: ['es2020'],
198+
define: { 'process.env.NODE_ENV': '"production"' }
199+
};
200+
201+
await build({
202+
...shared,
203+
format: 'esm',
204+
outfile: path.join(distDir, pkg.base + '.mjs'),
205+
minify: false
206+
});
207+
208+
// TODO: use es-module-lexer to transform
209+
// ESM into CJS
210+
211+
// TODO: ensure UMD build uses globals rather than inlining
212+
await build({
213+
...shared,
214+
format: 'iife',
215+
globalName: pkg.globalName,
216+
outfile: path.join(distDir, pkg.base + '.umd.js'),
217+
minify: false
218+
});
219+
220+
await Promise.all([
221+
minifyFile(
222+
path.join(distDir, pkg.base + '.js'),
223+
path.join(distDir, pkg.base + '.js'),
224+
{ module: true }
225+
),
226+
minifyFile(
227+
path.join(distDir, pkg.base + '.mjs'),
228+
path.join(distDir, pkg.base + '.mjs'),
229+
{ module: true }
230+
),
231+
minifyFile(
232+
path.join(distDir, pkg.base + '.umd.js'),
233+
path.join(distDir, pkg.base + '.umd.js'),
234+
{ module: false }
235+
)
236+
]);
237+
238+
for (const ext of ['.js', '.mjs', '.umd.js']) {
239+
const file = pkg.base + ext;
240+
const abs = path.join(distDir, file);
241+
const code = await fs.readFile(abs);
242+
const raw = code.length;
243+
const gz = zlib.gzipSync(code).length;
244+
const br = zlib.brotliCompressSync(code, {
245+
params: { [zlib.constants.BROTLI_PARAM_QUALITY]: 11 }
246+
}).length;
247+
sizeRows.push({
248+
pkg: pkg.id,
249+
file: path.relative(root, abs),
250+
raw,
251+
gz,
252+
br
253+
});
254+
}
255+
}
256+
257+
console.log('\n[build] Artifact sizes (bytes):');
258+
console.log(['Package', 'File', 'Raw', 'Gzip', 'Brotli'].join('\t'));
259+
for (const row of sizeRows)
260+
console.log([row.pkg, row.file, row.raw, row.gz, row.br].join('\t'));
261+
console.log('\nDone.');
262+
}
263+
264+
main().catch(err => {
265+
console.error('[build] Failed:', err);
266+
process.exit(1);
267+
});

src/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,5 @@ export { cloneElement } from './clone-element';
1111
export { createContext } from './create-context';
1212
export { toChildArray } from './diff/children';
1313
export { default as options } from './options';
14+
15+
// test

0 commit comments

Comments
 (0)