Import Cost is a popular VS Code extension that shows the size of imported packages inline in your editor. The original version used Webpack and Babel — heavy tools that worked, but were slow and bloated. I rewrote the core to use modern, Rust/Go-based tooling and the results were dramatic.
The Problem
The original Import Cost had 16 runtime dependencies including Webpack, Babel, cheerio, terser, worker-farm, css-loader, file-loader, url-loader, and 12 browser polyfills. For what’s essentially a “bundle this import and tell me the size” operation, this was overkill.
Every time you typed an import statement, the extension would:
- Parse the file with Babel (JavaScript, slow)
- Bundle the import with Webpack (JavaScript, slow)
- Minify with terser (JavaScript, slow)
- Report the size
The whole pipeline could take several seconds for large packages.
The Rewrite
I replaced every JavaScript tool with its Rust or Go equivalent:
| Before | After | Speedup |
|---|---|---|
| Babel (parser) | SWC (Rust) | 5-10x faster |
| Webpack (bundler) | esbuild (Go) | 10-100x faster |
| ESLint + Prettier | Biome (Rust) | Single tool |
| Webpack (extension build) | esbuild | Instant builds |
SWC for Parsing
SWC is a Rust-based JavaScript/TypeScript compiler. For parsing import statements, it’s massively faster than Babel:
import { parseFile } from '@swc/core';
async function extractImports(filePath: string) {
const ast = await parseFile(filePath, {
syntax: 'typescript',
tsx: true,
});
const imports: string[] = [];
for (const node of ast.body) {
if (node.type === 'ImportDeclaration') {
imports.push(node.source.value);
}
if (node.type === 'VariableDeclaration') {
for (const decl of node.declarations) {
if (
decl.init?.type === 'CallExpression' &&
decl.init.callee.type === 'Identifier' &&
decl.init.callee.value === 'require'
) {
const arg = decl.init.arguments[0];
if (arg?.expression.type === 'StringLiteral') {
imports.push(arg.expression.value);
}
}
}
}
}
return imports;
}
esbuild for Bundling
esbuild replaces both Webpack (bundling) and terser (minification) in a single pass:
import { build } from 'esbuild';
import { writeFileSync, unlinkSync } from 'fs';
import { tmpdir } from 'os';
import { join } from 'path';
import { gzipSync } from 'zlib';
async function calculateImportSize(packageName: string) {
const entryContent = `import '${packageName}';`;
const entryFile = join(tmpdir(), `import-cost-${Date.now()}.js`);
writeFileSync(entryFile, entryContent);
try {
const result = await build({
entryPoints: [entryFile],
bundle: true,
write: false,
minify: true,
platform: 'browser',
format: 'esm',
treeShaking: true,
metafile: true,
});
const output = result.outputFiles[0];
const minifiedSize = output.contents.byteLength;
const gzippedSize = gzipSync(output.contents).byteLength;
return { minifiedSize, gzippedSize };
} finally {
unlinkSync(entryFile);
}
}
This runs in milliseconds where Webpack took seconds. esbuild’s Go implementation handles bundling, tree-shaking, and minification in a single pass.
Project Structure
The project uses npm workspaces as a monorepo:
packages/
├── import-cost/ # Core Node module
│ ├── src/
│ │ ├── parser.ts # SWC-based import extraction
│ │ ├── bundler.ts # esbuild-based size calculation
│ │ └── index.ts # Public API
│ └── package.json
├── vscode-import-cost/ # VS Code extension
│ ├── src/
│ │ ├── extension.ts # Extension entry point
│ │ └── decorator.ts # Inline size display
│ └── package.json
├── coc-import-cost/ # Vim/Neovim extension
│ └── ...
└── native-fs-adapter/ # Filesystem cache adapter
└── ...
The core import-cost package is framework-agnostic — it exports a simple API that any editor extension can use:
import { importCost } from 'import-cost';
const results = await importCost('/path/to/file.ts');
// [{ name: 'lodash', size: 72384, gzip: 25412, line: 1 }]
Results
After the rewrite:
- Runtime dependencies: 16 → 2 (
esbuild,@swc/core) - Extension startup: ~3s → ~200ms
- Import size calculation: 2-5s → 50-200ms
- Extension bundle size: Significantly smaller
The key takeaway: modern Rust/Go tooling isn’t just faster — it’s simpler. One tool replaces five, and the code is more readable because there are fewer layers of configuration.
Check out the project on GitHub.