[Web翻译]迈进零打包

823 阅读10分钟

原文地址:dev.to/open-wc/on-…

原文作者:dev.to/thepassle

发布时间:2019年7月30日更新于2020年1月11日 ・12分钟阅读

迈进零打包

大家好👋。

我有一段异地恋,这意味着每隔几个星期我就会坐飞机去英国。每次我在飞机上,我都会想,如果能读一些reddit的帖子该多好。我可以做的是找到一个reddit的应用,让你缓存离线的帖子(我相信肯定有一个),或者我可以利用这个机会自己写一些东西,使用一些最新的和最伟大的技术和网络标准,并享受一些乐趣。

除此之外,最近有很多讨论围绕着我喜欢称之为 "去构建无 "的东西,我认为这是一个非常迷人和伟大的最新发展。而这也正是这篇文章的主题;为开发带来乐趣。

我也喜欢把这篇博文想象成是对社区中一些非常棒的人的致敬,他们让一些非常棒的事情成为可能,同时也是对一些令人兴奋的新技术和标准的展示,我会在我们前进的过程中链接到所有这些好东西。

请注意,这不会是一个逐步的教程,但如果你想查看代码,你可以在github上找到完成的项目。我们的最终结果应该是这样的。

所以让我们直接进入并快速安装一些依赖关系。

npm i @babel/core babel-loader @babel/reset-env @babel/reset-react webpack webpack-cli react-dom redux react-redux html-webpack-plugin are-you-tired-yet html-loader webpack-dev-server

我开玩笑的。我们不会使用任何的。我们会尽量避免使用更多的工具/依赖性,并保持较低的进入门槛。

我们将使用的是

  • LitElement 在这个项目中,我们将使用LitElement作为我们的组件模型。它易于使用,轻量级,接近金属,并利用Web组件。

  • @vaadin/router Vaadin路由器是一款非常小巧(<7kb)的路由器,它的开发者体验非常棒,我非常推荐。

  • snowpack Snowpack将帮助我们把我们的模块整合在一起,方便开发。

  • es-dev-server 一个简单的开发服务器,用于现代Web开发工作流程,由我们open-wc制作。虽然任何http服务器都可以,但也可以自带。

就是这样。我们还将使用一些浏览器标准,即:es模块web组件import-mapkv-storageservice-worker

所以让我们继续安装我们的依赖关系。

npm i -S lit-element @vaadin/router
npm i -D snowpack es-dev-server

我们还将在package.json中添加一个postinstall钩子,它将为我们运行Snowpack。

  "scripts": {
    "start": "es-dev-server",
    "prepare": "snowpack"
  }

🐭皮卡 - 雪堆

Pika是Fred K. Schott的一个项目,旨在将2014年的那种怀旧、简单的风格带到2019年的Web开发中。Fred正在做各种厉害的事情,其中,他做了pika.dev,让你在npm上轻松搜索现代JavaScript包。他最近还在DinosaurJS 2019上发表了他的演讲Reimagining the Registry,我强烈推荐你观看。

snowpack让事情更进一步。如果我们运行snowpack,它会将我们的依赖关系作为单个javascript文件安装到一个新的web_modules/目录中。如果你的依赖关系在其package.json清单中导出了ES "模块 "入口点,那么snowpack就会支持它。如果你有任何转义的依赖关系,Snowpack将为你的依赖关系中的任何共享代码创建单独的chunks。轻松的柠檬汁。它甚至支持剪枝!

这意味着,在我们的例子中,我们的输出将看起来像这样。

└─ web_modules/
   ├─ lit-element.js
   └─ @vaadin
        └─ router.js

很好!就是这样。就是这样,我们已经准备好了我们的依赖关系,作为单一的javascript模块文件。我们已经准备好了我们的依赖关系,可以作为单一的javascript模块文件,这将使我们在后面的博文中的事情变得非常方便,请继续关注。

📥导入地图

好了!现在我们已经整理好了我们的依赖关系,让我们开始工作。我们将制作一个index.html,看起来像这样。

<html>
    <!-- head etc -->
    <body>
      <reddit-pwa-app></reddit-pwa-app>
      <script src="./src/reddit-pwa-app.js" type="module"></script>
    </body>
</html>

reddit-pwa-app.js


import { LitElement, html } from 'lit-element';

class RedditPwaApp extends LitElement {

  // ...

  render() {
    return html`
      <h1>Hello world!</h1>
    `;
  }
}

customElements.define('reddit-pwa-app', RedditPwaApp);

我们有了一个好的开始。让我们试着看看这个在浏览器中的效果如何,所以让我们启动服务器,打开浏览器,然后... 这是什么?一个错误?

哦,天啊

我们才刚开始呢 好吧,让我们来看看。这里的问题是,我们的模块指定器是裸露的。他们是裸露的模块指定器。这意味着没有指定路径,没有文件扩展名,他们只是... ...很空洞。我们的浏览器不知道该怎么处理这个问题,所以会抛出一个错误。

import { LitElement, html } from 'lit-element'; // <-- bare module specifier
import { Router } from '@vaadin/router'; // <-- bare module specifier

import { foo } from './bar.js'; // <-- not bare!
import { html } from 'https://unpkg.com/lit-html'; // <-- not bare!

当然,我们可以使用一些工具来实现这个功能,比如webpack,或者rollup,或者一个开发服务器,它可以将裸露的模块指定符重写成对浏览器有意义的东西,这样我们就可以加载我们的导入。但这意味着我们必须引入一大堆工具,深入到配置中去,而且我们正试图在这里保持最小化。 我们只想写代码! 为了解决这个问题,我们要看看导入地图

导入地图是一个新的提案,可以让你控制JavaScript导入的行为。 使用导入地图,我们可以控制JavaScriptimport语句和import()表达式会获取哪些URL,并允许这个映射在非导入上下文中重复使用。 这有几个原因。

  • 允许我们的裸模块指定器工作。
  • 提供了一个回退的解决方案,所以从import $ from "jquery";可以尝试先去CDN,但如果CDN服务器宕机,则回退到本地版本。
  • 实现了对内置模块的polyfill充或其他控制(稍后会有更多介绍,请抓紧!)。
  • 解决了嵌套依赖的问题(去读那篇博客吧!)。

听起来很不错,不是吗?目前在Chrome 75+中,导入地图在一个标志后面是可用的,有了这些知识,让我们进入index.html,在<head>中添加一个导入地图。

  <head>
    <script type="importmap">
      {
        "imports": {
          "@vaadin/router": "/web_modules/@vaadin/router.js",
          "lit-element": "/web_modules/lit-element.js"
        }
      }
    </script>
  </head>

如果我们回到浏览器,并刷新我们的页面,我们就不会再出现错误,而且我们应该在屏幕上看到我们的<h1>Hello world!</h1>

导入地图是一个非常有趣的新标准,绝对是你应该关注的东西。如果你对实验它们感兴趣,并基于yarn.lock文件生成你自己的导入地图,你可以尝试我们的open-wc import-maps-generate包,并玩一玩。我真的很期待看到大家会结合导入地图开发出什么。

📡Service Worker

好了,我们要跳过前面的时间一点点。我们已经做好了依赖关系,我们已经设置好了路由器,我们已经完成了一些API调用来从reddit获取数据,并将其显示在我们的屏幕上。在这篇博文中,所有的代码都不在讨论范围内,但请记住,如果你想阅读实现细节,你可以在github repo中找到所有的代码。

由于我们做这个应用是为了在飞机上阅读reddit线程,如果我们的应用可以离线工作,并且我们可以以某种方式保存一些帖子来阅读,那就太好了。

服务工作者是一种在后台运行的JavaScript Worker,你可以把它想象成坐在网页和网络之间。你可以把它想象成坐在网页,和网络之间。每当你的网页发出请求时,它都会先通过服务工作者。这意味着我们可以拦截这个请求,并对其进行处理。例如,我们可以让请求通过网络获得响应,并在返回时将其缓存起来,这样我们就可以在以后离线时使用缓存的数据。我们也可以使用服务工作者来预缓存我们的资产。这意味着我们可以预先缓存我们的应用程序可能需要的任何关键资产,以便离线工作。如果我们没有网络连接,我们可以简单地回到我们缓存的资产,并且仍然有一个可以工作(尽管是离线)的应用程序。

如果你有兴趣了解更多关于Progressive Web Apps和service worker的信息,我强烈推荐你阅读Jake Archibald所著的The Offline Cookbook。以及Jad Joubran的这个系列视频教程。

因此,让我们继续实现一个服务工作者。在index.html中,我们将添加以下片段。

  <script>
    if ('serviceWorker' in navigator) {
      window.addEventListener('load', () => {
        navigator.serviceWorker.register('./sw.js').then(() => {
          console.log('ServiceWorker registered!');
        }, (err) => {
          console.log('ServiceWorker registration failed: ', err);
        });
      });
    }
  </script>

我们还要在项目的根目录下添加一个sw.js文件。所以,我们即将预缓存我们的应用程序的资产,这就是Snowpack让我们生活变得非常简单的地方。如果你看看service worker文件中的安装处理程序,你就会发现,它是这样的

self.addEventListener('install', (event) => {
  event.waitUntil(
    caches.open(CACHENAME).then((cache) => {
      return cache.addAll([
        '/',
        './web_modules/lit-element.js',
        './web_modules/@vaadin/router.js',
        './src/reddit-pwa-app.js',
        './src/reddit-pwa-comment.js',
        './src/reddit-pwa-search.js',
        './src/reddit-pwa-subreddit.js',
        './src/reddit-pwa-thread.js',
        './src/utils.js',
      ]);
    })
  );
});

你会发现,我们完全控制了我们的资产,我们有一个漂亮的、干净的文件列表,以便离线工作。

📴脱机工作

好吧,既然我们已经缓存了我们的资产,以便离线工作,那么如果我们能够保存一些帖子,以便在离线时阅读,那就太好了。现在我们已经把资产缓存起来,可以离线工作了,如果我们能保存一些帖子,在离线时可以阅读,那就太好了。通往罗马的路有很多条 但既然我们现在生活在边缘地带 我们就选择: Kv存储!

📦内置模块

这里有几件事要讲。Kv-storage是一个内置模块。内置模块和普通的JavaScript模块非常相似,只不过它们是和浏览器一起发布的。值得注意的是,虽然内置模块随浏览器一起发布,但它们并不暴露在全局范围内,而是用std:(是的,真的。)来命名。这有几个好处:它们不会增加任何启动新的JavaScript运行时上下文的开销(例如一个新的tab、worker或service worker),也不会消耗任何内存或CPU,除非它们真的被导入,以及避免与现有代码的命名冲突。

另一个有趣的,如果不是有些争议的话,作为一个内置模块的建议是std-toast元素,以及std-switch元素。

🗃 Kv-storage

好了,说了这么多,我们来谈谈kv-storage。Kv-storage(或称 "键值存储")与localStorage相当相似,只有几个主要的区别,它是在IndexedDB的基础上分层的。

kv-storage的动机是,localStorage是同步的,这会导致性能不好和同步问题。它也仅限于专门的String键/值对。另一种选择,IndexedDb,则是......很难使用。它如此难用的原因是,它比诺言更早,这导致了,嗯,相当糟糕的开发者体验。不好玩。然而,Kv-storage却非常有趣,异步的,而且使用起来很方便 考虑下面的例子。

import { storage, /* StorageArea */ } from "std:kv-storage";

(async () => {
  await storage.set("mycat", "Tom");
  console.log(await storage.get("mycat")); // Tom
})();

注意到我们是如何从std:kv-storage导入的吗?这个导入指定符也是裸露的,但在这种情况下,它是没有问题的,因为它实际上是与浏览器一起发布的。

非常整洁。我们完全可以用它来添加一个 "保存为离线 "的按钮,并简单地存储一个reddit线程的JSON数据,当我们需要的时候就可以得到它。

reddit-pwa-thread.js:52

const savedPosts = new StorageArea("saved-posts");

// ...

async saveForOffline() {
  await savedPosts.set(this.location.params.id, this.thread); // id of the post + thread as json
  this.isPostSaved = true;
}

所以现在如果我们点击 "保存为离线 "按钮,进入开发者工具 "应用 "选项卡,我们可以看到一个kv-storage:saved-posts,里面保存着这个帖子的json数据。

而如果我们回到搜索页面,就会有一个保存的帖子列表,里面有我们刚刚保存的帖子。

🔮 Polyfilling

很好,不过,我们即将遇到另一个问题。然而,我们即将遇到另一个问题。生活在边缘很有趣,但也很危险。我们在这里遇到的问题是,在写这篇文章的时候,kv存储只在Chrome浏览器中通过一个标志实现。这显然不是很好。幸运的是,我们有一个Polyfill,同时我们可以展示导入地图的另一个非常有用的功能;Polyfilling!

首先,让我们安装 kv-storage-polyfill。

npm i -S kv -storage -polyfill

请注意,我们的prepare钩将再次为我们运行Snowpack。

让我们在index.html中添加以下内容到我们的导入地图中。

<script type="importmap">
  {
    "imports": {
      "@vaadin/router": "/web_modules/@vaadin/router.js",
      "lit-element": "/web_modules/lit-element.js",
      "/web_modules/kv-storage-polyfill.js": [
        "std:kv-storage",
        "/web_modules/kv-storage-polyfill.js"
      ]
    }
  }
</script>

所以这里发生的情况是,每当请求或导入/web_modules/kv-storage-polyfill.js时,浏览器会首先尝试查看std:kv-storage是否可用;然而,如果失败,它会加载/web_modules/kv-storage-polyfill.js来代替。

所以在代码中,如果我们导入

import { StorageArea } from '/web_modules/kv-storage-polyfill.js';

这将会发生什么。

"/web_modules/kv-storage-polyfill.js": [     // when I'm requested
  "std:kv-storage",                      // try me first!
  "/web_modules/kv-storage-polyfill.js"      // or fallback to me
]

🎉 结论

现在我们应该有一个简单的、功能完善的PWA,而且依赖性最小。对于这个项目,我们可以抱怨一些小毛病,而且这些小毛病都很公平。例如;我们也许可以不使用Snowpack,但它确实让我们的生活变得非常简单。你也可以提出同样的观点,说要增加一个简单的Webpack配置,但你会忽略了这一点。这里的重点是做一个有趣的应用程序,同时使用一些最新的功能,丢掉一些流行语,并有一个低的进入门槛。正如弗雷德-肖特所说的那样。"在2019年,你应该使用一个捆绑器,因为你想,而不是因为你需要。"

不过,如果你对挑剔感兴趣,你可以阅读这篇关于使用Webpack vs Pika vs buildless的精彩讨论,你会从Webpack核心团队的Sean Larkinn本人以及Pika的创造者Fred K. Schott那里得到一些很好的见解。

我希望你喜欢这篇博文,也希望你能学到一些东西,或者发现一些新的有趣的人。现在这个领域有很多令人兴奋的发展,我希望我让你和我一样对它们感到兴奋。如果你有任何问题、评论、反馈或挑剔,欢迎在twitter上@passle_@openwc联系我,别忘了查看open-wc.org 😉。

荣誉奖

在这个博客的最后,我想向一些非常有趣的人致敬,他们正在做一些伟大的事情,你可能会想关注一下。

首先,我想说的是 Guy Bedford,他写了es-module-shims,这,嗯,shims es模块,和导入地图。如果你问我,这是一个相当惊人的壮举,并允许我实际使用一些新的技术,还没有在所有的浏览器上实现。

如果你对更多相同的东西感兴趣,你一定要看看Luke Jackson的演讲Don't Build That App! 没有webpack,就没有🤓🤙,就像Luke说的那样。

我还要感谢Benny PowersLars den Bakker的有益评论和反馈。