Skip to content

Commit cfd82f9

Browse files
authored
support esbuild (microsoft#461)
* support esbuild * update * add test * support esbuild in web
1 parent eb4951c commit cfd82f9

33 files changed

+2592
-1748
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ Options:
5757
--extensionId # Id of the extension
5858
--extensionDescription # Description of the extension
5959
--pkgManager # 'npm', 'yarn' or 'pnpm'
60-
--webpack # Bundle the extension with webpack
60+
--bundle # 'webpack', 'esbuild'. Bundle the extension with webpack or esbuild
6161
--gitInit # Initialize a git repo
6262
6363
Example usages:

generators/app/dependencyVersions/package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@
3030
"webpack-cli": "^5.1.4",
3131
"webpack-dev-server": "^5.0.4",
3232
"assert": "^2.1.0",
33-
"process": "^0.11.10"
33+
"process": "^0.11.10",
34+
"npm-run-all": "^4.1.5",
35+
"esbuild": "^0.20.2",
36+
"@esbuild-plugins/node-globals-polyfill": "^0.2.3"
3437
}
3538
}

generators/app/generate-command-ts.js

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,21 +22,24 @@ export default {
2222
await prompts.askForExtensionDescription(generator, extensionConfig);
2323

2424
await prompts.askForGit(generator, extensionConfig);
25-
await prompts.askForWebpack(generator, extensionConfig);
25+
await prompts.askForBundler(generator, extensionConfig);
2626
await prompts.askForPackageManager(generator, extensionConfig);
2727
},
2828
/**
2929
* @param {Generator} generator
3030
* @param {Object} extensionConfig
3131
*/
3232
writing: (generator, extensionConfig) => {
33-
if (extensionConfig.webpack) {
34-
generator.fs.copy(generator.templatePath('vscode-webpack/vscode'), generator.destinationPath('.vscode'));
35-
generator.fs.copyTpl(generator.templatePath('vscode-webpack/package.json'), generator.destinationPath('package.json'), extensionConfig);
36-
generator.fs.copyTpl(generator.templatePath('vscode-webpack/tsconfig.json'), generator.destinationPath('tsconfig.json'), extensionConfig);
37-
generator.fs.copyTpl(generator.templatePath('vscode-webpack/.vscodeignore'), generator.destinationPath('.vscodeignore'), extensionConfig);
38-
generator.fs.copyTpl(generator.templatePath('vscode-webpack/webpack.config.js'), generator.destinationPath('webpack.config.js'), extensionConfig);
39-
generator.fs.copyTpl(generator.templatePath('vscode-webpack/vsc-extension-quickstart.md'), generator.destinationPath('vsc-extension-quickstart.md'), extensionConfig);
33+
const bundler = extensionConfig.bundler;
34+
if (bundler && (bundler === 'webpack' || bundler === 'esbuild')) {
35+
const bundlerPath = bundler === 'esbuild' ? 'vscode-esbuild' : 'vscode-webpack';
36+
const bundlerFile = bundler === 'esbuild' ? 'esbuild.js' : 'webpack.config.js';
37+
generator.fs.copy(generator.templatePath(bundlerPath, 'vscode'), generator.destinationPath('.vscode'));
38+
generator.fs.copyTpl(generator.templatePath(bundlerPath, 'package.json'), generator.destinationPath('package.json'), extensionConfig);
39+
generator.fs.copyTpl(generator.templatePath(bundlerPath, 'tsconfig.json'), generator.destinationPath('tsconfig.json'), extensionConfig);
40+
generator.fs.copyTpl(generator.templatePath(bundlerPath, '.vscodeignore'), generator.destinationPath('.vscodeignore'), extensionConfig);
41+
generator.fs.copyTpl(generator.templatePath(bundlerPath, bundlerFile), generator.destinationPath(bundlerFile), extensionConfig);
42+
generator.fs.copyTpl(generator.templatePath(bundlerPath, 'vsc-extension-quickstart.md'), generator.destinationPath('vsc-extension-quickstart.md'), extensionConfig);
4043
} else {
4144
generator.fs.copy(generator.templatePath('vscode'), generator.destinationPath('.vscode'));
4245
generator.fs.copyTpl(generator.templatePath('package.json'), generator.destinationPath('package.json'), extensionConfig);

generators/app/generate-command-web.js

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,20 @@ export default {
2121
await prompts.askForExtensionDescription(generator, extensionConfig);
2222

2323
await prompts.askForGit(generator, extensionConfig);
24+
await prompts.askForBundler(generator, extensionConfig, false, 'webpack');
2425
await prompts.askForPackageManager(generator, extensionConfig);
2526
},
2627
/**
2728
* @param {Generator} generator
2829
* @param {Object} extensionConfig
2930
*/
3031
writing: (generator, extensionConfig) => {
31-
generator.fs.copy(generator.templatePath('vscode'), generator.destinationPath('.vscode'));
32-
generator.fs.copy(generator.templatePath('src/web/test'), generator.destinationPath('src/web/test'));
32+
const bundler = extensionConfig.bundler;
33+
if (bundler === 'esbuild') {
34+
generator.fs.copy(generator.templatePath('vscode-esbuild'), generator.destinationPath('.vscode'));
35+
} else {
36+
generator.fs.copy(generator.templatePath('vscode-webpack'), generator.destinationPath('.vscode'));
37+
}
3338

3439
generator.fs.copy(generator.templatePath('.vscodeignore'), generator.destinationPath('.vscodeignore'));
3540
if (extensionConfig.gitInit) {
@@ -42,8 +47,17 @@ export default {
4247

4348
generator.fs.copyTpl(generator.templatePath('src/web/extension.ts'), generator.destinationPath('src/web/extension.ts'), extensionConfig);
4449

45-
generator.fs.copyTpl(generator.templatePath('webpack.config.js'), generator.destinationPath('webpack.config.js'), extensionConfig);
46-
generator.fs.copyTpl(generator.templatePath('package.json'), generator.destinationPath('package.json'), extensionConfig);
50+
generator.fs.copy(generator.templatePath('src/web/test/suite/extension.test.ts'), generator.destinationPath('src/web/test/suite/extension.test.ts'));
51+
52+
if (bundler === 'esbuild') {
53+
generator.fs.copyTpl(generator.templatePath('esbuild.js'), generator.destinationPath('esbuild.js'), extensionConfig);
54+
generator.fs.copyTpl(generator.templatePath('esbuild-package.json'), generator.destinationPath('package.json'), extensionConfig);
55+
generator.fs.copy(generator.templatePath('src/web/test/suite/esbuild-mochaTestRunner.ts'), generator.destinationPath('src/web/test/suite/mochaTestRunner.ts'));
56+
} else {
57+
generator.fs.copyTpl(generator.templatePath('webpack.config.js'), generator.destinationPath('webpack.config.js'), extensionConfig);
58+
generator.fs.copyTpl(generator.templatePath('webpack-package.json'), generator.destinationPath('package.json'), extensionConfig);
59+
generator.fs.copy(generator.templatePath('src/web/test/suite/webpack-mochaTestRunner.ts'), generator.destinationPath('src/web/test/suite/index.ts'));
60+
}
4761

4862
generator.fs.copy(generator.templatePath('.eslintrc.json'), generator.destinationPath('.eslintrc.json'));
4963

generators/app/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ export default class extends Generator {
4444
this.option('extensionDescription', { type: String, description: 'Description of the extension' });
4545

4646
this.option('pkgManager', { type: String, description: `'npm', 'yarn' or 'pnpm'` });
47-
this.option('webpack', { type: Boolean, description: `Bundle the extension with webpack` });
47+
this.option('bundler', { type: String, default: 'none', description: `Bundle the extension: 'webpack', 'esbuild', 'none` });
4848
this.option('gitInit', { type: Boolean, description: `Initialize a git repo` });
4949

5050
this.option('snippetFolder', { type: String, description: `Snippet folder location` });

generators/app/prompts.js

Lines changed: 33 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ export function askForExtensionDisplayName(generator, extensionConfig) {
4141
* @param {Object} extensionConfig
4242
*/
4343
export function askForExtensionId(generator, extensionConfig) {
44-
let extensionName = generator.options['extensionId'];
44+
const extensionName = generator.options['extensionId'];
4545
if (extensionName) {
4646
extensionConfig.name = extensionName;
4747
return Promise.resolve();
@@ -72,7 +72,7 @@ export function askForExtensionId(generator, extensionConfig) {
7272
* @param {Object} extensionConfig
7373
*/
7474
export function askForExtensionDescription(generator, extensionConfig) {
75-
let extensionDescription = generator.options['extensionDescription'];
75+
const extensionDescription = generator.options['extensionDescription'];
7676
if (extensionDescription) {
7777
extensionConfig.description = extensionDescription;
7878
return Promise.resolve();
@@ -97,7 +97,7 @@ export function askForExtensionDescription(generator, extensionConfig) {
9797
* @param {Object} extensionConfig
9898
*/
9999
export function askForGit(generator, extensionConfig) {
100-
let gitInit = generator.options['gitInit'];
100+
const gitInit = generator.options['gitInit'];
101101
if (typeof gitInit === 'boolean') {
102102
extensionConfig.gitInit = Boolean(gitInit);
103103
return Promise.resolve();
@@ -122,7 +122,7 @@ export function askForGit(generator, extensionConfig) {
122122
* @param {Object} extensionConfig
123123
*/
124124
export function askForPackageManager(generator, extensionConfig) {
125-
let pkgManager = generator.options['pkgManager'];
125+
const pkgManager = generator.options['pkgManager'];
126126
if (pkgManager === 'npm' || pkgManager === 'yarn' || pkgManager === 'pnpm') {
127127
extensionConfig.pkgManager = pkgManager;
128128
return Promise.resolve();
@@ -161,24 +161,41 @@ export function askForPackageManager(generator, extensionConfig) {
161161
* @param {Generator} generator
162162
* @param {Object} extensionConfig
163163
*/
164-
export function askForWebpack(generator, extensionConfig) {
165-
let webpack = generator.options['webpack'];
166-
if (typeof webpack === 'boolean') {
167-
extensionConfig.webpack = Boolean(webpack);
164+
export function askForBundler(generator, extensionConfig, allowNone = true, defaultBundler = 'none') {
165+
const bundler = generator.options['bundler'];
166+
if (bundler === 'webpack' || bundler === 'esbuild') {
167+
extensionConfig.bundler = bundler;
168+
return Promise.resolve();
169+
}
170+
const webpack = generator.options['webpack']; // backwards compatibility
171+
if (typeof webpack === 'boolean' && webpack) {
172+
extensionConfig.bundler = 'webpack';
168173
return Promise.resolve();
169174
}
170-
171175
if (generator.options['quick']) {
172-
extensionConfig.webpack = false;
176+
extensionConfig.bundler = defaultBundler;
173177
return Promise.resolve();
174178
}
175179

180+
const choices = allowNone ? [{ name: 'none', value: 'none' }] : [];
181+
176182
return generator.prompt({
177-
type: 'confirm',
178-
name: 'webpack',
179-
message: 'Bundle the source code with webpack?',
180-
default: false
181-
}).then(gitAnswer => {
182-
extensionConfig.webpack = gitAnswer.webpack;
183+
type: 'list',
184+
default: defaultBundler,
185+
name: 'bundler',
186+
message: 'Which bundler to use?',
187+
choices: [
188+
...choices,
189+
{
190+
name: 'webpack',
191+
value: 'webpack'
192+
},
193+
{
194+
name: 'esbuild',
195+
value: 'esbuild'
196+
}
197+
]
198+
}).then(bundlerAnswer => {
199+
extensionConfig.bundler = bundlerAnswer.bundler;
183200
});
184201
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
.vscode/**
2+
.vscode-test/**
3+
out/**
4+
node_modules/**
5+
src/**
6+
.gitignore
7+
.yarnrc
8+
esbuild.js
9+
vsc-extension-quickstart.md
10+
**/.eslintrc.json
11+
**/*.map
12+
**/*.ts
13+
**/.vscode-test.*
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
const esbuild = require("esbuild");
2+
3+
const production = process.argv.includes('--production');
4+
const watch = process.argv.includes('--watch');
5+
6+
/**
7+
* @type {import('esbuild').Plugin}
8+
*/
9+
const esbuildProblemMatcherPlugin = {
10+
name: 'esbuild-problem-matcher',
11+
12+
setup(build) {
13+
build.onStart(() => {
14+
console.log('[watch] build started');
15+
});
16+
build.onEnd((result) => {
17+
result.errors.forEach(({ text, location }) => {
18+
console.error(`✘ [ERROR] ${text}`);
19+
console.error(` ${location.file}:${location.line}:${location.column}:`);
20+
});
21+
console.log('[watch] build finished');
22+
});
23+
},
24+
};
25+
26+
async function main() {
27+
const ctx = await esbuild.context({
28+
entryPoints: [
29+
'src/extension.ts'
30+
],
31+
bundle: true,
32+
format: 'cjs',
33+
minify: production,
34+
sourcemap: !production,
35+
sourcesContent: false,
36+
platform: 'node',
37+
outfile: 'dist/extension.js',
38+
external: ['vscode'],
39+
logLevel: 'silent',
40+
plugins: [
41+
/* add to the end of plugins array */
42+
esbuildProblemMatcherPlugin,
43+
],
44+
});
45+
if (watch) {
46+
await ctx.watch();
47+
} else {
48+
await ctx.rebuild();
49+
await ctx.dispose();
50+
}
51+
}
52+
53+
main().catch(e => {
54+
console.error(e);
55+
process.exit(1);
56+
});
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
{
2+
"name": <%- JSON.stringify(name) %>,
3+
"displayName": <%- JSON.stringify(displayName) %>,
4+
"description": <%- JSON.stringify(description) %>,
5+
"version": "0.0.1",
6+
"engines": {
7+
"vscode": <%- JSON.stringify(vsCodeEngine) %>
8+
},
9+
"categories": [
10+
"Other"
11+
],
12+
"activationEvents": [],
13+
"main": "./dist/extension.js",<% if (insiders) { %>
14+
"enabledApiProposals": [],<% } %>
15+
"contributes": {
16+
"commands": [
17+
{
18+
"command": <%- JSON.stringify(`${name}.helloWorld`) %>,
19+
"title": "Hello World"
20+
}
21+
]
22+
},
23+
"scripts": {
24+
"vscode:prepublish": "<%= pkgManager %> run package",
25+
"compile": "<%= pkgManager %> run check-types && <%= pkgManager %> run lint && node esbuild.js",
26+
"watch": "npm-run-all -p watch:*",
27+
"watch:esbuild": "node esbuild.js --watch",
28+
"watch:tsc": "tsc --noEmit --watch --project tsconfig.json",
29+
"package": "<%= pkgManager %> run check-types && <%= pkgManager %> run lint && node esbuild.js --production",
30+
"compile-tests": "tsc -p . --outDir out",
31+
"watch-tests": "tsc -p . -w --outDir out",
32+
"pretest": "<%= pkgManager %> run compile-tests && <%= pkgManager %> run compile && <%= pkgManager %> run lint",
33+
"check-types": "tsc --noEmit",
34+
"lint": "eslint src --ext ts",
35+
"test": "vscode-test"<% if (insiders) { %>,
36+
"update-proposed-api": "vscode-dts dev"<% } %>
37+
},
38+
"devDependencies": {
39+
<%- dep("@types/vscode") %>,
40+
<%- dep("@types/mocha") %>,
41+
<%- dep("@types/node") %>,
42+
<%- dep("@typescript-eslint/eslint-plugin") %>,
43+
<%- dep("@typescript-eslint/parser") %>,
44+
<%- dep("eslint") %>,
45+
<%- dep("esbuild") %>,
46+
<%- dep("npm-run-all") %>,
47+
<%- dep("typescript") %>,
48+
<%- dep("@vscode/test-cli") %>,
49+
<%- dep("@vscode/test-electron") %><% if (insiders) { %>,
50+
<%- dep("vscode-dts") %><% } %>
51+
}
52+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"compilerOptions": {
3+
"module": "Node16",
4+
"target": "ES2022",
5+
"lib": [
6+
"ES2022"
7+
],
8+
"sourceMap": true,
9+
"rootDir": "src",
10+
"strict": true /* enable all strict type-checking options */
11+
/* Additional Checks */
12+
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
13+
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
14+
// "noUnusedParameters": true, /* Report errors on unused parameters. */
15+
}
16+
}

0 commit comments

Comments
 (0)