Snowpack 更快的前端构建工具

背景

最近对脚手架技术架构的升级改造, 虽然在构建速度方面提升了3+倍, 接入项目的大佬们也都甚为满意, 毕竟还是没能达到秒级的开发体验。想着这算到了 webpack 的瓶颈了, 要去调研更快的打包工具及落地的可能性了。

现状

新的脚手架对 webpack 的构建速度优化简述为以下几点, 后面可以单独展开分享一下

  1. 多线程
  2. 前置构建
  3. 多页面动态编译/单页多路由动态编译
  4. loader缓存/plugin缓存
  5. 持久缓存

参赛工具

Parcel

其实一直是写 demo 的首选, 说要落地到生产, 没想过 … 官网上宣传的速度只是 webpack 的两倍左右, 加上仅存的三两项目遇到的一些 bug(陆续也转到了 webpack), 算上社区的活跃度,没有实践的动力。

对 parcel 的评估只是个人的经验, 缺乏数据说明。话说回来即使是稳定且健壮的 parcel 也是达不到我们最初的秒级的开发体验。

小结: parcel 仍然不适合中大型项目。

Snowpack

终于回到了本次的主题, 为什么说 snowpack 快 ? 它是希望你能写出浏览器直接能运行的代码, 你说它能不快吗 … 当然还是要有理有据, 看看官方解释。

Snowpack leverages JavaScript’s native module system (known as ESM) to create a first-of-its-kind build system that never builds the same file twice. Snowpack pushes changes instantly to the browser, saving you hours of development time traditionally spent waiting around for your bundler.

主要帮我们做了什么

# 1. 把项目依赖的 node_modules 模块通过 rollup 编译成 esm 标准代码输出到 
build/web_modules 目录

# snowpack/src/commands/dev.ts#297

// 如果是不存在 'package-lock.json' 或者 'yarn.lock'|| 'package-lock.json' 或者 'yarn.lock' 有变化 || 第一次构建 等情况会重新编译
if (!(await checkLockfileHash(DEV_DEPENDENCIES_DIR)) || !existsSync(dependencyImportMapLoc)) {
// ...
}

# 2. 通过 babel 编译 ts, tsx, js, jsx 代码输出到 build/_dist_ 目录(毕竟浏览器跑不了 jsx, ts 代码)

# plugins/plugin-babel/worker.js#4

async function transformFileAsync(path, options) {
  const {code, map} = await babel.transformFileAsync(path, options);
  return JSON.stringify({code, map});
}

// 使用如下文件的 babel 配置
# app-scripts-react/babel.config.json

// 实践证明 @babel/preset-typescript 坑不少, 老项目上线几个白屏几个
{
  "presets": [["@babel/preset-react"], "@babel/preset-typescript"],
  "plugins": ["@babel/plugin-syntax-import-meta"],
  "env": {
    "development": {
      "plugins": [
        "react-refresh/babel"
      ]
    }
  }
}

# 3. 开发环境启动一个 devServer

# snowpack/src/commands/dev.ts#875

const server = createServer((req, res) => {
// ...
})

demo 目录结构对比

➜  new-dir tree src
src
├── App.css
├── App.jsx
├── App.test.jsx
├── index.css
├── index.jsx
├── logo.svg
└── test.css

➜  new-dir tree build
build
├── __snowpack__
│   └── env.js
├── _dist_
│   ├── App.css
│   ├── App.js
│   ├── index.css
│   ├── index.css.proxy.js
│   ├── index.js
│   ├── logo.svg
│   ├── logo.svg.proxy.js
│   └── test.css
├── favicon.ico
├── index.html
├── robots.txt
└── web_modules
    ├── import-map.json
    └── react-dom.js

发现的一些问题

  1. 源码可能会被篡改
# plugins/plugin-babel/plugin.js#42

code = code.replace(/process\.env/g, 'import.meta.env');

// 会造成如下问题

// src
function getEnvLabel() {
 return 'process.env.NODE_ENV'
}
// dist
function getEnvLabel() {
 return 'import.meta.env'
}

// enum ....
  1. css 文件 @import 另一个 css 文件不会处理

    没有进行 ast 语法分析, 你只能在 js 文件里面 import, 也许你想着帮忙一起完善这块, 到了日趋完善的一天发现之前不就有一个成熟的 webpack 为何不用 ? 这 …

  2. ….

小结

现在的 snowpack 还是希望你能写出浏览器直接能运行的代码, 对于新小项目开发环境可尝试, 其他还是先等一等 …

最近的文章

JavaScript 二进制 (上篇)

前情提要 在 JavaScript 处理二进制数据方面上常接触的只有 Node 读写文件的 Buffer 对象, 脑海中只知道 ES6 也有 ArrayBuffer 相关的二进制处理的对象, 具体两者之间有何关系及 ArrayBuffer 的相关知识, 进阶用法都算是知识盲区了, 所以打算这次好好梳理学习一下, 顺便写个博客记录。// 扫盲之前 😂type ArrayBuffer = Array<Buffer>ArrayBuffer 2011 年 2 月发布 ArrayBu...…

继续阅读
更早的文章

React, TypeScript 写游戏探索

简单介绍游戏按键如下图顶这个墙出现蘑菇, 此蘑菇接了才可以发子弹React 写游戏的一些心得1. React的优势 数据驱动, 根据state或者props的变化 => 视图的变化, 以前的方式往往是直接操作 DOM 实现, 触发某事件使得元素移动代码类似如: moveRight() { this.left += 8; this.draw(); } draw() { if(this.ele === null){ ...…

继续阅读