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 ref="float" v-drag> <el-card :body-style="{ padding: '15px' }"> <div slot="header" 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"> <span> {} </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
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" > </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)