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

时事   2024-10-31 11:50   浙江  

本文译者为 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 的最佳部分是什么? 控制和学习。

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

推荐阅读  点击标题可跳转

1、揭秘海报生成技术

2、不止WebSocket可以实现长连接,它也可以

3、10分钟速成:轻松搭建前端monorepo架构与CI/CD自动化!

前端大全
点击获取精选前端开发资源。「前端大全」日常分享 Web 前端相关的技术文章、实用案例、工具资源、精选课程、热点资讯。
 最新文章