Find the cost of adding an npm package to your app's bundle size teardown.kelinci.dev
14
fork

Configure Feed

Select the types of activity you want to include in your feed.

refactor: clean stuff up

Mary f085dbeb a54aed0c

+353 -417
+1 -1
src/app.tsx
··· 18 18 import { createDerivedSignal } from './lib/signals'; 19 19 import { useSearchParams } from './lib/use-search-params'; 20 20 import { progress } from './npm/events'; 21 + import type { ProgressMessage } from './npm/types'; 21 22 import { initPackage } from './npm/worker-client'; 22 - import type { ProgressMessage } from './npm/worker-protocol'; 23 23 import Button from './primitives/button'; 24 24 import Tooltip from './primitives/tooltip'; 25 25
+1 -3
src/components/package-bundle.tsx
··· 5 5 import { LRUCache } from '../lib/lru'; 6 6 import { createQuery } from '../lib/query'; 7 7 import { createDerivedSignal } from '../lib/signals'; 8 - import type { BundleResult } from '../npm/bundler'; 9 8 import { progress } from '../npm/events'; 10 - import type { DiscoveredSubpaths } from '../npm/subpaths'; 9 + import type { BundleResult, DiscoveredSubpaths, ProgressMessage } from '../npm/types'; 11 10 import type { BundlerWorker } from '../npm/worker-client'; 12 - import type { ProgressMessage } from '../npm/worker-protocol'; 13 11 import Button from '../primitives/button'; 14 12 import * as Dropdown from '../primitives/dropdown'; 15 13 import * as Field from '../primitives/field';
+1 -1
src/components/package-dependencies.tsx
··· 3 3 import { LucideSearch } from '../icons/lucide'; 4 4 import { tw } from '../lib/classes'; 5 5 import { formatBytes } from '../lib/format'; 6 - import type { InstalledPackage } from '../npm/worker-protocol'; 6 + import type { InstalledPackage } from '../npm/types'; 7 7 import * as Dropdown from '../primitives/dropdown'; 8 8 import Input from '../primitives/input'; 9 9 import Tooltip from '../primitives/tooltip';
+1
src/lib/strings.ts
··· 3 3 } 4 4 5 5 // matches ANSI escape sequences (colors, cursor movement, etc.) 6 + // oxlint-disable-next-line no-control-regex 6 7 const ANSI_REGEX = /\x1b\[[0-9;]*[a-zA-Z]/g; 7 8 8 9 /**
+5 -56
src/npm/bundler.ts src/npm/lib/bundler.ts
··· 2 2 import { rolldown } from '@rolldown/browser'; 3 3 import { memfs } from '@rolldown/browser/experimental'; 4 4 5 + import { progress } from '../events'; 6 + import type { BundleChunk, BundleOptions, BundleResult } from '../types'; 7 + 5 8 import { BundleError } from './errors'; 6 - import { progress } from './events'; 7 9 import { analyzeModule } from './module-type'; 8 10 9 - const { volume } = memfs!; 10 - 11 - // #region types 11 + export type { BundleChunk, BundleOptions, BundleResult }; 12 12 13 - /** 14 - * options for bundling. 15 - */ 16 - export interface BundleOptions { 17 - /** additional rolldown options */ 18 - rolldown?: { 19 - /** external packages to exclude from bundle */ 20 - external?: string[]; 21 - /** whether to minify */ 22 - minify?: boolean; 23 - }; 24 - } 25 - 26 - /** 27 - * a bundled chunk. 28 - */ 29 - export interface BundleChunk { 30 - /** chunk filename */ 31 - fileName: string; 32 - /** the bundled code */ 33 - code: string; 34 - /** raw size in bytes */ 35 - size: number; 36 - /** gzipped size in bytes */ 37 - gzipSize: number; 38 - /** brotli size in bytes, if supported */ 39 - brotliSize?: number; 40 - /** whether this is the entry chunk */ 41 - isEntry: boolean; 42 - /** exported names from this chunk */ 43 - exports: string[]; 44 - } 45 - 46 - /** 47 - * result of bundling a package. 48 - */ 49 - export interface BundleResult { 50 - /** all output chunks */ 51 - chunks: BundleChunk[]; 52 - /** total raw size in bytes (all chunks) */ 53 - size: number; 54 - /** total gzipped size in bytes (all chunks) */ 55 - gzipSize: number; 56 - /** total brotli size in bytes (all chunks), if supported */ 57 - brotliSize?: number; 58 - /** exported names from the entry chunk */ 59 - exports: string[]; 60 - /** whether the entry module is CommonJS */ 61 - isCjs: boolean; 62 - } 63 - 64 - // #endregion 13 + const { volume } = memfs!; 65 14 66 15 // #region helpers 67 16
src/npm/errors.ts src/npm/lib/errors.ts
+1 -1
src/npm/events.ts
··· 1 1 import { createEventEmitter } from '../lib/emitter'; 2 2 3 - import type { ProgressMessage } from './worker-protocol'; 3 + import type { ProgressMessage } from './types'; 4 4 5 5 /** 6 6 * emitted during package initialization and bundling.
src/npm/fetch.test.ts src/npm/lib/fetch.test.ts
+2 -1
src/npm/fetch.ts src/npm/lib/fetch.ts
··· 1 1 import { untar } from '@mary/tar'; 2 2 import type { Volume } from 'memfs'; 3 3 4 + import { progress } from '../events'; 5 + 4 6 import { FetchError } from './errors'; 5 - import { progress } from './events'; 6 7 import type { HoistedNode, HoistedResult } from './types'; 7 8 8 9 /**
src/npm/hoist.ts src/npm/lib/hoist.ts
src/npm/installed-packages.test.ts src/npm/lib/installed-packages.test.ts
+2 -10
src/npm/installed-packages.ts src/npm/lib/installed-packages.ts
··· 1 - import type { ResolvedPackage } from './types'; 2 - import type { InstalledPackage } from './worker-protocol'; 1 + import type { InstalledPackage, PackageRef } from '../types'; 3 2 4 - /** 5 - * reference to a package in a dependency relationship. 6 - */ 7 - interface PackageRef { 8 - name: string; 9 - version: string; 10 - isPeer: boolean; 11 - } 3 + import type { ResolvedPackage } from './types'; 12 4 13 5 /** 14 6 * builds the installed packages list from the resolved dependency tree.
+141
src/npm/lib/types.ts
··· 1 + /** 2 + * the parsed package.json of the main package. 3 + * includes fields relevant for export discovery and display. 4 + */ 5 + export interface PackageJson { 6 + name: string; 7 + version: string; 8 + description?: string; 9 + license?: string; 10 + main?: string; 11 + module?: string; 12 + browser?: string | Record<string, string | false>; 13 + types?: string; 14 + typings?: string; 15 + exports?: PackageExports; 16 + type?: 'module' | 'commonjs'; 17 + peerDependencies?: Record<string, string>; 18 + } 19 + 20 + /** 21 + * package exports field - can be a string, array, object, or nested conditions. 22 + * https://nodejs.org/api/packages.html#exports 23 + */ 24 + export type PackageExports = string | string[] | { [key: string]: PackageExports } | null; 25 + 26 + /** 27 + * npm registry packument - the full metadata for a package including all versions. 28 + * fetched from registry.npmjs.org/{package-name} 29 + */ 30 + export interface Packument { 31 + name: string; 32 + 'dist-tags': Record<string, string>; 33 + versions: Record<string, PackageManifest>; 34 + time?: Record<string, string>; 35 + } 36 + 37 + /** 38 + * package manifest for a specific version. 39 + * this is what you'd find in a package.json plus registry metadata. 40 + */ 41 + export interface PackageManifest { 42 + name: string; 43 + version: string; 44 + description?: string; 45 + license?: string; 46 + main?: string; 47 + module?: string; 48 + exports?: PackageExports; 49 + type?: 'module' | 'commonjs'; 50 + dependencies?: Record<string, string>; 51 + devDependencies?: Record<string, string>; 52 + peerDependencies?: Record<string, string>; 53 + peerDependenciesMeta?: Record<string, { optional?: boolean }>; 54 + optionalDependencies?: Record<string, string>; 55 + dist: { 56 + tarball: string; 57 + integrity?: string; 58 + shasum?: string; 59 + /** total unpacked size in bytes */ 60 + unpackedSize?: number; 61 + /** number of files in the tarball */ 62 + fileCount?: number; 63 + }; 64 + } 65 + 66 + /** 67 + * a resolved package with its dependencies. 68 + * this is the output of the resolution step before hoisting. 69 + */ 70 + export interface ResolvedPackage { 71 + name: string; 72 + version: string; 73 + /** the tarball URL for fetching */ 74 + tarball: string; 75 + /** SRI integrity hash if available */ 76 + integrity?: string; 77 + /** unpacked size in bytes (from registry) */ 78 + unpackedSize?: number; 79 + /** package description */ 80 + description?: string; 81 + /** license identifier */ 82 + license?: string; 83 + /** resolved dependencies (name -> ResolvedPackage) */ 84 + dependencies: Map<string, ResolvedPackage>; 85 + } 86 + 87 + /** 88 + * supported package registries. 89 + */ 90 + export type Registry = 'npm' | 'jsr'; 91 + 92 + /** 93 + * the input to the resolver - a package specifier. 94 + * can be just a name (uses latest) or name@version/range. 95 + */ 96 + export interface PackageSpecifier { 97 + name: string; 98 + /** version, range, or dist-tag. defaults to 'latest' */ 99 + range: string; 100 + /** which registry to fetch from. defaults to 'npm' */ 101 + registry: Registry; 102 + } 103 + 104 + /** 105 + * the full resolution result - a tree of resolved packages. 106 + */ 107 + export interface ResolutionResult { 108 + /** the root package(s) that were requested */ 109 + roots: ResolvedPackage[]; 110 + /** all unique packages in the resolution (for deduping) */ 111 + packages: Map<string, ResolvedPackage>; 112 + } 113 + 114 + /** 115 + * a node in the hoisted node_modules structure. 116 + * represents what should be written to node_modules/{name} 117 + */ 118 + export interface HoistedNode { 119 + name: string; 120 + version: string; 121 + tarball: string; 122 + integrity?: string; 123 + /** unpacked size in bytes (from registry) */ 124 + unpackedSize?: number; 125 + /** package description */ 126 + description?: string; 127 + /** license identifier */ 128 + license?: string; 129 + /** number of direct dependencies */ 130 + dependencyCount: number; 131 + /** nested node_modules for this package (when hoisting fails) */ 132 + nested: Map<string, HoistedNode>; 133 + } 134 + 135 + /** 136 + * the result of hoisting - a flat(ish) node_modules structure. 137 + */ 138 + export interface HoistedResult { 139 + /** top-level node_modules entries */ 140 + root: Map<string, HoistedNode>; 141 + }
src/npm/module-type.test.ts src/npm/lib/module-type.test.ts
src/npm/module-type.ts src/npm/lib/module-type.ts
src/npm/registry.ts src/npm/lib/registry.ts
src/npm/resolve.test.ts src/npm/lib/resolve.test.ts
+2 -1
src/npm/resolve.ts src/npm/lib/resolve.ts
··· 1 1 import * as semver from 'semver'; 2 2 3 + import { progress } from '../events'; 4 + 3 5 import { InvalidSpecifierError, NoMatchingVersionError } from './errors'; 4 - import { progress } from './events'; 5 6 import { fetchPackument, reverseJsrName } from './registry'; 6 7 import type { PackageManifest, PackageSpecifier, Registry, ResolvedPackage, ResolutionResult } from './types'; 7 8
+3 -25
src/npm/subpaths.ts src/npm/lib/subpaths.ts
··· 1 1 import type { Volume } from 'memfs'; 2 2 3 - import type { PackageExports, PackageJson } from './types'; 4 - 5 - // #region types 6 - 7 - /** 8 - * a discovered subpath entry from package.json exports. 9 - */ 10 - export interface Subpath { 11 - /** the subpath pattern (e.g., ".", "./utils", "./feature/*") */ 12 - subpath: string; 13 - /** the resolved file path relative to package root */ 14 - target: string; 15 - /** whether this is a wildcard-expanded entry */ 16 - isWildcard: boolean; 17 - } 3 + import type { DiscoveredSubpaths, Subpath } from '../types'; 18 4 19 - /** 20 - * result of discovering package subpaths. 21 - */ 22 - export interface DiscoveredSubpaths { 23 - /** all available subpaths */ 24 - subpaths: Subpath[]; 25 - /** the default subpath to select (usually ".") */ 26 - defaultSubpath: string | null; 27 - } 5 + import type { PackageExports, PackageJson } from './types'; 28 6 29 - // #endregion 7 + export type { DiscoveredSubpaths, Subpath }; 30 8 31 9 // #region condition resolution 32 10
+179 -132
src/npm/types.ts
··· 1 - /** 2 - * the parsed package.json of the main package. 3 - * includes fields relevant for export discovery and display. 4 - */ 5 - export interface PackageJson { 6 - name: string; 7 - version: string; 8 - description?: string; 9 - license?: string; 10 - main?: string; 11 - module?: string; 12 - browser?: string | Record<string, string | false>; 13 - types?: string; 14 - typings?: string; 15 - exports?: PackageExports; 16 - type?: 'module' | 'commonjs'; 17 - peerDependencies?: Record<string, string>; 18 - } 1 + import * as v from 'valibot'; 2 + 3 + // #region option schemas 4 + 5 + const resolveOptionsSchema = v.object({ 6 + installPeers: v.optional(v.boolean()), 7 + }); 8 + 9 + const fetchOptionsSchema = v.object({ 10 + concurrency: v.optional(v.number()), 11 + exclude: v.optional(v.array(v.instance(RegExp))), 12 + }); 19 13 20 - /** 21 - * package exports field - can be a string, array, object, or nested conditions. 22 - * https://nodejs.org/api/packages.html#exports 23 - */ 24 - export type PackageExports = string | string[] | { [key: string]: PackageExports } | null; 14 + const initOptionsSchema = v.object({ 15 + resolve: v.optional(resolveOptionsSchema), 16 + fetch: v.optional(fetchOptionsSchema), 17 + }); 25 18 26 - /** 27 - * npm registry packument - the full metadata for a package including all versions. 28 - * fetched from registry.npmjs.org/{package-name} 29 - */ 30 - export interface Packument { 31 - name: string; 32 - 'dist-tags': Record<string, string>; 33 - versions: Record<string, PackageManifest>; 34 - time?: Record<string, string>; 35 - } 19 + export type InitOptions = v.InferOutput<typeof initOptionsSchema>; 20 + 21 + const bundleOptionsSchema = v.object({ 22 + rolldown: v.optional( 23 + v.object({ 24 + external: v.optional(v.array(v.string())), 25 + minify: v.optional(v.boolean()), 26 + }), 27 + ), 28 + }); 29 + 30 + export type BundleOptions = v.InferOutput<typeof bundleOptionsSchema>; 31 + 32 + // #endregion 33 + 34 + // #region result schemas 35 + 36 + const subpathSchema = v.object({ 37 + subpath: v.string(), 38 + target: v.string(), 39 + isWildcard: v.boolean(), 40 + }); 41 + 42 + export type Subpath = v.InferOutput<typeof subpathSchema>; 43 + 44 + const discoveredSubpathsSchema = v.object({ 45 + subpaths: v.array(subpathSchema), 46 + defaultSubpath: v.nullable(v.string()), 47 + }); 48 + 49 + export type DiscoveredSubpaths = v.InferOutput<typeof discoveredSubpathsSchema>; 50 + 51 + const packageRefSchema = v.object({ 52 + name: v.string(), 53 + version: v.string(), 54 + isPeer: v.boolean(), 55 + }); 56 + 57 + export type PackageRef = v.InferOutput<typeof packageRefSchema>; 58 + 59 + const installedPackageSchema = v.object({ 60 + name: v.string(), 61 + version: v.string(), 62 + size: v.number(), 63 + path: v.string(), 64 + level: v.number(), 65 + dependents: v.array(packageRefSchema), 66 + dependencies: v.array(packageRefSchema), 67 + description: v.optional(v.string()), 68 + license: v.optional(v.string()), 69 + isPeer: v.boolean(), 70 + }); 71 + 72 + export type InstalledPackage = v.InferOutput<typeof installedPackageSchema>; 73 + 74 + const initResultSchema = v.object({ 75 + name: v.string(), 76 + version: v.string(), 77 + subpaths: discoveredSubpathsSchema, 78 + installSize: v.number(), 79 + packages: v.array(installedPackageSchema), 80 + peerDependencies: v.array(v.string()), 81 + }); 82 + 83 + export type InitResult = v.InferOutput<typeof initResultSchema>; 84 + 85 + const bundleChunkSchema = v.object({ 86 + fileName: v.string(), 87 + code: v.string(), 88 + size: v.number(), 89 + gzipSize: v.number(), 90 + brotliSize: v.optional(v.number()), 91 + isEntry: v.boolean(), 92 + exports: v.array(v.string()), 93 + }); 94 + 95 + export type BundleChunk = v.InferOutput<typeof bundleChunkSchema>; 96 + 97 + const bundleResultSchema = v.object({ 98 + chunks: v.array(bundleChunkSchema), 99 + size: v.number(), 100 + gzipSize: v.number(), 101 + brotliSize: v.optional(v.number()), 102 + exports: v.array(v.string()), 103 + isCjs: v.boolean(), 104 + }); 105 + 106 + export type BundleResult = v.InferOutput<typeof bundleResultSchema>; 107 + 108 + // #endregion 109 + 110 + // #region request schemas (worker parses these) 111 + 112 + const initRequestSchema = v.object({ 113 + id: v.number(), 114 + type: v.literal('init'), 115 + packageSpec: v.string(), 116 + options: v.optional(initOptionsSchema), 117 + }); 118 + 119 + const bundleRequestSchema = v.object({ 120 + id: v.number(), 121 + type: v.literal('bundle'), 122 + subpath: v.string(), 123 + selectedExports: v.nullable(v.array(v.string())), 124 + options: v.optional(bundleOptionsSchema), 125 + }); 126 + 127 + export const workerRequestSchema = v.variant('type', [initRequestSchema, bundleRequestSchema]); 128 + 129 + export type WorkerRequest = v.InferOutput<typeof workerRequestSchema>; 130 + 131 + // #endregion 132 + 133 + // #region response schemas (main thread parses these) 134 + 135 + const initResponseSchema = v.object({ 136 + id: v.number(), 137 + type: v.literal('init'), 138 + result: initResultSchema, 139 + }); 36 140 37 - /** 38 - * package manifest for a specific version. 39 - * this is what you'd find in a package.json plus registry metadata. 40 - */ 41 - export interface PackageManifest { 42 - name: string; 43 - version: string; 44 - description?: string; 45 - license?: string; 46 - main?: string; 47 - module?: string; 48 - exports?: PackageExports; 49 - type?: 'module' | 'commonjs'; 50 - dependencies?: Record<string, string>; 51 - devDependencies?: Record<string, string>; 52 - peerDependencies?: Record<string, string>; 53 - peerDependenciesMeta?: Record<string, { optional?: boolean }>; 54 - optionalDependencies?: Record<string, string>; 55 - dist: { 56 - tarball: string; 57 - integrity?: string; 58 - shasum?: string; 59 - /** total unpacked size in bytes */ 60 - unpackedSize?: number; 61 - /** number of files in the tarball */ 62 - fileCount?: number; 63 - }; 64 - } 141 + const bundleResponseSchema = v.object({ 142 + id: v.number(), 143 + type: v.literal('bundle'), 144 + result: bundleResultSchema, 145 + }); 65 146 66 - /** 67 - * a resolved package with its dependencies. 68 - * this is the output of the resolution step before hoisting. 69 - */ 70 - export interface ResolvedPackage { 71 - name: string; 72 - version: string; 73 - /** the tarball URL for fetching */ 74 - tarball: string; 75 - /** SRI integrity hash if available */ 76 - integrity?: string; 77 - /** unpacked size in bytes (from registry) */ 78 - unpackedSize?: number; 79 - /** package description */ 80 - description?: string; 81 - /** license identifier */ 82 - license?: string; 83 - /** resolved dependencies (name -> ResolvedPackage) */ 84 - dependencies: Map<string, ResolvedPackage>; 85 - } 147 + const errorResponseSchema = v.object({ 148 + id: v.number(), 149 + type: v.literal('error'), 150 + error: v.string(), 151 + }); 86 152 87 - /** 88 - * supported package registries. 89 - */ 90 - export type Registry = 'npm' | 'jsr'; 153 + const progressResponseSchema = v.variant('kind', [ 154 + v.object({ 155 + type: v.literal('progress'), 156 + kind: v.literal('resolve'), 157 + name: v.string(), 158 + version: v.string(), 159 + }), 160 + v.object({ 161 + type: v.literal('progress'), 162 + kind: v.literal('fetch'), 163 + current: v.number(), 164 + total: v.number(), 165 + name: v.string(), 166 + }), 167 + v.object({ 168 + type: v.literal('progress'), 169 + kind: v.literal('bundle'), 170 + }), 171 + v.object({ 172 + type: v.literal('progress'), 173 + kind: v.literal('compress'), 174 + }), 175 + ]); 91 176 92 - /** 93 - * the input to the resolver - a package specifier. 94 - * can be just a name (uses latest) or name@version/range. 95 - */ 96 - export interface PackageSpecifier { 97 - name: string; 98 - /** version, range, or dist-tag. defaults to 'latest' */ 99 - range: string; 100 - /** which registry to fetch from. defaults to 'npm' */ 101 - registry: Registry; 102 - } 177 + export type ProgressMessage = v.InferOutput<typeof progressResponseSchema>; 103 178 104 - /** 105 - * the full resolution result - a tree of resolved packages. 106 - */ 107 - export interface ResolutionResult { 108 - /** the root package(s) that were requested */ 109 - roots: ResolvedPackage[]; 110 - /** all unique packages in the resolution (for deduping) */ 111 - packages: Map<string, ResolvedPackage>; 112 - } 179 + export const workerResponseSchema = v.variant('type', [ 180 + initResponseSchema, 181 + bundleResponseSchema, 182 + errorResponseSchema, 183 + progressResponseSchema, 184 + ]); 113 185 114 - /** 115 - * a node in the hoisted node_modules structure. 116 - * represents what should be written to node_modules/{name} 117 - */ 118 - export interface HoistedNode { 119 - name: string; 120 - version: string; 121 - tarball: string; 122 - integrity?: string; 123 - /** unpacked size in bytes (from registry) */ 124 - unpackedSize?: number; 125 - /** package description */ 126 - description?: string; 127 - /** license identifier */ 128 - license?: string; 129 - /** number of direct dependencies */ 130 - dependencyCount: number; 131 - /** nested node_modules for this package (when hoisting fails) */ 132 - nested: Map<string, HoistedNode>; 133 - } 186 + export type WorkerResponse = v.InferOutput<typeof workerResponseSchema>; 134 187 135 - /** 136 - * the result of hoisting - a flat(ish) node_modules structure. 137 - */ 138 - export interface HoistedResult { 139 - /** top-level node_modules entries */ 140 - root: Map<string, HoistedNode>; 141 - } 188 + // #endregion
+4 -3
src/npm/worker-client.ts
··· 1 1 import * as v from 'valibot'; 2 2 3 - import type { BundleOptions, BundleResult } from './bundler'; 4 3 import { progress } from './events'; 5 4 import { 6 5 workerResponseSchema, 6 + type BundleOptions, 7 + type BundleResult, 7 8 type InitOptions, 8 9 type InitResult, 9 10 type WorkerRequest, 10 - } from './worker-protocol'; 11 + } from './types'; 11 12 12 13 export type { InitResult }; 13 14 ··· 36 37 this.resolveReady = resolve; 37 38 }); 38 39 39 - this.worker = new Worker(new URL('./worker.ts', import.meta.url), { type: 'module' }); 40 + this.worker = new Worker(new URL('./lib/worker-entry.ts', import.meta.url), { type: 'module' }); 40 41 this.worker.onmessage = this.handleMessage.bind(this); 41 42 this.worker.onerror = this.handleError.bind(this); 42 43 }
-174
src/npm/worker-protocol.ts
··· 1 - import * as v from 'valibot'; 2 - 3 - // #region option schemas 4 - 5 - const resolveOptionsSchema = v.object({ 6 - installPeers: v.optional(v.boolean()), 7 - }); 8 - 9 - const fetchOptionsSchema = v.object({ 10 - concurrency: v.optional(v.number()), 11 - exclude: v.optional(v.array(v.instance(RegExp))), 12 - }); 13 - 14 - const initOptionsSchema = v.object({ 15 - resolve: v.optional(resolveOptionsSchema), 16 - fetch: v.optional(fetchOptionsSchema), 17 - }); 18 - 19 - const bundleOptionsSchema = v.object({ 20 - rolldown: v.optional( 21 - v.object({ 22 - external: v.optional(v.array(v.string())), 23 - minify: v.optional(v.boolean()), 24 - }), 25 - ), 26 - }); 27 - 28 - // #endregion 29 - 30 - // #region result schemas 31 - 32 - const subpathSchema = v.object({ 33 - subpath: v.string(), 34 - target: v.string(), 35 - isWildcard: v.boolean(), 36 - }); 37 - 38 - const discoveredSubpathsSchema = v.object({ 39 - subpaths: v.array(subpathSchema), 40 - defaultSubpath: v.nullable(v.string()), 41 - }); 42 - 43 - const packageRefSchema = v.object({ 44 - name: v.string(), 45 - version: v.string(), 46 - isPeer: v.boolean(), 47 - }); 48 - 49 - const installedPackageSchema = v.object({ 50 - name: v.string(), 51 - version: v.string(), 52 - size: v.number(), 53 - path: v.string(), 54 - level: v.number(), 55 - dependents: v.array(packageRefSchema), 56 - dependencies: v.array(packageRefSchema), 57 - description: v.optional(v.string()), 58 - license: v.optional(v.string()), 59 - isPeer: v.boolean(), 60 - }); 61 - 62 - const initResultSchema = v.object({ 63 - name: v.string(), 64 - version: v.string(), 65 - subpaths: discoveredSubpathsSchema, 66 - installSize: v.number(), 67 - packages: v.array(installedPackageSchema), 68 - peerDependencies: v.array(v.string()), 69 - }); 70 - 71 - const bundleChunkSchema = v.object({ 72 - fileName: v.string(), 73 - code: v.string(), 74 - size: v.number(), 75 - gzipSize: v.number(), 76 - brotliSize: v.optional(v.number()), 77 - isEntry: v.boolean(), 78 - exports: v.array(v.string()), 79 - }); 80 - 81 - const bundleResultSchema = v.object({ 82 - chunks: v.array(bundleChunkSchema), 83 - size: v.number(), 84 - gzipSize: v.number(), 85 - brotliSize: v.optional(v.number()), 86 - exports: v.array(v.string()), 87 - isCjs: v.boolean(), 88 - }); 89 - 90 - // #endregion 91 - 92 - // #region request schemas (worker parses these) 93 - 94 - const initRequestSchema = v.object({ 95 - id: v.number(), 96 - type: v.literal('init'), 97 - packageSpec: v.string(), 98 - options: v.optional(initOptionsSchema), 99 - }); 100 - 101 - const bundleRequestSchema = v.object({ 102 - id: v.number(), 103 - type: v.literal('bundle'), 104 - subpath: v.string(), 105 - selectedExports: v.nullable(v.array(v.string())), 106 - options: v.optional(bundleOptionsSchema), 107 - }); 108 - 109 - export const workerRequestSchema = v.variant('type', [initRequestSchema, bundleRequestSchema]); 110 - 111 - export type WorkerRequest = v.InferOutput<typeof workerRequestSchema>; 112 - 113 - // #endregion 114 - 115 - // #region response schemas (main thread parses these) 116 - 117 - const initResponseSchema = v.object({ 118 - id: v.number(), 119 - type: v.literal('init'), 120 - result: initResultSchema, 121 - }); 122 - 123 - const bundleResponseSchema = v.object({ 124 - id: v.number(), 125 - type: v.literal('bundle'), 126 - result: bundleResultSchema, 127 - }); 128 - 129 - const errorResponseSchema = v.object({ 130 - id: v.number(), 131 - type: v.literal('error'), 132 - error: v.string(), 133 - }); 134 - 135 - const progressResponseSchema = v.variant('kind', [ 136 - v.object({ 137 - type: v.literal('progress'), 138 - kind: v.literal('resolve'), 139 - name: v.string(), 140 - version: v.string(), 141 - }), 142 - v.object({ 143 - type: v.literal('progress'), 144 - kind: v.literal('fetch'), 145 - current: v.number(), 146 - total: v.number(), 147 - name: v.string(), 148 - }), 149 - v.object({ 150 - type: v.literal('progress'), 151 - kind: v.literal('bundle'), 152 - }), 153 - v.object({ 154 - type: v.literal('progress'), 155 - kind: v.literal('compress'), 156 - }), 157 - ]); 158 - 159 - export const workerResponseSchema = v.variant('type', [ 160 - initResponseSchema, 161 - bundleResponseSchema, 162 - errorResponseSchema, 163 - progressResponseSchema, 164 - ]); 165 - 166 - export type ProgressMessage = v.InferOutput<typeof progressResponseSchema>; 167 - export type WorkerResponse = v.InferOutput<typeof workerResponseSchema>; 168 - 169 - // re-export inferred types for convenience 170 - export type InitOptions = v.InferOutput<typeof initOptionsSchema>; 171 - export type InitResult = v.InferOutput<typeof initResultSchema>; 172 - export type InstalledPackage = v.InferOutput<typeof installedPackageSchema>; 173 - 174 - // #endregion
+10 -9
src/npm/worker.ts src/npm/lib/worker-entry.ts
··· 1 1 import { memfs } from '@rolldown/browser/experimental'; 2 2 import * as v from 'valibot'; 3 3 4 - import { stripAnsi } from '../lib/strings'; 4 + import { stripAnsi } from '../../lib/strings'; 5 + import { progress } from '../events'; 6 + import { 7 + workerRequestSchema, 8 + type BundleOptions, 9 + type InitOptions, 10 + type InitResult, 11 + type WorkerResponse, 12 + } from '../types'; 5 13 6 - import { bundlePackage, type BundleOptions } from './bundler'; 7 - import { progress } from './events'; 14 + import { bundlePackage } from './bundler'; 8 15 import { fetchPackagesToVolume } from './fetch'; 9 16 import { hoist } from './hoist'; 10 17 import { buildInstalledPackages } from './installed-packages'; 11 18 import { resolve } from './resolve'; 12 19 import { discoverSubpaths } from './subpaths'; 13 20 import type { PackageJson } from './types'; 14 - import { 15 - workerRequestSchema, 16 - type InitOptions, 17 - type InitResult, 18 - type WorkerResponse, 19 - } from './worker-protocol'; 20 21 21 22 const { volume } = memfs!; 22 23