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