โ ๏ธ This article is the first contract article in the nuggets community. It is prohibited to reprint without authorization
Hi everyone, I’m Luo Zhu ๐, a woodworking front end ๐ง๐ปโ๏ธ living in Hangzhou. If you like my article ๐ง๐ป, you can gather spiritulist blog for me by clicking like.
Warm tips: combined with this article supporting source reading experience is better!
preface
In order to reduce cost and improve efficiency, luo zhu developed a vscode plug-in. In the first version, I used the built-in UI of vscode. Although it can be used, the user experience is not good. Since vscode’s built-in UI is not flexible enough, after some research I decided to use webview for refactoring.
Those of you who have developed vscode plug-ins may know that there are many knowledge points, difficult documents to read, and few references to develop plug-ins. This is especially true with webView plug-in development, scour the web, there are good projects, but there are no complete and good tutorials. In order to cultivate vscode development spirit, might as well with luo zhu challenge from zero to one development of a webview-based vscode plug-in.
Hello vscode
Heroes often start in the market, high-rise are from flat ground. No matter how great the software starts from Hello World, this chapter tries to use the most concise language to describe the birth of a vscode plug-in Hello World.
Initialize the project
Install Yeoman and VS Code Extension Generator:
$ npm install -g yo generator-code
Copy the code
The scaffolding will generate a project that can be developed immediately. Run the generator and fill in the following fields:
$ yo code
# _ -- -- -- -- -- _ โญ โ โ โ โ โ โ โ โ โ โ โ โ โ โ โ โ โ โ โ โ โ โ โ โ โ โ โฎ
# | | โ Welcome to the Visual โ
# | - (o) - | โ Studio Code Extension โ
# ` -- -- -- -- -- -- -- -- -- ยด โ the generator! โ
โฐโ (_ยดU '_) โฐโ First taste of time
# /___A___\ /
# | to |
# __ '. ___. '__
# ยด ` | ยฐ ยด ` Y
#? What type of extension do you want to create? New Extension (TypeScript)
#? What's the name of your extension? Juejin Posts
#? What's the identifier of your extension? juejin-posts
#? What's the description of your extension? Nuggets article management
#? Initialize a git repository? Yes
#? Bundle the source code with webpack? No
#? Which package manager to use? yarn
$ code ./juejin-posts
Copy the code
Submit record: Hello World
Code specification
The default scaffold also generates an ESLint configuration, but Editor, Prettier don’t, and ESLint doesn’t fit my conventions. The packages for front-end engineering are in youngjuning/luozhu, and the package configured for ESlint is @luozhu/eslint-config-*. Since we develop plug-ins using Typescript, we choose @orphzhu /eslint-config-typescript.
Install dependencies:
$ yarn add @luozhu/eslint-config-typescript @luozhu/prettier-config prettier -D
Copy the code
Specific configuration:
Configuration involves many files, please refer to coding-style, students who do not care about can also directly skip.
Submit for testing:
Install dependencies:
$ yarn add lint-staged yorkie -D
Copy the code
Modify the configuration:
// package.json
{
"gitHooks": {
"pre-commit": "lint-staged"
},
"lint-staged": {
"**/*.{js,jsx,ts,tsx}": ["eslint --fix"]."**/*.{md,json}": ["prettier --write"]}}Copy the code
Eslint – fis:
After modifying the configuration, you need to run fix to format all files.
$ yarn lint --fix
Copy the code
Submit records: Chore: Code Style Config
Contract submission
I use progressive scaffolding @wafzhu /create-commitlint, and executing NPX @ wafzhu /create-commitlint in a project makes the project conform to the configuration of a standardized submission. Today, students who are not familiar with Conventional Commits strongly recommend reading the article to identify Conventional Commits.
Chore: NFx@wafzhu/CREate-commitlint
debugging
Press F5 to start debugging and the Extension Development Host window appears, then press Command+Shift+P component keys to enter Hello World. As shown below, vscode pops up Hello World from Juejin Posts! The prompt.
At the same time, a terminal for watch task will appear in our development window:
The debug console in the development window will output the plug-in run log (ignore the red warning) :
The tasks performed by debugging are configured in.vscode/tasks.json:
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
{
"version": "2.0.0".// The configured version number.
"tasks": [ // Task configuration. Usually an extension of a defined task in an external task runner.
{
"type": "npm".// The task type to customize.
"script": "watch".// To customize the NPM script.
"problemMatcher": "$tsc-watch".// The problem matcher to use. It can be a string or a problem matcher definition, or it can be an array of strings and multiple problem matchers.
"isBackground": true.// Whether the executed task remains active and runs in the background.
"presentation": { // Configure the panel that displays the output of the task and reads its input.
"reveal": "never" // Controls whether the terminal running the task is displayed. The option "revealProblems" is available instead. The default value is always.
},
"group": { // Define the execution group to which this task belongs. It supports "build" to add it to a build group and "test" to add it to a test group.
"kind": "build".// The execution group of the task.
"isDefault": true // Define whether this task is the default task in the group.}}}]Copy the code
packaging
Before our plug-in development is completed, would you like to share it with your friends? The answer is yes, vscode provides us with vsCE implementation requirements, we install the VSCE module globally, and then use the VSCE package command to try packaging:
$ vsce package
ERROR Missing publisher name. Learn more: https://code.visualstudio.com/api/working-with-extensions/publishing-extension#publishing-extensions
Copy the code
Ah, how also reported wrong? What is publisher? He looked confused. Not to panic, follow the link to learn that Publisher is an identity that can publish extensions to the Visual Studio Code Marketplace. Each extension needs to include a publisher name in its package.json file. If the registered publisher is described later, we set Publisher as Luozhu.
$ vsce package
INFO Detected presence of yarn.lock. Using 'yarn' instead of 'npm' (to override this pass '--no-yarn' on the command line).
ERROR Make sure to edit the README.md file before you package or publish your extension.
Copy the code
Md: yes, the original README is in the vscode template. We need to edit it before we can package it. Try vsCE package again after modification:
Finally, packing success! In pursuit of perfection, let’s finally do some optimization:
- perform
vsce package
When you add--no-yarn
- inpackage.jsonadd
repository
Field can not see any warning. - For convenience, we installed VSCE into the project and put
vsce package --no-yarn
Add to NPM Scripts. - package.jsonadd
license
Field.
Then try yarn Package again and it’s perfect:
Tip: VSCE package will first execute the vscode:prepublish prepublish script to compile the project.
Submit records: chore: Config VSCE Package
Principle of packaging
If you follow along, you’ll find a vsix ending file in the project root directory:
This is the vsCode plug-in installation package, we don’t rush to install, let’s take a look at the file is what it is. Try using archive tool to extract the following directory folder:
Js,.prettierrc.js, and commitlint.config.js are also compressed at development time. Running plug-ins is completely useless, which is obviously unreasonable. In fact, like other plug-in systems, vscode also provides.vscodeignore to implement packaging ignore configuration, we ignore the above irrelevant files and repackage.
Is that how it works? It does not exist. If we open extension.js, we will find a reference to vscode:
Node_modules is not in our installation package, so where does vscode live? After reading the source code, I realized THAT I was right:
Vscode implements the interceptor to add vscode to the built-in package when loading the Node environment, which has the benefit of reducing the size of the plug-in.
So what if we use tripartite plug-ins? Take the common LoDash as an example. After installing LoDash, repack:
$YARN Package YARN run v1.22.10 $VSce package -- no-YARN Executing prepublish script'npm run vscode:prepublish'. > [email protected] vscode:prepublish > YARN run compile $TSC-p./ This extension consists of 1060 files, out ofwhich1049 are JavaScript files. For performance reasons, you should bundle your extension: https://aka.ms/vscode-bundle-extension . You should also exclude unnecessary files by adding them to your .vscodeignore: https://aka.ms/vscode-vscodeignore DONE Packaged: / Users/luozhu/Desktop/playground/juejin - posts/juejin - posts - 0.0.1. Vsix (1060 files, 644.72 KB) โจ Donein5.54 s.Copy the code
Node_modules () : node_modules () : node_modules ();
Not surprisingly, vscode’s default packaging method is to simply compile copies, and reducing the size by ignoring files is not a big deal. And vscode extensions tend to grow quickly in size. They are written in multiple source files and rely on the modules of NPM. Decomposition and reuse are development best practices, but they come at a cost when installing and running extensions. Loading 100 small files is much slower than loading one large file. That’s why we recommend bundling. Bundling is the process of combining multiple small source files into a single file.
There are different packaging tools available in JavaScript, popular ones are rollup.js, Parcel, esbuild, and Webpack. Webpack is the default for official scaffolding, but we recommend using the faster and more powerful Esbuild.
Submit records: chore: Ignore Config file when package, chore: add esModuleInterop to tsConfig
Optimize packaging with esbuild
Install dependencies:
$ yarn add -D esbuild
Copy the code
NPM scripts:
"scripts": {
- "vscode:prepublish": "yarn run compile",
- "compile": "tsc -p ./",
- "watch": "tsc -watch -p ./",
- "pretest": "yarn run compile && yarn run lint",
+ "vscode:prepublish": "yarn esbuild-base -- --minify",
+ "esbuild-base": "esbuild ./src/extension.ts --bundle --outfile=out/extension.js --external:vscode --format=cjs --platform=node",
+ "esbuild": "yarn esbuild-base --sourcemap",
+ "esbuild-watch": "yarn esbuild-base --sourcemap --watch",
+ "test-compile": "tsc -p ./",
+ "pretest": "yarn test-compile && yarn lint",
}
Copy the code
Note: Since watch is changed to esbuild-watch, the scripts subparagraph in.vscode/tasks.json also needs to be modified accordingly.
Vscode tasks:
In theory, after changing the package command to esbuild, we should set the problem matcher in the vscode task to $esbuild-watch, but vscode will prompt us with an unrecognized problem matcher:
Try the search extension, and sure enough there’s an esbuild Problem Matchers plugin, We installed it and added “Connor4312.esbuild-problem-matchers” to.vscode/extensions. Json file recommendations.
Ignore files:
When you use esbuild, you pack out/extension.js, but vsCE packs the dependencies package into the install package regardless of whether you use it or not, so you omit node_modules.
Achievements:
As you can see from the figure, the size of the installation package has been greatly reduced.
Submit the record: chore: Config esbuild
Hello Umijs
Initialize the UMI project
Create a new Web directory in the root directory using UMI scaffolding.
$ mkdir web && cd web
Copy the code
Create projects using official tools:
$ yarn create @umijs/umi-app
Copy the code
Modify the.umirc.ts configuration:
import { defineConfig, IConfig } from 'umi';
export default defineConfig({
nodeModulesTransform: {
type: 'none',},routes: [{ path: '/'.component: '@/pages/index'}].fastRefresh: {}, // Maintain component state while developing while editing provides immediate feedback.
history: {
type: 'memory'.// the default type is' browser ', but since the vscode webview environment does not have browser routing, 'memory' and 'hash' can be changed
},
devServer: {
// Write files to the output directory at dev time to ensure that there are js/ CSS files at development time
writeToDisk: filePath= >
['umi.js'.'umi.css'].some(name= > filePath.endsWith(name)),
},
} as IConfig);
Copy the code
Modify package.json to add name, version, description:
{
"name": "web"."version": "0.0.0"."description": "web for juejin-posts"
}
Copy the code
Ignore files
Gitignore:
Merge the VScode extension with the Gitignore generated by the umiJS scaffolding as follows:
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# vscode
.vscode-test/
*.vsix
# dependencies
node_modules
npm-debug.log*
yarn-error.log
package-lock.json
# production
out
dist
# misc
.DS_Store
# umi
**/src/.umi
**/src/.umi-production
**/src/.umi-test
**/.env.local
web/yarn.lock
Copy the code
Vscodeignore:
Since vscode only needs to get the umijs packaged product, all are added to web/** and! Web /dist/** ignores useless files.
.vscode/** .vscode-test/** out/test/** src/** .gitignore .yarnrc vsc-extension-quickstart.md **/tsconfig.json **/*.map **/*.ts .cz-config.js .prettierrc.js commitlint.config.js **/node_modules/** yarn-error.log web/** ! web/dist/**Copy the code
yarn workspace
Since our project is a hybrid of vscode extension and web project. To facilitate the management of scripts and dependencies, we introduced yarn Workspace to manage the project. Add the following configuration to the package.json in the root directory:
{
"private": "true"."workspaces": ["web"]}Copy the code
debugging
Since our Web project also needs to be compiled, we need to modify vscode launch.json to add to the compilation task of our Web project. The configuration is based on AppWorks.
First modify the root package.json scripts:
{
"scripts": {
"vscode:prepublish": "yarn build:web && yarn esbuild-base --minify"."preesbuild-base": "rimraf out"."esbuild-base": "esbuild ./src/extension.ts --bundle --outfile=out/extension.js --external:vscode --format=cjs --platform=node"."esbuild": "yarn esbuild-base --sourcemap"."esbuild-watch": "yarn esbuild-base --sourcemap --watch"."web-build": "yarn workspace web run build"."web-watch": "yarn workspace web run start"."test-compile": "tsc -p ./"."lint": "eslint src --ext ts,tsx"."pretest": "yarn test-compile && yarn lint"."test": "node ./out/test/runTest.js"."commit": "git cz"."package": "vsce package --no-yarn"}},Copy the code
Then modify.vscode/launch.json to:
// A launch configuration that compiles the extension and then opens it inside a new window
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
{
"version": "0.2.0"."compounds": [
// Composite list. Each compound can reference multiple configurations that will start together.
{
"name": "Debug Extension".// The name of the compound. Displayed in the startup configuration drop-down menu.
"configurations": [
// The name of the configuration to be launched as part of this composition.
"Run Extension"."Watch Webview"]."presentation": {
"order": 0}}]."configurations": [{"name": "Watch Webview"."request": "attach"."type": "node"."preLaunchTask": "npm: web-watch"
},
{
"name": "Run Extension"."type": "extensionHost"."request": "launch"."args": ["--extensionDevelopmentPath=${workspaceFolder}"]."outFiles": ["${workspaceFolder}/out/**/*.js"]."preLaunchTask": "${defaultBuildTask}"}}]Copy the code
When you’re done, go to VS Code, press F5, and you’ll immediately see a plug-in launch host window where the plug-in is running. At this time you will find that the console reported an error โ :
error TS6059: File '/Users/luozhu/Desktop/github/juejin-posts/web/src/pages/index.tsx' is not under 'rootDir' '/Users/luozhu/Desktop/github/juejin-posts/src'. 'rootDir' is expected to contain all source files.
The file is in the program because:
Matched by include pattern '**/*' in '/Users/luozhu/Desktop/github/juejin-posts/tsconfig.json'
Copy the code
The reason is because umi’s convention project structure and vscode extension both contain SRC directories. Since vscode and umi are compiled separately, we ignore the web directory in the tsconfig.json root directory:
{
"exclude": ["web"]}Copy the code
Now, you can press F5 to see the plug-in launch host window and see two debugging tasks:
Other optimization work
- Based on Yarn Workspace, we merge common dependencies
- Eslint is configured for use
@luozhu/eslint-config-react-typescrip
Submit the record: chore: config umijs
Core concepts of vscode plug-in development
Before we begin webview capability development, it is important to understand the core concepts of vscode plug-in development. To get a global perspective, let’s look at the main directory structure of our current project:
. โ โ โ CHANGELOG. Md# Update log file generated based on standard-versionโ โ โ the README. Md โ โ โ package. The json# VScode package configuration files, such as the plug-in LOGO, name, description, registration activation eventโโ SRC โ โโ exercisesThe plugin entry file exposes the activate method to register commands and initialize some configurations, and the deactivate method to perform cleanup before the plugin is closedโ โ โ tsconfig. Json# vscode compiler configurationโ โ โ the web# Umi based Web, which is also the content of our webview to bearโ โ โ yarn. The lockCopy the code
As you can see from the directory structure, the key files are package.json and extension.ts. Using the helloWorld command as an example, we introduce the three core concepts of vscode plug-in.
1. Activate events
The activation event is a JSON array object declared in the activationEvents field in package.json. To register the helloWorld command, the first step is to register the activation event. There are many types of activation events. The activation event for the registration command is onCommand:
{
"activationEvents": ["onCommand:juejin-posts.helloWorld"]}Copy the code
2. Configure publish content
Publish content configuration (that is, the configuration items VS Code provides for plug-in extensions) is the entire package.json contribute field in which you register various configuration items extending VS Code’s capabilities. The helloWorld activation event we registered in the previous step only tells vscode that it can be triggered by the juejin-posts.helloworld command. We also need to register our juejin-posts.helloworld command in contributes.commands again:
{
"contributes": {
"commands": [{"command": "juejin-posts.helloWorld"."title": "Hello World"}}}]Copy the code
3. VS Code API
The VS Code API is a set of Javascript apis that VS Code provides for plug-ins to use. With the capabilities of the first two core concepts, we have already registered commands and events, so the next logical step is to register event callbacks. Event callback is through vscode.com in vscode mands. RegisterCommand function to register, ๐๐ป : juejin-posts.helloWorld: SRC /extension.ts
// VS Code this module contains the VS Code extension API
import vscode from 'vscode';
// This method is called when your extension is activated, which is activated the first time the command is executed
export function activate(context: vscode.ExtensionContext) {
// This line of code will be executed only once when your extension is activated
//
// Use console.log to output log information or use console.error to output error information.
//
console.log('Congratulations, your extension "juejin-posts" is now active! ');
// Now that the entry command is defined in the package.json file, call the registerCommand method
// The parameters in registerCommand must be the same as the commands in package.json
const disposable = vscode.commands.registerCommand('juejin-posts.helloWorld'.() = > {
// Write your code here, which will be called every time the command is executed
// Display a message to the user
vscode.window.showInformationMessage('Hello World from Juejin Posts! ');
});
context.subscriptions.push(disposable);
}
// This method is called when your extension is disabled.
export function deactivate() {}
Copy the code
Integration of the webview
Register the command
1. Add “onCommand:juejin-posts.start” to package.json activationEvents
2, package.json commands add:
{
"command": "juejin-posts.start"."title": "start"."category": "Juejin Posts"
}
Copy the code
SRC /extension.ts
context.subscriptions.push(
vscode.commands.registerCommand('juejin-posts.start'.() = > {
// Truth is endless. Keep coding...}))Copy the code
Create the WebView panel
Create a blank panel
import vscode from 'vscode';
// Create and display a new WebView
const panel = vscode.window.createWebviewPanel(
'juejin-posts'.// For internal use only, the webView identity
'Juejin Posts'.// Display the panel title to the user
vscode.vscode.ViewColumn.One, // Give the new WebView panel an editor view
{
enableScripts: true.// Enable javascript scripts
retainContextWhenHidden: true.// Preserve context when hiding
} // WebView panel content configuration
);
Copy the code
We use Windows. CreateWebviewPanel API creates a webview panel, now we try to run juejin – posts. Start can open a webview panel:
Set the content for the panel
Above we created a blank panel, so how do we add content to the panel? We can set the HTML content using panel.webview.html:
function getWebviewContent() {
return `
`; }...// Set the HTML content for the WebView Panelpanel.webview.html = getWebviewContent(); .Copy the code
Re-use the juejin-posts.start command to tease Captain Youyou:
Limit webView to one view
export function activate(context: vscode.ExtensionContext) {
// Trace the current WebView panel
let currentPanel: vscode.WebviewPanel | undefined = undefined;
context.subscriptions.push(
vscode.commands.registerCommand('juejin-posts.start'.() = > {
// Get the currently active editor
const columnToShowIn = vscode.window.activeTextEditor
? vscode.window.activeTextEditor.viewColumn
: undefined;
if (currentPanel) {
// If we already have a panel, display it in the target column layout
currentPanel.reveal(columnToShowIn);
} else {
// Otherwise, create a new panel
currentPanel = vscode.window.createWebviewPanel();
// Reset after the current panel is closed
currentPanel.onDidDispose(
() = > {
currentPanel = undefined;
},
null, context.subscriptions ); }})); }Copy the code
- Vscode. Window. ActiveTextEditor: text editor for current activities
- currentPanel.reveal(): call
reveal()
Or drag the WebView panel to the new edit layout.
Set the Icon
/ / set the Logo
panel.iconPath = vscode.Uri.file(
path.join(context.extensionPath, 'assets'.'icon-juejin.png'));Copy the code
In the VScode extension we need the vscode.uri. file method to get the resource path on disk.
The WebView gets the Uri of the content
You should use asWebviewUri to manage plug-in resources. Instead of hard-coding vscode-resource://, use asWebviewUri to make sure your plugin works in the cloud.
In @ orphzhu /vscode-utils, we have encapsulated the path of obtaining local resources:
// Get the Uri of the content
const getDiskPath = (fileName: string) = > {
return webviewPanel.webview.asWebviewUri(
vscode.Uri.file(path.join(context.extensionPath, rootPath, 'dist', fileName))
);
};
Copy the code
Use UMI to develop webView
In the last section we learned how to create a WebView panel by teasing Captain Yoyo. In this section we’ll look at how to use UMiJS instead of HTML content.
The content in panel.webview.html is actually normal HTML+JavaScript+CSS code. You can write it using any front-end technology, such as jquery, Bootstrap, Vue, and React. Although the example in this article is based on umiJS to develop webView content, the principle of other technologies is the same, Luo Zhu will provide multiple technologies in the future vscode WebView development scaffolding.
Encapsulate the method to get the umiJS package
The umi build command generates three files in web/dist: index.html, umi.js, and umi. CSS.
import vscode from 'vscode';
import path from 'path';
/** * Get umijs-based webView content *@param Context Extension context *@param WebviewPanel WebView object *@param RootPath Path of the WebView. The default path is Web *@param UmiVersion UMI version *@returns string* /
export const getUmiContent = (context: vscode.ExtensionContext, webviewPanel: vscode.WebviewPanel, umiVersion? :string,
rootPath = 'web'
) = > {
// Get the resource path on the disk
const getDiskPath = (fileName: string) = > {
return webviewPanel.webview.asWebviewUri(
vscode.Uri.file(path.join(context.extensionPath, rootPath, 'dist', fileName))
);
};
return `
<html>
<head>
<meta charset="utf-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no"
/>
<link rel="stylesheet" href="${getDiskPath('umi.css')}" />
<style>
html, body, #root {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
overflow: hidden;
}
</style>
<script>
//! umi version: ${umiVersion}
</script>
</head>
<body>
<div id="root"></div>
<script src="${getDiskPath('umi.js')}"></script>
</body>
</html>
`;
};
Copy the code
Note: THE above method has been encapsulated in @ orphzhu /vscode-utils.
We use getUmiContent to redo the previous code:
import { getUmiContent } from '@luozhu/vscode-utils'; . panel.webview.html = getUmiContent(context, panel,'3.5.17');
Copy the code
Optimization of packaging
Since we have encapsulated the getUmiContent method, the index.html generated by umi Build is useless. We can use the HTML= None umi build command to not generate the index.html file when packaging.
In addition, the current MFSU of UMiJS does not support the writeToDisk method. If it is supported later, mfSU can be used to optimize the debugging speed.
Most of the tasks of creating a webview panel are repetitive, and in order to precipitate best practices, I encapsulated the createUmiWebviewPanel method at @apzhu /vscode-utils.
Theme webView content
Webviews can style themselves based on the current VS Code theme and CSS. VS Code divides topics into three categories, and by adding a special class name to the body element to indicate the current topic, we globally add the following styles to UMI:
body.vscode-light {
h1, h2, h3, h4, h5, h6 {
color: black;
}
color: black;
background-color: var(--vscode-editor-background);
}
body.vscode-dark {
h1, h2, h3, h4, h5, h6 {
color: white;
}
color: white;
background-color: var(--vscode-editor-background);
}
body.vscode-high-contrast {
h1, h2, h3, h4, h5, h6 {
color: red;
}
color: red;
background-color: var(--vscode-editor-background);
}
Copy the code
Since most of these adaptations are universal, I have also encapsulated them in the getUmiContent of @ orphzhu/VScode-utils.
Webview interacts with vscode
Execute scripts in webView
A webview in vscode is essentially an iframe, so you can execute scripts in a webview, but JavaScript is disabled in vscode by default. We just pass enableScripts: true when calling createWebviewPanel API.
The plug-in passes information to the WebView
Webview scripts can do anything a normal Web script can do, but the WebView runs in its own context and scripts do not have access to VS Code APIS. We need to send messages in the form of events like postMessage. In vscode, we can use webview. postMessage on the vscode side to publish events and send any serialized JSON data, On the WebView side use window.addEventListener(‘message’ event => {… }) to process this information:
Vscode side:
// Register a new command
context.subscriptions.push(
vscode.commands.registerCommand('juejin-me.author'.() = > {
if(! currentPanel) {return;
}
// Send the information to the webView
// You can send any serialized JSON data
currentPanel.webview.postMessage({ method: 'showAuthor'}); }));Copy the code
The webview side:
import { Modal } from 'antd'; .window.addEventListener('message'.event= > {
const message = event.data;
switch (message.method) {
case 'showAuthor': {
Modal.info({
title: 'los bamboo'.content: (
<div>Hello everyone, I am Luo Zhu ๐ a wood department front end in Hangzhou ๐ง๐ปโ๏ธ, if you like my article on ๐ง, you can pass<a href="https://juejin.cn/user/325111174662855/posts">give a like</a>Help me gather psychic power โญ๏ธ.</div>
),
okText: <a href="https://juejin.cn/user/325111174662855/posts">Thumbs up o(~ โฝ ~)d</a>});break;
}
default:
break; }});Copy the code
Effect:
The WebView passes information to the plug-in
The same principle is used by the WebView to pass information back to the plugin. However, due to the context of the WebView, we can only obtain the castrated VS Code API object through the acquireVsCodeApi function. The castrated object has a postMessage function that we can use to send events. AcquireVsCodeApi can only be called once in a session. Repeated calls will generate an error. While in the plug-in side can be through the Webview. OnDidReceiveMessage processing Webview’s message. Let’s write a webview invokes the vscode. Window. ShowInformationMessage example:
The webview side:
const vscode = acquireVsCodeApi();
vscode.postMessage({
method: 'showMessage'.params: {
text: 'Serve the people',}});Copy the code
The plug-in side:
// Process information in webView
currentPanel.webview.onDidReceiveMessage(
message= > {
if (message.method === 'showMessage') { vscode.window.showInformationMessage(message.params.content); }},undefined,
context.subscriptions
);
Copy the code
Effect:
Request the interface in the WebView
At first, I thought this was an easy job, until I encountered cross-domain for a long time after I despair, in VSCode WebView plug-in (extension) development practice article I finally know that VSCode WebView is not allowed to send Ajax requests, all Ajax requests are cross-domain, Because webView itself doesn’t have a host.
Man split, what the hell, our core requirement is to ask the nuggets interface to get our list of articles, so what can we do? The answer is yes, in fact, we use the above communication mechanism to request the interface to vscode to handle the task, and then let VScode send data back to us via postMessage, more talk, let’s look at the code:
The webview side:
React.useEffect(() = > {
// @ts-ignore
const vscode = typeof acquireVsCodeApi === 'function' ? acquireVsCodeApi() : null;
vscode.postMessage({
method: 'queryPosts'});window.addEventListener('message'.event= > {
if (method === 'queryPosts') {
const message = event.data;
console.log(message); }}); } []);Copy the code
Vscode side:
// Process the information in webView and return the data requested by the interface
currentPanel.webview.onDidReceiveMessage(
async message => {
const data = awaitevents(message); currentPanel? .webview.postMessage({ data }); },undefined,
context.subscriptions
);
Copy the code
@luozhu/vscode-channel
We know the Webview. In front of the postMessage, Webview. OnDidReceiveMessage, acquireVsCodeApi (). PostMessage and window. The addEventListener Therefore, it can meet various communication requirements. What is @luozhu/ VS code-channel?
Inspired by JS-channel, @ wafzhu/VScode-channel mainly encapsulates the interaction process between WebView and VScode. The core principle is to smooth API differences by exposing call and bind methods and reduce the amount of repeated code. Referring to AppWorks and CS-Channel, uUID is used to ensure the reliability of interaction. Talk is cheap, show you the code:
The webview side:
// Create a channel object
const channel = new Channel();
const getData = async() = > {// Initiate a request and wait for it to return data
const { payload } = await channel.call({ method: 'queryPosts' });
console.log(payload);
};
Copy the code
In webView, since the acquireVsCodeApi can only be called once and then used in multiple places, it’s probably best to create one in wev/ SRC /layouts/index.ts and mount it to the Window object.
Vscode side:
// vscode side channels depend on context and WebviewPanel instance
const channel = new Channel(context, currentPanel);
// Bind a callback function, usually just create one and distribute it according to convention
channel.bind(async message => {
const { eventType, method, params } = message;
// Where the request is actually made to get data
const data = await events[eventType][method](params);
// The message is merged and sent back to the channel.
// If only a function is executed without a return statement, internal judgment will be degraded to simplex communication.
return data;
});
Copy the code
Vscode internationalization
We all know that vscode can switch locales, and a good vscode extension should support at least two languages. And supporting internationalization allows your plug-in audience to go directly beyond national boundaries. Vscode internationalization is divided into three parts, one is internationalization of configuration, one is internationalization of code, and the other is internationalization of umiJS in webView. In this chapter, we’ll take a look at internationalization in vscode.
Configure internationalization
We already know that the configuration in vscode is in package.json, and the internationalization of the configuration is written by convention in package.nls. For example, if we want to command the Chinese and English version in the Chinese and English environment, we can write in package.nls. Json:
{
"contributes.category.juejin-me": "Juejin Me"
}
Copy the code
In the package. The NLS. Useful – cn. Json wrote:
{
"contributes.category.juejin-me": "Dig for gold."
}
Copy the code
Then package.json says:
{
"contributes": {
"commands": [{..."category": "%contributes.category.juejin-me%"}}},]Copy the code
Internationalization in code
SRC /extension.ts init, localize (); SRC /extension.ts init, localize ();
import { init, localize } from 'vscode-nls-i18n';
export function activate(context: vscode.ExtensionContext) {
init(context.extensionPath); // Initialize the internationalization configuration. Only initialized once during extension activation
console.log(localize('extension.activeLog')); // Can then be used in individual files.
}
Copy the code
Umijs internationalization
Umijs internationalization is supported by the @umijs/plugin-locale plugin. This plugin encapsulates react-intl and is configured as follows:
1. Configure local in umirc.ts
locale: {}
Copy the code
2. Create locales in the SRC directory and create en.ts or zh-cn
// src/locales/en.js
export default {
WELCOME_TO_UMI_WORLD: "welcome to umi's world"};Copy the code
// src/locales/zh-CN.js
export default {
WELCOME_TO_UMI_WORLD: 'Welcome to Umi's world'};Copy the code
3. Use internationalization
import React from 'react';
import { useIntl } from 'umi';
export default: React.FunctionComponent = (props) = > {
const intl = useIntl()
return (
<div>
{intl.formatMessage(
{
id: 'WELCOME_TO_UMI_WORLD',
}
)}<div>
)
}
Copy the code
4. Switch languages
To switch languages, we need to use the setLocale method, and notice that we pass false to the second parameter of this method to do the dynamic switch without refreshing.
import { setLocale } from 'umi';
// Do not refresh the page
setLocale('zh-CN'.false);
Copy the code
But when is the time to switch languages? Switching timing is when our language environment changes. In the vscode webview environment, when the Config display language method is used to switch the locale, vscode will be required to restart. This means that we only need to set the locale once when the WebView is created. Since it is too difficult to upload vscode and webView values, we choose to upload vscode.env when getUmiHTMLContent:
<script>
window.vscodeEnv = ${JSON.stringify(vscode.env)}
</script>
Copy the code
In web/ SRC /layouts/index.ts, you can see the layouts in the layouts/ SRC /layouts/index.ts directory.
setLocale(window.vscodeEnv.language, false);
Copy the code
“Dig for gold” extends the core implementation
Inspired by reality, as a heavy user of nuggets, almost every article and note is synchronized here. When something forgets to look up or copy code, I have the need to dig for my articles. But the nuggets search is site-wide, even if the addition of their own name search will also appear a lot of irrelevant records. The name “dig for Gold” is like a plugin function, you can open the plugin “dig for gold” when you want to search for your own articles.
In fact, in order to only search for their own articles, I think of chrome plug-in to achieve. However, considering the market and convenience, I finally decided to develop vscode plug-in to implement the inspiration. This chapter is the integration of the previous experience to achieve the core logic of “digging gold once”.
juejin-me.start
The command
Enable channel communication on vscode
The vscode side binds an event handler through channel.bind.
import events from './events'; . context.subscriptions.push( vscode.commands.registerCommand('juejin-me.start'.async () => {
currentPanel = createUmiWebviewPanel(
context,
'juejin-me',
localize('extension.webview-panel.title'),
'assets/icon-luozhu.png'.'3.5.17'
);
// Process information in webView
channel = new Channel(context, currentPanel);
channel.bind(async message => {
const { eventType, method, params } = message;
// make an API call based on the eventType, method, and parameter. The built-in eventtypes include request, command, and variable.
const data = await events[eventType][method](params);
returndata; }, vscode); }));Copy the code
Note: we do not need to give the name of the listener event, and we will guarantee reliability and global uniqueness internally according to eventId
Registry events
The events/index. Ts:
import requests from './requests';
export default {
request: requests,
};
Copy the code
The events/requests:
import vscode from 'vscode';
import request from '.. /utils/request';
const queryPosts = async (params: { cursor: string}) :Promise<any> = > {// here we configure the user id to be dynamically fetched according to vscode
const { userId } = vscode.workspace.getConfiguration('juejin-me');
const { cursor } = params;
const data = await request.post('/article/query_list', {
cursor: `${cursor}`.sort_type: 2.user_id: userId,
});
return data;
};
export default {
queryPosts,
};
Copy the code
Utils/request:
This simply encapsulates the AXIOS-based request object.
/* eslint-disable no-param-reassign */
import axios from 'axios';
import vscode from 'vscode';
import qs from 'qs';
// Chinese document: http://t.cn/ROfXFuj
// Create an instance
const request = axios.create({
baseURL: 'https://api.juejin.cn/content_api/v1/'.timeout: 10000});// Add request interceptor
request.interceptors.request.use(
config= > {
if (config.method === 'get') {
config.paramsSerializer = params= > qs.stringify(params, { arrayFormat: 'repeat' });
}
return config;
},
error= > {
vscode.window.showErrorMessage(error.message);
return Promise.reject(error); });// Add a response interceptor
request.interceptors.response.use(
response= > {
const { data } = response;
return data;
},
error= > {
vscode.window.showErrorMessage(error.message);
return Promise.reject(error); });export default request;
Copy the code
Call interface in webView
Channel is in the web/SRC/layouts/index in the TSX initialization and mounted on the window, we in the web/SRC/pages/index. The TSX is called Windows. The channel. The call can invoke the specified interface. Since we need to fuzzily search all the articles, we need to request all the data at once when initializing the page.
const Homepage = () = > {
const getData = async() = > {const { payload } = (await window.channel.call({
eventType: 'request'.method: 'queryPosts'.params: { cursor },
})) as any;
tempData = tempData.concat(payload.data);
setData(tempData);
if(! payload.has_more) { setInitLoading(false);
setCategories(_union(['all'. tempData.map(item= > item.category.category_name)]));
tempData = [];
} else {
cursor += 10; getData(); }}; }Copy the code
More specific implementation details are some page writing logic, not the focus of this article, interested students can directly into view the source code.
Configure the gold digging ID
Declare configuration:
Vscode configuration we need to use package. The json contributes. The configuration properties, we the nuggets ID is a string, so the statement is as follows:
{
"contributes": {
"configuration": {
"title": "%configuration.title%"."type": "object"."properties": {
"juejin-me.userId": {
"type": "string"."default": "325111174662855"."description": "%configuration.properties.juejin-me.userId%"
}
}
}
}
}
Copy the code
Commands for modifying configurations:
It is also possible for the user to open the Settings to modify the configuration, but for user experience, we provide the juejin-me.configUserId command, let’s look at the implementation of the command:
context.subscriptions.push(
vscode.commands.registerCommand('juejin-me.configUserId'.async() = > {const userId = await vscode.window.showInputBox({
placeHolder: localize('extension.juejin-me.configUserId.placeHolder'),
validateInput: value= > {
if (value) {
return null;
}
return localize('extension.juejin-me.configUserId.validateInput'); }});const config = vscode.workspace.getConfiguration('juejin-me');
config.update('userId', userId, true); }));Copy the code
- Vscode. Window. ShowInputBox: open an input box, prompt the user for the nuggets user ID
- Vscode. Workspace. GetConfiguration: access to the workspace configuration object
- A configuration value WorkspaceConfiguration. Update: update.
- InputBoxOptions. ValidateInput: an optional function that is invoked to verify the information and prompt the user input
Plug-in effect Display
If you’re interested, you can also search for “dig for gold” in the extension to try it out for yourself.
eggs
@luozhu/create-vscode-webview
There are a lot of best practices in this article, but to make it easier to create new projects later, Luo zhu has pulled out a simple template. Users can create their own VScode extension by directly using yarn create @apzhu /vscode-webview myvscode. Take a look at some of the practices in this article and add some creativity to create a great webview-based vscode extension.
Word Count Juejin
In recognition of the support from digg and Digg, I created a VS Code extension for the word count of Markdown files, which is displayed in the status bar in real time. Compared with the official Word Count of vscode, we support Chinese Word Count. Compared with Word Count CJK, we support both Chinese and English. If you like to use VS Code’s Markdown editing capabilities, don’t miss this plugin.
If you’re still on the fence about whether to download it or not, take a look at the statistical comparison of the three plug-ins. I love nuggets to test the functionality of three plugins:
Word Count | Word Count CJK | Word Count Juejin |
---|---|---|
4 words | 4 words | Seven words |
Chinese is one word | I just ignored English | Chinese four characters plus English three characters, the pattern is just right |
vscode api cn
One of the biggest pain points in learning and developing vscode plug-ins is the lack of API documentation translation. Even trying to read the original English API documentation is a poor reading experience. In order to facilitate ourselves and give back to the community, han cao and I decided to translate vscode API type declarations and use Typedoc to carry them. In addition, after completion, we will also output @types/vscode-cn package to replace @types/vscode to further facilitate the developers of VScode plug-in. Current Status of team members:
Translation is an enterprise with chivalrous spirit, welcome more friends to join us. You can browse the warehouse and the official website for details.
Afterword.
This is my first attempt to write such a long article, which has lasted for half a month intermittently. With a responsible attitude towards readers, the practice in this article has been tested repeatedly and discussed with colleagues and friends. Of course, vscode plug-in development concept and API more, an article is also difficult to tell the whole, thorough. If you’re interested, let Luo Zhu know in the comments, and I can keep updating this tutorial.
The recent oliver
- Vscode plug-in released
- Front end componentization actual combat Button
- Each front-end deserves its own component library, like having a watermelon every summer ๐
- Lerna based multi-package JavaScript project construction maintenance
- Conventional Commits
- The best Node.js framework to use in 2021
- React Interview series
- Go language tutorial series
This article first appeared on The Nuggets column and also on the app Life.