一篇文章教會你如何使用Vue開發Chrome插件


前言

作為一個常年的B端前端開發者來說,千篇一律的業務開發有着些許的枯燥無味。在聯調過程中,會經常發現后端在部署服務,然后又不知什么時候部署好,由於公司的部署系統查看系統部署狀態入口較深,所以閑暇之余,研究了一下Chrome插件開發。因此從今天起,我們進行Chrome插件的開發學習。

通過下拉框可以快速的切換不同項目的前后端部署狀態,先看一下成果:

插件

1. 什么是Chrome插件

  • 谷歌瀏覽器插件是一種小型的定制瀏覽器體驗的程序,通過插件可以自定義瀏覽器的一些行為來適合個人的需求,例如上面的查看服務器狀態插件。
  • 在應用商店中下載下來的插件基本上都是以.crx為文件后綴,該文件其實就是一個壓縮包,包括插件所需要的HTMLJavascriptCSS 、圖片資源等等文件。
  • 開發Chrome插件只需要會 HTMLJavascriptCSS 就可以動手開發了。

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在插件打包之后,后面可以根據版本號來判斷插件是否需要更新。

給自己的插件添加一個瀏覽器右上角的圖標

image-20211126225723155

增加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>
  • 我們的圖標文件

image-20211126230325202

新增快捷鍵

修改我們的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"
    }
  }
}

安裝瀏覽器

  • 找到我們的擴展程序,如下圖所示

image-20211126230821843

點擊加載以解壓的擴展程序

image-20211126230929845

加載我們的擴展程序之后,便可以在拓展程序列表里面查看到我們自己的擴展程序了

image-20211126231039351

從拓展程序的顯示,我們查看我們的擴展程序名稱hello-world-plugin,擴展程序的描述hello-world-plugin ,以及我們擴展程序的版本號1.0

  • 點擊我們的擴展程序

點擊我們的擴展程序,我們就可以看到我們的html內容了

image-20211126231401083

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

常規的項目跑起來之后可以在瀏覽器看到基本的頁面

image-20211127194122797

此時的項目結構目錄大致為

├── 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.jsApp.vuecomponents以及文件夾下的HelloWorld.vuelogo.pngpublic文件
  • 在根目錄下創建vue.config.jsvue配置文件
  • 阿里巴巴矢量圖標庫 下載自己想要的圖標文件,筆者用了16、48、128這三種大小的圖標,將下載的圖標放到src/assets下面。
  • src文件夾下面創建 backgroundpluginspopuputils文件
  • popup文件夾下面創建componentsmain.jsindex.html,在components文件夾下創建App.vue
  • plugins 文件夾下創建 inject.jsmanifest.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 插件所需要的項目目錄。

  1. 添加 copy-webpack-plugin 模塊,主要是用來拷貝文件
// 安裝
npm install copy-webpack-plugin@6.0.2 -save-dev
  1. 配置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

可以看到打印台已經完成打包

image-20211127204106509

此時的項目目錄

.
├── 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 文件,發現無法加載

image-20211127205353154

是因為缺少了可以執行的js 文件,因此將我們打包生成的js/chunk-vendors.8a95bbe7.js文件放到manifest.json 文件里面

	"content_scripts": [
		{
			"matches": ["http://*/*", "https://*/*"],
			"css": [],
      "js": ["js/chunk-vendors.8a95bbe7.js"], // 修改
			"run_at": "document_idle"
		}
	],

再進行打包加載,即可發現我們的插件已經成功安裝

image-20211127205637069

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 文件

img

不要慌,這個時候我們去看一下我們的manifest.json 文件,畢竟它是插件的一切配置來源,然后在content_scripts里面我們找到了樣式的配置

img

那么后面就簡單了,將生成的樣式文件配置在里面即可

"content_scripts": [
  {
   "matches": ["http://*/*", "https://*/*"],
   "css": ["css/popup.css"],
   "js": ["js/chunk-vendors.69cf2690.js"],
   "run_at": "document_idle"
  }
 ],

重新刷新插件,我們可以看到我們想要的內容了

img

6.2 修改chunk-vendors.xxx.js

dist 文件夾下,我們可以看到有一個 chunk-vendors.69cf2690.js ,這個是vue 打包之后的文件,每次生成的 chunk-vendors.js 都會帶一個 hash 值,如果每次我們修改了內容,在執行 npm run build 之后,hash 值就會發現變化,那我們豈不是又要在manifest.json 修改 content_scriptsjs 配置再重新打包。

修改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

img

刷新重新加載插件,可以看到頁面正常顯示

img

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,可以發現一只在監聽改變

img

但是刷新頁面之后,我們發現報錯

img

  • 按照報錯給的提示,我們在manifest.json 中添加
"content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'",
  • 重新執行打包監聽命令,清除錯誤信息,刷新插件,發現即可隨時刷新界面變化了

我們已經搭建完成了一個基本針對Chrome插件開發的的 Vue開發的配置,同時也完成了基本的改進配置。剩下的就是簡單的Vue 開發了

歡迎關注我的公眾號
img


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM