Developing Chrome plug-ins using Vue

Original link:   Using Vue to develop Chrome plug-in - ashamed Xiaozhan (kuizuo.cn)

preface

When I was learning to develop chrome plug-ins, I didn't know Vue, let alone Webpack, so I used native html development, not to mention the efficiency. This time, I'm going to use Vue cli to write a function of a B station to obtain video information and comments (originally intended to make automatic replies), and consolidate chrome development (I haven't touched script related technologies for nearly a year). By the way, I write a set of templates to pave the way for my subsequent chrome plug-ins.

I won't repeat the basic knowledge of Chrome plug-in development. I wrote an article on native development before Chrome plug-in development - ashamed station , with links to relevant documents.

Related code open source github address

Environment construction

Vue Web-Extension - A Web-Extension preset for VueJS (vue-web-extension.netlify.app)

npm install -g @vue/cli
npm install -g @vue/cli-init
vue create --preset kocal/vue-web-extension my-extension
cd my-extension
npm run server
 Copy code

Several options are provided, such as Eslint, background.js, tab page and axios, as shown in the following figure

After selection, the dependency will be automatically downloaded. Through npm run server, a dist folder will be generated in the root directory. Drag the file to Chrome plug-in management to install it. Due to the use of webpack, the changed code will be hot updated without repeated compilation and import.

Project structure

├─src
|  ├─App.vue
|  ├─background.js
|  ├─main.js
|  ├─manifest.json
|  ├─views
|  |   ├─About.vue
|  |   └Home.vue
|  ├─store
|  |   └index.js
|  ├─standalone
|  |     ├─App.vue
|  |     └main.js
|  ├─router
|  |   └index.js
|  ├─popup
|  |   ├─App.vue
|  |   └main.js
|  ├─override
|  |    ├─App.vue
|  |    └main.js
|  ├─options
|  |    ├─App.vue
|  |    └main.js
|  ├─devtools
|  |    ├─App.vue
|  |    └main.js
|  ├─content-scripts
|  |        └content-script.js
|  ├─components
|  |     └HelloWorld.vue
|  ├─assets
|  |   └logo.png
├─public
├─.browserslistrc
├─.eslintrc.js
├─.gitignore
├─babel.config.js
├─package.json
├─vue.config.js
├─yarn.lock
 Copy code

According to the selected page and configure the page information in src and vue.config.js, the compiled dist directory structure is as follows

├─devtools.html
├─favicon.ico
├─index.html
├─manifest.json
├─options.html
├─override.html
├─popup.html
├─_locales
├─js
├─icons
├─css
 Copy code

Install component library

Install elementUI

There is basically no big difference between the overall development and vue2 development, but since it is developed with Vue, there must be a component library.

It is also very simple to import element UI, Vue. Use (element UI);   Vue2 imports elements as they are imported. The demonstration is as follows

However, I didn't use the Babel plugin component to import on-demand. After one button is packaged, it is about 1.6m, while the full import is about 5.5. Why not? I need to import the element component in content-scripts.js. If Babel plugin component is used, I can't import components and styles on demand (it should only support vue files to be imported on demand. In short, it's bothering me all night)

Installing tailwindcss

However, the official provides how to use tailwind CSS. Here's a demonstration

Installing tailwind CSS on Vue 3 and Vite - tailwind CSS Chinese documentation

It is recommended to install the lower version. The latest version has compatibility problems

npm install tailwindcss@npm:@tailwindcss/postcss7-compat postcss@^7 autoprefixer@^9
 Copy code

Create the postcss.config.js file

// postcss.config.js
module.exports = {
  plugins: [
    // ...
    require('tailwindcss'),
    require('autoprefixer'), // if you have installed `autoprefixer`
    // ...
  ]
}
Copy code

Create tailwind.config.js file

// tailwind.config.js
module.exports = {
  purge: {
    // Specify the paths to all of the template files in your project
    content: ['src/**/*.vue'],
  
    // Whitelist selectors by using regular expression
    whitelistPatterns: [
        /-(leave|enter|appear)(|-(to|from|active))$/, // transitions
        /data-v-.*/, // scoped css
    ],
  }
  // ...
}
Copy code

Import styles in src/popup/App.vue, or create a new style.css, and import "../style.css" in mian.js;

<style>
/* purgecss start ignore */
@tailwind base;
@tailwind components;
/* purgecss end ignore */

@tailwind utilities;
</style>
Copy code

Import a login form from the official example. The effect is as follows

Project construction

Page construction

There's nothing to say about page building. Because the element UI is used, the page is built soon, and the effect is shown in the figure

Suspension window

In fact, the floating window is optional, but the floating window was written when the Chrome plug-in was written before, so the vue version is also included.

It should be noted that the floating window is embedded in the web page (and loaded before the document is loaded, that is, "run_at": "document_start"), so the Dom element of the page can be operated through content-scripts.js. First, match the website to be added and the injected JS code in the configuration lists manifest.json and vue.configuring.js, as shown below

  "content_scripts": [
    {
      "matches": ["https://www.bilibili.com/video/*"],
      "js": ["js/jquery.js", "js/content-script.js"],
      "css": ["css/index.css"],
      "run_at": "document_start"
    },
    {
      "matches": ["https://www.bilibili.com/video/*"],
      "js": ["js/jquery.js", "js/bilibili.js"],
      "run_at": "document_end"
    }
  ]
Copy code
	contentScripts: {
          entries: {
            'content-script': ['src/content-scripts/content-script.js'],
            bilibili: ['src/content-scripts/bilibili.js'],
          },
        },
Copy code

Because Vue is used, but components need to be generated in js, document.createElement is used to create elements. Vue components are as follows (you can drag and drop)

:::danger

If you use Babel plugin component to import on-demand, the style of the component cannot be loaded. At the same time, if you write a style tag for a custom component, it also cannot be loaded. An error is reported: Cannot read properties of undefined (reading 'appendChild')

Roughly, css loader cannot load the corresponding css code. If you insist on writing css, you can directly inject css into manifest.json

:::

Complete code

// Note that the vue introduced here is a runtime module, because content is inserted into the target page. The rendering of components requires the vue of the runtime, not the vue of the compilation environment (I don't know what I'm talking about, anyway, that's what I mean)
import Vue from 'vue/dist/vue.esm.js';
import ElementUI, { Message } from 'element-ui';
Vue.use(ElementUI);

// Note that run_at=document_start must be set for this code to take effect
document.addEventListener('DOMContentLoaded', function() {
  console.log('vue-chrome Extension loaded');

  insertFloat();
});

// Create a new DOM element with id in the target page and mount the vue object on the dom.
function insertFloat() {
  let element = document.createElement('div');
  let attr = document.createAttribute('id');
  attr.value = 'appPlugin';
  element.setAttributeNode(attr);
  document.getElementsByTagName('body')[0].appendChild(element);

  let link = document.createElement('link');
  let linkAttr = document.createAttribute('rel');
  linkAttr.value = 'stylesheet';
  let linkHref = document.createAttribute('href');
  linkHref.value = 'https://unpkg.com/element-ui/lib/theme-chalk/index.css';
  link.setAttributeNode(linkAttr);
  link.setAttributeNode(linkHref);
  document.getElementsByTagName('head')[0].appendChild(link);

  let left = 0;
  let top = 0;
  let mx = 0;
  let my = 0;
  let onDrag = false;

  var drag = {
    inserted: function(el) {
      (el.onmousedown = function(e) {
        left = el.offsetLeft;
        top = el.offsetTop;
        mx = e.clientX;
        my = e.clientY;
        if (my - top > 40) return;

        onDrag = true;
      }),
        (window.onmousemove = function(e) {
          if (onDrag) {
            let nx = e.clientX - mx + left;
            let ny = e.clientY - my + top;
            let width = el.clientWidth;
            let height = el.clientHeight;
            let bodyWidth = window.document.body.clientWidth;
            let bodyHeight = window.document.body.clientHeight;

            if (nx < 0) nx = 0;
            if (ny < 0) ny = 0;

            if (ny > bodyHeight - height && bodyHeight - height > 0) {
              ny = bodyHeight - height;
            }

            if (nx > bodyWidth - width) {
              nx = bodyWidth - width;
            }

            el.style.left = nx + 'px';
            el.style.top = ny + 'px';
          }
        }),
        (el.onmouseup = function(e) {
          if (onDrag) {
            onDrag = false;
          }
        });
    },
  };

  window.kz_vm = new Vue({
    el: '#appPlugin',
    directives: {
      drag: drag,
    },
    template: `
      <div class="float-page" ref="float" v-drag>
        <el-card class="box-card" :body-style="{ padding: '15px' }">
          <div slot="header" class="clearfix" style="cursor: move">
            <span>Suspension window</span>
            <el-button style="float: right; padding: 3px 0" type="text" @click="toggle">{{ show ? 'Put away' : 'open'}}</el-button>
          </div>
          <transition name="ul">
            <div v-if="show" class="ul-box">
              <span> {{user}} </span>
            </div>
          </transition>
        </el-card>
      </div>
      `,
    data: function() {
      return {
        show: true,
        list: [],
        user: {
          username: '',
          follow: 0,
          title: '',
          view: 0,
        },
      };
    },
    mounted() {},
    methods: {
      toggle() {
        this.show = !this.show;
      },
    },
  });
}

Copy code

Because you can only write vue components in js, you have to use the template template and directives to add drag and drop functions to the components (especially window.onmousemove. If the element binds its own mouse movement event, dragging and dropping the mouse will be very catchy). You also use transition to perform slow animation effects. The injected css code is as follows

.float-page {
  width: 400px;
  border-radius: 8px;
  position: fixed;
  left: 50%;
  top: 25%;
  z-index: 1000001;
}

.el-card__header {
  padding: 10px 15px !important
}

.ul-box {
  height: 200px;
  overflow: hidden;
}

.ul-enter-active,
.ul-leave-active {
  transition: all 0.5s;
}
.ul-enter,
.ul-leave-to {
  height: 0;
}
Copy code

The relevant logic can be viewed by yourself. It is not repeated here and is not complex.

By the way, I'll review the mouse events and vue custom commands in HTML

Function realization

major function

  • Detect the video page, output the corresponding up main, attention number and video title play (if there are too many parameters, they will not be displayed one by one)

  • Monitoring keywords judge whether to like according to the content. For example, if the text appears next time, then like.

Output relevant information

In fact, as long as you have contacted DIU DIU crawler, you will know how to implement it. Right click to review elements, like this

Then use the dom operation, select the corresponding element, and the output can be

> document.querySelector("#v_upinfo > div.up-info_right > div.name > a.username").innerText
< 'Old tomato'
Copy code

Of course, the effect of using JQuery is the same. I will use JQuery for operation in the future

Write the following code in Src / content script / bilibilibili.js

window.onload = function() {
  console.log('Loading complete');

  function getInfo() {
    let username = $('#v_upinfo > div.up-info_right > div.name > a.username').text();
    let follow = $(`#v_upinfo > div.up-info_right > div.btn-panel > div.default-btn.follow-btn.btn-transition.b-gz.following > span > span > span`).text();
    let title = $(`#viewbox_report > h1 > span`).text();
    let view = $('#viewbox_report > div > span.view').attr('title');

    console.log(username, follow, title, view);
  }
  
  getInfo();
};
Copy code

Reload the plug-in and output the viewing results

Loading complete
bilibili.js:19 Old tomato 1606.0 Million top picture quality total playback 2368406
 Copy code

These data must be simply output, which must be useless. It should be displayed in an embedded floating window or on a pop page (or even send an ajax request to a remote server for storage)

Slightly change the above code

window.onload = function() {
  console.log('Loading complete');

  function getInfo() {
    let username = $('#v_upinfo > div.up-info_right > div.name > a.username').text().trim()
    let follow = $(`#v_upinfo > div.up-info_right > div.btn-panel > div.default-btn.follow-btn.btn-transition.b-gz.following > span > span > span`).text();
    let title = $(`#viewbox_report > h1 > span`).text();
    let view = $('#viewbox_report > div > span.view').attr('title');

    //console.log(username, follow, title, view);
    window.kz_vm.user = {
      username,
      follow,
      title,
      view,
    };

  }
  getInfo();
};
Copy code

Where window.kz_vm is through window.kz_vm = new Vue()   To initialize and facilitate the operation of VM objects, we need to select elements through jquery and add attributes. If you want, you can also write code directly on content-script.js, so there is no need to use window objects. However, some business logic is piled in one file, so I am used to dividing it into bili bilibili.js, and then the injection method is document_end, and then operate dom element? The implementation effect is as follows

If you want to display a pop page, you only need to communicate through the page, but the premise is to open pop first, so you usually transfer through background. Generally speaking, there is little content – > pop (because the premise of operating pop is to open pop), and relatively more content – > background or pop – > content

Content script actively sends messages to the backstage. I'm Xiaoming's classmate - blog Garden (cnblogs.com)

Implementation Review

Here, we have written a simple page, and sent it to the content through pop. Let the content enter the comment content, and click send to see the effect first

Similarly, find the corresponding element location

// Comment text box
$('#Comment > div > div.comment > div > div.comment-send > div.textarea-container > textarea '). Val ("content to reply");
// Comment button
$('#comment > div > div.comment > div > div.comment-send > div.textarea-container > button').click();
Copy code

The next step is to write the page communication. You can see that the pop sends a request to the content

window.onload = function() {
  console.log('content Loading complete');

  function comment() {
    chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
      let { cmd, message } = request;
      if (cmd === 'addComment') {
        $('#comment > div > div.comment > div > div.comment-send > div.textarea-container > textarea').val(message);
        $('#comment > div > div.comment > div > div.comment-send > div.textarea-container > button').click();
      }
  
      sendResponse('I got your message!');
    });
  }
  
  comment();
};
Copy code
<template>
  <div>
    <el-container>
      <el-header height="24">B Station gadget</el-header>
      <el-main>
        <el-row :gutter="5">
          <el-input
            type="textarea"
            :rows="2"
            placeholder="Please enter the content"
            v-model="message"
            class="mb-5"
          >
          </el-input>

          <div>
            <el-button @click="addComment">comment</el-button>
          </div>
        </el-row>
      </el-main>
    </el-container>
  </div>
</template>

<script>
export default {
  name: 'App',
  data() {
    return {
      message: '',
      list: [],
      open: false,
    }
  },
  created() {
    chrome.storage.sync.get('list', (obj) => {
      this.list = obj['list']
    })
  },
  mounted() {
    chrome.runtime.onMessage.addListener(function (
      request,
      sender,
      sendResponse
    ) {
      console.log('Received from content-script Message:')
      console.log(request, sender, sendResponse)
      sendResponse('I'm backstage. I've received your message:' + JSON.stringify(request))
    })
  },
  methods: {
    sendMessageToContentScript(message, callback) {
      chrome.tabs.query({ active: true, currentWindow: true }, function (tabs) {
        chrome.tabs.sendMessage(tabs[0].id, message, function (response) {
          if (callback) callback(response)
        })
      })
    },
    addComment() {
      this.sendMessageToContentScript(
        { cmd: 'addComment', message: this.message },
        function () {
          console.log('come from content Your reply:' + response)
        }
      )
    },
  },
}
</script>
Copy code

The code will not be interpreted. Just call the sendMessageToContentScript method. Relevant source code can be downloaded and viewed by yourself

It is the same to realize similar praise functions.

Overall experience

At that time, the efficiency of writing Chrome plug-ins could not be said to be slow. Anyway, it was not fast. For example, some tips had to be encapsulated by themselves. Everyone who has used Vue knows that it is convenient to write web pages. Writing Chrome plug-ins is not to write a web page. At that time, after contacting Vue, I germinated the idea of using Vue to write Chrome. Of course, I am not the only one who thought so, so I can search the corresponding source code on github, so I have this article.

If there is something related to crawling data, I certainly prefer to use HTTP protocol. If I'm not sure, I'll choose to use puppeterjs. However, the Chrome plug-in mainly enhances the page function and can realize the functions that the original page does not have.

This article is only a preliminary experience. I simply wrote a small project. In the later stage, it is possible to realize a Baidu online disk, fill in the extraction code with one click, and Js spits out Hooke related information. (originally, it was intended that the pdd merchant would reply automatically. The customer said to use the client instead of the web side (the client can log in multiple numbers). However, this blog takes station B as a demonstration)

Tags: Javascript html5 Vue.js chrome

Posted on Sun, 19 Sep 2021 10:34:47 -0400 by SwarleyAUS