2023年chrome/edge浏览器插件环境快速搭建

475 阅读2分钟

背景:

之前在网上找了好多chrome浏览器插件搭建环境的博客,但是发现都很糟糕,我的目标是实现ts + react + webpack进行插件开发,但是看的一些教程却非常的繁琐,需要一步一步创建文件、初始化等待,或者就是直接用react脚手架创建项目再进行修改,对于新手也不是很友好,所以给出我目前使用的几个方法

探索:

首先,我在github上面搜索了一些chrome插件项目,查看了他们的源码,然后做了一些尝试下的修改。

其次,由于国内无法直接访问chrome插件商店,所以我从国内的一个chrome插件网站极简插件上面下载下来crx插件包,然后在 crxviewer.com/ 这个网站上面解压源码进行查看。学习到了插件的大致构成。

环境搭建

image.png

在vscode中下载chrome extension相关插件,然后command+P弹出命令面板,输入“chroem”

image.png

然后按步骤进行操作,进入项目文件后,运行npm install & npm run build,然后在build目录就会生成源码,就可以直接在chrome浏览器加载插件了。

image.png

支持ts和react

添加tsconfig.json:

{
  "compilerOptions": {
    "experimentalDecorators": true,
    "target": "ESNext",
    "useDefineForClassFields": true,
    "lib": ["DOM", "DOM.Iterable", "ESNext"],
    "allowJs": false,
    "skipLibCheck": true,
    "esModuleInterop": false,
    "allowSyntheticDefaultImports": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "module": "ESNext",
    "moduleResolution": "Node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "react",
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"]
    }
  },
  "include": ["./src/**/*.ts", "./src/**/*.tsx"]
}

初始ts相关的库:

npm install typescript
npm install babel-loader @babel/core @babel/preset-env @babel/preset-react @babel/preset-typescript @babel/plugin-proposal-decorators --save-dev

修改config/webpack.common.js:

...

module: {
    rules: [
        {
            test: /\.(ts|tsx)$/,
            use: [
              {
                loader: "babel-loader",
                options: {
                  presets: [
                    [
                      "@babel/preset-env",
                      {
                        corejs: 3,
                        useBuiltIns: "usage",
                      },
                    ],
                    "@babel/preset-react",
                    "@babel/preset-typescript",
                  ],
                  plugins: [
                    ["@babel/plugin-proposal-decorators", { legacy: true }],
                  ].filter(Boolean),
                },
              },
            ],
        },
    ]
}
...

config/webpack.config.js加入自己需要的ts文件:

...
const config = merge(common, {
  entry: {
    options: PATHS.src + "/modules/options/index.tsx",
    popup: PATHS.src + "/modules/popup/index.tsx",
    background: PATHS.src + "/modules/background/index.ts",
    content: PATHS.src + "/modules/content/index.ts",
  },
...

在public加入对应的html文件,在文件中引入js,以popup.html为例:

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>New Tab</title>
  <link rel="stylesheet" href="popup.css" />
</head>

<body>
  <div class="container">
    <p id="day" class="day"></p>
    <div id="clock" class="clock"></div>

    <hr class="divider" />

    <p class="title">Chrome Extension is Ready!</p>
    <p class="subtitle">Start by updating <code>index.html</code></p>
  </div>
  <div id="root"></div>
  <script src="popup.js"></script>

</body>

</html>

后就是编写src/modules/popup/index.tsx:

import ReactDOM from "react-dom";
import React from "react";
import "./popup.css";

function HelloWord(props: { desc?: string }) {
  return <h1>hello word! {props.desc}</h1>;
}

function main() {
  const root = document.querySelector("#root");
  if (root) {
    ReactDOM.render(<HelloWord />, root);
  }
}

main();

这样,所有的功能就都具备了,如果不需要react的话,可以将@babel/preset-react去掉将tsx文件路径改为ts

优化

构建时去掉一些不需要的文件,并复制到dist目录下。

添加config/pack.js:

const fs = require("fs");
const path = require("path");

const build = path.join(__dirname, "../build");
const target = path.join(build, "manifest.json");
const pack = path.join(__dirname, "../dist");

const manifest = require(target);
delete manifest["$schema"];

fs.writeFileSync(target, JSON.stringify(manifest, null, "\t"), "utf-8");

if (fs.existsSync(pack)) {
  fs.rmSync(pack, { recursive: true, force: true });
}
fs.mkdirSync(pack);

function replace(target) {
  if (!/\.(js|css)$/.test(target)) {
    return;
  }
  let content = fs.readFileSync(target, "utf-8");
  if (target.endsWith("js")) {
    content = content
      .replace(/^\/\*![^\*]*\*\//g, "")
      .replace(/\/\/#.*$/g, "")
      .replace(/\n{2,}/g, "\n");
  } else if (target.endsWith(".css")) {
    content = content.replace(/\/\*#.*\*\/$/g, "").replace(/\n{2,}/g, "\n");
  }
  fs.writeFileSync(target, content, "utf-8");
}

function copy(_build, _pack) {
  const files = fs.readdirSync(_build);
  files.forEach((filename) => {
    if (filename.endsWith(".map") || filename.endsWith(".js.LICENSE.txt")) {
      return;
    }
    const p = path.join(_build, filename);
    const stat = fs.statSync(p);
    if (stat.isFile()) {
      const t = path.join(_pack, filename);
      fs.copyFileSync(p, t);
      replace(t);
    } else if (stat.isDirectory()) {
      const s = path.join(_pack, filename);
      fs.mkdirSync(s);
      copy(p, s);
    }
  });
}

copy(build, pack);

修改package.json

...
  "scripts": {
    "watch": "node ./node_modules/webpack/bin/webpack.js --mode=development --watch --config config/webpack.config.js",
    "build": "node ./node_modules/webpack/bin/webpack.js --mode=production --config config/webpack.config.js && node config/pack.js"
  },
...

这部分的代码可以根据个人需求进行调整,这样,项目的基本架构就完成了。