├── .editorconfig ├── .gitattributes ├── .gitignore ├── .vscode └── settings.json ├── CHANGELOG.md ├── LICENSE ├── README.md ├── README.zh_CN.md ├── commitlint.config.mjs ├── env.d.ts ├── eslint.config.mjs ├── examples ├── react │ ├── .vscode │ │ ├── launch.json │ │ └── tasks.json │ ├── README.md │ ├── eslint.config.mjs │ ├── extension │ │ ├── env.d.ts │ │ ├── index.ts │ │ └── views │ │ │ ├── helper.ts │ │ │ └── panel.ts │ ├── index.html │ ├── package.json │ ├── src │ │ ├── App.css │ │ ├── App.tsx │ │ ├── main.tsx │ │ ├── utils │ │ │ ├── index.ts │ │ │ └── vscode.ts │ │ └── vite-env.d.ts │ ├── tsconfig.json │ ├── tsconfig.node.json │ └── vite.config.ts ├── vue-esm │ ├── .vscode │ │ ├── launch.json │ │ └── tasks.json │ ├── README.md │ ├── eslint.config.mjs │ ├── extension │ │ ├── env.d.ts │ │ ├── index.ts │ │ └── views │ │ │ ├── helper.ts │ │ │ └── panel.ts │ ├── index.html │ ├── package.json │ ├── src │ │ ├── App.vue │ │ ├── main.ts │ │ ├── utils │ │ │ ├── index.ts │ │ │ └── vscode.ts │ │ ├── vite-env.d.ts │ │ └── window.d.ts │ ├── tsconfig.json │ ├── tsconfig.node.json │ └── vite.config.ts ├── vue-import │ ├── .vscode │ │ ├── launch.json │ │ └── tasks.json │ ├── README.md │ ├── eslint.config.mjs │ ├── extension │ │ ├── env.d.ts │ │ ├── index.ts │ │ └── views │ │ │ ├── helper.ts │ │ │ ├── index.ts │ │ │ ├── panel.ts │ │ │ └── panel2.ts │ ├── index.html │ ├── index2.html │ ├── package.json │ ├── src │ │ ├── App.vue │ │ ├── App2.vue │ │ ├── main.ts │ │ ├── main2.ts │ │ ├── utils │ │ │ ├── index.ts │ │ │ └── vscode.ts │ │ └── vite-env.d.ts │ ├── tsconfig.json │ ├── tsconfig.node.json │ └── vite.config.ts └── vue │ ├── .vscode │ ├── launch.json │ └── tasks.json │ ├── README.md │ ├── eslint.config.mjs │ ├── extension │ ├── env.d.ts │ ├── index.ts │ └── views │ │ ├── helper.ts │ │ └── panel.ts │ ├── index.html │ ├── package.json │ ├── src │ ├── App.vue │ ├── main.ts │ ├── utils │ │ ├── index.ts │ │ └── vscode.ts │ ├── vite-env.d.ts │ └── window.d.ts │ ├── tsconfig.json │ ├── tsconfig.node.json │ └── vite.config.ts ├── lint-staged.config.mjs ├── package.json ├── pnpm-lock.yaml ├── pnpm-workspace.yaml ├── simple-git-hooks.mjs ├── src ├── constants.ts ├── index.ts ├── logger.ts ├── types.ts ├── utils.ts └── webview │ ├── client.ts │ ├── global.d.ts │ ├── template.html │ ├── webview.ts │ └── window.d.ts ├── stylelint.config.mjs ├── tsconfig.json ├── tsconfig.web.json └── tsup.config.ts /.editorconfig: -------------------------------------------------------------------------------- 1 | # 🎨 editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | charset = utf-8 7 | end_of_line = lf 8 | indent_style = space 9 | indent_size = 2 10 | trim_trailing_whitespace = true 11 | insert_final_newline = true 12 | 13 | [*.md] 14 | insert_final_newline = false 15 | trim_trailing_whitespace = false 16 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ## GITATTRIBUTES FOR WEB PROJECTS 2 | # 3 | # These settings are for any web project. 4 | # 5 | # Details per file setting: 6 | # text These files should be normalized (i.e. convert CRLF to LF). 7 | # binary These files are binary and should be left untouched. 8 | # 9 | # Note that binary is a macro for -text -diff. 10 | ###################################################################### 11 | 12 | ## AUTO-DETECT 13 | ## Handle line endings automatically for files detected as 14 | ## text and leave all files detected as binary untouched. 15 | ## This will handle all files NOT defined below. 16 | * text eol=lf 17 | 18 | ## SOURCE CODE 19 | *.bat text eol=crlf 20 | *.coffee text 21 | *.css text 22 | *.htm text 23 | *.html text 24 | *.inc text 25 | *.ini text 26 | *.java text 27 | *.js text 28 | *.json text 29 | *.jsx text 30 | *.less text 31 | *.od text 32 | *.onlydata text 33 | *.php text 34 | *.pl text 35 | *.py text 36 | *.rb text 37 | *.sass text 38 | *.scm text 39 | *.scss text 40 | *.sh text eol=lf 41 | *.sql text 42 | *.styl text 43 | *.tag text 44 | *.ts text 45 | *.tsx text 46 | *.xml text 47 | *.xhtml text 48 | 49 | ## DOCKER 50 | *.dockerignore text 51 | Dockerfile text 52 | 53 | ## DOCUMENTATION 54 | *.markdown text 55 | *.md text 56 | *.mdwn text 57 | *.mdown text 58 | *.mkd text 59 | *.mkdn text 60 | *.mdtxt text 61 | *.mdtext text 62 | *.txt text 63 | AUTHORS text 64 | CHANGELOG text 65 | CHANGES text 66 | CONTRIBUTING text 67 | COPYING text 68 | copyright text 69 | *COPYRIGHT* text 70 | INSTALL text 71 | license text 72 | LICENSE text 73 | NEWS text 74 | readme text 75 | *README* text 76 | 77 | ## TEMPLATES 78 | *.dot text 79 | *.ejs text 80 | *.haml text 81 | *.handlebars text 82 | *.hbs text 83 | *.hbt text 84 | *.jade text 85 | *.latte text 86 | *.mustache text 87 | *.njk text 88 | *.phtml text 89 | *.tmpl text 90 | *.tpl text 91 | *.twig text 92 | 93 | ## LINTERS 94 | .csslintrc text 95 | .eslintrc text 96 | .jscsrc text 97 | .jshintrc text 98 | .jshintignore text 99 | .stylelintrc text 100 | 101 | ## CONFIGS 102 | *.bowerrc text 103 | *.cnf text 104 | *.conf text 105 | *.config text 106 | .browserslistrc text 107 | .editorconfig text 108 | .gitattributes text 109 | .gitconfig text 110 | .gitignore text 111 | .htaccess text 112 | *.npmignore text 113 | *.yaml text 114 | *.yml text 115 | browserslist text 116 | Makefile text 117 | makefile text 118 | 119 | ## HEROKU 120 | Procfile text 121 | .slugignore text 122 | 123 | ## GRAPHICS 124 | *.ai binary 125 | *.bmp binary 126 | *.eps binary 127 | *.gif binary 128 | *.ico binary 129 | *.jng binary 130 | *.jp2 binary 131 | *.jpg binary 132 | *.jpeg binary 133 | *.jpx binary 134 | *.jxr binary 135 | *.pdf binary 136 | *.png binary 137 | *.psb binary 138 | *.psd binary 139 | *.svg text 140 | *.svgz binary 141 | *.tif binary 142 | *.tiff binary 143 | *.wbmp binary 144 | *.webp binary 145 | 146 | ## AUDIO 147 | *.kar binary 148 | *.m4a binary 149 | *.mid binary 150 | *.midi binary 151 | *.mp3 binary 152 | *.ogg binary 153 | *.ra binary 154 | 155 | ## VIDEO 156 | *.3gpp binary 157 | *.3gp binary 158 | *.as binary 159 | *.asf binary 160 | *.asx binary 161 | *.fla binary 162 | *.flv binary 163 | *.m4v binary 164 | *.mng binary 165 | *.mov binary 166 | *.mp4 binary 167 | *.mpeg binary 168 | *.mpg binary 169 | *.swc binary 170 | *.swf binary 171 | *.webm binary 172 | 173 | ## ARCHIVES 174 | *.7z binary 175 | *.gz binary 176 | *.rar binary 177 | *.tar binary 178 | *.zip binary 179 | 180 | ## FONTS 181 | *.ttf binary 182 | *.eot binary 183 | *.otf binary 184 | *.woff binary 185 | *.woff2 binary 186 | 187 | ## EXECUTABLES 188 | *.exe binary 189 | *.pyc binary 190 | *.jar binary 191 | *.dll binary 192 | 193 | ## DOCUMENT 194 | *.doc binary 195 | *.docx binary 196 | *.xls binary 197 | *.xlsx binary 198 | *.ppt binary 199 | *.pptx binary 200 | 201 | ## EXTENSIONS 202 | *.crx binary 203 | *.xpi binary 204 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # logs 2 | *.log 3 | lerna-debug.log* 4 | logs 5 | npm-debug.log* 6 | pnpm-debug.log* 7 | yarn-debug.log* 8 | yarn-error.log* 9 | 10 | # diagnostic reports (https://nodejs.org/api/report.html) 11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 12 | 13 | # runtime data 14 | *.pid 15 | *.pid.lock 16 | *.seed 17 | pids 18 | 19 | # dependency directory 20 | bower_components 21 | jspm_packages 22 | node_modules 23 | web_modules 24 | 25 | # cache 26 | .cache 27 | .npm 28 | .tmp 29 | .temp 30 | .eslintcache 31 | .stylelintcache 32 | tsconfig.tsbuildinfo 33 | 34 | # environment 35 | *.local 36 | 37 | # macOS 38 | .DS_Store 39 | 40 | # package files 41 | *.7z 42 | *.gz 43 | *.gzip 44 | *.rar 45 | *.tar 46 | *.tar.* 47 | *.tgz 48 | *.zip 49 | 50 | # IDEA 51 | .idea 52 | 53 | # vscode 54 | .history 55 | 56 | # build output 57 | build 58 | dist 59 | release 60 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | // Disable the default formatter, use eslint instead 3 | "prettier.enable": false, 4 | "editor.formatOnSave": false, 5 | // Auto fix 6 | "editor.codeActionsOnSave": { 7 | "source.fixAll.eslint": "explicit", 8 | "source.organizeImports": "never" 9 | }, 10 | // Silent the stylistic rules in you IDE, but still auto fix them 11 | "eslint.rules.customizations": [ 12 | { "rule": "style/*", "severity": "warn" }, 13 | { "rule": "format/*", "severity": "warn" }, 14 | { "rule": "jsonc/indent", "severity": "warn" }, 15 | { "rule": "*-indent", "severity": "warn" }, 16 | { "rule": "*-spacing", "severity": "warn" }, 17 | { "rule": "*-spaces", "severity": "warn" }, 18 | { "rule": "*-order", "severity": "warn" }, 19 | { "rule": "*-dangle", "severity": "warn" }, 20 | { "rule": "*-newline", "severity": "warn" }, 21 | { "rule": "*quotes", "severity": "warn" }, 22 | { "rule": "*semi", "severity": "warn" } 23 | ], 24 | // Enable eslint for all supported languages 25 | "eslint.validate": [ 26 | "javascript", 27 | "typescript", 28 | "markdown", 29 | "vue", 30 | "json" 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [4.2.1](https://github.com/tomjs/vite-plugin-vscode/compare/v4.2.0...v4.2.1) (2025-05-11) 2 | 3 | - feat: use @tomjs/logger [9ec87cb](https://github.com/tomjs/vite-plugin-vscode/commit/9ec87cb) 4 | - fix: examples eslint config [236f533](https://github.com/tomjs/vite-plugin-vscode/commit/236f533) 5 | - chore: update packageManager to v10 [242f2b5](https://github.com/tomjs/vite-plugin-vscode/commit/242f2b5) 6 | - fix: remove husky [06b5a0b](https://github.com/tomjs/vite-plugin-vscode/commit/06b5a0b) 7 | - chore: update lint and format code [1479751](https://github.com/tomjs/vite-plugin-vscode/commit/1479751) 8 | 9 | ## [4.2.0](https://github.com/tomjs/vite-plugin-vscode/compare/v4.1.0...v4.2.0) (2025-05-10) 10 | 11 | - feat: use tsup@8.4 and change target to node18 for esm [e0b7c7c](https://github.com/tomjs/vite-plugin-vscode/commit/e0b7c7c) 12 | 13 | ## [4.1.0](https://github.com/tomjs/vite-plugin-vscode/compare/v4.0.0...v4.1.0) (2025-05-10) 14 | 15 | - feat: support esm extension #27 [1a88b92](https://github.com/tomjs/vite-plugin-vscode/commit/1a88b92) 16 | 17 | ## [4.0.0](https://github.com/tomjs/vite-plugin-vscode/compare/v3.2.1...v4.0.0) (2025-03-20) 18 | 19 | - feat: merge the **getWebviewHtml** method for dev and prod into one [ebbb3ef](https://github.com/tomjs/vite-plugin-vscode/commit/ebbb3ef) 20 | - docs: fix readme-cn [26ad4f6](https://github.com/tomjs/vite-plugin-vscode/commit/26ad4f6) 21 | - docs: adjust the example of the Multi-files paragraph [dcd3f18](https://github.com/tomjs/vite-plugin-vscode/commit/dcd3f18) 22 | - chore: update deps and use eslint@9 [9d69c49](https://github.com/tomjs/vite-plugin-vscode/commit/9d69c49) 23 | 24 | ## [3.2.1](https://github.com/tomjs/vite-plugin-vscode/compare/v3.2.0...v3.2.1) (2024-12-05) 25 | 26 | - fix: husky [66cec8c](https://github.com/tomjs/vite-plugin-vscode/commit/66cec8c) 27 | - docs: modify description [10082f9](https://github.com/tomjs/vite-plugin-vscode/commit/10082f9) 28 | 29 | ## [3.2.0](https://github.com/tomjs/vite-plugin-vscode/compare/v3.1.1...v3.2.0) (2024-12-05) 30 | 31 | - feat: support copy/paste commands for macos #21 [d0eec00](https://github.com/tomjs/vite-plugin-vscode/commit/d0eec00) 32 | - feat: add --disable-extensions to examples [6bf213f](https://github.com/tomjs/vite-plugin-vscode/commit/6bf213f) 33 | 34 | ## [3.1.1](https://github.com/tomjs/vite-plugin-vscode/compare/v3.1.0...v3.1.1) (2024-10-29) 35 | 36 | - fix: devtools warn content [dd562a6](https://github.com/tomjs/vite-plugin-vscode/commit/dd562a6) 37 | 38 | ## [3.1.0](https://github.com/tomjs/vite-plugin-vscode/compare/v3.0.0...v3.1.0) (2024-10-25) 39 | 40 | - docs: optimize titles [f77be8f](https://github.com/tomjs/vite-plugin-vscode/commit/f77be8f) 41 | - feat: support devtools #18 [a65bb3e](https://github.com/tomjs/vite-plugin-vscode/commit/a65bb3e) 42 | 43 | ## [3.0.0](https://github.com/tomjs/vite-plugin-vscode/compare/v2.6.0...v3.0.0) (2024-10-08) 44 | 45 | - feat: chang getState and setState methods [b1f38c4](https://github.com/tomjs/vite-plugin-vscode/commit/b1f38c4) 46 | - fix: vite types error [cbf2d68](https://github.com/tomjs/vite-plugin-vscode/commit/cbf2d68) 47 | - fix: tsconfig.json error [e707a9a](https://github.com/tomjs/vite-plugin-vscode/commit/e707a9a) 48 | - fix: fix postmessage processing in app [648af76](https://github.com/tomjs/vite-plugin-vscode/commit/648af76) 49 | - fix: dependency version up and adjustments [7919573](https://github.com/tomjs/vite-plugin-vscode/commit/7919573) 50 | - fix: remove cloneDeep and fix tsup options #11 [c23448f](https://github.com/tomjs/vite-plugin-vscode/commit/c23448f) 51 | - fix: tsconfig error [2997f44](https://github.com/tomjs/vite-plugin-vscode/commit/2997f44) 52 | 53 | ## [2.6.0](https://github.com/tomjs/vite-plugin-vscode/compare/v2.5.5...v2.6.0) (2024-09-20) 54 | 55 | - docs: use jsdocs.io [0878e51](https://github.com/tomjs/vite-plugin-vscode/commit/0878e51) 56 | - fix: export types [a6cea35](https://github.com/tomjs/vite-plugin-vscode/commit/a6cea35) 57 | - fix: htmlCode key [6facf86](https://github.com/tomjs/vite-plugin-vscode/commit/6facf86) 58 | 59 | ## [2.5.5](https://github.com/tomjs/vite-plugin-vscode/compare/v2.5.4...v2.5.5) (2024-07-01) 60 | 61 | - docs: add api document [fd559b4](https://github.com/tomjs/vite-plugin-vscode/commit/fd559b4) 62 | - fix: add prepublishOnly script for release [2fbd83d](https://github.com/tomjs/vite-plugin-vscode/commit/2fbd83d) 63 | 64 | ## [2.5.4](https://github.com/tomjs/vite-plugin-vscode/compare/v2.5.3...v2.5.4) (2024-07-01) 65 | 66 | - docs: add api badge [986caf1](https://github.com/tomjs/vite-plugin-vscode/commit/986caf1) 67 | 68 | ## [2.5.3](https://github.com/tomjs/vite-plugin-vscode/compare/v2.5.2...v2.5.3) (2024-06-27) 69 | 70 | - docs: fix url [eefcbc5](https://github.com/tomjs/vite-plugin-vscode/commit/eefcbc5) 71 | 72 | ## [2.5.2](https://github.com/tomjs/vite-plugin-vscode/compare/v2.5.1...v2.5.2) (2024-06-27) 73 | 74 | - fix: client mock setState [1472665](https://github.com/tomjs/vite-plugin-vscode/commit/1472665) 75 | 76 | ## [2.5.1](https://github.com/tomjs/vite-plugin-vscode/compare/v2.5.0...v2.5.1) (2024-06-27) 77 | 78 | - chore: remove @tomjs/vscode-extension-webview package [f882fca](https://github.com/tomjs/vite-plugin-vscode/commit/f882fca) 79 | 80 | ## [2.5.0](https://github.com/tomjs/vite-plugin-vscode/compare/v2.4.1...v2.5.0) (2024-06-26) 81 | 82 | - fix: postMessage delay issue #5 [f62ef5b](https://github.com/tomjs/vite-plugin-vscode/commit/f62ef5b) 83 | 84 | ## [2.4.1](https://github.com/tomjs/vite-plugin-vscode/compare/v2.4.0...v2.4.1) (2024-06-22) 85 | 86 | - docs: update readme [0f067f6](https://github.com/tomjs/vite-plugin-vscode/commit/0f067f6) 87 | 88 | ## [2.4.0](https://github.com/tomjs/vite-plugin-vscode/compare/v2.3.1...v2.4.0) (2024-06-18) 89 | 90 | - fix: env.d.ts file export error [e23ad80](https://github.com/tomjs/vite-plugin-vscode/commit/e23ad80) 91 | - feat: copy the code of @tomjs/vscode-extension-webview to the current project to solve dependency issues #7 [aad58f2](https://github.com/tomjs/vite-plugin-vscode/commit/aad58f2) 92 | 93 | ## [2.3.1](https://github.com/tomjs/vite-plugin-vscode/compare/v2.3.0...v2.3.1) (2024-06-04) 94 | 95 | - fix: @tomjs/vscode-extension-webview dependency issue #4 [450827d](https://github.com/tomjs/vite-plugin-vscode/commit/450827d) 96 | 97 | ## [2.3.0](https://github.com/tomjs/vite-plugin-vscode/compare/v2.2.0...v2.3.0) (2024-06-03) 98 | 99 | - chore: remove np [14ddc2f](https://github.com/tomjs/vite-plugin-vscode/commit/14ddc2f) 100 | - feat: ddd private field to package.json in examples [a31570b](https://github.com/tomjs/vite-plugin-vscode/commit/a31570b) 101 | - feat: when webview is production, remove Whitespace [bb13f8c](https://github.com/tomjs/vite-plugin-vscode/commit/bb13f8c) 102 | - feat: add custom csp option to webview [3e52440](https://github.com/tomjs/vite-plugin-vscode/commit/3e52440) 103 | - chore: add packageManager [8a8b504](https://github.com/tomjs/vite-plugin-vscode/commit/8a8b504) 104 | - docs: synchronous Chinese readme [62bc2e9](https://github.com/tomjs/vite-plugin-vscode/commit/62bc2e9) 105 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023-PRESENT Tom Gao 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # @tomjs/vite-plugin-vscode 2 | 3 | [![npm](https://img.shields.io/npm/v/@tomjs/vite-plugin-vscode)](https://www.npmjs.com/package/@tomjs/vite-plugin-vscode) ![node-current (scoped)](https://img.shields.io/node/v/@tomjs/vite-plugin-vscode) ![NPM](https://img.shields.io/npm/l/@tomjs/vite-plugin-vscode) [![jsDocs.io](https://img.shields.io/badge/jsDocs.io-reference-blue)](https://www.jsdocs.io/package/@tomjs/vite-plugin-vscode) 4 | 5 | **English** | [中文](./README.zh_CN.md) 6 | 7 | > Use `vue`/`react` to develop [vscode extension webview](https://code.visualstudio.com/api/references/vscode-api#WebviewPanel), supporting `esm` and `cjs`. 8 | 9 | In development mode, inject the same code of [@tomjs/vscode-extension-webview](https://github.com/tomjs/vscode-extension-webview) into `vscode extension code` and `web page code`, use To support `HMR`; during production build, the final generated `index.html` code is injected into `vscode extension code` to reduce the workload. 10 | 11 | ## Features 12 | 13 | - Use [tsup](https://github.com/egoist/tsup) to quickly build `extension code` 14 | - Simple configuration, focus on business 15 | - Support `esm` and `cjs` 16 | - Support ESM extension (vscode `v1.100.0+`) 17 | - Support webview `HMR` 18 | - Support `acquireVsCodeApi` of [@types/vscode-webview](https://www.npmjs.com/package/@types/vscode-webview) 19 | - Support [Multi-Page App](https://vitejs.dev/guide/build.html#multi-page-app) 20 | - Supports `vue` and `react` and other [frameworks](https://cn.vitejs.dev/guide/#trying-vite-online) supported by `vite` 21 | 22 | ### ESM extension 23 | 24 | The NodeJS extension host now (`v1.100.0+`) supports extensions that use JavaScript-modules (ESM). All it needs is the `"type": "module"` entry in your extension's `package.json` file. With that, the JavaScript code can use `import` and `export` statements, including the special module `import('vscode')`. 25 | 26 | ## Install 27 | 28 | ```bash 29 | # pnpm 30 | pnpm add @tomjs/vite-plugin-vscode -D 31 | 32 | # yarn 33 | yarn add @tomjs/vite-plugin-vscode -D 34 | 35 | # npm 36 | npm i @tomjs/vite-plugin-vscode -D 37 | ``` 38 | 39 | ## Usage 40 | 41 | ### Recommended 42 | 43 | Setting `recommended` will modify some preset configurations. See [PluginOptions](#pluginoptions) and `recommended` parameter descriptions in detail. 44 | 45 | #### Directory Structure 46 | 47 | - By default, `recommended:true` will be based on the following directory structure as a convention. 48 | 49 | ``` 50 | |--extension // extension code 51 | | |--index.ts 52 | |--src // front-end code 53 | | |--App.vue 54 | | |--main.ts 55 | |--index.html 56 | ``` 57 | 58 | - Zero configuration, default dist output directory 59 | 60 | ``` 61 | |--dist 62 | | |--extension 63 | | | |--index.js 64 | | | |--index.js.map 65 | | |--webview 66 | | | |--index.html 67 | ``` 68 | 69 | - If you want to modify the `extension` source code directory to `src`, you can set `{ extension: { entry: 'src/index.ts' } }` 70 | 71 | ``` 72 | |--src // extension code 73 | | |--index.ts 74 | |--webview // front-end code 75 | | |--App.vue 76 | | |--main.ts 77 | |--index.html 78 | ``` 79 | 80 | ### extension 81 | 82 | code snippet, more code see examples 83 | 84 | ```ts 85 | const panel = window.createWebviewPanel('showHelloWorld', 'Hello World', ViewColumn.One, { 86 | enableScripts: true, 87 | localResourceRoots: [Uri.joinPath(extensionUri, 'dist/webview')], 88 | }); 89 | 90 | // Vite development mode and production mode inject different webview codes to reduce development work 91 | panel.webview.html = __getWebviewHtml__({ 92 | // vite dev mode 93 | serverUrl: process.env.VITE_DEV_SERVER_URL, 94 | // vite prod mode 95 | webview, 96 | context, 97 | inputName: 'index', 98 | injectCode: ``, 99 | }); 100 | ``` 101 | 102 | - `package.json` 103 | 104 | ```json 105 | { 106 | "main": "dist/extension/index.js" 107 | } 108 | ``` 109 | 110 | ### vue 111 | 112 | - `vite.config.ts` 113 | 114 | ```ts 115 | import vscode from '@tomjs/vite-plugin-vscode'; 116 | import vue from '@vitejs/plugin-vue'; 117 | import { defineConfig } from 'vite'; 118 | 119 | // https://vitejs.dev/config/ 120 | export default defineConfig({ 121 | plugins: [ 122 | vue({ 123 | template: { 124 | compilerOptions: { 125 | isCustomElement: (tag: string) => tag.startsWith('vscode-'), 126 | }, 127 | }, 128 | }), 129 | vscode(), 130 | // Modify the extension source code entry path, and also modify the `index.html` entry file path 131 | // vscode({ extension: { entry: 'src/index.ts' } }), 132 | ], 133 | }); 134 | ``` 135 | 136 | ### react 137 | 138 | - `vite.config.ts` 139 | 140 | ```ts 141 | import vscode from '@tomjs/vite-plugin-vscode'; 142 | import react from '@vitejs/plugin-react-swc'; 143 | import { defineConfig } from 'vite'; 144 | 145 | // https://vitejs.dev/config/ 146 | export default defineConfig({ 147 | plugins: [react(), vscode()], 148 | }); 149 | ``` 150 | 151 | ### **getWebviewHtml** 152 | 153 | See [vue-import](./examples/vue-import) example 154 | 155 | - `vite.config.ts` 156 | 157 | ```ts 158 | import path from 'node:path'; 159 | import vscode from '@tomjs/vite-plugin-vscode'; 160 | import { defineConfig } from 'vite'; 161 | 162 | export default defineConfig({ 163 | plugins: [vscode()], 164 | build: { 165 | rollupOptions: { 166 | // https://cn.vitejs.dev/guide/build.html#multi-page-app 167 | input: [path.resolve(__dirname, 'index.html'), path.resolve(__dirname, 'index2.html')], 168 | // You can also customize the name 169 | // input:{ 170 | // 'index': path.resolve(__dirname, 'index.html'), 171 | // 'index2': path.resolve(__dirname, 'index2.html'), 172 | // } 173 | }, 174 | }, 175 | }); 176 | ``` 177 | 178 | - page one 179 | 180 | ```ts 181 | __getWebviewHtml__({ 182 | // vite dev mode 183 | serverUrl: process.env.VITE_DEV_SERVER_URL, 184 | // vite prod mode 185 | webview, 186 | context, 187 | }); 188 | ``` 189 | 190 | - page two 191 | 192 | ```ts 193 | __getWebviewHtml__({ 194 | // vite dev mode 195 | serverUrl: `${process.env.VITE_DEV_SERVER_URL}/index2.html`, 196 | // vite prod mode 197 | webview, 198 | context, 199 | inputName: 'index2', 200 | }); 201 | ``` 202 | 203 | - A single page uses different parameters to achieve different functions 204 | 205 | ```ts 206 | __getWebviewHtml__({ 207 | // vite dev mode 208 | serverUrl: `${process.env.VITE_DEV_SERVER_URL}?id=666`, 209 | // vite prod mode 210 | webview, 211 | context, 212 | injectCode: ``, 213 | }); 214 | ``` 215 | 216 | **getWebviewHtml** Description 217 | 218 | ```ts 219 | interface WebviewHtmlOptions { 220 | /** 221 | * `[vite serve]` The url of the vite dev server. Please use `process.env.VITE_DEV_SERVER_URL` 222 | */ 223 | serverUrl?: string; 224 | /** 225 | * `[vite build]` The Webview instance of the extension. 226 | */ 227 | webview: Webview; 228 | /** 229 | * `[vite build]` The ExtensionContext instance of the extension. 230 | */ 231 | context: ExtensionContext; 232 | /** 233 | * `[vite build]` vite build.rollupOptions.input name. Default is `index`. 234 | */ 235 | inputName?: string; 236 | /** 237 | * `[vite build]` Inject code into the afterbegin of the head element. 238 | */ 239 | injectCode?: string; 240 | } 241 | 242 | /** 243 | * Gets the html of webview 244 | */ 245 | function __getWebviewHtml__(options?: WebviewHtmlOptions): string; 246 | ``` 247 | 248 | ### Warning 249 | 250 | When using the `acquireVsCodeApi().getState()` method of [@types/vscode-webview](https://www.npmjs.com/package/@types/vscode-webview), you must use `await` to call it. Since `acquireVsCodeApi` is a simulated implementation of this method by the plugin, it is inconsistent with the original method. I am very sorry. If you have other solutions, please share them. Thank you very much. 251 | 252 | ```ts 253 | const value = await acquireVsCodeApi().getState(); 254 | ``` 255 | 256 | ## Documentation 257 | 258 | - [index.d.ts](https://www.unpkg.com/browse/@tomjs/vite-plugin-vscode/dist/index.d.ts) provided by [unpkg.com](https://www.unpkg.com). 259 | 260 | ## Parameters 261 | 262 | ### PluginOptions 263 | 264 | | Property | Type | Default | Description | 265 | | ----------- | -------------------------------------------------------- | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 266 | | recommended | `boolean` | `true` | This option is intended to provide recommended default parameters and behavior. | 267 | | extension | [ExtensionOptions](#ExtensionOptions) | | Configuration options for the vscode extension. | 268 | | webview | `boolean` \| `string` \| [WebviewOption](#WebviewOption) | `__getWebviewHtml__` | Inject html code | 269 | | devtools | `boolean` | `true` | Inject script code for [react-devtools](https://github.com/facebook/react/tree/main/packages/react-devtools) or [vue-devtools](https://devtools.vuejs.org/guide/standalone) debugging | 270 | 271 | #### Notice 272 | 273 | The `recommended` option is used to set the default configuration and behavior, which can be used with almost zero configuration. The default is `true`. If you want to customize the configuration, set it to `false`. The following default prerequisites are to use the recommended [project structure](#directory-structure). 274 | 275 | - The output directory is based on the `build.outDir` parameter of `vite`, and outputs `extension` and `src` to `dist/extension` and `dist/webview` respectively. 276 | - Other behaviors to be implemented 277 | 278 | #### Webview 279 | 280 | Inject [@tomjs/vscode-extension-webview](https://github.com/tomjs/vscode-extension-webview) into vscode extension code and web client code, so that `webview` can support `HMR` during the development stage. 281 | 282 | - vite serve 283 | - extension: Inject `import __getWebviewHtml__ from '@tomjs/vite-plugin-vscode/webview';` at the top of the file that calls the `__getWebviewHtml__` method 284 | - web: Add ``, support [react-devtools](https://github.com/facebook/react/tree/main/packages/react-devtools) 295 | - `vue`: inject ``, support [vue-devtools](https://devtools.vuejs.org/guide/standalone) 296 | 297 | ### ExtensionOptions 298 | 299 | Based on [Options](https://paka.dev/npm/tsup) of [tsup](https://tsup.egoist.dev/), some default values are added for ease of use. 300 | 301 | | Property | Type | Default | Description | 302 | | --------- | ------------------------------------------------------------------- | --------------------- | ---------------------------------------------------------- | 303 | | entry | `string` | `extension/index.ts` | The vscode extension entry file. | 304 | | outDir | `string` | `dist-extension/main` | The output directory for the vscode extension file | 305 | | onSuccess | `() => Promise void \| Promise)>` | `undefined` | A function that will be executed after the build succeeds. | 306 | 307 | ### WebviewOption 308 | 309 | | Property | Type | Default | Description | 310 | | -------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------ | 311 | | name | `string` | `__getWebviewHtml__` | The inject method name | 312 | | csp | `string` | `` | The `CSP` meta for the webview | 313 | 314 | - `{{cspSource}}`: [webview.cspSource](https://code.visualstudio.com/api/references/vscode-api#Webview) 315 | - `{{nonce}}`: uuid 316 | 317 | ### Additional Information 318 | 319 | - Default values for `extension` when the relevant parameters are not configured 320 | 321 | | Parameter | Development Mode Default | Production Mode Default | 322 | | --------- | ------------------------ | ----------------------- | 323 | | sourcemap | `true` | `false` | 324 | | minify | `false` | `true` | 325 | 326 | ## Environment Variables 327 | 328 | ### Vite plugin variables 329 | 330 | `vscode extension` use. 331 | 332 | - `development` mode 333 | 334 | | Variable | Description | 335 | | --------------------- | ------------------------------ | 336 | | `VITE_DEV_SERVER_URL` | The url of the vite dev server | 337 | 338 | - `production` mode 339 | 340 | | Variable | Description | 341 | | ------------------- | ----------------------------- | 342 | | `VITE_WEBVIEW_DIST` | vite webview page output path | 343 | 344 | ## Debug 345 | 346 | Run `Debug Extension` through `vscode` to debug. For debugging tools, refer to [Official Documentation](https://code.visualstudio.com/docs/editor/debugging) 347 | 348 | `launch.json` is configured as follows: 349 | 350 | ```json 351 | { 352 | "version": "0.2.0", 353 | "configurations": [ 354 | { 355 | "name": "Debug Extension", 356 | "type": "extensionHost", 357 | "request": "launch", 358 | "args": ["--extensionDevelopmentPath=${workspaceFolder}"], 359 | "outFiles": ["${workspaceFolder}/dist/extension/*.js"], 360 | "preLaunchTask": "npm: dev" 361 | }, 362 | { 363 | "name": "Preview Extension", 364 | "type": "extensionHost", 365 | "request": "launch", 366 | "args": ["--extensionDevelopmentPath=${workspaceFolder}"], 367 | "outFiles": ["${workspaceFolder}/dist/extension/*.js"], 368 | "preLaunchTask": "npm: build" 369 | } 370 | ] 371 | } 372 | ``` 373 | 374 | `tasks.json` is configured as follows: 375 | 376 | ```json 377 | { 378 | "version": "2.0.0", 379 | "tasks": [ 380 | { 381 | "type": "npm", 382 | "script": "dev", 383 | "problemMatcher": { 384 | "owner": "typescript", 385 | "fileLocation": "relative", 386 | "pattern": { 387 | "regexp": "^([a-zA-Z]\\:/?([\\w\\-]/?)+\\.\\w+):(\\d+):(\\d+): (ERROR|WARNING)\\: (.*)$", 388 | "file": 1, 389 | "line": 3, 390 | "column": 4, 391 | "code": 5, 392 | "message": 6 393 | }, 394 | "background": { 395 | "activeOnStart": true, 396 | "beginsPattern": "^.*extension build start*$", 397 | "endsPattern": "^.*extension (build|rebuild) success.*$" 398 | } 399 | }, 400 | "isBackground": true, 401 | "presentation": { 402 | "reveal": "never" 403 | }, 404 | "group": { 405 | "kind": "build", 406 | "isDefault": true 407 | } 408 | }, 409 | { 410 | "type": "npm", 411 | "script": "build", 412 | "group": { 413 | "kind": "build", 414 | "isDefault": true 415 | }, 416 | "problemMatcher": [] 417 | } 418 | ] 419 | } 420 | ``` 421 | 422 | ## Examples 423 | 424 | First execute the following command to install dependencies and generate library files: 425 | 426 | ```bash 427 | pnpm install 428 | pnpm build 429 | ``` 430 | 431 | Open the [examples](./examples) directory, there are `vue` and `react` examples. 432 | 433 | - [react](./examples/react): Simple react example. 434 | - [vue](./examples/vue): Simple vue example. 435 | - [vue-esm](./examples/vue-esm): Simple vue (ESM Extension) example. 436 | - [vue-import](./examples/vue-import): Dynamic import() and multi-page examples. 437 | 438 | ## Related 439 | 440 | - [@tomjs/vscode](https://npmjs.com/package/@tomjs/vscode): Some utilities to simplify the development of [VSCode Extensions](https://marketplace.visualstudio.com/VSCode). 441 | - [@tomjs/vscode-dev](https://npmjs.com/package/@tomjs/vscode-dev): Some development tools to simplify the development of [vscode extensions](https://marketplace.visualstudio.com/VSCode). 442 | - [@tomjs/vscode-webview](https://npmjs.com/package/@tomjs/vscode-webview): Optimize the `postMessage` issue between `webview` page and [vscode extensions](https://marketplace.visualstudio.com/VSCode) 443 | 444 | ## Important Notes 445 | 446 | ### v4.0.0 447 | 448 | **Breaking Updates:** 449 | 450 | - Merge the `__getWebviewHtml__` method for development and production into one, see [getWebviewHtml](#getwebviewhtml) 451 | 452 | ### v3.0.0 453 | 454 | **Breaking Updates:** 455 | 456 | - The simulated `acquireVsCodeApi` is consistent with the `acquireVsCodeApi` of [@types/vscode-webview](https://www.npmjs.com/package/@types/vscode-webview), and `sessionStorage.getItem` and `sessionStorage.setItem` are used to implement `getState` and `setState`. 457 | -------------------------------------------------------------------------------- /README.zh_CN.md: -------------------------------------------------------------------------------- 1 | # @tomjs/vite-plugin-vscode 2 | 3 | [![npm](https://img.shields.io/npm/v/@tomjs/vite-plugin-vscode)](https://www.npmjs.com/package/@tomjs/vite-plugin-vscode) ![node-current (scoped)](https://img.shields.io/node/v/@tomjs/vite-plugin-vscode) ![NPM](https://img.shields.io/npm/l/@tomjs/vite-plugin-vscode) [![jsDocs.io](https://img.shields.io/badge/jsDocs.io-reference-blue)](https://www.jsdocs.io/package/@tomjs/vite-plugin-vscode) 4 | 5 | [English](./README.md) | **中文** 6 | 7 | > 用 `vue`/`react` 来开发 [vscode extension webview](https://code.visualstudio.com/api/references/vscode-api#WebviewPanel) ,支持 `esm` 和 `cjs`。 8 | 9 | 在开发模式时,给 `vscode 扩展代码` 和 `web 页面代码`中注入 [@tomjs/vscode-extension-webview](https://github.com/tomjs/vscode-extension-webview) 相同的代码,用来支持 `HMR`;生产构建时,将最终生成的`index.html` 代码注入到 `vscode 扩展代码` 中,减少工作量。 10 | 11 | ## 特性 12 | 13 | - 使用 [tsup](https://github.com/egoist/tsup) 快速构建 `扩展代码` 14 | - 配置简单,专注业务 15 | - 支持 `esm`和 `cjs` 16 | - 支持 ESM 扩展(vscode `v1.100.0+`) 17 | - 支持 webview `HMR` 18 | - 支持 [@types/vscode-webview](https://www.npmjs.com/package/@types/vscode-webview) 的 `acquireVsCodeApi` 19 | - 支持[多页面应用](https://cn.vitejs.dev/guide/build.html#multi-page-app) 20 | - 支持 `vue` 、`react` 等其他 `vite` 支持的[框架](https://cn.vitejs.dev/guide/#trying-vite-online) 21 | 22 | ### ESM 扩展 23 | 24 | NodeJS 扩展现在(`v1.100.0+`)支持使用 JavaScript 模块 (ESM) 的扩展。它只需要在扩展的 `package.json` 文件中添加 `"type": "module"` 条目即可。这样,JavaScript 代码就可以使用 `import` 和 `export` 语句,包括特殊的模块 `import('vscode')` 25 | 26 | ## 安装 27 | 28 | ```bash 29 | # pnpm 30 | pnpm add @tomjs/vite-plugin-vscode -D 31 | 32 | # yarn 33 | yarn add @tomjs/vite-plugin-vscode -D 34 | 35 | # npm 36 | npm i @tomjs/vite-plugin-vscode -D 37 | ``` 38 | 39 | ## 使用说明 40 | 41 | ### 推荐约定 42 | 43 | 设置 `recommended` 参数会修改一些预置配置,详细查看 [PluginOptions](#pluginoptions) 和 `recommended` 参数说明。 44 | 45 | #### 目录结构 46 | 47 | - 默认情况下,`recommended:true` 会根据如下目录结构作为约定 48 | 49 | ``` 50 | |--extension // extension code 51 | | |--index.ts 52 | |--src // front-end code 53 | | |--App.vue 54 | | |--main.ts 55 | |--index.html 56 | ``` 57 | 58 | - 零配置,默认 dist 输出目录 59 | 60 | ``` 61 | |--dist 62 | | |--extension 63 | | | |--index.js 64 | | | |--index.js.map 65 | | |--webview 66 | | | |--index.html 67 | ``` 68 | 69 | - 如果你想修改 `extension` 源码目录为 `src`,可以设置 `{ extension: { entry: 'src/index.ts' } }` 70 | 71 | ``` 72 | |--src // extension code 73 | | |--index.ts 74 | |--webview // front-end code 75 | | |--App.vue 76 | | |--main.ts 77 | |--index.html 78 | ``` 79 | 80 | ### extension 81 | 82 | 代码片段,更多配置看示例 83 | 84 | ```ts 85 | const panel = window.createWebviewPanel('showHelloWorld', 'Hello World', ViewColumn.One, { 86 | enableScripts: true, 87 | localResourceRoots: [Uri.joinPath(extensionUri, 'dist/webview')], 88 | }); 89 | 90 | // vite 开发模式和生产模式注入不同的webview代码,减少开发工作 91 | panel.webview.html = __getWebviewHtml__({ 92 | // vite 开发模式 93 | serverUrl: process.env.VITE_DEV_SERVER_URL, 94 | // vite 生产模式 95 | webview, 96 | context, 97 | inputName: 'index', 98 | injectCode: ``, 99 | }); 100 | ``` 101 | 102 | - `package.json` 103 | 104 | ```json 105 | { 106 | "main": "dist/extension/index.js" 107 | } 108 | ``` 109 | 110 | ### vue 111 | 112 | - `vite.config.ts` 113 | 114 | ```ts 115 | import vscode from '@tomjs/vite-plugin-vscode'; 116 | import vue from '@vitejs/plugin-vue'; 117 | import { defineConfig } from 'vite'; 118 | 119 | // https://vitejs.dev/config/ 120 | export default defineConfig({ 121 | plugins: [ 122 | vue({ 123 | template: { 124 | compilerOptions: { 125 | isCustomElement: (tag: string) => tag.startsWith('vscode-'), 126 | }, 127 | }, 128 | }), 129 | vscode(), 130 | // 修改扩展源码入口路径,同时修改`index.html`入口文件路径 131 | // vscode({ extension: { entry: 'src/index.ts' } }), 132 | ], 133 | }); 134 | ``` 135 | 136 | ### react 137 | 138 | - `vite.config.ts` 139 | 140 | ```ts 141 | import vscode from '@tomjs/vite-plugin-vscode'; 142 | import react from '@vitejs/plugin-react-swc'; 143 | import { defineConfig } from 'vite'; 144 | 145 | // https://vitejs.dev/config/ 146 | export default defineConfig({ 147 | plugins: [react(), vscode()], 148 | }); 149 | ``` 150 | 151 | ### **getWebviewHtml** 152 | 153 | 可查看 [vue-import](./examples/vue-import) 示例 154 | 155 | - `vite.config.ts` 156 | 157 | ```ts 158 | import path from 'node:path'; 159 | import vscode from '@tomjs/vite-plugin-vscode'; 160 | 161 | export default defineConfig({ 162 | plugins: [vscode()], 163 | build: { 164 | rollupOptions: { 165 | // https://cn.vitejs.dev/guide/build.html#multi-page-app 166 | input: [path.resolve(__dirname, 'index.html'), path.resolve(__dirname, 'index2.html')], 167 | // 也可自定义名称 168 | // input:{ 169 | // 'index': path.resolve(__dirname, 'index.html'), 170 | // 'index2': path.resolve(__dirname, 'index2.html'), 171 | // } 172 | }, 173 | }, 174 | }); 175 | ``` 176 | 177 | - 页面一 178 | 179 | ```ts 180 | __getWebviewHtml__({ 181 | // vite 开发模式 182 | serverUrl: process.env.VITE_DEV_SERVER_URL, 183 | // vite 生产模式 184 | webview, 185 | context, 186 | }); 187 | ``` 188 | 189 | - 页面二 190 | 191 | ```ts 192 | __getWebviewHtml__({ 193 | // vite 开发模式 194 | serverUrl: `${process.env.VITE_DEV_SERVER_URL}/index2.html`, 195 | // vite 生产模式 196 | webview, 197 | context, 198 | inputName: 'index2', 199 | }); 200 | ``` 201 | 202 | - 单个页面通过不同参数来实现不同功能 203 | 204 | ```ts 205 | __getWebviewHtml__({ 206 | // vite 开发模式 207 | serverUrl: `${process.env.VITE_DEV_SERVER_URL}?id=666`, 208 | // vite 生产模式 209 | webview, 210 | context, 211 | injectCode: ``, 212 | }); 213 | ``` 214 | 215 | **getWebviewHtml** 说明 216 | 217 | ```ts 218 | interface WebviewHtmlOptions { 219 | /** 220 | * `[vite serve]` vite开发服务器的url, 请用 `process.env.VITE_DEV_SERVER_URL` 221 | */ 222 | serverUrl?: string; 223 | /** 224 | * `[vite build]` 扩展的 Webview 实例 225 | */ 226 | webview: Webview; 227 | /** 228 | * `[vite build]` 扩展的 ExtensionContext 实例 229 | */ 230 | context: ExtensionContext; 231 | /** 232 | * `[vite build]` vite build.rollupOptions.input 设置的名称. 默认 `index`. 233 | */ 234 | inputName?: string; 235 | /** 236 | * `[vite build]` 向 head 元素的结束前注入代码 --inject-- 237 | */ 238 | injectCode?: string; 239 | } 240 | 241 | /** 242 | * 获取webview的html 243 | */ 244 | function __getWebviewHtml__(options?: WebviewHtmlOptions): string; 245 | ``` 246 | 247 | ### 警告 248 | 249 | 使用 [@types/vscode-webview](https://www.npmjs.com/package/@types/vscode-webview) 的 `acquireVsCodeApi().getState()` 方法时,要使用 `await` 调用。由于 `acquireVsCodeApi` 是插件对该方法的模拟实现,故与原方法出现不一致性,非常抱歉。如果有其他方案,请分享,非常感谢。 250 | 251 | ```ts 252 | const value = await acquireVsCodeApi().getState(); 253 | ``` 254 | 255 | ## 文档 256 | 257 | - [unpkg.com](https://www.unpkg.com/) 提供的 [index.d.ts](https://www.unpkg.com/browse/@tomjs/vite-plugin-vscode/dist/index.d.ts). 258 | 259 | ## 参数 260 | 261 | ### PluginOptions 262 | 263 | | 参数名 | 类型 | 默认值 | 说明 | 264 | | ----------- | -------------------------------------------------------- | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | 265 | | recommended | `boolean` | `true` | 这个选项是为了提供推荐的默认参数和行为 | 266 | | extension | [ExtensionOptions](#ExtensionOptions) | | vscode extension 可选配置 | 267 | | webview | `boolean` \| `string` \| [WebviewOption](#WebviewOption) | `__getWebviewHtml__` | 注入 html 代码 | 268 | | devtools | `boolean` | `true` | 注入 script 代码用于 [react-devtools](https://github.com/facebook/react/tree/main/packages/react-devtools) 或 [vue-devtools](https://devtools.vuejs.org/guide/standalone) 调试 | 269 | 270 | #### Notice 271 | 272 | `recommended` 选项用于设置默认配置和行为,几乎可以达到零配置使用,默认为 `true` 。如果你要自定义配置,请设置它为`false`。以下默认的前提条件是使用推荐的 [项目结构](#目录结构)。 273 | 274 | - 输出目录根据 `vite` 的 `build.outDir` 参数, 将 `extension`、`src` 分别输出到 `dist/extension`、`dist/webview` 275 | 276 | - 其他待实现的行为 277 | 278 | #### Webview 279 | 280 | 在 vscode 扩展代码和 web 客户端代码中注入 [@tomjs/vscode-extension-webview](https://github.com/tomjs/vscode-extension-webview),使 `webview` 在开发阶段能够支持 `HMR`。 281 | 282 | - vite serve 283 | - extension: 在调用 `__getWebviewHtml__` 方法的文件顶部注入 `import __getWebviewHtml__ from '@tomjs/vscode-extension-webview';` 284 | - web: 在 index.html 中添加 ``,支持 [react-devtools](https://github.com/facebook/react/tree/main/packages/react-devtools) 295 | - `vue`: 注入 ``,支持 [vue-devtools](https://devtools.vuejs.org/guide/standalone) 296 | 297 | ### ExtensionOptions 298 | 299 | 继承自 [tsup](https://tsup.egoist.dev/) 的 [Options](https://paka.dev/npm/tsup),添加了一些默认值,方便使用。 300 | 301 | | 参数名 | 类型 | 默认值 | 说明 | 302 | | --------- | ------------------------------------------------------------------- | --------------------- | ------------------------ | 303 | | entry | `string` | `extension/index.ts` | 入口文件 | 304 | | outDir | `string` | `dist-extension/main` | 输出文件夹 | 305 | | onSuccess | `() => Promise void \| Promise)>` | `undefined` | 构建成功后运行的回调函数 | 306 | 307 | ### WebviewOption 308 | 309 | | 参数名 | 类型 | 默认值 | 说明 | 310 | | ------ | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------- | 311 | | name | `string` | `__getWebviewHtml__` | 注入的方法名 | 312 | | csp | `string` | `` | webview 的 `CSP` | 313 | 314 | - `{{cspSource}}`: [webview.cspSource](https://code.visualstudio.com/api/references/vscode-api#Webview) 315 | - `{{nonce}}`: uuid 316 | 317 | ### 补充说明 318 | 319 | - `extension` 未配置相关参数时的默认值 320 | 321 | | 参数 | 开发模式默认值 | 生产模式默认值 | 322 | | --------- | -------------- | -------------- | 323 | | sourcemap | `true` | `false` | 324 | | minify | `false` | `true` | 325 | 326 | ## 环境变量 327 | 328 | `vscode extension` 使用 329 | 330 | - `development` 模式 331 | 332 | | 变量 | 描述 | 333 | | --------------------- | ------------------- | 334 | | `VITE_DEV_SERVER_URL` | vite开发服务器的url | 335 | 336 | - `production` 模式 337 | 338 | | 变量 | 描述 | 339 | | ------------------- | ------------------------- | 340 | | `VITE_WEBVIEW_DIST` | vite webview 页面输出路径 | 341 | 342 | ## Debug 343 | 344 | ### 扩展调试 345 | 346 | 通过 `vscode` 运行 `Debug Extension` 调试,调试工具参考 [官方文档](https://code.visualstudio.com/docs/editor/debugging) 347 | 348 | `launch.json` 配置如下: 349 | 350 | ```json 351 | { 352 | "version": "0.2.0", 353 | "configurations": [ 354 | { 355 | "name": "Debug Extension", 356 | "type": "extensionHost", 357 | "request": "launch", 358 | "args": ["--extensionDevelopmentPath=${workspaceFolder}"], 359 | "outFiles": ["${workspaceFolder}/dist/extension/*.js"], 360 | "preLaunchTask": "npm: dev" 361 | }, 362 | { 363 | "name": "Preview Extension", 364 | "type": "extensionHost", 365 | "request": "launch", 366 | "args": ["--extensionDevelopmentPath=${workspaceFolder}"], 367 | "outFiles": ["${workspaceFolder}/dist/extension/*.js"], 368 | "preLaunchTask": "npm: build" 369 | } 370 | ] 371 | } 372 | ``` 373 | 374 | `tasks.json` 配置如下: 375 | 376 | ```json 377 | { 378 | "version": "2.0.0", 379 | "tasks": [ 380 | { 381 | "type": "npm", 382 | "script": "dev", 383 | "problemMatcher": { 384 | "owner": "typescript", 385 | "fileLocation": "relative", 386 | "pattern": { 387 | "regexp": "^([a-zA-Z]\\:/?([\\w\\-]/?)+\\.\\w+):(\\d+):(\\d+): (ERROR|WARNING)\\: (.*)$", 388 | "file": 1, 389 | "line": 3, 390 | "column": 4, 391 | "code": 5, 392 | "message": 6 393 | }, 394 | "background": { 395 | "activeOnStart": true, 396 | "beginsPattern": "^.*extension build start*$", 397 | "endsPattern": "^.*extension (build|rebuild) success.*$" 398 | } 399 | }, 400 | "isBackground": true, 401 | "presentation": { 402 | "reveal": "never" 403 | }, 404 | "group": { 405 | "kind": "build", 406 | "isDefault": true 407 | } 408 | }, 409 | { 410 | "type": "npm", 411 | "script": "build", 412 | "group": { 413 | "kind": "build", 414 | "isDefault": true 415 | }, 416 | "problemMatcher": [] 417 | } 418 | ] 419 | } 420 | ``` 421 | 422 | ### 网页调试 423 | 424 | 可以使用 [react-devtools](https://github.com/facebook/react/tree/main/packages/react-devtools) 和 [vue-devtools](https://devtools.vuejs.org/guide/standalone) 的独立应用调试 `webview` 425 | 426 | ## 示例 427 | 428 | 先执行以下命令安装依赖,并生成库文件: 429 | 430 | ```bash 431 | pnpm install 432 | pnpm build 433 | ``` 434 | 435 | 打开 [examples](./examples) 目录,有 `vue` 和 `react` 示例。 436 | 437 | - [react](./examples/react):简单的 react 示例。 438 | - [vue](./examples/vue):简单的 vue 示例。 439 | - [vue-esm](./examples/vue-esm):简单的 vue(ESM 扩展)示例。 440 | - [vue-import](./examples/vue-import):动态 import() 和多页面示例。 441 | 442 | ## 关联 443 | 444 | - [@tomjs/vscode](https://npmjs.com/package/@tomjs/vscode): 一些实用工具,用于简化 [vscode 扩展](https://marketplace.visualstudio.com/VSCode) 的开发。 445 | - [@tomjs/vscode-dev](https://npmjs.com/package/@tomjs/vscode-dev): 一些开发工具,用于简化 [vscode 扩展](https://marketplace.visualstudio.com/VSCode) 的开发。 446 | - [@tomjs/vscode-webview](https://npmjs.com/package/@tomjs/vscode-webview): 优化 `webview` 页面与 [vscode 扩展](https://marketplace.visualstudio.com/VSCode) 的 `postMessage` 问题 447 | 448 | ## 重要说明 449 | 450 | ### v4.0.0 451 | 452 | **破坏性更新:** 453 | 454 | - 开发和生产的 `__getWebviewHtml__` 方法合并为同一个,参考 [getWebviewHtml](#getwebviewhtml) 455 | 456 | ### v3.0.0 457 | 458 | **破坏性更新:** 459 | 460 | - 模拟的 `acquireVsCodeApi` 与 [@types/vscode-webview](https://www.npmjs.com/package/@types/vscode-webview) 的 `acquireVsCodeApi` 保持一致,改用 `sessionStorage.getItem` 和 `sessionStorage.setItem` 来实现 `getState` 和 `setState`。 461 | -------------------------------------------------------------------------------- /commitlint.config.mjs: -------------------------------------------------------------------------------- 1 | export default { 2 | extends: ['@tomjs/commitlint'], 3 | }; 4 | -------------------------------------------------------------------------------- /env.d.ts: -------------------------------------------------------------------------------- 1 | import type { ExtensionContext, Webview } from 'vscode'; 2 | // Make this a module 3 | export {}; 4 | declare global { 5 | /** 6 | * fix code hint 7 | */ 8 | type UnionType = T | (string & {}); 9 | 10 | namespace NodeJS { 11 | interface ProcessEnv { 12 | /** 13 | * Node.js environment 14 | */ 15 | NODE_ENV: UnionType<'development' | 'production'>; 16 | /** 17 | * `[vite serve]` The url of the vite dev server. 18 | */ 19 | VITE_DEV_SERVER_URL?: string; 20 | /** 21 | * `[vite build]` All js files in the dist directory, excluding index.js. It's to be a json string. 22 | */ 23 | VITE_WEBVIEW_DIST?: string; 24 | } 25 | } 26 | 27 | interface WebviewHtmlOptions { 28 | /** 29 | * `[vite serve]` The url of the vite dev server. Please use `process.env.VITE_DEV_SERVER_URL` 30 | */ 31 | serverUrl?: string; 32 | /** 33 | * `[vite build]` The Webview instance of the extension. 34 | */ 35 | webview: Webview; 36 | /** 37 | * `[vite build]` The ExtensionContext instance of the extension. 38 | */ 39 | context: ExtensionContext; 40 | /** 41 | * `[vite build]` vite build.rollupOptions.input name. Default is `index`. 42 | */ 43 | inputName?: string; 44 | /** 45 | * `[vite build]` Inject code into the afterbegin of the head element. 46 | */ 47 | injectCode?: string; 48 | } 49 | 50 | /** 51 | * Gets the html of webview 52 | */ 53 | function __getWebviewHtml__(options?: WebviewHtmlOptions): string; 54 | } 55 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import { defineConfig } from '@tomjs/eslint'; 2 | 3 | export default defineConfig({ 4 | rules: { 5 | 'no-console': 'off', 6 | 'n/prefer-global/process': 'off', 7 | }, 8 | }); 9 | -------------------------------------------------------------------------------- /examples/react/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // A launch configuration that compiles the extension and then opens it inside a new window 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | { 6 | "version": "0.2.0", 7 | "configurations": [ 8 | { 9 | "name": "Debug Extension", 10 | "type": "extensionHost", 11 | "request": "launch", 12 | "args": [ 13 | "--disable-extensions", 14 | "--extensionDevelopmentPath=${workspaceFolder}" 15 | ], 16 | "outFiles": [ 17 | "${workspaceFolder}/dist/extension/*.js" 18 | ], 19 | "preLaunchTask": "npm: dev" 20 | }, 21 | { 22 | "name": "Preview Extension", 23 | "type": "extensionHost", 24 | "request": "launch", 25 | "args": [ 26 | "--extensionDevelopmentPath=${workspaceFolder}" 27 | ], 28 | "outFiles": [ 29 | "${workspaceFolder}/dist/extension/*.js" 30 | ], 31 | "preLaunchTask": "npm: build" 32 | } 33 | ] 34 | } 35 | -------------------------------------------------------------------------------- /examples/react/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | // See https://go.microsoft.com/fwlink/?LinkId=733558 2 | // for the documentation about the tasks.json format 3 | { 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "type": "npm", 8 | "script": "dev", 9 | "problemMatcher": { 10 | "owner": "typescript", 11 | "fileLocation": "relative", 12 | "pattern": { 13 | "regexp": "^([a-zA-Z]\\:/?([\\w\\-]/?)+\\.\\w+):(\\d+):(\\d+): (ERROR|WARNING)\\: (.*)$", 14 | "file": 1, 15 | "line": 3, 16 | "column": 4, 17 | "code": 5, 18 | "message": 6 19 | }, 20 | "background": { 21 | "activeOnStart": true, 22 | "beginsPattern": "^.*extension build start*$", 23 | "endsPattern": "^.*extension (build|rebuild) success.*$" 24 | } 25 | }, 26 | "isBackground": true, 27 | "presentation": { 28 | "reveal": "never" 29 | }, 30 | "group": { 31 | "kind": "build", 32 | "isDefault": true 33 | } 34 | }, 35 | { 36 | "type": "npm", 37 | "script": "build", 38 | "group": { 39 | "kind": "build", 40 | "isDefault": true 41 | }, 42 | "problemMatcher": [] 43 | } 44 | ] 45 | } 46 | -------------------------------------------------------------------------------- /examples/react/README.md: -------------------------------------------------------------------------------- 1 | # react 2 | 3 | vite + react 4 | -------------------------------------------------------------------------------- /examples/react/eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import { defineConfig } from '@tomjs/eslint'; 2 | 3 | export default defineConfig({ 4 | rules: { 5 | 'no-console': 'off', 6 | 'n/prefer-global/process': 'off', 7 | }, 8 | }); 9 | -------------------------------------------------------------------------------- /examples/react/extension/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /examples/react/extension/index.ts: -------------------------------------------------------------------------------- 1 | import type { ExtensionContext } from 'vscode'; 2 | import { commands } from 'vscode'; 3 | import { MainPanel } from './views/panel'; 4 | 5 | export function activate(context: ExtensionContext) { 6 | // Add command to the extension context 7 | context.subscriptions.push( 8 | commands.registerCommand('hello-world.showHelloWorld', async () => { 9 | MainPanel.render(context); 10 | }), 11 | ); 12 | } 13 | 14 | export function deactivate() {} 15 | -------------------------------------------------------------------------------- /examples/react/extension/views/helper.ts: -------------------------------------------------------------------------------- 1 | import type { Disposable, ExtensionContext, Webview } from 'vscode'; 2 | import { window } from 'vscode'; 3 | 4 | export class WebviewHelper { 5 | public static setupHtml(webview: Webview, context: ExtensionContext) { 6 | return __getWebviewHtml__({ 7 | serverUrl: process.env.VITE_DEV_SERVER_URL, 8 | webview, 9 | context, 10 | }); 11 | } 12 | 13 | public static setupWebviewHooks(webview: Webview, disposables: Disposable[]) { 14 | webview.onDidReceiveMessage( 15 | (message: any) => { 16 | const type = message.type; 17 | const data = message.data; 18 | console.log(`type: ${type}`); 19 | switch (type) { 20 | case 'hello': 21 | window.showInformationMessage(data); 22 | } 23 | }, 24 | undefined, 25 | disposables, 26 | ); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /examples/react/extension/views/panel.ts: -------------------------------------------------------------------------------- 1 | import type { Disposable, ExtensionContext, WebviewPanel } from 'vscode'; 2 | import { ViewColumn, window } from 'vscode'; 3 | import { WebviewHelper } from './helper'; 4 | 5 | export class MainPanel { 6 | public static currentPanel: MainPanel | undefined; 7 | private readonly _panel: WebviewPanel; 8 | private _disposables: Disposable[] = []; 9 | 10 | private constructor(panel: WebviewPanel, context: ExtensionContext) { 11 | this._panel = panel; 12 | 13 | this._panel.onDidDispose(() => this.dispose(), null, this._disposables); 14 | this._panel.webview.html = WebviewHelper.setupHtml(this._panel.webview, context); 15 | 16 | WebviewHelper.setupWebviewHooks(this._panel.webview, this._disposables); 17 | } 18 | 19 | public static render(context: ExtensionContext) { 20 | if (MainPanel.currentPanel) { 21 | MainPanel.currentPanel._panel.reveal(ViewColumn.One); 22 | } 23 | else { 24 | const panel = window.createWebviewPanel('showHelloWorld', 'Hello World', ViewColumn.One, { 25 | enableScripts: true, 26 | }); 27 | 28 | MainPanel.currentPanel = new MainPanel(panel, context); 29 | } 30 | MainPanel.currentPanel._panel.webview.postMessage({ type: 'hello', data: 'Hello World!' }); 31 | } 32 | 33 | /** 34 | * Cleans up and disposes of webview resources when the webview panel is closed. 35 | */ 36 | public dispose() { 37 | MainPanel.currentPanel = undefined; 38 | 39 | // Dispose of the current webview panel 40 | this._panel.dispose(); 41 | 42 | // Dispose of all disposables (i.e. commands) for the current webview panel 43 | while (this._disposables.length) { 44 | const disposable = this._disposables.pop(); 45 | if (disposable) { 46 | disposable.dispose(); 47 | } 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /examples/react/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Vite + React + TS 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /examples/react/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "publisher": "tomjs-test", 3 | "name": "template-react", 4 | "version": "0.0.0", 5 | "private": true, 6 | "description": "vite + react", 7 | "main": "dist/extension/index.js", 8 | "engines": { 9 | "node": ">=18", 10 | "vscode": "^1.75.0" 11 | }, 12 | "activationEvents": [], 13 | "contributes": { 14 | "commands": [ 15 | { 16 | "command": "hello-world.showHelloWorld", 17 | "title": "Hello World: Show" 18 | } 19 | ] 20 | }, 21 | "scripts": { 22 | "dev": "vite", 23 | "build": "tsc && vite build", 24 | "preview": "vite preview" 25 | }, 26 | "dependencies": { 27 | "@vscode/webview-ui-toolkit": "^1.4.0", 28 | "react": "^18.3.1", 29 | "react-dom": "^18.3.1" 30 | }, 31 | "devDependencies": { 32 | "@tomjs/tsconfig": "^1.7.1", 33 | "@tomjs/vite-plugin-vscode": "workspace:^", 34 | "@types/react": "^18.3.3", 35 | "@types/react-dom": "^18.3.0", 36 | "@types/vscode": "^1.93.0", 37 | "@types/vscode-webview": "^1.57.5", 38 | "@vitejs/plugin-react": "^4.4.1", 39 | "@vitejs/plugin-react-swc": "^3.9.0" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /examples/react/src/App.css: -------------------------------------------------------------------------------- 1 | main { 2 | display: flex; 3 | flex-direction: column; 4 | align-items: flex-start; 5 | justify-content: center; 6 | height: 100%; 7 | } 8 | -------------------------------------------------------------------------------- /examples/react/src/App.tsx: -------------------------------------------------------------------------------- 1 | import { VSCodeButton, VSCodeTextField } from '@vscode/webview-ui-toolkit/react'; 2 | import { useState } from 'react'; 3 | import { vscode } from './utils/vscode'; 4 | 5 | import './App.css'; 6 | 7 | function App() { 8 | const [message, setMessage] = useState(''); 9 | const [state, setState] = useState(''); 10 | 11 | const onSetState = () => { 12 | vscode.setState(state); 13 | }; 14 | const onGetState = () => { 15 | setState((vscode.getState() || '') as string); 16 | }; 17 | 18 | function onPostMessage() { 19 | vscode.postMessage({ 20 | type: 'hello', 21 | data: `💬: ${message || 'Empty'}`, 22 | }); 23 | } 24 | 25 | return ( 26 |
27 |

Hello React!

28 | Test VSCode Message 29 |
30 | setMessage(e.target.value)}> 31 | Please enter a message 32 | 33 |
34 | Message is: 35 | {message} 36 |
37 |
38 |
39 | setState(e.target.value)}> 40 | Please enter a state 41 | 42 |
43 | State is: 44 | {state} 45 |
46 |
47 | setState 48 | 49 | getState 50 | 51 |
52 |
53 |
54 | ); 55 | } 56 | 57 | export default App; 58 | -------------------------------------------------------------------------------- /examples/react/src/main.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom/client'; 3 | import App from './App.tsx'; 4 | 5 | ReactDOM.createRoot(document.getElementById('app')!).render( 6 | 7 | 8 | , 9 | ); 10 | -------------------------------------------------------------------------------- /examples/react/src/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from './vscode'; 2 | -------------------------------------------------------------------------------- /examples/react/src/utils/vscode.ts: -------------------------------------------------------------------------------- 1 | // Exports class singleton to prevent multiple invocations of acquireVsCodeApi. 2 | export const vscode = acquireVsCodeApi(); 3 | -------------------------------------------------------------------------------- /examples/react/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | -------------------------------------------------------------------------------- /examples/react/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tomjs/tsconfig/react.json", 3 | "references": [{ "path": "./tsconfig.node.json" }], 4 | "include": ["src"] 5 | } 6 | -------------------------------------------------------------------------------- /examples/react/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tomjs/tsconfig/node.json", 3 | "compilerOptions": { 4 | "composite": true 5 | }, 6 | "include": ["extension", "vite.config.ts"] 7 | } 8 | -------------------------------------------------------------------------------- /examples/react/vite.config.ts: -------------------------------------------------------------------------------- 1 | import vscode from '@tomjs/vite-plugin-vscode'; 2 | import react from '@vitejs/plugin-react-swc'; 3 | import { defineConfig } from 'vite'; 4 | 5 | // https://vitejs.dev/config/ 6 | export default defineConfig({ 7 | plugins: [ 8 | react(), 9 | vscode({ 10 | extension: { 11 | sourcemap: 'inline', 12 | }, 13 | }), 14 | ], 15 | }); 16 | -------------------------------------------------------------------------------- /examples/vue-esm/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // A launch configuration that compiles the extension and then opens it inside a new window 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | { 6 | "version": "0.2.0", 7 | "configurations": [ 8 | { 9 | "name": "Debug Extension", 10 | "type": "extensionHost", 11 | "request": "launch", 12 | "args": [ 13 | "--disable-extensions", 14 | "--extensionDevelopmentPath=${workspaceFolder}" 15 | ], 16 | "outFiles": [ 17 | "${workspaceFolder}/dist/extension/*.js" 18 | ], 19 | "preLaunchTask": "npm: dev" 20 | }, 21 | { 22 | "name": "Preview Extension", 23 | "type": "extensionHost", 24 | "request": "launch", 25 | "args": [ 26 | "--extensionDevelopmentPath=${workspaceFolder}" 27 | ], 28 | "outFiles": [ 29 | "${workspaceFolder}/dist/extension/*.js" 30 | ], 31 | "preLaunchTask": "npm: build" 32 | } 33 | ] 34 | } 35 | -------------------------------------------------------------------------------- /examples/vue-esm/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | // See https://go.microsoft.com/fwlink/?LinkId=733558 2 | // for the documentation about the tasks.json format 3 | { 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "type": "npm", 8 | "script": "dev", 9 | "problemMatcher": { 10 | "owner": "typescript", 11 | "fileLocation": "relative", 12 | "pattern": { 13 | "regexp": "^([a-zA-Z]\\:/?([\\w\\-]/?)+\\.\\w+):(\\d+):(\\d+): (ERROR|WARNING)\\: (.*)$", 14 | "file": 1, 15 | "line": 3, 16 | "column": 4, 17 | "code": 5, 18 | "message": 6 19 | }, 20 | "background": { 21 | "activeOnStart": true, 22 | "beginsPattern": "^.*extension build start*$", 23 | "endsPattern": "^.*extension (build|rebuild) success.*$" 24 | } 25 | }, 26 | "isBackground": true, 27 | "presentation": { 28 | "reveal": "never" 29 | }, 30 | "group": { 31 | "kind": "build", 32 | "isDefault": true 33 | } 34 | }, 35 | { 36 | "type": "npm", 37 | "script": "build", 38 | "group": { 39 | "kind": "build", 40 | "isDefault": true 41 | }, 42 | "problemMatcher": [] 43 | } 44 | ] 45 | } 46 | -------------------------------------------------------------------------------- /examples/vue-esm/README.md: -------------------------------------------------------------------------------- 1 | # vue 2 | 3 | vite + vue 4 | -------------------------------------------------------------------------------- /examples/vue-esm/eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import { defineConfig } from '@tomjs/eslint'; 2 | 3 | export default defineConfig({ 4 | rules: { 5 | 'no-console': 'off', 6 | 'n/prefer-global/process': 'off', 7 | }, 8 | }); 9 | -------------------------------------------------------------------------------- /examples/vue-esm/extension/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /examples/vue-esm/extension/index.ts: -------------------------------------------------------------------------------- 1 | import type { ExtensionContext } from 'vscode'; 2 | import { commands } from 'vscode'; 3 | import { MainPanel } from './views/panel'; 4 | 5 | export function activate(context: ExtensionContext) { 6 | context.subscriptions.push( 7 | commands.registerCommand('hello-world.showHelloWorld', async () => { 8 | MainPanel.render(context); 9 | }), 10 | ); 11 | } 12 | 13 | export function deactivate() {} 14 | -------------------------------------------------------------------------------- /examples/vue-esm/extension/views/helper.ts: -------------------------------------------------------------------------------- 1 | import type { Disposable, ExtensionContext, Webview } from 'vscode'; 2 | import { window } from 'vscode'; 3 | 4 | export class WebviewHelper { 5 | public static setupHtml(webview: Webview, context: ExtensionContext) { 6 | return __getWebviewHtml__({ 7 | serverUrl: process.env.VITE_DEV_SERVER_URL, 8 | webview, 9 | context, 10 | injectCode: ``, 11 | }); 12 | } 13 | 14 | public static setupWebviewHooks(webview: Webview, disposables: Disposable[]) { 15 | webview.onDidReceiveMessage( 16 | (message: any) => { 17 | const type = message.type; 18 | const data = message.data; 19 | console.log(`type: ${type}`); 20 | switch (type) { 21 | case 'hello': 22 | case 'hello2': 23 | case 'hello3': 24 | window.showInformationMessage(data); 25 | webview.postMessage({ type, data: Date.now() }); 26 | } 27 | }, 28 | undefined, 29 | disposables, 30 | ); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /examples/vue-esm/extension/views/panel.ts: -------------------------------------------------------------------------------- 1 | import type { Disposable, ExtensionContext, WebviewPanel } from 'vscode'; 2 | import { ViewColumn, window } from 'vscode'; 3 | import { WebviewHelper } from './helper'; 4 | 5 | export class MainPanel { 6 | public static currentPanel: MainPanel | undefined; 7 | private readonly _panel: WebviewPanel; 8 | private _disposables: Disposable[] = []; 9 | 10 | private constructor(panel: WebviewPanel, context: ExtensionContext) { 11 | this._panel = panel; 12 | 13 | this._panel.onDidDispose(() => this.dispose(), null, this._disposables); 14 | this._panel.webview.html = WebviewHelper.setupHtml(this._panel.webview, context); 15 | 16 | WebviewHelper.setupWebviewHooks(this._panel.webview, this._disposables); 17 | } 18 | 19 | public static render(context: ExtensionContext) { 20 | if (MainPanel.currentPanel) { 21 | MainPanel.currentPanel._panel.reveal(ViewColumn.One); 22 | } 23 | else { 24 | const panel = window.createWebviewPanel('showHelloWorld', 'Hello World', ViewColumn.One, { 25 | enableScripts: true, 26 | }); 27 | 28 | MainPanel.currentPanel = new MainPanel(panel, context); 29 | } 30 | MainPanel.currentPanel._panel.webview.postMessage({ type: 'hello', data: 'Hello World!' }); 31 | } 32 | 33 | /** 34 | * Cleans up and disposes of webview resources when the webview panel is closed. 35 | */ 36 | public dispose() { 37 | MainPanel.currentPanel = undefined; 38 | 39 | // Dispose of the current webview panel 40 | this._panel.dispose(); 41 | 42 | // Dispose of all disposables (i.e. commands) for the current webview panel 43 | while (this._disposables.length) { 44 | const disposable = this._disposables.pop(); 45 | if (disposable) { 46 | disposable.dispose(); 47 | } 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /examples/vue-esm/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Vite + Vue + TS 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /examples/vue-esm/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "publisher": "tomjs-test", 3 | "name": "vue-esm", 4 | "type": "module", 5 | "version": "0.0.0", 6 | "private": true, 7 | "description": "vite + vue", 8 | "main": "dist/extension/index.js", 9 | "engines": { 10 | "node": ">=18", 11 | "vscode": "^1.75.0" 12 | }, 13 | "activationEvents": [], 14 | "contributes": { 15 | "commands": [ 16 | { 17 | "command": "hello-world.showHelloWorld", 18 | "title": "Hello World: Show" 19 | } 20 | ] 21 | }, 22 | "scripts": { 23 | "dev": "vite", 24 | "build": "vue-tsc --noEmit && vite build", 25 | "preview": "vite preview" 26 | }, 27 | "dependencies": { 28 | "@tomjs/vscode-webview": "^2.0.0", 29 | "@vscode/webview-ui-toolkit": "^1.4.0", 30 | "vue": "^3.5.11" 31 | }, 32 | "devDependencies": { 33 | "@tomjs/vite-plugin-vscode": "workspace:^", 34 | "@types/vscode": "^1.75.0", 35 | "@types/vscode-webview": "^1.57.5" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /examples/vue-esm/src/App.vue: -------------------------------------------------------------------------------- 1 | 44 | 45 | 79 | 80 | 89 | -------------------------------------------------------------------------------- /examples/vue-esm/src/main.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue'; 2 | import App from './App.vue'; 3 | 4 | createApp(App).mount('#app'); 5 | -------------------------------------------------------------------------------- /examples/vue-esm/src/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from './vscode'; 2 | -------------------------------------------------------------------------------- /examples/vue-esm/src/utils/vscode.ts: -------------------------------------------------------------------------------- 1 | import { WebviewApi } from '@tomjs/vscode-webview'; 2 | 3 | // Exports class singleton to prevent multiple invocations of acquireVsCodeApi. 4 | export const vscodeApi = new WebviewApi(); 5 | -------------------------------------------------------------------------------- /examples/vue-esm/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | -------------------------------------------------------------------------------- /examples/vue-esm/src/window.d.ts: -------------------------------------------------------------------------------- 1 | export {}; 2 | 3 | declare global { 4 | interface Window { 5 | __FLAG1__: any; 6 | __FLAG2__: any; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /examples/vue-esm/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tomjs/tsconfig/vue.json", 3 | "references": [{ "path": "./tsconfig.node.json" }], 4 | "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"] 5 | } 6 | -------------------------------------------------------------------------------- /examples/vue-esm/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tomjs/tsconfig/node.json", 3 | "compilerOptions": { 4 | "composite": true 5 | }, 6 | "include": ["extension", "vite.config.ts"] 7 | } 8 | -------------------------------------------------------------------------------- /examples/vue-esm/vite.config.ts: -------------------------------------------------------------------------------- 1 | import vscode from '@tomjs/vite-plugin-vscode'; 2 | import vue from '@vitejs/plugin-vue'; 3 | import { defineConfig } from 'vite'; 4 | 5 | // https://vitejs.dev/config/ 6 | export default defineConfig({ 7 | plugins: [ 8 | vue({ 9 | template: { 10 | compilerOptions: { 11 | isCustomElement: (tag: string) => tag.startsWith('vscode-'), 12 | }, 13 | }, 14 | }), 15 | vscode({ 16 | webview: { 17 | // csp: '', 18 | }, 19 | }), 20 | ], 21 | }); 22 | -------------------------------------------------------------------------------- /examples/vue-import/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // A launch configuration that compiles the extension and then opens it inside a new window 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | { 6 | "version": "0.2.0", 7 | "configurations": [ 8 | { 9 | "name": "Debug Extension", 10 | "type": "extensionHost", 11 | "request": "launch", 12 | "args": [ 13 | "--disable-extensions", 14 | "--extensionDevelopmentPath=${workspaceFolder}" 15 | ], 16 | "outFiles": [ 17 | "${workspaceFolder}/dist/extension/*.js" 18 | ], 19 | "preLaunchTask": "npm: dev" 20 | }, 21 | { 22 | "name": "Preview Extension", 23 | "type": "extensionHost", 24 | "request": "launch", 25 | "args": [ 26 | "--extensionDevelopmentPath=${workspaceFolder}" 27 | ], 28 | "outFiles": [ 29 | "${workspaceFolder}/dist/extension/*.js" 30 | ], 31 | "preLaunchTask": "npm: build" 32 | } 33 | ] 34 | } 35 | -------------------------------------------------------------------------------- /examples/vue-import/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | // See https://go.microsoft.com/fwlink/?LinkId=733558 2 | // for the documentation about the tasks.json format 3 | { 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "type": "npm", 8 | "script": "dev", 9 | "problemMatcher": { 10 | "owner": "typescript", 11 | "fileLocation": "relative", 12 | "pattern": { 13 | "regexp": "^([a-zA-Z]\\:/?([\\w\\-]/?)+\\.\\w+):(\\d+):(\\d+): (ERROR|WARNING)\\: (.*)$", 14 | "file": 1, 15 | "line": 3, 16 | "column": 4, 17 | "code": 5, 18 | "message": 6 19 | }, 20 | "background": { 21 | "activeOnStart": true, 22 | "beginsPattern": "^.*extension build start*$", 23 | "endsPattern": "^.*extension (build|rebuild) success.*$" 24 | } 25 | }, 26 | "isBackground": true, 27 | "presentation": { 28 | "reveal": "never" 29 | }, 30 | "group": { 31 | "kind": "build", 32 | "isDefault": true 33 | } 34 | }, 35 | { 36 | "type": "npm", 37 | "script": "build", 38 | "group": { 39 | "kind": "build", 40 | "isDefault": true 41 | }, 42 | "problemMatcher": [] 43 | } 44 | ] 45 | } 46 | -------------------------------------------------------------------------------- /examples/vue-import/README.md: -------------------------------------------------------------------------------- 1 | # vue 2 | 3 | vite + vue 4 | -------------------------------------------------------------------------------- /examples/vue-import/eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import { defineConfig } from '@tomjs/eslint'; 2 | 3 | export default defineConfig({ 4 | rules: { 5 | 'no-console': 'off', 6 | 'n/prefer-global/process': 'off', 7 | }, 8 | }); 9 | -------------------------------------------------------------------------------- /examples/vue-import/extension/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /examples/vue-import/extension/index.ts: -------------------------------------------------------------------------------- 1 | import type { ExtensionContext } from 'vscode'; 2 | import { commands } from 'vscode'; 3 | import { MainPanel, MainPanel2 } from './views'; 4 | 5 | export function activate(context: ExtensionContext) { 6 | // Add command to the extension context 7 | context.subscriptions.push( 8 | commands.registerCommand('hello-world.showPage1', async () => { 9 | MainPanel.render(context); 10 | }), 11 | ); 12 | context.subscriptions.push( 13 | commands.registerCommand('hello-world.showPage2', async () => { 14 | MainPanel2.render(context); 15 | }), 16 | ); 17 | } 18 | 19 | export function deactivate() {} 20 | -------------------------------------------------------------------------------- /examples/vue-import/extension/views/helper.ts: -------------------------------------------------------------------------------- 1 | import type { Disposable, ExtensionContext, Webview } from 'vscode'; 2 | import { window } from 'vscode'; 3 | 4 | export class WebviewHelper { 5 | public static setupHtml(webview: Webview, context: ExtensionContext) { 6 | return __getWebviewHtml__({ 7 | serverUrl: process.env.VITE_DEV_SERVER_URL, 8 | webview, 9 | context, 10 | }); 11 | } 12 | 13 | public static setupHtml2(webview: Webview, context: ExtensionContext) { 14 | return __getWebviewHtml__({ 15 | serverUrl: `${process.env.VITE_DEV_SERVER_URL}/index2.html`, 16 | webview, 17 | context, 18 | inputName: 'index2', 19 | }); 20 | } 21 | 22 | public static setupWebviewHooks(webview: Webview, disposables: Disposable[]) { 23 | webview.onDidReceiveMessage( 24 | (message: any) => { 25 | const command = message.command; 26 | const text = message.text; 27 | console.log(`command: ${command}`); 28 | switch (command) { 29 | case 'hello': 30 | window.showInformationMessage(text); 31 | } 32 | }, 33 | undefined, 34 | disposables, 35 | ); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /examples/vue-import/extension/views/index.ts: -------------------------------------------------------------------------------- 1 | export * from './panel'; 2 | export * from './panel2'; 3 | -------------------------------------------------------------------------------- /examples/vue-import/extension/views/panel.ts: -------------------------------------------------------------------------------- 1 | import type { Disposable, ExtensionContext, WebviewPanel } from 'vscode'; 2 | import { ViewColumn, window } from 'vscode'; 3 | import { WebviewHelper } from './helper'; 4 | 5 | export class MainPanel { 6 | public static currentPanel: MainPanel | undefined; 7 | private readonly _panel: WebviewPanel; 8 | private _disposables: Disposable[] = []; 9 | 10 | private constructor(panel: WebviewPanel, context: ExtensionContext) { 11 | this._panel = panel; 12 | 13 | this._panel.onDidDispose(() => this.dispose(), null, this._disposables); 14 | this._panel.webview.html = WebviewHelper.setupHtml(this._panel.webview, context); 15 | 16 | WebviewHelper.setupWebviewHooks(this._panel.webview, this._disposables); 17 | } 18 | 19 | public static render(context: ExtensionContext) { 20 | if (MainPanel.currentPanel) { 21 | MainPanel.currentPanel._panel.reveal(ViewColumn.One); 22 | } 23 | else { 24 | const panel = window.createWebviewPanel('showPage1', 'Hello Page1', ViewColumn.One, { 25 | enableScripts: true, 26 | }); 27 | 28 | MainPanel.currentPanel = new MainPanel(panel, context); 29 | } 30 | } 31 | 32 | /** 33 | * Cleans up and disposes of webview resources when the webview panel is closed. 34 | */ 35 | public dispose() { 36 | MainPanel.currentPanel = undefined; 37 | 38 | // Dispose of the current webview panel 39 | this._panel.dispose(); 40 | 41 | // Dispose of all disposables (i.e. commands) for the current webview panel 42 | while (this._disposables.length) { 43 | const disposable = this._disposables.pop(); 44 | if (disposable) { 45 | disposable.dispose(); 46 | } 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /examples/vue-import/extension/views/panel2.ts: -------------------------------------------------------------------------------- 1 | import type { Disposable, ExtensionContext, WebviewPanel } from 'vscode'; 2 | import { ViewColumn, window } from 'vscode'; 3 | import { WebviewHelper } from './helper'; 4 | 5 | export class MainPanel2 { 6 | public static currentPanel: MainPanel2 | undefined; 7 | private readonly _panel: WebviewPanel; 8 | private _disposables: Disposable[] = []; 9 | 10 | private constructor(panel: WebviewPanel, context: ExtensionContext) { 11 | this._panel = panel; 12 | 13 | this._panel.onDidDispose(() => this.dispose(), null, this._disposables); 14 | this._panel.webview.html = WebviewHelper.setupHtml2(this._panel.webview, context); 15 | 16 | WebviewHelper.setupWebviewHooks(this._panel.webview, this._disposables); 17 | } 18 | 19 | public static render(context: ExtensionContext) { 20 | if (MainPanel2.currentPanel) { 21 | MainPanel2.currentPanel._panel.reveal(ViewColumn.One); 22 | } 23 | else { 24 | const panel = window.createWebviewPanel('showPage2', 'Hello Page2', ViewColumn.One, { 25 | enableScripts: true, 26 | }); 27 | 28 | MainPanel2.currentPanel = new MainPanel2(panel, context); 29 | } 30 | } 31 | 32 | /** 33 | * Cleans up and disposes of webview resources when the webview panel is closed. 34 | */ 35 | public dispose() { 36 | MainPanel2.currentPanel = undefined; 37 | 38 | // Dispose of the current webview panel 39 | this._panel.dispose(); 40 | 41 | // Dispose of all disposables (i.e. commands) for the current webview panel 42 | while (this._disposables.length) { 43 | const disposable = this._disposables.pop(); 44 | if (disposable) { 45 | disposable.dispose(); 46 | } 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /examples/vue-import/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | page 1 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /examples/vue-import/index2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | page 2 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /examples/vue-import/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "publisher": "tomjs-test", 3 | "name": "template-vue-import", 4 | "version": "0.0.0", 5 | "private": true, 6 | "description": "vite + vue", 7 | "main": "dist/extension/index.js", 8 | "engines": { 9 | "node": ">=18", 10 | "vscode": "^1.75.0" 11 | }, 12 | "activationEvents": [], 13 | "contributes": { 14 | "commands": [ 15 | { 16 | "command": "hello-world.showPage1", 17 | "title": "Hello World: Show Page 1" 18 | }, 19 | { 20 | "command": "hello-world.showPage2", 21 | "title": "Hello World: Show Page 2" 22 | } 23 | ] 24 | }, 25 | "scripts": { 26 | "dev": "vite", 27 | "build": "vue-tsc --noEmit && vite build", 28 | "preview": "vite preview" 29 | }, 30 | "dependencies": { 31 | "@vscode/webview-ui-toolkit": "^1.4.0", 32 | "pixi.js": "8.0.0-rc.7", 33 | "vue": "^3.5.11" 34 | }, 35 | "devDependencies": { 36 | "@tomjs/vite-plugin-vscode": "workspace:^", 37 | "@types/vscode": "^1.75.0", 38 | "@types/vscode-webview": "^1.57.5" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /examples/vue-import/src/App.vue: -------------------------------------------------------------------------------- 1 | 38 | 39 | 61 | 62 | 71 | -------------------------------------------------------------------------------- /examples/vue-import/src/App2.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 29 | 30 | 39 | -------------------------------------------------------------------------------- /examples/vue-import/src/main.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue'; 2 | import App from './App.vue'; 3 | 4 | createApp(App).mount('#app'); 5 | -------------------------------------------------------------------------------- /examples/vue-import/src/main2.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue'; 2 | import App from './App2.vue'; 3 | 4 | createApp(App).mount('#app'); 5 | -------------------------------------------------------------------------------- /examples/vue-import/src/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from './vscode'; 2 | -------------------------------------------------------------------------------- /examples/vue-import/src/utils/vscode.ts: -------------------------------------------------------------------------------- 1 | // Exports class singleton to prevent multiple invocations of acquireVsCodeApi. 2 | export const vscode = acquireVsCodeApi(); 3 | -------------------------------------------------------------------------------- /examples/vue-import/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | -------------------------------------------------------------------------------- /examples/vue-import/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tomjs/tsconfig/vue.json", 3 | "references": [{ "path": "./tsconfig.node.json" }], 4 | "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"] 5 | } 6 | -------------------------------------------------------------------------------- /examples/vue-import/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tomjs/tsconfig/node.json", 3 | "compilerOptions": { 4 | "composite": true 5 | }, 6 | "include": ["extension", "vite.config.ts"] 7 | } 8 | -------------------------------------------------------------------------------- /examples/vue-import/vite.config.ts: -------------------------------------------------------------------------------- 1 | import path from 'node:path'; 2 | import vscode from '@tomjs/vite-plugin-vscode'; 3 | import vue from '@vitejs/plugin-vue'; 4 | import { defineConfig } from 'vite'; 5 | 6 | // https://vitejs.dev/config/ 7 | export default defineConfig({ 8 | plugins: [ 9 | vue({ 10 | template: { 11 | compilerOptions: { 12 | isCustomElement: (tag: string) => tag.startsWith('vscode-'), 13 | }, 14 | }, 15 | }), 16 | vscode({ 17 | extension: { 18 | minify: false, 19 | }, 20 | }), 21 | ], 22 | build: { 23 | minify: false, 24 | rollupOptions: { 25 | input: [path.resolve(__dirname, 'index.html'), path.resolve(__dirname, 'index2.html')], 26 | output: { 27 | // https://rollupjs.org/configuration-options/#output-manualchunks 28 | manualChunks: (id) => { 29 | if (id.includes('pixi.js')) { 30 | return 'pixi'; 31 | } 32 | }, 33 | }, 34 | }, 35 | }, 36 | }); 37 | -------------------------------------------------------------------------------- /examples/vue/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // A launch configuration that compiles the extension and then opens it inside a new window 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | { 6 | "version": "0.2.0", 7 | "configurations": [ 8 | { 9 | "name": "Debug Extension", 10 | "type": "extensionHost", 11 | "request": "launch", 12 | "args": [ 13 | "--disable-extensions", 14 | "--extensionDevelopmentPath=${workspaceFolder}" 15 | ], 16 | "outFiles": [ 17 | "${workspaceFolder}/dist/extension/*.js" 18 | ], 19 | "preLaunchTask": "npm: dev" 20 | }, 21 | { 22 | "name": "Preview Extension", 23 | "type": "extensionHost", 24 | "request": "launch", 25 | "args": [ 26 | "--extensionDevelopmentPath=${workspaceFolder}" 27 | ], 28 | "outFiles": [ 29 | "${workspaceFolder}/dist/extension/*.js" 30 | ], 31 | "preLaunchTask": "npm: build" 32 | } 33 | ] 34 | } 35 | -------------------------------------------------------------------------------- /examples/vue/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | // See https://go.microsoft.com/fwlink/?LinkId=733558 2 | // for the documentation about the tasks.json format 3 | { 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "type": "npm", 8 | "script": "dev", 9 | "problemMatcher": { 10 | "owner": "typescript", 11 | "fileLocation": "relative", 12 | "pattern": { 13 | "regexp": "^([a-zA-Z]\\:/?([\\w\\-]/?)+\\.\\w+):(\\d+):(\\d+): (ERROR|WARNING)\\: (.*)$", 14 | "file": 1, 15 | "line": 3, 16 | "column": 4, 17 | "code": 5, 18 | "message": 6 19 | }, 20 | "background": { 21 | "activeOnStart": true, 22 | "beginsPattern": "^.*extension build start*$", 23 | "endsPattern": "^.*extension (build|rebuild) success.*$" 24 | } 25 | }, 26 | "isBackground": true, 27 | "presentation": { 28 | "reveal": "never" 29 | }, 30 | "group": { 31 | "kind": "build", 32 | "isDefault": true 33 | } 34 | }, 35 | { 36 | "type": "npm", 37 | "script": "build", 38 | "group": { 39 | "kind": "build", 40 | "isDefault": true 41 | }, 42 | "problemMatcher": [] 43 | } 44 | ] 45 | } 46 | -------------------------------------------------------------------------------- /examples/vue/README.md: -------------------------------------------------------------------------------- 1 | # vue 2 | 3 | vite + vue 4 | -------------------------------------------------------------------------------- /examples/vue/eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import { defineConfig } from '@tomjs/eslint'; 2 | 3 | export default defineConfig({ 4 | rules: { 5 | 'no-console': 'off', 6 | 'n/prefer-global/process': 'off', 7 | }, 8 | }); 9 | -------------------------------------------------------------------------------- /examples/vue/extension/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /examples/vue/extension/index.ts: -------------------------------------------------------------------------------- 1 | import type { ExtensionContext } from 'vscode'; 2 | import { commands } from 'vscode'; 3 | import { MainPanel } from './views/panel'; 4 | 5 | export function activate(context: ExtensionContext) { 6 | context.subscriptions.push( 7 | commands.registerCommand('hello-world.showHelloWorld', async () => { 8 | MainPanel.render(context); 9 | }), 10 | ); 11 | } 12 | 13 | export function deactivate() {} 14 | -------------------------------------------------------------------------------- /examples/vue/extension/views/helper.ts: -------------------------------------------------------------------------------- 1 | import type { Disposable, ExtensionContext, Webview } from 'vscode'; 2 | import { window } from 'vscode'; 3 | 4 | export class WebviewHelper { 5 | public static setupHtml(webview: Webview, context: ExtensionContext) { 6 | return __getWebviewHtml__({ 7 | serverUrl: process.env.VITE_DEV_SERVER_URL, 8 | webview, 9 | context, 10 | injectCode: ``, 11 | }); 12 | } 13 | 14 | public static setupWebviewHooks(webview: Webview, disposables: Disposable[]) { 15 | webview.onDidReceiveMessage( 16 | (message: any) => { 17 | const type = message.type; 18 | const data = message.data; 19 | console.log(`type: ${type}`); 20 | switch (type) { 21 | case 'hello': 22 | case 'hello2': 23 | case 'hello3': 24 | window.showInformationMessage(data); 25 | webview.postMessage({ type, data: Date.now() }); 26 | } 27 | }, 28 | undefined, 29 | disposables, 30 | ); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /examples/vue/extension/views/panel.ts: -------------------------------------------------------------------------------- 1 | import type { Disposable, ExtensionContext, WebviewPanel } from 'vscode'; 2 | import { ViewColumn, window } from 'vscode'; 3 | import { WebviewHelper } from './helper'; 4 | 5 | export class MainPanel { 6 | public static currentPanel: MainPanel | undefined; 7 | private readonly _panel: WebviewPanel; 8 | private _disposables: Disposable[] = []; 9 | 10 | private constructor(panel: WebviewPanel, context: ExtensionContext) { 11 | this._panel = panel; 12 | 13 | this._panel.onDidDispose(() => this.dispose(), null, this._disposables); 14 | this._panel.webview.html = WebviewHelper.setupHtml(this._panel.webview, context); 15 | 16 | WebviewHelper.setupWebviewHooks(this._panel.webview, this._disposables); 17 | } 18 | 19 | public static render(context: ExtensionContext) { 20 | if (MainPanel.currentPanel) { 21 | MainPanel.currentPanel._panel.reveal(ViewColumn.One); 22 | } 23 | else { 24 | const panel = window.createWebviewPanel('showHelloWorld', 'Hello World', ViewColumn.One, { 25 | enableScripts: true, 26 | }); 27 | 28 | MainPanel.currentPanel = new MainPanel(panel, context); 29 | } 30 | MainPanel.currentPanel._panel.webview.postMessage({ type: 'hello', data: 'Hello World!' }); 31 | } 32 | 33 | /** 34 | * Cleans up and disposes of webview resources when the webview panel is closed. 35 | */ 36 | public dispose() { 37 | MainPanel.currentPanel = undefined; 38 | 39 | // Dispose of the current webview panel 40 | this._panel.dispose(); 41 | 42 | // Dispose of all disposables (i.e. commands) for the current webview panel 43 | while (this._disposables.length) { 44 | const disposable = this._disposables.pop(); 45 | if (disposable) { 46 | disposable.dispose(); 47 | } 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /examples/vue/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Vite + Vue + TS 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /examples/vue/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "publisher": "tomjs-test", 3 | "name": "template-vue", 4 | "version": "0.0.0", 5 | "private": true, 6 | "description": "vite + vue", 7 | "main": "dist/extension/index.js", 8 | "engines": { 9 | "node": ">=18", 10 | "vscode": "^1.75.0" 11 | }, 12 | "activationEvents": [], 13 | "contributes": { 14 | "commands": [ 15 | { 16 | "command": "hello-world.showHelloWorld", 17 | "title": "Hello World: Show" 18 | } 19 | ] 20 | }, 21 | "scripts": { 22 | "dev": "vite", 23 | "build": "vue-tsc --noEmit && vite build", 24 | "preview": "vite preview" 25 | }, 26 | "dependencies": { 27 | "@tomjs/vscode-webview": "^2.0.0", 28 | "@vscode/webview-ui-toolkit": "^1.4.0", 29 | "vue": "^3.5.11" 30 | }, 31 | "devDependencies": { 32 | "@tomjs/vite-plugin-vscode": "workspace:^", 33 | "@types/vscode": "^1.75.0", 34 | "@types/vscode-webview": "^1.57.5" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /examples/vue/src/App.vue: -------------------------------------------------------------------------------- 1 | 44 | 45 | 79 | 80 | 89 | -------------------------------------------------------------------------------- /examples/vue/src/main.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue'; 2 | import App from './App.vue'; 3 | 4 | createApp(App).mount('#app'); 5 | -------------------------------------------------------------------------------- /examples/vue/src/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from './vscode'; 2 | -------------------------------------------------------------------------------- /examples/vue/src/utils/vscode.ts: -------------------------------------------------------------------------------- 1 | import { WebviewApi } from '@tomjs/vscode-webview'; 2 | 3 | // Exports class singleton to prevent multiple invocations of acquireVsCodeApi. 4 | export const vscodeApi = new WebviewApi(); 5 | -------------------------------------------------------------------------------- /examples/vue/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | -------------------------------------------------------------------------------- /examples/vue/src/window.d.ts: -------------------------------------------------------------------------------- 1 | export {}; 2 | 3 | declare global { 4 | interface Window { 5 | __FLAG1__: any; 6 | __FLAG2__: any; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /examples/vue/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tomjs/tsconfig/vue.json", 3 | "references": [{ "path": "./tsconfig.node.json" }], 4 | "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"] 5 | } 6 | -------------------------------------------------------------------------------- /examples/vue/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tomjs/tsconfig/node.json", 3 | "compilerOptions": { 4 | "composite": true 5 | }, 6 | "include": ["extension", "vite.config.ts"] 7 | } 8 | -------------------------------------------------------------------------------- /examples/vue/vite.config.ts: -------------------------------------------------------------------------------- 1 | import vscode from '@tomjs/vite-plugin-vscode'; 2 | import vue from '@vitejs/plugin-vue'; 3 | import { defineConfig } from 'vite'; 4 | 5 | // https://vitejs.dev/config/ 6 | export default defineConfig({ 7 | plugins: [ 8 | vue({ 9 | template: { 10 | compilerOptions: { 11 | isCustomElement: (tag: string) => tag.startsWith('vscode-'), 12 | }, 13 | }, 14 | }), 15 | vscode({ 16 | webview: { 17 | // csp: '', 18 | }, 19 | }), 20 | ], 21 | }); 22 | -------------------------------------------------------------------------------- /lint-staged.config.mjs: -------------------------------------------------------------------------------- 1 | export default { 2 | '*.{css,scss,less,html,vue}': ['stylelint --fix'], 3 | '*': ['eslint --fix'], 4 | }; 5 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@tomjs/vite-plugin-vscode", 3 | "version": "4.2.1", 4 | "packageManager": "pnpm@10.10.0", 5 | "description": "Use vue/react to develop 'vscode extension webview', supporting esm/cjs", 6 | "author": { 7 | "name": "Tom Gao", 8 | "email": "tom@tomgao.cc" 9 | }, 10 | "license": "MIT", 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/tomjs/vite-plugin-vscode.git" 14 | }, 15 | "keywords": [ 16 | "vite", 17 | "plugin", 18 | "vscode", 19 | "html", 20 | "hmr", 21 | "extension", 22 | "webview", 23 | "esm", 24 | "cjs" 25 | ], 26 | "exports": { 27 | ".": { 28 | "import": "./dist/index.mjs", 29 | "require": "./dist/index.js" 30 | }, 31 | "./webview": { 32 | "import": "./dist/webview.mjs", 33 | "require": "./dist/webview.js" 34 | }, 35 | "./client": "./dist/client.global.js", 36 | "./env": "./env.d.ts" 37 | }, 38 | "main": "./dist/index.js", 39 | "module": "./dist/index.mjs", 40 | "types": "./dist/index.d.ts", 41 | "files": [ 42 | "dist", 43 | "env.d.ts" 44 | ], 45 | "engines": { 46 | "node": ">=18" 47 | }, 48 | "publishConfig": { 49 | "access": "public", 50 | "registry": "https://registry.npmjs.org/" 51 | }, 52 | "scripts": { 53 | "dev": "pnpm clean && tsup --watch", 54 | "build": "pnpm clean && tsup", 55 | "clean": "rimraf ./dist", 56 | "lint": "run-s lint:stylelint lint:eslint", 57 | "lint:eslint": "eslint --fix", 58 | "lint:stylelint": "stylelint \"examples/**/*.{css,scss,less,vue,html}\" --fix --cache", 59 | "prepare": "simple-git-hooks", 60 | "prepublishOnly": "pnpm build" 61 | }, 62 | "peerDependencies": { 63 | "vite": ">=2" 64 | }, 65 | "dependencies": { 66 | "@tomjs/logger": "^1.4.0", 67 | "@tomjs/node": "^2.2.3", 68 | "dayjs": "^1.11.13", 69 | "execa": "^5.1.1", 70 | "kolorist": "^1.8.0", 71 | "lodash.clonedeep": "^4.5.0", 72 | "lodash.merge": "^4.6.2", 73 | "node-html-parser": "^6.1.13", 74 | "tsup": "^8.4.0" 75 | }, 76 | "devDependencies": { 77 | "@commitlint/cli": "^19.8.1", 78 | "@tomjs/commitlint": "^4.0.0", 79 | "@tomjs/eslint": "^5.0.0", 80 | "@tomjs/stylelint": "^6.0.0", 81 | "@tomjs/tsconfig": "^1.7.2", 82 | "@types/lodash.clonedeep": "^4.5.9", 83 | "@types/lodash.merge": "^4.6.9", 84 | "@types/node": "18.19.100", 85 | "@vitejs/plugin-vue": "^5.2.4", 86 | "cross-env": "^7.0.3", 87 | "eslint": "^9.26.0", 88 | "globals": "^15.15.0", 89 | "lint-staged": "^15.5.2", 90 | "npm-run-all": "^4.1.5", 91 | "rimraf": "^6.0.1", 92 | "simple-git-hooks": "^2.13.0", 93 | "stylelint": "^16.19.1", 94 | "tsx": "^4.19.4", 95 | "typescript": "~5.7.3", 96 | "vite": "^6.3.5", 97 | "vue-tsc": "^2.2.10" 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - examples/* 3 | -------------------------------------------------------------------------------- /simple-git-hooks.mjs: -------------------------------------------------------------------------------- 1 | export default { 2 | 'pre-commit': 'pnpm lint-staged', 3 | 'commit-msg': 'pnpm commitlint --edit "$1"', 4 | }; 5 | -------------------------------------------------------------------------------- /src/constants.ts: -------------------------------------------------------------------------------- 1 | export const PLUGIN_NAME = 'tomjs:vscode'; 2 | export const ORG_NAME = '@tomjs'; 3 | export const PACKAGE_NAME = '@tomjs/vite-plugin-vscode'; 4 | export const WEBVIEW_METHOD_NAME = '__getWebviewHtml__'; 5 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import type { Options as TsupOptions } from 'tsup'; 2 | import type { PluginOption, ResolvedConfig, UserConfig } from 'vite'; 3 | import type { ExtensionOptions, PluginOptions, WebviewOption } from './types'; 4 | import fs from 'node:fs'; 5 | import os from 'node:os'; 6 | import path from 'node:path'; 7 | import { cwd } from 'node:process'; 8 | import { emptyDirSync, readFileSync, readJsonSync } from '@tomjs/node'; 9 | import merge from 'lodash.merge'; 10 | import { parse as htmlParser } from 'node-html-parser'; 11 | import { build as tsupBuild } from 'tsup'; 12 | import { PACKAGE_NAME, WEBVIEW_METHOD_NAME } from './constants'; 13 | import { createLogger } from './logger'; 14 | import { resolveServerUrl } from './utils'; 15 | 16 | export * from './types'; 17 | 18 | const isDev = process.env.NODE_ENV === 'development'; 19 | const logger = createLogger(); 20 | 21 | function getPkg() { 22 | const pkgFile = path.resolve(process.cwd(), 'package.json'); 23 | if (!fs.existsSync(pkgFile)) { 24 | throw new Error('Main file is not specified, and no package.json found'); 25 | } 26 | 27 | const pkg = readJsonSync(pkgFile); 28 | if (!pkg.main) { 29 | throw new Error('Main file is not specified, please check package.json'); 30 | } 31 | 32 | return pkg; 33 | } 34 | 35 | function preMergeOptions(options?: PluginOptions): PluginOptions { 36 | const pkg = getPkg(); 37 | const format = pkg.type === 'module' ? 'esm' : 'cjs'; 38 | 39 | const opts: PluginOptions = merge( 40 | { 41 | webview: true, 42 | recommended: true, 43 | debug: false, 44 | extension: { 45 | entry: 'extension/index.ts', 46 | outDir: 'dist-extension', 47 | target: format === 'esm' ? ['node20'] : ['es2019', 'node14'], 48 | format, 49 | shims: true, 50 | clean: true, 51 | dts: false, 52 | treeshake: isDev ? false : 'smallest', 53 | outExtension() { 54 | return { js: '.js' }; 55 | }, 56 | external: ['vscode'], 57 | skipNodeModulesBundle: isDev, 58 | } as ExtensionOptions, 59 | }, 60 | options, 61 | ); 62 | 63 | const opt = opts.extension || {}; 64 | 65 | ['entry', 'format'].forEach((prop) => { 66 | const value = opt[prop]; 67 | if (!Array.isArray(value) && value) { 68 | opt[prop] = [value]; 69 | } 70 | }); 71 | if (isDev) { 72 | opt.sourcemap = opt.sourcemap ?? true; 73 | } 74 | else { 75 | opt.minify ??= true; 76 | } 77 | 78 | opt.external = (['vscode'] as (string | RegExp)[]).concat(opt.external ?? []); 79 | 80 | if (!opt.skipNodeModulesBundle) { 81 | opt.noExternal = Object.keys(pkg.dependencies || {}).concat( 82 | Object.keys(pkg.peerDependencies || {}), 83 | ); 84 | } 85 | 86 | opts.extension = opt; 87 | 88 | if (opts.webview !== false) { 89 | let name = WEBVIEW_METHOD_NAME; 90 | if (typeof opts.webview === 'string') { 91 | name = opts.webview ?? WEBVIEW_METHOD_NAME; 92 | } 93 | opts.webview = Object.assign({ name }, opts.webview); 94 | } 95 | 96 | return opts; 97 | } 98 | 99 | const prodCachePkgName = `${PACKAGE_NAME}-inject`; 100 | function genProdWebviewCode(cache: Record, webview?: WebviewOption) { 101 | webview = Object.assign({}, webview); 102 | 103 | const prodCacheFolder = path.join(cwd(), 'node_modules', prodCachePkgName); 104 | emptyDirSync(prodCacheFolder); 105 | const destFile = path.join(prodCacheFolder, 'index.js'); 106 | 107 | function handleHtmlCode(html: string) { 108 | const root = htmlParser(html); 109 | const head = root.querySelector('head')!; 110 | if (!head) { 111 | root?.insertAdjacentHTML('beforeend', ''); 112 | } 113 | 114 | const csp 115 | = webview?.csp 116 | || ``; 117 | head.insertAdjacentHTML('afterbegin', csp); 118 | 119 | if (csp && csp.includes('{{nonce}}')) { 120 | const tags = { 121 | script: 'src', 122 | link: 'href', 123 | }; 124 | 125 | Object.keys(tags).forEach((tag) => { 126 | const elements = root.querySelectorAll(tag); 127 | elements.forEach((element) => { 128 | const attr = element.getAttribute(tags[tag]); 129 | if (attr) { 130 | element.setAttribute(tags[tag], `{{baseUri}}${attr}`); 131 | } 132 | 133 | element.setAttribute('nonce', '{{nonce}}'); 134 | }); 135 | }); 136 | } 137 | 138 | return root.removeWhitespace().toString(); 139 | } 140 | 141 | const cacheCode = /* js */ `const htmlCode = { 142 | ${Object.keys(cache) 143 | .map(s => `'${s}': \`${handleHtmlCode(cache[s])}\`,`) 144 | .join('\n')} 145 | };`; 146 | 147 | const code = /* js */ `import { Uri } from 'vscode'; 148 | 149 | ${cacheCode} 150 | 151 | function uuid() { 152 | let text = ''; 153 | const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; 154 | for (let i = 0; i < 32; i++) { 155 | text += possible.charAt(Math.floor(Math.random() * possible.length)); 156 | } 157 | return text; 158 | } 159 | 160 | export default function getWebviewHtml(options){ 161 | const { webview, context, inputName, injectCode } = options || {}; 162 | const nonce = uuid(); 163 | const baseUri = webview.asWebviewUri(Uri.joinPath(context.extensionUri, (process.env.VITE_WEBVIEW_DIST || 'dist'))); 164 | let html = htmlCode[inputName || 'index'] || ''; 165 | if (injectCode) { 166 | html = html.replace('', ''+ injectCode); 167 | } 168 | 169 | return html.replaceAll('{{cspSource}}', webview.cspSource).replaceAll('{{nonce}}', nonce).replaceAll('{{baseUri}}', baseUri); 170 | } 171 | `; 172 | fs.writeFileSync(destFile, code, { encoding: 'utf8' }); 173 | 174 | return fixWindowsPath(destFile); 175 | } 176 | 177 | function fixWindowsPath(webviewPath: string) { 178 | if (os.platform() === 'win32') { 179 | webviewPath = webviewPath.replaceAll('\\', '/'); 180 | } 181 | return webviewPath; 182 | } 183 | 184 | export function useVSCodePlugin(options?: PluginOptions): PluginOption { 185 | const opts = preMergeOptions(options); 186 | 187 | const handleConfig = (config: UserConfig): UserConfig => { 188 | let outDir = config?.build?.outDir || 'dist'; 189 | opts.extension ??= {}; 190 | if (opts.recommended) { 191 | opts.extension.outDir = path.resolve(outDir, 'extension'); 192 | outDir = path.resolve(outDir, 'webview'); 193 | } 194 | 195 | // assets 196 | const assetsDir = config?.build?.assetsDir || 'assets'; 197 | const output = { 198 | chunkFileNames: `${assetsDir}/[name].js`, 199 | entryFileNames: `${assetsDir}/[name].js`, 200 | assetFileNames: `${assetsDir}/[name].[ext]`, 201 | }; 202 | 203 | let rollupOutput = config?.build?.rollupOptions?.output ?? {}; 204 | if (Array.isArray(rollupOutput)) { 205 | rollupOutput.map(s => Object.assign(s, output)); 206 | } 207 | else { 208 | rollupOutput = Object.assign({}, rollupOutput, output); 209 | } 210 | 211 | return { 212 | build: { 213 | outDir, 214 | sourcemap: isDev ? true : config?.build?.sourcemap, 215 | rollupOptions: { 216 | output: rollupOutput, 217 | }, 218 | }, 219 | }; 220 | }; 221 | 222 | let devWebviewClient: string; 223 | if (opts.webview) { 224 | devWebviewClient = readFileSync(path.join(__dirname, 'client.global.js')); 225 | } 226 | 227 | let resolvedConfig: ResolvedConfig; 228 | // multiple entry index.html 229 | const prodHtmlCache: Record = {}; 230 | 231 | let devtoolsFlag = false; 232 | 233 | return [ 234 | { 235 | name: '@tomjs:vscode', 236 | apply: 'serve', 237 | config(config) { 238 | return handleConfig(config); 239 | }, 240 | configResolved(config) { 241 | resolvedConfig = config; 242 | }, 243 | configureServer(server) { 244 | if (!server || !server.httpServer) { 245 | return; 246 | } 247 | server.httpServer?.once('listening', async () => { 248 | const env = { 249 | NODE_ENV: server.config.mode || 'development', 250 | VITE_DEV_SERVER_URL: resolveServerUrl(server), 251 | }; 252 | 253 | logger.info('extension build start'); 254 | 255 | let buildCount = 0; 256 | 257 | const webview = opts?.webview as WebviewOption; 258 | 259 | const { onSuccess: _onSuccess, ...tsupOptions } = opts.extension || {}; 260 | await tsupBuild( 261 | merge(tsupOptions, { 262 | watch: true, 263 | env, 264 | silent: true, 265 | esbuildPlugins: !webview 266 | ? [] 267 | : [ 268 | { 269 | name: '@tomjs:vscode:inject', 270 | setup(build) { 271 | build.onLoad({ filter: /\.ts$/ }, async (args) => { 272 | const file = fs.readFileSync(args.path, 'utf-8'); 273 | if (file.includes(`${webview.name}(`)) { 274 | return { 275 | contents: 276 | `import ${webview.name} from '${PACKAGE_NAME}/webview';\n${file}`, 277 | loader: 'ts', 278 | }; 279 | } 280 | 281 | return {}; 282 | }); 283 | }, 284 | }, 285 | ], 286 | async onSuccess() { 287 | if (typeof _onSuccess === 'function') { 288 | await _onSuccess(); 289 | } 290 | 291 | if (buildCount++ > 1) { 292 | logger.info('extension rebuild success'); 293 | } 294 | else { 295 | logger.info('extension build success'); 296 | } 297 | }, 298 | } as TsupOptions), 299 | ); 300 | }); 301 | }, 302 | transformIndexHtml(html) { 303 | if (!opts.webview) { 304 | return html; 305 | } 306 | 307 | if (opts.devtools ?? true) { 308 | let port: number | undefined; 309 | if ( 310 | resolvedConfig.plugins.find(s => 311 | ['vite:react-refresh', 'vite:react-swc'].includes(s.name), 312 | ) 313 | ) { 314 | port = 8097; 315 | } 316 | else if (resolvedConfig.plugins.find(s => ['vite:vue', 'vite:vue2'].includes(s.name))) { 317 | port = 8098; 318 | } 319 | 320 | if (port) { 321 | html = html.replace( 322 | //i, 323 | ``, 324 | ); 325 | } 326 | else if (!devtoolsFlag) { 327 | devtoolsFlag = true; 328 | logger.warn('Only support react-devtools and vue-devtools!'); 329 | } 330 | } 331 | 332 | return html.replace(/<\/title>/i, ``); 333 | }, 334 | }, 335 | { 336 | name: '@tomjs:vscode', 337 | apply: 'build', 338 | enforce: 'post', 339 | config(config) { 340 | return handleConfig(config); 341 | }, 342 | configResolved(config) { 343 | resolvedConfig = config; 344 | }, 345 | transformIndexHtml(html, ctx) { 346 | if (!opts.webview) { 347 | return html; 348 | } 349 | 350 | prodHtmlCache[ctx.chunk?.name as string] = html; 351 | return html; 352 | }, 353 | closeBundle() { 354 | let webviewPath: string; 355 | 356 | const webview = opts?.webview as WebviewOption; 357 | if (webview) { 358 | webviewPath = genProdWebviewCode(prodHtmlCache, webview); 359 | } 360 | 361 | let outDir = resolvedConfig.build.outDir.replace(cwd(), '').replaceAll('\\', '/'); 362 | if (outDir.startsWith('/')) { 363 | outDir = outDir.substring(1); 364 | } 365 | const env = { 366 | NODE_ENV: resolvedConfig.mode || 'production', 367 | VITE_WEBVIEW_DIST: outDir, 368 | }; 369 | 370 | logger.info('extension build start'); 371 | 372 | const { onSuccess: _onSuccess, ...tsupOptions } = opts.extension || {}; 373 | tsupBuild( 374 | merge(tsupOptions, { 375 | env, 376 | silent: true, 377 | esbuildPlugins: !webview 378 | ? [] 379 | : [ 380 | { 381 | name: '@tomjs:vscode:inject', 382 | setup(build) { 383 | build.onLoad({ filter: /\.ts$/ }, async (args) => { 384 | const file = fs.readFileSync(args.path, 'utf-8'); 385 | if (file.includes(`${webview.name}(`)) { 386 | return { 387 | contents: `import ${webview.name} from \`${webviewPath}\`;\n${file}`, 388 | loader: 'ts', 389 | }; 390 | } 391 | 392 | return {}; 393 | }); 394 | }, 395 | }, 396 | ], 397 | async onSuccess() { 398 | if (typeof _onSuccess === 'function') { 399 | await _onSuccess(); 400 | } 401 | 402 | logger.info('extension build success'); 403 | }, 404 | }) as TsupOptions, 405 | ); 406 | }, 407 | }, 408 | ]; 409 | } 410 | 411 | export default useVSCodePlugin; 412 | -------------------------------------------------------------------------------- /src/logger.ts: -------------------------------------------------------------------------------- 1 | import Logger from '@tomjs/logger'; 2 | import { PLUGIN_NAME } from './constants'; 3 | 4 | export function createLogger() { 5 | return new Logger({ 6 | prefix: `[${PLUGIN_NAME}]`, 7 | time: true, 8 | }); 9 | } 10 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | import type { Options } from 'tsup'; 2 | 3 | /** 4 | * vscode extension options. See [tsup](https://tsup.egoist.dev/) and [API Doc](https://paka.dev/npm/tsup) for more information. 5 | */ 6 | export interface ExtensionOptions 7 | extends Omit< 8 | Options, 9 | 'entry' | 'format' | 'outDir' | 'watch' | 'onSuccess' | 'skipNodeModulesBundle' 10 | > { 11 | /** 12 | * The extension entry file. 13 | * @default "extension/index.ts" 14 | */ 15 | entry?: string; 16 | /** 17 | * The output directory for the extension files. Default is `dist-extension`. 18 | * @default "dist-extension" 19 | */ 20 | outDir?: string; 21 | /** 22 | * The bundle format. Currently only supports cjs. 23 | */ 24 | format?: 'cjs'; 25 | /** 26 | * Skip dependencies and peerDependencies bundle. Default is false. 27 | */ 28 | skipNodeModulesBundle?: boolean; 29 | /** 30 | * A function that will be executed after the build succeeds. 31 | */ 32 | onSuccess?: () => Promise void | Promise)>; 33 | } 34 | 35 | /** 36 | * vscode webview options. 37 | */ 38 | export interface WebviewOption { 39 | /** 40 | * The method name to inject. Default is '__getWebviewHtml__' 41 | */ 42 | name?: string; 43 | /** 44 | * The CSP meta for the webview. Default is `` 45 | */ 46 | csp?: string; 47 | } 48 | 49 | /** 50 | * vite plugin options. 51 | */ 52 | export interface PluginOptions { 53 | /** 54 | * Recommended switch. Default is true. 55 | * if true, will have the following default behavior: 56 | * will change the extension/webview outDir to be parallel outDir; 57 | * eg. if vite build.outDir is 'dist', will change extension/webview to 'dist/extension' and 'dist/webview' 58 | * @default true 59 | */ 60 | recommended?: boolean; 61 | /** 62 | * Inject code into vscode extension code and web client code, so that webview can support HMR during the development stage. 63 | * 64 | * - vite serve 65 | * - extension: Inject `import __getWebviewHtml__ from '@tomjs/vite-plugin-vscode/webview';` at the top of the file that calls the `__getWebviewHtml__` method 66 | * - web: Add `` into webview client . Default is true. 88 | * - true: 89 | * - react: inject `` 90 | * - vue: inject `` 91 | * @default true 92 | */ 93 | devtools?: boolean; 94 | } 95 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | import type { AddressInfo } from 'node:net'; 2 | import type { ViteDevServer } from 'vite'; 3 | 4 | /** 5 | * @see https://github.com/vitejs/vite/blob/v4.0.1/packages/vite/src/node/constants.ts#L137-L147 6 | */ 7 | export function resolveHostname(hostname: string) { 8 | const loopbackHosts = new Set([ 9 | 'localhost', 10 | '127.0.0.1', 11 | '::1', 12 | '0000:0000:0000:0000:0000:0000:0000:0001', 13 | ]); 14 | const wildcardHosts = new Set(['0.0.0.0', '::', '0000:0000:0000:0000:0000:0000:0000:0000']); 15 | 16 | return loopbackHosts.has(hostname) || wildcardHosts.has(hostname) ? 'localhost' : hostname; 17 | } 18 | 19 | export function resolveServerUrl(server: ViteDevServer) { 20 | const addressInfo = server.httpServer!.address(); 21 | const isAddressInfo = (x: any): x is AddressInfo => x?.address; 22 | 23 | if (isAddressInfo(addressInfo)) { 24 | const { address, port } = addressInfo; 25 | const hostname = resolveHostname(address); 26 | 27 | const options = server.config.server; 28 | const protocol = options.https ? 'https' : 'http'; 29 | const devBase = server.config.base; 30 | 31 | const path = typeof options.open === 'string' ? options.open : devBase; 32 | const url = path.startsWith('http') ? path : `${protocol}://${hostname}:${port}${path}`; 33 | 34 | return url; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/webview/client.ts: -------------------------------------------------------------------------------- 1 | if (window.top === window.self) { 2 | throw new Error('[vscode:client]: must run in vscode webview'); 3 | } 4 | 5 | const TAG = '[@tomjs:vscode:client] '; 6 | 7 | patchAcquireVsCodeApi(); 8 | 9 | function onDomReady(callback) { 10 | if (document.readyState === 'interactive' || document.readyState === 'complete') { 11 | callback(); 12 | } 13 | else { 14 | document.addEventListener('DOMContentLoaded', callback); 15 | } 16 | } 17 | 18 | // patch style and state 19 | function patchInitData(data) { 20 | onDomReady(() => { 21 | console.log(TAG, 'patch client style'); 22 | const { style, body, root } = data; 23 | 24 | document.documentElement.style.cssText = root.cssText; 25 | document.body.className = body.className; 26 | Object.keys(body.dataset).forEach((key) => { 27 | document.body.dataset[key] = body.dataset[key]; 28 | }); 29 | 30 | const defaultStyles = document.createElement('style'); 31 | defaultStyles.id = '_defaultStyles'; 32 | defaultStyles.textContent = style; 33 | document.head.appendChild(defaultStyles); 34 | }); 35 | } 36 | 37 | const POST_MESSAGE_TYPE = '[vscode:client]:postMessage'; 38 | function patchAcquireVsCodeApi() { 39 | class AcquireVsCodeApi { 40 | postMessage(message: any) { 41 | console.log(TAG, 'mock acquireVsCodeApi.postMessage:', message); 42 | window.parent.postMessage({ type: POST_MESSAGE_TYPE, data: message }, '*'); 43 | } 44 | 45 | getState() { 46 | console.log(TAG, 'mock acquireVsCodeApi.getState'); 47 | const state = sessionStorage.getItem('vscodeState'); 48 | return state ? JSON.parse(state) : undefined; 49 | } 50 | 51 | setState(newState: any) { 52 | console.log(TAG, 'mock acquireVsCodeApi.setState:', newState); 53 | sessionStorage.setItem('vscodeState', JSON.stringify(newState)); 54 | return newState; 55 | } 56 | } 57 | 58 | console.log(TAG, 'patch acquireVsCodeApi'); 59 | let api; 60 | window.acquireVsCodeApi = () => { 61 | if (!api) { 62 | api = new AcquireVsCodeApi(); 63 | return api; 64 | } 65 | else { 66 | return api; 67 | } 68 | }; 69 | } 70 | 71 | const INIT_TYPE = '[vscode:extension]:init'; 72 | window.addEventListener('message', (e) => { 73 | const { type, data } = e.data || {}; 74 | if (!e.origin.startsWith('vscode-webview://') || type !== INIT_TYPE) { 75 | return; 76 | } 77 | 78 | patchInitData(data); 79 | }); 80 | 81 | const KEYBOARD_EVENT_TYPE = '[vscode:client]:commands'; 82 | const isMac = navigator.userAgent.includes('Macintosh'); 83 | document.addEventListener('keydown', (e) => { 84 | console.log(e); 85 | const { metaKey, shiftKey, ctrlKey, altKey, key } = e; 86 | if (key === 'F1') { 87 | window.parent.postMessage({ type: KEYBOARD_EVENT_TYPE, data: 'F1' }, '*'); 88 | } 89 | else if (isMac && metaKey && !altKey && !ctrlKey) { 90 | if (shiftKey) { 91 | if (key === 'z') { 92 | document.execCommand('redo'); 93 | } 94 | } 95 | else if (key === 'a') { 96 | document.execCommand('selectAll'); 97 | } 98 | else if (key === 'c') { 99 | document.execCommand('copy'); 100 | } 101 | else if (key === 'v') { 102 | document.execCommand('paste'); 103 | } 104 | else if (key === 'x') { 105 | document.execCommand('cut'); 106 | } 107 | else if (key === 'z') { 108 | document.execCommand('undo'); 109 | } 110 | } 111 | }); 112 | -------------------------------------------------------------------------------- /src/webview/global.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.html' { 2 | const content: string; 3 | export default content; 4 | } 5 | -------------------------------------------------------------------------------- /src/webview/template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 29 | 30 | 174 | 175 | 176 | 177 |
178 | 185 |
186 | 187 | 188 | -------------------------------------------------------------------------------- /src/webview/webview.ts: -------------------------------------------------------------------------------- 1 | import template from './template.html'; 2 | 3 | export interface WebviewHtmlOptions { 4 | /** 5 | * local server url 6 | */ 7 | serverUrl: string; 8 | } 9 | 10 | /** 11 | * 12 | * @param options serverUrl string or object options 13 | */ 14 | 15 | export function getWebviewHtml(options: WebviewHtmlOptions) { 16 | const opts: WebviewHtmlOptions = { 17 | serverUrl: '', 18 | }; 19 | 20 | Object.assign(opts, options); 21 | 22 | return (template as string).replace(/\{\{serverUrl\}\}/g, opts.serverUrl); 23 | } 24 | 25 | export default getWebviewHtml; 26 | -------------------------------------------------------------------------------- /src/webview/window.d.ts: -------------------------------------------------------------------------------- 1 | export {}; 2 | 3 | declare global { 4 | interface Window { 5 | acquireVsCodeApi: any; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /stylelint.config.mjs: -------------------------------------------------------------------------------- 1 | export default { 2 | extends: ['@tomjs/stylelint'], 3 | }; 4 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tomjs/tsconfig/node.json", 3 | "references": [ 4 | { 5 | "path": "./tsconfig.web.json" 6 | } 7 | ], 8 | "include": ["src/**/*.ts", "src/**/*.d.ts"], 9 | "exclude": ["src/webview/client.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /tsconfig.web.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tomjs/tsconfig/vue.json", 3 | "compilerOptions": { 4 | "composite": true, 5 | "noEmit": false 6 | }, 7 | "include": ["src/webview/client.ts", "src/webview/window.d.ts"] 8 | } 9 | -------------------------------------------------------------------------------- /tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'tsup'; 2 | import pkg from './package.json'; 3 | 4 | export default defineConfig(() => { 5 | return [ 6 | { 7 | entry: ['src/index.ts'], 8 | format: ['esm', 'cjs'], 9 | target: ['es2021', 'node16'], 10 | external: ['vite'].concat(Object.keys(pkg.dependencies || {})), 11 | shims: true, 12 | clean: false, 13 | dts: true, 14 | splitting: true, 15 | }, 16 | { 17 | entry: ['src/webview/webview.ts'], 18 | format: ['esm', 'cjs'], 19 | target: ['es2020', 'node14'], 20 | shims: true, 21 | clean: false, 22 | dts: true, 23 | splitting: true, 24 | loader: { 25 | '.html': 'text', 26 | }, 27 | }, 28 | { 29 | entry: ['src/webview/client.ts'], 30 | format: ['iife'], 31 | target: ['chrome89'], 32 | platform: 'browser', 33 | clean: false, 34 | dts: false, 35 | }, 36 | ]; 37 | }); 38 | --------------------------------------------------------------------------------