├── .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 | [](https://voracious.link/sponsor)
2 | [](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 | setCount((count) => count + 1)}>
18 | count is {count}
19 |
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 |
17 | {{ msg }}
18 |
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 |
--------------------------------------------------------------------------------