vike 0.4.177 → 0.4.178-commit-fae90a1

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 (50) hide show
  1. package/dist/cjs/node/plugin/plugins/envVars.js +11 -3
  2. package/dist/cjs/node/plugin/plugins/importUserCode/v1-design/getVikeConfig/crawlPlusFiles.js +131 -20
  3. package/dist/cjs/node/runtime/html/injectAssets/getHtmlTags.js +6 -9
  4. package/dist/cjs/node/runtime/html/injectAssets/{getViteDevScripts.js → getViteDevScript.js} +5 -5
  5. package/dist/cjs/node/runtime/html/injectAssets/injectHtmlTags.js +20 -18
  6. package/dist/cjs/node/runtime/html/injectAssets.js +13 -10
  7. package/dist/cjs/node/runtime/html/renderHtml.js +4 -4
  8. package/dist/cjs/node/runtime/html/stream/react-streaming.js +11 -10
  9. package/dist/cjs/node/runtime/html/stream.js +18 -10
  10. package/dist/cjs/node/runtime/utils.js +1 -0
  11. package/dist/cjs/utils/assert.js +1 -1
  12. package/dist/cjs/utils/assertSingleInstance.js +7 -7
  13. package/dist/cjs/utils/isVikeReactApp.js +9 -0
  14. package/dist/cjs/utils/parseUrl.js +17 -12
  15. package/dist/cjs/utils/projectInfo.js +1 -1
  16. package/dist/esm/client/client-routing-runtime/entry.js +5 -2
  17. package/dist/esm/client/client-routing-runtime/getPageContextFromHooks.js +1 -0
  18. package/dist/esm/client/server-routing-runtime/entry.js +5 -2
  19. package/dist/esm/client/shared/removeFoucBuster.d.ts +2 -0
  20. package/dist/esm/client/shared/removeFoucBuster.js +39 -0
  21. package/dist/esm/client/shared/utils.d.ts +1 -0
  22. package/dist/esm/client/shared/utils.js +1 -0
  23. package/dist/esm/node/plugin/plugins/envVars.js +11 -3
  24. package/dist/esm/node/plugin/plugins/importUserCode/v1-design/getVikeConfig/crawlPlusFiles.js +132 -21
  25. package/dist/esm/node/runtime/html/injectAssets/getHtmlTags.d.ts +2 -2
  26. package/dist/esm/node/runtime/html/injectAssets/getHtmlTags.js +6 -9
  27. package/dist/esm/node/runtime/html/injectAssets/getViteDevScript.d.ts +2 -0
  28. package/dist/esm/node/runtime/html/injectAssets/{getViteDevScripts.js → getViteDevScript.js} +4 -4
  29. package/dist/esm/node/runtime/html/injectAssets/injectHtmlTags.d.ts +5 -2
  30. package/dist/esm/node/runtime/html/injectAssets/injectHtmlTags.js +19 -17
  31. package/dist/esm/node/runtime/html/injectAssets.d.ts +2 -2
  32. package/dist/esm/node/runtime/html/injectAssets.js +14 -11
  33. package/dist/esm/node/runtime/html/renderHtml.js +5 -5
  34. package/dist/esm/node/runtime/html/stream/react-streaming.d.ts +16 -23
  35. package/dist/esm/node/runtime/html/stream/react-streaming.js +11 -10
  36. package/dist/esm/node/runtime/html/stream.d.ts +2 -2
  37. package/dist/esm/node/runtime/html/stream.js +19 -11
  38. package/dist/esm/node/runtime/utils.d.ts +1 -0
  39. package/dist/esm/node/runtime/utils.js +1 -0
  40. package/dist/esm/utils/assert.js +2 -2
  41. package/dist/esm/utils/assertSingleInstance.d.ts +6 -6
  42. package/dist/esm/utils/assertSingleInstance.js +6 -6
  43. package/dist/esm/utils/isVikeReactApp.d.ts +1 -0
  44. package/dist/esm/utils/isVikeReactApp.js +5 -0
  45. package/dist/esm/utils/parseUrl.d.ts +1 -1
  46. package/dist/esm/utils/parseUrl.js +17 -12
  47. package/dist/esm/utils/projectInfo.d.ts +2 -2
  48. package/dist/esm/utils/projectInfo.js +1 -1
  49. package/package.json +3 -3
  50. package/dist/esm/node/runtime/html/injectAssets/getViteDevScripts.d.ts +0 -2
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.onAssertModuleLoad = exports.onClientEntry_ClientRouting = exports.onClientEntry_ServerRouting = void 0;
6
+ exports.assertSingleInstance_onAssertModuleLoad = exports.assertSingleInstance_onClientEntryClientRouting = exports.assertSingleInstance_onClientEntryServerRouting = void 0;
7
7
  // - Throw error if there are two different versions of vike loaded
8
8
  // - Show warning if entry of Client Routing and entry of Server Routing are both loaded
9
9
  // - Show warning if vike is loaded twice
@@ -35,7 +35,7 @@ function assertSingleInstance() {
35
35
  //*/
36
36
  }
37
37
  }
38
- function onClientEntry_ServerRouting(isProduction) {
38
+ function assertSingleInstance_onClientEntryServerRouting(isProduction) {
39
39
  assertWarning(globalObject.isClientRouting !== true, clientRuntimesClonflict, {
40
40
  onlyOnce: true,
41
41
  showStackTrace: true
@@ -49,8 +49,8 @@ function onClientEntry_ServerRouting(isProduction) {
49
49
  globalObject.checkSingleInstance = true;
50
50
  assertSingleInstance();
51
51
  }
52
- exports.onClientEntry_ServerRouting = onClientEntry_ServerRouting;
53
- function onClientEntry_ClientRouting(isProduction) {
52
+ exports.assertSingleInstance_onClientEntryServerRouting = assertSingleInstance_onClientEntryServerRouting;
53
+ function assertSingleInstance_onClientEntryClientRouting(isProduction) {
54
54
  assertWarning(globalObject.isClientRouting !== false, clientRuntimesClonflict, {
55
55
  onlyOnce: true,
56
56
  showStackTrace: true
@@ -64,13 +64,13 @@ function onClientEntry_ClientRouting(isProduction) {
64
64
  globalObject.checkSingleInstance = true;
65
65
  assertSingleInstance();
66
66
  }
67
- exports.onClientEntry_ClientRouting = onClientEntry_ClientRouting;
67
+ exports.assertSingleInstance_onClientEntryClientRouting = assertSingleInstance_onClientEntryClientRouting;
68
68
  // Called by utils/assert.ts which is (most certainly) loaded by all entries. That way we don't have to call a callback for every entry. (There are a lot of entries: `client/router/`, `client/`, `node/runtime/`, `node/plugin/`, `node/cli`.)
69
- function onAssertModuleLoad() {
69
+ function assertSingleInstance_onAssertModuleLoad() {
70
70
  globalObject.instances.push(projectInfo_js_1.projectInfo.projectVersion);
71
71
  assertSingleInstance();
72
72
  }
73
- exports.onAssertModuleLoad = onAssertModuleLoad;
73
+ exports.assertSingleInstance_onAssertModuleLoad = assertSingleInstance_onAssertModuleLoad;
74
74
  function assertUsage(condition, errorMessage) {
75
75
  if (condition) {
76
76
  return;
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isVikeReactApp = void 0;
4
+ function isVikeReactApp() {
5
+ const g = globalThis;
6
+ // Set by vike-react https://github.com/vikejs/vike-react/blob/23e92434424f10e7e742b6bf587edee5aa8832df/packages/vike-react/src/renderer/onRenderHtml.tsx#L75
7
+ return !!g._isVikeReactApp;
8
+ }
9
+ exports.isVikeReactApp = isVikeReactApp;
@@ -10,17 +10,9 @@ exports.isUriWithProtocol = exports.createUrlFromComponents = exports.assertUrlC
10
10
  const slice_js_1 = require("./slice.js");
11
11
  const assert_js_1 = require("./assert.js");
12
12
  const picocolors_1 = __importDefault(require("@brillout/picocolors"));
13
- const PROTOCOLS = [
14
- 'http://',
15
- 'https://',
16
- // For [Tauri](https://tauri.app/)
17
- 'tauri://',
18
- // For Electron: https://github.com/vikejs/vike/issues/1557
19
- 'file://'
20
- ];
21
13
  function isParsable(url) {
22
14
  // `parseUrl()` works with these URLs
23
- return (PROTOCOLS.some((p) => url.startsWith(p)) ||
15
+ return (isUrlWithProtocol(url) ||
24
16
  url.startsWith('/') ||
25
17
  url.startsWith('.') ||
26
18
  url.startsWith('?') ||
@@ -138,7 +130,8 @@ function getPathname(url, baseServer) {
138
130
  }
139
131
  }
140
132
  function parseOrigin(url) {
141
- if (!PROTOCOLS.some((protocol) => url.startsWith(protocol))) {
133
+ if (!isUrlWithProtocol(url)) {
134
+ (0, assert_js_1.assert)(!isUriWithProtocol(url));
142
135
  return { pathname: url, origin: null };
143
136
  }
144
137
  else {
@@ -239,8 +232,20 @@ function createUrlFromComponents(origin, pathname, searchOriginal, hashOriginal)
239
232
  return urlRecreated;
240
233
  }
241
234
  exports.createUrlFromComponents = createUrlFromComponents;
242
- function isUriWithProtocol(uri) {
235
+ function isUriWithProtocol(str) {
243
236
  // https://en.wikipedia.org/wiki/List_of_URI_schemes
244
- return /^[a-z0-9][a-z0-9\.\+\-]*:/i.test(uri);
237
+ // https://www.rfc-editor.org/rfc/rfc7595
238
+ // https://github.com/vikejs/vike/commit/886a99ff21e86a8ca699a25cee7edc184aa058e4#r143308934
239
+ // Examples:
240
+ // http://
241
+ // https://
242
+ // tauri:// # [Tauri](https://tauri.app)
243
+ // file:// # [Electron](https://github.com/vikejs/vike/issues/1557)
244
+ // capacitor:// # [Capacitor](https://github.com/vikejs/vike/issues/1706)
245
+ return /^[a-z][a-z0-9\+\-]*:/i.test(str);
245
246
  }
246
247
  exports.isUriWithProtocol = isUriWithProtocol;
248
+ // Same as isUriWithProtocol() but with trailing :// which is needed for parseOrigin()
249
+ function isUrlWithProtocol(str) {
250
+ return /^[a-z][a-z0-9\+\-]*:\/\//i.test(str);
251
+ }
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.PROJECT_VERSION = exports.projectInfo = void 0;
4
- const PROJECT_VERSION = '0.4.177';
4
+ const PROJECT_VERSION = '0.4.178-commit-fae90a1';
5
5
  exports.PROJECT_VERSION = PROJECT_VERSION;
6
6
  const projectInfo = {
7
7
  projectName: 'Vike',
@@ -2,8 +2,11 @@ import { assertClientRouting } from '../../utils/assertRoutingType.js';
2
2
  assertClientRouting();
3
3
  import './pageFiles';
4
4
  import { installClientRouter } from './installClientRouter.js';
5
- import { onClientEntry_ClientRouting } from './utils.js';
5
+ import { assertSingleInstance_onClientEntryClientRouting } from './utils.js';
6
+ import { removeFoucBuster } from '../shared/removeFoucBuster.js';
6
7
  // @ts-ignore Since dist/cjs/client/ is never used, we can ignore this error.
7
8
  const isProd = import.meta.env.PROD;
8
- onClientEntry_ClientRouting(isProd);
9
+ assertSingleInstance_onClientEntryClientRouting(isProd);
10
+ if (import.meta.env.DEV)
11
+ removeFoucBuster();
9
12
  installClientRouter();
@@ -19,6 +19,7 @@ import { isServerSideError } from '../../shared/misc/isServerSideError.js';
19
19
  const globalObject = getGlobalObject('router/getPageContext.ts', {});
20
20
  function getPageContextFromHooks_serialized() {
21
21
  const pageContextSerialized = getPageContextSerializedInHtml();
22
+ assertUsage(!('urlOriginal' in pageContextSerialized), "Adding 'urlOriginal' to passToClient is forbidden");
22
23
  processPageContextFromServer(pageContextSerialized);
23
24
  objectAssign(pageContextSerialized, {
24
25
  _hasPageContextFromServer: true
@@ -4,10 +4,13 @@ import './pageFiles';
4
4
  import { getPageContext } from './getPageContext.js';
5
5
  import { executeOnRenderClientHook } from '../shared/executeOnRenderClientHook.js';
6
6
  import { assertHook } from '../../shared/hooks/getHook.js';
7
- import { onClientEntry_ServerRouting } from './utils.js';
7
+ import { assertSingleInstance_onClientEntryServerRouting } from './utils.js';
8
+ import { removeFoucBuster } from '../shared/removeFoucBuster.js';
8
9
  // @ts-ignore Since dist/cjs/client/ is never used, we can ignore this error.
9
10
  const isProd = import.meta.env.PROD;
10
- onClientEntry_ServerRouting(isProd);
11
+ assertSingleInstance_onClientEntryServerRouting(isProd);
12
+ if (import.meta.env.DEV)
13
+ removeFoucBuster();
11
14
  hydrate();
12
15
  async function hydrate() {
13
16
  const pageContext = await getPageContext();
@@ -0,0 +1,2 @@
1
+ export { removeFoucBuster };
2
+ declare function removeFoucBuster(): void;
@@ -0,0 +1,39 @@
1
+ export { removeFoucBuster };
2
+ import { assert } from './utils.js';
3
+ function removeFoucBuster() {
4
+ assert(import.meta.env.DEV);
5
+ let sleep = 2;
6
+ const runClean = () => {
7
+ if (sleep < 1000)
8
+ sleep = 2 * sleep;
9
+ const isClean = clean();
10
+ if (!isClean) {
11
+ setTimeout(runClean, sleep);
12
+ }
13
+ };
14
+ setTimeout(runClean, sleep);
15
+ }
16
+ function clean() {
17
+ const VITE_ID = 'data-vite-dev-id';
18
+ const injectedByVite = [...document.querySelectorAll(`style[${VITE_ID}]`)].map((style) => style.getAttribute(VITE_ID));
19
+ // ```
20
+ // <link rel="stylesheet" type="text/css" href="/renderer/css/index.css?direct">
21
+ // <link rel="stylesheet" type="text/css" href="/renderer/Layout.css?direct">
22
+ // ```
23
+ const suffix = '?direct';
24
+ const injectedByVike = [...document.querySelectorAll(`link[rel="stylesheet"][type="text/css"][href$="${suffix}"]`)];
25
+ if (injectedByVike.length === 0) {
26
+ // clearInterval(interval)
27
+ }
28
+ let isClean = true;
29
+ injectedByVike.forEach((link) => {
30
+ const filePathAbsoluteUserRootDir = link.getAttribute('href').slice(0, -suffix.length);
31
+ if (injectedByVite.some((filePathAbsoluteFilesystem) => filePathAbsoluteFilesystem.endsWith(filePathAbsoluteUserRootDir))) {
32
+ link.remove();
33
+ }
34
+ else {
35
+ isClean = false;
36
+ }
37
+ });
38
+ return isClean;
39
+ }
@@ -0,0 +1 @@
1
+ export * from '../../utils/assert.js';
@@ -0,0 +1 @@
1
+ export * from '../../utils/assert.js';
@@ -5,6 +5,15 @@ import { loadEnv } from 'vite';
5
5
  import { assert, assertPosixPath, assertUsage, assertWarning, escapeRegex, isArray, lowerFirst } from '../utils.js';
6
6
  import { sourceMapPassthrough } from '../shared/rollupSourceMap.js';
7
7
  import { getModuleFilePath } from '../shared/getFilePath.js';
8
+ // TODO/enventually: (after we implemented vike.config.js)
9
+ // - Make import.meta.env work inside +config.js
10
+ // - For it to work, we'll probably need the user to define the settings (e.g. `envDir`) for loadEnv() inside vike.config.js instead of vite.config.js
11
+ // - Or stop using Vite's `mode` implemention and have Vike implement its own `mode` feature? (So that the only dependencies are `$ vike build --mode staging` and `$ MODE=staging vike build`.)
12
+ const PUBLIC_ENV_PREFIX = 'PUBLIC_ENV__';
13
+ const PUBLIC_ENV_WHITELIST = [
14
+ // https://github.com/vikejs/vike/issues/1724
15
+ 'STORYBOOK'
16
+ ];
8
17
  function envVarsPlugin() {
9
18
  let envsAll;
10
19
  let config;
@@ -37,14 +46,13 @@ function envVarsPlugin() {
37
46
  // Security check
38
47
  {
39
48
  const envStatement = getEnvStatement(envName);
40
- const publicPrefix = 'PUBLIC_ENV__';
41
- const isPrivate = !envName.startsWith(publicPrefix);
49
+ const isPrivate = !envName.startsWith(PUBLIC_ENV_PREFIX) && !PUBLIC_ENV_WHITELIST.includes(envName);
42
50
  if (isPrivate && isClientSide) {
43
51
  if (!code.includes(envStatement))
44
52
  return;
45
53
  const modulePath = getModuleFilePath(id, config);
46
54
  const errMsgAddendum = isBuild ? '' : ' (Vike will prevent your app from building for production)';
47
- const keyPublic = `${publicPrefix}${envName}`;
55
+ const keyPublic = `${PUBLIC_ENV_PREFIX}${envName}`;
48
56
  const errMsg = `${envStatement} is used in client-side file ${modulePath} which means that the environment variable ${envName} will be included in client-side bundles and, therefore, ${envName} will be publicly exposed which can be a security leak${errMsgAddendum}. Use ${envStatement} only in server-side files, or rename ${envName} to ${keyPublic}, see https://vike.dev/env`;
49
57
  if (isBuild) {
50
58
  assertUsage(false, errMsg);
@@ -1,12 +1,14 @@
1
1
  export { crawlPlusFiles };
2
- import { assertPosixPath, assert, assertWarning, scriptFileExtensionList, scriptFileExtensions, humanizeTime, assertIsSingleModuleInstance, assertIsNotProductionRuntime, isVersionOrAbove } from '../../../../utils.js';
2
+ import { assertPosixPath, assert, assertWarning, scriptFileExtensions, humanizeTime, assertIsSingleModuleInstance, assertIsNotProductionRuntime, isVersionOrAbove, isScriptFile } from '../../../../utils.js';
3
3
  import path from 'path';
4
+ import fs from 'fs/promises';
4
5
  import glob from 'fast-glob';
5
6
  import { exec } from 'child_process';
6
7
  import { promisify } from 'util';
7
8
  import pc from '@brillout/picocolors';
8
9
  import { isTemporaryBuildFile } from './transpileAndExecuteFile.js';
9
10
  const execA = promisify(exec);
11
+ const TOO_MANY_UNTRACKED_FILES = 5;
10
12
  assertIsNotProductionRuntime();
11
13
  assertIsSingleModuleInstance('crawlPlusFiles.ts');
12
14
  let gitIsNotUsable = false;
@@ -30,15 +32,17 @@ async function crawlPlusFiles(userRootDir, outDirAbsoluteFilesystem, isDev, craw
30
32
  let files = [];
31
33
  const res = crawlWithGit !== false && (await gitLsFiles(userRootDir, outDirRelativeFromUserRootDir));
32
34
  if (res &&
33
- // Fallback to fast-glob for users that dynamically generate plus files. (Assuming all (generetad) plus files to be skipped because users usually included them in `.gitignore`.)
34
- res.length > 0) {
35
- files = res;
35
+ // Fallback to fast-glob for users that dynamically generate plus files. (Assuming that no plus file is found because of the user's .gitignore list.)
36
+ res.files.length > 0) {
37
+ files = res.files;
38
+ // We cannot find files inside symlink directories with `$ git ls-files` => we use fast-glob
39
+ files.push(...(await crawlSymlinkDirs(res.symlinkDirs, userRootDir, outDirRelativeFromUserRootDir)));
36
40
  }
37
41
  else {
38
42
  files = await fastGlob(userRootDir, outDirRelativeFromUserRootDir);
39
43
  }
40
44
  // Filter build files
41
- files = files.filter((file) => !isTemporaryBuildFile(file));
45
+ files = files.filter((filePath) => !isTemporaryBuildFile(filePath));
42
46
  // Check performance
43
47
  {
44
48
  const timeAfter = new Date().getTime();
@@ -46,8 +50,8 @@ async function crawlPlusFiles(userRootDir, outDirAbsoluteFilesystem, isDev, craw
46
50
  if (isDev) {
47
51
  // We only warn in dev, because while building it's expected to take a long time as crawling is competing for resources with other tasks.
48
52
  // Although, in dev, it's also competing for resources e.g. with Vite's `optimizeDeps`.
49
- assertWarning(timeSpent < 3 * 1000, `Crawling your ${pc.cyan('+')} files took an unexpected long time (${humanizeTime(timeSpent)}). If you repeatedly get this warning, then consider creating a new issue on Vike's GitHub.`, {
50
- onlyOnce: 'slow-page-files-search'
53
+ assertWarning(timeSpent < 3 * 1000, `Crawling your ${pc.cyan('+')} files took an unexpected long time (${humanizeTime(timeSpent)}). If you consistently get this warning, then consider reaching out on GitHub.`, {
54
+ onlyOnce: 'slow-crawling'
51
55
  });
52
56
  }
53
57
  }
@@ -76,20 +80,27 @@ async function gitLsFiles(userRootDir, outDirRelativeFromUserRootDir) {
76
80
  'git',
77
81
  preserveUTF8,
78
82
  'ls-files',
79
- ...scriptFileExtensionList.map((ext) => `"**/+*.${ext}"`),
83
+ // We don't filter because:
84
+ // - It would skip symlink directories
85
+ // - Performance gain seems negligible: https://github.com/vikejs/vike/pull/1688#issuecomment-2166206648
86
+ // ...scriptFileExtensionList.map((ext) => `"**/+*.${ext}"`),
87
+ // Performance gain is non-negligible.
88
+ // - https://github.com/vikejs/vike/pull/1688#issuecomment-2166206648
89
+ // - When node_modules/ is untracked the performance gain could be significant?
80
90
  ...ignoreAsPatterns.map((pattern) => `--exclude="${pattern}"`),
81
- // --others lists untracked files only (but using .gitignore because --exclude-standard)
82
- // --cached adds the tracked files to the output
83
- '--others --cached --exclude-standard'
91
+ // --others --exclude-standard => list untracked files (--others) while using .gitignore (--exclude-standard)
92
+ // --cached => list tracked files
93
+ // --stage => get file modes which we use to find symlink directories
94
+ '--others --exclude-standard --cached --stage'
84
95
  ].join(' ');
85
- let files;
96
+ let resultLines;
86
97
  let filesDeleted;
87
98
  try {
88
99
  ;
89
- [files, filesDeleted] = await Promise.all([
100
+ [resultLines, filesDeleted] = await Promise.all([
90
101
  // Main command
91
102
  runCmd1(cmd, userRootDir),
92
- // Get tracked by deleted files
103
+ // Get tracked but deleted files
93
104
  runCmd1('git ls-files --deleted', userRootDir)
94
105
  ]);
95
106
  }
@@ -100,11 +111,40 @@ async function gitLsFiles(userRootDir, outDirRelativeFromUserRootDir) {
100
111
  }
101
112
  throw err;
102
113
  }
103
- files = files
104
- // We have to repeat the same exclusion logic here because the `git ls-files` option --exclude only applies to untracked files. (We use --exclude only to speed up the command.)
105
- .filter(ignoreAsFilterFn)
106
- .filter((file) => !filesDeleted.includes(file));
107
- return files;
114
+ const filePaths = resultLines.map(parseGitLsResultLine);
115
+ // If there are too many files without mode we fallback to fast-glob
116
+ if (filePaths.filter((f) => !f.mode).length > TOO_MANY_UNTRACKED_FILES)
117
+ return null;
118
+ const symlinkDirs = [];
119
+ const files = [];
120
+ for (const { filePath, mode } of filePaths) {
121
+ // Deleted?
122
+ if (filesDeleted.includes(filePath))
123
+ continue;
124
+ // We have to repeat the same exclusion logic here because the option --exclude of `$ git ls-files` only applies to untracked files. (We use --exclude only to speed up the `$ git ls-files` command.)
125
+ if (!ignoreAsFilterFn(filePath))
126
+ continue;
127
+ // Symlink directory?
128
+ {
129
+ const isSymlinkDir = await isSymlinkDirectory(mode, filePath, userRootDir);
130
+ if (isSymlinkDir) {
131
+ symlinkDirs.push(filePath);
132
+ continue;
133
+ }
134
+ // Skip deleted files and non-symlink directories
135
+ if (isSymlinkDir === null) {
136
+ continue;
137
+ }
138
+ }
139
+ // + file?
140
+ if (!path.posix.basename(filePath).startsWith('+'))
141
+ continue;
142
+ // JavaScript file?
143
+ if (!isScriptFile(filePath))
144
+ continue;
145
+ files.push(filePath);
146
+ }
147
+ return { files, symlinkDirs };
108
148
  }
109
149
  // Same as gitLsFiles() but using fast-glob
110
150
  async function fastGlob(userRootDir, outDirRelativeFromUserRootDir) {
@@ -115,7 +155,7 @@ async function fastGlob(userRootDir, outDirRelativeFromUserRootDir) {
115
155
  });
116
156
  return files;
117
157
  }
118
- // Same as getIgnoreFilter() but as glob pattern
158
+ // Same as getIgnoreAsFilterFn() but as glob pattern
119
159
  function getIgnoreAsPatterns(outDirRelativeFromUserRootDir) {
120
160
  const ignoreAsPatterns = [
121
161
  '**/node_modules/**',
@@ -132,7 +172,7 @@ function getIgnoreAsPatterns(outDirRelativeFromUserRootDir) {
132
172
  }
133
173
  return ignoreAsPatterns;
134
174
  }
135
- // Same as getIgnorePatterns() but for Array.filter()
175
+ // Same as getIgnoreAsPatterns() but for Array.filter()
136
176
  function getIgnoreAsFilterFn(outDirRelativeFromUserRootDir) {
137
177
  assert(outDirRelativeFromUserRootDir === null || !outDirRelativeFromUserRootDir.startsWith('/'));
138
178
  return (file) => !file.includes('node_modules/') &&
@@ -169,6 +209,77 @@ async function isGitNotUsable(userRootDir) {
169
209
  return false;
170
210
  }
171
211
  }
212
+ async function crawlSymlinkDirs(symlinkDirs, userRootDir, outDirRelativeFromUserRootDir) {
213
+ const filesInSymlinkDirs = (await Promise.all(symlinkDirs.map(async (symlinkDir) => (await fastGlob(path.posix.join(userRootDir, symlinkDir), outDirRelativeFromUserRootDir)).map((filePath) => path.posix.join(symlinkDir, filePath))))).flat();
214
+ return filesInSymlinkDirs;
215
+ }
216
+ // Parse:
217
+ // ```
218
+ // some/not/tracked/path
219
+ // 100644 f6928073402b241b468b199893ff6f4aed0b7195 0\tpages/index/+Page.tsx
220
+ // ```
221
+ function parseGitLsResultLine(resultLine) {
222
+ const [part1, part2, ...rest] = resultLine.split('\t');
223
+ assert(part1);
224
+ assert(rest.length === 0);
225
+ // Git doesn't provide the mode for untracked paths.
226
+ // `resultLine` is:
227
+ // ```
228
+ // some/not/tracked/path
229
+ // ```
230
+ if (part2 === undefined) {
231
+ return { filePath: part1, mode: null };
232
+ }
233
+ assert(part2);
234
+ // `resultLine` is:
235
+ // ```
236
+ // 100644 f6928073402b241b468b199893ff6f4aed0b7195 0\tpages/index/+Page.tsx
237
+ // ```
238
+ const [mode, _, __, ...rest2] = part1.split(' ');
239
+ assert(mode && _ && __ && rest2.length === 0);
240
+ return { filePath: part2, mode };
241
+ }
242
+ async function isSymlinkDirectory(mode, filePath, userRootDir) {
243
+ const filePathAbsolute = path.posix.join(userRootDir, filePath);
244
+ let stats = null;
245
+ let isSymlink = false;
246
+ if (mode === '120000') {
247
+ isSymlink = true;
248
+ }
249
+ else if (mode === null) {
250
+ // `$ git ls-files` doesn't provide the mode when Git doesn't track the path
251
+ stats = await getFileStats(filePathAbsolute);
252
+ if (stats === null)
253
+ return null;
254
+ isSymlink = stats.isSymbolicLink();
255
+ if (!isSymlink && stats.isDirectory())
256
+ return null;
257
+ }
258
+ else {
259
+ assert(mode);
260
+ }
261
+ if (!isSymlink)
262
+ return false;
263
+ if (!stats)
264
+ stats = await getFileStats(filePathAbsolute);
265
+ if (stats === null)
266
+ return null;
267
+ const isDirectory = stats.isDirectory();
268
+ return isDirectory;
269
+ }
270
+ async function getFileStats(filePathAbsolute) {
271
+ let stats;
272
+ try {
273
+ stats = await fs.lstat(filePathAbsolute);
274
+ }
275
+ catch (err) {
276
+ // File was deleted, usually a temporary file such as +config.js.build-j95xb988fpln.mjs
277
+ // ENOENT: no such file or directory
278
+ assert(err.code === 'ENOENT');
279
+ return null;
280
+ }
281
+ return stats;
282
+ }
172
283
  async function runCmd1(cmd, cwd) {
173
284
  const { stdout } = await execA(cmd, { cwd });
174
285
  /* Not always true: https://github.com/vikejs/vike/issues/1440#issuecomment-1892831303
@@ -3,7 +3,7 @@ export type { HtmlTag };
3
3
  export type { PreloadFilter };
4
4
  export type { InjectFilterEntry };
5
5
  import type { PageContextInjectAssets } from '../injectAssets.js';
6
- import type { InjectToStream } from '../stream/react-streaming.js';
6
+ import type { StreamFromReactStreamingPackage } from '../stream/react-streaming.js';
7
7
  import type { PageAsset } from '../../renderPage/getPageAssets.js';
8
8
  type PreloadFilter = null | ((assets: InjectFilterEntry[]) => InjectFilterEntry[]);
9
9
  type PreloadFilterInject = false | 'HTML_BEGIN' | 'HTML_END';
@@ -28,4 +28,4 @@ type HtmlTag = {
28
28
  };
29
29
  declare function getHtmlTags(pageContext: {
30
30
  _isStream: boolean;
31
- } & PageContextInjectAssets, injectToStream: null | InjectToStream, injectFilter: PreloadFilter): Promise<HtmlTag[]>;
31
+ } & PageContextInjectAssets, streamFromReactStreamingPackage: null | StreamFromReactStreamingPackage, injectFilter: PreloadFilter, pageAssets: PageAsset[], viteDevScript: string): HtmlTag[];
@@ -3,16 +3,14 @@ import { assert, assertWarning, assertUsage, isObject, freezePartial } from '../
3
3
  import { serializePageContextClientSide } from '../serializePageContextClientSide.js';
4
4
  import { sanitizeJson } from './sanitizeJson.js';
5
5
  import { inferAssetTag, inferPreloadTag } from './inferHtmlTags.js';
6
- import { getViteDevScripts } from './getViteDevScripts.js';
7
6
  import { mergeScriptTags } from './mergeScriptTags.js';
8
7
  import { getGlobalContext } from '../../globalContext.js';
9
8
  import pc from '@brillout/picocolors';
10
9
  const stamp = '__injectFilterEntry';
11
- async function getHtmlTags(pageContext, injectToStream, injectFilter) {
10
+ function getHtmlTags(pageContext, streamFromReactStreamingPackage, injectFilter, pageAssets, viteDevScript) {
12
11
  assert([true, false].includes(pageContext._isHtmlOnly));
13
12
  const isHtmlOnly = pageContext._isHtmlOnly;
14
13
  const { isProduction } = getGlobalContext();
15
- const pageAssets = await pageContext.__getPageAssets();
16
14
  const injectFilterEntries = pageAssets
17
15
  .filter((asset) => {
18
16
  if (asset.isEntry && asset.assetType === 'script') {
@@ -85,11 +83,11 @@ async function getHtmlTags(pageContext, injectToStream, injectFilter) {
85
83
  // See https://github.com/vikejs/vike/pull/1271
86
84
  const positionJavaScriptEntry = (() => {
87
85
  if (pageContext._pageContextPromise) {
88
- assertWarning(!injectToStream, "[getHtmlTags()] We recommend against using streaming and a pageContext promise at the same time, because progressive hydration won't work.", { onlyOnce: true });
86
+ assertWarning(!streamFromReactStreamingPackage, "[getHtmlTags()] We recommend against using streaming and a pageContext promise at the same time, because progressive hydration won't work.", { onlyOnce: true });
89
87
  // If there is a pageContext._pageContextPromise (which is resolved after the stream has ended) then the pageContext JSON data needs to await for it: https://vike.dev/streaming#initial-data-after-stream-end
90
88
  return 'HTML_END';
91
89
  }
92
- if (injectToStream) {
90
+ if (streamFromReactStreamingPackage && !streamFromReactStreamingPackage.hasStreamEnded()) {
93
91
  // If there is a stream then, in order to support progressive hydration, inject the JavaScript during the stream after React(/Vue/Solid/...) resolved the first suspense boundary
94
92
  return 'STREAM';
95
93
  }
@@ -107,7 +105,7 @@ async function getHtmlTags(pageContext, injectToStream, injectFilter) {
107
105
  });
108
106
  }
109
107
  // The JavaScript entry <script> tag
110
- const scriptEntry = await mergeScriptEntries(pageAssets);
108
+ const scriptEntry = mergeScriptEntries(pageAssets, viteDevScript);
111
109
  if (scriptEntry) {
112
110
  htmlTags.push({
113
111
  htmlTag: scriptEntry,
@@ -128,10 +126,9 @@ async function getHtmlTags(pageContext, injectToStream, injectFilter) {
128
126
  });
129
127
  return htmlTags;
130
128
  }
131
- async function mergeScriptEntries(pageAssets) {
129
+ function mergeScriptEntries(pageAssets, viteDevScript) {
132
130
  const scriptEntries = pageAssets.filter((pageAsset) => pageAsset.isEntry && pageAsset.assetType === 'script');
133
- const viteScripts = await getViteDevScripts();
134
- const scriptTagsHtml = `${viteScripts}${scriptEntries.map((asset) => inferAssetTag(asset)).join('')}`;
131
+ const scriptTagsHtml = `${viteDevScript}${scriptEntries.map((asset) => inferAssetTag(asset)).join('')}`;
135
132
  const scriptTag = mergeScriptTags(scriptTagsHtml);
136
133
  return scriptTag;
137
134
  }
@@ -0,0 +1,2 @@
1
+ export { getViteDevScript };
2
+ declare function getViteDevScript(): Promise<string>;
@@ -1,8 +1,8 @@
1
- export { getViteDevScripts };
1
+ export { getViteDevScript };
2
2
  import { getGlobalContext } from '../../globalContext.js';
3
3
  import { assert, assertUsage, assertWarning } from '../../utils.js';
4
4
  import pc from '@brillout/picocolors';
5
- async function getViteDevScripts() {
5
+ async function getViteDevScript() {
6
6
  const globalContext = getGlobalContext();
7
7
  if (globalContext.isProduction) {
8
8
  return '';
@@ -18,6 +18,6 @@ async function getViteDevScripts() {
18
18
  const viteInjection = fakeHtml.slice(fakeHtmlBegin.length, -1 * fakeHtmlEnd.length);
19
19
  assert(viteInjection.includes('script'));
20
20
  assertWarning(!viteInjection.includes('import('), 'Unexpected Vite HMR code. Reach out to a Vike maintainer on GitHub.', { onlyOnce: true });
21
- const scriptTags = viteInjection;
22
- return scriptTags;
21
+ const viteDevScript = viteInjection;
22
+ return viteDevScript;
23
23
  }
@@ -1,10 +1,13 @@
1
1
  export { injectHtmlTags };
2
+ export { injectHtmlTagsUsingStream };
2
3
  export { createHtmlHeadIfMissing };
3
4
  export { injectAtOpeningTag };
4
5
  export { injectAtClosingTag };
6
+ import type { StreamFromReactStreamingPackage } from '../stream/react-streaming.js';
5
7
  import type { HtmlTag } from './getHtmlTags.js';
6
- import type { InjectToStream } from '../stream/react-streaming.js';
7
- declare function injectHtmlTags(htmlString: string, htmlTags: HtmlTag[], injectToStream: null | InjectToStream): string;
8
+ type Position = 'HTML_BEGIN' | 'HTML_END';
9
+ declare function injectHtmlTags(htmlString: string, htmlTags: HtmlTag[], position: Position): string;
10
+ declare function injectHtmlTagsUsingStream(htmlTags: HtmlTag[], streamFromReactStreamingPackage: null | StreamFromReactStreamingPackage): Promise<void>;
8
11
  declare function injectAtOpeningTag(tag: 'head' | 'html' | '!doctype', htmlString: string, htmlFragment: string): string;
9
12
  declare function injectAtClosingTag(tag: 'body' | 'html', htmlString: string, htmlFragment: string): string;
10
13
  declare function createHtmlHeadIfMissing(htmlString: string): string;
@@ -1,24 +1,31 @@
1
1
  // Unit tests at ./injectHtmlTags.spec.ts
2
2
  export { injectHtmlTags };
3
+ export { injectHtmlTagsUsingStream };
3
4
  export { createHtmlHeadIfMissing };
4
5
  // Only needed for unit tests
5
6
  export { injectAtOpeningTag };
6
7
  export { injectAtClosingTag };
7
8
  import { assert, assertUsage, slice } from '../../utils.js';
8
- const POSITIONS = ['HTML_BEGIN', 'HTML_END', 'STREAM'];
9
- function injectHtmlTags(htmlString, htmlTags, injectToStream) {
10
- POSITIONS.forEach((position) => {
11
- const htmlFragment = htmlTags
12
- .filter((h) => h.position === position)
13
- .map((h) => resolveHtmlTag(h.htmlTag))
14
- .join('');
15
- if (htmlFragment) {
16
- htmlString = injectHtmlFragment(position, htmlFragment, htmlString, injectToStream);
17
- }
18
- });
9
+ function injectHtmlTags(htmlString, htmlTags, position) {
10
+ const htmlFragment = joinHtmlTags(htmlTags.filter((h) => h.position === position));
11
+ if (htmlFragment) {
12
+ htmlString = injectHtmlFragment(position, htmlFragment, htmlString);
13
+ }
19
14
  return htmlString;
20
15
  }
21
- function injectHtmlFragment(position, htmlFragment, htmlString, injectToStream) {
16
+ async function injectHtmlTagsUsingStream(htmlTags, streamFromReactStreamingPackage) {
17
+ const htmlFragment = joinHtmlTags(htmlTags.filter((h) => h.position === 'STREAM'));
18
+ if (htmlFragment) {
19
+ assert(streamFromReactStreamingPackage);
20
+ assert(!streamFromReactStreamingPackage.hasStreamEnded());
21
+ await streamFromReactStreamingPackage.injectToStream(htmlFragment, { flush: true });
22
+ }
23
+ }
24
+ function joinHtmlTags(htmlTags) {
25
+ const htmlFragment = htmlTags.map((h) => resolveHtmlTag(h.htmlTag)).join('');
26
+ return htmlFragment;
27
+ }
28
+ function injectHtmlFragment(position, htmlFragment, htmlString) {
22
29
  if (position === 'HTML_BEGIN') {
23
30
  {
24
31
  const res = injectAtPaceholder(htmlFragment, htmlString, true);
@@ -43,11 +50,6 @@ function injectHtmlFragment(position, htmlFragment, htmlString, injectToStream)
43
50
  }
44
51
  return htmlString + '\n' + htmlFragment;
45
52
  }
46
- if (position === 'STREAM') {
47
- assert(injectToStream);
48
- injectToStream(htmlFragment, { flush: true });
49
- return htmlString;
50
- }
51
53
  assert(false);
52
54
  }
53
55
  function resolveHtmlTag(htmlTag) {