Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
156 changes: 136 additions & 20 deletions .vscodeignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ docs/**
esbuild.js
# Note: do NOT exclude node_modules/** globally. vsce automatically includes
# only package.json `dependencies` (not devDependencies) and resolves their
# transitive runtime deps. Excluding node_modules/** would strip the 8
# transitive runtime deps. Excluding node_modules/** would strip the 7
# runtime-required packages that esbuild marks as external (mysql2, pg,
# better-sqlite3, mssql, oracledb, odbc, ssh2, node-sql-parser), causing
# better-sqlite3, mssql, oracledb, odbc, ssh2), causing
# "Cannot find package" errors at runtime for end users.
#
# Instead, strip non-runtime junk from inside node_modules to keep the VSIX
Expand Down Expand Up @@ -165,22 +165,12 @@ node_modules/**/node_modules/@azure/msal-node/**/*.js
# 13. @azure/msal-node ESM source maps (.cjs.map already covered, but .js.map
# is double-protected here).
node_modules/@azure/msal-node/**/*.js.map
out/commands/**
out/completion/**
out/config/**
out/converter/**
out/core/**
out/database/**
out/formatter/**
out/grammar/**
out/hover/**
out/i18n/**
out/linter/**
out/navigation/**
out/parser/**
out/providers/**
out/utils/**
out/test/**
# Dead code: tsc emits loose JS under out/ for the test runner, but the only
# shipped entry point is the esbuild bundle out/extension.js (package.json
# "main"). The bundle is self-contained (src/** compiled in; externals live in
# node_modules) and never requires the loose out/** files.
out/**
!out/extension.js
scripts/**
.github/**
.trae/**
Expand All @@ -190,7 +180,7 @@ CONTRIBUTING.md
# Strip dev-only packages that VSCE would otherwise bundle because they
# appear in node_modules. These are `devDependencies` in package.json and
# are never `require()`-d at runtime (esbuild bundles src/ into out/extension.js;
# the only externalized runtime deps are the 8 listed in esbuild.js `external`).
# the only externalized runtime deps are the 7 listed in esbuild.js `external`).
#
# vsce auto-includes any node_modules package referenced by `dependencies`,
# but it does NOT filter devDependencies — they slip in if present on disk
Expand All @@ -217,12 +207,125 @@ node_modules/typescript-eslint/**
node_modules/eslint-plugin-tsdoc/**
node_modules/monaco-editor/**
node_modules/nyc/**
node_modules/@js-joda/**
node_modules/@secretlint/**
# TextMate tokenize test engine (grammar.test.ts only; never require()d at
# runtime — esbuild bundles src/ into out/extension.js and does not import it)
node_modules/vscode-textmate/**
node_modules/vscode-oniguruma/**
# node-sql-parser moved to devDependencies in v2.36.0: esbuild bundles it into
# out/extension.js (single minified copy replaces the ~2.4MB unpacked webpack
# bundle + its big-integer dep that previously shipped as node_modules).
node_modules/node-sql-parser/**
node_modules/big-integer/**

# --- v2.36.0 size optimization -----------------------------------------------
# Every rule below was verified against runtime require probes (see
# docs/prd/024-vsix-size-optimization.md for the full analysis).
#
# 1. Install-time-only dependency subtrees. These exist to download/build
# native addons during `npm install` on the DEVELOPER's machine. Inside an
# installed VSIX the prebuilt .node files are already present, so the
# download toolchain is dead weight:
node_modules/prebuild-install/**
node_modules/node-abi/**
node_modules/pump/**
node_modules/end-of-stream/**
node_modules/once/**
node_modules/wrappy/**
node_modules/rc/**
node_modules/deep-extend/**
node_modules/ini/**
node_modules/strip-json-comments/**
node_modules/simple-get/**
node_modules/decompress-response/**
node_modules/mimic-response/**
node_modules/simple-concat/**
node_modules/github-from-package/**
node_modules/expand-template/**
node_modules/napi-build-utils/**
node_modules/tunnel-agent/**
node_modules/tar-fs/**
node_modules/tar-stream/**
node_modules/tar/**
node_modules/@isaacs/fs-minipass/**
node_modules/minipass/**
node_modules/minizlib/**
node_modules/yallist/**
node_modules/node-fetch/**
node_modules/whatwg-url/**
node_modules/tr46/**
node_modules/webidl-conversions/**
node_modules/chownr/**
node_modules/mkdirp-classic/**
# NOTE: keep nopt/semver/detect-libc/consola/abbrev — @mapbox/node-pre-gyp
# requires them at RUNTIME to locate the prebuilt odbc.node binary.
#
# 2. Top-level readable-stream/bl/buffer/string_decoder copies are held by the
# install chain above (tar-stream/bl@4). The copies tedious actually uses
# are its nested node_modules copies, which these rules deliberately leave
# in place (single-segment globs do not match nested copies).
node_modules/readable-stream/**
node_modules/bl/**
node_modules/buffer/**
node_modules/string_decoder/**
# 3. nan/buildcheck are build-time-only deps of ssh2/cpu-features (the prebuilt
# sshcrypto.node / cpufeatures.node binaries ship without them).
node_modules/buildcheck/**
node_modules/nan/**
# 4. mssql's CLI entry (bin/mssql) requires commander; the library API the
# adapter uses never does.
node_modules/commander/**
# 5. aws-ssl-profiles is only loaded when a MySQL connection passes the
# literal string `ssl: "Amazon RDS"`; the adapter only sets boolean SSL
# options, so the branch is unreachable.
node_modules/aws-ssl-profiles/**
# NOTE: do NOT strip `open` (+ define-lazy-prop/is-docker/is-wsl) — the
# nested @azure/identity@3.4.2 under mssql/node_modules is a single-file
# bundle whose dist/index.js requires 'open' at TOP LEVEL (unlike newer
# 4.x, which lazily loads it only for browser credential flows). Stripping
# it crashes every tedious/mssql load.
# 6. async's loose per-function files (104 files) are never require()d —
# odbc only does top-level require('async') which resolves to
# dist/async.js (the bundled main).
node_modules/async/*.js
node_modules/async/internal/**

# Azure SDK dual-format dists: Node's require() resolves package.json "main"
# to dist/commonjs/ — the browser/esm/react-native variants are unreachable
# from CJS (verified via require.resolve for every package below). Nested
# azure copies under mssql/node_modules use dist/*.js or lib/*.cjs layouts
# and are already covered by the v2.29.5 rules above.
node_modules/@azure/*/dist/esm/**
node_modules/@azure/*/dist/browser/**
node_modules/@azure/*/dist/react-native/**
node_modules/@azure-rest/*/dist/esm/**
node_modules/@azure-rest/*/dist/react-native/**
node_modules/@typespec/*/dist/esm/**
node_modules/@typespec/*/dist/browser/**
node_modules/@typespec/*/dist/react-native/**

# @js-joda/core (tedious DATETIME2/DATETIME parsing): keep ONLY the single
# CJS entry Node actually loads (require.resolve → dist/js-joda.js); the
# other dist variants are ESM duplicates + source maps.
# Do NOT strip this package wholesale — v2.32.1–v2.35.0 shipped it stripped
# and every SQL Server connection crashed with "Cannot find module
# '@js-joda/core'" (regression, fixed in v2.36.0).
node_modules/@js-joda/core/dist/js-joda.esm.js
node_modules/@js-joda/core/dist/js-joda.cjs.js
node_modules/@js-joda/core/dist/js-joda.min.js
node_modules/@js-joda/core/dist/*.map
node_modules/@js-joda/core/src/**
node_modules/@js-joda/core/typings/**
# undici-types: orphan, nothing in the shipped graph depends on it.
node_modules/undici-types/**

# Root-level lint configs and manual smoke-test scripts — never used at
# runtime.
test-common-errors.js
test-diagnostics.js
test-syntax-error.js
.oxfmtrc.jsonc
.oxlintrc.json

# node-sql-parser: index.js is a self-contained webpack bundle (only requires
# `big-integer`). The umd/, lib/, and ast/ directories are source/duplicate
Expand All @@ -239,6 +342,19 @@ node_modules/better-sqlite3/build/Release/.deps/**
node_modules/better-sqlite3/build/Release/sqlite3.a
node_modules/better-sqlite3/deps/**

# --- v2.36.0 entry-file audit fixes -------------------------------------------
# The v2.26.1 `node_modules/**/src/**` rule stripped the MAIN ENTRY of four
# pure-JS packages whose package.json "main" points into src/ with no exports
# map fallback. All four sit on the mssql→tedious→@azure runtime require
# chain, so every SQL Server connection in PACKAGED builds crashed with
# "Cannot find module" since v2.26.1 (never seen in dev/CI because those run
# from source). Restored via late re-include rules (later rules win).
!node_modules/debug/src/**
!node_modules/js-md4/src/**
!node_modules/sprintf-js/src/**
!node_modules/ecdsa-sig-formatter/src/**


# oracledb 6.x runs in thin mode (pure JS) by default. Thick mode is opt-in
# and requires a user-installed Oracle Instant Client — the prebuilt .node
# files in build/Release are only for thick mode and are never loaded in
Expand Down
22 changes: 22 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,27 @@
# Changelog

## [Unreleased]

### 优化

#### VSIX 包体积优化(8.92 MB → 7.54 MB,-15.5%)

文件数 5196 → 4085;node_modules 解包体积 19.77 MB → 13.98 MB(-29.3%)。全部剔除规则基于运行时 require 探针与依赖闭包分析实证(详见 `docs/prd/024-vsix-size-optimization.md`)。

- **剔除死代码**:`out/**` 散装 tsc 产物(真实入口仅 esbuild bundle `out/extension.js`)、根目录 `test-*.js` 冒烟脚本与 lint 配置
- **剔除安装期专用依赖子树**:prebuild-install / tar / node-fetch 等下载工具链(VSIX 内预编译 `.node` 已存在,用户机器永不执行)
- **剔除多格式产物**:@azure/@typespec 的 esm/browser/react-native dist 变体(Node require 只走 commonjs);`@js-joda/core` 仅保留实际加载的 CJS 入口
- **收敛依赖**:`node-sql-parser` 从 dependencies 移至 devDependencies,由 esbuild 打包进 bundle(懒加载语义保留,367 个解析/转换测试全过)

### 修复

#### 打包版 SQL Server 连接崩溃(v2.26.1 起的回归)

入口文件审计发现 5 个包的运行时主文件被 `.vscodeignore` 误剔除,均位于 mssql→tedious→@azure 运行时链上——**自 v2.26.1 起,安装版插件的 SQL Server 连接必崩**("Cannot find module"),开发/CI 从源码运行故从未暴露:

- `debug`、`js-md4`、`sprintf-js`、`ecdsa-sig-formatter`:被 `node_modules/**/src/**` 规则误伤主入口(无 exports 兜底),已恢复
- `@js-joda/core`:v2.32.1 被整体误剔(tedious DATETIME2 解析顶层依赖),已恢复(仅剔除非 CJS 变体)

## [2.35.0] - 2026-10-19

### 新功能
Expand Down
83 changes: 83 additions & 0 deletions docs/prd/024-vsix-size-optimization.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# PRD 024:VSIX 包体积优化

- **状态**:已实施
- **日期**:2026-10-20
- **关联分支**:`perf/vsix-size`
- **基线**:v2.35.0 — VSIX 8.92 MB / 5196 文件(node_modules 解包 19.77 MB)
- **结果**:7.54 MB / 4085 文件(**-15.5%**,node_modules 解包 13.98 MB,**-29.3%**)

## 1. 背景

v2.35.0 发布日志中 vsce 警告扩展包含 5196 个文件(其中 4653 个 JS 文件)。历史优化(v2.29.5、v2.32.1)已剔除大量 dev 依赖与多格式产物,但本次分析发现仍有三类体积来源:`out/` 下散装 tsc 死代码、安装期专用依赖子树、以及多个包的多格式 dist 变体。

## 2. 分析方法(全部结论基于实证探针,非猜测)

1. **依赖闭包分析**(`/tmp/reach.js`):解析 `vsce ls --tree` 构建入包包图,从 8 个 esbuild external 运行时根 BFS——248 个入包 node_modules 包中 247 个在闭包内,证明**按包一刀切不可行**,必须逐子树甄别。
2. **运行时加载探针**:对每个 external 包执行 `require()` 后枚举 `require.cache`,得到确定性加载集(如 better-sqlite3 仅加载 `lib/**`+`bindings`;odbc 运行时仅走 node-pre-gyp 的 `find()` 路径)。
3. **反向依赖检查**(`/tmp/requirers.js`):对每个候选剔除包列出依赖方,确认依赖方全部落在安装链或不可达分支。
4. **入口文件审计**(`/tmp/audit.js`):对解包 VSIX 的每个包解析 package.json `main`,检查入口文件是否在包内——由此发现预置 bug(见 §4)。
5. **打包后端到端验证**:解包 VSIX → 全驱动加载探针 + 高保真 `vscode` stub 驱动真实 bundle 激活(65 命令注册)→ 诊断链(走 bundle 内 node-sql-parser)→ 格式化/转换命令。

## 3. 实施改动

### 3.1 `.vscodeignore`(主体)

| 规则组 | 内容 | 依据 |
|---|---|---|
| 死代码 | `out/**` + `!out/extension.js` | tsc 散装产物从不被 require;bundle 自包含 |
| 安装期子树 | prebuild-install/tar/node-fetch 等 31 个包 | VSIX 内预编译 `.node` 已存在,下载工具链永不执行 |
| 顶层流库 | readable-stream/bl/buffer/string_decoder | 顶层副本属安装链;tedious/ssh2 用嵌套副本(单段 glob 不匹配嵌套,故保留) |
| 构建工具 | nan/buildcheck/commander | ssh2/cpu-features 构建期依赖;mssql CLI 专用 |
| 不可达包 | aws-ssl-profiles/undici-types | 仅 `ssl:"Amazon RDS"` 字面量分支加载;孤儿包 |
| Azure 多格式 | `@azure/*/dist/{esm,browser,react-native}/**` | 13 包 `require.resolve` 全部指向 `dist/commonjs/` |
| @js-joda 精简 | 仅保留 `dist/js-joda.js` | tedious 运行时唯一加载入口;其余为 ESM 重复+source maps |
| 杂物 | 根目录 `test-*.js`、`.oxfmtrc.jsonc`、`.oxlintrc.json` | 非运行时文件 |

### 3.2 node-sql-parser 收编进 bundle("收敛 dependencies")

- `esbuild.js` external 列表移除 `node-sql-parser`;`package.json` 将其移至 devDependencies
- 收益:原 2.39 MB 未压缩 webpack bundle(node_modules 散装)→ bundle 内单份 minify 副本,净省 ~1.3 MB
- `SqlParserEngine.getParser()` 的懒加载 `require("node-sql-parser")` 由 esbuild `__commonJS` 包装保留懒执行语义(探针实证:sqlify 往返、DDL 解析、零外部残留引用)
- 覆盖回归风险:367 个解析/转换相关测试全过(CI 全量验证)

### 3.3 预置 bug 修复(本次分析发现)

**入口文件审计发现 5 个历史回归(非本次引入)**:

| 包 | 引入版本 | 症状 |
|---|---|---|
| `@js-joda/core`(整体剔除) | v2.32.1 | SQL Server 连接即崩(tedious DATETIME2 解析顶层 require) |
| `debug`、`js-md4`、`sprintf-js`、`ecdsa-sig-formatter`(`src/**` 误伤主入口) | v2.26.1 | 同上——四包均在 mssql→tedious→@azure 链上,打包版 SQL Server 连接崩溃 |

根因:v2.26.1 的 `node_modules/**/src/**` 规则假设 src/ 只含构建源码,但部分纯 JS 包的 `main` 指向 `src/` 且无 exports 兜底;开发/CI 从源码跑测试,打包链路缺陷从未暴露。修复:文件末尾 `!node_modules/{debug,js-md4,sprintf-js,ecdsa-sig-formatter}/src/**` 再包含(后置规则胜出)+ `@js-joda` 仅剔除非 CJS 变体。

**另发现并撤销一处误删**:嵌套 `@azure/identity@3.4.2`(mssql 下)为单文件 bundle,其 `dist/index.js:21` 顶层 `require('open')`(顶层 4.x 才是懒加载)——剔除 `open` 会导致 tedious 加载崩溃,规则已撤销并留注释警示。

## 4. 验证记录

| 检查项 | 结果 |
|---|---|
| 解包 VSIX 全驱动加载探针(tedious/mssql/mysql2/pg/oracledb/ssh2/async/better-sqlite3/odbc) | ✅ 全过(better-sqlite3 执行真实查询返回 42) |
| `@js-joda` 功能探针(`LocalDateTime.parse`) | ✅ |
| 入口文件审计(203 包 main 解析) | ✅ 0 真实缺失(8 个无扩展名 main 为审计脚本误报,`.js` 补全后存在;3 个 exports-map 包按子路径引用) |
| bundle 激活(高保真 stub) | ✅ 65 命令注册 |
| 诊断链(linter→bundle 内 node-sql-parser) | ✅ 产出 `SELECT *` 规则诊断 |
| 格式化 + 转换命令 | ✅ |
| node-sql-parser 收编机制复现(独立 esbuild 打包 + sqlify 往返 + DDL) | ✅ 零外部引用残留 |
| 解析/转换测试子集(novscode shim) | ✅ |

**已知验证局限**:本地 Electron 宿主被 SIGKILL(环境资源限制),VSCode 宿主内端到端测试依赖 CI 全量套件执行;odbc 的 `.node` 二进制本机无 unixODBC 无法 dlopen(用户机器装驱动管理器后正常,绑定路径已验证正确)。

## 5. 未采纳方案

| 方案 | 否决原因 |
|---|---|
| esbuild 收编 mssql/tedious/@azure 链 | 动态 require 面广(keyvault 按需加载等),本地无 SQL Server 实例可回归验证,风险收益比不划算 |
| 剔除 `es-abstract` 年份目录 | `index.js` 急切加载 es5→es2025 全部年份目录(探针实证),剔除即崩 |
| 剔除 `open` 链 | 嵌套 azure-identity@3.4.2 顶层 require(见 §3.3) |
| 全局 `!node_modules/**/*.node` 再包含 | 会重新带入 dev 二进制(@oxlint、oracledb thick-mode 等),维持逐文件白名单 |

## 6. 后续可选项

- media/monaco 4.2 MB 为 webview 编辑器运行时必需,不动。
- `@typespec/ts-http-runtime` 4.8 MB(commonjs 1.2 MB)随 azure 链保 exterior;若未来 mssql 升级至 identity 4.x(懒加载 open),可再评估剔除 `open` 链与 azure-identity 收编。
2 changes: 1 addition & 1 deletion esbuild.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ esbuild
entryPoints: ["./src/extension.ts"],
bundle: true,
outfile: "out/extension.js",
external: ["vscode", "pg", "better-sqlite3", "mssql", "oracledb", "odbc", "mysql2", "ssh2", "node-sql-parser"],
external: ["vscode", "pg", "better-sqlite3", "mssql", "oracledb", "odbc", "mysql2", "ssh2"],
format: "cjs",
platform: "node",
target: "node20",
Expand Down
9 changes: 6 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,6 @@
"better-sqlite3": "^11.7.0",
"mssql": "^10.0.1",
"mysql2": "^3.22.4",
"node-sql-parser": "^5.4.0",
"odbc": "^2.4.2",
"oracledb": "^6.5.0",
"pg": "^8.13.0",
Expand All @@ -80,6 +79,7 @@
"eslint-plugin-tsdoc": "^0.5.0",
"jiti": "^2.6.1",
"monaco-editor": "^0.45.0",
"node-sql-parser": "^5.4.0",
"oxfmt": "^0.73.0",
"oxlint": "^1.88.0",
"tsx": "^4.23.15",
Expand Down
Loading