@shopify/cli-hydrogen 7.1.0 → 7.1.2

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 (72) hide show
  1. package/dist/commands/hydrogen/build-vite.js +131 -0
  2. package/dist/commands/hydrogen/build.js +6 -15
  3. package/dist/commands/hydrogen/check.js +1 -1
  4. package/dist/commands/hydrogen/codegen.js +3 -3
  5. package/dist/commands/hydrogen/debug/cpu.js +1 -1
  6. package/dist/commands/hydrogen/deploy.js +46 -31
  7. package/dist/commands/hydrogen/deploy.test.js +35 -49
  8. package/dist/commands/hydrogen/dev-vite.js +159 -0
  9. package/dist/commands/hydrogen/dev.js +11 -14
  10. package/dist/commands/hydrogen/env/list.js +1 -1
  11. package/dist/commands/hydrogen/env/pull.js +3 -3
  12. package/dist/commands/hydrogen/env/pull.test.js +2 -0
  13. package/dist/commands/hydrogen/env/push__unstable.js +190 -0
  14. package/dist/commands/hydrogen/env/push__unstable.test.js +383 -0
  15. package/dist/commands/hydrogen/generate/route.js +2 -2
  16. package/dist/commands/hydrogen/init.d.ts +69 -0
  17. package/dist/commands/hydrogen/init.js +5 -5
  18. package/dist/commands/hydrogen/init.test.js +2 -2
  19. package/dist/commands/hydrogen/link.js +2 -2
  20. package/dist/commands/hydrogen/list.js +1 -1
  21. package/dist/commands/hydrogen/login.js +2 -9
  22. package/dist/commands/hydrogen/logout.js +1 -1
  23. package/dist/commands/hydrogen/preview.js +15 -7
  24. package/dist/commands/hydrogen/setup/css.js +3 -3
  25. package/dist/commands/hydrogen/setup/markets.js +4 -4
  26. package/dist/commands/hydrogen/setup/vite.js +209 -0
  27. package/dist/commands/hydrogen/setup.js +8 -6
  28. package/dist/commands/hydrogen/unlink.js +1 -1
  29. package/dist/commands/hydrogen/upgrade.js +5 -3
  30. package/dist/generator-templates/assets/vite/package.json +15 -0
  31. package/dist/generator-templates/assets/vite/vite.config.js +13 -0
  32. package/dist/generator-templates/starter/CHANGELOG.md +49 -0
  33. package/dist/generator-templates/starter/app/components/Search.tsx +12 -7
  34. package/dist/generator-templates/starter/app/root.tsx +1 -2
  35. package/dist/generator-templates/starter/app/routes/api.predictive-search.tsx +8 -15
  36. package/dist/generator-templates/starter/package.json +9 -8
  37. package/dist/generator-templates/starter/public/.gitkeep +0 -0
  38. package/dist/lib/build.js +2 -1
  39. package/dist/lib/codegen.js +8 -3
  40. package/dist/lib/environment-variables.test.js +4 -2
  41. package/dist/lib/flags.js +149 -95
  42. package/dist/lib/graphql/admin/pull-variables.js +1 -0
  43. package/dist/lib/graphql/admin/pull-variables.test.js +7 -1
  44. package/dist/lib/graphql/admin/push-variables.js +35 -0
  45. package/dist/lib/log.js +1 -0
  46. package/dist/lib/mini-oxygen/common.js +2 -1
  47. package/dist/lib/mini-oxygen/node.js +2 -2
  48. package/dist/lib/mini-oxygen/workerd-inspector.js +1 -1
  49. package/dist/lib/mini-oxygen/workerd.js +29 -17
  50. package/dist/lib/onboarding/common.js +0 -3
  51. package/dist/lib/onboarding/local.js +4 -1
  52. package/dist/lib/onboarding/remote.js +16 -11
  53. package/dist/lib/remix-config.js +1 -1
  54. package/dist/lib/request-events.js +3 -3
  55. package/dist/lib/setups/css/assets.js +7 -2
  56. package/dist/lib/template-diff.js +26 -11
  57. package/dist/lib/template-downloader.js +11 -2
  58. package/dist/lib/vite/hydrogen-middleware.js +82 -0
  59. package/dist/lib/vite/mini-oxygen.js +152 -0
  60. package/dist/lib/vite/plugins.d.ts +27 -0
  61. package/dist/lib/vite/plugins.js +139 -0
  62. package/dist/lib/vite/shared.js +10 -0
  63. package/dist/lib/vite/utils.js +55 -0
  64. package/dist/lib/vite/worker-entry.js +1518 -0
  65. package/dist/lib/vite-config.js +45 -0
  66. package/dist/virtual-routes/lib/useDebugNetworkServer.jsx +4 -2
  67. package/dist/virtual-routes/routes/index.jsx +5 -5
  68. package/dist/virtual-routes/routes/subrequest-profiler.jsx +1 -1
  69. package/dist/virtual-routes/virtual-root.jsx +1 -1
  70. package/oclif.manifest.json +1127 -494
  71. package/package.json +36 -11
  72. /package/dist/generator-templates/starter/{public → app/assets}/favicon.svg +0 -0
@@ -3,6 +3,7 @@ import { temporaryDirectory } from 'tempy';
3
3
  import { createSymlink, copy, remove } from 'fs-extra/esm';
4
4
  import { copyFile, removeFile } from '@shopify/cli-kit/node/fs';
5
5
  import { joinPath, relativePath } from '@shopify/cli-kit/node/path';
6
+ import { readAndParsePackageJson } from '@shopify/cli-kit/node/node-package-manager';
6
7
  import colors from '@shopify/cli-kit/node/colors';
7
8
  import { getRepoNodeModules, getStarterDir } from './build.js';
8
9
  import { mergePackageJson } from './file.js';
@@ -65,35 +66,49 @@ ${colors.dim(
65
66
  return targetDirectory;
66
67
  }
67
68
  async function applyTemplateDiff(targetDirectory, diffDirectory, templateDir = getStarterDir()) {
68
- const createFilter = (re) => (filepath) => !re.test(relativePath(templateDir, filepath));
69
+ const pkgJson = await readAndParsePackageJson(
70
+ joinPath(diffDirectory, "package.json")
71
+ );
72
+ const createFilter = (re, skipFiles) => (filepath) => {
73
+ const filename = relativePath(templateDir, filepath);
74
+ return !re.test(filename) && !skipFiles?.includes(filename);
75
+ };
69
76
  await copy(templateDir, targetDirectory, {
70
77
  filter: createFilter(
71
- /(^|\/|\\)(dist|node_modules|\.cache|CHANGELOG\.md)(\/|\\|$)/i
78
+ /(^|\/|\\)(dist|node_modules|\.cache|.turbo|CHANGELOG\.md)(\/|\\|$)/i,
79
+ pkgJson["h2:diff"]?.["skip-files"] || []
72
80
  )
73
81
  });
74
82
  await copy(diffDirectory, targetDirectory, {
75
83
  filter: createFilter(
76
- /(^|\/|\\)(dist|node_modules|\.cache|package\.json|tsconfig\.json)(\/|\\|$)/i
84
+ /(^|\/|\\)(dist|node_modules|\.cache|.turbo|package\.json|tsconfig\.json)(\/|\\|$)/i
77
85
  )
78
86
  });
79
87
  await mergePackageJson(diffDirectory, targetDirectory, {
80
- onResult: (pkgJson) => {
88
+ ignoredKeys: ["h2:diff"],
89
+ onResult: (pkgJson2) => {
81
90
  for (const key of ["build", "dev"]) {
82
- const scriptLine = pkgJson.scripts?.[key];
83
- if (pkgJson.scripts?.[key] && typeof scriptLine === "string") {
84
- pkgJson.scripts[key] = scriptLine.replace(/\s+--diff/, "");
91
+ const scriptLine = pkgJson2.scripts?.[key];
92
+ if (pkgJson2.scripts?.[key] && typeof scriptLine === "string") {
93
+ pkgJson2.scripts[key] = scriptLine.replace(/\s+--diff/, "");
85
94
  }
86
95
  }
87
- return pkgJson;
96
+ return pkgJson2;
88
97
  }
89
98
  });
90
99
  }
91
100
  async function copyDiffBuild(targetDirectory, diffDirectory) {
92
101
  const targetDist = joinPath(diffDirectory, "dist");
93
102
  await remove(targetDist);
94
- await copy(joinPath(targetDirectory, "dist"), targetDist, {
95
- overwrite: true
96
- });
103
+ await Promise.all([
104
+ copy(joinPath(targetDirectory, "dist"), targetDist, {
105
+ overwrite: true
106
+ }),
107
+ copyFile(
108
+ joinPath(targetDirectory, ".env"),
109
+ joinPath(diffDirectory, ".env")
110
+ )
111
+ ]);
97
112
  }
98
113
 
99
114
  export { applyTemplateDiff, copyDiffBuild, prepareDiffDirectory };
@@ -1,11 +1,12 @@
1
- import path from 'path';
1
+ import path from 'node:path';
2
+ import { fileURLToPath } from 'node:url';
2
3
  import { pipeline } from 'stream/promises';
3
4
  import gunzipMaybe from 'gunzip-maybe';
4
5
  import { extract } from 'tar-fs';
5
6
  import { fetch } from '@shopify/cli-kit/node/http';
6
7
  import { fileExists, mkdir } from '@shopify/cli-kit/node/fs';
7
8
  import { AbortError } from '@shopify/cli-kit/node/error';
8
- import { fileURLToPath } from 'url';
9
+ import { getSkeletonSourceDir } from './build.js';
9
10
 
10
11
  const REPO_RELEASES_URL = `https://api.github.com/repos/shopify/hydrogen/releases/latest`;
11
12
  const getTryMessage = (status) => status === 403 ? `If you are using a VPN, WARP, or similar service, consider disabling it momentarily.` : void 0;
@@ -50,6 +51,14 @@ async function downloadTarball(url, storageDir, signal) {
50
51
  async function getLatestTemplates({
51
52
  signal
52
53
  } = {}) {
54
+ if (process.env.LOCAL_DEV) {
55
+ const templatesDir = path.dirname(getSkeletonSourceDir());
56
+ return {
57
+ version: "local",
58
+ templatesDir,
59
+ examplesDir: path.resolve(templatesDir, "..", "examples")
60
+ };
61
+ }
53
62
  try {
54
63
  const { version, url } = await getLatestReleaseDownloadUrl(signal);
55
64
  const templateStoragePath = fileURLToPath(
@@ -0,0 +1,82 @@
1
+ import { normalizePath } from 'vite';
2
+ import path from 'node:path';
3
+ import { createRequire } from 'node:module';
4
+ import { createFileReadStream } from '@shopify/cli-kit/node/fs';
5
+ import { setConstructors, handleDebugNetworkRequest } from '../request-events.js';
6
+ import { SUBREQUEST_PROFILER_ENDPOINT } from '../mini-oxygen/common.js';
7
+ import { toWeb, pipeFromWeb } from './utils.js';
8
+ import { addVirtualRoutes } from '../virtual-routes.js';
9
+
10
+ function setupRemixDevServerHooks(viteUrl) {
11
+ globalThis["__remix_devServerHooks"] = {
12
+ getCriticalCss: (...args) => fetch(new URL("/__vite_critical_css", viteUrl), {
13
+ method: "POST",
14
+ body: JSON.stringify(args)
15
+ }).then((res) => res.json())
16
+ };
17
+ }
18
+ function setupHydrogenMiddleware(viteDevServer, options) {
19
+ viteDevServer.middlewares.use(
20
+ "/__vite_critical_css",
21
+ function h2HandleCriticalCss(req, res) {
22
+ toWeb(req).json().then(async (args) => {
23
+ const result = await globalThis["__remix_devServerHooks"]?.getCriticalCss?.(...args);
24
+ res.writeHead(200, { "Content-Type": "application/json" });
25
+ res.end(JSON.stringify(result ?? ""));
26
+ });
27
+ }
28
+ );
29
+ if (options.disableVirtualRoutes)
30
+ return;
31
+ addVirtualRoutesToRemix(viteDevServer);
32
+ setConstructors({ Response: globalThis.Response });
33
+ viteDevServer.middlewares.use(
34
+ SUBREQUEST_PROFILER_ENDPOINT,
35
+ function h2HandleSubrequestProfilerEvent(req, res) {
36
+ const webResponse = handleDebugNetworkRequest(toWeb(req));
37
+ pipeFromWeb(webResponse, res);
38
+ }
39
+ );
40
+ viteDevServer.middlewares.use(
41
+ "/graphiql/customer-account.schema.json",
42
+ function h2HandleGraphiQLCustomerSchema(req, res) {
43
+ const require2 = createRequire(import.meta.url);
44
+ const filePath = require2.resolve(
45
+ "@shopify/hydrogen/customer-account.schema.json"
46
+ );
47
+ res.writeHead(200, { "Content-Type": "application/json" });
48
+ createFileReadStream(filePath).pipe(res);
49
+ }
50
+ );
51
+ }
52
+ let virtualRoutesAdded = false;
53
+ async function addVirtualRoutesToRemix(viteDevServer) {
54
+ if (virtualRoutesAdded)
55
+ return;
56
+ const appDirectory = await reloadRemixVirtualRoutes(viteDevServer.config);
57
+ viteDevServer.watcher.on("all", (eventName, filepath) => {
58
+ const appFileAddedOrRemoved = (eventName === "add" || eventName === "unlink") && normalizePath(filepath).startsWith(normalizePath(appDirectory));
59
+ const viteConfigChanged = eventName === "change" && normalizePath(filepath) === normalizePath(viteDevServer.config.configFile ?? "");
60
+ if (appFileAddedOrRemoved || viteConfigChanged) {
61
+ setTimeout(() => reloadRemixVirtualRoutes(viteDevServer.config), 100);
62
+ }
63
+ });
64
+ virtualRoutesAdded = true;
65
+ }
66
+ async function reloadRemixVirtualRoutes(config) {
67
+ const remixPluginContext = config.__remixPluginContext;
68
+ remixPluginContext.remixConfig = { ...remixPluginContext.remixConfig };
69
+ remixPluginContext.remixConfig.routes = {
70
+ ...remixPluginContext.remixConfig.routes
71
+ };
72
+ await addVirtualRoutes(remixPluginContext.remixConfig).catch((error) => {
73
+ console.debug(
74
+ "Could not add virtual routes: " + (error?.stack ?? error?.message ?? error)
75
+ );
76
+ });
77
+ Object.freeze(remixPluginContext.remixConfig.routes);
78
+ Object.freeze(remixPluginContext.remixConfig);
79
+ return remixPluginContext?.remixConfig?.appDirectory ?? path.join(config.root, "app");
80
+ }
81
+
82
+ export { setupHydrogenMiddleware, setupRemixDevServerHooks };
@@ -0,0 +1,152 @@
1
+ import { fetchModule } from 'vite';
2
+ import { fileURLToPath } from 'node:url';
3
+ import crypto from 'node:crypto';
4
+ import { Miniflare, NoOpLog } from 'miniflare';
5
+ import { OXYGEN_HEADERS_MAP, logRequestLine } from '../mini-oxygen/common.js';
6
+ import { PRIVATE_WORKERD_INSPECTOR_PORT, OXYGEN_WORKERD_COMPAT_PARAMS } from '../mini-oxygen/workerd.js';
7
+ import { findPort } from '../find-port.js';
8
+ import { createInspectorConnector } from '../mini-oxygen/workerd-inspector.js';
9
+ import { getHmrUrl, toURL, toWeb, pipeFromWeb } from './utils.js';
10
+
11
+ const scriptPath = fileURLToPath(new URL("./worker-entry.js", import.meta.url));
12
+ const FETCH_MODULE_PATHNAME = "/__vite_fetch_module";
13
+ const WARMUP_PATHNAME = "/__vite_warmup";
14
+ const oxygenHeadersMap = Object.values(OXYGEN_HEADERS_MAP).reduce(
15
+ (acc, item) => {
16
+ acc[item.name] = item.defaultValue;
17
+ return acc;
18
+ },
19
+ {}
20
+ );
21
+ async function startMiniOxygenRuntime({
22
+ viteDevServer,
23
+ env,
24
+ services,
25
+ debug = false,
26
+ inspectorPort,
27
+ workerEntryFile,
28
+ setupScripts
29
+ }) {
30
+ const [publicInspectorPort, privateInspectorPort] = await Promise.all([
31
+ findPort(inspectorPort),
32
+ findPort(PRIVATE_WORKERD_INSPECTOR_PORT)
33
+ ]);
34
+ const mf = new Miniflare({
35
+ cf: false,
36
+ verbose: false,
37
+ log: new NoOpLog(),
38
+ inspectorPort: privateInspectorPort,
39
+ handleRuntimeStdio(stdout, stderr) {
40
+ stdout.destroy();
41
+ stderr.destroy();
42
+ },
43
+ workers: [
44
+ {
45
+ name: "oxygen",
46
+ modulesRoot: "/",
47
+ modules: [{ type: "ESModule", path: scriptPath }],
48
+ ...OXYGEN_WORKERD_COMPAT_PARAMS,
49
+ serviceBindings: { ...services },
50
+ bindings: {
51
+ ...env,
52
+ __VITE_ROOT: viteDevServer.config.root,
53
+ __VITE_RUNTIME_EXECUTE_URL: workerEntryFile,
54
+ __VITE_FETCH_MODULE_PATHNAME: FETCH_MODULE_PATHNAME,
55
+ __VITE_HMR_URL: getHmrUrl(viteDevServer),
56
+ __VITE_WARMUP_PATHNAME: WARMUP_PATHNAME
57
+ },
58
+ unsafeEvalBinding: "__VITE_UNSAFE_EVAL",
59
+ wrappedBindings: {
60
+ __VITE_SETUP_ENV: "setup-environment"
61
+ }
62
+ },
63
+ {
64
+ name: "setup-environment",
65
+ modules: true,
66
+ scriptPath,
67
+ script: `
68
+ const setupScripts = [${setupScripts ?? ""}];
69
+ export default (env) => (request) => {
70
+ const viteUrl = new URL(request.url).origin;
71
+ setupScripts.forEach((setup) => setup?.(viteUrl));
72
+ setupScripts.length = 0;
73
+ }`
74
+ }
75
+ ]
76
+ });
77
+ const warmupWorkerdCache = () => {
78
+ let viteUrl = viteDevServer.resolvedUrls?.local[0] ?? viteDevServer.resolvedUrls?.network[0];
79
+ if (!viteUrl) {
80
+ const address = viteDevServer.httpServer?.address?.();
81
+ viteUrl = address && typeof address !== "string" ? `http://localhost:${address.port}` : address ?? void 0;
82
+ }
83
+ if (viteUrl) {
84
+ mf.dispatchFetch(new URL(WARMUP_PATHNAME, viteUrl)).catch(() => {
85
+ });
86
+ }
87
+ };
88
+ viteDevServer.httpServer?.listening ? warmupWorkerdCache() : viteDevServer.httpServer?.once("listening", warmupWorkerdCache);
89
+ mf.ready.then(() => {
90
+ const reconnect = createInspectorConnector({
91
+ debug,
92
+ sourceMapPath: "",
93
+ absoluteBundlePath: "",
94
+ privateInspectorPort,
95
+ publicInspectorPort
96
+ });
97
+ return reconnect();
98
+ });
99
+ return {
100
+ ready: mf.ready,
101
+ publicInspectorPort,
102
+ dispatch: (webRequest) => mf.dispatchFetch(webRequest),
103
+ async dispose() {
104
+ await mf.dispose();
105
+ }
106
+ };
107
+ }
108
+ function setupOxygenMiddleware(viteDevServer, dispatchFetch) {
109
+ viteDevServer.middlewares.use(
110
+ FETCH_MODULE_PATHNAME,
111
+ function o2HandleModuleFetch(req, res) {
112
+ const url = toURL(req);
113
+ const id = url.searchParams.get("id");
114
+ const importer = url.searchParams.get("importer") ?? void 0;
115
+ if (id) {
116
+ res.setHeader("cache-control", "no-store");
117
+ res.setHeader("content-type", "application/json");
118
+ fetchModule(viteDevServer, id, importer).then((ssrModule) => res.end(JSON.stringify(ssrModule))).catch((error) => {
119
+ console.error("Error during module fetch:", error);
120
+ res.writeHead(500, { "Content-Type": "text/plain" });
121
+ res.end("Internal server error");
122
+ });
123
+ } else {
124
+ res.statusCode = 400;
125
+ res.writeHead(400, { "Content-Type": "text/plain" });
126
+ res.end("Invalid request");
127
+ }
128
+ }
129
+ );
130
+ viteDevServer.middlewares.use(function o2HandleWorkerRequest(req, res) {
131
+ if (!req.headers.host)
132
+ throw new Error("Missing host header");
133
+ const webRequest = toWeb(req, {
134
+ "request-id": crypto.randomUUID(),
135
+ ...oxygenHeadersMap
136
+ });
137
+ const startTimeMs = Date.now();
138
+ dispatchFetch(webRequest).then((webResponse) => {
139
+ pipeFromWeb(webResponse, res);
140
+ logRequestLine(webRequest, {
141
+ responseStatus: webResponse.status,
142
+ durationMs: Date.now() - startTimeMs
143
+ });
144
+ }).catch((error) => {
145
+ console.error("Error during evaluation:", error);
146
+ res.writeHead(500);
147
+ res.end();
148
+ });
149
+ });
150
+ }
151
+
152
+ export { setupOxygenMiddleware, startMiniOxygenRuntime };
@@ -0,0 +1,27 @@
1
+ import { Plugin } from 'vite';
2
+
3
+ type HydrogenPluginOptions = {
4
+ disableVirtualRoutes?: boolean;
5
+ };
6
+ type OxygenPluginOptions = {
7
+ ssrEntry?: string;
8
+ debug?: boolean;
9
+ inspectorPort?: number;
10
+ env?: Record<string, any>;
11
+ };
12
+
13
+ /**
14
+ * Enables Hydrogen utilities for local development
15
+ * such as GraphiQL, Subrequest Profiler, etc.
16
+ * It must be used in combination with the `oxygen` plugin and Hydrogen CLI.
17
+ * @experimental
18
+ */
19
+ declare function hydrogen(pluginOptions?: HydrogenPluginOptions): Plugin[];
20
+ /**
21
+ * Runs backend code in an Oxygen worker instead of Node.js during development.
22
+ * It must be placed after `hydrogen` but before `remix` in the Vite plugins list.
23
+ * @experimental
24
+ */
25
+ declare function oxygen(pluginOptions?: OxygenPluginOptions): Plugin[];
26
+
27
+ export { hydrogen, oxygen };
@@ -0,0 +1,139 @@
1
+ import path from 'node:path';
2
+ import { setupRemixDevServerHooks, setupHydrogenMiddleware } from './hydrogen-middleware.js';
3
+ import { startMiniOxygenRuntime, setupOxygenMiddleware } from './mini-oxygen.js';
4
+ import { setH2OPluginContext, getH2OPluginContext, DEFAULT_SSR_ENTRY } from './shared.js';
5
+ import { H2O_BINDING_NAME, createLogRequestEvent } from '../request-events.js';
6
+
7
+ function hydrogen(pluginOptions = {}) {
8
+ const isRemixChildCompiler = (config) => !config.plugins?.some((plugin) => plugin.name === "remix");
9
+ return [
10
+ {
11
+ name: "hydrogen:main",
12
+ config(config) {
13
+ return {
14
+ ssr: {
15
+ optimizeDeps: {
16
+ // Add CJS dependencies that break code in workerd
17
+ // with errors like "require/module/exports is not defined":
18
+ include: [
19
+ // React deps:
20
+ "react",
21
+ "react/jsx-runtime",
22
+ "react/jsx-dev-runtime",
23
+ "react-dom",
24
+ "react-dom/server",
25
+ // Remix deps:
26
+ "set-cookie-parser",
27
+ "cookie",
28
+ // Hydrogen deps:
29
+ "content-security-policy-builder"
30
+ ]
31
+ }
32
+ },
33
+ // Pass the setup functions to the Oxygen runtime.
34
+ ...setH2OPluginContext({
35
+ setupScripts: [setupRemixDevServerHooks],
36
+ shouldStartRuntime: (config2) => !isRemixChildCompiler(config2),
37
+ services: {
38
+ [H2O_BINDING_NAME]: createLogRequestEvent({
39
+ transformLocation: (partialLocation) => path.join(config.root ?? process.cwd(), partialLocation)
40
+ })
41
+ }
42
+ })
43
+ };
44
+ },
45
+ configureServer(viteDevServer) {
46
+ if (isRemixChildCompiler(viteDevServer.config))
47
+ return;
48
+ const { cliOptions } = getH2OPluginContext(viteDevServer.config) || {};
49
+ return () => {
50
+ setupHydrogenMiddleware(viteDevServer, {
51
+ ...pluginOptions,
52
+ ...cliOptions
53
+ });
54
+ };
55
+ }
56
+ }
57
+ ];
58
+ }
59
+ function oxygen(pluginOptions = {}) {
60
+ let resolvedConfig;
61
+ let absoluteWorkerEntryFile;
62
+ return [
63
+ {
64
+ name: "oxygen:runtime",
65
+ config(config, env) {
66
+ return {
67
+ appType: "custom",
68
+ resolve: {
69
+ conditions: ["worker", "workerd"]
70
+ },
71
+ ssr: {
72
+ noExternal: true,
73
+ target: "webworker"
74
+ },
75
+ // When building, the CLI will set the `ssr` option to `true`
76
+ // if no --entry flag is passed for the default SSR entry file.
77
+ // Replace it here with a default value.
78
+ ...env.isSsrBuild && config.build?.ssr && {
79
+ build: {
80
+ ssr: config.build?.ssr === true ? (
81
+ // No --entry flag passed by the user, use the
82
+ // option passed to the plugin or the default value
83
+ pluginOptions.ssrEntry ?? DEFAULT_SSR_ENTRY
84
+ ) : (
85
+ // --entry flag passed by the user, keep it
86
+ config.build?.ssr
87
+ )
88
+ }
89
+ }
90
+ };
91
+ },
92
+ configureServer(viteDevServer) {
93
+ resolvedConfig = viteDevServer.config;
94
+ const { shouldStartRuntime, cliOptions, setupScripts, services } = getH2OPluginContext(resolvedConfig) || {};
95
+ if (shouldStartRuntime && !shouldStartRuntime(resolvedConfig))
96
+ return;
97
+ const workerEntryFile = cliOptions?.ssrEntry ?? pluginOptions.ssrEntry ?? DEFAULT_SSR_ENTRY;
98
+ absoluteWorkerEntryFile = path.isAbsolute(workerEntryFile) ? workerEntryFile : path.resolve(viteDevServer.config.root, workerEntryFile);
99
+ const envPromise = cliOptions?.envPromise ?? Promise.resolve();
100
+ let miniOxygen;
101
+ const miniOxygenPromise = envPromise.then((remoteEnv) => {
102
+ return startMiniOxygenRuntime({
103
+ viteDevServer,
104
+ workerEntryFile,
105
+ setupScripts,
106
+ services,
107
+ env: { ...remoteEnv, ...pluginOptions.env },
108
+ debug: cliOptions?.debug ?? pluginOptions.debug ?? false,
109
+ inspectorPort: cliOptions?.inspectorPort ?? pluginOptions.inspectorPort ?? 9229
110
+ });
111
+ });
112
+ process.once("SIGTERM", async () => {
113
+ try {
114
+ await miniOxygen?.dispose();
115
+ } finally {
116
+ process.exit();
117
+ }
118
+ });
119
+ return () => {
120
+ setupOxygenMiddleware(viteDevServer, async (request) => {
121
+ miniOxygen ??= await miniOxygenPromise;
122
+ return miniOxygen.dispatch(request);
123
+ });
124
+ };
125
+ },
126
+ transform(code, id, options) {
127
+ if (resolvedConfig?.command === "serve" && resolvedConfig?.server?.hmr !== false && options?.ssr && (id === absoluteWorkerEntryFile || id === absoluteWorkerEntryFile + path.extname(id))) {
128
+ return {
129
+ // Accept HMR in server entry module to avoid full-page refresh in the browser.
130
+ // Note: appending code at the end should not break the source map.
131
+ code: code + "\nif (import.meta.hot) import.meta.hot.accept();"
132
+ };
133
+ }
134
+ }
135
+ }
136
+ ];
137
+ }
138
+
139
+ export { hydrogen, oxygen };
@@ -0,0 +1,10 @@
1
+ const DEFAULT_SSR_ENTRY = "./server";
2
+ const H2O_CONTEXT_KEY = "__h2oPluginContext";
3
+ function getH2OPluginContext(config) {
4
+ return config?.[H2O_CONTEXT_KEY];
5
+ }
6
+ function setH2OPluginContext(options) {
7
+ return { [H2O_CONTEXT_KEY]: options };
8
+ }
9
+
10
+ export { DEFAULT_SSR_ENTRY, getH2OPluginContext, setH2OPluginContext };
@@ -0,0 +1,55 @@
1
+ import path from 'node:path';
2
+ import { Readable } from 'node:stream';
3
+ import { Request } from 'miniflare';
4
+
5
+ function toURL(req = "/", origin) {
6
+ const isRequest = typeof req !== "string";
7
+ const pathname = (isRequest ? req.url : req) || "/";
8
+ return new URL(
9
+ pathname,
10
+ origin || isRequest && req.headers.host && `http://${req.headers.host}` || "http://example.com"
11
+ );
12
+ }
13
+ function toWeb(req, headers) {
14
+ return new Request(toURL(req), {
15
+ method: req.method,
16
+ headers: { ...headers, ...req.headers },
17
+ body: req.headers["content-length"] ? Readable.toWeb(req) : void 0,
18
+ duplex: "half",
19
+ // This is required when sending a ReadableStream as body
20
+ redirect: "manual"
21
+ // Avoid consuming 300 responses here, return to browser
22
+ });
23
+ }
24
+ function pipeFromWeb(webResponse, res) {
25
+ const headers = Object.fromEntries(webResponse.headers.entries());
26
+ const setCookieHeader = "set-cookie";
27
+ if (headers[setCookieHeader]) {
28
+ delete headers[setCookieHeader];
29
+ res.setHeader(setCookieHeader, webResponse.headers.getSetCookie());
30
+ }
31
+ res.writeHead(webResponse.status, webResponse.statusText, headers);
32
+ if (webResponse.body) {
33
+ Readable.fromWeb(webResponse.body).pipe(res);
34
+ } else {
35
+ res.end();
36
+ }
37
+ }
38
+ function getHmrUrl(viteDevServer) {
39
+ const userHmrValue = viteDevServer.config.server?.hmr;
40
+ if (userHmrValue === false) {
41
+ console.warn(
42
+ "HMR is disabled. Code changes will not be reflected in neither browser or server."
43
+ );
44
+ return "";
45
+ }
46
+ const configHmr = typeof userHmrValue === "object" ? userHmrValue : {};
47
+ const hmrPort = configHmr.port;
48
+ const hmrPath = configHmr.path;
49
+ let hmrBase = viteDevServer.config.base;
50
+ if (hmrPath)
51
+ hmrBase = path.posix.join(hmrBase, hmrPath);
52
+ return (hmrPort ? `http://localhost:${hmrPort}` : "") + hmrBase;
53
+ }
54
+
55
+ export { getHmrUrl, pipeFromWeb, toURL, toWeb };