Skip to content

Commit

Permalink
support esbuild (#461)
Browse files Browse the repository at this point in the history
* support esbuild

* update

* add test

* support esbuild in web
  • Loading branch information
aeschli authored May 2, 2024
1 parent eb4951c commit cfd82f9
Show file tree
Hide file tree
Showing 33 changed files with 2,592 additions and 1,748 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ Options:
--extensionId # Id of the extension
--extensionDescription # Description of the extension
--pkgManager # 'npm', 'yarn' or 'pnpm'
--webpack # Bundle the extension with webpack
--bundle # 'webpack', 'esbuild'. Bundle the extension with webpack or esbuild
--gitInit # Initialize a git repo
Example usages:
Expand Down
5 changes: 4 additions & 1 deletion generators/app/dependencyVersions/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@
"webpack-cli": "^5.1.4",
"webpack-dev-server": "^5.0.4",
"assert": "^2.1.0",
"process": "^0.11.10"
"process": "^0.11.10",
"npm-run-all": "^4.1.5",
"esbuild": "^0.20.2",
"@esbuild-plugins/node-globals-polyfill": "^0.2.3"
}
}
19 changes: 11 additions & 8 deletions generators/app/generate-command-ts.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,21 +22,24 @@ export default {
await prompts.askForExtensionDescription(generator, extensionConfig);

await prompts.askForGit(generator, extensionConfig);
await prompts.askForWebpack(generator, extensionConfig);
await prompts.askForBundler(generator, extensionConfig);
await prompts.askForPackageManager(generator, extensionConfig);
},
/**
* @param {Generator} generator
* @param {Object} extensionConfig
*/
writing: (generator, extensionConfig) => {
if (extensionConfig.webpack) {
generator.fs.copy(generator.templatePath('vscode-webpack/vscode'), generator.destinationPath('.vscode'));
generator.fs.copyTpl(generator.templatePath('vscode-webpack/package.json'), generator.destinationPath('package.json'), extensionConfig);
generator.fs.copyTpl(generator.templatePath('vscode-webpack/tsconfig.json'), generator.destinationPath('tsconfig.json'), extensionConfig);
generator.fs.copyTpl(generator.templatePath('vscode-webpack/.vscodeignore'), generator.destinationPath('.vscodeignore'), extensionConfig);
generator.fs.copyTpl(generator.templatePath('vscode-webpack/webpack.config.js'), generator.destinationPath('webpack.config.js'), extensionConfig);
generator.fs.copyTpl(generator.templatePath('vscode-webpack/vsc-extension-quickstart.md'), generator.destinationPath('vsc-extension-quickstart.md'), extensionConfig);
const bundler = extensionConfig.bundler;
if (bundler && (bundler === 'webpack' || bundler === 'esbuild')) {
const bundlerPath = bundler === 'esbuild' ? 'vscode-esbuild' : 'vscode-webpack';
const bundlerFile = bundler === 'esbuild' ? 'esbuild.js' : 'webpack.config.js';
generator.fs.copy(generator.templatePath(bundlerPath, 'vscode'), generator.destinationPath('.vscode'));
generator.fs.copyTpl(generator.templatePath(bundlerPath, 'package.json'), generator.destinationPath('package.json'), extensionConfig);
generator.fs.copyTpl(generator.templatePath(bundlerPath, 'tsconfig.json'), generator.destinationPath('tsconfig.json'), extensionConfig);
generator.fs.copyTpl(generator.templatePath(bundlerPath, '.vscodeignore'), generator.destinationPath('.vscodeignore'), extensionConfig);
generator.fs.copyTpl(generator.templatePath(bundlerPath, bundlerFile), generator.destinationPath(bundlerFile), extensionConfig);
generator.fs.copyTpl(generator.templatePath(bundlerPath, 'vsc-extension-quickstart.md'), generator.destinationPath('vsc-extension-quickstart.md'), extensionConfig);
} else {
generator.fs.copy(generator.templatePath('vscode'), generator.destinationPath('.vscode'));
generator.fs.copyTpl(generator.templatePath('package.json'), generator.destinationPath('package.json'), extensionConfig);
Expand Down
22 changes: 18 additions & 4 deletions generators/app/generate-command-web.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,20 @@ export default {
await prompts.askForExtensionDescription(generator, extensionConfig);

await prompts.askForGit(generator, extensionConfig);
await prompts.askForBundler(generator, extensionConfig, false, 'webpack');
await prompts.askForPackageManager(generator, extensionConfig);
},
/**
* @param {Generator} generator
* @param {Object} extensionConfig
*/
writing: (generator, extensionConfig) => {
generator.fs.copy(generator.templatePath('vscode'), generator.destinationPath('.vscode'));
generator.fs.copy(generator.templatePath('src/web/test'), generator.destinationPath('src/web/test'));
const bundler = extensionConfig.bundler;
if (bundler === 'esbuild') {
generator.fs.copy(generator.templatePath('vscode-esbuild'), generator.destinationPath('.vscode'));
} else {
generator.fs.copy(generator.templatePath('vscode-webpack'), generator.destinationPath('.vscode'));
}

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

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

generator.fs.copyTpl(generator.templatePath('webpack.config.js'), generator.destinationPath('webpack.config.js'), extensionConfig);
generator.fs.copyTpl(generator.templatePath('package.json'), generator.destinationPath('package.json'), extensionConfig);
generator.fs.copy(generator.templatePath('src/web/test/suite/extension.test.ts'), generator.destinationPath('src/web/test/suite/extension.test.ts'));

if (bundler === 'esbuild') {
generator.fs.copyTpl(generator.templatePath('esbuild.js'), generator.destinationPath('esbuild.js'), extensionConfig);
generator.fs.copyTpl(generator.templatePath('esbuild-package.json'), generator.destinationPath('package.json'), extensionConfig);
generator.fs.copy(generator.templatePath('src/web/test/suite/esbuild-mochaTestRunner.ts'), generator.destinationPath('src/web/test/suite/mochaTestRunner.ts'));
} else {
generator.fs.copyTpl(generator.templatePath('webpack.config.js'), generator.destinationPath('webpack.config.js'), extensionConfig);
generator.fs.copyTpl(generator.templatePath('webpack-package.json'), generator.destinationPath('package.json'), extensionConfig);
generator.fs.copy(generator.templatePath('src/web/test/suite/webpack-mochaTestRunner.ts'), generator.destinationPath('src/web/test/suite/index.ts'));
}

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

Expand Down
2 changes: 1 addition & 1 deletion generators/app/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export default class extends Generator {
this.option('extensionDescription', { type: String, description: 'Description of the extension' });

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

this.option('snippetFolder', { type: String, description: `Snippet folder location` });
Expand Down
49 changes: 33 additions & 16 deletions generators/app/prompts.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export function askForExtensionDisplayName(generator, extensionConfig) {
* @param {Object} extensionConfig
*/
export function askForExtensionId(generator, extensionConfig) {
let extensionName = generator.options['extensionId'];
const extensionName = generator.options['extensionId'];
if (extensionName) {
extensionConfig.name = extensionName;
return Promise.resolve();
Expand Down Expand Up @@ -72,7 +72,7 @@ export function askForExtensionId(generator, extensionConfig) {
* @param {Object} extensionConfig
*/
export function askForExtensionDescription(generator, extensionConfig) {
let extensionDescription = generator.options['extensionDescription'];
const extensionDescription = generator.options['extensionDescription'];
if (extensionDescription) {
extensionConfig.description = extensionDescription;
return Promise.resolve();
Expand All @@ -97,7 +97,7 @@ export function askForExtensionDescription(generator, extensionConfig) {
* @param {Object} extensionConfig
*/
export function askForGit(generator, extensionConfig) {
let gitInit = generator.options['gitInit'];
const gitInit = generator.options['gitInit'];
if (typeof gitInit === 'boolean') {
extensionConfig.gitInit = Boolean(gitInit);
return Promise.resolve();
Expand All @@ -122,7 +122,7 @@ export function askForGit(generator, extensionConfig) {
* @param {Object} extensionConfig
*/
export function askForPackageManager(generator, extensionConfig) {
let pkgManager = generator.options['pkgManager'];
const pkgManager = generator.options['pkgManager'];
if (pkgManager === 'npm' || pkgManager === 'yarn' || pkgManager === 'pnpm') {
extensionConfig.pkgManager = pkgManager;
return Promise.resolve();
Expand Down Expand Up @@ -161,24 +161,41 @@ export function askForPackageManager(generator, extensionConfig) {
* @param {Generator} generator
* @param {Object} extensionConfig
*/
export function askForWebpack(generator, extensionConfig) {
let webpack = generator.options['webpack'];
if (typeof webpack === 'boolean') {
extensionConfig.webpack = Boolean(webpack);
export function askForBundler(generator, extensionConfig, allowNone = true, defaultBundler = 'none') {
const bundler = generator.options['bundler'];
if (bundler === 'webpack' || bundler === 'esbuild') {
extensionConfig.bundler = bundler;
return Promise.resolve();
}
const webpack = generator.options['webpack']; // backwards compatibility
if (typeof webpack === 'boolean' && webpack) {
extensionConfig.bundler = 'webpack';
return Promise.resolve();
}

if (generator.options['quick']) {
extensionConfig.webpack = false;
extensionConfig.bundler = defaultBundler;
return Promise.resolve();
}

const choices = allowNone ? [{ name: 'none', value: 'none' }] : [];

return generator.prompt({
type: 'confirm',
name: 'webpack',
message: 'Bundle the source code with webpack?',
default: false
}).then(gitAnswer => {
extensionConfig.webpack = gitAnswer.webpack;
type: 'list',
default: defaultBundler,
name: 'bundler',
message: 'Which bundler to use?',
choices: [
...choices,
{
name: 'webpack',
value: 'webpack'
},
{
name: 'esbuild',
value: 'esbuild'
}
]
}).then(bundlerAnswer => {
extensionConfig.bundler = bundlerAnswer.bundler;
});
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
.vscode/**
.vscode-test/**
out/**
node_modules/**
src/**
.gitignore
.yarnrc
esbuild.js
vsc-extension-quickstart.md
**/.eslintrc.json
**/*.map
**/*.ts
**/.vscode-test.*
56 changes: 56 additions & 0 deletions generators/app/templates/ext-command-ts/vscode-esbuild/esbuild.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
const esbuild = require("esbuild");

const production = process.argv.includes('--production');
const watch = process.argv.includes('--watch');

/**
* @type {import('esbuild').Plugin}
*/
const esbuildProblemMatcherPlugin = {
name: 'esbuild-problem-matcher',

setup(build) {
build.onStart(() => {
console.log('[watch] build started');
});
build.onEnd((result) => {
result.errors.forEach(({ text, location }) => {
console.error(`✘ [ERROR] ${text}`);
console.error(` ${location.file}:${location.line}:${location.column}:`);
});
console.log('[watch] build finished');
});
},
};

async function main() {
const ctx = await esbuild.context({
entryPoints: [
'src/extension.ts'
],
bundle: true,
format: 'cjs',
minify: production,
sourcemap: !production,
sourcesContent: false,
platform: 'node',
outfile: 'dist/extension.js',
external: ['vscode'],
logLevel: 'silent',
plugins: [
/* add to the end of plugins array */
esbuildProblemMatcherPlugin,
],
});
if (watch) {
await ctx.watch();
} else {
await ctx.rebuild();
await ctx.dispose();
}
}

main().catch(e => {
console.error(e);
process.exit(1);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
{
"name": <%- JSON.stringify(name) %>,
"displayName": <%- JSON.stringify(displayName) %>,
"description": <%- JSON.stringify(description) %>,
"version": "0.0.1",
"engines": {
"vscode": <%- JSON.stringify(vsCodeEngine) %>
},
"categories": [
"Other"
],
"activationEvents": [],
"main": "./dist/extension.js",<% if (insiders) { %>
"enabledApiProposals": [],<% } %>
"contributes": {
"commands": [
{
"command": <%- JSON.stringify(`${name}.helloWorld`) %>,
"title": "Hello World"
}
]
},
"scripts": {
"vscode:prepublish": "<%= pkgManager %> run package",
"compile": "<%= pkgManager %> run check-types && <%= pkgManager %> run lint && node esbuild.js",
"watch": "npm-run-all -p watch:*",
"watch:esbuild": "node esbuild.js --watch",
"watch:tsc": "tsc --noEmit --watch --project tsconfig.json",
"package": "<%= pkgManager %> run check-types && <%= pkgManager %> run lint && node esbuild.js --production",
"compile-tests": "tsc -p . --outDir out",
"watch-tests": "tsc -p . -w --outDir out",
"pretest": "<%= pkgManager %> run compile-tests && <%= pkgManager %> run compile && <%= pkgManager %> run lint",
"check-types": "tsc --noEmit",
"lint": "eslint src --ext ts",
"test": "vscode-test"<% if (insiders) { %>,
"update-proposed-api": "vscode-dts dev"<% } %>
},
"devDependencies": {
<%- dep("@types/vscode") %>,
<%- dep("@types/mocha") %>,
<%- dep("@types/node") %>,
<%- dep("@typescript-eslint/eslint-plugin") %>,
<%- dep("@typescript-eslint/parser") %>,
<%- dep("eslint") %>,
<%- dep("esbuild") %>,
<%- dep("npm-run-all") %>,
<%- dep("typescript") %>,
<%- dep("@vscode/test-cli") %>,
<%- dep("@vscode/test-electron") %><% if (insiders) { %>,
<%- dep("vscode-dts") %><% } %>
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"compilerOptions": {
"module": "Node16",
"target": "ES2022",
"lib": [
"ES2022"
],
"sourceMap": true,
"rootDir": "src",
"strict": true /* enable all strict type-checking options */
/* Additional Checks */
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
// "noUnusedParameters": true, /* Report errors on unused parameters. */
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Welcome to your VS Code Extension

## What's in the folder

* This folder contains all of the files necessary for your extension.
* `package.json` - this is the manifest file in which you declare your extension and command.
* The sample plugin registers a command and defines its title and command name. With this information VS Code can show the command in the command palette. It doesn’t yet need to load the plugin.
* `src/extension.ts` - this is the main file where you will provide the implementation of your command.
* The file exports one function, `activate`, which is called the very first time your extension is activated (in this case by executing the command). Inside the `activate` function we call `registerCommand`.
* We pass the function containing the implementation of the command as the second parameter to `registerCommand`.

## Setup

* install the recommended extensions (amodio.tsl-problem-matcher, ms-vscode.extension-test-runner, and dbaeumer.vscode-eslint)


## Get up and running straight away

* Press `F5` to open a new window with your extension loaded.
* Run your command from the command palette by pressing (`Ctrl+Shift+P` or `Cmd+Shift+P` on Mac) and typing `Hello World`.
* Set breakpoints in your code inside `src/extension.ts` to debug your extension.
* Find output from your extension in the debug console.

## Make changes

* You can relaunch the extension from the debug toolbar after changing code in `src/extension.ts`.
* You can also reload (`Ctrl+R` or `Cmd+R` on Mac) the VS Code window with your extension to load your changes.


## Explore the API

* You can open the full set of our API when you open the file `node_modules/@types/vscode/index.d.ts`.

## Run tests

* Install the [Extension Test Runner](https://marketplace.visualstudio.com/items?itemName=ms-vscode.extension-test-runner)
* Run the "watch" task via the **Tasks: Run Task** command. Make sure this is running, or tests might not be discovered.
* Open the Testing view from the activity bar and click the Run Test" button, or use the hotkey `Ctrl/Cmd + ; A`
* See the output of the test result in the Test Results view.
* Make changes to `src/test/extension.test.ts` or create new test files inside the `test` folder.
* The provided test runner will only consider files matching the name pattern `**.test.ts`.
* You can create folders inside the `test` folder to structure your tests any way you want.

## Go further

* Reduce the extension size and improve the startup time by [bundling your extension](https://code.visualstudio.com/api/working-with-extensions/bundling-extension).
* [Publish your extension](https://code.visualstudio.com/api/working-with-extensions/publishing-extension) on the VS Code extension marketplace.
* Automate builds by setting up [Continuous Integration](https://code.visualstudio.com/api/working-with-extensions/continuous-integration).
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
// See http://go.microsoft.com/fwlink/?LinkId=827846
// for the documentation about the extensions.json format
"recommendations": ["dbaeumer.vscode-eslint", "connor4312.esbuild-problem-matchers", "ms-vscode.extension-test-runner"]
}
Loading

0 comments on commit cfd82f9

Please sign in to comment.