15000字大章带你一步一步使用Vue3开发chrome浏览器插件
之前写过一篇 chrome 浏览器插件开发的文章 全方面手把手从0到1带你开发谷歌浏览器插件 ,但是不是 vue/react 这种第三方框架的,是原生和 jquery 混合的,但是那种开发前端方式比较麻烦,所以下面是用 vue 来开发插件github地址:https://github.com/18055975947/my-vue3-plugin码云地址:https://gitee.com/guoq
之前写过一篇 chrome 浏览器插件开发的文章 全方面手把手从0到1带你开发谷歌浏览器插件 ,但是不是
vue/react
这种第三方框架的,是原生和jquery
混合的,但是那种开发前端方式比较麻烦,所以下面是用vue
来开发插件
github地址:https://github.com/18055975947/my-vue3-plugin
码云地址:https://gitee.com/guoqiankun/my-vue3-plugin
一、创建 Vue 项目
使用 vue-cli
创建 vue3.x
版本的 vue
项目
vue create my-vue3-plugin
如果在创建项目的时候报错,报错内容如下:
error Couldn't find package "postcss-normalize-string@^4.0.2" required by "cssnano-preset-default@^4.0.0" on the "npm" registry.
Error: Couldn't find package "@vue/cli-overlay@^4.5.9" required by "@vue/cli-service@~4.5.0" on the "npm" registry.
at MessageError.ExtendableBuiltin (/usr/local/lib/node_modules/yarn/lib/cli.js:243:66)
at new MessageError (/usr/local/lib/node_modules/yarn/lib/cli.js:272:123)
at PackageRequest.<anonymous> (/usr/local/lib/node_modules/yarn/lib/cli.js:38988:17)
at Generator.throw (<anonymous>)
at step (/usr/local/lib/node_modules/yarn/lib/cli.js:92:30)
at /usr/local/lib/node_modules/yarn/lib/cli.js:105:13
at process._tickCallback (internal/process/next_tick.js:68:7)
ERROR command failed: yarn
可以参考 使用 vue-cli
创建 vue3.x
版本项目报错 文章来处理
此时文件目录为:
.
├── README.md
├── babel.config.js
├── package.json
├── public
│ ├── favicon.ico
│ └── index.html
├── src
│ ├── App.vue
│ ├── assets
│ │ └── logo.png
│ ├── components
│ │ └── HelloWorld.vue
│ └── main.js
└── yarn.lock
二、修改项目
因为我们要开发 chrome
插件项目,而这种生成的 vue
项目里面的文件夹和文件很多我们不需要,所以我们需要处理下:
- 在根目录下创建
vue.config.js
的vue
配置文件; - 把
src
文件夹下面的app.vue、components
文件夹删除 - 在
assets
文件中创建images
文件夹,并在images
文件夹里面添加自己插件的icon
- 删除根目录下的
public
文件夹 - 在
src
文件夹下 创建background、content、plugins、popup、utils
文件夹 - 在
background
文件夹下创建main.js
- 在
content
文件夹下创建components
文件夹和main.js
,components
文件夹下创建app.vue
- 在
plugins
文件夹下创建inject.js、manifest.json
文件 - 在
popup
文件夹下创建components
文件夹main.js
和index.html
,components
文件夹下创建app.vue
步骤解析:
vue.config.js
是vue
项目的打包、运行、等的配置文件,我们需要生成插件项目,这个文件需要创建并且自行配置- 删除多余的文件,我们插件里面目前只有 需要一个
popup
页面,不需要 外部的app.vue
和 组件 - 自己的插件
icon
,按照16 * 16、48 * 48、128 * 128
三个尺寸 - 不需要
public
文件夹里面的index.html
- 创建我们插件需要的
background.js、content.js、popup页面、插件配置
等 - 创建
background.js
文件 - 创建
content.js
文件 - 创建
popup.js、popup.html
文件
此时文件目录:
.
├── README.md
├── babel.config.js
├── package.json
├── src
│ ├── assets
│ │ ├── images
│ │ │ ├── icon128.png
│ │ │ ├── icon16.png
│ │ │ └── icon48.png
│ │ └── logo.png
│ ├── background
│ │ └── main.js
│ ├── content
│ │ ├── components
│ │ │ └── app.vue
│ │ └── main.js
│ ├── main.js
│ ├── plugins
│ │ ├── inject.js
│ │ └── manifest.json
│ ├── popup
│ │ ├── components
│ │ │ └── app.vue
│ │ ├── index.html
│ │ └── main.js
│ └── utils
├── vue.config.js
└── yarn.lock
三、配置项目
1、plugins/manifest.json
文件配置
先配置 manifest.json
文件,在按照此文件配置 vue.config.js
文件
{
"manifest_version": 2,
"name": "my-vue3-plugin",
"description": "基于vue3.x版本的chrome插件",
"version": "1.0.0",
"browser_action": {
"default_title": "my-vue3-plugin",
"default_icon": "assets/images/icon48.png",
"default_popup": "popup.html"
},
"permissions": [],
"background": {
"scripts": ["js/background.js"]
},
"icons": {
"16": "assets/images/icon16.png",
"48": "assets/images/icon48.png",
"128": "assets/images/icon128.png"
},
"content_scripts": [
{
"matches": ["https://*.taobao.com/*"],
"css": ["css/content.css"],
"js": ["js/content.js"],
"run_at": "document_idle"
}
],
"web_accessible_resources": ["js/inject.js"]
}
解析:
browser_action
中的default_popup
配置为 和manifest.json
文件一级的popup.html
browser_action
中的default_icon
配置为assets/images/icon48.png
background
配置为js/background.js
icons
文件进行 项目的配置content_scripts
配置对应的js、css、和 matches
web_accessible_resources
配置网页内置js/inject.js
2、配置 vue.config.js
文件
通过上面的 manifest.json
文件可以看出,我们需要配置 js
文件夹,css
文件夹,popup.html
文件,background.js
文件,inject.js
文件,content.js
文件,content.css
文件;
1. 添加 copy-webpack-plugin
模块,用于复制文件
我们需要把 plugins
文件夹下的文件复制到打包之后的 dist
文件中
安装:
yarn add copy-webpack-plugin@6.0.2 --dev
2. 文件内容
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"
}
}
}
3. 解析:
copyFiles
是复制文件的字段pages
是配置多页面的文件字段configureWebpack
来配置content.js
、background.js
文件css
配置content.css
文件
3、popup
文件夹修改
从上面的配置我们知道了,popup
文件夹是用来生成 browser_action
的 popup.html
文件的,所以此时我们来写入 popup
文件夹
1. popup/index.html
popup
文件夹下的 index.html
文件,因为这个是 html
文件,我们就只需要按照 vue create
生成的项目中的 public
文件夹下的 index.html
文件内容拷贝过来即可,顺便把 favicon
删除,把 title
修改下
<!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>
2. popup/main.js
这个是 vue
项目的入口配置文件,就按照 src
下面的 main.js
复制过来即可,别忘了改下 大小写
import { createApp } from 'vue'
import app from './components/app.vue'
createApp(app).mount('#app')
3. popup/components/app.vue
此文件就是正常的 vue
文件,按照平时写 vue
项目开发即可
<template>
<div class="popup_page">
this is popup page
<div class="popup_page_main">
this is popup page main
</div>
</div>
</template>
<script>
export default {
}
</script>
<style></style>
4、content
文件夹修改
content
文件夹下是对应 chrome
插件的 content.js
,这个可以在嵌入页面里面渲染页面,我们也可以用 vue
开发
1. content/components/app.vue
正常的 vue
开发
<template>
<div class="content_page">
content_page
<div class="content_page_main">
content_page_main
</div>
</div>
</template>
<script>
export default {
}
</script>
<style>
</style>
2. content/main.js
main.js
这个文件是比较重要的,是通过这个文件引入 vue
组件以及使用 vue
开发 content
页面的,所以这个页面,需要在插件嵌入的页面,增加一个 dom
元素,并把这个插件的 content
页面,渲染进去。
import { createApp } from 'vue'
import app from './components/app.vue'
joinContent(app)
function joinContent (element) {
const div = document.createElement('div')
div.id = 'joinContentApp'
document.body.appendChild(div)
console.log(div)
createApp(element).mount('#joinContentApp')
}
解析:
- 引入
vue3
的createApp
- 引入
app
组件 - 创建一个
id
为joinContentApp
的dom
元素,把此元素插入body
中,并把应用实例挂载到此dom
上
5、background
文件夹
此文件夹是对应的 background.js
文件,可以只写一个简单的日志打印即可
console.log('this is background main.js')
6、yarn run build
打包
此时,先进行 run build
打包,如果你报错了,是 eslint
报错,可以进行在 .eslintrc.js
文件中进行配置,添加一些我常用的 eslint
配置
module.exports = {
root: true,
env: {
node: true
},
extends: [
'plugin:vue/vue3-essential',
'@vue/standard'
],
parserOptions: {
parser: 'babel-eslint'
},
rules: {
"generator-star-spacing": "off",
"object-curly-spacing": "off",
"no-var": "error",
"semi": 0,
"eol-last": "off",
"no-tabs": "off",
"indent": "off",
"quote-props": 0,
"no-mixed-spaces-and-tabs": "off",
"no-trailing-spaces": "off",
"arrow-parens": 0,
"spaced-comment": "off",
"space-before-function-paren": "off",
"no-empty": "off",
"no-else-return": "off",
"no-unused-vars": [2, {"vars": "all", "args": "after-used"}],
"no-console": "off",
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off'
}
}
然后在进行打包
此时的文件内容
.
├── README.md
├── babel.config.js
├── dist
│ ├── assets
│ │ ├── images
│ │ │ ├── icon128.png
│ │ │ ├── icon16.png
│ │ │ └── icon48.png
│ │ └── logo.png
│ ├── js
│ │ ├── background.js
│ │ ├── chunk-vendors.fa86ccee.js
│ │ ├── content.js
│ │ ├── inject.js
│ │ └── popup.js
│ ├── manifest.json
│ └── popup.html
├── package.json
├── src
│ ├── assets
│ │ ├── images
│ │ │ ├── icon128.png
│ │ │ ├── icon16.png
│ │ │ └── icon48.png
│ │ └── logo.png
│ ├── background
│ │ └── main.js
│ ├── content
│ │ ├── components
│ │ │ └── app.vue
│ │ └── main.js
│ ├── main.js
│ ├── plugins
│ │ ├── inject.js
│ │ └── manifest.json
│ ├── popup
│ │ ├── components
│ │ │ └── app.vue
│ │ ├── index.html
│ │ └── main.js
│ └── utils
├── vue.config.js
└── yarn.lock
此时我们可以看到 dist
文件夹下已经按照我们需要的内容进行打包了,但是没有 css
文件夹那是因为我们没有写入 css
7、引入 less
我们写页面少不了使用 css
,现在都是使用预处理器,我比较倾向于 less
,所以我使用 less、less-loader
1. 引入 less less-loader
yarn add less less-loader --dev
2. 修改 app.vue
文件
然后我们在 content/components/app.vue
和 popup/components/app.vue
文件中写入 css
样式
content/components/app.vue
<template>
<div class="content_page">
content_page
<div class="content_page_main">
content_page_main
</div>
</div>
</template>
<script>
export default {
}
</script>
<style lang="less" scoped>
.content_page{
color: red;
position: fixed;
z-index: 100001;
right: 10px;
bottom: 10px;
.content_page_main{
color: green;
}
}
</style>
popup/components/app.vue
<template>
<div class="popup_page">
this is popup page
<div class="popup_page_main">
this is popup page main
</div>
</div>
</template>
<script>
export default {
}
</script>
<style lang="less" scoped>
.popup_page{
color: red;
.popup_page_main{
color: green;
}
}
</style>
3. yarn run build
打包
此时 tree dist
查看 dist
文件夹内容
dist
├── assets
│ ├── images
│ │ ├── icon128.png
│ │ ├── icon16.png
│ │ └── icon48.png
│ └── logo.png
├── css
│ ├── content.css
│ └── popup.css
├── js
│ ├── background.js
│ ├── chunk-vendors.4f73d0d4.js
│ ├── content.js
│ ├── inject.js
│ └── popup.js
├── manifest.json
└── popup.html
我们可以看到,我们通过 vue.config.js
文件配置的内容都已经生成到 dist
文件夹中了
四、导入插件项目
1、在谷歌拓展程序中打开我们的插件
点击 加载已解压的拓展程序,选择我们的 dist
文件夹,此时我们的插件就被引入进来了
2、打开 淘宝首页
1. 为什么打开淘宝首页呢?
因为我们在 manifest.json
中的 content_scripts
中配置 "matches": ["https://*.taobao.com/*"]
2. 点击右上角的插件 icon
我们可以看到我们的 popup
页面已经我们给它写的样式
3、我们的 content
文件呢?
我们在 content
文件中配置的 main.js
和 app.vue
也写入了样式,也挂载到 dom
实例上了,但是为什么没有渲染,也没有打印
function joinContent (element) {
const div = document.createElement('div')
div.id = 'joinContentApp'
document.body.appendChild(div)
console.log(div)
createApp(element).mount('#joinContentApp')
}
我们在 js
文件中有个 console.log
日志输入,但是可以看到淘宝页面的控制台并没有输入
1. 为什么没有输出
因为我们是用的 vue
开发项目,在 main.js
中是用的 vue
开发,所以我们得引入 vue
文件,得在 content
中引入 vue
才可以
2. 解决方法,引入 vue
我们可以看到 dist
文件夹下面有一个 chunk-vendors.4f73d0d4.js
,这个就是 vue
打包之后的文件,我们先在 dist
中的 manifest.json
文件先把它引入进来先看下
dist/manifest.json
文件下的 content_scripts
字段
"content_scripts": [
{
"matches": ["https://*.taobao.com/*"],
"css": ["css/content.css"],
"js": ["js/chunk-vendors.4f73d0d4.js", "js/content.js"],
"run_at": "document_idle"
}
],
此时,拓展程序页面刷新插件,并刷新淘宝首页,可以看到
此时可以看到我们的 content
文件已经输出了。
4、background.js
文件
还记得我们在 background
文件夹下中的 main.js
写入日志输出吗?
console.log('this is background main.js')
我们打开拓展程序,找到我们的插件,点击 背景页 按钮
此时,背景页的控制台就出来,我们可以看到我们的日志输出,好像并没有输出我们的日志???
1. 原因
此问题的原因和上面的 content
文件的原因是一致的,也是没有引入 vue
文件
2. 解决,引入 vue
dist/manifest.json
中 background
字段
"background": {
"scripts": ["js/chunk-vendors.4f73d0d4.js", "js/background.js"]
},
此时,刷新插件,可以看到日志输出
5、引入 inject
文件
1. 首先我们在 plugins/inject.js
文件中输出日志
console.log('this is my inject.js')
2. 然后在 content/main.js
文件中引入 inject.js
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')
}
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)
})
}
3. yarn run build
打包
此时打包可以发现报错了
yarn run v1.22.10
$ vue-cli-service build
⠼ Building for production...
ERROR Failed to compile with 1 error 上午11:12:48
error in ./src/content/main.js
Module Error (from ./node_modules/thread-loader/dist/cjs.js):
/Users/guoqiankun/work/chromePlugin/my-vue3-plugin/src/content/main.js
21:16 error 'chrome' is not defined no-undef
✖ 1 problem (1 error, 0 warnings)
ERROR Build failed with errors.
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
error 'chrome' is not defined no-undef
我们在上面插入 jnject
文件使用的 chrome
未定义,那我们就定义一下;
4. 修改 .eslintrc.js
文件
root: true,
globals: {
chrome: true,
},
env: {
node: true
},
增加一个 globals
字段,里面 chrome: true
然后在进行 yarn run build
打包
然后我们可以看到 dist/manifest.json
文件中的 content_scripts
和 background/scripts
已经没有引入 vue
了,所以我们不能在 dist
文件夹中修改,我们要在 plugins/manifest.json
文件中修改
但是我们可以看到,我们每次打包生成的 chunk-vendors.js
会跟一个 hash
,因为我们此时没有修改别的文件,所以 hash
后缀没有变化,但是如果我们改了内容之后在 yarn run build
呢?此时 hash
就会变化,总不能在改一次 manifest.json
再打一次包吧…
6、修改 vue.config.js
文件,让打包时生成的 chunk-vendors.js
不带 hash
1. 配置 chainWebpack
字段
配置 chainWebpack
字段,对 config
内容进行处理
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()
}
}
}
2. 修改 plugin/manifest.json
文件
在此文件中引入 chunk-vendors.js
plugin/manifest.json
"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_scripts": [
{
"matches": ["https://*.taobao.com/*"],
"css": ["css/content.css"],
"js": ["js/chunk-vendors.js", "js/content.js"],
"run_at": "document_idle"
}
],
3. yarn run build
打包
dist
├── assets
│ ├── images
│ │ ├── icon128.png
│ │ ├── icon16.png
│ │ └── icon48.png
│ └── logo.png
├── css
│ ├── content.css
│ └── popup.css
├── js
│ ├── background.js
│ ├── chunk-vendors.js
│ ├── content.js
│ ├── inject.js
│ └── popup.js
├── manifest.json
└── popup.html
4. 刷新插件,刷新页面
属性插件,刷新页面,之后可以看到
五、热加载
此时我们的 vue
开发插件项目已经基本上可以了,剩下的就是按照需求开发插件页面,按照需求添加 manifest.json
字段即可,但是我们不能每一次想看样式就打个包,然后属性插件,刷新页面看下,这样也可以,但是我们是开发,这样效率比较低,我不服…
所以我们需要添加一下热加载
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;
2、引入
在 bckground/main.js
中 引入
import hotReload from '@/utils/hotReload'
hotReload()
console.log('this is background main.js')
3、修改 package.json
中的 scripts
1. 增加一个 watch
用来监听打包
"scripts": {
"watch": "vue-cli-service build --watch",
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
},
2. 此时运行 yarn run watch
yarn run v1.22.10
$ vue-cli-service build --watch
⠙ Building for development...
DONE Compiled successfully in 3956ms 上午11:39:00
File Size Gzipped
dist/js/chunk-vendors.js 668.79 KiB 122.48 KiB
dist/js/content.js 26.47 KiB 3.71 KiB
dist/js/popup.js 26.23 KiB 3.55 KiB
dist/js/background.js 15.57 KiB 3.30 KiB
dist/js/inject.js 0.04 KiB 0.05 KiB
dist/css/content.css 0.18 KiB 0.14 KiB
dist/css/popup.css 0.11 KiB 0.09 KiB
Images and other types of assets omitted.
DONE Build complete. Watching for changes...
可以看到一直在监听改变
3. 然后我们刷新插件和页面
发现有一个报错
Uncaught EvalError: Refused to evaluate a string as JavaScript because 'unsafe-eval' is not an allowed source of script in the following Content Security Policy directive: "script-src 'self' blob: filesystem:".
4. 按照错误解决问题:
在 plugins/manifest.json
中添加:
"content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'",
5. 重新运行 yarn run bild
6. 清除插件错误、刷新插件、淘宝页面
4、修改 content/components/app.vue
文件
<template>
<div class="content_page">
content_page
<div class="content_page_main">
content_page_main
</div>
<div class="content_page_footer">
content_page_footer
</div>
</div>
</template>
保存
然后发现插件自动刷新、浏览器页面自动刷新。
此时浏览器页面右下角我们新加的内容就展示在上面了。
六、总结
- 使用
vue
开发插件,要先想一下我们要做成什么样 - 针对性的修改对应的文件,按照我们的需求去配置
- 遇到问题先想一下哪一步的问题,为什么出现,可以自己先想想,最后在寻求帮助
- 完结🎉🎉🎉
七、源码地址
github地址:https://github.com/18055975947/my-vue3-plugin
码云地址:https://gitee.com/guoqiankun/my-vue3-plugin
参考资料
更多推荐
所有评论(0)