Создание интерактивного расширения Chrome с помощью React

Расширения Chrome - забытые средние дети Интернета. Изо дня в день они выполняют огромный объем закулисной работы, блокируя рекламу, выявляя сделки, настраивая стили и многое другое. Тем не менее, большинство из них не обеспечивает пользователям прямого взаимодействия и взаимодействия, предлагаемого их более популярными веб- и мобильными братьями и сестрами. В результате многие расширения, как и студенты Милфордской академии, остаются незамеченными и не слышными.

Одна из причин, по которой расширения Chrome так задерживаются в разработке, заключается в том, что они часто полностью создаются с использованием необработанного JavaScript и HTML / CSS. Хотя этот минималистский подход может помочь уменьшить перегрузку браузера, это часто происходит за счет привлекательного взаимодействия с пользователем. В этой статье будет рассмотрено использование расширения для рендеринга более интерактивного приложения React в собственном модальном окне. После успешного развертывания нашего приложения мы доработаем его, чтобы получать заголовки, относящиеся к сайту, на котором мы находимся.

Идея

Наше расширение будет использовать приложение Create React в качестве шаблона. Предполагая, что вы установили Node.js версии 6 или выше, вы сможете сгенерировать необходимые файлы с помощью следующих команд терминала:

npx create-react-app headline-fetcher
cd headline-fetcher

Файлы в этом каталоге будут вам знакомы, если вы раньше использовали приложение Create React, но если вы этого не сделали, не беспокойтесь - мы будем использовать только некоторые из них. Большая часть нашей работы будет находиться в папке public, которая будет служить точкой входа для нашего расширения Chrome. В этой папке мы создадим два новых файла, background.js и content.js, которые обеспечат необходимый интерфейс для взаимодействия с пользователем.

Разделение задач между этими двумя файлами важно, потому что они служат двум разным целям в Chrome. Фоновый сценарий в background.js взаимодействует с API браузера, обеспечивая функции, которые будут сохраняться во всех частях Интернета. Напротив, сценарий содержимого в content.js выполняется всякий раз, когда загружается новая страница, обеспечивая доступ к элементам HTML на определенной странице. Эти различия станут более очевидными, когда мы рассмотрим код для каждого файла ниже.

Фоновый скрипт

Создайте файл background.js в папке public и добавьте следующий код:

chrome.contextMenus.create({ 
  id: "HeadlineFetcher",
  title: "Get Headlines",
  contexts: ["all"]
});

Контекстные меню в расширениях Chrome позволяют нам добавить параметр в контекстное меню браузера. После развертывания пользователи смогут щелкнуть правой кнопкой мыши в любом месте и увидеть такую ​​опцию «Получить заголовки»:

На данный момент наша кнопка ничего не делает, поэтому нам нужно добавить некоторые функции.

chrome.contextMenus.onClicked.addListener(() => {
    chrome.tabs.query({active: true, currentWindow: true}, tabs => {
        chrome.tabs.sendMessage(tabs[0].id, {type: "getHeadlines"});
    });
});

Это добавляет функцию обратного вызова, которая будет вызываться всякий раз, когда пользователь нажимает на опцию «Получить заголовки». Действие, выполняемое в рамках функции, идентифицирует вкладку, на которой в данный момент находится пользователь, и отправляет на эту вкладку сообщение с указанием получить заголовки.

Скрипт содержимого

Теперь, когда у нас есть некоторые функции в background.js, мы можем перейти к созданию нашего content.js файла в той же папке. Как показано выше, расширение отправит сообщение из фонового сценария в сценарий содержимого. Тогда было бы разумно добавить своего рода слушателя для этих сообщений:

chrome.runtime.onMessage.addListener(request => {
  if (request.type === "getHeadlines") {
      // DO SOMETHING
  }
});

В случае с нашим приложением после запроса «getHeadlines» мы хотим открыть содержимое нашего приложения React. Итак, как мы это сделаем? Через диалоговые теги! Внедряя HTML <dialog>, мы можем гарантировать, что наше приложение отображается как отдельное модальное окно, что делает его более отличным от содержимого страницы и более трехмерным для пользователя.

const modal = document.createElement("dialog");
modal.setAttribute("style", "height:40%");
modal.innerHTML =
       `<iframe id="headlineFetcher" style="height:100%"></iframe>
        <div style="position:absolute; top:0px; left:5px;">  
            <button>x</button>
        </div>`;
document.body.appendChild(modal);
const dialog = document.querySelector("dialog");
dialog.showModal();

Теперь у нас есть элемент диалога с внутренним iframe, дающий нам возможность получить доступ к ресурсам URL через наше расширение. Стиль этого элемента будет меняться в зависимости от того, на каком сайте мы находимся, поэтому не привязывайтесь слишком к каким-либо конкретным спецификациям CSS.

Теперь перейдем к ключевому элементу соединительной ткани: вставим наше приложение React в iframe. Поскольку index.html является точкой входа для нашего приложения React, эти строки будут гарантировать, что приложение будет обслуживаться при каждом открытии модального окна:

const iframe = document.getElementById("headlineFetcher");  
iframe.src = chrome.extension.getURL("index.html");
iframe.frameBorder = 0;

Наш внедренный HTML также включает кнопку, которая позволит нам закрыть диалоговое окно, когда это необходимо. Хотя кнопка в настоящее время ничего не делает, мы можем добавить функциональность с помощью простого прослушивателя щелчков.

dialog.querySelector("button").addEventListener("click", () => {
    dialog.close();
 });

И теперь мы закончили как фоновые, так и контентные скрипты!

Манифест

Последний файл, который нам нужно будет изменить перед развертыванием нашего расширения, - это манифест. Подобно package.json проекта Node, manifest.json - это файл, в котором описаны все необходимые метаданные о расширении Chrome. Наша public папка уже содержит manifest.json, поэтому нам не нужно создавать новую. Хотя этот файл был инициализирован для другой цели в приложении Create React, мы можем воспользоваться этим семантическим удобством при разработке нашего расширения.

Удалите существующий код в manifest.json и добавьте следующее:

{
  "name": "Headline Fetcher",
  "manifest_version": 2,
  "version": "0.0.1",
  "description": "Retrieves headlines relevant to the website you are currently on",
  "content_scripts": [{
    "js": [ "content.js"],
    "matches": [ "<all_urls>"]
   }],
  "background": {
    "scripts": ["background.js"]
   },
   "permissions": ["contextMenus", "tabs"],
   "web_accessible_resources" : ["*.html"]
}

Это дает базовое описание нашего приложения и определяет content.js и background.js в качестве сценариев содержимого и фона, соответственно. Он также запрашивает разрешение для частей API Chrome, с которыми мы будем взаимодействовать (контекстные меню и вкладки), и разрешает загрузку ресурсов HTML, что позволит нам внедрить index.html нашего приложения React.

Развертывание

Чтобы подготовить расширение к развертыванию, мы можем использовать собственный сценарий сборки приложения Create React. Выполнение следующей команды гарантирует, что наше приложение настроено правильно:

npm run build

Это создает папку build для нашего расширения, которую мы сможем использовать для развертывания. Чтобы загрузить его в Chrome, зайдите в chrome: // extensions в своем браузере, переключите переключатель Режим разработчика в правом верхнем углу, затем нажмите Загрузить распакованный. Если выбрать build в каталоге приложения, необходимо загрузить расширение сборщика заголовков.

Вот и все - вы официально сделали расширение Chrome! Теперь у вас должна быть возможность протестировать его на большинстве веб-сайтов, щелкнув правой кнопкой мыши и выбрав «Получить заголовки». Если модальное окно не отображается, попробуйте обновить страницу; это обеспечит загрузку сценария содержимого вместе с другими элементами страницы.

Разработка нашего приложения

Теперь, когда мы успешно внедрили наше собственное приложение React на веб-сайт, мы можем дополнить его, чтобы получать заголовки, относящиеся к сайту, на котором мы находимся. Чтобы настроить сборщик заголовков, выполните следующие действия:

  1. Получите API-ключ из News API
  2. Установите axios с помощью команды npm i axios и импортируйте его в App.js
  3. Добавьте/* global chrome */ в первую строку App.js (это позволит нашему линтеру узнать, что мы обращаемся к методам браузера Chrome)

Теперь добавьте в App.js конструктор с начальными состояниями для домена и соответствующими заголовками:

/* global chrome */
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
import axios from 'axios';
class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
      domain: '',
      headlines: []
    }
  }

Первое, что нужно сделать нашему приложению, - это определить домен URL-адреса, на котором в данный момент находится пользователь. Поскольку это должно происходить всякий раз, когда открывается наше приложение React, мы можем поместить это в componentDidMount() метод.

componentDidMount() {
    chrome.tabs.query({active: true, currentWindow: true}, tabs => {
      const url = new URL(tabs[0].url);
      const domain = url.hostname;
      this.setState({ domain });
      this.getHeadlines(domain);
    });
  }

Как только у нас будет доступ к домену, наш getHeadlines метод будет извлекать соответствующие ресурсы из API новостей и использовать первые пять результатов в качестве заголовков.

getHeadlines(query) {
    axios.get('https://newsapi.org/v2/everything',{
      params: {
        q: query,
        language: 'en',
        apiKey: {{ YOUR KEY HERE }}
      }
    }).then(results => {
        this.setState({
          headlines: results.data.articles.slice(0,5)
        });
    }).catch(error => {
        console.log('Error in obtaining headlines', error);
    });
  }

Наконец, нам нужно обновить наш render() метод, чтобы отображать эти результаты.

render() {
    return (
      <div className="App">
        <h1 className="App-title">{this.state.domain}</h1>
        Top Headlines:
        {this.state.headlines.map(headline => (
          <h4 className="link" onClick={() => {
            window.open(headline.url)}}>{headline.title}</h4>))}
      </div>
    );
  }

Вам может быть интересно, почему мы используем здесь функцию onClick вместо того, чтобы просто заключать заголовки в теги<a>. Причина в том, что мы хотим, чтобы наши ссылки открывались в окне браузера, а не внутри iframe (что и делают обычные теги <a>). Хотя это означает, что мы не получим стили HTML-ссылок по умолчанию, мы можем добавить в наш App.css файл следующий косметический пластырь:

.link { 
  color: blue; 
  text-decoration: underline; 
  cursor: pointer; 
}

И мы официально создали бета-версию нашего приложения! Запустите npm run build, чтобы настроить наши новые обновления и обновить нашу версию сборщика заголовков на chrome: // extensions. Затем попробуйте посетить разные веб-сайты и щелкните правой кнопкой мыши, чтобы увидеть, как они освещаются в новостях!

Чтобы сослаться на завершенный код для нашего расширения, вы можете посетить это репо: https://github.com/ctrobins/headline-fetcher.

Заключение

Расширения Chrome предоставляют разработчикам множество способов изменить опыт просмотра, включая сценарии контента, которые могут быть нацелены на определенные элементы веб-сайта. Теоретически это создает уникальные преимущества как перед веб-платформами, так и перед мобильными платформами, поскольку позволяет расширениям повсеместно работать через Интернет. Несмотря на этот потенциал, многие популярные расширения предпочитают завершать свою работу на необработанном JavaScript, избегая взаимодействия с любыми интерфейсными библиотеками или фреймворками. Хотя такой скрытый подход может сделать расширения легкими и ненавязчивыми, он также с большей вероятностью сделает их легко забываемыми.

Внедрение приложения React в его собственное модальное окно может предложить новый уровень интерактивности для пользователей Chrome. Поскольку приложение содержится в собственном iframe, оно может получать доступ к внешним ресурсам, а также использовать определенные функции API браузера Chrome. Хотя в этой статье рассматривается только один пример приложения - простой сборщик заголовков - возможности использования этой технологии поистине безграничны. Опытные разработчики React должны продолжить изучение других альтернатив, чтобы лучше понять, как Chrome и React могут работать в тандеме.

📝 Прочтите этот рассказ позже в Журнале.

🗞 Просыпайтесь каждое воскресное утро и слышите самые интересные истории, мнения и новости недели, ожидающие в вашем почтовом ящике: Получите примечательный информационный бюллетень›