Skip to content

feat(enhanced): add request pattern filtering support (PR5) #3908

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: feat/basic-share-filtering
Choose a base branch
from
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
24 changes: 24 additions & 0 deletions packages/enhanced/src/lib/sharing/ConsumeSharedPlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,16 @@ class ConsumeSharedPlugin {
);

if (match !== undefined) {
// Apply request filters if defined
if (
!testRequestFilters(
request,
match.include?.request,
match.exclude?.request,
)
) {
return;
}
return createConsumeSharedModule(context, request, match);
}
for (const [prefix, options] of prefixedConsumes) {
Expand Down Expand Up @@ -482,6 +492,20 @@ class ConsumeSharedPlugin {
if (resource) {
const options = resolvedConsumes.get(resource);
if (options !== undefined) {
// Extract request from resource path for filtering
const request =
extractPathAfterNodeModules(resource) || resource;

// Apply request filters if defined
if (
!testRequestFilters(
request,
options.include?.request,
options.exclude?.request,
)
) {
return Promise.resolve();
}
return createConsumeSharedModule(context, resource, options);
}
}
Expand Down
10 changes: 10 additions & 0 deletions packages/enhanced/src/lib/sharing/ProvideSharedPlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,16 @@ class ProvideSharedPlugin {
});
const config = matchProvides.get(requestKey);
if (config !== undefined && resource) {
// Apply request filters if defined
if (
!testRequestFilters(
request,
config.include?.request,
config.exclude?.request,
)
) {
return module;
}
this.provideSharedModule(
compilation,
resolvedProvideMap,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ if (Math.random() < 0) {
require('./version-include-fail.js');
require('./version-exclude-fail.js');
require('./singleton-filter.js');
require('./request-filter/components/Button.js');
require('./request-filter/components/Modal.js');
require('./request-filter/utils/helper.js');
}

let warnings = [];
Expand Down Expand Up @@ -117,3 +120,32 @@ it('should warn about singleton filters', async () => {
).toBeDefined();
expectWarning(/singleton.*include.*version/);
});

it('should provide modules that match request include filters', async () => {
// Initialize the share scope first
await __webpack_init_sharing__('default');

// Import components that should match the filter
const button = await import('./request-filter/components/Button.js');
expect(button.default).toBe('Button');

// Check that the module was provided to the share scope
expect(__webpack_require__.S['default']['request-prefix']).toBeDefined();
expect(
__webpack_require__.S['default']['request-prefix']['1.0.0'],
).toBeDefined();
expectWarning();
});

it('should not provide modules that do not match request include filters', async () => {
// Initialize the share scope first
await __webpack_init_sharing__('default');

// Import utils that should NOT match the components/ filter
const helper = await import('./request-filter/utils/helper.js');
expect(helper.default).toBe('helper');

// The request-prefix scope should not include utils/ since it only includes components/
// This test verifies request filtering works for prefix matches
expectWarning();
});
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default 'request-filter-index';
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,14 @@ module.exports = {
version: '^1.0.0',
},
},
// Request pattern filtering tests
'./request-filter/': {
shareKey: 'request-prefix',
version: '1.0.0',
include: {
request: /components/,
},
},
},
}),
],
Expand Down
62 changes: 62 additions & 0 deletions packages/enhanced/test/unit/sharing/utils-filtering.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import {
testRequestFilters,
addSingletonFilterWarning,
extractPathAfterNodeModules,
createLookupKeyForSharing,
} from '../../../src/lib/sharing/utils';
import type { Compilation } from 'webpack';

Expand Down Expand Up @@ -127,4 +129,64 @@ describe('Filtering Utils', () => {
expect(mockCompilation.warnings[0].message).toContain('test');
});
});

describe('extractPathAfterNodeModules', () => {
it('should extract path after node_modules', () => {
const filePath = '/project/node_modules/package/lib/index.js';
const result = extractPathAfterNodeModules(filePath);
expect(result).toBe('package/lib/index.js');
});

it('should handle paths with multiple node_modules (use last occurrence)', () => {
const filePath =
'/project/node_modules/package/node_modules/sub-package/index.js';
const result = extractPathAfterNodeModules(filePath);
expect(result).toBe('sub-package/index.js');
});

it('should return null for paths without node_modules', () => {
const filePath = '/project/src/components/Component.js';
const result = extractPathAfterNodeModules(filePath);
expect(result).toBeNull();
});

it('should handle Windows-style paths', () => {
const filePath = 'C:\\project\\node_modules\\package\\lib\\index.js';
const result = extractPathAfterNodeModules(filePath);
expect(result).toBe('package\\lib\\index.js');
});

it('should handle scoped packages', () => {
const filePath = '/project/node_modules/@scope/package/lib/index.js';
const result = extractPathAfterNodeModules(filePath);
expect(result).toBe('@scope/package/lib/index.js');
});
});

describe('createLookupKeyForSharing', () => {
it('should return request when no layer is provided', () => {
const result = createLookupKeyForSharing('react');
expect(result).toBe('react');
});

it('should return request when layer is null', () => {
const result = createLookupKeyForSharing('react', null);
expect(result).toBe('react');
});

it('should return request when layer is undefined', () => {
const result = createLookupKeyForSharing('react', undefined);
expect(result).toBe('react');
});

it('should create composite key when layer is provided', () => {
const result = createLookupKeyForSharing('react', 'ssr');
expect(result).toBe('(ssr)react');
});

it('should handle complex requests with layers', () => {
const result = createLookupKeyForSharing('@scope/package/lib', 'client');
expect(result).toBe('(client)@scope/package/lib');
});
});
});