vike 0.4.249-commit-f416149 → 0.4.249-commit-92f1383

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.
@@ -22,6 +22,7 @@ import { pluginWorkaroundCssModuleHmr } from './plugins/pluginWorkaroundCssModul
22
22
  import { pluginWorkaroundVite6HmrRegression } from './plugins/pluginWorkaroundVite6HmrRegression.js';
23
23
  import { pluginReplaceConstantsPageContext } from './plugins/pluginReplaceConstantsPageContext.js';
24
24
  import { pluginReplaceConstantsGlobalThis } from './plugins/pluginReplaceConstantsGlobalThis.js';
25
+ import { pluginStaticReplace } from './plugins/pluginStaticReplace.js';
25
26
  import { pluginViteRPC } from './plugins/non-runnable-dev/pluginViteRPC.js';
26
27
  import { pluginBuildApp } from './plugins/build/pluginBuildApp.js';
27
28
  import { pluginDistPackageJsonFile } from './plugins/build/pluginDistPackageJsonFile.js';
@@ -33,7 +34,7 @@ import { pluginModuleBanner } from './plugins/build/pluginModuleBanner.js';
33
34
  import { pluginReplaceConstantsNonRunnableDev } from './plugins/non-runnable-dev/pluginReplaceConstantsNonRunnableDev.js';
34
35
  import { isVikeCliOrApi } from '../../shared-server-node/api-context.js';
35
36
  import { pluginViteConfigVikeExtensions } from './plugins/pluginViteConfigVikeExtensions.js';
36
- import { isOnlyResolvingUserConfig } from '../api/resolveViteConfigFromUser.js';
37
+ import { getVikeConfigInternalEarly, isOnlyResolvingUserConfig } from '../api/resolveViteConfigFromUser.js';
37
38
  // We don't call this in ./onLoad.ts to avoid a cyclic dependency with utils.ts
38
39
  setGetClientEntrySrcDev(getClientEntrySrcDev);
39
40
  assertIsNotProductionRuntime();
@@ -42,6 +43,7 @@ function plugin(vikeVitePluginOptions = {}) {
42
43
  const promise = (async () => {
43
44
  if (skip())
44
45
  return [];
46
+ const vikeConfig = await getVikeConfigInternalEarly();
45
47
  const plugins = [
46
48
  ...pluginCommon(vikeVitePluginOptions),
47
49
  ...pluginVirtualFiles(),
@@ -58,8 +60,9 @@ function plugin(vikeVitePluginOptions = {}) {
58
60
  ...pluginWorkaroundVite6HmrRegression(),
59
61
  ...pluginReplaceConstantsPageContext(),
60
62
  ...pluginReplaceConstantsGlobalThis(),
63
+ ...pluginStaticReplace(vikeConfig),
61
64
  ...pluginNonRunnabeDev(),
62
- ...(await pluginViteConfigVikeExtensions()),
65
+ ...(await pluginViteConfigVikeExtensions(vikeConfig)),
63
66
  ];
64
67
  return plugins;
65
68
  })();
@@ -11,6 +11,7 @@ const constantsIsClientSide = [
11
11
  ];
12
12
  // - See https://vike.dev/pageContext#narrowing-down
13
13
  // - We cannot use [`define`](https://vite.dev/config/shared-options.html#define) because of https://github.com/rolldown/rolldown/issues/4300
14
+ // filterRolldown
14
15
  const skipNodeModules = '/node_modules/';
15
16
  const skipIrrelevant = 'Context.isClientSide';
16
17
  assert(constantsIsClientSide.every((constant) => constant.endsWith(skipIrrelevant)));
@@ -0,0 +1,123 @@
1
+ export { applyStaticReplace };
2
+ export type { StaticReplace };
3
+ /**
4
+ * Condition to match an argument value.
5
+ * - string starting with 'import:' matches an imported identifier
6
+ * - { prop, equals } matches a property value inside an object argument
7
+ * - { call, args } matches a call expression with specific arguments
8
+ * - { member, object, property } matches a member expression like $setup["ClientOnly"]
9
+ */
10
+ type ArgCondition = string | {
11
+ prop: string;
12
+ equals: unknown;
13
+ } | {
14
+ call: string;
15
+ args?: Record<number, ArgCondition>;
16
+ } | {
17
+ member: true;
18
+ object: string;
19
+ property: string | ArgCondition;
20
+ };
21
+ /**
22
+ * Target for replace operation.
23
+ */
24
+ type ReplaceTarget = {
25
+ with: unknown;
26
+ } | {
27
+ arg: number;
28
+ prop: string;
29
+ with: unknown;
30
+ } | {
31
+ arg: number;
32
+ with: unknown;
33
+ } | {
34
+ argsFrom: number;
35
+ with: unknown;
36
+ };
37
+ /**
38
+ * Target for remove operation.
39
+ */
40
+ type RemoveTarget = {
41
+ arg: number;
42
+ prop: string;
43
+ } | {
44
+ arg: number;
45
+ } | {
46
+ argsFrom: number;
47
+ };
48
+ /**
49
+ * Rule for static replacement/removal.
50
+ *
51
+ * @example
52
+ * // jsx/jsxs/jsxDEV: replace children prop with null
53
+ * {
54
+ * env: 'server',
55
+ * type: 'call',
56
+ * match: {
57
+ * function: ['jsx', 'jsxs', 'jsxDEV'],
58
+ * args: { 0: 'import:vike-react/ClientOnly:ClientOnly' }
59
+ * },
60
+ * replace: { arg: 1, prop: 'children', with: null }
61
+ * }
62
+ *
63
+ * @example
64
+ * // createElement: remove all children (args from index 2)
65
+ * {
66
+ * env: 'server',
67
+ * type: 'call',
68
+ * match: {
69
+ * function: 'createElement',
70
+ * args: { 0: 'import:vike-react/ClientOnly:ClientOnly' }
71
+ * },
72
+ * remove: { argsFrom: 2 }
73
+ * }
74
+ *
75
+ * @example
76
+ * // ssrRenderComponent: match nested call expression and remove default slot
77
+ * {
78
+ * env: 'server',
79
+ * type: 'call',
80
+ * match: {
81
+ * function: 'import:vue/server-renderer:ssrRenderComponent',
82
+ * args: {
83
+ * 0: {
84
+ * call: 'import:vue:unref',
85
+ * args: { 0: 'import:vike-vue/ClientOnly:ClientOnly' }
86
+ * }
87
+ * }
88
+ * },
89
+ * remove: { arg: 2, prop: 'default' }
90
+ * }
91
+ */
92
+ type StaticReplace = {
93
+ /** Type of transformation - currently only 'call' is supported, but can be extended in the future */
94
+ type?: 'call';
95
+ /** Environment filter: 'client' = client only, 'server' = everything except client */
96
+ env?: 'server' | 'client';
97
+ /** Match criteria */
98
+ match: {
99
+ /**
100
+ * Function name(s) to match.
101
+ * - Plain string: matches function name directly (e.g., 'jsx')
102
+ * - Import string: 'import:importPath:exportName' (e.g., 'import:react/jsx-runtime:jsx')
103
+ */
104
+ function: string | string[];
105
+ /** Conditions on arguments: index -> condition */
106
+ args?: Record<number, ArgCondition>;
107
+ };
108
+ /** Replace target (optional) */
109
+ replace?: ReplaceTarget;
110
+ /** Remove target (optional) */
111
+ remove?: RemoveTarget;
112
+ };
113
+ type TransformResult = {
114
+ code: string;
115
+ map: any;
116
+ } | null;
117
+ type TransformInput = {
118
+ code: string;
119
+ id: string;
120
+ env: string;
121
+ options: StaticReplace[];
122
+ };
123
+ declare function applyStaticReplace({ code, id, env, options }: TransformInput): Promise<TransformResult>;
@@ -0,0 +1,404 @@
1
+ export { applyStaticReplace };
2
+ import { transformAsync } from '@babel/core';
3
+ import * as t from '@babel/types';
4
+ import { parseImportString } from '../../shared/importString.js';
5
+ async function applyStaticReplace({ code, id, env, options }) {
6
+ // 'server' means "not client" (covers ssr, cloudflare, etc.)
7
+ const filteredRules = options.filter((rule) => {
8
+ if (!rule.env)
9
+ return true;
10
+ if (rule.env === 'client')
11
+ return env === 'client';
12
+ if (rule.env === 'server')
13
+ return env !== 'client';
14
+ return false;
15
+ });
16
+ if (filteredRules.length === 0) {
17
+ return null;
18
+ }
19
+ try {
20
+ const state = {
21
+ modified: false,
22
+ imports: new Map(),
23
+ alreadyUnreferenced: new Set(),
24
+ };
25
+ const result = await transformAsync(code, {
26
+ filename: id,
27
+ ast: true,
28
+ sourceMaps: true,
29
+ plugins: [collectImportsPlugin(state), applyRulesPlugin(state, filteredRules), removeUnreferencedPlugin(state)],
30
+ });
31
+ if (!result?.code || !state.modified) {
32
+ return null;
33
+ }
34
+ return { code: result.code, map: result.map };
35
+ }
36
+ catch (error) {
37
+ console.error(`Error transforming ${id}:`, error);
38
+ return null;
39
+ }
40
+ }
41
+ // ============================================================================
42
+ // Helpers
43
+ // ============================================================================
44
+ function valueToAst(value) {
45
+ if (value === null)
46
+ return t.nullLiteral();
47
+ if (value === undefined)
48
+ return t.identifier('undefined');
49
+ if (typeof value === 'string')
50
+ return t.stringLiteral(value);
51
+ if (typeof value === 'number')
52
+ return t.numericLiteral(value);
53
+ if (typeof value === 'boolean')
54
+ return t.booleanLiteral(value);
55
+ return t.callExpression(t.memberExpression(t.identifier('JSON'), t.identifier('parse')), [
56
+ t.stringLiteral(JSON.stringify(value)),
57
+ ]);
58
+ }
59
+ function getCalleeName(callee) {
60
+ if (t.isIdentifier(callee))
61
+ return callee.name;
62
+ if (t.isMemberExpression(callee) && t.isIdentifier(callee.property))
63
+ return callee.property.name;
64
+ return null;
65
+ }
66
+ /**
67
+ * Check if an identifier matches an import condition
68
+ */
69
+ function matchesImport(arg, parsed, state) {
70
+ if (!t.isIdentifier(arg))
71
+ return false;
72
+ const imported = state.imports.get(arg.name);
73
+ if (!imported)
74
+ return false;
75
+ return imported.importPath === parsed.importPath && imported.exportName === parsed.exportName;
76
+ }
77
+ // ============================================================================
78
+ // Babel plugins
79
+ // ============================================================================
80
+ /**
81
+ * Collect all imports: localName -> { importPath, exportName }
82
+ */
83
+ function collectImportsPlugin(state) {
84
+ return {
85
+ visitor: {
86
+ ImportDeclaration(path) {
87
+ const importPath = path.node.source.value;
88
+ for (const specifier of path.node.specifiers) {
89
+ let exportName;
90
+ if (t.isImportSpecifier(specifier) && t.isIdentifier(specifier.imported)) {
91
+ exportName = specifier.imported.name;
92
+ }
93
+ else if (t.isImportDefaultSpecifier(specifier)) {
94
+ exportName = 'default';
95
+ }
96
+ else {
97
+ continue;
98
+ }
99
+ state.imports.set(specifier.local.name, { importPath, exportName });
100
+ }
101
+ },
102
+ },
103
+ };
104
+ }
105
+ /**
106
+ * Apply replacement rules to matching call expressions
107
+ */
108
+ function applyRulesPlugin(state, rules) {
109
+ return {
110
+ visitor: {
111
+ CallExpression(path) {
112
+ const calleeName = getCalleeName(path.node.callee);
113
+ if (!calleeName)
114
+ return;
115
+ for (const rule of rules) {
116
+ if (!matchesRule(path, rule, calleeName, state))
117
+ continue;
118
+ if (rule.replace) {
119
+ applyReplace(path, rule.replace, state);
120
+ }
121
+ else if (rule.remove) {
122
+ applyRemove(path, rule.remove, state);
123
+ }
124
+ }
125
+ },
126
+ },
127
+ };
128
+ }
129
+ /**
130
+ * Check if a call expression matches a rule
131
+ */
132
+ function matchesRule(path, staticReplace, calleeName, state) {
133
+ const { match } = staticReplace;
134
+ // Check callee name
135
+ const functions = Array.isArray(match.function) ? match.function : [match.function];
136
+ if (!matchesCallee(path.node.callee, calleeName, functions, state))
137
+ return false;
138
+ // Check argument conditions
139
+ if (match.args) {
140
+ for (const [indexStr, condition] of Object.entries(match.args)) {
141
+ const index = Number(indexStr);
142
+ const arg = path.node.arguments[index];
143
+ if (!arg)
144
+ return false;
145
+ if (!matchesCondition(arg, condition, state))
146
+ return false;
147
+ }
148
+ }
149
+ return true;
150
+ }
151
+ /**
152
+ * Check if callee matches any of the function patterns
153
+ */
154
+ function matchesCallee(callee, calleeName, functions, state) {
155
+ for (const fn of functions) {
156
+ const parsed = parseImportString(fn);
157
+ if (parsed) {
158
+ // Import string: check if callee is an imported identifier
159
+ if (t.isIdentifier(callee)) {
160
+ const imported = state.imports.get(callee.name);
161
+ if (imported && imported.importPath === parsed.importPath && imported.exportName === parsed.exportName) {
162
+ return true;
163
+ }
164
+ }
165
+ // Import string: check member expression
166
+ if (t.isMemberExpression(callee) && t.isIdentifier(callee.object) && t.isIdentifier(callee.property)) {
167
+ const imported = state.imports.get(callee.object.name);
168
+ if (imported &&
169
+ imported.importPath === parsed.importPath &&
170
+ imported.exportName === 'default' &&
171
+ callee.property.name === parsed.exportName) {
172
+ return true;
173
+ }
174
+ }
175
+ }
176
+ else {
177
+ // Plain string: match function name directly
178
+ if (calleeName === fn)
179
+ return true;
180
+ }
181
+ }
182
+ return false;
183
+ }
184
+ /**
185
+ * Check if an argument matches a condition
186
+ */
187
+ function matchesCondition(arg, condition, state) {
188
+ // String condition
189
+ if (typeof condition === 'string') {
190
+ // Import condition: 'import:importPath:exportName'
191
+ const parsed = parseImportString(condition);
192
+ if (parsed) {
193
+ return t.isExpression(arg) && matchesImport(arg, parsed, state);
194
+ }
195
+ // Plain string: match string literal or identifier name
196
+ if (t.isStringLiteral(arg))
197
+ return arg.value === condition;
198
+ if (t.isIdentifier(arg))
199
+ return arg.name === condition;
200
+ return false;
201
+ }
202
+ // Call expression condition: match call with specific arguments
203
+ if ('call' in condition) {
204
+ if (!t.isCallExpression(arg))
205
+ return false;
206
+ const calleeName = getCalleeName(arg.callee);
207
+ if (!calleeName)
208
+ return false;
209
+ // Check if callee matches
210
+ const parsed = parseImportString(condition.call);
211
+ if (parsed) {
212
+ // Import string: check if callee is an imported identifier
213
+ if (!t.isIdentifier(arg.callee))
214
+ return false;
215
+ const imported = state.imports.get(arg.callee.name);
216
+ if (!imported || imported.importPath !== parsed.importPath || imported.exportName !== parsed.exportName) {
217
+ return false;
218
+ }
219
+ }
220
+ else {
221
+ // Plain string: match function name directly
222
+ if (calleeName !== condition.call)
223
+ return false;
224
+ }
225
+ // Check argument conditions
226
+ if (condition.args) {
227
+ for (const [indexStr, argCondition] of Object.entries(condition.args)) {
228
+ const index = Number(indexStr);
229
+ const nestedArg = arg.arguments[index];
230
+ if (!nestedArg)
231
+ return false;
232
+ if (!matchesCondition(nestedArg, argCondition, state))
233
+ return false;
234
+ }
235
+ }
236
+ return true;
237
+ }
238
+ // Member expression condition: match $setup["ClientOnly"]
239
+ if ('member' in condition) {
240
+ if (!t.isMemberExpression(arg))
241
+ return false;
242
+ // Check object
243
+ if (!t.isIdentifier(arg.object) || arg.object.name !== condition.object)
244
+ return false;
245
+ // Check property
246
+ if (typeof condition.property === 'string') {
247
+ // Simple string property
248
+ if (t.isIdentifier(arg.property) && !arg.computed) {
249
+ return arg.property.name === condition.property;
250
+ }
251
+ if (t.isStringLiteral(arg.property) && arg.computed) {
252
+ return arg.property.value === condition.property;
253
+ }
254
+ return false;
255
+ }
256
+ else {
257
+ // Nested condition on property (for future extensibility)
258
+ return false;
259
+ }
260
+ }
261
+ // Object condition: match prop value inside an object argument
262
+ if (!t.isObjectExpression(arg))
263
+ return false;
264
+ for (const prop of arg.properties) {
265
+ if (!t.isObjectProperty(prop))
266
+ continue;
267
+ if (!t.isIdentifier(prop.key) || prop.key.name !== condition.prop)
268
+ continue;
269
+ // Check value
270
+ if (condition.equals === null && t.isNullLiteral(prop.value))
271
+ return true;
272
+ if (condition.equals === true && t.isBooleanLiteral(prop.value) && prop.value.value === true)
273
+ return true;
274
+ if (condition.equals === false && t.isBooleanLiteral(prop.value) && prop.value.value === false)
275
+ return true;
276
+ if (typeof condition.equals === 'string' && t.isStringLiteral(prop.value) && prop.value.value === condition.equals)
277
+ return true;
278
+ if (typeof condition.equals === 'number' && t.isNumericLiteral(prop.value) && prop.value.value === condition.equals)
279
+ return true;
280
+ }
281
+ return false;
282
+ }
283
+ /**
284
+ * Apply a replacement to a call expression
285
+ */
286
+ function applyReplace(path, replace, state) {
287
+ // Replace the entire call expression
288
+ if (!('arg' in replace) && !('argsFrom' in replace)) {
289
+ path.replaceWith(valueToAst(replace.with));
290
+ state.modified = true;
291
+ return;
292
+ }
293
+ // Replace a prop inside an object argument
294
+ if ('arg' in replace && 'prop' in replace) {
295
+ const arg = path.node.arguments[replace.arg];
296
+ if (!t.isObjectExpression(arg))
297
+ return;
298
+ for (const prop of arg.properties) {
299
+ if (!t.isObjectProperty(prop))
300
+ continue;
301
+ if (!t.isIdentifier(prop.key) || prop.key.name !== replace.prop)
302
+ continue;
303
+ prop.value = valueToAst(replace.with);
304
+ state.modified = true;
305
+ return;
306
+ }
307
+ }
308
+ // Replace entire argument
309
+ else if ('arg' in replace) {
310
+ if (path.node.arguments.length > replace.arg) {
311
+ path.node.arguments[replace.arg] = valueToAst(replace.with);
312
+ state.modified = true;
313
+ }
314
+ }
315
+ // Replace all args from index onwards with a single value
316
+ else if ('argsFrom' in replace) {
317
+ if (path.node.arguments.length > replace.argsFrom) {
318
+ path.node.arguments = [...path.node.arguments.slice(0, replace.argsFrom), valueToAst(replace.with)];
319
+ state.modified = true;
320
+ }
321
+ }
322
+ }
323
+ /**
324
+ * Apply a removal to a call expression
325
+ */
326
+ function applyRemove(path, remove, state) {
327
+ // Remove a prop inside an object argument
328
+ if ('prop' in remove) {
329
+ const arg = path.node.arguments[remove.arg];
330
+ if (!t.isObjectExpression(arg))
331
+ return;
332
+ const index = arg.properties.findIndex((prop) => {
333
+ // Check ObjectProperty with Identifier key
334
+ if (t.isObjectProperty(prop) && t.isIdentifier(prop.key) && prop.key.name === remove.prop) {
335
+ return true;
336
+ }
337
+ // Check ObjectMethod (getter/setter)
338
+ if (t.isObjectMethod(prop) && t.isIdentifier(prop.key) && prop.key.name === remove.prop) {
339
+ return true;
340
+ }
341
+ return false;
342
+ });
343
+ if (index !== -1) {
344
+ arg.properties.splice(index, 1);
345
+ state.modified = true;
346
+ }
347
+ }
348
+ // Remove entire argument
349
+ else if ('arg' in remove) {
350
+ if (path.node.arguments.length > remove.arg) {
351
+ path.node.arguments.splice(remove.arg, 1);
352
+ state.modified = true;
353
+ }
354
+ }
355
+ // Remove all args from index onwards
356
+ else if ('argsFrom' in remove) {
357
+ if (path.node.arguments.length > remove.argsFrom) {
358
+ path.node.arguments = path.node.arguments.slice(0, remove.argsFrom);
359
+ state.modified = true;
360
+ }
361
+ }
362
+ }
363
+ /**
364
+ * Remove unreferenced bindings after modifications
365
+ */
366
+ function removeUnreferencedPlugin(state) {
367
+ return {
368
+ visitor: {
369
+ Program: {
370
+ enter(program) {
371
+ for (const [name, binding] of Object.entries(program.scope.bindings)) {
372
+ if (!binding.referenced)
373
+ state.alreadyUnreferenced.add(name);
374
+ }
375
+ },
376
+ exit(program) {
377
+ if (!state.modified)
378
+ return;
379
+ removeUnreferenced(program, state.alreadyUnreferenced);
380
+ },
381
+ },
382
+ },
383
+ };
384
+ }
385
+ function removeUnreferenced(program, alreadyUnreferenced) {
386
+ for (;;) {
387
+ program.scope.crawl();
388
+ let removed = false;
389
+ for (const [name, binding] of Object.entries(program.scope.bindings)) {
390
+ if (binding.referenced || alreadyUnreferenced.has(name))
391
+ continue;
392
+ const parent = binding.path.parentPath;
393
+ if (parent?.isImportDeclaration() && parent.node.specifiers.length === 1) {
394
+ parent.remove();
395
+ }
396
+ else {
397
+ binding.path.remove();
398
+ }
399
+ removed = true;
400
+ }
401
+ if (!removed)
402
+ break;
403
+ }
404
+ }
@@ -0,0 +1,9 @@
1
+ export { buildFilterRolldown };
2
+ import type { StaticReplace } from './applyStaticReplace.js';
3
+ /**
4
+ * Build a filterRolldown from staticReplaceList by extracting all import strings.
5
+ * For a single entry, ALL import strings must be present (AND logic),
6
+ * except for call.match.function array which is OR logic.
7
+ * Between staticReplace entries it's OR logic.
8
+ */
9
+ declare function buildFilterRolldown(staticReplaceList: StaticReplace[]): RegExp | null;
@@ -0,0 +1,100 @@
1
+ export { buildFilterRolldown };
2
+ import { escapeRegex } from '../../utils.js';
3
+ import { parseImportString } from '../../shared/importString.js';
4
+ /**
5
+ * Build a filterRolldown from staticReplaceList by extracting all import strings.
6
+ * For a single entry, ALL import strings must be present (AND logic),
7
+ * except for call.match.function array which is OR logic.
8
+ * Between staticReplace entries it's OR logic.
9
+ */
10
+ function buildFilterRolldown(staticReplaceList) {
11
+ const rulePatterns = [];
12
+ // Process each entry separately
13
+ for (const staticReplaceEntry of staticReplaceList) {
14
+ const importStrings = new Set();
15
+ const functionImportStrings = new Set();
16
+ // Extract function import strings separately
17
+ extractImportStrings(staticReplaceEntry.match.function, functionImportStrings);
18
+ // Extract arg import strings
19
+ if (staticReplaceEntry.match.args) {
20
+ for (const condition of Object.values(staticReplaceEntry.match.args)) {
21
+ extractImportStringsFromCondition(condition, importStrings);
22
+ }
23
+ }
24
+ // Build pattern for this staticReplaceEntry
25
+ const ruleParts = [];
26
+ // For function imports: if array, use OR; otherwise use AND
27
+ if (functionImportStrings.size > 0) {
28
+ const functionPatterns = [];
29
+ for (const importStr of functionImportStrings) {
30
+ const parts = parseImportString(importStr);
31
+ if (parts) {
32
+ // Each function import should match both importPath and export name
33
+ functionPatterns.push(`(?=.*${escapeRegex(parts.importPath)})(?=.*${escapeRegex(parts.exportName)})`);
34
+ }
35
+ }
36
+ // If multiple functions, they are alternatives (OR)
37
+ if (functionPatterns.length > 0) {
38
+ if (functionPatterns.length === 1) {
39
+ ruleParts.push(functionPatterns[0]);
40
+ }
41
+ else {
42
+ // Multiple function patterns: file must match at least one
43
+ ruleParts.push(`(?:${functionPatterns.join('|')})`);
44
+ }
45
+ }
46
+ }
47
+ // For arg imports: all must be present (AND)
48
+ for (const importStr of importStrings) {
49
+ const parts = parseImportString(importStr);
50
+ if (parts) {
51
+ // Each arg import should match both importPath and export name
52
+ ruleParts.push(`(?=.*${escapeRegex(parts.importPath)})(?=.*${escapeRegex(parts.exportName)})`);
53
+ }
54
+ }
55
+ // Combine all parts for this rule with AND logic
56
+ if (ruleParts.length > 0) {
57
+ // All parts must match for this rule
58
+ rulePatterns.push(ruleParts.join(''));
59
+ }
60
+ }
61
+ if (rulePatterns.length === 0)
62
+ return null;
63
+ // Create a regex that matches if any rule pattern matches (OR between staticReplace entries)
64
+ return new RegExp(rulePatterns.join('|'), 's');
65
+ }
66
+ /**
67
+ * Extract import strings from function patterns
68
+ */
69
+ function extractImportStrings(functions, result) {
70
+ const arr = Array.isArray(functions) ? functions : [functions];
71
+ for (const fn of arr) {
72
+ if (parseImportString(fn)) {
73
+ result.add(fn);
74
+ }
75
+ }
76
+ }
77
+ /**
78
+ * Extract import strings from argument conditions
79
+ */
80
+ function extractImportStringsFromCondition(condition, result) {
81
+ if (typeof condition === 'string') {
82
+ if (parseImportString(condition)) {
83
+ result.add(condition);
84
+ }
85
+ }
86
+ else if (condition && typeof condition === 'object') {
87
+ // Handle call condition
88
+ if ('call' in condition && typeof condition.call === 'string') {
89
+ if (parseImportString(condition.call)) {
90
+ result.add(condition.call);
91
+ }
92
+ // Recursively check nested args
93
+ if (condition.args) {
94
+ for (const nestedCondition of Object.values(condition.args)) {
95
+ extractImportStringsFromCondition(nestedCondition, result);
96
+ }
97
+ }
98
+ }
99
+ }
100
+ }
@@ -0,0 +1,4 @@
1
+ export { pluginStaticReplace };
2
+ import type { Plugin } from 'vite';
3
+ import { VikeConfigInternal } from '../shared/resolveVikeConfigInternal.js';
4
+ declare function pluginStaticReplace(vikeConfig: VikeConfigInternal): Plugin[];
@@ -0,0 +1,71 @@
1
+ export { pluginStaticReplace };
2
+ import { assert } from '../utils.js';
3
+ import { isViteServerSide_extraSafe } from '../shared/isViteServerSide.js';
4
+ import { applyStaticReplace } from './pluginStaticReplace/applyStaticReplace.js';
5
+ import { buildFilterRolldown } from './pluginStaticReplace/buildFilterRolldown.js';
6
+ function pluginStaticReplace(vikeConfig) {
7
+ let config;
8
+ // staticReplaceList
9
+ const staticReplaceList = getStaticReplaceList(vikeConfig);
10
+ if (staticReplaceList.length === 0)
11
+ return [];
12
+ // filterRolldown
13
+ const skipNodeModules = '/node_modules/';
14
+ const include = buildFilterRolldown(staticReplaceList);
15
+ assert(include);
16
+ const filterRolldown = {
17
+ id: {
18
+ exclude: `**${skipNodeModules}**`,
19
+ },
20
+ code: {
21
+ include,
22
+ },
23
+ };
24
+ const filterFunction = (id, code) => {
25
+ if (id.includes(skipNodeModules))
26
+ return false;
27
+ if (!include.test(code))
28
+ return false;
29
+ return true;
30
+ };
31
+ return [
32
+ {
33
+ name: 'vike:pluginStaticReplace',
34
+ enforce: 'post',
35
+ configResolved: {
36
+ async handler(config_) {
37
+ config = config_;
38
+ },
39
+ },
40
+ transform: {
41
+ filter: filterRolldown,
42
+ async handler(code, id, options) {
43
+ assert(filterFunction(id, code));
44
+ const env = isViteServerSide_extraSafe(config, this.environment, options) ? 'server' : 'client';
45
+ const result = await applyStaticReplace({
46
+ code,
47
+ id,
48
+ env,
49
+ options: staticReplaceList,
50
+ });
51
+ return result;
52
+ },
53
+ },
54
+ },
55
+ ];
56
+ }
57
+ /**
58
+ * Extract all staticReplaceList from vikeConfig
59
+ */
60
+ function getStaticReplaceList(vikeConfig) {
61
+ const staticReplaceConfigs = vikeConfig._from.configsCumulative.staticReplace;
62
+ if (!staticReplaceConfigs)
63
+ return [];
64
+ const staticReplaceList = [];
65
+ for (const configValue of staticReplaceConfigs.values) {
66
+ const entries = configValue.value;
67
+ assert(Array.isArray(entries));
68
+ staticReplaceList.push(...entries);
69
+ }
70
+ return staticReplaceList;
71
+ }
@@ -1,3 +1,4 @@
1
1
  export { pluginViteConfigVikeExtensions };
2
2
  import type { Plugin } from 'vite';
3
- declare function pluginViteConfigVikeExtensions(): Promise<Plugin[]>;
3
+ import type { VikeConfigInternal } from '../shared/resolveVikeConfigInternal.js';
4
+ declare function pluginViteConfigVikeExtensions(vikeConfig: VikeConfigInternal): Promise<Plugin[]>;
@@ -1,11 +1,9 @@
1
1
  export { pluginViteConfigVikeExtensions };
2
2
  import { mergeConfig } from 'vite';
3
3
  import { assertUsage, isCallable, isObject } from '../utils.js';
4
- import { getVikeConfigInternalEarly } from '../../api/resolveViteConfigFromUser.js';
5
4
  // Apply +vite
6
5
  // - For example, Vike extensions adding Vite plugins
7
- async function pluginViteConfigVikeExtensions() {
8
- const vikeConfig = await getVikeConfigInternalEarly();
6
+ async function pluginViteConfigVikeExtensions(vikeConfig) {
9
7
  if (vikeConfig === null)
10
8
  return [];
11
9
  let viteConfigFromExtensions = {};
@@ -0,0 +1,50 @@
1
+ export { parseImportString };
2
+ export { isImportString };
3
+ export { serializeImportString };
4
+ export type { ImportString };
5
+ export type { ImportStringList };
6
+ export type { ImportStringParsed };
7
+ /** `import:${importPath}:${exportName}`
8
+ * @example import:./Layout:default
9
+ */
10
+ type ImportString = `import:${string}:${string}`;
11
+ type ImportStringList = ImportString | ImportString[];
12
+ type ImportStringParsed = {
13
+ importPath: string;
14
+ exportName: string;
15
+ };
16
+ /**
17
+ * Parse import string in format: import:importPath:exportName
18
+ *
19
+ * @example parseImportString('import:react/jsx-runtime:jsx')
20
+ * // => { importPath: 'react/jsx-runtime', exportName: 'jsx' }
21
+ *
22
+ * @example parseImportString('import:./Layout:default')
23
+ * // => { importPath: './Layout', exportName: 'default' }
24
+ *
25
+ * @example parseImportString('import:./Layout', { legacy: true })
26
+ * // => { importPath: './Layout', exportName: 'default' }
27
+ */
28
+ declare function parseImportString(importString: string, { legacy }?: {
29
+ legacy?: boolean;
30
+ }): null | ImportStringParsed;
31
+ /**
32
+ * Check if a string is an import string (starts with 'import:').
33
+ *
34
+ * @example isImportString('import:react:jsx')
35
+ * // => true
36
+ *
37
+ * @example isImportString('some-other-string')
38
+ * // => false
39
+ */
40
+ declare function isImportString(str: string): str is ImportString;
41
+ /**
42
+ * Serialize import string from importPath and export name.
43
+ *
44
+ * @example serializeImportString({ importPath: 'react/jsx-runtime', exportName: 'jsx' })
45
+ * // => 'import:react/jsx-runtime:jsx'
46
+ *
47
+ * @example serializeImportString({ importPath: './Layout', exportName: 'default' })
48
+ * // => 'import:./Layout:default'
49
+ */
50
+ declare function serializeImportString({ importPath, exportName }: ImportStringParsed): `import:${string}:${string}`;
@@ -0,0 +1,64 @@
1
+ export { parseImportString };
2
+ export { isImportString };
3
+ export { serializeImportString };
4
+ import { assert } from '../utils.js';
5
+ const IMPORT = 'import';
6
+ const SEP = ':';
7
+ /**
8
+ * Parse import string in format: import:importPath:exportName
9
+ *
10
+ * @example parseImportString('import:react/jsx-runtime:jsx')
11
+ * // => { importPath: 'react/jsx-runtime', exportName: 'jsx' }
12
+ *
13
+ * @example parseImportString('import:./Layout:default')
14
+ * // => { importPath: './Layout', exportName: 'default' }
15
+ *
16
+ * @example parseImportString('import:./Layout', { legacy: true })
17
+ * // => { importPath: './Layout', exportName: 'default' }
18
+ */
19
+ function parseImportString(importString, { legacy = false } = {}) {
20
+ if (!isImportString(importString))
21
+ return null;
22
+ const parts = importString.split(SEP);
23
+ assert(parts[0] === IMPORT);
24
+ if (legacy && parts.length === 2) {
25
+ /* TODO
26
+ assertWarning(false, 'To-Do', { onlyOnce: true, showStackTrace: true })
27
+ */
28
+ const exportName = 'default';
29
+ const importPath = parts[1];
30
+ assert(importPath);
31
+ return { importPath, exportName };
32
+ }
33
+ assert(parts.length >= 3);
34
+ const exportName = parts[parts.length - 1];
35
+ const importPath = parts.slice(1, -1).join(SEP);
36
+ assert(exportName);
37
+ assert(importPath);
38
+ return { importPath, exportName };
39
+ }
40
+ /**
41
+ * Check if a string is an import string (starts with 'import:').
42
+ *
43
+ * @example isImportString('import:react:jsx')
44
+ * // => true
45
+ *
46
+ * @example isImportString('some-other-string')
47
+ * // => false
48
+ */
49
+ function isImportString(str) {
50
+ return str.startsWith(`${IMPORT}${SEP}`);
51
+ }
52
+ /**
53
+ * Serialize import string from importPath and export name.
54
+ *
55
+ * @example serializeImportString({ importPath: 'react/jsx-runtime', exportName: 'jsx' })
56
+ * // => 'import:react/jsx-runtime:jsx'
57
+ *
58
+ * @example serializeImportString({ importPath: './Layout', exportName: 'default' })
59
+ * // => 'import:./Layout:default'
60
+ */
61
+ function serializeImportString({ importPath, exportName }) {
62
+ const importString = `${IMPORT}${SEP}${importPath}${SEP}${exportName}`;
63
+ return importString;
64
+ }
@@ -270,6 +270,14 @@ const configDefinitionsBuiltIn = {
270
270
  env: { server: true },
271
271
  cumulative: true,
272
272
  },
273
+ staticReplace: {
274
+ env: { config: true },
275
+ cumulative: true,
276
+ global: true,
277
+ /* TODO
278
+ vite: true,
279
+ */
280
+ },
273
281
  };
274
282
  function getConfigEnv(pageConfig, configName) {
275
283
  const source = getConfigValueSourceRelevantAnyEnv(configName, pageConfig);
@@ -5,6 +5,7 @@ export { assertPointerImportPath };
5
5
  // Playground: https://github.com/brillout/acorn-playground
6
6
  // Notes about `with { type: 'pointer' }`
7
7
  // - It works well with TypeScript: it doesn't complain upon `with { type: 'unknown-to-typescript' }` and go-to-definition & types are preserved: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-3.html#import-attributes
8
+ // - Babel already supports it: https://babeljs.io/docs/babel-parser#plugins => search for `importAttributes`
8
9
  // - Acorn support for import attributes: https://github.com/acornjs/acorn/issues/983
9
10
  // - Acorn plugin: https://github.com/acornjs/acorn/issues/983
10
11
  // - Isn't stage 4 yet: https://github.com/tc39/proposal-import-attributes
@@ -17,9 +18,10 @@ export { assertPointerImportPath };
17
18
  // - Using a magic comment `// @vike-real-import` is probably a bad idea:
18
19
  // - Esbuild removes comments: https://github.com/evanw/esbuild/issues/1439#issuecomment-877656182
19
20
  // - Using source maps to track these magic comments is brittle (source maps can easily break)
20
- import { parse } from 'acorn';
21
+ import { parseSync } from '@babel/core';
21
22
  import { assert, assertUsage, assertWarning, isFilePathAbsolute, isImportPath, styleFileRE } from '../../utils.js';
22
23
  import pc from '@brillout/picocolors';
24
+ import { parseImportString, isImportString, serializeImportString } from '../importString.js';
23
25
  function transformPointerImports(code, filePathToShowToUser2, pointerImports,
24
26
  // For ./transformPointerImports.spec.ts
25
27
  skipWarnings) {
@@ -103,11 +105,11 @@ skipWarnings) {
103
105
  return codeMod;
104
106
  }
105
107
  function getImports(code) {
106
- const { body } = parse(code, {
107
- ecmaVersion: 'latest',
108
+ const result = parseSync(code, {
108
109
  sourceType: 'module',
109
- // https://github.com/acornjs/acorn/issues/1136
110
110
  });
111
+ assert(result);
112
+ const { body } = result.program;
111
113
  const imports = [];
112
114
  body.forEach((node) => {
113
115
  if (node.type === 'ImportDeclaration')
@@ -115,16 +117,13 @@ function getImports(code) {
115
117
  });
116
118
  return imports;
117
119
  }
118
- const import_ = 'import';
119
- const SEP = ':';
120
120
  const zeroWidthSpace = '\u200b';
121
121
  function serializePointerImportData({ importPath, exportName, importStringWasGenerated, }) {
122
122
  const tag = importStringWasGenerated ? zeroWidthSpace : '';
123
- // `import:${importPath}:${importPath}`
124
- return `${tag}${import_}${SEP}${importPath}${SEP}${exportName}`;
123
+ return `${tag}${serializeImportString({ importPath, exportName })}`;
125
124
  }
126
125
  function isPointerImportData(str) {
127
- return str.startsWith(import_ + SEP) || str.startsWith(zeroWidthSpace + import_ + SEP);
126
+ return isImportString(str) || (str.startsWith(zeroWidthSpace) && isImportString(str.slice(1)));
128
127
  }
129
128
  function parsePointerImportData(importString) {
130
129
  if (!isPointerImportData(importString)) {
@@ -136,15 +135,9 @@ function parsePointerImportData(importString) {
136
135
  assert(zeroWidthSpace.length === 1);
137
136
  importString = importString.slice(1);
138
137
  }
139
- const parts = importString.split(SEP).slice(1);
140
- if (!importStringWasGenerated && parts.length === 1) {
141
- const exportName = 'default';
142
- const importPath = parts[0];
143
- return { importPath, exportName, importStringWasGenerated, importString };
144
- }
145
- assert(parts.length >= 2);
146
- const exportName = parts[parts.length - 1];
147
- const importPath = parts.slice(0, -1).join(SEP);
138
+ const parsed = parseImportString(importString, { legacy: !importStringWasGenerated });
139
+ assert(parsed);
140
+ const { importPath, exportName } = parsed;
148
141
  if (importPath.startsWith('.') && !(importPath.startsWith('./') || importPath.startsWith('../'))) {
149
142
  assert(!importStringWasGenerated);
150
143
  assertUsage(false, `Invalid relative import path ${pc.code(importPath)} defined by ${pc.code(JSON.stringify(importString))} because it should start with ${pc.code('./')} or ${pc.code('../')}, or use an npm package import instead.`);
@@ -32,6 +32,7 @@ async function generateNonce() {
32
32
  return cryptoModule.randomBytes(16).toString('base64url');
33
33
  }
34
34
  function inferNonceAttr(pageContext) {
35
+ // No need to escape the injected HTML — pageContext.cspNone is controlled by the developer, not by the website visitor (I wouldn't know why the developer would set +csp.none to a value coming from the database)
35
36
  const nonceAttr = pageContext.cspNonce ? ` nonce="${pageContext.cspNonce}"` : '';
36
37
  return nonceAttr;
37
38
  }
@@ -41,5 +42,6 @@ function addCspResponseHeader(pageContext, headersResponse) {
41
42
  return;
42
43
  if (headersResponse.get('Content-Security-Policy'))
43
44
  return;
45
+ // No need to sanitize the injected header — see comment above about not escaping HTML
44
46
  headersResponse.set('Content-Security-Policy', `script-src 'self' 'nonce-${pageContext.cspNonce}'`);
45
47
  }
@@ -1,8 +1,9 @@
1
1
  export type { ConfigResolved };
2
- import type { ConfigBuiltIn, ConfigBuiltInResolved, ImportString } from '../Config.js';
2
+ import type { ConfigBuiltIn, ConfigBuiltInResolved } from '../Config.js';
3
+ import type { ImportStringList } from '../../node/vite/shared/importString.js';
3
4
  type ConfigUnresolved = WithoutImportString<ConfigBuiltIn & Vike.Config>;
4
5
  type ConfigResolvedOnly = ConfigBuiltInResolved & Vike.ConfigResolved;
5
6
  type ConfigResolved = ConfigResolvedOnly & Omit<ConfigUnresolved, keyof ConfigResolvedOnly>;
6
7
  type WithoutImportString<T> = {
7
- [K in keyof T]: Exclude<T[K], ImportString>;
8
+ [K in keyof T]: Exclude<T[K], ImportStringList>;
8
9
  };
@@ -8,7 +8,6 @@ export type { HookName };
8
8
  export type { HookNameOld };
9
9
  export type { HookNamePage };
10
10
  export type { HookNameGlobal };
11
- export type { ImportString };
12
11
  export type { Route };
13
12
  export type { KeepScrollPosition };
14
13
  export type { DataAsync };
@@ -47,6 +46,8 @@ import type { GlobalContext } from './GlobalContext.js';
47
46
  import type { InlineConfig } from 'vite';
48
47
  import type { PassToClientPublic } from '../server/runtime/renderPageServer/html/serializeContext.js';
49
48
  import type { CliPreviewConfig } from '../node/api/preview.js';
49
+ import type { StaticReplace } from '../node/vite/plugins/pluginStaticReplace/applyStaticReplace.js';
50
+ import type { ImportStringList } from '../node/vite/shared/importString.js';
50
51
  type HookNameOld = HookName | HookNameOldDesign;
51
52
  type HookName = HookNamePage | HookNameGlobal;
52
53
  type HookNamePage = 'onHydrationEnd' | 'onBeforePrerenderStart' | 'onBeforeRender' | 'onPageTransitionStart' | 'onPageTransitionEnd' | 'onRenderHtml' | 'onRenderClient' | 'guard' | 'data' | 'onData' | 'route';
@@ -250,12 +251,12 @@ type ConfigBuiltIn = {
250
251
  *
251
252
  * https://vike.dev/route
252
253
  */
253
- route?: Route | ImportString;
254
+ route?: Route | ImportStringList;
254
255
  /** Protect page(s), e.g. forbid unauthorized access.
255
256
  *
256
257
  * https://vike.dev/guard
257
258
  */
258
- guard?: GuardAsync | GuardSync | ImportString;
259
+ guard?: GuardAsync | GuardSync | ImportStringList;
259
260
  /**
260
261
  * Pre-render page(s).
261
262
  *
@@ -264,7 +265,7 @@ type ConfigBuiltIn = {
264
265
  *
265
266
  * @default false
266
267
  */
267
- prerender?: boolean | ImportString | {
268
+ prerender?: boolean | ImportStringList | {
268
269
  /**
269
270
  * Allow only some of your pages to be pre-rendered.
270
271
  *
@@ -339,32 +340,32 @@ type ConfigBuiltIn = {
339
340
  *
340
341
  * https://vike.dev/extends
341
342
  */
342
- extends?: Config | Config[] | ImportString;
343
+ extends?: Config | Config[] | ImportStringList;
343
344
  /** Hook called before the page is rendered.
344
345
  *
345
346
  * https://vike.dev/onBeforeRender
346
347
  */
347
- onBeforeRender?: OnBeforeRenderAsync | OnBeforeRenderSync | ImportString | null;
348
+ onBeforeRender?: OnBeforeRenderAsync | OnBeforeRenderSync | ImportStringList | null;
348
349
  /** Hook called when a `pageContext` object is created.
349
350
  *
350
351
  * https://vike.dev/onCreatePageContext
351
352
  */
352
- onCreatePageContext?: ((pageContext: PageContextServer) => void) | ImportString | null;
353
+ onCreatePageContext?: ((pageContext: PageContextServer) => void) | ImportStringList | null;
353
354
  /** Hook called when an error occurs during server-side rendering.
354
355
  *
355
356
  * https://vike.dev/onError
356
357
  */
357
- onError?: ((error: unknown, pageContext: null | PageContextServer) => void) | ImportString | null;
358
+ onError?: ((error: unknown, pageContext: null | PageContextServer) => void) | ImportStringList | null;
358
359
  /** Hook called when the `globalContext` object is created.
359
360
  *
360
361
  * https://vike.dev/onCreateGlobalContext
361
362
  */
362
- onCreateGlobalContext?: ((globalContext: GlobalContext) => void) | ImportString | null;
363
+ onCreateGlobalContext?: ((globalContext: GlobalContext) => void) | ImportStringList | null;
363
364
  /** Hook for fetching data.
364
365
  *
365
366
  * https://vike.dev/data
366
367
  */
367
- data?: DataAsync<unknown> | DataSync<unknown> | ImportString | null;
368
+ data?: DataAsync<unknown> | DataSync<unknown> | ImportStringList | null;
368
369
  /** Hook called as soon as `pageContext.data` is available.
369
370
  *
370
371
  * https://vike.dev/onData
@@ -374,83 +375,83 @@ type ConfigBuiltIn = {
374
375
  *
375
376
  * https://vike.dev/passToClient
376
377
  */
377
- passToClient?: PassToClientPublic | ImportString;
378
+ passToClient?: PassToClientPublic | ImportStringList;
378
379
  /** Hook called when page is rendered on the client-side.
379
380
  *
380
381
  * https://vike.dev/onRenderClient
381
382
  */
382
- onRenderClient?: OnRenderClientAsync | OnRenderClientSync | ImportString;
383
+ onRenderClient?: OnRenderClientAsync | OnRenderClientSync | ImportStringList;
383
384
  /** Hook called when page is rendered to HTML on the server-side.
384
385
  *
385
386
  * https://vike.dev/onRenderHtml
386
387
  */
387
- onRenderHtml?: OnRenderHtmlAsync | OnRenderHtmlSync | ImportString;
388
+ onRenderHtml?: OnRenderHtmlAsync | OnRenderHtmlSync | ImportStringList;
388
389
  /** Enable async Route Functions.
389
390
  *
390
391
  * https://vike.dev/route-function#async
391
392
  */
392
- iKnowThePerformanceRisksOfAsyncRouteFunctions?: boolean | ImportString;
393
+ iKnowThePerformanceRisksOfAsyncRouteFunctions?: boolean | ImportStringList;
393
394
  /** Change the URL root of Filesystem Routing.
394
395
  *
395
396
  * https://vike.dev/filesystemRoutingRoot
396
397
  */
397
- filesystemRoutingRoot?: string | ImportString;
398
+ filesystemRoutingRoot?: string | ImportStringList;
398
399
  /** Page Hook called when pre-rendering starts.
399
400
  *
400
401
  * https://vike.dev/onPrerenderStart
401
402
  */
402
- onPrerenderStart?: OnPrerenderStartAsync | OnPrerenderStartSync | ImportString;
403
+ onPrerenderStart?: OnPrerenderStartAsync | OnPrerenderStartSync | ImportStringList;
403
404
  /** Global Hook called before the whole pre-rendering process starts.
404
405
  *
405
406
  * https://vike.dev/onBeforePrerenderStart
406
407
  */
407
- onBeforePrerenderStart?: OnBeforePrerenderStartAsync | OnBeforePrerenderStartSync | ImportString;
408
+ onBeforePrerenderStart?: OnBeforePrerenderStartAsync | OnBeforePrerenderStartSync | ImportStringList;
408
409
  /** Hook called before the URL is routed to a page.
409
410
  *
410
411
  * https://vike.dev/onBeforeRoute
411
412
  */
412
- onBeforeRoute?: OnBeforeRouteAsync | OnBeforeRouteSync | ImportString;
413
+ onBeforeRoute?: OnBeforeRouteAsync | OnBeforeRouteSync | ImportStringList;
413
414
  /** Hook called after the page is hydrated.
414
415
  *
415
416
  * https://vike.dev/onHydrationEnd
416
417
  */
417
- onHydrationEnd?: OnHydrationEndAsync | OnHydrationEndSync | ImportString;
418
+ onHydrationEnd?: OnHydrationEndAsync | OnHydrationEndSync | ImportStringList;
418
419
  /** Hook called before the user navigates to a new page.
419
420
  *
420
421
  * https://vike.dev/onPageTransitionStart
421
422
  */
422
- onPageTransitionStart?: OnPageTransitionStartAsync | OnPageTransitionStartSync | ImportString;
423
+ onPageTransitionStart?: OnPageTransitionStartAsync | OnPageTransitionStartSync | ImportStringList;
423
424
  /** Hook called after the user navigates to a new page.
424
425
  *
425
426
  * https://vike.dev/onPageTransitionEnd
426
427
  */
427
- onPageTransitionEnd?: OnPageTransitionEndAsync | OnPageTransitionEndSync | ImportString;
428
+ onPageTransitionEnd?: OnPageTransitionEndAsync | OnPageTransitionEndSync | ImportStringList;
428
429
  /** Whether the UI framework (React/Vue/Solid/...) allows the page's hydration to be aborted.
429
430
  *
430
431
  * https://vike.dev/hydrationCanBeAborted
431
432
  */
432
- hydrationCanBeAborted?: boolean | ImportString;
433
+ hydrationCanBeAborted?: boolean | ImportStringList;
433
434
  /** Add client code.
434
435
  *
435
436
  * https://vike.dev/client
436
437
  */
437
- client?: string | ImportString;
438
+ client?: string | ImportStringList;
438
439
  /** Enable Client Routing.
439
440
  *
440
441
  * https://vike.dev/clientRouting
441
442
  */
442
- clientRouting?: boolean | ImportString;
443
+ clientRouting?: boolean | ImportStringList;
443
444
  /**
444
445
  * Whether hooks are loaded on the client-side.
445
446
  *
446
447
  * https://vike.dev/clientHooks
447
448
  */
448
- clientHooks?: boolean | null | ImportString;
449
+ clientHooks?: boolean | null | ImportStringList;
449
450
  /** Create new or modify existing configurations.
450
451
  *
451
452
  * https://vike.dev/meta
452
453
  */
453
- meta?: ConfigMeta | ImportString;
454
+ meta?: ConfigMeta | ImportStringList;
454
455
  /** Vite configuration.
455
456
  *
456
457
  * https://vite.dev/config/
@@ -498,13 +499,13 @@ type ConfigBuiltIn = {
498
499
  *
499
500
  * https://vike.dev/prefetch
500
501
  */
501
- prefetch?: PrefetchSetting | ImportString;
502
+ prefetch?: PrefetchSetting | ImportStringList;
502
503
  /** @deprecated Use `prefetch` setting (https://vike.dev/prefetch) instead. */
503
504
  /** Prefetch links.
504
505
  *
505
506
  * https://vike.dev/prefetchStaticAssets
506
507
  */
507
- prefetchStaticAssets?: PrefetchStaticAssets | ImportString;
508
+ prefetchStaticAssets?: PrefetchStaticAssets | ImportStringList;
508
509
  /** Modify the timeouts of hooks. */
509
510
  hooksTimeout?: HooksTimeoutProvidedByUser;
510
511
  /** `Cache-Control` HTTP header value.
@@ -587,14 +588,19 @@ type ConfigBuiltIn = {
587
588
  cli?: {
588
589
  preview?: CliPreviewConfig;
589
590
  };
591
+ /**
592
+ * Static code transformations for optimizations like removing component children server-side.
593
+ *
594
+ * @experimental
595
+ */
596
+ staticReplace?: StaticReplace[];
590
597
  };
591
598
  type ConfigBuiltInResolved = {
592
599
  passToClient?: string[][];
593
600
  redirects?: Record<string, string>[];
594
- prerender?: Exclude<Config['prerender'], ImportString | undefined>[];
601
+ prerender?: Exclude<Config['prerender'], ImportStringList | undefined>[];
595
602
  middleware?: Function[];
596
- headersResponse?: Exclude<Config['headersResponse'], ImportString | undefined>[];
603
+ headersResponse?: Exclude<Config['headersResponse'], ImportStringList | undefined>[];
604
+ staticReplace?: StaticReplace[][];
597
605
  };
598
606
  type ConfigMeta = Record<string, ConfigDefinition>;
599
- type ImportString = ImportStringVal | ImportStringVal[];
600
- type ImportStringVal = `import:${string}`;
@@ -4,7 +4,8 @@ export type { PrerenderContextPublic as PrerenderContext } from '../node/prerend
4
4
  export type { PageContextBuiltInServer } from './PageContext.js';
5
5
  export type { PageContextBuiltInClientWithClientRouting } from './PageContext.js';
6
6
  export type { PageContextBuiltInClientWithServerRouting } from './PageContext.js';
7
- export type { Config, ConfigMeta as Meta, ImportString, KeepScrollPosition, } from './Config.js';
7
+ export type { Config, ConfigMeta as Meta, KeepScrollPosition, } from './Config.js';
8
+ export type { ImportString } from '../node/vite/shared/importString.js';
8
9
  export type { DataAsync, DataSync, GuardAsync, GuardSync, OnBeforePrerenderStartAsync, OnBeforePrerenderStartSync, OnBeforeRenderAsync, OnBeforeRenderSync, OnBeforeRouteAsync, OnBeforeRouteSync, OnHydrationEndAsync, OnHydrationEndSync, OnPageTransitionEndAsync, OnPageTransitionEndSync, OnPageTransitionStartAsync, OnPageTransitionStartSync, OnPrerenderStartAsync, OnPrerenderStartSync, OnRenderClientAsync, OnRenderClientSync, OnRenderHtmlAsync, OnRenderHtmlSync, RouteAsync, RouteSync, } from './Config.js';
9
10
  export type { ConfigResolved } from './Config/ConfigResolved.js';
10
11
  export type { ConfigEnv } from './PageConfig.js';
@@ -1 +1 @@
1
- export declare const PROJECT_VERSION: "0.4.249-commit-f416149";
1
+ export declare const PROJECT_VERSION: "0.4.249-commit-92f1383";
@@ -1,2 +1,2 @@
1
1
  // Automatically updated by @brillout/release-me
2
- export const PROJECT_VERSION = '0.4.249-commit-f416149';
2
+ export const PROJECT_VERSION = '0.4.249-commit-92f1383';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vike",
3
- "version": "0.4.249-commit-f416149",
3
+ "version": "0.4.249-commit-92f1383",
4
4
  "repository": "https://github.com/vikejs/vike",
5
5
  "exports": {
6
6
  "./server": {
@@ -118,12 +118,12 @@
118
118
  }
119
119
  },
120
120
  "dependencies": {
121
+ "@babel/core": "^7.28.5",
121
122
  "@brillout/import": "^0.2.6",
122
123
  "@brillout/json-serializer": "^0.5.21",
123
124
  "@brillout/picocolors": "^1.0.30",
124
125
  "@brillout/require-shim": "^0.1.2",
125
126
  "@brillout/vite-plugin-server-entry": "^0.7.15",
126
- "acorn": "^8.0.0",
127
127
  "cac": "^6.0.0",
128
128
  "es-module-lexer": "^1.0.0",
129
129
  "esbuild": ">=0.19.0",
@@ -244,7 +244,9 @@
244
244
  "./universal-middleware.js"
245
245
  ],
246
246
  "devDependencies": {
247
+ "@babel/types": "^7.28.5",
247
248
  "@brillout/release-me": "^0.4.12",
249
+ "@types/babel__core": "^7.20.5",
248
250
  "@types/estree": "^1.0.5",
249
251
  "@types/node": "^20.10.5",
250
252
  "@types/picomatch": "^3.0.2",