├── .eslintrc.cjs ├── .github ├── actions │ └── install-dependencies │ │ └── action.yml └── workflows │ └── ci.yml ├── .gitignore ├── .npmignore ├── .npmrc ├── .vscode ├── extensions.json └── settings.json ├── LICENSE ├── README.md ├── examples ├── react │ ├── index.html │ ├── package.json │ ├── src │ │ ├── app.tsx │ │ ├── env.d.ts │ │ └── main.ts │ ├── tsconfig.json │ └── vite.config.ts ├── vanilla │ ├── index.html │ ├── package.json │ ├── playwright.config.ts │ ├── src │ │ ├── env.d.ts │ │ └── main.ts │ ├── test │ │ ├── e2e │ │ │ └── main.spec.ts │ │ └── utils │ │ │ └── index.ts │ ├── tsconfig.json │ └── vite.config.ts └── vue │ ├── index.html │ ├── package.json │ ├── src │ ├── app.ts │ ├── app.vue │ ├── env.d.ts │ └── main.ts │ ├── tsconfig.json │ └── vite.config.ts ├── package.json ├── patches └── buffer@6.0.3.patch ├── pnpm-lock.yaml ├── pnpm-workspace.yaml ├── shims ├── buffer │ ├── index.ts │ └── package.json ├── global │ ├── index.ts │ └── package.json ├── package.json ├── process │ ├── index.ts │ └── package.json └── vite.config.ts ├── src ├── env.d.ts ├── index.ts └── utils.ts ├── test ├── error-repros │ ├── missing-zlib-imports │ │ ├── index.html │ │ ├── package.json │ │ ├── playwright.config.ts │ │ ├── src │ │ │ └── index.ts │ │ ├── test │ │ │ └── e2e │ │ │ │ └── main.spec.ts │ │ └── vite.config.ts │ ├── process-disabled │ │ ├── index.html │ │ ├── package.json │ │ ├── playwright.config.ts │ │ ├── src │ │ │ ├── env.d.ts │ │ │ └── main.ts │ │ ├── test │ │ │ ├── e2e │ │ │ │ └── main.spec.ts │ │ │ └── utils │ │ │ │ └── index.ts │ │ ├── tsconfig.json │ │ └── vite.config.ts │ └── vite-scan-buffer-import-error │ │ ├── index.html │ │ ├── package.json │ │ ├── test.ts │ │ └── vite.config.ts ├── integration │ ├── global-references │ │ └── index.test.ts │ └── import-globals │ │ └── index.test.ts └── utils │ └── index.ts ├── tsconfig.json ├── vite.config.ts └── vitest.config.ts /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: [ 3 | 'artisan', 4 | ], 5 | overrides: [ 6 | { 7 | files: [ 8 | './.github/**/*.yml', 9 | ], 10 | rules: { 11 | 'yml/no-empty-mapping-value': 'off', 12 | }, 13 | }, 14 | ], 15 | } 16 | -------------------------------------------------------------------------------- /.github/actions/install-dependencies/action.yml: -------------------------------------------------------------------------------- 1 | name: Install dependencies 2 | description: Install and cache dependencies 3 | runs: 4 | using: composite 5 | steps: 6 | - name: Checkout 7 | uses: actions/checkout@v4 8 | - name: Setup pnpm 9 | uses: pnpm/action-setup@v4 10 | with: 11 | run_install: false 12 | - name: Setup Node.js 13 | uses: actions/setup-node@v4 14 | with: 15 | node-version: 18 16 | cache: pnpm 17 | - name: Install dependencies 18 | shell: bash 19 | run: pnpm install 20 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | on: 3 | pull_request: 4 | push: 5 | branches: 6 | - main 7 | workflow_dispatch: 8 | jobs: 9 | lint: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v4 13 | - uses: ./.github/actions/install-dependencies 14 | - name: Run the linter 15 | run: pnpm lint 16 | test-e2e: 17 | runs-on: ubuntu-22.04 18 | steps: 19 | - uses: actions/checkout@v4 20 | - uses: ./.github/actions/install-dependencies 21 | - run: pnpm build:core 22 | - run: pnpm build:shims 23 | - run: pnpm playwright install --with-deps 24 | - run: pnpm -r test:e2e 25 | test-unit: 26 | runs-on: ubuntu-latest 27 | steps: 28 | - uses: actions/checkout@v4 29 | - uses: ./.github/actions/install-dependencies 30 | - run: pnpm build:core 31 | - run: pnpm build:shims 32 | - run: pnpm -r test 33 | typecheck: 34 | runs-on: ubuntu-latest 35 | steps: 36 | - uses: actions/checkout@v4 37 | - uses: ./.github/actions/install-dependencies 38 | - run: pnpm build:core 39 | - run: pnpm build:shims 40 | - run: pnpm typecheck 41 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | .DS_Store 3 | .cache 4 | /*.tgz 5 | blob-report 6 | dist 7 | node_modules 8 | playwright-report 9 | test-results 10 | tmp 11 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | /.vscode 2 | /src 3 | /test 4 | /tsconfig.json 5 | /vite.config.ts 6 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | ignore-workspace-root-check=true 2 | include-workspace-root=true 3 | # https://github.com/pnpm/pnpm/issues/6463 4 | resolution-mode=highest 5 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "arcanis.vscode-zipfs" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsdk": "node_modules/typescript/lib", 3 | "files.eol": "\n" 4 | } 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 David R. Myers 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Sponsor me](https://img.shields.io/badge/sponsor-DB61A2?style=for-the-badge&logo=GitHub-Sponsors&logoColor=white)](https://voracious.link/sponsor) 2 | [![Donate](https://img.shields.io/badge/donate-FF5F5F?style=for-the-badge&logo=ko-fi&logoColor=white)](https://voracious.link/donate) 3 | 4 | # vite-plugin-node-polyfills 5 | 6 | A Vite plugin to polyfill Node's Core Modules for browser environments. Supports [`node:` protocol imports](https://nodejs.org/dist/latest-v16.x/docs/api/esm.html#node-imports). 7 | 8 | ## Why do I need this? 9 | 10 | ``` 11 | Module "stream" has been externalized for browser compatibility. Cannot access "stream.Readable" in client code. 12 | ``` 13 | 14 | Since browsers do not support Node's [Core Modules](https://nodejs.org/dist/latest-v16.x/docs/api/modules.html#core-modules), packages that use them must be polyfilled to function in browser environments. In an attempt to prevent runtime errors, Vite produces [errors](https://github.com/vitejs/vite/issues/9200) or [warnings](https://github.com/vitejs/vite/pull/9837) when your code references builtin modules such as `fs` or `path`. 15 | 16 | ## Getting Started 17 | 18 | Install the package as a dev dependency. 19 | 20 | ```sh 21 | # npm 22 | npm install --save-dev vite-plugin-node-polyfills 23 | 24 | # pnpm 25 | pnpm install --save-dev vite-plugin-node-polyfills 26 | 27 | # yarn 28 | yarn add --dev vite-plugin-node-polyfills 29 | ``` 30 | 31 | Add the plugin to your `vite.config.ts` file. 32 | 33 | ```ts 34 | import { defineConfig } from 'vite' 35 | import { nodePolyfills } from 'vite-plugin-node-polyfills' 36 | 37 | // https://vitejs.dev/config/ 38 | export default defineConfig({ 39 | plugins: [ 40 | nodePolyfills(), 41 | ], 42 | }) 43 | ``` 44 | 45 | ### Customizable when you need it 46 | 47 | The following options are available to customize it for your needs. 48 | 49 | ```ts 50 | import { defineConfig } from 'vite' 51 | import { nodePolyfills } from 'vite-plugin-node-polyfills' 52 | 53 | // https://vitejs.dev/config/ 54 | export default defineConfig({ 55 | plugins: [ 56 | nodePolyfills({ 57 | // To add only specific polyfills, add them here. If no option is passed, adds all polyfills 58 | include: ['path'], 59 | // To exclude specific polyfills, add them to this list. Note: if include is provided, this has no effect 60 | exclude: [ 61 | 'http', // Excludes the polyfill for `http` and `node:http`. 62 | ], 63 | // Whether to polyfill specific globals. 64 | globals: { 65 | Buffer: true, // can also be 'build', 'dev', or false 66 | global: true, 67 | process: true, 68 | }, 69 | // Override the default polyfills for specific modules. 70 | overrides: { 71 | // Since `fs` is not supported in browsers, we can use the `memfs` package to polyfill it. 72 | fs: 'memfs', 73 | }, 74 | // Whether to polyfill `node:` protocol imports. 75 | protocolImports: true, 76 | }), 77 | ], 78 | }) 79 | ``` 80 | 81 | ### All polyfills 82 | 83 | - If protocolImports is true, also adds node:[module] 84 | 85 | ```js 86 | [ 87 | '_stream_duplex', 88 | '_stream_passthrough', 89 | '_stream_readable', 90 | '_stream_transform', 91 | '_stream_writable', 92 | 'assert', 93 | 'buffer', 94 | 'child_process', 95 | 'cluster', 96 | 'console', 97 | 'constants', 98 | 'crypto', 99 | 'dgram', 100 | 'dns', 101 | 'domain', 102 | 'events', 103 | 'fs', 104 | 'http', 105 | 'http2', 106 | 'https', 107 | 'module', 108 | 'net', 109 | 'os', 110 | 'path', 111 | 'process', 112 | 'punycode', 113 | 'querystring', 114 | 'readline', 115 | 'repl', 116 | 'stream', 117 | 'string_decoder', 118 | 'sys', 119 | 'timers', 120 | 'timers/promises', 121 | 'tls', 122 | 'tty', 123 | 'url', 124 | 'util', 125 | 'vm', 126 | 'zlib', 127 | ] 128 | ``` 129 | 130 | ## About the author 131 | 132 | Hello! My name is David, and in my spare time, I build tools to help developers be more productive. If you find my work valuable, I would really appreciate a [sponsorship](https://voracious.link/sponsor) or [donation](https://voracious.link/donate). If you want to see more of my work, check out [davidmyers.dev](https://davidmyers.dev). 133 | 134 | Thanks for your support! 🪴 135 | -------------------------------------------------------------------------------- /examples/react/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Example (React) 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /examples/react/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module", 3 | "private": true, 4 | "scripts": { 5 | "build": "vite build", 6 | "dev": "vite", 7 | "typecheck": "tsc" 8 | }, 9 | "devDependencies": { 10 | "@tanstack/react-query": "^4.36.1", 11 | "@tanstack/react-query-devtools": "^4.36.1", 12 | "@types/node": "^18.18.8", 13 | "@types/react": "^18.2.34", 14 | "@types/react-dom": "^18.2.14", 15 | "@vitejs/plugin-react": "^4.1.1", 16 | "react": "^18.2.0", 17 | "react-dom": "^18.2.0", 18 | "typescript": "^5.2.2", 19 | "vite": "^4.5.0", 20 | "vite-plugin-node-polyfills": "workspace:*" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /examples/react/src/app.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import ReactDOM from 'react-dom/client' 3 | import { QueryClient, QueryClientProvider } from '@tanstack/react-query' 4 | import { ReactQueryDevtools } from '@tanstack/react-query-devtools' 5 | 6 | const queryClient = new QueryClient() 7 | 8 | function App() { 9 | const [count, setCount] = useState(0) 10 | 11 | return ( 12 |
13 |

Example (React)

14 |

The following app includes React Query DevTools (see below) to assert that this plugin does not conflict with it.

15 |

Here is a dynamic counter to test that React works as expected.

16 |

17 | 20 |

21 |

22 | {/* eslint-disable-next-line n/prefer-global/buffer */} 23 | The following text is encoded and decoded with Buffer: {Buffer.from('Hello!').toString()} 24 |

25 |
26 | ) 27 | } 28 | 29 | export const app = () => { 30 | ReactDOM.createRoot(document.getElementById('react-app') as HTMLElement).render( 31 | 32 | 33 | 34 | 35 | 36 | , 37 | ) 38 | } 39 | -------------------------------------------------------------------------------- /examples/react/src/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /examples/react/src/main.ts: -------------------------------------------------------------------------------- 1 | import { app } from './app' 2 | 3 | app() 4 | -------------------------------------------------------------------------------- /examples/react/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "jsx": "react-jsx", 4 | "module": "ESNext", 5 | "moduleResolution": "Bundler", 6 | "noEmit": true, 7 | "skipLibCheck": true 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /examples/react/vite.config.ts: -------------------------------------------------------------------------------- 1 | import react from '@vitejs/plugin-react' 2 | import { defineConfig } from 'vite' 3 | import { nodePolyfills } from 'vite-plugin-node-polyfills' 4 | 5 | // https://vitejs.dev/config/ 6 | export default defineConfig({ 7 | plugins: [ 8 | nodePolyfills(), 9 | react(), 10 | ], 11 | }) 12 | -------------------------------------------------------------------------------- /examples/vanilla/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Example (Vanilla) 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /examples/vanilla/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module", 3 | "private": true, 4 | "scripts": { 5 | "build": "vite build", 6 | "dev": "vite", 7 | "test:e2e": "CI=true run-p test:e2e:*", 8 | "test:e2e:build": "VITE_COMMAND=build WEB_SERVER_COMMAND='vite build && vite preview --port 15174' WEB_SERVER_URL='http://localhost:15174' playwright test", 9 | "test:e2e:dev": "VITE_COMMAND=dev WEB_SERVER_COMMAND='vite dev --port 15173' WEB_SERVER_URL='http://localhost:15173' playwright test", 10 | "typecheck": "tsc" 11 | }, 12 | "devDependencies": { 13 | "@types/lodash-es": "^4.17.12", 14 | "@types/node": "^18.18.8", 15 | "lodash-es": "^4.17.21", 16 | "memfs": "^4.6.0", 17 | "npm-run-all": "^4.1.5", 18 | "ohmyfetch": "^0.4.21", 19 | "typescript": "^5.2.2", 20 | "vite": "^4.5.0", 21 | "vite-plugin-node-polyfills": "workspace:*" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /examples/vanilla/playwright.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig, devices } from '@playwright/test' 2 | 3 | const webServerCommand = process.env.WEB_SERVER_COMMAND || 'pnpm dev' 4 | const webServerUrl = process.env.WEB_SERVER_URL || 'http://localhost:5173' 5 | 6 | // https://playwright.dev/docs/test-configuration 7 | export default defineConfig({ 8 | forbidOnly: !!process.env.CI, 9 | fullyParallel: true, 10 | projects: [ 11 | { 12 | name: 'chromium', 13 | use: { ...devices['Desktop Chrome'] }, 14 | }, 15 | ], 16 | reporter: 'html', 17 | retries: process.env.CI ? 2 : 0, 18 | testDir: './test/e2e', 19 | use: { 20 | baseURL: webServerUrl, 21 | trace: 'on-first-retry', 22 | }, 23 | webServer: { 24 | command: webServerCommand, 25 | stdout: 'ignore', 26 | url: webServerUrl, 27 | }, 28 | workers: process.env.CI ? 1 : undefined, 29 | }) 30 | -------------------------------------------------------------------------------- /examples/vanilla/src/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /examples/vanilla/src/main.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | import { Buffer } from 'node:buffer' 3 | import { resolve } from 'node:path' 4 | import process from 'node:process' 5 | import fs, { readFileSync } from 'node:fs' 6 | import { cloneDeep } from 'lodash-es' 7 | import { fetch } from 'ohmyfetch' 8 | 9 | const something = { 10 | some: true, 11 | else: 1, 12 | inner: { 13 | buffer: [0, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5, 6, 7], 14 | }, 15 | } 16 | 17 | class Symbol {} 18 | 19 | fs.writeFileSync('./test.txt', 'Hello from fs!', 'utf-8') 20 | 21 | console.log(Symbol) 22 | console.log(fs) 23 | console.log(fetch) 24 | console.log(resolve('.')) 25 | console.log(process) 26 | console.log(process.env) 27 | console.log(globalThis.Array) 28 | console.log(Buffer.from([0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF]).readBigUInt64BE(0).toString()) 29 | console.log(Buffer.from([0x62, 0x75, 0x66, 0x66, 0x65, 0x72])) 30 | console.log(Array) 31 | console.log(readFileSync('./test.txt', 'utf-8')) 32 | console.log(cloneDeep(something)) 33 | -------------------------------------------------------------------------------- /examples/vanilla/test/e2e/main.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect, test } from '@playwright/test' 2 | import { isBuild, isDev } from '/test/utils' 3 | 4 | test('sets the page title', async ({ page }) => { 5 | await page.goto('/') 6 | 7 | await expect(page).toHaveTitle('Example (Vanilla)') 8 | }) 9 | 10 | test('logs the correct values', async ({ page }) => { 11 | const errors = [] 12 | const logs = [] 13 | 14 | page.on('console', (message) => { 15 | logs.push(message.text()) 16 | }) 17 | 18 | page.on('pageerror', (error) => { 19 | errors.push(error.message) 20 | }) 21 | 22 | await page.goto('/') 23 | 24 | expect(errors).toEqual([]) 25 | 26 | if (isBuild) { 27 | expect(logs).toEqual([ 28 | 'class Symbol {\n }', 29 | '{Volume: , vol: Volume, createFsFromVolume: , fs: Object, memfs: }', 30 | 'function fetch() { [native code] }', 31 | '/', 32 | '{nextTick: , title: browser, browser: true, env: Object, argv: Array(0)}', 33 | '{}', 34 | 'function Array() { [native code] }', 35 | '4294967295', 36 | 'Uint8Array(6)', 37 | 'function Array() { [native code] }', 38 | 'Hello from fs!', 39 | '{some: true, else: 1, inner: Object}', 40 | ]) 41 | } 42 | 43 | if (isDev) { 44 | expect(logs).toEqual([ 45 | '[vite] connecting...', 46 | '[vite] connected.', 47 | 'class Symbol {\n}', 48 | '{Volume: , vol: _Volume, createFsFromVolume: , fs: Object, memfs: }', 49 | 'function fetch() { [native code] }', 50 | '/', 51 | '{nextTick: , title: browser, browser: true, env: Object, argv: Array(0)}', 52 | '{}', 53 | 'function Array() { [native code] }', 54 | '4294967295', 55 | 'Uint8Array(6)', 56 | 'function Array() { [native code] }', 57 | 'Hello from fs!', 58 | '{some: true, else: 1, inner: Object}', 59 | ]) 60 | } 61 | }) 62 | -------------------------------------------------------------------------------- /examples/vanilla/test/utils/index.ts: -------------------------------------------------------------------------------- 1 | export const isBuild = process.env.VITE_COMMAND === 'build' 2 | export const isDev = process.env.VITE_COMMAND === 'dev' 3 | -------------------------------------------------------------------------------- /examples/vanilla/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "module": "ESNext", 5 | "moduleResolution": "Bundler", 6 | "noEmit": true, 7 | "paths": { 8 | "/*": ["./*"] 9 | }, 10 | "skipLibCheck": true 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /examples/vanilla/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { resolve } from 'node:path' 2 | import { defineConfig } from 'vite' 3 | import { nodePolyfills } from 'vite-plugin-node-polyfills' 4 | 5 | // https://vitejs.dev/config/ 6 | export default defineConfig({ 7 | build: { 8 | minify: false, 9 | }, 10 | plugins: [ 11 | nodePolyfills({ 12 | overrides: { 13 | fs: 'memfs', 14 | }, 15 | protocolImports: true, 16 | }), 17 | ], 18 | root: resolve(__dirname), 19 | }) 20 | -------------------------------------------------------------------------------- /examples/vue/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Example (Vue) 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /examples/vue/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module", 3 | "private": true, 4 | "scripts": { 5 | "build": "vite build", 6 | "dev": "vite", 7 | "typecheck": "tsc" 8 | }, 9 | "devDependencies": { 10 | "@types/node": "^18.18.8", 11 | "@vitejs/plugin-vue": "^4.4.0", 12 | "typescript": "^5.2.2", 13 | "vite": "^4.5.0", 14 | "vite-plugin-node-polyfills": "workspace:*", 15 | "vue": "^3.3.7" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /examples/vue/src/app.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue' 2 | import App from './app.vue' 3 | 4 | export const app = () => { 5 | const app = createApp(App) 6 | 7 | app.mount('#vue-app') 8 | } 9 | -------------------------------------------------------------------------------- /examples/vue/src/app.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 19 | -------------------------------------------------------------------------------- /examples/vue/src/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | declare module '*.vue' { 4 | import { DefineComponent } from 'vue' 5 | 6 | const component: DefineComponent<{}, {}, any> 7 | 8 | export default component 9 | } 10 | -------------------------------------------------------------------------------- /examples/vue/src/main.ts: -------------------------------------------------------------------------------- 1 | import { app } from './app' 2 | 3 | app() 4 | -------------------------------------------------------------------------------- /examples/vue/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "jsx": "preserve", 4 | "module": "ESNext", 5 | "moduleResolution": "Bundler", 6 | "noEmit": true, 7 | "skipLibCheck": true 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /examples/vue/vite.config.ts: -------------------------------------------------------------------------------- 1 | import vue from '@vitejs/plugin-vue' 2 | import { defineConfig } from 'vite' 3 | import { nodePolyfills } from 'vite-plugin-node-polyfills' 4 | 5 | // https://vitejs.dev/config/ 6 | export default defineConfig({ 7 | plugins: [ 8 | nodePolyfills(), 9 | vue(), 10 | ], 11 | }) 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vite-plugin-node-polyfills", 3 | "type": "module", 4 | "version": "0.23.0", 5 | "packageManager": "pnpm@9.1.0+sha256.22e36fba7f4880ecf749a5ca128b8435da085ecd49575e7fb9e64d6bf4fad394", 6 | "description": "A Vite plugin to polyfill Node's Core Modules for browser environments.", 7 | "author": "David Myers ", 8 | "license": "MIT", 9 | "funding": "https://github.com/sponsors/davidmyersdev", 10 | "homepage": "https://github.com/davidmyersdev/vite-plugin-node-polyfills", 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/davidmyersdev/vite-plugin-node-polyfills.git" 14 | }, 15 | "keywords": [ 16 | "node", 17 | "node-core-modules", 18 | "node-polyfills", 19 | "node-stdlib", 20 | "polyfills", 21 | "vite", 22 | "vite-plugin" 23 | ], 24 | "exports": { 25 | ".": { 26 | "types": "./dist/index.d.ts", 27 | "require": "./dist/index.cjs", 28 | "import": "./dist/index.js" 29 | }, 30 | "./shims": { 31 | "require": "./shims/dist/index.cjs", 32 | "import": "./shims/dist/index.js" 33 | }, 34 | "./shims/buffer": { 35 | "require": "./shims/buffer/dist/index.cjs", 36 | "import": "./shims/buffer/dist/index.js" 37 | }, 38 | "./shims/buffer/": { 39 | "require": "./shims/buffer/dist/index.cjs", 40 | "import": "./shims/buffer/dist/index.js" 41 | }, 42 | "./shims/global": { 43 | "require": "./shims/global/dist/index.cjs", 44 | "import": "./shims/global/dist/index.js" 45 | }, 46 | "./shims/global/": { 47 | "require": "./shims/global/dist/index.cjs", 48 | "import": "./shims/global/dist/index.js" 49 | }, 50 | "./shims/process": { 51 | "require": "./shims/process/dist/index.cjs", 52 | "import": "./shims/process/dist/index.js" 53 | }, 54 | "./shims/process/": { 55 | "require": "./shims/process/dist/index.cjs", 56 | "import": "./shims/process/dist/index.js" 57 | } 58 | }, 59 | "main": "./dist/index.cjs", 60 | "module": "./dist/index.js", 61 | "types": "./dist/index.d.ts", 62 | "files": [ 63 | "dist", 64 | "shims" 65 | ], 66 | "scripts": { 67 | "build": "run-s build:core build:shims build:types", 68 | "build:core": "vite build", 69 | "build:shims": "run-p build:shims:*", 70 | "build:shims:buffer": "vite build -c ./shims/vite.config.ts ./shims/buffer", 71 | "build:shims:global": "vite build -c ./shims/vite.config.ts ./shims/global", 72 | "build:shims:process": "vite build -c ./shims/vite.config.ts ./shims/process", 73 | "build:types": "run-s typecheck", 74 | "lint": "eslint .", 75 | "lint:fix": "eslint --fix .", 76 | "test": "run-p test:build test:error-repros test:unit", 77 | "test:build": "run-p test:build:*", 78 | "test:build:react": "pnpm -C ./examples/react run build", 79 | "test:build:vanilla": "pnpm -C ./examples/vanilla run build", 80 | "test:build:vue": "pnpm -C ./examples/vue run build", 81 | "test:dev:react": "pnpm -C ./examples/react run dev", 82 | "test:dev:vanilla": "pnpm -C ./examples/vanilla run dev", 83 | "test:dev:vue": "pnpm -C ./examples/vue run dev", 84 | "test:error-repros": "run-p test:error-repros:*", 85 | "test:error-repros:vite-scan-buffer-import-error": "pnpm --filter vite-scan-buffer-import-error test", 86 | "test:unit": "vitest run --dir ./test", 87 | "typecheck": "run-p typecheck:*", 88 | "typecheck:core": "tsc", 89 | "typecheck:react": "pnpm -C ./examples/react run typecheck", 90 | "typecheck:vanilla": "pnpm -C ./examples/vanilla run typecheck", 91 | "typecheck:vue": "pnpm -C ./examples/vue run typecheck" 92 | }, 93 | "peerDependencies": { 94 | "vite": "^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0" 95 | }, 96 | "dependencies": { 97 | "@rollup/plugin-inject": "^5.0.5", 98 | "node-stdlib-browser": "^1.2.0" 99 | }, 100 | "devDependencies": { 101 | "@playwright/test": "^1.40.1", 102 | "@types/node": "^18.18.8", 103 | "buffer": "6.0.3", 104 | "esbuild": "^0.19.8", 105 | "eslint": "^8.54.0", 106 | "eslint-config-artisan": "^0.2.1", 107 | "npm-run-all": "^4.1.5", 108 | "process": "^0.11.10", 109 | "rollup": "^4.6.0", 110 | "typescript": "4.8.3", 111 | "vite": "^5.0.2", 112 | "vite-node": "^0.34.6", 113 | "vite-plugin-externalize-deps": "^0.1.5", 114 | "vite-plugin-inspect": "^0.7.42", 115 | "vite-plugin-node-polyfills": "workspace:*", 116 | "vitest": "^1.2.2" 117 | }, 118 | "publishConfig": { 119 | "access": "public" 120 | }, 121 | "pnpm": { 122 | "overrides": { 123 | "buffer": "6.0.3", 124 | "vite": "^5.0.2" 125 | }, 126 | "patchedDependencies": { 127 | "buffer@6.0.3": "patches/buffer@6.0.3.patch" 128 | } 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /patches/buffer@6.0.3.patch: -------------------------------------------------------------------------------- 1 | diff --git a/index.js b/index.js 2 | index 7a0e9c2a123bc9d26c20bb3de4a3c4e49b24ee40..c9af1ef3ebdab802bd32609bfb87a2545f4d54ec 100644 3 | --- a/index.js 4 | +++ b/index.js 5 | @@ -21,6 +21,7 @@ exports.INSPECT_MAX_BYTES = 50 6 | 7 | const K_MAX_LENGTH = 0x7fffffff 8 | exports.kMaxLength = K_MAX_LENGTH 9 | +const { Uint8Array: GlobalUint8Array, ArrayBuffer: GlobalArrayBuffer, SharedArrayBuffer: GlobalSharedArrayBuffer } = globalThis 10 | 11 | /** 12 | * If `Buffer.TYPED_ARRAY_SUPPORT`: 13 | @@ -49,9 +50,9 @@ if (!Buffer.TYPED_ARRAY_SUPPORT && typeof console !== 'undefined' && 14 | function typedArraySupport () { 15 | // Can typed array instances can be augmented? 16 | try { 17 | - const arr = new Uint8Array(1) 18 | + const arr = new GlobalUint8Array(1) 19 | const proto = { foo: function () { return 42 } } 20 | - Object.setPrototypeOf(proto, Uint8Array.prototype) 21 | + Object.setPrototypeOf(proto, GlobalUint8Array.prototype) 22 | Object.setPrototypeOf(arr, proto) 23 | return arr.foo() === 42 24 | } catch (e) { 25 | @@ -80,7 +81,7 @@ function createBuffer (length) { 26 | throw new RangeError('The value "' + length + '" is invalid for option "size"') 27 | } 28 | // Return an augmented `Uint8Array` instance 29 | - const buf = new Uint8Array(length) 30 | + const buf = new GlobalUint8Array(length) 31 | Object.setPrototypeOf(buf, Buffer.prototype) 32 | return buf 33 | } 34 | @@ -115,7 +116,7 @@ function from (value, encodingOrOffset, length) { 35 | return fromString(value, encodingOrOffset) 36 | } 37 | 38 | - if (ArrayBuffer.isView(value)) { 39 | + if (GlobalArrayBuffer.isView(value)) { 40 | return fromArrayView(value) 41 | } 42 | 43 | @@ -126,14 +127,14 @@ function from (value, encodingOrOffset, length) { 44 | ) 45 | } 46 | 47 | - if (isInstance(value, ArrayBuffer) || 48 | - (value && isInstance(value.buffer, ArrayBuffer))) { 49 | + if (isInstance(value, GlobalArrayBuffer) || 50 | + (value && isInstance(value.buffer, GlobalArrayBuffer))) { 51 | return fromArrayBuffer(value, encodingOrOffset, length) 52 | } 53 | 54 | - if (typeof SharedArrayBuffer !== 'undefined' && 55 | - (isInstance(value, SharedArrayBuffer) || 56 | - (value && isInstance(value.buffer, SharedArrayBuffer)))) { 57 | + if (typeof GlobalSharedArrayBuffer !== 'undefined' && 58 | + (isInstance(value, GlobalSharedArrayBuffer) || 59 | + (value && isInstance(value.buffer, GlobalSharedArrayBuffer)))) { 60 | return fromArrayBuffer(value, encodingOrOffset, length) 61 | } 62 | 63 | @@ -176,8 +177,8 @@ Buffer.from = function (value, encodingOrOffset, length) { 64 | 65 | // Note: Change prototype *after* Buffer.from is defined to workaround Chrome bug: 66 | // https://github.com/feross/buffer/pull/148 67 | -Object.setPrototypeOf(Buffer.prototype, Uint8Array.prototype) 68 | -Object.setPrototypeOf(Buffer, Uint8Array) 69 | +Object.setPrototypeOf(Buffer.prototype, GlobalUint8Array.prototype) 70 | +Object.setPrototypeOf(Buffer, GlobalUint8Array) 71 | 72 | function assertSize (size) { 73 | if (typeof size !== 'number') { 74 | @@ -263,8 +264,8 @@ function fromArrayLike (array) { 75 | } 76 | 77 | function fromArrayView (arrayView) { 78 | - if (isInstance(arrayView, Uint8Array)) { 79 | - const copy = new Uint8Array(arrayView) 80 | + if (isInstance(arrayView, GlobalUint8Array)) { 81 | + const copy = new GlobalUint8Array(arrayView) 82 | return fromArrayBuffer(copy.buffer, copy.byteOffset, copy.byteLength) 83 | } 84 | return fromArrayLike(arrayView) 85 | @@ -281,11 +282,11 @@ function fromArrayBuffer (array, byteOffset, length) { 86 | 87 | let buf 88 | if (byteOffset === undefined && length === undefined) { 89 | - buf = new Uint8Array(array) 90 | + buf = new GlobalUint8Array(array) 91 | } else if (length === undefined) { 92 | - buf = new Uint8Array(array, byteOffset) 93 | + buf = new GlobalUint8Array(array, byteOffset) 94 | } else { 95 | - buf = new Uint8Array(array, byteOffset, length) 96 | + buf = new GlobalUint8Array(array, byteOffset, length) 97 | } 98 | 99 | // Return an augmented `Uint8Array` instance 100 | @@ -342,8 +343,8 @@ Buffer.isBuffer = function isBuffer (b) { 101 | } 102 | 103 | Buffer.compare = function compare (a, b) { 104 | - if (isInstance(a, Uint8Array)) a = Buffer.from(a, a.offset, a.byteLength) 105 | - if (isInstance(b, Uint8Array)) b = Buffer.from(b, b.offset, b.byteLength) 106 | + if (isInstance(a, GlobalUint8Array)) a = Buffer.from(a, a.offset, a.byteLength) 107 | + if (isInstance(b, GlobalUint8Array)) b = Buffer.from(b, b.offset, b.byteLength) 108 | if (!Buffer.isBuffer(a) || !Buffer.isBuffer(b)) { 109 | throw new TypeError( 110 | 'The "buf1", "buf2" arguments must be one of type Buffer or Uint8Array' 111 | @@ -408,12 +409,12 @@ Buffer.concat = function concat (list, length) { 112 | let pos = 0 113 | for (i = 0; i < list.length; ++i) { 114 | let buf = list[i] 115 | - if (isInstance(buf, Uint8Array)) { 116 | + if (isInstance(buf, GlobalUint8Array)) { 117 | if (pos + buf.length > buffer.length) { 118 | if (!Buffer.isBuffer(buf)) buf = Buffer.from(buf) 119 | buf.copy(buffer, pos) 120 | } else { 121 | - Uint8Array.prototype.set.call( 122 | + GlobalUint8Array.prototype.set.call( 123 | buffer, 124 | buf, 125 | pos 126 | @@ -433,7 +434,7 @@ function byteLength (string, encoding) { 127 | if (Buffer.isBuffer(string)) { 128 | return string.length 129 | } 130 | - if (ArrayBuffer.isView(string) || isInstance(string, ArrayBuffer)) { 131 | + if (GlobalArrayBuffer.isView(string) || isInstance(string, GlobalArrayBuffer)) { 132 | return string.byteLength 133 | } 134 | if (typeof string !== 'string') { 135 | @@ -626,7 +627,7 @@ if (customInspectSymbol) { 136 | } 137 | 138 | Buffer.prototype.compare = function compare (target, start, end, thisStart, thisEnd) { 139 | - if (isInstance(target, Uint8Array)) { 140 | + if (isInstance(target, GlobalUint8Array)) { 141 | target = Buffer.from(target, target.offset, target.byteLength) 142 | } 143 | if (!Buffer.isBuffer(target)) { 144 | @@ -742,11 +743,11 @@ function bidirectionalIndexOf (buffer, val, byteOffset, encoding, dir) { 145 | return arrayIndexOf(buffer, val, byteOffset, encoding, dir) 146 | } else if (typeof val === 'number') { 147 | val = val & 0xFF // Search for a byte value [0-255] 148 | - if (typeof Uint8Array.prototype.indexOf === 'function') { 149 | + if (typeof GlobalUint8Array.prototype.indexOf === 'function') { 150 | if (dir) { 151 | - return Uint8Array.prototype.indexOf.call(buffer, val, byteOffset) 152 | + return GlobalUint8Array.prototype.indexOf.call(buffer, val, byteOffset) 153 | } else { 154 | - return Uint8Array.prototype.lastIndexOf.call(buffer, val, byteOffset) 155 | + return GlobalUint8Array.prototype.lastIndexOf.call(buffer, val, byteOffset) 156 | } 157 | } 158 | return arrayIndexOf(buffer, [val], byteOffset, encoding, dir) 159 | @@ -1714,11 +1715,11 @@ Buffer.prototype.copy = function copy (target, targetStart, start, end) { 160 | 161 | const len = end - start 162 | 163 | - if (this === target && typeof Uint8Array.prototype.copyWithin === 'function') { 164 | + if (this === target && typeof GlobalUint8Array.prototype.copyWithin === 'function') { 165 | // Use built-in when available, missing from IE11 166 | this.copyWithin(targetStart, start, end) 167 | } else { 168 | - Uint8Array.prototype.set.call( 169 | + GlobalUint8Array.prototype.set.call( 170 | target, 171 | this.subarray(start, end), 172 | targetStart -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - . 3 | - './examples/*' 4 | - './test/error-repros/*' 5 | -------------------------------------------------------------------------------- /shims/buffer/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Blob, 3 | BlobOptions, 4 | Buffer, 5 | File, 6 | FileOptions, 7 | INSPECT_MAX_BYTES, 8 | // eslint-disable-next-line n/no-deprecated-api 9 | SlowBuffer, 10 | TranscodeEncoding, 11 | atob, 12 | btoa, 13 | constants, 14 | isAscii, 15 | isUtf8, 16 | kMaxLength, 17 | kStringMaxLength, 18 | resolveObjectURL, 19 | transcode, 20 | // eslint-disable-next-line unicorn/prefer-node-protocol 21 | } from 'buffer' 22 | 23 | export { 24 | Blob, 25 | BlobOptions, 26 | Buffer, 27 | File, 28 | FileOptions, 29 | INSPECT_MAX_BYTES, 30 | SlowBuffer, 31 | TranscodeEncoding, 32 | atob, 33 | btoa, 34 | constants, 35 | isAscii, 36 | isUtf8, 37 | kMaxLength, 38 | kStringMaxLength, 39 | resolveObjectURL, 40 | transcode, 41 | } 42 | 43 | export default Buffer 44 | -------------------------------------------------------------------------------- /shims/buffer/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module", 3 | "sideEffects": false, 4 | "exports": { 5 | ".": { 6 | "require": "./dist/index.cjs", 7 | "import": "./dist/index.js" 8 | } 9 | }, 10 | "main": "./dist/index.cjs", 11 | "module": "./dist/index.js", 12 | "browser": "./dist/index.js" 13 | } 14 | -------------------------------------------------------------------------------- /shims/global/index.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line @typescript-eslint/no-invalid-this 2 | const global = globalThis || this || self 3 | 4 | export { global } 5 | export default global 6 | -------------------------------------------------------------------------------- /shims/global/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module", 3 | "sideEffects": false, 4 | "exports": { 5 | ".": { 6 | "require": "./dist/index.cjs", 7 | "import": "./dist/index.js" 8 | } 9 | }, 10 | "main": "./dist/index.cjs", 11 | "module": "./dist/index.js", 12 | "browser": "./dist/index.js" 13 | } 14 | -------------------------------------------------------------------------------- /shims/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module", 3 | "sideEffects": false, 4 | "exports": { 5 | ".": { 6 | "require": "./dist/index.cjs", 7 | "import": "./dist/index.js" 8 | }, 9 | "./buffer": { 10 | "require": "./buffer/dist/index.cjs", 11 | "import": "./buffer/dist/index.js" 12 | }, 13 | "./global": { 14 | "require": "./global/dist/index.cjs", 15 | "import": "./global/dist/index.js" 16 | }, 17 | "./process": { 18 | "require": "./process/dist/index.cjs", 19 | "import": "./process/dist/index.js" 20 | } 21 | }, 22 | "main": "./dist/index.cjs", 23 | "module": "./dist/index.js" 24 | } 25 | -------------------------------------------------------------------------------- /shims/process/index.ts: -------------------------------------------------------------------------------- 1 | // We cannot use `process-polyfill` as the package name due to a bug in Yarn v1. The errors results in a dependency 2 | // conflict with `node-stdlib-browser` which fails to import `process/browser.js`. 3 | // https://github.com/yarnpkg/yarn/issues/6907 4 | // eslint-disable-next-line unicorn/prefer-node-protocol 5 | import process from 'process' 6 | 7 | export { process } 8 | export default process 9 | -------------------------------------------------------------------------------- /shims/process/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module", 3 | "sideEffects": false, 4 | "exports": { 5 | ".": { 6 | "require": "./dist/index.cjs", 7 | "import": "./dist/index.js" 8 | } 9 | }, 10 | "main": "./dist/index.cjs", 11 | "module": "./dist/index.js", 12 | "browser": "./dist/index.js" 13 | } 14 | -------------------------------------------------------------------------------- /shims/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | 3 | // https://vitejs.dev/config/ 4 | export default defineConfig({ 5 | build: { 6 | lib: { 7 | entry: { 8 | index: './index.ts', 9 | }, 10 | }, 11 | minify: false, 12 | rollupOptions: { 13 | external: [/^node:.*$/], 14 | output: [ 15 | { 16 | esModule: true, 17 | exports: 'named', 18 | format: 'es', 19 | }, 20 | { 21 | exports: 'named', 22 | format: 'cjs', 23 | interop: 'auto', 24 | }, 25 | ], 26 | }, 27 | sourcemap: true, 28 | target: 'esnext', 29 | }, 30 | }) 31 | -------------------------------------------------------------------------------- /src/env.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'node-stdlib-browser/helpers/esbuild/plugin' { 2 | import { Plugin } from 'esbuild' 3 | import stdLibBrowser from 'node-stdlib-browser' 4 | 5 | export default function(options: Record): Plugin 6 | } 7 | 8 | declare module 'node-stdlib-browser/helpers/rollup/plugin' { 9 | import type { WarningHandlerWithDefault } from 'rollup' 10 | 11 | export const handleCircularDependancyWarning: WarningHandlerWithDefault 12 | } 13 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { createRequire } from 'node:module' 2 | import inject from '@rollup/plugin-inject' 3 | import stdLibBrowser from 'node-stdlib-browser' 4 | import { handleCircularDependancyWarning } from 'node-stdlib-browser/helpers/rollup/plugin' 5 | import esbuildPlugin from 'node-stdlib-browser/helpers/esbuild/plugin' 6 | import type { Plugin } from 'vite' 7 | import { compareModuleNames, isEnabled, isNodeProtocolImport, toRegExp, withoutNodeProtocol } from './utils' 8 | 9 | export type BuildTarget = 'build' | 'dev' 10 | export type BooleanOrBuildTarget = boolean | BuildTarget 11 | export type ModuleName = keyof typeof stdLibBrowser 12 | export type ModuleNameWithoutNodePrefix = T extends `node:${infer P}` ? P : never 13 | 14 | export type PolyfillOptions = { 15 | /** 16 | * Includes specific modules. If empty, includes all modules 17 | * @example 18 | * 19 | * ```ts 20 | * nodePolyfills({ 21 | * include: ['fs', 'path'], 22 | * }) 23 | * ``` 24 | */ 25 | include?: ModuleNameWithoutNodePrefix[], 26 | /** 27 | * @example 28 | * 29 | * ```ts 30 | * nodePolyfills({ 31 | * exclude: ['fs', 'path'], 32 | * }) 33 | * ``` 34 | */ 35 | exclude?: ModuleNameWithoutNodePrefix[], 36 | /** 37 | * Specify whether specific globals should be polyfilled. 38 | * 39 | * @example 40 | * 41 | * ```ts 42 | * nodePolyfills({ 43 | * globals: { 44 | * Buffer: false, 45 | * global: true, 46 | * process: 'build', 47 | * }, 48 | * }) 49 | * ``` 50 | */ 51 | globals?: { 52 | Buffer?: BooleanOrBuildTarget, 53 | global?: BooleanOrBuildTarget, 54 | process?: BooleanOrBuildTarget, 55 | }, 56 | /** 57 | * Specify alternative modules to use in place of the default polyfills. 58 | * 59 | * @example 60 | * 61 | * ```ts 62 | * nodePolyfills({ 63 | * overrides: { 64 | * fs: 'memfs', 65 | * }, 66 | * }) 67 | * ``` 68 | */ 69 | overrides?: { [Key in ModuleNameWithoutNodePrefix]?: string }, 70 | /** 71 | * Specify whether the Node protocol version of an import (e.g. `node:buffer`) should be polyfilled too. 72 | * 73 | * @default true 74 | */ 75 | protocolImports?: boolean, 76 | } 77 | 78 | export type PolyfillOptionsResolved = { 79 | include: ModuleNameWithoutNodePrefix[], 80 | exclude: ModuleNameWithoutNodePrefix[], 81 | globals: { 82 | Buffer: BooleanOrBuildTarget, 83 | global: BooleanOrBuildTarget, 84 | process: BooleanOrBuildTarget, 85 | }, 86 | overrides: { [Key in ModuleNameWithoutNodePrefix]?: string }, 87 | protocolImports: boolean, 88 | } 89 | 90 | const globalShimBanners = { 91 | buffer: [ 92 | `import __buffer_polyfill from 'vite-plugin-node-polyfills/shims/buffer'`, 93 | `globalThis.Buffer = globalThis.Buffer || __buffer_polyfill`, 94 | ], 95 | global: [ 96 | `import __global_polyfill from 'vite-plugin-node-polyfills/shims/global'`, 97 | `globalThis.global = globalThis.global || __global_polyfill`, 98 | ], 99 | process: [ 100 | `import __process_polyfill from 'vite-plugin-node-polyfills/shims/process'`, 101 | `globalThis.process = globalThis.process || __process_polyfill`, 102 | ], 103 | } 104 | 105 | /** 106 | * Returns a Vite plugin to polyfill Node's Core Modules for browser environments. Supports `node:` protocol imports. 107 | * 108 | * @example 109 | * 110 | * ```ts 111 | * // vite.config.ts 112 | * import { defineConfig } from 'vite' 113 | * import { nodePolyfills } from 'vite-plugin-node-polyfills' 114 | * 115 | * export default defineConfig({ 116 | * plugins: [ 117 | * nodePolyfills({ 118 | * // Specific modules that should not be polyfilled. 119 | * exclude: [], 120 | * // Whether to polyfill specific globals. 121 | * globals: { 122 | * Buffer: true, // can also be 'build', 'dev', or false 123 | * global: true, 124 | * process: true, 125 | * }, 126 | * // Whether to polyfill `node:` protocol imports. 127 | * protocolImports: true, 128 | * }), 129 | * ], 130 | * }) 131 | * ``` 132 | */ 133 | export const nodePolyfills = (options: PolyfillOptions = {}): Plugin => { 134 | const optionsResolved: PolyfillOptionsResolved = { 135 | include: [], 136 | exclude: [], 137 | overrides: {}, 138 | protocolImports: true, 139 | ...options, 140 | globals: { 141 | Buffer: true, 142 | global: true, 143 | process: true, 144 | ...options.globals, 145 | }, 146 | } 147 | 148 | const isExcluded = (moduleName: ModuleName) => { 149 | if (optionsResolved.include.length > 0) { 150 | return !optionsResolved.include.some((includedName) => compareModuleNames(moduleName, includedName)) 151 | } 152 | 153 | return optionsResolved.exclude.some((excludedName) => compareModuleNames(moduleName, excludedName)) 154 | } 155 | 156 | const toOverride = (name: ModuleNameWithoutNodePrefix): string | void => { 157 | if (isEnabled(optionsResolved.globals.Buffer, 'dev') && /^buffer$/.test(name)) { 158 | return 'vite-plugin-node-polyfills/shims/buffer' 159 | } 160 | 161 | if (isEnabled(optionsResolved.globals.global, 'dev') && /^global$/.test(name)) { 162 | return 'vite-plugin-node-polyfills/shims/global' 163 | } 164 | 165 | if (isEnabled(optionsResolved.globals.process, 'dev') && /^process$/.test(name)) { 166 | return 'vite-plugin-node-polyfills/shims/process' 167 | } 168 | 169 | if (name in optionsResolved.overrides) { 170 | return optionsResolved.overrides[name] 171 | } 172 | } 173 | 174 | const polyfills = (Object.entries(stdLibBrowser) as Array<[ModuleName, string]>).reduce>((included, [name, value]) => { 175 | if (!optionsResolved.protocolImports) { 176 | if (isNodeProtocolImport(name)) { 177 | return included 178 | } 179 | } 180 | 181 | if (!isExcluded(name)) { 182 | included[name] = toOverride(withoutNodeProtocol(name)) || value 183 | } 184 | 185 | return included 186 | }, {} as Record) 187 | 188 | const require = createRequire(import.meta.url) 189 | const globalShimPaths = [ 190 | ...((isEnabled(optionsResolved.globals.Buffer, 'dev')) ? [require.resolve('vite-plugin-node-polyfills/shims/buffer')] : []), 191 | ...((isEnabled(optionsResolved.globals.global, 'dev')) ? [require.resolve('vite-plugin-node-polyfills/shims/global')] : []), 192 | ...((isEnabled(optionsResolved.globals.process, 'dev')) ? [require.resolve('vite-plugin-node-polyfills/shims/process')] : []), 193 | ] 194 | 195 | const globalShimsBanner = [ 196 | ...((isEnabled(optionsResolved.globals.Buffer, 'dev')) ? globalShimBanners.buffer : []), 197 | ...((isEnabled(optionsResolved.globals.global, 'dev')) ? globalShimBanners.global : []), 198 | ...((isEnabled(optionsResolved.globals.process, 'dev')) ? globalShimBanners.process : []), 199 | ``, 200 | ].join('\n') 201 | 202 | return { 203 | name: 'vite-plugin-node-polyfills', 204 | config: (config, env) => { 205 | const isDev = env.command === 'serve' 206 | 207 | const shimsToInject = { 208 | // https://github.com/niksy/node-stdlib-browser/blob/3e7cd7f3d115ac5c4593b550e7d8c4a82a0d4ac4/README.md#vite 209 | ...(isEnabled(optionsResolved.globals.Buffer, 'build') ? { Buffer: 'vite-plugin-node-polyfills/shims/buffer' } : {}), 210 | ...(isEnabled(optionsResolved.globals.global, 'build') ? { global: 'vite-plugin-node-polyfills/shims/global' } : {}), 211 | ...(isEnabled(optionsResolved.globals.process, 'build') ? { process: 'vite-plugin-node-polyfills/shims/process' } : {}), 212 | } 213 | 214 | return { 215 | build: { 216 | rollupOptions: { 217 | onwarn: (warning, rollupWarn) => { 218 | handleCircularDependancyWarning(warning, () => { 219 | if (config.build?.rollupOptions?.onwarn) { 220 | return config.build.rollupOptions.onwarn(warning, rollupWarn) 221 | } 222 | 223 | rollupWarn(warning) 224 | }) 225 | }, 226 | plugins: Object.keys(shimsToInject).length > 0 ? [inject(shimsToInject)] : [], 227 | }, 228 | }, 229 | esbuild: { 230 | // In dev, the global polyfills need to be injected as a banner in order for isolated scripts (such as Vue SFCs) to have access to them. 231 | banner: isDev ? globalShimsBanner : undefined, 232 | }, 233 | optimizeDeps: { 234 | exclude: [ 235 | ...globalShimPaths, 236 | ], 237 | esbuildOptions: { 238 | banner: isDev ? { js: globalShimsBanner } : undefined, 239 | // https://github.com/niksy/node-stdlib-browser/blob/3e7cd7f3d115ac5c4593b550e7d8c4a82a0d4ac4/README.md?plain=1#L203-L209 240 | define: { 241 | ...((isDev && isEnabled(optionsResolved.globals.Buffer, 'dev')) ? { Buffer: 'Buffer' } : {}), 242 | ...((isDev && isEnabled(optionsResolved.globals.global, 'dev')) ? { global: 'global' } : {}), 243 | ...((isDev && isEnabled(optionsResolved.globals.process, 'dev')) ? { process: 'process' } : {}), 244 | }, 245 | inject: [ 246 | ...globalShimPaths, 247 | ], 248 | plugins: [ 249 | esbuildPlugin(polyfills), 250 | // Supress the 'injected path "..." cannot be marked as external' error in Vite 4 (emitted by esbuild). 251 | // https://github.com/evanw/esbuild/blob/edede3c49ad6adddc6ea5b3c78c6ea7507e03020/internal/bundler/bundler.go#L1469 252 | { 253 | name: 'vite-plugin-node-polyfills-shims-resolver', 254 | setup(build) { 255 | for (const globalShimPath of globalShimPaths) { 256 | const globalShimsFilter = toRegExp(globalShimPath) 257 | 258 | // https://esbuild.github.io/plugins/#on-resolve 259 | build.onResolve({ filter: globalShimsFilter }, () => { 260 | return { 261 | // https://github.com/evanw/esbuild/blob/edede3c49ad6adddc6ea5b3c78c6ea7507e03020/internal/bundler/bundler.go#L1468 262 | external: false, 263 | path: globalShimPath, 264 | } 265 | }) 266 | } 267 | }, 268 | }, 269 | ], 270 | }, 271 | }, 272 | resolve: { 273 | // https://github.com/niksy/node-stdlib-browser/blob/3e7cd7f3d115ac5c4593b550e7d8c4a82a0d4ac4/README.md?plain=1#L150 274 | alias: { 275 | ...polyfills, 276 | }, 277 | }, 278 | } 279 | }, 280 | } 281 | } 282 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | import type { BooleanOrBuildTarget, ModuleName, ModuleNameWithoutNodePrefix } from './index' 2 | 3 | export const compareModuleNames = (moduleA: ModuleName, moduleB: ModuleName) => { 4 | return withoutNodeProtocol(moduleA) === withoutNodeProtocol(moduleB) 5 | } 6 | 7 | export const isEnabled = (value: BooleanOrBuildTarget, mode: 'build' | 'dev') => { 8 | if (!value) return false 9 | if (value === true) return true 10 | 11 | return value === mode 12 | } 13 | 14 | export const isNodeProtocolImport = (name: string) => { 15 | return name.startsWith('node:') 16 | } 17 | 18 | export const toRegExp = (text: string) => { 19 | // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#escaping 20 | const escapedText = text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') 21 | 22 | return new RegExp(`^${escapedText}$`) 23 | } 24 | 25 | export const withoutNodeProtocol = (name: ModuleName): ModuleNameWithoutNodePrefix => { 26 | return name.replace(/^node:/, '') as ModuleNameWithoutNodePrefix 27 | } 28 | -------------------------------------------------------------------------------- /test/error-repros/missing-zlib-imports/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | missing-zlib-imports 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /test/error-repros/missing-zlib-imports/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "missing-zlib-imports", 3 | "type": "module", 4 | "version": "0.0.0", 5 | "private": true, 6 | "scripts": { 7 | "build": "vite build", 8 | "dev": "vite", 9 | "test:e2e": "CI=true run-p test:e2e:*", 10 | "test:e2e:build": "VITE_COMMAND=build WEB_SERVER_COMMAND='vite build && vite preview --port 15176' WEB_SERVER_URL='http://localhost:15176' playwright test", 11 | "test:e2e:dev": "VITE_COMMAND=dev WEB_SERVER_COMMAND='vite dev --port 15175' WEB_SERVER_URL='http://localhost:15175' playwright test" 12 | }, 13 | "dependencies": { 14 | "fast-zlib": "^2.0.1", 15 | "vite-plugin-node-polyfills": "workspace:*" 16 | }, 17 | "devDependencies": { 18 | "vite": "^5.1.1" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /test/error-repros/missing-zlib-imports/playwright.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig, devices } from '@playwright/test' 2 | 3 | const webServerCommand = process.env.WEB_SERVER_COMMAND || 'pnpm dev' 4 | const webServerUrl = process.env.WEB_SERVER_URL || 'http://localhost:5173' 5 | 6 | // https://playwright.dev/docs/test-configuration 7 | export default defineConfig({ 8 | forbidOnly: !!process.env.CI, 9 | fullyParallel: true, 10 | projects: [ 11 | { 12 | name: 'chromium', 13 | use: { ...devices['Desktop Chrome'] }, 14 | }, 15 | ], 16 | reporter: 'html', 17 | retries: process.env.CI ? 2 : 0, 18 | testDir: './test/e2e', 19 | use: { 20 | baseURL: webServerUrl, 21 | trace: 'on-first-retry', 22 | }, 23 | webServer: { 24 | command: webServerCommand, 25 | stdout: 'ignore', 26 | url: webServerUrl, 27 | }, 28 | workers: process.env.CI ? 1 : undefined, 29 | }) 30 | -------------------------------------------------------------------------------- /test/error-repros/missing-zlib-imports/src/index.ts: -------------------------------------------------------------------------------- 1 | import zlib from 'fast-zlib' 2 | 3 | // eslint-disable-next-line no-console 4 | console.log(Object.keys(zlib)) 5 | -------------------------------------------------------------------------------- /test/error-repros/missing-zlib-imports/test/e2e/main.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect, test } from '@playwright/test' 2 | 3 | test('sets the page title', async ({ page }) => { 4 | await page.goto('/') 5 | 6 | await expect(page).toHaveTitle('missing-zlib-imports') 7 | }) 8 | 9 | test('logs the correct values', async ({ page }) => { 10 | const errors = [] 11 | const logs = [] 12 | 13 | page.on('console', (message) => { 14 | logs.push(message.text()) 15 | }) 16 | 17 | page.on('pageerror', (error) => { 18 | errors.push(error.message) 19 | }) 20 | 21 | await page.goto('/') 22 | 23 | expect(errors).toEqual([]) 24 | expect(logs).toContainEqual( 25 | '[Inflate, Deflate, InflateRaw, DeflateRaw, Gzip, Gunzip, Unzip, BrotliCompress, BrotliDecompress, constants, default]', 26 | ) 27 | }) 28 | -------------------------------------------------------------------------------- /test/error-repros/missing-zlib-imports/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import { nodePolyfills } from 'vite-plugin-node-polyfills' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | optimizeDeps: { 7 | force: true, 8 | }, 9 | plugins: [ 10 | nodePolyfills(), 11 | ], 12 | }) 13 | -------------------------------------------------------------------------------- /test/error-repros/process-disabled/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Example (Vanilla) 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /test/error-repros/process-disabled/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module", 3 | "private": true, 4 | "scripts": { 5 | "build": "vite build", 6 | "dev": "vite", 7 | "test:e2e": "CI=true run-p test:e2e:*", 8 | "test:e2e:build": "VITE_COMMAND=build WEB_SERVER_COMMAND='vite build && vite preview --port 15178' WEB_SERVER_URL='http://localhost:15178' playwright test", 9 | "test:e2e:dev": "VITE_COMMAND=dev WEB_SERVER_COMMAND='vite dev --port 15177' WEB_SERVER_URL='http://localhost:15177' playwright test", 10 | "typecheck": "tsc" 11 | }, 12 | "devDependencies": { 13 | "@types/lodash-es": "^4.17.12", 14 | "@types/node": "^18.18.8", 15 | "lodash-es": "^4.17.21", 16 | "memfs": "^4.6.0", 17 | "npm-run-all": "^4.1.5", 18 | "ohmyfetch": "^0.4.21", 19 | "typescript": "^5.2.2", 20 | "vite": "^4.5.0", 21 | "vite-plugin-node-polyfills": "workspace:*" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /test/error-repros/process-disabled/playwright.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig, devices } from '@playwright/test' 2 | 3 | const webServerCommand = process.env.WEB_SERVER_COMMAND || 'pnpm dev' 4 | const webServerUrl = process.env.WEB_SERVER_URL || 'http://localhost:5173' 5 | 6 | // https://playwright.dev/docs/test-configuration 7 | export default defineConfig({ 8 | forbidOnly: !!process.env.CI, 9 | fullyParallel: true, 10 | projects: [ 11 | { 12 | name: 'chromium', 13 | use: { ...devices['Desktop Chrome'] }, 14 | }, 15 | ], 16 | reporter: 'html', 17 | retries: process.env.CI ? 2 : 0, 18 | testDir: './test/e2e', 19 | use: { 20 | baseURL: webServerUrl, 21 | trace: 'on-first-retry', 22 | }, 23 | webServer: { 24 | command: webServerCommand, 25 | stdout: 'ignore', 26 | url: webServerUrl, 27 | }, 28 | workers: process.env.CI ? 1 : undefined, 29 | }) 30 | -------------------------------------------------------------------------------- /test/error-repros/process-disabled/src/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /test/error-repros/process-disabled/src/main.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | console.log(process) 3 | console.log(process.env) 4 | -------------------------------------------------------------------------------- /test/error-repros/process-disabled/test/e2e/main.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect, test } from '@playwright/test' 2 | 3 | test('sets the page title', async ({ page }) => { 4 | await page.goto('/') 5 | 6 | await expect(page).toHaveTitle('Example (Vanilla)') 7 | }) 8 | 9 | test('logs the correct values', async ({ page }) => { 10 | const errors = [] 11 | const logs = [] 12 | 13 | page.on('console', (message) => { 14 | if (/^\[vite\]/.test(message.text())) { 15 | return 16 | } 17 | 18 | logs.push(message.text()) 19 | }) 20 | 21 | page.on('pageerror', (error) => { 22 | errors.push(error.message) 23 | }) 24 | 25 | await page.goto('/') 26 | 27 | expect(errors).toEqual([ 28 | 'process is not defined', 29 | ]) 30 | 31 | expect(logs).toEqual([]) 32 | }) 33 | -------------------------------------------------------------------------------- /test/error-repros/process-disabled/test/utils/index.ts: -------------------------------------------------------------------------------- 1 | export const isBuild = process.env.VITE_COMMAND === 'build' 2 | export const isDev = process.env.VITE_COMMAND === 'dev' 3 | -------------------------------------------------------------------------------- /test/error-repros/process-disabled/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "module": "ESNext", 5 | "moduleResolution": "Bundler", 6 | "noEmit": true, 7 | "paths": { 8 | "/*": ["./*"] 9 | }, 10 | "skipLibCheck": true 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /test/error-repros/process-disabled/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { resolve } from 'node:path' 2 | import { defineConfig } from 'vite' 3 | import { nodePolyfills } from 'vite-plugin-node-polyfills' 4 | 5 | // https://vitejs.dev/config/ 6 | export default defineConfig({ 7 | build: { 8 | minify: false, 9 | }, 10 | plugins: [ 11 | nodePolyfills({ 12 | globals: { 13 | process: false, 14 | }, 15 | protocolImports: true, 16 | }), 17 | ], 18 | root: resolve(__dirname), 19 | }) 20 | -------------------------------------------------------------------------------- /test/error-repros/vite-scan-buffer-import-error/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | vite-scan-buffer-import-error 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /test/error-repros/vite-scan-buffer-import-error/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vite-scan-buffer-import-error", 3 | "type": "module", 4 | "private": true, 5 | "scripts": { 6 | "test": "vite optimize" 7 | }, 8 | "devDependencies": { 9 | "vite-plugin-node-polyfills": "workspace:*", 10 | "vite@5.1.0": "npm:vite@5.1.0" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /test/error-repros/vite-scan-buffer-import-error/test.ts: -------------------------------------------------------------------------------- 1 | import { Buffer } from 'node:buffer' 2 | 3 | // eslint-disable-next-line no-console 4 | console.log(Buffer.from('hello').toString()) 5 | -------------------------------------------------------------------------------- /test/error-repros/vite-scan-buffer-import-error/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { nodePolyfills } from 'vite-plugin-node-polyfills' 2 | 3 | export default { 4 | optimizeDeps: { 5 | force: true, 6 | }, 7 | plugins: [ 8 | nodePolyfills(), 9 | ], 10 | } 11 | -------------------------------------------------------------------------------- /test/integration/global-references/index.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest' 2 | import { formatWhitespace, transformDev } from '../../../test/utils' 3 | 4 | describe('import globals', () => { 5 | describe('buffer', () => { 6 | it('injects Buffer', async () => { 7 | const result = await transformDev(`Buffer.from('test')`) 8 | 9 | expect(result?.code).toEqual(formatWhitespace(` 10 | import __buffer_polyfill from "/shims/buffer/dist/index.js" 11 | globalThis.Buffer = globalThis.Buffer || __buffer_polyfill 12 | import __global_polyfill from "/shims/global/dist/index.js" 13 | globalThis.global = globalThis.global || __global_polyfill 14 | import __process_polyfill from "/shims/process/dist/index.js" 15 | globalThis.process = globalThis.process || __process_polyfill 16 | 17 | Buffer.from("test"); 18 | `)) 19 | }) 20 | 21 | it('injects Buffer only', async () => { 22 | const result = await transformDev(`Buffer.from('test')`, { 23 | globals: { 24 | Buffer: true, 25 | global: false, 26 | process: false, 27 | }, 28 | }) 29 | 30 | expect(result?.code).toEqual(formatWhitespace(` 31 | import __buffer_polyfill from "/shims/buffer/dist/index.js" 32 | globalThis.Buffer = globalThis.Buffer || __buffer_polyfill 33 | 34 | Buffer.from("test"); 35 | `)) 36 | }) 37 | }) 38 | 39 | describe('global', () => { 40 | it('injects global', async () => { 41 | const result = await transformDev(`console.log(global)`) 42 | 43 | expect(result?.code).toEqual(formatWhitespace(` 44 | import __buffer_polyfill from "/shims/buffer/dist/index.js" 45 | globalThis.Buffer = globalThis.Buffer || __buffer_polyfill 46 | import __global_polyfill from "/shims/global/dist/index.js" 47 | globalThis.global = globalThis.global || __global_polyfill 48 | import __process_polyfill from "/shims/process/dist/index.js" 49 | globalThis.process = globalThis.process || __process_polyfill 50 | 51 | console.log(global); 52 | `)) 53 | }) 54 | 55 | it('injects global only', async () => { 56 | const result = await transformDev(`console.log(global)`, { 57 | globals: { 58 | Buffer: false, 59 | global: true, 60 | process: false, 61 | }, 62 | }) 63 | 64 | expect(result?.code).toEqual(formatWhitespace(` 65 | import __global_polyfill from "/shims/global/dist/index.js" 66 | globalThis.global = globalThis.global || __global_polyfill 67 | 68 | console.log(global); 69 | `)) 70 | }) 71 | }) 72 | 73 | describe('process', () => { 74 | it('injects process', async () => { 75 | const result = await transformDev(`console.log(process)`) 76 | 77 | expect(result?.code).toEqual(formatWhitespace(` 78 | import __buffer_polyfill from "/shims/buffer/dist/index.js" 79 | globalThis.Buffer = globalThis.Buffer || __buffer_polyfill 80 | import __global_polyfill from "/shims/global/dist/index.js" 81 | globalThis.global = globalThis.global || __global_polyfill 82 | import __process_polyfill from "/shims/process/dist/index.js" 83 | globalThis.process = globalThis.process || __process_polyfill 84 | 85 | console.log(process); 86 | `)) 87 | }) 88 | 89 | it('injects process only', async () => { 90 | const result = await transformDev(`console.log(process)`, { 91 | globals: { 92 | Buffer: false, 93 | global: false, 94 | process: true, 95 | }, 96 | }) 97 | 98 | expect(result?.code).toEqual(formatWhitespace(` 99 | import __process_polyfill from "/shims/process/dist/index.js" 100 | globalThis.process = globalThis.process || __process_polyfill 101 | 102 | console.log(process); 103 | `)) 104 | }) 105 | }) 106 | }) 107 | -------------------------------------------------------------------------------- /test/integration/import-globals/index.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest' 2 | import { formatWhitespace, transformDev } from '../../../test/utils' 3 | 4 | describe('import globals', () => { 5 | describe('buffer', () => { 6 | it('resolves normally', async () => { 7 | const result = await transformDev(` 8 | import Buffer from 'buffer' 9 | console.log(Buffer) 10 | `) 11 | 12 | expect(result?.code).toEqual(formatWhitespace(` 13 | import __buffer_polyfill from "/shims/buffer/dist/index.js" 14 | globalThis.Buffer = globalThis.Buffer || __buffer_polyfill 15 | import __global_polyfill from "/shims/global/dist/index.js" 16 | globalThis.global = globalThis.global || __global_polyfill 17 | import __process_polyfill from "/shims/process/dist/index.js" 18 | globalThis.process = globalThis.process || __process_polyfill 19 | 20 | import Buffer from "/shims/buffer/dist/index.js"; 21 | console.log(Buffer); 22 | `)) 23 | }) 24 | 25 | it('resolves with a `node:` prefix', async () => { 26 | const result = await transformDev(` 27 | import Buffer from 'node:buffer' 28 | console.log(Buffer) 29 | `) 30 | 31 | expect(result?.code).toEqual(formatWhitespace(` 32 | import __buffer_polyfill from "/shims/buffer/dist/index.js" 33 | globalThis.Buffer = globalThis.Buffer || __buffer_polyfill 34 | import __global_polyfill from "/shims/global/dist/index.js" 35 | globalThis.global = globalThis.global || __global_polyfill 36 | import __process_polyfill from "/shims/process/dist/index.js" 37 | globalThis.process = globalThis.process || __process_polyfill 38 | 39 | import Buffer from "/shims/buffer/dist/index.js"; 40 | console.log(Buffer); 41 | `)) 42 | }) 43 | 44 | it('resolves with a trailing slash', async () => { 45 | const result = await transformDev(` 46 | import Buffer from 'buffer/' 47 | console.log(Buffer) 48 | `) 49 | 50 | expect(result?.code).toEqual(formatWhitespace(` 51 | import __buffer_polyfill from "/shims/buffer/dist/index.js" 52 | globalThis.Buffer = globalThis.Buffer || __buffer_polyfill 53 | import __global_polyfill from "/shims/global/dist/index.js" 54 | globalThis.global = globalThis.global || __global_polyfill 55 | import __process_polyfill from "/shims/process/dist/index.js" 56 | globalThis.process = globalThis.process || __process_polyfill 57 | 58 | import Buffer from "/shims/buffer/dist/index.js"; 59 | console.log(Buffer); 60 | `)) 61 | }) 62 | 63 | it('resolves with a `node:` prefix and a trailing slash', async () => { 64 | const result = await transformDev(` 65 | import Buffer from 'node:buffer/' 66 | console.log(Buffer) 67 | `) 68 | 69 | expect(result?.code).toEqual(formatWhitespace(` 70 | import __buffer_polyfill from "/shims/buffer/dist/index.js" 71 | globalThis.Buffer = globalThis.Buffer || __buffer_polyfill 72 | import __global_polyfill from "/shims/global/dist/index.js" 73 | globalThis.global = globalThis.global || __global_polyfill 74 | import __process_polyfill from "/shims/process/dist/index.js" 75 | globalThis.process = globalThis.process || __process_polyfill 76 | 77 | import Buffer from "/shims/buffer/dist/index.js"; 78 | console.log(Buffer); 79 | `)) 80 | }) 81 | }) 82 | 83 | describe('process', () => { 84 | it('resolves normally', async () => { 85 | const result = await transformDev(` 86 | import process from 'process' 87 | console.log(process) 88 | `) 89 | 90 | expect(result?.code).toEqual(formatWhitespace(` 91 | import __buffer_polyfill from "/shims/buffer/dist/index.js" 92 | globalThis.Buffer = globalThis.Buffer || __buffer_polyfill 93 | import __global_polyfill from "/shims/global/dist/index.js" 94 | globalThis.global = globalThis.global || __global_polyfill 95 | import __process_polyfill from "/shims/process/dist/index.js" 96 | globalThis.process = globalThis.process || __process_polyfill 97 | 98 | import process from "/shims/process/dist/index.js"; 99 | console.log(process); 100 | `)) 101 | }) 102 | 103 | it('resolves with a `node:` prefix', async () => { 104 | const result = await transformDev(` 105 | import process from 'node:process' 106 | console.log(process) 107 | `) 108 | 109 | expect(result?.code).toEqual(formatWhitespace(` 110 | import __buffer_polyfill from "/shims/buffer/dist/index.js" 111 | globalThis.Buffer = globalThis.Buffer || __buffer_polyfill 112 | import __global_polyfill from "/shims/global/dist/index.js" 113 | globalThis.global = globalThis.global || __global_polyfill 114 | import __process_polyfill from "/shims/process/dist/index.js" 115 | globalThis.process = globalThis.process || __process_polyfill 116 | 117 | import process from "/shims/process/dist/index.js"; 118 | console.log(process); 119 | `)) 120 | }) 121 | 122 | it('resolves with a trailing slash', async () => { 123 | const result = await transformDev(` 124 | import process from 'process/' 125 | console.log(process) 126 | `) 127 | 128 | expect(result?.code).toEqual(formatWhitespace(` 129 | import __buffer_polyfill from "/shims/buffer/dist/index.js" 130 | globalThis.Buffer = globalThis.Buffer || __buffer_polyfill 131 | import __global_polyfill from "/shims/global/dist/index.js" 132 | globalThis.global = globalThis.global || __global_polyfill 133 | import __process_polyfill from "/shims/process/dist/index.js" 134 | globalThis.process = globalThis.process || __process_polyfill 135 | 136 | import process from "/shims/process/dist/index.js"; 137 | console.log(process); 138 | `)) 139 | }) 140 | 141 | it('resolves with a `node:` prefix and a trailing slash', async () => { 142 | const result = await transformDev(` 143 | import process from 'node:process/' 144 | console.log(process) 145 | `) 146 | 147 | expect(result?.code).toEqual(formatWhitespace(` 148 | import __buffer_polyfill from "/shims/buffer/dist/index.js" 149 | globalThis.Buffer = globalThis.Buffer || __buffer_polyfill 150 | import __global_polyfill from "/shims/global/dist/index.js" 151 | globalThis.global = globalThis.global || __global_polyfill 152 | import __process_polyfill from "/shims/process/dist/index.js" 153 | globalThis.process = globalThis.process || __process_polyfill 154 | 155 | import process from "/shims/process/dist/index.js"; 156 | console.log(process); 157 | `)) 158 | }) 159 | }) 160 | }) 161 | -------------------------------------------------------------------------------- /test/utils/index.ts: -------------------------------------------------------------------------------- 1 | import { createServer } from 'vite' 2 | import type { PolyfillOptions } from 'vite-plugin-node-polyfills' 3 | import { nodePolyfills } from 'vite-plugin-node-polyfills' 4 | 5 | /** 6 | * Format code by removing the smallest indentation from each line. 7 | */ 8 | export const formatWhitespace = (code: string) => { 9 | const lines = code.split('\n') 10 | const smallestIndentation = lines.reduce((currentIndentation, line) => { 11 | if (line.trim() === '') { 12 | return currentIndentation 13 | } 14 | 15 | const lineIndentation = line.match(/^\s*/)?.at(0)?.length ?? 0 16 | 17 | if (currentIndentation < 0) { 18 | return lineIndentation 19 | } 20 | 21 | return Math.min(currentIndentation, lineIndentation) 22 | }, -1) 23 | 24 | const formatted = lines.map((line) => line.slice(smallestIndentation)).join('\n').trim() 25 | 26 | return `${formatted}\n` 27 | } 28 | 29 | export const transformDev = async (code: string, options?: PolyfillOptions) => { 30 | const server = await createServer({ 31 | configFile: false, 32 | esbuild: { 33 | format: 'esm', 34 | }, 35 | plugins: [ 36 | nodePolyfills(options), 37 | { 38 | name: 'test-code-loader', 39 | load(id) { 40 | if (id === 'virtual:test.ts') { 41 | return code 42 | } 43 | }, 44 | }, 45 | ], 46 | }) 47 | 48 | try { 49 | return await server.transformRequest('virtual:test.ts') 50 | } finally { 51 | await server.close() 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "alwaysStrict": true, 4 | "declaration": true, 5 | "emitDeclarationOnly": true, 6 | "esModuleInterop": true, 7 | "module": "ESNext", 8 | "moduleResolution": "Node", 9 | "noImplicitReturns": false, 10 | "noUnusedLocals": true, 11 | "noUnusedParameters": true, 12 | "outDir": "./dist", 13 | "resolveJsonModule": true, 14 | "skipLibCheck": true, 15 | "sourceMap": true, 16 | "strict": true, 17 | "target": "ESNext", 18 | "useDefineForClassFields": true 19 | }, 20 | "include": ["./src/**/*"] 21 | } 22 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import { externalizeDeps } from 'vite-plugin-externalize-deps' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | build: { 7 | lib: { 8 | entry: './src/index.ts', 9 | fileName: 'index', 10 | }, 11 | rollupOptions: { 12 | external: [/^node:.*$/], 13 | output: [ 14 | { 15 | esModule: true, 16 | exports: 'named', 17 | format: 'es', 18 | }, 19 | { 20 | exports: 'named', 21 | format: 'cjs', 22 | inlineDynamicImports: true, 23 | interop: 'auto', 24 | }, 25 | ], 26 | }, 27 | sourcemap: true, 28 | target: 'esnext', 29 | }, 30 | plugins: [ 31 | externalizeDeps(), 32 | ], 33 | }) 34 | -------------------------------------------------------------------------------- /vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vitest/config' 2 | 3 | export default defineConfig({ 4 | test: { 5 | include: ['**/*.test.ts'], 6 | }, 7 | }) 8 | --------------------------------------------------------------------------------