vike 0.4.148 → 0.4.149-commit-041ee42

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 (28) hide show
  1. package/dist/cjs/node/plugin/plugins/devConfig/index.js +20 -9
  2. package/dist/cjs/node/plugin/plugins/importUserCode/v1-design/getVikeConfig/crawlPlusFiles.js +52 -26
  3. package/dist/cjs/node/runtime/html/injectAssets/getHtmlTags.js +117 -81
  4. package/dist/cjs/node/runtime/html/injectAssets/inferHtmlTags.js +5 -5
  5. package/dist/cjs/node/runtime/html/injectAssets/mergeScriptTags.js +2 -1
  6. package/dist/cjs/node/runtime/html/renderHtml.js +1 -1
  7. package/dist/cjs/node/runtime/renderPage/getPageAssets/retrieveAssetsProd.js +2 -3
  8. package/dist/cjs/node/runtime/renderPage/getPageAssets/sortPageAssetsForEarlyHintsHeader.js +6 -0
  9. package/dist/cjs/node/runtime/renderPage/inferMediaType.js +38 -0
  10. package/dist/cjs/shared/page-configs/assertExports.js +11 -4
  11. package/dist/cjs/utils/projectInfo.js +1 -1
  12. package/dist/esm/client/shared/getPageContextSerializedInHtml.js +7 -1
  13. package/dist/esm/node/plugin/plugins/devConfig/index.js +20 -9
  14. package/dist/esm/node/plugin/plugins/importUserCode/v1-design/getVikeConfig/crawlPlusFiles.js +52 -26
  15. package/dist/esm/node/runtime/html/injectAssets/getHtmlTags.d.ts +4 -0
  16. package/dist/esm/node/runtime/html/injectAssets/getHtmlTags.js +117 -81
  17. package/dist/esm/node/runtime/html/injectAssets/inferHtmlTags.d.ts +2 -0
  18. package/dist/esm/node/runtime/html/injectAssets/inferHtmlTags.js +4 -4
  19. package/dist/esm/node/runtime/html/injectAssets/mergeScriptTags.js +2 -1
  20. package/dist/esm/node/runtime/html/renderHtml.js +1 -1
  21. package/dist/esm/node/runtime/renderPage/getPageAssets/retrieveAssetsProd.js +2 -3
  22. package/dist/esm/node/runtime/renderPage/getPageAssets/sortPageAssetsForEarlyHintsHeader.js +6 -0
  23. package/dist/esm/node/runtime/renderPage/inferMediaType.d.ts +2 -2
  24. package/dist/esm/node/runtime/renderPage/inferMediaType.js +38 -0
  25. package/dist/esm/shared/page-configs/assertExports.js +11 -4
  26. package/dist/esm/utils/projectInfo.d.ts +2 -2
  27. package/dist/esm/utils/projectInfo.js +1 -1
  28. package/package.json +2 -2
@@ -24,22 +24,33 @@ function devConfig() {
24
24
  config() {
25
25
  return {
26
26
  appType: 'custom',
27
+ // TODO:v1-release: remove (AFAICT we only need to use config.optimizeDeps for the old design)
27
28
  optimizeDeps: {
28
29
  exclude: [
29
- // We exclude the vike client to be able to use `import.meta.glob()`
30
+ // We exclude Vike's client runtime to be able to use Vite's import.meta.glob()
30
31
  'vike/client',
31
32
  'vike/client/router',
32
33
  'vike/routing',
33
- // - We also exclude @brillout/json-serializer and @brillout/picocolors to avoid:
34
- // ```
35
- // 9:28:58 AM [vite] ✨ new dependencies optimized: @brillout/json-serializer/parse
36
- // 9:28:58 AM [vite] ✨ optimized dependencies changed. reloading
37
- // ```
38
- // - Previously, we had to exclude @brillout/json-serializer and @brillout/picocolors because of pnpm, but this doesn't seem to be the case anymore.
39
- // - Actually, this should be still the case? How can Vite resolve @brillout/json-serializer when using pnpm?
34
+ // We exclude @brillout/json-serializer and @brillout/picocolors to avoid:
35
+ // ```
36
+ // 9:28:58 AM [vite] ✨ new dependencies optimized: @brillout/json-serializer/parse
37
+ // 9:28:58 AM [vite] ✨ optimized dependencies changed. reloading
38
+ // ```
40
39
  '@brillout/json-serializer/parse',
41
40
  '@brillout/json-serializer/stringify',
42
- '@brillout/picocolors'
41
+ '@brillout/picocolors',
42
+ // We exclude all packages that depend on any optimizeDeps.exclude entry because, otherwise, the entry cannot be resolved when using pnpm. For example:
43
+ // ```
44
+ // Failed to resolve import "@brillout/json-serializer/parse" from "../../packages/vike-react-query/dist/renderer/VikeReactQueryWrapper.js". Does the file exist?
45
+ // 343| // ../../node_modules/.pnpm/react-streaming@0.3.16_react-dom@18.2.0_react@18.2.0/node_modules/react-streaming/dist/esm/client/useAsync.js
46
+ // 344| import { parse as parse2 } from "@brillout/json-serializer/parse";
47
+ // ```
48
+ // The source map is confusing, the import actually lives at node_modules/.vite/deps/vike-react-query_renderer_VikeReactQueryWrapper.js which contains:
49
+ // ```js
50
+ // // ../../node_modules/.pnpm/react-streaming@0.3.16_react-dom@18.2.0_react@18.2.0/node_modules/react-streaming/dist/esm/client/useAsync.js
51
+ // import { parse as parse2 } from "@brillout/json-serializer/parse";
52
+ // ```
53
+ 'react-streaming'
43
54
  ]
44
55
  }
45
56
  };
@@ -13,19 +13,24 @@ const execA = (0, util_1.promisify)(child_process_1.exec);
13
13
  async function crawlPlusFiles(userRootDir, outDirAbsoluteFilesystem, isDev) {
14
14
  (0, utils_js_1.assertPosixPath)(userRootDir);
15
15
  (0, utils_js_1.assertPosixPath)(outDirAbsoluteFilesystem);
16
+ // Vike prepends userRootDir without resolving, e.g. outDirRelativeFromUserRootDir can be /home/rom/my-monorepo/my-app/../my-build/dist/ while userRootDir is /home/rom/my-monorepo/my-app/
16
17
  (0, utils_js_1.assert)(outDirAbsoluteFilesystem.startsWith(userRootDir));
17
- const outDir = path_1.default.posix.relative(userRootDir, outDirAbsoluteFilesystem);
18
- (0, utils_js_1.assert)(!outDir.startsWith('.'));
18
+ let outDirRelativeFromUserRootDir = path_1.default.posix.relative(userRootDir, outDirAbsoluteFilesystem);
19
+ if (outDirRelativeFromUserRootDir.startsWith('../')) {
20
+ // config.outDir is outside of config.root => it's going to be ignored anyways
21
+ outDirRelativeFromUserRootDir = null;
22
+ }
23
+ (0, utils_js_1.assert)(outDirRelativeFromUserRootDir === null || !outDirRelativeFromUserRootDir.startsWith('.'));
19
24
  const timeBase = new Date().getTime();
20
25
  let files = [];
21
- const res = await gitLsFiles(userRootDir, outDir);
26
+ const res = await gitLsFiles(userRootDir, outDirRelativeFromUserRootDir);
22
27
  if (res &&
23
28
  // Fallback to fast-glob for users that dynamically generate plus files (we assume generetad plus files to be skipped because they are usually included in .gitignore)
24
29
  res.length > 0) {
25
30
  files = res;
26
31
  }
27
32
  else {
28
- files = await fastGlob(userRootDir, outDir);
33
+ files = await fastGlob(userRootDir, outDirRelativeFromUserRootDir);
29
34
  }
30
35
  {
31
36
  const time = new Date().getTime() - timeBase;
@@ -50,46 +55,39 @@ async function crawlPlusFiles(userRootDir, outDirAbsoluteFilesystem, isDev) {
50
55
  }
51
56
  exports.crawlPlusFiles = crawlPlusFiles;
52
57
  // Same as fastGlob() but using `$ git ls-files`
53
- async function gitLsFiles(userRootDir, outDir) {
58
+ async function gitLsFiles(userRootDir, outDirRelativeFromUserRootDir) {
59
+ if (!(await isUsingGit(userRootDir)))
60
+ return null;
54
61
  const cmd = [
55
62
  'git ls-files',
56
63
  ...utils_js_1.scriptFileExtensionList.map((ext) => `"**/+*.${ext}"`),
57
- ...getIgnorePatterns(outDir).map((pattern) => `--exclude="${pattern}"`),
64
+ ...getIgnorePatterns(outDirRelativeFromUserRootDir).map((pattern) => `--exclude="${pattern}"`),
58
65
  // --others lists untracked files only (but using .gitignore because --exclude-standard)
59
66
  // --cached adds the tracked files to the output
60
67
  '--others --cached --exclude-standard'
61
68
  ].join(' ');
62
- let stdout;
63
- try {
64
- const res = await execA(cmd, { cwd: userRootDir });
65
- stdout = res.stdout;
66
- }
67
- catch (err) {
68
- if (err.message.includes('not a git repository'))
69
- return null;
70
- throw err;
71
- }
72
- let files = stdout.split('\n').filter(Boolean);
73
- (0, utils_js_1.assert)(!outDir.startsWith('/'));
69
+ let files = await runCmd(cmd, userRootDir);
74
70
  files = files.filter(
75
71
  // 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.)
76
- (file) => getIgnoreFilter(file, outDir));
72
+ (file) => getIgnoreFilter(file, outDirRelativeFromUserRootDir));
73
+ // Remove tracked but deleted files
74
+ const filesIgnore = await runCmd('git ls-files --deleted', userRootDir);
75
+ files = files.filter((file) => !filesIgnore.includes(file));
77
76
  return files;
78
77
  }
79
78
  // Same as gitLsFiles() but using fast-glob
80
- async function fastGlob(userRootDir, outDir) {
79
+ async function fastGlob(userRootDir, outDirRelativeFromUserRootDir) {
81
80
  const files = await (0, fast_glob_1.default)(`**/+*.${utils_js_1.scriptFileExtensions}`, {
82
- ignore: getIgnorePatterns(outDir),
81
+ ignore: getIgnorePatterns(outDirRelativeFromUserRootDir),
83
82
  cwd: userRootDir,
84
83
  dot: false
85
84
  });
86
85
  return files;
87
86
  }
88
87
  // Same as getIgnoreFilter() but as glob pattern
89
- function getIgnorePatterns(outDir) {
90
- return [
88
+ function getIgnorePatterns(outDirRelativeFromUserRootDir) {
89
+ const ignorePatterns = [
91
90
  '**/node_modules/**',
92
- `${outDir}/**`,
93
91
  // Allow:
94
92
  // ```
95
93
  // +Page.js
@@ -97,8 +95,36 @@ function getIgnorePatterns(outDir) {
97
95
  // ```
98
96
  '**/*.telefunc.*'
99
97
  ];
98
+ if (outDirRelativeFromUserRootDir) {
99
+ (0, utils_js_1.assert)(!outDirRelativeFromUserRootDir.startsWith('/'));
100
+ ignorePatterns.push(`${outDirRelativeFromUserRootDir}/**`);
101
+ }
102
+ return ignorePatterns;
100
103
  }
101
104
  // Same as getIgnorePatterns() but for Array.filter()
102
- function getIgnoreFilter(file, outDir) {
103
- return !file.includes('node_modules/') && !file.includes('.telefunc.') && !file.startsWith(`${outDir}/`);
105
+ function getIgnoreFilter(file, outDirRelativeFromUserRootDir) {
106
+ (0, utils_js_1.assert)(!file.startsWith('/'));
107
+ (0, utils_js_1.assert)(outDirRelativeFromUserRootDir === null || !outDirRelativeFromUserRootDir.startsWith('/'));
108
+ return (!file.includes('node_modules/') &&
109
+ !file.includes('.telefunc.') &&
110
+ (!outDirRelativeFromUserRootDir || !file.startsWith(`${outDirRelativeFromUserRootDir}/`)));
111
+ }
112
+ // Whether Git is installed and whether userRootDir is inside a Git repository
113
+ async function isUsingGit(userRootDir) {
114
+ let res;
115
+ try {
116
+ res = await execA('git rev-parse --is-inside-work-tree', { cwd: userRootDir });
117
+ }
118
+ catch {
119
+ return false;
120
+ }
121
+ const { stdout, stderr } = res;
122
+ (0, utils_js_1.assert)(stderr.toString().trim() === '');
123
+ (0, utils_js_1.assert)(stdout.toString().trim() === 'true');
124
+ return true;
125
+ }
126
+ async function runCmd(cmd, cwd) {
127
+ const res = await execA(cmd, { cwd });
128
+ (0, utils_js_1.assert)(res.stderr === '');
129
+ return res.stdout.toString().trim().split('\n');
104
130
  }
@@ -12,39 +12,37 @@ const getViteDevScripts_js_1 = require("./getViteDevScripts.js");
12
12
  const mergeScriptTags_js_1 = require("./mergeScriptTags.js");
13
13
  const globalContext_js_1 = require("../../globalContext.js");
14
14
  const picocolors_1 = __importDefault(require("@brillout/picocolors"));
15
+ const stamp = '__injectFilterEntry';
15
16
  async function getHtmlTags(pageContext, injectToStream, injectFilter) {
16
17
  (0, utils_js_1.assert)([true, false].includes(pageContext._isHtmlOnly));
17
18
  const isHtmlOnly = pageContext._isHtmlOnly;
18
- const globalContext = (0, globalContext_js_1.getGlobalContext)();
19
- const { isProduction } = globalContext;
20
- const injectJavaScriptDuringStream = !pageContext._pageContextPromise && !!injectToStream;
19
+ const { isProduction } = (0, globalContext_js_1.getGlobalContext)();
21
20
  const pageAssets = await pageContext.__getPageAssets();
22
- const stamp = Symbol('injectFilterEntryStamp');
23
- const getInject = (asset) => {
24
- if (!isProduction) {
25
- return 'HTML_BEGIN';
26
- }
27
- if (asset.assetType === 'style' || asset.assetType === 'font') {
28
- return 'HTML_BEGIN';
29
- }
30
- if (asset.assetType === 'script') {
31
- return 'HTML_END';
32
- }
33
- return false;
34
- };
35
21
  const injectFilterEntries = pageAssets
36
22
  .filter((asset) => {
37
23
  if (asset.isEntry && asset.assetType === 'script') {
38
- // We could in principle allow the user to change the position of <script> but we don't because of `getMergedScriptTag()`
39
- return false;
40
- }
41
- if (isHtmlOnly && asset.assetType === 'script') {
24
+ // We could allow the user to change the position of <script> but we currently don't:
25
+ // - Because of mergeScriptEntries()
26
+ // - We would need to add STREAM to to PreloadFilterInject
27
+ // To suppor this, we should add the JavaScript entry to injectFilterEntries (with an `src` value of `null`)
42
28
  return false;
43
29
  }
44
30
  return true;
45
31
  })
46
32
  .map((asset) => {
47
- const inject = getInject(asset);
33
+ const inject = (() => {
34
+ if (!isProduction) {
35
+ // In development, we should always load assets as soon as possible, in order to eagerly process assets (e.g. applying the transform() hooks of Vite plugins) which are lazily discovered.
36
+ return 'HTML_BEGIN';
37
+ }
38
+ if (asset.assetType === 'style' || asset.assetType === 'font') {
39
+ return 'HTML_BEGIN';
40
+ }
41
+ if (asset.assetType === 'script') {
42
+ return 'HTML_END';
43
+ }
44
+ return false;
45
+ })();
48
46
  const entry = {
49
47
  ...asset,
50
48
  inject,
@@ -53,92 +51,104 @@ async function getHtmlTags(pageContext, injectToStream, injectFilter) {
53
51
  };
54
52
  return entry;
55
53
  });
56
- assertInjectFilterEntries(injectFilterEntries, stamp);
54
+ assertInjectFilterEntries(injectFilterEntries);
55
+ // ==============
56
+ // injectFilter()
57
+ // ==============
57
58
  if (injectFilter && isProduction) {
58
59
  Object.seal(injectFilterEntries); // `Object.seal()` instead of `Object.freeze()` to allow the user to `assets.sort()`
59
60
  Object.values(injectFilterEntries).forEach((entry) => (0, utils_js_1.freezePartial)(entry, { inject: (val) => val === false || val === 'HTML_BEGIN' || val === 'HTML_END' }));
61
+ // Call the user's injectFilter() hook https://vike.dev/injectFilter
60
62
  const res = injectFilter(injectFilterEntries);
61
- (0, utils_js_1.assertUsage)(res === undefined, 'Wrong injectFilter() usage, see https://vike.dev/injectFilter');
62
- assertInjectFilterUsage(injectFilterEntries, stamp);
63
- injectFilterEntries.forEach((a) => {
64
- /*
65
- if (a.assetType === 'script' && a.isEntry) {
66
- assertUsage(a.inject, `[injectFilter()] ${a.src} needs to be injected`)
67
- }
68
- */
69
- if (a.assetType === 'style' && a.isEntry) {
70
- // In development, Vite automatically inject styles, but we still inject `<link rel="stylesheet" type="text/css" href="${src}">` tags in order to avoid FOUC (flash of unstyled content).
71
- // - https://github.com/vitejs/vite/issues/2282
72
- // - https://github.com/vikejs/vike/issues/261
73
- (0, utils_js_1.assertWarning)(a.inject, `[injectFilter()] We recommend against not injecting ${a.src}`, {
74
- onlyOnce: true
75
- });
76
- }
77
- if (!isHtmlOnly && a.assetType === 'script') {
78
- (0, utils_js_1.assertWarning)(a.inject, `[injectFilter()] We recommend against not preloading JavaScript (${a.src})`, {
79
- onlyOnce: true
80
- });
81
- }
82
- });
63
+ (0, utils_js_1.assertUsage)(res === undefined, `injectFilter() should return ${picocolors_1.default.cyan('undefined')}, see https://vike.dev/injectFilter`);
64
+ assertInjectFilterUsage(injectFilterEntries);
83
65
  }
84
66
  const htmlTags = [];
67
+ // ==============
85
68
  // Non-JavaScript
86
- for (const asset of injectFilterEntries) {
87
- if (asset.assetType !== 'script' && asset.inject) {
88
- const htmlTag = asset.isEntry ? (0, inferHtmlTags_js_1.inferAssetTag)(asset) : (0, inferHtmlTags_js_1.inferPreloadTag)(asset);
89
- htmlTags.push({ htmlTag, position: asset.inject });
90
- }
91
- }
69
+ // ==============
70
+ injectFilterEntries
71
+ .filter((asset) => asset.assetType !== 'script' && asset.inject)
72
+ .forEach((asset) => {
73
+ if (!asset.inject)
74
+ return;
75
+ const htmlTag = asset.isEntry ? (0, inferHtmlTags_js_1.inferAssetTag)(asset) : (0, inferHtmlTags_js_1.inferPreloadTag)(asset);
76
+ htmlTags.push({ htmlTag, position: asset.inject });
77
+ });
78
+ // ==========
92
79
  // JavaScript
93
- const positionProd = injectJavaScriptDuringStream ? 'STREAM' : 'HTML_END';
94
- const positionScript = !isProduction ? 'HTML_BEGIN' : positionProd;
95
- const positionJsonData = !isProduction && !pageContext._pageContextPromise && !pageContext._isStream ? 'HTML_BEGIN' : positionProd;
96
- const jsScript = await getMergedScriptTag(pageAssets, isProduction);
97
- if (jsScript) {
80
+ // ==========
81
+ // In order to avoid the race-condition depicted in https://github.com/vikejs/vike/issues/567
82
+ // 1. <script id="vike_pageContext" type="application/json"> must appear before the entry <script> (which loads Vike's client runtime).
83
+ // 2. <script id="vike_pageContext" type="application/json"> can't be async nor defer.
84
+ // Additionally:
85
+ // 3. the entry <script> can't be defer, otherwise progressive hydration while SSR streaming won't work.
86
+ // 4. the entry <script> should be towards the end of the HTML as performance-wise it's more interesting to parse
87
+ // <div id="page-view"> before running the entry <script> which initiates the hydration.
88
+ // See https://github.com/vikejs/vike/pull/1271
89
+ const positionJavaScriptEntry = (() => {
90
+ if (pageContext._pageContextPromise) {
91
+ (0, utils_js_1.assertWarning)(!injectToStream, "[getHtmlTags()] We recommend against using streaming and a pageContext promise at the same time as partial hydration won't work", { onlyOnce: true });
92
+ // 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/stream#initial-data-after-stream-end
93
+ return 'HTML_END';
94
+ }
95
+ if (injectToStream) {
96
+ // If there is a stream then, in order to support partial hydration, inject the JavaScript during the stream after React(/Vue/Solid/...) resolved the first suspense boundary
97
+ return 'STREAM';
98
+ }
99
+ else {
100
+ return 'HTML_END';
101
+ }
102
+ })();
103
+ // <script id="vike_pageContext" type="application/json">
104
+ if (!isHtmlOnly) {
98
105
  htmlTags.push({
99
- htmlTag: jsScript,
100
- position: positionScript
106
+ htmlTag: () =>
107
+ // Needs to be called after resolvePageContextPromise()
108
+ getPageContextJsonScriptTag(pageContext),
109
+ position: positionJavaScriptEntry
101
110
  });
102
111
  }
103
- for (const asset of injectFilterEntries) {
104
- if (asset.assetType === 'script' && asset.inject) {
105
- const htmlTag = asset.isEntry ? (0, inferHtmlTags_js_1.inferAssetTag)(asset) : (0, inferHtmlTags_js_1.inferPreloadTag)(asset);
106
- const position = asset.inject === 'HTML_END' ? positionScript : asset.inject;
107
- htmlTags.push({ htmlTag, position });
108
- }
109
- }
110
- // `pageContext` JSON data
111
- if (!isHtmlOnly) {
112
- // Don't allow the user to manipulate with injectFilter(): injecting <script type="application/json"> before the stream can break the app when:
113
- // - using https://vike.dev/stream#initial-data-after-stream-end
114
- // - `pageContext` is modified during the stream, e.g. https://github.com/brillout/vike-with-pinia which uses https://vuejs.org/api/composition-api-lifecycle.html#onserverprefetch
115
- // The <script> tags are handled separately by vike down below.
112
+ // The JavaScript entry <script> tag
113
+ const scriptEntry = await mergeScriptEntries(pageAssets, isProduction);
114
+ if (scriptEntry) {
116
115
  htmlTags.push({
117
- // Needs to be called after `resolvePageContextPromise()`
118
- htmlTag: () => getPageContextTag(pageContext),
119
- position: positionJsonData
116
+ htmlTag: scriptEntry,
117
+ position: positionJavaScriptEntry
120
118
  });
121
119
  }
120
+ // Preload tags
121
+ injectFilterEntries
122
+ .filter((asset) => asset.assetType === 'script')
123
+ .forEach((asset) => {
124
+ (0, utils_js_1.assert)(!asset.isEntry); // Users cannot re-order JavaScript entries, see creation of injectFilterEntries
125
+ const htmlTag = (0, inferHtmlTags_js_1.inferPreloadTag)(asset);
126
+ if (!asset.inject)
127
+ return;
128
+ // Ideally, instead of this conditional ternary operator, we should add STREAM to PreloadFilterInject (or a better fitting name such as HTML_STREAM)
129
+ const position = asset.inject === 'HTML_END' ? positionJavaScriptEntry : asset.inject;
130
+ htmlTags.push({ htmlTag, position });
131
+ });
122
132
  return htmlTags;
123
133
  }
124
134
  exports.getHtmlTags = getHtmlTags;
125
- async function getMergedScriptTag(pageAssets, isProduction) {
126
- const scriptAssets = pageAssets.filter((pageAsset) => pageAsset.isEntry && pageAsset.assetType === 'script');
135
+ async function mergeScriptEntries(pageAssets, isProduction) {
136
+ const scriptEntries = pageAssets.filter((pageAsset) => pageAsset.isEntry && pageAsset.assetType === 'script');
127
137
  const viteScripts = await (0, getViteDevScripts_js_1.getViteDevScripts)();
128
- const scriptTagsHtml = `${viteScripts}${scriptAssets.map((asset) => (0, inferHtmlTags_js_1.inferAssetTag)(asset)).join('')}`;
138
+ const scriptTagsHtml = `${viteScripts}${scriptEntries.map((asset) => (0, inferHtmlTags_js_1.inferAssetTag)(asset)).join('')}`;
129
139
  const scriptTag = (0, mergeScriptTags_js_1.mergeScriptTags)(scriptTagsHtml, isProduction);
130
140
  return scriptTag;
131
141
  }
132
- function getPageContextTag(pageContext) {
142
+ function getPageContextJsonScriptTag(pageContext) {
133
143
  const pageContextSerialized = (0, sanitizeJson_js_1.sanitizeJson)((0, serializePageContextClientSide_js_1.serializePageContextClientSide)(pageContext));
134
144
  const htmlTag = `<script id="vike_pageContext" type="application/json">${pageContextSerialized}</script>`;
135
145
  // @ts-expect-error
136
146
  pageContext._pageContextHtmlTag = htmlTag;
137
147
  return htmlTag;
138
148
  }
139
- function assertInjectFilterEntries(injectFilterEntries, stamp) {
149
+ function assertInjectFilterEntries(injectFilterEntries) {
140
150
  try {
141
- assertInjectFilterUsage(injectFilterEntries, stamp);
151
+ checkForWrongUsage(injectFilterEntries);
142
152
  }
143
153
  catch (err) {
144
154
  if (err?.message.includes('[Wrong Usage]')) {
@@ -147,7 +157,11 @@ function assertInjectFilterEntries(injectFilterEntries, stamp) {
147
157
  throw err;
148
158
  }
149
159
  }
150
- function assertInjectFilterUsage(injectFilterEntries, stamp) {
160
+ function assertInjectFilterUsage(injectFilterEntries) {
161
+ checkForWrongUsage(injectFilterEntries);
162
+ checkForWarnings(injectFilterEntries);
163
+ }
164
+ function checkForWrongUsage(injectFilterEntries) {
151
165
  injectFilterEntries.forEach((entry, i) => {
152
166
  (0, utils_js_1.assertUsage)((0, utils_js_1.isObject)(entry), `[injectFilter()] Entry ${i} isn't an object`);
153
167
  (0, utils_js_1.assertUsage)(typeof entry.src === 'string', `[injectFilter()] Entry ${i} is missing property ${picocolors_1.default.cyan('src')}`);
@@ -156,6 +170,28 @@ function assertInjectFilterUsage(injectFilterEntries, stamp) {
156
170
  (0, utils_js_1.assert)(entry.assetType === null || typeof entry.assetType === 'string');
157
171
  (0, utils_js_1.assert)(entry.mediaType === null || typeof entry.mediaType === 'string');
158
172
  (0, utils_js_1.assert)(typeof entry.isEntry === 'boolean');
159
- (0, utils_js_1.assert)(Object.keys(entry).length === 5);
173
+ (0, utils_js_1.assert)(Object.keys(entry).length === 6);
174
+ });
175
+ }
176
+ function checkForWarnings(injectFilterEntries) {
177
+ injectFilterEntries.forEach((a) => {
178
+ /*
179
+ if (a.assetType === 'script' && a.isEntry) {
180
+ assertUsage(a.inject, `[injectFilter()] ${a.src} needs to be injected`)
181
+ }
182
+ */
183
+ if (a.assetType === 'style' && a.isEntry) {
184
+ // In development, Vite automatically inject styles, but we still inject `<link rel="stylesheet" type="text/css" href="${src}">` tags in order to avoid FOUC (flash of unstyled content).
185
+ // - https://github.com/vitejs/vite/issues/2282
186
+ // - https://github.com/vikejs/vike/issues/261
187
+ (0, utils_js_1.assertWarning)(a.inject, `[injectFilter()] We recommend against not injecting ${a.src}`, {
188
+ onlyOnce: true
189
+ });
190
+ }
191
+ if (a.assetType === 'script') {
192
+ (0, utils_js_1.assertWarning)(a.inject, `[injectFilter()] We recommend against not preloading JavaScript (${a.src})`, {
193
+ onlyOnce: true
194
+ });
195
+ }
160
196
  });
161
197
  }
@@ -1,7 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.inferEarlyHintLink = exports.inferPreloadTag = exports.inferAssetTag = void 0;
3
+ exports.scriptAttrs = exports.inferEarlyHintLink = exports.inferPreloadTag = exports.inferAssetTag = void 0;
4
4
  const utils_js_1 = require("../../utils.js");
5
+ // We can't use `defer` here. With `defer`, the entry script won't start before `</body>` has been parsed, preventing partial hydration during SSR streaming, see https://github.com/vikejs/vike/pull/1271
6
+ const scriptAttrs = 'type="module" async';
7
+ exports.scriptAttrs = scriptAttrs;
5
8
  function inferPreloadTag(pageAsset) {
6
9
  const { src, assetType, mediaType } = pageAsset;
7
10
  const rel = getRel(pageAsset);
@@ -22,10 +25,7 @@ function inferAssetTag(pageAsset) {
22
25
  const { src, assetType, mediaType } = pageAsset;
23
26
  if (assetType === 'script') {
24
27
  (0, utils_js_1.assert)(mediaType === 'text/javascript');
25
- // Using <script async> seems problematic:
26
- // - in dev: https://github.com/vikejs/vike/issues/524
27
- // - in prod: https://github.com/vikejs/vike/issues/567
28
- return `<script type="module" src="${src}" defer></script>`;
28
+ return `<script src="${src}" ${scriptAttrs}></script>`;
29
29
  }
30
30
  if (assetType === 'style') {
31
31
  return `<link rel="stylesheet" type="text/css" href="${src}">`;
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.mergeScriptTags = void 0;
4
4
  const utils_js_1 = require("../../utils.js");
5
+ const inferHtmlTags_js_1 = require("./inferHtmlTags.js");
5
6
  const scriptRE = /(<script\b(?:\s[^>]*>|>))(.*?)<\/script>/gims;
6
7
  const srcRE = /\bsrc\s*=\s*(?:"([^"]+)"|'([^']+)'|([^\s'">]+))/im;
7
8
  const typeRE = /\btype\s*=\s*(?:"([^"]+)"|'([^']+)'|([^\s'">]+))/im;
@@ -34,7 +35,7 @@ function mergeScriptTags(scriptTagsHtml, isProduction) {
34
35
  }
35
36
  });
36
37
  if (contents.length > 0) {
37
- scriptTag += `<script type="module" defer>\n${contents.join('\n')}\n</script>`;
38
+ scriptTag += `<script ${inferHtmlTags_js_1.scriptAttrs}>\n${contents.join('\n')}\n</script>`;
38
39
  }
39
40
  }
40
41
  }
@@ -179,7 +179,7 @@ async function renderTemplate(templateContent, pageContext) {
179
179
  };
180
180
  (0, utils_js_1.assertUsage)(!(0, utils_js_1.isPromise)(templateVar), getErrMsg('a promise', `Did you forget to ${picocolors_1.default.cyan('await')} the promise?`));
181
181
  if (templateVar === undefined || templateVar === null) {
182
- (0, utils_js_1.assertWarning)(false, getErrMsg(`${picocolors_1.default.cyan(String(templateVar))} which will be converted to an empty string`, `Pass an empty string instead of ${picocolors_1.default.cyan(String(templateVar))} to remove this warning.`), { onlyOnce: false });
182
+ (0, utils_js_1.assertWarning)(false, getErrMsg(`${picocolors_1.default.cyan(String(templateVar))} which will be converted to an empty string`, `Pass the empty string ${picocolors_1.default.cyan("''")} instead of ${picocolors_1.default.cyan(String(templateVar))} to remove this warning.`), { onlyOnce: false });
183
183
  templateVar = '';
184
184
  }
185
185
  {
@@ -14,8 +14,7 @@ function retrieveAssetsProd(clientDependencies, clientManifest, includeAssetsImp
14
14
  if (onlyAssets) {
15
15
  if (!includeAssetsImportedByServer)
16
16
  return;
17
- // We assume that all npm packages have already built their Vike page files.
18
- // - Bundlers (Rollup, esbuild, tsup, ...) extract the CSS out of JavaScript => we can assume JavaScript to not import any CSS/assets
17
+ // We assume that all npm packages have already built their files: bundlers (Rollup, esbuild, tsup, ...) extract the CSS out of JavaScript => we can assume JavaScript to not import any CSS/assets.
19
18
  if ((0, utils_js_1.isNpmPackageImport)(id))
20
19
  return;
21
20
  if (id.includes('.page.server.')) {
@@ -52,7 +51,7 @@ function collectAssets(manifestKey, assetUrls, visistedAssets, manifest, onlyCol
52
51
  assetUrls.add(`/${asset}`);
53
52
  }
54
53
  }
55
- // Support `config.build.cssCodeSplit: false`, https://github.com/vikejs/vike/issues/644
54
+ // Support `config.build.cssCodeSplit: false`, see https://github.com/vikejs/vike/issues/644
56
55
  function collectSingleStyle(assetUrls, manifest) {
57
56
  const style = manifest['style.css'];
58
57
  if (style && Object.values(manifest).filter((asset) => asset.file.endsWith('.css')).length === 1) {
@@ -22,6 +22,12 @@ function sortPageAssetsForEarlyHintsHeader(pageAssets) {
22
22
  if (assetType === 'image')
23
23
  return priority;
24
24
  priority--;
25
+ if (assetType === 'video')
26
+ return priority;
27
+ priority--;
28
+ if (assetType === 'audio')
29
+ return priority;
30
+ priority--;
25
31
  // Others
26
32
  if (assetType !== 'script')
27
33
  return priority;
@@ -36,6 +36,44 @@ function inferMediaType(href) {
36
36
  if (href.endsWith('.woff2')) {
37
37
  return { assetType: 'font', mediaType: 'font/woff2' };
38
38
  }
39
+ // Videos
40
+ if (href.endsWith('.mp4')) {
41
+ return { assetType: 'video', mediaType: 'video/mp4' };
42
+ }
43
+ if (href.endsWith('.webm')) {
44
+ return { assetType: 'video', mediaType: 'video/webm' };
45
+ }
46
+ if (href.endsWith('.ogv')) {
47
+ return { assetType: 'video', mediaType: 'video/ogg' };
48
+ }
49
+ if (href.endsWith('.mpeg') || href.endsWith('.mpg')) {
50
+ return { assetType: 'video', mediaType: 'video/mpeg' };
51
+ }
52
+ if (href.endsWith('.avi')) {
53
+ return { assetType: 'video', mediaType: 'video/x-msvideo' };
54
+ }
55
+ if (href.endsWith('.mov') || href.endsWith('.qt')) {
56
+ return { assetType: 'video', mediaType: 'video/quicktime' };
57
+ }
58
+ // Audios
59
+ if (href.endsWith('.mp3')) {
60
+ return { assetType: 'audio', mediaType: 'audio/mpeg' };
61
+ }
62
+ if (href.endsWith('.wav')) {
63
+ return { assetType: 'audio', mediaType: 'audio/wav' };
64
+ }
65
+ if (href.endsWith('.ogg')) {
66
+ return { assetType: 'audio', mediaType: 'audio/ogg' };
67
+ }
68
+ if (href.endsWith('.m4a')) {
69
+ return { assetType: 'audio', mediaType: 'audio/aac' };
70
+ }
71
+ if (href.endsWith('midi') || href.endsWith('.mid')) {
72
+ return { assetType: 'audio', mediaType: 'audio/midi' };
73
+ }
74
+ if (href.endsWith('.flac')) {
75
+ return { assetType: 'audio', mediaType: 'audio/flac' };
76
+ }
39
77
  return null;
40
78
  }
41
79
  exports.inferMediaType = inferMediaType;
@@ -34,13 +34,20 @@ function assertExports(fileExports, filePathToShowToUser, configName) {
34
34
  if (exportsRelevant.length === 1) {
35
35
  return;
36
36
  }
37
- else {
38
- (0, utils_js_1.assert)(exportsRelevant.length === 0);
39
- let errMsg = `${filePathToShowToUser} doesn't export any value, but it should have a ${picocolors_1.default.cyan('export default')}`;
37
+ const exportDefault = picocolors_1.default.cyan('export default');
38
+ const exportConfigName = picocolors_1.default.cyan(`export { ${configName} }`);
39
+ if (exportsRelevant.length === 0) {
40
+ let errMsg = `${filePathToShowToUser} doesn't export any value, but it should have a ${exportDefault}`;
40
41
  if (configName)
41
- errMsg += ` or ${picocolors_1.default.cyan(`export { ${configName} }`)}`;
42
+ errMsg += ` or ${exportConfigName}`;
42
43
  (0, utils_js_1.assertUsage)(false, errMsg);
43
44
  }
45
+ else {
46
+ (0, utils_js_1.assert)(exportsRelevant.length === 2);
47
+ (0, utils_js_1.assertWarning)(false, `${filePathToShowToUser} remove ${exportConfigName} or ${exportDefault}`, {
48
+ onlyOnce: true
49
+ });
50
+ }
44
51
  }
45
52
  else {
46
53
  // !configName => isConfigFile
@@ -2,7 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.PROJECT_VERSION = exports.projectInfo = void 0;
4
4
  const assertSingleInstance_js_1 = require("./assertSingleInstance.js");
5
- const PROJECT_VERSION = '0.4.148';
5
+ const PROJECT_VERSION = '0.4.149-commit-041ee42';
6
6
  exports.PROJECT_VERSION = PROJECT_VERSION;
7
7
  const projectInfo = {
8
8
  projectName: 'Vike',
@@ -2,9 +2,15 @@ import { parse } from '@brillout/json-serializer/parse';
2
2
  import { hasProp, assert, assertUsage, objectAssign } from '../server-routing-runtime/utils.js';
3
3
  export { getPageContextSerializedInHtml };
4
4
  function getPageContextSerializedInHtml() {
5
+ // elem should exist because:
6
+ // 1. <script id="vike_pageContext" type="application/json"> appears before the <script> that loads Vike's client runtime (which includes this file)
7
+ // 2. <script id="vike_pageContext" type="application/json"> is neither async nor defer
8
+ // See https://github.com/vikejs/vike/pull/1271
5
9
  const id = 'vike_pageContext';
6
10
  const elem = document.getElementById(id);
7
- assertUsage(elem, `The element #${id} (which Vike automatically injects into the HTML) is missing from the DOM. This may happen if your HTML is malformed. Make sure your HTML isn't malformed, and make sure you don't remove #${id} from the HTML nor from the DOM.`);
11
+ assertUsage(elem,
12
+ // It seems like it can be missing because of malformed HTML: https://github.com/vikejs/vike/issues/913
13
+ `Couldn't find #${id} (which Vike automatically injects in the HTML): make sure it exists (i.e. don't remove it and make sure your HTML isn't malformed)`);
8
14
  const pageContextJson = elem.textContent;
9
15
  assert(pageContextJson);
10
16
  const pageContextSerializedInHtml = parse(pageContextJson);