放弃plasmo,webpack5搭建了一个chrome基础插件

由于业务需要,最近在做chrome插件,于是寻找构建插件社区,找到了plasmo,应该是社区中比较优秀的构建chrome插件的脚手架,但最终还是没有用,原生插件非常简单,具体可以参考写个自己的chrome插件,为满足当下业务需要,于是基于webpack5搭建一个构建插件的基本脚手架,本文是一篇搭建chrome插件的实践总结,希望看完在项目中有所帮助。

插件基本能力

  • 使用webpack5搭建,支持react18.x
  • 支持tsxjsx构建函数组件
  • 支持tailwindcss
  • 支持预览tabs页面预览

前置

在使用webpack5搭建插件,可以参考笔者在之前写的webpack5系列笔记中有部分搭建案例,chrome插件本质上是运行在chrome浏览器的网页,因此本质上也是用网页来展现的,只是一个插件的必须满足根目录必须mainifest.json,我们具体以下面一张图来重新回顾下插件的基本要素

插件核心

  • base chrome plugin
  • 关键文件

manifest.json

在这个文件中,构建一个插件manifest.json是必不可少的,其中注意一点manifest_version3版本,因为好多新特性2版本并不支持,同时当我们需要操作chromeapi时,在permissions开放操作权限

json 复制代码
{
    "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主要是backgroundpopupcontentset的几个文件,这是打包的入口文件,在webpack入口文件,会根据entry找到自己依赖的模块,从而进行加载

js 复制代码
// 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指定绝对路径访问打包后的资源

js 复制代码
 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插件生成popupset页面
  • 使用copy-webpack-plugin插件将引入的资源复制到指定输出的文件夹中
js 复制代码
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(),
    ],
    }
}
  • 支持jsxtsx

支持jsxtsx主要是利用babel-loader,在rules这个配置中,同时我们也对.tsx,.ts单独使用ts-loader去加载

js 复制代码
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中我们也需要设置

json 复制代码
{
    "compilerOptions": {
        ...
      "target": "ES5",
      "jsx": "react-jsx",
       "paths": {
          "@public/*": ["public/*"],
          "@comp/*": ["src/components/*"],
          "@utils/*": ["src/utils/*"],
          "@src/*": ["src/*"],
          "@assets": ["src/assets/*"]
        },
        "baseUrl": "."
    },
  
}

其中你看到有设置通用路径别名,不过,除了这里设置,我们同时也需要设置resolve.alains

js 复制代码
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,不过注意在postcss.config.js中设置
js 复制代码
module.exports = {
    plugins: {
      "postcss-import": {},
      "tailwindcss/nesting": {},
      tailwindcss: {},
      autoprefixer: {},
    },
  };
  

其实我们已经完成了一个插件,当我们执行pnpm run build:local时,就会打包成一个插件的最终输出文件了

我们打开chrome浏览器,扩展程序>打开开发者模式>加载已解压的扩展程序这个文件夹即可

安装插件后,访问任何一个网站,我们在content写入的内容就生效了

不知道你有没有好奇,当我们使用插件时,我们必须修改代码,然后执行打包,然后重新加载插件看效果,这显然与我们实际开发有些繁琐,因此,你可以执行将content变成一个多页页面,这样可以就可以高效的开发了,不过你需要稍修改下manifest.jsonexclude_matches,主要为了插件不在当前访问本地开发页面生效

json 复制代码
   "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,支持tsxjsx构建组件
  • 如何让项目支持tailwindcss
  • 预览当前构建的页面比如set页面,需要在exclude_matches字段中排出当前指定的端口域名
  • 本文示例code example
相关推荐
长风清留扬8 分钟前
小程序毕业设计-音乐播放器+源码(可播放)下载即用
javascript·小程序·毕业设计·课程设计·毕设·音乐播放器
m0_7482478022 分钟前
Flutter Intl包使用指南:实现国际化和本地化
前端·javascript·flutter
ZJ_.1 小时前
WPSJS:让 WPS 办公与 JavaScript 完美联动
开发语言·前端·javascript·vscode·ecmascript·wps
joan_851 小时前
layui表格templet图片渲染--模板字符串和字符串拼接
前端·javascript·layui
还是大剑师兰特2 小时前
什么是尾调用,使用尾调用有什么好处?
javascript·大剑师·尾调用
Watermelo6172 小时前
详解js柯里化原理及用法,探究柯里化在Redux Selector 的场景模拟、构建复杂的数据流管道、优化深度嵌套函数中的精妙应用
开发语言·前端·javascript·算法·数据挖掘·数据分析·ecmascript
一个处女座的程序猿O(∩_∩)O4 小时前
小型 Vue 项目,该不该用 Pinia 、Vuex呢?
前端·javascript·vue.js
燃先生._.10 小时前
Day-03 Vue(生命周期、生命周期钩子八个函数、工程化开发和脚手架、组件化开发、根组件、局部注册和全局注册的步骤)
前端·javascript·vue.js
高山我梦口香糖11 小时前
[react]searchParams转普通对象
开发语言·前端·javascript