Nuggets | Разработка расширения vscode на основе веб-просмотра с нуля

внешний интерфейс Visual Studio Code React.js
Nuggets | Разработка расширения vscode на основе веб-просмотра с нуля

⚠️Эта статья является первой подписанной статьей сообщества Nuggets, и её перепечатка без разрешения запрещена.

Привет всем, яЛуожу🎋, деревянный фронтенд, живущий в Ханчжоу 🧚🏻‍♀️, если вам понравилась моя статья 📚, вы можете помочь мне собрать духовную силу ⭐️ лайком.

Напоминание: объединить с этой статьейисходный кодЛучший опыт чтения!

предисловие

При построении инфраструктуры команды для снижения затрат и повышения эффективности Луожу разработал плагин vscode.В первой версии я использовал встроенный пользовательский интерфейс vscode.Хотя его можно использовать, пользовательский интерфейс не очень хорош. Поскольку встроенный пользовательский интерфейс vscode недостаточно гибкий, после некоторых исследований я решил использовать рефакторинг веб-просмотра.

Учащиеся, разрабатывавшие подключаемые модули vscode, могут иметь некоторый опыт работы с подключаемыми модулями, трудности с чтением документов и небольшое количество справочных материалов. Тем более это относится к разработке плагинов на основе веб-просмотра.Поиск в Интернете, хотя есть отличные проекты, но нет полных и отличных руководств. Чтобы культивировать духовную силу разработки vscode, вы можете бросить вызов Луо Чжу, чтобы разработать плагин vscode, основанный на веб-просмотре от нуля до единицы.

Hello vscode

Герои часто начинают на рынке, а все высотные здания начинаются на земле. Независимо от того, насколько хороша программа, все начинается с Hello World В этой главе мы пытаемся максимально лаконичным языком описать рождение плагина vscode Hello World.

Инициализировать проект

УстановитьYeomanа такжеVS Code Extension Generator:

$ npm install -g yo generator-code

Эти леса создадут проект, который можно будет разработать прямо сейчас. Запустите генератор и заполните следующие поля:

$ yo code
#     _-----_     ╭──────────────────────────╮
#    |       |    │   Welcome to the Visual  │
#    |--(o)--|    │   Studio Code Extension  │
#   `---------´   │        generator!        │
#    ( _´U`_ )    ╰──────────────────────────╯
#    /___A___\   /
#     |  ~  |
#   __'.___.'__
# ´   `  |° ´ 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? 掘金文章管理
# ? Initialize a git repository? Yes
# ? Bundle the source code with webpack? No
# ? Which package manager to use? yarn

$ code ./juejin-posts

Зафиксировать запись:hello world

спецификация кода

Скаффолдинг по умолчанию также имеет конфигурацию ESLint, но ни конфигурация Editor, ни Prettier, и конфигурация ESLint не соответствует моим привычкам. Пакеты Luozhu по фронтенд-инжинирингу готовы.youngjuning/luozhu, пакет, настроенный ESlint,@luozhu/eslint-config-*. Поскольку мы используем Typescript для разработки плагинов, мы выбираем@luozhu/eslint-config-typescript.

Установить зависимости:

$ yarn add @luozhu/eslint-config-typescript @luozhu/prettier-config prettier -D

Конкретная конфигурация:

Конфигурация включает много файлов, см.coding-style, учащиеся, которым все равно, могут пропустить его напрямую.

Отправить тест:

Установить зависимости:

$ yarn add lint-staged yorkie -D

Изменить настройку:

// package.json
{
  "gitHooks": {
    "pre-commit": "lint-staged"
  },
  "lint-staged": {
    "**/*.{js,jsx,ts,tsx}": ["eslint --fix"],
    "**/*.{md,json}": ["prettier --write"]
  }
}

эслинт --fix:

После изменения конфигурации вам необходимо выполнить исправление для форматирования всех файлов один раз.

$ yarn lint --fix

Зафиксировать запись:chore: code style config

Совершить по соглашению

Обычные коммиты Я использую прогрессивные леса@luozhu/create-commitlint, выполненный в проектеnpx @luozhu/create-commitlintВы можете привести проект в соответствие с конфигурацией канонической отправки. Учащимся, которые не понимают стандартную подачу, настоятельно рекомендуется прочитатьСделайте это одним куском Обычные коммиты.

Зафиксировать запись:chore: npx @luozhu/create-commitlint

отладка

нажиматьF5Когда отладка включена, появится окно [Extended Development Host], затем нажмитеCommand+Shift+PВвод ключа компонентаHello WorldЗаказ. Как показано ниже, всплывает vscodeHello World from Juejin Posts!намекать.

При этом в нашем окне разработки появится терминал для задачи watch:

Консоль отладки окна разработки выведет журнал работы плагина (не обращайте внимания на красное предупреждение):

Задача отладки выполнения находится в.vscode/tasks.jsonнастроено в:

// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
{
	"version": "2.0.0", // 配置的版本号。
	"tasks": [ // 任务配置。通常是外部任务运行程序中已定义任务的扩充。
		{
			"type": "npm", // 要自定义的任务类型。
			"script": "watch", // 要自定义的 npm 脚本。
			"problemMatcher": "$tsc-watch", // 要使用的问题匹配程序。可以是一个字符串或一个问题匹配程序定义,也可以是一个字符串数组和多个问题匹配程序。
			"isBackground": true, // 执行的任务是否保持活动状态并在后台运行。
			"presentation": { // 配置用于显示任务输出并读取其输入的面板。
				"reveal": "never" // 控制运行任务的终端是否显示。可按选项 "revealProblems" 进行替代。默认设置为“始终”。
			},
			"group": { // 定义此任务属于的执行组。它支持 "build" 以将其添加到生成组,也支持 "test" 以将其添加到测试组。
				"kind": "build", // 任务的执行组。
				"isDefault": true // 定义此任务是否为组中的默认任务。
			}
		}
	]
}

Пакет

Прежде чем разработка нашего плагина будет завершена, не могли бы вы поделиться опытом с друзьями? Ответ положительный, vscode предоставляет намvsceЧтобы выполнить это требование, мы устанавливаем модуль vsce глобально, а затем используемvsce packageКоманда пытается упаковать:

$ vsce package
 ERROR  Missing publisher name. Learn more: https://code.visualstudio.com/api/working-with-extensions/publishing-extension#publishing-extensions

Ах, почему это все еще неправильно?publisherЧто это такое? ? Смущенный. Не паникуйте, нажмитеСвязьЯ понимаю, что издатель — это удостоверение, которое может публиковать расширения в Visual Studio Code Marketplace. Каждое расширение должноpackage.jsonФайл содержит имя издателя. Если зарегистрированный издатель будет указан позже, здесь мы ставимpublisherУстановить как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.

Эх, треснуло, это все-таки ошибка, притворяемся спокойным, читаем подсказку, оказывается нужно править README.md, да, в шаблоне vscode есть начальный README, надо его перед этим отредактировать упаковка. Попробуйте еще раз после модификацииvsce package:

Наконец, упаковка удалась! Чтобы добиться совершенства, мы, наконец, проведем некоторую работу по оптимизации:

  1. воплощать в жизньvsce packageпри добавлении--no-yarn
  2. существуетpackage.jsonплюсrepositoryполе, чтобы не видеть предупреждений.
  3. Для удобства установим vsce в проект, затем поставимvsce package --no-yarnДобавлено в скрипты npm.
  4. package.jsonплюсlicenseполе.

затем попробуйте еще разyarn packageПросто идеально:

Совет: пакет vsce будет выполняться первымvscode:prepublishЭтот предварительный скрипт идет на компиляцию проекта.

Зафиксировать запись:chore: config vsce package

Принцип упаковки

Если вы последовали во всем пути здесь, вы найдете его в корневом каталоге проектаvsixКонец файла:

Это установочный пакет плагина vscode, не будем спешить сначала его устанавливать, а посмотрим, что это за файл. После попытки извлечь его с помощью инструмента архивации вы получите следующую папку каталога:

Мы видим скомпилированную папкуoutи некоторые другие файлы напрямую сжаты в установочный пакет, вы, должно быть, сочли это умным.cz-config.js,.prettierrc.jsа такжеcommitlint.config.jsТакого рода файл разработки тоже сжат, и запускать плагин совершенно бесполезно, что явно неразумно. На самом деле, как и другие подключаемые системы, vscode также предоставляет.vscodeignoreЧтобы реализовать конфигурацию игнорирования пакета, мы можем игнорировать и переупаковывать вышеупомянутые нерелевантные файлы.

Это принцип? не существует, мы открываемextension.jsнайдет ссылкиvscodeЭтот пакет:

Но в нашем инсталляционном пакете нетnode_modules, то где же существует пакет vscode? Я предполагаю, что это висит в среде узла, читайтеисходный кодПотом я узнал, что был на самом деле прав:

vscode реализует, что перехватчик добавляет vscode во встроенный пакет при загрузке среды Node, что дает преимущество в уменьшении размера плагина.

Так что, если мы используем сторонний плагин? Возьмем в качестве примера часто используемый lodash, переупаковываем его после установки lodash:

$ yarn package
yarn run v1.22.10
$ vsce package --no-yarn
Executing prepublish script 'npm run vscode:prepublish'...

> juejin-posts@0.0.1 vscode:prepublish
> yarn run compile

$ tsc -p ./
This extension consists of 1060 files, out of which 1049 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.72KB)
✨  Done in 5.54s.

В это время нам подсказывают, что файлов более 1000, с большой вероятностьюnode_modulesПапка запакована, разархивируем для свидетелей:

Неудивительно, что метод упаковки vscode по умолчанию заключается в простой компиляции и копировании, а уменьшение размера за счет игнорирования файлов также является каплей в море. И размер расширений vscode имеет тенденцию очень быстро расти. Они написаны в нескольких исходных файлах и зависят от модулей npm. Декомпозиция и повторное использование являются передовыми методами разработки, но при установке и запуске расширений приходится платить. Загрузка 100 маленьких файлов намного медленнее, чем загрузка одного большого файла. Поэтому мы рекомендуем комплектацию. Объединение — это процесс объединения нескольких небольших исходных файлов в один файл.

В JavaScript доступны различные инструменты для создания пакетов. Популярными из них являются rollup.js, Parcel, esbuild и webpack. По умолчанию для официального создания шаблонов можно выбрать только webpack. Мы рекомендуем напрямую использовать более быстрый и надежный esbuild.

Зафиксировать запись:chore: ignore config file when package,chore: add esModuleInterop to tsconfig

Оптимизация упаковки с помощью esbuild

Установить зависимости:

$ yarn add -D esbuild

нпм скрипты:

"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",
}

Примечание. Поскольку часы были изменены на esbuild-watch, поэтому.vscode/tasks.jsonПодраздел scripts в скрипте также необходимо изменить соответствующим образом.

Задачи VSCode:

Теоретически, после того, как мы изменим команду упаковки на esbuild, мы должны установить программу сопоставления проблем в задаче vscode на$esbuild-watch, но vscode запрашивает нераспознанный сопоставитель задач:

Пробовали искать расширения и, конечно, там был плагин esbuild Problem Matchers, мы установили его и добавили"connor4312.esbuild-problem-matchers"прибыть.vscode/extensions.jsonдокументrecommendationsсередина.

Игнорировать файл:

После того, как мы используем esbuild для упаковки, мы упакуем весь код, который мы используем, вout/extension.js, но механизм упаковки vsce таков, что независимо от того, используете вы его или нет, он будетdependenciesв установочный пакет, поэтому нам нужно поместитьnode_modulesИгнорируй это.

Отображение достижений:

Как видно из рисунка, размер инсталляционного пакета сильно уменьшен.

Зафиксировать запись:chore: config esbuild

Интегрировать umijs

Инициализировать проект umi

Используйте скаффолдинг umi, чтобы создать новый в корневом каталоге.webсодержание.

$ mkdir web && cd web

Создайте проект через официальные инструменты:

$ yarn create @umijs/umi-app

Исправлять.umirc.tsКонфигурация:

import { defineConfig, IConfig } from 'umi';

export default defineConfig({
  nodeModulesTransform: {
    type: 'none',
  },
  routes: [{ path: '/', component: '@/pages/index' }],
  fastRefresh: {}, // 开发时可以保持组件状态,同时编辑提供即时反馈。
  history: {
    type: 'memory', // 默认的类型是 `browser`,但是由于 vscode webview 环境不存在浏览器路由,改成 `memory` 和 `hash` 都可以
  },
  devServer: {
    // 需要在 dev 时写文件到输出目录,这样保证开发阶段有 js/css 文件
    writeToDisk: filePath =>
      ['umi.js', 'umi.css'].some(name => filePath.endsWith(name)),
  },
} as IConfig);

Исправлятьpackage.jsonПрисоединяйсяname,version,description:

{
  "name": "web",
  "version": "0.0.0",
  "description": "web for juejin-posts"
}

игнорировать файлы

.gitignore:

Объедините расширение vscode и gitignore, сгенерированный скаффолдом umijs, в следующее:

# 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

.vscodeignore:

Поскольку при упаковке vscode необходимо получить только упакованный продукт umijs, все добавляютweb/**а также!web/dist/**Бесполезные файлы игнорируются.

.vscode/**
.vscode-test/**
out/test/**

src/**
.gitignore
.yarnrc
vsc-extension-quickstart.md
**/tsconfig.json
**/*.map
**/*.ts

.cz-config.js
.prettierrc.js
.commitlintrc.js
**/node_modules/**
yarn-error.log
web/**
!web/dist/**

yarn workspace

Так как наш проект представляет собой смесь расширений vscode и веб-проектов. Чтобы облегчить управление сценариями и зависимостями, мы ввелиyarn workspaceдля управления проектом. в корневом каталогеpackage.jsonДобавьте следующую конфигурацию в:

{
  "private": "true",
  "workspaces": ["web"]
}

отладка

Поскольку наш веб-проект также необходимо скомпилировать, нам нужно изменить vscodelaunch.jsonДобавьте задачу компиляции для веб-проектов. ссылка на конфигурациюappworks.

сначала в корневом каталогеpackage.jsonв скриптах добавить:

{
  "scripts": {
    "web-build": "yarn workspace web run build",
    "web-watch": "yarn workspace web run start"
  },
}

затем изменить.vscode/launch.jsonНастроен как:

// 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": [
    // 复合列表。每个复合可引用多个配置,这些配置将一起启动。
    {
      "name": "Debug Extension", // 复合的名称。在启动配置下拉菜单中显示。
      "configurations": [
        // 将作为此复合的一部分启动的配置名称。
        "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}"
    }
  ]
}

После завершения ввода кода VS нажмитеF5, вы сразу увидитеХост разработки плагиновокно, в котором запущен плагин. В это время вы обнаружите, что консоль сообщает об ошибке ❌:

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'

Причина в том, что контрактная структура проекта umi и расширение vscode содержатsrcсодержание. Так как компиляция плагина vscode и umi идет отдельно, то мы находимся в корневом каталогеtsconfig.jsonгенерал-лейтенантwebКаталог можно игнорировать:

{
  "exclude": ["web"]
}

Теперь вы можете нажатьF5ВидетьХост разработки плагиновВ окне также видны две задачи отладки:

Примечание 📢: выберите задачу отладки Debug Extension вместо Run Extension.

Другие работы по оптимизации

  1. Поскольку он основан на рабочей области пряжи, мы объединяем общие зависимости
  2. Объединить конфигурацию Eslint и использовать@luozhu/eslint-config-react-typescrip
  3. Объединить конфигурацию Editorconfig и Prettier
  4. Добавить кprestartа такжеprebuild script
  5. настраиватьHTML=none umi build

Зафиксировать запись:chore: config umijs

Основные понятия разработки плагинов VSCode

Прежде чем начать возможность разработки WebView, нам нужно посмотреть на основные концепции VSCode Plug-in Development. Для того, чтобы иметь глобальное понимание, мы сначала посмотрим на основную структуру каталогов наших текущих проектов:

.
├── CHANGELOG.md # 基于 standard-version 生成的更新日志文件
├── README.md
├── package.json # vscode 包配置文件,诸如插件 LOGO、名字、描述、注册激活事件
├── src
│   └── extension.ts # 插件入口文件,暴露 activate 方法用于注册命令和初始化一些配置,暴露 deactivate 方法用于插件关闭前执行清理工作
├── tsconfig.json # vscode 的编译配置
├── web # 基于 umi 的 web,也是我们后边 webview 要承载的内容
└── yarn.lock

Как видно из структуры каталогов, ключевые файлыpackage.jsonа такжеextension.ts, мы возьмем команду helloWorld в качестве примера, чтобы представить три основные концепции плагина vscode.

1. Активировать событие

событие активациивpackage.jsonсерединаactivationEventsОбъект массива объявлений полей JSON. Чтобы зарегистрировать команду helloWorld, первым шагом является регистрация события активации.Существует много типов событий активации.Событие активации команды регистрацииonCommand:

{
  "activationEvents": ["onCommand:juejin-posts.helloWorld"]
}

2. Опубликовать конфигурацию контента

Конфигурация публикации контента (то есть элементы конфигурации, предоставляемые VS Code для расширений плагинов)package.jsonизcontributesполе, где вы можете зарегистрировать различные элементы конфигурации для расширения возможностей VS Code. Событие активации helloWorld, которое мы зарегистрировали на предыдущем шаге, просто сообщает vscode, что оно может пройти.juejin-posts.helloWorldсработала команда. нам нужно большеcontributes.commandsзарегистрируйтесь в нашемjuejin-posts.helloWorldЗаказ:

{
  "contributes": {
    "commands": [
      {
        "command": "juejin-posts.helloWorld",
        "title": "Hello World"
      }
    ]
  }
}

3. VS Code API

VS Code APIЭто серия API-интерфейсов Javascript, предоставляемых VS Code для использования плагинами. Благодаря возможностям первых двух основных концепций мы зарегистрировали команды и события, поэтому следующим шагом должна быть регистрация обратных вызовов событий. Обратные вызовы событий передаются в vscodevscode.commands.registerCommandфункция для регистрации, следующий 👇🏻 наш входной файлsrc/extension.tsзарегистрирован вjuejin-posts.helloWorldЗаказ.

// vscode 这个模块包含了 VS Code 扩展的 API
import vscode from 'vscode';

// 这个方法当你的扩展激活时调用,扩展会在命令首次执行时激活
export function activate(context: vscode.ExtensionContext) {
  // 当你的扩展被激活时,这行代码将只被执行一次
  //
  // 使用 console.log 输出日志信息或使用 console.error 输出错误信息。
  //
  console.log('Congratulations, your extension "juejin-posts" is now active!');

  // 入口命令已经在 package.json 文件中定义好了,现在调用 registerCommand 方法
  // registerCommand 中的参数必须与 package.json 中的 command 保持一致
  const disposable = vscode.commands.registerCommand('juejin-posts.helloWorld', () => {
    // 把你的代码写在这里,每次命令执行时都会调用这里的代码
    // 给用户显示一个消息提示
    vscode.window.showInformationMessage('Hello World from Juejin Posts!');
  });

  context.subscriptions.push(disposable);
}

// 当你的扩展被停用时,这个方法被调用。
export function deactivate() {}

Интегрировать веб-просмотр

команда регистрации

1,package.jsonсобытие активации (activationEvents) добавить"onCommand:juejin-posts.start"

2,package.jsonЗаказ(commands) Добавить:

{
  "command": "juejin-posts.start",
  "title": "start",
  "category": "Juejin Posts"
}

3.src/extension.tsкоманда регистрации

context.subscriptions.push(
  vscode.commands.registerCommand('juejin-posts.start', () => {
    // Truth is endless. Keep coding...
  })
)

Создать панель веб-просмотра

Создать пустую панель

import vscode from 'vscode';

// 创建并显示新的webview
const panel = vscode.window.createWebviewPanel(
  'juejin-posts', // 只供内部使用,这个 webview 的标识
  'Juejin Posts', // 给用户显示的面板标题
  vscode.vscode.ViewColumn.One, // 给新的 webview 面板一个编辑器视图
  {
    enableScripts: true, // 启用 javascript 脚本
    retainContextWhenHidden: true, // 隐藏时保留上下文
  } // webview 面板的内容配置
);

мы использовалиwindow.createWebviewPanelAPI создал панель webview, теперь пробуем запуститьjuejin-posts.startВы можете открыть панель веб-просмотра:

Установить содержимое для панели

Выше мы создали пустую панель, так как мы добавляем контент на панель? мы можем использоватьpanel.webview.htmlЧтобы установить содержимое HTML:

function getWebviewContent() {
  return `
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Juejin Posts</title>
        <style>
          html, body {
            padding: 0px;
            height: 100vh;
            position: relative;
            margin: 0;
            padding: 0;
            overflow: hidden;
          }
          #yoyo {
            position: absolute;
            bottom: 50px;
            right: -90px;
            opacity: 0;
            transition: .25s ease-in-out
          }
          #yoyo:hover {
            opacity: 1;
            right: 0;
          }
        </style>
    </head>
    <body>
      <a href="https://juejin.cn"><img id="yoyo" src="https://cdn.jsdelivr.net/gh/youngjuning/images/20210817163229.png" width="100" /></a>
    </body>
    </html>
  `;
}
...
// 给 webview panel 设置 HTML 内容
panel.webview.html = getWebviewContent();
...

повторное использованиеjuejin-posts.startКоманда приставать к капитану Йойо:

Ограничьте представление веб-просмотра одним

export function activate(context: vscode.ExtensionContext) {
  // 追踪当前 webview 面板
  let currentPanel: vscode.WebviewPanel | undefined = undefined;

  context.subscriptions.push(
    vscode.commands.registerCommand('juejin-posts.start', () => {
      // 获取当前活动的编辑器
      const columnToShowIn = vscode.window.activeTextEditor
        ? vscode.window.activeTextEditor.viewColumn
        : undefined;

      if (currentPanel) {
        // 如果我们已经有了一个面板,那就把它显示到目标列布局中
        currentPanel.reveal(columnToShowIn);
      } else {
        // 不然,创建一个新面板
        currentPanel = vscode.window.createWebviewPanel();
        // 当前面板被关闭后重置
        currentPanel.onDidDispose(
          () => {
            currentPanel = undefined;
          },
          null,
          context.subscriptions
        );
      }
    })
  );
}
  • vscode.window.activeTextEditor: получить текущий активный текстовый редактор
  • currentPanel.reveal():передачаreveal()Или перетащите панель веб-просмотра на новый макет редактирования.

Установить значок

// 设置 Logo
panel.iconPath = vscode.Uri.file(
  path.join(context.extensionPath, 'assets', 'icon-juejin.png')
);

В расширении vscode нам нужно передатьvscode.Uri.fileспособ получить путь к ресурсу на диске.

webview получает Uri контента

вы должны использоватьasWebviewUriУправление ресурсами плагина. не жестко кодироватьvscode-resource://, вместо этого используйтеasWebviewUriУбедитесь, что ваши плагины хорошо работают в облачной среде.

существует@luozhu/vscode-utilsВ , мы инкапсулируем доступ к пути к локальному ресурсу:

// 获取内容的 Uri
const getDiskPath = (fileName: string) => {
  return webviewPanel.webview.asWebviewUri(
    vscode.Uri.file(path.join(context.extensionPath, rootPath, 'dist', fileName))
  );
};

Разработка веб-просмотра с umi

В предыдущем разделе мы ознакомились с созданием панелей веб-просмотра, дразня капитана Йойо, В этом разделе мы увидим, как использовать umijs для замены HTML-контента.

panel.webview.htmlСодержимое на самом деле представляет собой обычный код HTML+JavaScript+CSS. Вы можете использовать любую фронтенд-технологию для написания своего контента, такую ​​как jquery, bootstrap, Vue и React. Хотя пример в этой статье основан на umijs для разработки содержимого веб-просмотра, другие технические принципы остаются теми же, и в будущем Luozhu также предоставит несколько технических каркасов для разработки веб-просмотра vscode.

Инкапсулировать метод получения упакованных продуктов umijs

мы знаемumi buildкоманда будет вweb/distГенерируем три файла index.html, umi.js, umi.css, преобразуем предыдущий метод getWebviewContent в соответствии с index.html следующим образом:

import vscode from 'vscode';
import path from 'path';

/**
 * 获取基于 umijs 的 webview 内容
 * @param context 扩展上下文
 * @param webviewPanel webview 面板对象
 * @param rootPath webview 所在路径,默认 web
 * @param umiVersion umi 版本
 * @returns string
 */
export const getUmiContent = (
  context: vscode.ExtensionContext,
  webviewPanel: vscode.WebviewPanel,
  umiVersion?: string,
  rootPath = 'web'
) => {
  // 获取磁盘上的资源路径
  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>
  `;
};

Совет: описанный выше метод я инкапсулировал в@luozhu/vscode-utilsв.

Мы используем getUmiContent для воссоздания предыдущего кода:

import { getUmiContent } from '@luozhu/vscode-utils';
...
panel.webview.html = getUmiContent(context, panel, '3.5.17');

Оптимизированная упаковка

Поскольку мы инкапсулировалиgetUmiContentметод,umi buildСгенерированный index.html бесполезен, мы можем использоватьHTML=none umi buildКоманда не создает файл index.html при упаковке.

Кроме того, в настоящее времяmfsu umijs не поддерживает метод writeToDisk, если он будет поддерживаться позже, вы можете использовать mfsu для оптимизации скорости отладки.

Большинство задач по созданию веб-панели относительно повторяющиеся.@luozhu/vscode-utilsупакованныйcreateUmiWebviewPanelметод.

Контент веб-просмотра темы

Веб-просмотр может изменить свой стиль в зависимости от текущей темы VS Code и CSS. VS Code делит темы на 3 категории и добавляет к элементу body специальное имя класса для обозначения текущей темы.Мы глобально добавляем следующие стили в 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);
}

Поскольку большая часть этой части адаптации носит общий характер, я также включил ее в@luozhu/vscode-utilsизgetUmiContentбинго.

webview vscode взаимодействовать с

Выполнить скрипт в веб-просмотре

Веб-просмотр в vscode по сути является iframe, поэтому мы можем выполнять скрипты в веб-просмотре, но JavaScript по умолчанию отключен в веб-просмотре в vscode, мы вызываемcreateWebviewPanelПередано, когда APIenableScripts: trueВот и все.

Плагины передают информацию в webview

Сценарий веб-просмотра может делать все то же, что и обычный скрипт веб-страницы, но веб-просмотр работает в своем собственном контексте, и скрипт не может получить доступ к API VS Code. Нам нужно передать информацию с помощью событий postMessage. В vscode мы можем использовать на стороне vscodeWebview.postMessageОпубликуйте событие и отправьте любые сериализованные данные JSON на стороне веб-просмотра.window.addEventListener('message' event => { ... })для обработки этой информации:

VScode сторона:

// 注册一个新的命令
context.subscriptions.push(
  vscode.commands.registerCommand('juejin-me.author', () => {
    if (!currentPanel) {
      return;
    }

    // 把信息发送到 webview
    // 你可以发送任何序列化的 JSON 数据
    currentPanel.webview.postMessage({ method: 'showAuthor' });
  })
);

сторона веб-просмотра:

import { Modal } from 'antd';
...
window.addEventListener('message', event => {
  const message = event.data;
  switch (message.method) {
    case 'showAuthor': {
      Modal.info({
        title: '洛竹',
        content: (
          <div>
            大家好,我是洛竹🎋一只住在杭城的木系前端🧚🏻‍♀️,如果你喜欢我的文章📚,可以通过
            <a href="https://juejin.cn/user/325111174662855/posts">点赞</a>帮我聚集灵力⭐️。
          </div>
        ),
        okText: <a href="https://juejin.cn/user/325111174662855/posts">点赞 o( ̄▽ ̄)d</a>,
      });
      break;
    }
    default:
      break;
  }
});

Эффект:

webview передает информацию плагину

Принцип обратной передачи информации webview плагину тот же, но из-за контекстного ограничения webview мы можем передать толькоacquireVsCodeApiФункция получает кастрированную версию объекта API VS Code, который имеет кастрированный объект.postMessageФункции могут использоваться нами для отправки событий. УведомлениеacquireVsCodeApiЕго можно вызвать только один раз в каждом сеансе, а повторные вызовы приведут к ошибке. На стороне плагина вы можете передатьWebview.onDidReceiveMessageОбработать информацию, переданную веб-просмотром. Напишем звонок в webviewvscode.window.showInformationMessageпример:

сторона веб-просмотра:

const vscode = acquireVsCodeApi();
vscode.postMessage({
  method: 'showMessage',
  params: {
    text: `为人民服务`,
  },
});

Сторона плагина:

// 处理 webview 中的信息
currentPanel.webview.onDidReceiveMessage(
  message => {
    if (message.method === 'showMessage') {
      vscode.window.showInformationMessage(message.params.content);
    }
  },
  undefined,
  context.subscriptions
);

Эффект:

интерфейс запроса в веб-просмотре

Сначала я думал, что это легкая работа, но я был в отчаянии после того, как долгое время сталкивался с междоменными решениями.Практика разработки плагина (расширения) VSCode WebViewВ этой статье я, наконец, узнал, что vscode webview не может отправлять ajax-запросы, все ajax-запросы являются междоменными, потому что сам webview не имеет хоста.

Люди разделились, какого черта, наша основная потребность — запросить интерфейс Nuggets, чтобы получить наш список статей, так можем ли мы это сделать? Ответ — да, на самом деле мы по-прежнему используем упомянутый выше механизм связи, чтобы передать задачу запроса интерфейса в vscode для обработки, а затем позволить vscode передать данные.postMessageВернемся к нам, больше говорить бесполезно, давайте посмотрим на код:

сторона веб-просмотра:

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);
    }
  });
}, []);

VScode сторона:

// 处理 webview 中的信息,并返回接口请求的数据
currentPanel.webview.onDidReceiveMessage(
  async message => {
    const data = await events(message);
    currentPanel?.webview.postMessage({ data });
  },
  undefined,
  context.subscriptions
);

@luozhu/vscode-channel

Раньше мы знали, как использоватьWebview.postMessage,Webview.onDidReceiveMessage,acquireVsCodeApi().postMessageа такжеwindow.addEventListenerОн может удовлетворить различные коммуникационные потребности, а затем@luozhu/vscode-channelЧто это такое?

кjs-channelвдохновленный,@luozhu/vscode-channelВ основном он инкапсулирует процесс взаимодействия между webview и vscode.call,bindЭтот метод сглаживает различия API и уменьшает количество повторяющегося кода. Среди них относятся appworks и cs-channel для использования uuid для обеспечения надежности взаимодействия. Говорить дешево, покажи код:

сторона веб-просмотра:

// 创建 channel 对象
const channel = new Channel();
const getData = async () => {
  // 发起一个请求,并等待其返回数据
  const { payload } = await channel.call({ method: 'queryPosts' });
  console.log(payload);
};

В веб-просмотре, потому что методAcquireVsCodeApi можно вызвать только один раз, а затем его нужно использовать в нескольких местах, поэтому мы находимся вwev/src/layouts/index.tsСоздавать один раз и монтировать наwindowбольше подходит для объекта.

VScode сторона:

// vscode 侧的 channel 需要依赖上下文和 WebviewPanel 实例
const channel = new Channel(context, currentPanel);
// 绑定一个回调函数,一般只需要创建一个,然后根据约定做分发即可
channel.bind(async message => {
  const { eventType, method, params } = message;
  // 实际发起请求获取数据的地方
  const data = await events[eventType][method](params);
  // 这里将获取的数据直接返回即可,channel 内部会进行消息合并和回传。
  // 如果只是执行一个功能,不写 return 语句即可,内部会进行判断降级成单工通信。
  return data;
});

интернационализация vscode

Все мы знаем, что в vscode можно переключать языковую среду, а отличное расширение vscode должно поддерживать как минимум китайский и английский языки. А поддержка интернационализации позволяет вашей подключаемой аудитории напрямую преодолевать национальные границы. Интернационализация vscode разделена на три части: одна — интернационализация конфигурации, другая — интернационализация кода, а третья — интернационализация umijs в веб-просмотре. В этой главе мы подробно рассмотрим, как реализовать интернационализацию в vscode.

Настроить интернационализацию

Мы уже знаем, что конфигурация в vscode находится вpackage.json, в то время как интернационализация конфигурации по соглашению вpackage.nls.jsonа такжеpackage.nls.zh-cn.jsonнаписано в этом файле. Например, если мы хотим настроить китайскую и английскую версии команд в китайской и английской среде, мы можемpackage.nls.jsonзаписывать:

{
  "contributes.category.juejin-me": "Juejin Me"
}

существуетpackage.nls.zh-cn.jsonНапишите:

{
  "contributes.category.juejin-me": "掘金一下"
}

потомpackage.jsonзаписывать:

{
  "contributes": {
    "commands": [
      {
        ...
        "category": "%contributes.category.juejin-me%"
      },
    ]
  }
}

Интернационализация в коде

Рекомендуется использовать предоставленный Luozhu кодvscode-nls-i18n, метод использования также очень прост, конфигурация такая же, как и в предыдущем разделе, вsrc/extension.tsиспользуется вinitинициализация метода, затем используйтеlocalizeМетоды достижения интернационализации:

import { init, localize } from 'vscode-nls-i18n';
export function activate(context: vscode.ExtensionContext) {
  init(context.extensionPath); // 初始化国际化配置。只用在扩展激活时初始化一次
  console.log(localize('extension.activeLog')); // 之后就可以在各个文件中使用。
}

umijs интернационализация

Интернационализация umijs должна использовать@umijs/plugin-localeПоддержка плагинов, этот плагин инкапсулируетreact-intl, конфигурация следующая:

1. Конфигурация в .umirc.tslocal

locale: {}

2. ВsrcСоздано в каталогеlocalesи создатьen.tsилиzh-CN.ts

// src/locales/en.js
export default {
  WELCOME_TO_UMI_WORLD: "welcome to umi's world",
};
// src/locales/zh-CN.js
export default {
  WELCOME_TO_UMI_WORLD: '欢迎光临  umi  的世界',
};

3. Используйте интернационализацию

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>
   )
}

4. Переключить язык

Для переключения языков нам нужно использоватьsetLocaleметод, следует отметить, что мы передаем второй параметр этому методуfalseДля достижения динамического переключения без обновления.

import { setLocale } from 'umi';
// 不刷新页面
setLocale('zh-CN', false);

Но когда пора переключать языки? Время переключения — это когда меняется наша локаль. Фактически, в среде vscode webview при использованииConfig display languageПосле того, как метод переключит локаль, он попросит vscode перезапуститься. То есть нам нужно установить локаль только один раз при создании веб-просмотра. Поскольку передавать значения между vscode и webview слишком сложно, мы выбираемgetUmiHTMLContentзанятиеvscode.env:

<script>
  window.vscodeEnv = ${JSON.stringify(vscode.env)}
</script>

Тогда мыweb/src/layouts/index.tsВы можете установить его в:

setLocale(window.vscodeEnv.language, false);

«Самородок» для расширения основной реализации

Вдохновение приходит из реальности.Как активный пользователь Nuggets, здесь синхронизируются почти все статьи и заметки. Когда какие-то знания забывают проконсультироваться или скопировать код, мне приходится искать свои статьи в Nuggets. Но поиск наггетсов идет по всему сайту, и даже если вы добавите в поиск собственное имя, будет много неактуальных записей. Название «Самородки» похоже на функцию плагина.Если вы хотите найти свои собственные наггетсы, вы можете открыть плагин «Самородки» для поиска.

На самом деле, чтобы искать только свои собственные статьи, я также подумал о разработке плагина для Chrome для достижения этой цели. Но, учитывая рынок и удобство, я наконец решил разработать плагин vscode для реализации этого вдохновения. Эта глава представляет собой основную логику объединения предыдущего опыта для реализации «Самородков».

juejin-me.startЗаказ

Открытый канал связи на стороне vscode

боковой проход vscodechannel.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'
    );
    // 处理 webview 中的信息
    channel = new Channel(context, currentPanel);
    channel.bind(async message => {
      const { eventType, method, params } = message;
      // 根据事件类型、方法、参数来完成一次 api 调用,内置的 eventType 有 request、command 和 variable。
      const data = await events[eventType][method](params);
      return data;
    }, vscode);
  })
);

Примечание. Нам не нужно указывать имя прослушиваемого события, надежность и глобальная уникальность будут гарантироваться внутри на основе идентификатора события.

Зарегистрироваться на мероприятия

events/index.ts:

import requests from './requests';

export default {
  request: requests,
};

events/requests:

import vscode from 'vscode';
import request from '../utils/request';

const queryPosts = async (params: { cursor: string }): Promise<any> => {
  // 这里我们根据 vscode 配置动态取的用户 id
  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,
};

utils/request:

Это просто инкапсулирует объект запроса на основе axios.

/* eslint-disable no-param-reassign */
import axios from 'axios';
import vscode from 'vscode';
import qs from 'qs';

// 中文文档: http://t.cn/ROfXFuj
// 创建实例
const request = axios.create({
  baseURL: 'https://api.juejin.cn/content_api/v1/',
  timeout: 10000,
});

// 添加请求拦截器
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);
  }
);

// 添加响应拦截器
request.interceptors.response.use(
  response => {
    const { data } = response;
    return data;
  },
  error => {
    vscode.window.showErrorMessage(error.message);
    return Promise.reject(error);
  }
);

export default request;

Интерфейс вызова в веб-просмотре

канал вweb/src/layouts/index.tsxИнициализированный и смонтированный в окне, мы находимся вweb/src/pages/index.tsxвызыватьwindow.channel.callУказанный интерфейс может быть вызван. Так как нам нужен нечеткий поиск по всем статьям, нам нужно запросить все данные сразу при инициализации страницы.

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(['全部', ...tempData.map(item => item.category.category_name)]));
      tempData = [];
    } else {
      cursor += 10;
      getData();
    }
  };
}

Более конкретные детали реализации — это некоторая логика написания страницы, которая не является предметом этой статьи. Заинтересованные студенты могут проверить это непосредственно.исходный код.

Настроить идентификатор самородков

Декларативная конфигурация:

Для настройки vscode нам понадобится помощь package.jsoncontributes.configurationнаш идентификатор слепка представляет собой строку, поэтому объявите его следующим образом:

{
  "contributes": {
    "configuration": {
      "title": "%configuration.title%",
      "type": "object",
      "properties": {
        "juejin-me.userId": {
          "type": "string",
          "default": "325111174662855",
          "description": "%configuration.properties.juejin-me.userId%"
        }
      }
    }
  }
}

команда для изменения конфигурации:

Также можно разрешить пользователям открывать настройки для изменения конфигурации, но для удобства пользователей мы предоставляемjuejin-me.configUserIdКоманда, давайте посмотрим на реализацию команды:

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);
  })
);
  • vscode.window.showInputBox: открывает поле ввода, предлагающее пользователю ввести идентификатор пользователя Nuggets.
  • vscode.workspace.getConfiguration: получить объект конфигурации рабочей области
  • WorkspaceConfiguration.update: обновить значение конфигурации.
  • InputBoxOptions.validateInput: необязательная функция, которая вызывается для проверки ввода и запроса пользователя

Отображение эффекта плагина

Если вы заинтересованы, вы также можете напрямую выполнить поиск «Наггетс» в расширении, чтобы испытать его на себе.

пасхальные яйца

@luozhu/create-vscode-webview

В этой статье есть много лучших практик.Чтобы уменьшить дублирование работы при создании новых проектов в будущем, Луо Чжу извлек простой шаблон. Копайте друзья, используйте напрямуюyarn create @luozhu/vscode-webview myvscodeВы можете создать собственное расширение vscode. Обратитесь к некоторым практикам в этой статье и добавьте немного своего творчества, чтобы завершить отличное расширение vscode на основе веб-просмотра.

Word Count Juejin

Чтобы поблагодарить платформу Nuggets и ее друзей за поддержку, я написал расширение VS Code для подсчета слов в файле Markdown, специально адаптированное для Nuggets, и количество слов будет отображаться в строке состояния в режиме реального времени. По сравнению с официальным подсчетом слов vscode, мы поддерживаем статистику подсчета слов на китайском языке.По сравнению с подсчетом слов CJK, мы поддерживаем смешанный китайский и английский языки. Если вам также нравится использовать возможности редактирования Markdown в VS Code, вы не должны пропустить этот плагин от Luozhu, пожалуйста, найдите загрузку:

Если вы все еще колеблетесь, стоит ли скачивать, посмотрите статистическое сравнение трех плагинов.i love juejin. 我爱掘金Эта строка проверяет функциональность трех плагинов:

Word Count Word Count CJK Word Count Juejin
4 слова 4 слова 7 слов
Китайский как слово игнорировать английский язык 4 символа на китайском языке и 3 символа на английском языке, шаблон в самый раз

vscode api cn

В процессе изучения и разработки плагинов vscode самой большой проблемой является отсутствие перевода документации API. Даже если вы стиснете зубы и прочитаете оригинальную англоязычную документацию по API, опыт чтения будет очень плохим. Чтобы помочь себе и отдать должное сообществу, я ихолодная траваПодождите, пока мелкие партнеры решат перевести объявление типа vscode api и использовать Typedoc для его переноса, и мы также выведем его после завершения.@types/vscode-cnВместо этого введите пакет@types/vscodeДальнейшее удобство для разработчиков плагинов vscode. Статус членов команды:

Перевод - это карьера с помощью рыцарского духа, добро пожаловать больше партнеров, чтобы присоединиться к нам маленьким. Вы можете просматриватьсклада такжеОфициальный сайтУзнайте о специфике.

постскриптум

Такую длинную статью пыталась написать впервые.Вот уже полмесяца то и дело.Ответственное отношение к читателям,практика в статье проверена неоднократно и обсуждена с коллегами и друзьями. Конечно, в разработке плагинов vscode много концепций и API, и объяснить их все в одной статье сложно. Если вы заинтересованы, вы можете сообщить Луо Чжу в области комментариев, и я могу продолжать обновлять учебник в этом отношении.

последние хорошие статьи

Эта статья была впервые опубликована в «Колонке Наггетс» и синхронизирована с пабликом «Программа Лайф».