@webflow/webflow-cli 1.22.0-next.0 → 1.22.0-next.2
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/dist/cloud-scaffolds/registry.ts +2 -2
- package/dist/index.js +116 -92
- package/dist/index.js.map +1 -1
- package/dist/templates/astro/astro.config.webflow.ts +67 -0
- package/dist/templates/astro/v5/astro.config.mjs.template +25 -0
- package/dist/templates/astro/{webflow-loader.ts → v5/webflow-loader.ts} +12 -12
- package/dist/templates/astro/{wrangler.astro.json → v5/wrangler.json} +1 -1
- package/dist/templates/astro/v6/astro.config.mjs.template +34 -0
- package/dist/templates/astro/v6/webflow-loader.ts +122 -0
- package/dist/templates/astro/v6/wrangler.json +9 -0
- package/dist/templates/nextjs/next.config.ts.template +10 -21
- package/dist/templates/nextjs/next.config.webflow.ts +55 -0
- package/dist/templates/nextjs/open-next.config.ts +3 -0
- package/dist/templates/nextjs/webflow-loader.ts +11 -9
- package/dist/templates/vite/vite.config.ts.template +14 -0
- package/dist/templates/vite/vite.config.webflow.ts +58 -0
- package/dist/templates/vite/wrangler.json +10 -0
- package/package.json +1 -1
- package/dist/cloud-scaffolds/__tests__/fetcher.test.ts +0 -249
- package/dist/cloud-scaffolds/__tests__/registry.test.ts +0 -134
- package/dist/templates/astro/astro.config.mjs.template +0 -52
- package/dist/templates/nextjs/next.config.ts +0 -5
- /package/dist/templates/nextjs/{wrangler.nextjs.json → wrangler.json} +0 -0
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// Merge logic for the generated astro.config.mjs (shared between Astro 5 + 6).
|
|
2
|
+
// Copied verbatim into the user's project; types are local to avoid Astro
|
|
3
|
+
// cross-version drift.
|
|
4
|
+
|
|
5
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
6
|
+
type AstroIntegration = { name?: string; [key: string]: any };
|
|
7
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
8
|
+
type AstroUserConfig = Record<string, any>;
|
|
9
|
+
|
|
10
|
+
export type WebflowOverridesOptions = {
|
|
11
|
+
mountPath: string;
|
|
12
|
+
deployUrl: string;
|
|
13
|
+
/** The result of `cloudflare({ ... })` from `@astrojs/cloudflare`. */
|
|
14
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
15
|
+
adapter: any;
|
|
16
|
+
/** The result of `react()` from `@astrojs/react`. Auto-added if the user hasn't already added `@astrojs/react`. */
|
|
17
|
+
reactIntegration: AstroIntegration;
|
|
18
|
+
/** Extra Vite resolve.alias to inject (e.g. react-dom/server -> react-dom/server.edge for React 19+ in prod). Pass `undefined` to skip. */
|
|
19
|
+
reactDomServerAlias: Record<string, string> | undefined;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Wrap the user's Astro config with Webflow Cloud's required overrides.
|
|
24
|
+
* `userExport` is always provided — `{}` when the user ships no astro.config.*.
|
|
25
|
+
*
|
|
26
|
+
* Matches pre-1.2 behaviour: top-level keys in the override block REPLACE the
|
|
27
|
+
* user's. We need our adapter / base / output to win.
|
|
28
|
+
*/
|
|
29
|
+
export function withWebflowOverrides(
|
|
30
|
+
userExport: AstroUserConfig,
|
|
31
|
+
opts: WebflowOverridesOptions
|
|
32
|
+
): AstroUserConfig {
|
|
33
|
+
const userIntegrations: AstroIntegration[] = userExport.integrations || [];
|
|
34
|
+
const hasReact = userIntegrations.some((i) => i?.name === "@astrojs/react");
|
|
35
|
+
const integrations = hasReact
|
|
36
|
+
? userIntegrations
|
|
37
|
+
: [...userIntegrations, opts.reactIntegration];
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
...userExport,
|
|
41
|
+
base: opts.mountPath,
|
|
42
|
+
output: "server",
|
|
43
|
+
adapter: opts.adapter,
|
|
44
|
+
integrations,
|
|
45
|
+
vite: {
|
|
46
|
+
...userExport.vite,
|
|
47
|
+
resolve: {
|
|
48
|
+
...userExport.vite?.resolve,
|
|
49
|
+
alias: opts.reactDomServerAlias,
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
build: {
|
|
53
|
+
assetsPrefix:
|
|
54
|
+
opts.deployUrl + (opts.mountPath === "/" ? "" : opts.mountPath),
|
|
55
|
+
},
|
|
56
|
+
image: {
|
|
57
|
+
...userExport.image,
|
|
58
|
+
service: {
|
|
59
|
+
entrypoint: "./webflow-loader.ts",
|
|
60
|
+
config: {
|
|
61
|
+
deployUrl: opts.deployUrl,
|
|
62
|
+
mountPath: opts.mountPath,
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// Generated by the Webflow Cloud builder. Merge logic lives in ./astro.config.webflow.ts.
|
|
2
|
+
import { defineConfig } from 'astro/config';
|
|
3
|
+
import cloudflare from '@astrojs/cloudflare';
|
|
4
|
+
import react from '@astrojs/react';
|
|
5
|
+
import userConfig from './clouduser.astro.config';
|
|
6
|
+
import { withWebflowOverrides } from './astro.config.webflow';
|
|
7
|
+
|
|
8
|
+
export default defineConfig(
|
|
9
|
+
withWebflowOverrides(userConfig, {
|
|
10
|
+
mountPath: '${COSMIC_MOUNT_PATH}',
|
|
11
|
+
deployUrl: '${COSMIC_DEPLOY_URL}',
|
|
12
|
+
adapter: cloudflare({
|
|
13
|
+
imageService: 'custom',
|
|
14
|
+
platformProxy: {
|
|
15
|
+
enabled: true,
|
|
16
|
+
},
|
|
17
|
+
}),
|
|
18
|
+
reactIntegration: react(),
|
|
19
|
+
// Use react-dom/server.edge instead of react-dom/server.browser for React 19.
|
|
20
|
+
// Without this, MessageChannel from node:worker_threads needs to be polyfilled.
|
|
21
|
+
reactDomServerAlias: import.meta.env.PROD
|
|
22
|
+
? { 'react-dom/server': 'react-dom/server.edge' }
|
|
23
|
+
: undefined,
|
|
24
|
+
})
|
|
25
|
+
);
|
|
@@ -84,18 +84,18 @@ const cloudflareLoader: ExternalImageService = {
|
|
|
84
84
|
getHTMLAttributes(options: ImageTransform) {
|
|
85
85
|
const { targetWidth, targetHeight } = getTargetDimensions(options);
|
|
86
86
|
const {
|
|
87
|
-
src
|
|
88
|
-
width
|
|
89
|
-
height
|
|
90
|
-
format
|
|
91
|
-
quality
|
|
92
|
-
densities
|
|
93
|
-
widths
|
|
94
|
-
formats
|
|
95
|
-
layout
|
|
96
|
-
priority
|
|
97
|
-
fit
|
|
98
|
-
position
|
|
87
|
+
src,
|
|
88
|
+
width,
|
|
89
|
+
height,
|
|
90
|
+
format,
|
|
91
|
+
quality,
|
|
92
|
+
densities,
|
|
93
|
+
widths,
|
|
94
|
+
formats,
|
|
95
|
+
layout,
|
|
96
|
+
priority,
|
|
97
|
+
fit,
|
|
98
|
+
position,
|
|
99
99
|
...attributes
|
|
100
100
|
} = options;
|
|
101
101
|
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// Generated by the Webflow Cloud builder. Merge logic lives in ./astro.config.webflow.ts.
|
|
2
|
+
import { defineConfig } from 'astro/config';
|
|
3
|
+
import { readFileSync } from 'node:fs';
|
|
4
|
+
import cloudflare from '@astrojs/cloudflare';
|
|
5
|
+
import react from '@astrojs/react';
|
|
6
|
+
import userConfig from './clouduser.astro.config';
|
|
7
|
+
import { withWebflowOverrides } from './astro.config.webflow';
|
|
8
|
+
|
|
9
|
+
// Detect React 19+ so we know whether to alias react-dom/server -> .edge.
|
|
10
|
+
let isReact19 = false;
|
|
11
|
+
try {
|
|
12
|
+
const pkg = JSON.parse(readFileSync('./package.json', 'utf8'));
|
|
13
|
+
const reactVersion = pkg.dependencies?.react || pkg.devDependencies?.react || '';
|
|
14
|
+
// Match versions like "19", "^19", "~19", "19.0.0", "^19.0.0", etc.
|
|
15
|
+
isReact19 = /(?:^|[^\d])19(?:\.|$)/.test(reactVersion);
|
|
16
|
+
} catch {
|
|
17
|
+
// Could not read package.json, assume not React 19.
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export default defineConfig(
|
|
21
|
+
withWebflowOverrides(userConfig, {
|
|
22
|
+
mountPath: '${COSMIC_MOUNT_PATH}',
|
|
23
|
+
deployUrl: '${COSMIC_DEPLOY_URL}',
|
|
24
|
+
adapter: cloudflare({
|
|
25
|
+
imageService: 'custom',
|
|
26
|
+
// Note: platformProxy removed in Astro 6 — workerd runs natively via @cloudflare/vite-plugin.
|
|
27
|
+
}),
|
|
28
|
+
reactIntegration: react(),
|
|
29
|
+
// Only React 19+ has react-dom/server.edge. React 18 has only .browser, which doesn't run in edge runtimes.
|
|
30
|
+
reactDomServerAlias: import.meta.env.PROD && isReact19
|
|
31
|
+
? { 'react-dom/server': 'react-dom/server.edge' }
|
|
32
|
+
: undefined,
|
|
33
|
+
})
|
|
34
|
+
);
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Webflow Image Loader for Astro 6
|
|
3
|
+
*
|
|
4
|
+
* This is the same loader as Astro 5 - it handles image optimization
|
|
5
|
+
* through Cloudflare's image resizing service.
|
|
6
|
+
*
|
|
7
|
+
* Note: Astro 6 defaults to 'cloudflare-binding' for imageService,
|
|
8
|
+
* but we use 'custom' to maintain compatibility with our CDN setup.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { ExternalImageService, ImageTransform, AstroConfig } from "astro";
|
|
12
|
+
|
|
13
|
+
function normalizeSrc(src: string, deployUrl?: string, mountPath?: string) {
|
|
14
|
+
// For local assets, include the mount path but not the deploy url, and remove the leading slash
|
|
15
|
+
if (deployUrl && src.startsWith(deployUrl)) {
|
|
16
|
+
return `${src.slice(deployUrl.length)}`.slice(1);
|
|
17
|
+
} else if (mountPath && src.startsWith(mountPath)) {
|
|
18
|
+
return src.slice(1);
|
|
19
|
+
} else if (src.startsWith("/")) {
|
|
20
|
+
return `${mountPath}${src}`.slice(1);
|
|
21
|
+
}
|
|
22
|
+
return src;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Default implementation copied from Astro's baseService
|
|
26
|
+
function getTargetDimensions(options: ImageTransform) {
|
|
27
|
+
let targetWidth = options.width;
|
|
28
|
+
let targetHeight = options.height;
|
|
29
|
+
|
|
30
|
+
// For ESM imported images, calculate missing dimensions based on aspect ratio
|
|
31
|
+
if (
|
|
32
|
+
typeof options.src === "object" &&
|
|
33
|
+
"width" in options.src &&
|
|
34
|
+
"height" in options.src
|
|
35
|
+
) {
|
|
36
|
+
const aspectRatio = options.src.width / options.src.height;
|
|
37
|
+
if (targetHeight && !targetWidth) {
|
|
38
|
+
targetWidth = Math.round(targetHeight * aspectRatio);
|
|
39
|
+
} else if (targetWidth && !targetHeight) {
|
|
40
|
+
targetHeight = Math.round(targetWidth / aspectRatio);
|
|
41
|
+
} else if (!targetWidth && !targetHeight) {
|
|
42
|
+
targetWidth = options.src.width;
|
|
43
|
+
targetHeight = options.src.height;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
targetWidth,
|
|
49
|
+
targetHeight,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const cloudflareLoader: ExternalImageService = {
|
|
54
|
+
getURL(options: ImageTransform, imageConfig: AstroConfig["image"]) {
|
|
55
|
+
const normalizedSrc = normalizeSrc(
|
|
56
|
+
typeof options.src === "object" ? options.src.src : options.src,
|
|
57
|
+
imageConfig.service.config.deployUrl,
|
|
58
|
+
imageConfig.service.config.mountPath
|
|
59
|
+
);
|
|
60
|
+
// Our cloudflare zone doesn't allow optimizing external images for security reasons, so just load the original image
|
|
61
|
+
// For now, also skip optimization for images hosted on regular webflow sites (cdn.website-files.com)
|
|
62
|
+
// because optimizing them would result in two bandwidth charges: one from the website-files zone (for the full image)
|
|
63
|
+
// and one from the cosmic.webflow.services zone (for the resized image)
|
|
64
|
+
if (
|
|
65
|
+
normalizedSrc.startsWith("http://") ||
|
|
66
|
+
normalizedSrc.startsWith("https://")
|
|
67
|
+
) {
|
|
68
|
+
return normalizedSrc;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const supportedOptions = ["width", "height", "quality", "format"];
|
|
72
|
+
const params = [];
|
|
73
|
+
for (const option of supportedOptions) {
|
|
74
|
+
if (options[option]) {
|
|
75
|
+
params.push(`${option}=${options[option]}`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const workerUrl = imageConfig.service.config.deployUrl;
|
|
80
|
+
// Skip resizing svgs, since it doesn't do anything
|
|
81
|
+
const isSvg =
|
|
82
|
+
typeof options.src === "object"
|
|
83
|
+
? options.src.format === "svg"
|
|
84
|
+
: options.src.endsWith(".svg");
|
|
85
|
+
if (isSvg || params.length === 0) {
|
|
86
|
+
return `${workerUrl}/${normalizedSrc}`;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const paramsString = params.join(",");
|
|
90
|
+
return `${workerUrl}/cdn-cgi/image/${paramsString}/${normalizedSrc}`;
|
|
91
|
+
},
|
|
92
|
+
|
|
93
|
+
// Default implementation copied from Astro's baseService
|
|
94
|
+
getHTMLAttributes(options: ImageTransform) {
|
|
95
|
+
const { targetWidth, targetHeight } = getTargetDimensions(options);
|
|
96
|
+
const {
|
|
97
|
+
src,
|
|
98
|
+
width,
|
|
99
|
+
height,
|
|
100
|
+
format,
|
|
101
|
+
quality,
|
|
102
|
+
densities,
|
|
103
|
+
widths,
|
|
104
|
+
formats,
|
|
105
|
+
layout,
|
|
106
|
+
priority,
|
|
107
|
+
fit,
|
|
108
|
+
position,
|
|
109
|
+
...attributes
|
|
110
|
+
} = options;
|
|
111
|
+
|
|
112
|
+
return {
|
|
113
|
+
...attributes,
|
|
114
|
+
width: targetWidth,
|
|
115
|
+
height: targetHeight,
|
|
116
|
+
loading: attributes.loading ?? "lazy",
|
|
117
|
+
decoding: attributes.decoding ?? "async",
|
|
118
|
+
};
|
|
119
|
+
},
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
export default cloudflareLoader;
|
|
@@ -1,24 +1,13 @@
|
|
|
1
|
-
|
|
1
|
+
// Generated by the Webflow Cloud builder. Merge logic lives in ./next.config.webflow.ts.
|
|
2
2
|
import userConfig from './clouduser.next.config';
|
|
3
|
+
import { withWebflowOverrides } from './next.config.webflow';
|
|
3
4
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
...userConfig.images,
|
|
9
|
-
// TODO: determine whether any of the non-custom loader options (imgix, cloudinary, akamai) work
|
|
10
|
-
// and if so allow them to be used here
|
|
11
|
-
loader: 'custom',
|
|
12
|
-
loaderFile: userConfig.images?.loaderFile || './webflow-loader.ts',
|
|
13
|
-
},
|
|
14
|
-
};
|
|
5
|
+
// Next.js disallows '/' as basePath, so '/' maps to ''.
|
|
6
|
+
const mountPath: string = '${COSMIC_MOUNT_PATH}';
|
|
7
|
+
export const webflowBasePath = mountPath === '/' ? '' : mountPath;
|
|
8
|
+
export const webflowAssetPrefix = '${COSMIC_DEPLOY_URL}' + webflowBasePath;
|
|
15
9
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
export default nextConfig;
|
|
22
|
-
|
|
23
|
-
import { initOpenNextCloudflareForDev } from "@opennextjs/cloudflare";
|
|
24
|
-
initOpenNextCloudflareForDev();
|
|
10
|
+
export default withWebflowOverrides(userConfig, {
|
|
11
|
+
basePath: webflowBasePath,
|
|
12
|
+
assetPrefix: webflowAssetPrefix,
|
|
13
|
+
});
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// Merge logic for the generated next.config.ts. Copied verbatim into the
|
|
2
|
+
// user's project; types are local to avoid Next.js cross-version drift.
|
|
3
|
+
import type { NextConfig } from "next";
|
|
4
|
+
|
|
5
|
+
type NextConfigContext = { defaultConfig: NextConfig };
|
|
6
|
+
type UserNextConfigFn = (
|
|
7
|
+
phase: string,
|
|
8
|
+
ctx: NextConfigContext
|
|
9
|
+
) => NextConfig | Promise<NextConfig>;
|
|
10
|
+
|
|
11
|
+
export type UserNextConfig = NextConfig | UserNextConfigFn;
|
|
12
|
+
export type WebflowOverridesOptions = {
|
|
13
|
+
basePath: string;
|
|
14
|
+
assetPrefix: string;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Wrap the user's Next.js config with Webflow Cloud's required overrides.
|
|
19
|
+
* `userExport` is always provided — `{}` when the user ships no next.config.
|
|
20
|
+
*/
|
|
21
|
+
export function withWebflowOverrides(
|
|
22
|
+
userExport: UserNextConfig,
|
|
23
|
+
opts: WebflowOverridesOptions
|
|
24
|
+
): UserNextConfigFn {
|
|
25
|
+
return async function nextConfig(
|
|
26
|
+
phase: string,
|
|
27
|
+
ctx: NextConfigContext
|
|
28
|
+
): Promise<NextConfig> {
|
|
29
|
+
const resolved = await resolveUserConfig(userExport, phase, ctx);
|
|
30
|
+
return {
|
|
31
|
+
...resolved,
|
|
32
|
+
basePath: opts.basePath,
|
|
33
|
+
assetPrefix: opts.assetPrefix,
|
|
34
|
+
images: {
|
|
35
|
+
...resolved.images,
|
|
36
|
+
// TODO: support non-custom loaders (imgix, cloudinary, akamai) if they work
|
|
37
|
+
loader: "custom",
|
|
38
|
+
loaderFile: resolved.images?.loaderFile || "./webflow-loader.ts",
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async function resolveUserConfig(
|
|
45
|
+
userExport: UserNextConfig,
|
|
46
|
+
phase: string,
|
|
47
|
+
ctx: NextConfigContext
|
|
48
|
+
): Promise<NextConfig> {
|
|
49
|
+
if (typeof userExport === "function") {
|
|
50
|
+
return (await userExport(phase, ctx)) ?? {};
|
|
51
|
+
}
|
|
52
|
+
// `export default null` is rare but not impossible; guard so the spread
|
|
53
|
+
// below in withWebflowOverrides can't NPE.
|
|
54
|
+
return userExport ?? {};
|
|
55
|
+
}
|
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { webflowAssetPrefix, webflowBasePath } from "./next.config";
|
|
2
2
|
|
|
3
|
-
const basePath =
|
|
4
|
-
const assetPrefix =
|
|
3
|
+
const basePath = webflowBasePath;
|
|
4
|
+
const assetPrefix = webflowAssetPrefix || basePath;
|
|
5
5
|
|
|
6
6
|
function normalizeSrc(src: string) {
|
|
7
|
-
// For local assets, include the base path but not the asset prefix
|
|
7
|
+
// For local assets, include the base path but not the asset prefix
|
|
8
8
|
if (assetPrefix && src.startsWith(assetPrefix)) {
|
|
9
|
-
return `${basePath}${src.slice(assetPrefix.length)}
|
|
9
|
+
return `${basePath}${src.slice(assetPrefix.length)}`;
|
|
10
10
|
} else if (basePath && src.startsWith(basePath)) {
|
|
11
|
-
return src
|
|
11
|
+
return src;
|
|
12
12
|
} else if (src.startsWith("/")) {
|
|
13
|
-
return
|
|
13
|
+
return basePath + src;
|
|
14
14
|
}
|
|
15
15
|
return src;
|
|
16
16
|
}
|
|
@@ -42,6 +42,8 @@ export default function cloudflareLoader({
|
|
|
42
42
|
params.push(`quality=${quality}`);
|
|
43
43
|
}
|
|
44
44
|
const paramsString = params.join(",");
|
|
45
|
-
const workerUrl =
|
|
46
|
-
|
|
45
|
+
const workerUrl = basePath
|
|
46
|
+
? assetPrefix.slice(0, -basePath.length)
|
|
47
|
+
: assetPrefix;
|
|
48
|
+
return `${workerUrl}/cdn-cgi/image/${paramsString}${normalizedSrc}`;
|
|
47
49
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// Generated by the Webflow Cloud builder. Merge logic lives in ./vite.config.webflow.ts.
|
|
2
|
+
import { defineConfig } from 'vite';
|
|
3
|
+
import { cloudflare } from '@cloudflare/vite-plugin';
|
|
4
|
+
import userConfig from './clouduser.vite.config';
|
|
5
|
+
import { withWebflowOverrides } from './vite.config.webflow';
|
|
6
|
+
|
|
7
|
+
const WF_BASE_PATH = '${COSMIC_MOUNT_PATH}';
|
|
8
|
+
|
|
9
|
+
export default defineConfig(
|
|
10
|
+
withWebflowOverrides(userConfig, {
|
|
11
|
+
basePath: WF_BASE_PATH,
|
|
12
|
+
cloudflarePlugins: [cloudflare()],
|
|
13
|
+
})
|
|
14
|
+
);
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// Merge logic for the generated vite.config.ts. Copied verbatim into the
|
|
2
|
+
// user's project; types are local to avoid Vite cross-version drift.
|
|
3
|
+
|
|
4
|
+
type ViteConfigEnv = { mode: string; command: string; isPreview?: boolean };
|
|
5
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
6
|
+
type ViteUserConfig = Record<string, any>;
|
|
7
|
+
type ViteUserConfigFn = (
|
|
8
|
+
env: ViteConfigEnv
|
|
9
|
+
) => ViteUserConfig | Promise<ViteUserConfig>;
|
|
10
|
+
|
|
11
|
+
export type UserViteConfig = ViteUserConfig | ViteUserConfigFn;
|
|
12
|
+
export type WebflowOverridesOptions = {
|
|
13
|
+
basePath: string;
|
|
14
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
15
|
+
cloudflarePlugins: any[];
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Wrap the user's Vite config with Webflow Cloud's required overrides.
|
|
20
|
+
* `userExport` is always provided — `{}` when the user ships no vite.config.*.
|
|
21
|
+
*
|
|
22
|
+
* Returns an async factory so the resulting config plays nicely with Vite's
|
|
23
|
+
* loadConfigFromFile under both ESM and CJS package types.
|
|
24
|
+
*/
|
|
25
|
+
export function withWebflowOverrides(
|
|
26
|
+
userExport: UserViteConfig,
|
|
27
|
+
opts: WebflowOverridesOptions
|
|
28
|
+
): ViteUserConfigFn {
|
|
29
|
+
return async function viteConfig(
|
|
30
|
+
env: ViteConfigEnv
|
|
31
|
+
): Promise<ViteUserConfig> {
|
|
32
|
+
const resolved = await resolveUserConfig(userExport, env);
|
|
33
|
+
return {
|
|
34
|
+
...resolved,
|
|
35
|
+
base: opts.basePath,
|
|
36
|
+
define: {
|
|
37
|
+
...resolved.define,
|
|
38
|
+
__WF_BASE_PATH__: JSON.stringify(opts.basePath),
|
|
39
|
+
__WF_BASE_URL__: JSON.stringify(
|
|
40
|
+
opts.basePath.endsWith("/") ? opts.basePath : opts.basePath + "/"
|
|
41
|
+
),
|
|
42
|
+
},
|
|
43
|
+
// Cloudflare's plugins go first so they sit at the head of the chain;
|
|
44
|
+
// user plugins are preserved after them (matching pre-1.2 behaviour).
|
|
45
|
+
plugins: [...opts.cloudflarePlugins, ...(resolved.plugins || [])],
|
|
46
|
+
};
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function resolveUserConfig(
|
|
51
|
+
userExport: UserViteConfig,
|
|
52
|
+
env: ViteConfigEnv
|
|
53
|
+
): Promise<ViteUserConfig> {
|
|
54
|
+
if (typeof userExport === "function") {
|
|
55
|
+
return (await userExport(env)) ?? {};
|
|
56
|
+
}
|
|
57
|
+
return userExport ?? {};
|
|
58
|
+
}
|