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

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

简介

我们都喜欢 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.ts、background.ts 和 contentScript.ts 将被编译为 popup.js、background.js 和 contentScript.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.ts、background.ts 和 contentScript.ts 的入口点。

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

  • Loaders: 使用 ts-loader 处理 TypeScript,并使用 css-loader 和 postcss-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-loader 和 css-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 的最佳部分是什么? 控制和学习。

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

本文译者为 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

本文内容仅供个人学习、研究或参考使用,不构成任何形式的决策建议、专业指导或法律依据。未经授权,禁止任何单位或个人以商业售卖、虚假宣传、侵权传播等非学习研究目的使用本文内容。如需分享或转载,请保留原文来源信息,不得篡改、删减内容或侵犯相关权益。感谢您的理解与支持!

链接: https://shenqiku.cn/article/FLY_12718