背景:承受二手项目会开发周期较长得项目,会忘记自己引证了哪些iconfont的icon内容。了解引入的icon需求到iconfont官网登陆查看,不同开发者可能还不知道对方引证了哪些icon。新增iconfont时,又会影响到原有的icon内容。

问题: 1.iconfont.js是压缩后的代码,不够直观化,运用icon的名字进程太过繁琐,乃至还无从查找。

对以上问题思考后,决定处理以上脚本比较好的方式就是开发一个解析iconfont.js的vscode插件,便能够直接在项目中运用。

效果:

IconView——在项目中可视化iconfont.js

初始化一个项目

初始化一个名为IconView的插件

// 安装需求的包
npm install  yo generator-code -D
// 运转,然后按流程走一遍,能够生成初始模板的插件
npx yo code

开发进程中,按F5进行调试。

可视化进程

新建一个webview

vscode插件机制提供了一个可翻开一个自界说页面的功用,即webview,这儿选用在webview中展现解析出来的icon。

extension.ts(vscode 插件的进口文件)文件中

export function activate(context: vscode.ExtensionContext) {
  console.log('Congratulations, your extension "IconView" is now active!');
  // 追寻当时 webview 面板
  let currentPanel: vscode.WebviewPanel | undefined | any = undefined;
  // 创立翻开IconView的指令
  const openIconViewCommand = vscode.commands.registerCommand(
    "IconView.openIconView",
    async (uri: vscode.Uri) => {
      // 获取当时活动的编辑器
      const columnToShowIn = vscode.window.activeTextEditor
        ? vscode.window.activeTextEditor.viewColumn
        : undefined;
      if (currentPanel) {
        // 如果我们已经有了一个面板,那就把它显现到方针列布局中
        if (columnToShowIn)
          currentPanel.reveal(columnToShowIn === 2 ? 1 : 2);
      } else {
        // 不然,创立一个新面板
        vscode.window.showInformationMessage("IconView.openIconView");
        currentPanel = await openIconView(context, uri, columnToShowIn === 2 ? 1 : 2);
        // 当时面板被关闭后重置
        currentPanel.onDidDispose(
          () => {
            currentPanel = undefined;
          },
          null,
          context.subscriptions
        );
      }
    }
  );
  context.subscriptions.push(openIconViewCommand);
}

这儿注册了一个IconView.openIconView的指令,指令首要是获取webview显现的面板方位。将指令丢进监听池。具体新建和翻开webview的操作在openIconView中。

为了方便运用,需求将openIconView注册到翻开文件的右键菜单,需求在package.json中装备相关参数,注册指令和装备菜单项,即可在翻开的iconfont.js文件右键翻开webview

// 插件激活事情
"activationEvents": [
    "onCommand:IconView.openIconView",
],
// 功用装备点 
"contributes": {
    "commands": [
      {
        "command": "IconView.openIconView",
        "title": "IconView Open IconView"
      }
    ],
    "menus": {
      "editor/context": [
        {
          "command": "IconView.openIconView",
          "group": "navigation",
          "when": "editorFocus"
        }
      ]
    },
    "configuration": {
      "title": "IconView"
    }
  },

openIconView的代码

export async function openIconView(context: vscode.ExtensionContext, uri: vscode.Uri, viewColumn: vscode.ViewColumn) {
  // 当时文件绝对途径
  const currentFile = uri.fsPath;
  if (projectPath) {
    // 创立webview
    const panel: vscode.WebviewPanel = vscode.window.createWebviewPanel(
      'IconView', // viewType
      "IconView", // 视图标题
      viewColumn, // 显现在编辑器的哪个部位
      {
        enableScripts: true, // 启用JS,默许禁用
        retainContextWhenHidden: true, // webview被隐藏时保持状况,避免被重置
      }
    );
    panel.webview.html = await getWebViewContent(
      context,
      uri,
      './iconView/index.html',
      {
        beforeBodyScripts: [currentFile]
      }
    );
    // 监听音讯
    let global = { currentFile, panel, uri };
    panel.webview.onDidReceiveMessage(message => {
      const [_, cmdName] = message.cmd.split(':')
      if (messageHandler[cmdName]) {
        // cmd表明要履行的办法称号
        messageHandler[cmdName](global, message);
      } else {
        vscode.window.showErrorMessage(`未找到名为 ${cmdName} 的办法!`);
      }
    }, undefined, context.subscriptions);
    return panel;
  }
}
/**
* 寄存一切音讯回调函数,根据 message.cmd 来决定调用哪个办法,
*/
var messageHandler: any = {
  // 弹出提示
  alert(global: any, message: any) {
    vscode.window.showInformationMessage(message.info);
  },
  // 显现过错提示
  error(global: any, message: any) {
    vscode.window.showErrorMessage(message.info);
  },
  /** 将内容写入剪贴板 */
  copy(global: any, message: any) {
    vscode.env.clipboard.writeText(message.content);
    invokeCallback(global.panel, message, { success: true, msg: '内容已仿制,可直接运用!' });
  },
  // 获取urls信息
  async getUrlsInfo(global: any, message: any) {
    const data = {
      targetPath: global.uri.fsPath,
    }
    invokeCallback(global.panel, message, data);
  },
}
function invokeCallback(panel: any, message: any, resp: any) {
  // 过错码在400-600之间的,默许弹出过错提示
  if (typeof resp == 'object' && resp.code && resp.code >= 400 && resp.code < 600) {
    vscode.window.showErrorMessage(resp.message || '发生不知道过错!');
  }
  panel.webview.postMessage({ cmd: 'vscodeCallback', cbid: message.cbid, data: resp });
}

openIconView中运用vscode.window.createWebviewPanel新建了一个webviewPanel,模板途径是./iconView/index.html,panel需求加载html字符串内容,然后panel开启音讯监听并履行相应事情。

messageHandler是信息通道的处理器,首要处理来自webView传递的事情,这儿界说了几个根本事情处理函数。

invokeCallback是处理音讯监听的回调函数,在具体的操作事情中调用。

webview首要经过音讯通道的机制来履行vscode的插件能力,包括文件的读取修正,仿制等操作。

getWebViewContent代码

export async function getWebViewContent(context: any, uri: vscode.Uri, templatePath: any, config: any = {}) {
    const projectPath = await getProjectPath(uri);
    const resourcePath = path.join(context.extensionPath, templatePath);
    const dirPath = path.dirname(resourcePath);
    let html = fs.readFileSync(resourcePath, 'utf-8');
    // 增加前置script 用户从项目本地刺进脚本
    let beforeBodyScriptStr = '';
    if (config?.beforeBodyScripts && Array.isArray(config?.beforeBodyScripts)) {
        console.log('beforeBodyScripts',config?.beforeBodyScripts)
        beforeBodyScriptStr = config?.beforeBodyScripts.map((src: string) => {
            return `<script src="https://juejin.im/post/7202526307329409081/${src}"></script>`
        }).join('\n');
    }
    html = html.replace('{@beforeBodyScript}',beforeBodyScriptStr)
    // vscode不支持直接加载本地资源,需求替换成其专有途径格式,这儿只是简略的将款式和JS的途径替换
    html = html.replace(/(<link.+?href=["']|<script.+?src=["']|<img.+?src=["'])([^@].+?)["']/g, (m, $1, $2) => {
        return $1 + vscode.Uri.file(path.resolve(dirPath, $2)).with({ scheme: 'vscode-resource' }).toString() + $1[$1.length - 1];
    });
    html = html.replace(/(<link.+?href=["']@|<script.+?src=["']@|<img.+?src=["']@)(.+?)["']/g, (m, $1, $2) => {
        return $1.substring(0, $1.length - 1) + vscode.Uri.file(path.resolve(projectPath, $2)).with({ scheme: 'vscode-resource' }).toString() + $1[$1.length - 2];
    });
    return html;
}

getWebViewContent除了读取界说的html模板之外,还将html中的引证途径改为vscode插件的资源途径,不然不能拜访。

增加前置script 用户从项目本地刺进脚本部分用于注入iconfont的js文件途径,这儿经过html引入script的方式,能够统一来自iconfont,iconPark的iconjs文件。然后webview中可经过use来展现。

这儿选用html途径的方式来开发,而不是直接选用字符串模板,是html有格式化,结构清晰。留意的是iconView一定是要榜首级,因为打包后的代码中是没有src的,所以放到src中无法辨认。

展现icon列表

在html引入了vue@2.x,便于html开发。下载vue.js的离线版本,这儿放在了在index.html中引入vue.js,以及相关初始化脚本index.js

var app = new Vue({
    el: '#root',
    data: {
        searchIcon: '',
        icons: [],
        showIcons: [],
    },
    mounted: function () {
        // 调用vscode,经过音讯机制
        callVscode({ cmd: 'vscode:getUrlsInfo' }, (data) => {
            if (data) {
                this.targetPath = data.targetPath;
            }
        });
        let icons = [];
        if (document) {
            // 等待页面渲染完成,延迟获取symbol标签
            setTimeout(() => {
                const iconElements = document.querySelectorAll('symbol');
                Array.from(iconElements).forEach((ele) => {
                    icons.push({
                        id: `#${ele.id}`,
                        name: ele.id
                    })
                })
                this.icons = icons;
                this.showIcons = icons.slice();
            }, 1000)
        }
    },
    methods: {
        /** icon查找 */
        onSearch: function (e) {
            const value = e.target.value;
            if (value) {
                this.showIcons = this.icons.filter((v) => {
                    return v.name.indexOf(value) > -1;
                })
            } else {
                this.showIcons = this.icons.slice();
            }
        },
        onClickIcon: function ({ item }) {
            const temp = `<IconFont type="https://juejin.im/post/7202526307329409081/${item.id.substring(1)}"/>`;
            callVscode({ cmd: 'vscode:copy', content: temp }, (data) => {
                if (data.success) {
                    createMsg(data.msg, 'success')
                }
            });
        },
    },
})

新建一个vue实例,挂载在html中,然后在mounted钩子中延迟获取到注入html中的symbol标签,一般来说一个symbol代表一个icon,这儿拿到之后,提取symbol的id,以便后边的运用,这儿区别两个icon列表,用于查找展现的全量副本和展现副本。

一起新增onSearch,onClickIcon办法,用于查找和点击仿制模板。

icon是个列表,为了方便操作,新建一个vue的component

Vue.component('icon-item', {
    props: {
        id: {
            type: String,
            default: '',
        },
        name: {
            type: String,
            default: '',
        }
    },
    data() {
        return { preview: false };
    },
    methods: {},
    template: `
        <div class="--ch-icon-item">
            <div class="--ch-icon-item-icon">
                <svg>
                    <use v-bind:xlink:href="id"></use>
                </svg>   
            </div>
            <span class="--ch-icon-name" :title="name">{{name}}</span>
        </div>
    `
})

icon用于展现icon列表

接下来看看html内容

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <meta
      name="viewport"
      content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no"
      />
    <link rel="stylesheet" type="text/css" href="index.css" />
  </head>
  <script src="./other/vue.js"></script>
  {@beforeBodyScript}
  <body>
    <div id="root">
      <div class="--ch-target-path">
        <span>文件途径:</span>
        <span>{{targetPath}}</span>
      </div>
      <input v-model="searchIcon" class="--ch-search" v-on:input="onSearch($event)" placeholder="请输入icon名字"></input>
      <div class="--ch-icon-list">
        <div
          v-for="item in showIcons"
          :key="item.id"
          class="--ch-icon-item-wrapper"
          v-on:click="onClickIcon({item})"
          >
          <icon-item
            v-bind:id="item.id"
            v-bind:name="item.name"
            ></icon-item>
        </div>
      </div>
    </div>
    <script src="./communication.js"></script>
    <script src="./utils.js"></script>
    <!-- 组件部分 -->
    <script src="./components/iconItem.js"></script>
    <script src="./index.js"></script>
  </body>
</html>

html中简略界说了查找,文件途径,icon列表展现。

展现出来之后,接下来要完成的是点击icon可仿制模板代码到剪贴板中。iconItem的容器组件绑定了点击事情,点击事情中可获取到item内容,这儿界说了<IconFont type="${item.id.substring(1)}"/>(结合antd的IconFont组件运用),然后调用了vscode的仿制功用。

callVsCode的代码

var callbacks = {}; // 寄存一切的回调函数
var vscode = window.acquireVsCodeApi ? window.acquireVsCodeApi() : {
  postMessage(data) {
    console.log(data)
  }
};
function callVscode(data, cb) {
  if (typeof data === 'string') {
    data = { cmd: data };
  }
  if (cb) {
    // 时刻戳加上5位随机数
    const cbid = Date.now() + '' + Math.round(Math.random() * 100000);
    // 将回调函数分配一个随机cbid然后存起来,后续需求履行的时候再捞起来
    callbacks[cbid] = cb;
    data.cbid = cbid;
  }
  vscode.postMessage(data);
}
window.addEventListener('message', event => {
  const message = event.data;
  switch (message.cmd) {
      // 来自vscode的回调
    case 'vscodeCallback':
      console.log(message.data);
      (callbacks[message.cbid] || function () { })(message.data);
      delete callbacks[message.cbid]; // 履行完回调删去
      break;
    default: break;
  }
});

打包运用

因为没有发布到插件市场,这次运用打包成vsix插件,经过本地安装。

// 安装打包东西vsce
npm i vsce -g
// 打包成vsix文件
vsce package

下一篇:IconView——在项目中保护confont.js

注: 以上部分代码来自于网络。

参考资料:

blog.haoji.me/vscode-plug…