suchen
HeimWeb-Frontendjs-TutorialErstellen eines benutzerdefinierten Schedulers mit React und Supabase

Einführung

Planung ist eines der entscheidenden Merkmale moderner Anwendungen. Es kann uns ermöglichen, regelmäßige Aufgaben auszuführen, die automatisiert werden können. Aufgaben wie das Versenden von Erinnerungen, das Planen von Beiträgen, das Aktualisieren von Daten oder das Automatisieren von Arbeitsabläufen.

In diesem Artikel werden wir einen Planer für die Veröffentlichung von Artikeln auf dev.to erstellen. Obwohl dev.to über Planungsfunktionen verfügt, werden wir sie auf unsere Weise implementieren, die zum Erstellen jeder Art von Planungsanwendung verwendet werden kann.

Also, fangen wir an.

Tech-Stack

Wir werden den folgenden Tech-Stack verwenden:

  • Reagieren: Wir werden React verwenden, insbesondere ViteJS mit React, um das Fronted zu erstellen.
  • Supabase: Es bietet eine Komplettlösung für die Erstellung von Anwendungen. Es bietet eine Datenbank, Authentifizierung, Speicher, Edge-Funktion und vieles mehr. Wir werden Folgendes von Supbase verwenden:
    • Datenbank: Hier werden die Artikelinformationen und die Terminplanung gespeichert.
    • Cron-Job: Zur regelmäßigen Ausführung, um die Edge-Funktion aufzurufen
    • Edge-Funktion: Dadurch wird überprüft, ob für einen Artikel die aktuelle Zeit als geplante Zeit festgelegt ist. Wenn dann, wird der Artikel veröffentlicht.

Das wird ausreichen, um problemlos eine Zeitplaneranwendung zu erstellen.

Arbeiten an der Bewerbung

Lassen Sie uns besprechen, wie die Anwendung funktioniert, wodurch es recht einfach wird, den Ablauf der Anwendung zu verstehen. Hier ist der Ablauf nacheinander:

  1. Artikel über das Frontend zur Datenbank hinzufügen.
  2. Der Cron-Job wird jede Minute ausgeführt, um die Edge-Funktion aufzurufen.
  3. Eine Randfunktion wird ausgeführt, um die aktuelle Zeit als geplanten Artikel zu überprüfen. Wenn es einen Artikel gibt, wird der Artikel veröffentlicht.
  4. Artikeldaten in der Beitragstabelle werden aktualisiert. # Aufbau des Frontends

Das Gebäude-Frontend ist in letzter Zeit ruhig geworden, da viel generative KI zum Einsatz kommt. Eine dieser KIs, die wir verwenden werden, ist Bolt.new. Warum Bolt.new? Es kann vollständige React-Anwendungen mit Abhängigkeiten und allen Konfigurationen wie tailwindcss generieren. Sie können Artikel direkt mit StackBlitz bearbeiten und die Anwendung auch bereitstellen. Bei Bedarf können Sie den Code herunterladen, um ihn lokal auszuführen. Der Bonuspunkt ist, dass es sich recht gut in Supabase integrieren lässt, sodass Sie mit der Supbase-Integration eine funktionierende React-Anwendung generieren können.

Ich habe es verwendet, um die Front zu generieren. Hier sind alle Seiten.

App.tsx

Dadurch wird die Seite für die Anzeige von Komponenten und die Bereitstellung der Zielseite verwaltet.

    function App() {
      const [posts, setPosts] = useState<scheduledpost>([]);
      const handleSchedulePost = async (data: CreatePostData) => {
        // In a real app, this would make an API call to your edge function
        const newPost: ScheduledPost = {
          content: data.content,
          scheduled_time: data.scheduledTime,
          status: 'pending',
          title: data.title,
          tags: data.tags
        };
        const { error } = await supabase
      .from('scheduled_posts')
      .insert(newPost)
      if (error){
        alert(`Erorr: ${error}`)
        return
      }
        // setPosts((prev) => [...prev, newPost]);
      };
      const fetchScheduedPost = async () => {
        const { data, error } = await supabase
      .from('scheduled_posts')
      .select()
      if(error){
        alert(`Erorr Fetching Data: ${error}`)
        return
      }
      setPosts(data)
      } 
      useEffect(() => {
        fetchScheduedPost()
      },[])
      return (
        <div classname="min-h-screen bg-gray-50">
          <header classname="bg-white shadow-sm">
            <div classname="max-w-4xl mx-auto px-4 py-4">
              <div classname="flex items-center gap-2">
                <newspaper classname="h-8 w-8 text-blue-500"></newspaper>
                <h1 id="Dev-to-Post-Scheduler">Dev.to Post Scheduler</h1>
              </div>
            </div>
          </header>
          <main classname="max-w-4xl mx-auto px-4 py-8">
            <div classname="grid gap-8 md:grid-cols-2">
              <div>
                <h2 id="Schedule-New-Post">Schedule New Post</h2>
                <postform onsubmit="{handleSchedulePost}"></postform>
              </div>
              <div>
                <scheduledposts posts="{posts}"></scheduledposts>
              </div>
            </div>
          </main>
        </div>
      );
    }
    export default App;
</scheduledpost>

SchudledPost.tsx

Hier werden die geplanten Artikel angezeigt.

    const StatusIcon = ({ status }: { status: ScheduledPost['status'] }) => {
      switch (status) {
        case 'posted':
          return <checkcircle classname="h-5 w-5 text-green-500"></checkcircle>;
        case 'failed':
          return <xcircle classname="h-5 w-5 text-red-500"></xcircle>;
        default:
          return <clock3 classname="h-5 w-5 text-yellow-500"></clock3>;
      }
    };
    export function ScheduledPosts({ posts }: ScheduledPostsProps) {
      return (
        <div classname="space-y-4">
          <h2 id="Scheduled-Posts">Scheduled Posts</h2>
          {posts.length === 0 ? (
            <p classname="text-gray-500 text-center py-8">No scheduled posts yet</p>
          ) : (
            <div classname="space-y-4">
              {posts.map((post, index) => (
                <div key="{index}" classname="bg-white p-4 rounded-lg shadow-md border border-gray-100">
                  <div classname="flex items-start justify-between">
                    <div classname="flex-1">
                      <p classname="text-gray-800 mb-2">{post.title}</p>
                      <div classname="flex items-center gap-4 text-sm text-gray-500">
                        <div classname="flex items-center gap-1">
                          <calendar classname="h-4 w-4"></calendar>
                          {new Date(post.scheduled_time).toLocaleDateString()}
                        </div>
                        <div classname="flex items-center gap-1">
                          <clock classname="h-4 w-4"></clock>
                          {new Date(post.scheduled_time).toLocaleTimeString()}
                        </div>
                      </div>
                    </div>
                    <statusicon status="{post.status}"></statusicon>
                  </div>
                </div>
              ))}
            </div>
          )}
        </div>
      );
    }

PostForm.tsx

Hiermit wird das Formular verwaltet, in dem der Benutzer Informationen zum Artikel angeben kann.

    function App() {
      const [posts, setPosts] = useState<scheduledpost>([]);
      const handleSchedulePost = async (data: CreatePostData) => {
        // In a real app, this would make an API call to your edge function
        const newPost: ScheduledPost = {
          content: data.content,
          scheduled_time: data.scheduledTime,
          status: 'pending',
          title: data.title,
          tags: data.tags
        };
        const { error } = await supabase
      .from('scheduled_posts')
      .insert(newPost)
      if (error){
        alert(`Erorr: ${error}`)
        return
      }
        // setPosts((prev) => [...prev, newPost]);
      };
      const fetchScheduedPost = async () => {
        const { data, error } = await supabase
      .from('scheduled_posts')
      .select()
      if(error){
        alert(`Erorr Fetching Data: ${error}`)
        return
      }
      setPosts(data)
      } 
      useEffect(() => {
        fetchScheduedPost()
      },[])
      return (
        <div classname="min-h-screen bg-gray-50">
          <header classname="bg-white shadow-sm">
            <div classname="max-w-4xl mx-auto px-4 py-4">
              <div classname="flex items-center gap-2">
                <newspaper classname="h-8 w-8 text-blue-500"></newspaper>
                <h1 id="Dev-to-Post-Scheduler">Dev.to Post Scheduler</h1>
              </div>
            </div>
          </header>
          <main classname="max-w-4xl mx-auto px-4 py-8">
            <div classname="grid gap-8 md:grid-cols-2">
              <div>
                <h2 id="Schedule-New-Post">Schedule New Post</h2>
                <postform onsubmit="{handleSchedulePost}"></postform>
              </div>
              <div>
                <scheduledposts posts="{posts}"></scheduledposts>
              </div>
            </div>
          </main>
        </div>
      );
    }
    export default App;
</scheduledpost>

Kantenfunktion

Edge-Funktionen sind serverseitige TypeScript-Funktionen, die global am Edge verteilt werden – in der Nähe Ihrer Benutzer. Sie können zum Abhören von Webhooks oder zur Integration Ihres Supabase-Projekts mit Drittanbietern wie Stripe verwendet werden. Kantenfunktionen werden mit Deno entwickelt.

Um die Edge-Funktion lokal ausführen und bereitstellen zu können, benötigen Sie Folgendes:

  • Supbase CLI: Mit dieser Anleitung können Sie CLI lokal installieren. Es ist ganz einfach, einfach npm und npx zu verwenden.
  • Docker Desktop: Installieren Sie den Docker-Desktop von hier aus.

Nach der Installation können Sie also Ihr Frontend-Codeverzeichnis oder ein anderes verwenden, um die Supabase Edge-Funktion zu erstellen.

Führen Sie den folgenden Befehl aus, um ein Supabase-Projekt zu starten:

    const StatusIcon = ({ status }: { status: ScheduledPost['status'] }) => {
      switch (status) {
        case 'posted':
          return <checkcircle classname="h-5 w-5 text-green-500"></checkcircle>;
        case 'failed':
          return <xcircle classname="h-5 w-5 text-red-500"></xcircle>;
        default:
          return <clock3 classname="h-5 w-5 text-yellow-500"></clock3>;
      }
    };
    export function ScheduledPosts({ posts }: ScheduledPostsProps) {
      return (
        <div classname="space-y-4">
          <h2 id="Scheduled-Posts">Scheduled Posts</h2>
          {posts.length === 0 ? (
            <p classname="text-gray-500 text-center py-8">No scheduled posts yet</p>
          ) : (
            <div classname="space-y-4">
              {posts.map((post, index) => (
                <div key="{index}" classname="bg-white p-4 rounded-lg shadow-md border border-gray-100">
                  <div classname="flex items-start justify-between">
                    <div classname="flex-1">
                      <p classname="text-gray-800 mb-2">{post.title}</p>
                      <div classname="flex items-center gap-4 text-sm text-gray-500">
                        <div classname="flex items-center gap-1">
                          <calendar classname="h-4 w-4"></calendar>
                          {new Date(post.scheduled_time).toLocaleDateString()}
                        </div>
                        <div classname="flex items-center gap-1">
                          <clock classname="h-4 w-4"></clock>
                          {new Date(post.scheduled_time).toLocaleTimeString()}
                        </div>
                      </div>
                    </div>
                    <statusicon status="{post.status}"></statusicon>
                  </div>
                </div>
              ))}
            </div>
          )}
        </div>
      );
    }

Mit dem folgenden Befehl kann die Edge-Funktion erstellt werden

    export function PostForm({ onSubmit }: PostFormProps) {
      const [content, setContent] = useState('');
      const [title, setTitle] = useState('');
      const [tags, setTags] = useState<string>(['javascript', 'react']);
      const [scheduledTime, setScheduledTime] = useState('');
      const handleSubmit = async (e: React.FormEvent) => {
        e.preventDefault();
        onSubmit({ content, title, scheduledTime, tags });
        setContent('');
        setTitle('');
        setScheduledTime('');
        setTags([]);
      };
      const handleTagChange = (e: React.ChangeEvent<htmlselectelement>) => {
        const selectedOptions = Array.from(e.target.selectedOptions);
        const selectedTags = selectedOptions.map(option => option.value);
        if(tags.length {
          const newTags = selectedTags.filter(tag => !prevTags.includes(tag));
          return [...prevTags, ...newTags];
        });
        }

      };
      const removeTag = (tagToRemove: string) => {
        setTags(tags.filter(tag => tag !== tagToRemove));
      };
      return (
        <form onsubmit="{handleSubmit}" classname="space-y-4 bg-white p-6 rounded-lg shadow-md">
          <div>
            <label htmlfor="title" classname="block text-sm font-medium text-gray-700 mb-2">
              Post Title
            </label>
            <input type="text">



<p>I will provide the whole code as a GitHub repository at the end. </p>

<p>Now, let’s look at Supbase Integration.</p>

<h2>
  
  
  Supabase
</h2>

<p>First create an account on supabase, if you don’t have one. You can look at this article to get information about the creating an account on Supbase, Using ChatGPT with Your Own Data using LangChain and Supabase.</p>

<p>Create the table scheduled_post. You can use the below SQL code to run in the SQL Editor to create the table or you can create the table with Table Editor.<br>
</p>

<pre class="brush:php;toolbar:false">    create table
      public.scheduled_posts (
        id serial not null,
        content text not null,
        scheduled_time timestamp with time zone not null,
        status text null default 'pending'::text,
        created_at timestamp without time zone null default now(),
        title character varying null,
        devto_article_id character varying null,
        posted_at character varying null,
        tags character varying[] null,
        error_message character varying null,
        constraint scheduled_posts_pkey primary key (id)
      ) tablespace pg_default;
    create index if not exists idx_scheduled_time_status on public.scheduled_posts using btree (scheduled_time, status) tablespace pg_default;

Der obige Befehl erstellt ein Verzeichnis „functions/xscheduler“ innerhalb der Supabase. Dort finden Sie die index.ts. Die Edge-Funktion nutzt die Deno-Umgebung.

Der folgende Code gilt für die Kantenfunktion:

    npx supabase init

Für die ENV stehen Ihnen automatisch SUPABASE_URL und SUPABASE_SERVICE_ROLE_KEY zur Verfügung. Für DEVTO_ACCESS_TOKEN können Sie es hier generieren und zu Projekteinstellungen → Kantenfunktionen gehen, um das Token hinzuzufügen. Dieser Token wird in der Deno-Umgebung verfügbar sein.

Sie können diese Anleitung für die Bereitstellung der benötigten Edge-Funktion verwenden.

Cron-Job

Supbase hat kürzlich die Cron-Job-Funktionalität aktualisiert. Jetzt können Sie das Dashboard verwenden, um den Maisjob zu erstellen, zuvor mussten Sie dafür Code schreiben. Sie können einen Job erstellen, der Folgendes ausführen kann:

  • SQL-Snippet
  • Datenbankfunktion
  • HTTP-Anfrage
  • Supbase-Kantenfunktion

Wir werden die Edge-Funktion verwenden. Sie können die Details der Edge-Funktion wie Name und Autorisierung mit dem Anon-Schlüssel als Bearer-Token hinzufügen.

Building a Custom Scheduler Using React and Supabase

Funktionsweise der Anwendung

Da wir nun die Anwendung erstellt haben, werfen wir einen Blick auf die Funktionsweise. Führen Sie fronted mit dem folgenden Befehl aus:

    supabase functions new xscheduler

Building a Custom Scheduler Using React and Supabase

Fügen Sie Details wie Titel, Inhalt, Zeit und Tags hinzu. Klicken Sie nach dem Hinzufügen auf „Beitrag planen“. Der Cron-Job wird jede Minute ausgeführt, sobald die geplante Zeit des Artikels mit der aktuellen Zeit übereinstimmt. Es wird veröffentlicht.

Der Artikel wird auf dev.to veröffentlicht, wenn der Zeitraum übereinstimmt.

Building a Custom Scheduler Using React and Supabase

Zusätzliche Funktionen

Mit der oben genannten Technik können Sie eine Planungsanwendung für alles wie X, Instagram, LinkedIn usw. erstellen. Sie können daran arbeiten und Funktionen wie die folgenden hinzufügen:

  • Bild: Verwenden Sie den Supabase-Speicher, um Bilder für Miniaturansichten hochzuladen und abzurufen.
  • Edge-Funktionsaufruf aus SQL: Sie können es noch effizienter gestalten, indem Sie die Edge-Funktion aus einem SQL-Snippet oder einer Datenbankfunktion aufrufen. Dadurch wird die Kantenfunktion nur dann aufgerufen, wenn der Artikel mit der aktuellen Zeit übereinstimmt.

Sie können sich den Code dieses Projekts hier auf GitHub ansehen.

Abschluss

Das Erstellen einer Planeranwendung vereinfacht die Automatisierung von Aufgaben wie dem Veröffentlichen von Artikeln, dem Versenden von Erinnerungen und dem Verwalten von Arbeitsabläufen. Mit React für das Frontend und Supabase für das Backend haben wir eine skalierbare Lösung erstellt, die Datenbanken, Cron-Jobs und Edge-Funktionen nutzt. Dieser Ansatz kann für verschiedene Anwendungsfälle angepasst werden und ermöglicht so eine effiziente Automatisierung. Mit diesen Tools sind Sie in der Lage, leistungsstarke Planungsanwendungen zu erstellen, die auf Ihre Bedürfnisse zugeschnitten sind.

Ich hoffe, dieser Artikel hat Ihnen ein Verständnis für den Cron-Job vermittelt. Vielen Dank, dass Sie den Artikel gelesen haben.

Das obige ist der detaillierte Inhalt vonErstellen eines benutzerdefinierten Schedulers mit React und Supabase. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn
JavaScript -Datentypen: Gibt es einen Unterschied zwischen Browser und NodeJs?JavaScript -Datentypen: Gibt es einen Unterschied zwischen Browser und NodeJs?May 14, 2025 am 12:15 AM

JavaScript -Kerndatentypen sind in Browsern und Knoten.js konsistent, werden jedoch unterschiedlich als die zusätzlichen Typen behandelt. 1) Das globale Objekt ist ein Fenster im Browser und global in node.js. 2) Node.js 'eindeutiges Pufferobjekt, das zur Verarbeitung von Binärdaten verwendet wird. 3) Es gibt auch Unterschiede in der Leistung und Zeitverarbeitung, und der Code muss entsprechend der Umgebung angepasst werden.

JavaScript -Kommentare: Eine Anleitung zur Verwendung // und / * * /JavaScript -Kommentare: Eine Anleitung zur Verwendung // und / * * /May 13, 2025 pm 03:49 PM

JavaScriptUSESTWOTYPESOFCOMMENMENTEN: Einzelzeilen (//) und Multi-Linie (//). 1) Verwendung // Forquicknotesorsingle-Linexplanationen.2 Verwendung // ForlongerExPlanationsCompomentingingoutblocks-

Python gegen JavaScript: Eine vergleichende Analyse für EntwicklerPython gegen JavaScript: Eine vergleichende Analyse für EntwicklerMay 09, 2025 am 12:22 AM

Der Hauptunterschied zwischen Python und JavaScript sind die Typ -System- und Anwendungsszenarien. 1. Python verwendet dynamische Typen, die für wissenschaftliche Computer- und Datenanalysen geeignet sind. 2. JavaScript nimmt schwache Typen an und wird in Front-End- und Full-Stack-Entwicklung weit verbreitet. Die beiden haben ihre eigenen Vorteile bei der asynchronen Programmierung und Leistungsoptimierung und sollten bei der Auswahl gemäß den Projektanforderungen entschieden werden.

Python vs. JavaScript: Auswählen des richtigen Tools für den JobPython vs. JavaScript: Auswählen des richtigen Tools für den JobMay 08, 2025 am 12:10 AM

Ob die Auswahl von Python oder JavaScript vom Projekttyp abhängt: 1) Wählen Sie Python für Datenwissenschafts- und Automatisierungsaufgaben aus; 2) Wählen Sie JavaScript für die Entwicklung von Front-End- und Full-Stack-Entwicklung. Python ist für seine leistungsstarke Bibliothek in der Datenverarbeitung und -automatisierung bevorzugt, während JavaScript für seine Vorteile in Bezug auf Webinteraktion und Full-Stack-Entwicklung unverzichtbar ist.

Python und JavaScript: Verständnis der Stärken der einzelnenPython und JavaScript: Verständnis der Stärken der einzelnenMay 06, 2025 am 12:15 AM

Python und JavaScript haben jeweils ihre eigenen Vorteile, und die Wahl hängt von den Projektbedürfnissen und persönlichen Vorlieben ab. 1. Python ist leicht zu erlernen, mit prägnanter Syntax, die für Datenwissenschaft und Back-End-Entwicklung geeignet ist, aber eine langsame Ausführungsgeschwindigkeit hat. 2. JavaScript ist überall in der Front-End-Entwicklung und verfügt über starke asynchrone Programmierfunktionen. Node.js macht es für die Entwicklung der Vollstapel geeignet, die Syntax kann jedoch komplex und fehleranfällig sein.

JavaScripts Kern: Ist es auf C oder C aufgebaut?JavaScripts Kern: Ist es auf C oder C aufgebaut?May 05, 2025 am 12:07 AM

JavaScriptisnotbuiltoncorc; Es ist angehört, dass sich JavaScriptWasdedeSthatrunsonGineoFtencninc.

JavaScript-Anwendungen: Von Front-End bis Back-EndJavaScript-Anwendungen: Von Front-End bis Back-EndMay 04, 2025 am 12:12 AM

JavaScript kann für die Entwicklung von Front-End- und Back-End-Entwicklung verwendet werden. Das Front-End verbessert die Benutzererfahrung durch DOM-Operationen, und die Back-End-Serveraufgaben über node.js. 1. Beispiel für Front-End: Ändern Sie den Inhalt des Webseitentextes. 2. Backend Beispiel: Erstellen Sie einen Node.js -Server.

Python vs. JavaScript: Welche Sprache sollten Sie lernen?Python vs. JavaScript: Welche Sprache sollten Sie lernen?May 03, 2025 am 12:10 AM

Die Auswahl von Python oder JavaScript sollte auf Karriereentwicklung, Lernkurve und Ökosystem beruhen: 1) Karriereentwicklung: Python ist für die Entwicklung von Datenwissenschaften und Back-End-Entwicklung geeignet, während JavaScript für die Entwicklung von Front-End- und Full-Stack-Entwicklung geeignet ist. 2) Lernkurve: Die Python -Syntax ist prägnant und für Anfänger geeignet; Die JavaScript -Syntax ist flexibel. 3) Ökosystem: Python hat reichhaltige wissenschaftliche Computerbibliotheken und JavaScript hat ein leistungsstarkes Front-End-Framework.

See all articles

Heiße KI -Werkzeuge

Undresser.AI Undress

Undresser.AI Undress

KI-gestützte App zum Erstellen realistischer Aktfotos

AI Clothes Remover

AI Clothes Remover

Online-KI-Tool zum Entfernen von Kleidung aus Fotos.

Undress AI Tool

Undress AI Tool

Ausziehbilder kostenlos

Clothoff.io

Clothoff.io

KI-Kleiderentferner

Video Face Swap

Video Face Swap

Tauschen Sie Gesichter in jedem Video mühelos mit unserem völlig kostenlosen KI-Gesichtstausch-Tool aus!

Heißer Artikel

Nordhold: Fusionssystem, erklärt
4 Wochen vorBy尊渡假赌尊渡假赌尊渡假赌
Mandragora: Flüstern des Hexenbaum
3 Wochen vorBy尊渡假赌尊渡假赌尊渡假赌

Heiße Werkzeuge

SublimeText3 Englische Version

SublimeText3 Englische Version

Empfohlen: Win-Version, unterstützt Code-Eingabeaufforderungen!

PHPStorm Mac-Version

PHPStorm Mac-Version

Das neueste (2018.2.1) professionelle, integrierte PHP-Entwicklungstool

SAP NetWeaver Server-Adapter für Eclipse

SAP NetWeaver Server-Adapter für Eclipse

Integrieren Sie Eclipse mit dem SAP NetWeaver-Anwendungsserver.

Sicherer Prüfungsbrowser

Sicherer Prüfungsbrowser

Safe Exam Browser ist eine sichere Browserumgebung für die sichere Teilnahme an Online-Prüfungen. Diese Software verwandelt jeden Computer in einen sicheren Arbeitsplatz. Es kontrolliert den Zugriff auf alle Dienstprogramme und verhindert, dass Schüler nicht autorisierte Ressourcen nutzen.

WebStorm-Mac-Version

WebStorm-Mac-Version

Nützliche JavaScript-Entwicklungstools