@module-federation/treeshake-server 0.0.1-alpha.0

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 (64) hide show
  1. package/README.md +71 -0
  2. package/bin/treeshake-server.js +35 -0
  3. package/dist/adapters/createAdapterDeps.d.ts +10 -0
  4. package/dist/adapters/local/adapter.d.ts +10 -0
  5. package/dist/adapters/local/index.d.ts +3 -0
  6. package/dist/adapters/local/localObjectStore.d.ts +13 -0
  7. package/dist/adapters/registry.d.ts +7 -0
  8. package/dist/adapters/types.d.ts +74 -0
  9. package/dist/app.d.ts +18 -0
  10. package/dist/cli/ossEnv.d.ts +18 -0
  11. package/dist/domain/build/constant.d.ts +1 -0
  12. package/dist/domain/build/normalize-config.d.ts +22 -0
  13. package/dist/domain/build/retrieve-global-name.d.ts +1 -0
  14. package/dist/domain/build/schema.d.ts +31 -0
  15. package/dist/domain/upload/constant.d.ts +2 -0
  16. package/dist/domain/upload/retrieve-cdn-path.d.ts +4 -0
  17. package/dist/frontend/adapter/index.d.ts +13 -0
  18. package/dist/frontend/adapter/index.js +128 -0
  19. package/dist/frontend/adapter/index.mjs +83 -0
  20. package/dist/frontend/favicon.ico +0 -0
  21. package/dist/frontend/index.html +1 -0
  22. package/dist/frontend/static/css/index.16175e0f.css +1 -0
  23. package/dist/frontend/static/js/296.084d1b43.js +2 -0
  24. package/dist/frontend/static/js/296.084d1b43.js.LICENSE.txt +16 -0
  25. package/dist/frontend/static/js/async/873.21368adc.js +2 -0
  26. package/dist/frontend/static/js/async/951.ec9191e2.js +12 -0
  27. package/dist/frontend/static/js/async/951.ec9191e2.js.LICENSE.txt +6 -0
  28. package/dist/frontend/static/js/async/987.86ff6794.js +2 -0
  29. package/dist/frontend/static/js/async/987.86ff6794.js.LICENSE.txt +6 -0
  30. package/dist/frontend/static/js/index.5488f626.js +88 -0
  31. package/dist/frontend/static/js/lib-react.c59642e3.js +2 -0
  32. package/dist/frontend/static/js/lib-react.c59642e3.js.LICENSE.txt +39 -0
  33. package/dist/frontend/static/js/lib-router.75e1e689.js +4 -0
  34. package/dist/frontend/static/js/lib-router.75e1e689.js.LICENSE.txt +10 -0
  35. package/dist/http/env.d.ts +10 -0
  36. package/dist/http/middlewares/di.middleware.d.ts +4 -0
  37. package/dist/http/middlewares/logger.middleware.d.ts +3 -0
  38. package/dist/http/routes/build.d.ts +3 -0
  39. package/dist/http/routes/index.d.ts +2 -0
  40. package/dist/http/routes/maintenance.d.ts +3 -0
  41. package/dist/http/routes/static.d.ts +5 -0
  42. package/dist/index.d.ts +10 -0
  43. package/dist/index.js +1028 -0
  44. package/dist/index.mjs +957 -0
  45. package/dist/infra/logger.d.ts +6 -0
  46. package/dist/nodeServer.d.ts +7 -0
  47. package/dist/ports/objectStore.d.ts +1 -0
  48. package/dist/ports/projectPublisher.d.ts +1 -0
  49. package/dist/server.d.ts +2 -0
  50. package/dist/server.js +1160 -0
  51. package/dist/server.mjs +1126 -0
  52. package/dist/services/buildService.d.ts +8 -0
  53. package/dist/services/cacheService.d.ts +15 -0
  54. package/dist/services/pnpmMaintenance.d.ts +4 -0
  55. package/dist/services/uploadService.d.ts +36 -0
  56. package/dist/template/re-shake-share/Collect.js +115 -0
  57. package/dist/template/re-shake-share/EmitManifest.js +49 -0
  58. package/dist/template/re-shake-share/index.ts +1 -0
  59. package/dist/template/re-shake-share/package.json +23 -0
  60. package/dist/template/re-shake-share/rspack.config.ts +33 -0
  61. package/dist/utils/runCommand.d.ts +20 -0
  62. package/dist/utils/runtimeEnv.d.ts +3 -0
  63. package/dist/utils/uploadSdk.d.ts +10 -0
  64. package/package.json +68 -0
package/README.md ADDED
@@ -0,0 +1,71 @@
1
+ # Treeshake Server
2
+
3
+ An adapter-driven build service for shared treeshaking. The CLI runs in **local filesystem mode only** and embeds the treeshake UI automatically. When importing the package, you can register your own adapters, middlewares, and optional frontend UI.
4
+
5
+ ## CLI (embedded UI, local-only)
6
+
7
+ ```bash
8
+ pnpm --filter @module-federation/treeshake-server dev
9
+ # or
10
+ pnpm --filter @module-federation/treeshake-server start
11
+ ```
12
+
13
+ Defaults:
14
+
15
+ - API: `http://localhost:3000/tree-shaking-shared`
16
+ - UI: `http://localhost:3000/tree-shaking`
17
+ - Storage: local filesystem (`LOCAL_STORE_DIR`, default `./log/static`)
18
+
19
+ The CLI **ignores** `ADAPTER_ID` and always uses the local filesystem adapter.
20
+
21
+ ### Frontend env overrides (CLI)
22
+
23
+ The CLI always embeds the UI. You can only override where it is served from:
24
+
25
+ - `TREESHAKE_FRONTEND_DIST=/path/to/dist`
26
+ - `TREESHAKE_FRONTEND_BASE_PATH=/tree-shaking`
27
+ - `TREESHAKE_FRONTEND_SPA_FALLBACK=0`
28
+
29
+ ## Library usage (custom adapters + middlewares)
30
+
31
+ ```ts
32
+ import {
33
+ createAdapterRegistry,
34
+ createAdapterDeps,
35
+ createApp,
36
+ createServer,
37
+ LocalAdapter,
38
+ } from "@module-federation/treeshake-server";
39
+ import { createTreeshakeFrontendAdapter } from "@module-federation/treeshake-frontend/adapter";
40
+
41
+ const registry = createAdapterRegistry([
42
+ new LocalAdapter(),
43
+ // new MyCustomAdapter(),
44
+ ]);
45
+
46
+ const deps = await createAdapterDeps({
47
+ registry,
48
+ adapterId: "local", // or your custom adapter id
49
+ });
50
+
51
+ const app = createApp(deps, {
52
+ appExtensions: [
53
+ (appInstance) => {
54
+ appInstance.use("*", async (c, next) => {
55
+ c.res.headers.set("x-treeshake", "true");
56
+ await next();
57
+ });
58
+ },
59
+ ],
60
+ frontendAdapters: [
61
+ createTreeshakeFrontendAdapter({
62
+ basePath: "/tree-shaking",
63
+ distDir: "/path/to/treeshake-frontend/dist",
64
+ }),
65
+ ],
66
+ });
67
+
68
+ createServer({ app, port: 3000, hostname: "0.0.0.0" });
69
+ ```
70
+
71
+ In library mode you control the adapter registry, middleware, and frontend embedding.
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Wrapper entry for the CLI.
4
+ *
5
+ * pnpm creates workspace bins during install. If the target is a build artifact
6
+ * (e.g. dist/server.js) and the package hasn't been built yet, install fails
7
+ * with ENOENT. This wrapper keeps installs clean, and still runs the built
8
+ * server when it's available.
9
+ */
10
+
11
+ const path = require('path');
12
+
13
+ const entry = path.join(__dirname, '..', 'dist', 'server.js');
14
+
15
+ try {
16
+ // eslint-disable-next-line import/no-dynamic-require, global-require
17
+ require(entry);
18
+ } catch (err) {
19
+ // If dist hasn't been built yet, show a helpful message and exit.
20
+ if (err && (err.code === 'MODULE_NOT_FOUND' || err.code === 'ENOENT')) {
21
+ // eslint-disable-next-line no-console
22
+ console.error(
23
+ [
24
+ 'treeshake-server: missing build output at dist/server.js.',
25
+ '',
26
+ 'Run:',
27
+ ' pnpm --filter @module-federation/treeshake-server build',
28
+ '',
29
+ ].join('\n'),
30
+ );
31
+ process.exit(1);
32
+ }
33
+
34
+ throw err;
35
+ }
@@ -0,0 +1,10 @@
1
+ import type { AdapterConfig, AdapterCreateResult, AdapterLogger } from './types';
2
+ import type { AdapterRegistry } from './registry';
3
+ export type AdapterDeps = AdapterCreateResult;
4
+ export declare function createAdapterDeps(params: {
5
+ registry: AdapterRegistry;
6
+ adapterId: string;
7
+ adapterConfig?: AdapterConfig;
8
+ logger?: AdapterLogger;
9
+ uploadedDir?: string;
10
+ }): Promise<AdapterDeps>;
@@ -0,0 +1,10 @@
1
+ import type { AdapterContext, AdapterCreateResult, AdapterEnv, TreeShakeAdapter } from '../types';
2
+ export type LocalAdapterConfig = {
3
+ rootDir?: string;
4
+ publicBaseUrl?: string;
5
+ };
6
+ export declare class LocalAdapter implements TreeShakeAdapter<LocalAdapterConfig> {
7
+ readonly id = "local";
8
+ fromEnv(env: AdapterEnv): LocalAdapterConfig;
9
+ create(config: LocalAdapterConfig, _context?: AdapterContext): Promise<AdapterCreateResult>;
10
+ }
@@ -0,0 +1,3 @@
1
+ export { LocalAdapter } from './adapter';
2
+ export type { LocalAdapterConfig } from './adapter';
3
+ export { LocalObjectStore } from './localObjectStore';
@@ -0,0 +1,13 @@
1
+ import type { ObjectStore } from '../types';
2
+ export declare class LocalObjectStore implements ObjectStore {
3
+ private readonly rootDir;
4
+ private readonly publicBaseUrl;
5
+ constructor(opts?: {
6
+ rootDir?: string;
7
+ publicBaseUrl?: string;
8
+ });
9
+ exists(key: string): Promise<boolean>;
10
+ uploadFile(localPath: string, key: string): Promise<void>;
11
+ downloadFile(key: string, localPath: string): Promise<void>;
12
+ publicUrl(key: string): string;
13
+ }
@@ -0,0 +1,7 @@
1
+ import type { TreeShakeAdapter } from './types';
2
+ export type AdapterRegistry = {
3
+ adapters: TreeShakeAdapter[];
4
+ getAdapterById: (id: string) => TreeShakeAdapter;
5
+ };
6
+ export declare function createAdapterRegistry(adapters: TreeShakeAdapter[]): AdapterRegistry;
7
+ export declare function createOssAdapterRegistry(): AdapterRegistry;
@@ -0,0 +1,74 @@
1
+ export type AdapterLogger = {
2
+ info: (...args: unknown[]) => void;
3
+ warn?: (...args: unknown[]) => void;
4
+ error?: (...args: unknown[]) => void;
5
+ };
6
+ export type AdapterContext = {
7
+ logger?: AdapterLogger;
8
+ uploadedDir?: string;
9
+ };
10
+ export type AdapterConfig = Record<string, unknown>;
11
+ export interface ObjectStore {
12
+ /**
13
+ * Return true if an object exists for the given key.
14
+ *
15
+ * "key" is the object-store path/key (not a full URL).
16
+ */
17
+ exists(key: string): Promise<boolean>;
18
+ /**
19
+ * Upload a local file to the object store under the given key.
20
+ */
21
+ uploadFile(localPath: string, key: string): Promise<void>;
22
+ /**
23
+ * Download a file from the object store to a local path.
24
+ */
25
+ downloadFile(key: string, localPath: string): Promise<void>;
26
+ /**
27
+ * Convert a key to a publicly reachable URL.
28
+ */
29
+ publicUrl(key: string): string;
30
+ }
31
+ export type UploadOptions = {
32
+ scmName: string;
33
+ bucketName: string;
34
+ publicFilePath: string;
35
+ publicPath: string;
36
+ cdnRegion: string;
37
+ };
38
+ export interface ProjectPublisher {
39
+ /**
40
+ * Return true if a public URL is already reachable (cache hit).
41
+ */
42
+ exists(publicUrl: string): Promise<boolean>;
43
+ /**
44
+ * Compute the public URL for a would-be published file.
45
+ *
46
+ * This lets the caller do an existence check before uploading.
47
+ */
48
+ publicUrl(params: {
49
+ sharedName: string;
50
+ hash: string;
51
+ fileName: string;
52
+ options: UploadOptions;
53
+ }): string;
54
+ /**
55
+ * Publish a file to a project-owned CDN/bucket and return its public URL.
56
+ */
57
+ publishFile(params: {
58
+ localPath: string;
59
+ sharedName: string;
60
+ hash: string;
61
+ options: UploadOptions;
62
+ }): Promise<string>;
63
+ }
64
+ export type AdapterCreateResult = {
65
+ objectStore: ObjectStore;
66
+ projectPublisher?: ProjectPublisher;
67
+ shutdown?: () => Promise<void>;
68
+ };
69
+ export type AdapterEnv = Record<string, string | undefined>;
70
+ export interface TreeShakeAdapter<TConfig = AdapterConfig> {
71
+ readonly id: string;
72
+ create(config: TConfig, context?: AdapterContext): Promise<AdapterCreateResult>;
73
+ fromEnv?: (env: AdapterEnv) => TConfig;
74
+ }
package/dist/app.d.ts ADDED
@@ -0,0 +1,18 @@
1
+ import { Hono } from 'hono';
2
+ import type { AppEnv } from './http/env';
3
+ import type { ObjectStore } from './ports/objectStore';
4
+ import type { ProjectPublisher } from './ports/projectPublisher';
5
+ import type { FrontendAdapter } from './frontend/types';
6
+ import { setLogger } from './infra/logger';
7
+ export declare function createApp(deps: {
8
+ objectStore: ObjectStore;
9
+ projectPublisher?: ProjectPublisher;
10
+ }, opts?: {
11
+ corsOrigin?: string;
12
+ staticRootDir?: string;
13
+ pruneIntervalMs?: number;
14
+ logger?: Parameters<typeof setLogger>[0];
15
+ runtimeEnv?: Record<string, string | undefined>;
16
+ frontendAdapters?: FrontendAdapter[];
17
+ appExtensions?: Array<(app: Hono<AppEnv>) => void>;
18
+ }): Hono<AppEnv, import("hono/types").BlankSchema, "/">;
@@ -0,0 +1,18 @@
1
+ import type { AdapterEnv, AdapterConfig } from '../adapters/types';
2
+ import type { AdapterRegistry } from '../adapters/registry';
3
+ export type ResolvedOssEnv = {
4
+ adapterId: string;
5
+ adapterConfig: AdapterConfig;
6
+ corsOrigin: string;
7
+ staticRootDir: string;
8
+ logLevel: string;
9
+ port: number;
10
+ hostname: string;
11
+ pruneIntervalMs: number;
12
+ runtimeEnv: AdapterEnv;
13
+ };
14
+ export declare function resolveOssEnv(params: {
15
+ env: AdapterEnv;
16
+ registry: AdapterRegistry;
17
+ defaultAdapterId?: string;
18
+ }): ResolvedOssEnv;
@@ -0,0 +1 @@
1
+ export declare const STATS_NAME = "mf-stats.json";
@@ -0,0 +1,22 @@
1
+ import type { CheckTreeShaking, Config } from './schema';
2
+ export type NormalizedConfig = {
3
+ [sharedName: string]: (Config | CheckTreeShaking) & {
4
+ usedExports: string[];
5
+ };
6
+ };
7
+ export type BuildType = 're-shake' | 'full';
8
+ export declare const parseNormalizedKey: (key: string) => {
9
+ name: string;
10
+ version: string;
11
+ };
12
+ export declare const normalizedKey: (name: string, v: string) => string;
13
+ export declare function normalizeConfig(config: Config | CheckTreeShaking): NormalizedConfig;
14
+ export declare function extractBuildConfig(config: NormalizedConfig[string], type: BuildType): {
15
+ shared: [string, string, string[]][];
16
+ plugins: [string, (string | undefined)?][] | undefined;
17
+ target: string | string[];
18
+ libraryType: string;
19
+ usedExports: string[];
20
+ type: BuildType;
21
+ hostName: string;
22
+ };
@@ -0,0 +1 @@
1
+ export declare function retrieveGlobalName(mfName: string, sharedName: string, version: string): string;
@@ -0,0 +1,31 @@
1
+ import { z } from 'zod';
2
+ export declare const ConfigSchema: z.ZodObject<{
3
+ shared: z.ZodArray<z.ZodTuple<[z.ZodString, z.ZodString, z.ZodArray<z.ZodString>], null>>;
4
+ plugins: z.ZodOptional<z.ZodArray<z.ZodTuple<[z.ZodString, z.ZodOptional<z.ZodString>], null>>>;
5
+ target: z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>;
6
+ libraryType: z.ZodString;
7
+ hostName: z.ZodString;
8
+ uploadOptions: z.ZodOptional<z.ZodObject<{
9
+ scmName: z.ZodString;
10
+ bucketName: z.ZodString;
11
+ publicFilePath: z.ZodString;
12
+ publicPath: z.ZodString;
13
+ cdnRegion: z.ZodString;
14
+ }, z.core.$strip>>;
15
+ }, z.core.$strip>;
16
+ export type Config = z.infer<typeof ConfigSchema>;
17
+ export declare const CheckTreeShakingSchema: z.ZodObject<{
18
+ shared: z.ZodArray<z.ZodTuple<[z.ZodString, z.ZodString, z.ZodArray<z.ZodString>], null>>;
19
+ plugins: z.ZodOptional<z.ZodArray<z.ZodTuple<[z.ZodString, z.ZodOptional<z.ZodString>], null>>>;
20
+ target: z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>;
21
+ libraryType: z.ZodString;
22
+ hostName: z.ZodString;
23
+ uploadOptions: z.ZodOptional<z.ZodObject<{
24
+ scmName: z.ZodString;
25
+ bucketName: z.ZodString;
26
+ publicFilePath: z.ZodString;
27
+ publicPath: z.ZodString;
28
+ cdnRegion: z.ZodString;
29
+ }, z.core.$strip>>;
30
+ }, z.core.$strip>;
31
+ export type CheckTreeShaking = z.infer<typeof CheckTreeShakingSchema>;
@@ -0,0 +1,2 @@
1
+ export declare const SERVER_VERSION = "v0-011501";
2
+ export declare const UPLOADED_DIR = "_shared-tree-shaking";
@@ -0,0 +1,4 @@
1
+ export declare function retrieveCDNPath({ configHash, sharedKey, }: {
2
+ configHash: string;
3
+ sharedKey: string;
4
+ }): string;
@@ -0,0 +1,13 @@
1
+ export type FrontendAdapter = {
2
+ id: string;
3
+ register: (app: {
4
+ get: (path: string, handler: any) => void;
5
+ }) => void;
6
+ };
7
+ export type TreeshakeFrontendAdapterOptions = {
8
+ basePath?: string;
9
+ distDir?: string;
10
+ indexFile?: string;
11
+ spaFallback?: boolean;
12
+ };
13
+ export declare function createTreeshakeFrontendAdapter(opts?: TreeshakeFrontendAdapterOptions): FrontendAdapter;
@@ -0,0 +1,128 @@
1
+ "use strict";
2
+ var __webpack_require__ = {};
3
+ (()=>{
4
+ __webpack_require__.n = (module)=>{
5
+ var getter = module && module.__esModule ? ()=>module['default'] : ()=>module;
6
+ __webpack_require__.d(getter, {
7
+ a: getter
8
+ });
9
+ return getter;
10
+ };
11
+ })();
12
+ (()=>{
13
+ __webpack_require__.d = (exports1, definition)=>{
14
+ for(var key in definition)if (__webpack_require__.o(definition, key) && !__webpack_require__.o(exports1, key)) Object.defineProperty(exports1, key, {
15
+ enumerable: true,
16
+ get: definition[key]
17
+ });
18
+ };
19
+ })();
20
+ (()=>{
21
+ __webpack_require__.o = (obj, prop)=>Object.prototype.hasOwnProperty.call(obj, prop);
22
+ })();
23
+ (()=>{
24
+ __webpack_require__.r = (exports1)=>{
25
+ if ('undefined' != typeof Symbol && Symbol.toStringTag) Object.defineProperty(exports1, Symbol.toStringTag, {
26
+ value: 'Module'
27
+ });
28
+ Object.defineProperty(exports1, '__esModule', {
29
+ value: true
30
+ });
31
+ };
32
+ })();
33
+ var __webpack_exports__ = {};
34
+ __webpack_require__.r(__webpack_exports__);
35
+ __webpack_require__.d(__webpack_exports__, {
36
+ createTreeshakeFrontendAdapter: ()=>createTreeshakeFrontendAdapter
37
+ });
38
+ const external_node_fs_namespaceObject = require("node:fs");
39
+ var external_node_fs_default = /*#__PURE__*/ __webpack_require__.n(external_node_fs_namespaceObject);
40
+ const external_node_path_namespaceObject = require("node:path");
41
+ var external_node_path_default = /*#__PURE__*/ __webpack_require__.n(external_node_path_namespaceObject);
42
+ const defaultBasePath = '/tree-shaking';
43
+ const contentTypeByExt = (filePath)=>{
44
+ if (filePath.endsWith('.html')) return 'text/html; charset=utf-8';
45
+ if (filePath.endsWith('.js')) return "application/javascript";
46
+ if (filePath.endsWith('.css')) return 'text/css';
47
+ if (filePath.endsWith('.json')) return 'application/json';
48
+ if (filePath.endsWith('.svg')) return 'image/svg+xml';
49
+ if (filePath.endsWith('.png')) return 'image/png';
50
+ if (filePath.endsWith('.jpg') || filePath.endsWith('.jpeg')) return 'image/jpeg';
51
+ if (filePath.endsWith('.webp')) return 'image/webp';
52
+ if (filePath.endsWith('.ico')) return 'image/x-icon';
53
+ return 'application/octet-stream';
54
+ };
55
+ const resolveDistDir = (override)=>{
56
+ if (override) return override;
57
+ const candidate = external_node_path_default().resolve(__dirname, '..');
58
+ const candidateIndex = external_node_path_default().join(candidate, 'index.html');
59
+ if (external_node_fs_default().existsSync(candidateIndex)) return candidate;
60
+ return external_node_path_default().resolve(__dirname, '..', '..', 'dist');
61
+ };
62
+ const safeResolve = (rootDir, requestPath)=>{
63
+ const rootResolved = external_node_path_default().resolve(rootDir);
64
+ const rel = requestPath.replace(/^\/+/, '');
65
+ const filePath = external_node_path_default().resolve(rootResolved, rel);
66
+ if (filePath !== rootResolved && !filePath.startsWith(`${rootResolved}${external_node_path_default().sep}`)) return null;
67
+ return filePath;
68
+ };
69
+ function createTreeshakeFrontendAdapter(opts = {}) {
70
+ const basePath = opts.basePath ?? defaultBasePath;
71
+ const normalizedBase = '/' === basePath ? '' : basePath.replace(/\/$/, '');
72
+ const distDir = resolveDistDir(opts.distDir);
73
+ const indexFile = opts.indexFile ?? 'index.html';
74
+ const spaFallback = opts.spaFallback ?? true;
75
+ const handler = async (c)=>{
76
+ let requestPath = c.req.path;
77
+ try {
78
+ requestPath = decodeURIComponent(requestPath);
79
+ } catch {
80
+ return c.text('Not Found', 404);
81
+ }
82
+ let relPath = requestPath;
83
+ if (normalizedBase && requestPath.startsWith(normalizedBase)) relPath = requestPath.slice(normalizedBase.length);
84
+ if (!relPath || '/' === relPath) relPath = `/${indexFile}`;
85
+ const filePath = safeResolve(distDir, relPath);
86
+ if (filePath) try {
87
+ const stat = await external_node_fs_default().promises.stat(filePath);
88
+ if (stat.isFile()) {
89
+ const buf = await external_node_fs_default().promises.readFile(filePath);
90
+ return new Response(buf, {
91
+ status: 200,
92
+ headers: {
93
+ 'Content-Type': contentTypeByExt(filePath)
94
+ }
95
+ });
96
+ }
97
+ } catch {}
98
+ if (!spaFallback) return c.text('Not Found', 404);
99
+ const fallbackPath = safeResolve(distDir, `/${indexFile}`);
100
+ if (!fallbackPath) return c.text('Not Found', 404);
101
+ try {
102
+ const buf = await external_node_fs_default().promises.readFile(fallbackPath);
103
+ return new Response(buf, {
104
+ status: 200,
105
+ headers: {
106
+ 'Content-Type': contentTypeByExt(fallbackPath)
107
+ }
108
+ });
109
+ } catch {
110
+ return c.text('Not Found', 404);
111
+ }
112
+ };
113
+ return {
114
+ id: 'treeshake-frontend',
115
+ register (app) {
116
+ const base = normalizedBase || '/';
117
+ app.get(base, handler);
118
+ app.get(`${base}/*`, handler);
119
+ }
120
+ };
121
+ }
122
+ exports.createTreeshakeFrontendAdapter = __webpack_exports__.createTreeshakeFrontendAdapter;
123
+ for(var __webpack_i__ in __webpack_exports__)if (-1 === [
124
+ "createTreeshakeFrontendAdapter"
125
+ ].indexOf(__webpack_i__)) exports[__webpack_i__] = __webpack_exports__[__webpack_i__];
126
+ Object.defineProperty(exports, '__esModule', {
127
+ value: true
128
+ });
@@ -0,0 +1,83 @@
1
+ import node_fs from "node:fs";
2
+ import node_path from "node:path";
3
+ const defaultBasePath = '/tree-shaking';
4
+ const contentTypeByExt = (filePath)=>{
5
+ if (filePath.endsWith('.html')) return 'text/html; charset=utf-8';
6
+ if (filePath.endsWith('.js')) return "application/javascript";
7
+ if (filePath.endsWith('.css')) return 'text/css';
8
+ if (filePath.endsWith('.json')) return 'application/json';
9
+ if (filePath.endsWith('.svg')) return 'image/svg+xml';
10
+ if (filePath.endsWith('.png')) return 'image/png';
11
+ if (filePath.endsWith('.jpg') || filePath.endsWith('.jpeg')) return 'image/jpeg';
12
+ if (filePath.endsWith('.webp')) return 'image/webp';
13
+ if (filePath.endsWith('.ico')) return 'image/x-icon';
14
+ return 'application/octet-stream';
15
+ };
16
+ const resolveDistDir = (override)=>{
17
+ if (override) return override;
18
+ const candidate = node_path.resolve(__dirname, '..');
19
+ const candidateIndex = node_path.join(candidate, 'index.html');
20
+ if (node_fs.existsSync(candidateIndex)) return candidate;
21
+ return node_path.resolve(__dirname, '..', '..', 'dist');
22
+ };
23
+ const safeResolve = (rootDir, requestPath)=>{
24
+ const rootResolved = node_path.resolve(rootDir);
25
+ const rel = requestPath.replace(/^\/+/, '');
26
+ const filePath = node_path.resolve(rootResolved, rel);
27
+ if (filePath !== rootResolved && !filePath.startsWith(`${rootResolved}${node_path.sep}`)) return null;
28
+ return filePath;
29
+ };
30
+ function createTreeshakeFrontendAdapter(opts = {}) {
31
+ const basePath = opts.basePath ?? defaultBasePath;
32
+ const normalizedBase = '/' === basePath ? '' : basePath.replace(/\/$/, '');
33
+ const distDir = resolveDistDir(opts.distDir);
34
+ const indexFile = opts.indexFile ?? 'index.html';
35
+ const spaFallback = opts.spaFallback ?? true;
36
+ const handler = async (c)=>{
37
+ let requestPath = c.req.path;
38
+ try {
39
+ requestPath = decodeURIComponent(requestPath);
40
+ } catch {
41
+ return c.text('Not Found', 404);
42
+ }
43
+ let relPath = requestPath;
44
+ if (normalizedBase && requestPath.startsWith(normalizedBase)) relPath = requestPath.slice(normalizedBase.length);
45
+ if (!relPath || '/' === relPath) relPath = `/${indexFile}`;
46
+ const filePath = safeResolve(distDir, relPath);
47
+ if (filePath) try {
48
+ const stat = await node_fs.promises.stat(filePath);
49
+ if (stat.isFile()) {
50
+ const buf = await node_fs.promises.readFile(filePath);
51
+ return new Response(buf, {
52
+ status: 200,
53
+ headers: {
54
+ 'Content-Type': contentTypeByExt(filePath)
55
+ }
56
+ });
57
+ }
58
+ } catch {}
59
+ if (!spaFallback) return c.text('Not Found', 404);
60
+ const fallbackPath = safeResolve(distDir, `/${indexFile}`);
61
+ if (!fallbackPath) return c.text('Not Found', 404);
62
+ try {
63
+ const buf = await node_fs.promises.readFile(fallbackPath);
64
+ return new Response(buf, {
65
+ status: 200,
66
+ headers: {
67
+ 'Content-Type': contentTypeByExt(fallbackPath)
68
+ }
69
+ });
70
+ } catch {
71
+ return c.text('Not Found', 404);
72
+ }
73
+ };
74
+ return {
75
+ id: 'treeshake-frontend',
76
+ register (app) {
77
+ const base = normalizedBase || '/';
78
+ app.get(base, handler);
79
+ app.get(`${base}/*`, handler);
80
+ }
81
+ };
82
+ }
83
+ export { createTreeshakeFrontendAdapter };
Binary file
@@ -0,0 +1 @@
1
+ <!DOCTYPE html><html><head><link rel="icon" href="/tree-shaking/favicon.ico"><title>Tree Shaking Visualizer</title><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><script defer src="/tree-shaking/static/js/lib-react.c59642e3.js"></script><script defer src="/tree-shaking/static/js/lib-router.75e1e689.js"></script><script defer src="/tree-shaking/static/js/296.084d1b43.js"></script><script defer src="/tree-shaking/static/js/index.5488f626.js"></script><link href="/tree-shaking/static/css/index.16175e0f.css" rel="stylesheet"></head><body><div id="root"></div></body></html>