<?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: Julian Krispel-Samsel</title>
    <description>The latest articles on DEV Community by Julian Krispel-Samsel (@jkrsp).</description>
    <link>https://dev.to/jkrsp</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%2F184622%2Fff5d408b-8a08-4c66-b9d7-2276684d7d19.png</url>
      <title>DEV Community: Julian Krispel-Samsel</title>
      <link>https://dev.to/jkrsp</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jkrsp"/>
    <language>en</language>
    <item>
      <title>2021 is a great time to start freelancing 🚀</title>
      <dc:creator>Julian Krispel-Samsel</dc:creator>
      <pubDate>Mon, 12 Jul 2021 10:43:33 +0000</pubDate>
      <link>https://dev.to/jkrsp/now-is-a-really-good-time-to-leave-your-job-and-start-freelancing-2aam</link>
      <guid>https://dev.to/jkrsp/now-is-a-really-good-time-to-leave-your-job-and-start-freelancing-2aam</guid>
      <description>&lt;p&gt;&lt;strong&gt;DISCLAIMER&lt;/strong&gt;: This is a little bit of a rant, albeit a positive one. I feel optimistic because this year has brought good things, now that I'm back to freelancing. I want to help others achieve the same successes because I am convinced it should be possible and accessible to others.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Software Engineering is booming 📈 and there's never been a better time to start freelancing&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;When you are employed by someone, there are always limits, such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A holiday allowance&lt;/strong&gt;. Don't tell me your company offers the unlimited variety because there is no such thing even if they say so 😉&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A pay structure&lt;/strong&gt;. Although these limits are increasing with the demand for software Engineers and are at an all time high - companies will limit how much they pay there employees. It's a necessity to running a business.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Your role&lt;/strong&gt;. As an employee, unless you fill an executive role you will have little influence over the overall direction of the company. Your job will be defined by your role which comes with limits some of which will be spelled out in your onboarding, some will be unspoken and some boundaries will only be apparent when you push on them.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Of course as a company it's good practice to define these boundaries and as an employee it's helpful to have clear boundaries. For the self-employed none of them apply:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You can take as much holiday as you please, literally. Flip side is that you only get paid for your deliverables.&lt;/li&gt;
&lt;li&gt;The price for well written software and good consultancy has no ceiling 💰 and therefore your possible annual salary has the potential to be whatever you make it. Think of the highest salary you know and I guarantee there will be someone who makes 2x that salary but spends half the time doing it.&lt;/li&gt;
&lt;li&gt;As a company of one you have sole discretion over where to take it. I found this can be equally overwhelming and freeing.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let this sink in. Let your dreams be a driver. The road will be long and hard but you can take my word for it - there is a box of highly valuable unicorn poo 🦄 at the end of the rainbow 🌈. I've seen it with my own eyes and recently I think I may be edging closer.&lt;/p&gt;

&lt;p&gt;Finally, freelancing is not for everyone. I hope you feel encouraged to consider it, there is no shame in trying it out and going back to a full-time job if you feel it's too risky or too early in your career (as I did before).&lt;/p&gt;

&lt;p&gt;Good luck out there and happy Monday! 🙏&lt;/p&gt;

&lt;p&gt;PS: 📷 The cover picture is a lady blackbird from my garden, worm in mouth.&lt;/p&gt;

</description>
      <category>career</category>
      <category>webdev</category>
      <category>motivation</category>
    </item>
    <item>
      <title>10 tips for building accessible (rich) text editors</title>
      <dc:creator>Julian Krispel-Samsel</dc:creator>
      <pubDate>Fri, 11 Jun 2021 17:02:14 +0000</pubDate>
      <link>https://dev.to/jkrsp/10-tips-for-building-accessible-text-editors-19ef</link>
      <guid>https://dev.to/jkrsp/10-tips-for-building-accessible-text-editors-19ef</guid>
      <description>&lt;p&gt;If you're building an app that features a rich text editor - you should make it as accessible as possible for screen-readers.&lt;/p&gt;

&lt;p&gt;It's really not as hard as it sounds, so here's list of &lt;strong&gt;10 recommendations for building accessible rich text editors&lt;/strong&gt;. Although most points will be applicable to any kind of library you use, some of them are specific to react.js.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Test with an actual screen reader
&lt;/h3&gt;

&lt;p&gt;If you have never used a screen reader, try one out on your app or &lt;a href="https://docs.google.com/" rel="noopener noreferrer"&gt;any&lt;/a&gt; app &lt;a href="https://medium.com/" rel="noopener noreferrer"&gt;with&lt;/a&gt; that has a &lt;a href="https://www.notion.so/" rel="noopener noreferrer"&gt;rich text editor&lt;/a&gt;. I'd consider this blog-post a success even if you don't come back to finish reading it.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;On &lt;strong&gt;OSX&lt;/strong&gt; you already have one built in to your operating system: &lt;a href="https://support.apple.com/en-gb/guide/voiceover/welcome/mac" rel="noopener noreferrer"&gt;VoiceOver&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;For &lt;strong&gt;Windows&lt;/strong&gt; users there's &lt;a href="https://github.com/nvaccess/nvda" rel="noopener noreferrer"&gt;nvda&lt;/a&gt; (free) which is also the most widely used.&lt;/li&gt;
&lt;li&gt;Another popular screen reader is &lt;a href="https://www.freedomscientific.com/products/software/jaws/" rel="noopener noreferrer"&gt;JAWS&lt;/a&gt; (not free).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All of the below recommendations become more obvious if you put yourself into your users shoes.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Avoid keyboard traps
&lt;/h3&gt;

&lt;p&gt;Overriding default keyboard behaviour to implement features such as hotkeys or indenting text via tab is a common pattern seen in web-based rich-text editors and IDEs.&lt;/p&gt;

&lt;p&gt;However, screen-reader users rely entirely on the keyboard to navigate your app, their browser and operating system. It is frustrating and hard to overcome if default keyboard functionality is prevented by an application. If you are implementing such features, check they don't collide with hotkeys that are already in use for the browsers or operating systems.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. All interactive elements must be reachable via tab and/or arrow keys
&lt;/h3&gt;

&lt;p&gt;Most rich text editing applications make use of menus and toolbars, dynamically positioned or fixed in place.&lt;/p&gt;

&lt;p&gt;To ensure that elements are reachable via keyboard they need to be focusable. DOM elements such as &lt;code&gt;button&lt;/code&gt;, &lt;code&gt;input&lt;/code&gt;, &lt;code&gt;select&lt;/code&gt; or &lt;code&gt;textarea&lt;/code&gt; are focusable by default. If you're using an element that isn't considered interactive (such as a &lt;code&gt;div&lt;/code&gt;) you are required to add a &lt;code&gt;tabindex&lt;/code&gt; and a &lt;code&gt;role&lt;/code&gt; attribute so that the screen reader knows how to interact with the element.&lt;/p&gt;

&lt;p&gt;Consider how much work it is for a keyboard-only user (the mouse is mostly useless for a user with sight-issues) to navigate to these UI elements. This will depend on the position of the element in the DOM and how many other interactive elements are in between.&lt;/p&gt;

&lt;p&gt;If you're building an editor with a floating toolbar (such as medium - which is completely inaccessible via tab keys btw) pay close attention to how a keyboard-only user can navigate to and from these buttons and toolbar components.&lt;/p&gt;

&lt;p&gt;For complex usecases (if you have submenus or button groups) you should consider programmatically moving focus, &lt;a href="https://developer.mozilla.org/en-US/docs/Web/Accessibility/Keyboard-navigable_JavaScript_widgets#managing_focus_inside_groups" rel="noopener noreferrer"&gt;more info on this here&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Use alt text for your images and make them configurable
&lt;/h3&gt;

&lt;p&gt;If you're rendering images in your editor, it is recommended you &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img" rel="noopener noreferrer"&gt;use an alt attribute&lt;/a&gt; to represent what the image contains to a screen reader.&lt;/p&gt;

&lt;p&gt;However, for user submitted content it is too difficult to generate meaningful alt text automatically, that's why you should consider making the alt attribute configurable by the user. Here is what it looks like on medium.com:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fly2a8ogh77hj7okaqxnw.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fly2a8ogh77hj7okaqxnw.jpg" alt="Alt text for images on medium.com"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Label your icon buttons
&lt;/h3&gt;

&lt;p&gt;Most rich text editors make use of common icons for text formatting and layout.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6xabes35loc95uzzu1nj.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6xabes35loc95uzzu1nj.jpg" alt="Google docs tool bar"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;These icons don't have textual meaning on their own, hence we need to tell the screen reader what they are by assigning the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Techniques/Using_the_aria-label_attribute" rel="noopener noreferrer"&gt;&lt;code&gt;aria-label&lt;/code&gt; attribute&lt;/a&gt; like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&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;aria-label&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Bold"&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;makeTextBold&lt;/span&gt;&lt;span class="si"&gt;}&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="nc"&gt;MyIcon&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;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you're not using the semantic &lt;code&gt;button&lt;/code&gt; element, you need to assign the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/tabindex" rel="noopener noreferrer"&gt;&lt;code&gt;tabindex&lt;/code&gt; attribute&lt;/a&gt; (to make it tabbable) as well as the &lt;a href="https://dev.toeb/Accessibility/ARIA/Roles/button_role"&gt;&lt;code&gt;button&lt;/code&gt; role&lt;/a&gt; (to tell the screen reader that this is a button), see below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;
  &lt;span class="na"&gt;aria-label&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Bold"&lt;/span&gt;
  &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"button"&lt;/span&gt;
  &lt;span class="na"&gt;tabindex&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"0"&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;makeTextBold&lt;/span&gt;&lt;span class="si"&gt;}&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="nc"&gt;MyIcon&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;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  6. Use a linter with a11y support
&lt;/h3&gt;

&lt;p&gt;To make it easier to stay on top of your apps accessibility, a linter can go a long way.&lt;/p&gt;

&lt;p&gt;For react users, the &lt;a href="https://www.npmjs.com/package/eslint-plugin-jsx-a11y" rel="noopener noreferrer"&gt;a11y-jsx eslint plugin&lt;/a&gt; will make you aware of what attributes you're missing to make your web-app accessible. I cannot overstate how valuable this is especially for teams.&lt;/p&gt;

&lt;p&gt;I recommend running your linter automatically (either as part of your CI or with a git hook).&lt;/p&gt;

&lt;h3&gt;
  
  
  7. Use an accessible component library
&lt;/h3&gt;

&lt;p&gt;Using established component libraries will give you a leg-up on reaching a good accessibility baseline. &lt;a href="https://reach.tech/" rel="noopener noreferrer"&gt;Reach.ui&lt;/a&gt;, &lt;a href="https://material-ui.com/" rel="noopener noreferrer"&gt;material ui&lt;/a&gt; and &lt;a href="https://chakra-ui.com/" rel="noopener noreferrer"&gt;chakra&lt;/a&gt; are all react libraries that have decent accessibility defaults.&lt;/p&gt;

&lt;h3&gt;
  
  
  8. Think twice about whether you need collaborative editing
&lt;/h3&gt;

&lt;p&gt;If your rich text editor has collaborative editing the content of a users document can change at the same time as you're editing it. The screen-reader needs to be notified that these updates occur, however these notifications must not be too noisy or distracting which can be a difficult balancing act.&lt;/p&gt;

&lt;p&gt;Although there are no out of the box solutions for this, a good reference is &lt;a href="https://workspaceupdates.googleblog.com/2019/08/real-time-collab-accessibility.html" rel="noopener noreferrer"&gt;googles "Live Edit's" feature&lt;/a&gt;, providing a list of edits made by other users.&lt;/p&gt;

&lt;p&gt;Features such as this can be notoriously expensive to implement, test and maintain. My recommendation: Think twice about whether you actually need collaboration as part of your product before adding it to your roadmap.&lt;/p&gt;

&lt;h3&gt;
  
  
  9. Use a title attribute for iframes
&lt;/h3&gt;

&lt;p&gt;Embedding content via iframes is a staple for rich text editors these days. To make this accessible for screen readers you should use the &lt;a href="https://www.w3.org/TR/WCAG20-TECHS/H64.html" rel="noopener noreferrer"&gt;title attribute&lt;/a&gt; to describe what the iframe contains.&lt;/p&gt;

&lt;h3&gt;
  
  
  10. Always use the &lt;code&gt;textbox&lt;/code&gt; role
&lt;/h3&gt;

&lt;p&gt;Most frameworks (&lt;a href="https://draftjs.org/" rel="noopener noreferrer"&gt;draft.js&lt;/a&gt;, &lt;a href="https://www.slatejs.org/" rel="noopener noreferrer"&gt;slate.js&lt;/a&gt; and &lt;a href="https://prosemirror.net/" rel="noopener noreferrer"&gt;prosemirror&lt;/a&gt; as well as others) render this by default, but it's always good to double check this!&lt;/p&gt;

&lt;p&gt;Any rich text editor using the &lt;code&gt;contentEditable&lt;/code&gt; attribute (which is pretty much every rich text editor) needs to use the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/textbox_role" rel="noopener noreferrer"&gt;"textbox" role&lt;/a&gt;, to tell the screen-reader that it will respond to input and editing commands.&lt;/p&gt;

&lt;p&gt;Thanks for reading, this article was &lt;a href="https://jkrsp.com/accessibility-for-rich-text-editors/" rel="noopener noreferrer"&gt;originally posted on my website&lt;/a&gt;, for more content like this have a look and feel free to reach out on &lt;a href="https://twitter.com/_jkrsp" rel="noopener noreferrer"&gt;twitter too&lt;/a&gt;. &lt;/p&gt;

</description>
      <category>a11y</category>
      <category>webdev</category>
      <category>howto</category>
    </item>
    <item>
      <title>How to embed youtube videos in rich text documents with slate.js</title>
      <dc:creator>Julian Krispel-Samsel</dc:creator>
      <pubDate>Tue, 13 Apr 2021 09:03:38 +0000</pubDate>
      <link>https://dev.to/jkrsp/how-to-embed-youtube-videos-in-rich-text-documents-with-slate-js-3i14</link>
      <guid>https://dev.to/jkrsp/how-to-embed-youtube-videos-in-rich-text-documents-with-slate-js-3i14</guid>
      <description>&lt;p&gt;Embedding media such as youtube or vimeo links in a rich text document is a very common feature in rich text editors.&lt;/p&gt;

&lt;p&gt;In this post I'll go through a pattern that I see used across projects, which is to &lt;strong&gt;render embedded media in iframes&lt;/strong&gt;. In this case it's a youtube video, but it could really be anything, like a tweet for example.&lt;/p&gt;

&lt;p&gt;The finished &lt;a href="https://github.com/juliankrispel/slate-patterns/blob/master/src/iframe-elements/iframe-elements.tsx"&gt;example is available here&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Okay, let's get started ⬇️&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Setup
&lt;/h3&gt;

&lt;p&gt;At the time of writing, I'm using &lt;strong&gt;slate&lt;/strong&gt; version &lt;code&gt;^0.59&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If you don't have a react app already, please use &lt;code&gt;create-react-app&lt;/code&gt; (or something similar) to get started. I always include typescript for my projects but this is entirely optional.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx create-react-app my-awesome-editor &lt;span class="nt"&gt;--template&lt;/span&gt; typescript
&lt;span class="nb"&gt;cd &lt;/span&gt;my-awesome-editor
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add the dependencies &lt;code&gt;slate&lt;/code&gt;, &lt;code&gt;slate-react&lt;/code&gt; and &lt;code&gt;slate-history&lt;/code&gt; to your React app.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;yarn add slate slate-react slate-history
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now let's add the boilerplate for your editor component, importing all the right dependencies and handling onChange events.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&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="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useMemo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useState&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&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;createEditor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Node&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;slate&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;withHistory&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;slate-history&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;Editable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ReactEditor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Slate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;withReact&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;slate-react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;MyEditor&lt;/span&gt;&lt;span class="p"&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;editor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useMemo&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;withHistory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;withReact&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;createEditor&lt;/span&gt;&lt;span class="p"&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;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setValue&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Node&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="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;children&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
        &lt;span class="na"&gt;text&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="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="nc"&gt;Slate&lt;/span&gt; &lt;span class="na"&gt;editor&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;editor&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;onChange&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;setValue&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&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;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Editable&lt;/span&gt; &lt;span class="na"&gt;placeholder&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Write something..."&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="nc"&gt;Slate&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;h3&gt;
  
  
  2. Add a slate element for youtube embeds
&lt;/h3&gt;

&lt;p&gt;One of the three &lt;strong&gt;fundamental&lt;/strong&gt; building blocks of a slate document are &lt;strong&gt;Block Elements&lt;/strong&gt;. In their simplest form Block Elements are lines of text (or paragraphs), but they can also be non-text elements. All block elements are derived from this shape:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;children&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
    &lt;span class="na"&gt;text&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To create our youtube element we add our own properties to this element. Youtube videos have id's, so we add a &lt;code&gt;videoId&lt;/code&gt; alongside a &lt;code&gt;type&lt;/code&gt; for clarity.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;youtube&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;videoId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;CvZjupLir-8&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;children&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
    &lt;span class="na"&gt;text&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Update your default slate value to include this block. Next, we'll tackle rendering this element  ⬇&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Rendering embeddable elements
&lt;/h3&gt;

&lt;p&gt;In order to render the iframe we need to define the aptly named &lt;a href="https://docs.slatejs.org/concepts/08-rendering"&gt;&lt;code&gt;renderElement&lt;/code&gt; prop&lt;/a&gt; of slate's  &lt;code&gt;Editable&lt;/code&gt; component like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Editable&lt;/span&gt;
  &lt;span class="na"&gt;renderElement&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;children&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;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;youtube&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;videoId&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;null&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;div&lt;/span&gt;
        &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;attributes&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="na"&gt;contentEditable&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="si"&gt;}&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;iframe&lt;/span&gt;
          &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;`https://www.youtube.com/embed/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;videoId&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="na"&gt;aria&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Youtube video"&lt;/span&gt;
          &lt;span class="na"&gt;frameBorder&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"0"&lt;/span&gt;
        &lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;iframe&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;children&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;div&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;else&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;p&lt;/span&gt; &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;attributes&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;children&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;p&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;span class="si"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you've followed the steps so far, you should now see a youtube embed appear in your editor. Let's break down what's happening with our &lt;code&gt;renderElement&lt;/code&gt; method as shown above.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In our &lt;code&gt;renderElement&lt;/code&gt; method we check if the type of element is &lt;code&gt;'youtube'&lt;/code&gt; and if it is, we render our iframe. We construct the iframe src attribute by concatenating youtube's embed url with  with the video id.&lt;/li&gt;
&lt;li&gt;Our &lt;code&gt;renderElement&lt;/code&gt; callback must always render the &lt;code&gt;children&lt;/code&gt; prop as well as the element &lt;code&gt;attributes&lt;/code&gt; which can be spread over a html element (Otherwise slate.js will error when you try to interact with the element).&lt;/li&gt;
&lt;li&gt;If the element type isn't &lt;code&gt;'youtube'&lt;/code&gt; the &lt;code&gt;renderElement&lt;/code&gt; prop renders a paragraph by default. Slate will use the &lt;code&gt;renderElement&lt;/code&gt; method to render every &lt;code&gt;element&lt;/code&gt; in your document.&lt;/li&gt;
&lt;li&gt;For non-text elements, we need to add &lt;code&gt;contentEditable={false}&lt;/code&gt; to prevent the browser from adding a cursor to our content.&lt;/li&gt;
&lt;li&gt;Don't forget to add an &lt;code&gt;aria-label&lt;/code&gt; or a &lt;code&gt;title&lt;/code&gt; attribute to your iframe, otherwise &lt;a href="https://fae.disability.illinois.edu/rulesets/FRAME_2/"&gt;screen-readers will not be able to make sense of it&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. Treat &lt;code&gt;'youtube'&lt;/code&gt; blocks as voids
&lt;/h3&gt;

&lt;p&gt;By default slate assumes that every element has editable text. This is not the case for our youtube block.&lt;/p&gt;

&lt;p&gt;To make sure slate behaves appropriately we need to override the &lt;code&gt;editor.isVoid&lt;/code&gt; method like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;editor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isVoid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;  &lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;video&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For completeness, here's the entire useMemo callback producing the editor prop for the &lt;code&gt;Slate&lt;/code&gt; component:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;editor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useMemo&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="nx"&gt;_editor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;withHistory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;withReact&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;createEditor&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt;
  &lt;span class="nx"&gt;_editor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isVoid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;youtube&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;_editor&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;Now we're rendering and handling this block correctly, but how does a user actually add a youtube block?&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Inserting youtube blocks
&lt;/h3&gt;

&lt;p&gt;To insert an element - we use slate's &lt;a href="https://docs.slatejs.org/api/transforms"&gt;&lt;code&gt;Transforms&lt;/code&gt; library&lt;/a&gt;, in particular, the &lt;code&gt;insertNodes&lt;/code&gt; method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;Transforms&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;insertNodes&lt;/span&gt;&lt;span class="p"&gt;([{&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;youtube&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;videoId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;children&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
    &lt;span class="na"&gt;text&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However we still need the user interaction for input. Let's add an &lt;code&gt;onPaste&lt;/code&gt; prop to our Editable component for this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Editable&lt;/span&gt;
  &lt;span class="na"&gt;onPaste&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&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="nx"&gt;pastedText&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clipboardData&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;getData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)?.&lt;/span&gt;&lt;span class="nx"&gt;trim&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;youtubeRegex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sr"&gt;/^&lt;/span&gt;&lt;span class="se"&gt;(?:(?:&lt;/span&gt;&lt;span class="sr"&gt;https&lt;/span&gt;&lt;span class="se"&gt;?&lt;/span&gt;&lt;span class="sr"&gt;:&lt;/span&gt;&lt;span class="se"&gt;)?\/\/)?(?:(?:&lt;/span&gt;&lt;span class="sr"&gt;www|m&lt;/span&gt;&lt;span class="se"&gt;)\.)?(?:(?:&lt;/span&gt;&lt;span class="sr"&gt;youtube&lt;/span&gt;&lt;span class="se"&gt;\.&lt;/span&gt;&lt;span class="sr"&gt;com|youtu.be&lt;/span&gt;&lt;span class="se"&gt;))(?:\/(?:[\w\-]&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;\?&lt;/span&gt;&lt;span class="sr"&gt;v=|embed&lt;/span&gt;&lt;span class="se"&gt;\/&lt;/span&gt;&lt;span class="sr"&gt;|v&lt;/span&gt;&lt;span class="se"&gt;\/)?)([\w\-]&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;)(?:\S&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;)?&lt;/span&gt;&lt;span class="sr"&gt;$/&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;matches&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;pastedText&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;youtubeRegex&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="nx"&gt;matches&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// the first regex match will contain the entire url,&lt;/span&gt;
      &lt;span class="c1"&gt;// the second will contain the first capture group which is our video id&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;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;videoId&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;matches&lt;/span&gt;
      &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;preventDefault&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="nx"&gt;Transforms&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;insertNodes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;editor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;youtube&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;videoId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;children&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
          &lt;span class="na"&gt;text&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="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;renderElement&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's break this down:&lt;/p&gt;

&lt;p&gt;First we retrieve the text we pasted:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pastedText&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clipboardData&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;getData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)?.&lt;/span&gt;&lt;span class="nx"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To test if our pasted url is a youtube url and to capture the id from the url we use a regex. It's not pretty but I prefer examples with as few dependencies as possible. If you do want something easier to read, you could use libraries like &lt;a href="https://www.npmjs.com/package/get-youtube-id"&gt;&lt;code&gt;get-youtube-id&lt;/code&gt;&lt;/a&gt; for this purpose.&lt;/p&gt;

&lt;p&gt;If the regex matches we call &lt;code&gt;event.preventDefault()&lt;/code&gt; to prevent the pasted text from being inserted as text. Instead we insert a slate element of type &lt;code&gt;'youtube'&lt;/code&gt; and with a video id. Now we can embed youtube videos in our document by simply pasting the link, anywhere.&lt;/p&gt;




&lt;p&gt;That's it, I hope you enjoyed this tutorial. If you have questions or an idea of what you'd like me to cover in my next tutorial, reach out on twitter - I'm always happy to hear from the community!&lt;/p&gt;




&lt;p&gt;FYI this article was first posted on my website -&amp;gt; &lt;a href="https://jkrsp.com/slate-js-youtube-embeds/"&gt;https://jkrsp.com/slate-js-youtube-embeds/&lt;/a&gt;&lt;/p&gt;

</description>
      <category>tutorial</category>
      <category>react</category>
      <category>webdev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Validate your cloud function with 1 line of code</title>
      <dc:creator>Julian Krispel-Samsel</dc:creator>
      <pubDate>Sat, 14 Nov 2020 12:23:38 +0000</pubDate>
      <link>https://dev.to/jkrsp/validate-your-cloud-function-with-1-line-of-code-23fh</link>
      <guid>https://dev.to/jkrsp/validate-your-cloud-function-with-1-line-of-code-23fh</guid>
      <description>&lt;p&gt;&lt;a href="https://github.com/woutervh-/typescript-is"&gt;typescript-is&lt;/a&gt; is a library which enables type checks at run-time! This is an incredible tool to validate input and make your code more type-safe 🎉.&lt;/p&gt;

&lt;p&gt;Validating the input of cloud functions can be a challenging problem. Re-using your types to ensure that your input matches your type solves a big part of the validation problem. To do this with the &lt;code&gt;typescript-is&lt;/code&gt; library, the only thing you need to do is to use the &lt;code&gt;assertType&lt;/code&gt; method that &lt;code&gt;typescript-is&lt;/code&gt; exports:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// 2. Use the `assertType` method to perform your runtime check&lt;/span&gt;
&lt;span class="nx"&gt;assertType&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;MyEvent&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;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Above we use &lt;code&gt;assertType&lt;/code&gt; to check whether our runtime object &lt;code&gt;input&lt;/code&gt; matches our type &lt;code&gt;MyEvent&lt;/code&gt;. If it doesn't match the &lt;code&gt;MyEvent&lt;/code&gt; type, an error is thrown. &lt;code&gt;typescript-is&lt;/code&gt; has a bunch of other methods such as &lt;code&gt;is&lt;/code&gt; or &lt;code&gt;strictEqual&lt;/code&gt;. If you'd like to throw the error yourself you could do this for example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&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="k"&gt;is&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;MyEvent&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;input&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;input does not match type&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;p&gt;That's pretty much all it takes to add a run-time type check of your cloud functions input (provided you are a typescript user 🙃). Whereas previously you might have reached for validation libraries such as &lt;a href="https://github.com/sideway/joi"&gt;joi&lt;/a&gt; or god-forbid, written your own validator, it's now just one line of code. What are you going to do with all this new-found time?&lt;/p&gt;

&lt;p&gt;Below is the entirety of an AWS lambda function handler with this pattern applied:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Handler&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="s1"&gt;aws-lambda&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;assertType&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="s1"&gt;typescript-is&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;MyEvent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;handler&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Handler&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;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;MyEvent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;assertType&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;MyEvent&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;event&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="na"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Hello World&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;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;handler&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;🚀&lt;/p&gt;

&lt;p&gt;For a fully working example of this pattern including deployment scripts for AWS and compilation with webpack please have a &lt;a href="https://github.com/juliankrispel/typescript-aws-lambda-terraform"&gt;look at this boilerplate I've put together&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;👍&lt;/p&gt;

&lt;p&gt;Thanks for reading, &lt;a href="https://jkrsp.com/typescript-validate-lambda/"&gt;this post was originally published on my website&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>tutorial</category>
      <category>serverless</category>
      <category>node</category>
    </item>
    <item>
      <title>s3-auto-sync - watch a folder and automatically sync it with an s3 bucket 🎉</title>
      <dc:creator>Julian Krispel-Samsel</dc:creator>
      <pubDate>Sun, 08 Nov 2020 17:25:50 +0000</pubDate>
      <link>https://dev.to/jkrsp/s3-auto-sync-upload-files-to-s3-as-they-change-4pi8</link>
      <guid>https://dev.to/jkrsp/s3-auto-sync-upload-files-to-s3-as-they-change-4pi8</guid>
      <description>&lt;p&gt;So I wrote this little cli tool that lets you upload files to an s3 bucket, as changes happen.&lt;/p&gt;

&lt;p&gt;All you need to do is install it:&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;s3-auto-sync &lt;span class="nt"&gt;-g&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And use it like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;s3-auto-sync &lt;span class="nt"&gt;-b&lt;/span&gt; my-bucket &lt;span class="nt"&gt;-d&lt;/span&gt; ./my-folder &lt;span class="nt"&gt;-r&lt;/span&gt; eu-east-1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Make sure you set the region that you're in, otherwise performance will be slow.&lt;/p&gt;

&lt;p&gt;That's it, practically your own little dropbox 🚀&lt;/p&gt;

&lt;p&gt;The npm package is here: &lt;a href="https://www.npmjs.com/package/s3-auto-sync"&gt;https://www.npmjs.com/package/s3-auto-sync&lt;/a&gt;&lt;br&gt;
And the github repo is here: &lt;a href="https://github.com/juliankrispel/s3-auto-sync"&gt;https://github.com/juliankrispel/s3-auto-sync&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you have any suggestions for features or improvements, by all means open an issue or a pr 🙌&lt;/p&gt;

</description>
      <category>aws</category>
      <category>tooling</category>
      <category>devops</category>
    </item>
    <item>
      <title>How to write terraform, but with typescript</title>
      <dc:creator>Julian Krispel-Samsel</dc:creator>
      <pubDate>Sun, 08 Nov 2020 11:57:46 +0000</pubDate>
      <link>https://dev.to/jkrsp/how-to-write-terraform-infrastructure-code-just-using-typescript-abj</link>
      <guid>https://dev.to/jkrsp/how-to-write-terraform-infrastructure-code-just-using-typescript-abj</guid>
      <description>&lt;p&gt;Ever wished you could just use typescript to write your infrastructure? You may or may not have heard about the release of the &lt;a href="https://github.com/hashicorp/terraform-cdk" rel="noopener noreferrer"&gt;terraform cdk&lt;/a&gt; (short for cloud development kit). It's HashiCorps answer to the aws cdk. In the words of the projects readme:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;CDK (Cloud Development Kit) for Terraform allows developers to use familiar programming languages to define cloud infrastructure and provision it through HashiCorp Terraform.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Let's try this out shall we?&lt;/p&gt;

&lt;p&gt;To get the full developer experience, make sure you have &lt;a href="https://github.com/Microsoft/TypeScript/wiki/TypeScript-Editor-Support" rel="noopener noreferrer"&gt;typescript support installed for your IDE&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/cover.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/cover.png" alt="autocomplete-terraform"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Generating the boilerplate
&lt;/h3&gt;

&lt;p&gt;Let's open our terminal and install install cdktf-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; cdktf-cli
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next we'll initialize the project&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;hello-cdktf
&lt;span class="nb"&gt;cd &lt;/span&gt;hello-cdktf
cdktf init &lt;span class="nt"&gt;--template&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"typescript"&lt;/span&gt; &lt;span class="nt"&gt;--local&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Answer the two configuration questions and the project boilerplate will be generated.&lt;/p&gt;

&lt;p&gt;Now we should see a &lt;code&gt;main.ts&lt;/code&gt; file with the following contents in our folder:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Construct&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="s1"&gt;constructs&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;App&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;TerraformStack&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="s1"&gt;cdktf&lt;/span&gt;&lt;span class="dl"&gt;'&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;MyStack&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;TerraformStack&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Construct&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// define resources here&lt;/span&gt;

  &lt;span class="p"&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;App&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;MyStack&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hello-cdktf2&lt;/span&gt;&lt;span class="dl"&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;synth&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Adding a provider package and importing modules from it
&lt;/h3&gt;

&lt;p&gt;After generation finishes you'll see a message in the console listing instructions of what to do next. To add the prebuilt aws provider (which also gives us all the modules and types that we might want).&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;-a&lt;/span&gt; @cdktf/provider-aws
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you can import modules from &lt;code&gt;@cdktf/provider-aws&lt;/code&gt; such as &lt;code&gt;AwsProvider&lt;/code&gt; and others. We'll go for the &lt;code&gt;AwsProvider&lt;/code&gt;, &lt;code&gt;LambdaFunction&lt;/code&gt; and &lt;code&gt;IamRole&lt;/code&gt;. Add this at the top of your file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;AwsProvider&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;LambdaFunction&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;IamRole&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="s1"&gt;@cdktf/provider-aws&lt;/span&gt;&lt;span class="dl"&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 then create an AwsProvider in your stack:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;AwsProvider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;aws&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;region&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;eu-west-2&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Adding a lambda function to our stack
&lt;/h3&gt;

&lt;p&gt;To create a lambda we need to define an IAM role at first. Boring, but made easier by autocomplete of cours. Anyway here's the default policy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;roleForLambda&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;IamRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;iam-role-for-lambda&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;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;iam-role-for-lambda&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;assumeRolePolicy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Version&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;2012-10-17&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;Statement&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;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Action&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;sts:AssumeRole&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;Principal&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;Service&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;lambda.amazonaws.com&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;Effect&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;Allow&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;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we can add a lambda function to the stack like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;LambdaFunction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hello-world&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;filename&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cwd&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hello-world.zip&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="na"&gt;functionName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hello-world&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;index.handler&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;runtime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;nodejs12.x&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;roleForLambda&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arn&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;You will need to zip your lambda function - which is usually a separate step before running terraform. For example sake, let's say you have a file in your project named &lt;code&gt;hello-world.js&lt;/code&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="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handler&lt;/span&gt; &lt;span class="o"&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="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="na"&gt;hello&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;world&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;Then zip your lambda &lt;code&gt;zip -r lambda.zip hello-world.js&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Deploying your stack
&lt;/h3&gt;

&lt;p&gt;Before you deploy don't forget need to &lt;a href="https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-files.html" rel="noopener noreferrer"&gt;have your aws credentials in your path&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Now that you have everything ready you can deploy your stack with &lt;code&gt;cdktf deploy&lt;/code&gt;. This command will display an execution plan and ask you if you want to deploy. Press the &lt;code&gt;Y&lt;/code&gt; and &lt;code&gt;Enter&lt;/code&gt; key to deploy.&lt;/p&gt;

&lt;p&gt;Any errors at this stage should be farely self-explanatory. If they don't make sense, google the error message - other people have likely run into the same problem.&lt;/p&gt;




&lt;p&gt;If you're a terraform user and you've used the &lt;code&gt;lambda_function&lt;/code&gt; module before, you'll notice that the configuration is exactly the same.&lt;/p&gt;

&lt;p&gt;Ultimately, when you run &lt;code&gt;cdktf synth&lt;/code&gt; cdktf compiles your javascript/typescript modules into terraforms alternative &lt;a href="https://www.terraform.io/docs/configuration/syntax-json.html" rel="noopener noreferrer"&gt;&lt;code&gt;JSON&lt;/code&gt; configuration syntax&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This is an extremely powerful feature of terraform's design since it can be a compile target not just for javascript and typescript, but any kind of language. The open source community could add it's own language compilers.&lt;/p&gt;

&lt;p&gt;Why not write one in rust? 😅&lt;/p&gt;

&lt;p&gt;PS: The original version of this post was published on &lt;a href="https://jkrsp.com/writing-terraform-with-typescript/" rel="noopener noreferrer"&gt;my website&lt;/a&gt;, if you liked this post, please consider having a look around.&lt;/p&gt;

</description>
      <category>tutorial</category>
      <category>typescript</category>
      <category>devops</category>
      <category>terraform</category>
    </item>
    <item>
      <title>Environments per pull-request with github actions and terraform</title>
      <dc:creator>Julian Krispel-Samsel</dc:creator>
      <pubDate>Sat, 07 Nov 2020 17:11:28 +0000</pubDate>
      <link>https://dev.to/jkrsp/environments-per-pull-request-with-github-actions-and-terraform-27hh</link>
      <guid>https://dev.to/jkrsp/environments-per-pull-request-with-github-actions-and-terraform-27hh</guid>
      <description>&lt;p&gt;Using &lt;a href="https://github.com/features/actions"&gt;Github Actions&lt;/a&gt; and &lt;a href="https://www.terraform.io/"&gt;terraform&lt;/a&gt; - this post will guide you through automating the lifecycle of per pull request environments on github.&lt;/p&gt;

&lt;p&gt;As a sidenote, short-lived environments go by many names, some of these are: feature environments, ephemeral environments, &lt;a href="https://devcenter.heroku.com/articles/github-integration-review-apps"&gt;review apps&lt;/a&gt;, on demand environments or temporary environments.&lt;/p&gt;

&lt;h3&gt;
  
  
  What are short lived environments?
&lt;/h3&gt;

&lt;p&gt;Say you're on a team that works on multiple features simultaneously. There is a need to share the work with colleagues in Engineering, Product, Design or QA to collaborate, test and review. That's where short lived environments come in. Their lifecycle is tied to that of the project.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Infrastructure as Code?
&lt;/h3&gt;

&lt;p&gt;Although terraform is my weapon of choice (material for an entirely new blogpost), there are plenty other tools out there (such as &lt;a href="https://www.pulumi.com/"&gt;pulumi&lt;/a&gt; or &lt;a href="https://aws.amazon.com/cloudformation/"&gt;cloudformation&lt;/a&gt; 😬).&lt;/p&gt;

&lt;p&gt;The fact of the matter is - you should use IAC tools to maintain your sanity and this is especially true when it comes to managing short lived environments.&lt;/p&gt;

&lt;p&gt;Although in this tutorial I am just provisioning an S3 bucket with terraform (for my blog), the same concepts can easily be mapped to any resources that terraform has modules for and &lt;a href="https://www.terraform.io/docs/providers/aws/index.html"&gt;there are plenty&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pre-requisities
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;if you don't already have one yet, create an &lt;a href="http://aws.amazon.com/"&gt;aws account&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;make sure you add your aws credentials &lt;code&gt;AWS_ACCESS_KEY_ID&lt;/code&gt; and &lt;code&gt;AWS_SECRET_ACCESS_KEY&lt;/code&gt; &lt;a href="https://help.github.com/en/actions/configuring-and-managing-workflows/creating-and-storing-encrypted-secrets"&gt;to your github repository secrets&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Github Actions to control the lifecycle of short lived environments.
&lt;/h3&gt;

&lt;p&gt;We'll create 2 github actions to manage our environments. Github actions are basically &lt;code&gt;YAML&lt;/code&gt; files that are located in &lt;code&gt;.github/workflows/&lt;/code&gt;.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;create_and_update_pr_env.yml&lt;/code&gt; - When a pull request is opened or updated, we want to create/update our infrastructure. &lt;code&gt;terraform apply&lt;/code&gt; does both of that, simple!&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;destroy_pr_env.yml&lt;/code&gt; - When the pull request is closed, we need to destroy the environment to make sure we're not wasting resources (if we don't our AWS bill will be one nasty surprise).&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Defining the infrastructure with terraform
&lt;/h3&gt;

&lt;p&gt;For this tutorial, we're using the terraform configuration for this blog. At the time of writing, here's how this looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;
&lt;span class="k"&gt;provider&lt;/span&gt; &lt;span class="s2"&gt;"aws"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;region&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"eu-west-2"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;terraform&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;backend&lt;/span&gt; &lt;span class="s2"&gt;"s3"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// make sure you'll replace the above&lt;/span&gt;
    &lt;span class="c1"&gt;// with a bucket name that fits&lt;/span&gt;
    &lt;span class="nx"&gt;bucket&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"jkrsp-tf-state"&lt;/span&gt;
    &lt;span class="nx"&gt;region&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"eu-west-2"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"env_prefix"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"is_temp_env"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;default&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;Wouldn't it be awesome if everyone got access to aws playground so we can get people involved and potentially excited about it 🎉  &lt;/p&gt;

&lt;p&gt;As you can see, we're using &lt;code&gt;s3&lt;/code&gt; as our terraform backend. We're also defining two variables here &lt;code&gt;env_prefix&lt;/code&gt; and &lt;code&gt;is_temp_env&lt;/code&gt;. Next up you can see the only resource defined for this website, which is the we use for hosting the blog. The &lt;code&gt;env_prefix&lt;/code&gt; variable is what we use to specify our environment-specific resource so we can avoid naming collisions.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;is_temp_env&lt;/code&gt; is there to differentiate between production and a temporary environments. For temporary environments, we want to set &lt;code&gt;force_destroy&lt;/code&gt; to &lt;code&gt;true&lt;/code&gt;, so that we can easily destroy a bucket resource via terraform. If this is set to false, destroying the bucket will fail unless it is empty (which is exactly the kind of safety we want for production but not for a temporary environment)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_s3_bucket"&lt;/span&gt; &lt;span class="s2"&gt;"b"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;bucket&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env_prefix&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;jkrsp.com"&lt;/span&gt;
  &lt;span class="nx"&gt;acl&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"public-read"&lt;/span&gt;
  &lt;span class="nx"&gt;force_destroy&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;is_temp_env&lt;/span&gt;

  &lt;span class="nx"&gt;policy&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;POLICY&lt;/span&gt;&lt;span class="sh"&gt;
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "PublicReadGetObject",
      "Effect": "Allow",
      "Principal": "*",
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::${var.env_prefix}jkrsp.com/*"
    }
  ]
}
&lt;/span&gt;&lt;span class="no"&gt;  POLICY

&lt;/span&gt;  &lt;span class="nx"&gt;website&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;index_document&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"index.html"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;ManagedBy&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"terraform"&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;At last, we're using an output variable to feed the url for our bucket website back to our github action, we'll use this later to display a link in the pr.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"website"&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="s2"&gt;"http://&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;aws_s3_bucket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;website_endpoint&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now that we have a basic terraform setup, let's create our actions...&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating/updating an environment when a PR is opened
&lt;/h3&gt;

&lt;p&gt;First let's create a an action yml file &lt;code&gt;.github/workflows/create_and_update_pr_env.yml&lt;/code&gt; with the following contents:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;types&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;reopened&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;opened&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;synchronize&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;edited&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This block defines which events trigger the action (there's a more comprehensive list on &lt;a href="https://help.github.com/en/actions/reference/events-that-trigger-workflows"&gt;help.github.com&lt;/a&gt;), in this instance the action gets triggered when a pull request has been opened, reopened, synchronized or edited.&lt;/p&gt;

&lt;p&gt;Next we'll define the name of the action, the image being used for the action and the environment variables:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Create PR Env&lt;/span&gt;
&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;create_or_update_pr_env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;AWS_ACCESS_KEY_ID&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.AWS_ACCESS_KEY_ID }}&lt;/span&gt;
      &lt;span class="na"&gt;AWS_SECRET_ACCESS_KEY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.AWS_SECRET_ACCESS_KEY }}&lt;/span&gt;
      &lt;span class="na"&gt;BRANCH_NAME&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.head_ref }}&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;....&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Github actions not unlike other CI tools are containerized - &lt;code&gt;ubuntu-latest&lt;/code&gt; is our choice container image for this action. We've defined 3 variables, &lt;code&gt;AWS_ACCESS_KEY_ID&lt;/code&gt; and &lt;code&gt;AWS_SECRET_ACCESS_KEY&lt;/code&gt; we need for deploying to AWS via terraform. Terraform will automatically pick those up. We'll use the &lt;code&gt;BRANCH_NAME&lt;/code&gt; env variable as a prefix for naming our temporary environment.&lt;/p&gt;

&lt;p&gt;We're using github &lt;a href="https://help.github.com/en/actions/reference/context-and-expression-syntax-for-github-actions"&gt;actions context variables here&lt;/a&gt;, namely - &lt;code&gt;secrets&lt;/code&gt; and &lt;code&gt;github&lt;/code&gt;. Obviously the &lt;code&gt;secrets&lt;/code&gt; variable refers to the github repo's secrets for your AWS secrets.&lt;/p&gt;

&lt;p&gt;Next let's look at the actual steps:&lt;/p&gt;

&lt;p&gt;First the build steps:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Checkout&lt;/span&gt;
  &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v1&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install Dependencies&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;yarn&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Checkout&lt;/span&gt;
  &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v1&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install Dependencies&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;yarn&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build Site&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;yarn build&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is fairly standard, you'll always want to checkout your repository as a first step and install your dependencies. In this case I'm building a gatsby site, but this could just as well be any other app with a build step.&lt;/p&gt;

&lt;p&gt;Once the site is built, we use terraform to orchestrate our deployment. Hashicorps' &lt;a href="https://github.com/hashicorp/terraform-github-actions"&gt;github actions for terraform&lt;/a&gt; let you easily add any terraform command as a step. For this action, we'll use 4 commands: &lt;code&gt;init&lt;/code&gt;, &lt;code&gt;plan&lt;/code&gt;, &lt;code&gt;apply&lt;/code&gt; and &lt;code&gt;output&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The trick to achieving environments per pull request is to have different state files per environment as well as different names for resources. This is where the &lt;code&gt;BRANCH_NAME&lt;/code&gt; variable comes in.&lt;/p&gt;

&lt;p&gt;When initializing terraform we can pass in a &lt;code&gt;key&lt;/code&gt; for an s3 bucket specific to this environment, pay attention to the &lt;code&gt;args&lt;/code&gt; parameter to see how we achieve this by prefixing our key config with the &lt;code&gt;BRANCH_NAME&lt;/code&gt; variable.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Terraform&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Init'&lt;/span&gt;
  &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;hashicorp/terraform-github-actions@master&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;tf_actions_version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;0.12.13&lt;/span&gt;
    &lt;span class="na"&gt;tf_actions_subcommand&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;init'&lt;/span&gt;
    &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;-backend-config="key=${{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;env.BRANCH_NAME&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}.jkrsp.com.tfstate"'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next we use terraform plan and pass in the &lt;code&gt;BRANCH_NAME&lt;/code&gt; as a variable to parameterize our terraform config. Note that I also pass in a variable called &lt;code&gt;is_temp_env&lt;/code&gt; - this I do because we want our temporary s3 resource to be deletable. Once we used plan we can follow up with &lt;code&gt;apply&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Terraform Plan&lt;/span&gt;
  &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;hashicorp/terraform-github-actions@master&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;tf_actions_version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;0.12.13&lt;/span&gt;
    &lt;span class="na"&gt;tf_actions_subcommand&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;plan'&lt;/span&gt;
    &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;-var="is_temp_env=true"&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-var="env_prefix=${{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;env.BRANCH_NAME&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}."&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-out&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;tf.plan'&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Terraform Apply&lt;/span&gt;
  &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;hashicorp/terraform-github-actions@master&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;tf_actions_version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;0.12.13&lt;/span&gt;
    &lt;span class="na"&gt;tf_actions_subcommand&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;apply'&lt;/span&gt;
    &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;tf.plan'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next we use &lt;code&gt;output&lt;/code&gt; to extract the website url of our s3 bucket. We'll come back to this in a bit, when we post the url of the temporary website as a comment on our pr. To output a single value we add our desired field name to &lt;code&gt;args&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Terraform Output&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;terraform&lt;/span&gt;
  &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;hashicorp/terraform-github-actions@master&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;tf_actions_version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;0.12.13&lt;/span&gt;
    &lt;span class="na"&gt;tf_actions_subcommand&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;output'&lt;/span&gt;
    &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;website'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next we deploy our build to our s3 bucket via the cli. Again, we use our &lt;code&gt;BRANCH_NAME&lt;/code&gt; env variable to specify the bucket or the environment.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sync to s3&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;aws s3 sync public s3://${{ env.BRANCH_NAME }}.jkrsp.com&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As a final step in our action, I'm using a little JavaScript program to add a comment to the pr including the website.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;node .github/actions/comment.js&lt;/span&gt;
  &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;github_token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.GITHUB_TOKEN }}&lt;/span&gt;
    &lt;span class="na"&gt;issue_number&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.event.pull_request.number }}&lt;/span&gt;
    &lt;span class="na"&gt;repo&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;jkrsp'&lt;/span&gt;
    &lt;span class="na"&gt;owner&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.repository_owner }}&lt;/span&gt;
    &lt;span class="na"&gt;website_link&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ steps.terraform.outputs.tf_actions_output }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;comment.js&lt;/code&gt; file is located in &lt;code&gt;.github/actions/comment.js&lt;/code&gt; and looks like this:&lt;/p&gt;

&lt;p&gt;As you can see passing in the parameters above will give the script all the variables it needs to post a comment.&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;github&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@actions/github&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="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;run&lt;/span&gt;&lt;span class="p"&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;issue_number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;github_token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;website_link&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;octokit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;github&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GitHub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;github_token&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="kd"&gt;const&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="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;octokit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;issues&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createComment&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="nx"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;issue_number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="c1"&gt;// We unfortunately need to use replace to get rid&lt;/span&gt;
      &lt;span class="c1"&gt;// of extraneous double quotes&lt;/span&gt;
      &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`[Review website here](&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;website_link&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/"/g&lt;/span&gt;&lt;span class="nx"&gt;s&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="s2"&gt;)`&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="c1"&gt;// for debugging, lets log the created comment&lt;/span&gt;
    &lt;span class="nx"&gt;console&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;created comment&lt;/span&gt;&lt;span class="dl"&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="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;run&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. Having added this to your github repository should create your environment and display a link to the bucket website when you open a pr.&lt;/p&gt;

&lt;p&gt;But how do we get rid of the environment once we're done reviewing and the pr is closed/merged?&lt;/p&gt;

&lt;p&gt;That's where our second action comes in:&lt;/p&gt;

&lt;h3&gt;
  
  
  Destroying a short lived environment
&lt;/h3&gt;

&lt;p&gt;First lets start of with the block that defines for which events our action gets triggered&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;types&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;closed&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pretty self explanatory: This action gets triggered when a pull request has been closed.&lt;/p&gt;

&lt;p&gt;Next up, like before - we name the action and the job and define our environment variables:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Destroy PR Env&lt;/span&gt;
&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;destroy_pr_env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;AWS_ACCESS_KEY_ID&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.AWS_ACCESS_KEY_ID }}&lt;/span&gt;
      &lt;span class="na"&gt;AWS_SECRET_ACCESS_KEY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.AWS_SECRET_ACCESS_KEY }}&lt;/span&gt;
      &lt;span class="na"&gt;BRANCH_NAME&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.head_ref }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now to the steps. First we need to check out our repo again:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Checkout&lt;/span&gt;
  &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v1&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.head_ref }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we start initializing terraform, again we use the &lt;code&gt;BRANCH_NAME&lt;/code&gt; to specify the bucket for our environment&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Terraform&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Init'&lt;/span&gt;
  &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;hashicorp/terraform-github-actions@master&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;tf_actions_version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;0.12.13&lt;/span&gt;
    &lt;span class="na"&gt;tf_actions_subcommand&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;init'&lt;/span&gt;
    &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;-backend-config="key=${{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;env.BRANCH_NAME&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}.jkrsp.com.tfstate"'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next we destroy the environment, passing in two variables - &lt;code&gt;is_temp_env&lt;/code&gt; and &lt;code&gt;env_prefix&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Terraform&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Destroy'&lt;/span&gt;
  &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;hashicorp/terraform-github-actions@master&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;tf_actions_version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;0.12.13&lt;/span&gt;
    &lt;span class="na"&gt;tf_actions_subcommand&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;destroy'&lt;/span&gt;
    &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;-var="is_temp_env=true"&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-var="env_prefix=${{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;env.BRANCH_NAME&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}."'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, we comment on our pr again, to tell our colleagues that the environment has definitely been destroyed. Note how we pass in &lt;code&gt;secrets.GITHUB_TOKEN&lt;/code&gt; - we need this token for any interaction with the github api.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Tell PR that we have destroyed the thing&lt;/span&gt;
  &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;thollander/actions-comment-pull-request@master&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Temporary&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;environment&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;now&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;destroyed'&lt;/span&gt;
    &lt;span class="na"&gt;GITHUB_TOKEN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.GITHUB_TOKEN }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Right that's it. If you'd like to check out the workflows described in this blog post, please have a look a &lt;a href="https://github.com/juliankrispel/jkrsp/tree/master/.github/workflows"&gt;the repository for this blog&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The original version of &lt;a href="https://jkrsp.com/environments-per-pull-request/"&gt;this blog post is here&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>serverless</category>
      <category>devops</category>
      <category>terraform</category>
      <category>github</category>
    </item>
    <item>
      <title>✎ - Zettel - A library for building editors</title>
      <dc:creator>Julian Krispel-Samsel</dc:creator>
      <pubDate>Fri, 18 Oct 2019 09:18:47 +0000</pubDate>
      <link>https://dev.to/jkrsp/zettel-a-library-for-building-editors-in-the-making-34pe</link>
      <guid>https://dev.to/jkrsp/zettel-a-library-for-building-editors-in-the-making-34pe</guid>
      <description>&lt;p&gt;👋 Howdy DEV community!!!&lt;/p&gt;

&lt;p&gt;I'm working on &lt;a href="https://github.com/juliankrispel/zettel"&gt;this library I call Zettel&lt;/a&gt; for building editors with React. So here you go.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--07fhDTzm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/q1958o0zb0nc893b5qgk.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--07fhDTzm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/q1958o0zb0nc893b5qgk.gif" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Currently working on a pre-release, something that people can use in their apps, something that's documented and well tested.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/juliankrispel/zettel/tree/master/examples"&gt;github repo is full of examples&lt;/a&gt;, the examples are &lt;a href="http://zettel.software"&gt;also hosted on gh-pages&lt;/a&gt;, check it out and reach out if you like!&lt;/p&gt;

&lt;p&gt;I've just created a slack channel for Zettel, &lt;a href="https://join.slack.com/t/zetteljs/shared_invite/enQtNzk4NjM5Njc0Nzg5LTI2ZTljZTMwY2JjMmFkOWM3Yzk5YjdlODgxZWIwMzc5YmE4MGQ1MjViZjUxMmUxZmZjNmY3OTljOWRiMmNmZjg"&gt;here's the invite link&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="http://zettel.software"&gt;zettel.software&lt;/a&gt;&lt;/p&gt;

</description>
      <category>react</category>
      <category>javascript</category>
      <category>uiweekly</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
