كانت مهمتنا تنفيذ منشئ مواقع الويب. في المقدمة ، يتم تشغيل كل شيء بواسطة تطبيق React الذي يقوم ، بناءً على إجراءات المستخدم ، بإنشاء JSON بمعلومات حول كيفية إنشاء HTML ، وتخزينه في PHP الخلفية. بدلاً من تكرار منطق تجميع HTML على الواجهة الخلفية ، قررنا إعادة استخدام كود JS. من الواضح أن هذا سوف يبسط الصيانة ، لأن الرمز لن يتغير إلا في مكان واحد بواسطة شخص واحد. هنا يأتي عرض جانب الخادم للإنقاذ مع محرك V8 و PHP-extension V8JS.
في هذه المقالة ، سنغطي كيفية استخدامنا لمحركات V8J في مهمتنا المحددة ، ولكن حالات الاستخدام لا تقتصر على ذلك فقط. الأمر الأكثر وضوحًا هو القدرة على استخدام العرض الجانبي للخادم لتلبية احتياجات تحسين محركات البحث.
التخصيص
نستخدم Symfony و Docker ، لذا فإن الخطوة الأولى هي تهيئة مشروع فارغ وإعداد البيئة. دعنا نلاحظ النقاط الرئيسية:
- يجب تثبيت امتداد V8Js في Dockerfile:
... RUN apt-get install -y software-properties-common RUN add-apt-repository ppa:stesie/libv8 && apt-get update RUN apt-get install -y libv8-7.5 libv8-7.5-dev g++ expect RUN git clone https://github.com/phpv8/v8js.git /usr/local/src/v8js && \ cd /usr/local/src/v8js && phpize && ./configure --with-v8js=/opt/libv8-7.5 && \ export NO_INTERACTION=1 && make all -j4 && make test install RUN echo extension=v8js.so > /etc/php/7.2/fpm/conf.d/99-v8js.ini RUN echo extension=v8js.so > /etc/php/7.2/cli/conf.d/99-v8js.ini ...
- تثبيت React و ReactDOM بأسهل طريقة
- أضف مسار الفهرس ووحدة التحكم الافتراضية:
<?php declare(strict_types=1); namespace App\Controller; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Annotation\Route; final class DefaultController extends AbstractController { /** * @Route(path="/") */ public function index(): Response { return $this->render('index.html.twig'); } }
- أضف النموذج index.html.twig مع تضمين React
<html> <body> <div id="app"></div> <script src="{{ asset('assets/react.js') }}"></script> <script src="{{ asset('assets/react-dom.js') }}"></script> <script src="{{ asset('assets/babel.js') }}"></script> <script type="text/babel" src="{{ asset('assets/front.jsx') }}"></script> </body> </html>
باستخدام
لإثبات V8 ، دعنا ننشئ نصًا بسيطًا للعرض H1 و P مع نص الأصول / front.jsx:
'use strict';
class DataItem extends React.Component {
constructor(props) {
super(props);
this.state = {
checked: props.name,
names: ['h1', 'p']
};
this.change = this.change.bind(this);
this.changeText = this.changeText.bind(this);
}
render() {
return (
<li>
<select value={this.state.checked} onChange={this.change} >
{
this.state.names.map((name, k) => {
return (
<option key={k} value={name}>{name}</option>
);
})
}
</select>
<input type='text' value={this.state.value} onChange={this.changeText} />
</li>
);
}
change(e) {
let newval = e.target.value;
if (this.props.onChange) {
this.props.onChange(this.props.number, newval)
}
this.setState({checked: newval});
}
changeText(e) {
let newval = e.target.value;
if (this.props.onChangeText) {
this.props.onChangeText(this.props.number, newval)
}
}
}
class DataList extends React.Component {
constructor(props) {
super(props);
this.state = {
message: null,
items: []
};
this.add = this.add.bind(this);
this.save = this.save.bind(this);
this.updateItem = this.updateItem.bind(this);
this.updateItemText = this.updateItemText.bind(this);
}
render() {
return (
<div>
{this.state.message ? this.state.message : ''}
<ul>
{
this.state.items.map((item, i) => {
return (
<DataItem
key={i}
number={i}
value={item.name}
onChange={this.updateItem}
onChangeText={this.updateItemText}
/>
);
})
}
</ul>
<button onClick={this.add}></button>
<button onClick={this.save}></button>
</div>
);
}
add() {
let items = this.state.items;
items.push({
name: 'h1',
value: ''
});
this.setState({message: null, items: items});
}
save() {
fetch(
'/save',
{
method: 'POST',
headers: {
'Content-Type': 'application/json;charset=utf-8'
},
body: JSON.stringify({
items: this.state.items
})
}
).then(r => r.json()).then(r => {
this.setState({
message: r.id,
items: []
})
});
}
updateItem(k, v) {
let items = this.state.items;
items[k].name = v;
this.setState({items: items});
}
updateItemText(k, v) {
let items = this.state.items;
items[k].value = v;
this.setState({items: items});
}
}
const domContainer = document.querySelector('#app');
ReactDOM.render(React.createElement(DataList), domContainer);
انتقل إلى المضيف المحلي: 8088 (تم تحديد 8088 في Docker-compose.yml كمنفذ nginx):
- DB
create table data( id serial not null primary key, data json not null );
- طريق
/** * @Route(path="/save") */ public function save(Request $request): Response { $em = $this->getDoctrine()->getManager(); $data = (new Data())->setData(json_decode($request->getContent(), true)); $em->persist($data); $em->flush(); return new JsonResponse(['id' => $data->getId()]); }
نضغط على زر الحفظ ، عندما نضغط على طريقنا ، يتم إرسال JSON:
{
"items":[
{
"name":"h1",
"value":" "
},
{
"name":"p",
"value":" "
},
{
"name":"h1",
"value":" "
},
{
"name":"p",
"value":" "
}
]
}
استجابةً لذلك ، يتم إرجاع معرف السجل في قاعدة البيانات:
/**
* @Route(path="/save")
*/
public function save(Request $request): Response
{
$em = $this->getDoctrine()->getManager();
$data = (new Data())->setData(json_decode($request->getContent(), true));
$em->persist($data);
$em->flush();
return new JsonResponse(['id' => $data->getId()]);
}
الآن بعد أن أصبح لديك بعض بيانات الاختبار ، يمكنك تجربة V8 أثناء العمل. للقيام بذلك ، ستحتاج إلى رسم سكربت React الذي سيشكل مكونات من دعائم Dom التي تم تمريرها. لنضعها بجانب الأصول الأخرى ونسميها ssr.js:
'use strict';
class Render extends React.Component {
constructor(props) {
super(props);
}
render() {
return React.createElement(
'div',
{},
this.props.items.map((item, k) => {
return React.createElement(item.name, {}, item.value);
})
);
}
}
من أجل تكوين سلسلة من شجرة DOM التي تم إنشاؤها ، سنستخدم مكون ReactDomServer (https://unpkg.com/browse/react-dom@16.13.0/umd/react-dom-server.browser.production.min.js). لنكتب مسارًا باستخدام HTML جاهز:
/**
* @Route(path="/publish/{id}")
*/
public function renderPage(int $id): Response
{
$data = $this->getDoctrine()->getManager()->find(Data::class, $id);
if (!$data) {
return new Response('<h1>Page not found</h1>', Response::HTTP_NOT_FOUND);
}
$engine = new \V8Js();
ob_start();
$engine->executeString($this->createJsString($data));
return new Response(ob_get_clean());
}
private function createJsString(Data $data): string
{
$props = json_encode($data->getData());
$bundle = $this->getRenderString();
return <<<JS
var global = global || this, self = self || this, window = window || this;
$bundle;
print(ReactDOMServer.renderToString(React.createElement(Render, $props)));
JS;
}
private function getRenderString(): string
{
return
sprintf(
"%s\n%s\n%s\n%s",
file_get_contents($this->reactPath, true),
file_get_contents($this->domPath, true),
file_get_contents($this->domServerPath, true),
file_get_contents($this->ssrPath, true)
);
}
هنا:
- رد فعل - مسار رد. js
- domPath - مسار رد فعل dom.js
- domServerPath - مسار رد فعل dom-server.js
- ssrPath - المسار إلى النص ssr.js الخاص بنا
اتبع الرابط / انشر / 3:
كما ترى ، تم تقديم كل شيء تمامًا كما نحتاج إليه.
خاتمة
في الختام ، أود أن أقول إن عرض جانب الخادم تبين أنه ليس صعبًا ويمكن أن يكون مفيدًا للغاية. الشيء الوحيد الذي يستحق الإضافة هنا هو أن العرض قد يستغرق وقتًا طويلاً ، ومن الأفضل إضافة قائمة انتظار هنا - RabbitMQ أو Gearman.
يمكن الاطلاع على كود مصدر PPS هنا https://github.com/damir-in/ssr-php-symfony
Authors
دامير زينفابيل