├── .gitignore
├── app
├── api
│ └── index.ts
├── components
│ ├── Home.tsx
│ ├── ReceivedMessages.tsx
│ ├── SendMessage.tsx
│ ├── Message.tsx
│ ├── About.tsx
│ └── App.tsx
├── context
│ └── MessageContext.tsx
├── index.css
├── index.tsx
├── global.d.ts
├── tsconfig.json
└── routes
│ ├── RouteWithSubRoutes.tsx
│ └── config.tsx
├── .prettierrc
├── screenshot.gif
├── .vscode
├── extensions.json
├── tasks.json
├── settings.json
└── launch.json
├── src
├── config
│ └── index.ts
├── view
│ ├── messages
│ │ └── messageTypes.ts
│ └── ViewLoader.ts
└── extension.ts
├── .vscodeignore
├── CHANGELOG.md
├── test
├── extension.test.ts
├── runTest.ts
└── index.ts
├── webpack.config.js
├── tsconfig.json
├── .eslintrc.json
├── README.md
├── vsc-extension-quickstart.md
└── package.json
/.gitignore:
--------------------------------------------------------------------------------
1 | out
2 | node_modules
3 | .vscode-test/
4 | *.vsix
5 | .DS_Store
6 | .history
7 |
--------------------------------------------------------------------------------
/app/api/index.ts:
--------------------------------------------------------------------------------
1 | export const apiUrl = `https://randomuser.me/api/?gender=${apiUserGender}`;
2 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "printWidth": 100,
3 | "singleQuote": true,
4 | "arrowParens": "avoid"
5 | }
6 |
--------------------------------------------------------------------------------
/screenshot.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hacker0limbo/vscode-webview-react-boilerplate/HEAD/screenshot.gif
--------------------------------------------------------------------------------
/app/components/Home.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export const Home = () => {
4 | return
Home
;
5 | };
6 |
--------------------------------------------------------------------------------
/app/context/MessageContext.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export const MessagesContext = React.createContext([]);
4 |
--------------------------------------------------------------------------------
/app/index.css:
--------------------------------------------------------------------------------
1 | .navbar {
2 | padding: 0;
3 | }
4 |
5 | .navbar > li {
6 | display: inline-block;
7 | margin-right: 1rem;
8 | }
9 |
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | // See http://go.microsoft.com/fwlink/?LinkId=827846
3 | // for the documentation about the extensions.json format
4 | "recommendations": [
5 | "dbaeumer.vscode-eslint"
6 | ]
7 | }
8 |
--------------------------------------------------------------------------------
/app/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import { App } from './components/App';
4 | import './index.css';
5 |
6 | ReactDOM.render(, document.getElementById('root'));
7 |
--------------------------------------------------------------------------------
/src/config/index.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from 'vscode';
2 |
3 | export const getAPIUserGender = () => {
4 | const gender = vscode.workspace.getConfiguration('webviewReact').get('userApiGender', 'male');
5 | return gender;
6 | };
7 |
--------------------------------------------------------------------------------
/.vscodeignore:
--------------------------------------------------------------------------------
1 | .vscode/**
2 | .vscode-test/**
3 | out/test/**
4 | src/**
5 | test/**
6 | .gitignore
7 | vsc-extension-quickstart.md
8 | **/tsconfig.json
9 | **/.eslintrc.json
10 | **/*.map
11 | **/*.ts
12 | .prettierrc
13 | screenshot.gif
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change Log
2 |
3 | All notable changes to the "vscode-webview-react-boilerplate" extension will be documented in this file.
4 |
5 | Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file.
6 |
7 | ## [Unreleased]
8 |
9 | - Initial release
--------------------------------------------------------------------------------
/app/global.d.ts:
--------------------------------------------------------------------------------
1 | type Message = import('../src/view/messages/messageTypes').Message;
2 |
3 | type VSCode = {
4 | postMessage(message: T): void;
5 | getState(): any;
6 | setState(state: any): void;
7 | };
8 |
9 | declare const vscode: VSCode;
10 |
11 | declare const apiUserGender: string;
12 |
--------------------------------------------------------------------------------
/app/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "ESNext",
4 | "allowSyntheticDefaultImports": true,
5 | "moduleResolution": "node",
6 | "target": "ES5",
7 | "jsx": "react",
8 | "sourceMap": true,
9 | "experimentalDecorators": true,
10 | "lib": ["dom", "ES2015"],
11 | "strict": true
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/view/messages/messageTypes.ts:
--------------------------------------------------------------------------------
1 | export type MessageType = 'RELOAD' | 'COMMON';
2 |
3 | export interface Message {
4 | type: MessageType;
5 | payload?: any;
6 | }
7 |
8 | export interface CommonMessage extends Message {
9 | type: 'COMMON';
10 | payload: string;
11 | }
12 |
13 | export interface ReloadMessage extends Message {
14 | type: 'RELOAD';
15 | }
16 |
--------------------------------------------------------------------------------
/test/extension.test.ts:
--------------------------------------------------------------------------------
1 | import * as assert from 'assert';
2 | import * as vscode from 'vscode';
3 |
4 | describe('Extension Test Suite', () => {
5 | vscode.window.showInformationMessage('Start all tests.');
6 |
7 | it('Sample test', () => {
8 | assert.strictEqual(-1, [1, 2, 3].indexOf(5));
9 | assert.strictEqual(-1, [1, 2, 3].indexOf(0));
10 | });
11 | });
12 |
--------------------------------------------------------------------------------
/app/routes/RouteWithSubRoutes.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Route } from 'react-router-dom';
3 | import { RouteConfig } from './config';
4 |
5 | export const RouteWithSubRoutes = (route: RouteConfig) => {
6 | return (
7 |
8 |
9 |
10 | );
11 | };
12 |
--------------------------------------------------------------------------------
/.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": "watch",
9 | "problemMatcher": "$tsc-watch",
10 | "isBackground": true,
11 | "presentation": {
12 | "reveal": "never"
13 | },
14 | "group": {
15 | "kind": "build",
16 | "isDefault": true
17 | }
18 | }
19 | ]
20 | }
21 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | // Place your settings in this file to overwrite default and user settings.
2 | {
3 | "files.exclude": {
4 | "out": false // set this to true to hide the "out" folder with the compiled JS files
5 | },
6 | "search.exclude": {
7 | "out": true // set this to false to include "out" folder in search results
8 | },
9 | // Turn off tsc task auto detection since we have the necessary tasks as npm scripts
10 | "typescript.tsc.autoDetect": "off"
11 | }
--------------------------------------------------------------------------------
/app/components/ReceivedMessages.tsx:
--------------------------------------------------------------------------------
1 | import React, { useContext } from 'react';
2 | import { MessagesContext } from '../context/MessageContext';
3 |
4 | export const ReceivedMessages = () => {
5 | const receivedMessages = useContext(MessagesContext);
6 |
7 | return (
8 |
9 |
Received Messages from Extension:
10 |
11 | {receivedMessages.map((receivedMessage, i) => (
12 | - {receivedMessage}
13 | ))}
14 |
15 |
16 | );
17 | };
18 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 |
3 | module.exports = {
4 | entry: path.join(__dirname, 'app', 'index.tsx'),
5 | resolve: {
6 | extensions: ['.ts', '.tsx', '.js', '.jsx', '.css'],
7 | },
8 | devtool: 'inline-source-map',
9 | module: {
10 | rules: [
11 | {
12 | test: /\.tsx?$/,
13 | use: 'ts-loader',
14 | exclude: '/node_modules/',
15 | },
16 | {
17 | test: /\.css$/,
18 | use: ['style-loader', 'css-loader'],
19 | },
20 | ],
21 | },
22 | output: {
23 | filename: 'bundle.js',
24 | path: path.resolve(__dirname, 'out', 'app'),
25 | },
26 | };
27 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "commonjs",
4 | "target": "es6",
5 | "outDir": "out",
6 | "lib": [
7 | "es6"
8 | ],
9 | "sourceMap": true,
10 | "rootDir": ".",
11 | "strict": true /* enable all strict type-checking options */
12 | /* Additional Checks */
13 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
14 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
15 | // "noUnusedParameters": true, /* Report errors on unused parameters. */
16 | },
17 | "exclude": [
18 | "node_modules",
19 | ".vscode-test",
20 | "app"
21 | ]
22 | }
23 |
--------------------------------------------------------------------------------
/app/components/SendMessage.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { CommonMessage } from '../../src/view/messages/messageTypes';
3 |
4 | export const SendMessage = () => {
5 | const [message, setMessage] = useState('');
6 |
7 | const handleMessageChange = (e: React.ChangeEvent) => {
8 | setMessage(e.target.value);
9 | };
10 |
11 | const sendMessage = () => {
12 | vscode.postMessage({
13 | type: 'COMMON',
14 | payload: message,
15 | });
16 | };
17 |
18 | return (
19 |
20 |
Send Message to Extension:
21 |
22 |
23 |
24 | );
25 | };
26 |
--------------------------------------------------------------------------------
/test/runTest.ts:
--------------------------------------------------------------------------------
1 | import * as path from 'path';
2 |
3 | import { runTests } from 'vscode-test';
4 |
5 | async function main() {
6 | try {
7 | // The folder containing the Extension Manifest package.json
8 | // Passed to `--extensionDevelopmentPath`
9 | const extensionDevelopmentPath = path.resolve(__dirname, '../../');
10 |
11 | // The path to test runner
12 | // Passed to --extensionTestsPath
13 | const extensionTestsPath = path.resolve(__dirname, './index');
14 |
15 | // Download VS Code, unzip it and run the integration test
16 | await runTests({ extensionDevelopmentPath, extensionTestsPath });
17 | } catch (err) {
18 | console.error('Failed to run tests');
19 | process.exit(1);
20 | }
21 | }
22 |
23 | main();
24 |
--------------------------------------------------------------------------------
/app/components/Message.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Switch, Link } from 'react-router-dom';
3 | import { RouteWithSubRoutes } from '../routes/RouteWithSubRoutes';
4 | import { RouteConfigComponentProps } from '../routes/config';
5 |
6 | export const Message: React.FC = ({ routes }) => {
7 | return (
8 |
9 |
Message
10 |
11 | -
12 | Received Messages
13 |
14 | -
15 | Send Message
16 |
17 |
18 |
19 |
20 | {routes!.map((route, i) => (
21 |
22 | ))}
23 |
24 |
25 | );
26 | };
27 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "root": true,
3 | "parser": "@typescript-eslint/parser",
4 | "parserOptions": {
5 | "ecmaVersion": 6,
6 | "sourceType": "module"
7 | },
8 | "plugins": [
9 | "@typescript-eslint",
10 | "react-hooks"
11 | ],
12 | "rules": {
13 | "@typescript-eslint/naming-convention": [
14 | "warn",
15 | {
16 | "selector": "function",
17 | "format": ["PascalCase", "camelCase"]
18 | }
19 | ],
20 | "@typescript-eslint/semi": "warn",
21 | "curly": "warn",
22 | "eqeqeq": "warn",
23 | "no-throw-literal": "warn",
24 | "semi": "off",
25 | "react-hooks/rules-of-hooks": "error",
26 | "react-hooks/exhaustive-deps": "warn"
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/extension.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from 'vscode';
2 | import { ViewLoader } from './view/ViewLoader';
3 | import { CommonMessage } from './view/messages/messageTypes';
4 |
5 | export function activate(context: vscode.ExtensionContext) {
6 | context.subscriptions.push(
7 | vscode.commands.registerCommand('webview.open', () => {
8 | ViewLoader.showWebview(context);
9 | }),
10 |
11 | vscode.commands.registerCommand('extension.sendMessage', () => {
12 | vscode.window
13 | .showInputBox({
14 | prompt: 'Send message to Webview',
15 | })
16 | .then(result => {
17 | result &&
18 | ViewLoader.postMessageToWebview({
19 | type: 'COMMON',
20 | payload: result,
21 | });
22 | });
23 | })
24 | );
25 | }
26 |
27 | export function deactivate() {}
28 |
--------------------------------------------------------------------------------
/test/index.ts:
--------------------------------------------------------------------------------
1 | import * as path from 'path';
2 | import * as Mocha from 'mocha';
3 | import * as glob from 'glob';
4 |
5 | export function run(): Promise {
6 | // Create the mocha test
7 | const mocha = new Mocha({
8 | ui: 'bdd',
9 | color: true,
10 | });
11 |
12 | const testsRoot = path.resolve(__dirname);
13 |
14 | return new Promise((c, e) => {
15 | glob('**/**.test.js', { cwd: testsRoot }, (err, files) => {
16 | if (err) {
17 | return e(err);
18 | }
19 |
20 | // Add files to the test suite
21 | files.forEach(f => mocha.addFile(path.resolve(testsRoot, f)));
22 |
23 | try {
24 | // Run the mocha test
25 | mocha.run(failures => {
26 | if (failures > 0) {
27 | e(new Error(`${failures} tests failed.`));
28 | } else {
29 | c();
30 | }
31 | });
32 | } catch (err) {
33 | console.error(err);
34 | e(err);
35 | }
36 | });
37 | });
38 | }
39 |
--------------------------------------------------------------------------------
/app/routes/config.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Home } from '../components/Home';
3 | import { About } from '../components/About';
4 | import { Message } from '../components/Message';
5 | import { ReceivedMessages } from '../components/ReceivedMessages';
6 | import { SendMessage } from '../components/SendMessage';
7 |
8 | export type RouteConfigComponentProps = Pick;
9 |
10 | export type RouteConfig = {
11 | path: string;
12 | component: React.ComponentType;
13 | exact?: boolean;
14 | routes?: RouteConfig[];
15 | };
16 |
17 | export const routes: RouteConfig[] = [
18 | {
19 | path: '/',
20 | component: Home,
21 | exact: true
22 | },
23 | {
24 | path: '/about',
25 | component: About,
26 | },
27 | {
28 | path: '/message',
29 | component: Message,
30 | routes: [
31 | {
32 | path: '/message/received',
33 | component: ReceivedMessages,
34 | },
35 | {
36 | path: '/message/send',
37 | component: SendMessage,
38 | },
39 | ],
40 | },
41 | ];
42 |
--------------------------------------------------------------------------------
/.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": "Run Extension",
10 | "type": "extensionHost",
11 | "request": "launch",
12 | "args": [
13 | "--extensionDevelopmentPath=${workspaceFolder}"
14 | ],
15 | "outFiles": [
16 | "${workspaceFolder}/out/src/**/*.js"
17 | ],
18 | "preLaunchTask": "${defaultBuildTask}",
19 | "sourceMaps": true
20 | },
21 | {
22 | "name": "Extension Tests",
23 | "type": "extensionHost",
24 | "request": "launch",
25 | "args": [
26 | "--extensionDevelopmentPath=${workspaceFolder}",
27 | "--extensionTestsPath=${workspaceFolder}/out/test/index"
28 | ],
29 | "outFiles": [
30 | "${workspaceFolder}/out/test/**/*.js"
31 | ],
32 | "preLaunchTask": "${defaultBuildTask}",
33 | "sourceMaps": true
34 | }
35 | ]
36 | }
37 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # VSCode Webview React Boilerplate
2 |
3 | An awesome boilerplate for developing VSCode Extension Webview in `React`, `React Router` and `TypeScript`.
4 |
5 | 
6 |
7 | Project was inspired by:
8 | - [Reactception : extending a VS Code extension with Webviews and React](https://medium.com/younited-tech-blog/reactception-extending-vs-code-extension-with-webviews-and-react-12be2a5898fd)
9 | - [vscode-webview-react](https://github.com/rebornix/vscode-webview-react)
10 | - [VSCode Webview API](https://code.visualstudio.com/api/extension-guides/webview)
11 |
12 | ## Development
13 |
14 | Install dependencies first.
15 |
16 | ```bash
17 | $ npm install
18 | ```
19 |
20 | After the install process you can press `F5` to "Start Debugging" (or: select in menu **"Debug" -> "Run Extension"**). A new Extension Development Host window will open in which you need to open command palette (`Ctrl/Cmd + Shift + P`) and select **"Webview React: Open Webview"** to open webview.
21 |
22 | Functionalities covered:
23 | - Router
24 | - Communication between Webview and Extension
25 | - Integrated configuration
26 | - Mock API Request
--------------------------------------------------------------------------------
/app/components/About.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useCallback, useEffect } from 'react';
2 | import { apiUrl } from '../api';
3 |
4 | type UserInfo = {
5 | name: string;
6 | gender: string;
7 | email: string;
8 | };
9 |
10 | export const About = () => {
11 | const [userInfo, setUserInfo] = useState({
12 | name: '',
13 | gender: '',
14 | email: '',
15 | });
16 | const [loading, setLoading] = useState(false);
17 |
18 | const fetchUser = useCallback(() => {
19 | setLoading(true);
20 | fetch(apiUrl)
21 | .then(res => res.json())
22 | .then(({ results }) => {
23 | const user = results[0];
24 | setLoading(false);
25 | setUserInfo({
26 | name: `${user.name.first} ${user.name.last}`,
27 | gender: user.gender,
28 | email: user.email,
29 | });
30 | })
31 | .catch(err => {
32 | setLoading(false);
33 | });
34 | }, []);
35 |
36 | useEffect(() => {
37 | fetchUser();
38 | }, [fetchUser]);
39 |
40 | return (
41 |
42 |
About
43 |
User Info
44 | {loading ? (
45 |
Loading...
46 | ) : (
47 |
48 | - Name: {userInfo.name}
49 | - Gender: {userInfo.gender}
50 | - Email: {userInfo.email}
51 |
52 | )}
53 |
54 |
55 | );
56 | };
57 |
--------------------------------------------------------------------------------
/app/components/App.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState, useCallback } from 'react';
2 | import { MemoryRouter as Router, Link, Switch } from 'react-router-dom';
3 | import { routes } from '../routes/config';
4 | import { RouteWithSubRoutes } from '../routes/RouteWithSubRoutes';
5 | import { MessagesContext } from '../context/MessageContext';
6 | import { CommonMessage, Message, ReloadMessage } from '../../src/view/messages/messageTypes';
7 |
8 | export const App = () => {
9 | const [messagesFromExtension, setMessagesFromExtension] = useState([]);
10 |
11 | const handleMessagesFromExtension = useCallback(
12 | (event: MessageEvent) => {
13 | if (event.data.type === 'COMMON') {
14 | const message = event.data as CommonMessage;
15 | setMessagesFromExtension([...messagesFromExtension, message.payload]);
16 | }
17 | },
18 | [messagesFromExtension]
19 | );
20 |
21 | useEffect(() => {
22 | window.addEventListener('message', (event: MessageEvent) => {
23 | handleMessagesFromExtension(event);
24 | });
25 |
26 | return () => {
27 | window.removeEventListener('message', handleMessagesFromExtension);
28 | };
29 | }, [handleMessagesFromExtension]);
30 |
31 | const handleReloadWebview = () => {
32 | vscode.postMessage({
33 | type: 'RELOAD',
34 | });
35 | };
36 |
37 | return (
38 |
41 |
42 | -
43 | Home
44 |
45 | -
46 | About
47 |
48 | -
49 | Message
50 |
51 |
52 |
53 |
54 |
55 |
56 | {routes.map((route, i) => (
57 |
58 | ))}
59 |
60 |
61 |
62 | );
63 | };
64 |
--------------------------------------------------------------------------------
/vsc-extension-quickstart.md:
--------------------------------------------------------------------------------
1 | # Welcome to your VS Code Extension
2 |
3 | ## What's in the folder
4 |
5 | * This folder contains all of the files necessary for your extension.
6 | * `package.json` - this is the manifest file in which you declare your extension and command.
7 | * The sample plugin registers a command and defines its title and command name. With this information VS Code can show the command in the command palette. It doesn’t yet need to load the plugin.
8 | * `src/extension.ts` - this is the main file where you will provide the implementation of your command.
9 | * The file exports one function, `activate`, which is called the very first time your extension is activated (in this case by executing the command). Inside the `activate` function we call `registerCommand`.
10 | * We pass the function containing the implementation of the command as the second parameter to `registerCommand`.
11 |
12 | ## Get up and running straight away
13 |
14 | * Press `F5` to open a new window with your extension loaded.
15 | * Run your command from the command palette by pressing (`Ctrl+Shift+P` or `Cmd+Shift+P` on Mac) and typing `Hello World`.
16 | * Set breakpoints in your code inside `src/extension.ts` to debug your extension.
17 | * Find output from your extension in the debug console.
18 |
19 | ## Make changes
20 |
21 | * You can relaunch the extension from the debug toolbar after changing code in `src/extension.ts`.
22 | * You can also reload (`Ctrl+R` or `Cmd+R` on Mac) the VS Code window with your extension to load your changes.
23 |
24 |
25 | ## Explore the API
26 |
27 | * You can open the full set of our API when you open the file `node_modules/@types/vscode/index.d.ts`.
28 |
29 | ## Run tests
30 |
31 | * Open the debug viewlet (`Ctrl+Shift+D` or `Cmd+Shift+D` on Mac) and from the launch configuration dropdown pick `Extension Tests`.
32 | * Press `F5` to run the tests in a new window with your extension loaded.
33 | * See the output of the test result in the debug console.
34 | * Make changes to `src/test/suite/extension.test.ts` or create new test files inside the `test/suite` folder.
35 | * The provided test runner will only consider files matching the name pattern `**.test.ts`.
36 | * You can create folders inside the `test` folder to structure your tests any way you want.
37 |
38 | ## Go further
39 |
40 | * Reduce the extension size and improve the startup time by [bundling your extension](https://code.visualstudio.com/api/working-with-extensions/bundling-extension).
41 | * [Publish your extension](https://code.visualstudio.com/api/working-with-extensions/publishing-extension) on the VSCode extension marketplace.
42 | * Automate builds by setting up [Continuous Integration](https://code.visualstudio.com/api/working-with-extensions/continuous-integration).
43 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vscode-webview-react-boilerplate",
3 | "displayName": "VSCode webview react boilerplate",
4 | "description": "Boilerplate for developing webview using react in VSCode extension",
5 | "version": "0.0.1",
6 | "engines": {
7 | "vscode": "^1.52.0"
8 | },
9 | "categories": [
10 | "Other"
11 | ],
12 | "keywords": [
13 | "vscode",
14 | "vscode-extension",
15 | "vscode-extension-boilerplate",
16 | "boilerplate"
17 | ],
18 | "activationEvents": [
19 | "onStartupFinished"
20 | ],
21 | "main": "./out/src/extension.js",
22 | "contributes": {
23 | "commands": [
24 | {
25 | "command": "webview.open",
26 | "title": "Open Webview",
27 | "category": "Webview React"
28 | },
29 | {
30 | "command": "extension.sendMessage",
31 | "title": "Send Message to Webview",
32 | "category": "Webview React"
33 | }
34 | ],
35 | "configuration": {
36 | "title": "Webview React",
37 | "properties": {
38 | "webviewReact.userApiGender": {
39 | "type": "string",
40 | "default": "male",
41 | "enum": [
42 | "male",
43 | "female"
44 | ],
45 | "enumDescriptions": [
46 | "Fetching user information with gender of male",
47 | "Fetching user information with gender of female"
48 | ]
49 | }
50 | }
51 | }
52 | },
53 | "scripts": {
54 | "vscode:prepublish": "npm run compile",
55 | "compile": "npm-run-all compile:*",
56 | "compile:extension": "tsc -p ./",
57 | "compile:view": "webpack --mode development",
58 | "watch": "npm-run-all -p watch:*",
59 | "watch:extension": "tsc -watch -p ./",
60 | "watch:view": "webpack --watch --mode development",
61 | "pretest": "npm run compile && npm run lint",
62 | "lint": "eslint src --ext ts",
63 | "test": "node ./out/test/runTest.js"
64 | },
65 | "devDependencies": {
66 | "@types/glob": "^7.1.3",
67 | "@types/mocha": "^8.0.4",
68 | "@types/node": "^12.11.7",
69 | "@types/react": "^17.0.1",
70 | "@types/react-dom": "^17.0.0",
71 | "@types/react-router-dom": "^5.1.7",
72 | "@types/vscode": "^1.52.0",
73 | "@typescript-eslint/eslint-plugin": "^4.9.0",
74 | "@typescript-eslint/parser": "^4.9.0",
75 | "css-loader": "^5.0.1",
76 | "eslint": "^7.15.0",
77 | "eslint-plugin-react-hooks": "^4.2.0",
78 | "glob": "^7.1.6",
79 | "mocha": "^8.1.3",
80 | "npm-run-all": "^4.1.5",
81 | "prettier": "^2.2.1",
82 | "style-loader": "^2.0.0",
83 | "ts-loader": "^8.0.15",
84 | "typescript": "^4.1.2",
85 | "vscode-test": "^1.4.1",
86 | "webpack": "^5.21.1",
87 | "webpack-cli": "^4.5.0"
88 | },
89 | "dependencies": {
90 | "react": "^17.0.1",
91 | "react-dom": "^17.0.1",
92 | "react-router-dom": "^5.2.0"
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/src/view/ViewLoader.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from 'vscode';
2 | import * as path from 'path';
3 | import { getAPIUserGender } from '../config';
4 | import { Message, CommonMessage } from './messages/messageTypes';
5 |
6 | export class ViewLoader {
7 | public static currentPanel?: vscode.WebviewPanel;
8 |
9 | private panel: vscode.WebviewPanel;
10 | private context: vscode.ExtensionContext;
11 | private disposables: vscode.Disposable[];
12 |
13 | constructor(context: vscode.ExtensionContext) {
14 | this.context = context;
15 | this.disposables = [];
16 |
17 | this.panel = vscode.window.createWebviewPanel('reactApp', 'React App', vscode.ViewColumn.One, {
18 | enableScripts: true,
19 | retainContextWhenHidden: true,
20 | localResourceRoots: [vscode.Uri.file(path.join(this.context.extensionPath, 'out', 'app'))],
21 | });
22 |
23 | // render webview
24 | this.renderWebview();
25 |
26 | // listen messages from webview
27 | this.panel.webview.onDidReceiveMessage(
28 | (message: Message) => {
29 | if (message.type === 'RELOAD') {
30 | vscode.commands.executeCommand('workbench.action.webview.reloadWebviewAction');
31 | } else if (message.type === 'COMMON') {
32 | const text = (message as CommonMessage).payload;
33 | vscode.window.showInformationMessage(`Received message from Webview: ${text}`);
34 | }
35 | },
36 | null,
37 | this.disposables
38 | );
39 |
40 | this.panel.onDidDispose(
41 | () => {
42 | this.dispose();
43 | },
44 | null,
45 | this.disposables
46 | );
47 | }
48 |
49 | private renderWebview() {
50 | const html = this.render();
51 | this.panel.webview.html = html;
52 | }
53 |
54 | static showWebview(context: vscode.ExtensionContext) {
55 | const cls = this;
56 | const column = vscode.window.activeTextEditor
57 | ? vscode.window.activeTextEditor.viewColumn
58 | : undefined;
59 | if (cls.currentPanel) {
60 | cls.currentPanel.reveal(column);
61 | } else {
62 | cls.currentPanel = new cls(context).panel;
63 | }
64 | }
65 |
66 | static postMessageToWebview(message: T) {
67 | // post message from extension to webview
68 | const cls = this;
69 | cls.currentPanel?.webview.postMessage(message);
70 | }
71 |
72 | public dispose() {
73 | ViewLoader.currentPanel = undefined;
74 |
75 | // Clean up our resources
76 | this.panel.dispose();
77 |
78 | while (this.disposables.length) {
79 | const x = this.disposables.pop();
80 | if (x) {
81 | x.dispose();
82 | }
83 | }
84 | }
85 |
86 | render() {
87 | const bundleScriptPath = this.panel.webview.asWebviewUri(
88 | vscode.Uri.file(path.join(this.context.extensionPath, 'out', 'app', 'bundle.js'))
89 | );
90 |
91 | const gender = getAPIUserGender();
92 |
93 | return `
94 |
95 |
96 |
97 |
98 |
99 | React App
100 |
101 |
102 |
103 |
104 |
108 |
111 |
112 |
113 |
114 | `;
115 | }
116 | }
117 |
--------------------------------------------------------------------------------