简化 Chrome 扩展程序开发:无需 CRA 即可添加 React

本文译者为 360 奇舞团前端开发工程师

原文标题:Simplify Chrome Extension Development: Add React without CRA  

原文作者:Kristian Ivanov  

原文地址:

https://dev.to/k_ivanow/simplify-chrome-extension-development-add-react-without-cra-5h4c

简介

我们都喜欢 React,对吧(除了 Primeagen)。它通过提供一种简单的方法来处理状态,以及访问一个巨大的 npm 模块和组件库,极大地简化了复杂 UI 的构建。它甚至提供了 Create React App (CRA),只需一个命令就可以快速引导并完成初始设置。

我是 Create React App 的粉丝。它非常适合大多数 web 应用程序,在看到许多从 CRA 弹出的应用程序或使用 gulp 构建/打包的应用后(有时我会觉得自己老了),即使不使用 TypeScript,我也逐渐欣赏它的简单性,以及它在我启动任何新项目时所带来的便利。

然而,在构建像 Chrome 扩展这样的项目时,CRA并不适用,生成的代码过于臃肿。此外,在扩展中,UI 可能只是项目的一部分,大部分逻辑被封装在后台/服务工作线程或内容脚本中。这意味着将大量冗余代码与 React 的使用方式绑定并不总是最佳选择。

另外,如果你开始时只是简单地在 UI 侧写了一些 HTML 和 Tailwind 代码来测试你的想法,然后为扩展的其他部分编写了一堆代码,然后决定让 UI 更易于维护和扩展,并将 React 添加到其中,这该怎么办?

在本指南中,我们将使用 TypeScript、Webpack 和 Tailwind 构建一个 Chrome 扩展,然后以更轻量的方式引入 React,而不使用 CRA。

设置

我最近想开始一个非常简单的 Chrome 扩展,探索一些潜在的 Mutation Observer 用例来定制页面。同时,我也想借此机会尝试 TypeScript,因为我已经有一段时间没有使用它了。

1 设置项目

首先,我们创建一个新的项目目录并用 npm 初始化它:

mkdir ts-chrome-extension
cd ts-chrome-extension
npm init -y

2 添加 TypeScript

安装 TypeScript:

npm install --save-dev typescript

安装完成后,我们需要创建一个 TypeScript 配置文件。该文件将告诉 TypeScript 编译器如何工作。创建 tsconfig.json 文件:

{
  "compilerOptions": {
    "target": "es5",
    "lib": ["dom", "es2015"],
    "module": "esnext",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true
  },
  "include": ["src/**/*"]
}

有关 tsconfig.json 文件的更多细节,请查看官方文档。

3 添加基本文件

创建文件夹和基本文件:

mkdir -p src/scripts && mkdir src/popup && touch src/manifest.json src/scripts/{background.ts,contentScript.ts} src/popup/{popup.html,popup.ts}

在此时,我们的文件夹结构应如下所示:

├── package.json
├── package-lock.json
├── popup.ts
├── src
│   ├── manifest.json
│   ├── popup
│   │   ├── popup.html
│   │   └── popup.ts
│   └── scripts
│       ├── background.ts
│       └── contentScript.ts
└── tsconfig.json

4 清单文件

清单文件是 Chrome 扩展的基础。它告诉 Chrome 扩展的功能以及在哪里找到其脚本。我们创建了清单文件,现在填充它:

{
  "manifest_version": 3,
  "name": "TypeScript Chrome Extension",
  "version": "1.0",
  "description": "A Chrome extension built using TypeScript.",
  "action": {
    "default_popup": "popup.html"
  },
  "background": {
    "service_worker": "background.js"
  },
  "content_scripts": [
    {
      "matches": ["<all_urls>"],
      "js": ["contentScript.js"]
    }
  ],
  "permissions": ["storage"]
}
说明:
  • Action and Popup"action" 键告诉 Chrome 弹出 HTML 的位置。用户点击扩展图标时,弹出窗口将加载。

  • Service Worker"service_worker""background" 键中,用于后台任务。

  • Content Scripts:这些脚本允许扩展与网页进行交互。

  • Permissions"storage" 权限使扩展能够通过 Chrome 的存储 API 存储用户数据。

有关 Chrome 扩展的 manifest.json 的完整文档,在这里找到。

5 弹出窗口文件

由于我们还未使用 React,弹出窗口将简单地处理基本的 DOM 交互。

5.1 popup.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Popup</title>
  </head>
  <body>
    <div>
      <h1>Hello, Chrome Extension!</h1>
      <button id="alertButton">Click me</button>
    </div>
    <script src="popup.js"></script>
  </body>
</html>

这是一个简单的 HTML 页面,包含一个按钮和对 popup.js 的脚本引用(将从 popup.ts 打包而来)。

5.2 popup.ts

该文件将处理弹出窗口的基本交互:

document.addEventListener('DOMContentLoaded', () => {
  const alertButton = document.getElementById('alertButton');

  if (alertButton) {
    alertButton.addEventListener('click', () => {
      alert('Button clicked!');
    });
  }
});

这个简单的脚本在 DOM 加载后等待,然后为按钮添加点击监听器,以触发警报。在我们添加 React 之前,此结构为弹出功能奠定了基础。

6 编译 TypeScript

接下来,我们将 TypeScript 文件编译为 JavaScript。运行以下命令:

npx tsc

这将会把 TypeScript 文件转换为 JavaScript。popup.tsbackground.tscontentScript.ts 将被编译为 popup.jsbackground.jscontentScript.js。但由于我们很快会添加 Webpack 来管理打包,因此这只是一个临时步骤。

7 添加 Webpack 进行打包

现在我们有了基本结构,我们需要 Webpack 来处理将所有脚本打包成 Chrome 可加载的优化文件。它可以处理图标的复制、将 ts 转换为 js、自动注入脚本或样式文件等等。

7.1 安装 Webpack 和必要的加载器
npm install --save-dev webpack webpack-cli ts-loader html-webpack-plugin mini-css-extract-plugin css-loader postcss postcss-loader copy-webpack-plugin
  • webpack: 核心打包工具。

  • ts-loader: 为 Webpack 转换 TypeScript。

  • html-webpack-plugin: 帮助生成带有注入脚本标签的 HTML 文件。

  • CSS and PostCSS Loaders: 用于处理 CSS(我们稍后将与 Tailwind 一起使用)。

  • CopyWebpackPlugin: 确保将 manifest.json 和任何其他资产(如图标)从 src 文件夹复制到 dist 文件夹。

7.2 Webpack 配置

接下来,我们需要配置 Webpack 来打包所有内容。创建 webpack.config.js 在项目根目录下:

const path = require('path');

const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');

module.exports = (env, argv) => {
  const isProduction = argv.mode === 'production';

  return {
    entry: {
      popup: './src/popup/popup.ts',
      background: './src/scripts/background.ts',
      contentScript: './src/scripts/contentScript.ts',
    },
    output: {
      path: path.resolve(__dirname, 'dist'),
      filename: '[name].js',
    },
    resolve: {
      extensions: ['.ts', '.js'],
    },
    module: {
      rules: [
        {
          test: /\.ts$/,
          use: 'ts-loader',
          exclude: /node_modules/,
        },
        {
          test: /\.css$/i,
          use: [MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader'],
        },
      ],
    },
    plugins: [
      new MiniCssExtractPlugin({
        filename: '[name].css',
      }),
      new HtmlWebpackPlugin({
        filename: 'popup.html',
        template: 'src/popup/popup.html',
        chunks: ['popup'],
      }),
      new CopyWebpackPlugin({
        patterns: [
          { from: 'src/manifest.json', to: 'manifest.json' },
          // { from: 'src/icons', to: 'icons' },  // 复制任何其他资产
        ],
      }),
    ],
    mode: isProduction ? 'production' : 'development',
    devtool: isProduction ? false : 'inline-source-map',  // 在生产中禁用源映射
  };
};
说明:
  • Entry Points: 我们指定了 popup.tsbackground.tscontentScript.ts 的入口点。

  • Output: 打包文件将输出到 dist 文件夹。

  • Loaders: 使用 ts-loader 处理 TypeScript,并使用 css-loaderpostcss-loader 处理 CSS(稍后我们将引入 Tailwind CSS)。

  • HtmlWebpackPlugin: 此插件自动将打包后的 popup.js 文件注入到 popup.html 文件中,确保我们的脚本正确链接。

7.3 使用 Webpack 打包扩展

让我们通过运行以下命令来打包所有内容:

npx webpack --mode development

这将把打包后的文件输出到 dist 目录中。

如果你在图标部分遇到问题,可以选择暂时删除它或放置一些占位的 .png 文件。

测试弹出窗口

点击按钮会弹出警报,可能会弹出两次。为什么会这样?

Webpack 可以注入脚本标签。检查一下你在 dist 文件夹中的 popup.js,你会发现 popup.js 被注入了两次(一次来自 HTML 文件本身,一次来自 Webpack)。

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Popup</title>
    <script defer src="popup.js"></script>
  </head>
  <body>
    <div>
      <h1>Hello, Chrome Extension!</h1>
      <button id="alertButton">Click me</button>
    </div>
    <script src="popup.js"></script>
  </body>
</html>

要解决这个问题,只需删除 src/popup 文件夹中的 <script> 标签,确保它看起来像这样:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Popup</title>
  </head>
  <body>
    <div>
      <h1>Hello, Chrome Extension!</h1>
      <button id="alertButton">Click me</button>
    </div>
  </body>
</html>

不用担心,Webpack 仍会在内部添加它。

8 添加 Tailwind 进行样式设计

Tailwind CSS 是一个以实用优先的 CSS 框架,让你可以在不为每个组件编写自定义 CSS 的情况下为用户界面进行样式设计。

多年来,人们对它越来越喜欢。虽然自己添加一些基本样式会更简单,但当你知道会在这个项目上花费时间时,Tailwind 使得后期扩展更容易,并能保持一致性,以便其他人加入时使用(相同的类名总是意味着相同的样式,而如果是自己实现的,font-size-small 可能会有多种含义)。

8.1 安装 Tailwind
npm install tailwindcss postcss postcss-loader autoprefixer
8.2 配置 Tailwind 和 PostCSS

接下来,我们需要初始化 Tailwind 和配置 PostCSS。通过以下命令初始化 Tailwind:

npx tailwindcss init

这将在项目根目录创建一个 tailwind.config.js 文件。打开该文件并修改,以包含 Tailwind 应该应用样式的路径(在我们的案例中,位于 src 文件夹内):

module.exports = {
  content: ['./src/**/*.{ts,tsx,html}'],
  theme: {
    extend: {},
  },
  plugins: [],
};

content 键告诉 Tailwind 在你的文件中查找类。

接下来,在根目录中创建 postcss.config.js 文件,并配置它以使用 Tailwind 和 Autoprefixer:

module.exports = {
  plugins: {
    tailwindcss: {},
    autoprefixer: {},
  },
};
8.3 创建 Tailwind CSS 入口文件

src/styles 中创建一个名为 tailwind.css 的新文件。我们将在这里导入基本的 Tailwind 样式:

@tailwind base;
@tailwind components;
@tailwind utilities;

此文件将包括所有的 Tailwind 实用类。

8.4 修改 Webpack 配置以处理 CSS

我们需要确保 Webpack 知道如何使用 PostCSS 处理 CSS 文件。我们之前已经安装了 postcss-loadercss-loader,现在只需确保它们正确配置。

将以下配置更改为:

{
  test: /\.css$/i,
  use: [MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader'],
}

这样可以确保 Webpack 在我们在弹出窗口中包含 Tailwind CSS 时进行处理。接下来,我们将继续实现这个功能。

8.5 在弹出窗口中导入 Tailwind

要开始使用 Tailwind,我们需要在 popup.ts 文件顶部添加以下导入语句:

import '../styles/tailwind.css';
8.6 添加简单的 Tailwind 样式

更新 popup.html 文件,使用一些 Tailwind 类进行基本样式设置。这样,我们的 popup.html 应该看起来像这样:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Popup</title>
  </head>
  <body class="bg-gray-100 p-4 w-80">
    <div class="flex flex-col items-center">
      <h1 class="text-xl font-bold mb-4">Hello, Chrome Extension!</h1>
      <button id="alertButton" class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">
        Click me
      </button>
    </div>
    <script src="popup.js"></script>
  </body>
</html>

这为弹出窗口添加了背景色、居中内容和样式化的按钮。运行 npx webpack --mode development 重新构建它,看看效果。

9 为弹出窗口添加 React

现在我们有了一个功能性的扩展,包含 Tailwind 样式和 TypeScript 的语法优点。为了应对更复杂的 UI,比如加载和卸载数据、处理选项卡等,我们将添加 React。

9.1 安装 React 和 ReactDOM
npm install react react-dom @types/react @types/react-dom
9.2 更新 Webpack 以支持 React

我们需要更新 Webpack,以便它可以处理 .tsx 文件(使用 TypeScript 的 React)。记住,这正是我们设定的主要目标。 为此,请修改 webpack.config.js 文件,将 React 添加为入口点:

entry: {
    popup: './src/popup/popup.tsx',  // 更新为指向新的 React 组件
    ...
  },
  resolve: {
    extensions: ['.ts', '.tsx', '.js'],  // 添加 .tsx 以解析 React 文件
  },
  module: {
    rules: [
      {
        test: /\.tsx?$/,  // 同时处理 .ts 和 .tsx 文件
        ...
9.3 将 popup.ts 转换为 React 组件

现在我们将把 popup.ts 转换为一个 React 组件。将 popup.ts 重命名为 popup.tsx,并更新为一个基本的 React 组件:

import React from 'react';
import { createRoot } from 'react-dom/client';
import '../styles/tailwind.css';

const Popup = () => {
  const handleClick = () => {
    alert('Button clicked!');
  };

  return (
    <div className="flex flex-col items-center bg-gray-100 p-4">
      <h1 className="text-xl font-bold mb-4">Hello, React Chrome Extension!</h1>
      <button
        className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
        onClick={handleClick}
      >
        Click me
      </button>
    </div>
  );
};

const container = document.getElementById('root');
const root = createRoot(container as HTMLDivElement);
root.render(<Popup />);

这个简单的 React 组件实现了原始 popup.ts 的功能,但使用了 React 的 onClick 事件来处理按钮点击。

9.4 更新 popup.html

我们不需要对 popup.html 进行太多更改,只需确保它有合适的 div 来挂载我们的 React 组件。以下是更新后的 popup.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Popup</title>
  </head>
  <body class="bg-gray-100 w-80">
    <div id="root"></div>
  </body>
</html>
9.5 更新 tsconfig.json

我们需要在 tsconfig.json 中添加 "jsx": "react""moduleResolution": "node"。文件应如下所示:

{
  "compilerOptions": {
    "target": "es5",
    "lib": ["dom", "es2015"],
    "module": "esnext",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "jsx": "react",
    "moduleResolution": "node"
  },
  "include": ["src/**/*"]
}

通过这些步骤,我们为弹出窗口成功添加了 React!

9.6 使用 Webpack 重新构建

完成更改后,运行 Webpack 来重新构建扩展:

npx webpack --mode development

现在,当你在 Chrome 中重新加载扩展并打开弹出窗口时,你应该会看到一个由 React 驱动、使用 Tailwind CSS 样式的用户界面。

结论 为什么不使用 Create-React-App?

Create-React-App 对如何构建应用做出了很多假设,将所有内容打包成一个单页应用并动态注入 JS。这种方式非常适合 React 网络应用,但不太适合 Chrome 扩展, 因为Chrome 扩展使用单独的 HTML 页面来处理弹出窗口、后台脚本,有时甚至还有选项页面。

不使用 Create-React-App 的最佳部分是什么? 控制和学习。

你可以决定包含哪些内容,打包的方式,以及你的扩展是如何精简还是丰富的。 我还认为,尝试在没有库、框架或引导工具的情况下看看有多困难是个好主意。这有助于更深入地了解所有间接使用的、隐藏在你面前的技术。 如果你看到这里,我感谢你,并希望这些内容对你有用!

- END -

如果您关注前端+AI 相关领域可以扫码进群交流

 2d8e95a846445ed19bcd4709e33aa57f.jpeg

添加小编微信进群😊

关于奇舞团

奇舞团是 360 集团最大的大前端团队,非常重视人才培养,有工程师、讲师、翻译官、业务接口人、团队 Leader 等多种发展方向供员工选择,并辅以提供相应的技术力、专业力、通用力、领导力等培训课程。奇舞团以开放和求贤的心态欢迎各种优秀人才关注和加入奇舞团。

d3266d4f7fa8fb4c9099fd5288c3895d.png

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值