├── .editorconfig
├── .gitattributes
├── .github
├── FUNDING.yml
├── renovate.json5
└── workflows
│ ├── release.yml
│ └── unit-test.yml
├── .gitignore
├── .vscode
└── settings.json
├── LICENSE
├── README.md
├── eslint.config.js
├── jsr.json
├── package.json
├── pnpm-lock.yaml
├── src
├── core
│ └── options.ts
├── esbuild.ts
├── farm.ts
├── index.ts
├── rolldown.ts
├── rollup.ts
├── rspack.ts
├── vite.ts
└── webpack.ts
├── tests
├── __snapshots__
│ ├── esbuild.test.ts.snap
│ ├── rolldown.test.ts.snap
│ ├── rollup.test.ts.snap
│ ├── vite.test.ts.snap
│ └── webpack.test.ts.snap
├── esbuild.test.ts
├── fixtures
│ └── main.js
├── rolldown.test.ts
├── rollup.test.ts
├── vite.test.ts
└── webpack.test.ts
├── tsconfig.json
└── tsdown.config.ts
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_size = 2
5 | end_of_line = lf
6 | insert_final_newline = true
7 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto eol=lf
2 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: sxzz
2 |
--------------------------------------------------------------------------------
/.github/renovate.json5:
--------------------------------------------------------------------------------
1 | {
2 | extends: ['github>sxzz/renovate-config'],
3 | automerge: true,
4 | }
5 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Release
2 |
3 | permissions:
4 | contents: write
5 |
6 | on:
7 | push:
8 | tags:
9 | - 'v*'
10 |
11 | jobs:
12 | release:
13 | runs-on: ubuntu-latest
14 | steps:
15 | - uses: actions/checkout@v4
16 | with:
17 | fetch-depth: 0
18 |
19 | - name: Set node
20 | uses: actions/setup-node@v4
21 | with:
22 | node-version: lts/*
23 |
24 | - run: npx changelogithub
25 | env:
26 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
27 |
28 | publish-jsr:
29 | runs-on: ubuntu-latest
30 |
31 | permissions:
32 | contents: read
33 | id-token: write
34 |
35 | steps:
36 | - uses: actions/checkout@v4
37 |
38 | - name: Install pnpm
39 | uses: pnpm/action-setup@v4.1.0
40 |
41 | - name: Set node
42 | uses: actions/setup-node@v4
43 | with:
44 | node-version: lts/*
45 | cache: pnpm
46 |
47 | - name: Install
48 | run: pnpm i
49 |
50 | - name: Publish package
51 | run: npx jsr publish
52 |
--------------------------------------------------------------------------------
/.github/workflows/unit-test.yml:
--------------------------------------------------------------------------------
1 | name: Unit Test
2 |
3 | on:
4 | push:
5 | branches: [main]
6 | pull_request:
7 | branches: [main]
8 |
9 | jobs:
10 | lint:
11 | runs-on: ubuntu-latest
12 | steps:
13 | - name: Checkout
14 | uses: actions/checkout@v4
15 |
16 | - name: Install pnpm
17 | uses: pnpm/action-setup@v4.1.0
18 |
19 | - name: Set node
20 | uses: actions/setup-node@v4
21 | with:
22 | node-version: lts/*
23 | cache: pnpm
24 |
25 | - name: Install
26 | run: pnpm i
27 |
28 | - name: Lint
29 | run: pnpm lint
30 |
31 | - name: Typecheck
32 | run: pnpm typecheck
33 |
34 | test:
35 | runs-on: ${{ matrix.os }}
36 |
37 | strategy:
38 | matrix:
39 | os: [ubuntu-latest, windows-latest]
40 | node: [18, 20, 22]
41 | fail-fast: false
42 |
43 | steps:
44 | - name: Checkout
45 | uses: actions/checkout@v4
46 |
47 | - name: Install pnpm
48 | uses: pnpm/action-setup@v4.1.0
49 |
50 | - name: Set node ${{ matrix.node }}
51 | uses: actions/setup-node@v4
52 | with:
53 | node-version: ${{ matrix.node }}
54 | cache: pnpm
55 |
56 | - name: Install
57 | run: pnpm i
58 |
59 | - name: Build
60 | run: pnpm run build
61 |
62 | - name: Test
63 | run: pnpm run test
64 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .DS_Store
3 | dist
4 | *.log
5 | .vercel
6 | .eslintcache
7 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "editor.formatOnSave": true,
3 | "eslint.experimental.useFlatConfig": true
4 | }
5 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright © 2024-PRESENT 三咲智子 (https://github.com/sxzz)
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 | # unplugin-replace [](https://npmjs.com/package/unplugin-replace) [](https://jsr.io/@unplugin/replace)
2 |
3 | [](https://github.com/unplugin/unplugin-replace/actions/workflows/unit-test.yml)
4 |
5 | 🍣 A universal bundler plugin which replaces targeted strings in files, based on [@rollup/plugin-replace](https://www.npmjs.com/package/@rollup/plugin-replace).
6 |
7 | ## Installation
8 |
9 | ```bash
10 | # npm
11 | npm i -D unplugin-replace
12 |
13 | # jsr
14 | npx jsr add -D @unplugin/replace
15 | ```
16 |
17 |
18 | Vite
19 |
20 | ```ts
21 | // vite.config.ts
22 | import Replace from 'unplugin-replace/vite'
23 |
24 | export default defineConfig({
25 | plugins: [Replace()],
26 | })
27 | ```
28 |
29 |
30 |
31 |
32 | Rollup
33 |
34 | ```ts
35 | // rollup.config.js
36 | import Replace from 'unplugin-replace/rollup'
37 |
38 | export default {
39 | plugins: [Replace()],
40 | }
41 | ```
42 |
43 |
44 |
45 |
46 | esbuild
47 |
48 | ```ts
49 | // esbuild.config.js
50 | import { build } from 'esbuild'
51 |
52 | build({
53 | plugins: [require('unplugin-replace/esbuild')()],
54 | })
55 | ```
56 |
57 |
58 |
59 |
60 | Webpack
61 |
62 | ```ts
63 | // webpack.config.js
64 | module.exports = {
65 | /* ... */
66 | plugins: [require('unplugin-replace/webpack')()],
67 | }
68 | ```
69 |
70 |
71 |
72 | ## Usage
73 |
74 | ### Options
75 |
76 | For all options please refer to [docs](https://github.com/rollup/plugins/tree/master/packages/replace#options).
77 |
78 | This plugin accepts all [@rollup/plugin-replace](https://github.com/rollup/plugins/tree/master/packages/replace#options) options, and some extra options that are specific to this plugin.
79 |
80 | ### `options.values`
81 |
82 | - Type: `{ [find: string]: Replacement } | ReplaceItem[]`
83 | - Default: `[]`
84 |
85 | ```ts
86 | type ReplaceItem = {
87 | /** Supply a string or RegExp to find what you are looking for. */
88 | find: string | RegExp
89 |
90 | /**
91 | * Can be a string or a function.
92 | * - If it's a string, it will replace the substring matched by pattern. A number of special replacement patterns are supported
93 | * - If it's a function, it will be invoked for every match and its return value is used as the replacement text.
94 | */
95 | replacement: Replacement
96 | }
97 | type Replacement = string | ((id: string, match: RegExpExecArray) => string)
98 | ```
99 |
100 | Comparing with `@rollup/plugin-replace`, `find` option supports regex pattern.
101 |
102 | **Example:**
103 |
104 | ```ts
105 | Replace({
106 | values: [
107 | {
108 | find: /apples/gi,
109 | replacement: 'oranges',
110 | },
111 | {
112 | find: 'process.env.NODE_ENV',
113 | replacement: '"production"',
114 | },
115 | ],
116 | })
117 | ```
118 |
119 | ## Sponsors
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 | ## License
128 |
129 | [MIT](./LICENSE) License © 2024-PRESENT [三咲智子](https://github.com/sxzz)
130 |
--------------------------------------------------------------------------------
/eslint.config.js:
--------------------------------------------------------------------------------
1 | import { sxzz } from '@sxzz/eslint-config'
2 | export default sxzz()
3 |
--------------------------------------------------------------------------------
/jsr.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@unplugin/replace",
3 | "version": "0.6.0",
4 | "exports": {
5 | "./index": "./src/index.ts",
6 | "./esbuild": "./src/esbuild.ts",
7 | "./rollup": "./src/rollup.ts",
8 | "./rolldown": "./src/rolldown.ts",
9 | "./vite": "./src/vite.ts",
10 | "./webpack": "./src/webpack.ts",
11 | "./rspack": "./src/rspack.ts",
12 | "./farm": "./src/farm.ts"
13 | },
14 | "publish": {
15 | "include": [
16 | "src",
17 | "package.json",
18 | "jsr.json",
19 | "README.md",
20 | "LICENSE"
21 | ]
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "unplugin-replace",
3 | "version": "0.6.0",
4 | "packageManager": "pnpm@10.11.0",
5 | "description": "A universal bundler plugin which replaces targeted strings in files.",
6 | "type": "module",
7 | "keywords": [
8 | "unplugin",
9 | "rollup",
10 | "vite",
11 | "esbuild",
12 | "webpack"
13 | ],
14 | "license": "MIT",
15 | "homepage": "https://github.com/unplugin/unplugin-replace#readme",
16 | "bugs": {
17 | "url": "https://github.com/unplugin/unplugin-replace/issues"
18 | },
19 | "repository": {
20 | "type": "git",
21 | "url": "git+https://github.com/unplugin/unplugin-replace.git"
22 | },
23 | "author": "三咲智子 Kevin Deng ",
24 | "funding": "https://github.com/sponsors/sxzz",
25 | "files": [
26 | "dist"
27 | ],
28 | "main": "./dist/index.js",
29 | "module": "./dist/index.js",
30 | "types": "./dist/index.d.ts",
31 | "exports": {
32 | ".": "./dist/index.js",
33 | "./vite": "./dist/vite.js",
34 | "./webpack": "./dist/webpack.js",
35 | "./rspack": "./dist/rspack.js",
36 | "./rollup": "./dist/rollup.js",
37 | "./rolldown": "./dist/rolldown.js",
38 | "./esbuild": "./dist/esbuild.js",
39 | "./farm": "./dist/farm.js",
40 | "./*": "./*"
41 | },
42 | "typesVersions": {
43 | "*": {
44 | "*": [
45 | "./dist/*",
46 | "./*"
47 | ]
48 | }
49 | },
50 | "publishConfig": {
51 | "access": "public"
52 | },
53 | "scripts": {
54 | "lint": "eslint --cache .",
55 | "lint:fix": "pnpm run lint --fix",
56 | "build": "tsdown",
57 | "dev": "tsdown --watch",
58 | "test": "vitest",
59 | "typecheck": "tsc --noEmit",
60 | "release": "bumpp && pnpm publish",
61 | "prepublishOnly": "pnpm run build"
62 | },
63 | "dependencies": {
64 | "magic-string": "^0.30.17",
65 | "unplugin": "^2.3.4"
66 | },
67 | "devDependencies": {
68 | "@sxzz/eslint-config": "^7.0.1",
69 | "@sxzz/prettier-config": "^2.2.1",
70 | "@sxzz/test-utils": "^0.5.6",
71 | "@types/node": "^22.15.21",
72 | "bumpp": "^10.1.1",
73 | "esbuild": "^0.25.4",
74 | "eslint": "^9.27.0",
75 | "fast-glob": "^3.3.3",
76 | "prettier": "^3.5.3",
77 | "rolldown": "nightly",
78 | "rollup": "^4.41.1",
79 | "tsdown": "^0.12.3",
80 | "tsx": "^4.19.4",
81 | "typescript": "^5.8.3",
82 | "vite": "^6.3.5",
83 | "vitest": "^3.1.4",
84 | "webpack": "^5.99.9"
85 | },
86 | "engines": {
87 | "node": ">=20.18.0"
88 | },
89 | "prettier": "@sxzz/prettier-config"
90 | }
91 |
--------------------------------------------------------------------------------
/src/core/options.ts:
--------------------------------------------------------------------------------
1 | import type { FilterPattern } from 'unplugin'
2 |
3 | export type Replacement =
4 | | string
5 | | ((id: string, match: RegExpExecArray) => string)
6 | export type ReplaceItem = {
7 | find: F
8 | replacement: Replacement
9 | }
10 | export type ReplaceMap = {
11 | [str: string]: Replacement
12 | }
13 |
14 | export interface BaseOptions {
15 | /**
16 | * A picomatch pattern, or array of patterns, of files that should be
17 | * processed by this plugin (if omitted, all files are included by default)
18 | */
19 | include?: FilterPattern
20 | /**
21 | * Files that should be excluded, if `include` is otherwise too permissive.
22 | */
23 | exclude?: FilterPattern
24 | /**
25 | * If false, skips source map generation. This will improve performance.
26 | *
27 | * For Vite, this option will be enabled on build mode
28 | * and respect `build.sourcemap`.
29 | * @default true
30 | */
31 | sourceMap?: boolean
32 | /**
33 | * To replace every occurrence of `<@foo@>` instead of every occurrence
34 | * of `foo`, supply delimiters
35 | */
36 | delimiters?: [string, string]
37 | /**
38 | * Prevents replacing strings where they are followed by a single equals
39 | * sign.
40 | */
41 | preventAssignment?: boolean
42 | objectGuards?: boolean
43 | /**
44 | * You can separate values to replace from other options.
45 | */
46 | values?: ReplaceMap | ReplaceItem[]
47 | enforce?: 'pre' | 'post' | undefined
48 | }
49 |
50 | export interface Options extends BaseOptions {
51 | /**
52 | * All other options are treated as `string: replacement` replacers,
53 | * or `string: (id) => replacement` functions.
54 | */
55 | [str: string]:
56 | | Replacement
57 | | BaseOptions['include']
58 | | BaseOptions['values']
59 | | BaseOptions['preventAssignment']
60 | }
61 | type Overwrite = Pick> & U
62 |
63 | export type OptionsResolved = Overwrite<
64 | Required,
65 | {
66 | include: BaseOptions['include']
67 | exclude: BaseOptions['exclude']
68 | enforce: BaseOptions['enforce']
69 | values: ReplaceItem[]
70 | }
71 | >
72 |
73 | export function resolveOptions(options: Options): OptionsResolved {
74 | return {
75 | include: options.include,
76 | exclude: options.exclude,
77 | sourceMap: options.sourceMap ?? true,
78 | delimiters: options.delimiters || [String.raw`\b`, String.raw`\b(?!\.)`],
79 | preventAssignment: options.preventAssignment ?? false,
80 | objectGuards: options.objectGuards ?? false,
81 | values: getReplacements(),
82 | enforce: 'enforce' in options ? options.enforce : 'pre',
83 | }
84 |
85 | function getReplacements() {
86 | if (options.values) {
87 | if (Array.isArray(options.values)) {
88 | return options.values
89 | }
90 | return normalizeObjectValues(options.values)
91 | }
92 |
93 | const values = Object.assign({}, options)
94 | delete values.delimiters
95 | delete values.include
96 | delete values.exclude
97 | delete values.sourcemap
98 | delete values.sourceMap
99 | delete values.objectGuards
100 | return normalizeObjectValues(values as ReplaceMap)
101 | }
102 | }
103 |
104 | function normalizeObjectValues(values: ReplaceMap): ReplaceItem[] {
105 | return Object.entries(values).map(([find, replacement]) => ({
106 | find,
107 | replacement,
108 | }))
109 | }
110 |
--------------------------------------------------------------------------------
/src/esbuild.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * This entry file is for esbuild plugin. Requires esbuild >= 0.15
3 | *
4 | * @module
5 | */
6 |
7 | import unplugin from './index'
8 |
9 | /**
10 | * Esbuild plugin
11 | *
12 | * @example
13 | * ```ts
14 | * import { build } from 'esbuild'
15 | * import Replace from 'unplugin-replace/esbuild'
16 | *
17 | * build({ plugins: [Replace()] })
18 | ```
19 | */
20 | const esbuild = unplugin.esbuild as typeof unplugin.esbuild
21 | export default esbuild
22 |
--------------------------------------------------------------------------------
/src/farm.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * This entry file is for Farm plugin.
3 | *
4 | * @module
5 | */
6 |
7 | import unplugin from './index'
8 |
9 | /**
10 | * Farm plugin
11 | *
12 | * @example
13 | * ```ts
14 | * // farm.config.js
15 | * import Replace from 'unplugin-replace/farm'
16 | *
17 | * export default {
18 | * plugins: [Replace()],
19 | * }
20 | * ```
21 | */
22 | const farm = unplugin.farm as typeof unplugin.farm
23 | export default farm
24 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * This entry file is for main unplugin.
3 | * @module
4 | */
5 |
6 | import MagicString from 'magic-string'
7 | import {
8 | createUnplugin,
9 | type TransformResult,
10 | type UnpluginInstance,
11 | } from 'unplugin'
12 | import { resolveOptions, type Options, type ReplaceItem } from './core/options'
13 |
14 | /**
15 | * The main unplugin instance.
16 | */
17 | const plugin: UnpluginInstance = createUnplugin<
18 | Options | undefined,
19 | false
20 | >((rawOptions = {}) => {
21 | const options = resolveOptions(rawOptions)
22 | const {
23 | include,
24 | exclude,
25 | enforce,
26 | delimiters,
27 | objectGuards,
28 | preventAssignment,
29 | } = options
30 | const stringValues = options.values.filter(
31 | (value): value is ReplaceItem => typeof value.find === 'string',
32 | )
33 | const regexpValues = options.values.filter(
34 | (value): value is ReplaceItem => value.find instanceof RegExp,
35 | )
36 |
37 | if (objectGuards) expandTypeofReplacements(stringValues)
38 | const pattern = buildStringPattern()
39 |
40 | const values = [...regexpValues]
41 | if (pattern) {
42 | values.unshift({ find: pattern, replacement: null! })
43 | }
44 |
45 | const name = 'unplugin-replace'
46 | return {
47 | name,
48 | enforce,
49 |
50 | buildStart() {
51 | if (![true, false].includes(preventAssignment)) {
52 | console.warn({
53 | message: `${name}: 'preventAssignment' currently defaults to false. It is recommended to set this option to \`true\`, as the next major version will default this option to \`true\`.`,
54 | })
55 | }
56 | },
57 |
58 | transform: {
59 | filter: { id: { include, exclude } },
60 | handler(code, id) {
61 | if (values.length === 0) return
62 |
63 | return executeReplacement(code, id)
64 | },
65 | },
66 |
67 | vite: {
68 | configResolved(config) {
69 | options.sourceMap =
70 | config.command === 'build' ? !!config.build.sourcemap : true
71 | },
72 | },
73 | }
74 |
75 | function executeReplacement(code: string, id: string) {
76 | const magicString = new MagicString(code)
77 | if (!codeHasReplacements(code, id, magicString)) {
78 | return null
79 | }
80 |
81 | const result: TransformResult = { code: magicString.toString() }
82 | if (options.sourceMap) {
83 | result.map = magicString.generateMap({ hires: true })
84 | }
85 | return result
86 | }
87 |
88 | function codeHasReplacements(
89 | code: string,
90 | id: string,
91 | magicString: MagicString,
92 | ) {
93 | let has = false
94 | let match: RegExpExecArray | null
95 |
96 | for (const { find, replacement } of values) {
97 | while ((match = find.exec(code))) {
98 | has = true
99 |
100 | const start = match.index
101 | const end = start + match[0].length
102 |
103 | const finalReplacement =
104 | find === pattern
105 | ? stringValues.find(({ find }) => find === match![1])!.replacement
106 | : replacement
107 | const result = String(ensureFunction(finalReplacement)(id, match))
108 | magicString.overwrite(start, end, result)
109 |
110 | if (!find.global) break
111 | }
112 | }
113 |
114 | return has
115 | }
116 |
117 | function buildStringPattern(): RegExp | undefined {
118 | const escapedKeys = stringValues
119 | .map(({ find }) => find)
120 | .sort(longest)
121 | .map(escape)
122 | const lookbehind = preventAssignment
123 | ? String.raw`(? 0) {
132 | return pattern
133 | }
134 | }
135 | })
136 | export default plugin
137 |
138 | function escape(str: string) {
139 | // eslint-disable-next-line unicorn/prefer-string-replace-all
140 | return str.replace(/[$()*+./?[\\\]^{|}-]/g, String.raw`\$&`)
141 | }
142 |
143 | function ensureFunction(
144 | functionOrValue: any,
145 | ): (id: string, match: RegExpExecArray) => any {
146 | if (typeof functionOrValue === 'function') return functionOrValue
147 | return () => functionOrValue
148 | }
149 |
150 | function longest(a: string, b: string) {
151 | return b.length - a.length
152 | }
153 |
154 | const objKeyRegEx =
155 | /^([$A-Z_\u00A0-\uFFFF][\w$\u00A0-\uFFFF]*)(\.([$A-Z_\u00A0-\uFFFF][\w$\u00A0-\uFFFF]*))+$/i
156 | function expandTypeofReplacements(values: ReplaceItem[]) {
157 | values.forEach(({ find }) => {
158 | const objMatch = find.match(objKeyRegEx)
159 | if (!objMatch) return
160 | let dotIndex = objMatch[1].length
161 | let lastIndex = 0
162 | do {
163 | values.push(
164 | {
165 | find: `typeof ${find.slice(lastIndex, dotIndex)} ===`,
166 | replacement: '"object" ===',
167 | },
168 | {
169 | find: `typeof ${find.slice(lastIndex, dotIndex)} !==`,
170 | replacement: '"object" !==',
171 | },
172 | {
173 | find: `typeof ${find.slice(lastIndex, dotIndex)}===`,
174 | replacement: '"object"===',
175 | },
176 | {
177 | find: `typeof ${find.slice(lastIndex, dotIndex)}!==`,
178 | replacement: '"object"!==',
179 | },
180 | {
181 | find: `typeof ${find.slice(lastIndex, dotIndex)} ==`,
182 | replacement: '"object" ===',
183 | },
184 | {
185 | find: `typeof ${find.slice(lastIndex, dotIndex)} !=`,
186 | replacement: '"object" !==',
187 | },
188 | {
189 | find: `typeof ${find.slice(lastIndex, dotIndex)}==`,
190 | replacement: '"object"===',
191 | },
192 | {
193 | find: `typeof ${find.slice(lastIndex, dotIndex)}!=`,
194 | replacement: '"object"!==',
195 | },
196 | )
197 | lastIndex = dotIndex + 1
198 | dotIndex = find.indexOf('.', lastIndex)
199 | } while (dotIndex !== -1)
200 | })
201 | }
202 |
--------------------------------------------------------------------------------
/src/rolldown.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * This entry file is for Rolldown plugin.
3 | *
4 | * @module
5 | */
6 |
7 | import unplugin from './index'
8 |
9 | /**
10 | * Rolldown plugin
11 | *
12 | * @example
13 | * ```ts
14 | * // rolldown.config.js
15 | * import Replace from 'unplugin-replace/rolldown'
16 | *
17 | * export default {
18 | * plugins: [Replace()],
19 | * }
20 | * ```
21 | */
22 | const rolldown = unplugin.rolldown as typeof unplugin.rolldown
23 | export default rolldown
24 |
--------------------------------------------------------------------------------
/src/rollup.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * This entry file is for Rollup plugin.
3 | *
4 | * @module
5 | */
6 |
7 | import unplugin from './index'
8 |
9 | /**
10 | * Rollup plugin
11 | *
12 | * @example
13 | * ```ts
14 | * // rollup.config.js
15 | * import Replace from 'unplugin-replace/rollup'
16 | *
17 | * export default {
18 | * plugins: [Replace()],
19 | * }
20 | * ```
21 | */
22 | const rollup = unplugin.rollup as typeof unplugin.rollup
23 | export default rollup
24 |
--------------------------------------------------------------------------------
/src/rspack.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * This entry file is for Rspack plugin.
3 | *
4 | * @module
5 | */
6 |
7 | import unplugin from './index'
8 |
9 | /**
10 | * Rspack plugin
11 | *
12 | * @example
13 | * ```js
14 | * // rspack.config.js
15 | * import Replace from 'unplugin-replace/rspack'
16 | *
17 | * default export {
18 | * plugins: [Replace()],
19 | * }
20 | * ```
21 | */
22 | const rspack = unplugin.rspack as typeof unplugin.rspack
23 | export default rspack
24 |
--------------------------------------------------------------------------------
/src/vite.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * This entry file is for Vite plugin.
3 | *
4 | * @module
5 | */
6 |
7 | import unplugin from './index'
8 |
9 | /**
10 | * Vite plugin
11 | *
12 | * @example
13 | * ```ts
14 | * // vite.config.ts
15 | * import Replace from 'unplugin-replace/vite'
16 | *
17 | * export default defineConfig({
18 | * plugins: [Replace()],
19 | * })
20 | * ```
21 | */
22 | const vite = unplugin.vite as typeof unplugin.vite
23 | export default vite
24 |
--------------------------------------------------------------------------------
/src/webpack.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * This entry file is for webpack plugin.
3 | *
4 | * @module
5 | */
6 |
7 | import unplugin from './index'
8 |
9 | /**
10 | * Webpack plugin
11 | *
12 | * @example
13 | * ```js
14 | * // webpack.config.js
15 | * import Replace from 'unplugin-replace/webpack'
16 | *
17 | * default export {
18 | * plugins: [Replace()],
19 | * }
20 | * ```
21 | */
22 | const webpack = unplugin.webpack as typeof unplugin.webpack
23 | export default webpack
24 |
--------------------------------------------------------------------------------
/tests/__snapshots__/esbuild.test.ts.snap:
--------------------------------------------------------------------------------
1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2 |
3 | exports[`esbuild 1`] = `
4 | "// tests/fixtures/main.js
5 | var platform = "darwin";
6 | console.log('hello "darwin"', platform);
7 | console.log(DEV);
8 | export {
9 | platform
10 | };
11 | "
12 | `;
13 |
--------------------------------------------------------------------------------
/tests/__snapshots__/rolldown.test.ts.snap:
--------------------------------------------------------------------------------
1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2 |
3 | exports[`rolldown 1`] = `
4 | "import "node:process";
5 |
6 | //#region tests/fixtures/main.js
7 | const platform = "darwin";
8 | console.log("hello \\"darwin\\"", platform);
9 | console.log(DEV);
10 |
11 | //#endregion
12 | export { platform };"
13 | `;
14 |
--------------------------------------------------------------------------------
/tests/__snapshots__/rollup.test.ts.snap:
--------------------------------------------------------------------------------
1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2 |
3 | exports[`rollup > find regexp 1`] = `
4 | "// main.js
5 | import 'node:process';
6 |
7 | const platform = null;
8 |
9 | console.info('hello null', platform);
10 | console.info(dEV);
11 |
12 | export { platform };
13 | "
14 | `;
15 |
16 | exports[`rollup > find string 1`] = `
17 | "// main.js
18 | import 'node:process';
19 |
20 | const platform = "darwin";
21 |
22 | console.log('hello "darwin"', platform);
23 | console.log(DEV);
24 |
25 | export { platform };
26 | "
27 | `;
28 |
--------------------------------------------------------------------------------
/tests/__snapshots__/vite.test.ts.snap:
--------------------------------------------------------------------------------
1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2 |
3 | exports[`vite 1`] = `
4 | "import "node:process";
5 | const platform = "darwin";
6 | console.log('hello "darwin"', platform);
7 | console.log(DEV);
8 | "
9 | `;
10 |
--------------------------------------------------------------------------------
/tests/__snapshots__/webpack.test.ts.snap:
--------------------------------------------------------------------------------
1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2 |
3 | exports[`webpack 1`] = `
4 | "/******/ (() => { // webpackBootstrap
5 | /******/ "use strict";
6 |
7 | // UNUSED EXPORTS: platform
8 |
9 | ;// external "node:process"
10 | const external_node_process_namespaceObject = require("node:process");
11 | ;// ./tests/fixtures/main.js
12 |
13 |
14 | const platform = "darwin"
15 |
16 | console.log('hello "darwin"', platform)
17 | console.log(DEV)
18 |
19 | /******/ })()
20 | ;"
21 | `;
22 |
--------------------------------------------------------------------------------
/tests/esbuild.test.ts:
--------------------------------------------------------------------------------
1 | import path from 'node:path'
2 | import { build } from 'esbuild'
3 | import { expect, test } from 'vitest'
4 | import UnpluginReplace from '../src/esbuild'
5 |
6 | test('esbuild', async () => {
7 | const { outputFiles } = await build({
8 | entryPoints: [path.resolve(__dirname, 'fixtures/main.js')],
9 | format: 'esm',
10 | write: false,
11 | bundle: true,
12 | platform: 'node',
13 | plugins: [
14 | UnpluginReplace({
15 | 'process.platform': '"darwin"',
16 | }),
17 | ],
18 | })
19 | expect(outputFiles[0].text).toMatchSnapshot()
20 | expect(outputFiles[0].text).not.contains('process.platform')
21 | expect(outputFiles[0].text).contains('"darwin"')
22 | })
23 |
--------------------------------------------------------------------------------
/tests/fixtures/main.js:
--------------------------------------------------------------------------------
1 | import process from 'node:process'
2 |
3 | export const platform = process.platform
4 |
5 | console.log('hello process.platform', platform)
6 | console.log(DEV)
7 |
--------------------------------------------------------------------------------
/tests/rolldown.test.ts:
--------------------------------------------------------------------------------
1 | import path from 'node:path'
2 | import { rolldown } from 'rolldown'
3 | import { expect, test } from 'vitest'
4 | import UnpluginReplace from '../src/rolldown'
5 |
6 | test('rolldown', async () => {
7 | const build = await rolldown({
8 | input: path.resolve(__dirname, 'fixtures/main.js'),
9 | external: ['node:process'],
10 | plugins: [
11 | UnpluginReplace({
12 | 'process.platform': '"darwin"',
13 | }),
14 | ],
15 | })
16 | const { output } = await build.generate({ format: 'es' })
17 | const code = output[0].code
18 | expect(code).toMatchSnapshot()
19 | expect(code).not.contains('process.platform')
20 | expect(code).contains('"darwin"')
21 | })
22 |
--------------------------------------------------------------------------------
/tests/rollup.test.ts:
--------------------------------------------------------------------------------
1 | import path from 'node:path'
2 | import { rollupBuild } from '@sxzz/test-utils'
3 | import { describe, expect, test } from 'vitest'
4 | import UnpluginReplace from '../src/rollup'
5 |
6 | describe('rollup', () => {
7 | test('find string', async () => {
8 | const { snapshot } = await rollupBuild(
9 | path.resolve(__dirname, 'fixtures/main.js'),
10 | [
11 | UnpluginReplace({
12 | 'process.platform': '"darwin"',
13 | }),
14 | ],
15 | )
16 | expect(snapshot).toMatchSnapshot()
17 | expect(snapshot).not.contains('process.platform')
18 | expect(snapshot).contains('"darwin"')
19 | })
20 |
21 | test('find regexp', async () => {
22 | const { snapshot } = await rollupBuild(
23 | path.resolve(__dirname, 'fixtures/main.js'),
24 | [
25 | UnpluginReplace({
26 | values: [
27 | { find: /process\.\w+/g, replacement: 'null' },
28 | {
29 | find: /[A-Z]/,
30 | replacement: (id, match) => match[0].toLowerCase(),
31 | },
32 | {
33 | find: /console\.\w+/g,
34 | replacement: () => `console.info`,
35 | },
36 | ],
37 | }),
38 | ],
39 | )
40 | expect(snapshot).toMatchSnapshot()
41 | expect(snapshot).contains('console.info(dEV)')
42 | expect(snapshot).contains(`console.info('hello null', platform)`)
43 | })
44 | })
45 |
--------------------------------------------------------------------------------
/tests/vite.test.ts:
--------------------------------------------------------------------------------
1 | import path from 'node:path'
2 | import { build } from 'vite'
3 | import { expect, test } from 'vitest'
4 | import UnpluginReplace from '../src/vite'
5 | import type { RollupOutput } from 'rollup'
6 |
7 | test('vite', async () => {
8 | const root = path.resolve(__dirname, 'fixtures')
9 | const { output } = (await build({
10 | root,
11 | build: {
12 | minify: false,
13 | rollupOptions: {
14 | input: [path.resolve(root, 'main.js')],
15 | external: (id) => id === 'node:process',
16 | logLevel: 'silent',
17 | },
18 | write: false,
19 | },
20 | logLevel: 'silent',
21 | plugins: [
22 | UnpluginReplace({
23 | 'process.platform': '"darwin"',
24 | }),
25 | ],
26 | })) as RollupOutput
27 | expect(output[0].code).toMatchSnapshot()
28 | expect(output[0].code).not.contains('process.platform')
29 | expect(output[0].code).contains('"darwin"')
30 | })
31 |
--------------------------------------------------------------------------------
/tests/webpack.test.ts:
--------------------------------------------------------------------------------
1 | import { mkdtemp, readFile, rm } from 'node:fs/promises'
2 | import os from 'node:os'
3 | import path from 'node:path'
4 | import { expect, onTestFinished, test } from 'vitest'
5 | import webpack from 'webpack'
6 | import UnpluginReplace from '../src/webpack'
7 |
8 | test('webpack', async () => {
9 | const tmp = await mkdtemp(path.join(os.tmpdir(), 'unplugin-replace-'))
10 | onTestFinished(() => rm(tmp, { recursive: true, force: true }))
11 |
12 | await new Promise((resolve, reject) => {
13 | const compiler = webpack({
14 | entry: path.resolve(__dirname, 'fixtures/main.js'),
15 | output: {
16 | path: tmp,
17 | },
18 | plugins: [
19 | UnpluginReplace({
20 | 'process.platform': '"darwin"',
21 | }),
22 | ],
23 | mode: 'production',
24 | target: 'node',
25 | optimization: {
26 | minimize: false,
27 | },
28 | })
29 | compiler.run((error, stats) => {
30 | if (error) return reject(error)
31 | resolve(stats!)
32 | })
33 | })
34 |
35 | const result = await readFile(path.resolve(tmp, 'main.js'), 'utf-8')
36 | expect(result).toMatchSnapshot()
37 | expect(result).not.contains('process.platform')
38 | expect(result).contains('"darwin"')
39 | })
40 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "esnext",
4 | "lib": ["es2022"],
5 | "moduleDetection": "force",
6 | "module": "preserve",
7 | "moduleResolution": "bundler",
8 | "resolveJsonModule": true,
9 | "types": ["node"],
10 | "strict": true,
11 | "noUnusedLocals": true,
12 | "declaration": true,
13 | "isolatedDeclarations": true,
14 | "esModuleInterop": true,
15 | "isolatedModules": true,
16 | "verbatimModuleSyntax": true,
17 | "skipLibCheck": true
18 | },
19 | "include": ["src", "tests"]
20 | }
21 |
--------------------------------------------------------------------------------
/tsdown.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'tsdown'
2 |
3 | export default defineConfig({
4 | entry: './src/*.ts',
5 | })
6 |
--------------------------------------------------------------------------------