[JavaScript, Программирование, HTML, Node.JS] Что такое рендеринг на стороне сервера и нужен ли он мне? (перевод)
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
Привет, Хабр!
В новом году начнем общение с вами с затравочной статьи о серверном рендеринге (server-side rendering). В случае вашей заинтересованности возможна более свежая публикация о Nuxt.js и дальнейшая издательская работа в этом направлении
С появлением современных фреймворков и библиотек для JavaScript, которые предназначены, прежде всего, для создания интерактивных веб-страниц и одностраничных приложений, сильно изменился весь процесс показа страниц пользователю.
До пришествия приложений, полностью генерируемых на JS в браузере, HTML-разметка выдавалась клиенту в ответ на HTTP-вызов. Это могло происходить путем возврата статического HTML-файла с контентом, либо путем обработки отклика при помощи какого-либо серверного языка (PHP, Python или Java), причем, более динамическим образом.
Подобное решение позволяет создавать отзывчивые сайты, работающие гораздо быстрее стандартных сайтов, действующих по модели «запрос-отклик», поскольку при работе устраняется время, проводимое запросом «в пути».
Типичный отклик, отправляемый сервером на запрос к сайту, написанному на React, будет выглядеть примерно так:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<link rel="shortcut icon" href="/favicon.ico">
<title>React App</title>
</head>
<body>
<div id="root"></div>
<script src="/app.js"></script>
</body>
</html>
Выбрав этот отклик, наш браузер также выберет «пакет» app.js, содержащий наше приложение, и через одну-две секунды полностью отобразит страницу.
В этот момент уже можно воспользоваться встроенным в браузер инспектором HTML, чтобы просмотреть весь отображенный HTML. Однако, заглянув в исходный код, мы не увидим ничего кроме HTML, приведенного выше.
Почему это проблема?
Хотя такое поведение и не доставит проблем большинству наших пользователей либо при разработке приложения, оно может стать нежелательным, если:
- Конечный пользователь работает с медленным интернет-соединением, что особенно актуально для мобильных устройств,
- Конечный пользователь работает с маломощным устройством, например, со смартфоном старого поколения
Если с демографической точки зрения ваша целевая аудитория относится к одной из этих групп, то работать с сайтом будет неудобно – в частности, пользователям придется довольно долго ждать, уставившись в надпись “Loading …” (или того хуже – в пустой экран).
“Ладно, но в демографическом отношении моя целевая аудитория точно не относится ни к одной из этих групп, так стоит ли мне волноваться?”
Есть еще две вещи, которые следует учитывать при работе с приложением, рендеринг в котором выполняется на стороне клиента: поисковики и присутствие в социальных сетях.
Сегодня из всех поисковиков лишь Google обладает некоторыми возможностями отобразить и сайт и учесть его JS прежде, чем отобразить страницу. Кроме того, хотя Google и сможет отобразить индексную страницу вашего сайта, известно, что могут возникать проблемы с навигацией по сайтам, на которых реализован роутер.
Это означает, что вашему сайту будет очень непросто забраться в топ выдачи любого поисковика кроме Google.
Та же проблема просматривается и в социальных сетях, например, в Facebook — если ссылкой на ваш сайт поделятся, то ни его название, ни картинка-превью не отобразятся как следует.
Как решить эту проблему
Есть несколько способов ее решения.
A — Попробуйте оставить все ключевые страницы вашего сайта статическими
Когда создается сайт-платформа, куда пользователю придется входить под своим логином, а без входа в систему контент посетителю не предоставляется, можно попробовать оставить статическими (написанными на HTML) общедоступные страницы вашего сайта, в частности, индекс, «о нас», «контакты» и не использовать JS при их отображении.
Поскольку ваш контент ограничен требованиями по входу в систему, он не будет индексироваться поисковиками, и им нельзя будет поделиться в социальных сетях.
B — Генерируйте части вашего приложения в виде HTML-страниц в процессе сборки
В проект можно добавить такие библиотеки как react-snapshot; они используются для генерации HTML-копий страниц вашего приложения и для сохранения их в специально предназначенном каталоге. Затем этот каталог развертывается наряду с пакетом JS. Таким образом, HTML будет подаваться с сервера вместе с откликом, и ваш сайт увидят в том числе те пользователи, у которых отключен JavaScript, а также заметят поисковики и т.д.
Как правило, сконфигурировать react-snapshot не составляет труда: достаточно добавить библиотеку в ваш проект и изменить сборочный скрипт следующим образом:
"build": "webpack && react-snapshot --build-dir static"
Недостаток такого решения заключается в следующем: весь контент, который мы хотим сгенерировать, должен быть доступен во время сборки – мы не можем обратиться к каким-либо API, чтобы получить его, мы также не можем заранее сгенерировать контент, зависящий от данных, предоставляемых пользователем (например, от URL).
C — Создать на JS приложение, использующее серверный рендеринг
Один из важнейших выигрышных моментов у современного поколения приложений на JS заключается в том, что их можно запускать как на клиенте (в браузере), так и на сервере. Так удается генерировать HTML для страниц, являющихся более динамичными, таких, чей контент в период сборки еще не известен. Подобные приложения часто называют «изоморфными» или «универсальными».
Два наиболее популярных решения, обеспечивающих серверный рендеринг для React:
- next.js — github.com/zeit/next.js
- Gatsby — github.com/gatsbyjs/gatsby
Создайте собственную реализацию SSR
Важно: если вы собираетесь попробовать самостоятельно создать собственную реализацию SSR для приложений на React, то должны будете обеспечить работу node-бэкенда для вашего сервера. Вы не сможете развернуть это решение на статическом хосте, как в случае со страницами github.
Первым делом нам понадобится создать приложение, точно как в случае с любым другим приложением React.
Давайте создадим входную точку:
// index.js
import React from 'react';
import { render } from 'react-dom';
import App from './App.js';render(<App />, document.getElementById('root'));
И компонент-приложение (App):
// App.js
import React from 'react';const App = () => {
return (
<div>
Welcome to SSR powered React application!
</div>
);
}
А также “оболочку”, чтобы загрузить наше приложение:
// index.html
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
</head>
<body>
<div id="root"></div>
<script src="/bundle.js"></script>
</body>
</html>
Как видите, приложение получилось довольно простым. В рамках этой статьи мы не будем пошагово разбирать все шаги, необходимые для генерации правильной сборки webpack+babel.
Если запустить приложение в его текущем состоянии, то на экране появится сообщение-приветствие. Просмотрев исходный код, вы увидите содержимое файла index.html, но приветственного сообщения там не будет. Для решения этой проблемы добавим серверный рендеринг. Для начала добавим 3 пакета:
yarn add express pug babel-node --save-dev
Express – это мощный веб-сервер для node, pug – движок-шаблонизатор, который можно использовать с express, а babel-node – это обертка для node, обеспечивает транспиляцию на лету.
Сначала скопируем наш файл index.html и сохраним его как index.pug:
// index.pug
<!doctype html>
<html>
<head>
<meta charset=«utf-8» />
</head>
<body>
<div id=«root»>!{app}</div>
<script src=«bundle.js»></script>
</body>
</html>
Как видите, файл практически не изменился, не считая того, что теперь в HTML вставлено !{app}. Это переменная pug, которая впоследствии будет заменена реальным HTML.
Создадим наш сервер:
// server.jsimport React from 'react';
import { renderToString } from 'react-dom/server';
import express from 'express';
import path from 'path';import App from './src/App';const app = express();
app.set('view engine', 'pug');
app.use('/', express.static(path.join(__dirname, 'dist')));app.get('*', (req, res) => {
const html = renderToString(
<App />
); res.render(path.join(__dirname, 'src/index.pug'), {
app: html
});
});app.listen(3000, () => console.log('listening on port 3000'));
Разберем этот файл по порядку.
import { renderToString } from 'react-dom/server';
Библиотека react-dom содержит отдельный именованный экспорт renderToString, работающий подобно известному нам render, но отображает не DOM, а HTML в виде строки.
const app = express();
app.set('view engine', 'pug');
app.use('/', express.static(path.join(__dirname, 'dist')));
Мы создаем новый инстанс сервера express и сообщаем ему, что собираемся использовать движок-шаблонизатор pug. В данном случае мы могли бы обойтись обычным считыванием файла и выполнением операции «поиск с заменой», но такой подход в самом деле неэффективен и может спровоцировать проблемы из-за множественного доступа к файловой системе, либо проблемы с кэшированием.
В последней строке мы приказываем express искать файл в каталоге dist, и, если запрос (напр. /bundle.js) совпадает с файлом, присутствующем в этом каталоге, то выдать его в ответ.
app.get('*', (req, res) => {
});
Теперь мы приказываем express добавить обработчик на каждый несовпавший URL — в том числе, на наш несуществующий файл index.html (как вы помните, мы переименовали его в index.pug, и его нет в каталоге dist).
const html = renderToString(
<App />
);
При помощи renderToString мы отображаем наше приложение. Код выглядит точно как у входной точки, но такое совпадение не является обязательным.
res.render(path.join(__dirname, 'src/index.pug'), {
app: html
});
Теперь, когда у нас есть отображенный HTML, мы приказываем express отобразить в ответ файл index.pug и заменить переменную app тем HTML, что мы получили.
app.listen(3000, () => console.log('listening on port 3000'));
Наконец, мы обеспечиваем запуск сервера и настраиваем его так, чтобы он слушал порт 3000.
Теперь нам осталось всего лишь добавить нужный скрипт в package.json:
"scripts": {
"server": "babel-node server.js"
}
Теперь, вызвав yarn run server, мы должны получить подтверждение, что сервер действительно работает. Переходим в браузере по адресу localhost:3000, где мы, опять же, должны увидеть наше приложение. Если мы просмотрим исходный код на данном этапе, то увидим:
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
</head>
<body>
<div id="root">div data-reactroot="">Welcome to SSR powered React application!</div></div>
<script src="bundle.js"></script>
</body>
</html>
Если все выглядит именно так, это означает, что серверный рендеринг работает как положено, и можно приступать к расширению вашего приложения!
Зачем же нам по-прежнему нужен bundle.js?
В случае такого крайне простого приложения, которое рассмотрено здесь, включать bundle.js не обязательно – без этого файла наше приложение все равно останется работоспособным. Но в случае с реальным приложением включить этот файл все-таки потребуется.
Это позволит браузерам, умеющим обрабатывать JavaScript, взять работу на себя и далее взаимодействовать с вашей страницей уже на стороне клиента, а тем, что не умеют разбирать JS – перейти на страницу с нужным HTML, который возвратил сервер.
О чем необходимо помнить
Притом, что серверный рендеринг выглядит достаточно незамысловато, при разработке приложений нужно обращать внимание на некоторые темы, на первый взгляд не вполне очевидные:
- Любое состояние, сгенерированное на стороне сервера, не будет передаваться в состояние клиентского приложения. Это означает, что, если ваша серверная часть выберет некоторые данные и использует их для отображения HTML, то эти данные не попадут в this.state, которое увидит браузер
- componentDidMount не вызывается на сервере — это означает, что не будут вызываться никакие операции по выборке данных, которые вы привыкли там размещать. В принципе, это хорошо, поскольку вы должны предоставлять нужные вам данные в виде пропсов. Помните, что отображение нужно отложить (вызвав res.render) до тех пор, пока данные не будут выбраны. Из-за этого посетители могут заметить некоторые задержки в работе сайта
- если вы собираетесь использовать роутер react (напр. @reach/router или react-router) то должны убедиться, что приложению передается правильный URL, когда оно отображается на сервере. Обязательно почитайте об этом в документации!
===========
Источник:
habr.com
===========
===========
Автор оригинала: Bartosz Szczeciński
===========Похожие новости:
- [JavaScript, Node.JS, API, Визуализация данных, Финансы в IT] Поиск замены депозита в облигациях с учетом того, что с 1 января 2021 года все выплаты облагаются налогами
- [Разработка под Linux, Программирование микроконтроллеров, Схемотехника, Производство и разработка электроники] WSN-LTE шлюз на CC1310 и WP8548. Часть 1
- [Программирование, Читальный зал, История IT, Софт] О русском языке в программировании
- [Open source, Алгоритмы, Lua, Параллельное программирование] Это непростое условное выполнение
- [JavaScript, WebGL] Делаем свой minecraft на JavaScript
- [Python, JavaScript, Компиляторы] Ещё один способ использования python в браузере (и не только)
- [Open source, Алгоритмы, Lua, Параллельное программирование] Это непростое условное выполнение
- [JavaScript, Программирование, C#, Rust] Вышла версия 1.0 библиотеки для управления секс-игрушками Buttplug
- [Open source, Программирование, Системное программирование, Компиляторы, Rust] Rust 1.49.0: aarch64 и улучшения во фреймворке тестирования (перевод)
- [Программирование, Разработка игр, Изучение языков] Обзор GameLisp: нового языка для написания игр на Rust
Теги для поиска: #_javascript, #_programmirovanie (Программирование), #_html, #_node.js, #_js, #_serverside_rendering, #_react, #_node.js, #_vebrazrabotka (веб-разработка), #_programmirovanie (программирование), [url=https://torrents-local.xyz/search.php?nm=%23_blog_kompanii_izdatelskij_dom_«piter»&to=0&allw=0&o=1&s=0&f%5B%5D=820&f%5B%5D=959&f%5B%5D=958&f%5B%5D=872&f%5B%5D=967&f%5B%5D=954&f%5B%5D=885&f%5B%5D=882&f%5B%5D=863&f%5B%5D=881&f%5B%5D=860&f%5B%5D=884&f%5B%5D=865&f%5B%5D=873&f%5B%5D=861&f%5B%5D=864&f%5B%5D=883&f%5B%5D=957&f%5B%5D=859&f%5B%5D=966&f%5B%5D=956&f%5B%5D=955]#_blog_kompanii_izdatelskij_dom_«piter» (
Блог компании Издательский дом «Питер»
)[/url], #_javascript, #_programmirovanie (
Программирование
), #_html, #_node.js
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 13:06
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
Привет, Хабр! В новом году начнем общение с вами с затравочной статьи о серверном рендеринге (server-side rendering). В случае вашей заинтересованности возможна более свежая публикация о Nuxt.js и дальнейшая издательская работа в этом направлении С появлением современных фреймворков и библиотек для JavaScript, которые предназначены, прежде всего, для создания интерактивных веб-страниц и одностраничных приложений, сильно изменился весь процесс показа страниц пользователю. До пришествия приложений, полностью генерируемых на JS в браузере, HTML-разметка выдавалась клиенту в ответ на HTTP-вызов. Это могло происходить путем возврата статического HTML-файла с контентом, либо путем обработки отклика при помощи какого-либо серверного языка (PHP, Python или Java), причем, более динамическим образом. Подобное решение позволяет создавать отзывчивые сайты, работающие гораздо быстрее стандартных сайтов, действующих по модели «запрос-отклик», поскольку при работе устраняется время, проводимое запросом «в пути». Типичный отклик, отправляемый сервером на запрос к сайту, написанному на React, будет выглядеть примерно так: <!DOCTYPE html>
<html lang="en"> <head> <meta charset="utf-8"> <link rel="shortcut icon" href="/favicon.ico"> <title>React App</title> </head> <body> <div id="root"></div> <script src="/app.js"></script> </body> </html> Выбрав этот отклик, наш браузер также выберет «пакет» app.js, содержащий наше приложение, и через одну-две секунды полностью отобразит страницу. В этот момент уже можно воспользоваться встроенным в браузер инспектором HTML, чтобы просмотреть весь отображенный HTML. Однако, заглянув в исходный код, мы не увидим ничего кроме HTML, приведенного выше. Почему это проблема? Хотя такое поведение и не доставит проблем большинству наших пользователей либо при разработке приложения, оно может стать нежелательным, если:
Если с демографической точки зрения ваша целевая аудитория относится к одной из этих групп, то работать с сайтом будет неудобно – в частности, пользователям придется довольно долго ждать, уставившись в надпись “Loading …” (или того хуже – в пустой экран). “Ладно, но в демографическом отношении моя целевая аудитория точно не относится ни к одной из этих групп, так стоит ли мне волноваться?” Есть еще две вещи, которые следует учитывать при работе с приложением, рендеринг в котором выполняется на стороне клиента: поисковики и присутствие в социальных сетях. Сегодня из всех поисковиков лишь Google обладает некоторыми возможностями отобразить и сайт и учесть его JS прежде, чем отобразить страницу. Кроме того, хотя Google и сможет отобразить индексную страницу вашего сайта, известно, что могут возникать проблемы с навигацией по сайтам, на которых реализован роутер. Это означает, что вашему сайту будет очень непросто забраться в топ выдачи любого поисковика кроме Google. Та же проблема просматривается и в социальных сетях, например, в Facebook — если ссылкой на ваш сайт поделятся, то ни его название, ни картинка-превью не отобразятся как следует. Как решить эту проблему Есть несколько способов ее решения. A — Попробуйте оставить все ключевые страницы вашего сайта статическими Когда создается сайт-платформа, куда пользователю придется входить под своим логином, а без входа в систему контент посетителю не предоставляется, можно попробовать оставить статическими (написанными на HTML) общедоступные страницы вашего сайта, в частности, индекс, «о нас», «контакты» и не использовать JS при их отображении. Поскольку ваш контент ограничен требованиями по входу в систему, он не будет индексироваться поисковиками, и им нельзя будет поделиться в социальных сетях. B — Генерируйте части вашего приложения в виде HTML-страниц в процессе сборки В проект можно добавить такие библиотеки как react-snapshot; они используются для генерации HTML-копий страниц вашего приложения и для сохранения их в специально предназначенном каталоге. Затем этот каталог развертывается наряду с пакетом JS. Таким образом, HTML будет подаваться с сервера вместе с откликом, и ваш сайт увидят в том числе те пользователи, у которых отключен JavaScript, а также заметят поисковики и т.д. Как правило, сконфигурировать react-snapshot не составляет труда: достаточно добавить библиотеку в ваш проект и изменить сборочный скрипт следующим образом: "build": "webpack && react-snapshot --build-dir static" Недостаток такого решения заключается в следующем: весь контент, который мы хотим сгенерировать, должен быть доступен во время сборки – мы не можем обратиться к каким-либо API, чтобы получить его, мы также не можем заранее сгенерировать контент, зависящий от данных, предоставляемых пользователем (например, от URL). C — Создать на JS приложение, использующее серверный рендеринг Один из важнейших выигрышных моментов у современного поколения приложений на JS заключается в том, что их можно запускать как на клиенте (в браузере), так и на сервере. Так удается генерировать HTML для страниц, являющихся более динамичными, таких, чей контент в период сборки еще не известен. Подобные приложения часто называют «изоморфными» или «универсальными». Два наиболее популярных решения, обеспечивающих серверный рендеринг для React:
Создайте собственную реализацию SSR Важно: если вы собираетесь попробовать самостоятельно создать собственную реализацию SSR для приложений на React, то должны будете обеспечить работу node-бэкенда для вашего сервера. Вы не сможете развернуть это решение на статическом хосте, как в случае со страницами github. Первым делом нам понадобится создать приложение, точно как в случае с любым другим приложением React. Давайте создадим входную точку: // index.js
import React from 'react'; import { render } from 'react-dom'; import App from './App.js';render(<App />, document.getElementById('root')); И компонент-приложение (App): // App.js
import React from 'react';const App = () => { return ( <div> Welcome to SSR powered React application! </div> ); } А также “оболочку”, чтобы загрузить наше приложение: // index.html
<!doctype html> <html> <head> <meta charset="utf-8" /> </head> <body> <div id="root"></div> <script src="/bundle.js"></script> </body> </html> Как видите, приложение получилось довольно простым. В рамках этой статьи мы не будем пошагово разбирать все шаги, необходимые для генерации правильной сборки webpack+babel. Если запустить приложение в его текущем состоянии, то на экране появится сообщение-приветствие. Просмотрев исходный код, вы увидите содержимое файла index.html, но приветственного сообщения там не будет. Для решения этой проблемы добавим серверный рендеринг. Для начала добавим 3 пакета: yarn add express pug babel-node --save-dev Express – это мощный веб-сервер для node, pug – движок-шаблонизатор, который можно использовать с express, а babel-node – это обертка для node, обеспечивает транспиляцию на лету. Сначала скопируем наш файл index.html и сохраним его как index.pug: // index.pug <!doctype html> <html> <head> <meta charset=«utf-8» /> </head> <body> <div id=«root»>!{app}</div> <script src=«bundle.js»></script> </body> </html> Как видите, файл практически не изменился, не считая того, что теперь в HTML вставлено !{app}. Это переменная pug, которая впоследствии будет заменена реальным HTML. Создадим наш сервер: // server.jsimport React from 'react';
import { renderToString } from 'react-dom/server'; import express from 'express'; import path from 'path';import App from './src/App';const app = express(); app.set('view engine', 'pug'); app.use('/', express.static(path.join(__dirname, 'dist')));app.get('*', (req, res) => { const html = renderToString( <App /> ); res.render(path.join(__dirname, 'src/index.pug'), { app: html }); });app.listen(3000, () => console.log('listening on port 3000')); Разберем этот файл по порядку. import { renderToString } from 'react-dom/server'; Библиотека react-dom содержит отдельный именованный экспорт renderToString, работающий подобно известному нам render, но отображает не DOM, а HTML в виде строки. const app = express();
app.set('view engine', 'pug'); app.use('/', express.static(path.join(__dirname, 'dist'))); Мы создаем новый инстанс сервера express и сообщаем ему, что собираемся использовать движок-шаблонизатор pug. В данном случае мы могли бы обойтись обычным считыванием файла и выполнением операции «поиск с заменой», но такой подход в самом деле неэффективен и может спровоцировать проблемы из-за множественного доступа к файловой системе, либо проблемы с кэшированием. В последней строке мы приказываем express искать файл в каталоге dist, и, если запрос (напр. /bundle.js) совпадает с файлом, присутствующем в этом каталоге, то выдать его в ответ. app.get('*', (req, res) => {
}); Теперь мы приказываем express добавить обработчик на каждый несовпавший URL — в том числе, на наш несуществующий файл index.html (как вы помните, мы переименовали его в index.pug, и его нет в каталоге dist). const html = renderToString(
<App /> ); При помощи renderToString мы отображаем наше приложение. Код выглядит точно как у входной точки, но такое совпадение не является обязательным. res.render(path.join(__dirname, 'src/index.pug'), {
app: html }); Теперь, когда у нас есть отображенный HTML, мы приказываем express отобразить в ответ файл index.pug и заменить переменную app тем HTML, что мы получили. app.listen(3000, () => console.log('listening on port 3000')); Наконец, мы обеспечиваем запуск сервера и настраиваем его так, чтобы он слушал порт 3000. Теперь нам осталось всего лишь добавить нужный скрипт в package.json: "scripts": {
"server": "babel-node server.js" } Теперь, вызвав yarn run server, мы должны получить подтверждение, что сервер действительно работает. Переходим в браузере по адресу localhost:3000, где мы, опять же, должны увидеть наше приложение. Если мы просмотрим исходный код на данном этапе, то увидим: <!doctype html>
<html> <head> <meta charset="utf-8" /> </head> <body> <div id="root">div data-reactroot="">Welcome to SSR powered React application!</div></div> <script src="bundle.js"></script> </body> </html> Если все выглядит именно так, это означает, что серверный рендеринг работает как положено, и можно приступать к расширению вашего приложения! Зачем же нам по-прежнему нужен bundle.js? В случае такого крайне простого приложения, которое рассмотрено здесь, включать bundle.js не обязательно – без этого файла наше приложение все равно останется работоспособным. Но в случае с реальным приложением включить этот файл все-таки потребуется. Это позволит браузерам, умеющим обрабатывать JavaScript, взять работу на себя и далее взаимодействовать с вашей страницей уже на стороне клиента, а тем, что не умеют разбирать JS – перейти на страницу с нужным HTML, который возвратил сервер. О чем необходимо помнить Притом, что серверный рендеринг выглядит достаточно незамысловато, при разработке приложений нужно обращать внимание на некоторые темы, на первый взгляд не вполне очевидные:
=========== Источник: habr.com =========== =========== Автор оригинала: Bartosz Szczeciński ===========Похожие новости:
Блог компании Издательский дом «Питер» )[/url], #_javascript, #_programmirovanie ( Программирование ), #_html, #_node.js |
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 13:06
Часовой пояс: UTC + 5