Skip to content

Commit 56abe66

Browse files
committed
Add debug output format
1 parent 1ff64de commit 56abe66

File tree

7 files changed

+221
-50
lines changed

7 files changed

+221
-50
lines changed

CLAUDE.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ npm run jest -- test/index.test.js
8080
- **cjs** - CommonJS for Node.js
8181
- **umd** - Universal Module Definition for CDNs and older environments
8282
- **iife** - Immediately Invoked Function Expression
83+
- **debug** - Unminified modern ES2017+ build for debugging (automatically included when `debug` field exists in package.json)
8384

8485
### Key Subdirectories
8586

@@ -126,6 +127,14 @@ npm run jest -- test/index.test.js
126127
- Only works with es/modern formats
127128
- Enable with --workers flag
128129

130+
**Debug Format** (src/index.js:109-112, 326, 391, 607-608):
131+
132+
- Automatically included when `debug` field is present in package.json
133+
- Uses modern ES2017+ compilation (same as modern format)
134+
- Skips Terser compression/minification for readable debugging output
135+
- Preserves variable names, whitespace, and code structure
136+
- Output file specified by `debug` field (e.g., `"debug": "dist/my-lib.debug.js"`)
137+
129138
### Testing Structure
130139

131140
- **test/index.test.js** - Main test suite

README.md

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
<a href="#setup">Setup</a> ✯
1515
<a href="#formats">Formats</a> ✯
1616
<a href="#modern">Modern Mode</a> ✯
17+
<a href="#debug">Debug Mode</a> ✯
1718
<a href="#usage">Usage &amp; Configuration</a> ✯
1819
<a href="#options">All Options</a>
1920
</p>
@@ -144,6 +145,36 @@ The `"exports"` field can also be an object for packages with multiple entry mod
144145
}
145146
```
146147

148+
## 🐛 Debug Mode <a name="debug"></a>
149+
150+
Microbundle can generate an **unminified debug build** to help with debugging your library. This is useful when you need to step through your bundled code or investigate issues in production builds.
151+
152+
To enable debug mode, simply add a `"debug"` field to your `package.json`:
153+
154+
```jsonc
155+
{
156+
"name": "foo",
157+
"source": "src/index.js",
158+
"main": "./dist/foo.js",
159+
"module": "./dist/foo.module.js",
160+
"debug": "./dist/foo.debug.js", // 👈 Add this field
161+
"scripts": {
162+
"build": "microbundle"
163+
}
164+
}
165+
```
166+
167+
When the `debug` field is present, Microbundle will automatically generate an additional unminified bundle with:
168+
169+
- **Modern ES2017+ syntax** (same as the `modern` format - async/await, arrow functions, etc.)
170+
- **No minification** - readable code with preserved variable names
171+
- **Preserved comments** - your code comments remain in the output
172+
- **Proper formatting** - whitespace and indentation maintained
173+
174+
This makes it much easier to debug issues in your library code without needing to map back through source maps.
175+
176+
> 💡 **Tip**: The debug build is automatically included when the `debug` field exists - no need to modify the `--format` flag.
177+
147178
## 📦 Usage & Configuration <a name="usage"></a>
148179

149180
Microbundle includes two commands - `build` (the default) and `watch`.
@@ -172,7 +203,8 @@ The filenames and paths for generated bundles in each format are defined by the
172203
"require": "./dist/foo.js", // CommonJS output bundle
173204
"default": "./dist/foo.modern.mjs", // Modern ES Modules output bundle
174205
},
175-
"types": "dist/foo.d.ts" // TypeScript typings
206+
"types": "dist/foo.d.ts", // TypeScript typings
207+
"debug": "dist/foo.debug.js" // Unminified modern bundle for debugging (optional)
176208
}
177209
```
178210

src/index.js

Lines changed: 56 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,12 @@ export default async function microbundle(inputOptions) {
105105
let formats = (options.format || options.formats).split(',');
106106
// de-dupe formats and convert "esm" to "es":
107107
formats = Array.from(new Set(formats.map(f => (f === 'esm' ? 'es' : f))));
108+
109+
// add debug format if pkg.debug is present and not already in the list:
110+
if (options.pkg.debug && !formats.includes('debug')) {
111+
formats.push('debug');
112+
}
113+
108114
// always compile cjs first if it's there:
109115
formats.sort((a, b) => (a === 'cjs' ? -1 : a > b ? 1 : 0));
110116

@@ -317,6 +323,7 @@ function getMain({ options, entry, format }) {
317323
pkg['umd:main'] || pkg.unpkg || 'x.umd.js',
318324
mainNoExtension,
319325
);
326+
mainsByFormat.debug = replaceName(pkg.debug || 'x.debug.js', mainNoExtension);
320327

321328
return mainsByFormat[format] || mainsByFormat.cjs;
322329
}
@@ -378,7 +385,7 @@ function createConfig(options, entry, format, writeMeta) {
378385
);
379386
}
380387

381-
const modern = format === 'modern';
388+
const modern = format === 'modern' || format === 'debug';
382389

383390
// let rollupName = safeVariableName(basename(entry).replace(/\.js$/, ''));
384391

@@ -586,62 +593,63 @@ function createConfig(options, entry, format, writeMeta) {
586593
custom: {
587594
defines,
588595
modern,
589-
compress: options.compress !== false,
596+
compress: options.compress !== false && format !== 'debug',
590597
targets: options.target === 'node' ? { node: '12' } : undefined,
591598
pragma: options.jsx,
592599
pragmaFrag: options.jsxFragment,
593600
typescript: !!useTypescript,
594601
jsxImportSource: options.jsxImportSource || false,
595602
},
596603
}),
597-
options.compress !== false && [
598-
terser({
599-
compress: Object.assign(
600-
{
601-
keep_infinity: true,
602-
pure_getters: true,
603-
// Ideally we'd just get Terser to respect existing Arrow functions...
604-
// unsafe_arrows: true,
605-
passes: 10,
604+
options.compress !== false &&
605+
format !== 'debug' && [
606+
terser({
607+
compress: Object.assign(
608+
{
609+
keep_infinity: true,
610+
pure_getters: true,
611+
// Ideally we'd just get Terser to respect existing Arrow functions...
612+
// unsafe_arrows: true,
613+
passes: 10,
614+
},
615+
typeof minifyOptions.compress === 'boolean'
616+
? minifyOptions.compress
617+
: minifyOptions.compress || {},
618+
),
619+
format: {
620+
// By default, Terser wraps function arguments in extra parens to trigger eager parsing.
621+
// Whether this is a good idea is way too specific to guess, so we optimize for size by default:
622+
wrap_func_args: false,
623+
comments: /^\s*([@#]__[A-Z]+__\s*$|@cc_on)/,
624+
preserve_annotations: true,
625+
},
626+
module: modern,
627+
ecma: modern ? 2017 : 5,
628+
toplevel: modern || format === 'cjs' || format === 'es',
629+
mangle:
630+
typeof minifyOptions.mangle === 'boolean'
631+
? minifyOptions.mangle
632+
: Object.assign({}, minifyOptions.mangle || {}),
633+
nameCache,
634+
}),
635+
nameCache && {
636+
// before hook
637+
options: loadNameCache,
638+
// after hook
639+
writeBundle() {
640+
if (writeMeta && nameCache) {
641+
let filename = getNameCachePath();
642+
let json = JSON.stringify(
643+
nameCache,
644+
null,
645+
nameCacheIndentTabs ? '\t' : 2,
646+
);
647+
if (endsWithNewLine) json += EOL;
648+
fs.writeFile(filename, json, () => {});
649+
}
606650
},
607-
typeof minifyOptions.compress === 'boolean'
608-
? minifyOptions.compress
609-
: minifyOptions.compress || {},
610-
),
611-
format: {
612-
// By default, Terser wraps function arguments in extra parens to trigger eager parsing.
613-
// Whether this is a good idea is way too specific to guess, so we optimize for size by default:
614-
wrap_func_args: false,
615-
comments: /^\s*([@#]__[A-Z]+__\s*$|@cc_on)/,
616-
preserve_annotations: true,
617-
},
618-
module: modern,
619-
ecma: modern ? 2017 : 5,
620-
toplevel: modern || format === 'cjs' || format === 'es',
621-
mangle:
622-
typeof minifyOptions.mangle === 'boolean'
623-
? minifyOptions.mangle
624-
: Object.assign({}, minifyOptions.mangle || {}),
625-
nameCache,
626-
}),
627-
nameCache && {
628-
// before hook
629-
options: loadNameCache,
630-
// after hook
631-
writeBundle() {
632-
if (writeMeta && nameCache) {
633-
let filename = getNameCachePath();
634-
let json = JSON.stringify(
635-
nameCache,
636-
null,
637-
nameCacheIndentTabs ? '\t' : 2,
638-
);
639-
if (endsWithNewLine) json += EOL;
640-
fs.writeFile(filename, json, () => {});
641-
}
642651
},
643-
},
644-
],
652+
],
645653
options.visualize && visualizer(),
646654
// NOTE: OMT only works with amd and esm
647655
// Source: https://github.com/surma/rollup-plugin-off-main-thread#config

src/prog.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ export default handler => {
3434
.option('--output, -o', 'Directory to place build files into')
3535
.option(
3636
'--format, -f',
37-
`Only build specified formats (any of ${DEFAULT_FORMATS} or iife)`,
37+
`Only build specified formats (any of ${DEFAULT_FORMATS}, debug, or iife)`,
3838
DEFAULT_FORMATS,
3939
)
4040
.option('--watch, -w', 'Rebuilds on any change', false)

test/__snapshots__/index.test.js.snap

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1723,6 +1723,91 @@ exports[`fixtures build custom-source-with-cwd with microbundle 5`] = `
17231723
"
17241724
`;
17251725
1726+
exports[`fixtures build debug with microbundle 1`] = `
1727+
"Used script: microbundle
1728+
1729+
Directory tree:
1730+
1731+
debug
1732+
dist
1733+
debug-lib.debug.js
1734+
debug-lib.debug.js.map
1735+
debug-lib.esm.mjs
1736+
debug-lib.esm.mjs.map
1737+
debug-lib.js
1738+
debug-lib.js.map
1739+
debug-lib.umd.js
1740+
debug-lib.umd.js.map
1741+
package.json
1742+
src
1743+
index.js
1744+
1745+
1746+
Build \\"debug-lib\\" to dist:
1747+
328 B: debug-lib.js.gz
1748+
275 B: debug-lib.js.br
1749+
332 B: debug-lib.esm.mjs.gz
1750+
279 B: debug-lib.esm.mjs.br
1751+
415 B: debug-lib.umd.js.gz
1752+
343 B: debug-lib.umd.js.br
1753+
385 B: debug-lib.debug.js.gz
1754+
320 B: debug-lib.debug.js.br"
1755+
`;
1756+
1757+
exports[`fixtures build debug with microbundle 2`] = `8`;
1758+
1759+
exports[`fixtures build debug with microbundle 3`] = `
1760+
"// Test debug format with meaningful variable names and modern syntax
1761+
async function fetchUserData(userId) {
1762+
const response = await fetch(\`/api/users/\${userId}\`);
1763+
const userData = await response.json();
1764+
return userData;
1765+
}
1766+
const createGreeting = (name, age) => {
1767+
const greeting = \`Hello, \${name}! You are \${age} years old.\`;
1768+
return greeting;
1769+
};
1770+
class UserManager {
1771+
constructor(initialUsers = []) {
1772+
this.users = initialUsers;
1773+
this.count = initialUsers.length;
1774+
}
1775+
1776+
addUser(user) {
1777+
this.users.push(user);
1778+
this.count++;
1779+
}
1780+
1781+
async loadUsers() {
1782+
const allUsers = await Promise.all(this.users.map(u => fetchUserData(u.id)));
1783+
return allUsers;
1784+
}
1785+
1786+
}
1787+
1788+
export { UserManager, createGreeting, fetchUserData };
1789+
//# sourceMappingURL=debug-lib.debug.js.map
1790+
"
1791+
`;
1792+
1793+
exports[`fixtures build debug with microbundle 4`] = `
1794+
"var r=function(r){try{return Promise.resolve(fetch(\\"/api/users/\\"+r)).then(function(r){return Promise.resolve(r.json())})}catch(r){return Promise.reject(r)}},e=function(r,e){return\\"Hello, \\"+r+\\"! You are \\"+e+\\" years old.\\"},t=/*#__PURE__*/function(){function e(r){void 0===r&&(r=[]),this.users=r,this.count=r.length}var t=e.prototype;return t.addUser=function(r){this.users.push(r),this.count++},t.loadUsers=function(){try{return Promise.resolve(Promise.all(this.users.map(function(e){return r(e.id)})))}catch(r){return Promise.reject(r)}},e}();export{t as UserManager,e as createGreeting,r as fetchUserData};
1795+
//# sourceMappingURL=debug-lib.esm.mjs.map
1796+
"
1797+
`;
1798+
1799+
exports[`fixtures build debug with microbundle 5`] = `
1800+
"var e=function(e){try{return Promise.resolve(fetch(\\"/api/users/\\"+e)).then(function(e){return Promise.resolve(e.json())})}catch(e){return Promise.reject(e)}};exports.UserManager=/*#__PURE__*/function(){function r(e){void 0===e&&(e=[]),this.users=e,this.count=e.length}var t=r.prototype;return t.addUser=function(e){this.users.push(e),this.count++},t.loadUsers=function(){try{return Promise.resolve(Promise.all(this.users.map(function(r){return e(r.id)})))}catch(e){return Promise.reject(e)}},r}(),exports.createGreeting=function(e,r){return\\"Hello, \\"+e+\\"! You are \\"+r+\\" years old.\\"},exports.fetchUserData=e;
1801+
//# sourceMappingURL=debug-lib.js.map
1802+
"
1803+
`;
1804+
1805+
exports[`fixtures build debug with microbundle 6`] = `
1806+
"!function(e,t){\\"object\\"==typeof exports&&\\"undefined\\"!=typeof module?t(exports):\\"function\\"==typeof define&&define.amd?define([\\"exports\\"],t):t((e||self).debugLib={})}(this,function(e){var t=function(e){try{return Promise.resolve(fetch(\\"/api/users/\\"+e)).then(function(e){return Promise.resolve(e.json())})}catch(e){return Promise.reject(e)}};e.UserManager=/*#__PURE__*/function(){function e(e){void 0===e&&(e=[]),this.users=e,this.count=e.length}var r=e.prototype;return r.addUser=function(e){this.users.push(e),this.count++},r.loadUsers=function(){try{return Promise.resolve(Promise.all(this.users.map(function(e){return t(e.id)})))}catch(e){return Promise.reject(e)}},e}(),e.createGreeting=function(e,t){return\\"Hello, \\"+e+\\"! You are \\"+t+\\" years old.\\"},e.fetchUserData=t});
1807+
//# sourceMappingURL=debug-lib.umd.js.map
1808+
"
1809+
`;
1810+
17261811
exports[`fixtures build default-named with microbundle 1`] = `
17271812
"Used script: microbundle
17281813

test/fixtures/debug/package.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"name": "debug-lib",
3+
"debug": "dist/debug-lib.debug.js",
4+
"scripts": {
5+
"build": "microbundle"
6+
}
7+
}

test/fixtures/debug/src/index.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// Test debug format with meaningful variable names and modern syntax
2+
export async function fetchUserData(userId) {
3+
const response = await fetch(`/api/users/${userId}`);
4+
const userData = await response.json();
5+
return userData;
6+
}
7+
8+
export const createGreeting = (name, age) => {
9+
const greeting = `Hello, ${name}! You are ${age} years old.`;
10+
return greeting;
11+
};
12+
13+
export class UserManager {
14+
constructor(initialUsers = []) {
15+
this.users = initialUsers;
16+
this.count = initialUsers.length;
17+
}
18+
19+
addUser(user) {
20+
this.users.push(user);
21+
this.count++;
22+
}
23+
24+
async loadUsers() {
25+
const allUsers = await Promise.all(
26+
this.users.map(u => fetchUserData(u.id)),
27+
);
28+
return allUsers;
29+
}
30+
}

0 commit comments

Comments
 (0)