寻觅 webpack - 後日谈 - 使用 Snowpack 以原生模组系统建置专案

本系列已集结成书从 0 到 Webpack:学习 Modern Web 专案的建置方式,这是一本完整介绍 Webpack 的专书,如有学习 Webpack 相关的问题可以参考此书。

本文讲述如何使用 Snowpack 以原生模组系统建置专案

本文的范例程序放在 peterhpchen/webpack-quest 中,每个程序码区块的第一行都会标注档案的位置,请搭配文章作参考。

Snowpack 是个以 ESM 原生的模组系统编译专案的工具,与 webpack 将所有的模组合并为一个 bundle 的方式不同,在现代主流浏览器的支援下,它将开发者所撰写的模组直接放到浏览器上运行,这样的演进对於前端开发会有什麽样的变革,我们接着看下去。

当时 webpack 所要解决的问题现在还存在吗?

webpack 将模组合并为一个 bundle ,并用此 bundle 在浏览器上运行。这样的设计最主要的目的就是在解决:

如今这两个问题很有可能你已经不用理会了。

JavaScript 的模组化问题

现代的主流浏览器都已经支援 ESM ,如果你所开发的目标并不需要支援旧式的浏览器(Ex: IE),那 JavaScript 的模组化这点你就不在需要担心了。

HTTP 1.1 的多并发限制

在 Client Server 的框架下,请求次数本身所占效能比重非常高,在开发状态下,由於所有的资源都存於本机,因此请求所耗的时间极短,基本上可以不用在意,但到了生产环境,请求的问题会被放大。

在过去 HTTP 1.1 的时代中,由於请求的多并发会有限制的数量,因此就算浏览器本身支援原生的模组系统,我们还是需要将多个模组合并以减少请求的次数。

现在 HTTP/2 在浏览器服务器支援的情况下,可以使用多并发请求/回应,减少了请求所造成的时间流失,如此一来 bundle 的重要性就会降低。

重要性降低,不代表没有用处,依照专案的规模及架构,在大多数的情况下,生产环境使用 bundler 来优化系统的效能依然是现今前端开发所必须的。

原生模组建置工具 Snowpack

如果你的目标环境已不再支援旧时代的浏览器的话,现在你有另一种选择,使用原生模组的建置工具。

Snowpack 就是这样的工具,它会将你所写的模组(当然必须是使用 ESM 语法(import, export)撰写的)以它们原本样子抛给浏览器,让浏览器使用自己的方式载入模组。

而对於 node_modules 内的库,为避免其中依然有使用其他非原生的模组语法(像是 CJS 、 AMD)的模组,同时也避免相依模组过多造成的效能问题,会将其中的模组做 bundle ,并以 ESM 汇出给予我们的模组使用。

文字叙述的有点模糊,且感觉不出 Snowpack 的威力,接着以范例带大家认识这个走在最前面的建置工具。

来个例子

我们使用下面这个例子:

// ./demos/webpack-demo/src/index.js
import _ from "lodash";
import demoName from "./demoName.js";

console.log(_.join(["Hi", demoName], " "));

// ./demos/webpack-demo/src/demoName.js
export default "Webpack Demo";

这个例子有一个外部工具库: lodash ,一个内部模组: demoName.js

使用 webpack

首先我们先使用 webpack 来做建置:

// ./demos/webpack-demo/webpack.config.js
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = {
  plugins: [
    new HtmlWebpackPlugin({
      template: "./public/index.html",
    }),
  ],
};
<!-- ./demos/webpack-demo/public/index.html -->
<!DOCTYPE html>
<html>
  <head>
    <title>Webpack Quest: Webpack Demo</title>
  </head>
  <body>
    <!-- Auto inject bundle -->
  </body>
</html>

为了与等下的 Snowpack 相似,我们这里使用 HtmlWebpackPlugin 并且带入模板产生出 index.html

建置後的档案结构如下图:

https://ithelp.ithome.com.tw/upload/images/20201026/20107789TRz178TYEG.png

这是我们都熟悉的结果,所有的模组都被包进了 main.js 中。

接着来看看 Snowpack 。

使用 Snowpack

首先我们先做安装的动作:

npm install snowpack -D

当然 lodash 也请使用 npm 安装。

安装完成後,我们将 /public/index.html 做修改:

<!-- ./demos/snowpack-demo/public/index.html -->
<!DOCTYPE html>
<html>
  <head>
    <title>Webpack Quest: Snowpack Demo</title>
  </head>
  <body>
    <script type="module" src="./_dist_/index.js"></script>
  </body>
</html>

注意到 <script> 的地方,改为使用 type="module"module 的方式载入。

另外需设定 snowpack.config.js ,这是 Snowpack 的配置档:

// ./demos/snowpack-demo/snowpack.config.js
module.exports = {
  mount: {
    public: "/",
    src: "/_dist_",
  },
};

这里配置了 snowpack 的目标资料夹,上面的配置会产生下面的效果:

  • public: '/': 目前的 public 资料夹内的档案会在输出目录的 root 下
  • src: '/_dist_: 目前的 src 资料夹内的档案会在输出目录中的 /_dist_

这代表 /public/index.html 会被输出到 build 目录中的 root 中,而 /src/index.js/src/demoName.js 两个模组会被输出到 _dist_ 资料夹中,这也是 index.html 中引入 index.js 时的路径要是 ./_dist_/index.js 的原因。

Snowpack 的配置档设定方式可以参考官方文件

接着下指令做建置:

snowpack build

建置结果如下:

https://ithelp.ithome.com.tw/upload/images/20201026/20107789NZXTGTifIS.png

可以看到 lodash 被处理为一个 .js 档案,这个档案会在输出目录中的 web_module/ 下。

我们看一下输出目录:

https://ithelp.ithome.com.tw/upload/images/20201026/20107789Arn7He6ZYd.png

可以看到原本 src 目录中的档案都保留在输出的 build 目录中,改为我们所设定的 _dist_ 目录中。

观察 dist/index.js

// ./demos/snowpack-demo/build/_dist_/index.js
import _ from "../web_modules/lodash.js";
import demoName from "./demoName.js";

console.log(_.join(["Hi", demoName], " "));

可以看到除了 lodash 的引入行外,其他都没有变化,而原本 lodash 的引入也只是改变路径变为 ../web_modules/lodash.js ,指到 Snowpack 帮我们打包好的路径下。

使用原生模组建置工具的好处

使用原生模组的好处在於,你不需要额外的时间做打包的工作,今天你修改了一个档案,只需替换此档案即可看到结果,不用像是 webpack 只改了其中一个模组,整个 bundle 就要重新建置。

引入其他资源

snowpack 在引入 .css 或是图片时提供了开箱即用的功能:

// ./demos/snowpack-css/src/index.js
import "./style.css";

使用与 webpack 相同的语法,直接 import Style , Snowpack 会将其输出为 style.proxy.js , Snowpack 内部利用 .proxy.js 引入非 JavaScript 的档案,我们可以看一下他的实作:

// ./demos/snowpack-css/build/_dist_/style.css.proxy.js
// [snowpack] add styles to the page (skip if no document exists)
if (typeof document !== "undefined") {
  const code = ".demo {\n    background-color: green;\n}";

  const styleEl = document.createElement("style");
  const codeEl = document.createTextNode(code);
  styleEl.type = "text/css";

  styleEl.appendChild(codeEl);
  document.head.appendChild(styleEl);
}

Snowpack 只是单纯将其用 appendChild 加入至 Document 中,非常的简单。

引入新的技术

新技术的崛起一文中提到前端拥有多种不同的技术供使用者在开发时有更好的效率及更低的出错率,但由於浏览器看不懂这些技术所提供的语法,因此我们需要使用建置工具在建置时转为原生的代码。

我们在 webpack 中使用 loaders 解决此类的问题,而在 Snowpack 中可以使用 Plugin。

使用 Snowpack 的 Plugin

以 Babel 为例,我们可以安装 @snowpack/plugin-babel:

npm install @snowpack/plugin-babel -D

接着在 snowpack.config.js 中引入 Plugin:

// ./demos/snowpack-babel/snowpack.config.js
module.exports = {
  mount: {
    public: "/",
    src: "/_dist_",
  },
  plugins: ["@snowpack/plugin-babel"],
};

如此一来 .js 的档案都会被 @snowpack/plugin-babel 转换。

详细的配置可以参考范例 snowpack-babel

结语

在现代的前端环境下,原本 webpack 这类的 bundler 所能提供的优势慢慢地减弱,变成不是必须的工具。

如今有像是 snowpack 或是 vite这类的原生模组建置工具也是选项之一,但这类的工具在这个时间点(2020)还是不能像是 webpack 在生产环境中拥有同等的效能(主要还是因为请求数量的问题),因此 snowpack 在生产环境输出时也是有提供 webpack 的 Plugin 供使用者选择,而 vite 则是直接将 Rollup 作为建制生产环境的预设选择。

虽然在生产环境一时间还无法使用这类的工具,但是在开发环境,它们则是非常的强大(请求数量的问题在本机环境下可以忽略,详情可以看上面的介绍)。

藉由原生模组不需 bundle 的特性,大幅减少建置的时间,并且 snowpack 可以启动自己的 dev server 做到 HMR 热模组替换,而各种新技术在 snowpack 中也可以使用 Plugin 支援载入,这使得它不仅跟 bundler 的开发环境相似,甚至因为分散式的模组形式,让修改後载入可以不必重新建置所有的模组而更加的迅速,大大地增加了开发者的效率。

在 Snowpack 的作者所发表的 A Future Without Webpack 一文中,有一句话贯穿了整篇文章: Bundle because you want to, not because you need to.

就现阶段来说,在生产状态下, webpack 、 Rollup 之类的 bundler 工具依然存在了不可取代的特性,但是在开发阶段,我们可以尝试使用 snowpack 、 vite 等原生的模组建置工具来加速开发的效率。

参考资料


<<:  (後记) 突破一般视讯会议限制的视讯设备

>>:  [Day 32] - 手把手跨出第一步!用JavaScript在Arduino上写出第一支闪烁LED程序-Part 2

DAY8 - 用ngx-lottie动画制作一个吸引人的首页

一个网站要吸引人,除了要有适当的排版,还要有吸引人的文字与丰富的动画,让人的目光不由自主地伫足想要浏...

Day6. Array & Hash 之间的组合应用

Hash 在其他语言称为Object, Dictionary,但无论是在哪个程序语言中,Hash 和...

Flutter体验 Day 2-环境设定

环境设定 系统需求 作业系统:macOS 硬碟空间:至少2.8 GB 工具:git、Xcode 取得...

android studio 30天学习笔记-day 15-databinding 双向绑定

昨天了解如何使用databinding的单向绑定,把data放到view里,那反过来当view发生变...

【Day 15】jQuery效果

jQuery效果: jQuery网页上方点选API Documentation 开启後,於网页左方,...