@module-federation/treeshake-server 0.0.0-chore-bump-node-22-20260710161714
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.
- package/README.md +65 -0
- package/bin/treeshake-server.js +35 -0
- package/dist/876.mjs +940 -0
- package/dist/adapters/createAdapterDeps.d.ts +10 -0
- package/dist/adapters/local/adapter.d.ts +10 -0
- package/dist/adapters/local/index.d.ts +3 -0
- package/dist/adapters/local/localObjectStore.d.ts +12 -0
- package/dist/adapters/registry.d.ts +7 -0
- package/dist/adapters/types.d.ts +70 -0
- package/dist/app.d.ts +18 -0
- package/dist/cli/ossEnv.d.ts +18 -0
- package/dist/domain/build/constant.d.ts +1 -0
- package/dist/domain/build/normalize-config.d.ts +22 -0
- package/dist/domain/build/retrieve-global-name.d.ts +1 -0
- package/dist/domain/build/schema.d.ts +31 -0
- package/dist/domain/upload/constant.d.ts +2 -0
- package/dist/domain/upload/retrieve-cdn-path.d.ts +4 -0
- package/dist/frontend/adapter/index.d.ts +13 -0
- package/dist/frontend/adapter/index.js +132 -0
- package/dist/frontend/adapter/index.mjs +83 -0
- package/dist/frontend/favicon.ico +0 -0
- package/dist/frontend/index.html +1 -0
- package/dist/frontend/static/css/index.513418ff5d.css +1 -0
- package/dist/frontend/static/js/637.b47e0403af.js +2 -0
- package/dist/frontend/static/js/637.b47e0403af.js.LICENSE.txt +16 -0
- package/dist/frontend/static/js/async/427.dbb8b74259.js +2 -0
- package/dist/frontend/static/js/async/427.dbb8b74259.js.LICENSE.txt +6 -0
- package/dist/frontend/static/js/async/503.7bd056737e.js +12 -0
- package/dist/frontend/static/js/async/503.7bd056737e.js.LICENSE.txt +6 -0
- package/dist/frontend/static/js/async/515.1b7f26d7ec.js +2 -0
- package/dist/frontend/static/js/index.ace3fa3d0e.js +88 -0
- package/dist/frontend/static/js/lib-react.7c43a84261.js +2 -0
- package/dist/frontend/static/js/lib-react.7c43a84261.js.LICENSE.txt +39 -0
- package/dist/frontend/static/js/lib-router.5077a93d5a.js +4 -0
- package/dist/frontend/static/js/lib-router.5077a93d5a.js.LICENSE.txt +10 -0
- package/dist/http/env.d.ts +10 -0
- package/dist/http/middlewares/di.middleware.d.ts +4 -0
- package/dist/http/middlewares/logger.middleware.d.ts +3 -0
- package/dist/http/routes/build.d.ts +3 -0
- package/dist/http/routes/index.d.ts +2 -0
- package/dist/http/routes/maintenance.d.ts +3 -0
- package/dist/http/routes/static.d.ts +5 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +1015 -0
- package/dist/index.mjs +1 -0
- package/dist/infra/logger.d.ts +6 -0
- package/dist/nodeServer.d.ts +7 -0
- package/dist/ports/objectStore.d.ts +1 -0
- package/dist/ports/projectPublisher.d.ts +1 -0
- package/dist/server.d.ts +2 -0
- package/dist/server.js +1144 -0
- package/dist/server.mjs +170 -0
- package/dist/services/buildService.d.ts +8 -0
- package/dist/services/cacheService.d.ts +15 -0
- package/dist/services/pnpmMaintenance.d.ts +4 -0
- package/dist/services/uploadService.d.ts +36 -0
- package/dist/template/re-shake-share/Collect.js +115 -0
- package/dist/template/re-shake-share/EmitManifest.js +49 -0
- package/dist/template/re-shake-share/index.ts +1 -0
- package/dist/template/re-shake-share/package.json +23 -0
- package/dist/template/re-shake-share/rspack.config.ts +33 -0
- package/dist/utils/runCommand.d.ts +20 -0
- package/dist/utils/runtimeEnv.d.ts +3 -0
- package/dist/utils/uploadSdk.d.ts +10 -0
- package/package.json +68 -0
package/dist/server.mjs
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import node_fs from "node:fs";
|
|
3
|
+
import node_path from "node:path";
|
|
4
|
+
import { createServer, createOssAdapterRegistry, createLogger, DEFAULT_STATIC_ROOT, createApp, createAdapterDeps } from "./876.mjs";
|
|
5
|
+
const DEFAULT_PRUNE_INTERVAL_MS = 3600000;
|
|
6
|
+
function envStr(env, name, fallback) {
|
|
7
|
+
const v = env[name];
|
|
8
|
+
if (void 0 === v || '' === v) return fallback;
|
|
9
|
+
return v;
|
|
10
|
+
}
|
|
11
|
+
function envNum(env, name, fallback) {
|
|
12
|
+
const v = envStr(env, name);
|
|
13
|
+
if (!v) return fallback;
|
|
14
|
+
const n = Number(v);
|
|
15
|
+
return Number.isFinite(n) ? n : fallback;
|
|
16
|
+
}
|
|
17
|
+
function resolveOssEnv(params) {
|
|
18
|
+
const adapterId = envStr(params.env, 'ADAPTER_ID', params.defaultAdapterId) ?? 'local';
|
|
19
|
+
const adapter = params.registry.getAdapterById(adapterId);
|
|
20
|
+
const adapterConfig = adapter.fromEnv ? adapter.fromEnv(params.env) : {};
|
|
21
|
+
return {
|
|
22
|
+
adapterId,
|
|
23
|
+
adapterConfig,
|
|
24
|
+
corsOrigin: envStr(params.env, 'CORS_ORIGIN', '*') ?? '*',
|
|
25
|
+
staticRootDir: envStr(params.env, 'LOCAL_STORE_DIR', DEFAULT_STATIC_ROOT) ?? DEFAULT_STATIC_ROOT,
|
|
26
|
+
logLevel: envStr(params.env, 'LOG_LEVEL', 'info') ?? 'info',
|
|
27
|
+
port: envNum(params.env, 'PORT', 3000),
|
|
28
|
+
hostname: envStr(params.env, 'HOST', '0.0.0.0') ?? '0.0.0.0',
|
|
29
|
+
pruneIntervalMs: envNum(params.env, 'PNPM_PRUNE_INTERVAL_MS', DEFAULT_PRUNE_INTERVAL_MS),
|
|
30
|
+
runtimeEnv: params.env
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
const defaultBasePath = '/tree-shaking';
|
|
34
|
+
const contentTypeByExt = (filePath)=>{
|
|
35
|
+
if (filePath.endsWith('.html')) return 'text/html; charset=utf-8';
|
|
36
|
+
if (filePath.endsWith('.js')) return "application/javascript";
|
|
37
|
+
if (filePath.endsWith('.css')) return 'text/css';
|
|
38
|
+
if (filePath.endsWith('.json')) return 'application/json';
|
|
39
|
+
if (filePath.endsWith('.svg')) return 'image/svg+xml';
|
|
40
|
+
if (filePath.endsWith('.png')) return 'image/png';
|
|
41
|
+
if (filePath.endsWith('.jpg') || filePath.endsWith('.jpeg')) return 'image/jpeg';
|
|
42
|
+
if (filePath.endsWith('.webp')) return 'image/webp';
|
|
43
|
+
if (filePath.endsWith('.ico')) return 'image/x-icon';
|
|
44
|
+
return 'application/octet-stream';
|
|
45
|
+
};
|
|
46
|
+
const safeResolve = (rootDir, requestPath)=>{
|
|
47
|
+
const rootResolved = node_path.resolve(rootDir);
|
|
48
|
+
const rel = requestPath.replace(/^\/+/, '');
|
|
49
|
+
const filePath = node_path.resolve(rootResolved, rel);
|
|
50
|
+
if (filePath !== rootResolved && !filePath.startsWith(`${rootResolved}${node_path.sep}`)) return null;
|
|
51
|
+
return filePath;
|
|
52
|
+
};
|
|
53
|
+
function createEmbeddedFrontendAdapter(opts) {
|
|
54
|
+
const basePath = opts.basePath ?? defaultBasePath;
|
|
55
|
+
const normalizedBase = '/' === basePath ? '' : basePath.replace(/\/$/, '');
|
|
56
|
+
const distDir = opts.distDir;
|
|
57
|
+
const indexFile = opts.indexFile ?? 'index.html';
|
|
58
|
+
const spaFallback = opts.spaFallback ?? true;
|
|
59
|
+
const handler = async (c)=>{
|
|
60
|
+
let requestPath = c.req.path;
|
|
61
|
+
try {
|
|
62
|
+
requestPath = decodeURIComponent(requestPath);
|
|
63
|
+
} catch {
|
|
64
|
+
return c.text('Not Found', 404);
|
|
65
|
+
}
|
|
66
|
+
let relPath = requestPath;
|
|
67
|
+
if (normalizedBase && requestPath.startsWith(normalizedBase)) relPath = requestPath.slice(normalizedBase.length);
|
|
68
|
+
if (!relPath || '/' === relPath) relPath = `/${indexFile}`;
|
|
69
|
+
const filePath = safeResolve(distDir, relPath);
|
|
70
|
+
if (filePath) try {
|
|
71
|
+
const stat = await node_fs.promises.stat(filePath);
|
|
72
|
+
if (stat.isFile()) {
|
|
73
|
+
const buf = await node_fs.promises.readFile(filePath);
|
|
74
|
+
return new Response(new Uint8Array(buf), {
|
|
75
|
+
status: 200,
|
|
76
|
+
headers: {
|
|
77
|
+
'Content-Type': contentTypeByExt(filePath)
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
} catch {}
|
|
82
|
+
if (!spaFallback) return c.text('Not Found', 404);
|
|
83
|
+
const fallbackPath = safeResolve(distDir, `/${indexFile}`);
|
|
84
|
+
if (!fallbackPath) return c.text('Not Found', 404);
|
|
85
|
+
try {
|
|
86
|
+
const buf = await node_fs.promises.readFile(fallbackPath);
|
|
87
|
+
return new Response(new Uint8Array(buf), {
|
|
88
|
+
status: 200,
|
|
89
|
+
headers: {
|
|
90
|
+
'Content-Type': contentTypeByExt(fallbackPath)
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
} catch {
|
|
94
|
+
return c.text('Not Found', 404);
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
return {
|
|
98
|
+
id: 'treeshake-embedded-frontend',
|
|
99
|
+
register (app) {
|
|
100
|
+
const base = normalizedBase || '/';
|
|
101
|
+
app.get(base, handler);
|
|
102
|
+
app.get(`${base}/*`, handler);
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
const hasIndexHtml = (dir)=>Boolean(dir && node_fs.existsSync(node_path.join(dir, 'index.html')));
|
|
107
|
+
const resolveFrontendDistDir = ()=>{
|
|
108
|
+
const envDir = process.env.TREESHAKE_FRONTEND_DIST;
|
|
109
|
+
if (envDir) {
|
|
110
|
+
if (hasIndexHtml(envDir)) return envDir;
|
|
111
|
+
throw new Error(`TREESHAKE_FRONTEND_DIST is set but index.html is missing: ${envDir}`);
|
|
112
|
+
}
|
|
113
|
+
const candidates = [
|
|
114
|
+
node_path.resolve(__dirname, 'frontend'),
|
|
115
|
+
node_path.resolve(__dirname, '..', 'frontend')
|
|
116
|
+
];
|
|
117
|
+
for (const candidate of candidates)if (hasIndexHtml(candidate)) return candidate;
|
|
118
|
+
const workspaceCandidates = [
|
|
119
|
+
node_path.resolve(process.cwd(), 'packages', 'treeshake-frontend', 'dist'),
|
|
120
|
+
node_path.resolve(process.cwd(), 'treeshake-frontend', 'dist')
|
|
121
|
+
];
|
|
122
|
+
for (const candidate of workspaceCandidates)if (hasIndexHtml(candidate)) return candidate;
|
|
123
|
+
};
|
|
124
|
+
async function main() {
|
|
125
|
+
const registry = createOssAdapterRegistry();
|
|
126
|
+
const resolved = resolveOssEnv({
|
|
127
|
+
env: {
|
|
128
|
+
...process.env,
|
|
129
|
+
ADAPTER_ID: 'local'
|
|
130
|
+
},
|
|
131
|
+
registry,
|
|
132
|
+
defaultAdapterId: 'local'
|
|
133
|
+
});
|
|
134
|
+
const logger = createLogger({
|
|
135
|
+
level: resolved.logLevel
|
|
136
|
+
});
|
|
137
|
+
const deps = await createAdapterDeps({
|
|
138
|
+
registry,
|
|
139
|
+
adapterId: resolved.adapterId,
|
|
140
|
+
adapterConfig: resolved.adapterConfig,
|
|
141
|
+
logger
|
|
142
|
+
});
|
|
143
|
+
const frontendAdapters = [];
|
|
144
|
+
const distDir = resolveFrontendDistDir();
|
|
145
|
+
if (!distDir) throw new Error('Treeshake UI dist not found. Rebuild the CLI bundle or set TREESHAKE_FRONTEND_DIST.');
|
|
146
|
+
frontendAdapters.push(createEmbeddedFrontendAdapter({
|
|
147
|
+
basePath: process.env.TREESHAKE_FRONTEND_BASE_PATH ?? '/tree-shaking',
|
|
148
|
+
distDir,
|
|
149
|
+
spaFallback: '0' !== process.env.TREESHAKE_FRONTEND_SPA_FALLBACK && 'false' !== process.env.TREESHAKE_FRONTEND_SPA_FALLBACK
|
|
150
|
+
}));
|
|
151
|
+
const app = createApp(deps, {
|
|
152
|
+
corsOrigin: resolved.corsOrigin,
|
|
153
|
+
staticRootDir: resolved.staticRootDir,
|
|
154
|
+
pruneIntervalMs: resolved.pruneIntervalMs,
|
|
155
|
+
logger,
|
|
156
|
+
runtimeEnv: resolved.runtimeEnv,
|
|
157
|
+
frontendAdapters
|
|
158
|
+
});
|
|
159
|
+
createServer({
|
|
160
|
+
app,
|
|
161
|
+
port: resolved.port,
|
|
162
|
+
hostname: resolved.hostname
|
|
163
|
+
});
|
|
164
|
+
console.log('Build service listening.');
|
|
165
|
+
if (frontendAdapters.length) console.log('Treeshake UI available.');
|
|
166
|
+
}
|
|
167
|
+
main().catch((err)=>{
|
|
168
|
+
console.error(err);
|
|
169
|
+
process.exit(1);
|
|
170
|
+
});
|
|
@@ -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) => Promise<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): Promise<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,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, store: ObjectStore): 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,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,68 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@module-federation/treeshake-server",
|
|
3
|
+
"version": "0.0.0-chore-bump-node-22-20260710161714",
|
|
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": "bin/treeshake-server.js"
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"bin/",
|
|
20
|
+
"dist/",
|
|
21
|
+
"README.md"
|
|
22
|
+
],
|
|
23
|
+
"keywords": [],
|
|
24
|
+
"author": "",
|
|
25
|
+
"license": "ISC",
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"@hono/node-server": "1.19.13",
|
|
28
|
+
"@hono/zod-validator": "0.7.4",
|
|
29
|
+
"dotenv": "16.4.5",
|
|
30
|
+
"hono": "4.12.26",
|
|
31
|
+
"json-stable-stringify": "1.3.0",
|
|
32
|
+
"nanoid": "5.1.6",
|
|
33
|
+
"p-limit": "7.2.0",
|
|
34
|
+
"pino": "10.1.0",
|
|
35
|
+
"undici": "7.28.0",
|
|
36
|
+
"zod": "4.1.12"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@biomejs/biome": "^2.3.3",
|
|
40
|
+
"@playwright/test": "^1.57.0",
|
|
41
|
+
"@rstest/core": "^0.6.5",
|
|
42
|
+
"@types/node": "^20.11.30",
|
|
43
|
+
"@vercel/nft": "^1.1.1",
|
|
44
|
+
"pino-pretty": "^13.1.2",
|
|
45
|
+
"ts-node": "^10.9.2",
|
|
46
|
+
"typescript": "^6.0.3"
|
|
47
|
+
},
|
|
48
|
+
"engines": {
|
|
49
|
+
"node": ">=20.19.5",
|
|
50
|
+
"pnpm": ">=10.18.1"
|
|
51
|
+
},
|
|
52
|
+
"publishConfig": {
|
|
53
|
+
"access": "public"
|
|
54
|
+
},
|
|
55
|
+
"repository": {
|
|
56
|
+
"type": "git",
|
|
57
|
+
"url": "git+https://github.com/module-federation/core.git",
|
|
58
|
+
"directory": "packages/treeshake-server"
|
|
59
|
+
},
|
|
60
|
+
"scripts": {
|
|
61
|
+
"build": "rslib build && node scripts/copy-frontend.js",
|
|
62
|
+
"lint": "biome lint src scripts",
|
|
63
|
+
"prestart": "pnpm exec turbo run build --filter=@module-federation/treeshake-server",
|
|
64
|
+
"dev": "node scripts/dev.js",
|
|
65
|
+
"start": "node dist/server.js",
|
|
66
|
+
"test": "node ../../scripts/ensure-playwright.js && rstest"
|
|
67
|
+
}
|
|
68
|
+
}
|