@prosopo/config 3.4.2 → 3.4.3

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.
Files changed (57) hide show
  1. package/.turbo/turbo-build$colon$cjs.log +5 -8
  2. package/.turbo/turbo-build$colon$tsc.log +4 -7
  3. package/.turbo/turbo-build.log +6 -12
  4. package/CHANGELOG.md +13 -0
  5. package/LICENSE +201 -0
  6. package/dist/cjs/dependencies.cjs +86 -46
  7. package/dist/cjs/vite/vite.frontend.config.cjs +7 -0
  8. package/dist/cjs/webpack/webpack.config.cjs +8 -8
  9. package/dist/dependencies.d.ts.map +1 -1
  10. package/dist/dependencies.js +85 -44
  11. package/dist/dependencies.js.map +1 -1
  12. package/dist/getDependencies.test.js +151 -125
  13. package/dist/getDependencies.test.js.map +1 -1
  14. package/dist/vite/vite.frontend.config.d.ts.map +1 -1
  15. package/dist/vite/vite.frontend.config.js +8 -1
  16. package/dist/vite/vite.frontend.config.js.map +1 -1
  17. package/dist/webpack/webpack.config.d.ts +2 -0
  18. package/dist/webpack/webpack.config.d.ts.map +1 -1
  19. package/dist/webpack/webpack.config.js +8 -8
  20. package/dist/webpack/webpack.config.js.map +1 -1
  21. package/package.json +106 -103
  22. package/src/dependencies.test.ts +0 -249
  23. package/src/dependencies.ts +0 -259
  24. package/src/getDependencies.test.ts +0 -385
  25. package/src/index.ts +0 -16
  26. package/src/vite/CopyAssetsPlugin.ts +0 -49
  27. package/src/vite/NodejsPolarsDirnamePlugin.ts +0 -50
  28. package/src/vite/NodejsPolarsNativeFilePlugin.ts +0 -105
  29. package/src/vite/TsNoCheckPlugin.ts +0 -31
  30. package/src/vite/bundleConfigs.test.ts +0 -461
  31. package/src/vite/configs.test.ts +0 -223
  32. package/src/vite/index.ts +0 -30
  33. package/src/vite/modifyExportsFiles.test.ts +0 -56
  34. package/src/vite/plugins.test.ts +0 -365
  35. package/src/vite/testFileGlobs.test.ts +0 -162
  36. package/src/vite/testFileGlobs.ts +0 -57
  37. package/src/vite/translations.test.ts +0 -164
  38. package/src/vite/types.ts +0 -13
  39. package/src/vite/vite-plugin-close-and-copy.ts +0 -76
  40. package/src/vite/vite-plugin-copy.ts +0 -67
  41. package/src/vite/vite-plugin-externalize-obfuscator-deadcode.ts +0 -43
  42. package/src/vite/vite-plugin-fix-absolute-imports.ts +0 -40
  43. package/src/vite/vite-plugin-modify-exports-files.ts +0 -64
  44. package/src/vite/vite-plugin-remove-unused-translations.ts +0 -128
  45. package/src/vite/vite-plugin-sourcemap-exclude.ts +0 -36
  46. package/src/vite/vite.backend.config.ts +0 -172
  47. package/src/vite/vite.commonjs.config.ts +0 -98
  48. package/src/vite/vite.esm.config.ts +0 -99
  49. package/src/vite/vite.frontend.config.ts +0 -340
  50. package/src/vite/vite.test.config.ts +0 -115
  51. package/src/vite/vite.threads.test.config.ts +0 -100
  52. package/src/webpack/index.ts +0 -14
  53. package/src/webpack/webpack.config.ts +0 -159
  54. package/tsconfig.cjs.json +0 -9
  55. package/tsconfig.json +0 -17
  56. package/tsconfig.tsbuildinfo +0 -1
  57. package/tsconfig.types.json +0 -9
@@ -1,49 +0,0 @@
1
- // Copyright 2021-2026 Prosopo (UK) Ltd.
2
- //
3
- // Licensed under the Apache License, Version 2.0 (the "License");
4
- // you may not use this file except in compliance with the License.
5
- // You may obtain a copy of the License at
6
- //
7
- // http://www.apache.org/licenses/LICENSE-2.0
8
- //
9
- // Unless required by applicable law or agreed to in writing, software
10
- // distributed under the License is distributed on an "AS IS" BASIS,
11
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
- // See the License for the specific language governing permissions and
13
- // limitations under the License.
14
-
15
- import fs from "node:fs";
16
- import path from "node:path";
17
-
18
- export interface CopyAssetEntry {
19
- src: string;
20
- /** Basename to write into `outDir`. */
21
- dest: string;
22
- }
23
-
24
- /**
25
- * Copy files verbatim next to the bundle, leaving the module graph alone.
26
- *
27
- * Distinct from {@link nodejsPolarsNativeFilePlugin}, which also rewrites any
28
- * import of the file into a `createRequire` of the copy. That rewrite suits
29
- * .node addons and nothing else: an ES module rewritten that way fails at
30
- * runtime, because `require()` refuses an ESM file.
31
- *
32
- * This is for assets the bundle loads by path at runtime rather than by
33
- * import — where the point is that the file stays a separate file with a
34
- * stable name that code can construct a URL for.
35
- */
36
- export const copyAssetsPlugin = (assets: CopyAssetEntry[], outDir: string) => {
37
- const name = "copy-assets-plugin";
38
- return {
39
- name,
40
- generateBundle() {
41
- for (const { src, dest } of assets) {
42
- const out = path.join(outDir, dest);
43
- console.debug(name, "copy", src, "to", out);
44
- fs.mkdirSync(path.dirname(out), { recursive: true });
45
- fs.copyFileSync(src, out);
46
- }
47
- },
48
- };
49
- };
@@ -1,50 +0,0 @@
1
- // Copyright 2021-2026 Prosopo (UK) Ltd.
2
- //
3
- // Licensed under the Apache License, Version 2.0 (the "License");
4
- // you may not use this file except in compliance with the License.
5
- // You may obtain a copy of the License at
6
- //
7
- // http://www.apache.org/licenses/LICENSE-2.0
8
- //
9
- // Unless required by applicable law or agreed to in writing, software
10
- // distributed under the License is distributed on an "AS IS" BASIS,
11
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
- // See the License for the specific language governing permissions and
13
- // limitations under the License.
14
-
15
- import type { Rollup } from "vite";
16
-
17
- export const nodejsPolarsDirnamePlugin = () => {
18
- const name = "nodejs-polars-dirname-plugin";
19
- return {
20
- name,
21
- resolveId(
22
- source: string,
23
- importer: string | undefined,
24
- options: Rollup.ResolveIdExtraOptions,
25
- ) {
26
- // aim for the node_modules/nodejs-polars/bin/native-polars.js file
27
- if (source.endsWith("nodejs-polars/bin/native-polars.js")) {
28
- console.debug(name, "resolves", source, "imported by", importer);
29
- // return the source to indicate this plugin can resolve the import
30
- return source;
31
- }
32
- // return null if this plugin can't resolve the import
33
- return null;
34
- },
35
- transform(code: string, id: string) {
36
- // aim for the node_modules/nodejs-polars/bin/native-polars.js file
37
- if (id.endsWith("nodejs-polars/bin/native-polars.js")) {
38
- // replace all instances of __dirname with the path relative to the output bundle
39
- console.debug(name, "transform", id);
40
- const newCode = code.replaceAll(
41
- "__dirname",
42
- `new URL(import.meta.url).pathname.split('/').slice(0,-1).join('/')`,
43
- );
44
- return newCode;
45
- }
46
- // else return the original code (leave code unrelated to nodejs-polars untouched)
47
- return code;
48
- },
49
- };
50
- };
@@ -1,105 +0,0 @@
1
- // Copyright 2021-2026 Prosopo (UK) Ltd.
2
- //
3
- // Licensed under the Apache License, Version 2.0 (the "License");
4
- // you may not use this file except in compliance with the License.
5
- // You may obtain a copy of the License at
6
- //
7
- // http://www.apache.org/licenses/LICENSE-2.0
8
- //
9
- // Unless required by applicable law or agreed to in writing, software
10
- // distributed under the License is distributed on an "AS IS" BASIS,
11
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
- // See the License for the specific language governing permissions and
13
- // limitations under the License.
14
-
15
- import fs from "node:fs";
16
- import path from "node:path";
17
- import type { Rollup } from "vite";
18
-
19
- /**
20
- * Each entry is either a plain path (copied under its original basename) or
21
- * `{ src, dest }` where `dest` is the basename to write into `outDir`.
22
- * Explicit `dest` lets consumers avoid collisions when they copy multiple
23
- * .node files that napi-rs happens to name the same thing (each package it
24
- * generates puts its binary at `index.<triple>.node`).
25
- */
26
- export type NativeFileEntry = string | { src: string; dest: string };
27
-
28
- const entrySrc = (e: NativeFileEntry): string =>
29
- typeof e === "string" ? e : e.src;
30
-
31
- const entryDestBasename = (e: NativeFileEntry): string =>
32
- typeof e === "string" ? path.basename(e) : e.dest;
33
-
34
- export const nodejsPolarsNativeFilePlugin = (
35
- nodeFiles: NativeFileEntry[],
36
- outDir: string,
37
- ) => {
38
- const name = "nodejs-polars-native-file-plugin";
39
- return {
40
- name,
41
- resolveId(
42
- source: string,
43
- importer: string | undefined,
44
- options: Rollup.ResolveIdExtraOptions,
45
- ) {
46
- // return the id if this plugin can resolve the import
47
- for (const entry of nodeFiles) {
48
- if (path.basename(source) === path.basename(entrySrc(entry))) {
49
- console.debug(name, "resolves", source, "imported by", importer);
50
- return source;
51
- }
52
- }
53
- return null; // otherwise return null indicating that this plugin can't handle the import
54
- },
55
- transform(code: string, id: string) {
56
- for (const entry of nodeFiles) {
57
- // rewrite the code to import the .node file
58
- if (path.basename(id) === path.basename(entrySrc(entry))) {
59
- console.debug(name, "transform", id);
60
- // Load the (possibly renamed) copy that lives beside the bundle.
61
- const destBasename = entryDestBasename(entry);
62
- // https://stackoverflow.com/questions/66378682/nodejs-loading-es-modules-and-native-addons-in-the-same-project
63
- // this makes the .node file load at runtime from an esm context. .node files aren't native to esm, so we have to create a custom require function to load them. The custom require function is equivalent to the require function in commonjs, thus allowing the .node file to be loaded.
64
- return `
65
- // create a custom require function to load .node files
66
- import { createRequire } from 'module';
67
- const customRequire = createRequire(import.meta.url)
68
-
69
- // load the .node file expecting it to be in the same directory as the output bundle
70
- const content = customRequire('./${destBasename}')
71
-
72
- // export the content straight back out again
73
- export default content
74
- `;
75
- }
76
- }
77
- return code;
78
- },
79
- load(id: string) {
80
- for (const entry of nodeFiles) {
81
- if (path.basename(id) === path.basename(entrySrc(entry))) {
82
- console.debug(name, "load", id);
83
- // whenever we encounter an import of the .node file, we return an empty string. This makes it look like the .node file is empty to the bundler. This is because we're going to copy the .node file to the output directory ourselves, so we don't want the bundler to include it in the output bundle (also because the bundler can't handle .node files, it tries to read them as js and then complains that it's invalid js)
84
- const newCode = "";
85
- return newCode;
86
- }
87
- }
88
- return null;
89
- },
90
- generateBundle(
91
- options: Rollup.NormalizedOutputOptions,
92
- bundle: Rollup.OutputBundle,
93
- ) {
94
- for (const entry of nodeFiles) {
95
- const src = entrySrc(entry);
96
- const destBasename = entryDestBasename(entry);
97
- const out = `${outDir}/${destBasename}`;
98
- console.debug(name, "copy", src, "to", out);
99
- const nodeFile = fs.readFileSync(src);
100
- fs.mkdirSync(path.dirname(out), { recursive: true });
101
- fs.writeFileSync(out, nodeFile);
102
- }
103
- },
104
- };
105
- };
@@ -1,31 +0,0 @@
1
- // Copyright 2021-2026 Prosopo (UK) Ltd.
2
- //
3
- // Licensed under the Apache License, Version 2.0 (the "License");
4
- // you may not use this file except in compliance with the License.
5
- // You may obtain a copy of the License at
6
- //
7
- // http://www.apache.org/licenses/LICENSE-2.0
8
- //
9
- // Unless required by applicable law or agreed to in writing, software
10
- // distributed under the License is distributed on an "AS IS" BASIS,
11
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
- // See the License for the specific language governing permissions and
13
- // limitations under the License.
14
- import type { Rollup } from "vite";
15
-
16
- export function tsNoCheckPlugin() {
17
- return {
18
- name: "vite-plugin-ts-nocheck",
19
- generateBundle(
20
- _: Rollup.NormalizedOutputOptions,
21
- bundle: Rollup.OutputBundle,
22
- ) {
23
- for (const fileName in bundle) {
24
- const chunk = bundle[fileName];
25
- if (chunk?.type === "chunk" && chunk.code) {
26
- chunk.code = `// @ts-nocheck\n${chunk.code}`;
27
- }
28
- }
29
- },
30
- };
31
- }
@@ -1,461 +0,0 @@
1
- // Copyright 2021-2026 Prosopo (UK) Ltd.
2
- //
3
- // Licensed under the Apache License, Version 2.0 (the "License");
4
- // you may not use this file except in compliance with the License.
5
- // You may obtain a copy of the License at
6
- //
7
- // http://www.apache.org/licenses/LICENSE-2.0
8
- //
9
- // Unless required by applicable law or agreed to in writing, software
10
- // distributed under the License is distributed on an "AS IS" BASIS,
11
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
- // See the License for the specific language governing permissions and
13
- // limitations under the License.
14
-
15
- import fs from "node:fs";
16
- import { builtinModules } from "node:module";
17
- import os from "node:os";
18
- import path from "node:path";
19
- import type { Rollup, UserConfig } from "vite";
20
- import { afterEach, beforeEach, describe, expect, it } from "vitest";
21
- import { copyAssetsPlugin } from "./CopyAssetsPlugin.js";
22
- import { nodejsPolarsNativeFilePlugin } from "./NodejsPolarsNativeFilePlugin.js";
23
- import VitePluginCloseAndCopy from "./vite-plugin-close-and-copy.js";
24
- import ViteCommonJSConfig from "./vite.commonjs.config.js";
25
- import ViteEsmConfig from "./vite.esm.config.js";
26
-
27
- let root: string;
28
- let cwd: string;
29
-
30
- beforeEach(() => {
31
- root = fs.mkdtempSync(path.join(os.tmpdir(), "prosopo-bundle-config-test-"));
32
- cwd = process.cwd();
33
- });
34
-
35
- afterEach(() => {
36
- process.chdir(cwd);
37
- fs.rmSync(root, { recursive: true, force: true });
38
- });
39
-
40
- const resolveOptions: Rollup.ResolveIdExtraOptions = {
41
- isEntry: false,
42
- kind: "import-statement",
43
- };
44
- const outputOptions = {} as Rollup.NormalizedOutputOptions;
45
-
46
- /** A tsconfig with no references, so no npm lookups are needed. */
47
- const leafTsConfig = (): string => {
48
- const dir = path.join(root, "pkg");
49
- fs.mkdirSync(dir, { recursive: true });
50
- fs.writeFileSync(
51
- path.join(dir, "package.json"),
52
- JSON.stringify({ name: "@prosopo/pkg" }),
53
- );
54
- const tsConfigPath = path.join(dir, "tsconfig.json");
55
- fs.writeFileSync(tsConfigPath, JSON.stringify({ references: [] }));
56
- return tsConfigPath;
57
- };
58
-
59
- // Hand-rolled rather than `.flat(Infinity)`: a non-literal depth makes tsc
60
- // expand FlatArray until it gives up with TS2589.
61
- const flattenDeep = (value: unknown): unknown[] =>
62
- Array.isArray(value) ? value.flatMap(flattenDeep) : [value];
63
-
64
- const pluginNames = (config: UserConfig): string[] =>
65
- flattenDeep(config.plugins ?? []).map((plugin) => {
66
- if (plugin && typeof plugin === "object" && "name" in plugin) {
67
- return String(plugin.name);
68
- }
69
- return "";
70
- });
71
-
72
- /**
73
- * Drive the `generateBundle` hook of the inline import.meta.url plugin over a
74
- * fake bundle. Only that one plugin defines the hook.
75
- */
76
- type FakeBundle = Record<string, { type: string; code?: string }>;
77
-
78
- const generateBundle = async (
79
- config: UserConfig,
80
- bundle: FakeBundle,
81
- ): Promise<void> => {
82
- for (const plugin of flattenDeep(config.plugins ?? [])) {
83
- if (
84
- !plugin ||
85
- typeof plugin !== "object" ||
86
- !("generateBundle" in plugin)
87
- ) {
88
- continue;
89
- }
90
- const hook = plugin.generateBundle;
91
- if (typeof hook === "function") {
92
- await callHook(
93
- hook as (options: object, bundle: FakeBundle) => unknown,
94
- {},
95
- bundle,
96
- );
97
- }
98
- }
99
- };
100
-
101
- const externals = (config: UserConfig): string[] => {
102
- const external = config.build?.rollupOptions?.external;
103
- if (!Array.isArray(external)) {
104
- throw new Error("expected an array of externals");
105
- }
106
- return external.map(String);
107
- };
108
-
109
- describe("ViteEsmConfig", () => {
110
- it("emits a single esm library targeting the runtime node version", async () => {
111
- const config = await ViteEsmConfig("my-lib", leafTsConfig());
112
- expect(config.build?.lib).toMatchObject({
113
- name: "my-lib",
114
- formats: ["es"],
115
- entry: "src/index.ts",
116
- });
117
- expect(config.build?.target).toBe("node24");
118
- expect(config.build?.outDir).toBe("dist");
119
- });
120
-
121
- it("accepts an explicit entry, including a multi-entry map", async () => {
122
- const tsConfigPath = leafTsConfig();
123
- await expect(
124
- ViteEsmConfig("my-lib", tsConfigPath, "src/other.ts"),
125
- ).resolves.toMatchObject({ build: { lib: { entry: "src/other.ts" } } });
126
- await expect(
127
- ViteEsmConfig("my-lib", tsConfigPath, { a: "src/a.ts", b: "src/b.ts" }),
128
- ).resolves.toMatchObject({
129
- build: { lib: { entry: { a: "src/a.ts", b: "src/b.ts" } } },
130
- });
131
- });
132
-
133
- it("externalises every node builtin under both bare and node: specifiers", async () => {
134
- const list = externals(await ViteEsmConfig("my-lib", leafTsConfig()));
135
- for (const builtin of builtinModules) {
136
- expect(list).toContain(builtin);
137
- expect(list).toContain(`node:${builtin}`);
138
- }
139
- });
140
-
141
- it("externalises the webpack toolchain, which vite must not bundle", async () => {
142
- const list = externals(await ViteEsmConfig("my-lib", leafTsConfig()));
143
- expect(list).toContain("webpack");
144
- expect(list).toContain("html-webpack-plugin");
145
- expect(list).toContain("babel-loader");
146
- });
147
-
148
- it("uses the same external list for ssr and for rollup", async () => {
149
- const config = await ViteEsmConfig("my-lib", leafTsConfig());
150
- expect(config.ssr?.external).toEqual(externals(config));
151
- });
152
-
153
- it("pulls package names out of the referenced projects", async () => {
154
- const dep = path.join(root, "dep");
155
- fs.mkdirSync(dep, { recursive: true });
156
- fs.writeFileSync(
157
- path.join(dep, "package.json"),
158
- JSON.stringify({ name: "@prosopo/dep" }),
159
- );
160
- fs.writeFileSync(
161
- path.join(dep, "tsconfig.json"),
162
- JSON.stringify({ references: [] }),
163
- );
164
- const tsConfigPath = leafTsConfig();
165
- fs.writeFileSync(
166
- tsConfigPath,
167
- JSON.stringify({ references: [{ path: "../dep" }] }),
168
- );
169
-
170
- expect(externals(await ViteEsmConfig("my-lib", tsConfigPath))).toContain(
171
- "@prosopo/dep",
172
- );
173
- });
174
-
175
- it("disables treeshaking and keeps the output directory intact", async () => {
176
- // Packages emit esm and cjs into the same tree from two separate runs,
177
- // so emptying the out dir would delete the sibling build.
178
- const config = await ViteEsmConfig("my-lib", leafTsConfig());
179
- expect(config.build?.rollupOptions?.treeshake).toBe(false);
180
- expect(config.build?.emptyOutDir).toBe(false);
181
- });
182
-
183
- it("suppresses the CJS require polyfill so browsers can load the output", async () => {
184
- const config = await ViteEsmConfig("my-lib", leafTsConfig());
185
- const output = config.build?.rollupOptions?.output;
186
- expect(output).toMatchObject({ polyfillRequire: false });
187
- });
188
-
189
- it("registers the copy and tsconfig-paths plugins", async () => {
190
- const names = pluginNames(await ViteEsmConfig("my-lib", leafTsConfig()));
191
- expect(names).toContain("close-plugin");
192
- expect(names).toContain("vite-tsconfig-paths");
193
- });
194
-
195
- it("uses the automatic jsx runtime", async () => {
196
- const config = await ViteEsmConfig("my-lib", leafTsConfig());
197
- expect(config.esbuild).toMatchObject({ jsx: "automatic" });
198
- });
199
-
200
- it("rejects a tsconfig path that does not exist", async () => {
201
- await expect(
202
- ViteEsmConfig("my-lib", path.join(root, "missing.json")),
203
- ).rejects.toBeDefined();
204
- });
205
- });
206
-
207
- describe("ViteCommonJSConfig", () => {
208
- it("emits cjs into its own subdirectory so it cannot clash with the esm build", async () => {
209
- const config = await ViteCommonJSConfig("my-lib", leafTsConfig());
210
- expect(config.build?.lib).toMatchObject({
211
- name: "my-lib",
212
- formats: ["cjs"],
213
- });
214
- expect(config.build?.outDir).toBe("dist/cjs");
215
- });
216
-
217
- it("does not externalise the webpack toolchain, unlike the esm config", async () => {
218
- const tsConfigPath = leafTsConfig();
219
- expect(
220
- externals(await ViteCommonJSConfig("my-lib", tsConfigPath)),
221
- ).not.toContain("html-webpack-plugin");
222
- expect(externals(await ViteEsmConfig("my-lib", tsConfigPath))).toContain(
223
- "html-webpack-plugin",
224
- );
225
- });
226
-
227
- it("rewrites import.meta.url in every emitted chunk, since cjs has none", async () => {
228
- const bundle: Record<string, { type: string; code?: string }> = {
229
- "a.js": { type: "chunk", code: "const a = import.meta.url;" },
230
- "b.js": { type: "chunk", code: "const b = import.meta.url + 1;" },
231
- };
232
- await generateBundle(
233
- await ViteCommonJSConfig("my-lib", leafTsConfig()),
234
- bundle,
235
- );
236
-
237
- expect(bundle["a.js"]?.code).toBe(
238
- "const a = require('url').pathToFileURL(__filename).href;",
239
- );
240
- expect(bundle["b.js"]?.code).toBe(
241
- "const b = require('url').pathToFileURL(__filename).href + 1;",
242
- );
243
- });
244
-
245
- it("leaves assets and empty chunks alone", async () => {
246
- // An asset has no `code` property at all, so touching it would throw.
247
- const bundle: Record<string, { type: string; code?: string }> = {
248
- "style.css": { type: "asset" },
249
- "empty.js": { type: "chunk", code: "" },
250
- };
251
- await expect(
252
- generateBundle(
253
- await ViteCommonJSConfig("my-lib", leafTsConfig()),
254
- bundle,
255
- ),
256
- ).resolves.toBeUndefined();
257
- expect(bundle["empty.js"]?.code).toBe("");
258
- });
259
-
260
- it("honours an explicit entry", async () => {
261
- await expect(
262
- ViteCommonJSConfig("my-lib", leafTsConfig(), "src/cli.ts"),
263
- ).resolves.toMatchObject({ build: { lib: { entry: "src/cli.ts" } } });
264
- });
265
- });
266
-
267
- describe("nodejsPolarsNativeFilePlugin", () => {
268
- it("claims a native file by basename, wherever it is imported from", () => {
269
- const plugin = nodejsPolarsNativeFilePlugin(["/abs/polars.node"], "dist");
270
- expect(plugin.resolveId("./polars.node", "/src/a.ts", resolveOptions)).toBe(
271
- "./polars.node",
272
- );
273
- expect(
274
- plugin.resolveId("./other.node", "/src/a.ts", resolveOptions),
275
- ).toBeNull();
276
- });
277
-
278
- it("replaces the module with a createRequire shim", () => {
279
- const plugin = nodejsPolarsNativeFilePlugin(["polars.node"], "dist");
280
- const transformed = plugin.transform("ignored", "/abs/polars.node");
281
- expect(transformed).toContain("createRequire");
282
- expect(transformed).toContain("'./polars.node'");
283
- expect(transformed).toContain("export default content");
284
- });
285
-
286
- it("leaves every other module untouched", () => {
287
- const plugin = nodejsPolarsNativeFilePlugin(["polars.node"], "dist");
288
- expect(plugin.transform("const a = 1;", "/src/index.ts")).toBe(
289
- "const a = 1;",
290
- );
291
- });
292
-
293
- it("loads a native file as empty so the bundler never parses it as js", () => {
294
- const plugin = nodejsPolarsNativeFilePlugin(["polars.node"], "dist");
295
- expect(plugin.load("/abs/polars.node")).toBe("");
296
- expect(plugin.load("/src/index.ts")).toBeNull();
297
- });
298
-
299
- it("copies the native files into the output directory verbatim", () => {
300
- const src = path.join(root, "polars.node");
301
- const outDir = path.join(root, "nested", "dist");
302
- const bytes = Buffer.from([0, 1, 2, 255]);
303
- fs.writeFileSync(src, bytes);
304
-
305
- nodejsPolarsNativeFilePlugin([src], outDir).generateBundle(
306
- outputOptions,
307
- {},
308
- );
309
-
310
- expect(fs.readFileSync(path.join(outDir, "polars.node"))).toEqual(bytes);
311
- });
312
-
313
- it("throws when a declared native file is missing", () => {
314
- // A silent skip would produce a bundle that dies at runtime with an
315
- // unhelpful "cannot find module" instead of failing the build.
316
- const plugin = nodejsPolarsNativeFilePlugin(
317
- [path.join(root, "absent.node")],
318
- path.join(root, "dist"),
319
- );
320
- expect(() => plugin.generateBundle(outputOptions, {})).toThrow();
321
- });
322
-
323
- it("does nothing at all when there are no native files", () => {
324
- const plugin = nodejsPolarsNativeFilePlugin([], "dist");
325
- expect(plugin.resolveId("anything", undefined, resolveOptions)).toBeNull();
326
- expect(plugin.load("anything")).toBeNull();
327
- expect(() => plugin.generateBundle(outputOptions, {})).not.toThrow();
328
- });
329
- });
330
-
331
- describe("copyAssetsPlugin", () => {
332
- it("copies each asset under its declared basename", () => {
333
- const src = path.join(root, "decodePayload.js");
334
- const outDir = path.join(root, "nested", "dist");
335
- fs.writeFileSync(src, "export default 1;");
336
-
337
- copyAssetsPlugin(
338
- [{ src, dest: "decodePayload.js" }],
339
- outDir,
340
- ).generateBundle();
341
-
342
- expect(fs.readFileSync(path.join(outDir, "decodePayload.js"), "utf8")).toBe(
343
- "export default 1;",
344
- );
345
- });
346
-
347
- it("renames on copy when dest differs from the source basename", () => {
348
- const src = path.join(root, "a.js");
349
- const outDir = path.join(root, "dist");
350
- fs.writeFileSync(src, "x");
351
-
352
- copyAssetsPlugin([{ src, dest: "b.js" }], outDir).generateBundle();
353
-
354
- expect(fs.existsSync(path.join(outDir, "b.js"))).toBe(true);
355
- expect(fs.existsSync(path.join(outDir, "a.js"))).toBe(false);
356
- });
357
-
358
- it("throws when a declared asset is missing", () => {
359
- // Skipping silently would ship a bundle whose decoders resolve to
360
- // nothing, and the first request would be the one to find out.
361
- const plugin = copyAssetsPlugin(
362
- [{ src: path.join(root, "absent.js"), dest: "absent.js" }],
363
- path.join(root, "dist"),
364
- );
365
- expect(() => plugin.generateBundle()).toThrow();
366
- });
367
-
368
- it("does nothing at all when there are no assets", () => {
369
- expect(() =>
370
- copyAssetsPlugin([], path.join(root, "dist")).generateBundle(),
371
- ).not.toThrow();
372
- });
373
- });
374
-
375
- describe("VitePluginCloseAndCopy", () => {
376
- it("is a no-op at close time when no copy options are given", () => {
377
- const plugin = VitePluginCloseAndCopy();
378
- expect(plugin.name).toBe("close-plugin");
379
- expect(() => callHook(plugin.closeBundle)).not.toThrow();
380
- });
381
-
382
- it("clears the js already in the destination, then copies the bundle over", () => {
383
- process.chdir(root);
384
- fs.mkdirSync(path.join(root, "src"));
385
- fs.writeFileSync(path.join(root, "src", "new.js"), "new");
386
- fs.mkdirSync(path.join(root, "dest"));
387
- fs.writeFileSync(path.join(root, "dest", "stale.js"), "stale");
388
- fs.writeFileSync(path.join(root, "dest", "keep.css"), "keep");
389
-
390
- const plugin = VitePluginCloseAndCopy({
391
- srcDir: "src",
392
- destDir: ["dest"],
393
- });
394
- callHook(plugin.closeBundle);
395
-
396
- expect(fs.readdirSync(path.join(root, "dest")).sort()).toEqual([
397
- "keep.css",
398
- "new.js",
399
- ]);
400
- });
401
-
402
- it("copies into every destination", () => {
403
- process.chdir(root);
404
- fs.mkdirSync(path.join(root, "src"));
405
- fs.writeFileSync(path.join(root, "src", "a.js"), "a");
406
- fs.mkdirSync(path.join(root, "one"));
407
- fs.mkdirSync(path.join(root, "two"));
408
-
409
- callHook(
410
- VitePluginCloseAndCopy({ srcDir: "src", destDir: ["one", "two"] })
411
- .closeBundle,
412
- );
413
-
414
- expect(fs.existsSync(path.join(root, "one", "a.js"))).toBe(true);
415
- expect(fs.existsSync(path.join(root, "two", "a.js"))).toBe(true);
416
- });
417
-
418
- it("throws when a destination does not exist", () => {
419
- process.chdir(root);
420
- expect(() =>
421
- callHook(
422
- VitePluginCloseAndCopy({ srcDir: "src", destDir: ["absent"] })
423
- .closeBundle,
424
- ),
425
- ).toThrow();
426
- });
427
-
428
- it("logs the build error it is handed rather than swallowing it", () => {
429
- const plugin = VitePluginCloseAndCopy();
430
- expect(() =>
431
- callHook(plugin.buildEnd, new Error("bundle blew up")),
432
- ).not.toThrow();
433
- });
434
-
435
- it("announces the start of the build", () => {
436
- expect(() =>
437
- callHook(
438
- VitePluginCloseAndCopy().buildStart,
439
- {} as Rollup.NormalizedInputOptions,
440
- ),
441
- ).not.toThrow();
442
- });
443
- });
444
-
445
- /**
446
- * Vite plugin hooks may be a bare function or an object with a `handler`.
447
- * Neither of these hooks touches the rollup plugin context, so they are safe
448
- * to call unbound.
449
- */
450
- type Hook<A extends unknown[]> =
451
- | ((...args: A) => unknown)
452
- | { handler: (...args: A) => unknown }
453
- | undefined;
454
-
455
- const callHook = <A extends unknown[]>(hook: Hook<A>, ...args: A): unknown => {
456
- if (!hook) {
457
- throw new Error("plugin does not define the hook");
458
- }
459
- const handler = typeof hook === "function" ? hook : hook.handler;
460
- return handler(...args);
461
- };