@adonisjs/vite 5.1.0 → 6.0.0-next.0
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/build/index.js +69 -45
- package/build/providers/vite_provider.js +138 -139
- package/build/services/vite.js +9 -6
- package/build/src/client/main.js +1002 -59
- package/build/src/client/reload.d.ts +13 -0
- package/build/src/client/resolve_assets.d.ts +20 -0
- package/build/src/client/types.d.ts +26 -1
- package/build/src/hooks/build_hook.js +20 -7
- package/build/src/plugins/edge.js +85 -67
- package/build/src/server_modules/bundled_module_resolver.d.ts +17 -0
- package/build/src/server_modules/dev_module_runner.d.ts +25 -0
- package/build/src/server_modules/server_module_loader.d.ts +25 -0
- package/build/src/types.d.ts +32 -0
- package/build/src/types.js +1 -0
- package/build/src/vite.d.ts +22 -1
- package/build/src/vite_middleware.js +2 -6
- package/build/utils-CdZpa_tV.js +50 -0
- package/build/vite-De-RhsJ1.js +630 -0
- package/build/vite_middleware-CuOBHhnt.js +72 -0
- package/package.json +22 -20
- package/build/chunk-AF6PV64J.js +0 -56
- package/build/chunk-ZCIGOANY.js +0 -451
- package/build/chunk-ZCQTQOMI.js +0 -29
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type Plugin } from 'vite';
|
|
2
|
+
export interface ReloadOptions {
|
|
3
|
+
delay?: number;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Returns a Vite plugin that triggers a full browser reload whenever any file
|
|
7
|
+
* matching the supplied glob patterns changes. Patterns are resolved relative
|
|
8
|
+
* to the Vite project root.
|
|
9
|
+
*
|
|
10
|
+
* Replaces the previous `vite-plugin-restart` dependency. Only the reload
|
|
11
|
+
* (browser refresh) feature is implemented — server restart is not.
|
|
12
|
+
*/
|
|
13
|
+
export declare function reload(patterns: string | string[], options?: ReloadOptions): Plugin;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Plugin } from 'vite';
|
|
2
|
+
import type { PluginOptions } from './types.ts';
|
|
3
|
+
/**
|
|
4
|
+
* Returns a plugin that emits user-supplied files into the build output.
|
|
5
|
+
*
|
|
6
|
+
* `chunks` are passed to Vite's `emitFile({ type: 'chunk' })` after glob
|
|
7
|
+
* expansion. They are processed by the bundler and surface in the manifest
|
|
8
|
+
* with hashed filenames.
|
|
9
|
+
*
|
|
10
|
+
* `assets` are emitted as raw `type: 'asset'` files (no glob expansion —
|
|
11
|
+
* exact paths only) so the original source content is preserved verbatim.
|
|
12
|
+
* The `originalFileName` field tells Vite to use the source path as the
|
|
13
|
+
* manifest key, so templates can resolve them via
|
|
14
|
+
* `vite.assetPath('resources/images/logo.png')` without any post-build
|
|
15
|
+
* manifest rewriting.
|
|
16
|
+
*
|
|
17
|
+
* Returns an empty array (no-op) when neither chunks nor assets are
|
|
18
|
+
* configured.
|
|
19
|
+
*/
|
|
20
|
+
export declare function resolveAssets(input: PluginOptions['assets']): Plugin[];
|
|
@@ -22,5 +22,30 @@ export interface PluginOptions {
|
|
|
22
22
|
* @default 'public/assets'
|
|
23
23
|
*/
|
|
24
24
|
buildDirectory?: string;
|
|
25
|
+
/**
|
|
26
|
+
* Server-side entrypoints bundled into the SSR build output. Each
|
|
27
|
+
* entry is emitted as a single bundle (no hash, no shared chunks)
|
|
28
|
+
* under `<buildDirectory>/server/<name>.js` and becomes loadable
|
|
29
|
+
* through `vite.loadServerModule()` at runtime.
|
|
30
|
+
*
|
|
31
|
+
* Paths are relative to the project root.
|
|
32
|
+
*/
|
|
33
|
+
serverEntrypoints?: string[];
|
|
34
|
+
/**
|
|
35
|
+
* Additional files to include in the build that are not imported by the
|
|
36
|
+
* entrypoints.
|
|
37
|
+
*
|
|
38
|
+
* - When passed as a `string[]`, every entry is treated as a chunk: glob
|
|
39
|
+
* patterns are expanded and each matching file is emitted via Vite's
|
|
40
|
+
* `emitFile({ type: 'chunk' })`.
|
|
41
|
+
* - When passed as `{ chunks?, assets? }`, `chunks` behaves identically
|
|
42
|
+
* to the array form, while `assets` files are emitted as raw assets
|
|
43
|
+
* (no glob support, exact paths only) and the manifest is rewritten so
|
|
44
|
+
* the manifest key matches the source path.
|
|
45
|
+
*/
|
|
46
|
+
assets?: string[] | {
|
|
47
|
+
chunks?: string[];
|
|
48
|
+
assets?: string[];
|
|
49
|
+
};
|
|
25
50
|
}
|
|
26
|
-
export type PluginFullOptions = Required<PluginOptions
|
|
51
|
+
export type PluginFullOptions = Required<Omit<PluginOptions, 'assets'>>;
|
|
@@ -1,11 +1,24 @@
|
|
|
1
|
-
// src/hooks/build_hook.ts
|
|
2
1
|
import { hooks } from "@adonisjs/core/app";
|
|
3
2
|
import { createBuilder } from "vite";
|
|
3
|
+
//#region src/hooks/build_hook.ts
|
|
4
|
+
/**
|
|
5
|
+
* This is an Assembler hook that should be executed when the application is
|
|
6
|
+
* builded using the `node ace build` command.
|
|
7
|
+
*
|
|
8
|
+
* The hook is responsible for launching a Vite multi-build process.
|
|
9
|
+
*/
|
|
4
10
|
var build_hook_default = hooks.buildStarting(async (parent) => {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
11
|
+
/**
|
|
12
|
+
* Vite's CLI sets NODE_ENV to 'production' automatically when running
|
|
13
|
+
* `vite build`, but the programmatic `createBuilder` API does not.
|
|
14
|
+
* Without this, framework plugins (React, Vue, MDX, …) emit dev-only
|
|
15
|
+
* code paths in the production bundle.
|
|
16
|
+
*
|
|
17
|
+
* See https://github.com/remix-run/remix/issues/4081
|
|
18
|
+
*/
|
|
19
|
+
process.env.NODE_ENV = "production";
|
|
20
|
+
parent.ui.logger.info("building assets with vite");
|
|
21
|
+
await (await createBuilder({}, false)).buildApp();
|
|
8
22
|
});
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
};
|
|
23
|
+
//#endregion
|
|
24
|
+
export { build_hook_default as default };
|
|
@@ -1,69 +1,87 @@
|
|
|
1
|
-
// src/plugins/edge.ts
|
|
2
1
|
import { EdgeError } from "edge-error";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
2
|
+
//#region src/plugins/edge.ts
|
|
3
|
+
/**
|
|
4
|
+
* Creates an Edge.js plugin that integrates Vite functionality into templates
|
|
5
|
+
*
|
|
6
|
+
* Registers global helpers and custom tags (@vite, @viteReactRefresh) for use in Edge templates.
|
|
7
|
+
* Provides access to asset paths and HMR functionality within template rendering.
|
|
8
|
+
*
|
|
9
|
+
* @param vite - The Vite instance to integrate with Edge templates
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* const plugin = edgePluginVite(viteInstance)
|
|
13
|
+
* edge.use(plugin)
|
|
14
|
+
*
|
|
15
|
+
* // In templates:
|
|
16
|
+
* // @vite('app.js')
|
|
17
|
+
* // @viteReactRefresh({ nonce: 'abc123' })
|
|
18
|
+
*/
|
|
19
|
+
const edgePluginVite = (vite) => {
|
|
20
|
+
const edgeVite = (edge) => {
|
|
21
|
+
edge.global("vite", vite);
|
|
22
|
+
edge.global("asset", vite.assetPath.bind(vite));
|
|
23
|
+
edge.registerTag({
|
|
24
|
+
tagName: "viteReactRefresh",
|
|
25
|
+
seekable: true,
|
|
26
|
+
block: false,
|
|
27
|
+
compile(parser, buffer, token) {
|
|
28
|
+
let attributes = "";
|
|
29
|
+
if (token.properties.jsArg.trim()) {
|
|
30
|
+
/**
|
|
31
|
+
* Converting a single argument to a SequenceExpression so that we
|
|
32
|
+
* work around the following edge cases.
|
|
33
|
+
*
|
|
34
|
+
* - If someone passes an object literal to the tag, ie { nonce: 'foo' }
|
|
35
|
+
* it will be parsed as a LabeledStatement and not an object.
|
|
36
|
+
* - If we wrap the object literal inside parenthesis, ie ({nonce: 'foo'})
|
|
37
|
+
* then we will end up messing other expressions like a variable reference
|
|
38
|
+
* , or a member expression and so on.
|
|
39
|
+
* - So the best bet is to convert user supplied argument to a sequence expression
|
|
40
|
+
* and hence ignore it during stringification.
|
|
41
|
+
*/
|
|
42
|
+
const jsArg = `a,${token.properties.jsArg}`;
|
|
43
|
+
const parsed = parser.utils.transformAst(parser.utils.generateAST(jsArg, token.loc, token.filename), token.filename, parser);
|
|
44
|
+
attributes = parser.utils.stringify(parsed.expressions[1]);
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Get HMR script
|
|
48
|
+
*/
|
|
49
|
+
buffer.writeExpression(`const __vite_hmr_script = state.vite.getReactHmrScript(${attributes})`, token.filename, token.loc.start.line);
|
|
50
|
+
/**
|
|
51
|
+
* Check if the script exists (only in hot mode)
|
|
52
|
+
*/
|
|
53
|
+
buffer.writeStatement("if(__vite_hmr_script) {", token.filename, token.loc.start.line);
|
|
54
|
+
/**
|
|
55
|
+
* Write output
|
|
56
|
+
*/
|
|
57
|
+
buffer.outputExpression(`__vite_hmr_script.toString()`, token.filename, token.loc.start.line, false);
|
|
58
|
+
/**
|
|
59
|
+
* Close if block
|
|
60
|
+
*/
|
|
61
|
+
buffer.writeStatement("}", token.filename, token.loc.start.line);
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
edge.registerTag({
|
|
65
|
+
tagName: "vite",
|
|
66
|
+
seekable: true,
|
|
67
|
+
block: false,
|
|
68
|
+
compile(parser, buffer, token) {
|
|
69
|
+
/**
|
|
70
|
+
* Ensure an argument is defined
|
|
71
|
+
*/
|
|
72
|
+
if (!token.properties.jsArg.trim()) throw new EdgeError("Missing entrypoint name", "E_RUNTIME_EXCEPTION", {
|
|
73
|
+
filename: token.filename,
|
|
74
|
+
line: token.loc.start.line,
|
|
75
|
+
col: token.loc.start.col
|
|
76
|
+
});
|
|
77
|
+
const parsed = parser.utils.transformAst(parser.utils.generateAST(token.properties.jsArg, token.loc, token.filename), token.filename, parser);
|
|
78
|
+
const entrypoints = parser.utils.stringify(parsed);
|
|
79
|
+
const methodCall = parsed.type === "SequenceExpression" ? `generateEntryPointsTags${entrypoints}` : `generateEntryPointsTags(${entrypoints})`;
|
|
80
|
+
buffer.outputExpression(`(await state.vite.${methodCall}).join('\\n')`, token.filename, token.loc.start.line, false);
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
};
|
|
84
|
+
return edgeVite;
|
|
69
85
|
};
|
|
86
|
+
//#endregion
|
|
87
|
+
export { edgePluginVite };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolves and imports server-side modules from the production SSR build.
|
|
3
|
+
*
|
|
4
|
+
* In production there is no Vite dev server — entrypoints declared as
|
|
5
|
+
* `serverEntrypoints` are pre-built into `<buildDirectory>/server` and
|
|
6
|
+
* recorded in `<buildDirectory>/server/.vite/manifest.json`. The
|
|
7
|
+
* resolver reads that manifest to map an entry source path to the
|
|
8
|
+
* emitted bundle file, then imports it through Node's native `import()`.
|
|
9
|
+
*
|
|
10
|
+
* Imports are cached per entry so repeated calls reuse the same module
|
|
11
|
+
* instance and side-effects only run once.
|
|
12
|
+
*/
|
|
13
|
+
export declare class BundledModuleResolver {
|
|
14
|
+
#private;
|
|
15
|
+
constructor(buildDirectory: string);
|
|
16
|
+
import<T>(entry: string): Promise<T>;
|
|
17
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { ViteDevServer } from 'vite';
|
|
2
|
+
import type { ModuleRunner } from 'vite/module-runner';
|
|
3
|
+
import type { LoadServerModuleOptions } from '../types.ts';
|
|
4
|
+
/**
|
|
5
|
+
* Hosts the Vite `ModuleRunner` used to evaluate server-side modules in
|
|
6
|
+
* development mode.
|
|
7
|
+
*
|
|
8
|
+
* Owns a single shared runner across all `loadServerModule` calls, and
|
|
9
|
+
* detects Vite dev server restarts (which replace `server.environments.ssr`
|
|
10
|
+
* with a fresh instance) so the stale runner is closed and recreated.
|
|
11
|
+
*/
|
|
12
|
+
export declare class DevModuleRunner {
|
|
13
|
+
#private;
|
|
14
|
+
constructor(createRunner: () => Promise<ModuleRunner>);
|
|
15
|
+
/**
|
|
16
|
+
* Imports a module through the runner. Recreates the runner if the
|
|
17
|
+
* dev server's SSR environment was swapped underneath us.
|
|
18
|
+
*/
|
|
19
|
+
import<T>(server: ViteDevServer, entry: string, opts: LoadServerModuleOptions): Promise<T>;
|
|
20
|
+
/**
|
|
21
|
+
* Closes the runner, if any. Safe to call when no runner has been
|
|
22
|
+
* created yet.
|
|
23
|
+
*/
|
|
24
|
+
close(): Promise<void>;
|
|
25
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { ViteDevServer } from 'vite';
|
|
2
|
+
import type { ModuleRunner } from 'vite/module-runner';
|
|
3
|
+
import type { LoadServerModuleOptions } from '../types.ts';
|
|
4
|
+
/**
|
|
5
|
+
* Public entry point for loading server-side modules processed by Vite.
|
|
6
|
+
*
|
|
7
|
+
* Picks between two collaborators based on whether the Vite dev server
|
|
8
|
+
* is running:
|
|
9
|
+
*
|
|
10
|
+
* - In development, delegates to {@link DevModuleRunner} which evaluates
|
|
11
|
+
* the entry through Vite's `ModuleRunner`, with HMR-driven cache
|
|
12
|
+
* invalidation.
|
|
13
|
+
* - In production, delegates to {@link BundledModuleResolver} which
|
|
14
|
+
* imports the pre-built bundle from disk.
|
|
15
|
+
*
|
|
16
|
+
* Entry strings are passed through verbatim to mirror how client
|
|
17
|
+
* `entrypoints` are handled — the caller is responsible for using the
|
|
18
|
+
* exact string declared in `serverEntrypoints`.
|
|
19
|
+
*/
|
|
20
|
+
export declare class ServerModuleLoader {
|
|
21
|
+
#private;
|
|
22
|
+
constructor(getDevServer: () => ViteDevServer | undefined, buildDirectory: string, createRunner: () => Promise<ModuleRunner>);
|
|
23
|
+
load<T>(entry: string, opts?: LoadServerModuleOptions): Promise<T>;
|
|
24
|
+
close(): Promise<void>;
|
|
25
|
+
}
|
package/build/src/types.d.ts
CHANGED
|
@@ -54,3 +54,35 @@ export interface ViteOptions {
|
|
|
54
54
|
*/
|
|
55
55
|
scriptAttributes?: SetAttributes;
|
|
56
56
|
}
|
|
57
|
+
/**
|
|
58
|
+
* Augmentable map for typing entries passed to `vite.loadServerModule`.
|
|
59
|
+
* Apps and packages merge into this interface to associate entrypoint
|
|
60
|
+
* paths with the shape of their default exports.
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* declare module '@adonisjs/vite/types' {
|
|
64
|
+
* interface ServerModuleMap {
|
|
65
|
+
* 'inertia/app/ssr.ts': typeof import('../inertia/app/ssr.ts')
|
|
66
|
+
* }
|
|
67
|
+
* }
|
|
68
|
+
*/
|
|
69
|
+
export interface ServerModuleMap {
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Options accepted by `vite.loadServerModule`.
|
|
73
|
+
*/
|
|
74
|
+
export interface LoadServerModuleOptions {
|
|
75
|
+
/**
|
|
76
|
+
* Clear the module runner cache before importing in dev mode.
|
|
77
|
+
*
|
|
78
|
+
* Defaults to `false` — Vite's HMR pushes invalidations into the
|
|
79
|
+
* runner, so cached modules are already kept fresh on file change.
|
|
80
|
+
* Set to `true` only when the entrypoint registers top-level state
|
|
81
|
+
* that must be reset on every load.
|
|
82
|
+
*
|
|
83
|
+
* Has no effect in production (bundled imports are always cached).
|
|
84
|
+
*
|
|
85
|
+
* @default false
|
|
86
|
+
*/
|
|
87
|
+
fresh?: boolean;
|
|
88
|
+
}
|
package/build/src/types.js
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/build/src/vite.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type ModuleRunner } from 'vite/module-runner';
|
|
2
2
|
import type { Manifest, InlineConfig, ViteDevServer, ServerModuleRunnerOptions } from 'vite';
|
|
3
|
-
import type { AdonisViteElement, ViteOptions } from './types.ts';
|
|
3
|
+
import type { AdonisViteElement, LoadServerModuleOptions, ServerModuleMap, ViteOptions } from './types.ts';
|
|
4
4
|
/**
|
|
5
5
|
* Vite class exposes the APIs to generate tags and URLs for
|
|
6
6
|
* assets processed using vite.
|
|
@@ -111,6 +111,27 @@ export declare class Vite {
|
|
|
111
111
|
* const mod = await runner.import('./app.js')
|
|
112
112
|
*/
|
|
113
113
|
createModuleRunner(options?: ServerModuleRunnerOptions): Promise<ModuleRunner>;
|
|
114
|
+
/**
|
|
115
|
+
* Loads a server-side module that has been processed by Vite.
|
|
116
|
+
*
|
|
117
|
+
* In development, the entry is evaluated through Vite's `ModuleRunner`,
|
|
118
|
+
* giving full TypeScript / JSX / plugin support and HMR-driven cache
|
|
119
|
+
* invalidation. In production, the entry is imported from the
|
|
120
|
+
* pre-built SSR bundle on disk.
|
|
121
|
+
*
|
|
122
|
+
* The entry path must be declared in `serverEntrypoints` for the
|
|
123
|
+
* production import to succeed — otherwise the bundle won't exist.
|
|
124
|
+
*
|
|
125
|
+
* @example
|
|
126
|
+
* const mod = await vite.loadServerModule('inertia/app/ssr.ts')
|
|
127
|
+
* const html = await mod.default(payload)
|
|
128
|
+
*
|
|
129
|
+
* @example
|
|
130
|
+
* // Force re-evaluation (clear runner cache before import)
|
|
131
|
+
* await vite.loadServerModule('emails/welcome.ts', { fresh: true })
|
|
132
|
+
*/
|
|
133
|
+
loadServerModule<K extends keyof ServerModuleMap>(entry: K, opts?: LoadServerModuleOptions): Promise<ServerModuleMap[K]>;
|
|
134
|
+
loadServerModule<T = unknown>(entry: string, opts?: LoadServerModuleOptions): Promise<T>;
|
|
114
135
|
/**
|
|
115
136
|
* Gracefully stops the Vite development server
|
|
116
137
|
*
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
//#region src/utils.ts
|
|
2
|
+
/**
|
|
3
|
+
* Returns a new array with unique items filtered by the specified key
|
|
4
|
+
*
|
|
5
|
+
* @param array - The array to filter for unique items
|
|
6
|
+
* @param key - The key to use for uniqueness comparison
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* const users = [{ id: 1, name: 'John' }, { id: 2, name: 'Jane' }, { id: 1, name: 'John' }]
|
|
10
|
+
* const unique = uniqBy(users, 'id')
|
|
11
|
+
* // Returns: [{ id: 1, name: 'John' }, { id: 2, name: 'Jane' }]
|
|
12
|
+
*/
|
|
13
|
+
function uniqBy(array, key) {
|
|
14
|
+
const seen = /* @__PURE__ */ new Set();
|
|
15
|
+
return array.filter((item) => {
|
|
16
|
+
const k = item[key];
|
|
17
|
+
return seen.has(k) ? false : seen.add(k);
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Converts an object of HTML attributes to a valid HTML attribute string
|
|
22
|
+
*
|
|
23
|
+
* @param attributes - Object containing HTML attributes where values can be strings or booleans
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* const attrs = makeAttributes({ class: 'btn', disabled: true, hidden: false })
|
|
27
|
+
* // Returns: 'class="btn" disabled'
|
|
28
|
+
*/
|
|
29
|
+
function makeAttributes(attributes) {
|
|
30
|
+
return Object.keys(attributes).map((key) => {
|
|
31
|
+
const value = attributes[key];
|
|
32
|
+
if (value === true) return key;
|
|
33
|
+
if (!value) return null;
|
|
34
|
+
return `${key}="${value}"`;
|
|
35
|
+
}).filter((attr) => attr !== null).join(" ");
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Adds a trailing slash to a URL if it doesn't already have one
|
|
39
|
+
*
|
|
40
|
+
* @param url - The URL string to process
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* addTrailingSlash('/api') // Returns: '/api/'
|
|
44
|
+
* addTrailingSlash('/api/') // Returns: '/api/'
|
|
45
|
+
*/
|
|
46
|
+
const addTrailingSlash = (url) => {
|
|
47
|
+
return url.endsWith("/") ? url : `${url}/`;
|
|
48
|
+
};
|
|
49
|
+
//#endregion
|
|
50
|
+
export { makeAttributes as n, uniqBy as r, addTrailingSlash as t };
|