<?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: Alexandra Sunderland</title>
    <description>The latest articles on DEV Community by Alexandra Sunderland (@alexandras_dev).</description>
    <link>https://dev.to/alexandras_dev</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%2F189684%2F09a77a05-a495-48a7-8964-ddef2242ae4f.jpg</url>
      <title>DEV Community: Alexandra Sunderland</title>
      <link>https://dev.to/alexandras_dev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/alexandras_dev"/>
    <language>en</language>
    <item>
      <title>Hellow World</title>
      <dc:creator>Alexandra Sunderland</dc:creator>
      <pubDate>Fri, 20 Aug 2021 13:25:47 +0000</pubDate>
      <link>https://dev.to/felloweng/hellow-world-5209</link>
      <guid>https://dev.to/felloweng/hellow-world-5209</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--1SbMg1QY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2Am_X_HFcYAw0fqEqj286Mkg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--1SbMg1QY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2Am_X_HFcYAw0fqEqj286Mkg.png" alt=""&gt;&lt;/a&gt;Fellow Engineering Blog: The technology behind the platform that makes meetings delightful&lt;/p&gt;

&lt;p&gt;No, that’s not a typo. Over the last few years, all of us here have typed the name “Fellow” over and over again — so many times that now every word that ends with “ello” is accidentally followed up with a “w”. Most of the time we catch it, but it’s one of those adorable mistakes that we like to have fun with once in a while, and gives a glimpse into the culture of the team.&lt;/p&gt;

&lt;p&gt;Starting today with this new publication, we’re going to be making that mistake a whole lot more! Many members of the engineering team here have been contributing to tech blogs, giving tech talks, speaking on podcasts, and open-sourcing projects for a while now — because we love helping others learn, sharing the cool things we’re learning ourselves, and giving back to the community. We’re excited to launch this new Fellow Engineering blog as another platform for our team to do just that!&lt;/p&gt;

&lt;p&gt;We don’t quite know what this blog will be just yet, but it will grow with us and evolve over time as we build out new projects and discover cool tech that we want to share with the world. The core of our platform (and the heart &amp;amp; soul of the technology behind Fellow) includes our collaborative notes editor, our calendar, and our integrations. While we may give a peek into how we’ve built those once in a while (&lt;a href="https://fellow.app/careers"&gt;&lt;em&gt;join the team to get the full story&lt;/em&gt;&lt;/a&gt;), we want to start off by sharing our learnings about things like how to build a dark mode mobile app, or how to manage browser extension versions when you have multiple environments. We’ll also be writing about tech-adjacent topics too—like how to run engineering retrospectives, or why and how we run &lt;em&gt;the best&lt;/em&gt; internal hackathons. And we may even have posts about product &amp;amp; design too!&lt;/p&gt;

&lt;p&gt;As a team of engineers that loves to build and problem solve, we’re all figuring it out together as we go along. And that’s half the fun.&lt;/p&gt;

&lt;p&gt;See you on the blog! 👋&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--6GAMskN3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/652/1%2AWYEo-KFClZcPQur3LTh7Ow.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--6GAMskN3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/652/1%2AWYEo-KFClZcPQur3LTh7Ow.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;




</description>
      <category>engineering</category>
      <category>technology</category>
      <category>engineeringmangement</category>
      <category>python</category>
    </item>
    <item>
      <title>Decaffeinating 2020: 
Migrating our CoffeeScript app to TypeScript</title>
      <dc:creator>Alexandra Sunderland</dc:creator>
      <pubDate>Fri, 07 Feb 2020 16:05:26 +0000</pubDate>
      <link>https://dev.to/alexandras_dev/decaffeinating-2020-migrating-our-coffeescript-app-to-typescript-4lbo</link>
      <guid>https://dev.to/alexandras_dev/decaffeinating-2020-migrating-our-coffeescript-app-to-typescript-4lbo</guid>
      <description>&lt;p&gt;With the start of the new decade, the engineering team at &lt;a href="https://fellow.app" rel="noopener noreferrer"&gt;Fellow.app&lt;/a&gt; decided to take on a new year’s resolution to do something that was holding us back: we decided to convert our &lt;a href="https://coffeescript.org/" rel="noopener noreferrer"&gt;CoffeeScript&lt;/a&gt; web app to &lt;a href="https://www.typescriptlang.org/" rel="noopener noreferrer"&gt;TypeScript&lt;/a&gt;. This is the story of how we pulled through as a team to overcome the issues in our way, and how completing this massive migration improved our product.&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%2Falexandras.dev%2Fs%2Fhappy_coffee.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/https%3A%2F%2Falexandras.dev%2Fs%2Fhappy_coffee.png" alt="A happy person with a happy cup of coffee"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;(Illustration by &lt;a href="https://dribbble.com/matt_emond" rel="noopener noreferrer"&gt;Matt Emond&lt;/a&gt;, modifications to it below by me!)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;From the start, we’ve highly valued the ability to quickly iterate on features based on feedback we get from the teams that use Fellow, and CoffeeScript was part of what enabled that speed. CoffeeScript is a language that compiles to JavaScript and was meant to reduce the space code takes up and improve readability, while also adding new functionality that JavaScript would later adopt (like a &lt;a href="https://coffeescript.org/#classes" rel="noopener noreferrer"&gt;clean way of defining classes&lt;/a&gt;). With all the developers on the team already knowing Python, CoffeeScript was a natural choice because of the similarities between the two (like a lack of braces and use of whitespace for logical separation) which made context switching from one language to the other easy. Spending less time mentally shifting between two distinct syntaxes and having to write fewer braces and parentheses made us more efficient at building features, and we were able to keep up with our value of fast iteration. &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%2Falexandras.dev%2Fs%2Fsad_coffee.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/https%3A%2F%2Falexandras.dev%2Fs%2Fsad_coffee.png" alt="A sad person with a happy cup of coffee"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But suddenly, we didn’t need to keep iterating on features &lt;em&gt;quite&lt;/em&gt; so quickly anymore: while we still wanted to be able to ship new features often, we had built a product that we were proud of, and the user base had started to grow at a rapid pace. Our priorities shifted, and we needed to start providing the most stable experience that we could while building new features more methodically. CoffeeScript started to get in the way: the lack of parentheses made code hard to understand, the non-existent debugging tools made fixing bugs a lengthy process, and developers had to learn a whole new syntax when they joined the team. We were also running into completely avoidable bugs centered around types: maybe a function would incorrectly receive a null variable, or a number would be passed as a string and not an integer -- and these would all cause runtime errors, only detectable when people would use the app. It was creating a &lt;em&gt;latte&lt;/em&gt; extra work.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// The following is valid CoffeeScript. Can you easily understand the operation?&lt;/span&gt;
&lt;span class="nx"&gt;square&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&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;x&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt;
&lt;span class="nx"&gt;add&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;add&lt;/span&gt; &lt;span class="nx"&gt;square&lt;/span&gt; &lt;span class="nx"&gt;add&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;add&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// 17&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We were starting to lose time dealing with these issues, when we had sought to use CoffeeScript as a way to speed up development.&lt;/p&gt;

&lt;p&gt;On December 13th 2019, exactly 10 years after CoffeeScript came out, we sat down and started to plan our full transition plan off of the language, after having slowly converted some files here and there starting in the summer. We wanted to start using a language that had a strong community, effective debugging tools, IDE support, and would enable us to avoid as many errors as possible. TypeScript fit the bill: &lt;a href="https://www.typescriptlang.org/docs/handbook/functions.html#function-types" rel="noopener noreferrer"&gt;type annotations&lt;/a&gt; would allow us to reduce runtime errors while acting as mini documentation for our code, the available &lt;a href="https://github.com/Microsoft/TypeScript/wiki/TypeScript-Editor-Support" rel="noopener noreferrer"&gt;tooling and editor integrations&lt;/a&gt; are endless, and it’s something that we were excited to write code in instead of dreading. Our transition plan? Sit together as a team for an afternoon for “The Decaffeination” and convert tens of thousands of lines of CoffeeScript to TypeScript in parallel before going on break for the holidays, and eventually releasing the conversion to production in one go. &lt;em&gt;And we did it&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;We let the CoffeeScript removal plan &lt;em&gt;brew&lt;/em&gt; in our minds for a few days, and then &lt;em&gt;poured&lt;/em&gt; our ideas into our shared meeting notes for everyone to see. We compiled the list of files that needed to be converted and added them as action items in the same note, assigning each one to a member of the team so we’d be more or less equal in the task ahead. We decided to create a git branch dedicated to the task, &lt;code&gt;decaffeinate&lt;/code&gt;, and all pushed our code there. A week later, on December 20th, we got to work.&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%2Falexandras.dev%2Fs%2Fdecaffeination_note.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/https%3A%2F%2Falexandras.dev%2Fs%2Fdecaffeination_note.png" alt="A note with information about how we structured the decaffeination"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But even though we were ultimately successful, the task didn’t come without issues! Sitting all together spread out over couches and cozy chairs drinking tea (anything but coffee) enabled us to work really well together: any issues we ran into were shouted out and everyone would help, and every successfully converted file was celebrated. Some of the hardest parts we had to tackle were a result of our tech stack (we use &lt;a href="https://reactjs.org/" rel="noopener noreferrer"&gt;React&lt;/a&gt;/&lt;a href="https://relay.dev/" rel="noopener noreferrer"&gt;Relay&lt;/a&gt;), creating errors that had no results on StackOverflow and new bugs that were difficult to trace.&lt;/p&gt;

&lt;p&gt;We learned a lot by tackling this conversion, and found that there were limited resources on the web about our particular stack -- and so we’d like to contribute back to the community and share our struggles. If you’re looking to convert a project to TypeScript, I highly recommend reading &lt;a href="https://www.twilio.com/blog/move-to-typescript" rel="noopener noreferrer"&gt;Dominik Kundel’s detailed blog post&lt;/a&gt; about how to get started.&lt;/p&gt;

&lt;p&gt;Here are some of the top issues that we encountered during this process:&lt;/p&gt;




&lt;h3&gt;
  
  
  String interpolation
&lt;/h3&gt;

&lt;p&gt;We have a lot of strings in the codebase where we insert variables, which looks like this in CoffeeScript:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"You have  #{num_1on1s} upcoming 1-on-1s today." 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But in TypeScript, the same string interpolation would look like this:&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="s2"&gt;`You have &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;num_1on1s&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; upcoming 1-on-1s today.`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since TypeScript detects string interpolation through back-ticks and not quotation marks, we had to be extra careful to check our converted code for strings that would need to be converted manually, because TypeScript would literally output "You have  #{num_1on1s} upcoming 1-on-1s today." if we didn’t update the format to back-ticks and dollar signs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Incorrect types for 3rd party libraries
&lt;/h3&gt;

&lt;p&gt;A lot of 3rd party libraries have typed versions, which we believed would decrease the errors we saw in the code even more. Unfortunately, a number of the type interfaces for the libraries we were using weren’t being updated in conjunction with the non-typed versions that we had been using before the translation. This led to a number of extra errors, and we found that sometimes it was better to simply set the accepted types to &lt;code&gt;any&lt;/code&gt; and be extra careful about what we were passing around instead of trusting an incorrect version.&lt;/p&gt;

&lt;h3&gt;
  
  
  Typing Higher Order Components
&lt;/h3&gt;

&lt;p&gt;In &lt;a href="https://reactjs.org/" rel="noopener noreferrer"&gt;React&lt;/a&gt;, a &lt;a href="https://reactjs.org/docs/higher-order-components.html" rel="noopener noreferrer"&gt;Higher Order Component&lt;/a&gt; is a function that takes a component and returns a new component, the goal being to reuse code that modifies components in a similar way (for example, a HOC might accept a component and return a loading spinner instead of the component if some API call hasn’t finished yet, which is a pattern you may want to use for many different areas).&lt;br&gt;
Higher Order Components are notoriously difficult to type because of the range of component types that might be passed to them, and all the different props those components have. In our codebase we have one particularly large HOC &lt;code&gt;withNoteStream&lt;/code&gt;, which we use to control a lot of the data and rendering logic for the parts of the project that have streams of notes. And we have a lot of notes! Personal notes, meeting notes, 1-on-1 notes, goal notes, … all of these sections have their own components which use &lt;code&gt;withNoteStream&lt;/code&gt;, and each has their own unique set of properties that needs to be passed through. We haven’t yet found a great way to work around this issue without casting the types to a generic component, and it’s an ongoing discussion in the React/TypeScript community.&lt;/p&gt;
&lt;h3&gt;
  
  
  GraphQL types are undefined by default
&lt;/h3&gt;

&lt;p&gt;We use &lt;a href="https://graphql.org/" rel="noopener noreferrer"&gt;GraphQL&lt;/a&gt; to pass data between our React frontend and &lt;a href="https://www.djangoproject.com/" rel="noopener noreferrer"&gt;Django backend&lt;/a&gt;, so when we started converting to TypeScript and found out that GraphQL types are undefined by default, we had to either write all of our TypeScript code to check for the existence of data before using it (&lt;code&gt;if this.props.dataWeKnowExists &amp;amp;&amp;amp; this.props.dataWeKnowExists.value&lt;/code&gt;...), or we had to find another solution.&lt;br&gt;
We use &lt;a href="https://docs.graphene-python.org/projects/django/en/latest/" rel="noopener noreferrer"&gt;Graphene&lt;/a&gt; to expose our Django models to GraphQL, and we partially solved the undefined problem by wrapping the list fields in &lt;a href="https://docs.graphene-python.org/en/latest/types/list-and-nonnull/#nonnull" rel="noopener noreferrer"&gt;graphene.NonNull()&lt;/a&gt; and setting &lt;code&gt;required=True&lt;/code&gt; in the node fields.&lt;br&gt;
This solution did not work for &lt;a href="https://docs.graphene-python.org/en/latest/relay/connection/" rel="noopener noreferrer"&gt;DjangoConnectionFields&lt;/a&gt; though, which is a field type that is used to create a paginated list of related objects. Fields with this type were still being considered as possibly undefined in TypeScript, even if we knew with certainty that they would not be. To fix this issue, we created the following class to inherit from, which would set all of the nodes to &lt;code&gt;required=True&lt;/code&gt; and all of the edges to &lt;code&gt;graphene.NonNull()&lt;/code&gt; so that we could have peace of mind when writing our frontend code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;NonNullConnection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;graphene&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relay&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Connection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;abstract&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;  &lt;span class="c1"&gt;# type: ignore
&lt;/span&gt;   &lt;span class="nd"&gt;@classmethod&lt;/span&gt;
   &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init_subclass_with_meta__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;edge_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
       &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nf"&gt;hasattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Edge&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
           &lt;span class="n"&gt;_node&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;

           &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;EdgeBase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;graphene&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ObjectType&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
               &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Meta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                   &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;edge_name&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__name__&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;Edge&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;

               &lt;span class="n"&gt;cursor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;graphene&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;required&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
               &lt;span class="n"&gt;node&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;graphene&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_node&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;required&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

           &lt;span class="n"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Edge&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;EdgeBase&lt;/span&gt;

       &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nf"&gt;hasattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;edges&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
           &lt;span class="n"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;edges&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;graphene&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;graphene&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;NonNull&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Edge&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;required&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

       &lt;span class="nf"&gt;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NonNullConnection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;__init_subclass_with_meta__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;_node&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Typing anything that is passed inexplicit props
&lt;/h3&gt;

&lt;p&gt;Writing out &lt;code&gt;{...props}&lt;/code&gt; in a component is a nice and easy way to pass props down to a component that you’ve extended, without needing to be explicit about what those props are (maybe because the component you’re writing doesn’t care what they are, and you don’t want to maintain them in multiple spots). In TypeScript though, this caused issues for us, because for every component we did this for, we needed to find all the permutations of the properties that were being passed through and correctly type them. Even though this was a lengthy process, it ended up being well worth the effort because it also provided some refactoring! Refactoring is typically a risky idea when converting code because of the possibility of introducing bugs from both the refactor and the translation (with little to indicate which caused the issue), but we were able to remove props that we found we were no longer using at all, which also reduced the number of types we needed to resolve.&lt;/p&gt;




&lt;p&gt;A few weeks later when we finished converting the last few lines and tested out every last little bit of the product, we merged our decaffeinate pull request into our main branch and waited excitedly to see our error count go down and code readability go up 🎉&lt;/p&gt;

&lt;p&gt;We’re now writing all new features in TypeScript, and even though prototyping features may not be as fast as it was with CoffeeScript, the benefits we’re getting out of using it at our stage of team growth are undeniable:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;New hires and junior developers have an easier time adapting to the codebase because the code is more readable, and we’re using a more common language.&lt;/li&gt;
&lt;li&gt;Type-related errors are being caught as we’re writing code.&lt;/li&gt;
&lt;li&gt;Argument definitions are explicit, which helps the code self-document.&lt;/li&gt;
&lt;li&gt;Developers are happier because of the increase in tooling and ease of debugging.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Overall, our code is more maintainable, which is enabling us to create a reliable product and keep developers happy. We’re excited that we hit our goal as a team, and looking forward to our coffee-free 2020!&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%2Falexandras.dev%2Fs%2Fhappy_no_coffee.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/https%3A%2F%2Falexandras.dev%2Fs%2Fhappy_no_coffee.png" alt="A happy person with no coffee"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>graphql</category>
      <category>django</category>
      <category>coffeescript</category>
    </item>
    <item>
      <title>50 Questions to be ready to answer when you’re on booth duty at a career fair</title>
      <dc:creator>Alexandra Sunderland</dc:creator>
      <pubDate>Mon, 06 Jan 2020 20:43:14 +0000</pubDate>
      <link>https://dev.to/alexandras_dev/50-questions-to-be-ready-to-answer-when-you-re-on-booth-duty-at-a-career-fair-9c9</link>
      <guid>https://dev.to/alexandras_dev/50-questions-to-be-ready-to-answer-when-you-re-on-booth-duty-at-a-career-fair-9c9</guid>
      <description>&lt;p&gt;The start of the new year means that summer is just around the corner, and with that comes intern season! At &lt;a href="https://fellow.app/"&gt;Fellow.app&lt;/a&gt; we host a few engineering interns from May to August every year, and in January we start to ramp up our recruiting efforts by attending career fairs. Hosting a booth at a career fair is a great way for companies to meet a lot of candidates in a way that online applications can’t compare, but it can be daunting when on your first-ever booth duty time slot someone asks you “What is your typical day like?”. Suddenly time freezes, and you start questioning everything you’ve done in the last few days and try to find a way to explain that one time you spent an afternoon taking pictures of the office succulent friends because it doesn’t sound “professional”.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--5WM613gy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://alexandras.dev/s/plants.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--5WM613gy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://alexandras.dev/s/plants.jpg" alt="Four succulents with Xbox controllers"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;We really did do an office succulent friends photoshoot&lt;/em&gt; 🌵📷&lt;/p&gt;

&lt;p&gt;Having been to dozens of career fairs over the years at conferences and schools across North America, and having run booths for startups and post-IPO companies alike, I’ve been caught off guard by all sorts of questions from the thousands of people I’ve met. Some of the hardest questions to answer are the ones that are opinion-based: “What do you most enjoy about your job”, and “What’s something you found challenging in the last year” are oddly similar to interview questions, and it can be difficult to come up with answers on the spot that make both you and the company look good.&lt;/p&gt;

&lt;p&gt;In preparation for the upcoming career fairs we’ll be attending, I put together a list of commonly asked questions so that everyone can get a head start on answering them before the events — and avoid being stuck in front of someone when you can’t remember what projects your team has on the go.&lt;/p&gt;

&lt;p&gt;The list below is in no particular order and has been generalized to fit most companies. Whether you’re representing a company at a booth for the first time or putting together your own list of questions for your team to prepare, I hope that these questions give you reassurance that you’ll be able to get through booth duty without worry.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; Create a living document with these questions and outlines to your company-specific answers. I use Fellow to &lt;a href="https://www.fellow.app/blog/2019/fellow-is-the-best-meeting-notes-app/"&gt;add the questions to a note that’s linked to the career fair calendar event&lt;/a&gt;, so that everyone going can find the document easily.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  50 top questions to be ready for
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;How do I apply?&lt;/li&gt;
&lt;li&gt;Are you hiring full-time/part-time right now?&lt;/li&gt;
&lt;li&gt;Are you hiring for [&lt;em&gt;role that isn’t listed on careers page&lt;/em&gt;] positions?&lt;/li&gt;
&lt;li&gt;How many people are you hiring?&lt;/li&gt;
&lt;li&gt;Do you hire interns in the non-summer terms?&lt;/li&gt;
&lt;li&gt;What is the interview process?&lt;/li&gt;
&lt;li&gt;How long does it take from application to hiring decision?&lt;/li&gt;
&lt;li&gt;Do I need to know [&lt;em&gt;technology/skill&lt;/em&gt;] for the interview?&lt;/li&gt;
&lt;li&gt;Do I need to know [&lt;em&gt;something listed as a job requirement&lt;/em&gt;] specifically?&lt;/li&gt;
&lt;li&gt;What skills are you looking for?&lt;/li&gt;
&lt;li&gt;Do you hire first year students / entry level?&lt;/li&gt;
&lt;li&gt;Do I need to have [&lt;em&gt;x&lt;/em&gt;] education to apply?&lt;/li&gt;
&lt;li&gt;Can I work part time during the school year?&lt;/li&gt;
&lt;li&gt;When will I hear back about whether I’m getting an interview?&lt;/li&gt;
&lt;li&gt;Will I hear back from you if I’m rejected?&lt;/li&gt;
&lt;li&gt;Who can I contact if I have more questions?&lt;/li&gt;
&lt;li&gt;What are your company’s diversity initiatives?&lt;/li&gt;
&lt;li&gt;How much do you pay?&lt;/li&gt;
&lt;li&gt;How much do you make?&lt;/li&gt;
&lt;li&gt;What kinds of projects do you give interns/new hires?&lt;/li&gt;
&lt;li&gt;Will I be working alone or as a part of a team?&lt;/li&gt;
&lt;li&gt;Are there mentorship opportunities?&lt;/li&gt;
&lt;li&gt;Do you need to know [&lt;em&gt;spoken language&lt;/em&gt;]?&lt;/li&gt;
&lt;li&gt;What does someone in your position do in a typical day?&lt;/li&gt;
&lt;li&gt;What’s the company/team’s culture like?&lt;/li&gt;
&lt;li&gt;If you could, what would you change about the company?&lt;/li&gt;
&lt;li&gt;What are some perks of working at [&lt;em&gt;company&lt;/em&gt;]?&lt;/li&gt;
&lt;li&gt;Why should I work at [&lt;em&gt;company&lt;/em&gt;] instead of [&lt;em&gt;company you haven’t heard of&lt;/em&gt;]?&lt;/li&gt;
&lt;li&gt;Does [&lt;em&gt;company&lt;/em&gt;] offer job-related training?&lt;/li&gt;
&lt;li&gt;What’s the tech stack / what tools are commonly used on the job?&lt;/li&gt;
&lt;li&gt;What projects are being worked on right now?&lt;/li&gt;
&lt;li&gt;Why is [&lt;em&gt;company&lt;/em&gt;] better than [&lt;em&gt;competitor&lt;/em&gt;]?&lt;/li&gt;
&lt;li&gt;What does [&lt;em&gt;company&lt;/em&gt;] do?&lt;/li&gt;
&lt;li&gt;Startups are sketchy, how do I know you’ll still be around in a year?&lt;/li&gt;
&lt;li&gt;Enterprise companies are bureaucratic, how do I know I’ll be able to do meaningful work?&lt;/li&gt;
&lt;li&gt;How big is the company? How many offices are there?&lt;/li&gt;
&lt;li&gt;What’s the breakdown of roles?&lt;/li&gt;
&lt;li&gt;What does the management structure look like?&lt;/li&gt;
&lt;li&gt;Are you profitable? What’s your revenue strategy?&lt;/li&gt;
&lt;li&gt;Who are the main investors in the company?&lt;/li&gt;
&lt;li&gt;Why should people care about your product?&lt;/li&gt;
&lt;li&gt;What are some challenges the company is currently facing?&lt;/li&gt;
&lt;li&gt;What do you like about working there?&lt;/li&gt;
&lt;li&gt;What’s your least favourite part about working there?&lt;/li&gt;
&lt;li&gt;Why did you choose to work there?&lt;/li&gt;
&lt;li&gt;What do you think the company will be like in 2/5/10 years?&lt;/li&gt;
&lt;li&gt;Can I give you some feedback on your company’s product?&lt;/li&gt;
&lt;li&gt;What are the opportunities for advancement?&lt;/li&gt;
&lt;li&gt;Can I connect with you on LinkedIn?&lt;/li&gt;
&lt;li&gt;What was the interview process like for you?&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>devrel</category>
      <category>hiring</category>
      <category>career</category>
      <category>management</category>
    </item>
    <item>
      <title>Using Twilio to build the internet</title>
      <dc:creator>Alexandra Sunderland</dc:creator>
      <pubDate>Mon, 29 Jul 2019 16:41:41 +0000</pubDate>
      <link>https://dev.to/twilio/using-twilio-to-build-the-internet-2cnf</link>
      <guid>https://dev.to/twilio/using-twilio-to-build-the-internet-2cnf</guid>
      <description>&lt;p&gt;If you’ve ever travelled internationally, you’ve probably asked yourself: “do I buy an expensive roaming data plan, do I jump from one free wifi hotspot to the next, or do I throw caution to the wind and go connection-free in an unfamiliar place?”. Going anywhere without real-time navigation isn’t an option if you’re as directionally-impaired as myself and get lost walking in a straight line. I always have to opt for the data plan which can set me back $80 for a measly 1GB. The lack of competition in the Canadian telecom industry is driving our data prices to be some of the highest in the world, and this large extra travel cost frustrated me to the point where I decided that I was going to do something about it.&lt;/p&gt;

&lt;p&gt;As any reasonable person would do, I decided to build a browser for my phone that would transfer all of the content over SMS, while preserving the look and feel of a real browser. Since my phone plan at the time included unlimited SMS, I’d be able to use this app to get unlimited internet anywhere! I figured that this would be slow and a little old-school, and so my new project &lt;strong&gt;“Dial-Up”&lt;/strong&gt; was born.&lt;/p&gt;

&lt;p&gt;When I think SMS and code, I think &lt;a href="https://www.twilio.com/" rel="noopener noreferrer"&gt;Twilio&lt;/a&gt;. A few years back, &lt;a href="http://fluidsurveys.com/blog/conduct-voice-sms-surveying-fluidsurveys-new-integration-twhello/" rel="noopener noreferrer"&gt;an integration was released&lt;/a&gt; that let you answer surveys over SMS/voice between Twilio and FluidSurveys, the startup that I was working at (later acquired by SurveyMonkey, and I’m now back with the founders and working on &lt;a href="https://fellow.app" rel="noopener noreferrer"&gt;Fellow.app&lt;/a&gt;). I thought that it was extremely cool, and so I was excited to finally get to use Twilio’s services for my own non-traditional use case!&lt;/p&gt;

&lt;p&gt;There are two components to build for this project:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Phone app:&lt;/strong&gt; unlimited SMS, will act as the browser.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Server:&lt;/strong&gt; unlimited internet, will do all the actual webpage loading on behalf of the phone.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When I was starting this project it was intended to be a helpful tool for just myself, so I built it for Android only, in Java (there were more answers on StackOverflow about SMS for Java than Kotlin!). I built the server side of the project in Node.js, because I thought that it would be hilarious to use JavaScript on the server (where it doesn't belong), to make a JavaScript-less browser (where it's supposed to be).&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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fnapjb6916xirgue00cd9.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/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fnapjb6916xirgue00cd9.png" alt="Image showing data flow from phone to Twilio to server to the internet"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The image above shows the flow of information between each service. Let’s dive in and follow the lifecycle of a request through the app:&lt;/p&gt;

&lt;h3&gt;
  
  
  🔗 Requesting a URL
&lt;/h3&gt;

&lt;p&gt;The first thing that we'll want to do in the app is request a URL to load. The image below shows the layout of the app's home page, which provides a text box to enter the URL and a "Go" button. When the "Go" button is pressed, a few things happen:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If the app hasn't been granted the required permissions, it will request &lt;code&gt;SEND_SMS&lt;/code&gt;, &lt;code&gt;READ_SMS&lt;/code&gt;, and &lt;code&gt;RECEIVE_SMS&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The URL will be shortened: &lt;code&gt;https://www.&lt;/code&gt; will be removed since it's a given that it should exist, and any query parameters will be taken away since this app won't allow for anything fancy like that.&lt;/li&gt;
&lt;li&gt;The resulting URL will be sent via Android's built-in SMS API to a phone number that we own on Twilio.&lt;/li&gt;
&lt;/ul&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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fqb3sk4cgif4fajic573g.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/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fqb3sk4cgif4fajic573g.png" alt="Image of the homepage of the app"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  ☎️ Setting up Twilio
&lt;/h3&gt;

&lt;p&gt;Next up, we'll need to set up the phone number we own on Twilio. I decided to use Twilio's &lt;a href="https://support.twilio.com/hc/en-us/articles/223136047-Configuring-Phone-Numbers-to-Receive-and-Respond-to-SMS-and-MMS-Messages#webhook" rel="noopener noreferrer"&gt;webhooks&lt;/a&gt; which let me specify a URL that all SMS sent to my number should be forwarded to. I set up my webhook like this: &lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fziqbmfdtpx3eta6up2o5.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/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fziqbmfdtpx3eta6up2o5.png" alt="Image of Twilio message forwarding settings"&gt;&lt;/a&gt;&lt;br&gt;
After saving this, sending a text message to the number I set up will send a &lt;code&gt;POST&lt;/code&gt; request with a &lt;code&gt;json&lt;/code&gt; payload to the specified URL containing all sorts of information about the message, such as the sender's phone number, the country it originates from, and when it was sent.&lt;/p&gt;
&lt;h3&gt;
  
  
  🌎 Getting the webpage and sending it over SMS
&lt;/h3&gt;

&lt;p&gt;At this point we've been able to specify a URL and send it via SMS through Twilio, which will have forwarded it to our server. Let the real fun begin! 🎉&lt;/p&gt;

&lt;p&gt;As a developer who tends to work on seemingly small components at a time in frameworks like React, it's easy to forget just how large the HTML that makes up our websites ends up being. If you &lt;code&gt;View page source&lt;/code&gt; on your favourite single-box single-button simple-looking search engine, you'll notice that the HTML holding it together is almost a quarter of a million characters long. With SMS having a limit of 160 characters, transmitting that directly over SMS would take more than 1,300 messages!&lt;/p&gt;

&lt;p&gt;That's not going to fly.&lt;/p&gt;

&lt;p&gt;Even with unlimited message sending capabilities, SMS doesn't have guaranteed delivery. We'd need to be able to figure out which messages weren't received by the phone and resend them, which would add a lot of overhead to the already long time that it would take to receive that many messages at once.&lt;/p&gt;

&lt;p&gt;My phone tends to start dropping messages as soon as it gets more than ~10 at a time, so I set a goal to get the 1,300 SMS down to 10, &lt;strong&gt;reducing the size by over 99%&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It was an ambitious goal, but those kinds of impossible targets and interesting problems are exactly what drew me to computer science in the first place. Hitting it would mean getting a lot more creative than just using &lt;a href="https://www.gnu.org/software/gzip/" rel="noopener noreferrer"&gt;Gzip&lt;/a&gt;, so I ditched all ideas around traditional compression and got to work.&lt;/p&gt;
&lt;h4&gt;
  
  
  Compression step 1: Goodbye JavaScript! 👋
&lt;/h4&gt;

&lt;p&gt;The browser that we're building isn't going to support JavaScript, CSS, images, or anything that you wouldn't find in a website out of the 90s (animated illustrations and visitor counters aside) because of the large overhead it would add for little benefit. The first thing that we'll do after getting the HTML for the requested website is remove everything that doesn't serve an explicit purpose for our browser.&lt;/p&gt;

&lt;p&gt;I used &lt;a href="https://www.npmjs.com/package/sanitize-html" rel="noopener noreferrer"&gt;sanitize-html&lt;/a&gt; for this step, which lets you specify tags and attributes that should be kept or removed from some HTML, as plain lists or as functions of their values. Here's part of the configuration that I used:&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;sanitizeHtml&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sanitize-html&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nf"&gt;sanitizeHtml&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;HTML&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;allowedTags&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="s1"&gt;a&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="s1"&gt;input&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="s1"&gt;form&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;allowedAttributes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;input&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="s1"&gt;value&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="s1"&gt;type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;a&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="s1"&gt;href&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;exclusiveFilter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;f&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;var&lt;/span&gt; &lt;span class="nx"&gt;att&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;attribs&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="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tag&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;input&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;att&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&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;hidden&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
      &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tag&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;a&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;att&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;att&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;href&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; 
      &lt;span class="nx"&gt;att&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;href&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;indexOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;policies&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="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;The configuration I set up allows for only text, &lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;input&amp;gt;&lt;/code&gt;, and &lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt; tags to be kept in the resulting HTML, and only &lt;code&gt;value&lt;/code&gt;, &lt;code&gt;type&lt;/code&gt;, &lt;code&gt;name&lt;/code&gt;, and &lt;code&gt;href&lt;/code&gt; attributes to stick around on those tags. I decided on this small list because I felt that in the usage I wanted to get out of this browser, those were the only ones that would provide tangible value and allow for interaction with a site. Since we're cutting out all the CSS by not allowing &lt;code&gt;&amp;lt;style&amp;gt;&lt;/code&gt; tags, there's no need to allow &lt;code&gt;class&lt;/code&gt; tags (the same goes for JavaScript and other related tags).&lt;/p&gt;

&lt;p&gt;&lt;code&gt;sanitize-html&lt;/code&gt; also allows removing elements based on a function of their tag and attribute values. Part of the &lt;code&gt;exclusiveFilter&lt;/code&gt; that I've defined above removed all hidden elements, links to nowhere, and links to privacy policies and terms &amp;amp; conditions: we're never going to click on them anyway, so why waste the space?&lt;/p&gt;

&lt;h4&gt;
  
  
  Compression step 2: Shortening common words 📏
&lt;/h4&gt;

&lt;p&gt;Once we've run the HTML through &lt;code&gt;sanitize-html&lt;/code&gt;, we're left with a lot of text and links. A lot of languages have some very common words that show up a lot in written text, like "the" or "and" in English. Since we know there's a set of words like this, we can compress them in a deterministic way: by replacing them with single letters (that aren't "a" or "I"). If text is compressed such that &lt;em&gt;the&lt;/em&gt;➜&lt;em&gt;t&lt;/em&gt;, &lt;em&gt;and&lt;/em&gt;➜&lt;em&gt;n&lt;/em&gt;, or &lt;em&gt;that&lt;/em&gt;➜&lt;em&gt;s&lt;/em&gt;, both compression and decompression for these words becomes a simple "find-and-replace-all" for each pair because we know that "s" is not a valid word.&lt;br&gt;
&lt;em&gt;That is the dinosaur and the best thing&lt;/em&gt; ➜ &lt;em&gt;S is t dinosaur n t best thing&lt;/em&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  Compression step 3: Thesaurus-rex 🦖
&lt;/h4&gt;

&lt;p&gt;In the spirit of continuing with the theme of building something totally ridiculous and unnecessary, the second way that I compressed text is by using a thesaurus API. There are a lot of words in English that are overly long and can be shortened while keeping the same approximate meaning, for example &lt;em&gt;penitentiary&lt;/em&gt; ➜ &lt;em&gt;jail&lt;/em&gt; like in the image below (that's a 12 character to 4 character compression!). By using a thesaurus API, we can find synonyms for long words and do a replacement. This method is absolutely a lossy compression (usually both in actual data and in meaning), but it works, and it's fun!&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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fefeo74gioz4sqbw5unnc.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/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fefeo74gioz4sqbw5unnc.png" alt="Example compression of penitentiary to jail"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  Compression step 4: A new approach to links 🔗
&lt;/h4&gt;

&lt;p&gt;It wasn't obvious at first because they're hiding when HTML is rendered, but the links in anchor tags were taking up the majority of the remaining space. Behind every 10 character blue word on a page is a 200 character-long URL, and that's a problem. It's a pain to preview links on a phone, so when I'm clicking on them I don't care what the link is as long as it brings me to where it's supposed to. Because of that behaviour, I decided that sending the true &lt;code&gt;href&lt;/code&gt; value of an &lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt; isn't important, and as long as clicking a link can bring me to where I want, I can save a &lt;em&gt;lot&lt;/em&gt; of space.&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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F5tb9n7mabn7u0md5hzmr.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/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F5tb9n7mabn7u0md5hzmr.png" alt="Example link compression"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;sanitize-html&lt;/code&gt; lets you define a function to modify attribute values, which is what I made use of to modify the links. When a link is encountered in the HTML, the phone number the website is for and the &lt;em&gt;real&lt;/em&gt; link URL are passed to the function below, which stores key/value pairs of &lt;code&gt;{phone_number}_{shortUrl}&lt;/code&gt;/&lt;code&gt;realUrl&lt;/code&gt; in &lt;a href="https://redis.io/" rel="noopener noreferrer"&gt;Redis&lt;/a&gt;, where the &lt;code&gt;shortUrl&lt;/code&gt; is a random 3 character string.&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;redis&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;redis&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;redisClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createClient&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="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;REDIS_URL&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;urlShortener&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;phoneNum&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;url&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;url&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;urlShort&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;36&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;substr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;redisClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;phoneNum&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;_&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;urlShort&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;urlShort&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="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;The final HTML will have all links replaced with short codes generated from the above code. When a link is clicked on from the app, that short code is sent to the server (over SMS) which knows from its format to look up the full value in Redis, and to retrieve the website from that real URL.&lt;/p&gt;

&lt;p&gt;For a website like Wikipedia which is almost entirely links, this adds a lot of value to the compression.&lt;/p&gt;

&lt;h4&gt;
  
  
  Compression step 5: HTML to gibberish Ω
&lt;/h4&gt;

&lt;p&gt;We've now compressed all of our text and removed as much HTML as we can from the page, so we're ready for the last step before sending the web page to the app!&lt;/p&gt;

&lt;p&gt;The SMS charset that we're using is called the &lt;a href="https://www.twilio.com/docs/glossary/what-is-gsm-7-character-encoding" rel="noopener noreferrer"&gt;GSM-7&lt;/a&gt;, and it includes all English letters, numbers, basic symbols... and greek letters! We've already used up all the single English letters in part 2 of the compression, but unless we're looking at websites about math or science, there are probably no greek letters in the HTML.&lt;/p&gt;

&lt;p&gt;We can compress the finite set of HTML keywords with these letters, in a similar "find-and-replace-all" method as before. The image below shows the colour mapping between an element and its matching symbols. We can save space by combining characters that we know will show up together, like &lt;code&gt;&amp;lt;&lt;/code&gt; with &lt;code&gt;input&lt;/code&gt; or &lt;code&gt;value&lt;/code&gt; with &lt;code&gt;=&lt;/code&gt; and &lt;code&gt;"&lt;/code&gt;. Because this mapping is explicit, it's easy to decompress by going in the opposite direction.&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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fhajlltxrncf82gok0bsc.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/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fhajlltxrncf82gok0bsc.png" alt="Example HTML compression"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Ready for liftoff 🚀
&lt;/h4&gt;

&lt;p&gt;The target that I had set for compression was to get a webpage down from 1,300+ SMS to 10, so how did I do?&lt;br&gt;
&lt;strong&gt;I got it down to 3 SMS.&lt;/strong&gt;&lt;br&gt;
And the best part? None of the code that I wrote was specific to this website, it's generic for any text-based page.&lt;/p&gt;

&lt;p&gt;Now that the website is all compressed, we need to send it from the server back to the phone. Twilio provides a great &lt;a href="https://www.twilio.com/docs/libraries/node" rel="noopener noreferrer"&gt;node helper library&lt;/a&gt; that does all the heavy lifting. This is all that's required to get the messages sent back to the phone:&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;twilioClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;twilio&lt;/span&gt;&lt;span class="dl"&gt;'&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="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TWILIO_SID&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="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TWILIO_AUTH_TOKEN&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Divide HTML into the max sized SMS - 5&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;smss&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;HTML&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/.&lt;/span&gt;&lt;span class="se"&gt;{155}&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Send out all the SMS via Twilio&lt;/span&gt;
&lt;span class="nx"&gt;smss&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;sms&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;twilioClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;smss&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;sms&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="na"&gt;from&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="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TWILIO_NUMBER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;From&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;h3&gt;
  
  
  📱 Reconstructing the website in the app
&lt;/h3&gt;

&lt;p&gt;On the Android side, a &lt;code&gt;BroadcastReceiver&lt;/code&gt; is set up to listen for incoming SMS from our Twilio number. Once all the SMS that make up a website are received, they're chained together and decompressed following the steps of the compression in reverse (skipping over the Thesaurus-Rex 🦖). The resulting HTML is passed to a &lt;a href="https://developer.android.com/reference/android/webkit/WebView" rel="noopener noreferrer"&gt;Webview&lt;/a&gt; component (a Chrome browser within an app, which accepts URLs or HTML), and our website is displayed!&lt;/p&gt;

&lt;p&gt;The end result for google.ca looks like the image below, which includes the compressed SMS text. This is what the website looked like 15 years ago, not too shabby for a free internet connection!&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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fienbtsathy29vw90986d.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/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fienbtsathy29vw90986d.png" alt="What google.ca looks like in the app"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And that’s how I cheat the system and get unlimited internet! This method works pretty much only for text-based websites and it can be slow (it &lt;em&gt;is&lt;/em&gt; named Dial-Up after all), but I know that I’d rather be able to load a search result in 10 seconds using this app for free than have to find a wifi hotspot every few minutes to make sure I’m still walking in the right direction.&lt;/p&gt;

&lt;p&gt;If you want to hear more about how I built this project and see it explained with the help of some Downasaurs, check out &lt;a href="https://www.youtube.com/watch?v=ZsBAkSxwU5c" rel="noopener noreferrer"&gt;my talk from JSConf EU 2019&lt;/a&gt;, take a look at the code on &lt;a href="https://alexandras.dev" rel="noopener noreferrer"&gt;my website&lt;/a&gt;, or send me a message &lt;a href="https://twitter.com/alexandras_dev" rel="noopener noreferrer"&gt;@alexandras_dev&lt;/a&gt;!&lt;/p&gt;

</description>
      <category>node</category>
      <category>javascript</category>
      <category>android</category>
      <category>twilio</category>
    </item>
  </channel>
</rss>
