diff --git a/.vscodeignore b/.vscodeignore index 19e8fbd..7f2d48c 100644 --- a/.vscodeignore +++ b/.vscodeignore @@ -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 @@ -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/** @@ -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 @@ -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 @@ -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 diff --git a/CHANGELOG.md b/CHANGELOG.md index f57c6ff..360984b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 ### 新功能 diff --git a/docs/prd/024-vsix-size-optimization.md b/docs/prd/024-vsix-size-optimization.md new file mode 100644 index 0000000..01e0032 --- /dev/null +++ b/docs/prd/024-vsix-size-optimization.md @@ -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 收编。 diff --git a/esbuild.js b/esbuild.js index 117e13b..2ffeaff 100644 --- a/esbuild.js +++ b/esbuild.js @@ -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", diff --git a/package-lock.json b/package-lock.json index 2d7c9e8..9983e3c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,18 +1,17 @@ { "name": "hive-formatter", - "version": "2.34.0", + "version": "2.35.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "hive-formatter", - "version": "2.34.0", + "version": "2.35.0", "license": "MIT", "dependencies": { "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", @@ -34,6 +33,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", @@ -2271,6 +2271,7 @@ "version": "0.10.6", "resolved": "https://registry.npmjs.org/@types/pegjs/-/pegjs-0.10.6.tgz", "integrity": "sha512-eLYXDbZWXh2uxf+w8sXS8d6KSoXTswfps6fvCUuVAGN8eRpfe7h9eSRydxiSJvo9Bf+GzifsDOr9TMQlmJdmkw==", + "dev": true, "license": "MIT" }, "node_modules/@types/pg": { @@ -4210,6 +4211,7 @@ "version": "1.6.52", "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.52.tgz", "integrity": "sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==", + "dev": true, "license": "Unlicense", "engines": { "node": ">=0.6" @@ -8181,6 +8183,7 @@ "version": "5.4.0", "resolved": "https://registry.npmjs.org/node-sql-parser/-/node-sql-parser-5.4.0.tgz", "integrity": "sha512-jVe6Z61gPcPjCElPZ6j8llB3wnqGcuQzefim1ERsqIakxnEy5JlzV7XKdO1KmacRG5TKwPc4vJTgSRQ0LfkbFw==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@types/pegjs": "^0.10.0", diff --git a/package.json b/package.json index 63297fc..6552d52 100644 --- a/package.json +++ b/package.json @@ -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", @@ -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",