放棄 plasmo-webpack5 搭建基礎插件
由於業務需要,最近在做chrome
插件,於是尋找構建插件社區,找到了plasmo
, 應該是社區中比較優秀的構建chrome
插件的腳手架,但最終還是沒有選擇用,原生插件非常簡單,具體可以參考【寫個自己的 chrome 插件】, 爲滿足當下業務需要,於是基於webpack5
搭建一個構建插件的基礎腳手架,本文是一篇搭建chrome
插件的實踐總結,希望看完在項目中有所幫助。
插件基本能力
-
使用
webpack5
搭建,支持react18.x
-
支持
tsx
、jsx
構建函數組件 -
支持
tailwindcss
-
支持預覽
tabs
頁面預覽
前置
在使用webpack5
搭建插件,可以參考筆者在之前寫的【webpack5】系列筆記中有部分搭建案例,chrome插件
本質上是運行在 chrome 瀏覽器的網頁,因此本質上也是用網頁來展現的,只是一個插件的必須滿足根目錄必須有mainifest.json
,我們具體以下面一張圖來重新回顧下插件的基本要素
插件核心
-
base chrome plugin
-
關鍵文件
manifest.json
在這個文件中,構建一個插件manifest.json
是必不可少的,其中注意一點manifest_version
是3
版本,因爲好多新特性2
版本並不支持,同時當我們需要操作chrome
的api
時,在permissions
開放操作權限
{
"name": "base chrome plugin chrome", //插件的名稱
"description": "a simple chrome plugin", // 插件描述
"version": "1.0.0", // 當前插件版本
"manifest_version": 3, // chrome插件必填版本3
"action": {
"default_icon": "assets/imgs/icon.png", // 插件icon
"default_title": "demo chrome plugin",
"default_popup": "popup.html" // popup頁面
},
"background": {
"service_worker": "./src/background/index.js",
"type": "module"
},
"icons":
{
"16": "assets/imgs/icon.png"
},
"content_scripts": [
{
"matches": [
"<all_urls>" // 插件在任何頁面可用
],
"exclude_matches": [ // 排除插件在部分頁面中不可用
],
"js": [
"src/content/index.js" // 匹配的頁面中加載content.js
],
"css": [
"assets/css/index.css" //匹配的頁面加載css
],
"run_at": "document_end",
"all_frames": false
}
],
"host_permissions": [
],
"permissions": [
"tabs", // chrome api操作的權限
"contextMenus",
"notifications",
"webRequest",
"activeTab"
],
"omnibox": { "keyword" : "demo" },
"commands": {
"_execute_action": {
"suggested_key": {
"default": "Ctrl+Shift+Y",
"mac": "Command+Shift+Y"
},
"description": "Opens tabs.html"
}
}
}
webpack.config.js
這是插件的基礎配置文件, 主要講解幾個關鍵的配置
entry
我們看到entry
主要是background
、popup
、content
、set
的幾個文件,這是打包的入口文件,在webpack
入口文件,會根據entry
找到自己依賴的模塊,從而進行加載
// webpack.confg.js
const path = require("path");
const resolvePath = (dir) => {
return path.resolve(__dirname, dir);
};
module.exports = (env) => {
return {
entry: {
background: resolvePath("src/background/index"),
popup: resolvePath("src/pages/popup/index"),
content: resolvePath("src/pages/content/index"),
set: resolvePath("src/pages/tabs/set/index"),
}
}
}
output
output
會根據entry
的文件,輸出到指定path
的文件夾中,其中publicPatch
指定絕對路徑訪問打包後的資源
module.exports = (env) => {
const PluginFileAssetsName = `dist/${env.mode}/${fileName}`;
return {
...
output: {
publicPath: "/",
path: path.join(__dirname, PluginFileAssetsName),
filename: "src/[name]/index.js",
},
}
}
plugins
在這個配置中,主要做了一下幾件事情
-
註冊
env
變量,讓.env.xx
中的變量在其他文件中能被訪問 -
使用
html-webpack-plugin
插件生成popup
、set
頁面 -
使用
copy-webpack-plugin
插件將引入的資源複製到指定輸出的文件夾中
module.exports = (env) => {
return {
...,
plugins: [
new Dotenv({
path: path.resolve(process.cwd(), `.env.${env.mode}`),
}), // 讀取本地.env本地
new Html({
filename: "popup.html",
template: "./public/index.html",
chunks: ["popup"], // 打包後只會包含popup與content,避免將其他js引入
hash: false,
minify: {
removeComments: true,
collapseWhitespace: true,
minifyCSS: true,
},
title: "test popup",
}),
new Html({
filename: "set.html",
template: "./public/index.html",
chunks: ["set"],
hash: false,
title: "set",
minify: {
removeComments: true,
collapseWhitespace: true,
minifyCSS: true,
},
}),
new MiniCssExtractPlugin({
filename: "css/[name].css",
chunkFilename: "css/[name].css",
}),
new copyWebpackPlugin({
patterns: [
{
from: path.join(__dirname, "manifest.json"),
to: path.join(__dirname, `${PluginFileAssetsName}/`),
},
{
from: path.join(__dirname, "src/assets/imgs"),
to: path.join(__dirname, `${PluginFileAssetsName}/assets/imgs`),
},
{
from: path.join(__dirname, "src/assets/css"),
to: path.join(__dirname, `${PluginFileAssetsName}/assets/css`),
},
],
}),
new CleanWebpackPlugin(),
],
}
}
- 支持
jsx
與tsx
支持jsx
與tsx
主要是利用babel-loader
, 在rules
這個配置中, 同時我們也對.tsx,.ts
單獨使用ts-loader
去加載
module.exports = {
rules: [
{
test: /\.(js|jsx|tsx)$/i,
exclude: /node_modules/,
use: [
{
loader: "babel-loader",
options: {
presets: ["@babel/preset-env", "@babel/preset-react"],
},
},
],
},
{
test: /\.(tsx|ts)$/i,
use: [
{
loader: "ts-loader",
},
],
},
]
}
同時,在tsconfig.json
中我們也需要設置
{
"compilerOptions": {
...
"target": "ES5",
"jsx": "react-jsx",
"paths": {
"@public/*": ["public/*"],
"@comp/*": ["src/components/*"],
"@utils/*": ["src/utils/*"],
"@src/*": ["src/*"],
"@assets": ["src/assets/*"]
},
"baseUrl": "."
},
}
其中你看到有設置通用路徑別名,不過,除了整理設置,我們同時也需要設置resolve.alains
module.exports = {
...
resolve: {
extensions: [".tsx", ".ts", ".js", ".jsx"],
alias: {
"@public": resolvePath("public/"),
"@utils": resolvePath("src/utils/"),
"@src": resolvePath("src/"),
"@comp": resolvePath("src/components/"),
"@assets": resolvePath("src/assets/"),
}
}
}
這樣你就可以在tsx,jsx
文件中構建我們的組件了。
- 支持 tailwindcss 參考官網 tailwindcss[1], 不過注意在
postcss.config.js
中設置
module.exports = {
plugins: {
"postcss-import": {},
"tailwindcss/nesting": {},
tailwindcss: {},
autoprefixer: {},
},
};
其實我們已經完成了一個插件,當我們執行pnpm run build:local
時,就會打包成一個插件的最終輸出文件了
我們打開chrome瀏覽器
,擴展程序
>打開開發者模式
>加載已解壓的擴展程序
這個文件夾即可
安裝插件後,訪問任何一個網站,我們在content
寫入的內容就生效了
不知道你有沒有好奇,當我們使用插件時,我們必須修改代碼,然後執行打包,然後重新加載插件看效果,這顯然與我們實際開發有些繁瑣,因此,你可以執行將content
變成一個多頁頁面,這樣可以就可以高效的開發了, 不過你需要稍修改下manifest.json
的exclude_matches
, 主要爲了插件不在當前訪問本地開發頁面生效
"content_scripts": [
{
"matches": [
"<all_urls>"
],
"exclude_matches": [
"http://localhost:8080/*"
],
"js": [
"src/content/index.js"
],
"css": [
"assets/css/index.css"
],
"run_at": "document_end",
"all_frames": false
}
],
至於tab
頁面,我們也是直接訪問/set.html
就可以了
因此一個插件的基礎就基本完成了。
總結
-
理解一個插件的基本要素,關鍵是
manifest.json
這個文件必不可少 -
webpack5
支持構建react18.x
, 支持tsx
、jsx
構建組件 -
如何讓項目支持
tailwindcss
-
預覽當前構建的頁面比如
set
頁面,需要在exclude_matches
字段中排出當前指定的端口域名 -
本文示例 code example[2]
[1]tailwindcss: https://tailwindcss.com/docs/installation
[2]code example: https://github.com/maicFir/base-chrome-plugin
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/gEJ8wL7Q0xqS66ZPXtxz3Q