赞
踩
之前写过一篇 chrome 浏览器插件开发的文章 全方面手把手从0到1带你开发谷歌浏览器插件 ,但是不是
vue/react
这种第三方框架的,是原生和jquery
混合的,但是那种开发前端方式比较麻烦,所以下面是用vue
来开发插件
github地址:https://github.com/18055975947/my-vue3-plugin
码云地址:https://gitee.com/guoqiankun/my-vue3-plugin
使用 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
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
vue.config.js
文件通过上面的 manifest.json
文件可以看出,我们需要配置 js
文件夹,css
文件夹,popup.html
文件,background.js
文件,inject.js
文件,content.js
文件,content.css
文件;
copy-webpack-plugin
模块,用于复制文件我们需要把 plugins
文件夹下的文件复制到打包之后的 dist
文件中
安装:
yarn add copy-webpack-plugin@6.0.2 --dev
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" } } }
copyFiles
是复制文件的字段pages
是配置多页面的文件字段configureWebpack
来配置 content.js
、background.js
文件css
配置 content.css
文件popup
文件夹修改从上面的配置我们知道了,popup
文件夹是用来生成 browser_action
的 popup.html
文件的,所以此时我们来写入 popup
文件夹
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>
popup/main.js
这个是 vue
项目的入口配置文件,就按照 src
下面的 main.js
复制过来即可,别忘了改下 大小写
import { createApp } from 'vue'
import app from './components/app.vue'
createApp(app).mount('#app')
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>
content
文件夹修改content
文件夹下是对应 chrome
插件的 content.js
,这个可以在嵌入页面里面渲染页面,我们也可以用 vue
开发
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>
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
上background
文件夹此文件夹是对应的 background.js
文件,可以只写一个简单的日志打印即可
console.log('this is background main.js')
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
less
我们写页面少不了使用 css
,现在都是使用预处理器,我比较倾向于 less
,所以我使用 less、less-loader
less less-loader
yarn add less less-loader --dev
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>
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
文件夹中了
点击 加载已解压的拓展程序,选择我们的 dist
文件夹,此时我们的插件就被引入进来了
因为我们在 manifest.json
中的 content_scripts
中配置 "matches": ["https://*.taobao.com/*"]
icon
我们可以看到我们的 popup
页面已经我们给它写的样式
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
日志输入,但是可以看到淘宝页面的控制台并没有输入
因为我们是用的 vue
开发项目,在 main.js
中是用的 vue
开发,所以我们得引入 vue
文件,得在 content
中引入 vue
才可以
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
文件已经输出了。
background.js
文件还记得我们在 background
文件夹下中的 main.js
写入日志输出吗?
console.log('this is background main.js')
我们打开拓展程序,找到我们的插件,点击 背景页 按钮
此时,背景页的控制台就出来,我们可以看到我们的日志输出,好像并没有输出我们的日志???
此问题的原因和上面的 content
文件的原因是一致的,也是没有引入 vue
文件
vue
dist/manifest.json
中 background
字段
"background": {
"scripts": ["js/chunk-vendors.4f73d0d4.js", "js/background.js"]
},
此时,刷新插件,可以看到日志输出
inject
文件plugins/inject.js
文件中输出日志console.log('this is my inject.js')
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) }) }
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
未定义,那我们就定义一下;
.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
再打一次包吧…
vue.config.js
文件,让打包时生成的 chunk-vendors.js
不带 hash
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() } } }
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" } ],
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
属性插件,刷新页面,之后可以看到
此时我们的 vue
开发插件项目已经基本上可以了,剩下的就是按照需求开发插件页面,按照需求添加 manifest.json
字段即可,但是我们不能每一次想看样式就打个包,然后属性插件,刷新页面看下,这样也可以,但是我们是开发,这样效率比较低,我不服…
所以我们需要添加一下热加载
在 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;
在 bckground/main.js
中 引入
import hotReload from '@/utils/hotReload'
hotReload()
console.log('this is background main.js')
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"
},
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...
可以看到一直在监听改变
发现有一个报错
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:".
在 plugins/manifest.json
中添加:
"content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'",
yarn run bild
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
开发插件,要先想一下我们要做成什么样Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。