next-build-filter 0.2.2 → 0.2.5
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/lib/empty-module.js +45 -22
- package/lib/next-build-filter-plugin.js +200 -28
- package/lib/with-page-filter.js +13 -138
- package/package.json +2 -2
package/lib/empty-module.js
CHANGED
|
@@ -1,35 +1,47 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Empty module to replace filtered pages
|
|
3
|
-
*
|
|
3
|
+
*
|
|
4
4
|
* This module provides a custom 404 response with a detectable marker
|
|
5
5
|
* that can be verified in tests and provides clear feedback to users.
|
|
6
|
-
*
|
|
6
|
+
*
|
|
7
7
|
* Marker: NEXT_BUILD_FILTER_EXCLUDED_PAGE
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
+
// Marker string to identify filtered pages
|
|
11
|
+
const FILTER_MARKER = 'NEXT_BUILD_FILTER_EXCLUDED_PAGE';
|
|
12
|
+
|
|
10
13
|
// Try to import notFound for App Router (will fail gracefully for Pages Router)
|
|
11
|
-
let
|
|
14
|
+
let notFoundFn = null;
|
|
12
15
|
try {
|
|
13
|
-
|
|
16
|
+
notFoundFn = require('next/navigation').notFound;
|
|
14
17
|
} catch (e) {
|
|
15
18
|
// Not in App Router context or Next.js not available
|
|
16
|
-
notFound = null;
|
|
17
19
|
}
|
|
18
20
|
|
|
19
|
-
//
|
|
20
|
-
|
|
21
|
+
// Import React at module level for better compatibility
|
|
22
|
+
let React = null;
|
|
23
|
+
try {
|
|
24
|
+
React = require('react');
|
|
25
|
+
} catch (e) {
|
|
26
|
+
// React not available
|
|
27
|
+
}
|
|
21
28
|
|
|
22
|
-
|
|
29
|
+
/**
|
|
30
|
+
* Filtered page component
|
|
31
|
+
* Works with both Pages Router and App Router
|
|
32
|
+
*/
|
|
23
33
|
function FilteredPage() {
|
|
24
34
|
// If we're in App Router and notFound is available, use it
|
|
25
|
-
if (
|
|
26
|
-
|
|
35
|
+
if (notFoundFn && typeof notFoundFn === 'function') {
|
|
36
|
+
notFoundFn();
|
|
27
37
|
return null; // notFound() throws, so this won't be reached
|
|
28
38
|
}
|
|
29
|
-
|
|
39
|
+
|
|
30
40
|
// For Pages Router or fallback: Return a custom 404 component
|
|
31
|
-
|
|
32
|
-
|
|
41
|
+
if (!React) {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
|
|
33
45
|
return React.createElement(
|
|
34
46
|
'div',
|
|
35
47
|
{
|
|
@@ -42,18 +54,28 @@ function FilteredPage() {
|
|
|
42
54
|
fontFamily: 'system-ui, -apple-system, sans-serif',
|
|
43
55
|
backgroundColor: '#f5f5f5',
|
|
44
56
|
},
|
|
45
|
-
'data-filter-marker': FILTER_MARKER
|
|
57
|
+
'data-filter-marker': FILTER_MARKER,
|
|
46
58
|
},
|
|
47
59
|
[
|
|
48
|
-
React.createElement(
|
|
60
|
+
React.createElement(
|
|
61
|
+
'h1',
|
|
62
|
+
{ key: 'title', style: { fontSize: '3rem', margin: '0' } },
|
|
63
|
+
'404'
|
|
64
|
+
),
|
|
49
65
|
React.createElement(
|
|
50
66
|
'p',
|
|
51
|
-
{
|
|
67
|
+
{
|
|
68
|
+
key: 'message',
|
|
69
|
+
style: { fontSize: '1.2rem', color: '#666', marginTop: '1rem' },
|
|
70
|
+
},
|
|
52
71
|
'Page Not Available'
|
|
53
72
|
),
|
|
54
73
|
React.createElement(
|
|
55
74
|
'p',
|
|
56
|
-
{
|
|
75
|
+
{
|
|
76
|
+
key: 'detail',
|
|
77
|
+
style: { fontSize: '0.9rem', color: '#999', marginTop: '0.5rem' },
|
|
78
|
+
},
|
|
57
79
|
'This page has been filtered out during build.'
|
|
58
80
|
),
|
|
59
81
|
React.createElement(
|
|
@@ -80,15 +102,16 @@ module.exports.default = FilteredPage;
|
|
|
80
102
|
// Export the marker for testing
|
|
81
103
|
module.exports.FILTER_MARKER = FILTER_MARKER;
|
|
82
104
|
|
|
83
|
-
// Generate metadata for App Router
|
|
84
|
-
module.exports.generateMetadata = () => ({
|
|
105
|
+
// Generate metadata for App Router (async for Next.js best practices)
|
|
106
|
+
module.exports.generateMetadata = async () => ({
|
|
85
107
|
title: 'Page Not Available - 404',
|
|
86
108
|
description: 'This page has been filtered out during build',
|
|
87
109
|
});
|
|
88
110
|
|
|
89
111
|
// Generate static params for dynamic routes if needed
|
|
90
|
-
module.exports.generateStaticParams = () => [];
|
|
112
|
+
module.exports.generateStaticParams = async () => [];
|
|
91
113
|
|
|
92
114
|
// Export all common App Router functions
|
|
93
|
-
module.exports.generateViewport = () => ({});
|
|
94
|
-
module.exports.revalidate = false;
|
|
115
|
+
module.exports.generateViewport = async () => ({});
|
|
116
|
+
module.exports.revalidate = false;
|
|
117
|
+
module.exports.dynamic = 'force-static';
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
const path = require('path');
|
|
2
|
-
const fs = require('fs');
|
|
3
1
|
const { minimatch } = require('minimatch');
|
|
2
|
+
const path = require('path');
|
|
4
3
|
|
|
5
4
|
/**
|
|
6
5
|
* Next.js Build Filter Plugin
|
|
@@ -35,6 +34,13 @@ class NextBuildFilterPlugin {
|
|
|
35
34
|
supportAppRouter: options.supportAppRouter !== false,
|
|
36
35
|
supportPagesRouter: options.supportPagesRouter !== false
|
|
37
36
|
};
|
|
37
|
+
|
|
38
|
+
// Resolve the empty module path once and store it
|
|
39
|
+
// This ensures it resolves correctly regardless of where the plugin is loaded from
|
|
40
|
+
this.emptyModulePath = path.resolve(__dirname, 'empty-module.js');
|
|
41
|
+
|
|
42
|
+
// Store original Module._resolveFilename if we need to intercept Node.js module resolution
|
|
43
|
+
this.originalResolveFilename = null;
|
|
38
44
|
}
|
|
39
45
|
|
|
40
46
|
apply(compiler) {
|
|
@@ -47,6 +53,11 @@ class NextBuildFilterPlugin {
|
|
|
47
53
|
|
|
48
54
|
const pluginName = 'NextBuildFilterPlugin';
|
|
49
55
|
|
|
56
|
+
// Intercept Node.js module resolution to catch Next.js requirePage calls
|
|
57
|
+
// This is needed because Next.js uses requirePage which bypasses webpack
|
|
58
|
+
// Note: This is a workaround for Next.js's page collection phase
|
|
59
|
+
this.setupModuleResolutionInterceptor();
|
|
60
|
+
|
|
50
61
|
// Hook into the compilation process
|
|
51
62
|
compiler.hooks.beforeCompile.tapAsync(pluginName, (params, callback) => {
|
|
52
63
|
if (this.options.verbose) {
|
|
@@ -57,6 +68,8 @@ class NextBuildFilterPlugin {
|
|
|
57
68
|
|
|
58
69
|
// Filter out pages during the resolve phase
|
|
59
70
|
compiler.hooks.normalModuleFactory.tap(pluginName, (normalModuleFactory) => {
|
|
71
|
+
// Hook into beforeResolve to intercept page module requests
|
|
72
|
+
// Note: beforeResolve is a bailing hook - modify resolveData in place, don't return it
|
|
60
73
|
normalModuleFactory.hooks.beforeResolve.tap(pluginName, (resolveData) => {
|
|
61
74
|
if (!resolveData || !resolveData.request) {
|
|
62
75
|
return;
|
|
@@ -81,33 +94,75 @@ class NextBuildFilterPlugin {
|
|
|
81
94
|
}
|
|
82
95
|
|
|
83
96
|
// Replace with empty module instead of blocking the request
|
|
84
|
-
|
|
85
|
-
return
|
|
97
|
+
// Use the pre-resolved absolute path to ensure it works in all contexts
|
|
98
|
+
// Modify in place - don't return the object
|
|
99
|
+
resolveData.request = this.emptyModulePath;
|
|
100
|
+
return; // Continue processing with modified resolveData
|
|
86
101
|
}
|
|
87
102
|
}
|
|
88
103
|
|
|
89
|
-
// Don't return
|
|
104
|
+
// Don't return anything - let webpack continue with the original resolveData
|
|
90
105
|
});
|
|
91
|
-
|
|
106
|
+
|
|
107
|
+
// Also hook into afterResolve as a fallback
|
|
108
|
+
// Note: afterResolve is also a bailing hook
|
|
109
|
+
normalModuleFactory.hooks.afterResolve.tap(pluginName, (resolveData) => {
|
|
110
|
+
if (!resolveData || !resolveData.request) {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
92
113
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
114
|
+
const request = resolveData.request;
|
|
115
|
+
|
|
116
|
+
if (typeof request !== 'string') {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
97
119
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
120
|
+
// Check if this is a page file that should be excluded
|
|
121
|
+
if (this.isPageFile(request, resolveData.context)) {
|
|
122
|
+
const normalizedRequest = this.normalizePath(request);
|
|
123
|
+
const routePath = this.extractRoutePath(normalizedRequest);
|
|
124
|
+
const shouldExclude = this.shouldExcludePage(request);
|
|
125
|
+
|
|
126
|
+
if (shouldExclude && request !== this.emptyModulePath) {
|
|
127
|
+
if (this.options.verbose) {
|
|
128
|
+
console.log(`📄 Filtering out (afterResolve): ${routePath || request}`);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Replace with empty module - modify in place
|
|
132
|
+
resolveData.request = this.emptyModulePath;
|
|
133
|
+
return; // Continue processing with modified resolveData
|
|
103
134
|
}
|
|
104
135
|
}
|
|
105
136
|
|
|
106
|
-
//
|
|
107
|
-
|
|
108
|
-
Object.assign(entry, filteredEntry);
|
|
109
|
-
}
|
|
137
|
+
// Don't return anything - let webpack continue
|
|
138
|
+
});
|
|
110
139
|
});
|
|
140
|
+
|
|
141
|
+
// Hook into compilation to intercept module building
|
|
142
|
+
compiler.hooks.compilation.tap(pluginName, (compilation) => {
|
|
143
|
+
compilation.hooks.buildModule.tap(pluginName, (module) => {
|
|
144
|
+
if (module.resource && typeof module.resource === 'string') {
|
|
145
|
+
if (this.isPageFile(module.resource, module.context)) {
|
|
146
|
+
const normalizedRequest = this.normalizePath(module.resource);
|
|
147
|
+
const routePath = this.extractRoutePath(normalizedRequest);
|
|
148
|
+
const shouldExclude = this.shouldExcludePage(module.resource);
|
|
149
|
+
|
|
150
|
+
if (shouldExclude && module.resource !== this.emptyModulePath) {
|
|
151
|
+
if (this.options.verbose) {
|
|
152
|
+
console.log(`📄 Filtering module during build: ${routePath || module.resource}`);
|
|
153
|
+
}
|
|
154
|
+
// Mark module as filtered - this prevents it from being processed
|
|
155
|
+
module._filtered = true;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
// NOTE: We intentionally do NOT filter entries here because Next.js still needs
|
|
163
|
+
// compiled modules to exist for page data collection. Instead, we let all entries
|
|
164
|
+
// compile but replace their content with empty modules via beforeResolve/afterResolve hooks.
|
|
165
|
+
// This ensures Next.js can find and load the modules, but they'll be empty stubs.
|
|
111
166
|
}
|
|
112
167
|
|
|
113
168
|
/**
|
|
@@ -251,16 +306,21 @@ class NextBuildFilterPlugin {
|
|
|
251
306
|
routePath = routePath.replace(/\/?page\.(tsx|ts|jsx|js)$/, '');
|
|
252
307
|
// Remove leading slash if present
|
|
253
308
|
routePath = routePath.replace(/^\//, '');
|
|
309
|
+
// Handle route groups - remove (group) patterns like (auth), (admin)
|
|
310
|
+
routePath = routePath.replace(/\/?\([^)]+\)/g, '');
|
|
254
311
|
} else {
|
|
255
312
|
// For Pages Router, remove extension
|
|
256
313
|
routePath = routePath.replace(/\.(tsx|ts|jsx|js)$/, '');
|
|
257
314
|
}
|
|
258
|
-
|
|
315
|
+
|
|
316
|
+
// Clean up empty segments and normalize
|
|
317
|
+
routePath = routePath.replace(/\/+/g, '/').replace(/^\/|\/$/g, '');
|
|
318
|
+
|
|
259
319
|
// Handle root route
|
|
260
320
|
if (!routePath || routePath === '') {
|
|
261
321
|
return 'index';
|
|
262
322
|
}
|
|
263
|
-
|
|
323
|
+
|
|
264
324
|
return routePath;
|
|
265
325
|
}
|
|
266
326
|
|
|
@@ -269,15 +329,19 @@ class NextBuildFilterPlugin {
|
|
|
269
329
|
*/
|
|
270
330
|
shouldExcludeEntryPoint(entryKey) {
|
|
271
331
|
// Entry keys in Next.js typically follow patterns like:
|
|
272
|
-
//
|
|
273
|
-
//
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
if (entryKey.startsWith('pages/')) {
|
|
332
|
+
// Pages Router: pages/index, pages/about, pages/api/users
|
|
333
|
+
// App Router: app/page, app/about/page, app/admin/users/page
|
|
334
|
+
|
|
335
|
+
if (this.options.supportPagesRouter && entryKey.startsWith('pages/')) {
|
|
277
336
|
const pagePath = entryKey.replace('pages/', '');
|
|
278
|
-
return this.shouldExcludePage(pagePath);
|
|
337
|
+
return this.shouldExcludePage(`/${this.options.pagesDir}/${pagePath}.js`);
|
|
279
338
|
}
|
|
280
|
-
|
|
339
|
+
|
|
340
|
+
if (this.options.supportAppRouter && entryKey.startsWith('app/')) {
|
|
341
|
+
const pagePath = entryKey.replace('app/', '');
|
|
342
|
+
return this.shouldExcludePage(`/${this.options.appDir}/${pagePath}.js`);
|
|
343
|
+
}
|
|
344
|
+
|
|
281
345
|
return false;
|
|
282
346
|
}
|
|
283
347
|
|
|
@@ -290,6 +354,114 @@ class NextBuildFilterPlugin {
|
|
|
290
354
|
}
|
|
291
355
|
return filePath.replace(/\\/g, '/').toLowerCase();
|
|
292
356
|
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Setup Node.js module resolution interceptor to catch Next.js requirePage calls
|
|
360
|
+
* This is needed because Next.js uses requirePage which bypasses webpack
|
|
361
|
+
*/
|
|
362
|
+
setupModuleResolutionInterceptor() {
|
|
363
|
+
const Module = require('module');
|
|
364
|
+
|
|
365
|
+
// Only intercept if not already intercepted
|
|
366
|
+
if (this.originalResolveFilename) {
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// Store the original resolveFilename
|
|
371
|
+
this.originalResolveFilename = Module._resolveFilename;
|
|
372
|
+
const self = this;
|
|
373
|
+
|
|
374
|
+
// Intercept module resolution
|
|
375
|
+
Module._resolveFilename = function(request, parent, isMain, options) {
|
|
376
|
+
// Check if this request might be for a filtered page before trying to resolve
|
|
377
|
+
// Next.js requirePage uses paths like "/admin" or paths to page files
|
|
378
|
+
const normalizedRequest = self.normalizePath(request);
|
|
379
|
+
let routePath = self.extractRoutePath(normalizedRequest);
|
|
380
|
+
|
|
381
|
+
// If we can't extract route path from request, try to infer it
|
|
382
|
+
// Next.js might be looking for pages like "/admin" or "/dev/debug"
|
|
383
|
+
if (!routePath && request && typeof request === 'string') {
|
|
384
|
+
// Check if request looks like a Next.js page path (starts with / but not /node_modules)
|
|
385
|
+
if (request.startsWith('/') && !request.startsWith('/node_modules') && !request.includes('node_modules')) {
|
|
386
|
+
routePath = request.replace(/^\//, '').replace(/\/$/, '');
|
|
387
|
+
if (!routePath) routePath = 'index';
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// Check if this route should be excluded
|
|
392
|
+
if (routePath) {
|
|
393
|
+
// Try multiple path formats that Next.js might use
|
|
394
|
+
const testPaths = [
|
|
395
|
+
request,
|
|
396
|
+
`/${self.options.pagesDir}/${routePath}.js`,
|
|
397
|
+
`/${self.options.pagesDir}/${routePath}.jsx`,
|
|
398
|
+
`/${self.options.pagesDir}/${routePath}.ts`,
|
|
399
|
+
`/${self.options.pagesDir}/${routePath}.tsx`,
|
|
400
|
+
`/${self.options.appDir}/${routePath}/page.js`,
|
|
401
|
+
`/${self.options.appDir}/${routePath}/page.jsx`,
|
|
402
|
+
`/${self.options.appDir}/${routePath}/page.ts`,
|
|
403
|
+
`/${self.options.appDir}/${routePath}/page.tsx`,
|
|
404
|
+
routePath,
|
|
405
|
+
`/${routePath}`,
|
|
406
|
+
];
|
|
407
|
+
|
|
408
|
+
const shouldExclude = testPaths.some(testPath => self.shouldExcludePage(testPath));
|
|
409
|
+
|
|
410
|
+
if (shouldExclude) {
|
|
411
|
+
if (self.options.verbose) {
|
|
412
|
+
console.log(`📄 Intercepting module resolution for filtered page: ${routePath} (request: ${request})`);
|
|
413
|
+
}
|
|
414
|
+
// Return the empty module path instead
|
|
415
|
+
return self.emptyModulePath;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// Try to resolve normally
|
|
420
|
+
try {
|
|
421
|
+
const resolvedPath = self.originalResolveFilename.call(this, request, parent, isMain, options);
|
|
422
|
+
|
|
423
|
+
// Check if the resolved path is a page file that should be excluded
|
|
424
|
+
if (resolvedPath && self.isPageFile(resolvedPath, parent ? parent.filename : null)) {
|
|
425
|
+
const normalizedResolved = self.normalizePath(resolvedPath);
|
|
426
|
+
const resolvedRoutePath = self.extractRoutePath(normalizedResolved);
|
|
427
|
+
const shouldExclude = self.shouldExcludePage(resolvedPath);
|
|
428
|
+
|
|
429
|
+
if (shouldExclude) {
|
|
430
|
+
if (self.options.verbose) {
|
|
431
|
+
console.log(`📄 Intercepting resolved module for filtered page: ${resolvedRoutePath || resolvedPath}`);
|
|
432
|
+
}
|
|
433
|
+
// Return the empty module path instead
|
|
434
|
+
return self.emptyModulePath;
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
return resolvedPath;
|
|
439
|
+
} catch (e) {
|
|
440
|
+
// If resolution fails and it's a filtered page, return empty module
|
|
441
|
+
if (routePath) {
|
|
442
|
+
const testPaths = [
|
|
443
|
+
request,
|
|
444
|
+
`/${self.options.pagesDir}/${routePath}.js`,
|
|
445
|
+
`/${self.options.appDir}/${routePath}/page.js`,
|
|
446
|
+
routePath,
|
|
447
|
+
`/${routePath}`,
|
|
448
|
+
];
|
|
449
|
+
|
|
450
|
+
const shouldExclude = testPaths.some(testPath => self.shouldExcludePage(testPath));
|
|
451
|
+
|
|
452
|
+
if (shouldExclude) {
|
|
453
|
+
if (self.options.verbose) {
|
|
454
|
+
console.log(`📄 Intercepting failed resolution for filtered page: ${routePath} (request: ${request})`);
|
|
455
|
+
}
|
|
456
|
+
return self.emptyModulePath;
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// Re-throw the original error if it's not a filtered page
|
|
461
|
+
throw e;
|
|
462
|
+
}
|
|
463
|
+
};
|
|
464
|
+
}
|
|
293
465
|
}
|
|
294
466
|
|
|
295
467
|
module.exports = NextBuildFilterPlugin;
|
package/lib/with-page-filter.js
CHANGED
|
@@ -1,25 +1,23 @@
|
|
|
1
|
-
const path = require('path');
|
|
2
|
-
const { minimatch } = require('minimatch');
|
|
3
|
-
|
|
4
1
|
/**
|
|
5
2
|
* Next.js Page Filter Plugin
|
|
6
|
-
*
|
|
3
|
+
*
|
|
7
4
|
* Enhanced version that supports both Pages Router and App Router (Next.js 13+)
|
|
8
5
|
* Supports glob patterns for includedPages and excludedPages options
|
|
9
6
|
*/
|
|
10
7
|
function withPageFilter(options = {}) {
|
|
11
8
|
return (nextConfig = {}) => {
|
|
12
9
|
const filterOptions = {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
10
|
+
// Spread options first, then apply defaults for undefined values
|
|
11
|
+
...options,
|
|
12
|
+
includedPages: options.includedPages ?? [],
|
|
13
|
+
excludedPages: options.excludedPages ?? [],
|
|
14
|
+
enabled: options.enabled ?? process.env.FILTER_PAGES === 'true',
|
|
15
|
+
excludePatterns: options.excludePatterns ?? [],
|
|
16
|
+
verbose: options.verbose ?? false,
|
|
17
|
+
pagesDir: options.pagesDir ?? 'pages',
|
|
18
|
+
appDir: options.appDir ?? 'app',
|
|
20
19
|
supportAppRouter: options.supportAppRouter !== false, // Default to true
|
|
21
20
|
supportPagesRouter: options.supportPagesRouter !== false, // Default to true
|
|
22
|
-
...options
|
|
23
21
|
};
|
|
24
22
|
|
|
25
23
|
if (!filterOptions.enabled) {
|
|
@@ -32,14 +30,14 @@ function withPageFilter(options = {}) {
|
|
|
32
30
|
return {
|
|
33
31
|
...nextConfig,
|
|
34
32
|
|
|
35
|
-
webpack: (config,
|
|
33
|
+
webpack: (config, options) => {
|
|
36
34
|
// Apply the existing webpack config if it exists
|
|
37
35
|
if (nextConfig.webpack) {
|
|
38
|
-
config = nextConfig.webpack(config,
|
|
36
|
+
config = nextConfig.webpack(config, options);
|
|
39
37
|
}
|
|
40
38
|
|
|
41
39
|
// Don't apply filtering in development mode unless explicitly enabled
|
|
42
|
-
if (dev && !filterOptions.enableInDev) {
|
|
40
|
+
if (options.dev && !filterOptions.enableInDev) {
|
|
43
41
|
return config;
|
|
44
42
|
}
|
|
45
43
|
|
|
@@ -58,127 +56,4 @@ function withPageFilter(options = {}) {
|
|
|
58
56
|
};
|
|
59
57
|
}
|
|
60
58
|
|
|
61
|
-
function isPageFile(request, context, options) {
|
|
62
|
-
if (!request) return false;
|
|
63
|
-
|
|
64
|
-
const pageExtensions = ['.js', '.jsx', '.ts', '.tsx'];
|
|
65
|
-
const hasPageExtension = pageExtensions.some(ext => request.endsWith(ext));
|
|
66
|
-
|
|
67
|
-
if (!hasPageExtension) return false;
|
|
68
|
-
|
|
69
|
-
// Check for Pages Router
|
|
70
|
-
const isInPagesDir = options.supportPagesRouter && (
|
|
71
|
-
request.includes(`/${options.pagesDir}/`) ||
|
|
72
|
-
(context && context.includes(`/${options.pagesDir}/`))
|
|
73
|
-
);
|
|
74
|
-
|
|
75
|
-
// Check for App Router
|
|
76
|
-
const isInAppDir = options.supportAppRouter && (
|
|
77
|
-
request.includes(`/${options.appDir}/`) ||
|
|
78
|
-
(context && context.includes(`/${options.appDir}/`))
|
|
79
|
-
);
|
|
80
|
-
|
|
81
|
-
return isInPagesDir || isInAppDir;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
function shouldExcludePage(request, options) {
|
|
85
|
-
const normalizedRequest = normalizePath(request);
|
|
86
|
-
|
|
87
|
-
// Extract route path from request
|
|
88
|
-
const routePath = extractRoutePath(normalizedRequest, options);
|
|
89
|
-
|
|
90
|
-
if (!routePath) return false;
|
|
91
|
-
|
|
92
|
-
// If includedPages is specified, only include those pages
|
|
93
|
-
if (options.includedPages && options.includedPages.length > 0) {
|
|
94
|
-
const isIncluded = options.includedPages.some(page => {
|
|
95
|
-
const normalizedPage = normalizePath(page);
|
|
96
|
-
// Try glob matching first
|
|
97
|
-
if (minimatch(routePath, normalizedPage)) {
|
|
98
|
-
return true;
|
|
99
|
-
}
|
|
100
|
-
// Fallback to exact match or substring match for backward compatibility
|
|
101
|
-
return routePath === normalizedPage || routePath.includes(normalizedPage);
|
|
102
|
-
});
|
|
103
|
-
return !isIncluded;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// Check excluded pages
|
|
107
|
-
if (options.excludedPages && options.excludedPages.length > 0) {
|
|
108
|
-
const isExcluded = options.excludedPages.some(page => {
|
|
109
|
-
const normalizedPage = normalizePath(page);
|
|
110
|
-
// Try glob matching first
|
|
111
|
-
if (minimatch(routePath, normalizedPage)) {
|
|
112
|
-
return true;
|
|
113
|
-
}
|
|
114
|
-
// Fallback to exact match or substring match for backward compatibility
|
|
115
|
-
return routePath === normalizedPage || routePath.includes(normalizedPage);
|
|
116
|
-
});
|
|
117
|
-
if (isExcluded) return true;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// Check exclude patterns (regex patterns)
|
|
121
|
-
if (options.excludePatterns && options.excludePatterns.length > 0) {
|
|
122
|
-
const isPatternExcluded = options.excludePatterns.some(pattern => {
|
|
123
|
-
try {
|
|
124
|
-
const regex = new RegExp(pattern);
|
|
125
|
-
return regex.test(routePath);
|
|
126
|
-
} catch (e) {
|
|
127
|
-
console.warn(`📄 Invalid regex pattern: ${pattern}`);
|
|
128
|
-
return false;
|
|
129
|
-
}
|
|
130
|
-
});
|
|
131
|
-
if (isPatternExcluded) return true;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
return false;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
function extractRoutePath(normalizedRequest, options) {
|
|
138
|
-
// Extract route path from either /pages/ or /app/ directory
|
|
139
|
-
let routePath = null;
|
|
140
|
-
|
|
141
|
-
// Check for Pages Router path
|
|
142
|
-
if (options.supportPagesRouter) {
|
|
143
|
-
const pagesIndex = normalizedRequest.indexOf(`/${options.pagesDir}/`);
|
|
144
|
-
if (pagesIndex !== -1) {
|
|
145
|
-
routePath = normalizedRequest.substring(pagesIndex + `/${options.pagesDir}/`.length);
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
// Check for App Router path
|
|
150
|
-
if (!routePath && options.supportAppRouter) {
|
|
151
|
-
const appIndex = normalizedRequest.indexOf(`/${options.appDir}/`);
|
|
152
|
-
if (appIndex !== -1) {
|
|
153
|
-
routePath = normalizedRequest.substring(appIndex + `/${options.appDir}/`.length);
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
if (!routePath) return null;
|
|
158
|
-
|
|
159
|
-
// Remove file extension
|
|
160
|
-
routePath = routePath.replace(/\.(js|jsx|ts|tsx)$/, '');
|
|
161
|
-
|
|
162
|
-
// Handle special App Router files
|
|
163
|
-
if (options.supportAppRouter) {
|
|
164
|
-
// Remove App Router special files (page.tsx, layout.tsx, etc.)
|
|
165
|
-
routePath = routePath.replace(/\/(page|layout|loading|error|not-found|template|default)$/, '');
|
|
166
|
-
|
|
167
|
-
// Handle route groups - remove (group) patterns
|
|
168
|
-
routePath = routePath.replace(/\/\([^)]+\)/g, '');
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
// Clean up empty segments and normalize
|
|
172
|
-
routePath = routePath.replace(/\/+/g, '/').replace(/^\/|\/$/g, '');
|
|
173
|
-
|
|
174
|
-
return routePath || 'index';
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
function normalizePath(filePath) {
|
|
178
|
-
if (!filePath || typeof filePath !== 'string') {
|
|
179
|
-
return '';
|
|
180
|
-
}
|
|
181
|
-
return filePath.replace(/\\/g, '/').toLowerCase();
|
|
182
|
-
}
|
|
183
|
-
|
|
184
59
|
module.exports = withPageFilter;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "next-build-filter",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.5",
|
|
4
4
|
"description": "A Next.js plugin to exclude pages/routes during build without removing files. Supports both Pages Router and App Router (Next.js 13+).",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"test:e2e": "node tests/e2e/run-e2e-tests.js",
|
|
18
18
|
"test:all": "npm run test && npm run test:e2e",
|
|
19
19
|
"prepublishOnly": "npm run build",
|
|
20
|
-
"postversion": "npm publish",
|
|
20
|
+
"postversion": "npm run test:all && npm publish && git push --follow-tags",
|
|
21
21
|
"build": "echo \"No build process needed for now\""
|
|
22
22
|
},
|
|
23
23
|
"keywords": [
|