Skip to content

Commit 00eaa41

Browse files
authored
Use esbuild to bundle (#3834)
* Use esbuild to bundle * Removed unneeded dep, updated browser bundle * remove pull request event * added compile step * updated esbuild
1 parent 2a374ee commit 00eaa41

File tree

12 files changed

+591
-1257
lines changed

12 files changed

+591
-1257
lines changed

.github/workflows/main.yaml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
name: "Main"
22

3-
on: [push, pull_request]
3+
on:
4+
push:
45

56
jobs:
67
lint:
@@ -64,6 +65,7 @@ jobs:
6465
node-version-file: ".nvmrc"
6566
- uses: pnpm/action-setup@v4
6667
- run: pnpm install
68+
- run: pnpm compile
6769
- run: npx @vscode/vsce package --no-dependencies
6870
- run: echo "VSIX_PATH=$(find . -maxdepth 1 -type f -iname "*.vsix" | head -1)" >> $GITHUB_ENV
6971
- run: echo "VSIX_NAME=$(basename $(find . -maxdepth 1 -type f -iname "*.vsix" | head -1))" >> $GITHUB_ENV
@@ -91,6 +93,7 @@ jobs:
9193
echo "is_prerelease=false" >> $GITHUB_OUTPUT
9294
fi
9395
- run: pnpm install
96+
- run: pnpm compile
9497
- run: npx @vscode/vsce package --no-dependencies ${{ steps.check_prerelease.outputs.is_prerelease == 'true' && '--pre-release' || '' }}
9598
- run: echo "VSIX_PATH=$(find . -maxdepth 1 -type f -iname "*.vsix" | head -1)" >> $GITHUB_ENV
9699
- run: echo "VSIX_NAME=$(basename $(find . -maxdepth 1 -type f -iname "*.vsix" | head -1))" >> $GITHUB_ENV

.prettierignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@
22
out/
33
dist/
44
test-fixtures/
5-
node_modules/
5+
node_modules/
6+
pnpm-lock.yaml

.vscode/tasks.json

Lines changed: 46 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,57 @@
44
"version": "2.0.0",
55
"tasks": [
66
{
7+
"label": "watch",
8+
"dependsOn": ["watch:esbuild", "watch:tsc"],
9+
"group": {
10+
"kind": "build",
11+
"isDefault": true
12+
},
13+
"runOptions": {
14+
"runOn": "folderOpen"
15+
}
16+
},
17+
{
18+
"label": "watch:esbuild",
719
"type": "npm",
8-
"script": "compile",
20+
"script": "watch:esbuild",
21+
"problemMatcher": {
22+
"owner": "esbuild",
23+
"fileLocation": ["relative", "${workspaceFolder}"],
24+
"pattern": {
25+
"regexp": "^✘ \\[ERROR\\] (.*)$",
26+
"message": 1
27+
},
28+
"background": {
29+
"activeOnStart": true,
30+
"beginsPattern": "^\\[watch\\] build started$",
31+
"endsPattern": "^\\[watch\\] build finished$"
32+
}
33+
},
34+
"isBackground": true,
35+
"presentation": {
36+
"reveal": "never",
37+
"group": "watch"
38+
}
39+
},
40+
{
41+
"label": "watch:tsc",
42+
"type": "npm",
43+
"script": "watch:tsc",
944
"problemMatcher": "$tsc-watch",
1045
"isBackground": true,
46+
"presentation": {
47+
"reveal": "never",
48+
"group": "watch"
49+
}
50+
},
51+
{
52+
"label": "compile",
53+
"type": "npm",
54+
"script": "compile",
55+
"problemMatcher": "$tsc",
1156
"presentation": {
1257
"reveal": "silent"
13-
},
14-
"group": {
15-
"kind": "build",
16-
"isDefault": true
1758
}
1859
}
1960
]

CLAUDE.md

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,14 @@ This is the official Prettier VS Code extension (`prettier.prettier-vscode`). It
1313
pnpm install
1414

1515
# Build for development
16-
pnpm webpack
16+
pnpm compile
1717

1818
# Build for production
19-
pnpm vscode:prepublish
19+
pnpm package
2020

21-
# Watch mode (TypeScript only, without webpack)
21+
# Watch mode (esbuild + TypeScript type checking)
2222
pnpm watch
2323

24-
# Watch mode (webpack)
25-
pnpm webpack-dev
26-
2724
# Run linting
2825
pnpm lint
2926

@@ -97,13 +94,15 @@ Web tests are located in `src/test/web/suite/` and test the extension's browser
9794

9895
### Bundling
9996

100-
Webpack produces two bundles:
97+
esbuild produces two bundles:
10198

10299
- Node bundle (`dist/extension.js`) for desktop VS Code
103100
- Web bundle (`dist/web-extension.js`) for vscode.dev/browser
104101

105102
The browser build uses path aliasing to swap `ModuleResolver``BrowserModuleResolver`.
106103

104+
Build configuration is in `esbuild.js`.
105+
107106
## Test Fixtures
108107

109108
Test fixtures in `test-fixtures/` each have their own `package.json` and Prettier configurations:

esbuild.js

Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
const esbuild = require("esbuild");
2+
const fs = require("fs");
3+
const path = require("path");
4+
5+
const production = process.argv.includes("--production");
6+
const watch = process.argv.includes("--watch");
7+
8+
const extensionPackage = require("./package.json");
9+
10+
/**
11+
* @type {import('esbuild').Plugin}
12+
*/
13+
const esbuildProblemMatcherPlugin = {
14+
name: "esbuild-problem-matcher",
15+
setup(build) {
16+
build.onStart(() => {
17+
console.log("[watch] build started");
18+
});
19+
build.onEnd((result) => {
20+
result.errors.forEach(({ text, location }) => {
21+
console.error(`✘ [ERROR] ${text}`);
22+
if (location) {
23+
console.error(
24+
` ${location.file}:${location.line}:${location.column}:`,
25+
);
26+
}
27+
});
28+
console.log("[watch] build finished");
29+
});
30+
},
31+
};
32+
33+
/**
34+
* @type {import('esbuild').Plugin}
35+
*/
36+
const copyWorkerPlugin = {
37+
name: "copy-worker",
38+
setup(build) {
39+
build.onEnd(() => {
40+
const srcDir = path.join(__dirname, "src/worker");
41+
const destDir = path.join(__dirname, "dist/worker");
42+
43+
if (fs.existsSync(srcDir)) {
44+
fs.mkdirSync(destDir, { recursive: true });
45+
fs.readdirSync(srcDir).forEach((file) => {
46+
fs.copyFileSync(path.join(srcDir, file), path.join(destDir, file));
47+
});
48+
console.log("[copy] worker files copied");
49+
}
50+
});
51+
},
52+
};
53+
54+
/**
55+
* @type {import('esbuild').Plugin}
56+
*/
57+
const browserAliasPlugin = {
58+
name: "browser-alias",
59+
setup(build) {
60+
// Replace ModuleResolver imports with BrowserModuleResolver for browser build
61+
build.onResolve({ filter: /\.\/ModuleResolver$/ }, (args) => {
62+
return {
63+
path: path.join(args.resolveDir, "BrowserModuleResolver.ts"),
64+
};
65+
});
66+
},
67+
};
68+
69+
// Node extension configuration
70+
const nodeConfig = {
71+
entryPoints: ["src/extension.ts"],
72+
bundle: true,
73+
format: "cjs",
74+
minify: production,
75+
sourcemap: !production,
76+
sourcesContent: false,
77+
platform: "node",
78+
outfile: "dist/extension.js",
79+
external: ["vscode", "prettier"],
80+
define: {
81+
"process.env.EXTENSION_NAME": JSON.stringify(
82+
`${extensionPackage.publisher}.${extensionPackage.name}`,
83+
),
84+
"process.env.EXTENSION_VERSION": JSON.stringify(extensionPackage.version),
85+
},
86+
logLevel: "silent",
87+
plugins: [copyWorkerPlugin, esbuildProblemMatcherPlugin],
88+
};
89+
90+
/**
91+
* @type {import('esbuild').Plugin}
92+
*/
93+
const browserShimsPlugin = {
94+
name: "browser-shims",
95+
setup(build) {
96+
// Provide empty shims for Node.js built-ins not available in browser
97+
build.onResolve({ filter: /^os$/ }, () => {
98+
return { path: "os", namespace: "browser-shim" };
99+
});
100+
build.onLoad({ filter: /.*/, namespace: "browser-shim" }, (args) => {
101+
if (args.path === "os") {
102+
return {
103+
contents: `export function homedir() { return ""; }`,
104+
loader: "js",
105+
};
106+
}
107+
});
108+
},
109+
};
110+
111+
// Browser/web extension configuration
112+
const browserConfig = {
113+
entryPoints: ["src/extension.ts"],
114+
bundle: true,
115+
format: "cjs",
116+
minify: production,
117+
sourcemap: !production,
118+
sourcesContent: false,
119+
platform: "browser",
120+
outfile: "dist/web-extension.js",
121+
external: ["vscode"],
122+
define: {
123+
"process.env.EXTENSION_NAME": JSON.stringify(
124+
`${extensionPackage.publisher}.${extensionPackage.name}`,
125+
),
126+
"process.env.EXTENSION_VERSION": JSON.stringify(extensionPackage.version),
127+
"process.env.BROWSER_ENV": JSON.stringify("true"),
128+
global: "globalThis",
129+
},
130+
alias: {
131+
path: "path-browserify",
132+
},
133+
banner: {
134+
js: `var process = {env: {EXTENSION_NAME: "${extensionPackage.publisher}.${extensionPackage.name}", EXTENSION_VERSION: "${extensionPackage.version}", BROWSER_ENV: "true"}, platform: "browser", cwd: function() { return "/"; }, nextTick: function(cb) { setTimeout(cb, 0); }};`,
135+
},
136+
logLevel: "silent",
137+
plugins: [
138+
browserAliasPlugin,
139+
browserShimsPlugin,
140+
esbuildProblemMatcherPlugin,
141+
],
142+
};
143+
144+
// Web test bundle configuration
145+
const webTestConfig = {
146+
entryPoints: ["src/test/web/suite/index.ts"],
147+
bundle: true,
148+
format: "cjs",
149+
minify: false,
150+
sourcemap: true,
151+
sourcesContent: false,
152+
platform: "browser",
153+
outfile: "dist/web/test/suite/index.js",
154+
external: ["vscode"],
155+
define: {
156+
"process.env.BROWSER_ENV": JSON.stringify("true"),
157+
"process.platform": JSON.stringify("browser"),
158+
},
159+
alias: {
160+
path: "path-browserify",
161+
},
162+
logLevel: "silent",
163+
plugins: [esbuildProblemMatcherPlugin],
164+
};
165+
166+
async function main() {
167+
const nodeCtx = await esbuild.context(nodeConfig);
168+
const browserCtx = await esbuild.context(browserConfig);
169+
const webTestCtx = await esbuild.context(webTestConfig);
170+
171+
if (watch) {
172+
await Promise.all([
173+
nodeCtx.watch(),
174+
browserCtx.watch(),
175+
webTestCtx.watch(),
176+
]);
177+
} else {
178+
await Promise.all([
179+
nodeCtx.rebuild(),
180+
browserCtx.rebuild(),
181+
webTestCtx.rebuild(),
182+
]);
183+
await Promise.all([
184+
nodeCtx.dispose(),
185+
browserCtx.dispose(),
186+
webTestCtx.dispose(),
187+
]);
188+
}
189+
}
190+
191+
main().catch((e) => {
192+
console.error(e);
193+
process.exit(1);
194+
});

package.json

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -72,15 +72,16 @@
7272
"clean": "node ./scripts/clean.js",
7373
"lint": "eslint -c .eslintrc.js --ext .ts .",
7474
"pretest": "pnpm test-compile && node scripts/install-fixtures.js",
75-
"prettier": "prettier --write '**/*.{ts,json,md,hbs,yml,js}'",
76-
"test-compile": "pnpm clean && tsc -p ./ && pnpm webpack && cp -r ./src/worker ./out",
75+
"prettier": "prettier --write .",
76+
"compile": "pnpm check-types && node esbuild.js",
77+
"check-types": "tsc --noEmit",
78+
"watch": "pnpm run --parallel watch:esbuild watch:tsc",
79+
"watch:esbuild": "node esbuild.js --watch",
80+
"watch:tsc": "tsc --noEmit --watch --project tsconfig.json",
81+
"test-compile": "pnpm clean && tsc -p ./ && pnpm compile && cp -r ./src/worker ./out",
7782
"test": "node ./out/test/runTests.js",
7883
"version": "node ./scripts/version.js && git add CHANGELOG.md",
7984
"postversion": "git push origin main --tags",
80-
"vscode:prepublish": "webpack --mode production",
81-
"watch": "tsc --watch -p ./",
82-
"webpack-dev": "webpack --mode development --watch",
83-
"webpack": "webpack --mode development",
8485
"chrome": "pnpm webpack && vscode-test-web --browserType=chromium --extensionDevelopmentPath=. .",
8586
"test:web": "pnpm test-compile && node ./out/test/web/runTests.js",
8687
"prepare": "husky"
@@ -96,8 +97,6 @@
9697
"devDependencies": {
9798
"@playwright/test": "^1.57.0",
9899
"@types/fs-extra": "^11.0.4",
99-
"@types/glob": "^7.2.0",
100-
"@types/minimatch": "^5.1.2",
101100
"@types/mocha": "^10.0.7",
102101
"@types/node": "^22",
103102
"@types/resolve": "^1.20.3",
@@ -110,24 +109,19 @@
110109
"@vscode/test-electron": "^2.5.2",
111110
"@vscode/test-web": "^0.0.76",
112111
"@vscode/vsce": "^3.7.1",
113-
"copy-webpack-plugin": "^12.0.2",
112+
"esbuild": "^0.27.0",
114113
"eslint": "^8.31.0",
115114
"eslint-config-prettier": "^9.1.0",
116115
"fs-extra": "^11.2.0",
117-
"glob": "^7.2.0",
118116
"husky": "^9.1.4",
119117
"lint-staged": "^15.2.9",
120118
"mocha": "^10.7.3",
121119
"path-browserify": "^1.0.1",
122120
"process": "^0.11.10",
123121
"sinon": "^17.0.1",
124122
"tmp": "^0.2.3",
125-
"ts-loader": "^9.5.1",
126123
"typescript": "^4.6.3",
127-
"util": "^0.12.4",
128-
"vscode-nls-dev": "^4.0.4",
129-
"webpack": "^5.93.0",
130-
"webpack-cli": "^5.0.1"
124+
"util": "^0.12.4"
131125
},
132126
"dependencies": {
133127
"find-up": "5.0.0",

0 commit comments

Comments
 (0)