Как и почему в 2024 году мы разрабатываем сайты для крупных клиентов на WordPress?

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!

Сегодня WordPress — один из лучших бесплатных инструментов на рынке. Как и многие IT-компании, мы начинали с разработки сайтов на коробочных решениях с низким порогом входа. Расширив экспертизу и собрав мощную команду, мы научились профессионально «готовить» WordPress и теперь грамотно используем его, чтобы облегчить жизнь себе и нашим клиентам. Коммерческая разработка присутствует там, где есть хорошие разработчики и мощный менеджмент, а инструменты могут быть любыми.

В этой статье мы расскажем, как строим архитектуру наших приложений на WordPress

Любая CMS дает нам админку и ядро, а из ядра выделяется набор функций, которые мы можем использовать для разработки приложений. WordPress дает нам отличный движок на PHP (новые версии постоянно поддерживаются) с хорошей популярной админкой и неограниченным количеством расширений, а всю клиентскую часть мы пишем самостоятельно.

Мы подходим к этому максимально ответственно: продумываем архитектуру, применяем особые подходы к разработке и устанавливаем определенные правила:

  • Первое — мы используем систему готовых блоков и компонентов. У WordPress есть редактор Gutenberg (рассказывали об этом в одной из статей), и практически все страницы формируются из блоков, как в Tilda. Этот же принцип переносим и в построение архитектуры: формируем готовые блоки, shared-компоненты, глобальные пейдж-темплейты, отдельно бизнес-фичи и т д.

  • Второе — мы не используем JQL, пишем все на чистом JS с использованием объектно-ориентированного подхода к построению логики. Для создания сложных интерфейсов можем на отдельных страницах прикрутить Vue или React (который на WP доступен из коробки). К тому же в WP доступен REST API, и если у нас нет каких-то методов или эндпоинтов, мы можем легко их добавить.

Структура папок и файлов внутри темы WordPress

Файлы в корне темы:

  • style.css - Обязательный файл, содержит информацию о теме

    В этом файле указываются такие данные, как:

    • название темы

    • версия темы

    • минимальная версия WordPress

    • версия PHP

    Подробнее тут

  • theme.json - Настройки темы (настройки типографики, цветовая палитра)

    Видео с демонстрацией настроек

    https://www.loom.com/share/9fc072486f7e4f4ca883e76a3ae0be6e

    Пример файла настроек theme.json

    {
        "version": 1,
        "settings": {
            "typography": {
                "customFontSize": false,
                "lineHeight": true,
                "fontSizes": [
                    {
                        "slug": "h1",
                        "size": "6rem",
                        "name": "H1"
                    },
                    {
                        "slug": "h2",
                        "size": "2.88rem",
                        "name": "H2"
                    },
                    {
                        "slug": "h3",
                        "size": "1.5rem",
                        "name": "H3"
                    },
                    {
                        "slug": "text_46",
                        "size": "2.88rem",
                        "name": "Text 46"
                    },
                    {
                        "slug": "text_24",
                        "size": "1.5rem",
                        "name": "Text 24"
                    },
                    {
                        "slug": "text_16",
                        "size": "1rem",
                        "name": "Text 16"
                    }
                ]
            },
            "spacing": {
                "spacingSizes": [
                    {
                        "size": "clamp(1.5rem, 5vw, 2rem)",
                        "slug": "30",
                        "name": "1"
                    },
                    {
                        "size": "clamp(1.8rem, 1.8rem + ((1vw - 0.48rem) * 2.885), 3rem)",
                        "slug": "40",
                        "name": "2"
                    },
                    {
                        "size": "clamp(2.5rem, 8vw, 6.5rem)",
                        "slug": "50",
                        "name": "3"
                    }
                ],
                "blockGap": true,
                "customPadding": true,
                "customMargin": true,
                "units": [
                    "px",
                    "rem",
                    "%"
                ]
            },
            "color": {
                "palette": [
                    {
                        "slug": "black",
                        "color": "#000000",
                        "name": "Black"
                    },
                    {
                        "slug": "pink",
                        "color": "#F2CFCE",
                        "name": "Pink"
                    },
                    {
                        "slug": "white",
                        "color": "#ffffff",
                        "name": "White"
                    },
                    {
                        "slug": "grey",
                        "color": "#A5A5A5",
                        "name": "Grey"
                    }
                ]
            }
        }
    }
    
  • functions.php - Входной файл для всех скриптов php

    Тут выполняем объявления глобальных констант, подключение load файла из папки includes (где лежат все основные php скрипты), подключения load файла из папки blocks и components

  • header.php - Шаблон шапки сайта

    Тут выполняется функция wp_head(), которая подключает ресурсы и выводит мета-теги

  • footer.php - Шаблон подвала сайта

    Тут выполняется функция wp_footer(), которая подключает js-скрипты темы и плагинов

  • page.php - Дефолтный шаблон страницы

    Как правило, тут выполняется подключение header.php и footer.php c помощью функций get_header() и get_footer() соответственно.

    get_header();
    if ( have_posts() ) {
    while ( have_posts() ) {
    the_post();
    the_content();
    }
    }
    get_footer();
    
  • package.json - Зависимости приложения

  • index.php - Пустой индексный файл

Папки:

  • acf-json - Папка для автосохранения конфигов полей и страниц с опциями, созданных плагином Advanced Custom Fields. Это необходимо если разработка ведется в команде, все изменения полей будут отслеживаться в гите

  • assets - Тут лежат все стили, скрипты, шрифты и прочие файлы, отвечающие за фронтенд

  • blocks - Кастомные gutenberg блоки

  • components - Переиспользуемые компоненты

  • includes - Папка со всеми скриптами php

  • page-templates - Шаблоны страниц

Theme_folder
├── acf-json
│   ├── group_64c25ae72741a.json
│   └── ...
├── assets
**│   ├──** css
**│   ├──** js
****│   │   ├── config
**│   │   ├──** admin.js
**│   │   ├──** editor.js
**│   │   └──** index.js
**│**   └**──** resource
****├── blocks
│   ├── block1
│   │   ├── block.json
│   │   ├── functions.php
│   │   ├── template.php
│   │   ├── _index.scss
│   │   └── _index.js
│   └── load.php
├── components
│   ├── component1
│   │   ├── [component-name].php
│   │   ├── functions.php
│   │   ├── _index.scss
│   │   └── _index.js
│   └── load.php
├── includes
│   ├── [module-name].php
│   └── load.php
├── page-templates
├── footer.php
├── functions.php
├── header.php
├── index.php
├── package.json
├── page.php
├── style.css
└── theme.json

Gutenberg blocks

Видео с демо

https://www.loom.com/share/2c19f771a64f4c69b10484972d5163b5

В этой папке хранятся сами блоки в подпапках и файл load.php, в котором находится функция регистрации всех блоков.

Блок представлен в виде папки со следующими файлами:

  • block.json - Данные для блока, такие как name, title, и тд

  • template.php - Файл с разметкой блока

  • functions.php - PHP функции используемые только в рамках этого блока

  • _index.scss - Стили для блока

  • _index.js - Скрипты для блока

{
  "name" : "mytheme/custom-block",
  "title" : "Custom Block",
  "icon": "admin-site",
  "description": "My awesome custom block.",
  "apiVersion": 3,
  "textdomain": "mytheme",
  "supports": {
    "align" : false,
    "mode"  : false,
    "jsx"   : true,
    "anchor": true
  },
  "acf": {
    "mode": "preview",
    "renderTemplate": "./template.php"
  }
}
add_action( 'init', 'mytheme_block_registration' );
function mytheme_block_registration() {
foreach(glob(THEME_DIR . '/blocks/*', GLOB_ONLYDIR) as $dir){
register_block_type( $dir );
$dir_exploded = explode( '/', $dir );
$block = $dir_exploded[ count( $dir_exploded ) - 1 ];
//Подключение файла functions.php для каждого блока
if ( file_exists( get_template_directory() . '/blocks/' . $block . '/functions.php' ) ) {
include_once get_template_directory() . '/blocks/' . $block . '/functions.php';
}
}
}

Components

Примерно такая же концепция, как с gutenberg-блоками, набор компонентов представлен в виде папок со следующими файлами:

  • [component-name].php

  • functions.php

  • _index.scss

  • _index.js

И файл load.php, который подключает все php скрипты всех компонентов:

foreach(glob(THEME_DIR . '/components/*/functions.php') as $file){
	require_once $file;
}

Используем компоненты с помощью встроенной функции get_template_part() , куда передаем параметром args входные данные:

get_template_part( 'components/checkbox/checkbox', args: [
	'label' => 'Сheckbox label',
	'name'  => 'checkbox-name',
] );

Внутри файла компонента мы обозначаем параметры по умолчанию, затем объединяем их с входными параметрами и распаковываем в переменные, на основе которых и строим разметку:

// Fix PhpStorm inspection on undefined variable.
if ( empty( $args ) ) {
	$args = [];
}
$defaults = [
'label'         => '',
'checked'       => false,
'disabled'      => false,
'custom_class'  => '',
'id'            => '',
'name'          => '',
'value'         => '',
];
// Fill args with defaults to avoid errors.
$args = mytheme_parse_args( $args, $defaults );
// Unpack arguments to variables for better readability.
// Should be in the same order as the keys in $defaults array.
[
'label'         => $label,
'checked'       => $checked,
'disabled'      => $disabled,
'custom_class'  => $custom_class,
'id'            => $id,
'name'          => $name,
'value'         => $value,
] = $args;

Assets

Все фронтенд-файлы, которые не попали ни в components ни в blocks.

  • css - Папка с общими стилями, глобальными лэйаутами, переменными и миксинами

  • js - Папка с утилитарными js функциями и настройками webpack

  • resource - Папка с прочими файлами, такими как картинки, шрифты, видео и тд

В js/config лежат настройки webpack, они могут отличаться от проекта к проекту, но основной принцип такой, что у нас есть 3 точки входа:

  • index.js - Основная точка входа, используемая во фронт-енд части приложения

  • admin.js - Специфические стили и скрипты для admin-части приложения

  • editor.js - Специфические стили и скрипты для области редактирования

После компиляции создается по 3 css и js файла, которые мы подключаем в соответсвующих хуках WordPress.

  • wp_enqueue_scripts - Стили и скрипты для фронтенда

  • enqueue_block_editor_assets - Стили и скрипты для редактора

  • admin_enqueue_scripts - Стили и скрипты для админ части сайта

В index.js нужно подключить все файлы блоков и компонентов, выглядит это примерно так:

import '../css/style.scss';
function importAll(r) {
r.keys().forEach(r);
}
importAll(require.context('./../../assets/resource/fonts/', true, /\.(woff|woff2|eot|ttf|otf)$/));
// Импортируем все файлы js и scss из каждой подпапки 'components'
importAll(require.context('./../../components', true, /\.js/));
// Импортируем все файлы js и scss из каждой подпапки 'blocks'
importAll(require.context('./../../blocks', true, /\.js/));

Такой базовый сетап уже позволяет закрывать 90% задач в рамках разработки кастомной темы для WordPress.

Использование REST API
Для асинхронных запросов чаще всего мы используем WP REST API. Это не единственный способ создать кастомный эндпоинт, но, как показывает практика, самый удобный. Все что нам нужно сделать - это воспользоваться функцией register_rest_route в хуке rest_api_init и написать функцию-колбэк, которая будет обрабатывать этот маршрут

Пример создания эндпоинта для блока со списком новостей и фильтрацией по таксономиям, создание которого мы рассматривали в предыдущей статье:

add_action( 'rest_api_init', 'wwzrds_news_list_route' );
function wwzrds_news_list_route() {
register_rest_route( 'wwzrds/v1', '/news-list', [
'methods'             => 'GET',
'callback'            => 'wwzrds_news_list_route_callback',
'permission_callback' => '__return_true',
] );
}
function wwzrds_news_list_route_callback( WP_REST_Request $request ) {
$json_params = ! empty( $request['json'] ) ? $request['json'] : null;
if ( ! $json_params ) {
	return new WP_REST_Response( [
		'error' => 'Params are empty',
	], 400 );
}

$query_args = json_decode( $json_params, true );

if ( ! $query_args ) {
	return new WP_REST_Response( [
		'error' => 'Params are empty',
	], 400 );
}

$term = ! empty( $request['term'] ) ? $request['term'] : null;
$page = ! empty( $request['page'] ) ? $request['page'] : null;
$tax  = ! empty( $request['taxonomy'] ) ? $request['taxonomy'] : 'technology';

$query_args['tax_query'] = [
	'relation' => 'AND',
];

if ( $term && $term !== 'all' ) {
	$query_args['tax_query']['term'] = [
		'taxonomy' => $tax,
		'terms'    => [ $term ],
		'field'    => 'slug',
	];
}

if ( $page ) {
	$query_args['paged'] = $page;
}

if ( $query_args['post_type'] !== 'post' || $query_args['post_status'] !== 'publish' ) {
	return new WP_REST_Response( [
		'error' => 'Invalid data',
	], 400 );
}

$query     = new WP_Query( $query_args );
$max_pages = $query->max_num_pages;
$news      = $query->posts;
wp_reset_query();

$response = [];

if ( ! empty( $news ) ) {
	$response['posts'] = [];
	foreach ( $news as $new ) {
		$response['posts'][] = wwzrds_get_template_string( 'template-parts/components/post-item', [
			'post_id'      => $new->ID,
			'attrs'        => [
				'style'      => 'opacity:1; transform: translateY(0)',
			],
			'taxonomy'     => $tax,
    'custom_class' => 'news-list__post',
		] );
	}
	$response['pagination'] = wwzrds_generate_pagination( $page, $max_pages, false );
} else {
	$response['empty'] = wwzrds_get_template_string( 'template-parts/blocks/block-news-list/empty-results' );
}

return new WP_REST_Response( $response, 200 );

}

wwzrds_generate_pagination - Функция, которая формирует разметку пагинации на основе входных данных.

wwzrds_get_template_string - Функция-хелпер, которая возвращает шаблон как строку.

add_action( 'rest_api_init', 'db_user_bank_endpoints' );
/**

Get user's bank

@param  WP_REST_Request $request Full details about the request.
@return array $args.
**/
function db_user_bank_endpoints( $request ) {
register_rest_route('wp/v2', 'users/bank', array(
'methods'             => 'GET',
'callback'            => 'db_rest_user_bank_handler',
'permission_callback' => function() {
return is_user_logged_in();
},
));
}

function db_rest_user_bank_handler( $request = null ) {
$user     = wp_get_current_user();
$error    = new WP_Error();
if ( empty( $user ) ) {
	$error->add( 400, __( 'Invalid user.', 'wp-rest-run' ), ['status' => 400] );
	return $error;
}

return new WP_REST_Response( db_calculate_user_store( $user->ID ), 123 );

}

Пример интеграции React-приложения

Демо-видео: https://www.loom.com/share/a25318215847465c94a8af212f870ec6

Сначала в папке темы создаем отдельную папку для файлов виджета, и внутри этой папки инициализируем package.json командой:

npm init

После этого устанавливаем WP Scripts командой:

npm install @wordpress/scripts --save-dev

Открываем package.json и добавляем следующую секцию:

"scripts": {
	"test": "echo \\"Error: no test specified\\" && exit 1",
	"build": "wp-scripts build",
	"check-engines": "wp-scripts check-engines",
	"check-licenses": "wp-scripts check-licenses",
	"format": "wp-scripts format",
	"lint:css": "wp-scripts lint-style",
	"lint:js": "wp-scripts lint-js",
	"lint:md:docs": "wp-scripts lint-md-docs",
	"lint:md:js": "wp-scripts lint-md-js",
	"lint:pkg-json": "wp-scripts lint-pkg-json",
	"packages-update": "wp-scripts packages-update",
	"plugin-zip": "wp-scripts plugin-zip",
	"start": "wp-scripts start",
	"test:e2e": "wp-scripts test-e2e",
	"test:unit": "wp-scripts test-unit-js"
},

В той же папке добавляем webpack.config.js:

const defaults = require('@wordpress/scripts/config/webpack.config');
module.exports = {
...defaults,
externals: {
react: 'React',
'react-dom': 'ReactDOM',
},
};

В index.js будем использовать метод render не из ReactDom, а из wp.element, в остальном это будет обычное react приложение:

const { render } = wp.element; // we are using wp.element here!
import App from './calculator/components/App';
// check if element exists before rendering
const appElement = document.getElementById('calculator-app');
if (appElement) {
render(<App />, appElement);
}

В App.js поместим основное react приложение. Если нам необходимо импортировать что-то из React или ReactDOM то импортируем это из wp.element, например:

const {useState, useEffect} wp.element;

При подключении react приложения в WordPress указываем зависимость от 'wp-element':

add_action('wp_enqueue_scripts', 'calc_react_app');
function calc_react_app()
{
	$calc_script_url = THEME_URL . '/calculator/build/index.js';
	$calc_script_path = THEME_DIR . '/calculator/build/index.js';
	wp_enqueue_script(
		'my_react_app',
		$calc_script_url, // This refer to the built React app
		['wp-element'], //This dependency indicates that you need React at Frontend
		filemtime($calc_script_path) // This could be changed to the theme version for production
	);
}

Такой скелет приложения позволяет очень быстро создавать современные, технологичные и масштабируемые проекты, которые легко поддерживать. Для работы над темой WordPress вы можете использовать публичный репозиторий по ссылке.

Конечно, многие скажут, что надо писать фронт на Vue или React целиком, получать данные по REST API и т. д. — и мы частично согласны, но каждая задача требует индивидуального подхода, в том числе и в выборе наиболее подходящего инструмента для ее реализации. Подписывайтесь на соцсети We Wizards и читайте о том, как мы делаем Headless WordPress + Vue/React в следующих статьях.

Источник: https://habr.com/ru/articles/782530/


Интересные статьи

Интересные статьи

«Спорттовары» – ниша с высокой конкуренцией и с относительно узким товарным ассортиментом. Данное исследование поможет понять, как ранжируются сайты этой тематики. В статье рассмотрим особенности алго...
Насмотренность является частью тех самых желанных и популярных в последнее время - Soft Skills. Из приятного - можно натренировать её уделяя в день 15-20 минут. Сегодня предлагаю подумать, в кажд...
Ориентировочно с мая 2022 года в разных темах на форуме 4PDA и других интернет-площадках начали появляться сообщения вида "Что-то смартфон стал плохо ловить спутники GPS и показывать точное местополож...
Социальная инженерия в общем своем понимании подразумевает под собой метод получения необходимой конфиденциальной информации, различного рода доступов к ресурсам или сети компании посредством психолог...
·       Действительно ли электоральные предпочтения сельских жителей существенно отличаются от предпочтений городских жителей?·      &n...