<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: jean-smaug</title>
    <description>The latest articles on DEV Community by jean-smaug (@jeansmaug).</description>
    <link>https://dev.to/jeansmaug</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F28646%2F15eea80b-1e57-4ed9-9f5c-99798df435f6.png</url>
      <title>DEV Community: jean-smaug</title>
      <link>https://dev.to/jeansmaug</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jeansmaug"/>
    <language>en</language>
    <item>
      <title>Version Control Your WordPress Code Snippets</title>
      <dc:creator>jean-smaug</dc:creator>
      <pubDate>Wed, 29 Jul 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/jeansmaug/version-control-your-wordpress-code-snippets-4e11</link>
      <guid>https://dev.to/jeansmaug/version-control-your-wordpress-code-snippets-4e11</guid>
      <description>&lt;p&gt;If you use &lt;a href="https://wordpress.org/plugins/code-snippets/" rel="noopener noreferrer"&gt;Code Snippets&lt;/a&gt; or &lt;a href="https://wpcode.com/" rel="noopener noreferrer"&gt;WPCode&lt;/a&gt;, you’re probably editing your PHP functions directly in the admin panel. Click the snippet, make the change, save. Done.&lt;/p&gt;

&lt;p&gt;It works. Until it doesn’t.&lt;/p&gt;

&lt;p&gt;Someone edits a snippet and breaks something. You don’t know what changed. There’s no history. You either remember what it looked like before, or you don’t. There’s no &lt;code&gt;git log&lt;/code&gt;, no diff, no blame.&lt;/p&gt;

&lt;h2&gt;
  
  
  How people try to solve this today
&lt;/h2&gt;

&lt;p&gt;The current options aren’t great.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option 1: WPCodeBox or Code Snippets Cloud&lt;/strong&gt; These tools offer built-in versioning: you can see a history of changes and revert. It works, but it’s locked inside their platform. Your code history lives in their database, not in your repo, and it doesn’t integrate with your existing Git workflow.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option 2: Keeping snippets in your theme’s &lt;code&gt;functions.php&lt;/code&gt;&lt;/strong&gt; This at least puts the code in a file you can version. But it ties snippets to your theme, means they disappear on theme switch, and turns &lt;code&gt;functions.php&lt;/code&gt; into an unorganized dumping ground.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option 3: A custom plugin&lt;/strong&gt; Some agencies build a site-specific plugin to hold all their custom code. This is better: it’s version-controlled and theme-independent. But it requires setup for every project and doesn’t give you a clean way to sync between environments.&lt;/p&gt;

&lt;p&gt;None of these feel like a real developer workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Loopress approach
&lt;/h2&gt;

&lt;p&gt;Loopress treats your snippets as plain files in a Git repository. Pull them down from WordPress, edit them locally, push them back. Every change is a commit.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setup
&lt;/h3&gt;

&lt;p&gt;Install the Loopress CLI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; @loopress/cli

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Configure your project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
lps project config

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It asks for a project name, an environment (production, staging, local, or a custom name), and your WordPress URL. By default it then opens your browser to authorize and creates an Application Password for you automatically, no copy-pasting a generated password. If that flow can’t complete, it falls back to asking for a WordPress username and an Application Password you generate yourself.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pull your snippets
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
lps snippet pull

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates a &lt;code&gt;snippets/&lt;/code&gt; directory with one &lt;code&gt;.php&lt;/code&gt; file and one &lt;code&gt;.json&lt;/code&gt; sidecar per snippet:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;snippets/

  1-my-custom-function.php

  1-my-custom-function.json

  2-woocommerce-checkout-tweak.php

  2-woocommerce-checkout-tweak.json

  3-disable-gutenberg.php

  3-disable-gutenberg.json

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each &lt;code&gt;.php&lt;/code&gt; file contains the raw snippet code. The &lt;code&gt;.json&lt;/code&gt; sidecar stores the metadata (id, name, type, active status, tags) that Loopress uses to identify and update the snippet on WordPress.&lt;/p&gt;

&lt;h3&gt;
  
  
  Edit locally
&lt;/h3&gt;

&lt;p&gt;Open any file in your editor. Make changes. The feedback loop is your local environment, not the WordPress admin panel.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
git add snippets/2-woocommerce-checkout-tweak.php

git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"fix: apply discount code before tax calculation"&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you have a commit. A diff. A message explaining why. This is reviewable, reversible, and shareable.&lt;/p&gt;

&lt;h3&gt;
  
  
  Push back to WordPress
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
lps snippet push

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Loopress syncs your local files back to WordPress via the REST API. No FTP, no copy-paste.&lt;/p&gt;

&lt;p&gt;If you want to see what would change without actually changing anything:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
lps snippet push &lt;span class="nt"&gt;--dry-run&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Working across environments
&lt;/h2&gt;

&lt;p&gt;The real value shows up when you’re working with local, staging, and production environments. Register each one once with &lt;code&gt;lps project config&lt;/code&gt;, then target any of them with &lt;code&gt;--env&lt;/code&gt; on a single command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Pull from production&lt;/span&gt;

lps snippet pull &lt;span class="nt"&gt;--env&lt;/span&gt; production

&lt;span class="c"&gt;# Test locally, commit changes&lt;/span&gt;

&lt;span class="c"&gt;# Push to staging first&lt;/span&gt;

lps snippet push &lt;span class="nt"&gt;--env&lt;/span&gt; staging

&lt;span class="c"&gt;# When ready, push to production&lt;/span&gt;

lps snippet push &lt;span class="nt"&gt;--env&lt;/span&gt; production &lt;span class="nt"&gt;--yes&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That trailing &lt;code&gt;--yes&lt;/code&gt; isn’t optional flair: pushing to an environment literally named &lt;code&gt;production&lt;/code&gt; asks for confirmation in a terminal, and requires &lt;code&gt;--yes&lt;/code&gt; to skip that prompt outside one (CI, scripts). &lt;code&gt;--env&lt;/code&gt; also takes priority over whatever &lt;code&gt;lps project switch&lt;/code&gt; last left active globally, which matters the moment more than one person runs commands against the same project.&lt;/p&gt;

&lt;p&gt;Your snippets flow between environments the same way your code does. No database exports, no manual copy-paste.&lt;/p&gt;

&lt;h2&gt;
  
  
  One thing to keep in mind
&lt;/h2&gt;

&lt;p&gt;Loopress syncs snippets by ID (stored in the &lt;code&gt;.json&lt;/code&gt; sidecar). If you pull first, the ID is saved and subsequent pushes update the correct snippet regardless of name changes. Without a sidecar ID, Loopress creates a new snippet. Treat the files as the source of truth once you’re in this workflow.&lt;/p&gt;




&lt;p&gt;If this resonates, the next step is &lt;code&gt;lps project config&lt;/code&gt; and adding &lt;code&gt;snippets/&lt;/code&gt; to your project’s Git repository. From there, every snippet change is a commit.&lt;/p&gt;

</description>
      <category>codesnippets</category>
      <category>git</category>
      <category>wordpress</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Ship a Custom WordPress Endpoint Without Writing a Plugin</title>
      <dc:creator>jean-smaug</dc:creator>
      <pubDate>Mon, 27 Jul 2026 07:02:40 +0000</pubDate>
      <link>https://dev.to/jeansmaug/ship-a-custom-wordpress-endpoint-without-writing-a-plugin-24i1</link>
      <guid>https://dev.to/jeansmaug/ship-a-custom-wordpress-endpoint-without-writing-a-plugin-24i1</guid>
      <description>&lt;p&gt;Your JavaScript frontend needs a list of featured products from WordPress. Not the whole REST API surface, not raw post objects with forty fields, just the ten items the homepage carousel shows, shaped the way the frontend wants them.&lt;/p&gt;

&lt;p&gt;The endpoint is ten minutes of work: query, map, return. The question that eats the rest of the afternoon: where does that code go?&lt;/p&gt;

&lt;h2&gt;
  
  
  Where custom endpoints live today
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;functions.php&lt;/code&gt;.&lt;/strong&gt; Fastest to write, tied to your theme. Switch themes, lose the endpoint. Two years later it's line 1400 of a file nobody wants to scroll.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A site-specific plugin.&lt;/strong&gt; The clean classic answer. But now your ten minutes of logic come wrapped in a plugin header, a hook, and a registration array:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nf"&gt;add_action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'rest_api_init'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;register_rest_route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'acme/v1'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'/featured-products'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s1"&gt;'methods'&lt;/span&gt;             &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'GET'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'callback'&lt;/span&gt;            &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'acme_featured_products'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'permission_callback'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'__return_true'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And you still need a way to get that plugin onto the site. And onto staging. And to know which version runs where.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A snippets plugin.&lt;/strong&gt; Deployed in seconds, versioned never. The code lives in the database, edited through an admin textarea, invisible to Git and code review.&lt;/p&gt;

&lt;p&gt;The endpoint logic is never the problem. The delivery vehicle is.&lt;/p&gt;

&lt;h2&gt;
  
  
  One file, one route
&lt;/h2&gt;

&lt;p&gt;With &lt;a href="https://docs.loopress.dev/wordpress-plugin/" rel="noopener noreferrer"&gt;Loopress Full&lt;/a&gt; installed, a custom endpoint is a single PHP file in your repo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="k"&gt;declare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;strict_types&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;FeaturedProducts&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;WP_Query&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
            &lt;span class="s1"&gt;'post_type'&lt;/span&gt;      &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'product'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'meta_key'&lt;/span&gt;       &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'featured'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'meta_value'&lt;/span&gt;     &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'1'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'posts_per_page'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;]);&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;array_map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;WP_Post&lt;/span&gt; &lt;span class="nv"&gt;$post&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="s1"&gt;'id'&lt;/span&gt;    &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$post&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="no"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'title'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$post&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;post_title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'url'&lt;/span&gt;   &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;get_permalink&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$post&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Save it as &lt;code&gt;api/featured-products.php&lt;/code&gt; and deploy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;lps api push
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The conventions do all the wiring you saw above. The filename is the route. The class name is the filename in PascalCase. Each public method named after an HTTP verb (&lt;code&gt;get&lt;/code&gt;, &lt;code&gt;post&lt;/code&gt;, &lt;code&gt;put&lt;/code&gt;, &lt;code&gt;patch&lt;/code&gt;, &lt;code&gt;delete&lt;/code&gt;) becomes that verb's handler. No registration code, no plugin header, no hook.&lt;/p&gt;

&lt;h2&gt;
  
  
  Now call it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl https://your-site.com/wp-json/loopress-api/v1/featured-products
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"rest_forbidden"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"Sorry, you are not allowed to do that."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;401&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That 401 is the feature. A fresh route requires an administrator, the same authentication the Loopress CLI itself uses. Nothing you push is publicly reachable by accident.&lt;/p&gt;

&lt;p&gt;For an endpoint another system calls on your behalf, a reporting script, an internal dashboard, a cron job, that default is already the right answer: the caller authenticates with an &lt;a href="https://docs.loopress.dev/application-passwords/" rel="noopener noreferrer"&gt;application password&lt;/a&gt; over Basic auth, and you have a protected endpoint without writing a single line of security code.&lt;/p&gt;

&lt;p&gt;This feed, though, is meant for the browser. Making it public is a decision, so it's expressed where decisions belong, in the file, in the diff, in code review:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;permission&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;callable&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl https://your-site.com/wp-json/loopress-api/v1/featured-products
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;231&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"Weekender Backpack"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"https://your-site.com/product/weekender-backpack"&lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One greppable line. A reviewer seeing this diff knows exactly what's being opened to the world, which is not something you can say about a checkbox in an admin screen. The same &lt;code&gt;permission()&lt;/code&gt; method takes any callable, so routes that need their own rules, logged-in users only, a capability check, a signed webhook, express them the same way (the &lt;a href="https://docs.loopress.dev/api/routes/" rel="noopener noreferrer"&gt;docs&lt;/a&gt; cover those patterns).&lt;/p&gt;

&lt;p&gt;The frontend lives on another domain? Declare CORS headers in the file too, they apply to every request, preflight included:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'Access-Control-Allow-Origin'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'https://app.example.com'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Need an HTTP client or an SDK in a route? If the site uses &lt;a href="https://docs.loopress.dev/composer/" rel="noopener noreferrer"&gt;Loopress's Composer integration&lt;/a&gt;, your packages are directly available to &lt;code&gt;use&lt;/code&gt; in route files. No manual autoloader loading.&lt;/p&gt;

&lt;h2&gt;
  
  
  Built to not break your site
&lt;/h2&gt;

&lt;p&gt;Deployed code that runs inside WordPress has one non-negotiable requirement: a mistake must stay contained.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;lps api push&lt;/code&gt; syntax-checks every file server-side and rejects broken PHP with the actual parse error, before anything is written.&lt;/li&gt;
&lt;li&gt;A file that still fails at runtime (a class collision with another plugin, a throwing dependency) is skipped and logged. Every other route, and the rest of the site's REST API, keeps working.&lt;/li&gt;
&lt;li&gt;Files are protected from direct HTTP access and written atomically. Pull them back and you get exactly the source you wrote.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Endpoints your repository declares
&lt;/h2&gt;

&lt;p&gt;Because routes are plain files, everything you already do with code applies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;lps api pull                  &lt;span class="c"&gt;# mirror what's on the site locally&lt;/span&gt;
git checkout &lt;span class="nt"&gt;-b&lt;/span&gt; add-feed      &lt;span class="c"&gt;# branch, edit, review&lt;/span&gt;
lps api push                  &lt;span class="c"&gt;# deploy&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Different environments? Push the same files to staging first, then production, like the rest of your Loopress-managed configuration. Endpoints stop being something you set up on a site and become something your repository declares.&lt;/p&gt;




&lt;p&gt;Custom API Routes ship with Loopress Full, the free full edition of the plugin:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; @loopress/cli
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;a href="https://docs.loopress.dev/api/" rel="noopener noreferrer"&gt;documentation&lt;/a&gt; covers the complete file reference: request handling, response types, authentication, CORS, namespaces, and the security model.&lt;/p&gt;

</description>
      <category>wordpress</category>
      <category>restapi</category>
      <category>git</category>
      <category>loopress</category>
    </item>
    <item>
      <title>Installing Composer Packages in WordPress Without SSH</title>
      <dc:creator>jean-smaug</dc:creator>
      <pubDate>Fri, 24 Jul 2026 10:59:17 +0000</pubDate>
      <link>https://dev.to/jeansmaug/installing-composer-packages-in-wordpress-without-ssh-5dmk</link>
      <guid>https://dev.to/jeansmaug/installing-composer-packages-in-wordpress-without-ssh-5dmk</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Cross-posted from the Loopress docs&lt;br&gt;
Loopress is a toolset to &lt;strong&gt;make WordPress reproducible and reviewable via Git&lt;/strong&gt;. Versioned snippets, Composer without SSH, and more coming...&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://loopress.dev" class="crayons-btn crayons-btn--primary" rel="noopener noreferrer"&gt;Check Loopress&lt;/a&gt;
&lt;/p&gt;




&lt;p&gt;You need to add a PHP dependency to a WordPress site. Maybe a PDF generation library, a CSV parser, an image manipulation package. Something available on Packagist.&lt;/p&gt;

&lt;p&gt;Here's what the standard workflow looks like.&lt;/p&gt;

&lt;h2&gt;
  
  
  The current workflow
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;If you're on managed hosting (most clients are):&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Check if SSH is available (often it isn't, or it requires upgrading the hosting plan)&lt;/li&gt;
&lt;li&gt;If SSH is available: connect, check if Composer is installed, install it if not&lt;/li&gt;
&lt;li&gt;Navigate to the WordPress root&lt;/li&gt;
&lt;li&gt;Initialize &lt;code&gt;composer.json&lt;/code&gt; if it doesn't exist&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;composer require vendor/package&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Hope the server's PHP version matches your local environment&lt;/li&gt;
&lt;li&gt;Hope the &lt;code&gt;vendor/&lt;/code&gt; directory ends up in the right place&lt;/li&gt;
&lt;li&gt;Repeat for every site, every environment&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is assuming nothing goes wrong. In practice, you'll hit permission issues, PHP version mismatches, or a host that simply blocks Composer from running.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The workaround people actually use:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Run Composer locally, then upload the &lt;code&gt;vendor/&lt;/code&gt; directory to the server via FTP or rsync. This works but means you're manually syncing binary files, and the next developer who needs to add a dependency starts from scratch figuring out where everything lives.&lt;/p&gt;

&lt;p&gt;Neither option is good.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Loopress does differently
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Loopress Full&lt;/strong&gt; adds a &lt;strong&gt;Dependency Management&lt;/strong&gt; panel to your WordPress admin. It talks to Packagist, runs Composer server-side through a sandboxed process, and handles the install without you needing a terminal or SSH access.&lt;/p&gt;

&lt;p&gt;This is Loopress Full specifically, not Loopress Light (the free edition on wordpress.org). wordpress.org's plugin guidelines don't allow a plugin to install executable code from a third-party registry like Packagist, so this feature ships separately, from &lt;a href="https://loopress.dev" rel="noopener noreferrer"&gt;loopress.dev&lt;/a&gt;, rather than the official directory. &lt;a href="https://dev.to/blog/wordpress-org-composer-rejection/"&gt;Here's why&lt;/a&gt;, if you're curious.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/loopress/loopress/releases" class="crayons-btn crayons-btn--primary" rel="noopener noreferrer"&gt;Download Loopress Full&lt;/a&gt;
&lt;/p&gt;

&lt;h3&gt;
  
  
  Installing a package
&lt;/h3&gt;

&lt;p&gt;Here's what that looks like from the admin, in under a minute:&lt;/p&gt;


  &lt;p&gt;Your browser doesn't support HTML5 video. &lt;a href="https://github.com/loopress/loopress/raw/refs/heads/main/documentation/src/content/videos/composer-install-admin-ui.webm" rel="noopener noreferrer"&gt;Download the video&lt;/a&gt; instead.&lt;/p&gt;


&lt;ol&gt;
&lt;li&gt;Navigate to &lt;strong&gt;WordPress Admin → Loopress → Dependencies&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Search for a package by name (&lt;code&gt;guzzlehttp/guzzle&lt;/code&gt;, or whatever you need). Loopress queries Packagist directly and shows you available versions.&lt;/li&gt;
&lt;li&gt;Select the version, click &lt;strong&gt;Install&lt;/strong&gt;. Loopress runs &lt;code&gt;composer require&lt;/code&gt; on the server and installs the package into a managed &lt;code&gt;vendor/&lt;/code&gt; directory inside the plugin's scope.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Using it in a snippet
&lt;/h3&gt;

&lt;p&gt;Require the autoloader once, then use the package like any other Composer dependency. Here it is fetching and displaying data from an API in a code snippet:&lt;/p&gt;


  &lt;p&gt;Your browser doesn't support HTML5 video. &lt;a href="https://github.com/loopress/loopress/raw/refs/heads/main/documentation/src/content/videos/snippet-using-composer.webm" rel="noopener noreferrer"&gt;Download the video&lt;/a&gt; instead.&lt;/p&gt;




&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;require_once&lt;/span&gt; &lt;span class="no"&gt;WP_CONTENT_DIR&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;'/loopress/vendor/autoload.php'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;GuzzleHttp\Client&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;add_filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'the_content'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;in_the_loop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;is_main_query&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$content&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nv"&gt;$client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nv"&gt;$response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$client&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'https://jsonplaceholder.typicode.com/posts'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'query'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'_limit'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;]]);&lt;/span&gt;
    &lt;span class="nv"&gt;$posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;json_decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$response&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getBody&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getContents&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nv"&gt;$list&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;ul&amp;gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$posts&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$post&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$list&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;li style="color: red"&amp;gt;'&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nf"&gt;esc_html&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$post&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'title'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;/li&amp;gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nv"&gt;$list&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;/ul&amp;gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$content&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;$list&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Why this matters for agencies
&lt;/h3&gt;

&lt;p&gt;If you manage multiple client sites, the usual Composer workflow means either:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Maintaining SSH access to every server&lt;/li&gt;
&lt;li&gt;Teaching clients how to run terminal commands (not happening)&lt;/li&gt;
&lt;li&gt;Keeping a pile of FTP credentials and manually uploading &lt;code&gt;vendor/&lt;/code&gt; directories&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With Loopress, the dependency management surface moves into the WordPress admin. You can install, update, and remove packages from the same interface you use to manage everything else. No server access required.&lt;/p&gt;

&lt;h3&gt;
  
  
  Seeing what's installed
&lt;/h3&gt;

&lt;p&gt;The &lt;strong&gt;Dependencies&lt;/strong&gt; panel shows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;All installed packages with their current versions&lt;/li&gt;
&lt;li&gt;Available updates from Packagist&lt;/li&gt;
&lt;li&gt;A one-click update path&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the equivalent of &lt;code&gt;composer show&lt;/code&gt; and &lt;code&gt;composer outdated&lt;/code&gt;, surfaced in the UI.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Loopress doesn't replace
&lt;/h2&gt;

&lt;p&gt;Loopress's dependency management is designed for adding libraries to a running WordPress site, pulling in packages that your snippets or custom code need. It's not a replacement for a full Bedrock/Composer setup where WordPress itself is a Composer dependency.&lt;/p&gt;

&lt;p&gt;If you're building on &lt;a href="https://roots.io/bedrock/" rel="noopener noreferrer"&gt;Roots/Bedrock&lt;/a&gt;, you already have a proper Composer workflow and probably don't need this. Loopress fills the gap for the other 90% of WordPress sites that aren't running a full stack framework: sites on shared hosting, client sites where the server environment is opaque, or projects where "set up Bedrock" isn't a conversation you can have.&lt;/p&gt;




&lt;p&gt;Loopress Full is a free download, installed like any other plugin zip (&lt;strong&gt;Plugins → Add New → Upload Plugin&lt;/strong&gt;), no server configuration required. If you only need snippet sync, Loopress Light is the free edition submitted to the wordpress.org directory instead.&lt;/p&gt;

</description>
      <category>composer</category>
      <category>php</category>
      <category>wordpress</category>
      <category>loopress</category>
    </item>
    <item>
      <title>Why wordpress.org Won't Let You Install Composer Packages From a Plugin</title>
      <dc:creator>jean-smaug</dc:creator>
      <pubDate>Mon, 20 Jul 2026 06:55:14 +0000</pubDate>
      <link>https://dev.to/jeansmaug/why-wordpressorg-wont-let-you-install-composer-packages-from-a-plugin-3dhf</link>
      <guid>https://dev.to/jeansmaug/why-wordpressorg-wont-let-you-install-composer-packages-from-a-plugin-3dhf</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Cross-posted from the Loopress docs&lt;br&gt;
Loopress is a toolset to &lt;strong&gt;make WordPress reproducible and reviewable via Git&lt;/strong&gt;. Versioned snippets, Composer without SSH, and more coming...&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://loopress.dev" class="crayons-btn crayons-btn--primary" rel="noopener noreferrer"&gt;Check Loopress&lt;/a&gt;
&lt;/p&gt;




&lt;p&gt;WordPress doesn't have a package manager. If you want a PHP library in your project, be it Guzzle for HTTP calls or a PDF generation library in a snippet, you're either vendoring the code by hand or running Composer somewhere the WordPress admin can't see.&lt;/p&gt;

&lt;p&gt;We built a feature to fix that: a Composer UI inside the WordPress admin. Search Packagist, install a package, audit it for known vulnerabilities, all without SSH access (&lt;a href="https://dev.to/blog/composer-without-ssh/"&gt;full walkthrough here&lt;/a&gt;). Before shipping it, we asked the wordpress.org plugin review team whether it would be acceptable in the official directory.&lt;/p&gt;

&lt;p&gt;The answer was no.&lt;/p&gt;

&lt;h2&gt;
  
  
  The rule
&lt;/h2&gt;

&lt;p&gt;Here's the relevant line from Guideline 8 of the wordpress.org plugin directory:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Plugins may not send executable code via third-party systems."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Installing a PHP package from Packagist is, by definition, downloading executable code from a third party and writing it to disk. It doesn't matter that our plugin never calls the installed code's autoloader itself, that the user has to load it deliberately from their own snippet. The review team was clear: the indirection changes nothing. The mere presence of that capability is enough to trigger the rule, whether or not it's ever used. There's no folder you can hide it in that makes it acceptable, they weren't shy about saying that outright.&lt;/p&gt;

&lt;p&gt;If you want PHP dependencies in a plugin distributed on wordpress.org, the only accepted path is to vendor them at submission time: ship the code, with a compatible license and readable source, not fetch it dynamically.&lt;/p&gt;

&lt;p&gt;For us, that meant Composer package management could never live in the same plugin that ships on wordpress.org. Full stop, no clever workaround changes that.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we tried first, and undid
&lt;/h2&gt;

&lt;p&gt;Our first move was the obvious one: split into two plugins. A "Core" plugin with snippet sync, distributed on wordpress.org, and a "Plus" plugin with Composer management, distributed separately, with the two detecting each other at runtime.&lt;/p&gt;

&lt;p&gt;We built it. Two npm packages, two &lt;code&gt;composer.json&lt;/code&gt; files, two QA configs, two release pipelines, a filter contract between them so Plus could register itself with Core. Tests passed on both. Then we looked at what we'd actually built and reverted the whole thing the same day.&lt;/p&gt;

&lt;p&gt;The duplication cost more than the split saved us. Every config file, every CI job, every README existed twice for no product reason. Worse, one version of the idea we considered, Core dynamically loading Plus's code, would have reintroduced the exact pattern the review team had just told us was the problem. That one wasn't even a contender once we thought it through.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we shipped instead
&lt;/h2&gt;

&lt;p&gt;One codebase. Two build artifacts.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;wordpress-plugin/
├── src/
│   ├── Module/, Service/, RestApi/    snippet sync, shared by both editions
│   └── Dependencies/                  all Composer code, this directory
│                                      is physically absent from the
│                                      wordpress.org build, not just inactive
├── frontend/
│   ├── full/    → App.tsx             Composer UI lives here
│   └── light/   → LightApp.tsx        never imports anything from Dependencies/
└── scripts/build-flavor.cjs           assembles each edition and zips it
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;pnpm pack:light&lt;/code&gt; produces &lt;strong&gt;Loopress Light&lt;/strong&gt;: snippet sync only, no Composer code anywhere in the zip, not dead code, not a disabled feature flag, physically not present. &lt;code&gt;composer.json&lt;/code&gt; in that build ships with an empty &lt;code&gt;require&lt;/code&gt;. A CI step unzips the artifact after the build and greps it for Composer residue, because trusting the build script to get this right isn't good enough when the whole point is a compliance boundary.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;pnpm pack:full&lt;/code&gt; produces &lt;strong&gt;Loopress Full&lt;/strong&gt;: everything, including the package installer, the security audit, and CLI sync for your &lt;code&gt;composer.json&lt;/code&gt;/&lt;code&gt;composer.lock&lt;/code&gt;. It's distributed from our own site, never wordpress.org, and its plugin header sets an &lt;code&gt;Update URI&lt;/code&gt; that stops wordpress.org from ever pushing an update over it by mistake. If Loopress Light is already active on a site, activating Loopress Full deactivates it. The two editions don't coexist. Full replaces Light, it doesn't sit next to it.&lt;/p&gt;

&lt;p&gt;This is the same model ACF and Elementor use for their free/pro split: one codebase, one build script, two artifacts with a real feature boundary between them, not a paywall drawn in the UI.&lt;/p&gt;

&lt;h2&gt;
  
  
  The honest tradeoff
&lt;/h2&gt;

&lt;p&gt;If you only need snippet sync, Loopress Light is a free wordpress.org install, same as any other plugin in the directory. If you want Composer packages managed from WordPress without SSH, that's Loopress Full (&lt;a href="https://dev.to/blog/composer-without-ssh/"&gt;see how it works&lt;/a&gt;), a separate download from our site, precisely because wordpress.org's own rules don't allow that capability to live there. We'd rather tell you that upfront than have you discover it after installing the wrong one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Thanks
&lt;/h2&gt;

&lt;p&gt;Credit where it's due: the wordpress.org plugins review team answered our question directly and quickly, before we'd submitted anything. That single clear answer, citing the exact guideline, saved us from finding out the hard way through a full submission and rejection cycle. Loopress Light is in review now, built around that answer from the start.&lt;/p&gt;

</description>
      <category>composer</category>
      <category>wordpress</category>
      <category>loopress</category>
      <category>opensource</category>
    </item>
    <item>
      <title>WordPress Has a DX Problem, and Developers Are Paying for It</title>
      <dc:creator>jean-smaug</dc:creator>
      <pubDate>Wed, 15 Jul 2026 17:34:49 +0000</pubDate>
      <link>https://dev.to/jeansmaug/wordpress-has-a-dx-problem-and-developers-are-paying-for-it-c9f</link>
      <guid>https://dev.to/jeansmaug/wordpress-has-a-dx-problem-and-developers-are-paying-for-it-c9f</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Cross-posted from the Loopress docs&lt;br&gt;
Loopress is a toolset to &lt;strong&gt;make WordPress reproducible and reviewable via Git&lt;/strong&gt;. Versioned snippets, Composer without SSH, and more coming...&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://loopress.dev" class="crayons-btn crayons-btn--primary" rel="noopener noreferrer"&gt;Check Loopress&lt;/a&gt;
&lt;/p&gt;




&lt;p&gt;WordPress is remarkable. It powers well over 40% of all websites, according to &lt;a href="https://w3techs.com/technologies/details/cm-wordpress" rel="noopener noreferrer"&gt;W3Techs&lt;/a&gt;. Your clients love it. Their non-technical staff can update content without calling you. The plugin ecosystem is enormous.&lt;/p&gt;

&lt;p&gt;But if you're a developer, the moment you sit down to &lt;em&gt;build&lt;/em&gt; with WordPress, something feels off.&lt;/p&gt;

&lt;p&gt;You're editing files directly on the server. You're copy-pasting snippets from the admin panel. You're hoping you remember what you changed last week. You're sending a colleague a zip file with the "latest version" of some PHP function.&lt;/p&gt;

&lt;p&gt;None of this is how modern software is built. And yet, it's the default WordPress workflow. This is the gap Loopress exists to close, and it's worth understanding exactly where the friction comes from before looking at how to fix it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is developer experience?
&lt;/h2&gt;

&lt;p&gt;Developer experience (DX) refers to how smooth (or painful) it is to work with a tool as a developer. Good DX means: fast feedback loops, predictable environments, version-controlled code, and reproducible deployments.&lt;/p&gt;

&lt;p&gt;Think of how you work with a modern JavaScript project:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Code lives in Git&lt;/li&gt;
&lt;li&gt;Dependencies are declared in &lt;code&gt;package.json&lt;/code&gt; and locked in &lt;code&gt;package-lock.json&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;You run &lt;code&gt;npm install&lt;/code&gt; and the project works&lt;/li&gt;
&lt;li&gt;Staging and production match because the environment is defined in code&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now think about a typical WordPress project:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Snippets are stored in a database, edited through a UI&lt;/li&gt;
&lt;li&gt;PHP packages require SSH access to install&lt;/li&gt;
&lt;li&gt;"Deploying" means FTP or a database export&lt;/li&gt;
&lt;li&gt;Staging and production drift apart over time because changes happen manually in both&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This isn't a criticism of WordPress as a product; it was built for a different audience. But developers using it to build things deserve better.&lt;/p&gt;

&lt;h2&gt;
  
  
  What can go wrong
&lt;/h2&gt;

&lt;p&gt;Here's a real scenario: you add a PHP snippet to your WordPress site using the Code Snippets plugin. It works. Three months later, a junior dev edits it and introduces a bug that takes checkout down for an hour. You have no idea what changed, when, or why. There's no diff, no history, no rollback button. You dig through admin logs hoping to find something, then end up rewriting the snippet from memory.&lt;/p&gt;

&lt;p&gt;Or: a client wants a PDF export feature. You need to pull in a Packagist package. You SSH into the server, install Composer, run &lt;code&gt;composer require&lt;/code&gt;, and pray nothing breaks in production. It worked, but it was thirty minutes of friction that shouldn't exist.&lt;/p&gt;

&lt;p&gt;These aren't edge cases. They're the everyday reality of WordPress development.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a better workflow looks like
&lt;/h2&gt;

&lt;p&gt;Imagine instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Pull your snippets down as files&lt;/span&gt;
lps snippet pull

&lt;span class="c"&gt;# Edit them locally, in your editor, with Git tracking changes&lt;/span&gt;
git add snippets/my-function.php
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"fix: correct tax calculation logic"&lt;/span&gt;

&lt;span class="c"&gt;# Push back to WordPress&lt;/span&gt;
lps snippet push
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your snippet is now version-controlled. You have a diff. You can roll back. You can review the change in a PR. This is how software is supposed to work, and it's exactly what Loopress brings to WordPress: a CLI that treats your snippets and Composer dependencies as files, plus a plugin that lets you manage PHP dependencies from the admin panel without server access.&lt;/p&gt;

&lt;p&gt;WordPress stays WordPress. Your clients can still use it the way they love. You just work with it like a developer.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;The rest of this blog will cover specific workflows in detail: version-controlling Code Snippets with Git, installing Composer packages without SSH, syncing menus and styles between environments, and managing multiple client sites as code. Those posts are coming soon; this one lays the groundwork for why they matter.&lt;/p&gt;

</description>
      <category>dx</category>
      <category>wordpress</category>
      <category>beginners</category>
      <category>loopress</category>
    </item>
    <item>
      <title>Why I Built Loopress</title>
      <dc:creator>jean-smaug</dc:creator>
      <pubDate>Sat, 04 Jul 2026 14:11:46 +0000</pubDate>
      <link>https://dev.to/jeansmaug/why-i-built-loopress-3ao6</link>
      <guid>https://dev.to/jeansmaug/why-i-built-loopress-3ao6</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Cross-posted from the Loopress docs&lt;br&gt;
Loopress is a toolset to &lt;strong&gt;make WordPress reproducible and reviewable via Git&lt;/strong&gt;. Versioned snippets, Composer without SSH, and more coming...&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://loopress.dev" class="crayons-btn crayons-btn--primary" rel="noopener noreferrer"&gt;Check Loopress&lt;/a&gt;
&lt;/p&gt;




&lt;p&gt;I'm a frontend developer who's spent the last few years working in environments where everything is versioned, deployments are automated, and "it works on my machine" isn't an acceptable answer.&lt;/p&gt;

&lt;p&gt;One of the jobs that shaped how I think about this was &lt;strong&gt;Bedrock Streaming&lt;/strong&gt;, a streaming platform where every developer owned their infrastructure, frontend included, and the tool we used was &lt;strong&gt;Terraform&lt;/strong&gt;. I remember something clicking when I started using it: your infra is just a file, which means it gets versioned, reviewed, and rolled back like any other piece of code. That idea stuck with me well past that job.&lt;/p&gt;

&lt;h2&gt;
  
  
  A site for my sister
&lt;/h2&gt;

&lt;p&gt;My sister runs an online French tutoring business (&lt;a href="https://nativefrenchteacher.com" rel="noopener noreferrer"&gt;nativefrenchteacher.com&lt;/a&gt;). She's not technical, and she needed to manage her own content without calling me every time. WordPress made sense.&lt;/p&gt;

&lt;p&gt;And honestly, it worked great for her. Whatever I needed (bookings, SEO, forms, caching) there was a plugin. The ecosystem is massive.&lt;/p&gt;

&lt;p&gt;Building &lt;em&gt;with&lt;/em&gt; it as a developer was another thing entirely.&lt;/p&gt;

&lt;p&gt;I used the Code Snippets plugin constantly. And every time I touched a snippet, the same thought: if something breaks, I have no history. No diff. Just hope it doesn't break.&lt;/p&gt;

&lt;p&gt;The real moment was when I needed to manipulate PDFs and wanted to pull in a Packagist package. So I SSH'd into the server, installed Composer manually, ran the install, crossed my fingers. It worked. But it felt wrong. This is a solved problem everywhere else.&lt;/p&gt;

&lt;h2&gt;
  
  
  The gap
&lt;/h2&gt;

&lt;p&gt;I came from a world where code lives in Git and environments are reproducible. WordPress assumes you click things in the admin, upload files via FTP, and remember what you changed. Production is always slightly different from local.&lt;/p&gt;

&lt;p&gt;That's not a knock on WordPress. It's built for a different audience. But the developer building &lt;em&gt;on top&lt;/em&gt; of it is left to figure things out alone.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I built
&lt;/h2&gt;

&lt;p&gt;I started building &lt;strong&gt;Loopress&lt;/strong&gt;: a CLI to version-control code snippets in Git and sync them to any WordPress instance, and a plugin that brings Composer dependency management into the admin panel, no SSH needed.&lt;/p&gt;

&lt;p&gt;The goal was to bring the same idea I took away from Terraform into WordPress: your snippets, your dependencies, your configuration, all of it living in a repo, reviewable, deployable, not scattered across admin panels and SSH sessions you'll never remember.&lt;/p&gt;

&lt;p&gt;Still building. If any of this sounds familiar, I built it for you.&lt;/p&gt;

</description>
      <category>wordpress</category>
      <category>dx</category>
      <category>loopress</category>
    </item>
    <item>
      <title>How to fix the 'Received "true" for a non-boolean attribute' error</title>
      <dc:creator>jean-smaug</dc:creator>
      <pubDate>Mon, 23 Nov 2020 21:09:16 +0000</pubDate>
      <link>https://dev.to/jeansmaug/how-to-fix-the-received-true-for-a-non-boolean-attribute-error-3pm9</link>
      <guid>https://dev.to/jeansmaug/how-to-fix-the-received-true-for-a-non-boolean-attribute-error-3pm9</guid>
      <description>&lt;p&gt;If you use &lt;code&gt;styled-components&lt;/code&gt;, you maybe encountered the following error.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Warning: Received &lt;span class="s2"&gt;"true"&lt;/span&gt; &lt;span class="k"&gt;for &lt;/span&gt;a non-boolean attribute
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is an official solution that you can find &lt;a href="https://styled-components.com/docs/faqs#why-am-i-getting-html-attribute-warnings" rel="noopener noreferrer"&gt;here&lt;/a&gt;. I'll present an alternative to this solution.&lt;/p&gt;

&lt;p&gt;The trick is to use the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Unary_plus" rel="noopener noreferrer"&gt;unary plus operator&lt;/a&gt; to convert boolean to number.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Convert boolean to numbers&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// 1&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// 0&lt;/span&gt;

&lt;span class="c1"&gt;// Conditions are still valid using 0 and 1&lt;/span&gt;
&lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;jean&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;smaug&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// smaug&lt;/span&gt;
&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;jean&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;smaug&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// jean&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In order to make a real world example, we'll style the &lt;code&gt;Link&lt;/code&gt; component from &lt;code&gt;react-router&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;styled&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;styled-components&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Link&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react-router&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;StyledLink&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;styled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Link&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;`
  color: &lt;/span&gt;&lt;span class="p"&gt;${({&lt;/span&gt; &lt;span class="nx"&gt;inverted&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;inverted&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;white&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;chartreuse&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;;
`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Navbar&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;nav&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt; &lt;span class="nx"&gt;Bad&lt;/span&gt; &lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;StyledLink&lt;/span&gt; &lt;span class="na"&gt;inverted&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Home&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;StyledLink&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt; &lt;span class="nx"&gt;Good&lt;/span&gt; &lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;StyledLink&lt;/span&gt; &lt;span class="na"&gt;inverted&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;About&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;StyledLink&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;nav&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Fixing this error is very simple. You just need to add &lt;code&gt;+&lt;/code&gt; before your booleans values. According to &lt;a href="https://styled-components.com/docs/faqs#why-am-i-getting-html-attribute-warnings" rel="noopener noreferrer"&gt;MDN&lt;/a&gt; unary operator is the preferred way for number conversion.&lt;/p&gt;

&lt;p&gt;You can find the banner image &lt;a href="https://unsplash.com/photos/f7xoQpzl-Mo" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Thanks for reading.&lt;/p&gt;

</description>
      <category>styledcomponents</category>
      <category>emotion</category>
      <category>react</category>
    </item>
    <item>
      <title>File upload in Javascript using fetch</title>
      <dc:creator>jean-smaug</dc:creator>
      <pubDate>Mon, 26 Oct 2020 07:17:26 +0000</pubDate>
      <link>https://dev.to/jeansmaug/file-upload-in-javascript-using-fetch-4oeb</link>
      <guid>https://dev.to/jeansmaug/file-upload-in-javascript-using-fetch-4oeb</guid>
      <description>&lt;p&gt;In this article we'll see how to upload a file using the fetch API. We'll take a PHP example and update it, in order to improve user experience.&lt;/p&gt;

&lt;p&gt;The code is available here&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/jean-smaug" rel="noopener noreferrer"&gt;
        jean-smaug
      &lt;/a&gt; / &lt;a href="https://github.com/jean-smaug/demo-file-upload" rel="noopener noreferrer"&gt;
        demo-file-upload
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;
 

&lt;p&gt;📖 &lt;em&gt;This is a translation from &lt;a href="https://dev.to/blog/uploader-un-fichier-en-javascript-avec-fetch"&gt;this article&lt;/a&gt;. So there is some French in code in order to keep consistency with demonstrations GIFs&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Upload in PHP
&lt;/h2&gt;

&lt;p&gt;Let's take the following example :&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fm19dz9z3onf49a31diov.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fm19dz9z3onf49a31diov.gif" alt="upload-php" width="799" height="236"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After the form submission, we see that the URL is changing to &lt;code&gt;upload.php&lt;/code&gt;. This page store the uploaded file and display a success message. Finally the user is redirected to the first page. The example work but it require three pages loads.&lt;/p&gt;

&lt;p&gt;Let's see the code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class="c"&gt;&amp;lt;!-- ... --&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;action=&lt;/span&gt;&lt;span class="s"&gt;"upload.php"&lt;/span&gt; &lt;span class="na"&gt;method=&lt;/span&gt;&lt;span class="s"&gt;"post"&lt;/span&gt; &lt;span class="na"&gt;enctype=&lt;/span&gt;&lt;span class="s"&gt;"multipart/form-data"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"file"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"background"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Envoyer&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The HTML code is very common. We only have to pay attention to the &lt;a href="https://www.w3schools.com/tags/att_form_enctype.asp" rel="noopener noreferrer"&gt;&lt;strong&gt;enctype&lt;/strong&gt;&lt;/a&gt; attribute. The value must be &lt;strong&gt;multipart/form-data&lt;/strong&gt; in order to do a file upload.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
    &lt;span class="nb"&gt;header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Access-Control-Allow-Origin: *"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="cm"&gt;/* The superglobal variable $_FILES gives us the ability to access
    all files that were uploaded using an HTML form. The background key
    makes reference to the value of the name attribute in
    &amp;lt;input name="background" /&amp;gt; */&lt;/span&gt;
    &lt;span class="nv"&gt;$file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$_FILES&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"background"&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="nv"&gt;$isFileUploaded&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;move_uploaded_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"tmp_name"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="k"&gt;__DIR__&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s2"&gt;"/../storage/"&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$isFileUploaded&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nb"&gt;http_response_code&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Problème serveur"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nb"&gt;http_response_code&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;201&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nb"&gt;readfile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'success.html'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Redirection on index.html after 2 seconds&lt;/span&gt;
    &lt;span class="nb"&gt;header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Refresh:2; url=index.html"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;303&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The server side retrieve the file and move it into the storage folder. When processing is over, we display a success message and we redirect him to the &lt;code&gt;index.html&lt;/code&gt; page after two secondes. The main point is that the name given to the input element is important, because it's this name that the server is using to retrieve the file.&lt;/p&gt;

&lt;h2&gt;
  
  
  Web APIs
&lt;/h2&gt;

&lt;p&gt;We'll need two web API to perform the Upload in Javascript : &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/FormData/FormData" rel="noopener noreferrer"&gt;FormData&lt;/a&gt; and &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch" rel="noopener noreferrer"&gt;Fetch&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  FormData
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzlnh2o52r1pvls0uklmq.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzlnh2o52r1pvls0uklmq.jpg" alt="Form Data" width="800" height="197"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;FormData is well supported by browsers.&lt;/p&gt;

&lt;p&gt;This API gives us the ability to represent a form in Javscript. This form will have a &lt;strong&gt;multipart/form-data&lt;/strong&gt; encoding, so it's not necessary to precise it in the HTML. As already mentioned, this encoding is necessary when you're uploading a file.&lt;/p&gt;

&lt;h3&gt;
  
  
  Fetch
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F742s4t1w8zs2ftta5y53.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F742s4t1w8zs2ftta5y53.jpg" alt="Fetch" width="800" height="265"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Surprisingly, we have compatibilities problems with Internet Explorer.&lt;br&gt;
There are two solution to avoid this :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use a &lt;a href="https://github.com/github/fetch" rel="noopener noreferrer"&gt;polyfill&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;The trick I'm will use later in the article&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The fetch API is the modern way to perform HTTP requests. It's based on &lt;a href="https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Promise" rel="noopener noreferrer"&gt;promise&lt;/a&gt;. There are various syntaxes for using promises, here we'll use the&lt;br&gt;
On peut utiliser les promesses avec plusieurs syntaxe, ici on utilisera &lt;a href="https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Instructions/async_function" rel="noopener noreferrer"&gt;async/await&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="cm"&gt;/* The async keyword means that the await keyword
will be used inside the function. */&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getLucky&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="cm"&gt;/* The await keyword means that we're wating the results of a
   function, here somethingThatTakesTime. We don't know when the
   function will return a value, so we're awaiting the end of the
   process before executing next instructions. */&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;luck&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;somethingThatTakesTime&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;luck&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I explained it in a short way, because it's not the main subjects. You'll be able able to easily find articles that are talking about promises and async/await.&lt;/p&gt;

&lt;h2&gt;
  
  
  In Javascript
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class="c"&gt;&amp;lt;!-- ... --&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"form"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"file"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"background"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Envoyer&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"message"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"./app.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The HTML part is mainly the same. Previous form's attributes have been replaced by an id. And we're adding a paragraph where we'll display the success or failure message. And finally we load our script.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="cm"&gt;/* We get form and message elements */&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;form&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;form&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;message&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="cm"&gt;/* We're listing for the form submission and
we're preventing the default behaviour */&lt;/span&gt;
&lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;submit&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;preventDefault&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

  &lt;span class="cm"&gt;/* If your not using a polyfill for fetch, the trick for IE is to
  suggest the user to use an other browser. */&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fetch&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FormData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;R u fucking kidding me ??? Use another browser right now !&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="cm"&gt;/* When we're instanciating FormData, we can pass it a form element.
  FormData will be able to detect all inputs of the form and their
  values. */&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;formData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;FormData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="cm"&gt;/* fetch() is taking two parameters, the first is URL and
    the second are options. Here we're telling fetch that we'll
    make a POST request and the body of the request, the data
    we're sending will be our formData */&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;http://localhost:4000/upload.php&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;formData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="c1"&gt;// We display a success or failure message.&lt;/span&gt;
    &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerText&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Fichier uploadé avec succès &lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;o/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerText&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Il y a eu un problème /o&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// We clean the form&lt;/span&gt;
  &lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reset&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

  &lt;span class="c1"&gt;// We're removing the message after two seconds&lt;/span&gt;
  &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerText&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;2000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this code, we get this result.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fks022w1nxfq3mhb439ne.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fks022w1nxfq3mhb439ne.gif" alt="upload-js" width="799" height="236"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The last step is server optimization. You can remove these lines.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&amp;lt;?php
    header("Access-Control-Allow-Origin: *");
&lt;span class="err"&gt;
&lt;/span&gt;    $file = $_FILES["background"];
    $isFileUploaded = move_uploaded_file($file["tmp_name"], __DIR__ . "/../storage/" . $file["name"]);
&lt;span class="err"&gt;
&lt;/span&gt;    if($isFileUploaded === false) {
        http_response_code(500);
&lt;span class="gd"&gt;-       echo "Server issue";
&lt;/span&gt;    }
    else {
        http_response_code(201);
&lt;span class="gd"&gt;-       readfile('success.html');
&lt;/span&gt;    }
&lt;span class="gd"&gt;-
-   header("Refresh:2; url=index.html", true, 303);
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Combination of FormData and fetch APIs are making file upload really easy. We're avoiding pages reload in order to improve user experience.&lt;/p&gt;

&lt;p&gt;To perform HTTP requests, it's possible, like in this post, to use fetch. But you should consider :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/axios/axios" rel="noopener noreferrer"&gt;Axios&lt;/a&gt;, XMLHttpRequest based library. XMLHttpRequest is the old way to perform HTTP request, so it's compatible with IE.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/sindresorhus/ky" rel="noopener noreferrer"&gt;Ky&lt;/a&gt;, a wrapper for fetch which add some features. &lt;a href="https://twitter.com/sindresorhus/status/1037763588826398720" rel="noopener noreferrer"&gt;Ky compared to axios&lt;/a&gt; by Ky's creator.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Finally, it's possible to use these principles with libraries such as React or Vue.js. You'll just have to use references in order to access form element.&lt;/p&gt;

&lt;p&gt;Thanks for reading.&lt;/p&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.reference.com/history/tools-weapons-did-southwest-indians-use-a4c8c96e1556b7f8" rel="noopener noreferrer"&gt;Banner image&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>javascript</category>
      <category>upload</category>
      <category>fetch</category>
    </item>
    <item>
      <title>An issue with a lib? Create yours!</title>
      <dc:creator>jean-smaug</dc:creator>
      <pubDate>Tue, 06 Oct 2020 19:39:48 +0000</pubDate>
      <link>https://dev.to/jeansmaug/an-issue-with-a-lib-create-yours-5741</link>
      <guid>https://dev.to/jeansmaug/an-issue-with-a-lib-create-yours-5741</guid>
      <description>&lt;p&gt;I'm working on a Node.js project that is using &lt;a href="https://github.com/koajs/koa" rel="noopener noreferrer"&gt;Koa&lt;/a&gt;. I wanted to use &lt;a href="https://www.npmjs.com/package/twig" rel="noopener noreferrer"&gt;Twig&lt;/a&gt; as template engine in order to render views. I encountered some issues, and I want to relate how I dealt with.&lt;/p&gt;

&lt;h2&gt;
  
  
  Many libs and one problem
&lt;/h2&gt;

&lt;p&gt;The usual way to render HTML with Koa is by using &lt;a href="https://www.npmjs.com/package/koa-views" rel="noopener noreferrer"&gt;koa-views&lt;/a&gt;, which is using &lt;a href="https://www.npmjs.com/package/consolidate" rel="noopener noreferrer"&gt;consolidate&lt;/a&gt; under the hood. &lt;code&gt;consolidate&lt;/code&gt; require to add and configure manually the template engine we want to use.&lt;br&gt;
There are many libs that are dependent (&lt;code&gt;koa-views&lt;/code&gt; → &lt;code&gt;consolidate&lt;/code&gt; → &lt;code&gt;twig&lt;/code&gt;). This is not bad, but this requires each lib to communicate correctly with others.&lt;/p&gt;

&lt;p&gt;I encountered a problem when I wanted to include or extend other twig files. Let's take the following code as example.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight twig"&gt;&lt;code&gt;&lt;span class="c"&gt;{# template.twig #}&lt;/span&gt;

&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;...&lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;main&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;block&lt;/span&gt; &lt;span class="nv"&gt;main&lt;/span&gt; &lt;span class="cp"&gt;%}{%&lt;/span&gt; &lt;span class="k"&gt;endblock&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/main&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight twig"&gt;&lt;code&gt;&lt;span class="c"&gt;{# home.twig #}&lt;/span&gt;

&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;extends&lt;/span&gt; &lt;span class="s2"&gt;"template.html"&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;

&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;block&lt;/span&gt; &lt;span class="nv"&gt;main&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;Home&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;endblock&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This code leads to the following error. Even if add some specific configuration as explained in &lt;a href="https://github.com/queckezz/koa-views/issues/99" rel="noopener noreferrer"&gt;this issue&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Error parsing twig template undefined: 
TwigException: Cannot extend an inline template.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By reading issues, it looked like there was some interferences between these libs, so I decided to reduce their numbers and make the link between Koa and Twig by myself.&lt;/p&gt;

&lt;h2&gt;
  
  
  The koa-twig lib
&lt;/h2&gt;

&lt;p&gt;The goal of &lt;a href="https://github.com/jean-smaug/koa-twig" rel="noopener noreferrer"&gt;koa-twig&lt;/a&gt; is to enhance the &lt;strong&gt;Koa Context&lt;/strong&gt; with a &lt;code&gt;render&lt;/code&gt; function. It's heavily inspired of &lt;code&gt;koa-views&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I'll present to you the first version of the code. I improved it, and I'm still improving it in order to offer the same feature as the others template engines bindings for Koa (&lt;a href="https://github.com/koajs/ejs" rel="noopener noreferrer"&gt;koa-ejs&lt;/a&gt;, &lt;a href="https://github.com/koajs/koa-hbs" rel="noopener noreferrer"&gt;koa-hbs&lt;/a&gt;...).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;twig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;twig&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;util&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;util&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Use Promise instead of callback syntax.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;renderFile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;util&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;promisify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;twig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;renderFile&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="cm"&gt;/**
 * Give the ability to use Twig template engine in Koa
 * @param {object} config
 * @param {string} config.views - the views folder path
 * @param {object} config.data - the data to pass to each view
 * @param {object} config.extension - the data to pass to each view
 */&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;twigMiddleware&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;renderFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;views&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;extension&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;twig&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="cm"&gt;/* `render` function will be accessible
  on ctx and on ctx.response */&lt;/span&gt;
  &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;render&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;render&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;render&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;render&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;twigMiddleware&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here a basic use case.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Koa&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;koa&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;koaTwig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;koa-twig&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Koa&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Configuration of the middleware&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nf"&gt;koaTwig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;views&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/views`&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Calling `render` will render `./views/home.twig`&lt;/span&gt;
  &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;home&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;8080&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So it wasn't that hard to reach a quick win. I can now develop with less intermediate. This is also something that made me remember that you don't need a lib for all of your problems!&lt;/p&gt;

&lt;h2&gt;
  
  
  Bonus
&lt;/h2&gt;

&lt;p&gt;All the PRs I made for this little lib counted for the Hacktoberfest 🎉&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Flkcz2ot95rjt2o978haj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Flkcz2ot95rjt2o978haj.png" alt="hacktoberfest" width="799" height="357"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Another reason to create and share what you're doing !&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;If there is an obstacle on the road and you can't go to right, try the left. If it's closed, try to go up. If it's blocked, dig under! Don't wait for someone to clean the road for you.&lt;/p&gt;

&lt;p&gt;I still have some work to do in order to have the same features as the others Koa template engines bindings, but it's a first step I'm satisfied of!&lt;/p&gt;

&lt;p&gt;The short version : &lt;strong&gt;DIY&lt;/strong&gt; and &lt;strong&gt;KISS&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Thanks for reading.&lt;/p&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.artisan-ebeniste.com/quil-faut-savoir-atelier-debeniste/" rel="noopener noreferrer"&gt;Banner image&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>node</category>
      <category>koa</category>
      <category>hacktoberfest</category>
    </item>
    <item>
      <title>Créer un bot Telegram avec Elixir</title>
      <dc:creator>jean-smaug</dc:creator>
      <pubDate>Mon, 04 May 2020 10:48:17 +0000</pubDate>
      <link>https://dev.to/jeansmaug/creer-un-bot-telegram-avec-elixir-1581</link>
      <guid>https://dev.to/jeansmaug/creer-un-bot-telegram-avec-elixir-1581</guid>
      <description>&lt;p&gt;En ces périodes de confinement, il est important de continuer de générer du lead. C'est pour cela que nous allons voir comment créer un bot Telegram pour &lt;del&gt;nous assister dans nos taches du quotidien&lt;/del&gt; ne rien faire de productif.&lt;/p&gt;

&lt;p&gt;Les sources sont disponibles ici.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/jean-smaug" rel="noopener noreferrer"&gt;
        jean-smaug
      &lt;/a&gt; / &lt;a href="https://github.com/jean-smaug/demo-elixir-telegram-bot" rel="noopener noreferrer"&gt;
        demo-elixir-telegram-bot
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  Initialisation
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Côté Telegram
&lt;/h3&gt;

&lt;p&gt;On commence par s'adresser au daron de tous les bots, j'ai nommé &lt;a href="https://telegram.me/botfather" rel="noopener noreferrer"&gt;@botfather&lt;/a&gt;. On lui demande de créer un nouveau bot avec la commande &lt;code&gt;/newbot&lt;/code&gt;, puis en répondant aux questions.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Ftd5916l8x5bexu4khkvq.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Ftd5916l8x5bexu4khkvq.gif" alt="create-bot" width="600" height="233"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Une fois que le bot est créé, botfather nous affiche un token. Nous nous en servirons par la suite.&lt;/p&gt;

&lt;h3&gt;
  
  
  Côté code
&lt;/h3&gt;

&lt;p&gt;On initialise un projet Elixir avec la commande suivante.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mix new macron_bot &lt;span class="nt"&gt;--sup&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nous allons utiliser la librairie &lt;a href="https://github.com/rockneurotiko/ex_gram" rel="noopener noreferrer"&gt;ex_gram&lt;/a&gt;. Cette librairie est une couche d'abstraction autour des &lt;a href="https://core.telegram.org/bots/api" rel="noopener noreferrer"&gt;API Telegram&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Il faut ajouter cette dépendance ainsi que d'autres qui sont requises pour son bon fonctionnement.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# mix.exs&lt;/span&gt;

&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;MacronBot&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;MixProject&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="c1"&gt;# ...&lt;/span&gt;

  &lt;span class="k"&gt;defp&lt;/span&gt; &lt;span class="n"&gt;deps&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:ex_gram&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"~&amp;gt; 0.12"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:tesla&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"~&amp;gt; 1.2"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:hackney&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"~&amp;gt; 1.12"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:jason&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&amp;gt;= 1.0.0"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On installe les dépendances.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mix deps.get
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nous allons ensuite créer un fichier de configuration. (Qui n'est plus généré automatiquement depuis la version &lt;a href="https://github.com/elixir-lang/elixir/releases/tag/v1.9.0" rel="noopener noreferrer"&gt;1.9.0&lt;/a&gt; d'Elixir).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;config &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;touch &lt;/span&gt;config/config.exs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Dans le fichier créer il faut ajouter les lignes suivantes en remplaçant le token par celui fourni par Botfather.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# config/config.exs&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="no"&gt;Mix&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Config&lt;/span&gt;

&lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="ss"&gt;:tesla&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;adapter:&lt;/span&gt; &lt;span class="no"&gt;Tesla&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Adapter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Hackney&lt;/span&gt;

&lt;span class="c1"&gt;# Remplacez ce token avec celui fourni par Botfather&lt;/span&gt;
&lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="ss"&gt;:ex_gram&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;token:&lt;/span&gt; &lt;span class="s2"&gt;"1287038449:AAHvSU4cGipZUH6i2Pbbhiz9akkE7_wP4YQ"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;adapter:&lt;/span&gt; &lt;span class="no"&gt;ExGram&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Adapter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Tesla&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;json_engine:&lt;/span&gt; &lt;span class="no"&gt;Jason&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Enfin on configure le module application.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# lib/macron_bot/application.ex&lt;/span&gt;

&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;MacronBot&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Application&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="no"&gt;Application&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="c1"&gt;# Importe le token depuis la config&lt;/span&gt;
    &lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;ExGram&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:ex_gram&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;children&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="no"&gt;ExGram&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="c1"&gt;# Lance le module MacronBot&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;MacronBot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;method:&lt;/span&gt; &lt;span class="ss"&gt;:polling&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;token:&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="c1"&gt;# Si l'application rencontre un problème elle sera redémarrée&lt;/span&gt;
    &lt;span class="n"&gt;opts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;strategy:&lt;/span&gt; &lt;span class="ss"&gt;:one_for_one&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;name:&lt;/span&gt; &lt;span class="no"&gt;MacronBot&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Supervisor&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="no"&gt;Supervisor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start_link&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;children&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;L'initialisation peut sembler longue, mais par la suite, on modifie très peu ces fichiers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Création du bot
&lt;/h2&gt;

&lt;p&gt;Afin d'explorer les possibilités techniques qui sont à notre disposition notre bot aura les fonctionnalités suivantes :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Une commande slash &lt;code&gt;/parle&lt;/code&gt; qui affichera une citation au hasard (une commande slash est un "message qui commence par le signe /").&lt;/li&gt;
&lt;li&gt;Lorsqu'on lui pose une question le bot affichera des boutons pour permettre à l'utilisateur de répondre
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# lib/macron_bot.ex&lt;/span&gt;

&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;MacronBot&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="nv"&gt;@bot&lt;/span&gt; &lt;span class="ss"&gt;:Macron_Bot&lt;/span&gt;

  &lt;span class="c1"&gt;# Si on raisonne en Orienté Objet, on peut voir&lt;/span&gt;
  &lt;span class="c1"&gt;# le use comme "un héritage".&lt;/span&gt;
  &lt;span class="c1"&gt;# Le name serait un paramètre que l'on passe&lt;/span&gt;
  &lt;span class="c1"&gt;# au constructeur parent.&lt;/span&gt;
  &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="no"&gt;ExGram&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Bot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;name:&lt;/span&gt; &lt;span class="nv"&gt;@bot&lt;/span&gt;

  &lt;span class="c1"&gt;# Requis si vous ajoutez votre bot à un groupe&lt;/span&gt;
  &lt;span class="n"&gt;middleware&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;ExGram&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Middleware&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;IgnoreUsername&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="c1"&gt;# Comme nous utilisons le module ExGram.Bot,&lt;/span&gt;
  &lt;span class="c1"&gt;# il est nécessaire d'implémenter une fonction&lt;/span&gt;
  &lt;span class="c1"&gt;# nommée handle pour que le bot puisse fonctionner.&lt;/span&gt;
  &lt;span class="c1"&gt;#&lt;/span&gt;
  &lt;span class="c1"&gt;# Le premier paramètre est un tuple dont les valeurs&lt;/span&gt;
  &lt;span class="c1"&gt;# changent suivant les cas.&lt;/span&gt;
  &lt;span class="c1"&gt;#&lt;/span&gt;
  &lt;span class="c1"&gt;# Le second paramètre est le context,&lt;/span&gt;
  &lt;span class="c1"&gt;# il contient des informations sur le message reçu&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;

    &lt;span class="c1"&gt;# La fonction answer nous permet d'envoyer un message&lt;/span&gt;
    &lt;span class="c1"&gt;# Telgram en tant que bot&lt;/span&gt;
    &lt;span class="n"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"J'appelle à la responsabilité"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Désormais on peut lancer le bot avec la commande.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;iex &lt;span class="nt"&gt;-S&lt;/span&gt; mix
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Le bot répondera "J'appelle à la responsabilité" à chacun de nos messages.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fksmiz7189gq6ypw103av.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fksmiz7189gq6ypw103av.gif" alt="bot-hello" width="760" height="243"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Les commandes slash
&lt;/h3&gt;

&lt;p&gt;La commande slash parle affichera au hasard, des citations que j'ai piquée, sans vergogne, sur le site du &lt;a href="https://citation-celebre.leparisien.fr/auteur/emmanuel-macron" rel="noopener noreferrer"&gt;Parisien&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F2tn0fjnh5jvjqcec9ybr.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F2tn0fjnh5jvjqcec9ybr.gif" alt="slash-command" width="720" height="202"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;J'ai selectionné les phrases les plus croquignolesques mais bien sûr, adaptez selon vos envies.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# lib/macron_bot.ex&lt;/span&gt;

&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;MacronBot&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="c1"&gt;# Si l'on veut detecter une commande, le tuple&lt;/span&gt;
  &lt;span class="c1"&gt;# contiendra un atom :command à l'index 0.&lt;/span&gt;
  &lt;span class="c1"&gt;# Le nom de la commande à l'index 1.&lt;/span&gt;
  &lt;span class="c1"&gt;# Et le "message" à l'index 2,&lt;/span&gt;
  &lt;span class="c1"&gt;# c'est a dire le texte suivant la commande.&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="ss"&gt;:command&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"parle"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_msg&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;answers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="s2"&gt;"Si j'étais chômeur, je n'attendrais pas tout de l'autre, j'essaierais de me battre d'abord."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s2"&gt;"Je suis maoïste, [...] un bon programme c'est ce qui marche."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s2"&gt;"Le libéralisme est une valeur de gauche."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s2"&gt;"Il y a dans cette société une majorité de femmes. Il y en a qui sont, pour beaucoup, illettrées."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s2"&gt;"Les Tontons Flingueurs, c'est un de mes films préférés. &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;On n'est pas venus pour beurrer les sandwichs&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt; : ma réplique préférée."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s2"&gt;"Make our planet great again !"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s2"&gt;"Vous n'allez pas me faire peur avec votre t-shirt, la meilleure façon de se payer un costard c'est de travailler."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s2"&gt;"Je ne vais pas interdire Uber et les VTC, ce serait les renvoyer vendre de la drogue à Stains."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s2"&gt;"Vu la situation économique, ne plus payer les heures supplémentaires c'est une nécessité."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s2"&gt;"La tranche d'impôt de Hollande à 75 % ? C'est Cuba sans le soleil."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s2"&gt;"Quand des pays ont encore sept à huit enfants par femmes, vous pouvez décider d'y dépenser des milliards d'euros, vous ne stabiliserez rien."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s2"&gt;"Le kwassa-kwassa pêche peu. Il amène du Comorien."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s2"&gt;"Lorsque la politique n'est plus une mission mais une profession, les politiciens deviennent plus égoïstes que les fonctionnaires."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s2"&gt;"Ne laissez pas la critique de l'UE à ceux qui le détestent."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s2"&gt;"L'audiovisuel public est la honte de la République."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s2"&gt;"La politique sociale, regardez : on met un pognon de dingue dans des minimas sociaux, les gens sont quand même pauvres."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s2"&gt;"Chaque pays a sa propre diplomatie. Faire partie de l'Europe ne signifie pas renoncer à son indépendance ou ne plus pouvoir prendre l'initiative."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s2"&gt;"Je me bats sur le plan international pour qu'on arrive à faire baisser le prix du pétrole."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s2"&gt;"Parce que c'est notre PROJEEEEET !!!"&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="c1"&gt;# On répond avec une phrase au hasard&lt;/span&gt;
    &lt;span class="n"&gt;random_answer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;answers&lt;/span&gt; &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Enum&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;random_answer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Après avoir créé cette fonction, il faudra, au choix :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Couper le terminal et relancer &lt;code&gt;iex -S mix&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Taper &lt;code&gt;recompile&lt;/code&gt; dans le terminal interactif précédemment ouvert&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Question-réponse
&lt;/h3&gt;

&lt;p&gt;Le mécanisme de question-réponse permet d'aborder plusieurs notions :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;La détection de la question, détecter que la phrase se termine par un &lt;strong&gt;?&lt;/strong&gt;. On utilisera une REGEX.&lt;/li&gt;
&lt;li&gt;Envoyer un message contenant des boutons&lt;/li&gt;
&lt;li&gt;Détecter sur quel bouton l'utilisateur à cliqué&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fkeery9kiojlogw0b59n8.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fkeery9kiojlogw0b59n8.gif" alt="answer-question" width="720" height="231"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Pour parvenir à ce comportement, notre code implémentera deux nouvelles méthode &lt;code&gt;handle&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# lib/macron_bot.ex&lt;/span&gt;

&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;MacronBot&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="c1"&gt;# ...&lt;/span&gt;

  &lt;span class="c1"&gt;# On alias les models nécessaire pour construire&lt;/span&gt;
  &lt;span class="c1"&gt;# des messages contenant des boutons.&lt;/span&gt;
  &lt;span class="n"&gt;alias&lt;/span&gt; &lt;span class="no"&gt;Exgram&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Model&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;InlineKeyboardMarkup&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;InlineKeyboardButton&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;# Appelé lorsque l'on envoit un message textuel.&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="ss"&gt;:text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;

    &lt;span class="c1"&gt;# On teste si la phrase se termine&lt;/span&gt;
    &lt;span class="c1"&gt;# par un point d'interrogation.&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="no"&gt;String&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;match?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sr"&gt;~r/\?$/&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;

      &lt;span class="c1"&gt;# On supprime le message que l'on vient d'envoyer.&lt;/span&gt;
      &lt;span class="c1"&gt;# Dans la pratique cela est optionnel,&lt;/span&gt;
      &lt;span class="c1"&gt;# mais on découvre une autre fonctionnalité 👍&lt;/span&gt;
      &lt;span class="no"&gt;ExGram&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;delete_message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

      &lt;span class="c1"&gt;# La fonction answer peut prendre un troisième paramètre&lt;/span&gt;
      &lt;span class="c1"&gt;# permettant d'enrichir le message. Des boutons ici.&lt;/span&gt;
      &lt;span class="n"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="ss"&gt;reply_markup:&lt;/span&gt; &lt;span class="p"&gt;%&lt;/span&gt;&lt;span class="no"&gt;InlineKeyboardMarkup&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="ss"&gt;inline_keyboard:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="p"&gt;[&lt;/span&gt;
              &lt;span class="p"&gt;%&lt;/span&gt;&lt;span class="no"&gt;InlineKeyboardButton&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="ss"&gt;text:&lt;/span&gt; &lt;span class="s2"&gt;"Oui"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="ss"&gt;callback_data:&lt;/span&gt; &lt;span class="s2"&gt;"Oui"&lt;/span&gt;
              &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="p"&gt;[&lt;/span&gt;
              &lt;span class="p"&gt;%&lt;/span&gt;&lt;span class="no"&gt;InlineKeyboardButton&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="ss"&gt;text:&lt;/span&gt; &lt;span class="s2"&gt;"Non"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="ss"&gt;callback_data:&lt;/span&gt; &lt;span class="s2"&gt;"Non"&lt;/span&gt;
              &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;]&lt;/span&gt;
          &lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;


  &lt;span class="c1"&gt;# Appelé lorsque l'on clique sur le bouton&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="ss"&gt;:callback_query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;callback_query&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="c1"&gt;# La valeur que l'on récupère via callback_query.data&lt;/span&gt;
    &lt;span class="c1"&gt;# est celle défini dans les callback_data ci-dessus&lt;/span&gt;
    &lt;span class="n"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;callback_query&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, mais en même temps"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Bonus
&lt;/h2&gt;

&lt;p&gt;Je vous donne le code pour créer une commande slash &lt;code&gt;/covid&lt;/code&gt;. C'est cadeau, c'est pour moi. Emballé, c'est pesé !&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# lib/macron_bot.ex&lt;/span&gt;

&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;Macron&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="c1"&gt;# ...&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="ss"&gt;:command&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"covid"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_msg&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;covid_summary&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Tesla&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"https://api.covid19api.com/summary"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;json&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;covid_summary&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Jason&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;decode!&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;global&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt; &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Global"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;france&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
      &lt;span class="n"&gt;json&lt;/span&gt;
      &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Countries"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Enum&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="n"&gt;country&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;country&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"Slug"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"france"&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;covid_answer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sx"&gt;~s""&lt;/span&gt;&lt;span class="s2"&gt;"
    🌍
    Nouveaux cas : &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;global&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"NewConfirmed"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;
    Nouvelles morts : &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;global&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"NewDeaths"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;
    Cas totaux : &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;global&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"TotalConfirmed"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;
    Morts totales : &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;global&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"TotalDeaths"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;

    🇫🇷
    Nouveaux cas : &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;france&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"NewConfirmed"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;
    Nouvelles morts : &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;france&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"NewDeaths"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;
    Cas totaux : &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;france&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"TotalConfirmed"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;
    Morts totales : &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;france&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"TotalDeaths"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;
    """&lt;/span&gt;

    &lt;span class="n"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;covid_answer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;A travers cet article j'espère avoir pu vous éclairer sur le fonctionnement de la librairie &lt;strong&gt;ex_gram&lt;/strong&gt;. A titre personnel j'ai eu quelques difficultés a mettre en place certaines fonctionnalités, les réponses aux boutons par exemple. La documentation manque d'exemples à mon goût.&lt;/p&gt;

&lt;p&gt;Néanmoins, la librairie reste bien conçue et une fois que l'on a compris la philosophie, on peut développer un bot très rapidement.&lt;/p&gt;

&lt;p&gt;Je vous invite aussi à consulter la documentation Telegram afin de mieux comprendre d'ou viennent les structures utilisées.&lt;/p&gt;

&lt;p&gt;Merci de m'avoir lu.&lt;/p&gt;

&lt;h2&gt;
  
  
  Liens
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/rockneurotiko/ex_gram" rel="noopener noreferrer"&gt;Documentation ex_gram&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://core.telegram.org/bots/api" rel="noopener noreferrer"&gt;Documentation des bots Telegram&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://citation-celebre.leparisien.fr/auteur/emmanuel-macron" rel="noopener noreferrer"&gt;Citations croquignolesques&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>elixir</category>
      <category>bot</category>
      <category>telegram</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Le hoisting au service des composants React</title>
      <dc:creator>jean-smaug</dc:creator>
      <pubDate>Fri, 03 Apr 2020 15:03:48 +0000</pubDate>
      <link>https://dev.to/jeansmaug/le-hoisting-au-service-des-composants-react-4846</link>
      <guid>https://dev.to/jeansmaug/le-hoisting-au-service-des-composants-react-4846</guid>
      <description>&lt;p&gt;À première vue, le titre est un peu barbare. Pas d'inquiétude, nous allons commencer par découvrir ce qu'est le hoisting et dans un deuxième temps nous verrons quel peut être son intérêt pour les composants React.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hissez haut ! Santiano 🎵
&lt;/h2&gt;

&lt;p&gt;Pour découvrir le hoisting, ou "hissage" en français, nous allons partir du code suivant.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Il s'agirait de grandir&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Ce code affiche : "Il s'agirait de grandir"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Même si la fonction &lt;code&gt;print&lt;/code&gt; est déclarée &lt;strong&gt;après&lt;/strong&gt; avoir été appelée ce code fonctionne.&lt;/p&gt;

&lt;p&gt;Cela vient du fait que Javascript va "remonter" les déclarations de fonctions en haut du code. Dans la réalité, le code reste à sa place, mais est stocké en mémoire afin de pouvoir être appelé partout.&lt;/p&gt;

&lt;p&gt;Explication issue de la &lt;a href="https://developer.mozilla.org/fr/docs/Glossaire/Hoisting" rel="noopener noreferrer"&gt;MDN&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Conceptuellement, par exemple, une définition stricte du hissage suggère que les déclarations de variables et de fonctions sont déplacées physiquement en haut de votre code, mais ce n'est pas ce qui se passe en fait. A la place, les déclarations de variables et de fonctions sont mises en mémoire pendant la phase de compilation, mais restent exactement là où vous les avez tapées dans votre code.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Transformons le code précédent en utilisant une fonction fléchée.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Il s'agirait de grandir&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;print&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Ce code affiche : "Uncaught ReferenceError: Cannot access 'print' before initialization"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Avec &lt;code&gt;const&lt;/code&gt; et &lt;code&gt;let&lt;/code&gt; la remonté a lieu mais pas l'initialisation. En d'autres termes appeler &lt;code&gt;print&lt;/code&gt; ne générera pas une erreur du type &lt;code&gt;print is not defined&lt;/code&gt;. En revanche, &lt;code&gt;print&lt;/code&gt; n'a pas été initialisé, donc il correspond à rien.&lt;/p&gt;

&lt;h2&gt;
  
  
  Au sein de React
&lt;/h2&gt;

&lt;p&gt;Je me suis longtemps demandé quelle syntaxe privilégier pour les composants fonctionnels.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Button&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;onClick&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;onClick&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// ou&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;onClick&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;onClick&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Étant donné que l'on a parlé du hoisting juste avant, vous vous doutez que la seconde forme a un avantage sur la première.&lt;br&gt;
On peut trouver un intérêt dans la déclaration des &lt;code&gt;propTypes&lt;/code&gt; et des &lt;code&gt;defaultProps&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Avec une fonction fléchée.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;PropTypes&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;prop-types&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Button&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;onClick&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;onClick&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Nécéssairement en dessous du composant&lt;/span&gt;

&lt;span class="nx"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;propTypes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PropTypes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PropTypes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;func&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isRequired&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;defaultProps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;Button&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Avec une fonction "normale".&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;PropTypes&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;prop-types&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="c1"&gt;// Peut être déplacé au dessus du composant&lt;/span&gt;

&lt;span class="nx"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;propTypes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PropTypes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PropTypes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;func&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isRequired&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;defaultProps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;onClick&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;onClick&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;Button&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Les fonctions nous laissent le choix et nous permettent ainsi de réorganiser notre code en plaçant la signature du composant au début du fichier.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Il y a une chose à ne pas oublier lorsque l'on utilise React : "C'est juste du JS". Tous les concepts que l'on connait sont valides et tous ceux que l'on découvrira le seront.&lt;/p&gt;

&lt;p&gt;Grace au hoisting on peut arranger notre code de façon à avoir directement accès à la signature de notre composant. Cela évite de devoir scroller en bas de fichier pour savoir quelles sont les &lt;code&gt;props&lt;/code&gt; que l'on peut utiliser.&lt;/p&gt;

&lt;p&gt;Une question demeure : faut-il utiliser cette astuce ?&lt;br&gt;&lt;br&gt;
Faites en fonction de vos préférences.&lt;br&gt;&lt;br&gt;
Personnellement je l'utilise mais ce n'est en rien une règle absolue.&lt;/p&gt;

&lt;p&gt;Merci de m'avoir lu.&lt;/p&gt;

&lt;h2&gt;
  
  
  Liens
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.liveabout.com/how-to-raise-the-mainsail-2915461" rel="noopener noreferrer"&gt;Image en banière&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/fr/docs/Web/JavaScript/Guide/Types_et_grammaire#Remont%C3%A9e_de_variables_hoisting" rel="noopener noreferrer"&gt;Référence MDN hoisting&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>react</category>
      <category>javascript</category>
    </item>
    <item>
      <title>The git remaster command</title>
      <dc:creator>jean-smaug</dc:creator>
      <pubDate>Wed, 18 Mar 2020 16:09:24 +0000</pubDate>
      <link>https://dev.to/jeansmaug/the-git-remaster-command-3adb</link>
      <guid>https://dev.to/jeansmaug/the-git-remaster-command-3adb</guid>
      <description>&lt;p&gt;OK, OK. This command doesn't exist. It's one of the aliases that I'm using.&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;Execute this command once only.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git config &lt;span class="nt"&gt;--global&lt;/span&gt; alias.remaster &lt;span class="s1"&gt;'!git fetch &amp;amp;&amp;amp; git clean -d -f &amp;amp;&amp;amp; git reset --hard origin/master'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;When you totally fucked up your local repository and you want an atomic cleaning, use this one.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git remaster
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Your current branch will be in the same state as the remote master (the one which is on Gitlab, Github...)&lt;/p&gt;
&lt;h2&gt;
  
  
  The long version
&lt;/h2&gt;

&lt;p&gt;I'm writing this article because I saw this tweet from &lt;a href="https://dev.to/emmabostian"&gt;Emma Bostian&lt;/a&gt;. I was doing the same until I've found a solution. Btw, you should follow her.&lt;/p&gt;

&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-1232412585397571584-484" src="https://platform.twitter.com/embed/Tweet.html?id=1232412585397571584"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1232412585397571584-484');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1232412585397571584&amp;amp;theme=dark"
  }



&lt;/p&gt;

&lt;p&gt;In this part I will explain in detail the meaning of this command.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git config &lt;span class="nt"&gt;--global&lt;/span&gt; alias.remaster &lt;span class="s1"&gt;'!git fetch &amp;amp;&amp;amp; git clean -d -f &amp;amp;&amp;amp; git reset --hard origin/master'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;I will explain first how to configure alias (this part &lt;code&gt;git config --global alias.remaster&lt;/code&gt;) and secondary the meaning of the created alias (the part between quotes).&lt;/p&gt;
&lt;h3&gt;
  
  
  The alias
&lt;/h3&gt;

&lt;p&gt;Alias is just "a shorter name for something that is normally long to write". For example in maths you're using the &lt;code&gt;=&lt;/code&gt; sign instead of writing &lt;code&gt;is equal to&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The first part of the command is &lt;code&gt;git config --global alias.remaster '...'&lt;/code&gt;. It tells Git : "Dear Git, please create an alias named &lt;code&gt;remaster&lt;/code&gt; in the global config. After that, everytime I type &lt;code&gt;git remaster&lt;/code&gt; I want the command between quotes to be executed." &lt;a href="https://dev.to/jeansmaug/git-configuration-et-fourberies-2ajc"&gt;Learn more about git configuration&lt;/a&gt; (it's in french 🥖🍷🇫🇷).&lt;/p&gt;
&lt;h3&gt;
  
  
  The commands
&lt;/h3&gt;

&lt;p&gt;Let's now focus on the commands between quotes&lt;/p&gt;

&lt;p&gt;I will use the following schema to explain what's happening.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fn1r14o35uomerk1jcljk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fn1r14o35uomerk1jcljk.png" alt="git-schema" width="800" height="686"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;remote repository&lt;/strong&gt; : the git repo which is on Gitlab, Github...&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;local repository&lt;/strong&gt; : the git repo which is on your machine, containing your branches, your commits... Everything that has a meaning for git&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;working directory&lt;/strong&gt; : where you work, basically, it's the files and directory that you're seeing&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;staging area&lt;/strong&gt; : where your modifications go when you do a &lt;code&gt;git add&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Before presenting the underlying effects, do you know what is &lt;code&gt;origin/master&lt;/code&gt; ? It's a branch ! It's a copy of the remote master branch. It's updated if there are new commits appearing when you do a &lt;code&gt;git fetch&lt;/code&gt; or a &lt;code&gt;git pull&lt;/code&gt;. This means that the &lt;code&gt;origin/master&lt;/code&gt; branch is safe and that we can trust it.&lt;/p&gt;

&lt;p&gt;That's why my sensei is saying that you can delete your &lt;code&gt;master&lt;/code&gt; branch because you already have &lt;code&gt;origin/master&lt;/code&gt; which is the "always clean master".&lt;/p&gt;


&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/slashgear_/git-tip-why-you-should-not-keep-a-local-master-branch-3400" class="crayons-story__hidden-navigation-link"&gt;Git TIP -  Why you should not keep a local master branch ?&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/slashgear_" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F149225%2F784d84fd-4648-416c-9a2c-b1863435f041.jpg" alt="slashgear_ profile" class="crayons-avatar__image" width="400" height="400"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/slashgear_" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Antoine Caron
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Antoine Caron
                
              
              &lt;div id="story-author-preview-content-147986" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/slashgear_" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F149225%2F784d84fd-4648-416c-9a2c-b1863435f041.jpg" class="crayons-avatar__image" alt="" width="400" height="400"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Antoine Caron&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/slashgear_/git-tip-why-you-should-not-keep-a-local-master-branch-3400" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Jul 25 '19&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/slashgear_/git-tip-why-you-should-not-keep-a-local-master-branch-3400" id="article-link-147986"&gt;
          Git TIP -  Why you should not keep a local master branch ?
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/git"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;git&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/learn"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;learn&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/beginners"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;beginners&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/tip"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;tip&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/slashgear_/git-tip-why-you-should-not-keep-a-local-master-branch-3400" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;86&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/slashgear_/git-tip-why-you-should-not-keep-a-local-master-branch-3400#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              29&lt;span class="hidden s:inline"&gt;&amp;nbsp;comments&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            1 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;/div&gt;
&lt;br&gt;


&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;git fetch&lt;/code&gt; we update &lt;code&gt;origin/master&lt;/code&gt;, the copy of the remote master branch&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;git clean -f -d&lt;/code&gt; : it removes untracked files. In other terms it removes files that have never been added to the staging area&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;git reset --hard origin/master&lt;/code&gt; we want, our files (the working copy) to be in the same state as the &lt;code&gt;origin/master&lt;/code&gt; branch&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There's one thing that could remain strange : the &lt;code&gt;!&lt;/code&gt; at the beginning.&lt;br&gt;
The &lt;code&gt;!&lt;/code&gt; is necessary because I'm aliasing a bash command. It's not required if you want to alias a git command. For example you can write &lt;code&gt;git config alias.co = checkout&lt;/code&gt; without &lt;code&gt;!&lt;/code&gt;. More informations &lt;a href="https://git-scm.com/book/en/v2/Git-Basics-Git-Aliases" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  One small thing
&lt;/h3&gt;

&lt;p&gt;If your main branch isn't &lt;code&gt;master&lt;/code&gt; but &lt;code&gt;develop&lt;/code&gt;, for example.&lt;/p&gt;

&lt;p&gt;You can use.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git config &lt;span class="nt"&gt;--global&lt;/span&gt; alias.nuke &lt;span class="s1"&gt;'!git fetch &amp;amp;&amp;amp; git clean -d -f &amp;amp;&amp;amp; git reset --hard origin/develop'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The two parts that can change are the &lt;code&gt;ALIAS_NAME&lt;/code&gt; and the &lt;code&gt;MAIN_BRANCH&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git config &lt;span class="nt"&gt;--global&lt;/span&gt; alias.ALIAS_NAME &lt;span class="s1"&gt;'!git fetch &amp;amp;&amp;amp; git clean -d -f &amp;amp;&amp;amp; git reset --hard origin/MAIN_BRANCH'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's maybe trivial but I prefere to point it out. 😉&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;git remaster&lt;/code&gt; command is just an alias. It brings all the modifications from the remote repository into your local repository.&lt;br&gt;
After that, you need to bring these modifications into your working directory.&lt;/p&gt;

&lt;p&gt;You can run this command everytime you screw a branch and you want to get back to a clean state.&lt;/p&gt;

&lt;p&gt;Don't forget, time is too precious to reclone a repo. Hope it will be useful.&lt;/p&gt;

&lt;p&gt;Thanks to Marvin and Émilie who've helped me with the english translation.&lt;/p&gt;

&lt;p&gt;Thanks for reading.&lt;/p&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://www.motherjones.com/kevin-drum/2019/06/one-third-of-americans-want-to-nuke-north-korea/" rel="noopener noreferrer"&gt;Banner image&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://stackoverflow.com/questions/7534184/git-alias-multiple-commands-and-parameters" rel="noopener noreferrer"&gt;Create alias with mutliple git commands&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://stackoverflow.com/questions/4327708/git-reset-hard-head-leaves-untracked-files-behind" rel="noopener noreferrer"&gt;Remove untracked files with git reset → git clean&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>git</category>
      <category>eatsnobread</category>
      <category>thug</category>
      <category>nuke</category>
    </item>
  </channel>
</rss>
