From 8e2c288d7f3ed21e98b7749b2d28018d59730a6b Mon Sep 17 00:00:00 2001 From: Timothee Guerin Date: Fri, 30 Jan 2026 12:50:37 -0500 Subject: [PATCH 1/3] minified --- packages/bundler/src/bundler.ts | 56 +++++++++++++++++------- packages/bundler/src/index.ts | 1 + packages/bundler/src/vite/vite-plugin.ts | 32 ++++++++++---- 3 files changed, 65 insertions(+), 24 deletions(-) diff --git a/packages/bundler/src/bundler.ts b/packages/bundler/src/bundler.ts index aba0bd710ea..68784dd6d1b 100644 --- a/packages/bundler/src/bundler.ts +++ b/packages/bundler/src/bundler.ts @@ -57,9 +57,20 @@ interface PackageJson { exports?: Record; } -export async function createTypeSpecBundle(libraryPath: string): Promise { +export interface CreateTypeSpecBundleOptions { + /** + * Whether to minify the output bundle. + * @default true + */ + minify?: boolean; +} + +export async function createTypeSpecBundle( + libraryPath: string, + options?: CreateTypeSpecBundleOptions, +): Promise { const definition = await resolveTypeSpecBundleDefinition(libraryPath); - const context = await createEsBuildContext(definition); + const context = await createEsBuildContext(definition, [], options); try { const result = await context.rebuild(); return resolveTypeSpecBundle(definition, result); @@ -71,24 +82,33 @@ export async function createTypeSpecBundle(libraryPath: string): Promise void, + options?: CreateTypeSpecBundleOptions, ) { const definition = await resolveTypeSpecBundleDefinition(libraryPath); - const context = await createEsBuildContext(definition, [ - { - name: "example", - setup(build) { - build.onEnd((result) => { - const bundle = resolveTypeSpecBundle(definition, result); - onBundle(bundle); - }); + const context = await createEsBuildContext( + definition, + [ + { + name: "example", + setup(build) { + build.onEnd((result) => { + const bundle = resolveTypeSpecBundle(definition, result); + onBundle(bundle); + }); + }, }, - }, - ]); + ], + options, + ); await context.watch(); } -export async function bundleTypeSpecLibrary(libraryPath: string, outputDir: string) { - const bundle = await createTypeSpecBundle(libraryPath); +export async function bundleTypeSpecLibrary( + libraryPath: string, + outputDir: string, + options?: CreateTypeSpecBundleOptions, +) { + const bundle = await createTypeSpecBundle(libraryPath, options); await mkdir(outputDir, { recursive: true }); for (const file of bundle.files) { await writeFile(joinPaths(outputDir, file.filename), file.content); @@ -119,7 +139,12 @@ async function resolveTypeSpecBundleDefinition( }; } -async function createEsBuildContext(definition: TypeSpecBundleDefinition, plugins: Plugin[] = []) { +async function createEsBuildContext( + definition: TypeSpecBundleDefinition, + plugins: Plugin[] = [], + options?: CreateTypeSpecBundleOptions, +) { + const minify = options?.minify ?? true; const libraryPath = definition.path; const program = await compile(NodeHost, libraryPath, { noEmit: true, @@ -193,6 +218,7 @@ async function createEsBuildContext(definition: TypeSpecBundleDefinition, plugin platform: "browser", format: "esm", target: "es2024", + minify, plugins: [virtualPlugin, nodeModulesPolyfillPlugin({}), ...plugins], }); } diff --git a/packages/bundler/src/index.ts b/packages/bundler/src/index.ts index bdbbf59b0a0..8acf2351ab4 100644 --- a/packages/bundler/src/index.ts +++ b/packages/bundler/src/index.ts @@ -1,5 +1,6 @@ export { BundleManifest, + CreateTypeSpecBundleOptions, TypeSpecBundle, TypeSpecBundleDefinition, TypeSpecBundleFile, diff --git a/packages/bundler/src/vite/vite-plugin.ts b/packages/bundler/src/vite/vite-plugin.ts index 105c0afb9f1..3e581267247 100644 --- a/packages/bundler/src/vite/vite-plugin.ts +++ b/packages/bundler/src/vite/vite-plugin.ts @@ -2,6 +2,7 @@ import { resolvePath } from "@typespec/compiler"; import { resolve } from "path"; import type { IndexHtmlTransformContext, Plugin, ResolvedConfig } from "vite"; import { + CreateTypeSpecBundleOptions, TypeSpecBundle, TypeSpecBundleDefinition, createTypeSpecBundle, @@ -29,8 +30,10 @@ export function typespecBundlePlugin(options: TypeSpecBundlePluginOptions): Plug config = c; }, async buildStart() { + // Minify only in production mode + const minify = config.command === "build"; for (const name of options.libraries) { - const bundle = await bundleLibrary(config.root, name); + const bundle = await bundleLibrary(config.root, name, { minify }); bundles[name] = bundle; definitions[name] = bundle.definition; } @@ -79,11 +82,17 @@ export function typespecBundlePlugin(options: TypeSpecBundlePluginOptions): Plug }); for (const library of options.libraries) { - void watchBundleLibrary(config.root, library, (bundle) => { - bundles[library] = bundle; - definitions[library] = bundle.definition; - server.ws.send({ type: "full-reload" }); - }); + // Don't minify in dev/watch mode for faster rebuilds + void watchBundleLibrary( + config.root, + library, + (bundle) => { + bundles[library] = bundle; + definitions[library] = bundle.definition; + server.ws.send({ type: "full-reload" }); + }, + { minify: false }, + ); } }, @@ -132,13 +141,18 @@ function createImportMap( return importMap; } -async function bundleLibrary(projectRoot: string, name: string) { - return await createTypeSpecBundle(resolve(projectRoot, "node_modules", name)); +async function bundleLibrary( + projectRoot: string, + name: string, + options?: CreateTypeSpecBundleOptions, +) { + return await createTypeSpecBundle(resolve(projectRoot, "node_modules", name), options); } async function watchBundleLibrary( projectRoot: string, name: string, onChange: (bundle: TypeSpecBundle) => void, + options?: CreateTypeSpecBundleOptions, ) { - return await watchTypeSpecBundle(resolve(projectRoot, "node_modules", name), onChange); + return await watchTypeSpecBundle(resolve(projectRoot, "node_modules", name), onChange, options); } From 1addab91a03225b1897f0e3e68ff975bc7c84c4d Mon Sep 17 00:00:00 2001 From: Timothee Guerin Date: Fri, 30 Jan 2026 11:01:38 -0800 Subject: [PATCH 2/3] Change changeKind from fix to feature --- .../changes/minify-bundled-code-2026-0-30-17-53-27.md | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .chronus/changes/minify-bundled-code-2026-0-30-17-53-27.md diff --git a/.chronus/changes/minify-bundled-code-2026-0-30-17-53-27.md b/.chronus/changes/minify-bundled-code-2026-0-30-17-53-27.md new file mode 100644 index 00000000000..45bda7e3d87 --- /dev/null +++ b/.chronus/changes/minify-bundled-code-2026-0-30-17-53-27.md @@ -0,0 +1,8 @@ +--- +# Change versionKind to one of: internal, fix, dependencies, feature, deprecation, breaking +changeKind: feature +packages: + - "@typespec/bundler" +--- + +Minify bundler output From 978eaf487dc77a3184aa3b64a5c5ab313bfa8268 Mon Sep 17 00:00:00 2001 From: Timothee Guerin Date: Fri, 30 Jan 2026 14:40:58 -0500 Subject: [PATCH 3/3] gzip? --- packages/bundler/src/bundler.ts | 56 ++++++++++++++++++++++++++------- 1 file changed, 45 insertions(+), 11 deletions(-) diff --git a/packages/bundler/src/bundler.ts b/packages/bundler/src/bundler.ts index 68784dd6d1b..0fc8f5dfc31 100644 --- a/packages/bundler/src/bundler.ts +++ b/packages/bundler/src/bundler.ts @@ -3,8 +3,12 @@ import { BuildOptions, BuildResult, context, Plugin } from "esbuild"; import { nodeModulesPolyfillPlugin } from "esbuild-plugins-node-modules-polyfill"; import { mkdir, readFile, realpath, writeFile } from "fs/promises"; import { basename, join, resolve } from "path"; +import { promisify } from "util"; +import { gzip } from "zlib"; import { relativeTo } from "./utils.js"; +const gzipAsync = promisify(gzip); + export interface BundleManifest { name: string; version: string; @@ -45,6 +49,8 @@ export interface TypeSpecBundleFile { export?: string; filename: string; content: string; + /** Gzipped content, only present if gzip option is enabled */ + gzipContent?: Buffer; } interface PackageJson { @@ -63,6 +69,13 @@ export interface CreateTypeSpecBundleOptions { * @default true */ minify?: boolean; + + /** + * Whether to also generate gzipped versions of the output files. + * When enabled, each file will include a gzipContent buffer. + * @default false + */ + gzip?: boolean; } export async function createTypeSpecBundle( @@ -73,7 +86,7 @@ export async function createTypeSpecBundle( const context = await createEsBuildContext(definition, [], options); try { const result = await context.rebuild(); - return resolveTypeSpecBundle(definition, result); + return resolveTypeSpecBundle(definition, result, options); } finally { await context.dispose(); } @@ -91,8 +104,8 @@ export async function watchTypeSpecBundle( { name: "example", setup(build) { - build.onEnd((result) => { - const bundle = resolveTypeSpecBundle(definition, result); + build.onEnd(async (result) => { + const bundle = await resolveTypeSpecBundle(definition, result, options); onBundle(bundle); }); }, @@ -112,9 +125,16 @@ export async function bundleTypeSpecLibrary( await mkdir(outputDir, { recursive: true }); for (const file of bundle.files) { await writeFile(joinPaths(outputDir, file.filename), file.content); + if (file.gzipContent) { + await writeFile(joinPaths(outputDir, file.filename + ".gz"), file.gzipContent); + } } const manifest = createManifest(bundle.definition); await writeFile(joinPaths(outputDir, "manifest.json"), JSON.stringify(manifest, null, 2)); + if (options?.gzip) { + const manifestGzip = await gzipAsync(Buffer.from(JSON.stringify(manifest), "utf-8")); + await writeFile(joinPaths(outputDir, "manifest.json.gz"), manifestGzip); + } } async function resolveTypeSpecBundleDefinition( @@ -223,21 +243,35 @@ async function createEsBuildContext( }); } -function resolveTypeSpecBundle( +async function resolveTypeSpecBundle( definition: TypeSpecBundleDefinition, result: BuildResult, -): TypeSpecBundle { - return { - definition, - manifest: createManifest(definition), - files: result.outputFiles!.map((file) => { + options?: CreateTypeSpecBundleOptions, +): Promise { + const shouldGzip = options?.gzip ?? false; + + const files: TypeSpecBundleFile[] = await Promise.all( + result.outputFiles!.map(async (file) => { const entry = definition.exports[basename(file.path)]; - return { + const content = file.text; + const bundleFile: TypeSpecBundleFile = { filename: file.path.replaceAll("\\", "/").split("/out/")[1], - content: file.text, + content, export: entry ? getExportEntryPoint(entry) : undefined, }; + + if (shouldGzip) { + bundleFile.gzipContent = await gzipAsync(Buffer.from(content, "utf-8")); + } + + return bundleFile; }), + ); + + return { + definition, + manifest: createManifest(definition), + files, }; }