vike 0.4.266 → 0.4.267

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. package/dist/client/runtime-client-routing/history.d.ts +19 -19
  2. package/dist/client/runtime-client-routing/history.js +89 -83
  3. package/dist/client/runtime-client-routing/renderPageClient.js +1 -6
  4. package/dist/client/runtime-client-routing/setScrollPosition.js +6 -5
  5. package/dist/node/prerender/runPrerender.js +11 -0
  6. package/dist/node/vite/assertEnvVite.js +1 -1
  7. package/dist/node/vite/plugins/build/handleAssetsManifest.js +2 -1
  8. package/dist/node/vite/plugins/pluginDev/logSkillHint.js +6 -4
  9. package/dist/node/vite/plugins/pluginUniversalDeploy.js +3 -2
  10. package/dist/node/vite/shared/resolveVikeConfigInternal/getPlusFiles.d.ts +2 -2
  11. package/dist/node/vite/shared/resolveVikeConfigInternal/getPlusFiles.js +3 -3
  12. package/dist/node/vite/shared/resolveVikeConfigInternal/loadFileAtConfigTime.d.ts +4 -4
  13. package/dist/node/vite/shared/resolveVikeConfigInternal/loadFileAtConfigTime.js +9 -9
  14. package/dist/node/vite/shared/resolveVikeConfigInternal/metaBuiltIn.js +4 -0
  15. package/dist/node/vite/shared/resolveVikeConfigInternal/pointerImports.d.ts +3 -1
  16. package/dist/node/vite/shared/resolveVikeConfigInternal/pointerImports.js +18 -46
  17. package/dist/node/vite/shared/resolveVikeConfigInternal/transpileAndExecuteFile.d.ts +3 -3
  18. package/dist/node/vite/shared/resolveVikeConfigInternal/transpileAndExecuteFile.js +333 -212
  19. package/dist/node/vite/shared/resolveVikeConfigInternal.js +15 -15
  20. package/dist/types/Config.d.ts +8 -0
  21. package/dist/utils/PROJECT_VERSION.d.ts +1 -1
  22. package/dist/utils/PROJECT_VERSION.js +1 -1
  23. package/dist/utils/assertNodeVersion.d.ts +2 -0
  24. package/dist/utils/assertNodeVersion.js +4 -1
  25. package/dist/utils/debug.d.ts +1 -1
  26. package/dist/utils/debug.js +1 -1
  27. package/dist/utils/requireResolve.d.ts +0 -6
  28. package/dist/utils/requireResolve.js +0 -8
  29. package/package.json +5 -6
@@ -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())