Skip to content

perf(vscode-extension): keep the language server out of the client bundle - #1275

Open
EvilGenius13 wants to merge 4 commits into
mainfrom
jf/vscode-extension-bundle-size
Open

perf(vscode-extension): keep the language server out of the client bundle#1275
EvilGenius13 wants to merge 4 commits into
mainfrom
jf/vscode-extension-bundle-size

Conversation

@EvilGenius13

@EvilGenius13 EvilGenius13 commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

What this does

The VS Code extension was shipping a lot more code than it needed.

It pulls a couple of small helpers from two shared packages. Both came in through the package barrel (@shopify/theme-check-common), and a barrel import drags in everything the barrel touches — including the whole theme-check rule suite, which the extension never runs.

This trims it down.

Before After
Browser extension 6.72 MB 2.10 MB −68.7%
Desktop extension 6.49 MB 1.41 MB −78.3%

Less to download, quicker to start — most noticeable on the web version.

How

Both packages now declare their public entry points in package.json exports.

  • @shopify/theme-check-common., ./path, ./test, ./package.json
  • @shopify/theme-language-server-common., ./types, ./package.json

./path and ./types are narrow entry points: importing one loads that module alone, not the barrel. That's how the extension gets the path helpers and the LSP request types without pulling the server along. Putting them in exports is what makes them the supported public API rather than an informal convention.

typesVersions mirrors the map so TypeScript keeps up. This repo compiles with moduleResolution: node, which ignores exports entirely. Without typesVersions the subpaths would resolve at runtime but fail to type-check — that was the blocker from the earlier round of review, and this is the fix.

Unlisted deep imports are now unsupported, on purpose. Anything outside the map — @shopify/theme-check-common/src/..., .../dist/... — throws ERR_PACKAGE_PATH_NOT_EXPORTED in Node and in bundlers. That's the point: the packages own their internal layout again, and the bundle win holds because there is no back door into the barrel. Both libraries are major in the changeset.

What that cost inside the repo:

  • Seven symbols promoted to the theme-check-common barrel, having previously been reachable only through a now-retired deep import: getPosition, createDisabledChecksModule, UNMATCHED_COMMENT_CLOSE_PARSER_ERROR, UNMATCHED_RAW_CLOSE_PARSER_ERROR, hasRubyAcceptedInertCommentBodyCloser, hasJavascriptClosingTagAfter, hasRubyAcceptedRawTagCloserWithMarkup. Everything else those modules exposed stays private.
  • Internal test imports moved to /test. Seven language-server specs that reached into @shopify/theme-check-common/src/test (or /dist/test) now import @shopify/theme-check-common/test. One stale relative import of test-setup was dropped — Vitest already loads it through setupFiles.
  • tsconfig paths in the root config and theme-language-server-common/src/tsconfig.json map the new subpaths to src/ so in-repo development still resolves to source.

Type-only imports are type-only again. Roughly half the extension's imports were types, which never reach the bundle in the first place; those are back on the barrel as import type.

Known external consumer: shop/world has five dist/ deep imports across areas/clients/shell/packages/theme-check/check.ts and check-liquid-fused.ts. All the symbols they need are on the barrel now, but that migration has to land before that repo picks up the new majors.

Checked

  • Full suite green on a cold cache — 352 files, 6581 tests (1 file / 5 tests skipped)
  • New package-exports.spec.ts in both packages — 22 tests asserting every public subpath resolves to the expected dist file, every retired src//dist/ path fails with ERR_PACKAGE_PATH_NOT_EXPORTED, and all seven promoted symbols are present on the barrel
  • pnpm build:ts, pnpm type-check, pnpm format:check pass; pnpm changeset status reports 7 major + 2 patch
  • Bundle sizes from a clean rebuild (caches purged): browser/extension.js 2,103,405 B, node/extension.js 1,410,146 B
  • Negative control: pointing a single import back at the barrel sends the browser bundle straight back to 6.72 MB
  • CI green on this branch — tests on Node 22/24 × ubuntu/windows, code quality, E2E

@EvilGenius13
EvilGenius13 force-pushed the jf/vscode-extension-bundle-size branch from b70dcb1 to 2ac1ab6 Compare August 10, 2026 15:07
@EvilGenius13
EvilGenius13 force-pushed the jf/vscode-extension-bundle-size branch from 2ac1ab6 to 4e4df97 Compare August 10, 2026 17:46
@EvilGenius13
EvilGenius13 marked this pull request as ready for review August 10, 2026 18:33
@EvilGenius13
EvilGenius13 requested a review from a team as a code owner August 10, 2026 18:33

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I love the outcome of this PR but not a huge fan of how we're getting there. Reaching into the dist folder is creating an implicit connection to theme-check-common's build process. I'd rather fix this in theme-check-common itself by adding some public subpath exports that we intend for consumers to use.

Also side note: I don't think the type imports need this? I don't think these are included at runtime.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the suggestion Gray.

Type imports - you were totally right so I put it back the way it was.

Instead of reaching into the dist folder they got moved into the packages themselves and they each have an entry point pointing to the actual file. No more weird connections.

I tried the subpath exports but I ran into a couple problems. Our TS setup falls back to the older resolution mode and it ignores exports completely. When I tried setting up the new mode if a path wasn't explicitly listed it stopped resolving. It messed up deep-imports of MockTheme and MockFileSystem`. My concern is if any user of the libraries has deep links we would probably break for them as well.

What do you think about this current method vs the subpath export?

…paths

Replace the vscode-extension's `dist/` deep imports with root-level
re-export stubs, so consumers get a supported entry point instead of
reaching into build output.

`@shopify/theme-check-common/path` and
`@shopify/theme-language-server-common/types` re-export their `dist`
modules. The extension imports values from those subpaths and takes
types from the package barrels, keeping the language server out of the
client bundle: browser/extension.js is 2,103,681 bytes and
node/extension.js is 1,410,426 bytes, versus 6,722,159 and 6,497,684
when the same values come from a barrel.

Purely additive. No package.json, tsconfig, or webpack changes, and the
barrel imports behave exactly as before.
@EvilGenius13
EvilGenius13 requested review from a team and graygilmore August 12, 2026 14:10
Add curated `exports` + `typesVersions` maps to
`@shopify/theme-check-common` (`.`, `./path`, `./test`, `./package.json`)
and `@shopify/theme-language-server-common` (`.`, `./types`,
`./package.json`), replacing the root `path.*` / `types.*` forwarder
files with a single declared public surface.

BREAKING CHANGE: deep imports into `dist/` and `src/` no longer resolve.
Seven symbols previously reached via `dist/` deep paths are promoted to
the `@shopify/theme-check-common` barrel: `getPosition`,
`createDisabledChecksModule`, `UNMATCHED_COMMENT_CLOSE_PARSER_ERROR`,
`UNMATCHED_RAW_CLOSE_PARSER_ERROR`,
`hasRubyAcceptedInertCommentBodyCloser`, `hasJavascriptClosingTagAfter`,
and `hasRubyAcceptedRawTagCloserWithMarkup`.

In-repo `@shopify/theme-check-common/{src,dist}/test` call sites move to
the public `/test` subpath. New `package-exports.spec.ts` in each package
locks both the declared subpaths and the removed legacy ones.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants