Skip to content

Commit 049b05b

Browse files
committed
Simplify build process
1 parent 9b78b44 commit 049b05b

File tree

2 files changed

+257
-1
lines changed

2 files changed

+257
-1
lines changed

build.mjs

Lines changed: 255 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,255 @@
1+
#!/usr/bin/env node
2+
3+
import * as esbuild from 'esbuild';
4+
import { transformAsync } from '@babel/core';
5+
import { minify } from 'terser';
6+
import fs from 'node:fs/promises';
7+
import { readFileSync, existsSync } from 'node:fs';
8+
import path from 'node:path';
9+
10+
// Load mangle configuration
11+
const mangleConfig = JSON.parse(readFileSync('./mangle.json', 'utf8'));
12+
13+
// Create rename mapping for Babel plugin
14+
const rename = {};
15+
for (let prop in mangleConfig.props.props) {
16+
let name = prop;
17+
if (name[0] === '$') {
18+
name = name.slice(1);
19+
}
20+
rename[name] = mangleConfig.props.props[prop];
21+
}
22+
23+
// Build configurations for different packages
24+
const buildConfigs = [
25+
{
26+
name: 'preact',
27+
entry: 'src/index.js',
28+
outDir: 'dist',
29+
filename: 'preact',
30+
globalName: 'preact'
31+
},
32+
{
33+
name: 'preact/debug',
34+
entry: 'debug/src/index.js',
35+
outDir: 'debug/dist',
36+
external: ['preact'],
37+
filename: 'debug',
38+
globalName: 'preactDebug'
39+
},
40+
{
41+
name: 'preact/devtools',
42+
entry: 'devtools/src/index.js',
43+
outDir: 'devtools/dist',
44+
external: ['preact'],
45+
filename: 'devtools',
46+
globalName: 'preactDevtools'
47+
},
48+
{
49+
name: 'preact/hooks',
50+
entry: 'hooks/src/index.js',
51+
outDir: 'hooks/dist',
52+
external: ['preact'],
53+
filename: 'hooks',
54+
globalName: 'preactHooks'
55+
},
56+
{
57+
name: 'preact/test-utils',
58+
entry: 'test-utils/src/index.js',
59+
external: ['preact'],
60+
outDir: 'test-utils/dist',
61+
filename: 'testUtils',
62+
globalName: 'preactTestUtils'
63+
},
64+
{
65+
name: 'preact/compat',
66+
entry: 'compat/src/index.js',
67+
outDir: 'compat/dist',
68+
filename: 'compat',
69+
globalName: 'preactCompat',
70+
external: ['preact/hooks', 'preact'],
71+
globals: { 'preact/hooks': 'preactHooks' }
72+
},
73+
{
74+
name: 'preact/jsx-runtime',
75+
entry: 'jsx-runtime/src/index.js',
76+
outDir: 'jsx-runtime/dist',
77+
filename: 'jsxRuntime',
78+
external: ['preact'],
79+
globalName: 'preactJsxRuntime'
80+
}
81+
];
82+
83+
// Formats to build for each package
84+
const formats = ['cjs', 'esm', 'umd'];
85+
86+
// Function to apply Babel transformations
87+
async function applyBabelTransform(code, filename) {
88+
try {
89+
const result = await transformAsync(code, {
90+
filename,
91+
configFile: false,
92+
babelrc: false,
93+
presets: [],
94+
plugins: [['babel-plugin-transform-rename-properties', { rename }]]
95+
});
96+
return result.code;
97+
} catch (error) {
98+
console.error(`Babel transform failed for ${filename}:`, error);
99+
throw error;
100+
}
101+
}
102+
103+
// Function to minify with Terser
104+
async function minifyWithTerser(code, filename) {
105+
try {
106+
const result = await minify(code, {
107+
...mangleConfig.minify,
108+
compress: {
109+
keep_infinity: true,
110+
pure_getters: true,
111+
passes: 10
112+
},
113+
format: {
114+
wrap_func_args: false,
115+
comments: /^\s*([@#]__[A-Z]+__\s*$|@cc_on)/,
116+
preserve_annotations: true
117+
},
118+
ecma: 2017,
119+
sourceMap: false,
120+
module: filename.endsWith('.mjs'),
121+
toplevel: true
122+
});
123+
return result.code;
124+
} catch (error) {
125+
console.error(`Terser minification failed for ${filename}:`, error);
126+
throw error;
127+
}
128+
}
129+
130+
// Function to build a single package in a specific format
131+
async function buildPackage(config, format) {
132+
const {
133+
name,
134+
entry,
135+
outDir,
136+
filename,
137+
globalName,
138+
external = [],
139+
globals = {}
140+
} = config;
141+
142+
if (!existsSync(entry)) {
143+
console.warn(`⚠️ Entry file ${entry} not found, skipping ${name}`);
144+
return;
145+
}
146+
147+
console.log(`📦 Building ${name} (${format})...`);
148+
149+
// Determine output extension and format
150+
const extensions = {
151+
cjs: '.js',
152+
esm: '.mjs',
153+
umd: '.umd.js'
154+
};
155+
156+
const outputFile = path.join(outDir, `${filename}${extensions[format]}`);
157+
158+
// ESBuild configuration
159+
const esbuildConfig = /** @type {import('esbuild').BuildOptions} */ {
160+
entryPoints: [entry],
161+
bundle: true,
162+
format: format === 'cjs' ? 'cjs' : format === 'esm' ? 'esm' : 'iife',
163+
platform: format === 'cjs' ? 'node' : 'browser',
164+
target: ['es2015'],
165+
jsx: 'transform',
166+
jsxFactory: 'h',
167+
jsxFragment: 'Fragment',
168+
external: external,
169+
globalName: format === 'umd' ? globalName : undefined,
170+
plugins: [],
171+
write: false, // We'll handle writing ourselves
172+
minify: false, // We'll handle minification with Terser
173+
sourcemap: false,
174+
treeShaking: true
175+
};
176+
177+
// Add globals for UMD builds
178+
if (format === 'umd' && Object.keys(globals).length > 0) {
179+
esbuildConfig.globalName = globalName;
180+
// ESBuild doesn't support globals directly, we'll need to handle externals differently
181+
}
182+
183+
try {
184+
// Build with ESBuild
185+
// @ts-expect-error
186+
const result = await esbuild.build(esbuildConfig);
187+
let code = result.outputFiles[0].text;
188+
189+
// Apply Babel transformations
190+
code = await applyBabelTransform(code, outputFile);
191+
192+
// Ensure output directory exists
193+
await fs.mkdir(path.dirname(outputFile), { recursive: true });
194+
195+
// Write unminified version
196+
const minified = await minifyWithTerser(code, outputFile);
197+
await fs.writeFile(outputFile, minified);
198+
console.log(`✅ Built ${outputFile}`);
199+
200+
// Create minified version for UMD builds
201+
if (format === 'umd') {
202+
const minifiedCode = await minifyWithTerser(code, outputFile);
203+
const minifiedFile = path.join(outDir, `${filename}.min.js`);
204+
await fs.writeFile(minifiedFile, minifiedCode);
205+
console.log(`✅ Minified ${minifiedFile}`);
206+
}
207+
} catch (error) {
208+
console.error(`❌ Failed to build ${name} (${format}):`, error);
209+
throw error;
210+
}
211+
}
212+
213+
// Main build function
214+
async function build() {
215+
console.log('🚀 Starting Preact build with ESBuild + Babel + Terser...\n');
216+
217+
const startTime = Date.now();
218+
let successful = 0;
219+
let failed = 0;
220+
221+
// Build each package in each format
222+
for (const config of buildConfigs) {
223+
for (const format of formats) {
224+
try {
225+
await buildPackage(config, format);
226+
successful++;
227+
} catch (error) {
228+
console.error(`❌ Build failed for ${config.name} (${format})`);
229+
failed++;
230+
}
231+
}
232+
console.log(); // Empty line between packages
233+
}
234+
235+
const endTime = Date.now();
236+
const duration = ((endTime - startTime) / 1000).toFixed(2);
237+
238+
console.log(`\n🎉 Build completed in ${duration}s`);
239+
console.log(`✅ Successful: ${successful}`);
240+
if (failed > 0) {
241+
console.log(`❌ Failed: ${failed}`);
242+
process.exit(1);
243+
}
244+
}
245+
246+
// Run the build if this script is executed directly
247+
// @ts-expect-error
248+
if (import.meta.url === `file://${process.argv[1]}`) {
249+
build().catch(error => {
250+
console.error('❌ Build failed:', error);
251+
process.exit(1);
252+
});
253+
}
254+
255+
export { build, buildPackage };

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,8 @@
121121
"types": "src/index.d.ts",
122122
"scripts": {
123123
"prepare": "husky && run-s build",
124-
"build": "npm-run-all --parallel 'build:*'",
124+
"build": "node build.mjs && node ./config/compat-entries.js",
125+
"build:microbundle": "npm-run-all --parallel 'build:*'",
125126
"build:core": "microbundle build --raw --no-generateTypes -f cjs,esm,umd",
126127
"build:debug": "microbundle build --raw --no-generateTypes -f cjs,esm,umd --cwd debug",
127128
"build:devtools": "microbundle build --raw --no-generateTypes -f cjs,esm,umd --cwd devtools",

0 commit comments

Comments
 (0)