<?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: Thor Galle</title>
    <description>The latest articles on DEV Community by Thor Galle (@th0rgall).</description>
    <link>https://dev.to/th0rgall</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F373410%2Fd90cf14e-4433-4866-be9c-99c5de15bd79.jpeg</url>
      <title>DEV Community: Thor Galle</title>
      <link>https://dev.to/th0rgall</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/th0rgall"/>
    <language>en</language>
    <item>
      <title>Migrating static redirects from Gatsby Cloud to Vercel</title>
      <dc:creator>Thor Galle</dc:creator>
      <pubDate>Fri, 22 Sep 2023 12:37:42 +0000</pubDate>
      <link>https://dev.to/th0rgall/migrating-static-redirects-from-gatsby-cloud-to-vercel-2laa</link>
      <guid>https://dev.to/th0rgall/migrating-static-redirects-from-gatsby-cloud-to-vercel-2laa</guid>
      <description>&lt;p&gt;Since Gastby Cloud &lt;a href="https://www.netlify.com/blog/taking-gatsby-to-new-heights-embracing-adaptability-and-openness/"&gt;is being discontinued&lt;/a&gt; in favor of Netlify's offering, I was looking for a new home for my personal website, which was hosted using Gatsby Cloud's free tier.&lt;/p&gt;

&lt;p&gt;The suggested migration path logically led to Netlify, but since I already hosted some other sites via Vercel, I wanted to try that option first. Luckily, Vercel &lt;a href="https://vercel.com/docs/frameworks/gatsby"&gt;has&lt;/a&gt; built-in support for Gatsby.&lt;/p&gt;

&lt;p&gt;The migration was mostly a simple matter of linking my GitHub repository to a new Vercel project, bulk-copy-pasting environment variables, and clicking "Deploy", however, there was just one thing didn't work out-of-the-box: &lt;strong&gt;static redirects&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I couldn't find specific migration documentation on this, but it wasn't hard to figure out either. Hence this short writeup!&lt;/p&gt;

&lt;h2&gt;
  
  
  Recap: redirects in Gatsby Cloud
&lt;/h2&gt;

&lt;p&gt;Redirects in Gatsby Cloud were made available through the plugin &lt;a href="https://github.com/gatsbyjs/gatsby/tree/db41d1356c527cf4028142050978accd4abb1e9a/packages/gatsby-plugin-gatsby-cloud"&gt;gatsby-plugin-gatsby-cloud&lt;/a&gt;. In your &lt;code&gt;gatsby-node.{js,ts}&lt;/code&gt;, this plugin interacted with the &lt;a href="https://www.gatsbyjs.com/docs/reference/config-files/actions/#createRedirect"&gt;&lt;code&gt;createRedirect&lt;/code&gt;&lt;/a&gt; action. Wildcard replace rules were also supported:&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="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createPages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;actions&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;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createRedirect&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;actions&lt;/span&gt;

  &lt;span class="nf"&gt;createRedirect&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;fromPath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/posts/*&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;toPath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/articles/*&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Static redirects in Vercel
&lt;/h2&gt;

&lt;p&gt;In contrast to Gatsby Cloud, Vercel does not seem to automagically transform &lt;code&gt;createRedirect&lt;/code&gt; calls during the SSG build stage into runtime redirections.&lt;/p&gt;

&lt;p&gt;Instead, Vercel &lt;strong&gt;understands a configuration file called &lt;code&gt;vercel.json&lt;/code&gt;&lt;/strong&gt; in the root of your repository, which can be used to configure Vercel's build &amp;amp; runtime behavior for a static site.&lt;/p&gt;

&lt;p&gt;It also allows you to specify redirects, including wildcard replacement rules. The example above can be encoded as follows in &lt;a href="https://vercel.com/docs/projects/project-configuration"&gt;&lt;code&gt;vercel.json&lt;/code&gt;&lt;/a&gt;:&lt;br&gt;
&lt;/p&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="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"redirects"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/posts/:path*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"destination"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/articles/:path*"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&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;h2&gt;
  
  
  So, to migrate:
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Transform the format of your rules into Vercel's JSON format (&lt;a href="https://vercel.com/docs/projects/project-configuration#redirects"&gt;see docs&lt;/a&gt;). If you have many rules, you can use tools like VSCode regex search/replace, multi-cursor editing, or GNU sed to automate this process.&lt;/li&gt;
&lt;li&gt;Put those rules in &lt;code&gt;vercel.json&lt;/code&gt; at the root of your repository.&lt;/li&gt;
&lt;li&gt;Remove all traces of &lt;code&gt;createRedirect()&lt;/code&gt; from your code, remove &lt;code&gt;gatsby-plugin-gatsby-cloud&lt;/code&gt; from your &lt;code&gt;gatsby.config.js&lt;/code&gt;, and remove the plugin package using your package manager.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That's all there is to it!&lt;/p&gt;

</description>
      <category>vercel</category>
      <category>gatsby</category>
    </item>
    <item>
      <title>Running multiple Google Cloud functions locally with the functions-framework</title>
      <dc:creator>Thor Galle</dc:creator>
      <pubDate>Thu, 27 Oct 2022 10:47:17 +0000</pubDate>
      <link>https://dev.to/th0rgall/running-multiple-google-cloud-functions-locally-with-the-functions-framework-27p1</link>
      <guid>https://dev.to/th0rgall/running-multiple-google-cloud-functions-locally-with-the-functions-framework-27p1</guid>
      <description>&lt;p&gt;So, you want to run multiple node.js Google Cloud Functions locally at the same time using Google’s &lt;a href="https://github.com/GoogleCloudPlatform/functions-framework"&gt;functions-framework&lt;/a&gt;?&lt;/p&gt;

&lt;p&gt;You might have previously written cloud functions in Firebase, where the &lt;a href="https://firebase.google.com/docs/emulator-suite"&gt;Firebase Local Emulator Suite&lt;/a&gt; allowed you to run all your functions simultaneously, on a single local server, with a single command (&lt;code&gt;firebase emulators:start&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;The function framework does not provide an emulator that can do this out-of-the-box. However, &lt;strong&gt;you can very easily write one yourself&lt;/strong&gt;, and approximate Firebase’s local development experience this way.&lt;/p&gt;

&lt;p&gt;This approach combines your functions in a single Express “meta” app for development purposes only. You can still deploy the functions individually to Google Cloud.&lt;/p&gt;

&lt;h2&gt;
  
  
  Example setup
&lt;/h2&gt;

&lt;p&gt;In this example I have the following &lt;strong&gt;directory structure&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;├── package.json
└── src
    ├── index.js
    └── functions
        ├── firstFunction.js
        └── secondFunction.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The function scripts
&lt;/h3&gt;

&lt;p&gt;The two functions themselves are full-fledged Express.js handlers, like they would be in Firebase. &lt;/p&gt;

&lt;p&gt;To test that the two functions can interact, the first function returns HTTP 302 redirect, which redirects to a GET request on the second function.&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="c1"&gt;// src/functions/firstFunction.js&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;firstFunction&lt;/span&gt; &lt;span class="o"&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;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&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;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/secondFunction&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/functions/secondFunction.js&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;secondFunction&lt;/span&gt; &lt;span class="o"&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;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&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;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;OK! You were redirected here.&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  package.json
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;package.json&lt;/code&gt; refers to the &lt;code&gt;src/index.js&lt;/code&gt; as the main node script. We also need to tell the functions-framework to target the &lt;code&gt;index&lt;/code&gt; export within the &lt;code&gt;index.js&lt;/code&gt; module:&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="c1"&gt;// package.json&lt;/span&gt;

&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;type&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;module&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="c1"&gt;// Tells the functions-framework where to look for exports.&lt;/span&gt;
&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;main&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;src/index.js&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;scripts&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;start&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;functions-framework --target=index&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Select target export&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;debug&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;functions-framework --target=index --debug&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  index.js
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;index.js&lt;/code&gt; file is the core of this setup. It’s where we will expose all functions combined on a single local address, as well as expose functions individually.&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="c1"&gt;// src/index.js&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;express&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;express&lt;/span&gt;&lt;span class="dl"&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;firstFunction&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;./functions/firstFunction.js&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;secondFunction&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;./functions/secondFunction.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Solution to expose multiple cloud functions locally&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="nf"&gt;express&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/firstFunction&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;firstFunction&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/secondFunction&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;secondFunction&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;


&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;firstFunction&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;secondFunction&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The local functions-framework will target the &lt;code&gt;index&lt;/code&gt; export exported from &lt;code&gt;index.js&lt;/code&gt;, see the &lt;code&gt;package.json&lt;/code&gt; above. The &lt;code&gt;index&lt;/code&gt; export is only meant for local development purposes, so we can run multiple functions at once locally. &lt;/p&gt;

&lt;p&gt;We still export the individual functions too, so we can easily deploy both functions individually. See “Deploying functions individually” below.&lt;/p&gt;

&lt;h2&gt;
  
  
  Running the functions together
&lt;/h2&gt;

&lt;p&gt;Now if we run &lt;code&gt;npm run start&lt;/code&gt;, a development server will start on &lt;code&gt;http://localhost:8080&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Running &lt;code&gt;curl http://localhost:8080/firstFunction&lt;/code&gt; will print &lt;code&gt;OK! You were redirected here.&lt;/code&gt;, demonstrating that both functions are running at the same time.&lt;/p&gt;

&lt;p&gt;If you still want to test a function in isolation, you can run &lt;code&gt;functions-framework --target=firstFunction&lt;/code&gt; instead, after which you can call it as usual with &lt;code&gt;curl http://localhost:8080&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deploying functions individually
&lt;/h2&gt;

&lt;p&gt;Functions can still be individually deployed, with the &lt;code&gt;gcloud&lt;/code&gt; CLI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gcloud functions deploy firstFunction &lt;span class="nt"&gt;--project&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;my-project &lt;span class="nt"&gt;--runtime&lt;/span&gt; nodejs16 &lt;span class="nt"&gt;--trigger-http&lt;/span&gt; &lt;span class="nt"&gt;--allow-unauthenticated&lt;/span&gt;  &lt;span class="nt"&gt;--security-level&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;secure-always &lt;span class="nt"&gt;--region&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;eyour-region &lt;span class="nt"&gt;--entry-point&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;firstFunction &lt;span class="nt"&gt;--memory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;128MB &lt;span class="nt"&gt;--timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;60s
&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;gcloud functions deploy secondFunction &lt;span class="nt"&gt;--project&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;my-project &lt;span class="nt"&gt;--runtime&lt;/span&gt; nodejs16 &lt;span class="nt"&gt;--trigger-http&lt;/span&gt; &lt;span class="nt"&gt;--allow-unauthenticated&lt;/span&gt;  &lt;span class="nt"&gt;--security-level&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;secure-always &lt;span class="nt"&gt;--region&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;your-region &lt;span class="nt"&gt;--entry-point&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;secondFunction &lt;span class="nt"&gt;--memory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;128MB &lt;span class="nt"&gt;--timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;60s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key here is &lt;code&gt;--entrypoint firstFunction&lt;/code&gt; flag, which is similar to &lt;code&gt;--target&lt;/code&gt; flag on the &lt;code&gt;functions-framework&lt;/code&gt; command. It selects the module export of the index script that should be seen as the entry point for the cloud function.&lt;/p&gt;

&lt;p&gt;You &lt;em&gt;could&lt;/em&gt; also deploy the &lt;code&gt;index&lt;/code&gt; export as a single function that combines all functions in one, but then you would have call &lt;code&gt;/index/firstFunction&lt;/code&gt; and &lt;code&gt;/index/secondFunction&lt;/code&gt; on the cloud, and you then can’t scale or modify the function runtimes individually anymore.&lt;/p&gt;




&lt;h2&gt;
  
  
  Caveats
&lt;/h2&gt;

&lt;p&gt;Using Express “sub apps” this way is not a 100% watertight way to emulate multiple individual functions running in Google Cloud. There are some caveats.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;req.originalUrl&lt;/code&gt; ⚠️
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://expressjs.com/en/5x/api.html#req.originalUrl"&gt;Express Docs&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This property is much like &lt;code&gt;req.url&lt;/code&gt;; however, it retains the original request URL, allowing you to rewrite &lt;code&gt;req.url&lt;/code&gt; freely for internal routing purposes. For example, the “mounting” feature of &lt;a href="https://expressjs.com/en/5x/api.html#app.use"&gt;app.use()&lt;/a&gt; will rewrite &lt;code&gt;req.url&lt;/code&gt; to strip the mount point.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt; &lt;code&gt;req.originalUrl&lt;/code&gt; &lt;em&gt;doesn’t&lt;/em&gt; behave as it would in a real production Google Cloud  multi-function setup. &lt;/p&gt;

&lt;p&gt;On Google Cloud, &lt;code&gt;req.originalUrl&lt;/code&gt; excludes the function name. This is likely due to some internal redirections that Google Cloud does.&lt;/p&gt;

&lt;p&gt;With the index.js emulator proposed by this post, &lt;code&gt;req.originalUrl&lt;/code&gt; will still &lt;em&gt;include&lt;/em&gt; the function name.&lt;/p&gt;

&lt;p&gt;Make sure that your code does not depend on the value &lt;code&gt;req.originalUrl&lt;/code&gt; for some decisions. If it does, you might need to adapt this code.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;req.path&lt;/code&gt; behavior ✅
&lt;/h3&gt;

&lt;p&gt;In Google Cloud, accessing &lt;code&gt;req.path&lt;/code&gt; in a function running on &lt;code&gt;https://your-google-cloud-domain/firstFunction&lt;/code&gt; will yield &lt;code&gt;/&lt;/code&gt;, and &lt;em&gt;not&lt;/em&gt; &lt;code&gt;/firstFunction&lt;/code&gt; (somewhat suprisingly).&lt;/p&gt;

&lt;p&gt;With the above caveat, you might wonder, does this behavior also not copy to our local emulator setup?&lt;/p&gt;

&lt;p&gt;The answer is: this behavior &lt;strong&gt;remains the same&lt;/strong&gt;. See the Express documentation for &lt;a href="https://expressjs.com/en/5x/api.html#req.path"&gt;req.path&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;When called from a middleware, the &lt;strong&gt;mount point is not included&lt;/strong&gt; in &lt;code&gt;req.path&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;When we call &lt;code&gt;app.use('/firstFunction', firstFunction);&lt;/code&gt;, we register &lt;code&gt;firstFunction&lt;/code&gt; as &lt;a href="https://expressjs.com/en/guide/using-middleware.html#middleware.application"&gt;application-level middleware&lt;/a&gt; onto the &lt;code&gt;app&lt;/code&gt;  with the “mount point” being &lt;code&gt;/firstFunction&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  More?
&lt;/h3&gt;

&lt;p&gt;There might be other caveats I’m not aware of with this setup, but for now it suits my purposes, and the added local development convenience is worth any future complications.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;p&gt;Closing note: this solution is based on &lt;a href="https://github.com/GoogleCloudPlatform/functions-framework-nodejs/issues/23#issuecomment-533586507"&gt;an answer in a related GitHub Issue thread&lt;/a&gt; that you might have seen already when researching this issue.&lt;/p&gt;

&lt;p&gt;I wrote this post because that thread contains several approaches to this issue that were less relevant to my use-case (and, the thread was also filled with more drama than necessary). I hope developers used to the Firebase functions model find this setup suggestion helpful.&lt;/p&gt;

</description>
      <category>googlecloud</category>
      <category>serverless</category>
      <category>node</category>
      <category>express</category>
    </item>
    <item>
      <title>What makes a good programmer? A misleading question.</title>
      <dc:creator>Thor Galle</dc:creator>
      <pubDate>Wed, 24 Nov 2021 12:50:05 +0000</pubDate>
      <link>https://dev.to/th0rgall/what-makes-a-good-programmer-a-misleading-question-2mbn</link>
      <guid>https://dev.to/th0rgall/what-makes-a-good-programmer-a-misleading-question-2mbn</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a response to "Cargo cult programming is killing the (Sri Lankan) software industry"&lt;/em&gt; &lt;em&gt;(&lt;a href="https://link.medium.com/KH0Hiu1jglb"&gt;direct link&lt;/a&gt;, &lt;a href="https://readup.com/comments/hliyanmediumcom/cargo-cult-programming-is-killing-the-sri-lankan-software-industry"&gt;Readup link&lt;/a&gt;). I was writing a comment on Readup, but it became its own blog post!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;As a person who programs for a living, I think it's easy to have an opinion and personal bagage related to the skills and knowledge you need to succeed in your job. The topic of the article speaks to all of us, it plays on the question: what should a good programmer know, and what should they be able to do?&lt;/p&gt;

&lt;p&gt;It's a misleading question however. I don't think there is, and there should not be, one single kind of programmer.&lt;/p&gt;

&lt;p&gt;First of all, the genius programming nerd with all the right skills in computing doesn't exist anymore. They might have existed a few decennia ago, when computing was an offline niche with very few applications. But today's computing world is too diversified for this human to exist.&lt;/p&gt;

&lt;p&gt;Intimately knowing networking fundamentals, and how the Linux kernel interacts with networking hardware, doesn't automatically make you the good front-end web developer that can learn Svelte quickly after having built web apps in React for five years. In fact, you might not know much about the DOM at all.&lt;/p&gt;

&lt;p&gt;Computer systems in the hands of end-users are massive contraptions where millions of people and brains had a hand in: from the person who assembled a website, to the people who designed the HTTP protocol, to the admins of the cloud service that serves the web server container, and many more. I'd argue that since computing is "eating up" much of the global economy, the incomprehensible complexity of the economy becomes the complexity of computing too. That complexity is timelessly illustrated by the essay &lt;a href="https://readup.com/read/foundation-for-economic-education/i-pencil"&gt;I, Pencil&lt;/a&gt; (a recommended read!). &lt;/p&gt;

&lt;p&gt;A good point of the author though, is that we need to be able to distinguish different jobs in computing. "Programmer" or "Engineer" doesn't cut it. A "WordPress Technician", as the writer would call them, who knows all the best WP plugins and understands a little bit of PHP, can assemble a website fit for an indie magazine much faster, cheaper and better than a "cloud engineer" at Google who's creating algorithms for efficient load distribution of incoming network requests. They're people with totally different jobs doing totally different things.&lt;/p&gt;

&lt;p&gt;Another good point is that for many jobs in computing, it's a good idea to dig a little deeper down the levels of abstraction than would seem strictly necessary. But depending on your career goals and interests, you could constrain that effort to what &lt;em&gt;might&lt;/em&gt; be useful for you. A React front-end developer that entered programming through a one-month bootcamp would be wise to understand how React interfaces with the DOM, because that will indeed make understanding Svelte easier when React goes out of fashion (hypothetically). Learning sorting algorithms might be useful too, if they are trying to remix a big dataset for some purpose, with a triple for-loop in JS that makes their laptop overheat. Soon they will also need to adapt their jobs to be able to build front-end apps for the VR internet in 10 years. But is it essential for them to know how heaps and stacks work? Not really. That's why we have abstraction: to manage complexity. You only need dig deeper if you want to do something that would require that knowledge, or if you're just curious.&lt;/p&gt;

&lt;p&gt;This reminds me too of &lt;a href="https://twitter.com/seldo/status/1453094314439942149?s=20"&gt;a self-deprecating twitter thread&lt;/a&gt; of Lauri Voss, one of the NPM founders:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"I am, at best, a developer of average ability. [...] I have come to accept that I'm never going to invent some brand-new algorithm that changes the world."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But he comes to terms with this own assessment of his abilities in developing algorithms:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If there's a thing I'm above-average at, it's spotting patterns and weaving a narrative around them to explain what's going on. I &lt;em&gt;explain&lt;/em&gt;. [...] Given that's my deal, being a mediocre developer is weirdly kind of helpful. I tend to see things the way most developers do.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In the capacity as a teacher, he has likely made a massive contribution to the computing economy. And that's just fine.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The point of this blog post response was to illustrate that there are different skillsets in programming, and that this is just fine. The article it responds to however also talks about  a "brain drain" in relation to tech workers in Sri Lanka. It's another issue which &lt;a href="https://readup.com/comments/hliyanmediumcom/cargo-cult-programming-is-killing-the-sri-lankan-software-industry/zKyZl5"&gt;I assume is related to late-stage colonialism and outsourcing&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>fundamentals</category>
      <category>abstraction</category>
      <category>learning</category>
    </item>
    <item>
      <title>The Basics of Dependency Maintenance in NPM/yarn</title>
      <dc:creator>Thor Galle</dc:creator>
      <pubDate>Mon, 07 Jun 2021 09:22:35 +0000</pubDate>
      <link>https://dev.to/th0rgall/the-basics-of-dependency-maintenance-in-npm-yarn-4c1l</link>
      <guid>https://dev.to/th0rgall/the-basics-of-dependency-maintenance-in-npm-yarn-4c1l</guid>
      <description>&lt;p&gt;Think back: you're about to start contributing to a web project. You clone it, run &lt;code&gt;npm install&lt;/code&gt;, and then... you get one error after the other. You can probably name at least one of these experiences. In the worst case, this can lead us to abandon our plans of contribution. The bad news is that this bad experience is ubiquitous. The good news is that it is entirely avoidable, and it's not even that hard to avoid!&lt;/p&gt;

&lt;p&gt;This post is a collection of &lt;strong&gt;best practices regarding dependency maintenance in NPM&lt;/strong&gt;, they help you keep your project in a healthy state so that if you ever have to hand it over, invite collaborators, or revisit it, it won't be an absolute pain in the ***.&lt;/p&gt;

&lt;h3&gt;
  
  
  The tips
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Make sure you understand Semantic Versioning&lt;/li&gt;
&lt;li&gt;Use &amp;amp; commit the lockfile&lt;/li&gt;
&lt;li&gt;Update dependencies regularly&lt;/li&gt;
&lt;li&gt;Take on less dependencies&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;(clicking will lead you to the specific section)&lt;/p&gt;

&lt;p&gt;I wrote this as a synthesis of my experience maintaining a Nuxt.js website project for two years at &lt;a href="https://thorgalle.me/work#columbiaroad"&gt;Columbia Road&lt;/a&gt;, and working on several other projects in a sub-optimal state. It is intended for readers with some general JS-stack web development experience, but with little experience maintaining a web project.&lt;/p&gt;

&lt;p&gt;Note that the focus here lies on the &lt;em&gt;consumption&lt;/em&gt; side: the managing and updating of dependencies in a web project you're working on. This is not discussing how to maintain an NPM package that you have published, although there should be some overlap in best practices. Basic familiarity with git is assumed.&lt;/p&gt;

&lt;h1&gt;
  
  
  1. Make sure you understand Semantic Versioning &lt;a&gt;&lt;/a&gt;
&lt;/h1&gt;

&lt;p&gt;First things first: whether you're installing dependencies, updating dependencies, or you're publishing your own package and making changes to it, understanding &lt;a href="https://docs.npmjs.com/cli/v6/using-npm/semver"&gt;semantic versioning (semver)&lt;/a&gt; is essential. &lt;/p&gt;

&lt;p&gt;Most software projects today use semver to tag versions of their program releases (eg. &lt;code&gt;v1.2.15&lt;/code&gt;). The versions have three numeric parts: &lt;strong&gt;major.minor.patch.&lt;/strong&gt; The &lt;strong&gt;major&lt;/strong&gt; version should be increased by one ('bumped') when the software interface of the dependency has &lt;em&gt;breaking changes&lt;/em&gt; (which means: your code will break or behave differently if you update the dependency without changing your code). Even when the breaking change is seemingly small and simple, like a changed function name, the major version should have been bumped. If package publishers do not respect this, it can easily lead to problems when people consuming those packages update their dependencies: they end up installing incompatible dependency code!&lt;/p&gt;

&lt;p&gt;Another important realization is that semver defines several &lt;em&gt;range types&lt;/em&gt; of versions, that is, saying that any version that is included in a certain range of versions is OK to install as a dependency. &lt;a href="https://docs.npmjs.com/cli/v6/using-npm/semver#caret-ranges-123-025-004"&gt;The caret range (~)&lt;/a&gt; in particular is the default version notation used when you run &lt;code&gt;npm install some-package&lt;/code&gt; to add a new package to your project (thus, in your &lt;code&gt;package.json&lt;/code&gt;). It &lt;em&gt;mostly&lt;/em&gt; allows variations in the &lt;strong&gt;minor&lt;/strong&gt; and &lt;strong&gt;patch&lt;/strong&gt; versions, which is usually safe. However, its exact definition is important to check out, as there is an exception that allows more versions than you might expect!&lt;/p&gt;

&lt;h1&gt;
  
  
  2. Use &amp;amp; commit the lockfile &lt;a&gt;&lt;/a&gt;
&lt;/h1&gt;

&lt;p&gt;Both NPM and Yarn have had a system a lockfile for some time now: &lt;code&gt;package-lock.json&lt;/code&gt;in NPM or &lt;code&gt;yarn.lock&lt;/code&gt; in Yarn. But what does this file do?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This file keeps track of the exact versions of all your dependencies and their sub-dependencies.&lt;/strong&gt; Concretely, it lists which dependencies are stored in your &lt;code&gt;node_modules&lt;/code&gt; folders at this moment.&lt;/p&gt;

&lt;p&gt;This is very useful, because another developer with the same lockfile can install exactly the same dependency tree on a fresh &lt;code&gt;npm install&lt;/code&gt;. Without a lockfile in place, different dependency versions could be installed at different times despite being installed from the same &lt;code&gt;package.json&lt;/code&gt;. The reason for this is that "desired versions" in &lt;code&gt;package.json&lt;/code&gt; are often specified as a relatively loose range, such as the caret range discussed before.&lt;/p&gt;

&lt;p&gt;The problem with having a different (newer) version of a dependency than another team member, for example 3.7.24 instead of 3.5.2, is that it always carries the risk some changed behavior that breaks your code in one way or another.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Commit your lockfile&lt;/strong&gt; so that everyone shares access to it, and changes to the dependency tree are tracked in git. This will even enable you to time-travel (roll back) to the exact state of an older version of your project via git. &lt;/p&gt;

&lt;p&gt;Note also that in general, &lt;strong&gt;you should avoid mixing the usage of npm and yarn in your team&lt;/strong&gt;, because using two different lockfiles may lead to problems where you end up installing different package versions despite having a lockfile. The risks of having such issues have decreased over the last years (npm now also reads yarn.lock, which it didn't before), but even if it's just for consistency &amp;amp; simplicity, picking one is better.&lt;/p&gt;

&lt;p&gt;Read more on &lt;a href="https://docs.npmjs.com/cli/v6/configuring-npm/package-locks"&gt;lockfiles in the NPM docs&lt;/a&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  3. Update dependencies regularly &lt;a&gt;&lt;/a&gt;
&lt;/h1&gt;

&lt;p&gt;Check for new dependency updates regularly, for example, every 2-3 months, depending on how frequently you work on your project. &lt;/p&gt;

&lt;h2&gt;
  
  
  Why not just stick with what works now?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Not updating your dependencies means incurring technical debt.&lt;/strong&gt; This debt will make it progressively harder and costlier to update later, when you really need to.&lt;/p&gt;

&lt;p&gt;For the example's sake: let's say you didn't update dependencies for two years, this can can cause major headaches. Here's why:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The JS ecosystem moves fast.&lt;/strong&gt; Some of your dependencies might already be deprecated in two years time!&lt;/li&gt;
&lt;li&gt;It may become harder to update the dependencies, &lt;strong&gt;because dependencies typically make assumptions about their environment or other ('peer') dependencies&lt;/strong&gt; (for example, &lt;code&gt;sass-loader&lt;/code&gt; is built for a specific Node.js and webpack version). The latest version of one dependency might not fit in your old, potentially deprecated, environment anymore, causing issues.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Changelogs and migration guides become progressively harder to find as time advances.&lt;/strong&gt; Guides to update to a new major version are sometimes stored on a project's website, which might lose history quicker than git. It then requires detective-level work (e.g., the Internet Archive) to find back these migration guides. And when finding them, they may be outdated. I had this issue when updating from Nuxt 1.4 to Nuxt 2, which had been released a year before I did the update.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Community support is better for fresh issues.&lt;/strong&gt; When developers notice an issue in a fresh update, they usually file a GitHub issue on a project. This makes it possible to find quick workarounds and solutions that you can apply as a community. But if you research issues two years later; A) chances are no one cares anymore to help with a two-year old issue. B) It may be harder to find old solutions amongst a mess of other search results: search engines seem to prioritize more recent search hits.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now imagine that your product has a critical security flaw caused by one of its dependencies. To fix it you might encounter any of the above headaches. In the worst case, you have to do heavy refactoring or simply restart your project. &lt;/p&gt;

&lt;p&gt;Hence, spending maintenance time to keep things up-to-date is probably cheaper in the long run. Here's a simple routine of how to do it:&lt;/p&gt;

&lt;h2&gt;
  
  
  Dependency updating routine
&lt;/h2&gt;

&lt;p&gt;Use this routine to update your dependencies:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Run &lt;code&gt;npm outdated&lt;/code&gt; or similar to know which dependencies are outdated.

&lt;ul&gt;
&lt;li&gt;"Wanted" describes the maximum version of a package that is allowed by the range in your &lt;code&gt;package.json&lt;/code&gt; (which is usually the caret (~) range, which &lt;em&gt;excludes&lt;/em&gt; major breaking versions). &lt;/li&gt;
&lt;li&gt;"Latest" is the last version available (includes major breaking versions when available!).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;For each dependency, check the &lt;strong&gt;changelog&lt;/strong&gt; or version notes of all the versions ("releases") of the dependency between your target version, and the one you have currently installed. This step is crucial when you're doing a major-version update. It could be useful when doing a minor-version update; and can often be skipped when it is a patch-level version (see the section about semver. &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;
  👆 &lt;em&gt;Caveat: watch out when you use exact packages and you see a major upgrade&lt;/em&gt;
  &lt;p&gt;If you use exact version definitions and are on an old major version, the Wanted column will only show the version you have installed (for example, &lt;code&gt;vue-router@3.5.3&lt;/code&gt;)  When Latest shows a major version upgrade that you don't want (&lt;code&gt;vue-router@4.0.14&lt;/code&gt;), this can obscure minor/patch updates that you do want.&lt;/p&gt;

&lt;p&gt;In this case, also run &lt;code&gt;npm view vue-router@3 version&lt;/code&gt; to get the list of available v3.x.x package, and manually check if you can bump the minor/patch version.&lt;br&gt;
   &lt;/p&gt;

&lt;/p&gt;

&lt;p&gt;
  👆 &lt;em&gt;How to find the changelog of a dependency?&lt;/em&gt;
  &lt;br&gt;
There are various approaches that open-source package maintainers use to keep a change log. But mostly, they use GitHub's versions and releases pages to write notes on what changed (and updating instructions).

&lt;p&gt;To find the GitHub Releases of a package:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In a browser: go to &lt;a href="https://www.npmjs.com/package/"&gt;https://www.npmjs.com/package/&lt;/a&gt; and click "Repository" to find the GitHub repo of a package. Then, on the right in GitHub, click "Releases".&lt;/li&gt;
&lt;li&gt;Fun trick: run the npm CLI command &lt;code&gt;npm repo &amp;lt;package-name&amp;gt;&lt;/code&gt; to immediately open the GitHub repo of a pack
&lt;/li&gt;
&lt;/ul&gt;



  👆 &lt;em&gt;What if there is a major version (breaking) change?&lt;/em&gt;
  &lt;p&gt;Always read the release notes/changelog of breaking changes. Breaking changes might require you to change your code in some places when doing the update. Watch out especially for notes that relate to other dependencies: maybe a breaking change in one package requires another dependency to be updated or installed alongside it? Maybe it requires a new Node.js version?
&lt;/p&gt;

&lt;br&gt;
&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;3. If updating seems safe, perform the updates. &lt;br&gt;
   
  👆 &lt;em&gt;Choose the applicable update method depending on the version you want to update to&lt;/em&gt;
  &lt;br&gt;
Choose the applicable update method depending on the version you want to update to&lt;br&gt;
        - &lt;code&gt;npm install &amp;lt;package_name&amp;gt;@&amp;lt;version_number&amp;gt;&lt;/code&gt;, always installs the given version, ignoring whatever version ranges are specified in your &lt;code&gt;package.json&lt;/code&gt;. In fact, it will by default &lt;em&gt;modify&lt;/em&gt; your &lt;code&gt;package.json&lt;/code&gt; to hold the version you're installing. You might need to use this method to bump the major version, or when you don't use caret version ranges but exact versions in your package.json.&lt;br&gt;
        - &lt;code&gt;npm update &amp;lt;package_name&amp;gt;&lt;/code&gt; installs the latest version permitted by your &lt;code&gt;package.json&lt;/code&gt;, for example: new minor and patch versions when you were using a caret version range.&lt;br&gt;
   


  👆 &lt;em&gt;Update strategy: include limited update in a single commit&lt;/em&gt;
  &lt;p&gt;Updating one dependency per commit, or at least, updating outdated packages in batches, may be useful when doing many updates after not updating for a long time. I usually do it like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I bundle patch-version updates in a commit&lt;/li&gt;
&lt;li&gt;Minor/major versions are in their own commits&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Why? This way you can more easily find back the dependency update that broke something in your project.&lt;/p&gt;

&lt;p&gt;If you do &lt;code&gt;npm update&lt;/code&gt; with a list of 30 dependencies to be updated, there is a high likelihood that something will go wrong. And it might be hard to pinpoint which (combination) of those 30 dependencies was the culprit.&lt;/p&gt;
&lt;h3&gt;
  
  
  Test after each update
&lt;/h3&gt;

&lt;p&gt;Ideally you have a fully-automated &amp;amp; reliable test suite that can test whether things broke or not on every update. This is often not the case however, so you most likely should:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Do a build and run the project after an update. Resolve any errors that appear. Commit&lt;/li&gt;
&lt;li&gt;Repeat the above&lt;/li&gt;
&lt;li&gt;After the final update, do a more thorough testing round by trying out most paths in the app. If something seems off now that you didn't see before (e.g. something doesn't render, but there was no error thrown on build time), use the next method to detect which dependency caused it.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;
  
  
  Detect a problematic commit (= dep update) with &lt;code&gt;git bisect&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;If you do 1 update with 1 commit at a time, and an issue surfaces at the end, you can efficiently detect which dependency update introduced it with the git tool &lt;code&gt;git bisect&lt;/code&gt; (&lt;a href="https://git-scm.com/docs/git-bisect"&gt;docs&lt;/a&gt;, included in git). Instead of rolling back the commits one-by-one to see where the issue was introduced, this tool guides you through a "&lt;a href="https://en.wikipedia.org/wiki/Binary_search_algorithm"&gt;binary search&lt;/a&gt;" through a commit range, asking you to mark a commit as "good" or "bad" depending on their behavior, quickly narrowing down the range of potential commits that might be the culprit, ending at a single one! &lt;/p&gt;


  👆 ⚠️ &lt;em&gt;Don't have a lockfile? Duplicate the project before updating!&lt;/em&gt;
  &lt;p&gt;Do you have functioning project installed locally &lt;em&gt;without lockfile&lt;/em&gt; that has (very) old dependencies, e.g. it was running on node 4? &lt;strong&gt;Don't start updating immediately!&lt;/strong&gt; You risk losing control over the project beyond repair.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Make a duplicate of the entire project folder to start the update,&lt;/strong&gt; there's a high chance that an update will break something. Without lockfile, you can't travel back to the original functioning state. By duplicating you can preserve your original &lt;code&gt;node_modules&lt;/code&gt; which enables you inspect a &lt;em&gt;functioning&lt;/em&gt; version of the project if needed, or to roll back if you're really stuck.&lt;br&gt;
   &lt;/p&gt;

&lt;/p&gt;

&lt;p&gt;4. After updating, run &lt;code&gt;npm audit&lt;/code&gt; to figure out which dependencies have a security vulnerability. Is a fix announced? If not, maybe you can switch to a different dependency.&lt;/p&gt;

&lt;p&gt;
  👆 &lt;em&gt;What if a sub-dependency is causing a security issue?&lt;/em&gt;
  &lt;br&gt;
If a sub-dependency (dependency of a dependency, or even further) is causing the issue, but your direct dependency does not offer a new version that updates the sub-dependency, you can try to "override" the sub-dependency in the direct dependency using in Yarn using &lt;a href="https://classic.yarnpkg.com/en/docs/selective-version-resolutions/"&gt;selective version resolutions&lt;/a&gt; (since yarn v1, released on Sep 16, 2020, we're at 1.22.5 at the moment of writing). &lt;br&gt;


&lt;/p&gt;

&lt;h1&gt;
  
  
  4. Take on less dependencies &lt;a&gt;&lt;/a&gt;
&lt;/h1&gt;

&lt;p&gt;It's obvious but worth mentioning: the easiest way to avoid dependency issues is to avoid having dependencies. Taking on a dependency is always a trade-off between the value or time-savings it offers, and the maintenance costs of managing code that you didn't build yourself. &lt;/p&gt;

&lt;h1&gt;
  
  
  Recap: action points
&lt;/h1&gt;

&lt;p&gt;Here are action points to follow-up on these best practices.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Learn about &lt;a href="https://semver.org/"&gt;semver&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Make sure your lockfile is committed to git&lt;/li&gt;
&lt;li&gt;Set up a scheduled recurring task in your calendar/task manager to update your dependencies&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I hope this helped! In a possible next post on this topic, I may look at some more advanced ways of future-proofing &amp;amp; debugging, such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Preventing issues with a &lt;code&gt;ci&lt;/code&gt; install&lt;/li&gt;
&lt;li&gt;Using exact semver versions for all your dependencies&lt;/li&gt;
&lt;li&gt;Best practices specifically for Node.js projects&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Credits
&lt;/h1&gt;

&lt;p&gt;Apart from my own experiences, these notes were partially based on the talk &lt;em&gt;"Future-proof dependency management in TS projects talk"&lt;/em&gt; by &lt;a href="https://olavihaapala.fi/"&gt;Olavi Haapala&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=bNRbSlDtqg8"&gt;Tech Weeklies - Future-proof dependency management in TS projects - 05/20 - Olavi Haapala&lt;/a&gt;&lt;/p&gt;

</description>
      <category>npm</category>
      <category>dependency</category>
      <category>javascript</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Finding the original mp4 video of a GIPHY Capture</title>
      <dc:creator>Thor Galle</dc:creator>
      <pubDate>Mon, 03 May 2021 08:18:12 +0000</pubDate>
      <link>https://dev.to/th0rgall/finding-the-original-mp4-video-of-a-giphy-capture-284n</link>
      <guid>https://dev.to/th0rgall/finding-the-original-mp4-video-of-a-giphy-capture-284n</guid>
      <description>&lt;p&gt;What if you're using GIPHY's fun &lt;a href="https://giphy.com/apps/giphycapture" rel="noopener noreferrer"&gt;Capture&lt;/a&gt; tool on macOS, spend some time creating recordings, only to realize that GIFs are not the best format for your recordings?[^1] Or, you just want to archive/share/reuse a higher-quality version of your recording?&lt;/p&gt;

&lt;p&gt;The tool does not offer a solution to export the original recordings, but it &lt;em&gt;does&lt;/em&gt; store them somewhere! And that somewhere is here: &lt;/p&gt;

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

/Users/YOUR_USERNAME/Library/Containers/com.fasthatchapps.gifgrabberosx/Data/Documents{% raw %}`
```

![video-location](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bah4xtgqv0n0h3kslx82.png)

You can enter this directory by hitting `Shift+Cmd+G` in Finder, copy-pasting the above path, replacing `YOUR_USERNAME` with your username into the above path, and clicking "Go".

Hope this helps someone on the internet (hi! 👋).

## How did I find this?

All thanks to the nifty tool [AppCleaner](https://freemacsoft.net/appcleaner/). It's intended to erase programs from your Mac entirely, but it seems to work just as well as a forensic tool.

![appcleaner](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2vx3inygmo8ytv92q3tx.png)

[^1]: GIFs are enormously inefficient and bulky compared to MPEG/web format videos. Too bad we're stuck with them for our daily dose of internet madness.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
    </item>
    <item>
      <title>Use image-set() while supporting Firefox with dynamically loaded background images</title>
      <dc:creator>Thor Galle</dc:creator>
      <pubDate>Fri, 27 Nov 2020 14:20:14 +0000</pubDate>
      <link>https://dev.to/th0rgall/use-image-set-for-dynamically-loaded-background-images-while-supporting-firefox-16jl</link>
      <guid>https://dev.to/th0rgall/use-image-set-for-dynamically-loaded-background-images-while-supporting-firefox-16jl</guid>
      <description>&lt;p&gt;You don't need to do anything special anymore 🥳 There is full support for &lt;code&gt;image-set&lt;/code&gt; in Firefox since version 89 (released Jun 2021). Go browser standards!&lt;/p&gt;

&lt;p&gt;Here's a demo. You'll see it works in Firefox:&lt;/p&gt;

&lt;p&gt;&lt;iframe src="https://stackblitz.com/edit/react-ts-fwysuw?file=App.tsx,style.css" width="100%" height="500"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;The below is an INVALID, old article about a problem that once existed when Firefox didn't support &lt;code&gt;image-set&lt;/code&gt;. Only kept for questionable archival purposes. I will probably unpublish this article later.&lt;/p&gt;






&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"
/* fallback */
background-image: url('fallback-img.jpg');
/* responsive */
background-image: -webkit-image-set(url( ... responsive image set ...));"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Solution: put those rules in a &lt;code&gt;&amp;lt;style&amp;gt;&lt;/code&gt; tag instead&lt;/strong&gt; to support fallbacks.&lt;/p&gt;

&lt;p&gt;Note: this blog post was a learning from a project at &lt;a href="https://www.columbiaroad.com/"&gt;Columbia Road&lt;/a&gt;, an updated version can be found on the company site: &lt;a href="https://www.columbiaroad.com/blog/use-image-set-while-supporting-firefox-with-dynamically-loaded-background-images"&gt;Tech logs: Use image-set() while supporting Firefox with dynamically loaded background images&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;Modern browsers widely support the HTML &lt;code&gt;srcset&lt;/code&gt; attribute that enables responsive images (&lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img#attr-srcset"&gt;MDN&lt;/a&gt;, &lt;a href="https://caniuse.com/srcset"&gt;CanIUse&lt;/a&gt;). But for its CSS &lt;code&gt;background-image&lt;/code&gt; equivalent &lt;code&gt;image-set()&lt;/code&gt; (accessible through &lt;code&gt;-webkit-image-set()&lt;/code&gt;), there is no support for &lt;em&gt;IE11&lt;/em&gt;, &lt;em&gt;Firefox&lt;/em&gt;, &lt;em&gt;Firefox for Android&lt;/em&gt; and &lt;em&gt;Opera Mini&lt;/em&gt; (&lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/image-set()"&gt;MDN&lt;/a&gt;, &lt;a href="https://caniuse.com/css-image-set"&gt;CanIUse&lt;/a&gt;). (I'm ignoring IE11 overall).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F9o9rp9w86idkp9428csa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F9o9rp9w86idkp9428csa.png" alt="Screen Shot 2020-11-20 at 10.34.32" width="800" height="287"&gt;&lt;/a&gt;&lt;/p&gt;
Support of major browsers for image-set() as of November 20, 2020. Source: &lt;a href="https://caniuse.com/css-image-set"&gt;CanIUse&lt;/a&gt;



&lt;p&gt;Of course you'll want to have a non-responsive fallback image for these non-supporting browsers. Well, that's not as obvious as it sounds.&lt;/p&gt;

&lt;h1&gt;
  
  
  Problem: using &lt;code&gt;style=""&lt;/code&gt; doesn't work
&lt;/h1&gt;

&lt;p&gt;You might be tempted to try the following CSS with a fallback &lt;code&gt;url()&lt;/code&gt; expression, that gets overwritten with the &lt;code&gt;-webkit-image-set()&lt;/code&gt; expression whenever available.&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="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"
/* provide a fallback for Firefox */
background-image: url('../img/image-2x.jpg');
/* override with a responsive image */
background-image: -webkit-image-set(url('../img/image-1x.jpg') 1x, url('../img/image-2x.jpg') 2x);
"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Unfortunately, if you add the above code to a &lt;code&gt;style=""&lt;/code&gt; attribute (as you would often do when you generate responsive background images somewhere), Firefox 84 will complain with this error in the console:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;⚠️ Error in parsing value for 'background-image'. Declaration dropped.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;How sad. It dropped the whole style attribute value because it could't load the &lt;code&gt;-webkit-image-set()&lt;/code&gt;. As a result, it does also not display the fallback.&lt;/p&gt;

&lt;h1&gt;
  
  
  Solution: use a &lt;code&gt;&amp;lt;style&amp;gt;&lt;/code&gt; tag
&lt;/h1&gt;

&lt;p&gt;The following code works however:&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="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;style&amp;gt;&lt;/span&gt;
        &lt;span class="nc"&gt;.my-image-id&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c"&gt;/* provide a fallback for Firefox */&lt;/span&gt;
            &lt;span class="nl"&gt;background-image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sx"&gt;url('../img/image-2x.jpg')&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="c"&gt;/* override with a responsive image */&lt;/span&gt;
            &lt;span class="nl"&gt;background-image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;-webkit-image-set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sx"&gt;url('../img/image-1x.jpg')&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sx"&gt;url('../img/image-2x.jpg')&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/style&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="c"&gt;&amp;lt;!-- this div should have an image background on it --&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"my-image-id background-box"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;div/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It seems that a style tag &lt;em&gt;can&lt;/em&gt; still be parsed, even though one rule in it is not supported by the browser.&lt;/p&gt;

&lt;p&gt;A few things to consider here.&lt;/p&gt;

&lt;h2&gt;
  
  
  You need a unique class name for each image.
&lt;/h2&gt;

&lt;p&gt;The reason you came here in the first place might have been that you wanted to use the &lt;code&gt;style=""&lt;/code&gt; attribute, because you were dynamically adding background images to a template using client-side/server-side image data.&lt;/p&gt;

&lt;p&gt;In that use case, you now you need to perform a few intermediate steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Generate a unique class name for the background image set&lt;/li&gt;
&lt;li&gt;Add that class to the desired &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; (or other element)&lt;/li&gt;
&lt;li&gt;Add a CSS rule for that class to a &lt;code&gt;&amp;lt;style&amp;gt;&lt;/code&gt; tag somewhere. This could be a new tag in the &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; OR &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt;. It should contain the fallback &amp;amp; responsive background-image URLs for that specific images. &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For step 1, you could use a GUID generator. In my project however, I used &lt;a href="https://codepen.io/th0rgall/pen/abmbQYa?editors=0001"&gt;a JS function&lt;/a&gt; to generate valid class names from an alphanumeric image ID string that I got from an API.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tricky to add style rules to the &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt;?&lt;br&gt;You &lt;em&gt;could&lt;/em&gt; add them to the &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt; too!
&lt;/h2&gt;

&lt;p&gt;If you were previously trying to add the styles to a style tag, it might be tricky to add styles to a &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt;, for example, when coding in a restricted server-side template where you can't access the &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;One solution: it's actually possible to add &lt;code&gt;&amp;lt;style&amp;gt;&lt;/code&gt; tags to the &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt; as well. This is &lt;a href="https://stackoverflow.com/questions/2830296/using-style-tags-in-the-body-with-other-html"&gt;not a good practice since it's non-standard&lt;/a&gt;. But it seems to work in most browsers. I tested this in BrowserStack, and so far only UC Browser on Android seemed to have problems with this technique. Here's an adaptation of the previous example with body style tags:&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="c"&gt;&amp;lt;!-- this div should have an image background on it --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"my-image-id background-box"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;div/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;style&amp;gt;&lt;/span&gt;
    &lt;span class="nc"&gt;.my-image-id&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c"&gt;/* provide a fallback for Firefox */&lt;/span&gt;
        &lt;span class="nl"&gt;background-image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sx"&gt;url('../img/image-2x.jpg')&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="c"&gt;/* override with a responsive image */&lt;/span&gt;
        &lt;span class="nl"&gt;background-image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;-webkit-image-set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sx"&gt;url('../img/image-1x.jpg')&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sx"&gt;url('../img/image-2x.jpg')&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/style&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Hope this helped! Feel free to leave a comment if anything is not clear.&lt;/p&gt;

</description>
      <category>css</category>
      <category>imageset</category>
      <category>firefox</category>
    </item>
    <item>
      <title>HubSpot's Undocumented Image Resizing API</title>
      <dc:creator>Thor Galle</dc:creator>
      <pubDate>Thu, 19 Nov 2020 17:34:22 +0000</pubDate>
      <link>https://dev.to/th0rgall/hubspot-s-undocumented-image-optimization-api-25b2</link>
      <guid>https://dev.to/th0rgall/hubspot-s-undocumented-image-optimization-api-25b2</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; Resize your HubSpot-hosted images by using URLs of the form &lt;code&gt;https://f.hubspotusercontent40.net/hub/${PORTAL_ID}/hubfs/${IMG_NAME}.jpg&amp;amp;width=400&amp;amp;name=${IMG_NAME}.jpg&lt;/code&gt;. Your mileage may vary, read the post for more info!&lt;/p&gt;

&lt;p&gt;If you are looking for &lt;strong&gt;image optimization on HubSpot CMS templates only, HubSpot supports that &lt;a href="https://knowledge.hubspot.com/cos-general/can-i-disable-automatic-image-resizing"&gt;by default&lt;/a&gt;.&lt;/strong&gt; You can stop reading here 🙂&lt;/p&gt;

&lt;p&gt;This article is about requesting resized versions of images that are hosted on HubSpot, but &lt;em&gt;from a non-HubSpot site&lt;/em&gt;. You might be in this situation when you're getting HubSpot-hosted image URLs through the HubSpot CMS Blog API, CMS Files API or CMS HubDb API (possibly others too). &lt;/p&gt;

&lt;p&gt;Note: this blog post was a learning from a project at &lt;a href="https://www.columbiaroad.com/"&gt;Columbia Road&lt;/a&gt;, an updated version can be found on the company site: &lt;a href="https://www.columbiaroad.com/blog/tech-logs-hubspots-undocumented-image-resizing-api"&gt;Tech logs: HubSpot's Undocumented Image Resizing API&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;Nowadays, responsive images are not a gimmick anymore. With a web that has &lt;a href="https://gs.statcounter.com/platform-market-share/desktop-mobile-tablet"&gt;an equal share&lt;/a&gt; of mobile and desktop users, responsive images are a must if you don't want a user to bounce on a slow-loading, image-heavy web page.&lt;/p&gt;

&lt;p&gt;Modern browsers widely support the HTML &lt;code&gt;srcset&lt;/code&gt; attribute that enables responsive images (&lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img#attr-srcset"&gt;MDN&lt;/a&gt;, &lt;a href="https://caniuse.com/srcset"&gt;CanIUse&lt;/a&gt;) and its CSS &lt;code&gt;background-image&lt;/code&gt; equivalent &lt;code&gt;image-set()&lt;/code&gt; (&lt;a href="https://caniuse.com/css-image-set"&gt;CanIUse&lt;/a&gt;). But automatically creating responsive image sets is not trivial. You usually use a specialized media hosting service or Digital Asset Manager for that (e.g. Cloudinary or Contentful). HubSpot also provides this service, but it is somewhat hidden. Read on to learn how it works.&lt;/p&gt;

&lt;h1&gt;
  
  
  Problem: no documented HubSpot Image API
&lt;/h1&gt;

&lt;p&gt;In a project at &lt;a href="https://www.columbiaroad.com/"&gt;Columbia Road&lt;/a&gt; we recently became stewards of a site driven by Nuxt.js. Large images on the site were causing long loading times. Many of these images came from HubSpot: the Nuxt.js site used the &lt;a href="https://developers.hubspot.com/docs/api/cms/blog-post"&gt;HubSpot Blog API&lt;/a&gt; to generate a listing of blog posts, and these blog posts had featured images that were hosted on HubSpot.&lt;/p&gt;

&lt;p&gt;To reduce the loading time we needed to implement responsive images, but while HubSpot &lt;a href="https://knowledge.hubspot.com/cos-general/can-i-disable-automatic-image-resizing"&gt;magically supports responsive images and on-demand image resizing&lt;/a&gt; in its own HubSpot CMS template pages, it did not expose a documented API for doing that externally on a non-HubSpot page.&lt;/p&gt;

&lt;h1&gt;
  
  
  Solution: an undocumented API
&lt;/h1&gt;

&lt;p&gt;Here's how we did get access to an API anyway. Since HubSpot has an internal mechanism for generating responsive &lt;code&gt;srcset&lt;/code&gt; attributes and specifying a certain image &lt;code&gt;width&lt;/code&gt;, we could figure out if we could somehow influence that mechanism from the outside. A starting point was by looking at the output of responsive images generated by HubSpot.&lt;/p&gt;

&lt;h3&gt;
  
  
  Modifying the image size
&lt;/h3&gt;

&lt;p&gt;To inspect the output of HubSpot's responsive image resizing mechanism, we made a test Coded HubSpot CMS Blog Page Template in HubL. We included the following source code that handles the featured blog image, as suggested by the &lt;a href="https://knowledge.hubspot.com/cos-general/can-i-disable-automatic-image-resizing"&gt;image resizing documentation&lt;/a&gt; for HubL pages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jinja"&gt;&lt;code&gt;&lt;span class="c"&gt;{# Note: this is an excerpt of a Coded HubSpot Blog Template to use as an illustration, it's not a fully working template #}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;"200px"&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;content.post_list_summary_featured_image&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;"600px"&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;content.post_list_summary_featured_image&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When looking at a preview of a blog post with the above template, the corresponding HTML output was generated for the images (see this by inspecting the HTML of the preview page):&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="c"&gt;&amp;lt;!-- Variable Portal ID and image title are omitted for the privacy of our client --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;"200"&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://f.hubspotusercontent40.net/hub/${PORTAL_ID}/hubfs/${IMG_NAME}.jpg&amp;amp;width=400&amp;amp;name=${IMG_NAME}.jpg"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;"600"&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://f.hubspotusercontent40.net/hub/${PORTAL_ID}/hubfs/${IMG_NAME}.jpg&amp;amp;width=600&amp;amp;name=${IMG_NAME}.jpg"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We see here that the &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; tag that got a &lt;code&gt;width="400px"&lt;/code&gt; attribute in a Coded HubSpot Blog Template now has a corresponding URL &lt;code&gt;?width=400&lt;/code&gt; query parameter related to image resizing. &lt;strong&gt;This starts to reveal a solution to our problem: we can modify this parameter to request a different size of the image.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;But we're not ready yet. How do we generate these image URLs ourselves? &lt;/p&gt;

&lt;h3&gt;
  
  
  Transforming a generic image URL into responsive URLs
&lt;/h3&gt;

&lt;p&gt;Let's consider our original input in Nuxt.js, which we got from the Blog API data: in the response of the official Blog API, we found that the HubSpot Blog API gave us generic image URLs in the following basic formats:&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;# URL type 1 - for featured images&lt;/span&gt;
https://cdn2.hubspot.net/hubfs/&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PORTAL_ID&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;/&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;IMG_NAME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;.jpg

&lt;span class="c"&gt;# URL type 2 - for featured images&lt;/span&gt;
https://f.hubspotusercontent40.net/hubfs/&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PORTAL_ID&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;/&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;IMG_NAME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;.jpg

&lt;span class="c"&gt;# NOTE: this might vary on your portal.&lt;/span&gt;
&lt;span class="c"&gt;# The HubSpot File Manager suggest that you can also use branded domain name from the site for these two types&lt;/span&gt;
https://brand.com/hubfs/&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PORTAL_ID&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;/&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;IMG_NAME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;.jpg
&lt;span class="c"&gt;# A third image type for author icons also exists, but won't be covered here as they are already small thumbnails.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here I should note that these input URLs might vary based on your Portal and the source of the URLs. Check the API response you're looking at or HubSpot's media manager to find your input URLs. The output URLs might also vary. If you don't know what they are, replicate the template test I did in the previous section.&lt;/p&gt;

&lt;p&gt;For the URL types 1 and 2 that we encountered it was possible to extract the  &lt;code&gt;PORTAL_ID&lt;/code&gt; and  &lt;code&gt;IMG_NAME&lt;/code&gt; from the Blog API responses with a regular expression, and then reconstruct a URL of the output form which we saw in the last section. The resulting transformed URL succeeds in loading images of the requested size: mission accomplished.&lt;/p&gt;

&lt;p&gt;The following function on CodePen implements this transformation. Feel free to adapt it to your needs!&lt;/p&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/th0rgall/embed/OJXYxdm?height=600&amp;amp;default-tab=js&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h1&gt;
  
  
  Caveats
&lt;/h1&gt;

&lt;p&gt;There are some caveats to mention:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;'&lt;strong&gt;Undocumented' probably also means 'unsupported'.&lt;/strong&gt; If HubSpot would break or change this functionality, and you can not respond quickly to revert the image URLs to their original form, images on your site could break without warning. See Alternatives &amp;amp; Improvements on how to mitigate this situation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Give it some time.&lt;/strong&gt; For some images it took some time (tens of seconds to minutes) from the first requested resized image in order for the actual resized image to appear. This could be due to scheduled resizing processes or variable time in CDN distribution at HubSpot's side.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No complete support.&lt;/strong&gt; For some reason unknown to us this resizing only works for ~90% of the images where we tried it on. For some images the original resolution is returned, no matter what size parameters we give it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Only width &amp;amp; height controls.&lt;/strong&gt; Other Image optimization APIs pformat format (e.g. WebP) and quality parameters. This API doesn't clearly expose those. Note: it seems that HubSpot is serving WebP by default when using these resizing options APIs.&lt;/li&gt;
&lt;/ol&gt;

&lt;h1&gt;
  
  
  Alternatives &amp;amp; improvements
&lt;/h1&gt;

&lt;p&gt;One important improvement on the above code would be to load images via JavaScript. In such a case, you could detect potential 400 or 500 error codes when HubSpot changed this API's functionality or signature, and you could automatically insert the original URL instead. &lt;/p&gt;

&lt;p&gt;Another sure-fire approach to the whole problem would be to implement a microservice that scrapes the images from the HubSpot Blog API and uploads them to Contentful on a regular basis. This would protect you from API changes. However, this approach also has disadvantages. One, it's more costly to architect &amp;amp; develop. Two, it's more costly to operate because you need to manage an extra service. Still, it could be a solution if you need a massive number of HubSpot-sourced images on an external web page and you have a very low fault tolerance. At least one person on the internet has done &lt;a href="https://www.impactplus.com/blog/why-developers-love-hubspot"&gt;something similar&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;And with that thought put out there, this post is a wrap! I hope it can help someone who - like me - Googled for a solution to this problem unsuccessfully. It &lt;em&gt;is&lt;/em&gt; pretty annoying that HubSpot does not have a documented API for resizing images.&lt;/p&gt;

</description>
      <category>hubspot</category>
      <category>nuxt</category>
      <category>imageoptimization</category>
      <category>problemsolving</category>
    </item>
  </channel>
</rss>
