├── .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 └── tsdown.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 | ## [5.0.0](https://github.com/tomjs/vite-plugin-vscode/compare/v4.3.0...v5.0.0) (2025-07-29) 2 | 3 | - feat: remove console.log [04209e2](https://github.com/tomjs/vite-plugin-vscode/commit/04209e2) 4 | - feat: optimize the configuration of tsdown [1c8cb13](https://github.com/tomjs/vite-plugin-vscode/commit/1c8cb13) 5 | - feat: chang pkg to module type [9395cc7](https://github.com/tomjs/vite-plugin-vscode/commit/9395cc7) 6 | - feat: migrate to tsdown (#32) [10c5b13](https://github.com/tomjs/vite-plugin-vscode/commit/10c5b13) 7 | 8 | ## [4.3.0](https://github.com/tomjs/vite-plugin-vscode/compare/v4.2.1...v4.3.0) (2025-07-23) 9 | 10 | - fix: no title tag issues #30 [1ceed30](https://github.com/tomjs/vite-plugin-vscode/commit/1ceed30) 11 | - chore: update deps [6a66e09](https://github.com/tomjs/vite-plugin-vscode/commit/6a66e09) 12 | 13 | ## [4.2.1](https://github.com/tomjs/vite-plugin-vscode/compare/v4.2.0...v4.2.1) (2025-05-11) 14 | 15 | - feat: use @tomjs/logger [9ec87cb](https://github.com/tomjs/vite-plugin-vscode/commit/9ec87cb) 16 | - fix: examples eslint config [236f533](https://github.com/tomjs/vite-plugin-vscode/commit/236f533) 17 | - chore: update packageManager to v10 [242f2b5](https://github.com/tomjs/vite-plugin-vscode/commit/242f2b5) 18 | - fix: remove husky [06b5a0b](https://github.com/tomjs/vite-plugin-vscode/commit/06b5a0b) 19 | - chore: update lint and format code [1479751](https://github.com/tomjs/vite-plugin-vscode/commit/1479751) 20 | 21 | ## [4.2.0](https://github.com/tomjs/vite-plugin-vscode/compare/v4.1.0...v4.2.0) (2025-05-10) 22 | 23 | - feat: use tsup@8.4 and change target to node18 for esm [e0b7c7c](https://github.com/tomjs/vite-plugin-vscode/commit/e0b7c7c) 24 | 25 | ## [4.1.0](https://github.com/tomjs/vite-plugin-vscode/compare/v4.0.0...v4.1.0) (2025-05-10) 26 | 27 | - feat: support esm extension #27 [1a88b92](https://github.com/tomjs/vite-plugin-vscode/commit/1a88b92) 28 | 29 | ## [4.0.0](https://github.com/tomjs/vite-plugin-vscode/compare/v3.2.1...v4.0.0) (2025-03-20) 30 | 31 | - feat: merge the **getWebviewHtml** method for dev and prod into one [ebbb3ef](https://github.com/tomjs/vite-plugin-vscode/commit/ebbb3ef) 32 | - docs: fix readme-cn [26ad4f6](https://github.com/tomjs/vite-plugin-vscode/commit/26ad4f6) 33 | - docs: adjust the example of the Multi-files paragraph [dcd3f18](https://github.com/tomjs/vite-plugin-vscode/commit/dcd3f18) 34 | - chore: update deps and use eslint@9 [9d69c49](https://github.com/tomjs/vite-plugin-vscode/commit/9d69c49) 35 | 36 | ## [3.2.1](https://github.com/tomjs/vite-plugin-vscode/compare/v3.2.0...v3.2.1) (2024-12-05) 37 | 38 | - fix: husky [66cec8c](https://github.com/tomjs/vite-plugin-vscode/commit/66cec8c) 39 | - docs: modify description [10082f9](https://github.com/tomjs/vite-plugin-vscode/commit/10082f9) 40 | 41 | ## [3.2.0](https://github.com/tomjs/vite-plugin-vscode/compare/v3.1.1...v3.2.0) (2024-12-05) 42 | 43 | - feat: support copy/paste commands for macos #21 [d0eec00](https://github.com/tomjs/vite-plugin-vscode/commit/d0eec00) 44 | - feat: add --disable-extensions to examples [6bf213f](https://github.com/tomjs/vite-plugin-vscode/commit/6bf213f) 45 | 46 | ## [3.1.1](https://github.com/tomjs/vite-plugin-vscode/compare/v3.1.0...v3.1.1) (2024-10-29) 47 | 48 | - fix: devtools warn content [dd562a6](https://github.com/tomjs/vite-plugin-vscode/commit/dd562a6) 49 | 50 | ## [3.1.0](https://github.com/tomjs/vite-plugin-vscode/compare/v3.0.0...v3.1.0) (2024-10-25) 51 | 52 | - docs: optimize titles [f77be8f](https://github.com/tomjs/vite-plugin-vscode/commit/f77be8f) 53 | - feat: support devtools #18 [a65bb3e](https://github.com/tomjs/vite-plugin-vscode/commit/a65bb3e) 54 | 55 | ## [3.0.0](https://github.com/tomjs/vite-plugin-vscode/compare/v2.6.0...v3.0.0) (2024-10-08) 56 | 57 | - feat: chang getState and setState methods [b1f38c4](https://github.com/tomjs/vite-plugin-vscode/commit/b1f38c4) 58 | - fix: vite types error [cbf2d68](https://github.com/tomjs/vite-plugin-vscode/commit/cbf2d68) 59 | - fix: tsconfig.json error [e707a9a](https://github.com/tomjs/vite-plugin-vscode/commit/e707a9a) 60 | - fix: fix postmessage processing in app [648af76](https://github.com/tomjs/vite-plugin-vscode/commit/648af76) 61 | - fix: dependency version up and adjustments [7919573](https://github.com/tomjs/vite-plugin-vscode/commit/7919573) 62 | - fix: remove cloneDeep and fix tsup options #11 [c23448f](https://github.com/tomjs/vite-plugin-vscode/commit/c23448f) 63 | - fix: tsconfig error [2997f44](https://github.com/tomjs/vite-plugin-vscode/commit/2997f44) 64 | 65 | ## [2.6.0](https://github.com/tomjs/vite-plugin-vscode/compare/v2.5.5...v2.6.0) (2024-09-20) 66 | 67 | - docs: use jsdocs.io [0878e51](https://github.com/tomjs/vite-plugin-vscode/commit/0878e51) 68 | - fix: export types [a6cea35](https://github.com/tomjs/vite-plugin-vscode/commit/a6cea35) 69 | - fix: htmlCode key [6facf86](https://github.com/tomjs/vite-plugin-vscode/commit/6facf86) 70 | 71 | ## [2.5.5](https://github.com/tomjs/vite-plugin-vscode/compare/v2.5.4...v2.5.5) (2024-07-01) 72 | 73 | - docs: add api document [fd559b4](https://github.com/tomjs/vite-plugin-vscode/commit/fd559b4) 74 | - fix: add prepublishOnly script for release [2fbd83d](https://github.com/tomjs/vite-plugin-vscode/commit/2fbd83d) 75 | 76 | ## [2.5.4](https://github.com/tomjs/vite-plugin-vscode/compare/v2.5.3...v2.5.4) (2024-07-01) 77 | 78 | - docs: add api badge [986caf1](https://github.com/tomjs/vite-plugin-vscode/commit/986caf1) 79 | 80 | ## [2.5.3](https://github.com/tomjs/vite-plugin-vscode/compare/v2.5.2...v2.5.3) (2024-06-27) 81 | 82 | - docs: fix url [eefcbc5](https://github.com/tomjs/vite-plugin-vscode/commit/eefcbc5) 83 | 84 | ## [2.5.2](https://github.com/tomjs/vite-plugin-vscode/compare/v2.5.1...v2.5.2) (2024-06-27) 85 | 86 | - fix: client mock setState [1472665](https://github.com/tomjs/vite-plugin-vscode/commit/1472665) 87 | 88 | ## [2.5.1](https://github.com/tomjs/vite-plugin-vscode/compare/v2.5.0...v2.5.1) (2024-06-27) 89 | 90 | - chore: remove @tomjs/vscode-extension-webview package [f882fca](https://github.com/tomjs/vite-plugin-vscode/commit/f882fca) 91 | 92 | ## [2.5.0](https://github.com/tomjs/vite-plugin-vscode/compare/v2.4.1...v2.5.0) (2024-06-26) 93 | 94 | - fix: postMessage delay issue #5 [f62ef5b](https://github.com/tomjs/vite-plugin-vscode/commit/f62ef5b) 95 | 96 | ## [2.4.1](https://github.com/tomjs/vite-plugin-vscode/compare/v2.4.0...v2.4.1) (2024-06-22) 97 | 98 | - docs: update readme [0f067f6](https://github.com/tomjs/vite-plugin-vscode/commit/0f067f6) 99 | 100 | ## [2.4.0](https://github.com/tomjs/vite-plugin-vscode/compare/v2.3.1...v2.4.0) (2024-06-18) 101 | 102 | - fix: env.d.ts file export error [e23ad80](https://github.com/tomjs/vite-plugin-vscode/commit/e23ad80) 103 | - 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) 104 | 105 | ## [2.3.1](https://github.com/tomjs/vite-plugin-vscode/compare/v2.3.0...v2.3.1) (2024-06-04) 106 | 107 | - fix: @tomjs/vscode-extension-webview dependency issue #4 [450827d](https://github.com/tomjs/vite-plugin-vscode/commit/450827d) 108 | 109 | ## [2.3.0](https://github.com/tomjs/vite-plugin-vscode/compare/v2.2.0...v2.3.0) (2024-06-03) 110 | 111 | - chore: remove np [14ddc2f](https://github.com/tomjs/vite-plugin-vscode/commit/14ddc2f) 112 | - feat: ddd private field to package.json in examples [a31570b](https://github.com/tomjs/vite-plugin-vscode/commit/a31570b) 113 | - feat: when webview is production, remove Whitespace [bb13f8c](https://github.com/tomjs/vite-plugin-vscode/commit/bb13f8c) 114 | - feat: add custom csp option to webview [3e52440](https://github.com/tomjs/vite-plugin-vscode/commit/3e52440) 115 | - chore: add packageManager [8a8b504](https://github.com/tomjs/vite-plugin-vscode/commit/8a8b504) 116 | - docs: synchronous Chinese readme [62bc2e9](https://github.com/tomjs/vite-plugin-vscode/commit/62bc2e9) 117 | -------------------------------------------------------------------------------- /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 | During development, inject code into both `vscode extension code` and `web page` code to support `HMR`; during production builds, inject the final generated `index.html` code into the `vscode extension code` to minimize manual effort. 10 | 11 | ## Features 12 | 13 | - Use [tsdown](https://tsdown.dev/) 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://tsdown.dev/reference/api/Interface.Options) of [tsdown](https://tsdown.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 | | watchFiles | `string`\/`string[]` | `` | Watch extension code files during development | 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 | ### v5.0.0 447 | 448 | **Breaking Updates:** 449 | 450 | - Using [tsdown](https://tsdown.dev/zh-CN) instead of [tsup](https://tsup.egoist.dev/), the vscode extension [extension](#ExtensionOptions) configuration is changed to inherit [tsdown](https://tsdown.dev/zh-CN). 451 | 452 | ### v4.0.0 453 | 454 | **Breaking Updates:** 455 | 456 | - Merge the `__getWebviewHtml__` method for development and production into one, see [getWebviewHtml](#getwebviewhtml) 457 | 458 | ### v3.0.0 459 | 460 | **Breaking Updates:** 461 | 462 | - 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`. 463 | -------------------------------------------------------------------------------- /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 页面代码`中注入代码,用来支持 `HMR`;生产构建时,将最终生成的`index.html` 代码注入到 `vscode 扩展代码` 中,减少工作量。 10 | 11 | ## 特性 12 | 13 | - 使用 [tsdown](https://tsdown.dev/zh-CN/) 快速构建 `扩展代码` 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 | 继承自 [tsdown](https://tsdown.dev/zh-CN/) 的 [Options](https://tsdown.dev/zh-CN/reference/api/Interface.Options),添加了一些默认值,方便使用。 300 | 301 | | 参数名 | 类型 | 默认值 | 说明 | 302 | | ---------- | -------------------- | --------------------- | ------------------------ | 303 | | entry | `string` | `extension/index.ts` | 入口文件 | 304 | | outDir | `string` | `dist-extension/main` | 输出文件夹 | 305 | | watchFiles | `string`\/`string[]` | `` | 开发时监听扩展代码的文件 | 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 | ### v5.0.0 451 | 452 | **破坏性更新:** 453 | 454 | - 使用 [tsdown](https://tsdown.dev/zh-CN) 替代 [tsup](https://tsup.egoist.dev/),vscode 扩展 [extension](#ExtensionOptions) 配置改为继承 [tsdown](https://tsdown.dev/zh-CN) 455 | 456 | ### v4.0.0 457 | 458 | **破坏性更新:** 459 | 460 | - 开发和生产的 `__getWebviewHtml__` 方法合并为同一个,参考 [getWebviewHtml](#getwebviewhtml) 461 | 462 | ### v3.0.0 463 | 464 | **破坏性更新:** 465 | 466 | - 模拟的 `acquireVsCodeApi` 与 [@types/vscode-webview](https://www.npmjs.com/package/@types/vscode-webview) 的 `acquireVsCodeApi` 保持一致,改用 `sessionStorage.getItem` 和 `sessionStorage.setItem` 来实现 `getState` 和 `setState`。 467 | -------------------------------------------------------------------------------- /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 | "type": "commonjs", 5 | "version": "0.0.0", 6 | "private": true, 7 | "description": "vite + react", 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": "tsc && vite build", 25 | "preview": "vite preview" 26 | }, 27 | "dependencies": { 28 | "@vscode/webview-ui-toolkit": "^1.4.0", 29 | "react": "^18.3.1", 30 | "react-dom": "^18.3.1" 31 | }, 32 | "devDependencies": { 33 | "@tomjs/tsconfig": "^1.7.1", 34 | "@tomjs/vite-plugin-vscode": "workspace:^", 35 | "@types/react": "^18.3.3", 36 | "@types/react-dom": "^18.3.0", 37 | "@types/vscode": "^1.93.0", 38 | "@types/vscode-webview": "^1.57.5", 39 | "@vitejs/plugin-react": "^4.4.1", 40 | "@vitejs/plugin-react-swc": "^3.9.0" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /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 | "type": "commonjs", 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.showPage1", 18 | "title": "Hello World: Show Page 1" 19 | }, 20 | { 21 | "command": "hello-world.showPage2", 22 | "title": "Hello World: Show Page 2" 23 | } 24 | ] 25 | }, 26 | "scripts": { 27 | "dev": "vite", 28 | "build": "vue-tsc --noEmit && vite build", 29 | "preview": "vite preview" 30 | }, 31 | "dependencies": { 32 | "@vscode/webview-ui-toolkit": "^1.4.0", 33 | "pixi.js": "8.0.0-rc.7", 34 | "vue": "^3.5.11" 35 | }, 36 | "devDependencies": { 37 | "@tomjs/vite-plugin-vscode": "workspace:^", 38 | "@types/vscode": "^1.75.0", 39 | "@types/vscode-webview": "^1.57.5" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /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 { initExtension } from '@tomjs/vscode'; 3 | import { commands } from 'vscode'; 4 | import { MainPanel } from './views/panel'; 5 | 6 | export function activate(context: ExtensionContext) { 7 | initExtension(context); 8 | 9 | context.subscriptions.push( 10 | commands.registerCommand('hello-world.showHelloWorld', async () => { 11 | MainPanel.render(context); 12 | }), 13 | ); 14 | } 15 | 16 | export function deactivate() {} 17 | -------------------------------------------------------------------------------- /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 | "type": "commonjs", 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": "^2.5.0", 29 | "@tomjs/vscode-webview": "^2.0.0", 30 | "@vscode/webview-ui-toolkit": "^1.4.0", 31 | "vue": "^3.5.11" 32 | }, 33 | "devDependencies": { 34 | "@tomjs/vite-plugin-vscode": "workspace:^", 35 | "@types/vscode": "^1.75.0", 36 | "@types/vscode-webview": "^1.57.5" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /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 | "type": "module", 4 | "version": "5.0.0", 5 | "packageManager": "pnpm@10.13.1", 6 | "description": "Use vue/react to develop 'vscode extension webview', supporting esm/cjs", 7 | "author": { 8 | "name": "Tom Gao", 9 | "email": "tom@tomgao.cc" 10 | }, 11 | "license": "MIT", 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/tomjs/vite-plugin-vscode.git" 15 | }, 16 | "keywords": [ 17 | "vite", 18 | "plugin", 19 | "vscode", 20 | "html", 21 | "hmr", 22 | "extension", 23 | "webview", 24 | "esm", 25 | "cjs" 26 | ], 27 | "exports": { 28 | ".": "./dist/index.js", 29 | "./webview": "./dist/webview.js", 30 | "./client": "./dist/client.iife.js", 31 | "./env": "./env.d.ts" 32 | }, 33 | "module": "./dist/index.js", 34 | "types": "./dist/index.d.ts", 35 | "files": [ 36 | "dist", 37 | "env.d.ts" 38 | ], 39 | "engines": { 40 | "node": ">=18.19" 41 | }, 42 | "publishConfig": { 43 | "access": "public", 44 | "registry": "https://registry.npmjs.org/" 45 | }, 46 | "scripts": { 47 | "dev": "pnpm clean && tsdown --watch", 48 | "build": "pnpm clean && tsdown", 49 | "clean": "rimraf ./dist", 50 | "lint": "run-s lint:stylelint lint:eslint", 51 | "lint:eslint": "eslint --fix", 52 | "lint:stylelint": "stylelint \"examples/**/*.{css,scss,less,vue,html}\" --fix --cache", 53 | "prepare": "simple-git-hooks", 54 | "prepublishOnly": "pnpm build" 55 | }, 56 | "peerDependencies": { 57 | "vite": ">=2" 58 | }, 59 | "dependencies": { 60 | "@tomjs/logger": "^1.4.0", 61 | "@tomjs/node": "^2.2.3", 62 | "dayjs": "^1.11.13", 63 | "execa": "^8.0.1", 64 | "kolorist": "^1.8.0", 65 | "lodash.clonedeep": "^4.5.0", 66 | "lodash.merge": "^4.6.2", 67 | "node-html-parser": "^7.0.1", 68 | "tsdown": "~0.12.9" 69 | }, 70 | "devDependencies": { 71 | "@commitlint/cli": "^19.8.1", 72 | "@tomjs/commitlint": "^4.0.0", 73 | "@tomjs/eslint": "^5.2.0", 74 | "@tomjs/stylelint": "^6.0.0", 75 | "@tomjs/tsconfig": "^2.0.0", 76 | "@types/lodash.clonedeep": "^4.5.9", 77 | "@types/lodash.merge": "^4.6.9", 78 | "@types/node": "18.19.120", 79 | "@vitejs/plugin-vue": "^5.2.4", 80 | "cross-env": "^7.0.3", 81 | "eslint": "^9.31.0", 82 | "globals": "^16.3.0", 83 | "lint-staged": "^16.1.2", 84 | "npm-run-all": "^4.1.5", 85 | "publint": "^0.3.12", 86 | "rimraf": "^6.0.1", 87 | "simple-git-hooks": "^2.13.0", 88 | "stylelint": "^16.22.0", 89 | "tsx": "^4.20.3", 90 | "typescript": "~5.7.3", 91 | "vite": "^6.3.5", 92 | "vue-tsc": "^2.2.12" 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - examples/* 3 | 4 | onlyBuiltDependencies: 5 | - '@swc/core' 6 | - esbuild 7 | - simple-git-hooks 8 | - unrs-resolver 9 | -------------------------------------------------------------------------------- /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 TsdownOptions } from 'tsdown'; 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 { execa } from 'execa'; 10 | import merge from 'lodash.merge'; 11 | import { parse as htmlParser } from 'node-html-parser'; 12 | import { build as tsdownBuild } from 'tsdown'; 13 | import { ORG_NAME, PACKAGE_NAME, WEBVIEW_METHOD_NAME } from './constants'; 14 | import { createLogger } from './logger'; 15 | import { resolveServerUrl } from './utils'; 16 | 17 | export * from './types'; 18 | 19 | const isDev = process.env.NODE_ENV === 'development'; 20 | const logger = createLogger(); 21 | 22 | function getPkg() { 23 | const pkgFile = path.resolve(process.cwd(), 'package.json'); 24 | if (!fs.existsSync(pkgFile)) { 25 | throw new Error('Main file is not specified, and no package.json found'); 26 | } 27 | 28 | const pkg = readJsonSync(pkgFile); 29 | if (!pkg.main) { 30 | throw new Error('Main file is not specified, please check package.json'); 31 | } 32 | 33 | return pkg; 34 | } 35 | 36 | function preMergeOptions(options?: PluginOptions): PluginOptions { 37 | const pkg = getPkg(); 38 | const format = pkg.type === 'module' ? 'esm' : 'cjs'; 39 | 40 | const opts: PluginOptions = merge( 41 | { 42 | webview: true, 43 | recommended: true, 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, 53 | publint: false, 54 | // ignore tsdown.config.ts from project 55 | config: false, 56 | outExtensions() { 57 | return { js: '.js' }; 58 | }, 59 | external: ['vscode'], 60 | } as ExtensionOptions, 61 | } as PluginOptions, 62 | options, 63 | ); 64 | 65 | const opt = opts.extension || {}; 66 | 67 | if (isDev) { 68 | opt.sourcemap = opt.sourcemap ?? true; 69 | } 70 | else { 71 | opt.minify ??= true; 72 | opt.clean ??= true; 73 | } 74 | if (typeof opt.external !== 'function') { 75 | opt.external = (['vscode'] as (string | RegExp)[]).concat(opt.external ?? []); 76 | opt.external = [...new Set(opt.external)]; 77 | } 78 | else { 79 | const fn = opt.external; 80 | opt.external = function (id, parentId, isResolved) { 81 | if (id === 'vscode') { 82 | return true; 83 | } 84 | return fn(id, parentId, isResolved); 85 | }; 86 | } 87 | 88 | if (!isDev && !opt.skipNodeModulesBundle && !opt.noExternal) { 89 | opt.noExternal = Object.keys(pkg.dependencies || {}).concat( 90 | Object.keys(pkg.peerDependencies || {}), 91 | ); 92 | } 93 | 94 | opts.extension = opt; 95 | 96 | if (opts.webview !== false) { 97 | let name = WEBVIEW_METHOD_NAME; 98 | if (typeof opts.webview === 'string') { 99 | name = opts.webview ?? WEBVIEW_METHOD_NAME; 100 | } 101 | opts.webview = Object.assign({ name }, opts.webview); 102 | } 103 | 104 | return opts; 105 | } 106 | 107 | const prodCachePkgName = `${PACKAGE_NAME}-inject`; 108 | function genProdWebviewCode(cache: Record, webview?: WebviewOption) { 109 | webview = Object.assign({}, webview); 110 | 111 | const prodCacheFolder = path.join(cwd(), 'node_modules', prodCachePkgName); 112 | emptyDirSync(prodCacheFolder); 113 | const destFile = path.join(prodCacheFolder, 'index.js'); 114 | 115 | function handleHtmlCode(html: string) { 116 | const root = htmlParser(html); 117 | const head = root.querySelector('head')!; 118 | if (!head) { 119 | root?.insertAdjacentHTML('beforeend', ''); 120 | } 121 | 122 | const csp 123 | = webview?.csp 124 | || ``; 125 | head.insertAdjacentHTML('afterbegin', csp); 126 | 127 | if (csp && csp.includes('{{nonce}}')) { 128 | const tags = { 129 | script: 'src', 130 | link: 'href', 131 | }; 132 | 133 | Object.keys(tags).forEach((tag) => { 134 | const elements = root.querySelectorAll(tag); 135 | elements.forEach((element) => { 136 | const attr = element.getAttribute(tags[tag]); 137 | if (attr) { 138 | element.setAttribute(tags[tag], `{{baseUri}}${attr}`); 139 | } 140 | 141 | element.setAttribute('nonce', '{{nonce}}'); 142 | }); 143 | }); 144 | } 145 | 146 | return root.removeWhitespace().toString(); 147 | } 148 | 149 | const cacheCode = /* js */ `const htmlCode = { 150 | ${Object.keys(cache) 151 | .map(s => `'${s}': \`${handleHtmlCode(cache[s])}\`,`) 152 | .join('\n')} 153 | };`; 154 | 155 | const code = /* js */ `import { Uri } from 'vscode'; 156 | 157 | ${cacheCode} 158 | 159 | function uuid() { 160 | let text = ''; 161 | const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; 162 | for (let i = 0; i < 32; i++) { 163 | text += possible.charAt(Math.floor(Math.random() * possible.length)); 164 | } 165 | return text; 166 | } 167 | 168 | export default function getWebviewHtml(options){ 169 | const { webview, context, inputName, injectCode } = options || {}; 170 | const nonce = uuid(); 171 | const baseUri = webview.asWebviewUri(Uri.joinPath(context.extensionUri, (process.env.VITE_WEBVIEW_DIST || 'dist'))); 172 | let html = htmlCode[inputName || 'index'] || ''; 173 | if (injectCode) { 174 | html = html.replace('', ''+ injectCode); 175 | } 176 | 177 | return html.replaceAll('{{cspSource}}', webview.cspSource).replaceAll('{{nonce}}', nonce).replaceAll('{{baseUri}}', baseUri); 178 | } 179 | `; 180 | fs.writeFileSync(destFile, code, { encoding: 'utf8' }); 181 | 182 | return fixWindowsPath(destFile); 183 | } 184 | 185 | function fixWindowsPath(webviewPath: string) { 186 | if (os.platform() === 'win32') { 187 | webviewPath = webviewPath.replaceAll('\\', '/'); 188 | } 189 | return webviewPath; 190 | } 191 | 192 | export function useVSCodePlugin(options?: PluginOptions): PluginOption { 193 | const opts = preMergeOptions(options); 194 | 195 | const handleConfig = (config: UserConfig): UserConfig => { 196 | let outDir = config?.build?.outDir || 'dist'; 197 | opts.extension ??= {}; 198 | if (opts.recommended) { 199 | opts.extension.outDir = path.resolve(outDir, 'extension'); 200 | outDir = path.resolve(outDir, 'webview'); 201 | } 202 | 203 | // assets 204 | const assetsDir = config?.build?.assetsDir || 'assets'; 205 | const output = { 206 | chunkFileNames: `${assetsDir}/[name].js`, 207 | entryFileNames: `${assetsDir}/[name].js`, 208 | assetFileNames: `${assetsDir}/[name].[ext]`, 209 | }; 210 | 211 | let rollupOutput = config?.build?.rollupOptions?.output ?? {}; 212 | if (Array.isArray(rollupOutput)) { 213 | rollupOutput.map(s => Object.assign(s, output)); 214 | } 215 | else { 216 | rollupOutput = Object.assign({}, rollupOutput, output); 217 | } 218 | 219 | return { 220 | build: { 221 | outDir, 222 | sourcemap: isDev ? true : config?.build?.sourcemap, 223 | rollupOptions: { 224 | output: rollupOutput, 225 | }, 226 | }, 227 | }; 228 | }; 229 | 230 | let devWebviewClient: string; 231 | if (opts.webview) { 232 | devWebviewClient = readFileSync(path.join(__dirname, 'client.iife.js')); 233 | } 234 | 235 | let resolvedConfig: ResolvedConfig; 236 | // multiple entry index.html 237 | const prodHtmlCache: Record = {}; 238 | 239 | let devtoolsFlag = false; 240 | 241 | return [ 242 | { 243 | name: '@tomjs:vscode', 244 | apply: 'serve', 245 | config(config) { 246 | return handleConfig(config); 247 | }, 248 | configResolved(config) { 249 | resolvedConfig = config; 250 | }, 251 | configureServer(server) { 252 | if (!server || !server.httpServer) { 253 | return; 254 | } 255 | server.httpServer?.once('listening', async () => { 256 | const env = { 257 | NODE_ENV: server.config.mode || 'development', 258 | VITE_DEV_SERVER_URL: resolveServerUrl(server), 259 | }; 260 | 261 | logger.info('extension build start'); 262 | 263 | let buildCount = 0; 264 | 265 | const webview = opts?.webview as WebviewOption; 266 | 267 | const { onSuccess: _onSuccess, ignoreWatch, silent, watchFiles, ...tsdownOptions } = opts.extension || {}; 268 | await tsdownBuild( 269 | merge(tsdownOptions, { 270 | watch: watchFiles ?? (opts.recommended ? ['extension'] : true), 271 | ignoreWatch: ['.history', '.temp', '.tmp', '.cache', 'dist'].concat(Array.isArray(ignoreWatch) ? ignoreWatch : []), 272 | env, 273 | silent: silent ?? true, 274 | plugins: !webview 275 | ? [] 276 | : [ 277 | { 278 | name: `${ORG_NAME}:vscode:inject`, 279 | transform(code, id) { 280 | if (id.includes('node_modules')) { 281 | return; 282 | } 283 | 284 | if (code.includes(`${webview.name}(`)) { 285 | return `import ${webview.name} from '${PACKAGE_NAME}/webview';\n${code}`; 286 | } 287 | return code; 288 | }, 289 | }, 290 | ], 291 | async onSuccess(config, signal) { 292 | if (_onSuccess) { 293 | if (typeof _onSuccess === 'string') { 294 | await execa(_onSuccess); 295 | } 296 | else if (typeof _onSuccess === 'function') { 297 | await _onSuccess(config, signal); 298 | } 299 | } 300 | 301 | if (buildCount++ > 1) { 302 | logger.info('extension rebuild success'); 303 | } 304 | else { 305 | logger.info('extension build success'); 306 | } 307 | }, 308 | } as TsdownOptions), 309 | ); 310 | }); 311 | }, 312 | transformIndexHtml(html) { 313 | if (!opts.webview) { 314 | return html; 315 | } 316 | 317 | if (opts.devtools ?? true) { 318 | let port: number | undefined; 319 | if ( 320 | resolvedConfig.plugins.find(s => 321 | ['vite:react-refresh', 'vite:react-swc'].includes(s.name), 322 | ) 323 | ) { 324 | port = 8097; 325 | } 326 | else if (resolvedConfig.plugins.find(s => ['vite:vue', 'vite:vue2'].includes(s.name))) { 327 | port = 8098; 328 | } 329 | 330 | if (port) { 331 | html = html.replace(//i, ``); 332 | } 333 | else if (!devtoolsFlag) { 334 | devtoolsFlag = true; 335 | logger.warn('Only support react-devtools and vue-devtools!'); 336 | } 337 | } 338 | 339 | return html.replace(//i, ``); 340 | }, 341 | }, 342 | { 343 | name: '@tomjs:vscode', 344 | apply: 'build', 345 | enforce: 'post', 346 | config(config) { 347 | return handleConfig(config); 348 | }, 349 | configResolved(config) { 350 | resolvedConfig = config; 351 | }, 352 | transformIndexHtml(html, ctx) { 353 | if (!opts.webview) { 354 | return html; 355 | } 356 | 357 | prodHtmlCache[ctx.chunk?.name as string] = html; 358 | return html; 359 | }, 360 | closeBundle() { 361 | let webviewPath: string; 362 | 363 | const webview = opts?.webview as WebviewOption; 364 | if (webview) { 365 | webviewPath = genProdWebviewCode(prodHtmlCache, webview); 366 | } 367 | 368 | let outDir = resolvedConfig.build.outDir.replace(cwd(), '').replaceAll('\\', '/'); 369 | if (outDir.startsWith('/')) { 370 | outDir = outDir.substring(1); 371 | } 372 | const env = { 373 | NODE_ENV: resolvedConfig.mode || 'production', 374 | VITE_WEBVIEW_DIST: outDir, 375 | }; 376 | 377 | logger.info('extension build start'); 378 | 379 | const { onSuccess: _onSuccess, silent, ...tsupOptions } = opts.extension || {}; 380 | 381 | tsdownBuild( 382 | merge(tsupOptions, { 383 | env, 384 | silent: silent ?? true, 385 | plugins: !webview 386 | ? [] 387 | : [ 388 | { 389 | name: `${ORG_NAME}:vscode:inject`, 390 | transform(code, id) { 391 | if (id.includes('node_modules')) { 392 | return; 393 | } 394 | 395 | if (code.includes(`${webview.name}(`)) { 396 | return `import ${webview.name} from "${webviewPath}";\n${code}`; 397 | } 398 | return code; 399 | }, 400 | }, 401 | ], 402 | async onSuccess(config, signal) { 403 | if (_onSuccess) { 404 | if (typeof _onSuccess === 'string') { 405 | await execa(_onSuccess); 406 | } 407 | else if (typeof _onSuccess === 'function') { 408 | await _onSuccess(config, signal); 409 | } 410 | } 411 | logger.info('extension build success'); 412 | }, 413 | } as TsdownOptions), 414 | ); 415 | }, 416 | }, 417 | ]; 418 | } 419 | 420 | export default useVSCodePlugin; 421 | -------------------------------------------------------------------------------- /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 'tsdown'; 2 | 3 | /** 4 | * vscode extension options. See [tsdown](https://tsdown.dev/) and [Config Options](https://tsdown.dev/reference/config-options) for more information. 5 | */ 6 | export interface ExtensionOptions 7 | extends Omit< 8 | Options, 9 | 'entry' | 'format' | 'outDir' | 'watch' 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 | * `tsdown` watches the current working directory by default. You can set files that need to be watched, which may improve performance. 23 | * 24 | * If no value is specified, the default value of the "recommended" parameter is ["extension"] when it is true, otherwise it defaults to "true" 25 | */ 26 | watchFiles?: string | string[]; 27 | } 28 | 29 | /** 30 | * vscode webview options. 31 | */ 32 | export interface WebviewOption { 33 | /** 34 | * The method name to inject. Default is `__getWebviewHtml__` 35 | */ 36 | name?: string; 37 | /** 38 | * The CSP meta for the webview. Default is `` 39 | */ 40 | csp?: string; 41 | } 42 | 43 | /** 44 | * vite plugin options. 45 | */ 46 | export interface PluginOptions { 47 | /** 48 | * Recommended switch. Default is true. 49 | * if true, will have the following default behavior: 50 | * - will change the extension/webview outDir to be parallel outDir; 51 | * - if vite build.outDir is 'dist', will change extension/webview to 'dist/extension' and 'dist/webview' 52 | * @default true 53 | */ 54 | recommended?: boolean; 55 | /** 56 | * Inject code into vscode extension code and web client code, so that webview can support HMR during the development stage. 57 | * 58 | * - vite serve 59 | * - extension: Inject `import __getWebviewHtml__ from '@tomjs/vite-plugin-vscode/webview';` at the top of the file that calls the `__getWebviewHtml__` method 60 | * - web: Add `` into webview client . Default is true. 82 | * - true: 83 | * - react: inject `` 84 | * - vue: inject `` 85 | * @default true 86 | */ 87 | devtools?: boolean; 88 | } 89 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /tsdown.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'tsdown'; 2 | 3 | export default defineConfig(() => { 4 | return [ 5 | { 6 | entry: ['src/index.ts'], 7 | format: ['esm'], 8 | target: ['node18.19'], 9 | external: ['vite'], 10 | shims: true, 11 | clean: false, 12 | dts: true, 13 | publint: true, 14 | }, 15 | { 16 | entry: ['src/webview/webview.ts'], 17 | format: ['esm'], 18 | target: ['node18.19'], 19 | shims: true, 20 | clean: false, 21 | dts: true, 22 | publint: true, 23 | loader: { 24 | '.html': 'text', 25 | }, 26 | }, 27 | { 28 | entry: ['src/webview/client.ts'], 29 | format: ['iife'], 30 | target: ['chrome89'], 31 | platform: 'browser', 32 | clean: false, 33 | dts: false, 34 | }, 35 | ]; 36 | }); 37 | --------------------------------------------------------------------------------