5

使用 vite 开发和构建 nodejs 应用 - rxliuli blog

 1 year ago
source link: https://blog.rxliuli.com/p/49fb661c297b4544a208ea898c77e5a0/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

使用 vite 开发和构建 nodejs 应用

使用 vite 开发和构建 _
2023年6月6日 凌晨

3.1k 字

27 分钟

本文最后更新于:2023年6月6日 凌晨

vite 作为现代 web 应用的构建工具,许多开发者将之用于开发 web 应用(react、vue),由于其本身的易用性和高性能,许多 web 框架甚至官方编写了插件(solid、astro),可以称得上是近年来 webpack 的成功的挑战者。但实际上,发展至今,vite 已经远不仅仅是一个 web 层的工具,它的周边生态正在蓬勃发展,衍生除了一系列周边的工具,在之前的 使用 golang 重写 nodejs cli 有所提及,这里专门针对开发 nodejs 应用来说明。

vite 为什么适合开发 nodejs 应用?

首先,即便不使用 vite,你可能也需要使用 vitest 之类的工具进行单元测试,使用 tsx/ts-node 运行源代码调试,使用 tsup/esbuild 将代码 bundle 为最终要运行的 js。那么,如果使用了 vite,这些事情将在同一个生态中完成。

  • vitest: 单元测试工具,支持 esm,支持 ts
  • vite-node: ts 代码的运行工具,支持 vite 的各种特性,例如 ?raw 之类的
  • vite: 将 nodejs 应用打包最终运行的 js,可以选择性的捆绑依赖

开发流程.drawio.svg

开发流程.drawio.svg

vitest 和 vite-node 都是现成的,可以开箱即用,所以下面重点关注 vite 构建相关的事情。

首先安装依赖

pnpm i -D vite vite-node vitest

vitest

创建一个单元测试文件,例如 src/__tests__/index.test.ts

import { it } from 'vitest'

it('hello world', () => {
expect(1 + 1).eq(2)
})

使用下面的命令运行 vitest

pnpm vitest src/__tests__/index.test.ts

vite-node

可以这样说,你可以使用它替代 node 命令运行任何文件,它只是比 node 命令更加强大,包括

  • 支持 ts/tsx/esm
  • 在 esm 中有 cjs 的 polyfill,__dirname 什么的可以直接使用
  • 支持监视模式运行
  • 支持 vite 本身的特性,例如 ?raw
  • 支持使用 vite 插件

例如创建一个 src/main.ts 文件

import { readFile } from 'fs/promises'

console.log(await readFile(__filename, 'utf-8'))

然后使用 vite-node 运行

pnpm vite-node src/main.ts

vite 想要构建 nodejs 应用,确实需要修改一些配置和插件,主要解决几个问题

  1. 针对 cjs 特性做 polyfill,包括 __dirname/__filename/require/self
  2. 正确捆绑依赖项,需要将 devDependencies 中的依赖也打包进去,但应该排除 node 和 dependencies 中的依赖
  3. 应该包含一个默认的配置,开箱即用

然后我们分别解决这几个问题

构建时针对 cjs 特性做 polyfill

安装 magic-string,用于修改代码保持 sourcemap

pnpm i -D magic-string

然后再 renderChunk hook 中添加 polyfill 代码

import MagicString from 'magic-string'

function shims(): Plugin {
return {
name: 'node-shims',
renderChunk(code, chunk) {
if (!chunk.fileName.endsWith('.js')) {
return
}
// console.log('transform', chunk.fileName)
const s = new MagicString(code)
s.prepend(`
import __path from 'path'
import { fileURLToPath as __fileURLToPath } from 'url'
import { createRequire as __createRequire } from 'module'

const __getFilename = () => __fileURLToPath(import.meta.url)
const __getDirname = () => __path.dirname(__getFilename())
const __dirname = __getDirname()
const __filename = __getFilename()
const self = globalThis
const require = __createRequire(import.meta.url)
`)
return {
code: s.toString(),
map: s.generateMap(),
}
},
apply: 'build',
}
}

正确捆绑依赖项

这里为了简化使用现有的 rollup-plugin-node-externals 插件,它可以排除 node 的依赖,也会自动根据 package.json 中的 dependencies 和 devDependencies 进行排除,但需要针对 vite 做一些小的兼容性修改。

pnpm i -D rollup-plugin-node-externals

简单代理封装一下

import { nodeExternals } from 'rollup-plugin-node-externals'

function externals(): Plugin {
return {
...nodeExternals(),
name: 'node-externals',
enforce: 'pre',
apply: 'build',
}
}

添加默认配置

由于吾辈有很多项目,所以不希望每次都填写配置,而是通过约定 + 支持配置的方式来解决这个问题,所以简单实现一个 vite 插件。

import path from 'path'

function config(options?: { entry?: string }): Plugin {
const entry = options?.entry ?? 'src/main.ts'
return {
name: 'node-config',
config() {
return {
build: {
lib: {
entry,
formats: ['es'],
fileName: path.basename(entry, path.extname(entry)),
},
},
}
},
apply: 'build',
}
}

最终,我们将这些插件合并到一起,就可以使用 vite 构建 nodejs 应用了。

export function node(): Plugin[] {
return [shims(), externals(), config()]
}

然后在 vite.config.ts 中使用

export default defineConfig({
plugins: [node()],
})

现在,我们可以使用 vite 构建 nodejs 应用了

pnpm vite build

享受 vite 带来的一切吧!

吾辈发布了一个 vite 插件 @liuli-util/vite-plugin-node,已经解决了上面的几个问题。

好吧,这里其实仍然存在一些问题,包括

  • vite 没有官方支持构建 node 应用,项目的主要目标也不是它
  • vite-plugin-node 还有许多问题,例如没有自动 polyfill __dirname 之类的
  • vite 相比 esbuild 的性能仍然差了一个数量级

没什么选择是尽善尽美的,但吾辈现在选择相信 vite。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK