Use vite to develop and build nodejs applications

Original link: https://blog.rxliuli.com/p/49fb661c297b4544a208ea898c77e5a0/

Scenes

As a building tool for modern web applications, vite is used by many developers to develop web applications (react, vue). Due to its ease of use and high performance, many web frameworks even officially write plug-ins (solid, astro). It can be regarded as a successful challenger of webpack in recent years. But in fact, so far, vite is far more than just a web layer tool. Its surrounding ecology is booming, and a series of peripheral tools have been derived. It was mentioned in the previous use of golang to rewrite nodejs cli , here Specifically for the development of nodejs applications to illustrate.

motivation

Why is vite suitable for developing nodejs applications?

First of all, even if you don’t use vite, you may need to use tools such as vitest for unit testing, use tsx/ts-node to run source code debugging, and use tsup/esbuild to bundle the code into the final js to be run. Well, if vite is used, these things will be done in the same ecology.

  • vitest: unit testing tool, support esm, support ts
  • vite-node: A running tool for ts code, supporting various features of vite, such as ?raw
  • vite: Package the final running js of the nodejs application, and optionally bundle dependencies

development process.drawio.svg

use

Both vitest and vite-node are ready-made and can be used out of the box, so the following focuses on vite building related things.

First install dependencies

 1
 pnpm i -D vite vite-node vitest

vitest

Create a unit test file, e.g. src/__tests__/index.test.ts

 1
2
3
4
5
 import { it } from 'vitest'

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

Run vitest with the following command

 1
 pnpm vitest src/__tests__/index.test.ts

vite-node

It can be said that you can use it to replace the node command to run any file, it is just more powerful than the node command, including

  • support ts/tsx/esm
  • There is cjs polyfill in esm, __dirname can be used directly
  • Support monitor mode operation
  • Support the features of vite itself, such as ?raw
  • Support the use of vite plugin

For example create a src/main.ts file

 1
2
3
 import { readFile } from 'fs/promises'

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

Then use vite-node to run

 1
 pnpm vite-node src/main.ts

vite

If vite wants to build nodejs applications, it really needs to modify some configurations and plug-ins, mainly to solve several problems

  1. Do polyfill for cjs features, including __dirname/__filename/require/self
  2. To bundle dependencies correctly, dependencies in devDependencies need to be packaged, but dependencies in node and dependencies should be excluded
  3. Should contain a default configuration, out of the box

Then we solve these problems separately

Build polyfills for cjs features

Install magic-string, used to modify code to keep sourcemap

 1
 pnpm i -D magic-string

Then add polyfill code in renderChunk hook

 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
twenty one
twenty two
twenty three
twenty four
25
26
27
28
29
30
31
 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' ,
}
}

properly bundle dependencies

Here, in order to simplify the use of the existing rollup-plugin-node-externals plugin, it can exclude node dependencies, and will automatically exclude them according to the dependencies and devDependencies in package.json, but some small compatibility modifications need to be made for vite.

install dependencies

 1
 pnpm i -D rollup-plugin-node-externals

Simple proxy package

 1
2
3
4
5
6
7
8
9
10
 import { nodeExternals } from 'rollup-plugin-node-externals'

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

Add default configuration

Since we have many projects, we don’t want to fill in the configuration every time, but solve this problem through convention + support configuration, so we simply implement a vite plug-in.

 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
 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' ,
}
}

merge plugin

Finally, we merge these plugins together, and we can use vite to build nodejs applications.

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

Then use in vite.config.ts

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

Now, we can use vite to build nodejs applications

 1
 pnpm vite build

Enjoy everything that vite brings!

We released a vite plugin @liuli-util/vite-plugin-node , which has solved the above problems.

question

Well, there are actually still some issues here, including

  • vite has no official support for building node apps, nor is it the main goal of the project
  • vite-plugin-node still has many problems, such as no automatic polyfill __dirname and so on
  • The performance of vite is still an order of magnitude worse than esbuild

No choice is perfect, but we now choose to believe in vite.

This article is transferred from: https://blog.rxliuli.com/p/49fb661c297b4544a208ea898c77e5a0/
This site is only for collection, and the copyright belongs to the original author.