Develop Chrome plug-in based on VUE to obtain interface data and save it to the database

preface

In assessing project recently, want to open the evaluation platform, viewing platform and save platform, feel very tedious, developed a can obtain evaluation platform data, view the project scheduling and save the data directly to the database of chrome plug-ins, because before I need to use a calendar of the vue encapsulation plug-in, with vue to develop this plugin here.

Pre-development preparation

To develop a Chrome plug-in, we first need to understand the basic structure of chrome plug-in and corresponding functions. Each extension has a different file type and number of directories, but each must have a manifest. Some basic but useful extensions may consist of nothing more than manifest and its toolbar ICONS.

manifest.json

  {
    "name": "My Extension".// "extension"
    "version": "2.1".// The current extension version number is created
    "description": "Gets information from Google.".//" Extend description"
    "icons": {  // The extension tool interface uses ICONS
      "128": "icon_16.png"."128": "icon_32.png"."128": "icon_48.png"."128": "icon_128.png"
    },
    "background": {  // Extensions often use a single long-running script to manage tasks or state
      "persistent": false."scripts": ["background_script.js"]  // The background resident script runs automatically until the browser is closed. It can be set according to requirements
    },
    "permissions": ["https://*.google.com/"."activeTab"].// Enable extension permission
    "browser_action": { 
      "default_icon": "icon_16.png".// Display in the upper right corner
      "default_popup": "popup.html"  /** Mouse over to display a short extended text description **/
    },
     "content_scripts": [{   // ontent scripts are javascript scripts that run inside a Web page. Using the standard DOM, they can get detailed information about the pages the browser visits and modify that information.
    "js": ["script/contentscript.js"]./** The script to inject **/
    "matches": [   /** match url (support re), successful injection (other attributes query) **/
        "http://*/*"."https://*/*"]]}}Copy the code

Vue develops chrome plug-ins

We need to use VUE to develop plug-ins. After several searches, we found a sample, which is very convenient for us to develop vUE plug-ins, so we introduced the sample to develop.

Introduce vuE-Web-extension template to realize VUE development

  npm install -g @vue/cli
  npm install -g @vue/cli-init
  vue init kocal/vue-web-extension new-tab-page
Copy the code

Then switch to the project directory to install the dependencies

  cd new-tab-page
  npm install
Copy the code

We can run

  npm run watch:dev
Copy the code

We get a dist folder in the project root directory, and we install the unzipped extension directly. Select this dist to develop and monitor changes.

The basic format of a boilerplate

├ ─ ─ dist │ └ ─ ─ < the built the extension > ├ ─ ─ node_modules │ └ ─ ─ < one or two files and folders > ├ ─ ─ package. The json ├ ─ ─ Package - lock. Json ├ ─ ─ scripts │ ├ ─ ─ the build - zip. Js │ └ ─ ─ the remove - evals. Js ├ ─ ─ the SRC │ ├ ─ ─ background. Js │ ├ ─ ─ the ICONS │ │ ├ ─ ─ Icon_128. PNG │ │ ├ ─ ─ icon_48. PNG │ │ └ ─ ─ icon. The XCF │ ├ ─ ─ the manifest. Json │ └ ─ ─ popup │ ├ ─ ─ App. Vue │ ├ ─ ─ popup. The HTML │ └ ─ ─ Popup. Js └ ─ ─ webpack. Config. JsCopy the code

As you can see, boilerplate files are packaged using WebPack,

The SRC folder contains all the files we will use for the extension. The manifest file and background.js are familiar to us, but also note the popup folder that contains the Vue components. When boilerplate builds the extension into the dist folder, it manages all.vue files through vue-loader and outputs a JavaScript package that the browser can understand.

There is also an ICONS folder in the SRC folder. If you take a look at Chrome’s toolbar, you’ll see a new icon for our extension (also known as browser Action). This is what we got from this folder. If you click on it, you should see a pop-up window that says “Hello World!” This is created by popup/ app.vue.

Finally, note the two scripts in the scripts folder: one for removing eval usage to comply with Chrome Web Store content security policies, and one for packaging the extension into a.zip file when you upload it to the Chrome Web Store.

Various scripts are also declared in the package.json file. We will use NPM Run Watch :dev to develop the extension, and then use NPM run build-zip to generate a zip file and upload it to the Chrome Web Store.

Create plug-in interface

Let’s modify popup.html directly

popup.html

<! DOCTYPEhtml>
<html lang="zh">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
</head>
<body>
  <link href="popup.css" rel="stylesheet">
  <div id="app">
  </div>
  <script src="popup.js"></script>
</body>
</html>
Copy the code

Here we introduce popup. CSS and popup.js in popup. CSS and put the styles we need in popup.js to introduce our vue file

popup.js

  import Vue from 'vue'
 import { Tabs,TabPane, Dialog, Button,Form,FormItem,Input,DatePicker,Message,Alert,Tooltip,MessageBox } from 'element-ui';
 import 'element-ui/lib/theme-chalk/index.css';
 import App from './App'
 Vue.use(Tabs);
 Vue.use(TabPane);
 Vue.use(Dialog);
 Vue.use(Button);
 Vue.use(Form);
 Vue.use(FormItem);
 Vue.use(Input);
 Vue.use(DatePicker);
 Vue.use(Tooltip);
 Vue.use(Alert);
 Vue.prototype.$message = Message;
 Vue.prototype.$confirm = MessageBox.confirm;
 new Vue({
   el: '#app'.render: h= > h(App)
 })
Copy the code

Here, we mainly introduce controls in Element-UI and app.vue components on demand

app.vue

  <template>
 <div id="app" style="height: 580px; overflow-y: hidden; width:680px;">
   <div>The template</div>
   <customPlan :projectData="projectData" :loginPerson="loginPerson"></customPlan>
 </div>
</template>

<script>
import customPlan from '.. /components/customPlan'
let { Pinyin } = require('.. /script/pinyin')
let pinyin = new Pinyin()
export default {
 components: { customPlan },
 data() {
   return {
     loginPerson: ' '.projectData: {
       departmentName: ' '.developer: ' '.endDate: ' '.evaluator: ' '.isDeprecated: false.isIncludeSaturday: false.isNewComponent: false.issureAdress: ' '.msg: ' '.name: ' '.startDate: ' '.workDay: ' '.year: 2020}}},created() {
   this.getUrl()
 },
 methods: {  
   getCaption(obj) {
     var index = obj.lastIndexOf(', ')
     obj = obj.substring(index + 1, obj.length)
     return obj
   },
   / * * *@desc Gets the url of the current page */
   getUrl() {
     chrome.tabs.getSelected(null.tab= > {
       console.log(tab,"tab")
       this.projectData.issureAdress = tab.url
       chrome.tabs.sendMessage(tab.id, { greet: 'hello' }, response= > {
         if (response && response.developer && response.processName) {
           let developer = pinyin
             .getFullChars(this.getCaption(response.developer))
             .toLowerCase()
           this.projectData.evaluator = developer
           this.projectData.name = response.processName
         } else if(response && response.developer && ! response.processName) {var index = response.developer.lastIndexOf(The '@')
           response.developer = response.developer.substring(
             index + 1,
             response.developer.length
           )
           this.loginPerson = response.loginPerson
           this.projectData.evaluator = response.developer
           this.projectData.name =response.peocessName
         }
       })
     })
   }
 }
}
</script>
Copy the code

In manifest.json

   "browser_action": {
     "default_title": "Test"."default_popup": "popup/popup.html"
   },
Copy the code

Here we mainly introduced our calendar control customPlan, you can import their own components as needed. At this point, our plug-in interface is basically built.

Get the current interface data and listen in the plug-in

To get the current interface data, you need javascript scripts running inside the Web page. Using the standard DOM, they can get detailed information about the pages the browser visits and modify that information. Content_scripts introduces the contentScript.js file we need, and in this js file, we can get the details of the page visited by the browser

  "content_scripts": [{
    "js": ["script/contentscript.js"],
    "matches": [
      "http://*/*",
      "https://*/*"
    ]
  }]
Copy the code

The contentscript.js file is configured as follows

document.addEventListener('click'.function (e) {
    let isCurrect = e.path.length > 3&&e.path[4].innerText&&e.path[4].innerText.indexOf('Submit requirements') != -1 && e.target.innerText === 'determine' && document.getElementsByClassName('layout-nav') && document.getElementsByClassName('layout-nav') [0].children
    if (isCurrect) {
        if (document.getElementsByClassName('user-table') && document.getElementsByClassName('user-table') [0] && document.getElementsByClassName('user-table') [0].getElementsByClassName('el-table__row').length > 0) {
            var port = chrome.runtime.connect({ name: "custommanage" });// Channel name
            let loginPerson = document.getElementsByClassName('layout-nav') && document.getElementsByClassName('layout-nav') [0].children ? document.getElementsByClassName('layout-nav') [0].children[0].innerText : ' '
            let partMentName = document.getElementsByClassName('layout-nav') && document.getElementsByClassName('layout-nav') [0].children ? document.getElementsByClassName('layout-nav') [0].children[3].innerText : ' '
            let processName = document.getElementsByClassName('el-input__inner') && document.getElementsByClassName('layout-nav') [0].children ? document.getElementsByClassName('el-input__inner') [0].title : ' '
            let tableElement = document.getElementsByClassName('user-table')?document.getElementsByClassName('user-table') [0].getElementsByClassName('el-table__row') : []
            let choseSelect = []
            for (let value of tableElement) {
                if(value.innerText.indexOf(partMentName) ! = = -1) {
                    choseSelect = value
                }
            }
            let developPerson = ' '
            let startTime = ' '
            let endTime = ' '
            if (choseSelect && choseSelect.getElementsByTagName('td')) {
                developPerson = choseSelect.getElementsByTagName('td') [1].innerText
                startTime = choseSelect.getElementsByTagName('td') [3].getElementsByTagName('input') [0].title
                endTime = choseSelect.getElementsByTagName('td') [4].getElementsByTagName('input') [0].title
            }
            let item = {
                "loginPerson": loginPerson,
                "processName": processName,
                "developPerson": developPerson,
                "startTime": startTime,
                "endTime": endTime
            }
            port.postMessage(item);// Send a message
        } else {
            alert('We have not found the pre-scheduled personnel and pre-scheduled time of the project, please click the plug-in or open the customized management system to manually add the project! ')}}});Copy the code

Getting elements here is the BASICS of JS. Use the CHROME plugin API

chrome.runtime.connect

  • Maintain a persistent connection mode, create channels (you can name them) between Content Scripts and Chrome extension pages, and handle multiple messages. Each end of the channel has a Chrome.Runtime. Port object for sending and receiving messages. The main thing here is to send a message to the Chrome plugin when we click the desired button.

Content Scripts actively set up channels as follows:

var port = chrome.runtime.connect({name: "custommanage"}); PostMessage ({joke: "Knock Knock "}); / / send a message port. The onMessage. AddListener (function (MSG) {/ / port listening news. PostMessage ({answer: "custommanage"}); });Copy the code

After retrieving the interface information, content Scripts sends a request message to the Google Chrome extension, where we need to retrieve the interface information

Chrome extension to get information

We set up a channel in background.js to retrieve the information returned by the Web interface

chrome.tabs.query(
  { active: true.currentWindow: true },
  function (tabs) {
    var port = chrome.tabs.connect(// Create a channel
      tabs[0].id,
      { name: "custommanage" }// Channel name
    );
  });
chrome.runtime.onConnect.addListener((port) = > {
  console.assert(port.name == "custommanage");
  port.onMessage.addListener((res) = > {   
      addActon(res)
  });
});
Copy the code

The addAction function just saves the data we get to the database.

 / * * *@desc Add fetch data to database */
function addProject (params) {   
      let paramsObj = Object.assign({},  params)
      let optsUpdata = {
        method: 'POST'.// Request method
        body: JSON.stringify(paramsObj), / / request body
        headers: {
          Accept: 'application/json'.'Content-Type': 'application/json'
        }
      }
      fetch('http://****/api/EditConfirmWork', optsUpdata)
        .then(response= > {
          return response.json()
        })
        .then(data= > {
          if (data.code === 0) {
            alert('Update successful! ')
          }
        })
        .catch(error= > {
          alert(error)
        })
}
Copy the code

Fetch function is used to connect the database and modify the database. The back-end interface also needs to do some cross-domain related processing to normal connection. The back-end developed by Node is roughly coded as follows

/ / across domains
app.all(The '*'.function (req, res, next) {
  res.header("Access-Control-Allow-Origin"."*"); 
  res.header('Access-Control-Allow-Methods'.'PUT, GET, POST, DELETE, OPTIONS');
  res.header('Access-Control-Allow-Headers'.'Origin, X-Requested-With, Content-Type, Accept');
  res.header('Access-Control-Allow-Credentials'.true)
  next();
});
Copy the code

At this point, the function of retrieving interface data and automatically saving it to the database is complete,background.js we reference under manifest.json.

"background": {
    "scripts": ["script/background.js"]
  },
Copy the code

We need to package the edited plug-in through Webpack and configure it in webpack.config.js. Then run NPM run watch:dev to get the dist we need.

Webpack.config.js is configured as follows

const webpack = require('webpack');
const ejs = require('ejs');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const WebpackShellPlugin = require('webpack-shell-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const ChromeExtensionReloader = require('webpack-chrome-extension-reloader');
const { VueLoaderPlugin } = require('vue-loader');
const { version } = require('./package.json');

const config = {
  mode: process.env.NODE_ENV,
  context: __dirname + '/src'.entry: {
    'popup/popup': './popup/popup.js'.'script/contentscript': './script/contentscript.js'.'script/background': './script/background.js'
  },
  output: {
    path: __dirname + '/dist'.filename: '[name].js',},resolve: {
    extensions: ['.js'.'.vue'],},module: {
    rules: [{test: /\.vue$/,
        loaders: 'vue-loader'}, {test: /\.js$/,
        loader: 'babel-loader'.exclude: /node_modules/}, {test: /\.css$/,
        use: [MiniCssExtractPlugin.loader, 'css-loader'],}, {test: /\.scss$/,
        use: [MiniCssExtractPlugin.loader, 'css-loader'.'sass-loader'],}, {test: /\.sass$/,
        use: [MiniCssExtractPlugin.loader, 'css-loader'.'sass-loader? indentedSyntax'],}, {test: /\.(png|jpg|gif|svg|ico)$/,
        loader: 'file-loader'.options: {
          name: '[name].[ext]? emitFile=false',}}, {test: /\.(eot|svg|ttf|woff|woff2)(\? \S*)? $/,
        loader: 'url-loader'.options: {
          esModule: false.limin: 10000.name: "font/[name].[hash:8].[ext]"}}],},plugins: [    
    new VueLoaderPlugin(),
    new MiniCssExtractPlugin({
      filename: '[name].css',}).new CopyWebpackPlugin([
      { from: 'icons'.to: 'icons'.ignore: ['icon.xcf'] {},from: 'popup/popup.html'.to: 'popup/popup.html'.transform: transformHtml },
      {
        from: 'manifest.json'.to: 'manifest.json'.transform: (content) = > {
          const jsonContent = JSON.parse(content);
          jsonContent.version = version;

          if (config.mode === 'development') {
            jsonContent['content_security_policy'] = "script-src 'self' 'unsafe-eval'; object-src 'self'";
          }

          return JSON.stringify(jsonContent, null.2); },},])],};if (config.mode === 'production') {
  config.plugins = (config.plugins || []).concat([
    new webpack.DefinePlugin({
      'process.env': {
        NODE_ENV: '"production"',}})); }if (process.env.HMR === 'true') {
  config.plugins = (config.plugins || []).concat([
    new ChromeExtensionReloader(),
  ]);
}

function transformHtml(content) {
  returnejs.render(content.toString(), { ... process.env, }); }module.exports = config;

Copy the code

After data changes, if we want to open the plug-in, we can view the corresponding interface. Here, we can introduce the components we need as required to achieve different interface display.

Finally, attach manifest.json complete configuration

  {
  "name": "Plug-in"."description": "Description"."version": 2.0."manifest_version": 2."icons": {
    "48": "icons/icon_426.png"."128": "icons/icon_426.png"
  },
  "browser_action": {
    "default_title": "Plug-in"."default_popup": "popup/popup.html"
  },
  "permissions": [
    "tabs"."<all_urls>"]."background": {
    "scripts": ["script/background.js"]},"content_scripts": [{
    "js": ["script/contentscript.js"]."matches": [
      "http://*/*"."https://*/*"]]}}Copy the code

reference

www.cnblogs.com/champagne/p… www.jianshu.com/p/b3e544162… Github.com/facert/chro…