前言
作為一個常年的B端前端開發者來說,千篇一律的業務開發有着些許的枯燥無味。在聯調過程中,會經常發現后端在部署服務,然后又不知什么時候部署好,由於公司的部署系統查看系統部署狀態入口較深,所以閑暇之余,研究了一下Chrome
插件開發。因此從今天起,我們進行Chrome
插件的開發學習。
通過下拉框可以快速的切換不同項目的前后端部署狀態,先看一下成果:
1. 什么是Chrome
插件
- 谷歌瀏覽器插件是一種小型的定制瀏覽器體驗的程序,通過插件可以自定義瀏覽器的一些行為來適合個人的需求,例如上面的查看服務器狀態插件。
- 在應用商店中下載下來的插件基本上都是以
.crx
為文件后綴,該文件其實就是一個壓縮包,包括插件所需要的HTML
,Javascript
,CSS
、圖片資源等等文件。 - 開發
Chrome
插件只需要會HTML
,Javascript
,CSS
就可以動手開發了。
2. 基礎概念
2.1 基本原理
下面這張圖很好的可以理解Chrome
插件的原理。
2.2 文件的目錄
│ manifest.json
├─html
│ index.html
├─images
│ icon-128.png
│ icon-48.png
│ icon-16.png
├─scripts
│ background.js
├─styles
│ main.css
└─_locales
├─en
│ messages.json
└─zh_CN
messages.json
manifest.json
是整個插件的功能和文件配置清單,非常重要images
存放的為插件的圖標文件_locales
存放的為插件的國際化語言腳本scripts
存放的為js
文件styles
存放的為樣式文件html
存放的html
文件
着重說一下manifest.json
文件
{
// 清單文件的版本,這個必須寫,而且必須是2
"manifest_version": 2,
// 插件的名稱
"name": "hello-world-plugin",
// 插件的版本
"version": "1.0.0",
// 插件描述
"description": "簡單的Chrome擴展demo",
// 圖標,一般偷懶全部用一個尺寸的也沒問題
"icons":
{
"16": "img/icon.png",
"48": "img/icon.png",
"128": "img/icon.png"
},
// 會一直常駐的后台JS或后台頁面
"background":
{
// 2種指定方式,如果指定JS,那么會自動生成一個背景頁
"page": "background.html"
//"scripts": ["js/background.js"]
},
// 瀏覽器右上角圖標設置,browser_action、page_action、app必須三選一
"browser_action":
{
"default_icon": "img/icon.png",
// 圖標懸停時的標題,可選
"default_title": "hello-world-plugin",
"default_popup": "popup.html"
},
// 當某些特定頁面打開才顯示的圖標
/*"page_action":
{
"default_icon": "img/icon.png",
"default_title": "我是pageAction",
"default_popup": "popup.html"
},*/
// 需要直接注入頁面的JS
"content_scripts":
[
{
//"matches": ["http://*/*", "https://*/*"],
// "<all_urls>" 表示匹配所有地址
"matches": ["<all_urls>"],
// 多個JS按順序注入
"js": ["js/jquery-1.8.3.js", "js/content-script.js"],
// JS的注入可以隨便一點,但是CSS的注意就要千萬小心了,因為一不小心就可能影響全局樣式
"css": ["css/custom.css"],
// 代碼注入的時間,可選值: "document_start", "document_end", or "document_idle",最后一個表示頁面空閑時,默認document_idle
"run_at": "document_start"
},
// 這里僅僅是為了演示content-script可以配置多個規則
{
"matches": ["*://*/*.png", "*://*/*.jpg", "*://*/*.gif", "*://*/*.bmp"],
"js": ["js/show-image-content-size.js"]
}
],
// 權限申請
"permissions":
[
"contextMenus", // 右鍵菜單
"tabs", // 標簽
"notifications", // 通知
"webRequest", // web請求
"webRequestBlocking",
"storage", // 插件本地存儲
"http://*/*", // 可以通過executeScript或者insertCSS訪問的網站
"https://*/*" // 可以通過executeScript或者insertCSS訪問的網站
],
// 普通頁面能夠直接訪問的插件資源列表,如果不設置是無法直接訪問的
"web_accessible_resources": ["js/inject.js"],
// 插件主頁,這個很重要,不要浪費了這個免費廣告位
"homepage_url": "https://www.baidu.com",
// 覆蓋瀏覽器默認頁面
"chrome_url_overrides":
{
// 覆蓋瀏覽器默認的新標簽頁
"newtab": "newtab.html"
},
// Chrome40以前的插件配置頁寫法
"options_page": "options.html",
// Chrome40以后的插件配置頁寫法,如果2個都寫,新版Chrome只認后面這一個
"options_ui":
{
"page": "options.html",
// 添加一些默認的樣式,推薦使用
"chrome_style": true
},
// 向地址欄注冊一個關鍵字以提供搜索建議,只能設置一個關鍵字
"omnibox": { "keyword" : "go" },
// 默認語言
"default_locale": "zh_CN",
// devtools頁面入口,注意只能指向一個HTML文件,不能是JS文件
"devtools_page": "devtools.html"
}
3. Hello world
創建manifest.json
文件, 添加基本的配置
{
"name": "hello-world-plugin",
"description" : "hello-world-plugin",
"version": "1.0",
"manifest_version": 2,
}
在這里我們定義了當前的插件名字hello-world-plugin
,插件的描述和插件的版本。
注意:verison
在插件打包之后,后面可以根據版本號來判斷插件是否需要更新。
給自己的插件添加一個瀏覽器右上角的圖標
增加html
- 繼續修改我們的
manifest.json
文件
{
"name": "hello-world-plugin",
"description" : "hello-world-plugin",
"version": "1.0",
"manifest_version": 2,
# 新增內容
"browser_action": {
"default_popup": "hello_world.html",
"default_icon": "hello_world.png"
}
}
- 我們的
html
文件
<html>
<body>
<h1>hello world</h1>
</body>
</html>
- 我們的圖標文件
新增快捷鍵
修改我們的manifest.json
文件
{
"name": "hello-world-plugin",
"description" : "hello-world-plugin",
"version": "1.0",
"manifest_version": 2,
"browser_action": {
"default_popup": "hello_world.html",
"default_icon": "hello_world.png"
},
"commands": {
"_execute_browser_action": {
"suggested_key": {
"default": "Ctrl+Shift+F",
"mac": "MacCtrl+Shift+F"
},
"description": "Opens hello.html"
}
}
}
安裝瀏覽器
- 找到我們的擴展程序,如下圖所示
點擊加載以解壓的擴展程序
加載我們的擴展程序之后,便可以在拓展程序列表里面查看到我們自己的擴展程序了
從拓展程序的顯示,我們查看我們的擴展程序名稱hello-world-plugin
,擴展程序的描述hello-world-plugin
,以及我們擴展程序的版本號1.0
。
- 點擊我們的擴展程序
點擊我們的擴展程序,我們就可以看到我們的html
內容了
4. 創建 Vue
項目
當前Vue3
已經相對趨於穩定,很多公司和個人都開始嘗鮮使用其進行開發,那么我們今天也以Vue3
進行搭建學習。
使用vue-cli
創建vue3.x
版本的vue
項目 vue create hello-world-plugin
:
vue create hello-world-plugin
Default ([Vue 2] babel, eslint)
> Default (Vue 3) ([Vue 3] babel, eslint)
Manually select features
回車之后,我們選擇Default (Vue 3) ([Vue 3] babel, eslint)
,不過如何喜歡用自己配置的可以選擇第三條。
cd hello-world-plugin
npm run serve
常規的項目跑起來之后可以在瀏覽器看到基本的頁面

此時的項目結構目錄大致為
├── README.md
├── babel.config.js
├── package.json
├── public
│ ├── favicon.ico
│ └── index.html
├── src
│ ├── App.vue
│ ├── assets
│ │ └── logo.png
│ ├── components
│ │ └── HelloWorld.vue
│ └── main.js
└── package-lock.json
5. 改造項目
5.1. 修改項目目錄
- 刪除
main.js
、App.vue
、components
以及文件夾下的HelloWorld.vue
、logo.png
、public
文件 - 在根目錄下創建
vue.config.js
的vue
配置文件 - 從阿里巴巴矢量圖標庫 下載自己想要的圖標文件,筆者用了16、48、128這三種大小的圖標,將下載的圖標放到
src/assets
下面。 src
文件夾下面創建background
、plugins
、popup
、utils
文件- 在
popup
文件夾下面創建components
、main.js
和index.html
,在components
文件夾下創建App.vue
- 在
plugins
文件夾下創建inject.js
和manifest.json
- 在
background
下面創建main.js
文件
那么修改后的項目目錄:
├── src
│ ├── assets
│ │ ├── icon128.png
│ │ ├── icon16.png
│ │ └── icon48.png
│ ├── background
│ │ └── main.js
│ ├── main.js
│ ├── plugins
│ │ ├── inject.js
│ │ └── manifest.json
│ ├── popup
│ │ ├── components
│ │ │ └── app.vue
│ │ ├── index.html
│ │ └── main.js
│ └── utils
├── vue.config.js
└── package-lock.json
├── README.md
├── babel.config.js
├── package.json
5.2. 新增文件配置
manifest.json
我們先配置 src/plugins/manifest.json
文件,我們在前面已經說過,這個是 Chrome
插件必須的文件。
{
"manifest_version": 2,
"name": "hello-word-plugin",
"description": "vue3版本的chrome插件",
"version": "1.0.0",
"browser_action": {
"default_title": "hello-word-plugin",
"default_icon": "assets/icon48.png",
"default_popup": "popup.html"
},
"permissions": [],
"background": {
"scripts": ["js/background.js"]
},
"icons": {
"16": "assets/icon16.png",
"48": "assets/icon48.png",
"128": "assets/icon128.png"
},
"content_scripts": [
{
"matches": ["http://*/*", "https://*/*"],
"css": [],
"js": [],
"run_at": "document_idle"
}
],
"web_accessible_resources": ["js/inject.js"]
}
vue.config.js
vue.config.js
文件作為 vue
項目打包,運行等的基本配置,主要功能是打包成為我們 Chrome
插件所需要的項目目錄。
- 添加
copy-webpack-plugin
模塊,主要是用來拷貝文件
// 安裝
npm install copy-webpack-plugin@6.0.2 -save-dev
- 配置
vue.config.js
const CopyWebpackPlugin = require("copy-webpack-plugin");
const path = require("path");
// 復制文件到指定目錄
const copyFiles = [
{
from: path.resolve("src/plugins/manifest.json"),
to: `${path.resolve("dist")}/manifest.json`
},
{
from: path.resolve("src/assets"),
to: path.resolve("dist/assets")
},
{
from: path.resolve("src/plugins/inject.js"),
to: path.resolve("dist/js")
}
];
// 復制插件
const plugins = [
new CopyWebpackPlugin({
patterns: copyFiles
})
];
// 頁面文件
const pages = {};
// 配置 popup.html 頁面
const chromeName = ["popup"];
chromeName.forEach(name => {
pages[name] = {
entry: `src/${name}/main.js`,
template: `src/${name}/index.html`,
filename: `${name}.html`
};
});
module.exports = {
pages,
productionSourceMap: false,
// 配置 content.js background.js
configureWebpack: {
entry: {
background: "./src/background/main.js"
},
output: {
filename: "js/[name].js"
},
plugins
},
// 配置 content.css
css: {
extract: {
filename: "css/[name].css"
}
}
}
popup/index.html
index.html
我們只是將原來的 public
文件夾下的 index.html
文件內容拷貝過來。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>my-vue-chrome-plugin</title>
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>
popup/main.js
這個是 vue
項目的入口配置文件,將原來的 main.js
復制過來
import { createApp } from 'vue'
import App from './components/App.vue'
createApp(App).mount('#app')
popup/components/App.vue
正常的vue
文件
<template>
<div>hello world</div>
</template>
<script>
export default {
}
</script>
<style>
</style>
background/main.js
簡單打印一下
console.log('hello world background')
5.3. 打包
npm run build
可以看到打印台已經完成打包
此時的項目目錄
.
├── README.md
├── babel.config.js
├── dist
│ ├── assets
│ │ ├── icon128.png
│ │ ├── icon16.png
│ │ └── icon48.png
│ ├── js
│ │ ├── background.js
│ │ ├── chunk-vendors.fa86ccee.js
│ │ ├── inject.js
│ │ └── popup.js
│ ├── manifest.json
│ └── popup.html
├── package.json
├── src
│ ├── assets
│ │ ├── icon128.png
│ │ ├── icon16.png
│ │ └── icon48.png
│ ├── background
│ │ └── main.js
│ ├── plugins
│ │ ├── inject.js
│ │ └── manifest.json
│ ├── popup
│ │ ├── components
│ │ │ └── app.vue
│ │ ├── index.html
│ │ └── main.js
│ └── utils
├── vue.config.js
└── package-lock.json
5.4. 加載插件
在Chrome
插件中加載已解壓的擴展程序
,選擇我們的 dist
文件,發現無法加載
是因為缺少了可以執行的js
文件,因此將我們打包生成的js/chunk-vendors.8a95bbe7.js
文件放到manifest.json
文件里面
"content_scripts": [
{
"matches": ["http://*/*", "https://*/*"],
"css": [],
"js": ["js/chunk-vendors.8a95bbe7.js"], // 修改
"run_at": "document_idle"
}
],
再進行打包加載,即可發現我們的插件已經成功安裝
6. 改進配置
6.1 引入 less less-loader
npm install less@4.0.0 less-loader@7.2.1 -save-dev
修改popup/components/App.vue
文件
<template>
<div class="popup-header">hello world</div>
</template>
<script>
export default {
}
</script>
<style lang="less" scoped>
.popup-header {
width: 100px;
height: 100px;
color: red;
}
</style>
執行打包,發現樣式不生效,但是我們看打印日志確實生成了對應的Css
文件
不要慌,這個時候我們去看一下我們的manifest.json
文件,畢竟它是插件的一切配置來源,然后在content_scripts
里面我們找到了樣式的配置
那么后面就簡單了,將生成的樣式文件配置在里面即可
"content_scripts": [
{
"matches": ["http://*/*", "https://*/*"],
"css": ["css/popup.css"],
"js": ["js/chunk-vendors.69cf2690.js"],
"run_at": "document_idle"
}
],
重新刷新插件,我們可以看到我們想要的內容了
6.2 修改chunk-vendors.xxx.js
在dist
文件夾下,我們可以看到有一個 chunk-vendors.69cf2690.js
,這個是vue
打包之后的文件,每次生成的 chunk-vendors.js
都會帶一個 hash
值,如果每次我們修改了內容,在執行 npm run build
之后,hash
值就會發現變化,那我們豈不是又要在manifest.json
修改 content_scripts
的 js
配置再重新打包。
修改
chunk-vendors.js
使其不帶hash
6.2.1 配置 chainWebpack
字段
module.exports = {
pages,
productionSourceMap: false,
// 配置 content.js background.js
configureWebpack: {
entry: {
background: "./src/background/main.js"
},
output: {
filename: "js/[name].js"
},
plugins
},
// 配置 content.css
css: {
extract: {
filename: "css/[name].css"
}
},
// 增加chainWebpack配置
chainWebpack: config => {
if (process.env.NODE_ENV === 'production') {
config.output.filename('js/[name].js').end()
config.output.chunkFilename('js/[name].js').end()
}
}
}
6.2.2 修改manifest.json
"content_scripts": [
{
"matches": ["http://*/*", "https://*/*"],
"css": ["css/popup.css"],
"js": ["js/chunk-vendors.js"], // 修改不帶hash值
"run_at": "document_idle"
}
],
執行打包
npm run build
可以看到打印日志不再含有hash
值
刷新重新加載插件,可以看到頁面正常顯示
6.3 熱加載
至此我們的
vue
插件開發項目基本完成了配置,剩下的就是按照需求開發插件頁面了,但是我們發現每次打包完之后,都要刷新頁面查看,嚴重影響開發效率。
6.3.1在utils
文件夾下創建 hotReload.js
// 加載文件
const filesInDirectory = dir =>
new Promise(resolve =>
dir.createReader().readEntries(entries => {
Promise.all(
entries
.filter(e => e.name[0] !== '.')
.map(e =>
e.isDirectory ? filesInDirectory(e) : new Promise(resolve => e.file(resolve))
)
)
.then(files => [].concat(...files))
.then(resolve);
})
);
// 遍歷插件目錄,讀取文件信息,組合文件名稱和修改時間成數據
const timestampForFilesInDirectory = dir =>
filesInDirectory(dir).then(files =>
files.map(f => f.name + f.lastModifiedDate).join()
);
// 刷新當前活動頁
const reload = () => {
window.chrome.tabs.query({
active: true,
currentWindow: true
},
tabs => {
// NB: see https://github.com/xpl/crx-hotreload/issues/5
if (tabs[0]) {
window.chrome.tabs.reload(tabs[0].id);
}
// 強制刷新頁面
window.chrome.runtime.reload();
}
);
};
// 觀察文件改動
const watchChanges = (dir, lastTimestamp) => {
timestampForFilesInDirectory(dir).then(timestamp => {
// 文件沒有改動則循環監聽watchChanges方法
if (!lastTimestamp || lastTimestamp === timestamp) {
setTimeout(() => watchChanges(dir, timestamp), 1000); // retry after 1s
} else {
// 強制刷新頁面
reload();
}
});
};
const hotReload = () => {
window.chrome.management.getSelf(self => {
if (self.installType === 'development') {
// 獲取插件目錄,監聽文件變化
window.chrome.runtime.getPackageDirectoryEntry(dir => watchChanges(dir));
}
});
};
export default hotReload;
6.3.2 引入
在background/main.js
中引入
import hotReload from '@/utils/hotReload'
hotReload()
console.log('hello world background')
6.3.3 監聽
- 修改
package.json
中的scripts
,增加一個watch
來監聽打包
"scripts": {
"watch": "vue-cli-service build --watch",
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
}
- 執行監聽打包
npm run watch
,可以發現一只在監聽改變
但是刷新頁面之后,我們發現報錯
- 按照報錯給的提示,我們在
manifest.json
中添加
"content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'",
- 重新執行打包監聽命令,清除錯誤信息,刷新插件,發現即可隨時刷新界面變化了
我們已經搭建完成了一個基本針對Chrome
插件開發的的 Vue
開發的配置,同時也完成了基本的改進配置。剩下的就是簡單的Vue
開發了
歡迎關注我的公眾號