给vite添加node环境支持

3,944 阅读4分钟

前言

项目使用的两个第三方库中有使用到 node环境的apivite本身并不会自动引入这些polyfills,所以需要自己添加他们,中间也遇到了一些坑,包括分包,js加载顺序导致的polyfills没有正常生效,以及 global 和 process对象的特殊处理

代码

下面是我自己用的一个 plugin, 可以参考下

node-polyfills-plugins.ts

import { NodeGlobalsPolyfillPlugin } from '@esbuild-plugins/node-globals-polyfill'

import { NodeModulesPolyfillPlugin } from '@esbuild-plugins/node-modules-polyfill'

import rollupNodePolyFill from 'rollup-plugin-node-polyfills'

export function NodePolyFillsPlugin(): Plugin {
    return {
        name: 'vite-node-polyfills-plugins',
        config: async (options) => {
            return {
                resolve: {
                    alias: {
                        util: 'rollup-plugin-node-polyfills/polyfills/util',
                        sys: 'util',
                        events: 'rollup-plugin-node-polyfills/polyfills/events',
                        stream: 'rollup-plugin-node-polyfills/polyfills/stream',
                        path: 'rollup-plugin-node-polyfills/polyfills/path',
                        querystring: 'rollup-plugin-node-polyfills/polyfills/qs',
                        punycode: 'rollup-plugin-node-polyfills/polyfills/punycode',
                        url: 'rollup-plugin-node-polyfills/polyfills/url',
                        string_decoder: 'rollup-plugin-node-polyfills/polyfills/string-decoder',
                        http: 'rollup-plugin-node-polyfills/polyfills/http',
                        https: 'rollup-plugin-node-polyfills/polyfills/http',
                        os: 'rollup-plugin-node-polyfills/polyfills/os',
                        assert: 'rollup-plugin-node-polyfills/polyfills/assert',
                        constants: 'rollup-plugin-node-polyfills/polyfills/constants',
                        _stream_duplex: 'rollup-plugin-node-polyfills/polyfills/readable-stream/duplex',
                        _stream_passthrough: 'rollup-plugin-node-polyfills/polyfills/readable-stream/passthrough',
                        _stream_readable: 'rollup-plugin-node-polyfills/polyfills/readable-stream/readable',
                        _stream_writable: 'rollup-plugin-node-polyfills/polyfills/readable-stream/writable',
                        _stream_transform: 'rollup-plugin-node-polyfills/polyfills/readable-stream/transform',
                        timers: 'rollup-plugin-node-polyfills/polyfills/timers',
                        console: 'rollup-plugin-node-polyfills/polyfills/console',
                        vm: 'rollup-plugin-node-polyfills/polyfills/vm',
                        zlib: 'rollup-plugin-node-polyfills/polyfills/zlib',
                        tty: 'rollup-plugin-node-polyfills/polyfills/tty',
                        domain: 'rollup-plugin-node-polyfills/polyfills/domain'
                    }
                },
                optimizeDeps: {
                    esbuildOptions: {
                        define: {
                            global: 'globalThis'
                        },
                        plugins: [
                            NodeGlobalsPolyfillPlugin({
                                process: true,
                                buffer: true,
                            }),
                            NodeModulesPolyfillPlugin()
                        ]
                    }
                },
                plugins: [rollupNodePolyFill()]
            }
        }
    } as Plugin
}

使用

vite.config.ts

import {NodePolyFillsPlugin} from './node-polyfills-plugins'
export default definConfig({
  plugins: [
    NodePolyFillsPlugin()
  ]
})

vite.config.ts文件直接引入这个plugin,就能覆盖大部分的node api,如果打包出来发现有没覆盖到的,可以在这个图(库)里找找,根据打包后运行项目的提示,缺啥引啥就行

也可以到这个路径下去找找自己需要的 node_modules.pnpm\rollup-plugin-node-polyfills@0.2.1\node_modules\rollup-plugin-node-polyfills\dist\index.js

global 与 process

虽然前面已经通过plugin注入了这个两个对象,但打包生产后依然提示 global | process is not defined

如果只是缺少这两个对象,只需要做这一步就行。

所以需要再新建一个文件init.ts 内容如下

(window as any).global = window;
(window as any).process = {
    env: import.meta.env,
    argv: ['']
}

之后在main.ts中引入(保证他在第一行)

import './init'
import { createApp } from 'vue'
import AppComponent from './App.vue'
import router from '@/router/index'
import pinia from '@stores/index'
import '@assets/tailwind.css'

async function initApp() {
    const app = createApp(AppComponent)
    app.use(pinia).use(router)
    app.mount('#app')
}

initApp()

其他情况

使用node api的库被单独打包

大部分情况下,使用了以上的plugin就能解决大部分项目中使用了node 环境api的问题,但是当你在vite中使用manualChunks将使用了node api的库单独打包时,依然会有node环境 api is not defined 的报错。

这个问题是因为,使用node api的库比polyfills所在的包先加载完,先执行,这时,node环境的api还没来得及挂载到window上,所以依然会报错。

manualChunks 配置

const config = {
                plugins: [ViteCompression(),visualizer({
                    emitFile: true,
                    // @ts-ignore
                    file: 'stats.html'
                  })],
                esbuild: {
                    drop: ["console", "debugger"],
                },
                build: {
                    sourcemap: true,
                    outDir: path.resolve(__dirname, 'dist'),
                    chunkSizeWarningLimit: 650,
                    rollupOptions: {
                        manualChunks: {
                            mediainfo: ['mediainfo.js'],
                            slz: ['slz.storage'],
                            vue: ['vue'],
                            pinia: ['pinia'],
                            'element-plus': ['element-plus'],
                            'lodash-es': ['lodash-es']
                        }
                    }
                }
            }

解决方法

解决的思路主要就是将这个库的加载顺序尽量延后

利用路由懒加载

利用路由懒加载访问到才加载的特性,将包含使用node api库的页面修改为异步加载,这点在webpack上直接使用import()

vite中则稍微麻烦一些,需要使用 import.meta.glob

// 正常的页面,可以不用修改直接引入
import Home from '@views/home/home.vue'
// view就是存放页面的文件夹,这里先导入这个文件夹下所有的vue文件
const modules = import.meta.glob('../views/*/*.vue')
// modules实际上是一个对象

function asyncImport(path: string) {
    return modules[`../views${path}`]
}

// HomeEdit 这个页面包含使用node api的库,因此需要使用异步加载
const HomeEdit = asyncImport('/home/edit.vue')

modules 长这样

这样操作后,vite就会将edit.vue单独打包,访问时再加载

利用异步组件

当页面要求第一时间加载时,就没办法使用懒路由来处理了,这是我们可以把使用了node api的部分抽离为一个组件,并将他作为异步组件引入,同样能达到引入polyfills的代码执行后,再加载执行包含node api代码的效果。

包含node api的组件正常写就行,主要时组件引入时需要注意下,使用defineAsyncComponent包一层

<script setup lang="ts">
const upload = defineAsyncComponent(()=>import('@/components/common/up-load.vue'))
</script>