vike 0.4.266-commit-f1b186a → 0.4.267-commit-b005535

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.
@@ -12,7 +12,7 @@ function assertEnvVite() {
12
12
  assertIsNotBrowser();
13
13
  assertNodeVersion();
14
14
  assertViteVersion(viteVersion);
15
- assertIsNotProductionRuntime(); // Don't bloat server with heavy dependencies like Vite and esbuild
15
+ assertIsNotProductionRuntime(); // Don't bloat server with heavy dependencies like Vite
16
16
  }
17
17
  function onLoad() {
18
18
  installUncaughtErrorHandlers();
@@ -4,7 +4,7 @@ export type { PlusFileValue };
4
4
  export type { PlusFile };
5
5
  export type { PlusFilesByLocationId };
6
6
  import { type LocationId } from './filesystemRouting.js';
7
- import { type EsbuildCache } from './transpileAndExecuteFile.js';
7
+ import { type VikeTranspileCache } from './transpileAndExecuteFile.js';
8
8
  import { type ConfigFile, PointerImportLoaded } from './loadFileAtConfigTime.js';
9
9
  import type { FilePathResolved } from '../../../../types/FilePath.js';
10
10
  import '../../assertEnvVite.js';
@@ -40,5 +40,5 @@ type PlusFileValue = PlusFileCommon & {
40
40
  isExtensionConfig?: undefined;
41
41
  };
42
42
  type PlusFilesByLocationId = Record<LocationId, PlusFile[]>;
43
- declare function getPlusFiles(userRootDir: string, esbuildCache: EsbuildCache): Promise<PlusFilesByLocationId>;
43
+ declare function getPlusFiles(userRootDir: string, vikeTranspileCache: VikeTranspileCache): Promise<PlusFilesByLocationId>;
44
44
  declare function getPlusFileFromConfigFile(configFile: ConfigFile, isExtensionConfig: boolean, locationId: LocationId, userRootDir: string): PlusFileConfig;
@@ -10,7 +10,7 @@ import { resolvePointerImport } from './resolvePointerImport.js';
10
10
  import { getFilePathResolved } from '../getFilePath.js';
11
11
  import { assertExtensionsConventions, assertExtensionsRequire } from './assertExtensions.js';
12
12
  import '../../assertEnvVite.js';
13
- async function getPlusFiles(userRootDir, esbuildCache) {
13
+ async function getPlusFiles(userRootDir, vikeTranspileCache) {
14
14
  const plusFilePaths = (await crawlPlusFiles(userRootDir)).map(({ filePathAbsoluteUserRootDir }) => getFilePathResolved({ filePathAbsoluteUserRootDir, userRootDir }));
15
15
  const plusFilesByLocationId = {};
16
16
  await Promise.all(plusFilePaths.map(async (filePath) => {
@@ -18,7 +18,7 @@ async function getPlusFiles(userRootDir, esbuildCache) {
18
18
  // +config.js files
19
19
  const { filePathAbsoluteUserRootDir } = filePath;
20
20
  assert(filePathAbsoluteUserRootDir);
21
- const { configFile, extendsConfigs } = await loadConfigFile(filePath, userRootDir, [], false, esbuildCache);
21
+ const { configFile, extendsConfigs } = await loadConfigFile(filePath, userRootDir, [], false, vikeTranspileCache);
22
22
  assert(filePath.filePathAbsoluteUserRootDir);
23
23
  const locationId = getLocationId(filePathAbsoluteUserRootDir);
24
24
  const plusFile = getPlusFileFromConfigFile(configFile, false, locationId, userRootDir);
@@ -65,7 +65,7 @@ async function getPlusFiles(userRootDir, esbuildCache) {
65
65
  // We don't have access to the custom config definitions defined by the user yet.
66
66
  // - If `configDef` is `undefined` => we load the file +{configName}.js later.
67
67
  // - We already need to load +meta.js here (to get the custom config definitions defined by the user)
68
- await loadValueFile(plusFile, metaBuiltIn, userRootDir, esbuildCache);
68
+ await loadValueFile(plusFile, metaBuiltIn, userRootDir, vikeTranspileCache);
69
69
  }
70
70
  }));
71
71
  // Make lists element order deterministic
@@ -4,7 +4,7 @@ export { loadConfigFile };
4
4
  export type { ConfigFile };
5
5
  export type { PointerImportLoaded };
6
6
  import type { FilePathResolved } from '../../../../types/FilePath.js';
7
- import { type EsbuildCache } from './transpileAndExecuteFile.js';
7
+ import { type VikeTranspileCache } from './transpileAndExecuteFile.js';
8
8
  import type { PlusFileValue } from './getPlusFiles.js';
9
9
  import { PointerImport } from './resolvePointerImport.js';
10
10
  import type { ConfigDefinitionsInternal } from './metaBuiltIn.js';
@@ -14,15 +14,15 @@ type ConfigFile = {
14
14
  filePath: FilePathResolved;
15
15
  extendsFilePaths: string[];
16
16
  };
17
- declare function loadPointerImport(pointerImport: PointerImportLoaded, userRootDir: string, configName: string, configDefinitions: ConfigDefinitionsInternal, esbuildCache: EsbuildCache): Promise<unknown>;
17
+ declare function loadPointerImport(pointerImport: PointerImportLoaded, userRootDir: string, configName: string, configDefinitions: ConfigDefinitionsInternal, vikeTranspileCache: VikeTranspileCache): Promise<unknown>;
18
18
  type PointerImportLoaded = PointerImport & ({
19
19
  fileExportValueLoaded: true;
20
20
  fileExportValue: unknown;
21
21
  } | {
22
22
  fileExportValueLoaded: false;
23
23
  });
24
- declare function loadValueFile(interfaceValueFile: PlusFileValue, configDefinitions: ConfigDefinitionsInternal, userRootDir: string, esbuildCache: EsbuildCache): Promise<void>;
25
- declare function loadConfigFile(configFilePath: FilePathResolved, userRootDir: string, visited: string[], isExtensionConfig: boolean, esbuildCache: EsbuildCache): Promise<{
24
+ declare function loadValueFile(interfaceValueFile: PlusFileValue, configDefinitions: ConfigDefinitionsInternal, userRootDir: string, vikeTranspileCache: VikeTranspileCache): Promise<void>;
25
+ declare function loadConfigFile(configFilePath: FilePathResolved, userRootDir: string, visited: string[], isExtensionConfig: boolean, vikeTranspileCache: VikeTranspileCache): Promise<{
26
26
  configFile: ConfigFile;
27
27
  extendsConfigs: ConfigFile[];
28
28
  }>;
@@ -17,7 +17,7 @@ import { getConfigDefinedAt } from '../../../../shared-server-client/page-config
17
17
  import '../../assertEnvVite.js';
18
18
  assertIsNotProductionRuntime();
19
19
  // Load pointer import
20
- async function loadPointerImport(pointerImport, userRootDir, configName, configDefinitions, esbuildCache) {
20
+ async function loadPointerImport(pointerImport, userRootDir, configName, configDefinitions, vikeTranspileCache) {
21
21
  // The value of `extends` was already loaded and already used: we don't need the value of `extends` anymore
22
22
  if (configName === 'extends')
23
23
  return;
@@ -27,14 +27,14 @@ async function loadPointerImport(pointerImport, userRootDir, configName, configD
27
27
  return;
28
28
  const configDefinedAt = getConfigDefinedAt('Config', configName, pointerImport.fileExportPath);
29
29
  assertUsage(pointerImport.fileExportPath.filePathAbsoluteFilesystem, `${configDefinedAt} cannot be defined over an aliased import`);
30
- const { fileExports } = await transpileAndExecuteFile(pointerImport.fileExportPath, userRootDir, false, esbuildCache);
30
+ const { fileExports } = await transpileAndExecuteFile(pointerImport.fileExportPath, userRootDir, false, vikeTranspileCache);
31
31
  const fileExportValue = fileExports[pointerImport.fileExportPath.fileExportName];
32
32
  pointerImport.fileExportValueLoaded = true;
33
33
  assert(pointerImport.fileExportValueLoaded);
34
34
  pointerImport.fileExportValue = fileExportValue;
35
35
  }
36
36
  // Load +{configName}.js
37
- async function loadValueFile(interfaceValueFile, configDefinitions, userRootDir, esbuildCache) {
37
+ async function loadValueFile(interfaceValueFile, configDefinitions, userRootDir, vikeTranspileCache) {
38
38
  const { configName } = interfaceValueFile;
39
39
  const configDef = getConfigDefinitionOptional(configDefinitions, configName);
40
40
  // Only load value files with `env.config===true`
@@ -43,7 +43,7 @@ async function loadValueFile(interfaceValueFile, configDefinitions, userRootDir,
43
43
  interfaceValueFile.isNotLoaded = false;
44
44
  assert(!interfaceValueFile.isNotLoaded);
45
45
  interfaceValueFile.fileExportsByConfigName = {};
46
- const { fileExports } = await transpileAndExecuteFile(interfaceValueFile.filePath, userRootDir, false, esbuildCache);
46
+ const { fileExports } = await transpileAndExecuteFile(interfaceValueFile.filePath, userRootDir, false, vikeTranspileCache);
47
47
  const { filePathToShowToUser } = interfaceValueFile.filePath;
48
48
  assertPlusFileExport(fileExports, filePathToShowToUser, configName);
49
49
  Object.entries(fileExports).forEach(([exportName, configValue]) => {
@@ -52,11 +52,11 @@ async function loadValueFile(interfaceValueFile, configDefinitions, userRootDir,
52
52
  });
53
53
  }
54
54
  // Load +config.js, including all its extends pointer imports
55
- async function loadConfigFile(configFilePath, userRootDir, visited, isExtensionConfig, esbuildCache) {
55
+ async function loadConfigFile(configFilePath, userRootDir, visited, isExtensionConfig, vikeTranspileCache) {
56
56
  const { filePathAbsoluteFilesystem } = configFilePath;
57
57
  assertNoInfiniteLoop(visited, filePathAbsoluteFilesystem);
58
- const { fileExports } = await transpileAndExecuteFile(configFilePath, userRootDir, isExtensionConfig, esbuildCache);
59
- const { extendsConfigs, extendsFilePaths } = await loadExtendsConfigs(fileExports, configFilePath, userRootDir, [...visited, filePathAbsoluteFilesystem], esbuildCache);
58
+ const { fileExports } = await transpileAndExecuteFile(configFilePath, userRootDir, isExtensionConfig, vikeTranspileCache);
59
+ const { extendsConfigs, extendsFilePaths } = await loadExtendsConfigs(fileExports, configFilePath, userRootDir, [...visited, filePathAbsoluteFilesystem], vikeTranspileCache);
60
60
  const configFile = {
61
61
  fileExports,
62
62
  filePath: configFilePath,
@@ -72,7 +72,7 @@ function assertNoInfiniteLoop(visited, filePathAbsoluteFilesystem) {
72
72
  assert(loop[0] === filePathAbsoluteFilesystem);
73
73
  assertUsage(idx === -1, `Infinite extends loop ${[...loop, filePathAbsoluteFilesystem].join('>')}`);
74
74
  }
75
- async function loadExtendsConfigs(configFileExports, configFilePath, userRootDir, visited, esbuildCache) {
75
+ async function loadExtendsConfigs(configFileExports, configFilePath, userRootDir, visited, vikeTranspileCache) {
76
76
  const { extendsPointerImportData, extendsConfigs } = getExtendsPointerImportData(configFileExports, configFilePath);
77
77
  const extendsConfigFiles = [];
78
78
  extendsPointerImportData.map((pointerImportData) => {
@@ -80,7 +80,7 @@ async function loadExtendsConfigs(configFileExports, configFilePath, userRootDir
80
80
  assert(filePath.filePathAbsoluteFilesystem);
81
81
  extendsConfigFiles.push(filePath);
82
82
  });
83
- const results = await Promise.all(extendsConfigFiles.map(async (configFilePath) => await loadConfigFile(configFilePath, userRootDir, visited, true, esbuildCache)));
83
+ const results = await Promise.all(extendsConfigFiles.map(async (configFilePath) => await loadConfigFile(configFilePath, userRootDir, visited, true, vikeTranspileCache)));
84
84
  results.forEach((result) => {
85
85
  extendsConfigs.push(result.configFile);
86
86
  extendsConfigs.push(...result.extendsConfigs);
@@ -1,9 +1,11 @@
1
1
  export { transformPointerImports };
2
2
  export { parsePointerImportData };
3
3
  export { assertPointerImportPath };
4
+ export { pointerImportAttributeSuffix };
4
5
  export type { PointerImportData };
5
6
  import '../../assertEnvVite.js';
6
- declare function transformPointerImports(code: string, filePathToShowToUser2: string, pointerImports: Record<string, boolean> | 'all', skipWarnings?: true): string | null;
7
+ declare const pointerImportAttributeSuffix = "?vike:pointer";
8
+ declare function transformPointerImports(code: string, pointerImports: Record<string, boolean> | 'all'): string | null;
7
9
  /**
8
10
  * Data Structure for [pointer imports](https://vike.dev/config#pointer-imports):
9
11
  * `import { someExport as someImport } from './some-file'`
@@ -1,6 +1,7 @@
1
1
  export { transformPointerImports };
2
2
  export { parsePointerImportData };
3
3
  export { assertPointerImportPath };
4
+ export { pointerImportAttributeSuffix };
4
5
  // Playground: https://github.com/brillout/acorn-playground
5
6
  // Notes about `with { type: 'pointer' }`
6
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
@@ -10,24 +11,23 @@ export { assertPointerImportPath };
10
11
  // - Isn't stage 4 yet: https://github.com/tc39/proposal-import-attributes
11
12
  // - Using a import path suffix such as `import { Layout } from './Layout?real` breaks TypeScript, and TypeScript isn't working on supporting query params: https://github.com/microsoft/TypeScript/issues/10988#issuecomment-867135453
12
13
  // - Node.js >=21 supports import attributes: https://nodejs.org/api/esm.html#import-attributes
13
- // - Esbuid supports
14
- // - Blocker: https://github.com/evanw/esbuild/issues/3646
15
- // - Ugly hack to make it work: https://github.com/brillout/esbuild-playground/tree/experiment/import-attribute
16
- // - Discussion with esbuild maintainer: https://github.com/evanw/esbuild/issues/3384
14
+ // - Rolldown doesn't pass import attributes to plugins => we parse import attributes ourselves and mark the import path with pointerImportAttributeSuffix, see transpileFile()
17
15
  // - Using a magic comment `// @vike-real-import` is probably a bad idea:
18
- // - Esbuild removes comments: https://github.com/evanw/esbuild/issues/1439#issuecomment-877656182
16
+ // - Bundlers usually remove comments, e.g. esbuild: https://github.com/evanw/esbuild/issues/1439#issuecomment-877656182
19
17
  // - Using source maps to track these magic comments is brittle (source maps can easily break)
20
18
  import { parseSync } from '@babel/core';
21
- import { assert, assertUsage, assertWarning } from '../../../../utils/assert.js';
19
+ import { assert, assertUsage } from '../../../../utils/assert.js';
22
20
  import { isFilePathAbsolute } from '../../../../utils/isFilePathAbsoluteFilesystem.js';
23
21
  import { isImportPath } from '../../../../utils/isImportPath.js';
24
- import { styleFileRE } from '../../../../utils/styleFileRE.js';
25
22
  import pc from '@brillout/picocolors';
26
23
  import { parseImportString, isImportString, serializeImportString } from '../importString.js';
27
24
  import '../../assertEnvVite.js';
28
- function transformPointerImports(code, filePathToShowToUser2, pointerImports,
29
- // For ./transformPointerImports.spec.ts
30
- skipWarnings) {
25
+ // Appended to the import path of imports with the import attribute `with { type: 'vike:pointer' }`, see transpileFile()
26
+ const pointerImportAttributeSuffix = '?vike:pointer';
27
+ function removePointerImportAttributeSuffix(str) {
28
+ return str.split(pointerImportAttributeSuffix).join('');
29
+ }
30
+ function transformPointerImports(code, pointerImports) {
31
31
  const spliceOperations = [];
32
32
  // Collect all const declarations to prepend at the top, so that they are
33
33
  // available before any code runs (import declarations are hoisted but const
@@ -53,34 +53,8 @@ skipWarnings) {
53
53
  }
54
54
  const { start, end } = node;
55
55
  const importStatementCode = code.slice(start, end);
56
- /* Pointer import without importing any value => doesn't make sense and doesn't have any effect.
57
- ```js
58
- // Useless
59
- import './some.css'
60
- // Useless
61
- import './Layout.jsx'
62
- ``` */
63
- if (node.specifiers.length === 0) {
64
- const isWarning = !styleFileRE.test(importPath);
65
- let quote = indent(importStatementCode);
66
- if (isWarning) {
67
- quote = pc.cyan(quote);
68
- }
69
- else {
70
- quote = pc.bold(pc.red(quote));
71
- }
72
- const errMsg = [
73
- `The following import in ${filePathToShowToUser2} has no effect:`,
74
- quote,
75
- 'See https://vike.dev/config#pointer-imports',
76
- ].join('\n');
77
- if (!skipWarnings) {
78
- if (!isWarning) {
79
- assertUsage(false, errMsg);
80
- }
81
- assertWarning(false, errMsg, { onlyOnce: true });
82
- }
83
- }
56
+ // Pointer import without importing any value, e.g. `import './some.css'` => we remove it (it doesn't have any effect).
57
+ // - Rolldown transforms unused imports `import { unused } from './some.js'` into `import './some.js'`
84
58
  let constDeclaration = '';
85
59
  node.specifiers.forEach((specifier) => {
86
60
  assert(specifier.type === 'ImportSpecifier' ||
@@ -99,7 +73,11 @@ skipWarnings) {
99
73
  }
100
74
  return importLocalName;
101
75
  })();
102
- const importString = serializePointerImportData({ importPath, exportName, importStringWasGenerated: true });
76
+ const importString = serializePointerImportData({
77
+ importPath: removePointerImportAttributeSuffix(importPath),
78
+ exportName,
79
+ importStringWasGenerated: true,
80
+ });
103
81
  constDeclaration += `const ${importLocalName} = '${importString}';`;
104
82
  });
105
83
  if (constDeclaration)
@@ -162,7 +140,7 @@ function parsePointerImportData(importString) {
162
140
  // `importPath` is one of the following:
163
141
  // - A relative import path
164
142
  // - An npm package import
165
- // - A filesystem absolute path, see transpileWithEsbuild()
143
+ // - A filesystem absolute path, see transpileFile()
166
144
  function assertPointerImportPath(importPath) {
167
145
  return isImportPath(importPath) || isFilePathAbsolute(importPath);
168
146
  }
@@ -189,9 +167,3 @@ function spliceMany(str, operations) {
189
167
  strMod += str.slice(endPrev, str.length);
190
168
  return strMod;
191
169
  }
192
- function indent(str) {
193
- return str
194
- .split('\n')
195
- .map((s) => ` ${s}`)
196
- .join('\n');
197
- }
@@ -2,18 +2,18 @@ export { transpileAndExecuteFile };
2
2
  export { getConfigBuildErrorFormatted };
3
3
  export { getConfigExecutionErrorIntroMsg };
4
4
  export { isTemporaryBuildFile };
5
- export type { EsbuildCache };
5
+ export type { VikeTranspileCache };
6
6
  import type { FilePathResolved } from '../../../../types/FilePath.js';
7
7
  import '../../assertEnvVite.js';
8
8
  type FileExports = {
9
9
  fileExports: Record<string, unknown>;
10
10
  };
11
- type EsbuildCache = {
11
+ type VikeTranspileCache = {
12
12
  transpileCache: Record<string, // filePathAbsoluteFilesystem
13
13
  Promise<FileExports>>;
14
14
  vikeConfigDependencies: Set<string>;
15
15
  };
16
- declare function transpileAndExecuteFile(filePath: FilePathResolved, userRootDir: string, isExtensionConfig: boolean, esbuildCache: EsbuildCache): Promise<FileExports>;
16
+ declare function transpileAndExecuteFile(filePath: FilePathResolved, userRootDir: string, isExtensionConfig: boolean, vikeTranspileCache: VikeTranspileCache): Promise<FileExports>;
17
17
  declare function getConfigBuildErrorFormatted(err: unknown): `Failed to transpile ${string} because:
18
18
  ${string}` | null;
19
19
  declare function getConfigExecutionErrorIntroMsg(err: unknown): `Failed to execute ${string} because:` | null;
@@ -2,13 +2,15 @@ export { transpileAndExecuteFile };
2
2
  export { getConfigBuildErrorFormatted };
3
3
  export { getConfigExecutionErrorIntroMsg };
4
4
  export { isTemporaryBuildFile };
5
- import { build, formatMessages, version, } from 'esbuild';
5
+ import { rolldown, VERSION, } from 'rolldown';
6
+ import { parseAst } from 'rolldown/parseAst';
6
7
  import fs from 'node:fs';
7
8
  import path from 'node:path';
8
9
  import crypto from 'node:crypto';
9
10
  import pc from '@brillout/picocolors';
10
11
  import { import_ } from '@brillout/import';
11
12
  import { assert, assertWarning } from '../../../../utils/assert.js';
13
+ import { nodeVersionMin } from '../../../../utils/assertNodeVersion.js';
12
14
  import { assertIsNotProductionRuntime } from '../../../../utils/assertSetup.js';
13
15
  import { createDebug } from '../../../../utils/debug.js';
14
16
  import { genPromise } from '../../../../utils/genPromise.js';
@@ -19,27 +21,27 @@ import { isPlainJavaScriptFile, isPlainScriptFile } from '../../../../utils/isSc
19
21
  import { isVitest } from '../../../../utils/isVitest.js';
20
22
  import { assertImportIsNpmPackage, isImportNpmPackageOrPathAlias } from '../../../../utils/parseNpmPackage.js';
21
23
  import { assertPosixPath, toPosixPath } from '../../../../utils/path.js';
22
- import { requireResolveOptionalDir } from '../../../../utils/requireResolve.js';
23
- import { transformPointerImports } from './pointerImports.js';
24
+ import { pointerImportAttributeSuffix, transformPointerImports } from './pointerImports.js';
24
25
  import sourceMapSupport from 'source-map-support';
25
26
  import { getFilePathAbsoluteUserRootDir } from '../getFilePath.js';
27
+ import { getMagicString } from '../getMagicString.js';
26
28
  import '../../assertEnvVite.js';
27
29
  assertIsNotProductionRuntime();
28
30
  installSourceMapSupport();
29
31
  const debug = createDebug('vike:pointer-imports');
30
- const debugEsbuildResolve = createDebug('vike:esbuild-resolve');
32
+ const debugResolve = createDebug('vike:transpile-resolve');
31
33
  const debugConfig = createDebug('vike:config');
32
- if (debugEsbuildResolve.isActivated)
33
- debugEsbuildResolve('esbuild version', version);
34
- async function transpileAndExecuteFile(filePath, userRootDir, isExtensionConfig, esbuildCache) {
34
+ if (debugResolve.isActivated)
35
+ debugResolve('rolldown version', VERSION);
36
+ async function transpileAndExecuteFile(filePath, userRootDir, isExtensionConfig, vikeTranspileCache) {
35
37
  const { filePathAbsoluteFilesystem, filePathToShowToUserResolved } = filePath;
36
38
  assert(filePathAbsoluteFilesystem);
37
39
  const fileExtension = getFileExtension(filePathAbsoluteFilesystem);
38
- if (esbuildCache.transpileCache[filePathAbsoluteFilesystem]) {
39
- return await esbuildCache.transpileCache[filePathAbsoluteFilesystem];
40
+ if (vikeTranspileCache.transpileCache[filePathAbsoluteFilesystem]) {
41
+ return await vikeTranspileCache.transpileCache[filePathAbsoluteFilesystem];
40
42
  }
41
43
  const { promise, resolve } = genPromise();
42
- esbuildCache.transpileCache[filePathAbsoluteFilesystem] = promise;
44
+ vikeTranspileCache.transpileCache[filePathAbsoluteFilesystem] = promise;
43
45
  /* We tolerate .tsx so that a file can be both a runtime and config file (`meta.env.config === true && meta.env.server === true`), e.g. https://github.com/brillout/docpress/issues/86
44
46
  assertUsage(
45
47
  isPlainScriptFile(filePathAbsoluteFilesystem),
@@ -52,15 +54,15 @@ async function transpileAndExecuteFile(filePath, userRootDir, isExtensionConfig,
52
54
  }
53
55
  let fileExports;
54
56
  if (isExtensionConfig && !isHeader && fileExtension.endsWith('js')) {
55
- // This doesn't track dependencies => we should never use this for user land configs
56
57
  if (debugConfig.isActivated) {
57
- debugConfig(filePathToShowToUserResolved, 'executed directly (no esbuild transpilation)');
58
+ debugConfig(filePathToShowToUserResolved, 'executed directly (no transpilation)');
58
59
  }
60
+ // This doesn't track dependencies => we should never use this for user land configs
59
61
  fileExports = await executeFile(filePathAbsoluteFilesystem, filePath);
60
62
  }
61
63
  else {
62
64
  const transformImports = isHeader ? 'all' : true;
63
- const code = await transpileFile(filePath, transformImports, userRootDir, esbuildCache);
65
+ const code = await transpileFileAndTransformPointerImports(filePath, transformImports, userRootDir, vikeTranspileCache);
64
66
  if (debugConfig.isActivated) {
65
67
  debugConfig(filePathToShowToUserResolved, code);
66
68
  }
@@ -69,19 +71,19 @@ async function transpileAndExecuteFile(filePath, userRootDir, isExtensionConfig,
69
71
  resolve({ fileExports });
70
72
  return { fileExports };
71
73
  }
72
- async function transpileFile(filePath, transformImports, userRootDir, esbuildCache) {
74
+ async function transpileFileAndTransformPointerImports(filePath, transformImports, userRootDir, vikeTranspileCache) {
73
75
  const { filePathAbsoluteFilesystem, filePathToShowToUserResolved } = filePath;
74
76
  assert(filePathAbsoluteFilesystem);
75
77
  assertPosixPath(filePathAbsoluteFilesystem);
76
- esbuildCache.vikeConfigDependencies.add(filePathAbsoluteFilesystem);
78
+ vikeTranspileCache.vikeConfigDependencies.add(filePathAbsoluteFilesystem);
77
79
  if (debug.isActivated)
78
80
  debug('transpile', filePathToShowToUserResolved);
79
- let { code, pointerImports } = await transpileWithEsbuild(filePath, userRootDir, transformImports, esbuildCache);
81
+ let { code, pointerImports } = await transpileFile(filePath, userRootDir, transformImports, vikeTranspileCache);
80
82
  if (debug.isActivated)
81
- debug(`code, post esbuild (${filePathToShowToUserResolved})`, code);
83
+ debug(`code, post transpilation (${filePathToShowToUserResolved})`, code);
82
84
  let isImportTransformed = false;
83
85
  if (transformImports) {
84
- const codeMod = transformPointerImports(code, filePathToShowToUserResolved, pointerImports);
86
+ const codeMod = transformPointerImports(code, pointerImports);
85
87
  if (codeMod) {
86
88
  code = codeMod;
87
89
  isImportTransformed = true;
@@ -95,200 +97,316 @@ async function transpileFile(filePath, transformImports, userRootDir, esbuildCac
95
97
  }
96
98
  return code;
97
99
  }
98
- async function transpileWithEsbuild(filePath, userRootDir, transformImports, esbuildCache) {
100
+ async function transpileFile(filePath, userRootDir, transformImports, vikeTranspileCache) {
99
101
  const entryFilePath = filePath.filePathAbsoluteFilesystem;
100
102
  const entryFileDir = path.posix.dirname(entryFilePath);
101
- const options = {
102
- platform: 'node',
103
- entryPoints: [entryFilePath],
104
- sourcemap: 'inline',
105
- write: false,
106
- target: ['node14.18', 'node16'],
107
- outfile: path.posix.join(
108
- // Needed for correct inline source map
109
- entryFileDir,
110
- // `write: false` => no file is actually emitted
111
- 'NEVER_EMITTED.js'),
112
- logLevel: 'silent',
113
- format: 'esm',
114
- absWorkingDir: userRootDir,
115
- // Disable tree-shaking to avoid dead-code elimination, so that unused imports aren't removed.
116
- // Esbuild still sometimes removes unused imports because of TypeScript: https://github.com/evanw/esbuild/issues/3034
117
- treeShaking: false,
118
- minify: false,
119
- metafile: true,
120
- bundle: true,
121
- };
122
103
  const pointerImports = {};
123
- options.plugins = [
104
+ const unresolvedImports = [];
105
+ const plugins = [
124
106
  // Determine whether an import should be:
125
107
  // - A pointer import
126
108
  // - Externalized
127
109
  {
128
- name: 'vike-esbuild',
129
- setup(build) {
130
- // https://github.com/brillout/esbuild-playground
131
- build.onResolve({ filter: /.*/ }, async (args) => {
132
- if (args.kind !== 'import-statement')
133
- return;
134
- // Avoid infinite loop: https://github.com/evanw/esbuild/issues/3095#issuecomment-1546916366
135
- const useEsbuildResolver = 'useEsbuildResolver';
136
- if (args.pluginData?.[useEsbuildResolver])
137
- return;
138
- const { path, ...opts } = args;
139
- opts.pluginData = { [useEsbuildResolver]: true };
140
- let resolved = await build.resolve(path, opts);
141
- if (debugEsbuildResolve.isActivated)
142
- debugEsbuildResolve('args', args);
143
- if (debugEsbuildResolve.isActivated)
144
- debugEsbuildResolve('resolved', resolved);
145
- // Temporary workaround for https://github.com/evanw/esbuild/issues/3973
146
- // - Still required for esbuild@0.24.0 (November 2024).
147
- // - Let's try to remove this workaround again later.
148
- if (resolved.errors.length > 0) {
149
- const resolvedWithNode = requireResolveOptionalDir({
150
- importPath: path,
151
- importerDir: toPosixPath(args.resolveDir),
152
- userRootDir,
153
- });
154
- if (debugEsbuildResolve.isActivated)
155
- debugEsbuildResolve('resolvedWithNode', resolvedWithNode);
156
- if (resolvedWithNode)
157
- resolved = { path: resolvedWithNode };
158
- }
159
- if (resolved.errors && resolved.errors.length > 0) {
160
- /* We could do the following to let Node.js throw the error, but we don't because the error shown by esbuild is prettier: the Node.js error refers to the transpiled [build-f7i251e0iwnw]+config.ts.mjs whereas esbuild refers to the source +config.ts file.
161
- pointerImports[args.path] = false
162
- return { external: true }
163
- */
164
- // Let esbuild throw the error
165
- cleanEsbuildErrors(resolved.errors);
166
- return resolved;
167
- }
168
- assert(resolved.path);
169
- // Built-in modules e.g. node:fs
170
- if (resolved.path === args.path) {
171
- const isPointerImport = false;
172
- pointerImports[args.path] = isPointerImport;
173
- if (debug.isActivated)
174
- debug('onResolve() [built-in module]', { args, resolved });
175
- assert(resolved.external);
176
- return resolved;
177
- }
178
- const importPathResolved = toPosixPath(resolved.path);
179
- const importPathOriginal = args.path;
180
- // Esbuild resolves path aliases.
181
- // - Enabling us to use:
182
- // - assertImportIsNpmPackage()
183
- // - isImportNpmPackage(str, { cannotBePathAlias: true })
184
- assertFilePathAbsoluteFilesystem(importPathResolved);
185
- // Should we remove this? See comment below.
186
- const isVikeExtensionImport = (path.startsWith('vike-') && path.endsWith('/config')) || importPathResolved.endsWith('+config.js');
187
- const isPointerImport = transformImports === 'all' ||
188
- // .jsx, .vue, .svg, ... => obviously not config code => pointer import
189
- !isPlainScriptFile(importPathResolved) ||
190
- // Import of a Vike extension config => make it a pointer import because we want to show nice error messages (that can display whether a config has been set by the user or by a Vike extension).
191
- // - Should we stop doing this? (And instead let Node.js directly load Vike extensions.)
192
- // - In principle, we can use the setting 'name' value of Vike extensions.
193
- // - vike@0.4.162 started soft-requiring Vike extensions to set the name config.
194
- // - In practice, it seems like it requires some (non-trivial?) refactoring.
195
- isVikeExtensionImport ||
196
- args.with?.['type'] === 'vike:pointer';
197
- assertPosixPath(importPathResolved);
198
- // False positive if `importPathOriginal` is a path alias that a) looks like an npm package import and b) resolves outside of `userRootDir` => we then we wrongfully assume that `importPathOriginal` is an npm package import.
199
- // - For example: https://github.com/vikejs/vike/issues/2326
200
- const isMostLikelyNpmPkgImport = isImportNpmPackageOrPathAlias(importPathOriginal) &&
201
- (importPathResolved.includes('/node_modules/') ||
202
- // Linked npm package
203
- !importPathResolved.startsWith(userRootDir));
204
- const isExternal = isPointerImport ||
205
- // Performance: npm package imports can be externalized. (We could as well let esbuild transpile /node_modules/ code but it's useless as /node_modules/ code is already built. It would unnecessarily slow down transpilation.)
206
- (isMostLikelyNpmPkgImport && isPlainJavaScriptFile(importPathResolved));
207
- if (!isExternal) {
208
- // User-land config code (i.e. not runtime code) => let esbuild transpile it
209
- assert(!isPointerImport);
210
- if (debug.isActivated)
211
- debug('onResolve() [non-external]', { args, resolved, isPointerImport, isExternal });
212
- return resolved;
213
- }
214
- let importPathTranspiled;
215
- assertPosixPath(importPathOriginal);
216
- if (isImportPathRelative(importPathOriginal)) {
217
- importPathTranspiled = importPathResolved;
218
- }
219
- else {
220
- // `importPathOriginal` is either:
221
- // - Npm package import
222
- // - Path alias
223
- const filePathAbsoluteUserRootDir = getFilePathAbsoluteUserRootDir({
224
- filePathAbsoluteFilesystem: importPathResolved,
225
- userRootDir,
226
- });
227
- if (filePathAbsoluteUserRootDir && !isMostLikelyNpmPkgImport) {
228
- // `importPathOriginal` is most likely a path alias.
229
- // - We have to use esbuild's path alias resolution, because:
230
- // - Vike doesn't resolve path aliases at all.
231
- // - Node.js doesn't support `tsconfig.js#compilerOptions.paths`.
232
- // - Esbuild path alias resolution seems reliable, e.g. it supports `tsconfig.js#compilerOptions.paths`.
233
- importPathTranspiled = importPathResolved;
234
- }
235
- else {
236
- // `importPathOriginal` is most likely an npm package import.
237
- assertImportIsNpmPackage(importPathOriginal);
238
- // For improved error messages, let the resolution be handled by Vike or Node.js.
239
- importPathTranspiled = importPathOriginal;
240
- }
241
- }
242
- if (debug.isActivated)
243
- debug('onResolve() [external]', { args, resolved, importPathTranspiled, isPointerImport, isExternal });
110
+ name: 'vike:pointer-imports',
111
+ async resolveId(importPath, importer, options) {
112
+ if (options.isEntry || options.kind !== 'import-statement')
113
+ return;
114
+ assert(importer);
115
+ // Import with the import attribute `with { type: 'vike:pointer' }`, see transform() below
116
+ const isPointerImportAttribute = importPath.endsWith(pointerImportAttributeSuffix);
117
+ const importPathOriginal = isPointerImportAttribute
118
+ ? importPath.slice(0, -pointerImportAttributeSuffix.length)
119
+ : importPath;
120
+ const resolved = await resolveImport(this, importPathOriginal, importer, options);
121
+ if (!resolved) {
122
+ /* We could do the following to let Node.js throw the error, but we don't because the error shown by Rolldown is prettier: the Node.js error refers to the transpiled +config.ts.build-f7i251e0iwnw.mjs whereas Rolldown refers to the source +config.ts file.
123
+ pointerImports[importPathOriginal] = false
124
+ return { id: importPathOriginal, external: true }
125
+ */
126
+ // Let Rolldown throw the error (see onLog() below)
127
+ return null;
128
+ }
129
+ // Built-in modules e.g. node:fs
130
+ // - Rolldown externalizes only built-in modules and HTTP URLs (which Node.js can't import anyway): resolveImport() skips Vike's plugin and we don't use Rolldown's `external` option.
131
+ if (resolved.external) {
132
+ const importPathTranspiled = resolved.id;
133
+ const isPointerImport = false;
244
134
  pointerImports[importPathTranspiled] = isPointerImport;
245
- return { external: true, path: importPathTranspiled };
246
- });
135
+ if (debug.isActivated)
136
+ debug('resolveId() [built-in module]', { importPathOriginal, importer, resolved });
137
+ return resolved;
138
+ }
139
+ // Rolldown's internal modules, e.g. helpers injected by Oxc's transformer
140
+ if (isVirtualModule(resolved.id))
141
+ return resolved;
142
+ const importPathResolved = toPosixPath(resolved.id);
143
+ const classified = classifyImport(importPathOriginal, importPathResolved, isPointerImportAttribute, transformImports, userRootDir);
144
+ if (!classified.isExternal) {
145
+ if (debug.isActivated)
146
+ debug('resolveId() [non-external]', { importPathOriginal, importer, resolved });
147
+ return resolved;
148
+ }
149
+ let { isPointerImport, importPathTranspiled } = classified;
150
+ // Keep the mark, so that the ID is different than the ID of the same file loaded and executed at config-time, see transform() below.
151
+ // - The mark is removed by transformPointerImports()
152
+ if (isPointerImportAttribute)
153
+ importPathTranspiled += pointerImportAttributeSuffix;
154
+ if (debug.isActivated)
155
+ debug('resolveId() [external]', {
156
+ importPathOriginal,
157
+ importer,
158
+ resolved,
159
+ importPathTranspiled,
160
+ isPointerImport,
161
+ });
162
+ pointerImports[importPathTranspiled] = isPointerImport;
163
+ return { id: importPathTranspiled, external: true };
164
+ },
165
+ async transform(code, id) {
166
+ // Rolldown's internal modules, e.g. \0rolldown/runtime.js
167
+ if (isVirtualModule(id))
168
+ return;
169
+ // Rolldown doesn't pass import attributes `with { type: 'vike:pointer' }` to resolveId() => we parse the imports ourselves.
170
+ const importsWithPointerAttribute = parseImportsWithPointerAttribute(code, id);
171
+ // Rolldown calls resolveId() only once per import path of a module, and a file cannot be both external and bundled => we mark the import path of imports that have the import attribute, so that they're resolved separately. For example:
172
+ // ```js
173
+ // // Pointer import
174
+ // import { Page } from './Page.ts' with { type: 'vike:pointer' }
175
+ // // Loaded and executed at config-time
176
+ // import { title } from './Page.ts'
177
+ // ```
178
+ const { magicString, getMagicStringResult } = getMagicString(code, id);
179
+ for (const { importPath, importPathSpan } of importsWithPointerAttribute) {
180
+ // We don't mark import paths that cannot be resolved: we let Rolldown throw the resolve error, which then shows the original code
181
+ if (!(await resolveImport(this, importPath, id)))
182
+ continue;
183
+ // Mark the import path, right before its closing quote
184
+ magicString.appendLeft(importPathSpan.end - 1, pointerImportAttributeSuffix);
185
+ }
186
+ return {
187
+ ...getMagicStringResult(),
188
+ // Disable tree-shaking of user-land code: we want to execute user-land code as-is. (Rolldown's internal modules, such as its runtime helpers, are tree-shaken.)
189
+ moduleSideEffects: 'no-treeshake',
190
+ };
247
191
  },
248
192
  },
249
193
  // Track dependencies
194
+ // - We collect the dependencies with the load() hook (instead of using Rolldown's output), so that we also collect them if the build fails
250
195
  {
251
196
  name: 'vike:dependency-tracker',
252
- setup(b) {
253
- b.onLoad({ filter: /./ }, (args) => {
254
- // We collect the dependency `args.path` in case the build fails (upon build error => error is thrown => no metafile)
255
- let { path } = args;
256
- path = toPosixPath(path);
257
- esbuildCache.vikeConfigDependencies.add(path);
258
- return undefined;
259
- });
260
- /* To exhaustively collect all dependencies upon build failure, we would also need to use onResolve().
261
- * - Because onLoad() isn't call if the config dependency can't be resolved.
197
+ load(id) {
198
+ if (isVirtualModule(id))
199
+ return;
200
+ vikeTranspileCache.vikeConfigDependencies.add(toPosixPath(id));
201
+ /* To exhaustively collect all dependencies upon build failure, we would also need to use resolveId().
202
+ * - Because load() isn't call if the config dependency can't be resolved.
262
203
  * - For example, the following breaks auto-reload (the config is stuck in its error state and the user needs to touch the importer for the config to reload):
263
204
  * ```bash
264
205
  * mv ./some-config-dependency.js /tmp/ && mv /tmp/some-config-dependency.js .
265
206
  * ```
266
207
  * - But implementing a fix is complex and isn't worth it.
267
- b.onResolve(...)
208
+ resolveId(...)
268
209
  */
269
210
  },
270
211
  },
271
212
  ];
272
- let result;
213
+ let bundle;
214
+ let output;
273
215
  try {
274
- result = await build(options);
216
+ bundle = await rolldown({
217
+ input: entryFilePath,
218
+ cwd: userRootDir,
219
+ platform: 'node',
220
+ // Resolve path aliases defined in tsconfig.json
221
+ tsconfig: true,
222
+ transform: {
223
+ target: `node${nodeVersionMin}`,
224
+ },
225
+ // Keep the absolute path of pointer imports, so that the import paths of Rolldown's output match `pointerImports`
226
+ makeAbsoluteExternalsRelative: false,
227
+ plugins,
228
+ onLog(level, log, defaultHandler) {
229
+ if (debugResolve.isActivated)
230
+ debugResolve('log', { level, log });
231
+ // Rolldown treats unresolved imports that aren't relative (e.g. npm packages and path aliases) as external, but we want the build to fail instead (as with esbuild):
232
+ // - Rolldown's error shows the import in the source file, whereas the error thrown by Node.js would refer to the temporary build file, see resolveId() above.
233
+ // - Such an external import wouldn't be classified by resolveId() (it wouldn't be in `pointerImports`).
234
+ if (log.code === 'UNRESOLVED_IMPORT') {
235
+ unresolvedImports.push(log);
236
+ return;
237
+ }
238
+ defaultHandler(level, log);
239
+ },
240
+ });
241
+ output = await bundle.generate({
242
+ format: 'esm',
243
+ sourcemap: 'inline',
244
+ // Needed for correct inline source map (bundle.generate() doesn't emit any file)
245
+ dir: entryFileDir,
246
+ // Avoid dead-code elimination (Rolldown's default is `minify: 'dce-only'`)
247
+ minify: false,
248
+ // Single chunk
249
+ codeSplitting: false,
250
+ });
275
251
  }
276
252
  catch (err) {
277
- await formatBuildErr(err, filePath);
253
+ // Error thrown by one of our plugins, e.g. a failing assert()
254
+ const errVikePlugin = getErrVikePlugin(err);
255
+ if (errVikePlugin)
256
+ throw errVikePlugin;
257
+ formatBuildErr(err, filePath);
278
258
  throw err;
279
259
  }
280
- // Track dependencies
281
- assert(result.metafile);
282
- Object.keys(result.metafile.inputs).forEach((filePathRelative) => {
283
- filePathRelative = toPosixPath(filePathRelative);
284
- assertPosixPath(userRootDir);
285
- const filePathAbsoluteFilesystem = path.posix.join(userRootDir, filePathRelative);
286
- esbuildCache.vikeConfigDependencies.add(filePathAbsoluteFilesystem);
287
- });
288
- const code = result.outputFiles[0].text;
260
+ finally {
261
+ await bundle?.close();
262
+ }
263
+ if (unresolvedImports.length > 0)
264
+ throw getErrUnresolvedImports(unresolvedImports, filePath);
265
+ const chunk = output.output[0];
266
+ assert(output.output.length === 1);
267
+ assert(chunk.type === 'chunk');
268
+ const { code } = chunk;
289
269
  assert(typeof code === 'string');
290
270
  return { code, pointerImports };
291
271
  }
272
+ // Resolve with Rolldown
273
+ async function resolveImport(pluginContext, importPath, importer, options = { kind: 'import-statement' }) {
274
+ const resolved = await pluginContext.resolve(importPath, importer, {
275
+ ...options,
276
+ skipSelf: true,
277
+ });
278
+ if (debugResolve.isActivated)
279
+ debugResolve('args', { importPath, importer, options });
280
+ if (debugResolve.isActivated)
281
+ debugResolve('resolved', resolved);
282
+ return resolved;
283
+ }
284
+ // Determine whether an import should be:
285
+ // - A pointer import
286
+ // - Externalized
287
+ function classifyImport(importPathOriginal, importPathResolved, isPointerImportAttribute, transformImports, userRootDir) {
288
+ // Rolldown resolves path aliases.
289
+ // - Enabling us to use:
290
+ // - assertImportIsNpmPackage()
291
+ // - isImportNpmPackage(str, { cannotBePathAlias: true })
292
+ assertFilePathAbsoluteFilesystem(importPathResolved);
293
+ // Should we remove this? See comment below.
294
+ const isVikeExtensionImport = (importPathOriginal.startsWith('vike-') && importPathOriginal.endsWith('/config')) ||
295
+ importPathResolved.endsWith('+config.js');
296
+ const isPointerImport = transformImports === 'all' ||
297
+ // .jsx, .vue, .svg, ... => obviously not config code => pointer import
298
+ !isPlainScriptFile(importPathResolved) ||
299
+ // Import of a Vike extension config => make it a pointer import because we want to show nice error messages (that can display whether a config has been set by the user or by a Vike extension).
300
+ // - Should we stop doing this? (And instead let Node.js directly load Vike extensions.)
301
+ // - In principle, we can use the setting 'name' value of Vike extensions.
302
+ // - vike@0.4.162 started soft-requiring Vike extensions to set the name config.
303
+ // - In practice, it seems like it requires some (non-trivial?) refactoring.
304
+ isVikeExtensionImport ||
305
+ isPointerImportAttribute;
306
+ assertPosixPath(importPathResolved);
307
+ // False positive if `importPathOriginal` is a path alias that a) looks like an npm package import and b) resolves outside of `userRootDir` => we then we wrongfully assume that `importPathOriginal` is an npm package import.
308
+ // - For example: https://github.com/vikejs/vike/issues/2326
309
+ const isMostLikelyNpmPkgImport = isImportNpmPackageOrPathAlias(importPathOriginal) &&
310
+ (importPathResolved.includes('/node_modules/') ||
311
+ // Linked npm package
312
+ !importPathResolved.startsWith(userRootDir));
313
+ const isExternal = isPointerImport ||
314
+ // Performance: npm package imports can be externalized. (We could as well let Rolldown transpile /node_modules/ code but it's useless as /node_modules/ code is already built. It would unnecessarily slow down transpilation.)
315
+ (isMostLikelyNpmPkgImport && isPlainJavaScriptFile(importPathResolved));
316
+ if (!isExternal) {
317
+ // User-land config code (i.e. not runtime code) => let Rolldown transpile it
318
+ assert(!isPointerImport);
319
+ return { isExternal, isPointerImport };
320
+ }
321
+ let importPathTranspiled;
322
+ assertPosixPath(importPathOriginal);
323
+ if (isImportPathRelative(importPathOriginal)) {
324
+ importPathTranspiled = importPathResolved;
325
+ }
326
+ else {
327
+ // `importPathOriginal` is either:
328
+ // - Npm package import
329
+ // - Path alias
330
+ const filePathAbsoluteUserRootDir = getFilePathAbsoluteUserRootDir({
331
+ filePathAbsoluteFilesystem: importPathResolved,
332
+ userRootDir,
333
+ });
334
+ if (filePathAbsoluteUserRootDir && !isMostLikelyNpmPkgImport) {
335
+ // `importPathOriginal` is most likely a path alias.
336
+ // - We have to use Rolldown's path alias resolution, because:
337
+ // - Vike doesn't resolve path aliases at all.
338
+ // - Node.js doesn't support `tsconfig.js#compilerOptions.paths`.
339
+ // - Rolldown path alias resolution seems reliable, e.g. it supports `tsconfig.js#compilerOptions.paths`.
340
+ importPathTranspiled = importPathResolved;
341
+ }
342
+ else {
343
+ // `importPathOriginal` is most likely an npm package import.
344
+ assertImportIsNpmPackage(importPathOriginal);
345
+ // For improved error messages, let the resolution be handled by Vike or Node.js.
346
+ importPathTranspiled = importPathOriginal;
347
+ }
348
+ }
349
+ return { isExternal, isPointerImport, importPathTranspiled };
350
+ }
351
+ // Imports with the import attribute `with { type: 'vike:pointer' }`
352
+ function parseImportsWithPointerAttribute(code, id) {
353
+ // Performance trick
354
+ if (!code.includes('vike:pointer'))
355
+ return [];
356
+ let program;
357
+ try {
358
+ program = parseAst(code, { lang: getLang(id), sourceType: 'module' }, id);
359
+ }
360
+ catch {
361
+ // Let Rolldown throw the parse error (Rolldown shows a prettier error)
362
+ return [];
363
+ }
364
+ const imports = [];
365
+ program.body.forEach((node) => {
366
+ if (node.type === 'ImportDeclaration') {
367
+ // Removed by TypeScript transpilation
368
+ if (node.importKind === 'type')
369
+ return;
370
+ if (!hasPointerImportAttribute(node.attributes))
371
+ return;
372
+ imports.push({
373
+ importPath: node.source.value,
374
+ importPathSpan: { start: node.source.start, end: node.source.end },
375
+ });
376
+ }
377
+ if ((node.type === 'ExportNamedDeclaration' || node.type === 'ExportAllDeclaration') && node.source) {
378
+ // Removed by TypeScript transpilation
379
+ if (node.exportKind === 'type')
380
+ return;
381
+ if (!hasPointerImportAttribute(node.attributes))
382
+ return;
383
+ imports.push({
384
+ importPath: node.source.value,
385
+ importPathSpan: { start: node.source.start, end: node.source.end },
386
+ });
387
+ }
388
+ });
389
+ return imports;
390
+ }
391
+ function hasPointerImportAttribute(attributes) {
392
+ return attributes.some(({ key, value }) => {
393
+ const keyName = key.type === 'Identifier' ? key.name : key.value;
394
+ return keyName === 'type' && value.value === 'vike:pointer';
395
+ });
396
+ }
397
+ function getLang(filePath) {
398
+ const fileExtension = getFileExtension(filePath);
399
+ if (['ts', 'mts', 'cts'].includes(fileExtension))
400
+ return 'ts';
401
+ if (fileExtension === 'tsx')
402
+ return 'tsx';
403
+ if (fileExtension === 'jsx')
404
+ return 'jsx';
405
+ return 'js';
406
+ }
407
+ function isVirtualModule(id) {
408
+ return id.startsWith('\0');
409
+ }
292
410
  async function executeTranspiledFile(filePath, code) {
293
411
  const { filePathAbsoluteFilesystem } = filePath;
294
412
  // Alternative to using a temporary file: https://github.com/vitejs/vite/pull/13269
@@ -345,17 +463,40 @@ function getConfigBuildErrorFormatted(err) {
345
463
  const errMsgFormatted = err[formatted];
346
464
  return errMsgFormatted;
347
465
  }
348
- async function formatBuildErr(err, filePath) {
349
- assert(isObject(err) && err.errors);
350
- const msgEsbuild = (await formatMessages(err.errors, {
351
- kind: 'error',
352
- color: true,
353
- }))
354
- .map((m) => m.trim())
355
- .join('\n');
466
+ function formatBuildErr(err, filePath) {
467
+ if (!isRolldownBuildError(err))
468
+ return;
469
+ const msgRolldown = err.errors.map((e) => e.message.trim()).join('\n');
356
470
  const msgIntro = getErrIntroMsg('transpile', filePath);
357
- const errMsgFormatted = `${msgIntro}\n${msgEsbuild}`;
358
- err[formatted] = errMsgFormatted;
471
+ const errMsgFormatted = `${msgIntro}\n${msgRolldown}`;
472
+ // Non-enumerable, otherwise `$ vike build` prints it in addition to the error message
473
+ Object.defineProperty(err, formatted, { value: errMsgFormatted, enumerable: false, configurable: true });
474
+ }
475
+ function isRolldownBuildError(err) {
476
+ return (isObject(err) && Array.isArray(err.errors) && err.errors.every((e) => isObject(e) && typeof e.message === 'string'));
477
+ }
478
+ function getErrVikePlugin(err) {
479
+ if (!isRolldownBuildError(err))
480
+ return null;
481
+ const errVikePlugin = err.errors.find((e) => e.code === 'PLUGIN_ERROR' && e.plugin?.startsWith('vike:'));
482
+ return errVikePlugin ?? null;
483
+ }
484
+ function getErrUnresolvedImports(logs, filePath) {
485
+ const errors = logs.map((log) => ({
486
+ ...log,
487
+ // We make the build fail, thus:
488
+ // - Color it as an error (instead of as a warning)
489
+ // - Remove `, treating it as an external dependency` from the message `Module not found, treating it as an external dependency`
490
+ message: log.message
491
+ .replace('\x1b[33m[UNRESOLVED_IMPORT]', '\x1b[31m[UNRESOLVED_IMPORT]')
492
+ .replace(', treating it as an external dependency', '.'),
493
+ }));
494
+ const msg = `Build failed with ${errors.length} error${errors.length === 1 ? '' : 's'}:\n\n${errors.map((e) => e.message).join('\n')}`;
495
+ const err = new Error(msg);
496
+ // Getter (like Rolldown's errors), otherwise `$ vike build` prints it in addition to the error message (Node.js's util.inspect() always prints `error.errors` if it's an array)
497
+ Object.defineProperty(err, 'errors', { get: () => errors, enumerable: true, configurable: true });
498
+ formatBuildErr(err, filePath);
499
+ return err;
359
500
  }
360
501
  const execErrIntroMsg = new WeakMap();
361
502
  function getConfigExecutionErrorIntroMsg(err) {
@@ -418,26 +559,6 @@ function getErrIntroMsg(operation, filePath) {
418
559
  const msg = `${pc.red(`Failed to ${operation}`)} ${pc.bold(pc.red(filePathToShowToUserResolved))} ${pc.red(`because:`)}`;
419
560
  return msg;
420
561
  }
421
- function cleanEsbuildErrors(errors) {
422
- errors.forEach((e) => (e.notes = e.notes.filter((note) =>
423
- // Remove note:
424
- // ```shell
425
- // You can mark the path "#root/renderer/onRenderHtml_typo" as external to exclude it from the bundle, which will remove this error and leave the unresolved path in the bundle.
426
- // ```
427
- //
428
- // From error:
429
- // ```shell
430
- // ✘ [ERROR] Could not resolve "#root/renderer/onRenderHtml_typo" [plugin vike-esbuild]
431
- //
432
- // renderer/+config.h.js:1:29:
433
- // 1 │ import { onRenderHtml } from "#root/renderer/onRenderHtml_typo"
434
- // ╵ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
435
- //
436
- // You can mark the path "#root/renderer/onRenderHtml_typo" as external to exclude it from the bundle, which will remove this error and leave the unresolved path in the bundle.
437
- //
438
- // ```
439
- !note.text.includes('as external to exclude it from the bundle'))));
440
- }
441
562
  function installSourceMapSupport() {
442
563
  // Don't break Vitest's source mapping
443
564
  if (isVitest())
@@ -134,7 +134,7 @@ function isV1Design() {
134
134
  async function resolveVikeConfigInternal_withErrorHandling(userRootDir, isDev, vikeVitePluginOptions, doNotRestartViteOnError) {
135
135
  const { promise, resolve, reject } = genPromise();
136
136
  globalObject.vikeConfigPromise = promise;
137
- const esbuildCache = {
137
+ const vikeTranspileCache = {
138
138
  transpileCache: {},
139
139
  vikeConfigDependencies: new Set(),
140
140
  };
@@ -143,7 +143,7 @@ async function resolveVikeConfigInternal_withErrorHandling(userRootDir, isDev, v
143
143
  let ret;
144
144
  let err;
145
145
  try {
146
- ret = await resolveVikeConfigInternal(userRootDir, vikeVitePluginOptions, esbuildCache);
146
+ ret = await resolveVikeConfigInternal(userRootDir, vikeVitePluginOptions, vikeTranspileCache);
147
147
  }
148
148
  catch (err_) {
149
149
  hasError = true;
@@ -193,7 +193,7 @@ async function resolveVikeConfigInternal_withErrorHandling(userRootDir, isDev, v
193
193
  }
194
194
  else {
195
195
  logErrorServerDev(err, null);
196
- resolve(await getVikeConfigDummy(esbuildCache));
196
+ resolve(await getVikeConfigDummy(vikeTranspileCache));
197
197
  }
198
198
  }
199
199
  }
@@ -215,9 +215,9 @@ function hasViteConfigChanged(vikeConfigOld, vikeConfigNew) {
215
215
  }
216
216
  return false;
217
217
  }
218
- async function resolveVikeConfigInternal(userRootDir, vikeVitePluginOptions, esbuildCache) {
219
- const plusFilesByLocationId = await getPlusFiles(userRootDir, esbuildCache);
220
- const configDefinitionsResolved = await resolveConfigDefinitions(plusFilesByLocationId, userRootDir, esbuildCache);
218
+ async function resolveVikeConfigInternal(userRootDir, vikeVitePluginOptions, vikeTranspileCache) {
219
+ const plusFilesByLocationId = await getPlusFiles(userRootDir, vikeTranspileCache);
220
+ const configDefinitionsResolved = await resolveConfigDefinitions(plusFilesByLocationId, userRootDir, vikeTranspileCache);
221
221
  const { pageConfigGlobal, pageConfigs } = getPageConfigsBuildTime(configDefinitionsResolved, plusFilesByLocationId, userRootDir);
222
222
  if (!globalObject.isV1Design_)
223
223
  globalObject.isV1Design_ = pageConfigs.length > 0;
@@ -239,7 +239,7 @@ async function resolveVikeConfigInternal(userRootDir, vikeVitePluginOptions, esb
239
239
  prerenderContext,
240
240
  _pageConfigs: pageConfigs,
241
241
  _pageConfigGlobal: pageConfigGlobal,
242
- _vikeConfigDependencies: esbuildCache.vikeConfigDependencies,
242
+ _vikeConfigDependencies: vikeTranspileCache.vikeConfigDependencies,
243
243
  _extensions,
244
244
  };
245
245
  globalObject.vikeConfigSync = vikeConfig;
@@ -249,14 +249,14 @@ function resolveGlobalConfig(pageConfigGlobal, pageConfigs) {
249
249
  const globalConfigPublic = resolveGlobalConfigPublic(pageConfigs, pageConfigGlobal, getConfigValues);
250
250
  return globalConfigPublic;
251
251
  }
252
- async function resolveConfigDefinitions(plusFilesByLocationId, userRootDir, esbuildCache) {
252
+ async function resolveConfigDefinitions(plusFilesByLocationId, userRootDir, vikeTranspileCache) {
253
253
  const plusFilesByLocationIdOrdered = Object.values(plusFilesByLocationId)
254
254
  .flat()
255
255
  .sort((plusFile1, plusFile2) => sortAfterInheritanceOrderGlobal(plusFile1, plusFile2, plusFilesByLocationId, null));
256
256
  const configDefinitionsGlobal = getConfigDefinitions(
257
257
  // We use `plusFilesByLocationId` in order to allow non-global Vike extensions to create global configs, and to set the value of global configs such as `+vite` (enabling Vike extensions to add Vite plugins).
258
258
  plusFilesByLocationIdOrdered, (configDef) => !!configDef.global);
259
- await loadCustomConfigBuildTimeFiles(plusFilesByLocationId, configDefinitionsGlobal, userRootDir, esbuildCache);
259
+ await loadCustomConfigBuildTimeFiles(plusFilesByLocationId, configDefinitionsGlobal, userRootDir, vikeTranspileCache);
260
260
  const configNamesKnownAll = getConfigNames(Object.values(plusFilesByLocationId).flat());
261
261
  const configNamesKnownGlobal = Object.keys(configDefinitionsGlobal);
262
262
  assert(configNamesKnownGlobal.every((configName) => configNamesKnownAll.includes(configName)));
@@ -268,7 +268,7 @@ async function resolveConfigDefinitions(plusFilesByLocationId, userRootDir, esbu
268
268
  .flat()
269
269
  .sort((plusFile1, plusFile2) => sortAfterInheritanceOrderPage(plusFile1, plusFile2, locationIdPage, null));
270
270
  const { configDefinitions, peerDependencyConfigNames } = collectConfigDefinitions(plusFilesRelevant, (configDef) => configDef.global !== true);
271
- await loadCustomConfigBuildTimeFiles(plusFiles, configDefinitions, userRootDir, esbuildCache);
271
+ await loadCustomConfigBuildTimeFiles(plusFiles, configDefinitions, userRootDir, vikeTranspileCache);
272
272
  const configNamesKnownLocal = unique([
273
273
  ...Object.keys(configDefinitions),
274
274
  ...peerDependencyConfigNames,
@@ -293,15 +293,15 @@ async function resolveConfigDefinitions(plusFilesByLocationId, userRootDir, esbu
293
293
  }
294
294
  // Load value files (with `env.config===true`) of *custom* configs.
295
295
  // - The value files of *built-in* configs are already loaded at `getPlusFiles()`.
296
- async function loadCustomConfigBuildTimeFiles(plusFiles, configDefinitions, userRootDir, esbuildCache) {
296
+ async function loadCustomConfigBuildTimeFiles(plusFiles, configDefinitions, userRootDir, vikeTranspileCache) {
297
297
  const plusFileList = Object.values(plusFiles).flat(1);
298
298
  await Promise.all(plusFileList.map(async (plusFile) => {
299
299
  if (!plusFile.isConfigFile) {
300
- await loadValueFile(plusFile, configDefinitions, userRootDir, esbuildCache);
300
+ await loadValueFile(plusFile, configDefinitions, userRootDir, vikeTranspileCache);
301
301
  }
302
302
  else {
303
303
  await Promise.all(Object.entries(plusFile.pointerImportsByConfigName).map(async ([configName, pointerImports]) => {
304
- await Promise.all(pointerImports.map((pointerImport) => loadPointerImport(pointerImport, userRootDir, configName, configDefinitions, esbuildCache)));
304
+ await Promise.all(pointerImports.map((pointerImport) => loadPointerImport(pointerImport, userRootDir, configName, configDefinitions, vikeTranspileCache)));
305
305
  }));
306
306
  }
307
307
  }));
@@ -1325,7 +1325,7 @@ function restartViteDevServer() {
1325
1325
  })();
1326
1326
  swallowViteLogForceOptimization_disable();
1327
1327
  }
1328
- async function getVikeConfigDummy(esbuildCache) {
1328
+ async function getVikeConfigDummy(vikeTranspileCache) {
1329
1329
  const pageConfigsDummy = [];
1330
1330
  const pageConfigGlobalDummy = {
1331
1331
  configValueSources: {},
@@ -1345,7 +1345,7 @@ async function getVikeConfigDummy(esbuildCache) {
1345
1345
  },
1346
1346
  ...globalConfigPublicDummy,
1347
1347
  prerenderContext: prerenderContextDummy,
1348
- _vikeConfigDependencies: esbuildCache.vikeConfigDependencies,
1348
+ _vikeConfigDependencies: vikeTranspileCache.vikeConfigDependencies,
1349
1349
  _extensions: [],
1350
1350
  };
1351
1351
  globalObject.vikeConfigSync = vikeConfigDummy;
@@ -3,9 +3,10 @@ export { inferNonceAttr };
3
3
  export { addCspResponseHeader };
4
4
  export type { PageContextCspNonce };
5
5
  import type { PageContextConfig } from '../../../shared-server-client/getPageFiles.js';
6
+ import type { PageContextPublicMinimum } from '../../../shared-server-client/getPageContextPublicShared.js';
6
7
  import type { PageContextServer } from '../../../types/PageContext.js';
7
8
  import '../../assertEnvServer.js';
8
- declare function resolvePageContextCspNone(pageContext: PageContextConfig & Partial<PageContextCspNonce>): Promise<null | {
9
+ declare function resolvePageContextCspNone(pageContext: PageContextConfig & PageContextPublicMinimum & Partial<PageContextCspNonce>): Promise<null | {
9
10
  cspNonce: string | null;
10
11
  }>;
11
12
  type PageContextCspNonce = Pick<PageContextServer, 'cspNonce'>;
@@ -3,6 +3,7 @@ export { inferNonceAttr };
3
3
  export { addCspResponseHeader };
4
4
  import { import_ } from '@brillout/import';
5
5
  import { assert } from '../../../utils/assert.js';
6
+ import { getPageContextPublicServer } from './getPageContextPublicServer.js';
6
7
  import '../../assertEnvServer.js';
7
8
  async function resolvePageContextCspNone(pageContext) {
8
9
  if (pageContext.cspNonce)
@@ -14,7 +15,7 @@ async function resolvePageContextCspNone(pageContext) {
14
15
  pageContextAddendum.cspNonce = await generateNonce();
15
16
  }
16
17
  else {
17
- pageContextAddendum.cspNonce = await csp.nonce(pageContext);
18
+ pageContextAddendum.cspNonce = await csp.nonce(getPageContextPublicServer(pageContext));
18
19
  }
19
20
  }
20
21
  return pageContextAddendum;
@@ -3,6 +3,7 @@ export { resolveHeadersResponseFinal };
3
3
  import { addCspResponseHeader } from './csp.js';
4
4
  import { isCallable } from '../../../utils/isCallable.js';
5
5
  import { cacheControlDisable, getCacheControl } from './getCacheControl.js';
6
+ import { getPageContextPublicServer } from './getPageContextPublicServer.js';
6
7
  import '../../assertEnvServer.js';
7
8
  function resolveHeadersResponseFinal(pageContext, statusCode) {
8
9
  const headersResponse = pageContext.headersResponse || new Headers();
@@ -34,7 +35,7 @@ async function resolveHeadersResponseConfig(pageContext) {
34
35
  await Promise.all((pageContext.config.headersResponse ?? []).map(async (headers) => {
35
36
  let headersInit;
36
37
  if (isCallable(headers)) {
37
- headersInit = await headers(pageContext);
38
+ headersInit = await headers(getPageContextPublicServer(pageContext));
38
39
  }
39
40
  else {
40
41
  headersInit = headers;
@@ -1 +1 @@
1
- export declare const PROJECT_VERSION: '0.4.266-commit-f1b186a';
1
+ export declare const PROJECT_VERSION: '0.4.267-commit-b005535';
@@ -1,2 +1,2 @@
1
1
  // Automatically updated by @brillout/release-me
2
- export const PROJECT_VERSION = '0.4.266-commit-f1b186a';
2
+ export const PROJECT_VERSION = '0.4.267-commit-b005535';
@@ -1,2 +1,4 @@
1
1
  export { assertNodeVersion };
2
+ export { nodeVersionMin };
3
+ declare const nodeVersionMin = "20.19.0";
2
4
  declare function assertNodeVersion(): void;
@@ -1,6 +1,9 @@
1
1
  export { assertNodeVersion };
2
+ export { nodeVersionMin };
2
3
  import { isNodeJS } from './isNodeJS.js';
3
4
  import { assertVersion } from './assertVersion.js';
5
+ // Vike's minimum supported Node.js version
6
+ const nodeVersionMin = '20.19.0';
4
7
  // node_modules/vike/package.json#engines.node isn't enough, as users often ignore it
5
8
  function assertNodeVersion() {
6
9
  if (!isNodeJS())
@@ -8,5 +11,5 @@ function assertNodeVersion() {
8
11
  const version = process.versions.node;
9
12
  assertVersion('Node.js', version,
10
13
  // https://gist.github.com/brillout/8e0133716e169b981b6c4e8a938b0134
11
- ['20.19.0', '22.12.0']);
14
+ [nodeVersionMin, '22.12.0']);
12
15
  }
@@ -2,7 +2,7 @@ export { createDebug };
2
2
  export { isDebug };
3
3
  export { isDebugError };
4
4
  export { debug };
5
- declare const flags: ["vike", "vike:config", "vike:crawl", "vike:file-change", "vike:error", "vike:esbuild-resolve", "vike:pluginExtractAssets", "vike:pluginExtractExportNames", "vike:glob", "vike:globalContext", "vike:log", "vike:optimizeDeps", "vike:outDir", "vike:pageFiles", "vike:pointer-imports", "vike:requireResolve", "vike:routing", "vike:setup", "vike:staticReplace", "vike:stream", "vike:virtualFiles", "vike:vite-config-user", "vike:vite-rpc"];
5
+ declare const flags: ["vike", "vike:config", "vike:crawl", "vike:file-change", "vike:error", "vike:transpile-resolve", "vike:pluginExtractAssets", "vike:pluginExtractExportNames", "vike:glob", "vike:globalContext", "vike:log", "vike:optimizeDeps", "vike:outDir", "vike:pageFiles", "vike:pointer-imports", "vike:requireResolve", "vike:routing", "vike:setup", "vike:staticReplace", "vike:stream", "vike:virtualFiles", "vike:vite-config-user", "vike:vite-rpc"];
6
6
  type Flag = (typeof flags)[number];
7
7
  type Options = {
8
8
  serialization?: {
@@ -15,7 +15,7 @@ const flags = [
15
15
  'vike:crawl',
16
16
  'vike:file-change',
17
17
  'vike:error',
18
- 'vike:esbuild-resolve',
18
+ 'vike:transpile-resolve',
19
19
  'vike:pluginExtractAssets',
20
20
  'vike:pluginExtractExportNames',
21
21
  'vike:glob',
@@ -1,5 +1,4 @@
1
1
  export { requireResolveOptional };
2
- export { requireResolveOptionalDir };
3
2
  export { requireResolveNpmPackage };
4
3
  export { requireResolveDistFile };
5
4
  export { getPackageNodeModulesDirectory };
@@ -8,11 +7,6 @@ declare function requireResolveOptional({ importPath, importerFilePath, userRoot
8
7
  importerFilePath: string | null;
9
8
  userRootDir: string;
10
9
  }): string | null;
11
- declare function requireResolveOptionalDir({ importPath, importerDir, userRootDir, }: {
12
- importPath: string;
13
- importerDir: string;
14
- userRootDir: string;
15
- }): string | null;
16
10
  declare function requireResolveNpmPackage({ importPathNpmPackage, userRootDir, }: {
17
11
  importPathNpmPackage: string;
18
12
  userRootDir: string;
@@ -1,5 +1,4 @@
1
1
  export { requireResolveOptional };
2
- export { requireResolveOptionalDir };
3
2
  export { requireResolveNpmPackage };
4
3
  export { requireResolveDistFile };
5
4
  export { getPackageNodeModulesDirectory };
@@ -88,13 +87,6 @@ function requireResolveOptional({ importPath, importerFilePath, userRootDir, })
88
87
  return null;
89
88
  return res.importPathResolvedFilePath;
90
89
  }
91
- function requireResolveOptionalDir({ importPath, importerDir, userRootDir, }) {
92
- const importerFilePath = getFakeImporterFile(importerDir);
93
- const res = requireResolve_(importPath, importerFilePath, userRootDir);
94
- if (res.hasFailed)
95
- return null;
96
- return res.importPathResolvedFilePath;
97
- }
98
90
  function requireResolveNpmPackage({ importPathNpmPackage, userRootDir, }) {
99
91
  assertImportIsNpmPackage(importPathNpmPackage);
100
92
  const importerFilePath = getFakeImporterFile(userRootDir);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vike",
3
- "version": "0.4.266-commit-f1b186a",
3
+ "version": "0.4.267-commit-b005535",
4
4
  "repository": "https://github.com/vikejs/vike",
5
5
  "exports": {
6
6
  "./server": {
@@ -135,10 +135,10 @@
135
135
  "cac": "^6.0.0",
136
136
  "convert-route": "^1.1.1",
137
137
  "es-module-lexer": "^1.0.0",
138
- "esbuild": ">=0.19.0",
139
138
  "json5": "^2.0.0",
140
- "magic-string": "^1.4.1",
139
+ "magic-string": "^1.4.2",
141
140
  "picomatch": "^4.0.7",
141
+ "rolldown": ">=1.0.0",
142
142
  "semver": "^7.8.5",
143
143
  "sirv": "^3.0.2",
144
144
  "source-map-support": "^0.5.0",
@@ -269,7 +269,6 @@
269
269
  "@types/source-map-support": "^0.5.10",
270
270
  "react-streaming": "^0.4.20",
271
271
  "rimraf": "^6.1.3",
272
- "rolldown": "1.0.0-rc.17",
273
272
  "typescript": "^7.0.2",
274
273
  "vite": "^7.2.6"
275
274
  },