引言

本人Vue3小白一枚,目前正在研究基于Vue3的Chrome浏览器插件的使用,经过不断尝试和整合各路大佬方法,现将自己尝试有效的项目框架搭建流程进行总结。在此非常感谢大佬们的分享。
本文主要参考文章——15000字大章带你一步一步使用Vue3开发chrome浏览器插件

初始Vue项目的创建

  • 控制台中输入vue create XXX创建新的vue工程项目
  • 在创建工程过程中选择Manually select features手动配置模块
  • 选中babel、linter、router后进行下一步,其中router项目若无需求可不选
  • 对于vue.js选择3.x版本
  • Router模式选择history
  • 选择ESLint with error prevention only,不选其他的,不然代码规范对后续开发产生很多麻烦
  • 随后选择Lint on save,如果有Git也可选择下面那项
  • 选择In dedicated config files将eslint、babel等文件分别单独存放
  • 完成初始Vue项目的创建

Element-Plus组件的安装及导入

  • 控制台输入npm install element-plus --save向Vue项目安装element-plus组件
  • 项目结构可参考大佬的文章所给出的结构,我的项目最终产出的目录结构为:
├── dist
│   ├── assets
│   │   ├── images
│   │   │   ├── icon128.png
│   │   │   ├── icon16.png
│   │   │   └── icon48.png
│   │   └── logo.png
│   ├── js
│   │   ├── background.js
│   │   ├── chunk-vendors.js
│   │   ├── content.js
│   │   ├── inject.js
│   │   └── popup.js
│   ├── manifest.json
│   └── popup.html
├── src
│   ├── assets
│   │   ├── images
│   │   │   ├── icon128.png
│   │   │   ├── icon16.png
│   │   │   └── icon48.png
│   │   └── logo.png
│   ├── background
│   │   └── main.js
│   ├── content
│   │   ├── components
│   │   │   └── app.vue
│   │   └── main.js
│   ├── plugins
│   │   ├── inject.js
│   │   └── manifest.json
│   ├── popup
│   │   ├── components
│   │   │   └── app.vue
│   │   ├── index.html
│   │   └── main.js
│   ├── router
│	│	└── index.js
│   └── utils
│		└── hotReload.js
├── .browserslistrc
├── .eslintrc.js
├── .gitignore
├── babel.config.js
├── jsconfig.json
├── package-lock.json
├── package.json
├── README.md
└── vue.config.js
  • 其中dist文件夹为执行npm run build指令后生成的,其他均应在执行npm run build前准备就绪
  • (如果开发的插件不涉及路由等操作,则删除src文件夹下的router/index.js文件夹及文件)
  • src/popup/main.js中添加element-plus插件,代码如下:
import { createApp } from 'vue'
import App from './components/app.vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import zhCn from 'element-plus/es/locale/lang/zh-cn'

const app = createApp(App)
app.use(ElementPlus, {locale: zhCn})
app.mount('#app')
  • 至此,项目的创建和element-plus组件的导入完成

其他文件配置

.eslintrc.js文件

  • 主要是在globals中添加chrome: true从而让chrome属性在全局的.js文件中为可用状态
  • 复制以下内容到eslintrc.js文件中
module.exports = {
  root: true,
  globals: {
    chrome: true,
  },
  env: {
    node: true
  },
  'extends': [
    'plugin:vue/vue3-essential',
    'eslint:recommended'
  ],
  parserOptions: {
    parser: '@babel/eslint-parser'
  },
  rules: {
    'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
    'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off'
  }
}

vue.config.js文件

  • vue.config.js作为项目配置文件,对打包生成的dist文件夹以及src文件夹下的各文件起到关键作用
  • 复制以下代码到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: {
			content: "./src/content/main.js",
			background: "./src/background/main.js"
		},
		output: {
			filename: "js/[name].js"
		},
		plugins
	},
	// 配置 content.css
	css: {
		extract: {
			filename: "css/[name].css"
		}
	},
	chainWebpack: config => {
		if (process.env.NODE_ENV === 'production') {
			config.output.filename('js/[name].js').end()
			config.output.chunkFilename('js/[name].js').end()
		}
	}
}

package.json

  • 在 "scripts"字段中添加 “watch”: “vue-cli-service build --watch”,来增加NPM脚本中对插件的热监控功能
  "scripts": {
    "watch": "vue-cli-service build --watch",
    "serve": "vue-cli-service serve",
    "build": "vue-cli-service build",
    "lint": "vue-cli-service lint"
  },

src/文件夹

  • src文件夹下主要针对manifest.json文件、popup中的文件、content中的文件进行更改

assets/

  • assets文件中用于存放图标,注意images文件夹内的三张图片要是同一个图片的不同的名称,例如:
    assets文件夹样式

background/main.js

  • background作为Chrome插件的后台,随着浏览器的启动而启动,可将全局性的、一直运行的功能放在此处
  • 考虑到仅仅做一个demo,此处此处打印一串字作为测试,将以下内容复制到main.js中
console.log('this is background')

content/components/app.vue文件

  • 添加以下内容到app.vue文件作为植入到浏览器中的HTML文件(不植入无法获取网页中的DOM)
  • 注意:此时的.vue文件尚未具有注入HTML中的能力
<template>
	<div class="head">this is the content</div>
</template>

<script setup>
</script>

<style scoped>
.head{
	color: red;
	position: fixed;
	z-index: 100001;
	right: 10px;
	bottom: 10px;
}
</style>

content/main.js文件

  • 添加以下内容到main.js文件中,在浏览器当前Tab中创建一个< div >标签并将app.vue文件加载到标签中以完成HTML的植入的操作
  • 当app.vue成功植入到HTML中时,我们便可以通过chrome自带的功能猥琐欲为了
import { createApp } from 'vue'
import app from './components/app.vue'
joinContent(app)
injectJsInsert()
function joinContent (element) {
	const div = document.createElement('div')
	div.id = 'joinContentApp'
	document.body.appendChild(div)
	console.log(div)
	createApp(element).mount('#joinContentApp')
}
//chrome的API接口,用于传输或监听数据信号
chrome.extension.onRequest.addListener( 
  function (request) {
    if (request.popAction == "Test") {
			console.log("test")
    }
  }
);
function injectJsInsert () {
	document.addEventListener('readystatechange', () => {
		const injectPath = 'js/inject.js'
		const script = document.createElement('script')
		script.setAttribute('type', 'text/javascript')
		script.src = chrome.extension.getURL(injectPath)
		document.body.appendChild(script)
	})
}

plugins/inject.js

  • inject.js文件主要负责分析网页的DOM,拿到大图资源链接,并翻页,直到获取足够数量的图片
  • 可以在此文件内输入以下内容,最终结果可在浏览器窗口的控制台中打印出来
console.log('this is inject')

plugins/manifest.json文件

  • manifest.json文件的重要性再此不多做赘述了,文件中各个字段的意义和功能请见其他大佬的博客——Chrome插件manifest.json文件详解
  • 复制以下内容到manifest.json中以完成Chrome插件的配置
{
	"manifest_version": 2,
	"name": "test",
	"description": "Vue3的Chrome插件",
	"version": "1.0.0",
	"browser_action": {
		"default_title": "plugin-base-vue3",
		"default_icon": "assets/images/icon48.png",
		"default_popup": "popup.html"
	},
	"permissions": [],
	"background": {
		"scripts": ["js/chunk-vendors.js", "js/background.js"]
	},
	"icons": {
		"16": "assets/images/icon16.png",
		"48": "assets/images/icon48.png",
		"128": "assets/images/icon128.png"
	},
	"content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'",
	"content_scripts": [
		{
			"matches": ["http://*/*", "https://*/*"],
			"css": ["css/content.css"],
			"js": ["js/chunk-vendors.js", "js/content.js"],
			"run_at": "document_idle"
		}
	],
	"web_accessible_resources": ["js/inject.js"]
}

popup/components/app.vue文件

  • 添加以下内容到app.vue文件中作为浏览器插件的前端显示窗口
<template>
  <div class="wrapper">
    <el-button type="danger" @click="test">测试按钮</el-button>
  </div>
</template>
<script setup>
const test = () => {
  chrome.tabs.getSelected(null, function(tab){
  chrome.tabs.sendRequest(tab.id, { popAction: "Test"});
  });
}
</script>
<style scoped>
.wrapper{
  width: 100px;
  height: 100px;
  background-color: aquamarine;
}
</style>

popup/index.html

  • index.html文件作为.vue文件的载体,其内容使用新建项目时的初始内容即可
<!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>test</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>
  </body>
</html>
  • 上述文件内容填写完成后在控制台执行npm run build指令以获得dist文件,使用Chrome加载dist文件夹以加载插件

成果检验

  • 在加载插件的窗口中,点击查看视图旁的背景页可以看到来自background/main.js文件的打印内容
    background效果
  • 打开任意一个页面可以看到右下角出现了this is the content-script字样,这说明我们的content里的app.vue文件已经成功植入到网页的html文件中了
  • 打开插件,出现了带有Element-plus控件元素的按钮,说明Element已经成功引入
  • 打开控制台(F12)可以看到植入到html页面中的< div >元素被打印了出来,同时来自plugins/inject.js的内容也被打印了出来
  • 点击插件中的按钮在控制台中出现了test字样,这说明content中的js文件已经接收到了来自popup中js传来的信息(由于.vue内带有script的编写区域,因此chrome的数据传输功能可直接在.vue文件中进行)
    在这里插入图片描述
  • 恭喜你!你已经成功搭建了基于Vue3+Element-plus的Chrome框架

热更新设置

  • 如果不采用热更新,开发过程中将会不断经历"更改->打包->加载->再更改"的痛苦历程,为了提高开发效率,在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;
  • 同时修改background/main.js为:
import hotReload from '@/utils/hotReload'
hotReload()
console.log('this is background')
  • package.json中的scripts字段中添加"watch": "vue-cli-service build --watch",来向NPM脚本中添加热更新快捷操作

2022-8-17使用的vue环境

  • 本文创建于2022-8-17日,采用的package.json为:
{
  "name": "plugin",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "watch": "vue-cli-service build --watch",
    "serve": "vue-cli-service serve",
    "build": "vue-cli-service build",
    "lint": "vue-cli-service lint"
  },
  "dependencies": {
    "core-js": "^3.8.3",
    "element-plus": "^2.2.13",
    "vue": "^3.2.13",
    "vue-router": "^4.0.3"
  },
  "devDependencies": {
    "@babel/core": "^7.12.16",
    "@babel/eslint-parser": "^7.12.16",
    "@vue/cli-plugin-babel": "~5.0.0",
    "@vue/cli-plugin-eslint": "~5.0.0",
    "@vue/cli-plugin-router": "~5.0.0",
    "@vue/cli-service": "~5.0.0",
    "eslint": "^7.32.0",
    "eslint-plugin-vue": "^8.0.3",
    "less": "^4.1.3",
    "less-loader": "^11.0.0"
  }
}
  • 通常来说,即使不采用本文的各模块版本,按照本文提供的方法一步一步执行也可以创建出Chrome插件的框架
  • 本文所讲述内容,作者从项目创建开始一步一步执行,亲测有效,所涉及各项功能均无异常
  • ———————————————————————————————————————————
  • 2022.12.15 00:10:00更新——最近看到评论区的小伙伴们在按照文章步骤实现的过程中出现了各种各样的报错,于是我按照文章内容再次搭建了一遍,确认按照步骤搭建chrome插件是没问题的,为了方便大家能够更加简单方便的使用本文提供的方法,我已将代码上传至git代码仓库,有需要的小伙伴们可以自行【前往下载】
  • 代码仓库地址 —— https://gitee.com/XiaoYueDeCode/vue3-chrome-plugin-demo
  • 下载完成的代码,需要在本地使用npm inpm install element-plus --save指令完成所需环境的布置。环境布置完成后就可以愉快的开始你的创作啦。
  • 大半夜更新代码属实不易,希望小伙伴们能够关注收藏,多多支持,谢谢大家啦~~
Logo

前往低代码交流专区

更多推荐