头图

Preface

GithubBlog:https://github.com/Nealyang/PersonalBlog/issues/99

The background is like this:

With the provision of scaffolding, as well as the functional packaging of new pages and modules.

After all, provides an extra layer of specification, and an extra layer of constraints. and the essence of the architecture is to allow developers to focus more on business development without worrying about other things. For example, some module configurations, asynchronous loading and even some services that have been defined and retained in the initialization architecture are hooks etc., which are initialized by the above scaffolding.

For the above reasons, I hope to provide a set of visual operations (create a project, select dependencies, add pages, select required materials, configure material attributes, etc.). In a nutshell, for source code development, users only need to write corresponding service module assembly, without the module management structure is organized and distributed state, addition encoding service module, the other is visualization .

Because 100% of the students in the team use vscode as a job, so naturally vscode extinction is my first choice. The plan will provide a series of plug-ins such as project creation, new pages, module configuration, page configuration, and new modules. Follow-up phased progress, and then publish a summary. Ahem, yes, this will be a source workbench ~

Up to now, 90% of the project’s scaffolding has been basically built, and here is the first stage summary.

Achievement display

demo

项目目录

extensions folder is the folder of the vscode plug-in, the packages folder is for storing public components, scripts is the script for publishing, building, and developing, and the others are some project configurations.

Of course, the main thing here is not the display of product functions, quack~

packages.json scripts


  "scripts": {
    "publish": "lerna list && publish:package",
    "publish-beta": "lerna list && npm run publish-beta:package",
    "start":"tnpm run start:packages && tnpm run start:extensions",
    "start:packages": "tnpm run setup:packages && tnpm run packages:watch",
    "start:extensions":"tnpm run extensions:link",
    "commit": "git-cz",
    "env": "node ./scripts/env.js",
    "packages:link": "lerna link",
    "packages:install": "rm -rf node_modules && rm -rf ./packages/*/node_modules && rm -rf ./packages/*/package-lock.json && SASS_BINARY_SITE=https://npm.taobao.org/mirrors/node-sass/ yarn install --registry=http://registry.npm.taobao.org",
    "packages:clean": "rm -rf ./packages/*/lib",
    "packages:watch": "ts-node ./scripts/watch.ts",
    "packages:build": "npm run packages:clean && ts-node ./scripts/build.ts",
    "setup:packages": "npm run packages:install && lerna clean --yes && npm run packages:build && npm run packages:link ",
    "publish-beta:package": "ts-node ./scripts/publish-beta-package.ts",
    "publish:package": "ts-node ./scripts/publish-package.ts",
    "extensions:install": " rm -rf ./extensions/*/node_modules && rm -rf ./extensions/*/package-lock.json && rm -rf ./extensions/*/web/node_modules && rm -rf ./extensions/*/web/package-lock.json && ts-node ./scripts/extension-deps-install.ts",
    "extensions:link": "ts-node ./scripts/extension-link-package.ts"
  }

scripts has not been added completely. The current development direct npm start releases packages as npm run publish-beta:package and npm run publish:package respectively. There is also a summary of publish

Architecture selection

At present, it is to pmCli into plug-ins, and then visually replace the related operations on the architecture configuration during the coding process. Therefore, there must not be only one plug-in, but a operation set based on the source code architecture: more than extensions . There are many similar functional packages in the plug-in. Such as from gitlab read the file base, vscode and WebView communication, the AST substantially encapsulated the like, it is inevitable to rely very much packages , to develop and provide unified management of collection efficiency inevitably occur based lerna of monorepo project structure. 网图

I won’t say much about some of the pits of lerna, mainly because I just read most of the practical articles and official documents on the market, lacking some of my own practice (after all, I feel that more research can’t solve the big pain points, so I don’t want to spend it. Energy) The final monorepo is implemented based on yarn workspace , through lerna link to soft chain package , lerna release package more tasteless, just refer to App works to write some packaged and released to pre-release, online scripts.

The project workflow and coding constraints are through conventional configurations such as husky , lint-staged , git-cz , eslint , prettier

The code uses ts , so extensions and packages . Here you can extract the common parts and place them in the project root directory (as shown in the project directory screenshot above).

practice

I won’t talk about it here to initialize lerna init and lerna create xxx Anyway, after package.json it is a directory structure packages and 060d015e183507 files.

Project structure

项目结构

package structure

package

The above structure description is in the picture

Script package

scripts folder is placed in the root directory of the project, which stores some scripts for release, development, and dependent installation.

scripts

getPakcageInfo.ts

Used to obtain related publish information packages Among them, shouldPublish compares the local version with the online version , and judges that the master needs to execute publish
/*
 * @Author: 一凨
 * @Date: 2021-06-07 18:47:32
 * @Last Modified by: 一凨
 * @Last Modified time: 2021-06-07 19:12:28
 */
import { existsSync, readdirSync, readFileSync } from 'fs';
import { join } from 'path';
import { getLatestVersion } from 'ice-npm-utils';

const TARGET_DIRECTORY = join(__dirname, '../../packages');
// 定义需要获取到的信息结构
export interface IPackageInfo {
  name: string;
  directory: string;
  localVersion: string;
  mainFile: string; // package.json main file
  shouldPublish: boolean;
}
// 检查 package 是否 build 成功
function checkBuildSuccess(directory: string, mainFile: string): boolean {
  return existsSync(join(directory, mainFile));
}
// 判断线上最新version是否和本地 version 相同
function checkVersionExists(pkg: string, version: string): Promise<boolean> {
  return getLatestVersion(pkg)
    .then((latestVersion) => version === latestVersion)
    .catch(() => false);
}

export async function getPackageInfos ():Promise<IPackageInfo[]>{
  const packageInfos: IPackageInfo[] = [];
  if (!existsSync(TARGET_DIRECTORY)) {
    console.log(`[ERROR] Directory ${TARGET_DIRECTORY} not exist!`);
  } else {
    // 拿到所有packages 目录,再去遍历其 package.json
    const packageFolders: string[] = readdirSync(TARGET_DIRECTORY).filter((filename) => filename[0] !== '.');
    console.log('[PUBLISH] Start check with following packages:');
    await Promise.all(
      packageFolders.map(async (packageFolder) => {
        const directory = join(TARGET_DIRECTORY, packageFolder);
        const packageInfoPath = join(directory, 'package.json');

        // Process package info.
        if (existsSync(packageInfoPath)) {
          const packageInfo = JSON.parse(readFileSync(packageInfoPath, 'utf8'));
          const packageName = packageInfo.name || packageFolder;

          console.log(`- ${packageName}`);
                    // 从 package.json 中取信息 返回
          try {
            packageInfos.push({
              name: packageName,
              directory,
              localVersion: packageInfo.version,
              mainFile: packageInfo.main,
              // If localVersion not exist, publish it
              shouldPublish:
                checkBuildSuccess(directory, packageInfo.main) &&
                !(await checkVersionExists(packageName, packageInfo.version)),
            });
          } catch (e) {
            console.log(`[ERROR] get ${packageName} information failed: `, e);
          }
        } else {
          console.log(`[ERROR] ${packageFolder}'s package.json not found.`);
        }
      }),
    );
  }
  return packageInfos;
}

The explanation of the code is in the comments. What the core does is read the information in each package of package.json packages , and then compose the required format and return it for publishing.

publish-beta-package

/*
 * @Author: 一凨
 * @Date: 2021-06-07 18:45:51
 * @Last Modified by: 一凨
 * @Last Modified time: 2021-06-07 19:29:26
 */
import * as path from 'path';
import * as fs from 'fs-extra';
import { spawnSync } from 'child_process';
import { IPackageInfo, getPackageInfos } from './fn/getPackageInfos';

const BETA_REG = /([^-]+)-beta\.(\d+)/; // '1.0.0-beta.1'

interface IBetaPackageInfo extends IPackageInfo {
  betaVersion: string;
}

function setBetaVersionInfo(packageInfo: IPackageInfo): IBetaPackageInfo {
  const { name, localVersion } = packageInfo;

  let version = localVersion;

  if (!BETA_REG.test(localVersion)) {
    // 如果 localVersion 不是 beta version,则盘他!
    let betaVersion = 1;
    // 获取package 的 dist-tag 相关信息
    const childProcess = spawnSync('npm', ['show', name, 'dist-tags', '--json'], {
      encoding: 'utf-8',
    });

    const distTags = JSON.parse(childProcess.stdout || "{}") || {};
    const matched = (distTags.beta || '').match(BETA_REG);

    // 1.0.0-beta.1 -> ["1.0.0-beta.1", "1.0.0", "1"] -> 1.0.0-beta.2
    if (matched && matched[1] === localVersion && matched[2]) {
      // 盘 version,+1
      betaVersion = Number(matched[2]) + 1;
    }
    version += `-beta.${betaVersion}`;
  }

  return Object.assign({}, packageInfo, { betaVersion: version });
}

// 将矫正后的 betaVersion 写到对应 package.json 中
function updatePackageJson(betaPackageInfos: IBetaPackageInfo[]): void {
  betaPackageInfos.forEach((betaPackageInfo: IBetaPackageInfo) => {
    const { directory, betaVersion } = betaPackageInfo;

    const packageFile = path.join(directory, 'package.json');
    const packageData = fs.readJsonSync(packageFile);

    packageData.version = betaVersion;

    for (let i = 0; i < betaPackageInfos.length; i++) {
      const dependenceName = betaPackageInfos[i].name;
      const dependenceVersion = betaPackageInfos[i].betaVersion;

      if (packageData.dependencies && packageData.dependencies[dependenceName]) {
        packageData.dependencies[dependenceName] = dependenceVersion;
      } else if (packageData.devDependencies && packageData.devDependencies[dependenceName]) {
        packageData.devDependencies[dependenceName] = dependenceVersion;
      }
    }

    fs.writeFileSync(packageFile, JSON.stringify(packageData, null, 2));
  });
}
// npm publish --tag=beta 发布
function publish(pkg: string, betaVersion: string, directory: string): void {
  console.log('[PUBLISH BETA]', `${pkg}@${betaVersion}`);
  spawnSync('npm', ['publish', '--tag=beta'], {
    stdio: 'inherit',
    cwd: directory,
  });
}

// 入口文件
console.log('[PUBLISH BETA] Start:');
getPackageInfos().then((packageInfos: IPackageInfo[]) => {
  const shouldPublishPackages = packageInfos
    .filter((packageInfo) => packageInfo.shouldPublish)
    .map((packageInfo) => setBetaVersionInfo(packageInfo));

  updatePackageJson(shouldPublishPackages);

  // Publish
  let publishedCount = 0;
  const publishedPackages = [];


  shouldPublishPackages.forEach((packageInfo) => {
    const { name, directory, betaVersion } = packageInfo;
    publishedCount++;
    // 打印此次发布的相关信息
    console.log(`--- ${name}@${betaVersion} ---`);
    publish(name, betaVersion, directory);
    publishedPackages.push(`${name}:${betaVersion}`);
  });

  console.log(`[PUBLISH PACKAGE BETA] Complete (count=${publishedCount}):`);
  console.log(`${publishedPackages.join('\n')}`);

});

The basic functions are in the comments (not to repeat this sentence), summarize the function of the script:

  • Get all local packageInfo information
  • Compare online (released) information and correct the version information required for this release
  • Add (write) the corrected version information to package.json in the local corresponding package
  • Call the script and execute the release

publish-package is very simple, and it is relatively simple to write, that is, call npm publish . Of course, some basic online verification is also required, such as the above-mentioned shouldPublish . I won't go into details!

Note that, when released, need attention landed ( npm whoami ) and if you are using @xxx/ naming of it, pay attention to the corresponding organization rights

watch

Mainly rely on the ability of nsfw to monitor local files. has changes, we compile and we are done!

 /*
 * @Author: 一凨
 * @Date: 2021-06-07 20:16:09
 * @Last Modified by: 一凨
 * @Last Modified time: 2021-06-10 17:19:05
 */
import * as glob from 'glob';
import * as path from 'path';
import * as fs from 'fs-extra';
import { run } from './fn/shell';


// eslint-disable-next-line @typescript-eslint/no-var-requires
const nsfw = require('nsfw');

async function watchFiles(cwd, ext) {
  const files = glob.sync(ext, { cwd, nodir: true });

  const fileSet = new Set();
  /* eslint no-restricted-syntax:0 */
  for (const file of files) {
    /* eslint no-await-in-loop:0 */
    await copyOneFile(file, cwd);
    fileSet.add(path.join(cwd, file));
  }

  const watcher = await nsfw(cwd, (event) => {
    event.forEach((e) => {
      if (
        e.action === nsfw.actions.CREATED ||
        e.action === nsfw.actions.MODIFIED ||
        e.action === nsfw.actions.RENAMED
      ) {
        const filePath = e.newFile ? path.join(e.directory, e.newFile!) : path.join(e.directory, e.file!);
        if (fileSet.has(filePath)) {
          console.log('non-ts change detected:', filePath);
          copyOneFile(path.relative(cwd, filePath), cwd);
        }
      }
    });
  });
  watcher.start();
}

watchFiles(path.join(__dirname, '../packages'), '*/src/**/!(*.ts|*.tsx)').catch((e) => {
  console.trace(e);
  process.exit(128);
});

// 在这之上的代码都是为了解决 tsc 不支持 copy 非 .ts/.tsx 文件的问题
async function tscWatcher() {
  await run('npx tsc --build ./tsconfig.json -w');
}

tscWatcher();

async function copyOneFile(file, cwd) {
  const from = path.join(cwd, file);
  const to = path.join(cwd, file.replace(/src\//, '/lib/'));
  await fs.copy(from, to);
}

extensions-deps-install

Because our workspace is in the packages directory, so for the plug-ins under extensions web page, we can't directly install yarn , and freely provide a script for plug-in installation dependencies. actually went to the project directory to execute npm i

import * as path from 'path';
import * as fse from 'fs-extra';
import * as spawn from 'cross-spawn';

export default function () {
  const extensionsPath = path.join(__dirname, '..', '..', 'extensions');
  const extensionFiles = fse.readdirSync(extensionsPath);
  const installCommonds = ['install'];
  if (!process.env.CI) { // 拼接参数
    installCommonds.push('--no-package-lock');
    installCommonds.push('--registry');
    installCommonds.push(process.env.REGISTRY ? process.env.REGISTRY : 'http://registry.npm.taobao.org');
  }

  for (let i = 0; i < extensionFiles.length; i++) {
    // 遍历安装,如果有 web 目录,则继续安装 web 页面里的依赖
    const cwd = path.join(extensionsPath, extensionFiles[i]);
    // eslint-disable-next-line quotes
    console.log("Installing extension's dependencies", cwd);

    spawn.sync('tnpm', installCommonds, {
      stdio: 'inherit',
      cwd,
    });
    const webviewPath = path.join(cwd, 'web');
    if (fse.existsSync(webviewPath)) {
      // eslint-disable-next-line quotes
      console.log("Installing extension webview's dependencies", webviewPath);
      spawn.sync('tnpm', installCommonds, {
        stdio: 'inherit',
        cwd: webviewPath,
      });
    }
  }
}
Note that scripts are all ts codes, so use ts-node to execute npmScripts

extension-link-package

Delete the local related package and let it recursively find the package after the corresponding soft chain upwards (application level)

import * as path from 'path';
import * as fse from 'fs-extra';
import { run } from './fn/shell';

(async function () {
  const extensionsPath = path.join(__dirname, '../extensions');
  const extensionFiles = await fse.readdir(extensionsPath);
    // 获取 extensions 下的插件列表,挨个遍历执行 remove
  return await Promise.all(
    extensionFiles.map(async (extensionFile) => {
      const cwd = path.join(extensionsPath, extensionFile);
      if (fse.existsSync(cwd)) {
        // link packages to extension
        if (!process.env.CI) {
          await removePmworks(cwd);
        }
        const webviewPath = path.join(cwd, 'web');
        if (fse.existsSync(webviewPath)) {
          // link packages to extension webview
          if (!process.env.CI) {
            await removePmworks(webviewPath);
          }
        }
      }
    }),
  );
})().catch((e) => {
  console.trace(e);
  process.exit(128);
});

// 删除 @pmworks 下的依赖
async function removePmworks(cwd: string) {
  const cwdStat = await fse.stat(cwd);
  if (cwdStat.isDirectory()) {
    await run(`rm -rf ${path.join(cwd, 'node_modules', '@pmworks')}`);
  }
}

Small summary

The core scripts are as above, in fact, they are relatively simple and direct functions. I haven't written anything about the release of extensions. In fact, it can be borrowed (copied) from apps (copied). Wait for the subsequent release of the plug-in to add it.

After a project has completed its infrastructure, it can basically be started. Here I take the creation of the project as an example (focus on the infrastructure part, without specific explanations on the plug-in function and implementation, and then summarize it in the second stage).

vscode extensions (vscode-webview package example)

We use yo code to initialize the plug-in we want to write in the extensions For specific basic knowledge, refer to the official document: https://code.visualstudio.com/api

After the above, we have the basic structure of a project, a series of package management, we can already enter our development stage.

After all, our plug-in is a series of operations for visualization, so vscode must not satisfy us. We need an operation interface: webView . The picture above is an webView plug-in:

  • Common-xxx(utils) is responsible for some general function packages at the entire project level
  • Extension-utils is some method library extracted for a certain plug-in. For example, project-utils is createProject initializes the project, similar to a controller
  • extension-service is the vscode and webView communications, as the name implies: service

The above is a bit MVC difference from the traditional 060d015e184207 is that there are two views here: vscode-extension and extension-webview

Give a chestnut! Here is an example of initializing a project professor~

For basic concepts related to vscode extension with WebView, please see here: https://code.visualstudio.com/api/extension-guides/webview

WebView

In fact, there is not much to prepare for WebView, that is, to prepare the three front-end HTML, JavaScript and css.

Here is the project initialized from the scaffolding of ice I used: npm init ice

web

Then modify build.json in outputDir configuration, and specify mpa mode

{
  "mpa": true,
  "vendor": false,
  "publicPath": "./",
  "outputDir": "../build",
  "plugins": [
    [
      "build-plugin-fusion",
      {
        "themePackage": "@alifd/theme-design-pro"
      }
    ],
    [
      "build-plugin-moment-locales",
      {
        "locales": [
          "zh-cn"
        ]
      }
    ],
    "@ali/build-plugin-ice-def"
  ]
}

After coding the code, you can get our three big pieces.

build 后的文件输出

For more documentation about ice, please go to the official documentation

Extensions


import * as vscode from 'vscode';
import { getHtmlFroWebview, connectService } from "@pmworks/vscode-webview";
import { DEV_WORKS_ICON } from "@pmworks/constants";
import services from './services';

export function activate(context: vscode.ExtensionContext) {
    const { extensionPath } = context;

    let projectCreatorPanel: vscode.WebviewPanel | undefined;

    const activeProjectCreator = () => {
        const columnToShowIn = vscode.window.activeTextEditor ? vscode.window.activeTextEditor.viewColumn : undefined;
        if (projectCreatorPanel) {
            projectCreatorPanel.reveal(columnToShowIn)
        } else {
            projectCreatorPanel = vscode.window.createWebviewPanel('BeeDev', '初始化源码架构', columnToShowIn || vscode.ViewColumn.One, {
                enableScripts: true,
                retainContextWhenHidden: true,
            });
        }
        projectCreatorPanel.webview.html = getHtmlFroWebview(extensionPath, 'projectcreator', false);
        projectCreatorPanel.iconPath = vscode.Uri.parse(DEV_WORKS_ICON);
        projectCreatorPanel.onDidDispose(
            () => {
                projectCreatorPanel = undefined;
            },
            null,
            context.subscriptions,
        );
        connectService(projectCreatorPanel, context, { services });
    }

    let disposable = vscode.commands.registerCommand('devworks-project-creator.createProject.start', activeProjectCreator);

    context.subscriptions.push(disposable);
}

export function deactivate() { }

Here are also regular operations, register commands and related callbacks, and initialize WebView . Here we talk about getHtmlFroWebview


/**
 * 给本地资源带上安全协议
 * @param url 本地资源路径
 * @returns 带有 vscode-resource 协议的安全路径
 */
function originResourceProcess(url: string) {
  return vscode.Uri.file(url).with({ scheme: 'vscode-resource' });
}

export const getHtmlFroWebview = (
  extensionPath: string,
  entryName: string,
  needVendor?: boolean,
  cdnBasePath?: string,
  extraHtml?: string,
  resourceProcess?: (url: string) => vscode.Uri,): string => {
  resourceProcess = resourceProcess || originResourceProcess;
  const localBasePath = path.join(extensionPath, 'build');
  const rootPath = cdnBasePath || localBasePath;
  const scriptPath = path.join(rootPath, `js/${entryName}.js`);
  const scriptUri = cdnBasePath ?
    scriptPath :
    resourceProcess(scriptPath);
  const stylePath = path.join(rootPath, `css/${entryName}.css`);
  const styleUri = cdnBasePath ?
    stylePath :
    resourceProcess(stylePath);
  // vendor for MPA
  const vendorStylePath = path.join(rootPath, 'css/vendor.css');
  const vendorStyleUri = cdnBasePath
    ? vendorStylePath
    : resourceProcess(vendorStylePath);
  const vendorScriptPath = path.join(rootPath, 'js/vendor.js');
  const vendorScriptUri = cdnBasePath
    ? vendorScriptPath
    : resourceProcess(vendorScriptPath);

  // Use a nonce to whitelist which scripts can be run
  const nonce = getNonce();
  return `<!DOCTYPE html>
  <html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no">
    <meta name="theme-color" content="#000000">
    <title>Iceworks</title>
    <link rel="stylesheet" type="text/css" href="${styleUri}">
    ${extraHtml || ''}
    ` +
    (needVendor ? `<link rel="stylesheet" type="text/css" href="${vendorStyleUri}" />` : '') +
    `
  </head>
  <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="ice-container"></div>
    ` +
    (needVendor ? `<script nonce="${nonce}" src="${vendorScriptUri}"></script>` : '') +
    `<script nonce="${nonce}" src="${scriptUri}"></script>
  </body>
</html>`;
}

function getNonce(): string {
  let text = '';
  const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
  for (let i = 0; i < 32; i++) {
    text += possible.charAt(Math.floor(Math.random() * possible.length));
  }
  return text;
}

The method is located at packages/vscode-webview/vscode.ts . In fact, is to get a piece of html vscode protocol to the local resources. Support vendor , extraHtml etc.

Up to now, we can evoke our WebView in vscode.

webview

Communication

Then the problem of communication between vscode and WebView is solved. The communication here is very similar to pubSub:

  • Plug-in message to WebView

    panel.webview.postMessage({text:"你好,这里是 vscode 发送过来的消息"});
  • webview accepts messages

    window.addEventListener('message',event=>{
        const message = event.data;
        console.log(`WebView 接受到的消息:${message}`);
    })
  • webview sends a message to the plug-in

    vscode.postMessage({text:"你好,这是 webView 发送过来的消息"});
  • Plug-in acceptance

    panel.webview.onDidReceiveMessage(msg=>{
        console.log(`插件接受到的消息:${msg}`)
    },undefined,context.subscriptions);

This communication mechanism is too fragmented. In actual projects, webView more similar to our view layer. So theoretically only service to use 060d015e184f7c to call the controller interface to complete the underlying operation and tell me the result can be :

For example, when creating a project, you need to let the user choose to create a directory. The click handle of the select button on the HTML page should be as follows:

  const getAppPath = async () => {
    const projectPath = await callService('project', 'getFolderPath', 'ok');
    setAppPath(projectPath);
  };

callService the first parameter as service class, the second method as the class name which is required to call, their corresponding parameters of the subsequent process.

Just as above, we encapsulate a callService method:

// packages/vscode-webview/webview.ts

// @ts-ignore
export const vscode = typeof acquireVsCodeApi === 'function' ? acquireVsCodeApi() : null;

export const callService = function (service: string, method: string, ...args) {
  // 统一 return promise,统一调用方式
  return new Promise((resolve, reject) => {
    // 生成对应的 eventId
    const eventId = setTimeout(() => { });

    console.log(`WebView call vscode extension service:${service} ${method} ${eventId} ${args}`);

    // 收到 vscode 发来消息,一般为处理后 webView 的需求后
    const handler = event => {
      const msg = event.data;
      console.log(`webview receive vscode message:}`, msg);
      if (msg.eventId === eventId) {// 去定时对应的 eventID,说明此次通信结束,可以移除(结束)此次通信了
        window.removeEventListener('message', handler);
        msg.errorMessage ? reject(new Error(msg.errorMessage)) : resolve(msg.result);
      }
    }

    // webview 接受 vscode 发来的消息
    window.addEventListener('message', handler);

    // WebView 向 vscode 发送消息
    vscode.postMessage({
      service,
      method,
      eventId,
      args
    });

  });
}

webview layer has completed the encapsulation of the time request for sending time, receiving time request, and canceling after acceptance ( removeListener ). So we give in to extension add the corresponding webView need service.methodName job.

Here we encapsulate a method called connectService.

connectService(projectCreatorPanel, context, { services });

The above projectCreatorPanel is to create out WebviewPanel "instance", and services will be appreciated that a plurality of object classes comprising

const services = {
    project:{
        getFolderPath(...args){
            //xxx
        },
        xxx
    },
    xxx:{}
}

The specific connectService method is as follows:

export function connectService(
  webviewPanel: vscode.WebviewPanel,
  context: vscode.ExtensionContext,
  options: IConnectServiceOptions
) {
  const { subscriptions } = context;
  const { webview } = webviewPanel;
  const { services } = options;
  webview.onDidReceiveMessage(async (message: IMessage) => {
    const { service, method, eventId, args } = message;
    const api = services && services[service] && services[service][method];
    console.log('onDidReceiveMessage', message);
    if (api) {
      try {
        const fillApiArgLength = api.length - args.length;
        const newArgs = fillApiArgLength > 0 ? args.concat(Array(fillApiArgLength).fill(undefined)) : args;
        const result = await api(...newArgs, context, webviewPanel);
        console.log('invoke service result', result);
        webview.postMessage({ eventId, result });
      } catch (err) {
        console.error('invoke service error', err);
        webview.postMessage({ eventId, errorMessage: err.message });
      }
    } else {
      vscode.window.showErrorMessage(`invalid command ${message}`);
    }
  }, undefined, subscriptions);
}

The code above is relatively simple, it is registered listener function, then just listen to WebView post over message , to pick up the corresponding services an under service of method to execute and pass WebView pass over the parameters .

The extension services are introduced here

services

The @pmworks/project-service package only encapsulates some basic method calls. The core processing logic such as downloading the corresponding gitRpo , parsing local files, etc. are all performed in the corresponding extension-utils . service can only be called.

Little problem

The basic process encapsulation has been completed as above, and the rest is the preparation of specific logic. But in actual development, the web page needs to get the parameters passed in by vscode, and in web page development, the vscode plug-in cannot read the uncompiled code. How to solve it?

encapsulates a layer of callService in webView for local web page development

封装 callService

Follow-up outlook

Up to now, I have basically finished the introduction of some developments in addition to the business work in the past two weeks. Next, you need to make up for the related APIs of the vscode plug-in and get ready to do it. Of course, before this, another very, very urgent task is to upgrade the source code structure compiled last year to align some of the capabilities of the current rax system in the group.

In the development of this plug-in system (BeeDev source code workbench), the follow-up also needs:

  • Initialize the source code architecture
  • Create a page, drag and drop related H5 source code materials (requires the entire material background) to generate an initialization page
  • Create modules, visually configure module loading categories, etc.

If you have more energy, you actually need a node backend to get through the server and local capabilities (it's just a desktop application~)

Okay, don’t be YY, let’s go with the sauce first~ the next milestone will be summarized~~

As for the project source code. . .

参考以开源的 appworks

references

other

Pay attention to the WeChat public account [Selected Full Stack Front End], push selected articles every day~


isNealyang
2k 声望346 粉丝