آخر تحديث

لماذا يظهر Artifact مكتوب بـReact من Claude صفحةً فارغة خارج Claude

بقلم

في Claude، تؤدي النافذة المحيطة بملف React أربع مهام لا يؤديها الملف نفسه: تحوّل JSX، وتوفّر الحزم التي تذكرها أسطر import، وتركّب المكوّن، وتحمّل Tailwind. وحين يُحفظ ملفًا ويُفتح في متصفح، أو يوضع على استضافة تقدّم الملفات كما هي، لا يحدث شيء من ذلك، وغالبًا ما تحصل على صفحة بيضاء.

الصق هنا ملف React الذي ظهر فارغًا

اقرأ وحدة التحكم أولًا

افتح الصفحة، ثم وحدة التحكم (Console) في المتصفح. في Chrome على Mac، هي القائمة View ثم Developer ثم JavaScript Console، بهذه الأسماء في Chrome بالإنجليزية، أو Cmd+Option+J بأي لغة؛ وعلى Windows، Ctrl+Shift+J. أول سطر أحمر يقول أيّ هذه الحالات هي:

1. المتصفح لا يستطيع قراءة JSX

سطر مثل return <div className="p-8">…</div> هو JSX، لا JavaScript. ويجب أن يحوّله شيءٌ ما إلى استدعاءات دوال قبل أن يشغّله المتصفح. وفي وسم <script> عادي، لا يستطيع المتصفح قراءة السكربت بعد أول < لا يتوقعه، فلا يعمل أي جزء من السكربت. وإن حُفظ ملفًا بامتداد .jsx وفُتح مباشرة، فلا يعمل أصلًا: يعرض المتصفح الكود نصًا، أو ينزّله.

والحل خطوة تحويل: برنامج تجميع (bundler) مثل Vite أو esbuild، قبل أن تذهب الصفحة إلى أي مكان. ولتجربة سريعة، يستطيع Babel تحويل JSX في المتصفح عبر <script type="text/babel">، مع أن ذلك يترك أسطر import في القسم التالي دون حل.

2. أسطر import لا تشير إلى شيء

تبدأ ملفات React من Claude بأسطر مثل هذه:

import { useState } from "react";
import { LineChart, Line } from "recharts";
import { Card } from "@/components/ui/card";

في Claude، تُوفّر النافذة هذه الحزم. أما المتصفح فلا يعرف ما هو react. ففي سكربت من نوع module، يكون اسم مجرد كهذا خطأً حتى يربطه شيءٌ ما بعنوان؛ وفي سكربت عادي، تكون import نفسها خطأً في الصياغة. و@/ في السطر الثالث اختصار لمسار من إعدادات مشروع، وCard الذي يذكره مكوّن من shadcn/ui، وهو ملف لا وجود له خارج Claude حتى تضيفه.

والحل برنامج تجميع يثبّت الحزم من npm، أو خريطة استيراد (import map) تخبر المتصفح أين توجد كل حزمة:

<script type="importmap">
{
  "imports": {
    "react": "https://esm.sh/react@18.3.1",
    "react/jsx-runtime": "https://esm.sh/react@18.3.1/jsx-runtime",
    "react-dom/client": "https://esm.sh/react-dom@18.3.1/client?external=react"
  }
}
</script>

كل حزمة تستخدم React يجب أن تتشارك النسخة الوحيدة منه في الصفحة، وهذا ما يطلبه ?external=react من esm.sh. ومع نسختين، تفشل الصفحة بالخطأ Invalid hook call.

3. لا شيء يركّب المكوّن

ينتهي ملف Claude بـexport default function App() { … }. هذا يعرّف مكوّنًا، لكنه لا يضعه في الصفحة. في Claude، تتولى النافذة هذه الخطوة. ووحده، يعرّف الملف دالة لا يستدعيها شيء، وهذا ليس خطأً، فتبقى وحدة التحكم صامتة وتبقى الصفحة فارغة.

تحتاج الصفحة إلى عنصر يُعرض فيه المكوّن، واستدعاء واحد يعرضه:

<div id="root"></div>
<script type="module">
  import { createElement } from "react";
  import { createRoot } from "react-dom/client";
  import App from "./App.js";

  createRoot(document.getElementById("root"))
    .render(createElement(App));
</script>

هنا App.js هو الملف بعد تحويله، لا ملف JSX، ويجب أن يأتي من عنوان على الويب (القسم 5). وتستخدم الشروح الأقدم ReactDOM.render بدلًا من ذلك: أعلن React 18 أنها مهملة وأزالها React 19، فتفشل في إصدار React الحالي بالخطأ TypeError.

4. فئات Tailwind لا تفعل شيئًا

ينسّق Claude ملفات React بفئات Tailwind، مثل className="p-8 rounded-xl bg-slate-900 text-white". في Claude، تتضمن النافذة أنماط Tailwind. أما الصفحة العادية فلا تتضمنها، فتصبح الفئات مجرد أسماء.

ونادرًا ما تكون هذه الحالة فارغة تمامًا: المحتوى موجود، بلا تنسيق، مكدّس على الحافة. وتبدو فارغة حين يكون النص مصممًا ليظهر على خلفية داكنة فيصبح أبيض على أبيض، أو حين يعتمد التخطيط على فئات مثل h-screen وflex ليمنح أي شيء حجمًا.

والحل Tailwind نفسه. في عملية البناء، يبحث في ملفاتك عن أسماء الفئات ويكتب الـCSS الذي تستخدمه. ولتجربة سريعة هناك Play CDN من Tailwind، وهو سكربت يفعل الشيء نفسه في المتصفح، وتصفه وثائق Tailwind بأنه مخصص للتطوير، لا لبيئة الإنتاج.

5. فُتحت الصفحة من قرصك

انقر نقرًا مزدوجًا على ملف HTML فيُفتح على عنوان file:///. والصفحة هناك ليس لها أصل ويب خاص بها، فيرفض Chrome وEdge بعض ما تطلبه:

  • سكربت من نوع module من ملف آخر، <script type="module" src="app.js">. تُجلب سكربتات module وفق قواعد CORS، ولا تستطيع صفحة file:/// استيفاءها.
  • <script type="text/babel" src="App.jsx"> الخاص بـBabel، لأن Babel يجلب ذلك الملف بنفسه فيُرفض بالطريقة نفسها.
  • fetch() لملف JSON أو CSV موجود بجانب الصفحة.

الكود المكتوب داخل الصفحة يظل يعمل، والاستيراد من عناوين https تسمح بذلك، مثل esm.sh، يظل يُحمَّل. والحل أن تقدّم المجلد من خادم ويب محلي وتفتحه على http://localhost بالمنفذ الذي يستخدمه الخادم: 3000 للأول، و8000 للثاني. شغّل أيًّا منهما في المجلد:

npx serve
python3 -m http.server

أو تجاوز الحالات الخمس كلها

الصق الملف في Solta، في المربع في الأعلى. حين تنشره، يحوّل Solta الـJSX، ويوجّه كل import إلى esm.sh، مع إصدار مثبّت للحزم التي تستوردها هذه الملفات عادةً، ويقدّم مكوّنات shadcn/ui من نسخته الخاصة، ويولّد الـCSS لفئات Tailwind التي يستخدمها الملف، ويركّب التصدير الافتراضي في صفحة. وتُقدَّم الصفحة على عنوان https، فلا تنطبق قواعد file:/// أبدًا.

إن لم يكن في الملف export default ولم يركّب نفسه، فلن ينشره Solta، وسيقول ما يجب إضافته. وإن أطلق المكوّن خطأً أثناء عرضه، تُظهر الصفحة الخطأ بدلًا من صفحة بيضاء. أما ما لا يستطيع إصلاحه فهو حزمة لا يملكها esm.sh، أو أي شيء يحتاج إلى خادم: ما لا يعمل. والصفحة التي تسأل Claude شيئًا أثناء استخدام الناس لها تحتاج إلى تغيير ذلك الطلب أيضًا: كيف يستدعي الموقع نموذجًا.

أسئلة

لماذا يُظهر ملف React من Claude صفحة فارغة؟

لأن النافذة المحيطة به في Claude تحوّل JSX، وتوفّر الحزم التي تذكرها أسطر import، وتركّب المكوّن، وتحمّل Tailwind. وحين يُفتح مباشرة في متصفح، أو على استضافة تقدّم الملفات كما هي، لا يحدث شيء من ذلك.

ماذا يعني Unexpected token '<' في وحدة التحكم؟

يعني أن المتصفح وجد JSX، وهو ليس JavaScript. يجب تحويله أولًا، ببرنامج تجميع مثل Vite أو esbuild، أو بـBabel في المتصفح لتجربة سريعة.

كيف أصلح Failed to resolve module specifier "react"؟

ببرنامج تجميع يثبّت الحزم من npm، أو بخريطة استيراد (import map) تخبر المتصفح أين توجد كل حزمة، على esm.sh مثلًا. وكل حزمة تستخدم React يجب أن تتشارك النسخة نفسها منه، وإلا فشلت الصفحة بالخطأ Invalid hook call.

لماذا تبقى الصفحة فارغة ووحدة التحكم لا تُظهر أي خطأ؟

لأن لا شيء يركّب المكوّن: يعرّف الملف دالة لا يستدعيها شيء، وهذا ليس خطأً. تحتاج الصفحة إلى عنصر يُعرض فيه المكوّن، واستدعاء لـcreateRoot يعرضه فيه.

لماذا لا تعمل الصفحة حين أفتح الملف بنقرة مزدوجة؟

تُفتح على عنوان file:///، فيرفض Chrome وEdge سكربت module من ملف آخر وfetch() لملف بجانبها. قدّم المجلد من خادم محلي، عبر npx serve أو python3 -m http.server، وافتحه على http://localhost.

انشر مباشرة من محادثة الذكاء الاصطناعي

اربط Solta بـ Claude أو ChatGPT واطلب ذلك: يُنشر الموقع برابط، دون نسخ أي كود.