@open-xchange/vite-plugin-replace-pkg 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.vscode/settings.json +3 -0
- package/CHANGELOG.md +5 -0
- package/LICENSE +21 -0
- package/README.md +179 -0
- package/dist/index.d.ts +91 -0
- package/dist/index.js +111 -0
- package/package.json +41 -0
package/CHANGELOG.md
ADDED
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 OX Software GmbH, Germany <info@open-xchange.com>
|
|
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.
|
package/README.md
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
# @open-xchange/vite-plugin-replace-pkg
|
|
2
|
+
|
|
3
|
+
A Vite plugin that replaces a specific external package with an arbitrary source code module.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
This plugin replaces a specific external package with a custom source file that can be downloaded from an arbitrary URL, or that can be extracted from a GitLab project repository. If the replacement source file cannot be resolved, the original external package will be used.
|
|
8
|
+
|
|
9
|
+
A common use case is replacing a public package with an internally hosted commercial version of the package. When building the product internally, the internal version of the package will be bundled, otherwise the public version.
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
// vite.config.ts
|
|
13
|
+
|
|
14
|
+
import { defineConfig } from "vite" // or "vitest/config"
|
|
15
|
+
import replacePlugin from "@open-xchange/vite-plugin-replace-pkg"
|
|
16
|
+
|
|
17
|
+
export default defineConfig(() => {
|
|
18
|
+
plugins: [
|
|
19
|
+
replacePlugin({
|
|
20
|
+
package: "external-package",
|
|
21
|
+
replace: "https://example.org/path/to/index.js",
|
|
22
|
+
}),
|
|
23
|
+
],
|
|
24
|
+
})
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
// src/index.ts
|
|
29
|
+
|
|
30
|
+
import * as ext from "external-package" // imports replacement if available
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
It is possible to replace different packages by instantiating this plugin multiple times:
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
// vite.config.ts
|
|
37
|
+
|
|
38
|
+
import { defineConfig } from "vite" // or "vitest/config"
|
|
39
|
+
import replacePlugin from "@open-xchange/vite-plugin-replace-pkg"
|
|
40
|
+
|
|
41
|
+
export default defineConfig(() => {
|
|
42
|
+
plugins: [
|
|
43
|
+
replacePlugin({
|
|
44
|
+
package: "external-package",
|
|
45
|
+
replace: "https://example.org/path/to/index.js",
|
|
46
|
+
}),
|
|
47
|
+
replacePlugin({
|
|
48
|
+
package: "@namespace/another-package",
|
|
49
|
+
replace: { /* GitLab project file */ },
|
|
50
|
+
}),
|
|
51
|
+
],
|
|
52
|
+
})
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Options
|
|
56
|
+
|
|
57
|
+
#### `package`
|
|
58
|
+
|
|
59
|
+
- Type: `string`
|
|
60
|
+
- _required_
|
|
61
|
+
|
|
62
|
+
The name of the external package to be replaced, as appearing in `import` statements in source code.
|
|
63
|
+
|
|
64
|
+
#### `replace`
|
|
65
|
+
|
|
66
|
+
- Type: _various (see below)_
|
|
67
|
+
- _required_
|
|
68
|
+
|
|
69
|
+
The location of the source file to resolve the module imports with. There are different ways to declare the source file:
|
|
70
|
+
|
|
71
|
+
1. A simple URL as `string` (see above for an example).
|
|
72
|
+
|
|
73
|
+
2. A [`RequestInit`](https://developer.mozilla.org/en-US/docs/Web/API/RequestInit) configuration object for the `fetch` function (headers, body, etc.), with the following additional options:
|
|
74
|
+
|
|
75
|
+
| Name | Type | Default | Description |
|
|
76
|
+
| - | - | - | - |
|
|
77
|
+
| `url` | `string` | _required_ | The URL of the source file to be downloaded. |
|
|
78
|
+
| `query` | `Dict<string\|number\|boolean>` | `{}` | Query parameters to be added to the URL. |
|
|
79
|
+
|
|
80
|
+
Example:
|
|
81
|
+
|
|
82
|
+
```ts
|
|
83
|
+
// vite.config.ts
|
|
84
|
+
|
|
85
|
+
import { defineConfig } from "vite" // or "vitest/config"
|
|
86
|
+
import replacePlugin from "@open-xchange/vite-plugin-replace-pkg"
|
|
87
|
+
|
|
88
|
+
export default defineConfig(() => {
|
|
89
|
+
plugins: [
|
|
90
|
+
replacePlugin({
|
|
91
|
+
package: "external-package",
|
|
92
|
+
replace: {
|
|
93
|
+
method: "POST",
|
|
94
|
+
headers: { "X-Some-Header", "abc" },
|
|
95
|
+
url: "https://example.org/api/to/endpoint",
|
|
96
|
+
query: { ts: Date.now() },
|
|
97
|
+
},
|
|
98
|
+
}),
|
|
99
|
+
],
|
|
100
|
+
})
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
3. A specification for a file in a GitLab project repository, with the following options:
|
|
104
|
+
|
|
105
|
+
| Name | Type | Default | Description |
|
|
106
|
+
| - | - | - | - |
|
|
107
|
+
| `server` | `string` | _required_ | The URL of the GitLab server to be used for downloading the source file. |
|
|
108
|
+
| `project` | `string\|number` | _required_ | Name or numeric identifier of the GitLab project. The project name may contain slashes (e.g. `"my-group/my-project"`) which will be encoded internally. |
|
|
109
|
+
| `path` | `string` | _required_ | Path to the source file to be downloaded. Slashes in the path will be encoded internally. |
|
|
110
|
+
| `ref` | `string` | `HEAD` | The name of a branch or tag, or a commit ID. Slashes in branch names will be encoded internally. |
|
|
111
|
+
|
|
112
|
+
Example:
|
|
113
|
+
|
|
114
|
+
```ts
|
|
115
|
+
// vite.config.ts
|
|
116
|
+
|
|
117
|
+
import { defineConfig } from "vite" // or "vitest/config"
|
|
118
|
+
import replacePlugin from "@open-xchange/vite-plugin-replace-pkg"
|
|
119
|
+
|
|
120
|
+
export default defineConfig(() => {
|
|
121
|
+
plugins: [
|
|
122
|
+
replacePlugin({
|
|
123
|
+
package: "external-package",
|
|
124
|
+
replace: {
|
|
125
|
+
server: "https://gitlab.example.org",
|
|
126
|
+
project: "internals/commercial-package",
|
|
127
|
+
path: "path/to/index.js",
|
|
128
|
+
ref: "feature/branch-2",
|
|
129
|
+
},
|
|
130
|
+
}),
|
|
131
|
+
],
|
|
132
|
+
})
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
4. A custom callback function. Will be invoked with the original package name (the value of the option `package`), and returns the source code to be used instead.
|
|
136
|
+
|
|
137
|
+
Example:
|
|
138
|
+
|
|
139
|
+
```ts
|
|
140
|
+
// vite.config.ts
|
|
141
|
+
|
|
142
|
+
import { defineConfig } from "vite" // or "vitest/config"
|
|
143
|
+
import replacePlugin from "@open-xchange/vite-plugin-replace-pkg"
|
|
144
|
+
|
|
145
|
+
export default defineConfig(() => {
|
|
146
|
+
plugins: [
|
|
147
|
+
replacePlugin({
|
|
148
|
+
package: "external-package",
|
|
149
|
+
async replace() { /* ... */ },
|
|
150
|
+
}),
|
|
151
|
+
],
|
|
152
|
+
})
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
#### `transform`
|
|
156
|
+
|
|
157
|
+
- Type: `(source: string) => string | string[] | Promise<string | string[]>`
|
|
158
|
+
- _optional_
|
|
159
|
+
|
|
160
|
+
Transformation filter for the source code. The function will be invoked with the downloaded source code, and returns the transformed source code to be used. An array of strings will be concatenated with newline characters.
|
|
161
|
+
|
|
162
|
+
Example:
|
|
163
|
+
|
|
164
|
+
```ts
|
|
165
|
+
// vite.config.ts
|
|
166
|
+
|
|
167
|
+
import { defineConfig } from "vite" // or "vitest/config"
|
|
168
|
+
import replacePlugin from "@open-xchange/vite-plugin-replace-pkg"
|
|
169
|
+
|
|
170
|
+
export default defineConfig(() => {
|
|
171
|
+
plugins: [
|
|
172
|
+
replacePlugin({
|
|
173
|
+
package: "external-package"
|
|
174
|
+
replace: "https://example.org/path/to/index.js",
|
|
175
|
+
transform: source => ["/* replaced file header */", source],
|
|
176
|
+
}),
|
|
177
|
+
],
|
|
178
|
+
})
|
|
179
|
+
```
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import type { Plugin } from "vite";
|
|
2
|
+
import type { Dict } from "@open-xchange/vite-helper/utils";
|
|
3
|
+
/**
|
|
4
|
+
* The configuration options for the `fetch` function needed to download a
|
|
5
|
+
* source file.
|
|
6
|
+
*/
|
|
7
|
+
export interface VitePluginReplacePkg_RequestInit extends RequestInit {
|
|
8
|
+
/**
|
|
9
|
+
* The URL of the source file to be downloaded.
|
|
10
|
+
*/
|
|
11
|
+
url: string;
|
|
12
|
+
/**
|
|
13
|
+
* Query parameters to be added to the URL.
|
|
14
|
+
*/
|
|
15
|
+
query?: Dict<string | number | boolean>;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* The specification of a source file in a GitLab repository.
|
|
19
|
+
*
|
|
20
|
+
* Downloading the source file needs a valid GitLab API token stored in the
|
|
21
|
+
* environment variable "GITLAB_TOKEN". If the variable does not exist,
|
|
22
|
+
* downloading the source wile will be skipped.
|
|
23
|
+
*/
|
|
24
|
+
export interface VitePluginReplacePkg_GitLabFile {
|
|
25
|
+
/**
|
|
26
|
+
* The URL of the GitLab server to be used for downloading the source file.
|
|
27
|
+
*/
|
|
28
|
+
server: string;
|
|
29
|
+
/**
|
|
30
|
+
* Name or identifier of the GitLab project (repository). The project name
|
|
31
|
+
* may contain slashes (e.g. "my-group/my-project") which will be encoded
|
|
32
|
+
* internally.
|
|
33
|
+
*/
|
|
34
|
+
project: string | number;
|
|
35
|
+
/**
|
|
36
|
+
* Path to the source file to be downloaded. Slashes in the path will be
|
|
37
|
+
* encoded internally.
|
|
38
|
+
*/
|
|
39
|
+
path: string;
|
|
40
|
+
/**
|
|
41
|
+
* The name of a branch or a tag, or a commit ID. Slashes in branch names
|
|
42
|
+
* will be encoded internally. Default value is "HEAD".
|
|
43
|
+
*/
|
|
44
|
+
ref?: string;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* A custom callback function to fetch the replacement source code for an
|
|
48
|
+
* external package.
|
|
49
|
+
*/
|
|
50
|
+
export type VitePluginReplacePkg_Callback = (pkgName: string) => string | Promise<string>;
|
|
51
|
+
/**
|
|
52
|
+
* Configuration options needed to replace an external package.
|
|
53
|
+
*/
|
|
54
|
+
export interface VitePluginReplacePkgOptions {
|
|
55
|
+
/**
|
|
56
|
+
* The name of the external package to be replaced.
|
|
57
|
+
*/
|
|
58
|
+
package: string;
|
|
59
|
+
/**
|
|
60
|
+
* The location of the source file to resolve the module import with. Can
|
|
61
|
+
* be a simple URL as string, a configuration object for the `fetch`
|
|
62
|
+
* function (with URL, headers, etc.), a specification for a file in a
|
|
63
|
+
* GitLab project repository, or a custom callback function.
|
|
64
|
+
*/
|
|
65
|
+
replace: string | VitePluginReplacePkg_RequestInit | VitePluginReplacePkg_GitLabFile | VitePluginReplacePkg_Callback;
|
|
66
|
+
/**
|
|
67
|
+
* Transformation filter for the downloaded source code.
|
|
68
|
+
*
|
|
69
|
+
* @param source
|
|
70
|
+
* The downloaded source code.
|
|
71
|
+
*
|
|
72
|
+
* @returns
|
|
73
|
+
* The transformed source code to be used, as plain string, or as array of
|
|
74
|
+
* multiple source lines.
|
|
75
|
+
*/
|
|
76
|
+
transform?: (source: string) => string | string[] | Promise<string | string[]>;
|
|
77
|
+
}
|
|
78
|
+
export declare const PROJECT_NAME = "@open-xchange/vite-plugin-replace-pkg";
|
|
79
|
+
/**
|
|
80
|
+
* A plugin for Vite to replace a specific external package with an arbitrary
|
|
81
|
+
* source code module. If download of a requested source file fails for any
|
|
82
|
+
* reason, the plugin will step back silently, and the original package will be
|
|
83
|
+
* imported instead.
|
|
84
|
+
*
|
|
85
|
+
* @param options
|
|
86
|
+
* The plugin configuration.
|
|
87
|
+
*
|
|
88
|
+
* @returns
|
|
89
|
+
* The plugin instance.
|
|
90
|
+
*/
|
|
91
|
+
export default function vitePluginReplacePkg(options: VitePluginReplacePkgOptions): Plugin;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { onceFn } from "@open-xchange/vite-helper/utils";
|
|
2
|
+
import { Logger } from "@open-xchange/vite-helper/logger";
|
|
3
|
+
// constants ==================================================================
|
|
4
|
+
export const PROJECT_NAME = "@open-xchange/vite-plugin-replace-pkg";
|
|
5
|
+
const logger = new Logger({
|
|
6
|
+
loggerPrefix: "pkg",
|
|
7
|
+
logLevelEnvVar: "PLUGIN_REPLACE_PACKAGES_LOGLEVEL",
|
|
8
|
+
});
|
|
9
|
+
// functions ==================================================================
|
|
10
|
+
const resolveFetchOptions = (options) => {
|
|
11
|
+
const { replace } = options;
|
|
12
|
+
// callback function
|
|
13
|
+
if (typeof replace === "function") {
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
// plain string
|
|
17
|
+
if (typeof replace === "string") {
|
|
18
|
+
return replace ? { url: replace } : undefined;
|
|
19
|
+
}
|
|
20
|
+
// `RequestInit` object
|
|
21
|
+
if ("url" in replace) {
|
|
22
|
+
return replace.url ? replace : undefined;
|
|
23
|
+
}
|
|
24
|
+
// `GitLabFile` object
|
|
25
|
+
if ("project" in replace) {
|
|
26
|
+
// resolve GitLab API tokens
|
|
27
|
+
const token = process.env.GITLAB_TOKEN;
|
|
28
|
+
if (!token) {
|
|
29
|
+
logger.warn("missing environment variable %f with GitLab API token", "GITLAB_TOKEN");
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
// build the file URL for the GitLab API
|
|
33
|
+
// https://docs.gitlab.com/ee/api/repository_files.html#get-raw-file-from-repository
|
|
34
|
+
const project = encodeURIComponent(replace.project);
|
|
35
|
+
const path = encodeURIComponent(replace.path);
|
|
36
|
+
const url = `${replace.server}/api/v4/projects/${project}/repository/files/${path}/raw`;
|
|
37
|
+
// additional settings (query, headers, etc.)
|
|
38
|
+
const query = { lfs: true };
|
|
39
|
+
if (replace.ref) {
|
|
40
|
+
query.ref = replace.ref;
|
|
41
|
+
}
|
|
42
|
+
const headers = { "PRIVATE-TOKEN": token };
|
|
43
|
+
return { url, headers, query };
|
|
44
|
+
}
|
|
45
|
+
logger.error("invalid configuration for package %f", options.package);
|
|
46
|
+
return undefined;
|
|
47
|
+
};
|
|
48
|
+
// plugin =====================================================================
|
|
49
|
+
/**
|
|
50
|
+
* A plugin for Vite to replace a specific external package with an arbitrary
|
|
51
|
+
* source code module. If download of a requested source file fails for any
|
|
52
|
+
* reason, the plugin will step back silently, and the original package will be
|
|
53
|
+
* imported instead.
|
|
54
|
+
*
|
|
55
|
+
* @param options
|
|
56
|
+
* The plugin configuration.
|
|
57
|
+
*
|
|
58
|
+
* @returns
|
|
59
|
+
* The plugin instance.
|
|
60
|
+
*/
|
|
61
|
+
export default function vitePluginReplacePkg(options) {
|
|
62
|
+
const pkgName = options.package;
|
|
63
|
+
const moduleId = "\0" + PROJECT_NAME + "/" + pkgName;
|
|
64
|
+
const fetchSourceFile = onceFn(async () => {
|
|
65
|
+
try {
|
|
66
|
+
if (typeof options.replace === "function") {
|
|
67
|
+
return await options.replace(pkgName);
|
|
68
|
+
}
|
|
69
|
+
const fetchOptions = resolveFetchOptions(options);
|
|
70
|
+
if (fetchOptions) {
|
|
71
|
+
const query = Object.entries(fetchOptions.query ?? {}).map(entry => entry.map(encodeURIComponent).join("=")).join("&");
|
|
72
|
+
const url = fetchOptions.url + (query ? `?${query}` : "");
|
|
73
|
+
logger.info("requesting package %f from %f", pkgName, url);
|
|
74
|
+
const response = await fetch(url, fetchOptions);
|
|
75
|
+
if (response.ok) {
|
|
76
|
+
return await response.text();
|
|
77
|
+
}
|
|
78
|
+
logger.warn(`requesting package %f failed: ${response.status} ${response.statusText}`, pkgName);
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
logger.info("no replacement available for package %f", pkgName);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
logger.warn(`requesting package %f failed: ${error.message}`, pkgName);
|
|
86
|
+
}
|
|
87
|
+
return "";
|
|
88
|
+
});
|
|
89
|
+
// create and return the plugin object
|
|
90
|
+
return {
|
|
91
|
+
name: PROJECT_NAME,
|
|
92
|
+
resolveId: {
|
|
93
|
+
order: "pre",
|
|
94
|
+
async handler(source) {
|
|
95
|
+
if (source !== pkgName) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
const contents = await fetchSourceFile();
|
|
99
|
+
return contents ? moduleId : undefined;
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
async load(id) {
|
|
103
|
+
if (id !== moduleId) {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
const contents = await fetchSourceFile();
|
|
107
|
+
const transformed = options.transform ? await options.transform(contents) : contents;
|
|
108
|
+
return Array.isArray(transformed) ? transformed.join("\n") : transformed;
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@open-xchange/vite-plugin-replace-pkg",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Vite plugin replacing a specific external package with an arbitrary source code module",
|
|
5
|
+
"repository": {
|
|
6
|
+
"url": "https://gitlab.open-xchange.com/fspd/npm-packages/vite-plugin-replace-pkg"
|
|
7
|
+
},
|
|
8
|
+
"license": "MIT",
|
|
9
|
+
"engines": {
|
|
10
|
+
"node": "18.18.0 || ^20.9.0 || >=21.1.0"
|
|
11
|
+
},
|
|
12
|
+
"packageManager": "yarn@4.4.0",
|
|
13
|
+
"type": "module",
|
|
14
|
+
"exports": "./dist/index.js",
|
|
15
|
+
"scripts": {
|
|
16
|
+
"prepare": "husky",
|
|
17
|
+
"prepack": "yarn build && yarn lint",
|
|
18
|
+
"build": "npx --yes rimraf dist && tsc",
|
|
19
|
+
"lint": "eslint ."
|
|
20
|
+
},
|
|
21
|
+
"lint-staged": {
|
|
22
|
+
"*.{js,ts,json}": "yarn lint"
|
|
23
|
+
},
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"@open-xchange/vite-helper": "0.0.3"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@open-xchange/linter-presets": "0.4.2",
|
|
29
|
+
"@types/node": "22.1.0",
|
|
30
|
+
"eslint": "9.8.0",
|
|
31
|
+
"husky": "9.1.4",
|
|
32
|
+
"typescript": "5.5.4",
|
|
33
|
+
"vite": "5.3.5"
|
|
34
|
+
},
|
|
35
|
+
"peerDependencies": {
|
|
36
|
+
"vite": "^5.3"
|
|
37
|
+
},
|
|
38
|
+
"resolutions": {
|
|
39
|
+
"semver": "^7.6.2"
|
|
40
|
+
}
|
|
41
|
+
}
|