Skip to content

Commit 2457da4

Browse files
vite (start): Enable httpsDevServer config option (#1227)
Co-authored-by: Dan Drory <[email protected]>
1 parent a0d8200 commit 2457da4

File tree

12 files changed

+108
-34
lines changed

12 files changed

+108
-34
lines changed

.changeset/stupid-onions-speak.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'sku': minor
3+
---
4+
5+
`vite (start)`: Enable `httpsDevServer` config option

fixtures/sku-with-https/package.json

+4
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22
"name": "@sku-fixtures/sku-with-https",
33
"private": true,
44
"type": "module",
5+
"scripts": {
6+
"start": "sku start",
7+
"start:vite": "sku start --experimental-bundler --config sku.config.vite.mjs"
8+
},
59
"dependencies": {
610
"react": "^18.2.0",
711
"react-dom": "^18.2.0"

fixtures/sku-with-https/sku.config.vite.mjs

-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,5 @@ import baseConfig from './sku.config.mjs';
33
export default {
44
...baseConfig,
55
__UNSAFE_EXPERIMENTAL__bundler: 'vite',
6-
httpsDevServer: false, // not yet supported
76
devServerMiddleware: './dev-middleware.vite.js',
87
};

packages/sku/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@
106106
"@vanilla-extract/jest-transform": "^1.1.0",
107107
"@vanilla-extract/vite-plugin": "^5.0.1",
108108
"@vanilla-extract/webpack-plugin": "^2.2.0",
109+
"@vitejs/plugin-basic-ssl": "^2.0.0",
109110
"@vitejs/plugin-react-swc": "^3.8.0",
110111
"@vocab/core": "^1.6.2",
111112
"@vocab/phrase": "^2.0.1",

packages/sku/src/services/vite/helpers/server/createViteServer.ts

+7-3
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import type { SkuContext } from '@/context/createSkuContext.js';
55
import { createViteConfig } from '../createConfig.js';
66
import skuViteHMRTelemetryPlugin from '@/services/vite/plugins/skuViteHMRTelemetry.js';
77
import { skuViteStartTelemetryPlugin } from '../../plugins/skuViteStartTelemetry.js';
8+
import { getAppHosts } from '@/utils/contextUtils/hosts.js';
9+
import { skuViteHttpsDevServer } from '../../plugins/skuViteHttpsDevServer.js';
810

911
export const createViteServer = async (skuContext: SkuContext) => {
1012
const base = process.env.BASE || '/';
@@ -22,12 +24,14 @@ export const createViteServer = async (skuContext: SkuContext) => {
2224
target: 'node',
2325
type: 'static',
2426
}),
27+
skuContext.httpsDevServer && skuViteHttpsDevServer(skuContext),
2528
],
2629
}),
2730
server: {
28-
allowedHosts: skuContext.sites
29-
.map(({ host }) => host || false)
30-
.filter((host) => typeof host === 'string'),
31+
host: 'localhost',
32+
allowedHosts: getAppHosts(skuContext).filter(
33+
(host) => typeof host === 'string',
34+
),
3135
},
3236
base,
3337
});

packages/sku/src/services/vite/helpers/server/createViteServerSsr.ts

+9-3
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import type { SkuContext } from '@/context/createSkuContext.js';
1010

1111
import { createSsrHtml } from '@/services/vite/helpers/html/createSsrHtml.js';
1212
import { createCollector } from '@/services/vite/loadable/collector.js';
13+
import { getAppHosts } from '@/utils/contextUtils/hosts.js';
14+
import { skuViteHttpsDevServer } from '../../plugins/skuViteHttpsDevServer.js';
1315

1416
const base = process.env.BASE || '/';
1517

@@ -42,12 +44,16 @@ export const createViteServerSsr = async ({
4244
configType: 'ssr',
4345
}),
4446
server: {
47+
host: 'localhost',
4548
middlewareMode: true,
4649
hmr: true,
47-
allowedHosts: skuContext.sites
48-
.map(({ host }) => host || false)
49-
.filter((host) => typeof host === 'string'),
50+
allowedHosts: getAppHosts(skuContext).filter(
51+
(host) => typeof host === 'string',
52+
),
5053
},
54+
plugins: [
55+
skuContext.httpsDevServer && skuViteHttpsDevServer(skuContext),
56+
],
5157
appType: 'custom',
5258
base,
5359
});

packages/sku/src/services/vite/index.ts

+16-18
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { prerenderRoutes } from './helpers/prerenderRoutes.js';
88
import { cleanTargetDirectory } from '@/utils/buildFileUtils.js';
99
import { openBrowser } from '@/openBrowser/index.js';
1010
import { getAppHosts } from '@/utils/contextUtils/hosts.js';
11+
import chalk from 'chalk';
1112

1213
export const viteService = {
1314
buildSsr: async (skuContext: SkuContext) => {
@@ -33,15 +34,8 @@ export const viteService = {
3334
const url = `${proto}://${hosts[0]}:${skuContext.port.client}${skuContext.initialPath}`;
3435
openBrowser(url);
3536

36-
if (skuContext.sites.length > 1) {
37-
skuContext.sites.forEach((site) => {
38-
console.log(
39-
`Running ${site.name} on '${proto}://${site.host ?? 'localhost'}:${skuContext.port.client}'`,
40-
);
41-
});
42-
} else {
43-
server.printUrls();
44-
}
37+
printUrls(hosts, skuContext);
38+
4539
server.bindCLIShortcuts({ print: true });
4640
},
4741
startSsr: async (skuContext: SkuContext) => {
@@ -57,14 +51,18 @@ export const viteService = {
5751
const url = `${proto}://${hosts[0]}:${skuContext.port.server}${skuContext.initialPath}`;
5852
openBrowser(url);
5953

60-
if (skuContext.sites.length > 1) {
61-
skuContext.sites.forEach((site) => {
62-
console.log(
63-
`Running ${site.name} on '${proto}://${site.host ?? 'localhost'}:${skuContext.port.server}'`,
64-
);
65-
});
66-
} else {
67-
console.log(`Running on 'http://localhost:${skuContext.port.server}'`);
68-
}
54+
printUrls(hosts, skuContext);
6955
},
7056
};
57+
58+
const printUrls = (
59+
hosts: Array<string | undefined>,
60+
skuContext: SkuContext,
61+
) => {
62+
const proto = skuContext.httpsDevServer ? 'https' : 'http';
63+
hosts.forEach((site) => {
64+
console.log(
65+
`${chalk.green('➜')} ${chalk.bold('Local')}: ${chalk.cyan(`${proto}://${site}:${chalk.bold(skuContext.port.client)}`)}`,
66+
);
67+
});
68+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import type { SkuContext } from '@/context/createSkuContext.js';
2+
import { getAppHosts } from '@/utils/contextUtils/hosts.js';
3+
import basicSsl from '@vitejs/plugin-basic-ssl';
4+
import path from 'node:path';
5+
import type { Plugin } from 'vite';
6+
import { promises as fs } from 'node:fs';
7+
import debug from 'debug';
8+
import { hasErrorCode } from '@/utils/error-guards.js';
9+
10+
const log = debug('sku:vite:https');
11+
12+
/**
13+
* Cleans up stale certs from the cert directory. This is useful when the user changes their hosts in their sku config and the old certs are no longer needed.
14+
*/
15+
const cleanupStaleCerts = (certDir: string): Plugin => ({
16+
name: 'sku:cleanup-stale-certs',
17+
// We want to call this before the ssl generation which happens in the "configResolved" hook. However, adding a "enfore:pre" flag in "configResolved"
18+
// doesn't guarantee that it will delete the certs before the ssl generation since it's called async and in parallel.
19+
// So, we use the "config" hook which is guaranteed to be called and waited on before the "configResolved" hook.
20+
config: {
21+
async handler() {
22+
try {
23+
await fs.rm(certDir, { recursive: true });
24+
log(`Removed stale certs from ${certDir}`);
25+
} catch (e) {
26+
if (hasErrorCode(e) && e.code === 'ENOENT') {
27+
log(`Failed removing stale certs. Directory not found: ${certDir}`);
28+
return;
29+
}
30+
31+
log(`Failed to remove stale certs from ${certDir}`, e);
32+
}
33+
},
34+
},
35+
});
36+
37+
export const skuViteHttpsDevServer = async (skuContext: SkuContext) => {
38+
const certDir = path.join(process.cwd(), '.ssl');
39+
40+
return [
41+
cleanupStaleCerts(certDir),
42+
basicSsl({
43+
domains: getAppHosts(skuContext).filter((host) => host !== undefined),
44+
certDir,
45+
}),
46+
];
47+
};

packages/sku/src/services/vite/plugins/skuViteMiddlewarePlugin.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,10 @@ export const skuViteMiddlewarePlugin = (skuContext: SkuContext): Plugin => ({
4545
next();
4646
return;
4747
}
48-
const host = req.headers.host;
48+
49+
// `host` header is available in vite http requests. `:authority` pseudo-header is available in vite https requests. `:authority` is used in HTTP/2.
50+
// @see https://stackoverflow.com/questions/70502726/what-is-the-purpose-of-http2-pseudo-headers-authority-method
51+
const host = req.headers.host ?? (req.headers[':authority'] as string);
4952
const hostname = host?.split(':')[0];
5053

5154
if (!hostname) {

pnpm-lock.yaml

+13
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test-utils/waitForUrls.js

+1-7
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,7 @@ export const waitForUrls = async (...urls) => {
55

66
try {
77
return await waitOn({
8-
resources: urls.map((url) =>
9-
url
10-
.replace(/http(s?)\:/, 'http$1-get:')
11-
// As of node 17, ipv6 is preferred, so explicitly use ipv4
12-
// See https://github.com/jeffbski/wait-on/issues/133
13-
.replace(/localhost/, '0.0.0.0'),
14-
),
8+
resources: urls.map((url) => url.replace(/http(s?)\:/, 'http$1-get:')),
159
headers: { accept: 'text/html, application/javascript' },
1610
timeout,
1711
// Log output of wait behaviour timing to allow

tests/sku-with-https.test.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ describe('sku-with-https', () => {
2525
? ['--experimental-bundler', '--config', 'sku.config.vite.mjs']
2626
: [];
2727
describe('start', () => {
28-
const url = `http${bundler === 'vite' ? '' : 's'}://${bundler === 'vite' ? '[::1]' : 'localhost'}:${port}`;
28+
const url = `https://localhost:${port}`;
2929

3030
let process;
3131

0 commit comments

Comments
 (0)