<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xml:base="https://ericbidelman.com/" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Posts from Eric Bidelman</title>
    <link>https://ericbidelman.com/</link>
    <atom:link href="https://ericbidelman.com/rss.xml" rel="self" type="application/rss+xml" />
    <description>Code and ramblings by Eric Bidelman.</description>
    <language>en</language>
    <item>
      <title>Puppeteer is my new dev server</title>
      <link>https://ericbidelman.com/posts/2019/02/pptrstagingserver</link>
      <description>&lt;style&gt;
img.screenshot-table {
  width: 400px;
  max-width: 50vw;
}
.cutoff {
  position: relative;
}
.cutoff img {
  object-fit: cover;
  object-position: 0 0;
  height: 250px;
}
.cutoff::before {
  position: absolute;
  content: &#39;&#39;;
  height: 100%;
  width: 100%;
  left: 0;
  background: linear-gradient(transparent 75%, #fff 95%);
}
&lt;/style&gt;
&lt;h2 id=&quot;launching-a-website-without-a-dev-server&quot; tabindex=&quot;-1&quot;&gt;Launching a website without a dev server&lt;/h2&gt;
&lt;p&gt;Last year, my team launched &lt;a href=&quot;https://web.dev/&quot;&gt;web.dev&lt;/a&gt; at &lt;a href=&quot;https://developer.chrome.com/devsummit/&quot;&gt;Chrome Dev Summit 2018&lt;/a&gt;. If you haven&#39;t heard of web.dev, it&#39;s
a new educational resource for web developers that focuses on interactive
learning. For example, we embed Glitch codelabs so developers can
tinker with code as they read through documentation. We also integrated
tools like &lt;a href=&quot;https://developers.google.com/web/tools/lighthouse/&quot;&gt;Lighthouse&lt;/a&gt;
directly into the docs so developers can run + iterate on their site
performance...all without leaving our dev guides! &lt;img src=&quot;https://www.gstatic.com/devrel-devsite/prod/v80eb94e0352d656ad1e20abf6117cdec6c1343c7722ef10f52a1a3f77f1e58f7/web/images/lockup.svg&quot; title=&quot;web.dev logo&quot; alt=&quot;web.dev logo&quot; style=&quot;height:50px&quot; class=&quot;pull-right&quot;&gt;&lt;/p&gt;
&lt;p&gt;One of the challenges with working on web.dev is due to its architecture.
Essentially, half of the codebase (backend, core frontend, CSS/JS custom elements) is internal to Google. This critical code is internal so we can host, test, and build the site using Google&#39;s shared infrastructure for developer documentation, called &amp;quot;DevSite&amp;quot;. The non critical stuff (e.g. written content) was externalized on Github to foster content contributions from the community. However, this multi-repo sitch left us with a problem:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;We had no way to run web.dev locally.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;To illustrate the issue, fire up a Node server in a local checkout of web.dev&#39;s content repo (&lt;code&gt;git co https://github.com/GoogleChrome/web.dev&lt;/code&gt;). Styles are missing, template includes aren&#39;t processed, and entire sections are flat out missing. There&#39;s not much
to get excited about.&lt;/p&gt;
&lt;div class=&quot;layout horizontal around&quot;&gt;
  &lt;figure&gt;
    &lt;img src=&quot;https://ericbidelman.com/public/img/posts/2018/12/web.dev-home-unstyled.png&quot; class=&quot;screenshot&quot; style=&quot;max-height:300px&quot; alt=&quot;Locally serving a web.dev page&quot; title=&quot;Locally serving a web.dev page&quot;&gt;
    &lt;figcaption&gt;Homepage served locally.&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;figure&gt;
    &lt;img src=&quot;https://ericbidelman.com/public/img/posts/2018/12/wev.dev-styled.png&quot; class=&quot;screenshot&quot; style=&quot;max-height:300px&quot;&gt;
    &lt;figcaption&gt;Homepage as it renders on the site.&lt;/figcaption&gt;
  &lt;/figure&gt;
&lt;/div&gt;
&lt;p&gt;Our challenge was to fix this -- provide a tool for previewing content from
Github without having the full source of the page. How did we do that? We faked it!&lt;/p&gt;
&lt;h2 id=&quot;faking-a-dev-server&quot; tabindex=&quot;-1&quot;&gt;Faking a dev server&lt;/h2&gt;
&lt;p&gt;A typical development server is a web server that runs locally on your machine.
As you develop your site, it loads pages and tries to mimic production as much as possible.
You can review changes, try out new features before they&#39;re shipped...you know the drill.
For web.dev, we didn&#39;t have this luxury. But we needed &lt;em&gt;some&lt;/em&gt; tool for contributors to preview their stuff.&lt;/p&gt;
&lt;p&gt;Our solution was to build a &amp;quot;content preview server&amp;quot; using &lt;a href=&quot;https://developers.google.com/web/updates/2017/04/headless-chrome&quot;&gt;Headless Chrome&lt;/a&gt; and &lt;a href=&quot;https://developers.google.com/web/tools/puppeteer/&quot;&gt;Puppeteer&lt;/a&gt;. When an author wants to make a change on web.dev, we:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Fetch the live version of page from web.dev.&lt;/li&gt;
&lt;li&gt;Read the local checkout of the page from disk.&lt;/li&gt;
&lt;li&gt;Replace any template includes with the actual content.&lt;/li&gt;
&lt;li&gt;Replace the page body with the update content changes.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In other words, web.dev&#39;s preview server doesn&#39;t serve pages. It &lt;strong&gt;renders articles in the look and feel&lt;/strong&gt; of the site.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;web.dev&#39;s preview server doesn&#39;t serve pages. It &lt;strong&gt;renders articles in the look and feel&lt;/strong&gt; of the site.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That&#39;s go through each of these steps, one by one.&lt;/p&gt;
&lt;h3 id=&quot;step-1%3A-fetch-the-live-page-from-its-web.dev-url&quot; tabindex=&quot;-1&quot;&gt;Step 1: fetch the live page from its web.dev URL&lt;/h3&gt;
&lt;p&gt;First, we launch headless Chrome using Puppeteer using the lib&#39;s one-liner:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; puppeteer &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;puppeteer&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; browser &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; puppeteer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;launch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;headless&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With a &lt;code&gt;browser&lt;/code&gt; instance to interactive with, we load the page from its URL. We&#39;ll call this the &lt;strong&gt;remote page&lt;/strong&gt;. Loading the remote page gives us an important starting point for previewing a page: a fully rendered article. Since the production URL hosts the site CSS, built JS, and populated HTML templates, we don&#39;t need to maintain these resources ourselves.&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;/**
 * @param {string} url Remote page to load.
 * @return {!Page}
 */&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;fetchRemotePage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; page &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; browser&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;newPage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; resp &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; page&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;goto&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;waitUntil&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;domcontentloaded&#39;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// If author is creating a new page, it won&#39;t have an URL yet.&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// Instead, use a generic page (index.html) for rendering the content.&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;resp&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;status&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;404&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;fetchRemotePage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;https://web.dev/&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; page&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: if the user is creating a new page, it won&#39;t have a web.dev URL just yet.
To handle new pages, we fetch the homepage as the &amp;quot;base&amp;quot; template because it&#39;s generic and doesn&#39;t have special layout.&lt;/p&gt;
&lt;p&gt;As an example, if you were to run &lt;code&gt;fetchRemotePage(&#39;https://web.dev/learn&#39;)&lt;/code&gt;,
this is what you would see in full &amp;quot;headful&amp;quot; Chrome:&lt;/p&gt;
&lt;figure&gt;
  &lt;div class=&quot;cutoff&quot;&gt;
    &lt;img src=&quot;https://ericbidelman.com/public/img/posts/2018/12/learn-normal.png&quot; class=&quot;screenshot screenshot-table&quot;&gt;
  &lt;/div&gt;
&lt;/figure&gt;
&lt;h3 id=&quot;step-2%3A-read-the-local-version-of-the-page&quot; tabindex=&quot;-1&quot;&gt;Step 2: read the local version of the page&lt;/h3&gt;
&lt;p&gt;The second step is to get a copy of the author&#39;s local content that they want to preview.
Let&#39;s call this a &lt;strong&gt;local page&lt;/strong&gt;. A local page is an .html file that contains
a mixture of markup and plain markdown. We read this file from the same folder path
as its URL on the web.dev. For example, we want to preview &lt;code&gt;https://web.dev/foo/bar&lt;/code&gt;,
we read &lt;code&gt;./foo/bar/index.html&lt;/code&gt;.&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;/**
 * Read a local page from disk.
 * @param {string} path File path to read.
 * @return {!Promise&amp;lt;?string&gt;} Resolves with the HTML of the file, null if path
 *     doesn&#39;t exist or there was another error.
 */&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;readLocalPageContent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; filePath &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;./&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;path&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;.html&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// If the .html file doesn&#39;t exist, path may be describing a folder, in&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// which case we should try the index.html file in that folder.&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;fs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;existsSync&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;filePath&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      filePath &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;./&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;path&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;/index.html&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// Open a new tab. Create a new page by filling it with the file&#39;s HTML.&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; page &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; browser&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;newPage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; html &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; fs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;readFileSync&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;filePath&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;encoding&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;utf-8&#39;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; page&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setContent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;html&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// The written content appears in &amp;lt;body&gt;. Extract the stringified page&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// body and ignore other parts of the page.&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; body &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; page&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;evaluate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;document.body.innerHTML&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; page&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;close&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// done with the tab.&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; body&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;err&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;warn&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;err&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Running &lt;code&gt;readLocalPageContent()&lt;/code&gt; gets us one step closer to a preview, but it
returns the raw file content as-is. We&#39;re still missing the template includes:&lt;/p&gt;
&lt;figure&gt;
  &lt;div class=&quot;cutoff&quot;&gt;
    &lt;img src=&quot;https://ericbidelman.com/public/img/posts/2018/12/web.dev-home-unstyled.png&quot; class=&quot;screenshot screenshot-table&quot; alt=&quot;Local copy of page content&quot; title=&quot;Local copy of page content&quot;&gt;
  &lt;/div&gt;
&lt;/figure&gt;
&lt;h3 id=&quot;step-3%3A-populate-template-includes&quot; tabindex=&quot;-1&quot;&gt;Step 3: populate template includes&lt;/h3&gt;
&lt;p&gt;As seen in the previous screenshot, pages can contain
includes that get put together by web.dev&#39;s server:&lt;/p&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;{% include &quot;_root-cards.html&quot; %}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To render these locally, we created a simple regex to scan local page
files for include pragmas and replace them with the referenced file
content.&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;/**
 * Replaces page&#39;s {% include &quot;file.html&quot; %} pragmas with content of the file.
 * @param {?string} html
 * @return {?string}
 */&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;replaceIncludes&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;html&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;html&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; re &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token regex&quot;&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token regex-source language-regex&quot;&gt;{% include &quot;(.*)&#92;.html&quot; %}&lt;/span&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token regex-flags&quot;&gt;g&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; html&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;replace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;re&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;match&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; filename&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; offset&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; file &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;./build/&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;filename&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;.html&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; fs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;readFileSync&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;file&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;encoding&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;utf-8&#39;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Running this function replaces a pragma like &lt;code&gt;{% include &amp;quot;_root-cards.html&amp;quot; %}&lt;/code&gt; with
the content in &lt;code&gt;_root-cards.html&lt;/code&gt;. Visually, that looks like this:&lt;/p&gt;
&lt;figure&gt;
  &lt;div class=&quot;cutoff&quot;&gt;
    &lt;img src=&quot;https://ericbidelman.com/public/img/posts/2018/12/web.dev-local.png&quot; class=&quot;screenshot screenshot-table&quot;&gt;
  &lt;/div&gt;
&lt;/figure&gt;
&lt;h3 id=&quot;step-4%3A-swap-in-local-changes&quot; tabindex=&quot;-1&quot;&gt;Step 4: Swap-in local changes&lt;/h3&gt;
&lt;p&gt;At this point we have two things:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;A local page with updated HTML but lacks all of the site&#39;s layout/styling.&lt;/li&gt;
&lt;li&gt;A remote version of the page that contains the site&#39;s layout/styling but contains outdated HTML.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Merging these two will produce the result we need! Using Puppeteer, we can
manipulate the remote page&#39;s DOM and replace the outdated content with the new stuff.
The method for that is &lt;code&gt;page.evaluate()&lt;/code&gt;, which allows you to run code in the
context of a page:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Replace remote page body area with new content.&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; page&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;evaluate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;html&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; mainSection &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;querySelector&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;.devsite-article-body&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  mainSection&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;innerHTML &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; html&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; localPageHTML&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: The second argument to &lt;code&gt;page.evaluate()&lt;/code&gt; is how you pass a variable from
Node into the page. In this case, the variable is the HTML string from &lt;a href=&quot;https://ericbidelman.com/posts/2019/02/pptrstagingserver#step-3-populate-template-includes&quot;&gt;Step 3&lt;/a&gt;.&lt;/p&gt;
&lt;figure&gt;
  &lt;div class=&quot;cutoff&quot;&gt;
    &lt;img src=&quot;https://ericbidelman.com/public/img/posts/2018/12/web.dev-learn-cutout.png&quot; class=&quot;screenshot screenshot-table&quot;&gt;
  &lt;/div&gt;
  &lt;figcaption&gt;Section of the live page that gets replaced&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id=&quot;putting-it-all-together&quot; tabindex=&quot;-1&quot;&gt;Putting it all together&lt;/h3&gt;
&lt;p&gt;With the methods we&#39;ve created in steps 1-4, we have all the pieces necessary
to define a magical &lt;code&gt;constructPage()&lt;/code&gt; that returns the final HTML for the
preview server to render.&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; browser&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// Reuse browser instance throughout lifetime of preview server.&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;/**
 * Constructs a page by fetching it from web.dev, then merging it with the
 * the local version on disk.
 * @param {string} path File path to render.
 * @return {!Promise&amp;lt;?string&gt;} Serialized page output as an html string or null
 *     if the local page file does not exist.
 */&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;constructPage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  path &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; path&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;slice&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// strip leading &#39;/&#39;.&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; url &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;https://web.dev/&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;path&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// Launch new instance of browser to reuse it across renders.&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;browser&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    browser &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; puppeteer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;launch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;headless&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// Read local version of the page. Replace {% include %} with their content.&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; localPageHTML &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;replaceIncludes&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;readLocalPageContent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;path&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;localPageHTML&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; page &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;fetchRemotePage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// Replace main body of the remote page with the local changes.&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; page&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;evaluate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;localPageHTML&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; mainContentArea &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;querySelector&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;.devsite-article-body&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    mainContentArea&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;innerHTML &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; localPageHTML&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; localPageHTML&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; finalHTML &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; page&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// serialized HTML of assembled page.&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; page&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;close&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; finalHTML&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;server-code&quot; tabindex=&quot;-1&quot;&gt;Server code&lt;/h4&gt;
&lt;p&gt;The actual preview server for web.dev is tiny. It&#39;s an &lt;a href=&quot;https://github.com/GoogleChrome/web.dev/tree/master/server&quot;&gt;Express&lt;/a&gt; app
that uses &lt;a href=&quot;https://developers.google.com/web/tools/puppeteer/&quot;&gt;Puppeteer&lt;/a&gt; and headless Chrome to render previews. The
&lt;code&gt;constructPage()&lt;/code&gt; method from above does all of the heavy lifting and returns
the constructed page to users.&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; express &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;express&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; app &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;express&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

app&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;use&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;/&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;req&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; res&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; next&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// URLs on web.dev have no extension (/learn instead of /learn.html).&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// If there is an extension in the URL, drop to next route and try to&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// handle the request as a static file.&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; path &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; req&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;path&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;path &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; path&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;split&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;.&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;next&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; html &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;constructPage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; path &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// covered below.&lt;/span&gt;
  res&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;status&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;200&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;send&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;html&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// Catch all other requests as a static file.&lt;/span&gt;
app&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;use&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;express&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;static&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;build&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;extensions&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;html&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;htm&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;PORT&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; process&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;env&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;PORT&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;8080&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
app&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;listen&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;PORT&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;Started server on &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;PORT&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;. Press Ctrl+C to quit.&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;conclusion&quot; tabindex=&quot;-1&quot;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Puppeteer is a powerful tool for manipulating web pages in Node. For web.dev,
we used it to mimic a dev server without having all the required files to
render a full page. The process was simple: pull a page from its production URL,
inject the local edits, and serve the result to authors. What I liked about this approach is that we didn&#39;t need traditional tools or build pipelines to preview web.dev content. A headless browser and DOM APIs were our build tools!&lt;/p&gt;
&lt;p&gt;If you want to explore more in this space check out my article, &amp;quot;&lt;a href=&quot;https://developers.google.com/web/tools/puppeteer/articles/ssr&quot;&gt;Headless Chrome: an answer to server-side rendering JS sites&lt;/a&gt;&amp;quot;.&lt;/p&gt;
&lt;p&gt;p.s. Happy Valentine&#39;s Day! ❤️&lt;/p&gt;
</description>
      <pubDate>Wed, 13 Feb 2019 at 16:00:00</pubDate>
      <dc:creator>Eric Bidelman</dc:creator>
      <guid>https://ericbidelman.com/posts/2019/02/pptrstagingserver</guid>
    </item>
    <item>
      <title>Using http/2 for App Engine Local Development</title>
      <link>https://ericbidelman.com/posts/2016/09/using-http2-for-app-engine-local-development</link>
      <description>&lt;p&gt;I&amp;rsquo;ve been using &lt;a href=&quot;https://cloud.google.com/appengine/docs/about-the-standard-environment&quot; target=&quot;_blank&quot;&gt;App Engine&lt;/a&gt; for many years to develop web apps at Google. Most of the open source projects I&amp;rsquo;ve worked on use it for its simplicity and scalability. A couple of examples are the &lt;a href=&quot;https://events.google.com/io2016/&quot; target=&quot;_blank&quot;&gt;Google I/O web app&lt;/a&gt;, the Chrome team&amp;rsquo;s &lt;a href=&quot;https://www.chromestatus.com/features&quot; target=&quot;_blank&quot;&gt;Chromestatus.com&lt;/a&gt;, &lt;a href=&quot;https://www.polymer-project.org/1.0/&quot; target=&quot;_blank&quot;&gt;Polymer&amp;rsquo;s site&lt;/a&gt;, &lt;a href=&quot;https://developers.chrome.com/home&quot; target=&quot;_blank&quot;&gt;developers.chrome.com&lt;/a&gt;, and &lt;a href=&quot;https://developers.google.com/web/fundamentals/?hl=en&quot; target=&quot;_blank&quot;&gt;WebFundamentals&lt;/a&gt;, just to name a few. Heck, I&amp;rsquo;m even using it to build my wedding website!&lt;/p&gt;
&lt;h2&gt;http/2 brings development and production closer together&lt;/h2&gt;
&lt;p&gt;I could say a ton of nice things about App Engine, but one of its huge drawbacks is that the local development environment is far from Google&amp;rsquo;s production environment (where your app actually runs). One of the most important differences is that &lt;strong&gt;App Engine&amp;rsquo;s development server uses http/1.1 and Google&amp;rsquo;s infrastructure uses http/2 (h2)&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Besides being 16 years in the making, h2 offers many performance improvements over it&amp;rsquo;s 1.1 predecessor: multiplexing, header compression, server push). In a nutshell, this is difference between the two:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://64.media.tumblr.com/9d13dc545a79af5a44e63e10d8e0fc07/tumblr_odi8wqdOvF1r0c89zo1_1280.jpg&quot; alt=&quot;http/1.1 vs. http/2&quot;&gt;&lt;/p&gt;
&lt;p&gt;One of things I&amp;rsquo;m most excited about is that &lt;strong&gt;h2 makes development closer to production&lt;/strong&gt;. By that I mean the &lt;strong&gt;shape of a web app doesn&amp;rsquo;t change when we hit the deploy button&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;re like me, you probably develop source code in small, individual files. That&amp;rsquo;s good for organization, maintainability, and our general sanity :) But for production, we create an entirely different story. We roll sprite sheets, domain shard, concatenate CSS, and bundle massive amounts of JS together to squeeze out every last drop of performance. With h2, all of those techniques become a thing of the past; and in fact, anti-patterns.&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;The h2 protocol makes it more efficient to serve many small files rather than a few large ones.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Small, individual files &lt;em&gt;can&lt;/em&gt;  lead to improved performance through better HTTP caching. For example, a one-line change won&amp;rsquo;t invalidate 300KB of bundled JavaScript. Instead, a single file gets evicted from the browser&amp;rsquo;s cache and the rest of your code is left alone.&lt;/p&gt;
&lt;p&gt;So&amp;hellip;http/2 is pretty great. All major browsers support it and large cloud/CDN providers have finally started to bake it in. The place that&amp;rsquo;s still lacking is our development setups (remember my peeve about keeping dev ~= prod).&lt;/p&gt;
&lt;p&gt;Since I use App Engine all the time, I wanted a way to close the gap between its prod and dev environment and utilize http/2 on App Engine&amp;rsquo;s dev server. Turns out, that&amp;rsquo;s not too hard to do.&lt;/p&gt;
&lt;h2&gt;Enabling h2 with the App Engine dev server&lt;/h2&gt;
&lt;p&gt;It&amp;rsquo;s hard to performance tune an app when the HTTP protocol it uses locally is different than that of production. We want both environments to be as close as possible to each other.&lt;/p&gt;
&lt;p&gt;To get &lt;code&gt;dev_appserver.py&lt;/code&gt; serving resources over h2, I setup a reverse proxy using a server that supports h2 out of the box. I recommend &lt;a href=&quot;https://nginx.org/en/&quot; target=&quot;_blank&quot;&gt;nginx&lt;/a&gt; because it&amp;rsquo;s fast, easy to setup, and easy to configure. The second thing we&amp;rsquo;ll need to do is setup &lt;code&gt;localhost&lt;/code&gt; to serve off &lt;code&gt;https&lt;/code&gt;. That sounds scary, but it&amp;rsquo;s fairly straightfoward&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;SSL is not a requirement of the h2 protocol but &lt;strong&gt;all browsers have mandated it for http/2 to work&lt;/strong&gt; and many &lt;a href=&quot;https://www.chromium.org/Home/chromium-security/deprecating-powerful-features-on-insecure-origins&quot; target=&quot;_blank&quot;&gt;new&lt;/a&gt; (service worker) and &lt;a href=&quot;https://groups.google.com/a/chromium.org/forum/#!msg/blink-dev/2LXKVWYkOus/gT-ZamfwAKsJ&quot; target=&quot;_blank&quot;&gt;old&lt;/a&gt; (&lt;code&gt;getUserMedia&lt;/code&gt;, geo location) web platorm APIs are requiring it.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;Setting up nginx as reverse proxy to App Engine&lt;/h3&gt;
&lt;p&gt;First, I installed nginx using &lt;a href=&quot;http://brew.sh/&quot; target=&quot;_blank&quot;&gt;Homebrew&lt;/a&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;brew install --with-http2 nginx
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Nginx is supposed to support h2 out of the box &lt;a href=&quot;https://www.nginx.com/blog/nginx-1-9-5/&quot; target=&quot;_blank&quot;&gt;since v1.9.5&lt;/a&gt;, but I had to install it using &lt;code&gt;--with-http2&lt;/code&gt; to get the goodies.&lt;/p&gt;
&lt;p&gt;Homebrew installs nginx to &lt;code&gt;~/homebrew/etc/nginx&lt;/code&gt; and will server static assets from &lt;code&gt;~/homebrew/var/www/&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The Nginx install also creates a &lt;code&gt;~/homebrew/etc/nginx/servers&lt;/code&gt; directory where you can stick custom server configurations.&lt;/p&gt;
&lt;p&gt;To add a server, create &lt;code&gt;~/homebrew/etc/nginx/servers/appengine.conf&lt;/code&gt; with:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;server {
    listen          3000;
    server_name     localhost;

    # If nginx cant find file, forward request to GAE dev server.
    location / {
        try_files   $uri   @gae_server;
    }

    location @gae_server {
        proxy_pass   http://localhost:8080;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What this does is forward any requests that nginx can&amp;rsquo;t find (right now that&amp;rsquo;s all of them) to your GAE app running on &lt;code&gt;8080&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Next, fire up your GAE app on &lt;code&gt;8080&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cd your_gae_app;
dev_appserver.py . --port 8080
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and start nginx:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;nginx
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you need to stop the server, run:&lt;/p&gt;
&lt;p&gt;nginx -s stop&lt;/p&gt;
&lt;p&gt;At this point, you should be able to open &lt;code&gt;http://localhost:3000/&lt;/code&gt; and see your GAE app! Requests are still over http/1.1 because we haven&amp;rsquo;t setup SSL yet.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://78.media.tumblr.com/bee25237e7382944188670ba89282e4b/tumblr_odi8wqdOvF1r0c89zo2_1280.png&quot; alt=&quot;Still on http 1.1&quot;&gt;&lt;/p&gt;
&lt;h2&gt;Enabling SSL for localhost (ngnix)&lt;/h2&gt;
&lt;p&gt;First, generate a self-signing certificate in &lt;code&gt;~/homebrew/etc/nginx/&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo openssl req -x509 -sha256 -newkey rsa:2048 &#92;
    -keyout cert.key -out cert.pem &#92;
    -days 1024 -nodes -subj &#39;/CN=localhost&#39;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will create a private key (&lt;code&gt;cert.key&lt;/code&gt;) and a certificate (&lt;code&gt;cert.pem&lt;/code&gt;) for the domain &lt;code&gt;localhost&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Next, modify &lt;code&gt;appengine.conf&lt;/code&gt; like so:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;server {
    listen          443 ssl http2;
    server_name     localhost;

    ssl                        on;
    ssl_protocols              TLSv1 TLSv1.1 TLSv1.2;
    ssl_certificate            cert.pem; # or /path/to/cert.pem
    ssl_certificate_key        cert.key; # or /path/to/cert.key

    location / {
        try_files   $uri   @gae_server;
    }

    location @gae_server {
        proxy_pass   http://localhost:8080;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The first couple of lines enable the &lt;code&gt;ssl&lt;/code&gt; and &lt;code&gt;http2&lt;/code&gt; modules on &lt;code&gt;localhost:443&lt;/code&gt;. The next few instruct the server to read your private key and certificate (the ones you just generated). The rest of the file remains the same as before.&lt;/p&gt;
&lt;p&gt;The OS will throw permission errors for opening ports under 1024, so you&amp;rsquo;ll need to run &lt;code&gt;nginx&lt;/code&gt; using &lt;code&gt;sudo&lt;/code&gt; this time. The following command worked for me but you might be able to get away with just &lt;code&gt;sudo nginx&lt;/code&gt;:&lt;/p&gt;
&lt;p&gt;Start nginx using &lt;code&gt;sudo&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo ~/homebrew/bin/nginx
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Open &lt;code&gt;https://localhost/&lt;/code&gt; (note the &amp;ldquo;https&amp;rdquo;) and you&amp;rsquo;ll get a big ol&amp;rsquo; security warning from the browser:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://78.media.tumblr.com/735c5d0fa41542c889b885af6e5a78e3/tumblr_odi8wqdOvF1r0c89zo7_r1_1280.png&quot; alt=&quot;localhost SSL cert warning&quot;&gt;&lt;/p&gt;
&lt;p&gt;Don&amp;rsquo;t worry! We know that we&amp;rsquo;re legit. Click &amp;ldquo;ADVANCED&amp;rdquo;, and then &amp;ldquo;Proceed to localhost (unsafe)&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: if you really want the green lock, check out the instructions &lt;a href=&quot;https://tech.finn.no/2015/09/25/setup-nginx-with-http2-for-local-development/&quot; target=&quot;_blank&quot;&gt;here&lt;/a&gt; to add the self signed certificate as a trusted certificate in MacOS System Keychain.&lt;/p&gt;
&lt;p&gt;Hitting refresh again on &lt;code&gt;https://localhost/&lt;/code&gt; should give you responses over h2:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://78.media.tumblr.com/0243cbedb2bdcceec6f258184804639d/tumblr_odi8wqdOvF1r0c89zo3_1280.png&quot; alt=&quot;localhost over SSL&quot;&gt;&lt;/p&gt;
&lt;p&gt;Take this in. &lt;strong&gt;Your local GAE app is running over SSL and using http/2&lt;/strong&gt; to serve requests!&lt;/p&gt;
&lt;h2&gt;What about h2 server push?&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Tip&lt;/strong&gt; see my drop-in &lt;a href=&quot;https://github.com/GoogleChrome/http2push-gae&quot; target=&quot;_blank&quot;&gt;http2push-gae&lt;/a&gt; library for doing h2 push on Google App Engine.&lt;/p&gt;
&lt;p&gt;At the time of writing, Nginx doesn&amp;rsquo;t support h2 server push, but that doesn&amp;rsquo;t mean we can&amp;rsquo;t test with it locally!&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://h2o.examp1e.net/&quot; target=&quot;_blank&quot;&gt;h2o&lt;/a&gt; is another modern h2 server that&amp;rsquo;s even easier to &lt;a href=&quot;https://h2o.examp1e.net/configure/http2_directives.html&quot; target=&quot;_blank&quot;&gt;configure&lt;/a&gt;, comes with an up-to-date h2 implementation, and supports server push out of the box.&lt;/p&gt;
&lt;p&gt;First, install h2o using Homebrew:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;brew install h2o
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;By default, &lt;code&gt;h2o&lt;/code&gt; installs to  &lt;code&gt;~/homebrew/bin/h2o&lt;/code&gt; and will serve static files from &lt;code&gt;~/homebrew/var/h2o/&lt;/code&gt;. You can change where files are served by editing &lt;code&gt;~/homebrew/etc/h2o/h2o.conf&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Start a web server and verify that you see the default index.html page on &lt;code&gt;http://localhost:8080/&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;h2o -c ~/homebrew/etc/h2o/h2o.conf
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next, create a new config, &lt;code&gt;~/homebrew/etc/h2o/appengine.conf&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;hosts:
  &quot;localhost&quot;:
    listen:
      port: 3000
    paths:
      &quot;/&quot;:
        proxy.reverse.url: http://localhost:8080/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the example, I&amp;rsquo;ve done the same thing as the ngnix setup. We&amp;rsquo;ve setup a server on port &lt;code&gt;3000&lt;/code&gt; that will forward all requests to App Engine running on port &lt;code&gt;8080&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;Enabling SSL for localhost (h2o)&lt;/h3&gt;
&lt;p&gt;First, copy over your cert and key from the ngnix steps above (or generate new ones):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cp ~/homebrew/etc/nginx/cert.key ~/homebrew/etc/h2o/
cp ~/homebrew/etc/nginx/cert.pem ~/homebrew/etc/h2o/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Modify &lt;code&gt;~/homebrew/etc/h2o/appengine.conf&lt;/code&gt; to include an entry for &lt;code&gt;localhost:443&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;hosts:
  &quot;localhost:443&quot;:
    listen:
      port: 443
      ssl:
        certificate-file: cert.pem
        key-file:         cert.key
    paths:
      &quot;/&quot;:
        proxy.reverse.url: http://localhost:8080/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Start the server using &lt;code&gt;sudo&lt;/code&gt; (again, because we&amp;rsquo;re opening a special port, &lt;code&gt;443&lt;/code&gt;):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo ~/homebrew/bin/h2o -c ~/homebrew/etc/h2o/appengine.conf
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Be sure you&amp;rsquo;ve started the GAE dev server (&lt;code&gt;dev_appserver.py . --port 8080&lt;/code&gt;), and open &lt;code&gt;https://localhost&lt;/code&gt; to see your running GAE app. Any resources that contain a &lt;code&gt;Link rel=preload&lt;/code&gt; header will be server pushed by h2o:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://78.media.tumblr.com/b2915ef3edb0fa91b2271560c2350a4b/tumblr_odi8wqdOvF1r0c89zo4_1280.png&quot; alt=&quot;h2 pushed resources&quot;&gt;&lt;/p&gt;
&lt;p&gt;If you want to determine if a resource is being pushed, look for the &lt;code&gt;x-http2-push: pushed&lt;/code&gt; header in the response. h2o will set that header on pushed resources. Alternatively, you can drill into Chrome&amp;rsquo;s &lt;code&gt;chrome://net-internals&lt;/code&gt; to &lt;a href=&quot;https://github.com/GoogleChrome/http2push-gae/blob/master/EXPLAINER.md#verify-resources-are-pushed&quot; target=&quot;_blank&quot;&gt;verify pushed resources&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://78.media.tumblr.com/62c7f560b8a55ba2794906d2287ea357/tumblr_odi8wqdOvF1r0c89zo5_250.png&quot; alt=&quot;&amp;lt;code&amp;gt;x-http2-push: pushed&amp;lt;/code&amp;gt; header&quot;&gt;&lt;/p&gt;
&lt;h2&gt;Maximize perf: speeding up static resources&lt;/h2&gt;
&lt;p&gt;If you want even more speed, you can have nginx or h2o serve your static files directly instead proxying them to the dev server. Both servers are much faster than &lt;code&gt;dev_appserver.py&lt;/code&gt; and will better mimic production App Engine.&lt;/p&gt;
&lt;h3&gt;Configuring Ngnix to server static resources&lt;/h3&gt;
&lt;p&gt;Add &lt;code&gt;root /path/to/gae_app/src;&lt;/code&gt; to your server config:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;server {
    listen          443 ssl http2;
    server_name     localhost;
    root            /path/to/gae_app/src; # add this

    ssl                        on;
    ssl_protocols              TLSv1 TLSv1.1 TLSv1.2;
    ssl_certificate            cert.pem;
    ssl_certificate_key        cert.key;

    location / {
        try_files   $uri   @gae_server;
    }

    location @gae_server {
        proxy_pass   http://localhost:8080;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If nginx can find the file within your &lt;code&gt;root&lt;/code&gt;, it will serve it directly rather than (needlessly) forwarding it to App Engine. All other requests will be proxied to App Engine as usual.&lt;/p&gt;
&lt;h3&gt;Configuring h2o to server static resources&lt;/h3&gt;
&lt;p&gt;Likewise, h2o can be instructed to serve your static files using &lt;code&gt;file.dir&lt;/code&gt;. Just specify a URL request -&amp;gt; /path/to/src mapping:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;hosts:
  &quot;localhost:443&quot;:
    listen:
      port: 443
      ssl:
        certificate-file: cert.pem
        key-file:         cert.key
    paths:
      &quot;/&quot;:
        proxy.reverse.url: http://localhost:8080/
      &quot;/static&quot;:
        file.dir: /path/to/gae_app/static # add this
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, all files under &lt;code&gt;https://localhost/static/*&lt;/code&gt; will be served by h2o instead of GAE.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Tip&lt;/strong&gt;: Check your dev server logs to confirm ngnix/h2o are handling the static files. If requests don&amp;rsquo;t show when you refresh the page, you&amp;rsquo;re good to go. If requests show up, check that you&amp;rsquo;re using the correct path for &lt;code&gt;root&lt;/code&gt; or &lt;code&gt;file.dir&lt;/code&gt;.&lt;/p&gt;
&lt;hr&gt;&lt;p&gt;And with that, Voila! We&amp;rsquo;ve got the App Engine development server running fully over http/2.&lt;/p&gt;
&lt;p&gt;&lt;br&gt;&lt;br&gt;&lt;/p&gt;
&lt;h4&gt;Credits&lt;/h4&gt;
&lt;p&gt;These were invaluable resources when researching this post:&lt;/p&gt;
&lt;ul&gt;&lt;li&gt;&lt;a href=&quot;https://tech.finn.no/2015/09/25/setup-nginx-with-http2-for-local-development/&quot; target=&quot;_blank&quot;&gt;https://tech.finn.no/2015/09/25/setup-nginx-with-http2-for-local-development/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://desmondbrand.com/blog/2011/12/05/using-nginx-as-a-reverse-proxy-for-speedy-app-engine-development/&quot; target=&quot;_blank&quot;&gt;http://desmondbrand.com/blog/2011/12/05/using-nginx-as-a-reverse-proxy-for-speedy-app-engine-development/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
      <pubDate>Tue, 13 Sep 2016 at 17:00:00</pubDate>
      <dc:creator>Eric Bidelman</dc:creator>
      <guid>https://ericbidelman.com/posts/2016/09/using-http2-for-app-engine-local-development</guid>
    </item>
    <item>
      <title>Observing your web app</title>
      <link>https://ericbidelman.com/posts/2016/08/observing-your-web-app</link>
      <description>&lt;p&gt;TL;DR: a dozen+ examples of monitoring changes in a web application.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;The web has lots of APIs for knowing what’s going on in your app. You can monitor mucho stuff and observe just about any type of change.&lt;/p&gt;
&lt;p&gt;Changes range from simple things like DOM mutations and catching client-side errors to more complex notifications like knowing when the user’s battery is about to run out. The thing that remains constant are the ways to deal with them: callbacks, promises, events.&lt;/p&gt;
&lt;p&gt;Below are some of use cases that I came up with. By no means is the list exhaustive. They’re mostly examples for observing the structure of an app, its state, and the properties of the device it’s running on.&lt;/p&gt;
&lt;p&gt;Listen for &lt;strong&gt;DOM events&lt;/strong&gt; (both native and custom):&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;scroll&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token parameter&quot;&gt;e&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// user scrolls the page.&lt;/span&gt;

el&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;focus&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token parameter&quot;&gt;e&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// el is focused.&lt;/span&gt;
img&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;load&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token parameter&quot;&gt;e&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// img is done loading.&lt;/span&gt;
input&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;input&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token parameter&quot;&gt;e&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// user types into input.&lt;/span&gt;

el&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;custom-event&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token parameter&quot;&gt;e&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// catch custom event fired on el.&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Listen for &lt;strong&gt;modifications to the DOM&lt;/strong&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; observer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MutationObserver&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;mutations&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
observer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;observe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;body&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;childList&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;subtree&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;attributes&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;characterData&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Know when the &lt;strong&gt;URL&lt;/strong&gt; changes:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function-variable function&quot;&gt;onhashchange&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;location&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;hash&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function-variable function&quot;&gt;onpopstate&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;location&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;state&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Know when the &lt;strong&gt;app is being viewed fullscreen&lt;/strong&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;fullscreenchange&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;fullscreenElement&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Fullscreen_API&quot; target=&quot;_blank&quot;&gt;Read more&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Know when someone is sending you a &lt;strong&gt;message&lt;/strong&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Cross-domain / window /worker.&lt;/span&gt;
window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function-variable function&quot;&gt;onmessage&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token parameter&quot;&gt;e&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// WebRTC.&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; dc &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RTCPeerConnection&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createDataChannel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
dc&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function-variable function&quot;&gt;onmessage&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token parameter&quot;&gt;e&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Know about &lt;strong&gt;client-side errors&lt;/strong&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Client-size error?&lt;/span&gt;
window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function-variable function&quot;&gt;onerror&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;msg&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; src&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; lineno&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; colno&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// Unhandled rejected Promise?&lt;/span&gt;
window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function-variable function&quot;&gt;onunhandledrejection&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token parameter&quot;&gt;e&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;reason&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/GlobalEventHandlers/onerror&quot; target=&quot;_blank&quot;&gt;Read more&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Listen for changes to &lt;strong&gt;responsiveness&lt;/strong&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; media &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;matchMedia&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;(orientation: portrait)&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
media&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;mql&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;mql&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;matches&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// Orientation of device changes.&lt;/span&gt;
window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;orientationchange&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;screen&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;orientation&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;angle&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/Events/orientationchange&quot; target=&quot;_blank&quot;&gt;Read more&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Listen for changes to &lt;strong&gt;network connectivity&lt;/strong&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Online/offline events.&lt;/span&gt;
window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;online&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;assert&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;onLine&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;offline&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;assert&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;onLine&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// Network Information API&lt;/span&gt;
navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;connection&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;change&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;connection&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;type&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;connection&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;downlinkMax&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Network_Information_API&quot; target=&quot;_blank&quot;&gt;Read more&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Listen for changes to the device &lt;strong&gt;battery&lt;/strong&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getBattery&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;battery&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  battery&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;chargingchange&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;battery&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;charging&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  battery&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;levelchange&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;battery&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;level&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  battery&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;chargingtimechange&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;battery&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;chargingTime&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  battery&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;dischargingtimechange&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;battery&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;dischargingTime&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Battery_Status_API&quot; target=&quot;_blank&quot;&gt;Read more&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Know when the &lt;strong&gt;tab/page is visible&lt;/strong&gt; or in focus:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;visibilitychange&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;hidden&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Page_Visibility_API&quot; target=&quot;_blank&quot;&gt;Read more&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Know when the &lt;strong&gt;user’s position&lt;/strong&gt; changes:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;geolocation&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;watchPosition&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;pos&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;pos&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;coords&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Know when the &lt;strong&gt;permission of an API&lt;/strong&gt; changes:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; q &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;permissions&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;query&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;geolocation&#39;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
q&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;permission&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  permission&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;change&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;target&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;state&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Permissions_API&quot; target=&quot;_blank&quot;&gt;Read more&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Know when another &lt;strong&gt;tab updates &lt;code&gt;localStorage&lt;/code&gt;/&lt;code&gt;sessionStorage&lt;/code&gt;&lt;/strong&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;storage&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;alert&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Know when an &lt;strong&gt;element enters/leaves the viewport&lt;/strong&gt; (e.g. “Is this element visible?”):&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; observer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;IntersectionObserver&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;changes&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token literal-property property&quot;&gt;threshold&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0.25&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
observer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;observe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;querySelector&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;#watchMe&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API&quot; target=&quot;_blank&quot;&gt;Read more&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Know when an &lt;strong&gt;element changes size&lt;/strong&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; observer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ResizeObserver&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;entries&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; entry &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; entries&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; cr &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; entry&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;contentRect&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;Element:&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; entry&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;target&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;Element size: &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;cr&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;width&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;px x &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;cr&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;height&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;px&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;Element padding: &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;cr&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;top&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;px ; &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;cr&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;left&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;px&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
observer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;observe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;querySelector&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;#watchMe&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;a href=&quot;https://developers.google.com/web/updates/2016/10/resizeobserver&quot; target=&quot;_blank&quot;&gt;Read more&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Know when the &lt;strong&gt;browser is idle&lt;/strong&gt; (to perform extra work):&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token function&quot;&gt;requestIdleCallback&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;deadline&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token literal-property property&quot;&gt;timeout&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;a href=&quot;https://developers.google.com/web/updates/2015/08/using-requestidlecallback?hl=en&quot; target=&quot;_blank&quot;&gt;Read more&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Know when the browser fetches a resource, or a &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/User_Timing_API&quot; target=&quot;_blank&quot;&gt;User Timing&lt;/a&gt; event is recorded/measured:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; observer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PerformanceObserver&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;list&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getEntries&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
observer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;observe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;entryTypes&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;resource&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;mark&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;measure&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;a href=&quot;https://developers.google.com/web/updates/2016/06/performance-observer?hl=en&quot; target=&quot;_blank&quot;&gt;Read more&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Know when &lt;strong&gt;properties of an object&lt;/strong&gt; change (including DOM properties):&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Observe changes to a DOM node&#39;s .textContent.&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// From &amp;lt;a href=&quot;https://gist.github.com/ebidel/d923001dd7244dbd3fe0d5116050d227&quot; target=&quot;_blank&quot;&gt;https://gist.github.com/ebidel/d923001dd7244dbd3fe0d5116050d227&amp;lt;/a&gt;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; proxy &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Proxy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;querySelector&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;#target&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;target&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; propKey&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; value&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; receiver&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;propKey &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;textContent&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;textContent changed to: &#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    target&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;propKey&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; value&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
proxy&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;textContent &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Updated content!&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy&quot; target=&quot;_blank&quot;&gt;Read more&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;If you’re building &lt;strong&gt;&lt;a href=&quot;https://developers.google.com/web/fundamentals/primers/customelements&quot; target=&quot;_blank&quot;&gt;custom elements&lt;/a&gt;&lt;/strong&gt; (web components), there are several methods, called reactions, that you can define to observe important things in the element’s lifecycle:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;AppDrawer&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HTMLElement&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;constructor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// always need to call super() first in the ctor.&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// Instance of the element is instantiated.&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;connectedCallback&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// Called every time the element is inserted into the DOM.&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;disconnectedCallback&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// Called every time the element is removed from the DOM.&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;attributeChangedCallback&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;attrName&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; oldVal&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; newVal&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// An attribute was added, removed, updated, or replaced.&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;adoptedCallback&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// Called when the element is moved into a new document.&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;customElements&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;define&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;app-drawer&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; AppDrawer&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;a href=&quot;https://developers.google.com/web/fundamentals/primers/customelements/?hl=en#reactions&quot; target=&quot;_blank&quot;&gt;Read more&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Know when a CSP policy has been violated:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;securitypolicyviolation&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token string&quot;&gt;&#39;CSP error:&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;Violated &#39;&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;violatedDirective&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39; directive with policy &#39;&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;originalPolicy&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/SecurityPolicyViolationEvent&quot; target=&quot;_blank&quot;&gt;Read more&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Know when your sites uses a deprecated API, hits a browser intervention ,or feature policy violation:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; observer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ReportingObserver&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;reports&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; observer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    reports&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;report&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;report&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// ... send report to backend ...&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;buffered&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

observer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;observe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;a href=&quot;https://www.chromestatus.com/feature/4691191559880704&quot; target=&quot;_blank&quot;&gt;Read more&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Wowza! What’s crazy is that there are even &lt;a href=&quot;https://www.chromestatus.com/features/5705346022637568&quot; target=&quot;_blank&quot;&gt;more&lt;/a&gt; &lt;a href=&quot;https://www.chromestatus.com/features/5558926443544576&quot; target=&quot;_blank&quot;&gt;APIs&lt;/a&gt; in &lt;a href=&quot;https://www.chromestatus.com/features/5768542523752448&quot; target=&quot;_blank&quot;&gt;the&lt;/a&gt; &lt;a href=&quot;https://www.chromestatus.com/features/5662847321243648&quot; target=&quot;_blank&quot;&gt;works&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I suppose you could classify some of these examples as techniques or patterns (e.g. reacting to DOM events). However, many are completely new APIs designed for a specific purpose: measuring performance, knowing battery status, online/offline connectivity, etc.&lt;/p&gt;
&lt;p&gt;Honestly, it’s really impressive how much we have access to these days. There’s basically an API for everything.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Mistake? Something missing? Leave a comment.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Update 2016-08-17&lt;/strong&gt; - added custom element reaction example&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Update 2018-07-23&lt;/strong&gt; - added ResizeObserver&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Update 2018-07-25&lt;/strong&gt; - added CSP &lt;code&gt;securitypolicyviolation&lt;/code&gt; example&lt;/p&gt;
</description>
      <pubDate>Mon, 15 Aug 2016 at 17:00:00</pubDate>
      <dc:creator>Eric Bidelman</dc:creator>
      <guid>https://ericbidelman.com/posts/2016/08/observing-your-web-app</guid>
    </item>
    <item>
      <title>Blink. Chrome&#39;s new rendering engine</title>
      <link>https://ericbidelman.com/posts/2013/04/blink-chromes-new-rendering-engine</link>
      <description>&lt;p&gt;Chrome is departing WebKit as its rendering engine. This is &lt;a href=&quot;http://blog.chromium.org/2013/04/blink-rendering-engine-for-chromium.html&quot; target=&quot;_blank&quot;&gt;big news&lt;/a&gt; for web developers, so I thought I&amp;rsquo;d write up my personal take on the matter. Please realize these are my own thoughts and not those of Google.&lt;/p&gt;
&lt;p&gt;The new engine is called &lt;a href=&quot;http://www.chromium.org/blink&quot; target=&quot;_blank&quot;&gt;Blink&lt;/a&gt;. It&amp;rsquo;s open source and based WebKit.&lt;/p&gt;
&lt;h2&gt;&amp;ldquo;You&amp;rsquo;re kidding, right!?&amp;rdquo;&lt;/h2&gt;
&lt;p&gt;That was my reaction when I first heard the news. It was quickly followed by: &amp;ldquo;Won&amp;rsquo;t this segment the web even further?&amp;rdquo; and &amp;ldquo;Great. An additional rendering engine I have to test.&amp;rdquo; Being a web developer, I feel your pain.&lt;/p&gt;
&lt;p&gt;Honestly, I was extremely skeptical about the decision at first.  After several conversations with various members of the web platform team here at Google, I was slowly convinced it might not be such a terrible idea after all. In fact, I&amp;rsquo;m now convinced it&amp;rsquo;s a good idea for the long term health and innovation of browsers.&lt;/p&gt;
&lt;h2&gt;Reflecting on Chrome&amp;rsquo;s mission&lt;/h2&gt;
&lt;p&gt;Many of you will be in the same skeptic boat I was. But I think it&amp;rsquo;s worth remembering the continuing goals of the Chromium project.&lt;/p&gt;
&lt;p&gt;From day one the Chrome team&amp;rsquo;s mission has been to build the best browser possible. Speed, security, and simplicity are in its blood. Over the last four years, I have gained a deep respect for our engineering team. They&amp;rsquo;re some of the most brilliant engineers in the world. If their consensus is that &lt;strong&gt;Chrome cannot be the best browser it can be with WebKit at its core&lt;/strong&gt;, I fully trust and support that decision. After all, these folks know how to build browsers. If you think about it some more things start to make sense. The architecture of today&amp;rsquo;s web browser is dramatically different than it was back in 2001 (when WebKit was designed).&lt;/p&gt;
&lt;p&gt;The irony in all of this is that we were soon destined to have three render engines with &lt;a href=&quot;http://my.opera.com/ODIN/blog/300-million-users-and-move-to-webkit&quot; target=&quot;_blank&quot;&gt;Opera&amp;rsquo;s impending move to WebKit&lt;/a&gt;. Even today, Mozilla/Samsung &lt;a href=&quot;https://blog.mozilla.org/blog/2013/04/03/mozilla-and-samsung-collaborate-on-next-generation-web-browser-engine/&quot; target=&quot;_blank&quot;&gt;announced&lt;/a&gt; their work on a new engine, called Servo. So, we were at three engines. Now we have 4+. Interesting times indeed.&lt;/p&gt;
&lt;h2&gt;Things we can all look forward to&lt;/h2&gt;
&lt;p&gt;Ultimately, Chrome is engineering driven project and I&amp;rsquo;m personally excited about the potential this change offers. Here are a few:&lt;/p&gt;
&lt;h3&gt;Improved performance &amp;amp; security&lt;/h3&gt;
&lt;p&gt;Many &lt;a href=&quot;http://www.chromium.org/blink/developer-faq&quot; target=&quot;_blank&quot;&gt;ideas and proposals&lt;/a&gt; have sprung up about things like  &lt;a href=&quot;http://www.chromium.org/developers/design-documents/oop-iframes&quot; target=&quot;_blank&quot;&gt;out of process iframes&lt;/a&gt;, moving DOM to JS, multi-threaded layout, faster DOM bindings,&amp;hellip;. Big architectural changes and refactorings means Chrome gets smaller, more secure, and faster over time.&lt;/p&gt;
&lt;h3&gt;Increased transparency, accountability, responsibility&lt;/h3&gt;
&lt;p&gt;Every feature added to the web platform has a cost. Through efforts like &lt;a href=&quot;http://chromestatus.com&quot; target=&quot;_blank&quot;&gt;Chromium Feature Dashboard - chromestatus.com&lt;/a&gt;, developers will be in the full know about what features we&amp;rsquo;re adding. New APIs are going under a fine comb before being released. There&amp;rsquo;s an extensive &lt;a href=&quot;http://www.chromium.org/blink#new-features&quot; target=&quot;_blank&quot;&gt;process for adding new features&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;By the way, watch for chromestatus.com to get much more robust in the coming months. I&amp;rsquo;m personally helping with that project. Look forward to it&amp;rsquo;s v2 :)&lt;/p&gt;
&lt;h3&gt;No vendor prefixes&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;http://paulirish.com/2012/vendor-prefixes-are-not-developer-friendly/&quot; target=&quot;_blank&quot;&gt;What a debacle vendor prefixes have been&lt;/a&gt;! Features in Blink are going to be implemented unprefixed and kept behind the &amp;ldquo;Enable experimental web platform features&amp;rdquo; flag until they&amp;rsquo;re ready for prime time. This is a great thing for authors.&lt;/p&gt;
&lt;h3&gt;Testing Testing Testing&lt;/h3&gt;
&lt;p&gt;More conformance testing is a win. Period. There&amp;rsquo;s a huge benefit to all browser vendors when things are interoperable. Blink will be no exception.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;I see Blink as an opportunity to take browser engines to the next level. Innovation needs to happen at all levels of the stack, not just shiny new HTML5 features.&lt;/p&gt;
&lt;p&gt;Having multiple rendering engines—similar to having multiple browsers—will spur innovation and over time improve the health of the entire open web ecosystem.&lt;/p&gt;
&lt;p&gt;If you have burning questions for Blink&amp;rsquo;s engineering leads (Darin Fisher, Eric Seidel), &lt;a href=&quot;http://www.google.com/moderator/#15/e=20ac1d&amp;amp;t=20ac1d.40&amp;amp;v=5&quot; target=&quot;_blank&quot;&gt;post them&lt;/a&gt;. There will be a live video Q&amp;amp;A tomorrow (Thursday, April 4th) at 1PM PST: &lt;a href=&quot;http://developers.google.com/live&quot; target=&quot;_blank&quot;&gt;developers.google.com/live&lt;/a&gt;&lt;/p&gt;
</description>
      <pubDate>Tue, 02 Apr 2013 at 17:00:00</pubDate>
      <dc:creator>Eric Bidelman</dc:creator>
      <guid>https://ericbidelman.com/posts/2013/04/blink-chromes-new-rendering-engine</guid>
    </item>
    <item>
      <title>Creating .webm video from getUserMedia()</title>
      <link>https://ericbidelman.com/posts/2012/09/creating-webm-video-from-getusermedia</link>
      <description>&lt;p&gt;There’s a ton of motivation for being able to record live video. One scenario: you’re capturing video from the webcam. You add some post-production touchups in your favorite online video editing suite. You upload the final product to YouTube and share it out to friends. Stardom proceeds.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://dev.w3.org/2011/webrtc/editor/webrtc-20111004.html#mediastreamrecorder&quot; target=&quot;_blank&quot;&gt;&lt;code&gt;MediaStreamRecorder&lt;/code&gt;&lt;/a&gt; is a WebRTC API for recording &lt;code&gt;getUserMedia()&lt;/code&gt; streams (&lt;a href=&quot;http://www.htmlfivecan.com/#58&quot; target=&quot;_blank&quot;&gt;example code&lt;/a&gt;). It allows web apps to create a file from a live audio/video session.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;MediaStreamRecorder&lt;/code&gt; is &lt;a href=&quot;http://crbug.com/113676&quot; target=&quot;_blank&quot;&gt;currently unimplemented&lt;/a&gt; in the Chrome. However, all is not lost thanks to &lt;a href=&quot;https://github.com/antimatter15/whammy&quot; target=&quot;_blank&quot;&gt;Whammy.js&lt;/a&gt;. Whammy is a library that encodes .webm video from a list of .webp images, each represented as dataURLs.&lt;/p&gt;
&lt;p&gt;As a proof of concept, I’ve created a demo that captures live video from the webcam and creates a .webm file from it.&lt;/p&gt;
&lt;p style=&quot;text-align:center;&quot;&gt;
&lt;a href=&quot;https://html5-demos.appspot.com/static/getusermedia/record-user-webm.html&quot; target=&quot;_blank&quot;&gt;LAUNCH DEMO&lt;/a&gt;
&lt;/p&gt;&lt;p&gt;The demo also uses &lt;a href=&quot;http://updates.html5rocks.com/2011/08/Downloading-resources-in-HTML5-a-download&quot; target=&quot;_blank&quot;&gt;a[download]&lt;/a&gt; to let users download their file.&lt;/p&gt;
&lt;h3 id=&quot;creating-webp-images-from-%3Ccanvas%3E&quot; tabindex=&quot;-1&quot;&gt;Creating webp images from &lt;code&gt;&amp;lt;canvas&amp;gt;&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;The first step is to feed &lt;code&gt;getUserMedia()&lt;/code&gt; data into a &lt;code&gt;&amp;lt;video&amp;gt;&lt;/code&gt; element:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; video &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;querySelector&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;video&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
video&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;autoplay &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// Make sure we&#39;re not frozen!&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// Note: not using vendor prefixes!&lt;/span&gt;
navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getUserMedia&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;video&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;stream&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    video&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;src &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;URL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createObjectURL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;stream&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next, draw an individual video frame into a &lt;code&gt;&amp;lt;canvas&amp;gt;&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; canvas &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;querySelector&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;canvas&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
ctx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;drawImage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;video&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; canvas&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;width&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; canvas&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;height&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Chrome supports &lt;code&gt;canvas.toDataURL(&amp;quot;image/webp&amp;quot;)&lt;/code&gt;. This allows us to read back the &lt;code&gt;&amp;lt;canvas&amp;gt;&lt;/code&gt; as a .webp image and encode is as a dataURL, all in one swoop:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; url &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; canvas&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toDataURL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;image/webp&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// Second param is quality.&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Since this only gives us an single frame, we need to repeat the draw/read pattern using a &lt;code&gt;requestAnimationFrame()&lt;/code&gt; loop. That’ll give us webp frames at 60fps:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; rafId&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; frames &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;CANVAS_WIDTH&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; canvas&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;width&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;CANVAS_HEIGHT&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; canvas&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;height&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;drawVideoFrame&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;time&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  rafId &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;requestAnimationFrame&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;drawVideoFrame&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  ctx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;drawImage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;video&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;CANVAS_WIDTH&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;CANVAS_HEIGHT&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  frames&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;canvas&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toDataURL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;image/webp&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

rafId &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;requestAnimationFrame&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;drawVideoFrame&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The last step is to bring in Whammy. The library includes a static method &lt;code&gt;fromImageArray()&lt;/code&gt; that creates a &lt;code&gt;Blob&lt;/code&gt; (file) from an array of dataURLs. Perfect! That’s just what we have.&lt;/p&gt;
&lt;p&gt;Let’s package all of this goodness up in a &lt;code&gt;stop()&lt;/code&gt; method:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;stop&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;cancelAnimationFrame&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;rafId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// Note: not using vendor prefixes!&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// 2nd param: framerate for the video file.&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; webmBlob &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Whammy&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;fromImageArray&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;frames&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1000&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;60&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; video &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createElement&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;video&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  video&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;src &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;URL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createObjectURL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;webmBlob&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;body&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;appendChild&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;video&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When &lt;code&gt;stop()&lt;/code&gt; is called, the &lt;code&gt;requestAnimationFrame()&lt;/code&gt; recursion is terminated and the .webm file is created.&lt;/p&gt;
&lt;h3 id=&quot;performance-and-web-workers&quot; tabindex=&quot;-1&quot;&gt;Performance and Web Workers&lt;/h3&gt;
&lt;p&gt;Encoding webp images using &lt;code&gt;canvas.toDataURL(&#39;image/webp&#39;)&lt;/code&gt; takes ~120ms on my MBP. When you do something crazy like this in &lt;code&gt;requestAnimationFrame()&lt;/code&gt; callback, the framerate of the live &lt;code&gt;getUserMedia()&lt;/code&gt; video stream noticeably drops. It’s too much for the UI thread to handle.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Having the browser encode webp in C++ is far faster than encoding the .webp image in JS&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;My tests using &lt;a href=&quot;http://libwebpjs.hohenlimburg.org/&quot; target=&quot;_blank&quot;&gt;libwebpjs&lt;/a&gt; in a Web Worker were horrendously slow. The idea was to each frame as a &lt;code&gt;Uint8ClampedArray&lt;/code&gt; (raw pixel arrays), save them in an array, and &lt;code&gt;postMessage()&lt;/code&gt; that data to the Worker. The worker was responsible for encoding each pixel array into webp. The whole process took up to 20+ seconds to encode a single second’s worth of video. Not worth it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;It’s too bad &lt;code&gt;CanvasRenderingContext2D&lt;/code&gt; doesn’t exist in the Web Worker context&lt;/strong&gt;. That would solved a lot of the perf issues.&lt;/p&gt;
</description>
      <pubDate>Wed, 12 Sep 2012 at 17:00:00</pubDate>
      <dc:creator>Eric Bidelman</dc:creator>
      <guid>https://ericbidelman.com/posts/2012/09/creating-webm-video-from-getusermedia</guid>
    </item>
    <item>
      <title>Mashups using CORS and responseType=&#39;document&#39;</title>
      <link>https://ericbidelman.com/posts/2012/09/mashups-using-cors-and-responsetype-document</link>
      <description>&lt;p&gt;I always forget that you can request a resource as a &lt;code&gt;Document&lt;/code&gt; using XHR2. Combine this with &lt;a href=&quot;http://enable-cors.org/&quot; target=&quot;_blank&quot;&gt;CORS&lt;/a&gt; and things get pretty nice. No need to parse HTML strings and turn them into DOM yourself.&lt;/p&gt;
&lt;p&gt;For &lt;a href=&quot;http://www.html5rocks.com&quot; target=&quot;_blank&quot;&gt;html5rocks.com&lt;/a&gt;, we support CORS on all of our content. It&amp;rsquo;s trivial to pull down the tutorials page and query the DOM directly using  &lt;code&gt;querySelector()&lt;/code&gt;/&lt;code&gt;querySelectorAll()&lt;/code&gt; on the XHR&amp;rsquo;s response.&lt;/p&gt;
&lt;p&gt;Demo: &lt;a href=&quot;http://jsbin.com/bovetayuwu&quot; target=&quot;_blank&quot;&gt;http://jsbin.com/bovetayuwu&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://gist.github.com/3581825&quot; target=&quot;_blank&quot;&gt;https://gist.github.com/3581825&lt;/a&gt;&lt;/p&gt;
</description>
      <pubDate>Fri, 07 Sep 2012 at 17:00:00</pubDate>
      <dc:creator>Eric Bidelman</dc:creator>
      <guid>https://ericbidelman.com/posts/2012/09/mashups-using-cors-and-responsetype-document</guid>
    </item>
    <item>
      <title>Five things you didn&#39;t know the web could do</title>
      <link>https://ericbidelman.com/posts/2012/08/five-things-you-didnt-know-the-web-could-do</link>
      <description>&lt;p&gt;It’s been awhile since my last blog post. That’s because I’ve been busy writing articles all over the web! Check out my post on netmagazine, “&lt;a href=&quot;http://www.netmagazine.com/features/five-things-you-didnt-know-web-could-do&quot; target=&quot;_blank&quot;&gt;Five things you didn’t know the web could do&lt;/a&gt;”.&lt;/p&gt;
</description>
      <pubDate>Sun, 05 Aug 2012 at 17:00:00</pubDate>
      <dc:creator>Eric Bidelman</dc:creator>
      <guid>https://ericbidelman.com/posts/2012/08/five-things-you-didnt-know-the-web-could-do</guid>
    </item>
    <item>
      <title>Data Binding Using data-* Attributes</title>
      <link>https://ericbidelman.com/posts/2012/05/data-binding-using-data-attributes</link>
      <description>&lt;p&gt;Custom &lt;a href=&quot;https://developer.mozilla.org/en/DOM/element.dataset&quot; target=&quot;_blank&quot;&gt;&lt;code&gt;data-*&lt;/code&gt; attributes&lt;/a&gt; in HTML5 are pretty rad. They’re especially handy for stashing small amounts of data and retaining minimal state on the DOM. Turns out, they can also be used for one-way data binding!&lt;/p&gt;
&lt;p&gt;I’ve been using a nifty trick in recent projects that I thought would be worth sharing. The technique is to use a data attribute to store values (i.e. the data model) and &lt;code&gt;:before&lt;/code&gt;/&lt;code&gt;:after&lt;/code&gt; pseudo elements to render the values as generated content (i.e. the view). I call it &lt;strong&gt;“poor man’s data binding”&lt;/strong&gt; because it’s not true data binding in the traditional sense, but the semantics are similar. Count it!&lt;/p&gt;
&lt;p&gt;Here we go:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;style&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
  input &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    vertical&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;align&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; middle&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;margin&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; 2em&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    font&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;size&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; 14px&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; 20px&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;after &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;attr&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;data&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;/&#39;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;attr&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;max&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;position&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; relative&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;left&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; 135px&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;top&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;20px&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;style&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;input type&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;range&quot;&lt;/span&gt; min&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;0&quot;&lt;/span&gt; max&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;100&quot;&lt;/span&gt; value&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;25&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;script&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; input &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;querySelector&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;input&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  input&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;dataset&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; input&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// Set an initial value.&lt;/span&gt;
  input&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;change&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;dataset&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;script&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;a href=&quot;http://jsbin.com/iwitod/4/edit#html,live&quot; target=&quot;_blank&quot;&gt;&lt;strong&gt;TRY IT&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Notice the &lt;code&gt;25/100&lt;/code&gt; updates as you move the slider, but the &lt;code&gt;&amp;lt;input&amp;gt;&lt;/code&gt; is the only markup on the page.&lt;/p&gt;
&lt;p&gt;The magic line is the &lt;code&gt;content: attr(data-value) &#39;/&#39; attr(max)&lt;/code&gt;. It uses CSS &lt;code&gt;attr()&lt;/code&gt; to pull out the &lt;code&gt;data-value&lt;/code&gt; and &lt;code&gt;max&lt;/code&gt; attributes; both set using markup on the &lt;code&gt;&amp;lt;input&amp;gt;&lt;/code&gt;. As those values change, the generated content is automatically updated. Sick data binding bro.&lt;/p&gt;
&lt;p&gt;Really the only benefit of this technique is that we’re not including extraneous markup.&lt;/p&gt;
&lt;p&gt;Last but not least, here’s a more complex example that uses CSS transitions to change the height of a &lt;code&gt;div&lt;/code&gt; container when clicked. As the height changes, &lt;code&gt;requestAnimationFrame()&lt;/code&gt; updates the &lt;code&gt;data-height&lt;/code&gt; of the &lt;code&gt;div&lt;/code&gt; and the pseudo element picks that up.&lt;/p&gt;
&lt;figure style=&quot;text-align: center;border:1px solid #ccc;padding: 0.5em;&quot;&gt;
&lt;img src=&quot;data:image/png;base64,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&quot;&gt;
&lt;figcaption&gt;&lt;a href=&quot;http://jsbin.com/zekohaxenu&quot; target=&quot;_blank&quot;&gt;TRY IT&lt;/a&gt;&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;I’m sure if HTML was conceived in the age of web apps, we’d have proper DOM/JS data binding by now. Fortunately, initiatives like &lt;a href=&quot;http://code.google.com/p/mdv/&quot; target=&quot;_blank&quot;&gt;MDV&lt;/a&gt; and &lt;a href=&quot;http://dvcs.w3.org/hg/webcomponents/raw-file/tip/explainer/index.html&quot; target=&quot;_blank&quot;&gt;Web Components&lt;/a&gt; are on their way. One day this stuff will be a reality and native to HTML!&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;a href=&quot;http://en.wikipedia.org/wiki/Data_binding&quot; target=&quot;_blank&quot;&gt;Data binding&lt;/a&gt; is a technique for automatically synchronizing data between two sources. On the web, data binding typically manifests itself as updating DOM (UI) in response to events: XHRs, user input, or other business logic doing its thing. Take the canonical todo list for example. When I mark an item as done, the completed count increments. When it’s unchecked, the count decrements. That’s data binding!&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;If you want true two-way data binding, checkout one of the popular &lt;a href=&quot;http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller&quot; target=&quot;_blank&quot;&gt;MVC&lt;/a&gt; frameworks like &lt;a href=&quot;http://angularjs.org/&quot; target=&quot;_blank&quot;&gt;Angular&lt;/a&gt;, &lt;a href=&quot;http://knockoutjs.com/&quot; target=&quot;_blank&quot;&gt;Knockout&lt;/a&gt;, or &lt;a href=&quot;http://emberjs.org&quot; target=&quot;_blank&quot;&gt;Ember&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
</description>
      <pubDate>Tue, 22 May 2012 at 17:00:00</pubDate>
      <dc:creator>Eric Bidelman</dc:creator>
      <guid>https://ericbidelman.com/posts/2012/05/data-binding-using-data-attributes</guid>
    </item>
    <item>
      <title>idb.filesystem.js - Bringing the HTML5 Filesystem API to More Browsers</title>
      <link>https://ericbidelman.com/posts/2012/04/idbfilesystemjs-bringing-the-html5-filesystem</link>
      <description>&lt;p&gt;The &lt;a href=&quot;http://ericbidelman.tumblr.com/post/8165285763/my-book-is-finally-out-using-the-html5-filesystem&quot; target=&quot;_blank&quot;&gt;HTML5 Filesystem API&lt;/a&gt; is a versatile API that addresses many of the uses cases that the other offline APIs don&amp;rsquo;t. It can remedy their shortcomings, like making it difficult to  &lt;a href=&quot;http://updates.html5rocks.com/2012/04/Taking-an-Entire-Page-Offline-using-the-HTML5-FileSystem-API&quot; target=&quot;_blank&quot;&gt;dynamically caching a page&lt;/a&gt;. I&amp;rsquo;m looking at you AppCache!&lt;/p&gt;
&lt;p&gt;My ♥ for the API is deep&amp;ndash;so much so that I wrote a &lt;a href=&quot;http://ericbidelman.tumblr.com/post/8165285763/my-book-is-finally-out-using-the-html5-filesystem&quot; target=&quot;_blank&quot;&gt;book&lt;/a&gt; and released a library called &lt;a href=&quot;https://github.com/ebidel/filer.js&quot; target=&quot;_blank&quot;&gt;filer.js&lt;/a&gt; to help promote its adoption. While filer aims to make the API more consumable, it fails to address the elephant in the room: &lt;a href=&quot;http://caniuse.com/#search=filesystem&quot; target=&quot;_blank&quot;&gt;browser support&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Introducing idb.filesystem.js&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Today, I&amp;rsquo;m happy to bring the HTML5 Filesystem API to more browsers by releasing &lt;a href=&quot;https://github.com/ebidel/idb.filesystem.js&quot; target=&quot;_blank&quot;&gt;&lt;strong&gt;idb.filesystem.js&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;idb.filesystem.js is a well &lt;a href=&quot;https://github.com/ebidel/idb.filesystem.js/tree/master/tests&quot; target=&quot;_blank&quot;&gt;tested&lt;/a&gt; JavaScript &lt;a href=&quot;http://remysharp.com/2010/10/08/what-is-a-polyfill/&quot; target=&quot;_blank&quot;&gt;polyfill&lt;/a&gt; implementation of the Filesystem API intended for browsers that lack native support. Right now that&amp;rsquo;s everyone but Chrome. The library works by using IndexedDB as an underlying storage layer. This means any &lt;a href=&quot;http://caniuse.com/#search=indexeddb&quot; target=&quot;_blank&quot;&gt;browser supporting IndexedDB&lt;/a&gt;, now supports the Filesystem API! All you need to do is make Filesystem API calls and the rest is magic.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Demos&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve thrown together two demo apps to demonstrate the library&amp;rsquo;s usage. The first is a basic example. It allows you to create empty files/folders, drag files into the app from the desktop, and navigate into a folder or preview a file by clicking its name:&lt;/p&gt;
&lt;figure style=&quot;text-align:center&quot;&gt;&lt;a href=&quot;http://html5-demos.appspot.com/static/filesystem/idb.filesystem.js/demos/basic/index.html&quot; target=&quot;_blank&quot;&gt;&lt;figure class=&quot;tmblr-full&quot; data-orig-height=&quot;361&quot; data-orig-width=&quot;712&quot; data-orig-src=&quot;https://github.com/ebidel/idb.filesystem.js/raw/master/demos/basic/images/demo_screenshot.png&quot;&gt;&lt;img src=&quot;https://78.media.tumblr.com/bfc309486485f67fa756d551e0f85e75/tumblr_inline_p8nnpvA6yU1qlvegx_540.png&quot; data-orig-height=&quot;361&quot; data-orig-width=&quot;712&quot; data-orig-src=&quot;https://github.com/ebidel/idb.filesystem.js/raw/master/demos/basic/images/demo_screenshot.png&quot;&gt;&lt;/figure&gt;&lt;/a&gt;&lt;figcaption&gt;Try the (&lt;a href=&quot;http://html5-demos.appspot.com/static/filesystem/idb.filesystem.js/demos/basic/index.html&quot; target=&quot;_blank&quot;&gt;demo&lt;/a&gt;) in Firefox 11+&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;Want to use filer.js&amp;rsquo;s API with idb.filesystem.js? No problem. 90% of filer.js works out of the box with idb.filesystem.js. In fact, the &lt;a href=&quot;http://html5-demos.appspot.com/static/filesystem/idb.filesystem.js/demos/playground/index.html&quot; target=&quot;_blank&quot;&gt;second demo&lt;/a&gt; is a slightly modified version of filer.js&amp;rsquo;s playground app, showing that the two libraries can work in harmony. &#92;m/&lt;/p&gt;
&lt;p&gt;What&amp;rsquo;s exciting is that both of these apps work in FF, Chrome, and presumably other browsers that implement storing binary data in IndexedDB.&lt;/p&gt;
&lt;p&gt;I look forward to your feedback and pull requests!&lt;/p&gt;
</description>
      <pubDate>Sun, 22 Apr 2012 at 17:00:00</pubDate>
      <dc:creator>Eric Bidelman</dc:creator>
      <guid>https://ericbidelman.com/posts/2012/04/idbfilesystemjs-bringing-the-html5-filesystem</guid>
    </item>
    <item>
      <title>Introducing filer.js</title>
      <link>https://ericbidelman.com/posts/2011/12/introducing-filerjs</link>
      <description>&lt;p&gt;Some 1300+ lines of code, 106 &lt;a href=&quot;https://github.com/ebidel/filer.js/tree/master/tests&quot; target=&quot;_blank&quot;&gt;tests&lt;/a&gt;, and a year after I first started it, I&amp;rsquo;m happy to officially unleash &lt;strong&gt;&lt;a href=&quot;https://github.com/ebidel/filer.js&quot; target=&quot;_blank&quot;&gt;filer.js&lt;/a&gt;&lt;/strong&gt;; a wrapper library for the &lt;a href=&quot;http://ericbidelman.tumblr.com/post/8165285763/my-book-is-finally-out-using-the-html5-filesystem&quot; target=&quot;_blank&quot;&gt;HTML5 Filesystem API&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Unlike other libraries [&lt;a href=&quot;https://github.com/ajaxorg/webfs&quot; target=&quot;_blank&quot;&gt;1&lt;/a&gt;, &lt;a href=&quot;http://code.google.com/p/closure-library/source/browse/trunk/closure/goog/fs/fs.js&quot; target=&quot;_blank&quot;&gt;2&lt;/a&gt;], &lt;strong&gt;filer.js&lt;/strong&gt; takes a different approach and incorporates some lessons I learned while implementing the &lt;a href=&quot;http://code.google.com/p/gdata-python-client/source/browse/src/gdata/docs/client.py?r=d045d2d934e25266a02ff1e45c82fb68591e08e0&quot; target=&quot;_blank&quot;&gt;Google Docs Python client library&lt;/a&gt;. Namely, the library reuses familiar UNIX commands (&lt;code&gt;cp&lt;/code&gt;, &lt;code&gt;mv&lt;/code&gt;, &lt;code&gt;rm&lt;/code&gt;) for its API. My goal was to a.) make the HTML5 API more approachable for developers that have done file I/O in other languages, and b.) make repetitive operations (renaming, moving, duplicating) easier.&lt;/p&gt;
&lt;p&gt;So, say you wanted to list the files in a given folder. There&amp;rsquo;s an &lt;code&gt;ls()&lt;/code&gt; for that:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;var filer = new Filer();
filer.init({size: 1024 * 1024}, onInit.bind(filer), onError);

function onInit(fs) {
  filer.ls(&#39;/&#39;, function(entries) {
    // entries is an Array of file/directories in the root folder.
  }, onError);
}

function onError(e) { ... }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A majority of &lt;strong&gt;filer.js&lt;/strong&gt; calls are asynchronous. That&amp;rsquo;s because the underlying HTML5 API is also asynchronous. However, the library is extremely versatile and tries to be your friend whenever possible. In most cases, callbacks are optional. &lt;strong&gt;filer.js&lt;/strong&gt; is also good at accepting multiple types when working with entries. It accepts entries as string paths, &lt;a href=&quot;http://www.html5rocks.com/en/tutorials/file/filesystem/#toc-filesystemurls&quot; target=&quot;_blank&quot;&gt;filesystem: URLs&lt;/a&gt;, or as the &lt;code&gt;FileEntry&lt;/code&gt;/&lt;code&gt;DirectoryEntry&lt;/code&gt; object.&lt;/p&gt;
&lt;p&gt;For example, &lt;code&gt;ls()&lt;/code&gt; is happy to take your filesystem: URL or your &lt;code&gt;DirectoryEntry&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// These will produce the same results.
filer.ls(filer.fs.root.toURL(), function(entries) { ... });
filer.ls(filer.fs.root, function(entries) { ... });
filer.ls(&#39;/&#39;, function(entries) { ... });
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The library clocks in at 24kb (5.6kb compressed). I&amp;rsquo;ve thrown together a complete &lt;a href=&quot;http://html5-demos.appspot.com/static/filesystem/filer.js/demos/index.html&quot; target=&quot;_blank&quot;&gt;sample app&lt;/a&gt; to demonstrate most of &lt;strong&gt;filer.js&lt;/strong&gt;&amp;rsquo;s functionality:&lt;/p&gt;
&lt;figure style=&quot;text-align:center&quot;&gt;&lt;a href=&quot;http://html5-demos.appspot.com/static/filesystem/filer.js/demos/index.html&quot; target=&quot;_blank&quot;&gt;&lt;figure class=&quot;tmblr-full&quot; data-orig-height=&quot;188&quot; data-orig-width=&quot;400&quot; data-orig-src=&quot;https://github.com/ebidel/filer.js/raw/master/demos/images/demo_screenshot.png&quot;&gt;&lt;img src=&quot;https://78.media.tumblr.com/40a6fcc2ed6352e8abb65ee774a1731a/tumblr_inline_p8nnpvs2du1qlvegx_540.png&quot; data-orig-height=&quot;188&quot; data-orig-width=&quot;400&quot; data-orig-src=&quot;https://github.com/ebidel/filer.js/raw/master/demos/images/demo_screenshot.png&quot;&gt;&lt;/figure&gt;&lt;/a&gt;&lt;figcaption&gt;Try the (&lt;a href=&quot;http://html5-demos.appspot.com/static/filesystem/filer.js/demos/index.html&quot; target=&quot;_blank&quot;&gt;DEMO&lt;/a&gt;)&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;Lastly, there&amp;rsquo;s room for improvement:&lt;/p&gt;
&lt;ol&gt;&lt;li&gt;Incorporate Chrome&amp;rsquo;s &lt;a href=&quot;http://code.google.com/chrome/whitepapers/storage.html&quot; target=&quot;_blank&quot;&gt;Quota Management API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Make usage in Web Workers more friendly (there is a synchronous API).&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;I look forward to your feedback and pull requests!&lt;/p&gt;
</description>
      <pubDate>Mon, 26 Dec 2011 at 16:00:00</pubDate>
      <dc:creator>Eric Bidelman</dc:creator>
      <guid>https://ericbidelman.com/posts/2011/12/introducing-filerjs</guid>
    </item>
    <item>
      <title>Making file inputs a pleasure to look at</title>
      <link>https://ericbidelman.com/posts/2011/12/making-file-inputs-a-pleasure-to-look-at</link>
      <description>&lt;p&gt;I&amp;rsquo;ve seen a lot of people ask how to 1.) apply custom styles to a &lt;code&gt;&amp;lt;input type=&quot;file&quot;&amp;gt;&lt;/code&gt; and 2.) programmatically open the browser&amp;rsquo;s file dialog with JavaScript. Turns out, the first is a cinch WebKit. The second comes with a couple of caveats.&lt;/p&gt;
&lt;p&gt;If you want to skip ahead, there&amp;rsquo;s a &lt;a href=&quot;http://html5-demos.appspot.com/static/styled_file_input.html&quot; target=&quot;_blank&quot;&gt;demo&lt;/a&gt;.&lt;/p&gt;
&lt;h1&gt;Custom file inputs in WebKit&lt;/h1&gt;
&lt;p&gt;The first example on that demo page shows how to style your basic file input into something great. To achieve magnificence, we start with some standard issue markup:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;input type=&amp;quot;file&amp;quot; class=&amp;quot;button&amp;quot; multiple&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;followed by some semi-rowdy CSS that to hide the &lt;code&gt;::-webkit-file-upload-button&lt;/code&gt; pseudo-element and create a fake button using &lt;code&gt;:before&lt;/code&gt; content:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;.button::-webkit-file-upload-button {
  visibility: hidden;
}
.button:before {
  content: &#39;Select some files&#39;;
  display: inline-block;
  background: -webkit-linear-gradient(top, #f9f9f9, #e3e3e3);
  border: 1px solid #999;
  border-radius: 3px;
  padding: 5px 8px;
  outline: none;
  white-space: nowrap;
  -webkit-user-select: none;
  cursor: pointer;
  text-shadow: 1px 1px #fff;
  font-weight: 700;
  font-size: 10pt;
}
.button:hover:before {
  border-color: black;
}
.button:active:before {
  background: -webkit-linear-gradient(top, #e3e3e3, #f9f9f9);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Reference: &lt;img src=&quot;https://78.media.tumblr.com/tumblr_lwmagoBrrs1qlvegx.png&quot; style=&quot;vertical-align:middle;&quot; alt=&quot;i&#39;m just a reference&quot; title=&quot;i&#39;m just a reference&quot;&gt;&lt;/p&gt;
&lt;p&gt;Since this one is only available in WebKit, I&amp;rsquo;ve left out the other vendor prefixes.&lt;/p&gt;
&lt;h1&gt;Programmatically opening a file dialog&lt;/h1&gt;
&lt;p&gt;No browser that I know of lets you simulate a manual click on a file input without user intervention. The reason is security. Browsers require that a user make an explicit manual click (user-initiated click) somewhere on the page. However, once that happens, it&amp;rsquo;s straightforward to hijack the click and route it to a file input.&lt;/p&gt;
&lt;p&gt;My second technique (&lt;a href=&quot;https://twitter.com/#!/ebidel/status/26056441022382080&quot; target=&quot;_blank&quot;&gt;see this tweet&lt;/a&gt;) for styling a file input works across the modern browsers. It requires a bit of extra markup but allows us to &amp;ldquo;send&amp;rdquo; the user&amp;rsquo;s click to a file input.&lt;/p&gt;
&lt;p&gt;The trick is to hide the &lt;code&gt;&amp;lt;input type=&quot;file&quot;&amp;gt;&lt;/code&gt; by setting it to &lt;code&gt;visibility: hidden;&lt;/code&gt; and subbing in an extra &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt; to hand the user&amp;rsquo;s actual click:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;style&amp;gt;
#fileElem {
  /* Note: display:none on the input won&#39;t trigger the click event in WebKit.
    Setting visibility: hidden and height/width:0 works great. */
  visibility: hidden;
  width: 0;
  height: 0;
}
#fileSelect {
  /* style the button any way you want */
}
&amp;amp;lt;/style&amp;amp;gt;

&amp;amp;lt;input type=&amp;quot;file&amp;quot; id=&amp;quot;fileElem&amp;quot; multiple&amp;amp;gt;
&amp;amp;lt;button id=&amp;quot;fileSelect&amp;quot;&amp;amp;gt;Select some files&amp;amp;lt;/button&amp;amp;gt;

&amp;amp;lt;script&amp;amp;gt;
document.querySelector(&#39;#fileSelect&#39;).addEventListener(&#39;click&#39;, function(e) {
  // Use the native click() of the file input.
  document.querySelector(&#39;#fileElem&#39;).click();
}, false);
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Reference: &lt;img src=&quot;https://78.media.tumblr.com/tumblr_lwml6yuC3P1qlvegx.png&quot; style=&quot;vertical-align:middle;&quot; alt=&quot;i&#39;m just a reference&quot; title=&quot;i&#39;m just a reference&quot;&gt;&lt;/p&gt;
&lt;p&gt;You&amp;rsquo;ll be even cooler if you use custom events:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function click(el) {
  var evt = document.createEvent(&#39;Event&#39;);
  evt.initEvent(&#39;click&#39;, true, true);
  el.dispatchEvent(evt);
}

document.querySelector(&#39;#fileSelect&#39;).onclick = function(e) {
  // Simulate the click on fileInput with a custom event.
  click(document.querySelector(&#39;#fileElem&#39;));
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;Caveat&lt;/h1&gt;
&lt;p&gt;Most browsers require the &lt;code&gt;fileInput.click()&lt;/code&gt; to be called within 1000ms of the user-initiated click. For example, waiting 1.5s will fail because it&amp;rsquo;s too long after the user initiates a click:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;document.querySelector(&#39;#fileSelect&#39;).onclick = function(e) {
  setTimeout(function() {
    document.querySelector(&#39;#fileElem&#39;).click(); // Will fail.
  }, 1500);
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The cap gives you the chance to call &lt;code&gt;window.open&lt;/code&gt;, adjust UI, whatever before the file dialog opens.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://html5-demos.appspot.com/static/styled_file_input.html&quot; target=&quot;_blank&quot;&gt;Live demo&lt;/a&gt;&lt;/p&gt;
</description>
      <pubDate>Wed, 21 Dec 2011 at 16:00:00</pubDate>
      <dc:creator>Eric Bidelman</dc:creator>
      <guid>https://ericbidelman.com/posts/2011/12/making-file-inputs-a-pleasure-to-look-at</guid>
    </item>
    <item>
      <title>Web Audio API how-to: Playing audio based on user interaction</title>
      <link>https://ericbidelman.com/posts/2011/11/web-audio-api-how-to-playing-audio-based-on-user</link>
      <description>&lt;p&gt;One thing the &lt;a href=&quot;http://www.html5rocks.com/tutorials/webaudio/intro/&quot; target=&quot;_blank&quot;&gt;Web Audio API&lt;/a&gt; does particularly well is play sound. Of course, this is something you&amp;rsquo;d expect from an audio API :). That said, the API is complex and it&amp;rsquo;s not immediately obvious on the best way to do something simple like load a sound file and play it based on a button click. That task alone can involve a number of new platform features likes XHR2, &lt;code&gt;FileReader&lt;/code&gt; API, and &lt;code&gt;ArrayBuffer&lt;/code&gt;s.&lt;/p&gt;
&lt;p&gt;So&amp;hellip;I threw together a quick example on how to load a audio file and play/stop it based on the user clicking a button:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;!-- Author: Eric Bidelman (ericbidelman@chromium.org) --&amp;gt;
&amp;lt;html&amp;gt;
&amp;lt;head&amp;gt;
  &amp;lt;meta charset=&quot;utf-8&quot; /&amp;gt;
  &amp;lt;meta http-equiv=&quot;X-UA-Compatible&quot; content=&quot;chrome=1&quot; /&amp;gt;
  &amp;lt;title&amp;gt;Web Audio API: Simple load + play&amp;lt;/title&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
  &amp;lt;p&amp;gt;Example of using the Web Audio API to load a sound file
  and start playing on user-click.&amp;lt;/p&amp;gt;
  &amp;lt;input type=&quot;file&quot; accept=&quot;audio/*&quot;&amp;gt;
  &amp;lt;button onclick=&quot;playSound()&quot; disabled&amp;gt;Start&amp;lt;/button&amp;gt;
  &amp;lt;button onclick=&quot;stopSound()&quot; disabled&amp;gt;Stop&amp;lt;/button&amp;gt;
&amp;lt;script&amp;gt;
var context = new window.webkitAudioContext();
var source = null;
var audioBuffer = null;

function stopSound() {
  if (source) {
    source.noteOff(0);
  }
}

function playSound() {
  // source is global so we can call .noteOff() later.
  source = context.createBufferSource();
  source.buffer = audioBuffer;
  source.loop = false;
  source.connect(context.destination);
  source.noteOn(0); // Play immediately.
}

function initSound(arrayBuffer) {
  context.decodeAudioData(arrayBuffer, function(buffer) {
    // audioBuffer is global to reuse the decoded audio later.
    audioBuffer = buffer;
    var buttons = document.querySelectorAll(&#39;button&#39;);
    buttons[0].disabled = false;
    buttons[1].disabled = false;
  }, function(e) {
    console.log(&#39;Error decoding file&#39;, e);
  });
}

// User selects file, read it as an ArrayBuffer and pass to the API.
var fileInput = document.querySelector(&#39;input[type=&quot;file&quot;]&#39;);
fileInput.addEventListener(&#39;change&#39;, function(e) {
  var reader = new FileReader();
  reader.onload = function(e) {
    initSound(this.result);
  };
  reader.readAsArrayBuffer(this.files[0]);
}, false);

// Load file from a URL as an ArrayBuffer.
// Example: loading via xhr2: loadSoundFile(&#39;sounds/test.mp3&#39;);
function loadSoundFile(url) {
  var xhr = new XMLHttpRequest();
  xhr.open(&#39;GET&#39;, url, true);
  xhr.responseType = &#39;arraybuffer&#39;;
  xhr.onload = function(e) {
    initSound(this.response); // this.response is an ArrayBuffer.
  };
  xhr.send();
}
&amp;lt;/script&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;a href=&quot;http://html5-demos.appspot.com/static/webaudio/load_and_play.html&quot; target=&quot;_blank&quot;&gt;Demo&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;As you can see, this example includes two different ways to get an audio file into the Web Audio API: via an &lt;code&gt;&amp;lt;input type=&quot;file&quot;&amp;gt;&lt;/code&gt; and an &lt;code&gt;XMLHttpRequest&lt;/code&gt;. Both methods call &lt;code&gt;initSound()&lt;/code&gt;, which is passed an &lt;code&gt;ArrayBuffer&lt;/code&gt; containing the audio file. That method then decodes the audio and stores the result in a global variable (so it can be reused as play/stop are pressed).&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s it! Straightforward once you see it, right!?&lt;/p&gt;
</description>
      <pubDate>Sun, 27 Nov 2011 at 16:00:00</pubDate>
      <dc:creator>Eric Bidelman</dc:creator>
      <guid>https://ericbidelman.com/posts/2011/11/web-audio-api-how-to-playing-audio-based-on-user</guid>
    </item>
    <item>
      <title>Reading .mp3 ID3 tags in JavaScript</title>
      <link>https://ericbidelman.com/posts/2011/08/reading-mp3-id3-tags-in-javascript</link>
      <description>&lt;p&gt;For a recent project, I needed to read an .mp3&amp;rsquo;s &lt;a href=&quot;http://en.wikipedia.org/wiki/ID3&quot; target=&quot;_blank&quot;&gt;ID3&lt;/a&gt; metadata (song title, artist, year, album) in pure JS. The idea was to have the user select a song file, and boom!, its info would display to the user. Good news&amp;hellip;totally easy with the &lt;code&gt;FileReader&lt;/code&gt; API and JS typed arrays.&lt;/p&gt;
&lt;p&gt;Initially, I did a quick search to find some examples, but all of the examples I found used older techniques like manipulating a binary string. Ugh! We have better tools now for working with binary data in JS. Typed arrays to the rescue, specifically &lt;a href=&quot;https://developer.mozilla.org/en/JavaScript_typed_arrays/DataView&quot; target=&quot;_blank&quot;&gt;&lt;code&gt;DataView&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;DataView&lt;/code&gt; is a cousin to &lt;code&gt;ArrayBufferView&lt;/code&gt;, which is a &amp;ldquo;view&amp;rdquo; of a portion of an &lt;code&gt;ArrayBuffer&lt;/code&gt;.  Array buffers represent chunks of bytes in memory. Multiple views can be created from a single &lt;code&gt;ArrayBuffer&lt;/code&gt;. For example, one could create a &lt;code&gt;Int8Array&lt;/code&gt; and a &lt;code&gt;Float32Array&lt;/code&gt; from the same underlying data. Hence, &amp;ldquo;views&amp;rdquo;. This property makes them extremely versatile for binary data.&lt;/p&gt;
&lt;p&gt;For my purposes, &lt;code&gt;DataView&lt;/code&gt; turned out to be a perfect container for pulling sections of bytes as a string. However, I found the API to be a bit unintuitive in practice. Fortunately, I stumbled upon &lt;a href=&quot;http://blog.vjeux.com/&quot; target=&quot;_blank&quot;&gt;Christopher Chedeau&lt;/a&gt;&amp;rsquo;s &lt;a href=&quot;https://github.com/vjeux/jDataView&quot; target=&quot;_blank&quot;&gt;jDataView&lt;/a&gt; a while back and things started making sense. jDataView is an excellent wrapper for &lt;code&gt;DataView&lt;/code&gt;, improving much of its jankiness and adding a few extra utility methods for things like seeking and getting at data (e.g. &lt;code&gt;getString()&lt;/code&gt;, &lt;code&gt;getChar()&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s all the code that I needed:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;document.querySelector(&#39;input[type=&amp;quot;file&amp;quot;]&#39;).onchange = function(e) {
  var reader = new FileReader();

  reader.onload = function(e) {
    var dv = new jDataView(this.result);

    // &amp;quot;TAG&amp;quot; starts at byte -128 from EOF.
    // See &amp;lt;a href=&amp;quot;http://en.wikipedia.org/wiki/ID3&amp;quot; target=&amp;quot;_blank&amp;quot;&amp;gt;http://en.wikipedia.org/wiki/ID3&amp;lt;/a&amp;gt;
    if (dv.getString(3, dv.byteLength - 128) == &#39;TAG&#39;) {
      var title = dv.getString(30, dv.tell());
      var artist = dv.getString(30, dv.tell());
      var album = dv.getString(30, dv.tell());
      var year = dv.getString(4, dv.tell());
    } else {
      // no ID3v1 data found.
    }
  };

  reader.readAsArrayBuffer(this.files[0]);
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Pretty slick.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;DataView&lt;/code&gt; is implemented in Chrome 9+ and Webkit nightlies. However, jDataView provides the &lt;code&gt;DataView&lt;/code&gt; API for all the browsers using the best available option between Strings, JS typed arrays, and &lt;code&gt;DataView&lt;/code&gt;.&lt;/p&gt;
</description>
      <pubDate>Sun, 31 Jul 2011 at 17:00:00</pubDate>
      <dc:creator>Eric Bidelman</dc:creator>
      <guid>https://ericbidelman.com/posts/2011/08/reading-mp3-id3-tags-in-javascript</guid>
    </item>
    <item>
      <title>My book is finally out: Using the HTML5 Filesystem API, &quot;A True Filesystem for the Browser&quot;</title>
      <link>https://ericbidelman.com/posts/2011/07/my-book-is-finally-out-using-the-html5-filesystem</link>
      <description>&lt;figure&gt;
  &lt;img src=&quot;https://78.media.tumblr.com/tumblr_lp1ac6uOib1r0c89zo1_250.gif&quot; title=&quot;Book cover&quot;&gt;
&lt;/figure&gt;
&lt;p&gt;&lt;em&gt;Until now, web applications have been unable to organize binary data
into a hierarchy of folders. That has changed with the advent of HTML5.
With this book, you&#39;ll learn how to provide your applications with a
true file system that enables them to create, read, and write files ands
folders in a sandboxed section of the user&#39;s local filesystem. Author
Eric Bidelman provides several techniques and complete code examples for
working with the HTML5 Filesystem API.&lt;/em&gt;&lt;/p&gt;
</description>
      <pubDate>Wed, 27 Jul 2011 at 17:00:00</pubDate>
      <dc:creator>Eric Bidelman</dc:creator>
      <guid>https://ericbidelman.com/posts/2011/07/my-book-is-finally-out-using-the-html5-filesystem</guid>
    </item>
  </channel>
</rss>