@module-federation/treeshake-server 0.0.1

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 (63) hide show
  1. package/README.md +71 -0
  2. package/dist/adapters/createAdapterDeps.d.ts +10 -0
  3. package/dist/adapters/local/adapter.d.ts +10 -0
  4. package/dist/adapters/local/index.d.ts +3 -0
  5. package/dist/adapters/local/localObjectStore.d.ts +12 -0
  6. package/dist/adapters/registry.d.ts +7 -0
  7. package/dist/adapters/types.d.ts +70 -0
  8. package/dist/app.d.ts +18 -0
  9. package/dist/cli/ossEnv.d.ts +18 -0
  10. package/dist/domain/build/constant.d.ts +1 -0
  11. package/dist/domain/build/normalize-config.d.ts +21 -0
  12. package/dist/domain/build/retrieve-global-name.d.ts +1 -0
  13. package/dist/domain/build/schema.d.ts +31 -0
  14. package/dist/domain/upload/constant.d.ts +2 -0
  15. package/dist/domain/upload/retrieve-cdn-path.d.ts +4 -0
  16. package/dist/frontend/adapter/index.d.ts +13 -0
  17. package/dist/frontend/adapter/index.js +128 -0
  18. package/dist/frontend/adapter/index.mjs +83 -0
  19. package/dist/frontend/favicon.ico +0 -0
  20. package/dist/frontend/index.html +1 -0
  21. package/dist/frontend/static/css/index.16175e0f.css +1 -0
  22. package/dist/frontend/static/js/954.dfe166a3.js +2 -0
  23. package/dist/frontend/static/js/954.dfe166a3.js.LICENSE.txt +16 -0
  24. package/dist/frontend/static/js/async/873.6ccd5409.js +2 -0
  25. package/dist/frontend/static/js/async/951.ec9191e2.js +12 -0
  26. package/dist/frontend/static/js/async/951.ec9191e2.js.LICENSE.txt +6 -0
  27. package/dist/frontend/static/js/async/987.6bf8e9b0.js +2 -0
  28. package/dist/frontend/static/js/async/987.6bf8e9b0.js.LICENSE.txt +6 -0
  29. package/dist/frontend/static/js/index.db4e73c6.js +88 -0
  30. package/dist/frontend/static/js/lib-react.c59642e3.js +2 -0
  31. package/dist/frontend/static/js/lib-react.c59642e3.js.LICENSE.txt +39 -0
  32. package/dist/frontend/static/js/lib-router.75e1e689.js +4 -0
  33. package/dist/frontend/static/js/lib-router.75e1e689.js.LICENSE.txt +10 -0
  34. package/dist/http/env.d.ts +10 -0
  35. package/dist/http/middlewares/di.middleware.d.ts +4 -0
  36. package/dist/http/middlewares/logger.middleware.d.ts +3 -0
  37. package/dist/http/routes/build.d.ts +3 -0
  38. package/dist/http/routes/index.d.ts +2 -0
  39. package/dist/http/routes/maintenance.d.ts +3 -0
  40. package/dist/http/routes/static.d.ts +5 -0
  41. package/dist/index.d.ts +10 -0
  42. package/dist/index.js +958 -0
  43. package/dist/index.mjs +889 -0
  44. package/dist/infra/logger.d.ts +6 -0
  45. package/dist/nodeServer.d.ts +7 -0
  46. package/dist/ports/objectStore.d.ts +1 -0
  47. package/dist/ports/projectPublisher.d.ts +1 -0
  48. package/dist/server.d.ts +2 -0
  49. package/dist/server.js +1090 -0
  50. package/dist/server.mjs +1058 -0
  51. package/dist/services/buildService.d.ts +8 -0
  52. package/dist/services/cacheService.d.ts +15 -0
  53. package/dist/services/pnpmMaintenance.d.ts +4 -0
  54. package/dist/services/uploadService.d.ts +36 -0
  55. package/dist/template/re-shake-share/Collect.js +115 -0
  56. package/dist/template/re-shake-share/EmitManifest.js +49 -0
  57. package/dist/template/re-shake-share/index.ts +1 -0
  58. package/dist/template/re-shake-share/package.json +23 -0
  59. package/dist/template/re-shake-share/rspack.config.ts +33 -0
  60. package/dist/utils/runCommand.d.ts +20 -0
  61. package/dist/utils/runtimeEnv.d.ts +3 -0
  62. package/dist/utils/uploadSdk.d.ts +10 -0
  63. package/package.json +61 -0
@@ -0,0 +1,8 @@
1
+ import { type BuildType, type NormalizedConfig } from '../domain/build/normalize-config';
2
+ import type { SharedFilePath } from './uploadService';
3
+ export declare const createUniqueTempDirByKey: (key: string) => string;
4
+ export declare const runBuild: (normalizedConfig: NormalizedConfig, excludeShared: Array<[string, string]>, type: BuildType) => Promise<{
5
+ sharedFilePaths: SharedFilePath[];
6
+ dir: string;
7
+ }>;
8
+ export declare function cleanUp(tmpDir?: string): void;
@@ -0,0 +1,15 @@
1
+ import { type BuildType, type NormalizedConfig } from '../domain/build/normalize-config';
2
+ import type { ObjectStore } from '../ports/objectStore';
3
+ import type { UploadResult } from './uploadService';
4
+ export declare function createCacheHash(config: NormalizedConfig[string], type: BuildType): string;
5
+ export declare function retrieveCDNPath({ config, sharedKey, type, }: {
6
+ config: NormalizedConfig[string];
7
+ sharedKey: string;
8
+ type: BuildType;
9
+ }): string;
10
+ export declare function hitCache(sharedKey: string, config: NormalizedConfig[string], type: BuildType, store: ObjectStore): Promise<string | null>;
11
+ export declare const retrieveCacheItems: (normalizedConfig: NormalizedConfig, type: BuildType, store: ObjectStore) => Promise<{
12
+ cacheItems: UploadResult[];
13
+ excludeShared: [sharedName: string, version: string][];
14
+ restConfig: NormalizedConfig;
15
+ }>;
@@ -0,0 +1,4 @@
1
+ export declare const markInstallStart: () => void;
2
+ export declare const markInstallEnd: () => void;
3
+ export declare const maybePrune: () => Promise<void>;
4
+ export declare const startPeriodicPrune: (intervalMs?: number) => void;
@@ -0,0 +1,36 @@
1
+ import { type BuildType, type NormalizedConfig } from '../domain/build/normalize-config';
2
+ import type { Config } from '../domain/build/schema';
3
+ import type { ObjectStore } from '../ports/objectStore';
4
+ import type { ProjectPublisher } from '../ports/projectPublisher';
5
+ export interface SharedFilePath {
6
+ name: string;
7
+ version: string;
8
+ filepath: string;
9
+ globalName: string;
10
+ type: BuildType;
11
+ canTreeShaking: boolean;
12
+ modules?: string[];
13
+ }
14
+ export interface UploadResult {
15
+ name: string;
16
+ version: string;
17
+ globalName: string;
18
+ cdnUrl: string;
19
+ type: BuildType;
20
+ modules?: string[];
21
+ canTreeShaking?: boolean;
22
+ }
23
+ /**
24
+ * Upload shared files to CDN
25
+ * @param sharedFilePaths Array of shared file paths to upload
26
+ * @returns Array of upload results with CDN URLs
27
+ */
28
+ export declare function uploadToCacheStore(sharedFilePaths: SharedFilePath[], normalizedConfig: NormalizedConfig, store: ObjectStore): Promise<UploadResult[]>;
29
+ export interface UploadOpts {
30
+ bucketName: string;
31
+ scmName: string;
32
+ cdnRegion: string;
33
+ publicRoot: string;
34
+ }
35
+ export declare function uploadProject(uploadResults: UploadResult[], sharedFilePaths: SharedFilePath[], normalizedConfig: NormalizedConfig, publisher: ProjectPublisher): Promise<UploadResult[]>;
36
+ export declare function upload(sharedFilePaths: SharedFilePath[], uploadResults: UploadResult[], normalizedConfig: NormalizedConfig, uploadOptions: Config['uploadOptions'], store: ObjectStore, publisher?: ProjectPublisher): Promise<UploadResult[]>;
@@ -0,0 +1,115 @@
1
+ function getProvidedExports(compilation, mod, statsModules, identifier) {
2
+ try {
3
+ const mg = compilation.moduleGraph;
4
+ if (!mg) return 'unknown';
5
+ const exportsInfo = mg.getExportsInfo(mod);
6
+ if (!exportsInfo) return 'unknown';
7
+ const provided = exportsInfo.getProvidedExports && exportsInfo.getProvidedExports();
8
+ if (Array.isArray(provided)) return provided.filter((x)=>'string' == typeof x);
9
+ const statModule = statsModules.find((x)=>x.identifier === identifier);
10
+ if (!statModule) return 'unknown';
11
+ if (statModule.providedExports) return statModule.providedExports;
12
+ return 'unknown';
13
+ } catch (e) {
14
+ return 'unknown';
15
+ }
16
+ }
17
+ function isEsmLikeModule(mod) {
18
+ const bm = mod && mod.buildMeta;
19
+ if (!bm) return;
20
+ if ('string' == typeof bm.exportsType) return 'default' !== bm.exportsType;
21
+ if ('boolean' == typeof bm.strictHarmonyModule) return bm.strictHarmonyModule;
22
+ if ('boolean' == typeof bm.harmonyModule) return bm.harmonyModule;
23
+ }
24
+ function isSideEffectFree(mod) {
25
+ const fm = mod && mod.factoryMeta;
26
+ if (fm && 'boolean' == typeof fm.sideEffectFree) return fm.sideEffectFree;
27
+ const bm = mod && mod.buildMeta;
28
+ if (bm && 'boolean' == typeof bm.sideEffectFree) return bm.sideEffectFree;
29
+ return false;
30
+ }
31
+ class SharedTreeShakingAuditPlugin {
32
+ constructor(opts){
33
+ opts = opts || {};
34
+ this.options = {
35
+ libs: opts.libs || [],
36
+ filename: opts.filename || 'shared-tree-shaking-report.json',
37
+ entryOnly: void 0 !== opts.entryOnly ? !!opts.entryOnly : true,
38
+ debug: !!opts.debug
39
+ };
40
+ }
41
+ apply(compiler) {
42
+ const pluginName = 'SharedTreeShakingAuditPlugin';
43
+ compiler.hooks.thisCompilation.tap(pluginName, (compilation)=>{
44
+ const collectSharedEntryPlugin = compiler.options.plugins.find((p)=>'CollectSharedEntryPlugin' === p.name);
45
+ if (!collectSharedEntryPlugin) return;
46
+ const stage = compiler.webpack && compiler.webpack.Compilation && compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_SUMMARIZE || 4000;
47
+ compilation.hooks.processAssets.tapPromise({
48
+ name: pluginName,
49
+ stage
50
+ }, async ()=>{
51
+ const reports = [];
52
+ const statsModules = compilation.getStats().toJson().modules;
53
+ for (const libName of this.options.libs){
54
+ const report = {
55
+ lib: libName,
56
+ resolvedEntry: void 0,
57
+ entryModuleIdentifier: void 0,
58
+ canTreeShaking: false,
59
+ reasons: [],
60
+ evidence: {},
61
+ exports: 'unknown'
62
+ };
63
+ if (!libName) {
64
+ report.reasons.push('lib configuration is empty');
65
+ reports.push(report);
66
+ continue;
67
+ }
68
+ let entryModule;
69
+ for (const mod of compilation.modules){
70
+ const res = mod && mod.resource;
71
+ if (res && 'string' == typeof res) {
72
+ if (mod.rawRequest === libName) {
73
+ entryModule = mod;
74
+ break;
75
+ }
76
+ }
77
+ }
78
+ if (!entryModule) {
79
+ report.reasons.push(`can not find module for ${libName}`);
80
+ reports.push(report);
81
+ continue;
82
+ }
83
+ report.entryModuleIdentifier = 'function' == typeof entryModule.identifier && entryModule.identifier() || entryModule.debugId || 'unknown';
84
+ const providedExports = getProvidedExports(compilation, entryModule, statsModules, report.entryModuleIdentifier);
85
+ report.exports = providedExports;
86
+ const esmLike = isEsmLikeModule(entryModule);
87
+ const sideEffectFree = isSideEffectFree(entryModule);
88
+ report.evidence.isEsmLike = esmLike;
89
+ report.evidence.sideEffectFree = sideEffectFree;
90
+ report.evidence.buildMeta = entryModule.buildMeta;
91
+ report.evidence.factoryMeta = entryModule.factoryMeta;
92
+ if (false === esmLike) report.reasons.push('entry module is not ESM (likely CJS), static treeshake is hard');
93
+ if (false === sideEffectFree) report.reasons.push('sideEffectFree=false, treeshake might be forbidden');
94
+ const canTreeshake = false !== esmLike && false !== sideEffectFree;
95
+ report.canTreeShaking = canTreeshake;
96
+ if (canTreeshake) report.reasons.push('entry module is ESM-like and not marked as side-effect, necessary conditions are met');
97
+ if (this.options.debug) compilation.warnings.push(new Error(`[${pluginName}] ${libName} entry=${resolvedEntry} exports=${Array.isArray(providedExports) ? providedExports.length : providedExports}`));
98
+ reports.push(report);
99
+ }
100
+ const output = {
101
+ generatedAt: new Date().toISOString(),
102
+ libs: reports
103
+ };
104
+ const json = JSON.stringify(output, null, 2);
105
+ const RawSource = compiler.webpack && compiler.webpack.sources && compiler.webpack.sources.RawSource ? compiler.webpack.sources.RawSource : null;
106
+ if (RawSource) compilation.emitAsset(this.options.filename, new RawSource(json));
107
+ else compilation.assets[this.options.filename] = {
108
+ source: ()=>json,
109
+ size: ()=>json.length
110
+ };
111
+ });
112
+ });
113
+ }
114
+ }
115
+ export default SharedTreeShakingAuditPlugin;
@@ -0,0 +1,49 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ class EmitManifest {
4
+ constructor(secondaryTreeShaking){
5
+ this.secondaryTreeShaking = secondaryTreeShaking;
6
+ }
7
+ apply(compiler) {
8
+ const { secondaryTreeShaking } = this;
9
+ compiler.hooks.compilation.tap('SecondaryTreeShakingPlugin', (compilation)=>{
10
+ compilation.hooks.processAssets.tapPromise({
11
+ name: 'SecondaryTreeShakingPlugin',
12
+ stage: compilation.constructor.PROCESS_ASSETS_STAGE_ADDITIONAL - 1000
13
+ }, async ()=>{
14
+ const treeshakeSharedPlugin = compiler.options.plugins.find((p)=>'TreeShakingSharedPlugin' === p.name);
15
+ if (!treeshakeSharedPlugin) return;
16
+ const mfConfig = treeshakeSharedPlugin.mfConfig;
17
+ const fakeManifest = {
18
+ shared: []
19
+ };
20
+ const { shared } = fakeManifest;
21
+ Object.entries(mfConfig.shared).forEach(([sharedName, sharedConfig])=>{
22
+ shared.push({
23
+ name: sharedName,
24
+ version: sharedConfig.version
25
+ });
26
+ });
27
+ try {
28
+ if (!secondaryTreeShaking) {
29
+ const treeshakeReport = fs.readFileSync(path.resolve(compilation.options.output.path, 'independent-packages/shared-tree-shaking-report.json'));
30
+ const content = JSON.parse(treeshakeReport);
31
+ content.libs.forEach((libInfo)=>{
32
+ const { lib, canTreeShaking, exports } = libInfo;
33
+ const targetShared = shared.find((s)=>s.name === lib);
34
+ if (targetShared) {
35
+ targetShared.canTreeshake = canTreeShaking;
36
+ targetShared.usedExports = exports;
37
+ }
38
+ });
39
+ }
40
+ } catch (e) {
41
+ console.error(e);
42
+ }
43
+ compilation.emitAsset('mf-manifest.json', new compiler.webpack.sources.RawSource(JSON.stringify(fakeManifest)));
44
+ compilation.emitAsset('mf-stats.json', new compiler.webpack.sources.RawSource(JSON.stringify(fakeManifest)));
45
+ });
46
+ });
47
+ }
48
+ }
49
+ export default EmitManifest;
@@ -0,0 +1 @@
1
+ ${SHARED_IMPORT}
@@ -0,0 +1,23 @@
1
+ {
2
+ "name": "re-shake",
3
+ "version": "1.0.0",
4
+ "scripts": {
5
+ "build": "SECONDARY_TREE_SHAKING=true rspack build",
6
+ "build:full": "rspack build"
7
+ },
8
+ "license": "ISC",
9
+ "dependencies": {
10
+ "@rspack/cli": "^1.5.8",
11
+ "@rspack/core": "^1.5.8"
12
+ },
13
+ "pnpm": {
14
+ "overrides": {
15
+ "@rspack/core": "npm:@rspack-canary/core@1.7.3-canary-58d41d16-20260115035302"
16
+ },
17
+ "peerDependencyRules": {
18
+ "allowAny": [
19
+ "@rspack/*"
20
+ ]
21
+ }
22
+ }
23
+ }
@@ -0,0 +1,33 @@
1
+ import { sharing } from "@rspack/core";
2
+ import path from 'path';
3
+ import CollectPlugin from "./Collect";
4
+ import EmitManifest from './EmitManifest';
5
+
6
+
7
+ const SECONDARY_TREE_SHAKING = Boolean(process.env.SECONDARY_TREE_SHAKING)
8
+
9
+ const mfConfig = ${ MF_CONFIG };
10
+
11
+ module.exports = {
12
+ entry: './index.ts',
13
+ output:{
14
+ path: path.resolve(process.cwd(), SECONDARY_TREE_SHAKING ? 'dist' : 'full-shared'),
15
+ },
16
+ plugins: [
17
+ new EmitManifest(SECONDARY_TREE_SHAKING),
18
+ new sharing.TreeShakingSharedPlugin({
19
+ secondary: SECONDARY_TREE_SHAKING,
20
+ plugins: ${ PLUGINS },
21
+ mfConfig,
22
+ }),
23
+ !SECONDARY_TREE_SHAKING && new CollectPlugin({
24
+ libs: Object.entries(mfConfig.shared).map(([k, v]) => {
25
+ if(!v.treeShaking) {
26
+ return null;
27
+ }
28
+ return k;
29
+ }).filter(Boolean),
30
+ filename: "shared-tree-shaking-report.json",
31
+ }),
32
+ ]
33
+ };
@@ -0,0 +1,20 @@
1
+ export interface CommandOptions {
2
+ cwd?: string;
3
+ env?: Record<string, string | undefined>;
4
+ }
5
+ export interface CommandResult {
6
+ stdout: string;
7
+ stderr: string;
8
+ exitCode: number;
9
+ }
10
+ export declare class CommandExecutionError extends Error {
11
+ readonly command: string;
12
+ readonly exitCode: number;
13
+ readonly stdout: string;
14
+ readonly stderr: string;
15
+ constructor(command: string, exitCode: number, stdout: string, stderr: string);
16
+ }
17
+ /**
18
+ * Execute a shell command and collect its output.
19
+ */
20
+ export declare const runCommand: (command: string, options?: CommandOptions) => Promise<CommandResult>;
@@ -0,0 +1,3 @@
1
+ export type RuntimeEnv = Record<string, string | undefined>;
2
+ export declare const setRuntimeEnv: (env: RuntimeEnv) => void;
3
+ export declare const getRuntimeEnv: () => RuntimeEnv;
@@ -0,0 +1,10 @@
1
+ export interface UploadDirectoryOptions {
2
+ outputDir: string;
3
+ cdnBaseUrl: string;
4
+ targetPath: string;
5
+ }
6
+ /**
7
+ * Placeholder for a real CDN upload SDK.
8
+ * In real usage you should import the vendor SDK and replace this implementation.
9
+ */
10
+ export declare function uploadDirectory({ outputDir, cdnBaseUrl, targetPath, }: UploadDirectoryOptions): Promise<string>;
package/package.json ADDED
@@ -0,0 +1,61 @@
1
+ {
2
+ "name": "@module-federation/treeshake-server",
3
+ "version": "0.0.1",
4
+ "description": "Build service powered by Hono that installs dependencies, builds with Rspack, and uploads artifacts.",
5
+ "main": "dist/index.js",
6
+ "module": "dist/index.mjs",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.mjs",
12
+ "require": "./dist/index.js"
13
+ }
14
+ },
15
+ "bin": {
16
+ "treeshake-server": "dist/server.js"
17
+ },
18
+ "scripts": {
19
+ "build": "pnpm -C ../treeshake-frontend build && rslib build && node scripts/copy-frontend.js",
20
+ "lint": "biome lint src scripts",
21
+ "prestart": "npm run build",
22
+ "dev": "node scripts/dev.js",
23
+ "start": "node dist/server.js",
24
+ "test": "rstest"
25
+ },
26
+ "files": [
27
+ "dist/",
28
+ "README.md"
29
+ ],
30
+ "keywords": [],
31
+ "author": "",
32
+ "license": "ISC",
33
+ "dependencies": {
34
+ "@hono/node-server": "1.19.5",
35
+ "@hono/zod-validator": "0.7.4",
36
+ "dotenv": "16.4.5",
37
+ "hono": "4.10.2",
38
+ "json-stable-stringify": "1.3.0",
39
+ "nanoid": "5.1.6",
40
+ "pino": "10.1.0",
41
+ "undici": "5.29.0",
42
+ "zod": "4.1.12"
43
+ },
44
+ "devDependencies": {
45
+ "@biomejs/biome": "^2.3.3",
46
+ "@playwright/test": "^1.57.0",
47
+ "@rstest/core": "^0.6.5",
48
+ "@types/node": "^20.11.30",
49
+ "@vercel/nft": "^1.1.1",
50
+ "pino-pretty": "^13.1.2",
51
+ "ts-node": "^10.9.2",
52
+ "typescript": "^5.9.3"
53
+ },
54
+ "engines": {
55
+ "node": ">=20.19.5",
56
+ "pnpm": ">=10.18.1"
57
+ },
58
+ "publishConfig": {
59
+ "access": "public"
60
+ }
61
+ }