Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/enhanced/.eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"ignorePatterns": ["!**/*", "**/*.d.ts"],
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"files": ["*.ts", "*.tsx", "*.js", "*.jsx", ".cjs"],
"rules": {
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/ban-ts-comment": "warn",
Expand Down
47 changes: 47 additions & 0 deletions packages/enhanced/jest.config.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/* eslint-disable */
var { readFileSync } = require('fs');
var path = require('path');
var os = require('os');
var rimraf = require('rimraf');

// Reading the SWC compilation config and remove the "exclude"
// for the test files to be compiled by SWC
var swcJestConfig = JSON.parse(
readFileSync(path.join(__dirname, '.swcrc'), 'utf-8'),
);

rimraf.sync(path.join(__dirname, 'test/js'));

// Disable .swcrc look-up by SWC core because we're passing in swcJestConfig
// ourselves. If we do not disable this, SWC Core will read .swcrc and won't
// transform our test files due to "exclude".
if (swcJestConfig && typeof swcJestConfig.exclude !== 'undefined') {
delete swcJestConfig.exclude;
}

if (swcJestConfig.swcrc === undefined) {
swcJestConfig.swcrc = false;
}

/** @type {import('jest').Config} */
var config = {
displayName: 'enhanced',
preset: '../../jest.preset.js',
cacheDirectory: path.join(os.tmpdir(), 'enhanced'),
transform: {
'^.+\\.[tj]s$': ['@swc/jest', swcJestConfig],
},
moduleFileExtensions: ['ts', 'js', 'html'],
coverageDirectory: '../../coverage/packages/enhanced',
rootDir: __dirname,
testMatch: [
'<rootDir>/test/*.basictest.js',
'<rootDir>/test/unit/**/*.test.ts',
],
silent: true,
verbose: false,
testEnvironment: path.resolve(__dirname, './test/patch-node-env.js'),
setupFilesAfterEnv: ['<rootDir>/test/setupTestFramework.js'],
};

module.exports = config;
2 changes: 1 addition & 1 deletion packages/enhanced/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
"parallel": false,
"commands": [
{
"command": "node --expose-gc --max-old-space-size=4096 --experimental-vm-modules --trace-deprecation ./node_modules/jest-cli/bin/jest --logHeapUsage --config packages/enhanced/jest.config.ts --silent",
"command": "node --expose-gc --max-old-space-size=4096 --experimental-vm-modules --trace-deprecation ./node_modules/jest-cli/bin/jest --logHeapUsage --config packages/enhanced/jest.config.cjs --silent",
"forwardAllArgs": false
}
]
Expand Down
8 changes: 8 additions & 0 deletions packages/enhanced/src/lib/container/ContainerEntryModule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@ export type ExposeOptions = {
* custom chunk name for the exposed module
*/
name: string;
/**
* optional webpack layer to assign to the exposed module
*/
layer?: string;
};

class ContainerEntryModule extends Module {
Expand Down Expand Up @@ -187,6 +191,10 @@ class ContainerEntryModule extends Module {
let idx = 0;
for (const request of options.import) {
const dep = new ContainerExposedDependency(name, request);
// apply per-expose module layer if provided
if (options.layer) {
dep.layer = options.layer;
}
dep.loc = {
name,
index: idx++,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import type {
class ContainerExposedDependency extends dependencies.ModuleDependency {
exposedName: string;
override request: string;
// optional layer to assign to the created normal module
layer?: string;

/**
* @param {string} exposedName public name
Expand Down Expand Up @@ -50,6 +52,7 @@ class ContainerExposedDependency extends dependencies.ModuleDependency {
*/
override serialize(context: ObjectSerializerContext): void {
context.write(this.exposedName);
context.write(this.layer);
super.serialize(context);
}

Expand All @@ -58,6 +61,7 @@ class ContainerExposedDependency extends dependencies.ModuleDependency {
*/
override deserialize(context: ObjectDeserializerContext): void {
this.exposedName = context.read();
this.layer = context.read();
super.deserialize(context);
}
}
Expand Down
44 changes: 41 additions & 3 deletions packages/enhanced/src/lib/container/ContainerPlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,17 @@ const validate = createSchemaValidation(checkOptions, () => schema, {

const PLUGIN_NAME = 'ContainerPlugin';

type ExposesConfigInput = {
import: string | string[];
name?: string;
layer?: string;
};
type ExposesConfig = {
import: string[];
name: string | undefined;
layer?: string;
};

class ContainerPlugin {
_options: containerPlugin.ContainerPluginOptions;
name: string;
Expand All @@ -59,16 +70,19 @@ class ContainerPlugin {
},
runtime: options.runtime,
filename: options.filename || undefined,
//@ts-ignore
exposes: parseOptions(
options.exposes,
//@ts-ignore normalized tuple form used internally
exposes: parseOptions<ExposesConfigInput, ExposesConfig>(
// supports array or object shapes
options.exposes as containerPlugin.ContainerPluginOptions['exposes'],
(item) => ({
import: Array.isArray(item) ? item : [item],
name: undefined,
layer: undefined,
}),
(item) => ({
import: Array.isArray(item.import) ? item.import : [item.import],
name: item.name || undefined,
layer: item.layer || undefined,
}),
),
runtimePlugins: options.runtimePlugins,
Expand Down Expand Up @@ -322,6 +336,30 @@ class ContainerPlugin {
normalModuleFactory,
);
}

// Propagate per-expose `layer` from ContainerExposedDependency to the created NormalModule
normalModuleFactory.hooks.createModule.tapAsync(
PLUGIN_NAME,
(
createData: { layer?: string } & Record<string, unknown>,
resolveData: { dependencies?: import('webpack').Dependency[] },
callback: (err?: Error | null) => void,
) => {
try {
const deps = resolveData?.dependencies || [];
const first = deps[0];
if (first && first instanceof ContainerExposedDependency) {
const layer = (first as ContainerExposedDependency).layer;
if (layer) {
createData.layer = layer;
}
}
callback();
} catch (e) {
callback(e as Error);
}
},
);
},
);

Expand Down
77 changes: 45 additions & 32 deletions packages/enhanced/src/lib/sharing/ConsumeSharedPlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -757,43 +757,56 @@ class ConsumeSharedPlugin {
if (!pkgName) {
pkgName = await getPackageNameForResource(resource);
}
if (!pkgName) return;

// Candidate configs: include
// - exact package name keys (legacy behavior)
// - deep-path shares whose keys start with `${pkgName}/` (alias-aware)
const candidates: ConsumeOptions[] = [];
const seen = new Set<ConsumeOptions>();
const k1 = createLookupKeyForSharing(pkgName, issuerLayer);
const k2 = createLookupKeyForSharing(pkgName, undefined);
const c1 = unresolvedConsumes.get(k1);
const c2 = unresolvedConsumes.get(k2);
if (c1 && !seen.has(c1)) {
candidates.push(c1);
seen.add(c1);
}
if (c2 && !seen.has(c2)) {
candidates.push(c2);
seen.add(c2);

if (originalRequest) {
const directCfg =
unresolvedConsumes.get(
createLookupKeyForSharing(originalRequest, issuerLayer),
) ||
unresolvedConsumes.get(
createLookupKeyForSharing(originalRequest, undefined),
);
if (directCfg && !seen.has(directCfg)) {
candidates.push(directCfg);
seen.add(directCfg);
}
}

// Also scan for deep-path keys beginning with `${pkgName}/` (both layered and unlayered)
const prefixLayered = createLookupKeyForSharing(
pkgName + '/',
issuerLayer,
);
const prefixUnlayered = createLookupKeyForSharing(
pkgName + '/',
undefined,
);
for (const [key, cfg] of unresolvedConsumes) {
if (
(key.startsWith(prefixLayered) ||
key.startsWith(prefixUnlayered)) &&
!seen.has(cfg)
) {
candidates.push(cfg);
seen.add(cfg);
if (pkgName) {
const k1 = createLookupKeyForSharing(pkgName, issuerLayer);
const k2 = createLookupKeyForSharing(pkgName, undefined);
const c1 = unresolvedConsumes.get(k1);
const c2 = unresolvedConsumes.get(k2);
if (c1 && !seen.has(c1)) {
candidates.push(c1);
seen.add(c1);
}
if (c2 && !seen.has(c2)) {
candidates.push(c2);
seen.add(c2);
}

// Also scan for deep-path keys beginning with `${pkgName}/` (both layered and unlayered)
const prefixLayered = createLookupKeyForSharing(
pkgName + '/',
issuerLayer,
);
const prefixUnlayered = createLookupKeyForSharing(
pkgName + '/',
undefined,
);
for (const [key, cfg] of unresolvedConsumes) {
if (
(key.startsWith(prefixLayered) ||
key.startsWith(prefixUnlayered)) &&
!seen.has(cfg)
) {
candidates.push(cfg);
seen.add(cfg);
}
}
}
if (candidates.length === 0) return;
Expand Down
Loading
Loading