使用Vite开发和构建Node.js应用程序
#node #vite

设想

vite作为现代Web应用程序的构建工具,许多开发人员用于Web应用程序开发(React,Vue)。由于其易用性和高性能,许多网络框架甚至官方插件(Solid,Astro)已为其编写,这使其成为近年来WebPack的成功挑战者。但是,迄今为止,Vite的发展远远超出了Web层工具。它的周围生态系统正在蓬勃发展,产生了一系列外围工具。如上一篇Vite - More Than Just a Build Tool所述,本文专门说明了如何开发Node.js应用程序。

动机

为什么Vite适合开发Node.js应用程序?

首先,即使不使用Vite,您可能需要使用vitest等工具进行单元测试,运行源代码使用TSX/TS节点调试,然后将代码捆绑到最终的JS中,以使用TSUP/ESBUILD运行。因此,如果您使用Vite,所有这些任务将在同一生态系统中完成。

  • vitest:单元测试工具,支持ESM,支持TS
  • vite节点:一种用于运行TS代码的工具,支持Vite的各种功能,例如?raw
  • vite:将node.js应用程序包装到要运行的最终js中,并且可以选择捆绑依赖项

Image description

用法

Vitest和Vite节点都可以开箱即用,因此这里的重点是与Vite构建相关的问题。

首先,安装依赖项

pnpm i -D vite vite-node 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

知识节点

您可以说可以使用它替换Node命令运行任何文件。它比节点命令更强大,包括

  • 支持TS/TSX/ESM
  • 在ESM,__dirname等中具有CJS Polyfill,可以直接使用
  • 支持在手表模式下运行
  • 支持Vite自己的功能,例如?raw
  • 支持使用Vite插件

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

import { readFile } from 'fs/promises'

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

然后用vite节点运行

pnpm vite-node src/main.ts

快速地

要构建使用vite的node.js应用程序,确实需要一些配置和插件修改来解决一些问题:

  1. 实施CJS Polyfill用于ESM代码,包括__dirname/__filename/require/self
  2. 正确捆绑dev依赖性依赖性依赖性,但排除节点/依赖关系依赖关系
  3. 提供开箱即用的默认配置

然后,我们一个一个

一个人解决这些问题

CJS功能的构建时间多填充

安装魔术弦,用于修改代码的同时维护Sourcemap

pnpm i -D magic-string

然后在renderchunk挂钩中添加polyfill代码

import MagicString from 'magic-string'

function sh

ims(): 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',
  }
}

正确捆绑依赖

在这里,为了简化,我们使用现有的croul-plugin节点插件。它可以排除节点依赖项,并会根据package.json中的依赖关系和dev依赖关系自动排除。但是,需要对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构建node.js应用程序。

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

然后在vite.config.ts
中使用它

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

现在,我们可以使用vite构建node.js应用程序

pnpm vite build

享受Vite带来的一切!

我发布了一个vite插件@liuli-util/vite-plugin-node,该插件解决了上述问题。

限制

好吧,这里还有一些问题,包括

  • vite不正式支持building node.js应用程序,它不是项目的主要目标
  • vite-plugin节点(NPM现有软件包)仍然有许多问题,例如不自动多利填充__dirname
  • Vite的表现仍然比Esbuild差的数量级

别无选择,但我现在选择相信Vite。

遇到的问题

  • ZX在构建后无法运行 - 粉笔未正确配置 - 无法识别imports中的node字段 - 维护者不在乎,考虑切换到ANSI-COLORS,REF

https://github.com/chalk/chalk/issues/535