@esmx/core 3.0.0-rc.117 → 3.0.0-rc.118
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 +17 -3
- package/README.zh-CN.md +20 -6
- package/dist/app.mjs +4 -2
- package/dist/cli/cli.d.ts +1 -0
- package/dist/cli/cli.mjs +28 -1
- package/dist/cli/validate.d.ts +15 -0
- package/dist/cli/validate.mjs +139 -0
- package/dist/cli/validate.test.d.ts +1 -0
- package/dist/cli/validate.test.mjs +216 -0
- package/dist/core.d.ts +31 -0
- package/dist/core.mjs +71 -5
- package/dist/declaration/index.d.ts +47 -0
- package/dist/declaration/index.mjs +63 -0
- package/dist/declaration/index.test.d.ts +1 -0
- package/dist/declaration/index.test.mjs +202 -0
- package/dist/declaration/lower.d.ts +11 -0
- package/dist/declaration/lower.mjs +78 -0
- package/dist/declaration/lower.test.d.ts +1 -0
- package/dist/declaration/lower.test.mjs +333 -0
- package/dist/declaration/reader.d.ts +28 -0
- package/dist/declaration/reader.mjs +55 -0
- package/dist/declaration/reinit.e2e.test.d.ts +1 -0
- package/dist/declaration/reinit.e2e.test.mjs +117 -0
- package/dist/declaration/resolver.d.ts +59 -0
- package/dist/declaration/resolver.mjs +430 -0
- package/dist/declaration/resolver.test.d.ts +1 -0
- package/dist/declaration/resolver.test.mjs +1005 -0
- package/dist/declaration/schema.d.ts +89 -0
- package/dist/declaration/schema.mjs +282 -0
- package/dist/declaration/schema.test.d.ts +1 -0
- package/dist/declaration/schema.test.mjs +101 -0
- package/dist/declaration/semver.d.ts +22 -0
- package/dist/declaration/semver.mjs +229 -0
- package/dist/declaration/semver.test.d.ts +1 -0
- package/dist/declaration/semver.test.mjs +87 -0
- package/dist/declaration/test-fixtures.d.ts +18 -0
- package/dist/declaration/test-fixtures.mjs +69 -0
- package/dist/declaration/types.d.ts +58 -0
- package/dist/declaration/types.mjs +21 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.mjs +10 -0
- package/dist/manifest-json.d.ts +61 -0
- package/dist/manifest-json.mjs +68 -5
- package/dist/manifest-json.test.d.ts +1 -0
- package/dist/manifest-json.test.mjs +196 -0
- package/dist/module-config.d.ts +45 -5
- package/dist/module-config.mjs +60 -49
- package/dist/module-config.test.mjs +227 -91
- package/dist/pack-config.d.ts +2 -9
- package/dist/render-context.mjs +22 -5
- package/dist/utils/import-map.d.ts +23 -3
- package/dist/utils/import-map.mjs +38 -1
- package/dist/utils/import-map.test.mjs +228 -0
- package/package.json +9 -6
- package/src/app.ts +5 -2
- package/src/cli/cli.ts +44 -1
- package/src/cli/validate.test.ts +251 -0
- package/src/cli/validate.ts +196 -0
- package/src/core.ts +84 -5
- package/src/declaration/index.test.ts +223 -0
- package/src/declaration/index.ts +135 -0
- package/src/declaration/lower.test.ts +372 -0
- package/src/declaration/lower.ts +135 -0
- package/src/declaration/reader.ts +93 -0
- package/src/declaration/reinit.e2e.test.ts +148 -0
- package/src/declaration/resolver.test.ts +1111 -0
- package/src/declaration/resolver.ts +638 -0
- package/src/declaration/schema.test.ts +118 -0
- package/src/declaration/schema.ts +339 -0
- package/src/declaration/semver.test.ts +101 -0
- package/src/declaration/semver.ts +278 -0
- package/src/declaration/test-fixtures.ts +96 -0
- package/src/declaration/types.ts +71 -0
- package/src/index.ts +28 -1
- package/src/manifest-json.test.ts +236 -0
- package/src/manifest-json.ts +166 -5
- package/src/module-config.test.ts +266 -105
- package/src/module-config.ts +130 -58
- package/src/pack-config.ts +2 -9
- package/src/render-context.ts +34 -6
- package/src/utils/import-map.test.ts +261 -0
- package/src/utils/import-map.ts +92 -6
|
@@ -1,15 +1,18 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
1
2
|
import path from 'node:path';
|
|
2
3
|
import { describe, expect, it } from 'vitest';
|
|
3
4
|
import {
|
|
4
5
|
addPackageExportsToScopes,
|
|
5
|
-
|
|
6
|
+
createEntryExports,
|
|
7
|
+
DEFAULT_MODULE_ENTRY,
|
|
8
|
+
getEntryChunkId,
|
|
6
9
|
getEnvironmentExports,
|
|
7
10
|
getEnvironmentImports,
|
|
8
|
-
getEnvironmentScopes,
|
|
9
11
|
getEnvironments,
|
|
10
12
|
getLinks,
|
|
11
13
|
type ModuleConfig,
|
|
12
14
|
parsedExportValue,
|
|
15
|
+
parseEntryConfig,
|
|
13
16
|
parseModuleConfig,
|
|
14
17
|
processExportArray,
|
|
15
18
|
processObjectExport,
|
|
@@ -234,43 +237,18 @@ describe('Module Config Parser', () => {
|
|
|
234
237
|
});
|
|
235
238
|
});
|
|
236
239
|
|
|
237
|
-
describe('getEnvironmentScopes', () => {
|
|
238
|
-
it('should process scoped imports per environment', () => {
|
|
239
|
-
const scopes = {
|
|
240
|
-
utils: {
|
|
241
|
-
lodash: 'lodash',
|
|
242
|
-
moment: {
|
|
243
|
-
client: 'moment',
|
|
244
|
-
server: 'moment/server'
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
};
|
|
248
|
-
const result = getEnvironmentScopes('client', scopes);
|
|
249
|
-
expect(result.utils.lodash).toBe('lodash');
|
|
250
|
-
expect(result.utils.moment).toBe('moment');
|
|
251
|
-
});
|
|
252
|
-
|
|
253
|
-
it('should handle empty scopes object', () => {
|
|
254
|
-
const result = getEnvironmentScopes('client', {});
|
|
255
|
-
expect(Object.keys(result)).toHaveLength(0);
|
|
256
|
-
});
|
|
257
|
-
});
|
|
258
|
-
|
|
259
240
|
describe('getEnvironments', () => {
|
|
260
|
-
it('should combine imports
|
|
241
|
+
it('should combine imports and exports into the root scope', () => {
|
|
261
242
|
const config: ModuleConfig = {
|
|
262
243
|
imports: {
|
|
263
244
|
react: 'react'
|
|
264
|
-
},
|
|
265
|
-
scopes: {
|
|
266
|
-
utils: {
|
|
267
|
-
lodash: 'lodash'
|
|
268
|
-
}
|
|
269
245
|
}
|
|
270
246
|
};
|
|
271
247
|
const result = getEnvironments(config, 'client', 'test-module');
|
|
272
248
|
expect(result.imports.react).toBe('react');
|
|
273
|
-
|
|
249
|
+
// The only scope is the derived root scope, carrying imports.
|
|
250
|
+
expect(Object.keys(result.scopes)).toEqual(['']);
|
|
251
|
+
expect(result.scopes[''].react).toBe('react');
|
|
274
252
|
expect(result.exports).toBeDefined();
|
|
275
253
|
});
|
|
276
254
|
|
|
@@ -298,49 +276,22 @@ describe('Module Config Parser', () => {
|
|
|
298
276
|
expect(emptyScope['./src/component']).toBeUndefined();
|
|
299
277
|
});
|
|
300
278
|
|
|
301
|
-
it('should
|
|
302
|
-
const config: ModuleConfig = {
|
|
303
|
-
exports: ['pkg:lodash'],
|
|
304
|
-
scopes: {
|
|
305
|
-
'': {
|
|
306
|
-
existing: 'existing-value'
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
};
|
|
310
|
-
const result = getEnvironments(config, 'client', 'test-module');
|
|
311
|
-
const emptyScope = result.scopes[''];
|
|
312
|
-
expect(emptyScope).toBeDefined();
|
|
313
|
-
expect(emptyScope.existing).toBe('existing-value');
|
|
314
|
-
expect(emptyScope.lodash).toBe('test-module/lodash');
|
|
315
|
-
});
|
|
316
|
-
|
|
317
|
-
it('should verify the specific scopes merging logic with imports', () => {
|
|
279
|
+
it('should merge imports and pkg exports into the single root scope', () => {
|
|
318
280
|
const config: ModuleConfig = {
|
|
319
281
|
imports: {
|
|
320
282
|
react: 'react',
|
|
321
283
|
vue: 'vue'
|
|
322
284
|
},
|
|
323
|
-
|
|
324
|
-
utils: {
|
|
325
|
-
lodash: 'lodash'
|
|
326
|
-
},
|
|
327
|
-
'': {
|
|
328
|
-
existing: 'existing-value'
|
|
329
|
-
}
|
|
330
|
-
}
|
|
285
|
+
exports: ['pkg:lodash']
|
|
331
286
|
};
|
|
332
287
|
const result = getEnvironments(config, 'client', 'test-module');
|
|
333
288
|
|
|
334
|
-
//
|
|
335
|
-
|
|
336
|
-
expect(result.scopes
|
|
337
|
-
expect(result.scopes[''].react).toBe('react');
|
|
338
|
-
expect(result.scopes[''].vue).toBe('vue');
|
|
339
|
-
|
|
340
|
-
// 验证其他 scopes 不受影响
|
|
341
|
-
expect(result.scopes.utils.lodash).toBe('lodash');
|
|
342
|
-
|
|
343
|
-
// 验证 result.imports 也包含相同的 imports
|
|
289
|
+
// The derived root scope carries both supply imports and the
|
|
290
|
+
// module's own pkg-exports — no user-authored scopes exist.
|
|
291
|
+
expect(Object.keys(result.scopes)).toEqual(['']);
|
|
292
|
+
expect(result.scopes[''].react).toBe('react');
|
|
293
|
+
expect(result.scopes[''].vue).toBe('vue');
|
|
294
|
+
expect(result.scopes[''].lodash).toBe('test-module/lodash');
|
|
344
295
|
expect(result.imports.react).toBe('react');
|
|
345
296
|
expect(result.imports.vue).toBe('vue');
|
|
346
297
|
});
|
|
@@ -403,40 +354,6 @@ describe('Module Config Parser', () => {
|
|
|
403
354
|
});
|
|
404
355
|
|
|
405
356
|
describe('Export Processing Functions', () => {
|
|
406
|
-
describe('createDefaultExports', () => {
|
|
407
|
-
it('should generate client default exports', () => {
|
|
408
|
-
const result = createDefaultExports('client');
|
|
409
|
-
expect(result['src/entry.client'].file).toBe(
|
|
410
|
-
'./src/entry.client'
|
|
411
|
-
);
|
|
412
|
-
expect(result['src/entry.server'].file).toBe('');
|
|
413
|
-
});
|
|
414
|
-
|
|
415
|
-
it('should generate server default exports', () => {
|
|
416
|
-
const result = createDefaultExports('server');
|
|
417
|
-
expect(result['src/entry.client'].file).toBe('');
|
|
418
|
-
expect(result['src/entry.server'].file).toBe(
|
|
419
|
-
'./src/entry.server'
|
|
420
|
-
);
|
|
421
|
-
});
|
|
422
|
-
|
|
423
|
-
it('should handle client environment switch case', () => {
|
|
424
|
-
const result = createDefaultExports('client');
|
|
425
|
-
expect(result['src/entry.client'].file).toBe(
|
|
426
|
-
'./src/entry.client'
|
|
427
|
-
);
|
|
428
|
-
expect(result['src/entry.server'].file).toBe('');
|
|
429
|
-
});
|
|
430
|
-
|
|
431
|
-
it('should handle server environment switch case', () => {
|
|
432
|
-
const result = createDefaultExports('server');
|
|
433
|
-
expect(result['src/entry.client'].file).toBe('');
|
|
434
|
-
expect(result['src/entry.server'].file).toBe(
|
|
435
|
-
'./src/entry.server'
|
|
436
|
-
);
|
|
437
|
-
});
|
|
438
|
-
});
|
|
439
|
-
|
|
440
357
|
describe('processStringExport', () => {
|
|
441
358
|
it('should parse simple string export', () => {
|
|
442
359
|
const result = processStringExport('./src/component');
|
|
@@ -725,11 +642,6 @@ describe('Module Config Parser', () => {
|
|
|
725
642
|
server: 'vue/server'
|
|
726
643
|
}
|
|
727
644
|
},
|
|
728
|
-
scopes: {
|
|
729
|
-
utils: {
|
|
730
|
-
lodash: 'lodash'
|
|
731
|
-
}
|
|
732
|
-
},
|
|
733
645
|
exports: [
|
|
734
646
|
'./src/component',
|
|
735
647
|
{
|
|
@@ -826,3 +738,252 @@ describe('Module Config Parser', () => {
|
|
|
826
738
|
});
|
|
827
739
|
});
|
|
828
740
|
});
|
|
741
|
+
|
|
742
|
+
describe('Framework entry threading (RFC 0001 Phase 2)', () => {
|
|
743
|
+
describe('parseEntryConfig', () => {
|
|
744
|
+
it('should default to legacy entries for an empty config', () => {
|
|
745
|
+
const entry = parseEntryConfig({});
|
|
746
|
+
|
|
747
|
+
expect(entry).toEqual({
|
|
748
|
+
client: {
|
|
749
|
+
name: 'src/entry.client',
|
|
750
|
+
file: './src/entry.client'
|
|
751
|
+
},
|
|
752
|
+
server: {
|
|
753
|
+
name: 'src/entry.server',
|
|
754
|
+
file: './src/entry.server'
|
|
755
|
+
}
|
|
756
|
+
});
|
|
757
|
+
});
|
|
758
|
+
|
|
759
|
+
it('should resolve both entries to null for lib modules', () => {
|
|
760
|
+
const entry = parseEntryConfig({ lib: true });
|
|
761
|
+
|
|
762
|
+
expect(entry).toEqual({ client: null, server: null });
|
|
763
|
+
});
|
|
764
|
+
|
|
765
|
+
it('should derive the export name from a custom entry file path', () => {
|
|
766
|
+
const entry = parseEntryConfig({
|
|
767
|
+
entry: { client: './custom/main.ts' }
|
|
768
|
+
});
|
|
769
|
+
|
|
770
|
+
expect(entry.client).toEqual({
|
|
771
|
+
name: 'custom/main',
|
|
772
|
+
file: './custom/main.ts'
|
|
773
|
+
});
|
|
774
|
+
expect(entry.server).toEqual(DEFAULT_MODULE_ENTRY.server);
|
|
775
|
+
});
|
|
776
|
+
|
|
777
|
+
it('should keep the path-derived default name for a standard declaration path', () => {
|
|
778
|
+
const entry = parseEntryConfig({
|
|
779
|
+
entry: { client: './src/entry.client.ts' }
|
|
780
|
+
});
|
|
781
|
+
|
|
782
|
+
expect(entry.client).toEqual({
|
|
783
|
+
name: 'src/entry.client',
|
|
784
|
+
file: './src/entry.client.ts'
|
|
785
|
+
});
|
|
786
|
+
});
|
|
787
|
+
|
|
788
|
+
it('should disable a side declared as false', () => {
|
|
789
|
+
const entry = parseEntryConfig({ entry: { server: false } });
|
|
790
|
+
|
|
791
|
+
expect(entry.server).toBeNull();
|
|
792
|
+
expect(entry.client).toEqual(DEFAULT_MODULE_ENTRY.client);
|
|
793
|
+
});
|
|
794
|
+
});
|
|
795
|
+
|
|
796
|
+
describe('parseModuleConfig entry population', () => {
|
|
797
|
+
it('should populate entry for legacy configs', () => {
|
|
798
|
+
const result = parseModuleConfig('test-module', '/test/root');
|
|
799
|
+
|
|
800
|
+
expect(result.entry.client).toEqual(DEFAULT_MODULE_ENTRY.client);
|
|
801
|
+
expect(result.entry.server).toEqual(DEFAULT_MODULE_ENTRY.server);
|
|
802
|
+
});
|
|
803
|
+
|
|
804
|
+
it('should populate null entries for lib configs and omit entry exports', () => {
|
|
805
|
+
const result = parseModuleConfig('test-module', '/test/root', {
|
|
806
|
+
lib: true
|
|
807
|
+
});
|
|
808
|
+
|
|
809
|
+
expect(result.entry).toEqual({ client: null, server: null });
|
|
810
|
+
expect(result.environments.client.exports).toEqual({});
|
|
811
|
+
expect(result.environments.server.exports).toEqual({});
|
|
812
|
+
});
|
|
813
|
+
|
|
814
|
+
it('should thread a custom entry into the environment exports', () => {
|
|
815
|
+
const result = parseModuleConfig('test-module', '/test/root', {
|
|
816
|
+
entry: { client: './custom/main.ts' }
|
|
817
|
+
});
|
|
818
|
+
|
|
819
|
+
expect(result.environments.client.exports['custom/main']).toEqual({
|
|
820
|
+
name: 'custom/main',
|
|
821
|
+
file: './custom/main.ts',
|
|
822
|
+
pkg: false
|
|
823
|
+
});
|
|
824
|
+
expect(result.environments.server.exports['custom/main']).toEqual({
|
|
825
|
+
name: 'custom/main',
|
|
826
|
+
file: '',
|
|
827
|
+
pkg: false
|
|
828
|
+
});
|
|
829
|
+
expect(
|
|
830
|
+
result.environments.server.exports['src/entry.server']
|
|
831
|
+
).toEqual({
|
|
832
|
+
name: 'src/entry.server',
|
|
833
|
+
file: './src/entry.server',
|
|
834
|
+
pkg: false
|
|
835
|
+
});
|
|
836
|
+
expect(
|
|
837
|
+
result.environments.client.exports['src/entry.client']
|
|
838
|
+
).toBeUndefined();
|
|
839
|
+
});
|
|
840
|
+
});
|
|
841
|
+
|
|
842
|
+
describe('default entry exports single source of truth', () => {
|
|
843
|
+
it('should derive byte-identical defaults from DEFAULT_MODULE_ENTRY', () => {
|
|
844
|
+
const entry = parseEntryConfig({});
|
|
845
|
+
|
|
846
|
+
expect(createEntryExports(entry, 'client')).toEqual({
|
|
847
|
+
'src/entry.client': {
|
|
848
|
+
name: 'src/entry.client',
|
|
849
|
+
file: './src/entry.client',
|
|
850
|
+
pkg: false
|
|
851
|
+
},
|
|
852
|
+
'src/entry.server': {
|
|
853
|
+
name: 'src/entry.server',
|
|
854
|
+
file: '',
|
|
855
|
+
pkg: false
|
|
856
|
+
}
|
|
857
|
+
});
|
|
858
|
+
expect(createEntryExports(entry, 'server')).toEqual({
|
|
859
|
+
'src/entry.client': {
|
|
860
|
+
name: 'src/entry.client',
|
|
861
|
+
file: '',
|
|
862
|
+
pkg: false
|
|
863
|
+
},
|
|
864
|
+
'src/entry.server': {
|
|
865
|
+
name: 'src/entry.server',
|
|
866
|
+
file: './src/entry.server',
|
|
867
|
+
pkg: false
|
|
868
|
+
}
|
|
869
|
+
});
|
|
870
|
+
});
|
|
871
|
+
});
|
|
872
|
+
|
|
873
|
+
describe('getEntryChunkId', () => {
|
|
874
|
+
it('should append .ts to the legacy extensionless default', () => {
|
|
875
|
+
const id = getEntryChunkId(
|
|
876
|
+
'test-module',
|
|
877
|
+
DEFAULT_MODULE_ENTRY.client
|
|
878
|
+
);
|
|
879
|
+
|
|
880
|
+
expect(id).toBe('test-module@src/entry.client.ts');
|
|
881
|
+
});
|
|
882
|
+
|
|
883
|
+
it('should preserve an explicit extension on custom entries', () => {
|
|
884
|
+
expect(
|
|
885
|
+
getEntryChunkId('m', {
|
|
886
|
+
name: 'custom/main',
|
|
887
|
+
file: './custom/main.tsx'
|
|
888
|
+
})
|
|
889
|
+
).toBe('m@custom/main.tsx');
|
|
890
|
+
expect(
|
|
891
|
+
getEntryChunkId('m', {
|
|
892
|
+
name: 'src/entry.client',
|
|
893
|
+
file: './src/entry.client.ts'
|
|
894
|
+
})
|
|
895
|
+
).toBe('m@src/entry.client.ts');
|
|
896
|
+
});
|
|
897
|
+
});
|
|
898
|
+
|
|
899
|
+
describe('fleet invariant: built example manifests', () => {
|
|
900
|
+
const repoRoot = path.resolve(import.meta.dirname, '../../..');
|
|
901
|
+
const examplesDir = path.join(repoRoot, 'examples');
|
|
902
|
+
|
|
903
|
+
const findClientManifests = (dir: string): string[] => {
|
|
904
|
+
if (!fs.existsSync(dir)) {
|
|
905
|
+
return [];
|
|
906
|
+
}
|
|
907
|
+
const found: string[] = [];
|
|
908
|
+
for (const item of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
909
|
+
if (!item.isDirectory() || item.name === 'node_modules') {
|
|
910
|
+
continue;
|
|
911
|
+
}
|
|
912
|
+
const child = path.join(dir, item.name);
|
|
913
|
+
const manifest = path.join(child, 'dist/client/manifest.json');
|
|
914
|
+
if (fs.existsSync(manifest)) {
|
|
915
|
+
found.push(manifest);
|
|
916
|
+
}
|
|
917
|
+
found.push(...findClientManifests(child));
|
|
918
|
+
}
|
|
919
|
+
return found;
|
|
920
|
+
};
|
|
921
|
+
|
|
922
|
+
const manifests = findClientManifests(examplesDir);
|
|
923
|
+
|
|
924
|
+
it.skipIf(manifests.length === 0)(
|
|
925
|
+
'should compute the exact historical chunk seed for every built example',
|
|
926
|
+
() => {
|
|
927
|
+
expect(manifests.length).toBeGreaterThan(0);
|
|
928
|
+
for (const manifestPath of manifests) {
|
|
929
|
+
const manifest = JSON.parse(
|
|
930
|
+
fs.readFileSync(manifestPath, 'utf-8')
|
|
931
|
+
) as {
|
|
932
|
+
name: string;
|
|
933
|
+
chunks: Record<string, unknown>;
|
|
934
|
+
exports: Record<string, unknown>;
|
|
935
|
+
};
|
|
936
|
+
const parsed = parseModuleConfig(
|
|
937
|
+
manifest.name,
|
|
938
|
+
path.resolve(manifestPath, '../../..')
|
|
939
|
+
);
|
|
940
|
+
if (!parsed.entry.client) {
|
|
941
|
+
continue;
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
const seed = getEntryChunkId(
|
|
945
|
+
manifest.name,
|
|
946
|
+
parsed.entry.client
|
|
947
|
+
);
|
|
948
|
+
|
|
949
|
+
expect(seed).toBe(`${manifest.name}@src/entry.client.ts`);
|
|
950
|
+
if (manifest.exports['src/entry.client']) {
|
|
951
|
+
expect(Object.keys(manifest.chunks)).toContain(seed);
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
);
|
|
956
|
+
|
|
957
|
+
it.skipIf(
|
|
958
|
+
!fs.existsSync(
|
|
959
|
+
path.join(
|
|
960
|
+
examplesDir,
|
|
961
|
+
'micro-app/ssr-micro-shared/dist/client/manifest.json'
|
|
962
|
+
)
|
|
963
|
+
)
|
|
964
|
+
)(
|
|
965
|
+
'should match the ssr-micro-shared manifest chunk key exactly',
|
|
966
|
+
() => {
|
|
967
|
+
const manifestPath = path.join(
|
|
968
|
+
examplesDir,
|
|
969
|
+
'micro-app/ssr-micro-shared/dist/client/manifest.json'
|
|
970
|
+
);
|
|
971
|
+
const manifest = JSON.parse(
|
|
972
|
+
fs.readFileSync(manifestPath, 'utf-8')
|
|
973
|
+
) as { name: string; chunks: Record<string, unknown> };
|
|
974
|
+
const parsed = parseModuleConfig(
|
|
975
|
+
manifest.name,
|
|
976
|
+
path.resolve(manifestPath, '../../..')
|
|
977
|
+
);
|
|
978
|
+
|
|
979
|
+
const seed = getEntryChunkId(
|
|
980
|
+
manifest.name,
|
|
981
|
+
parsed.entry.client!
|
|
982
|
+
);
|
|
983
|
+
|
|
984
|
+
expect(seed).toBe('ssr-micro-shared@src/entry.client.ts');
|
|
985
|
+
expect(Object.keys(manifest.chunks)).toContain(seed);
|
|
986
|
+
}
|
|
987
|
+
);
|
|
988
|
+
});
|
|
989
|
+
});
|
package/src/module-config.ts
CHANGED
|
@@ -3,12 +3,24 @@ import type { BuildEnvironment } from './core';
|
|
|
3
3
|
|
|
4
4
|
export interface ModuleConfig {
|
|
5
5
|
lib?: boolean;
|
|
6
|
+
entry?: ModuleConfigEntry;
|
|
6
7
|
links?: Record<string, string>;
|
|
7
8
|
imports?: ModuleConfigImportMapping;
|
|
8
|
-
scopes?: Record<string, ModuleConfigImportMapping>;
|
|
9
9
|
exports?: ModuleConfigExportExports;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
+
/**
|
|
13
|
+
* Framework entry declaration (RFC 0001 Phase 2).
|
|
14
|
+
*
|
|
15
|
+
* Per side: a `./`-relative source file path declares a custom entry,
|
|
16
|
+
* `false` disables the side, and `undefined` keeps the legacy default
|
|
17
|
+
* (`./src/entry.client` / `./src/entry.server`).
|
|
18
|
+
*/
|
|
19
|
+
export interface ModuleConfigEntry {
|
|
20
|
+
client?: string | false;
|
|
21
|
+
server?: string | false;
|
|
22
|
+
}
|
|
23
|
+
|
|
12
24
|
export type ModuleConfigImportMapping = Record<
|
|
13
25
|
string,
|
|
14
26
|
string | Record<BuildEnvironment, string>
|
|
@@ -30,6 +42,7 @@ export interface ParsedModuleConfig {
|
|
|
30
42
|
name: string;
|
|
31
43
|
root: string;
|
|
32
44
|
lib: boolean;
|
|
45
|
+
entry: ParsedModuleConfigEntry;
|
|
33
46
|
links: Record<string, ParsedModuleConfigLink>;
|
|
34
47
|
environments: {
|
|
35
48
|
client: ParsedModuleConfigEnvironment;
|
|
@@ -37,6 +50,19 @@ export interface ParsedModuleConfig {
|
|
|
37
50
|
};
|
|
38
51
|
}
|
|
39
52
|
|
|
53
|
+
/** A resolved framework entry: export name + source file path. */
|
|
54
|
+
export interface ParsedModuleConfigEntryTarget {
|
|
55
|
+
/** Export name (import specifier suffix), e.g. 'src/entry.client'. */
|
|
56
|
+
name: string;
|
|
57
|
+
/** Source file path, e.g. './src/entry.client' or './custom/main.ts'. */
|
|
58
|
+
file: string;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface ParsedModuleConfigEntry {
|
|
62
|
+
client: ParsedModuleConfigEntryTarget | null;
|
|
63
|
+
server: ParsedModuleConfigEntryTarget | null;
|
|
64
|
+
}
|
|
65
|
+
|
|
40
66
|
export type ParsedModuleConfigExports = Record<
|
|
41
67
|
string,
|
|
42
68
|
ParsedModuleConfigExport
|
|
@@ -63,19 +89,88 @@ export interface ParsedModuleConfigLink {
|
|
|
63
89
|
serverManifestJson: string;
|
|
64
90
|
}
|
|
65
91
|
|
|
92
|
+
/**
|
|
93
|
+
* Single source of truth for the default framework entries, used as the
|
|
94
|
+
* fallback when a module declares no explicit `entry` (RFC 0001 Phase 2
|
|
95
|
+
* threads resolved entries through the config instead of hard-coding them).
|
|
96
|
+
*/
|
|
97
|
+
export const DEFAULT_MODULE_ENTRY: Record<
|
|
98
|
+
'client' | 'server',
|
|
99
|
+
ParsedModuleConfigEntryTarget
|
|
100
|
+
> = {
|
|
101
|
+
client: { name: 'src/entry.client', file: './src/entry.client' },
|
|
102
|
+
server: { name: 'src/entry.server', file: './src/entry.server' }
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const FILE_EXT_REGEX =
|
|
106
|
+
/\.(js|mjs|cjs|jsx|mjsx|cjsx|ts|mts|cts|tsx|mtsx|ctsx)$/i;
|
|
107
|
+
|
|
108
|
+
function parseEntryTarget(
|
|
109
|
+
value: string | false | undefined,
|
|
110
|
+
fallback: ParsedModuleConfigEntryTarget
|
|
111
|
+
): ParsedModuleConfigEntryTarget | null {
|
|
112
|
+
if (value === false) {
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
if (value === undefined) {
|
|
116
|
+
return { ...fallback };
|
|
117
|
+
}
|
|
118
|
+
const relativePath = value.startsWith('./') ? value.slice(2) : value;
|
|
119
|
+
const parsed = parsedExportValue(`root:${relativePath}`);
|
|
120
|
+
return { name: parsed.name, file: parsed.file };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export function parseEntryConfig(
|
|
124
|
+
config: ModuleConfig
|
|
125
|
+
): ParsedModuleConfigEntry {
|
|
126
|
+
if (config.lib) {
|
|
127
|
+
return { client: null, server: null };
|
|
128
|
+
}
|
|
129
|
+
return {
|
|
130
|
+
client: parseEntryTarget(
|
|
131
|
+
config.entry?.client,
|
|
132
|
+
DEFAULT_MODULE_ENTRY.client
|
|
133
|
+
),
|
|
134
|
+
server: parseEntryTarget(
|
|
135
|
+
config.entry?.server,
|
|
136
|
+
DEFAULT_MODULE_ENTRY.server
|
|
137
|
+
)
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Manifest chunk identifier for an entry target: `<module>@<srcRelPath>`
|
|
143
|
+
* including the file extension (bundlers key chunks by the resolved source
|
|
144
|
+
* file, so the legacy extensionless default maps to `.ts`).
|
|
145
|
+
*/
|
|
146
|
+
export function getEntryChunkId(
|
|
147
|
+
moduleName: string,
|
|
148
|
+
target: ParsedModuleConfigEntryTarget
|
|
149
|
+
): string {
|
|
150
|
+
const relativePath = target.file.startsWith('./')
|
|
151
|
+
? target.file.slice(2)
|
|
152
|
+
: target.file;
|
|
153
|
+
const withExtension = FILE_EXT_REGEX.test(relativePath)
|
|
154
|
+
? relativePath
|
|
155
|
+
: `${relativePath}.ts`;
|
|
156
|
+
return `${moduleName}@${withExtension}`;
|
|
157
|
+
}
|
|
158
|
+
|
|
66
159
|
export function parseModuleConfig(
|
|
67
160
|
name: string,
|
|
68
161
|
root: string,
|
|
69
162
|
config: ModuleConfig = {}
|
|
70
163
|
): ParsedModuleConfig {
|
|
164
|
+
const entry = parseEntryConfig(config);
|
|
71
165
|
return {
|
|
72
166
|
name,
|
|
73
167
|
root,
|
|
74
168
|
lib: config.lib ?? false,
|
|
169
|
+
entry,
|
|
75
170
|
links: getLinks(name, root, config),
|
|
76
171
|
environments: {
|
|
77
|
-
client: getEnvironments(config, 'client', name),
|
|
78
|
-
server: getEnvironments(config, 'server', name)
|
|
172
|
+
client: getEnvironments(config, 'client', name, entry),
|
|
173
|
+
server: getEnvironments(config, 'server', name, entry)
|
|
79
174
|
}
|
|
80
175
|
};
|
|
81
176
|
}
|
|
@@ -128,33 +223,21 @@ export function getEnvironmentImports(
|
|
|
128
223
|
return result;
|
|
129
224
|
}
|
|
130
225
|
|
|
131
|
-
export function getEnvironmentScopes(
|
|
132
|
-
environment: BuildEnvironment,
|
|
133
|
-
scopes: Record<string, ModuleConfigImportMapping> = {}
|
|
134
|
-
): Record<string, Record<string, string>> {
|
|
135
|
-
const result: Record<string, Record<string, string>> = {};
|
|
136
|
-
|
|
137
|
-
for (const [scopeName, scopeImports] of Object.entries(scopes)) {
|
|
138
|
-
result[scopeName] = getEnvironmentImports(environment, scopeImports);
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
return result;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
226
|
export function getEnvironments(
|
|
145
227
|
config: ModuleConfig,
|
|
146
228
|
env: BuildEnvironment,
|
|
147
|
-
moduleName: string
|
|
229
|
+
moduleName: string,
|
|
230
|
+
entry: ParsedModuleConfigEntry = parseEntryConfig(config)
|
|
148
231
|
): ParsedModuleConfigEnvironment {
|
|
149
232
|
const imports = getEnvironmentImports(env, config.imports);
|
|
150
|
-
const exports = getEnvironmentExports(config, env);
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
}
|
|
157
|
-
}
|
|
233
|
+
const exports = getEnvironmentExports(config, env, entry);
|
|
234
|
+
// The single root scope (`''`) is derived from the supply wiring and the
|
|
235
|
+
// module's own pkg-exports. There is no user-authored directory-scope
|
|
236
|
+
// remapping (RFC 0001 §4): per-module isolation and multi-major
|
|
237
|
+
// coexistence are derived from declarations, never hand-mapped.
|
|
238
|
+
const scopes: Record<string, Record<string, string>> = {
|
|
239
|
+
'': { ...imports }
|
|
240
|
+
};
|
|
158
241
|
addPackageExportsToScopes(exports, scopes, moduleName);
|
|
159
242
|
return {
|
|
160
243
|
imports,
|
|
@@ -163,37 +246,28 @@ export function getEnvironments(
|
|
|
163
246
|
};
|
|
164
247
|
}
|
|
165
248
|
|
|
166
|
-
|
|
249
|
+
/**
|
|
250
|
+
* Builds the per-environment exports for the resolved framework entries.
|
|
251
|
+
* Each entry exports its source file in its own environment and an empty
|
|
252
|
+
* file on the other side (the inactive side is skipped by adapters).
|
|
253
|
+
*/
|
|
254
|
+
export function createEntryExports(
|
|
255
|
+
entry: ParsedModuleConfigEntry,
|
|
167
256
|
env: BuildEnvironment
|
|
168
257
|
): ParsedModuleConfigExports {
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
pkg: false
|
|
181
|
-
}
|
|
182
|
-
};
|
|
183
|
-
case 'server':
|
|
184
|
-
return {
|
|
185
|
-
'src/entry.client': {
|
|
186
|
-
name: 'src/entry.client',
|
|
187
|
-
file: '',
|
|
188
|
-
pkg: false
|
|
189
|
-
},
|
|
190
|
-
'src/entry.server': {
|
|
191
|
-
name: 'src/entry.server',
|
|
192
|
-
file: './src/entry.server',
|
|
193
|
-
pkg: false
|
|
194
|
-
}
|
|
195
|
-
};
|
|
258
|
+
const exports: ParsedModuleConfigExports = {};
|
|
259
|
+
for (const side of ['client', 'server'] as const) {
|
|
260
|
+
const target = entry[side];
|
|
261
|
+
if (!target) {
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
exports[target.name] = {
|
|
265
|
+
name: target.name,
|
|
266
|
+
file: side === env ? target.file : '',
|
|
267
|
+
pkg: false
|
|
268
|
+
};
|
|
196
269
|
}
|
|
270
|
+
return exports;
|
|
197
271
|
}
|
|
198
272
|
|
|
199
273
|
export function processStringExport(
|
|
@@ -263,9 +337,10 @@ export function processExportArray(
|
|
|
263
337
|
|
|
264
338
|
export function getEnvironmentExports(
|
|
265
339
|
config: ModuleConfig,
|
|
266
|
-
env: BuildEnvironment
|
|
340
|
+
env: BuildEnvironment,
|
|
341
|
+
entry: ParsedModuleConfigEntry = parseEntryConfig(config)
|
|
267
342
|
): ParsedModuleConfigExports {
|
|
268
|
-
const exports =
|
|
343
|
+
const exports = createEntryExports(entry, env);
|
|
269
344
|
|
|
270
345
|
if (config.exports) {
|
|
271
346
|
const userExports = processExportArray(config.exports, env);
|
|
@@ -293,9 +368,6 @@ export function addPackageExportsToScopes(
|
|
|
293
368
|
}
|
|
294
369
|
|
|
295
370
|
export function parsedExportValue(value: string): ParsedModuleConfigExport {
|
|
296
|
-
const FILE_EXT_REGEX =
|
|
297
|
-
/\.(js|mjs|cjs|jsx|mjsx|cjsx|ts|mts|cts|tsx|mtsx|ctsx)$/i;
|
|
298
|
-
|
|
299
371
|
if (value.startsWith('pkg:')) {
|
|
300
372
|
const item = value.substring('pkg:'.length);
|
|
301
373
|
return {
|