Полный исходный код: https://github.com/stevelukis/website-carbon

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

Как это работает?

Website Carbon API предоставляет надежную методологию и мощные инструменты для точного расчета выбросов углерода веб-сайтами. Для расчета энергии и выбросов веб-страницы сайтcarbon.com учитывает следующие данные:

  1. Передача данных по сети. Энергопотребление веб-сайта тесно связано с объемом передаваемых данных. Website Carbon API измеряет данные, передаваемые при загрузке веб-страницы, и умножает их на имеющиеся у них данные об энергопотреблении. Корректировки вносятся для повторных посетителей, ресурсы веб-сайта которых могут кэшироваться на их устройствах.
  2. Энергоемкость веб-данных. Энергия потребляется в центре обработки данных, в телекоммуникационных сетях и на устройствах конечных пользователей. API использует среднее значение для оценки энергоемкости, связанной с веб-данными.
  3. Источник энергии, используемый центром обработки данных. API предполагает, что все веб-сайты используют стандартное сетевое электричество для телекоммуникационных сетей и конечных пользователей. Однако в отношении использования энергии в центре обработки данных они проверяют базу данных The Green Web Foundation (TGWF), чтобы определить, использует ли центр обработки данных зеленую энергию. Если это так, выбросы углерода, связанные с этой частью энергии, уменьшаются. Важно отметить, что база данных включает в себя центры обработки данных, которые покупают стандартную сетевую электроэнергию, но компенсируют свои выбросы, которые учитываются в расчетах одинаково.
  4. Углеродоемкость электроэнергии. Углеродоемкость электроэнергии из сети основана на среднем международном уровне для электроэнергии из сети. Когда в центре обработки данных используется возобновляемая электроэнергия, применяется более низкий углеродный фактор.
  5. Трафик веб-сайта. Объединив всю вышеупомянутую информацию, можно получить оценку выбросов, связанных с посещением веб-сайта средним пользователем. Умножая выбросы углерода на просмотр страницы на типичное количество просмотров страниц в год, можно получить оценку общих годовых выбросов CO2.

Общедоступная версия инструмента, предоставляемого Website Carbon API, тестирует только один URL-адрес, введенный в форму, что дает приблизительное представление об эффективности веб-сайта. Однако вы можете протестировать столько отдельных URL-адресов, сколько захотите. Важно отметить, что таблицы ранжирования, созданные API, включают только те веб-сайты, которые соответствуют определенным правилам. Эти руководящие принципы гарантируют, что веб-сайты могут быть доступны для общественности, не требуют входа в систему, разрешают поисковые системы, содержат уникальный контент, предназначенный для посетителей, и не содержат незаконных или явных материалов.

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

Код

Для целей этой статьи предположим, что вы уже настроили новый проект React, используя Create React App или любой другой предпочтительный метод.

Чтобы установить необходимые зависимости для нашего проекта, мы будем использовать pnpm в качестве менеджера пакетов. Во-первых, убедитесь, что на вашем компьютере глобально установлен pnpm. Затем откройте терминал вашего проекта и выполните следующую команду, чтобы установить axios и @tanstack/react-query:

pnpm install axios @tanstack/react-query

Эта команда извлечет последние версии axios и @tanstack/react-query из реестра пакетов и добавит их в зависимости вашего проекта. Использование pnpm обеспечивает эффективное управление зависимостями, так как использует общее хранилище пакетов и оптимизирует использование дискового пространства.

Чтобы стилизовать и улучшить пользовательский интерфейс нашего приложения, мы установим несколько зависимостей из библиотеки Material-UI вместе с Emotion, популярной библиотекой CSS-in-JS. Откройте терминал вашего проекта и выполните следующую команду, чтобы установить эти пакеты с помощью pnpm:

pnpm install @mui/icons-material @mui/material @emotion/styled @emotion/react

В исходном каталоге вашего проекта создайте новый файл с именем entities.ts. Этот файл будет содержать определение интерфейса Site, представляющего структуру данных веб-сайта. Добавьте следующий код в файл entities.ts:

export interface Site {
  url: string;
  cleanerThan: number;
  co2: number;
}

В каталоге src/api вашего проекта создайте новый файл с именем index.ts. Этот файл будет служить модулем API, отвечающим за взаимодействие с Carbon API веб-сайта. Добавьте следующий код в файл index.ts:

import axios from "axios";
import { Site } from "../entities";

const client = axios.create({
  baseURL: "https://api.websitecarbon.com",
});

interface SiteResponse {
  url: string;
  c: number;
  p: number;
}

export const retrieveSite = async (url: string): Promise<Site> => {
  const response = await client.get<SiteResponse>("/b?url=" + url);
  return {
    url: response.data.url,
    cleanerThan: response.data.p,
    co2: response.data.c,
  };
};

В этом коде мы импортируем библиотеку axios и интерфейс Site из ранее созданного файла entities.ts. Затем мы создаем экземпляр клиента Axios с базовым URL-адресом, установленным на "https://api.websitecarbon.com". Этот клиент будет использоваться для выполнения запросов к Carbon API веб-сайта.

Для взаимодействия с Website Carbon API и получения данных веб-сайта мы будем использовать конечную точку /b вместо конечной точки /site. Это связано с тем, что конечная точка /site не разрешает запросы из всех источников, что приводит к ошибкам CORS (совместное использование ресурсов между источниками). Хотя конечная точка /b изначально предназначена для простого значка и может не предоставлять полных данных, нам придется работать с ней для наших целей.

Затем мы определяем интерфейс SiteResponse для представления формы ответа, который мы получаем от API. У него есть свойства для url, c и p, которые соответствуют URL-адресу, выбросам углерода (co2) и рейтингу чистоты (cleanerThan) соответственно.

Функция retrieveSite — это асинхронная функция, которая принимает параметр url и возвращает обещание, которое разрешается в объект Site. Внутри функции мы делаем GET запрос к Carbon API веб-сайта с помощью клиента Axios. Мы добавляем параметр url к URL-адресу запроса, чтобы указать веб-сайт, для которого мы хотим получить данные. Затем данные ответа сопоставляются с интерфейсом Site, и возвращается результирующий объект Site.

В каталоге вашего проекта src/hooks создайте новый файл с именем useSite.ts. Этот файл будет содержать настраиваемый хук, который использует React Query для получения данных веб-сайта из Website Carbon API. Добавьте следующий код в файл useSite.ts:

import { useQuery } from "@tanstack/react-query";
import { retrieveSite } from "../api";
import { Site } from "../entities";

const useSite = (url: string) =>
  useQuery<Site>({
    queryKey: ["site", url],
    queryFn: () => retrieveSite(url),
    enabled: false,
  });

export default useSite;

В этом коде мы импортируем хук useQuery из файла @tanstack/react-query, функцию retrieveSite из файла api/index.ts и интерфейс Site из файла entities.ts.

Хук useSite принимает параметр url и использует useQuery из React Query для обработки выборки данных. Он возвращает результат запроса типа Site.

Внутри хука мы настраиваем useQuery со следующими параметрами:

  • queryKey — это массив, определяющий уникальный идентификатор запроса. В этом случае мы используем ["site", url], чтобы убедиться, что у каждого URL-адреса веб-сайта есть свой отдельный запрос.
  • queryFn — это функция, запускающая фактический поиск данных. Здесь мы вызываем функцию retrieveSite с предоставленным url для получения данных веб-сайта.
  • enabled изначально имеет значение false, что означает, что запрос отключен по умолчанию. Мы включим его позже, когда захотим инициировать выборку данных.

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

Чтобы включить пользовательские шрифты в наше приложение, нам нужно добавить HTML-код в файл index.html. Откройте файл public/index.html в своем проекте и добавьте следующие строки в раздел <head>:

<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Caprasimo&display=swap" rel="stylesheet">

В каталоге src вашего проекта создайте новый файл с именем theme.ts. Этот файл будет определять пользовательскую тему для нашего приложения с помощью функции createTheme Material-UI. Добавьте следующий код в файл theme.ts:

import { createTheme } from "@mui/material";

declare module "@mui/material/styles" {
  interface CustomPalette {
    homeBackground: {
      primary: string;
    };
    paperBackground: {
      primary: string;
    };
    highlight: {
      success: string;
      fail: string;
    };
  }

  interface Palette extends CustomPalette {}

  interface PaletteOptions extends CustomPalette {}
}

const primaryColor = {
  main: "#50692d",
};

const secondaryColor = {
  main: "#9ba66b",
};

const theme = createTheme({
  palette: {
    primary: primaryColor,
    secondary: secondaryColor,
    homeBackground: {
      primary: "#f8f1d5",
    },
    paperBackground: {
      primary: "#fffbea",
    },
    highlight: {
      success: "#e6ffc1",
      fail: "#ffa1a1",
    },
    text: {
      primary: primaryColor.main,
      secondary: secondaryColor.main,
    },
  },
  typography: {
    h1: {
      fontFamily: "Caprasimo, cursive",
    },
  },
});

export default theme;

В этом коде мы импортируем createTheme из @mui/material для создания нашей пользовательской темы. Мы также используем расширение модуля, чтобы расширить интерфейсы Palette и PaletteOptions, предоставляемые Material-UI, чтобы включить наши собственные цвета палитры.

Объект theme создается с использованием createTheme и состоит из объекта palette, определяющего первичный и вторичный цвета, а также пользовательские цвета для homeBackground, paperBackground и highlight. Мы также настраиваем типографику, устанавливая элемент h1 для использования шрифта «Caprasimo» и указав семейство шрифтов.

В каталоге src откройте файл main.tsx. Этот файл служит точкой входа для нашего приложения и содержит необходимые настройки для отображения нашего приложения в браузере. Добавьте следующий код в файл main.tsx:

import React from "react";
import ReactDOM from "react-dom/client";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import App from "./components/App";
import { worker } from "./mocks/browser";
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
import { CssBaseline, ThemeProvider } from "@mui/material";
import theme from "./theme";

const queryClient = new QueryClient();

ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
  <React.StrictMode>
    <ThemeProvider theme={theme}>
      <QueryClientProvider client={queryClient}>
        <CssBaseline />
        <App />
        <ReactQueryDevtools />
      </QueryClientProvider>
    </ThemeProvider>
  </React.StrictMode>
);

Мы создаем экземпляр QueryClient для управления состоянием и кэшированием наших запросов.

Функция ReactDOM.createRoot используется для рендеринга нашего приложения в браузере. Мы объединяем наше приложение с различными поставщиками, в том числе React.StrictMode для расширенных проверок режима разработки, ThemeProvider для предоставления пользовательской темы нашему приложению, QueryClientProvider для обеспечения доступности клиента запросов во всем приложении и CssBaseline для применения базового набора стилей CSS для последовательного рендеринга.

Внутри функции ReactDOM.createRoot мы визуализируем компонент App, а также включаем компонент ReactQueryDevtools для легкой отладки React Query.

Загрузите фоновое изображение https://github.com/stevelukis/website-carbon/tree/main/src/assets. Поставил образ как в src/assets/background.svg.

Теперь создайте новый каталог с именем components в исходном каталоге нашего проекта. Этот каталог будет служить контейнером для наших многократно используемых компонентов пользовательского интерфейса.

Внутри каталога components вашего проекта создайте новый файл с именем PaperBox.ts. Этот файл будет содержать пользовательский компонент с именем PaperBox, который будет отображать стилизованную коробку, похожую на бумагу. Добавьте следующий код в файл PaperBox.ts:

import { Box, styled } from "@mui/material";

const PaperBox = styled(Box)(({ theme }) => ({
  maxWidth: "500px",
  padding: "20px",
  backgroundColor: theme.palette.paperBackground.primary,
  borderRadius: "20px",
}));

export default PaperBox;

Компонент PaperBox создается путем применения функции styled к компоненту Box. Мы используем функцию стрелки, которая принимает объект theme в качестве аргумента для доступа к свойствам темы. Внутри функции мы определяем стили для компонента PaperBox, такие как maxWidth, padding, backgroundColor и borderRadius.

В каталоге components создайте новый файл с именем Error.tsx. Этот файл будет содержать пользовательский компонент с именем Error, который отображает сообщение об ошибке при сбое при получении результата. Добавьте следующий код в файл Error.tsx:

import PaperBox from "./PaperBox";
import { Stack, Typography } from "@mui/material";

const Error = () => {
  return (
    <PaperBox textAlign="center">
      <Stack gap={3}>
        <Typography variant="h5" fontWeight="bold">
          Error
        </Typography>
        <Typography>Cannot retrieve result.</Typography>
      </Stack>
    </PaperBox>
  );
};

export default Error;

Создайте новый файл с именем Result.tsx. Этот файл будет содержать пользовательский компонент под названием Result, который отображает результаты выбросов углерода для веб-сайта. Добавьте следующий код в файл Result.tsx:

import { Box, Stack, styled, Typography } from "@mui/material";
import { Site } from "../entities";
import PaperBox from "./PaperBox";

interface Props {
  site: Site;
}

const TextHighlight = styled(Typography)({
  padding: 6,
  borderRadius: "10px",
  display: "inline",
});

const TextHighlightSuccess = styled(TextHighlight)(({ theme }) => ({
  backgroundColor: theme.palette.highlight.success,
}));

const TextHighlightFail = styled(TextHighlight)(({ theme }) => ({
  backgroundColor: theme.palette.highlight.fail,
}));

const Result = ({ site }: Props) => {
  return (
    <PaperBox>
      <Stack gap={3}>
        <Box>
          <Typography variant="h5" textAlign="center" fontWeight="bold">
            Carbon Result
          </Typography>
          <Typography textAlign="center">
            {site.url.length > 30
              ? site.url.substring(0, 30) + "..."
              : site.url}
          </Typography>
        </Box>
        <Typography>
          This website is{" "}
          {site.cleanerThan >= 50 ? (
            <TextHighlightSuccess>
              cleaner than {site.cleanerThan}%
            </TextHighlightSuccess>
          ) : (
            <TextHighlightFail>
              dirtier than {100 - site.cleanerThan}%
            </TextHighlightFail>
          )}{" "}
          of web pages tested.
        </Typography>
        <Typography>
          {site.co2 < 0.5 ? (
            <TextHighlightSuccess>
              {site.co2.toFixed(2)}g of CO2
            </TextHighlightSuccess>
          ) : (
            <TextHighlightFail>{site.co2.toFixed(2)}g of CO2</TextHighlightFail>
          )}{" "}
          is produced every time someone visits this web page.
        </Typography>
      </Stack>
    </PaperBox>
  );
};

export default Result;

Компонент Result — это функциональный компонент, который принимает свойство site типа Site. Внутри компонента мы визуализируем компонент PaperBox, который заключает в себе содержимое результата. Мы используем компонент Stack для вертикального размещения различных секций.

Мы отображаем URL-адрес веб-сайта в результате, следя за тем, чтобы он не превышал 30 символов, усекая его при необходимости. Информация о рейтинге чистоты и выбросах углерода отображается с использованием Typography компонентов с условным рендерингом фоновых бликов на основе значений.

Переместите App.tsx в каталог компонентов и замените его этим кодом:

import { useState } from "react";
import {
  Box,
  CircularProgress,
  Container,
  InputAdornment,
  Stack,
  TextField,
  Typography,
  Zoom,
} from "@mui/material";
import SearchIcon from "@mui/icons-material/Search";
import BackgroundImage from "../assets/background.svg";
import Result from "./Result";
import useSite from "../hooks/useSite";
import Error from "./Error";

function App() {
  const [url, setUrl] = useState("");
  const { data, refetch, fetchStatus, isError } = useSite(url);
  const isLoading = fetchStatus === "fetching";

  return (
    <Box
      minHeight="100vh"
      width="100%"
      sx={{
        bgcolor: "homeBackground.primary",
        backgroundImage: `url(${BackgroundImage})`,
        backgroundRepeat: "no-repeat",
        backgroundPositionX: "center",
        backgroundPositionY: "bottom",
        backgroundSize: { xs: "100% auto", md: "cover" },
      }}
    >
      <Container sx={{ minHeight: "100vh" }} maxWidth="lg">
        <Stack
          height={{ xs: "auto", lg: "100vh" }}
          direction={{ xs: "column", lg: "row" }}
        >
          <Stack
            maxWidth="600px"
            height="100%"
            pt={{ xs: 10, md: 0 }}
            pr={{ xs: 0, lg: 5 }}
            direction="column"
            justifyContent={{ xs: "start", md: "center" }}
            gap={4}
          >
            <Typography variant="h1" fontSize={{ xs: 36, md: 52 }}>
              How is your website impacting the planet?
            </Typography>
            <form
              onSubmit={(e) => {
                e.preventDefault();
                refetch();
              }}
            >
              <TextField
                variant="standard"
                fullWidth={true}
                label="URL"
                value={url}
                InputProps={{
                  endAdornment: (
                    <InputAdornment position="end">
                      {isLoading ? (
                        <CircularProgress size={20} />
                      ) : (
                        <SearchIcon />
                      )}
                    </InputAdornment>
                  ),
                }}
                onChange={(e) => {
                  setUrl(e.target.value);
                }}
              />
            </form>
            <Typography maxWidth="400px">
              This app will do a test in real time to calculate the carbon emissions generated per page view.
              <br />
              <br />
              This result is cached and will only test the same URL once every 24 hours.
            </Typography>
          </Stack>
          <Stack
            width="100%"
            justifyContent="center"
            pt={{ xs: 5, lg: 0 }}
            pl={{ xs: 0, lg: 5 }}
          >
            <Zoom in={data !== undefined && !isLoading}>
              <Box>{data && <Result site={data} />}</Box>
            </Zoom>
            <Zoom in={isError}>
              <Box>
                <Error />
              </Box>
            </Zoom>
          </Stack>
        </Stack>
      </Container>
    </Box>
  );
}

export default App;

Компонент App — это функциональный компонент, представляющий основную структуру нашего приложения. Он использует хуки состояния для управления вводом URL и извлекает данные веб-сайта с помощью пользовательского хука useSite. Состояние загрузки определяется на основе fetchStatus, возвращаемого хуком.

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

pnpm run dev

Откройте URL-адрес, написанный в консоли, вы должны увидеть что-то похожее на это:

Введите google.com в поле ввода и нажмите Enter. Вы должны увидеть результат:

Вы можете посмотреть предварительный просмотр здесь: https://website-carbon.stevelukis.dev/

Полный исходный код на GitHub: https://github.com/stevelukis/website-carbon

В заключение, создание приложения для расчета выбросов углерода на веб-сайте с использованием React, Vite, TypeScript, пользовательского интерфейса материалов, Axios и React Query обеспечивает эффективный способ повышения осведомленности о воздействии веб-сайтов на окружающую среду и поощрения экологически безопасных методов в индустрии веб-дизайна. Используя возможности API веб-сайта carbon.com, мы смогли рассчитать выбросы углерода в реальном времени на просмотр страницы, предоставив ценную информацию как владельцам веб-сайтов, так и посетителям. Спасибо за прочтение!

Дополнительные материалы на PlainEnglish.io.

Подпишитесь на нашу бесплатную еженедельную рассылку новостей. Подпишитесь на нас в Twitter, LinkedIn, YouTube и Discord.