vike 0.4.237-commit-33e34e7 → 0.4.238-commit-5762291

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 (27) hide show
  1. package/dist/cjs/node/runtime/csp.js +46 -0
  2. package/dist/cjs/node/runtime/html/injectAssets/getHtmlTags.js +10 -7
  3. package/dist/cjs/node/runtime/html/injectAssets/inferHtmlTags.js +5 -2
  4. package/dist/cjs/node/runtime/html/injectAssets/mergeScriptTags.js +4 -2
  5. package/dist/cjs/node/runtime/renderPage/loadPageConfigsLazyServerSide.js +4 -0
  6. package/dist/cjs/node/vite/shared/resolveVikeConfigInternal/configDefinitionsBuiltIn.js +3 -0
  7. package/dist/cjs/shared/page-configs/resolveVikeConfigPublic.js +2 -1
  8. package/dist/cjs/utils/PROJECT_VERSION.js +1 -1
  9. package/dist/esm/node/prerender/runPrerender.d.ts +2 -0
  10. package/dist/esm/node/runtime/csp.d.ts +12 -0
  11. package/dist/esm/node/runtime/csp.js +44 -0
  12. package/dist/esm/node/runtime/html/injectAssets/getHtmlTags.js +10 -7
  13. package/dist/esm/node/runtime/html/injectAssets/inferHtmlTags.d.ts +2 -1
  14. package/dist/esm/node/runtime/html/injectAssets/inferHtmlTags.js +5 -2
  15. package/dist/esm/node/runtime/html/injectAssets/mergeScriptTags.d.ts +2 -1
  16. package/dist/esm/node/runtime/html/injectAssets/mergeScriptTags.js +4 -2
  17. package/dist/esm/node/runtime/html/serializeContext.d.ts +2 -1
  18. package/dist/esm/node/runtime/renderPage/loadPageConfigsLazyServerSide.d.ts +2 -0
  19. package/dist/esm/node/runtime/renderPage/loadPageConfigsLazyServerSide.js +4 -0
  20. package/dist/esm/node/runtime/renderPage/renderPageAfterRoute.d.ts +4 -0
  21. package/dist/esm/node/vite/shared/resolveVikeConfigInternal/configDefinitionsBuiltIn.js +3 -0
  22. package/dist/esm/shared/page-configs/resolveVikeConfigPublic.js +2 -1
  23. package/dist/esm/types/Config.d.ts +8 -0
  24. package/dist/esm/types/PageContext.d.ts +6 -0
  25. package/dist/esm/utils/PROJECT_VERSION.d.ts +1 -1
  26. package/dist/esm/utils/PROJECT_VERSION.js +1 -1
  27. package/package.json +1 -1
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolvePageContextCspNone = resolvePageContextCspNone;
4
+ exports.inferNonceAttr = inferNonceAttr;
5
+ exports.addCspHeader = addCspHeader;
6
+ const import_1 = require("@brillout/import");
7
+ async function resolvePageContextCspNone(pageContext) {
8
+ if (pageContext.cspNonce)
9
+ return; // already set by user e.g. `renderPage({ cspNonce: '123456789' })`
10
+ const { csp } = pageContext.config;
11
+ if (!csp?.nonce)
12
+ return;
13
+ let cspNonce;
14
+ if (csp.nonce === true) {
15
+ cspNonce = await generateNonce();
16
+ }
17
+ else {
18
+ cspNonce = await csp.nonce(pageContext);
19
+ }
20
+ const pageContextAddendum = { cspNonce };
21
+ return pageContextAddendum;
22
+ }
23
+ // Generate a cryptographically secure nonce for Content Security Policy (CSP).
24
+ // Returns a base64url-encoded nonce string (URL-safe, no padding).
25
+ // https://github.com/vikejs/vike/issues/1554#issuecomment-3181128304
26
+ async function generateNonce() {
27
+ let cryptoModule;
28
+ try {
29
+ cryptoModule = (await (0, import_1.import_)('crypto')).default;
30
+ }
31
+ catch {
32
+ return Math.random().toString(36).substring(2, 18);
33
+ }
34
+ return cryptoModule.randomBytes(16).toString('base64url');
35
+ }
36
+ function inferNonceAttr(pageContext) {
37
+ const nonceAttr = pageContext.cspNonce ? ` nonce="${pageContext.cspNonce}"` : '';
38
+ return nonceAttr;
39
+ }
40
+ function addCspHeader(pageContext, headersResponse) {
41
+ if (!pageContext.cspNonce)
42
+ return;
43
+ if (headersResponse.get('Content-Security-Policy'))
44
+ return;
45
+ headersResponse.set('Content-Security-Policy', `script-src 'self' 'nonce-${pageContext.cspNonce}'`);
46
+ }
@@ -15,6 +15,7 @@ const picocolors_1 = __importDefault(require("@brillout/picocolors"));
15
15
  const getConfigDefinedAt_js_1 = require("../../../../shared/page-configs/getConfigDefinedAt.js");
16
16
  const htmlElementIds_js_1 = require("../../../../shared/htmlElementIds.js");
17
17
  const isFontFallback_js_1 = require("../../renderPage/isFontFallback.js");
18
+ const csp_js_1 = require("../../csp.js");
18
19
  const stamp = '__injectFilterEntry';
19
20
  async function getHtmlTags(pageContext, streamFromReactStreamingPackage, injectFilter, pageAssets, viteDevScript, isStream) {
20
21
  (0, utils_js_1.assert)([true, false].includes(pageContext._isHtmlOnly));
@@ -81,7 +82,7 @@ async function getHtmlTags(pageContext, streamFromReactStreamingPackage, injectF
81
82
  .forEach((asset) => {
82
83
  if (!asset.inject)
83
84
  return;
84
- const htmlTag = asset.isEntry ? (0, inferHtmlTags_js_1.inferAssetTag)(asset) : (0, inferHtmlTags_js_1.inferPreloadTag)(asset);
85
+ const htmlTag = asset.isEntry ? (0, inferHtmlTags_js_1.inferAssetTag)(asset, pageContext) : (0, inferHtmlTags_js_1.inferPreloadTag)(asset);
85
86
  htmlTags.push({ htmlTag, position: asset.inject });
86
87
  });
87
88
  // ==========
@@ -142,7 +143,7 @@ async function getHtmlTags(pageContext, streamFromReactStreamingPackage, injectF
142
143
  });
143
144
  }
144
145
  // The JavaScript entry <script> tag
145
- const scriptEntry = mergeScriptEntries(pageAssets, viteDevScript);
146
+ const scriptEntry = mergeScriptEntries(pageAssets, viteDevScript, pageContext);
146
147
  if (scriptEntry) {
147
148
  htmlTags.push({
148
149
  htmlTag: scriptEntry,
@@ -163,9 +164,9 @@ async function getHtmlTags(pageContext, streamFromReactStreamingPackage, injectF
163
164
  });
164
165
  return htmlTags;
165
166
  }
166
- function mergeScriptEntries(pageAssets, viteDevScript) {
167
+ function mergeScriptEntries(pageAssets, viteDevScript, pageContext) {
167
168
  const scriptEntries = pageAssets.filter((pageAsset) => pageAsset.isEntry && pageAsset.assetType === 'script');
168
- let scriptEntry = `${viteDevScript}${scriptEntries.map((asset) => (0, inferHtmlTags_js_1.inferAssetTag)(asset)).join('')}`;
169
+ let scriptEntry = `${viteDevScript}${scriptEntries.map((asset) => (0, inferHtmlTags_js_1.inferAssetTag)(asset, pageContext)).join('')}`;
169
170
  // We merge scripts to avoid the infamous HMR preamble error.
170
171
  // - Infamous HMR preamble error:
171
172
  // ```browser-console
@@ -183,12 +184,13 @@ function mergeScriptEntries(pageAssets, viteDevScript) {
183
184
  // ```
184
185
  // - Maybe an alternative would be to make Vike's client runtime entry <script> tag non-async. Would that work? Would it be a performance issue?
185
186
  // - The entry <script> shouldn't be `<script defer>` upon HTML streaming, otherwise progressive hydration while SSR streaming won't work.
186
- scriptEntry = (0, mergeScriptTags_js_1.mergeScriptTags)(scriptEntry);
187
+ scriptEntry = (0, mergeScriptTags_js_1.mergeScriptTags)(scriptEntry, pageContext);
187
188
  return scriptEntry;
188
189
  }
189
190
  function getPageContextJsonScriptTag(pageContext) {
190
191
  const pageContextClientSerialized = (0, sanitizeJson_js_1.sanitizeJson)((0, serializeContext_js_1.getPageContextClientSerialized)(pageContext, true));
191
- const htmlTag = `<script id="${htmlElementIds_js_1.htmlElementId_pageContext}" type="application/json">${pageContextClientSerialized}</script>`;
192
+ const nonceAttr = (0, csp_js_1.inferNonceAttr)(pageContext);
193
+ const htmlTag = `<script id="${htmlElementIds_js_1.htmlElementId_pageContext}" type="application/json"${nonceAttr}>${pageContextClientSerialized}</script>`;
192
194
  // Used by contra.com https://github.com/gajus
193
195
  // @ts-expect-error
194
196
  pageContext._pageContextHtmlTag = htmlTag;
@@ -196,7 +198,8 @@ function getPageContextJsonScriptTag(pageContext) {
196
198
  }
197
199
  function getGlobalContextJsonScriptTag(pageContext) {
198
200
  const globalContextClientSerialized = (0, sanitizeJson_js_1.sanitizeJson)((0, serializeContext_js_1.getGlobalContextClientSerialized)(pageContext, true));
199
- const htmlTag = `<script id="${htmlElementIds_js_1.htmlElementId_globalContext}" type="application/json">${globalContextClientSerialized}</script>`;
201
+ const nonceAttr = (0, csp_js_1.inferNonceAttr)(pageContext);
202
+ const htmlTag = `<script id="${htmlElementIds_js_1.htmlElementId_globalContext}" type="application/json"${nonceAttr}>${globalContextClientSerialized}</script>`;
200
203
  return htmlTag;
201
204
  }
202
205
  function assertInjectFilterEntries(injectFilterEntries) {
@@ -5,6 +5,8 @@ exports.inferAssetTag = inferAssetTag;
5
5
  exports.inferPreloadTag = inferPreloadTag;
6
6
  exports.inferEarlyHintLink = inferEarlyHintLink;
7
7
  const utils_js_1 = require("../../utils.js");
8
+ const csp_js_1 = require("../../csp.js");
9
+ // TODO/now rename scriptAttrs scriptCommonAttrs
8
10
  // We can't use `defer` here. With `defer`, the entry script won't start before `</body>` has been parsed, preventing progressive hydration during SSR streaming, see https://github.com/vikejs/vike/pull/1271
9
11
  const scriptAttrs = 'type="module" async';
10
12
  exports.scriptAttrs = scriptAttrs;
@@ -23,11 +25,12 @@ function inferPreloadTag(pageAsset) {
23
25
  .join(' ');
24
26
  return `<link ${attributes}>`;
25
27
  }
26
- function inferAssetTag(pageAsset) {
28
+ function inferAssetTag(pageAsset, pageContext) {
27
29
  const { src, assetType, mediaType } = pageAsset;
28
30
  if (assetType === 'script') {
29
31
  (0, utils_js_1.assert)(mediaType === 'text/javascript');
30
- return `<script src="${src}" ${scriptAttrs}></script>`;
32
+ const nonceAttr = (0, csp_js_1.inferNonceAttr)(pageContext);
33
+ return `<script src="${src}" ${scriptAttrs}${nonceAttr}></script>`;
31
34
  }
32
35
  if (assetType === 'style') {
33
36
  // WARNING: if changing following line, then also update https://github.com/vikejs/vike/blob/fae90a15d88e5e87ca9fcbb54cf2dc8773d2f229/vike/client/shared/removeFoucBuster.ts#L29
@@ -1,12 +1,13 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.mergeScriptTags = mergeScriptTags;
4
+ const csp_js_1 = require("../../csp.js");
4
5
  const utils_js_1 = require("../../utils.js");
5
6
  const inferHtmlTags_js_1 = require("./inferHtmlTags.js");
6
7
  const scriptRE = /(<script\b(?:\s[^>]*>|>))(.*?)<\/script>/gims;
7
8
  const srcRE = /\bsrc\s*=\s*(?:"([^"]+)"|'([^']+)'|([^\s'">]+))/im;
8
9
  const typeRE = /\btype\s*=\s*(?:"([^"]+)"|'([^']+)'|([^\s'">]+))/im;
9
- function mergeScriptTags(scriptTagsHtml) {
10
+ function mergeScriptTags(scriptTagsHtml, pageContext) {
10
11
  let scriptTag = '';
11
12
  const scripts = parseScripts(scriptTagsHtml);
12
13
  // We need to merge module scripts to ensure execution order
@@ -35,7 +36,8 @@ function mergeScriptTags(scriptTagsHtml) {
35
36
  }
36
37
  });
37
38
  if (contents.length > 0) {
38
- scriptTag += `<script ${inferHtmlTags_js_1.scriptAttrs}>\n${contents.join('\n')}\n</script>`;
39
+ const nonceAttr = (0, csp_js_1.inferNonceAttr)(pageContext);
40
+ scriptTag += `<script ${inferHtmlTags_js_1.scriptAttrs}${nonceAttr}>\n${contents.join('\n')}\n</script>`;
39
41
  }
40
42
  }
41
43
  }
@@ -11,6 +11,7 @@ const analyzePage_js_1 = require("./analyzePage.js");
11
11
  const loadAndParseVirtualFilePageEntry_js_1 = require("../../../shared/page-configs/loadAndParseVirtualFilePageEntry.js");
12
12
  const execHookServer_js_1 = require("./execHookServer.js");
13
13
  const getCacheControl_js_1 = require("./getCacheControl.js");
14
+ const csp_js_1 = require("../csp.js");
14
15
  async function loadPageConfigsLazyServerSide(pageContext) {
15
16
  (0, utils_js_1.objectAssign)(pageContext, {
16
17
  _pageConfig: (0, findPageConfig_js_1.findPageConfig)(pageContext._globalContext._pageConfigs, pageContext.pageId),
@@ -55,6 +56,7 @@ async function resolvePageContext(pageContext) {
55
56
  passToClient.push(...valS);
56
57
  });
57
58
  }
59
+ (0, utils_js_1.objectAssign)(pageContext, await (0, csp_js_1.resolvePageContextCspNone)(pageContext));
58
60
  (0, utils_js_1.objectAssign)(pageContext, {
59
61
  Page: pageContext.exports.Page,
60
62
  _isHtmlOnly: isHtmlOnly,
@@ -125,6 +127,7 @@ async function loadPageUserFiles_v1Design(pageContext) {
125
127
  pageFilesLoaded: pageFilesServerSide,
126
128
  };
127
129
  }
130
+ // TODO/now: move all response headers code to headersResponse.ts
128
131
  function resolveHeadersResponse(pageContext) {
129
132
  const headersResponse = mergeHeaders(pageContext.config.headersResponse);
130
133
  if (!headersResponse.get('Cache-Control')) {
@@ -132,6 +135,7 @@ function resolveHeadersResponse(pageContext) {
132
135
  if (cacheControl)
133
136
  headersResponse.set('Cache-Control', cacheControl);
134
137
  }
138
+ (0, csp_js_1.addCspHeader)(pageContext, headersResponse);
135
139
  return headersResponse;
136
140
  }
137
141
  function mergeHeaders(headersList = []) {
@@ -173,6 +173,9 @@ const configDefinitionsBuiltIn = {
173
173
  env: { config: true },
174
174
  global: true,
175
175
  },
176
+ csp: {
177
+ env: { server: true },
178
+ },
176
179
  injectScriptsAt: {
177
180
  env: { server: true },
178
181
  },
@@ -1,9 +1,10 @@
1
1
  "use strict";
2
- // TO-DO/soon: rename PageConfig names
2
+ // TODO/now: rename PageConfig names
3
3
  // - Use `Internal` suffix, i.e. {Page,Global}ConfigInternal
4
4
  // - While keeping {Page,Global}ConfigPublic or remove Public suffix and rename it to {Page,Global}Config ?
5
5
  // - rename EagerLoaded EagerlyLoaded
6
6
  // - remove `LazyLoaded` suffix
7
+ // TODO/now: rename VikeConfigPublicPageLazyLoaded PageContextSomething (for `pageContext: PageContextSomething` usage)
7
8
  var __importDefault = (this && this.__importDefault) || function (mod) {
8
9
  return (mod && mod.__esModule) ? mod : { "default": mod };
9
10
  };
@@ -2,4 +2,4 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.PROJECT_VERSION = void 0;
4
4
  // Automatically updated by @brillout/release-me
5
- exports.PROJECT_VERSION = '0.4.237-commit-33e34e7';
5
+ exports.PROJECT_VERSION = '0.4.238-commit-5762291';
@@ -174,6 +174,8 @@ declare function createPageContextPrerendering(urlOriginal: string, prerenderCon
174
174
  _pageConfig: null | import("../../types/PageConfig.js").PageConfigRuntime;
175
175
  } & import("../../shared/getPageFiles.js").VikeConfigPublicPageLazyLoaded & {
176
176
  _pageConfig: null | import("../../types/PageConfig.js").PageConfigRuntime;
177
+ } & {
178
+ cspNonce: string;
177
179
  } & {
178
180
  Page: unknown;
179
181
  _isHtmlOnly: boolean;
@@ -0,0 +1,12 @@
1
+ export { resolvePageContextCspNone };
2
+ export { inferNonceAttr };
3
+ export { addCspHeader };
4
+ export type { PageContextCspNonce };
5
+ import type { VikeConfigPublicPageLazyLoaded } from '../../shared/getPageFiles.js';
6
+ import type { PageContextServer } from '../../types/PageContext.js';
7
+ declare function resolvePageContextCspNone(pageContext: VikeConfigPublicPageLazyLoaded & PageContextCspNonce): Promise<{
8
+ cspNonce: string;
9
+ } | undefined>;
10
+ type PageContextCspNonce = Pick<PageContextServer, 'cspNonce'>;
11
+ declare function inferNonceAttr(pageContext: PageContextCspNonce): string;
12
+ declare function addCspHeader(pageContext: PageContextCspNonce, headersResponse: Headers): void;
@@ -0,0 +1,44 @@
1
+ export { resolvePageContextCspNone };
2
+ export { inferNonceAttr };
3
+ export { addCspHeader };
4
+ import { import_ } from '@brillout/import';
5
+ async function resolvePageContextCspNone(pageContext) {
6
+ if (pageContext.cspNonce)
7
+ return; // already set by user e.g. `renderPage({ cspNonce: '123456789' })`
8
+ const { csp } = pageContext.config;
9
+ if (!csp?.nonce)
10
+ return;
11
+ let cspNonce;
12
+ if (csp.nonce === true) {
13
+ cspNonce = await generateNonce();
14
+ }
15
+ else {
16
+ cspNonce = await csp.nonce(pageContext);
17
+ }
18
+ const pageContextAddendum = { cspNonce };
19
+ return pageContextAddendum;
20
+ }
21
+ // Generate a cryptographically secure nonce for Content Security Policy (CSP).
22
+ // Returns a base64url-encoded nonce string (URL-safe, no padding).
23
+ // https://github.com/vikejs/vike/issues/1554#issuecomment-3181128304
24
+ async function generateNonce() {
25
+ let cryptoModule;
26
+ try {
27
+ cryptoModule = (await import_('crypto')).default;
28
+ }
29
+ catch {
30
+ return Math.random().toString(36).substring(2, 18);
31
+ }
32
+ return cryptoModule.randomBytes(16).toString('base64url');
33
+ }
34
+ function inferNonceAttr(pageContext) {
35
+ const nonceAttr = pageContext.cspNonce ? ` nonce="${pageContext.cspNonce}"` : '';
36
+ return nonceAttr;
37
+ }
38
+ function addCspHeader(pageContext, headersResponse) {
39
+ if (!pageContext.cspNonce)
40
+ return;
41
+ if (headersResponse.get('Content-Security-Policy'))
42
+ return;
43
+ headersResponse.set('Content-Security-Policy', `script-src 'self' 'nonce-${pageContext.cspNonce}'`);
44
+ }
@@ -10,6 +10,7 @@ import pc from '@brillout/picocolors';
10
10
  import { getConfigDefinedAt } from '../../../../shared/page-configs/getConfigDefinedAt.js';
11
11
  import { htmlElementId_globalContext, htmlElementId_pageContext } from '../../../../shared/htmlElementIds.js';
12
12
  import { isFontFallback } from '../../renderPage/isFontFallback.js';
13
+ import { inferNonceAttr } from '../../csp.js';
13
14
  const stamp = '__injectFilterEntry';
14
15
  async function getHtmlTags(pageContext, streamFromReactStreamingPackage, injectFilter, pageAssets, viteDevScript, isStream) {
15
16
  assert([true, false].includes(pageContext._isHtmlOnly));
@@ -76,7 +77,7 @@ async function getHtmlTags(pageContext, streamFromReactStreamingPackage, injectF
76
77
  .forEach((asset) => {
77
78
  if (!asset.inject)
78
79
  return;
79
- const htmlTag = asset.isEntry ? inferAssetTag(asset) : inferPreloadTag(asset);
80
+ const htmlTag = asset.isEntry ? inferAssetTag(asset, pageContext) : inferPreloadTag(asset);
80
81
  htmlTags.push({ htmlTag, position: asset.inject });
81
82
  });
82
83
  // ==========
@@ -137,7 +138,7 @@ async function getHtmlTags(pageContext, streamFromReactStreamingPackage, injectF
137
138
  });
138
139
  }
139
140
  // The JavaScript entry <script> tag
140
- const scriptEntry = mergeScriptEntries(pageAssets, viteDevScript);
141
+ const scriptEntry = mergeScriptEntries(pageAssets, viteDevScript, pageContext);
141
142
  if (scriptEntry) {
142
143
  htmlTags.push({
143
144
  htmlTag: scriptEntry,
@@ -158,9 +159,9 @@ async function getHtmlTags(pageContext, streamFromReactStreamingPackage, injectF
158
159
  });
159
160
  return htmlTags;
160
161
  }
161
- function mergeScriptEntries(pageAssets, viteDevScript) {
162
+ function mergeScriptEntries(pageAssets, viteDevScript, pageContext) {
162
163
  const scriptEntries = pageAssets.filter((pageAsset) => pageAsset.isEntry && pageAsset.assetType === 'script');
163
- let scriptEntry = `${viteDevScript}${scriptEntries.map((asset) => inferAssetTag(asset)).join('')}`;
164
+ let scriptEntry = `${viteDevScript}${scriptEntries.map((asset) => inferAssetTag(asset, pageContext)).join('')}`;
164
165
  // We merge scripts to avoid the infamous HMR preamble error.
165
166
  // - Infamous HMR preamble error:
166
167
  // ```browser-console
@@ -178,12 +179,13 @@ function mergeScriptEntries(pageAssets, viteDevScript) {
178
179
  // ```
179
180
  // - Maybe an alternative would be to make Vike's client runtime entry <script> tag non-async. Would that work? Would it be a performance issue?
180
181
  // - The entry <script> shouldn't be `<script defer>` upon HTML streaming, otherwise progressive hydration while SSR streaming won't work.
181
- scriptEntry = mergeScriptTags(scriptEntry);
182
+ scriptEntry = mergeScriptTags(scriptEntry, pageContext);
182
183
  return scriptEntry;
183
184
  }
184
185
  function getPageContextJsonScriptTag(pageContext) {
185
186
  const pageContextClientSerialized = sanitizeJson(getPageContextClientSerialized(pageContext, true));
186
- const htmlTag = `<script id="${htmlElementId_pageContext}" type="application/json">${pageContextClientSerialized}</script>`;
187
+ const nonceAttr = inferNonceAttr(pageContext);
188
+ const htmlTag = `<script id="${htmlElementId_pageContext}" type="application/json"${nonceAttr}>${pageContextClientSerialized}</script>`;
187
189
  // Used by contra.com https://github.com/gajus
188
190
  // @ts-expect-error
189
191
  pageContext._pageContextHtmlTag = htmlTag;
@@ -191,7 +193,8 @@ function getPageContextJsonScriptTag(pageContext) {
191
193
  }
192
194
  function getGlobalContextJsonScriptTag(pageContext) {
193
195
  const globalContextClientSerialized = sanitizeJson(getGlobalContextClientSerialized(pageContext, true));
194
- const htmlTag = `<script id="${htmlElementId_globalContext}" type="application/json">${globalContextClientSerialized}</script>`;
196
+ const nonceAttr = inferNonceAttr(pageContext);
197
+ const htmlTag = `<script id="${htmlElementId_globalContext}" type="application/json"${nonceAttr}>${globalContextClientSerialized}</script>`;
195
198
  return htmlTag;
196
199
  }
197
200
  function assertInjectFilterEntries(injectFilterEntries) {
@@ -3,7 +3,8 @@ export { inferPreloadTag };
3
3
  export { inferEarlyHintLink };
4
4
  export { scriptAttrs };
5
5
  import type { PageAsset } from '../../renderPage/getPageAssets.js';
6
+ import { type PageContextCspNonce } from '../../csp.js';
6
7
  declare const scriptAttrs = "type=\"module\" async";
7
8
  declare function inferPreloadTag(pageAsset: PageAsset): string;
8
- declare function inferAssetTag(pageAsset: PageAsset): string;
9
+ declare function inferAssetTag(pageAsset: PageAsset, pageContext: PageContextCspNonce): string;
9
10
  declare function inferEarlyHintLink(pageAsset: PageAsset): string;
@@ -3,6 +3,8 @@ export { inferPreloadTag };
3
3
  export { inferEarlyHintLink };
4
4
  export { scriptAttrs };
5
5
  import { assert } from '../../utils.js';
6
+ import { inferNonceAttr } from '../../csp.js';
7
+ // TODO/now rename scriptAttrs scriptCommonAttrs
6
8
  // We can't use `defer` here. With `defer`, the entry script won't start before `</body>` has been parsed, preventing progressive hydration during SSR streaming, see https://github.com/vikejs/vike/pull/1271
7
9
  const scriptAttrs = 'type="module" async';
8
10
  function inferPreloadTag(pageAsset) {
@@ -20,11 +22,12 @@ function inferPreloadTag(pageAsset) {
20
22
  .join(' ');
21
23
  return `<link ${attributes}>`;
22
24
  }
23
- function inferAssetTag(pageAsset) {
25
+ function inferAssetTag(pageAsset, pageContext) {
24
26
  const { src, assetType, mediaType } = pageAsset;
25
27
  if (assetType === 'script') {
26
28
  assert(mediaType === 'text/javascript');
27
- return `<script src="${src}" ${scriptAttrs}></script>`;
29
+ const nonceAttr = inferNonceAttr(pageContext);
30
+ return `<script src="${src}" ${scriptAttrs}${nonceAttr}></script>`;
28
31
  }
29
32
  if (assetType === 'style') {
30
33
  // WARNING: if changing following line, then also update https://github.com/vikejs/vike/blob/fae90a15d88e5e87ca9fcbb54cf2dc8773d2f229/vike/client/shared/removeFoucBuster.ts#L29
@@ -1,2 +1,3 @@
1
1
  export { mergeScriptTags };
2
- declare function mergeScriptTags(scriptTagsHtml: string): string;
2
+ import { type PageContextCspNonce } from '../../csp.js';
3
+ declare function mergeScriptTags(scriptTagsHtml: string, pageContext: PageContextCspNonce): string;
@@ -1,10 +1,11 @@
1
1
  export { mergeScriptTags };
2
+ import { inferNonceAttr } from '../../csp.js';
2
3
  import { assert } from '../../utils.js';
3
4
  import { scriptAttrs } from './inferHtmlTags.js';
4
5
  const scriptRE = /(<script\b(?:\s[^>]*>|>))(.*?)<\/script>/gims;
5
6
  const srcRE = /\bsrc\s*=\s*(?:"([^"]+)"|'([^']+)'|([^\s'">]+))/im;
6
7
  const typeRE = /\btype\s*=\s*(?:"([^"]+)"|'([^']+)'|([^\s'">]+))/im;
7
- function mergeScriptTags(scriptTagsHtml) {
8
+ function mergeScriptTags(scriptTagsHtml, pageContext) {
8
9
  let scriptTag = '';
9
10
  const scripts = parseScripts(scriptTagsHtml);
10
11
  // We need to merge module scripts to ensure execution order
@@ -33,7 +34,8 @@ function mergeScriptTags(scriptTagsHtml) {
33
34
  }
34
35
  });
35
36
  if (contents.length > 0) {
36
- scriptTag += `<script ${scriptAttrs}>\n${contents.join('\n')}\n</script>`;
37
+ const nonceAttr = inferNonceAttr(pageContext);
38
+ scriptTag += `<script ${scriptAttrs}${nonceAttr}>\n${contents.join('\n')}\n</script>`;
37
39
  }
38
40
  }
39
41
  }
@@ -8,6 +8,7 @@ import type { UrlRedirect } from '../../../shared/route/abort.js';
8
8
  import type { GlobalContextServerInternal } from '../globalContext.js';
9
9
  import type { PageContextCreated } from '../renderPage/createPageContextServerSide.js';
10
10
  import type { PageContextBegin } from '../renderPage.js';
11
+ import type { PageContextCspNonce } from '../csp.js';
11
12
  type PageContextSerialization = PageContextCreated & {
12
13
  pageId: string;
13
14
  routeParams: Record<string, string>;
@@ -17,7 +18,7 @@ type PageContextSerialization = PageContextCreated & {
17
18
  _pageContextInit: Record<string, unknown>;
18
19
  _globalContext: GlobalContextServerInternal;
19
20
  _isPageContextJsonRequest: null | PageContextBegin['_isPageContextJsonRequest'];
20
- };
21
+ } & PageContextCspNonce;
21
22
  declare function getPageContextClientSerialized(pageContext: PageContextSerialization, isHtmlJsonScript: boolean): string;
22
23
  declare function getGlobalContextClientSerialized(pageContext: PageContextSerialization, isHtmlJsonScript: boolean): string;
23
24
  type PassToClient = string[];
@@ -111,6 +111,8 @@ declare function loadPageConfigsLazyServerSide(pageContext: PageContext_loadPage
111
111
  _pageConfig: null | PageConfigRuntime;
112
112
  } & VikeConfigPublicPageLazyLoaded & {
113
113
  _pageConfig: null | PageConfigRuntime;
114
+ } & {
115
+ cspNonce: string;
114
116
  } & {
115
117
  Page: unknown;
116
118
  _isHtmlOnly: boolean;
@@ -9,6 +9,7 @@ import { analyzePage } from './analyzePage.js';
9
9
  import { loadAndParseVirtualFilePageEntry } from '../../../shared/page-configs/loadAndParseVirtualFilePageEntry.js';
10
10
  import { execHookServer } from './execHookServer.js';
11
11
  import { getCacheControl } from './getCacheControl.js';
12
+ import { addCspHeader, resolvePageContextCspNone } from '../csp.js';
12
13
  async function loadPageConfigsLazyServerSide(pageContext) {
13
14
  objectAssign(pageContext, {
14
15
  _pageConfig: findPageConfig(pageContext._globalContext._pageConfigs, pageContext.pageId),
@@ -53,6 +54,7 @@ async function resolvePageContext(pageContext) {
53
54
  passToClient.push(...valS);
54
55
  });
55
56
  }
57
+ objectAssign(pageContext, await resolvePageContextCspNone(pageContext));
56
58
  objectAssign(pageContext, {
57
59
  Page: pageContext.exports.Page,
58
60
  _isHtmlOnly: isHtmlOnly,
@@ -123,6 +125,7 @@ async function loadPageUserFiles_v1Design(pageContext) {
123
125
  pageFilesLoaded: pageFilesServerSide,
124
126
  };
125
127
  }
128
+ // TODO/now: move all response headers code to headersResponse.ts
126
129
  function resolveHeadersResponse(pageContext) {
127
130
  const headersResponse = mergeHeaders(pageContext.config.headersResponse);
128
131
  if (!headersResponse.get('Cache-Control')) {
@@ -130,6 +133,7 @@ function resolveHeadersResponse(pageContext) {
130
133
  if (cacheControl)
131
134
  headersResponse.set('Cache-Control', cacheControl);
132
135
  }
136
+ addCspHeader(pageContext, headersResponse);
133
137
  return headersResponse;
134
138
  }
135
139
  function mergeHeaders(headersList = []) {
@@ -127,6 +127,8 @@ declare function prerenderPage(pageContext: PageContextCreated & PageConfigsLazy
127
127
  _pageConfig: null | import("../../../types/PageConfig.js").PageConfigRuntime;
128
128
  } & import("../../../shared/getPageFiles.js").VikeConfigPublicPageLazyLoaded & {
129
129
  _pageConfig: null | import("../../../types/PageConfig.js").PageConfigRuntime;
130
+ } & {
131
+ cspNonce: string;
130
132
  } & {
131
133
  Page: unknown;
132
134
  _isHtmlOnly: boolean;
@@ -246,6 +248,8 @@ declare function prerenderPage(pageContext: PageContextCreated & PageConfigsLazy
246
248
  _pageConfig: null | import("../../../types/PageConfig.js").PageConfigRuntime;
247
249
  } & import("../../../shared/getPageFiles.js").VikeConfigPublicPageLazyLoaded & {
248
250
  _pageConfig: null | import("../../../types/PageConfig.js").PageConfigRuntime;
251
+ } & {
252
+ cspNonce: string;
249
253
  } & {
250
254
  Page: unknown;
251
255
  _isHtmlOnly: boolean;
@@ -171,6 +171,9 @@ const configDefinitionsBuiltIn = {
171
171
  env: { config: true },
172
172
  global: true,
173
173
  },
174
+ csp: {
175
+ env: { server: true },
176
+ },
174
177
  injectScriptsAt: {
175
178
  env: { server: true },
176
179
  },
@@ -1,8 +1,9 @@
1
- // TO-DO/soon: rename PageConfig names
1
+ // TODO/now: rename PageConfig names
2
2
  // - Use `Internal` suffix, i.e. {Page,Global}ConfigInternal
3
3
  // - While keeping {Page,Global}ConfigPublic or remove Public suffix and rename it to {Page,Global}Config ?
4
4
  // - rename EagerLoaded EagerlyLoaded
5
5
  // - remove `LazyLoaded` suffix
6
+ // TODO/now: rename VikeConfigPublicPageLazyLoaded PageContextSomething (for `pageContext: PageContextSomething` usage)
6
7
  // TO-DO/soon/same-api: use public API internally?
7
8
  // TO-DO/soon/flat-pageContext: rename definedAt => definedBy
8
9
  export { resolveVikeConfigPublicGlobal };
@@ -468,6 +468,14 @@ type ConfigBuiltIn = {
468
468
  * https://vike.dev/mode
469
469
  */
470
470
  mode?: string;
471
+ /**
472
+ * Content Security Policy (CSP).
473
+ *
474
+ * https://vike.dev/csp
475
+ */
476
+ csp?: {
477
+ nonce: boolean | ((pageContext: PageContextServer) => string | Promise<string>);
478
+ };
471
479
  /** Where scripts are injected in the HTML.
472
480
  *
473
481
  * https://vike.dev/injectScriptsAt
@@ -183,6 +183,12 @@ type PageContextBuiltInServer<Data> = PageContextBuiltInCommon<Data> & PageConte
183
183
  * https://vike.dev/pageContext#globalContext
184
184
  */
185
185
  globalContext: GlobalContextServer;
186
+ /**
187
+ * The CSP nonce.
188
+ *
189
+ * https://vike.dev/csp
190
+ */
191
+ cspNonce?: string;
186
192
  isHydration?: undefined;
187
193
  isBackwardNavigation?: undefined;
188
194
  previousPageContext?: undefined;
@@ -1 +1 @@
1
- export declare const PROJECT_VERSION: "0.4.237-commit-33e34e7";
1
+ export declare const PROJECT_VERSION: "0.4.238-commit-5762291";
@@ -1,2 +1,2 @@
1
1
  // Automatically updated by @brillout/release-me
2
- export const PROJECT_VERSION = '0.4.237-commit-33e34e7';
2
+ export const PROJECT_VERSION = '0.4.238-commit-5762291';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vike",
3
- "version": "0.4.237-commit-33e34e7",
3
+ "version": "0.4.238-commit-5762291",
4
4
  "repository": "https://github.com/vikejs/vike",
5
5
  "exports": {
6
6
  "./server": {