@module-federation/nextjs-mf 5.2.2 → 5.3.0
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/NextFederationPlugin2.js +0 -0
- package/lib/_virtual/UrlNode.js +8 -0
- package/lib/_virtual/_commonjsHelpers.js +26 -0
- package/lib/_virtual/_tslib.js +101 -0
- package/lib/_virtual/helpers.js +7 -0
- package/lib/_virtual/nextPageMapLoader.js +7 -0
- package/lib/client/CombinedPages.d.ts +28 -0
- package/lib/client/CombinedPages.d.ts.map +1 -0
- package/lib/client/CombinedPages.js +60 -0
- package/lib/client/MFClient.d.ts +70 -0
- package/lib/client/MFClient.d.ts.map +1 -0
- package/lib/client/MFClient.js +197 -0
- package/lib/client/RemoteContainer.d.ts +58 -0
- package/lib/client/RemoteContainer.d.ts.map +1 -0
- package/lib/client/RemoteContainer.js +161 -0
- package/lib/client/RemotePages.d.ts +48 -0
- package/lib/client/RemotePages.d.ts.map +1 -0
- package/lib/client/RemotePages.js +168 -0
- package/lib/client/UrlNode.d.ts +18 -0
- package/lib/client/UrlNode.d.ts.map +1 -0
- package/lib/client/UrlNode.js +162 -0
- package/lib/client/helpers.d.ts +17 -0
- package/lib/client/helpers.d.ts.map +1 -0
- package/lib/client/helpers.js +108 -0
- package/lib/client/useMFClient.d.ts +25 -0
- package/lib/client/useMFClient.d.ts.map +1 -0
- package/lib/client/useMFClient.js +79 -0
- package/lib/client/useMFRemote.d.ts +17 -0
- package/lib/client/useMFRemote.d.ts.map +1 -0
- package/lib/client/useMFRemote.js +72 -0
- package/lib/loaders/UrlNode.js +215 -0
- package/lib/loaders/helpers.js +10 -3
- package/lib/loaders/nextPageMapLoader.js +81 -17
- package/lib/loaders/patchNextClientPageLoader.js +53 -0
- package/lib/plugins/DevHmrFixInvalidPongPlugin.js +65 -0
- package/lib/utils.js +7 -3
- package/package.json +21 -4
- package/tsconfig.json +33 -0
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
var _tslib = require('../_virtual/_tslib.js');
|
|
6
|
+
var UrlNode = require('./UrlNode.js');
|
|
7
|
+
|
|
8
|
+
var TEST_DYNAMIC_ROUTE = /\/\[[^/]+?\](?=\/|$)/;
|
|
9
|
+
function isDynamicRoute(route) {
|
|
10
|
+
return TEST_DYNAMIC_ROUTE.test(route);
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Parses a given parameter from a route to a data structure that can be used
|
|
14
|
+
* to generate the parametrized route. Examples:
|
|
15
|
+
* - `[...slug]` -> `{ name: 'slug', repeat: true, optional: true }`
|
|
16
|
+
* - `[foo]` -> `{ name: 'foo', repeat: false, optional: true }`
|
|
17
|
+
* - `bar` -> `{ name: 'bar', repeat: false, optional: false }`
|
|
18
|
+
*/
|
|
19
|
+
function parseParameter(param) {
|
|
20
|
+
var optional = param.startsWith('[') && param.endsWith(']');
|
|
21
|
+
if (optional) {
|
|
22
|
+
param = param.slice(1, -1);
|
|
23
|
+
}
|
|
24
|
+
var repeat = param.startsWith('...');
|
|
25
|
+
if (repeat) {
|
|
26
|
+
param = param.slice(3);
|
|
27
|
+
}
|
|
28
|
+
return { key: param, repeat: repeat, optional: optional };
|
|
29
|
+
}
|
|
30
|
+
function getParametrizedRoute(route) {
|
|
31
|
+
// const segments = removeTrailingSlash(route).slice(1).split('/')
|
|
32
|
+
var segments = route.slice(1).split('/');
|
|
33
|
+
var groups = {};
|
|
34
|
+
var groupIndex = 1;
|
|
35
|
+
return {
|
|
36
|
+
parameterizedRoute: segments
|
|
37
|
+
.map(function (segment) {
|
|
38
|
+
if (segment.startsWith('[') && segment.endsWith(']')) {
|
|
39
|
+
var _a = parseParameter(segment.slice(1, -1)), key = _a.key, optional = _a.optional, repeat = _a.repeat;
|
|
40
|
+
groups[key] = { pos: groupIndex++, repeat: repeat, optional: optional };
|
|
41
|
+
return repeat ? (optional ? '(?:/(.+?))?' : '/(.+?)') : '/([^/]+?)';
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
return "/".concat(escapeStringRegexp(segment));
|
|
45
|
+
}
|
|
46
|
+
})
|
|
47
|
+
.join(''),
|
|
48
|
+
groups: groups,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
function getRouteRegex(normalizedRoute) {
|
|
52
|
+
var _a = getParametrizedRoute(normalizedRoute), parameterizedRoute = _a.parameterizedRoute, groups = _a.groups;
|
|
53
|
+
return {
|
|
54
|
+
re: new RegExp("^".concat(parameterizedRoute, "(?:/)?$")),
|
|
55
|
+
groups: groups,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
var reHasRegExp = /[|\\{}()[\]^$+*?.-]/;
|
|
59
|
+
var reReplaceRegExp = /[|\\{}()[\]^$+*?.-]/g;
|
|
60
|
+
function escapeStringRegexp(str) {
|
|
61
|
+
// see also: https://github.com/lodash/lodash/blob/2da024c3b4f9947a48517639de7560457cd4ec6c/escapeRegExp.js#L23
|
|
62
|
+
if (reHasRegExp.test(str)) {
|
|
63
|
+
return str.replace(reReplaceRegExp, '\\$&');
|
|
64
|
+
}
|
|
65
|
+
return str;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Convert browser pathname to NextJs route.
|
|
69
|
+
* This method is required for proper work of Dynamic routes in NextJS.
|
|
70
|
+
*/
|
|
71
|
+
function pathnameToRoute(cleanPathname, routes) {
|
|
72
|
+
var e_1, _a;
|
|
73
|
+
if (routes.includes(cleanPathname)) {
|
|
74
|
+
return cleanPathname;
|
|
75
|
+
}
|
|
76
|
+
try {
|
|
77
|
+
for (var routes_1 = _tslib.__values(routes), routes_1_1 = routes_1.next(); !routes_1_1.done; routes_1_1 = routes_1.next()) {
|
|
78
|
+
var route = routes_1_1.value;
|
|
79
|
+
if (isDynamicRoute(route) && getRouteRegex(route).re.test(cleanPathname)) {
|
|
80
|
+
return route;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
catch (e_1_1) { e_1 = { error: e_1_1 }; }
|
|
85
|
+
finally {
|
|
86
|
+
try {
|
|
87
|
+
if (routes_1_1 && !routes_1_1.done && (_a = routes_1.return)) _a.call(routes_1);
|
|
88
|
+
}
|
|
89
|
+
finally { if (e_1) throw e_1.error; }
|
|
90
|
+
}
|
|
91
|
+
return undefined;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Sort provided pages in correct nextjs order.
|
|
95
|
+
* This sorting is required if you are using dynamic routes in your apps.
|
|
96
|
+
* If order is incorrect then Nextjs may use dynamicRoute instead of exact page.
|
|
97
|
+
*/
|
|
98
|
+
function sortNextPages(pages) {
|
|
99
|
+
var root = new UrlNode.UrlNode();
|
|
100
|
+
pages.forEach(function (pageRoute) { return root.insert(pageRoute); });
|
|
101
|
+
// Smoosh will then sort those sublevels up to the point where you get the correct route definition priority
|
|
102
|
+
return root.smoosh();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
exports.getRouteRegex = getRouteRegex;
|
|
106
|
+
exports.isDynamicRoute = isDynamicRoute;
|
|
107
|
+
exports.pathnameToRoute = pathnameToRoute;
|
|
108
|
+
exports.sortNextPages = sortNextPages;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { MFClient } from './MFClient';
|
|
2
|
+
import type { RemoteContainer } from './RemoteContainer';
|
|
3
|
+
export declare type MFClientHookOptions = {
|
|
4
|
+
/**
|
|
5
|
+
* This callback will be called when user switches to federated page
|
|
6
|
+
* - as a first arg you will receive RemoteContainer
|
|
7
|
+
* If user return back to the host application page
|
|
8
|
+
* - then the first argument became `undefined`
|
|
9
|
+
*
|
|
10
|
+
* This callback is called only if changed remote from which served current visible page
|
|
11
|
+
* and does not called on internal nextjs route changes.
|
|
12
|
+
*
|
|
13
|
+
* This callback helps in very convenient way in _app.tsx (or any other React component)
|
|
14
|
+
* load additional data from RemoteContainer and pass it to your application. Eg.:
|
|
15
|
+
* - application menu
|
|
16
|
+
* - apollo configs
|
|
17
|
+
* - translation strings
|
|
18
|
+
*/
|
|
19
|
+
onChangeRemote?: (remote: RemoteContainer | undefined, MFClient: MFClient) => void;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* React hook which provides convenient way for working with ModuleFederation runtime changes in runtime;
|
|
23
|
+
*/
|
|
24
|
+
export declare function useMFClient(opts: MFClientHookOptions): MFClient;
|
|
25
|
+
//# sourceMappingURL=useMFClient.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useMFClient.d.ts","sourceRoot":"","sources":["../../src/client/useMFClient.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAGzD,oBAAY,mBAAmB,GAAG;IAChC;;;;;;;;;;;;;;OAcG;IACH,cAAc,CAAC,EAAE,CACf,MAAM,EAAE,eAAe,GAAG,SAAS,EACnC,QAAQ,EAAE,QAAQ,KACf,IAAI,CAAC;CACX,CAAC;AAQF;;GAEG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,mBAAmB,GAAG,QAAQ,CAgD/D"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
var React = require('react');
|
|
6
|
+
var singletonRouter = require('next/dist/client/router');
|
|
7
|
+
|
|
8
|
+
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
|
|
9
|
+
|
|
10
|
+
function _interopNamespace(e) {
|
|
11
|
+
if (e && e.__esModule) return e;
|
|
12
|
+
var n = Object.create(null);
|
|
13
|
+
if (e) {
|
|
14
|
+
Object.keys(e).forEach(function (k) {
|
|
15
|
+
if (k !== 'default') {
|
|
16
|
+
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
17
|
+
Object.defineProperty(n, k, d.get ? d : {
|
|
18
|
+
enumerable: true,
|
|
19
|
+
get: function () { return e[k]; }
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
n["default"] = e;
|
|
25
|
+
return Object.freeze(n);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
var React__namespace = /*#__PURE__*/_interopNamespace(React);
|
|
29
|
+
var singletonRouter__default = /*#__PURE__*/_interopDefaultLegacy(singletonRouter);
|
|
30
|
+
|
|
31
|
+
var isBrowser = typeof window !== 'undefined';
|
|
32
|
+
/**
|
|
33
|
+
* React hook which provides convenient way for working with ModuleFederation runtime changes in runtime;
|
|
34
|
+
*/
|
|
35
|
+
function useMFClient(opts) {
|
|
36
|
+
var MFClient = isBrowser
|
|
37
|
+
? window.mf_client
|
|
38
|
+
: /* TODO: inject here SSR version of MFClient if it will be needed in future */ {};
|
|
39
|
+
var innerState = React__namespace.useRef({
|
|
40
|
+
remote: undefined,
|
|
41
|
+
});
|
|
42
|
+
React__namespace.useEffect(function () {
|
|
43
|
+
// Step 1: Define handlers and helpers
|
|
44
|
+
var processRemoteChange = function (remote) {
|
|
45
|
+
if (innerState.current.remote !== remote) {
|
|
46
|
+
innerState.current.remote = remote;
|
|
47
|
+
if (opts === null || opts === void 0 ? void 0 : opts.onChangeRemote) {
|
|
48
|
+
opts.onChangeRemote(remote, MFClient);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
var handleRouterChange = function (pathname) {
|
|
53
|
+
if (MFClient.isFederatedPathname(pathname)) {
|
|
54
|
+
var remote = MFClient.remotePages.routeToRemote(pathname);
|
|
55
|
+
processRemoteChange(remote);
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
processRemoteChange(undefined);
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
// Step 2: run bootstrap logic
|
|
62
|
+
var initialRemote = MFClient.isFederatedPathname(window.location.pathname)
|
|
63
|
+
? MFClient.remotePages.routeToRemote(window.location.pathname)
|
|
64
|
+
: undefined;
|
|
65
|
+
if (initialRemote) {
|
|
66
|
+
// important for first load to fire `onChangeRemote` with different remote
|
|
67
|
+
// because in innerState by default we assume that used local application
|
|
68
|
+
processRemoteChange(initialRemote);
|
|
69
|
+
}
|
|
70
|
+
// Step 3: Subscribe on events
|
|
71
|
+
singletonRouter__default["default"].events.on('routeChangeStart', handleRouterChange);
|
|
72
|
+
return function () {
|
|
73
|
+
singletonRouter__default["default"].events.off('routeChangeStart', handleRouterChange);
|
|
74
|
+
};
|
|
75
|
+
}, []);
|
|
76
|
+
return MFClient;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
exports.useMFClient = useMFClient;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { RemoteContainer } from './RemoteContainer';
|
|
2
|
+
declare type UseMFRemoteResult = {
|
|
3
|
+
/** is container loaded or not */
|
|
4
|
+
loaded: boolean;
|
|
5
|
+
/** remote is Lazy, so it will be loaded if getModule(), getContainer() were called */
|
|
6
|
+
remote: RemoteContainer;
|
|
7
|
+
/** Present if error occurs during remote container loading */
|
|
8
|
+
error: Error | undefined;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* React hook which provides an access to RemoteContainer in Module Federation
|
|
12
|
+
*
|
|
13
|
+
* @param global - can be a global variable name OR connection string "global@url"
|
|
14
|
+
*/
|
|
15
|
+
export declare function useMFRemote(global: string): UseMFRemoteResult;
|
|
16
|
+
export {};
|
|
17
|
+
//# sourceMappingURL=useMFRemote.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useMFRemote.d.ts","sourceRoot":"","sources":["../../src/client/useMFRemote.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEpD,aAAK,iBAAiB,GAAG;IACvB,iCAAiC;IACjC,MAAM,EAAE,OAAO,CAAC;IAChB,sFAAsF;IACtF,MAAM,EAAE,eAAe,CAAC;IACxB,8DAA8D;IAC9D,KAAK,EAAE,KAAK,GAAG,SAAS,CAAC;CAC1B,CAAC;AAIF;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,iBAAiB,CAwC7D"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
var _tslib = require('../_virtual/_tslib.js');
|
|
6
|
+
var React = require('react');
|
|
7
|
+
var RemoteContainer = require('./RemoteContainer.js');
|
|
8
|
+
|
|
9
|
+
function _interopNamespace(e) {
|
|
10
|
+
if (e && e.__esModule) return e;
|
|
11
|
+
var n = Object.create(null);
|
|
12
|
+
if (e) {
|
|
13
|
+
Object.keys(e).forEach(function (k) {
|
|
14
|
+
if (k !== 'default') {
|
|
15
|
+
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
16
|
+
Object.defineProperty(n, k, d.get ? d : {
|
|
17
|
+
enumerable: true,
|
|
18
|
+
get: function () { return e[k]; }
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
n["default"] = e;
|
|
24
|
+
return Object.freeze(n);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
var React__namespace = /*#__PURE__*/_interopNamespace(React);
|
|
28
|
+
|
|
29
|
+
var isBrowser = typeof window !== 'undefined';
|
|
30
|
+
/**
|
|
31
|
+
* React hook which provides an access to RemoteContainer in Module Federation
|
|
32
|
+
*
|
|
33
|
+
* @param global - can be a global variable name OR connection string "global@url"
|
|
34
|
+
*/
|
|
35
|
+
function useMFRemote(global) {
|
|
36
|
+
var remote;
|
|
37
|
+
if (isBrowser) {
|
|
38
|
+
// on client (we get instances from global variable because webpack breaks Singletons)
|
|
39
|
+
var MFClient_1 = window.mf_client;
|
|
40
|
+
remote = MFClient_1.remotes[global] || MFClient_1.registerRemote(global);
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
// on server side
|
|
44
|
+
remote = RemoteContainer.RemoteContainer.createSingleton(global);
|
|
45
|
+
}
|
|
46
|
+
var _a = _tslib.__read(React__namespace.useState(remote.isLoaded()), 2), loaded = _a[0], setLoaded = _a[1];
|
|
47
|
+
var _b = _tslib.__read(React__namespace.useState(remote.error), 2), error = _b[0], setError = _b[1];
|
|
48
|
+
React__namespace.useEffect(function () {
|
|
49
|
+
var handleLoadComplete = function () {
|
|
50
|
+
setLoaded(true);
|
|
51
|
+
};
|
|
52
|
+
var handleLoadError = function (e) {
|
|
53
|
+
setError(e);
|
|
54
|
+
};
|
|
55
|
+
if (!loaded && remote.isLoaded()) {
|
|
56
|
+
handleLoadComplete();
|
|
57
|
+
}
|
|
58
|
+
remote.events.on('loadComplete', handleLoadComplete);
|
|
59
|
+
remote.events.on('loadError', handleLoadError);
|
|
60
|
+
return function () {
|
|
61
|
+
remote.events.off('loadComplete', handleLoadComplete);
|
|
62
|
+
remote.events.off('loadError', handleLoadError);
|
|
63
|
+
};
|
|
64
|
+
}, [remote]);
|
|
65
|
+
return {
|
|
66
|
+
remote: remote,
|
|
67
|
+
loaded: loaded,
|
|
68
|
+
error: error,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
exports.useMFRemote = useMFRemote;
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 🛑🛑🛑 Attention! 🛑🛑🛑
|
|
7
|
+
* Do not add type definitions to this file!!
|
|
8
|
+
* It already exists in src/client folder.
|
|
9
|
+
* So remove this file and import ts version from src/client/UrlNode.
|
|
10
|
+
*
|
|
11
|
+
* This file was add just for proper compilation of JS files without any rollup warnings:
|
|
12
|
+
* "(!) Unresolved dependencies"
|
|
13
|
+
* "nextjs-mf/src/client/UrlNode (imported by nextjs-mf/src/client/UrlNode?commonjs-external)"
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* This class provides a logic of sorting dynamic routes in NextJS.
|
|
18
|
+
*
|
|
19
|
+
* It was copied from
|
|
20
|
+
* @see https://github.com/vercel/next.js/blob/canary/packages/next/shared/lib/router/utils/sorted-routes.ts
|
|
21
|
+
*/
|
|
22
|
+
class UrlNode {
|
|
23
|
+
placeholder = true;
|
|
24
|
+
children = new Map();
|
|
25
|
+
slugName = null;
|
|
26
|
+
restSlugName = null;
|
|
27
|
+
optionalRestSlugName = null;
|
|
28
|
+
|
|
29
|
+
insert(urlPath) {
|
|
30
|
+
this._insert(urlPath.split('/').filter(Boolean), [], false);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
smoosh() {
|
|
34
|
+
return this._smoosh();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
_smoosh(prefix = '/') {
|
|
38
|
+
const childrenPaths = [...this.children.keys()].sort();
|
|
39
|
+
if (this.slugName !== null) {
|
|
40
|
+
childrenPaths.splice(childrenPaths.indexOf('[]'), 1);
|
|
41
|
+
}
|
|
42
|
+
if (this.restSlugName !== null) {
|
|
43
|
+
childrenPaths.splice(childrenPaths.indexOf('[...]'), 1);
|
|
44
|
+
}
|
|
45
|
+
if (this.optionalRestSlugName !== null) {
|
|
46
|
+
childrenPaths.splice(childrenPaths.indexOf('[[...]]'), 1);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const routes = childrenPaths
|
|
50
|
+
.map((c) => this.children.get(c)._smoosh(`${prefix}${c}/`))
|
|
51
|
+
.reduce((prev, curr) => [...prev, ...curr], []);
|
|
52
|
+
|
|
53
|
+
if (this.slugName !== null) {
|
|
54
|
+
routes.push(
|
|
55
|
+
...this.children.get('[]')._smoosh(`${prefix}[${this.slugName}]/`)
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (!this.placeholder) {
|
|
60
|
+
const r = prefix === '/' ? '/' : prefix.slice(0, -1);
|
|
61
|
+
if (this.optionalRestSlugName != null) {
|
|
62
|
+
throw new Error(
|
|
63
|
+
`You cannot define a route with the same specificity as a optional catch-all route ("${r}" and "${r}[[...${this.optionalRestSlugName}]]").`
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
routes.unshift(r);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (this.restSlugName !== null) {
|
|
71
|
+
routes.push(
|
|
72
|
+
...this.children
|
|
73
|
+
.get('[...]')
|
|
74
|
+
._smoosh(`${prefix}[...${this.restSlugName}]/`)
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (this.optionalRestSlugName !== null) {
|
|
79
|
+
routes.push(
|
|
80
|
+
...this.children
|
|
81
|
+
.get('[[...]]')
|
|
82
|
+
._smoosh(`${prefix}[[...${this.optionalRestSlugName}]]/`)
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return routes;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
_insert(urlPaths, slugNames, isCatchAll) {
|
|
90
|
+
if (urlPaths.length === 0) {
|
|
91
|
+
this.placeholder = false;
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (isCatchAll) {
|
|
96
|
+
throw new Error(`Catch-all must be the last part of the URL.`);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// The next segment in the urlPaths list
|
|
100
|
+
let nextSegment = urlPaths[0];
|
|
101
|
+
|
|
102
|
+
// Check if the segment matches `[something]`
|
|
103
|
+
if (nextSegment.startsWith('[') && nextSegment.endsWith(']')) {
|
|
104
|
+
// Strip `[` and `]`, leaving only `something`
|
|
105
|
+
let segmentName = nextSegment.slice(1, -1);
|
|
106
|
+
|
|
107
|
+
let isOptional = false;
|
|
108
|
+
if (segmentName.startsWith('[') && segmentName.endsWith(']')) {
|
|
109
|
+
// Strip optional `[` and `]`, leaving only `something`
|
|
110
|
+
segmentName = segmentName.slice(1, -1);
|
|
111
|
+
isOptional = true;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (segmentName.startsWith('...')) {
|
|
115
|
+
// Strip `...`, leaving only `something`
|
|
116
|
+
segmentName = segmentName.substring(3);
|
|
117
|
+
isCatchAll = true;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (segmentName.startsWith('[') || segmentName.endsWith(']')) {
|
|
121
|
+
throw new Error(
|
|
122
|
+
`Segment names may not start or end with extra brackets ('${segmentName}').`
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (segmentName.startsWith('.')) {
|
|
127
|
+
throw new Error(
|
|
128
|
+
`Segment names may not start with erroneous periods ('${segmentName}').`
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const handleSlug = function handleSlug(previousSlug, nextSlug) {
|
|
133
|
+
if (previousSlug !== null) {
|
|
134
|
+
// If the specific segment already has a slug but the slug is not `something`
|
|
135
|
+
// This prevents collisions like:
|
|
136
|
+
// pages/[post]/index.js
|
|
137
|
+
// pages/[id]/index.js
|
|
138
|
+
// Because currently multiple dynamic params on the same segment level are not supported
|
|
139
|
+
if (previousSlug !== nextSlug) {
|
|
140
|
+
// TODO: This error seems to be confusing for users, needs an error link, the description can be based on above comment.
|
|
141
|
+
throw new Error(
|
|
142
|
+
`You cannot use different slug names for the same dynamic path ('${previousSlug}' !== '${nextSlug}').`
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
slugNames.forEach((slug) => {
|
|
148
|
+
if (slug === nextSlug) {
|
|
149
|
+
throw new Error(
|
|
150
|
+
`You cannot have the same slug name "${nextSlug}" repeat within a single dynamic path`
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (slug.replace(/\W/g, '') === nextSegment.replace(/\W/g, '')) {
|
|
155
|
+
throw new Error(
|
|
156
|
+
`You cannot have the slug names "${slug}" and "${nextSlug}" differ only by non-word symbols within a single dynamic path`
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
slugNames.push(nextSlug);
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
if (isCatchAll) {
|
|
165
|
+
if (isOptional) {
|
|
166
|
+
if (this.restSlugName != null) {
|
|
167
|
+
throw new Error(
|
|
168
|
+
`You cannot use both an required and optional catch-all route at the same level ("[...${this.restSlugName}]" and "${urlPaths[0]}" ).`
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
handleSlug(this.optionalRestSlugName, segmentName);
|
|
173
|
+
// slugName is kept as it can only be one particular slugName
|
|
174
|
+
this.optionalRestSlugName = segmentName;
|
|
175
|
+
// nextSegment is overwritten to [[...]] so that it can later be sorted specifically
|
|
176
|
+
nextSegment = '[[...]]';
|
|
177
|
+
} else {
|
|
178
|
+
if (this.optionalRestSlugName != null) {
|
|
179
|
+
throw new Error(
|
|
180
|
+
`You cannot use both an optional and required catch-all route at the same level ("[[...${this.optionalRestSlugName}]]" and "${urlPaths[0]}").`
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
handleSlug(this.restSlugName, segmentName);
|
|
185
|
+
// slugName is kept as it can only be one particular slugName
|
|
186
|
+
this.restSlugName = segmentName;
|
|
187
|
+
// nextSegment is overwritten to [...] so that it can later be sorted specifically
|
|
188
|
+
nextSegment = '[...]';
|
|
189
|
+
}
|
|
190
|
+
} else {
|
|
191
|
+
if (isOptional) {
|
|
192
|
+
throw new Error(
|
|
193
|
+
`Optional route parameters are not yet supported ("${urlPaths[0]}").`
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
handleSlug(this.slugName, segmentName);
|
|
197
|
+
// slugName is kept as it can only be one particular slugName
|
|
198
|
+
this.slugName = segmentName;
|
|
199
|
+
// nextSegment is overwritten to [] so that it can later be sorted specifically
|
|
200
|
+
nextSegment = '[]';
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// If this UrlNode doesn't have the nextSegment yet we create a new child UrlNode
|
|
205
|
+
if (!this.children.has(nextSegment)) {
|
|
206
|
+
this.children.set(nextSegment, new UrlNode());
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
this.children
|
|
210
|
+
.get(nextSegment)
|
|
211
|
+
._insert(urlPaths.slice(1), slugNames, isCatchAll);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
exports.UrlNode = UrlNode;
|
package/lib/loaders/helpers.js
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var helpers = require('../_virtual/helpers.js');
|
|
4
|
+
|
|
1
5
|
/**
|
|
2
6
|
* Inject a loader into the current module rule.
|
|
3
7
|
* This function mutates `rule` argument!
|
|
4
8
|
*/
|
|
5
|
-
|
|
9
|
+
|
|
10
|
+
helpers.__exports.injectRuleLoader = function injectRuleLoader(rule, loader) {
|
|
6
11
|
if (rule.loader) {
|
|
7
12
|
rule.use = [loader, { loader: rule.loader, options: rule.options }];
|
|
8
13
|
delete rule.loader;
|
|
@@ -15,7 +20,7 @@ module.exports.injectRuleLoader = function injectRuleLoader(rule, loader) {
|
|
|
15
20
|
/**
|
|
16
21
|
* Check that current module rule has a loader with the provided name.
|
|
17
22
|
*/
|
|
18
|
-
|
|
23
|
+
helpers.__exports.hasLoader = function hasLoader(rule, loaderName) {
|
|
19
24
|
if (rule.loader === loaderName) {
|
|
20
25
|
return true;
|
|
21
26
|
} else if (rule.use) {
|
|
@@ -34,7 +39,7 @@ module.exports.hasLoader = function hasLoader(rule, loaderName) {
|
|
|
34
39
|
return false;
|
|
35
40
|
};
|
|
36
41
|
|
|
37
|
-
|
|
42
|
+
helpers.__exports.toDisplayErrors = function toDisplayErrors(err) {
|
|
38
43
|
return err
|
|
39
44
|
.map((error) => {
|
|
40
45
|
let message = error.message;
|
|
@@ -45,3 +50,5 @@ module.exports.toDisplayErrors = function toDisplayErrors(err) {
|
|
|
45
50
|
})
|
|
46
51
|
.join('\n');
|
|
47
52
|
};
|
|
53
|
+
|
|
54
|
+
module.exports = helpers.__exports;
|