catalyst-core 0.3.0-beta.3 → 0.3.0-beta.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/dist/index.jsx +1 -1
- package/dist/native/bridge/hooks/useNativeTransition.js +2 -2
- package/dist/server/expressServer.js +7 -0
- package/dist/server/renderer/document/Body.jsx +4 -1
- package/dist/server/renderer/handler.jsx +1 -1
- package/dist/vite/FastRefresh.jsx +3 -1
- package/dist/vite/loadCustomViteConfig.js +28 -0
- package/dist/vite/node-loader.mjs +30 -2
- package/dist/vite/resolveDevServerConfig.js +45 -0
- package/dist/vite/vite.config.client.js +1 -1
- package/dist/vite/vite.config.js +42 -11
- package/dist/vite/vite.config.server.js +5 -1
- package/dist/web-router/components/MetaTag.jsx +1 -1
- package/dist/web-router/components/RouterDataProvider.jsx +10 -10
- package/dist/web-router/context.jsx +1 -1
- package/dist/web-router/hooks.jsx +1 -1
- package/dist/web-router/types/components/RouterDataProvider.d.ts +4 -4
- package/dist/web-router/types/context.d.ts +2 -2
- package/dist/web-router/types/hooks.d.ts +1 -1
- package/dist/web-router/types/index.d.ts +1 -1
- package/dist/web-router/types/utils/metaDataUtils.d.ts +16 -0
- package/package.json +3 -2
- package/test/vite/resolveDevServerConfig.test.js +113 -0
package/dist/index.jsx
CHANGED
|
@@ -5,7 +5,7 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
5
5
|
});
|
|
6
6
|
exports.useNativeTransition = void 0;
|
|
7
7
|
var _react = require("react");
|
|
8
|
-
var
|
|
8
|
+
var _reactRouter = require("react-router");
|
|
9
9
|
var _NativeBridge = _interopRequireDefault(require("../utils/NativeBridge.js"));
|
|
10
10
|
var _NativeInterfaces = require("../constants/NativeInterfaces.js");
|
|
11
11
|
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
@@ -34,7 +34,7 @@ const MIN_TIMEOUT = 800;
|
|
|
34
34
|
* timeout: ms safety timer (default: max(duration*3, 800))
|
|
35
35
|
*/
|
|
36
36
|
const useNativeTransition = (defaults = {}) => {
|
|
37
|
-
const router = (0,
|
|
37
|
+
const router = (0, _reactRouter.useNavigate)();
|
|
38
38
|
const isNative = typeof window !== "undefined" && _NativeBridge.default.isAvailable();
|
|
39
39
|
const [transitioning, setTransitioning] = (0, _react.useState)(false);
|
|
40
40
|
const overlayRef = (0, _react.useRef)(null);
|
|
@@ -10,6 +10,7 @@ import pc from "picocolors"
|
|
|
10
10
|
import fs from "fs"
|
|
11
11
|
const { cyan, yellow, green } = pc
|
|
12
12
|
|
|
13
|
+
import { toMountPathPrefix } from "../vite/resolveDevServerConfig.js"
|
|
13
14
|
import { validateMiddleware, safeCall } from "./utils/validator.js"
|
|
14
15
|
import { botDetectionMiddleware } from "./utils/botDetectionMiddleware.js"
|
|
15
16
|
import { cjsRequire } from "./utils/cjsRequire.js"
|
|
@@ -208,6 +209,12 @@ async function createServer() {
|
|
|
208
209
|
appType: "custom",
|
|
209
210
|
root: process.env.src_path,
|
|
210
211
|
})
|
|
212
|
+
|
|
213
|
+
// Expose Vite's resolved base to the SSR renderer so the URLs it injects
|
|
214
|
+
// (client entry, react-refresh preamble) match the path Vite serves under.
|
|
215
|
+
// Vite normalizes base with a trailing slash; trim it for clean URL joins.
|
|
216
|
+
process.env.APP_MOUNT_PATH = toMountPathPrefix(vite.config.base)
|
|
217
|
+
|
|
211
218
|
app.use(vite.middlewares)
|
|
212
219
|
}
|
|
213
220
|
|
|
@@ -30,7 +30,10 @@ export function Body(props) {
|
|
|
30
30
|
__html: `window.__SAFE_AREA_INITIAL__ = ${JSON.stringify(safeArea)}; window.__CATALYST_NATIVE_WEBVIEW__ = ${nativeWebView ? "true" : "false"}`,
|
|
31
31
|
}}
|
|
32
32
|
/>
|
|
33
|
-
{process.env.NODE_ENV === "development" &&
|
|
33
|
+
{process.env.NODE_ENV === "development" && (
|
|
34
|
+
// Base pinned to Vite's resolved base by server/expressServer.js.
|
|
35
|
+
<script type="module" src={`${process.env.APP_MOUNT_PATH || ""}/client/index.js`}></script>
|
|
36
|
+
)}
|
|
34
37
|
{jsx}
|
|
35
38
|
<script
|
|
36
39
|
/* eslint-disable */
|
|
@@ -4,7 +4,7 @@ import { Provider } from "react-redux"
|
|
|
4
4
|
import { Body } from "./document/Body.jsx"
|
|
5
5
|
import { Head } from "./document/Head.jsx"
|
|
6
6
|
|
|
7
|
-
import { StaticRouter } from "react-router
|
|
7
|
+
import { StaticRouter } from "react-router"
|
|
8
8
|
import ServerRouter from "../../router/ServerRouter.js"
|
|
9
9
|
import { renderToPipeableStream } from "react-dom/server"
|
|
10
10
|
import { getUserAgentDetails } from "../utils/userAgentUtil.js"
|
|
@@ -6,8 +6,10 @@ const FastRefresh = () => {
|
|
|
6
6
|
type="module"
|
|
7
7
|
// eslint-disable-next-line risxss/catch-potential-xss-react
|
|
8
8
|
dangerouslySetInnerHTML={{
|
|
9
|
+
// Base-prefixed so the preamble resolves under a sub-path; otherwise
|
|
10
|
+
// plugin-react modules throw "$RefreshSig$ is not defined".
|
|
9
11
|
__html: `
|
|
10
|
-
import { injectIntoGlobalHook } from "/@react-refresh";
|
|
12
|
+
import { injectIntoGlobalHook } from "${process.env.APP_MOUNT_PATH || ""}/@react-refresh";
|
|
11
13
|
injectIntoGlobalHook(window);
|
|
12
14
|
window.$RefreshReg$ = () => {};
|
|
13
15
|
window.$RefreshSig$ = () => (type) => type;
|
|
@@ -5,6 +5,34 @@ import { pathToFileURL } from "url"
|
|
|
5
5
|
const emptyConfig = {
|
|
6
6
|
ssrPlugins: [],
|
|
7
7
|
clientPlugins: [],
|
|
8
|
+
/**
|
|
9
|
+
* devServer lets apps tune the Vite dev server without editing framework
|
|
10
|
+
* internals. All fields are optional and map 1-to-1 onto Vite's `server`
|
|
11
|
+
* config (https://vite.dev/config/server-options).
|
|
12
|
+
*
|
|
13
|
+
* The most common use-case is an app served under a sub-path behind an SSL
|
|
14
|
+
* reverse proxy (nginx/caddy):
|
|
15
|
+
*
|
|
16
|
+
* // buildConfig.js
|
|
17
|
+
* export default {
|
|
18
|
+
* devServer: {
|
|
19
|
+
* // Vite's base path — the single source of truth for the dev mount
|
|
20
|
+
* // path; must match the reverse-proxy location prefix. Defaults to "/".
|
|
21
|
+
* base: "/o-mweb",
|
|
22
|
+
*
|
|
23
|
+
* // HMR WebSocket tuning when the browser connects via the proxy
|
|
24
|
+
* hmr: {
|
|
25
|
+
* clientPort: 443, // port the browser uses (proxy's SSL port)
|
|
26
|
+
* path: "__vite_hmr" // WebSocket pathname (relative to base)
|
|
27
|
+
* },
|
|
28
|
+
*
|
|
29
|
+
* // Vite 6 rejects requests whose Host header isn't localhost/127.0.0.1.
|
|
30
|
+
* // Add your proxy hostname here so the dev server accepts them.
|
|
31
|
+
* allowedHosts: ["local.odinweb.1mg.com"],
|
|
32
|
+
* },
|
|
33
|
+
* }
|
|
34
|
+
*/
|
|
35
|
+
devServer: {},
|
|
8
36
|
}
|
|
9
37
|
|
|
10
38
|
export async function loadCustomViteConfig() {
|
|
@@ -308,9 +308,37 @@ function isValidExportName(name) {
|
|
|
308
308
|
// Loader-scoped require for discovering CJS exports
|
|
309
309
|
const loaderRequire = createRequire(import.meta.url)
|
|
310
310
|
|
|
311
|
-
const CJS_PATTERN = /\b(module\.exports\b|exports\.\w+\s*=)/
|
|
312
311
|
const REQUIRE_CALL_PATTERN = /\brequire\s*\(/
|
|
313
312
|
|
|
313
|
+
/**
|
|
314
|
+
* Decide whether a source file is really CommonJS by parsing it, not by
|
|
315
|
+
* regex-matching the raw text.
|
|
316
|
+
*
|
|
317
|
+
* A regex like /module\.exports\b/ cannot tell code from text, so any file that
|
|
318
|
+
* merely *mentions* module.exports inside a string, comment or template literal
|
|
319
|
+
* was misclassified as CJS. When such a file was real ESM with its own
|
|
320
|
+
* `export default`, the CJS shim appended a second one and Node failed the
|
|
321
|
+
* module with "Duplicate export of 'default'". Docs sites hit this constantly:
|
|
322
|
+
* a Markdown page documenting a CJS config compiles into a string literal in
|
|
323
|
+
* the bundled ESM output. Do not reintroduce source-regex detection.
|
|
324
|
+
*
|
|
325
|
+
* esbuild parses properly and lowers CJS itself, so a file it wraps in its
|
|
326
|
+
* __commonJS helper had no ESM syntax of its own — that is the CJS signal.
|
|
327
|
+
*/
|
|
328
|
+
function isCjsSource(source, filePath) {
|
|
329
|
+
try {
|
|
330
|
+
const { code } = transformSync(source, {
|
|
331
|
+
loader: "jsx",
|
|
332
|
+
format: "esm",
|
|
333
|
+
sourcefile: filePath,
|
|
334
|
+
target: "node20",
|
|
335
|
+
})
|
|
336
|
+
return /\bvar __commonJS =/.test(code)
|
|
337
|
+
} catch {
|
|
338
|
+
return false
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
314
342
|
// Shim __dirname and __filename for ESM app files (these globals only exist in CJS)
|
|
315
343
|
const createDirnameShim = (source) => {
|
|
316
344
|
const hasFilename = /\b(?:const|let|var)\s+__filename\b/.test(source)
|
|
@@ -396,7 +424,7 @@ export async function load(url, context, defaultLoad) {
|
|
|
396
424
|
// We can't use createRequire here because the file lives in a "type":"module"
|
|
397
425
|
// package — Node's CJS require refuses to load it. Instead, we wrap the
|
|
398
426
|
// source inline so that `module.exports` becomes the default export.
|
|
399
|
-
if (
|
|
427
|
+
if (isCjsSource(source, filePath)) {
|
|
400
428
|
const shimmed = [
|
|
401
429
|
createRequireShim(source),
|
|
402
430
|
createDirnameShim(source),
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// Pure resolvers for the Vite dev-server config derived from an app's
|
|
2
|
+
// `buildConfig.devServer`. Side-effect free so the base precedence and the fs
|
|
3
|
+
// allowlist merge can be unit-tested without spinning up Vite.
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Vite `base` for the dev server. `buildConfig.devServer.base` is the single
|
|
7
|
+
* source of truth (default "/"). Normalized to a leading slash and no trailing
|
|
8
|
+
* slash so it doubles as a URL prefix for the SSR renderer.
|
|
9
|
+
*/
|
|
10
|
+
export function resolveDevBase(devServer = {}) {
|
|
11
|
+
const base = devServer?.base
|
|
12
|
+
if (!base || base === "/") return "/"
|
|
13
|
+
return `/${String(base).trim().replace(/^\/+|\/+$/g, "")}`
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* A resolved base as a URL prefix. Root ("/") becomes "" so
|
|
18
|
+
* `${prefix}/client/index.js` stays single-slashed.
|
|
19
|
+
*/
|
|
20
|
+
export function toMountPathPrefix(base) {
|
|
21
|
+
return !base || base === "/" ? "" : String(base).replace(/\/+$/, "")
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* fs.allow for the dev server. Framework paths are always present; apps may
|
|
26
|
+
* extend the list via `devServer.fs.allow` but cannot replace it or relax other
|
|
27
|
+
* fs guards (e.g. `strict`), so buildConfig can't widen filesystem exposure.
|
|
28
|
+
*/
|
|
29
|
+
export function resolveDevFsAllow(devFs, frameworkPaths) {
|
|
30
|
+
const extra = Array.isArray(devFs?.allow) ? devFs.allow : []
|
|
31
|
+
return [...frameworkPaths, ...extra]
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Vite `server` block from `buildConfig.devServer`. Passthrough keys are spread
|
|
36
|
+
* first so the framework-owned `hmr` and `fs` always win.
|
|
37
|
+
*/
|
|
38
|
+
export function buildDevServer(devServer = {}, { frameworkPaths, isProduction = false } = {}) {
|
|
39
|
+
const { base, hmr, fs, ...serverOverrides } = devServer
|
|
40
|
+
return {
|
|
41
|
+
...serverOverrides,
|
|
42
|
+
hmr: isProduction ? false : (hmr ?? true),
|
|
43
|
+
fs: { allow: resolveDevFsAllow(fs, frameworkPaths) },
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import loadEnvironmentVariables from "../scripts/loadEnvironmentVariables.js"
|
|
2
2
|
loadEnvironmentVariables()
|
|
3
3
|
import { defineConfig } from "vite"
|
|
4
|
-
import baseConfig,
|
|
4
|
+
import { sharedViteConfig as baseConfig, getClientEnvVariables } from "./vite.config.js"
|
|
5
5
|
import path from "path"
|
|
6
6
|
import { manifestCategorizationPlugin } from "./manifest-categorization-plugin.js"
|
|
7
7
|
import { injectCacheKeyPlugin } from "./inject-cache-key-plugin.js"
|
package/dist/vite/vite.config.js
CHANGED
|
@@ -2,6 +2,8 @@ import { defineConfig, transformWithEsbuild } from "vite"
|
|
|
2
2
|
import react from "@vitejs/plugin-react"
|
|
3
3
|
import svgr from "vite-plugin-svgr"
|
|
4
4
|
import { builtinModules, createRequire } from "node:module"
|
|
5
|
+
import { loadCustomViteConfig } from "./loadCustomViteConfig.js"
|
|
6
|
+
import { resolveDevBase, buildDevServer } from "./resolveDevServerConfig.js"
|
|
5
7
|
|
|
6
8
|
/**
|
|
7
9
|
* Treat all .scss imports as CSS modules (webpack compat).
|
|
@@ -286,6 +288,7 @@ const browserOptimizeDeps = [
|
|
|
286
288
|
"react-dom",
|
|
287
289
|
"react-dom/client",
|
|
288
290
|
"react-redux",
|
|
291
|
+
"react-router",
|
|
289
292
|
"react-router-dom",
|
|
290
293
|
"redux",
|
|
291
294
|
"redux-thunk",
|
|
@@ -362,7 +365,10 @@ export const isNodeOnlyExternal = (id) =>
|
|
|
362
365
|
id === "@grpc/grpc-js" ||
|
|
363
366
|
id.startsWith("@opentelemetry/")
|
|
364
367
|
|
|
365
|
-
|
|
368
|
+
// ─── Shared config ────────────────────────────────────────────────────────────
|
|
369
|
+
// Exported for the production build (vite.config.client.js spreads this in).
|
|
370
|
+
// Does NOT include `base` or `server` — those differ between dev and prod.
|
|
371
|
+
export const sharedViteConfig = {
|
|
366
372
|
// Parallel `vite build` (SSR + client) must use separate dirs or Vite will block on shared `node_modules/.vite`.
|
|
367
373
|
cacheDir: path.join(
|
|
368
374
|
process.env.src_path,
|
|
@@ -391,6 +397,7 @@ export default defineConfig({
|
|
|
391
397
|
exclude: [
|
|
392
398
|
"react",
|
|
393
399
|
"react-dom",
|
|
400
|
+
"react-router",
|
|
394
401
|
"react-router-dom",
|
|
395
402
|
"@tata1mg/router",
|
|
396
403
|
// "@tata1mg/slowoi-react",
|
|
@@ -422,8 +429,15 @@ export default defineConfig({
|
|
|
422
429
|
resolve: {
|
|
423
430
|
alias: alias(),
|
|
424
431
|
// Ensure only one copy of React-related packages is used (prevents
|
|
425
|
-
// duplicate instances from hoisted/linked packages in monorepos)
|
|
426
|
-
|
|
432
|
+
// duplicate instances from hoisted/linked packages in monorepos).
|
|
433
|
+
// NOTE: react-router deliberately excluded from dedupe. Consumer apps
|
|
434
|
+
// that still have an older react-router (e.g. v6, hoisted transitively
|
|
435
|
+
// through another dependency) at their own node_modules root would
|
|
436
|
+
// have that copy forced onto catalyst-core's v7 import too, silently
|
|
437
|
+
// resolving to the wrong package version (wrong export list, e.g.
|
|
438
|
+
// missing StaticRouter/Link) instead of an error. See tata1mg/1mg_web
|
|
439
|
+
// fix/react-router-v7-upgrade-staging for the confirmed repro.
|
|
440
|
+
dedupe: ["react", "react-dom", "react-router-dom"],
|
|
427
441
|
},
|
|
428
442
|
define: {
|
|
429
443
|
...getClientEnvVariables(),
|
|
@@ -488,17 +502,34 @@ export default defineConfig({
|
|
|
488
502
|
"**/*.woff2",
|
|
489
503
|
],
|
|
490
504
|
|
|
491
|
-
// Server configuration
|
|
492
|
-
server: {
|
|
493
|
-
hmr: !isProduction,
|
|
494
|
-
fs: {
|
|
495
|
-
allow: [process.env.src_path, __dirname],
|
|
496
|
-
},
|
|
497
|
-
},
|
|
498
|
-
|
|
499
505
|
// Preview configuration for production preview
|
|
500
506
|
preview: {
|
|
501
507
|
port: process.env.NODE_SERVER_PORT ? parseInt(process.env.NODE_SERVER_PORT) : 3005,
|
|
502
508
|
host: process.env.NODE_SERVER_HOSTNAME || "localhost",
|
|
503
509
|
},
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
// ─── Dev server config ────────────────────────────────────────────────────────
|
|
513
|
+
// Async factory so it can read the app's buildConfig.devServer for `base`, HMR
|
|
514
|
+
// and other Vite server options. `base` comes solely from devServer.base; the
|
|
515
|
+
// resolved value is propagated to the SSR renderer by server/expressServer.js.
|
|
516
|
+
// The production build (vite.config.client.js) ignores this and spreads
|
|
517
|
+
// sharedViteConfig directly.
|
|
518
|
+
export default defineConfig(async () => {
|
|
519
|
+
const { devServer = {}, ssrPlugins = [] } = await loadCustomViteConfig()
|
|
520
|
+
|
|
521
|
+
return {
|
|
522
|
+
...sharedViteConfig,
|
|
523
|
+
base: resolveDevBase(devServer),
|
|
524
|
+
server: buildDevServer(devServer, {
|
|
525
|
+
frameworkPaths: [process.env.src_path, __dirname],
|
|
526
|
+
isProduction,
|
|
527
|
+
}),
|
|
528
|
+
// Apply the app's SSR plugins in dev too — the production build already
|
|
529
|
+
// does this in vite.config.server.js. This lets buildConfig.ssrPlugins
|
|
530
|
+
// inject SSR-only config (e.g. a plugin whose `config()` hook adds
|
|
531
|
+
// ssr.noExternal to bundle React-consuming deps so dev SSR shares the
|
|
532
|
+
// app's single React instance) without hardcoding anything in the framework.
|
|
533
|
+
plugins: [...(sharedViteConfig.plugins || []), ...ssrPlugins],
|
|
534
|
+
}
|
|
504
535
|
})
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import loadEnvironmentVariables from "../scripts/loadEnvironmentVariables.js"
|
|
2
2
|
loadEnvironmentVariables()
|
|
3
3
|
import { defineConfig } from "vite"
|
|
4
|
-
|
|
4
|
+
// Spread the static shared config (a plain object), NOT the default export —
|
|
5
|
+
// the default export is now an async dev-server factory function, and spreading
|
|
6
|
+
// a function would silently drop cacheDir, resolve aliases, css, plugins and
|
|
7
|
+
// build from the production SSR bundle. Mirrors vite.config.client.js.
|
|
8
|
+
import { sharedViteConfig as baseConfig, getClientEnvVariables, isNodeOnlyExternal } from "./vite.config.js"
|
|
5
9
|
import path from "path"
|
|
6
10
|
import { fileURLToPath } from "url"
|
|
7
11
|
import { dirname } from "path"
|
|
@@ -3,7 +3,7 @@ import { HelmetProvider, Helmet } from "react-helmet-async"
|
|
|
3
3
|
import { OneMgRouterContext } from "../context.jsx"
|
|
4
4
|
import { useRouterData } from "./RouterDataProvider.jsx"
|
|
5
5
|
import { deleteHeadTagsByDataAttribute, getMetaData } from "../utils/metaDataUtils.jsx"
|
|
6
|
-
import { useLocation } from "react-router
|
|
6
|
+
import { useLocation } from "react-router"
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* @description renders meta tags component assigned to setMetaData
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
useSearchParams,
|
|
8
8
|
UNSAFE_RouteContext,
|
|
9
9
|
matchRoutes,
|
|
10
|
-
} from "react-router
|
|
10
|
+
} from "react-router"
|
|
11
11
|
import { OneMgRouterContext } from "../context.jsx"
|
|
12
12
|
// import sanitizeHtml from "sanitize-html"
|
|
13
13
|
|
|
@@ -41,15 +41,15 @@ const INITIAL_DATA_STATE = {
|
|
|
41
41
|
/**
|
|
42
42
|
* @typedef RouterFetcherProps
|
|
43
43
|
* @property {any} route route object
|
|
44
|
-
* @property {import("react-router
|
|
45
|
-
* @property {import("react-router
|
|
44
|
+
* @property {import("react-router").Location} location the current location object, which represents the current URL in web browsers.
|
|
45
|
+
* @property {import("react-router").Params} params object of key/value pairs of the dynamic params from the current URL that were matched by the route path.
|
|
46
46
|
* @property {URLSearchParams} searchParams search parameters via URLSearchParams interface.
|
|
47
|
-
* @property {import("react-router
|
|
47
|
+
* @property {import("react-router").NavigateFunction} navigate function to navigate to other pages based on response.
|
|
48
48
|
*/
|
|
49
49
|
|
|
50
50
|
/**
|
|
51
51
|
* @typedef ServerFetchDataProps
|
|
52
|
-
* @property {import("react-router
|
|
52
|
+
* @property {import("react-router").RouteObject[]} routes routes Array
|
|
53
53
|
* @property {string} url current url
|
|
54
54
|
* @property {import("express").Request} req Express request object
|
|
55
55
|
*/
|
|
@@ -139,7 +139,7 @@ const fetchRouteData = async (routerProps, fetcherArgs, refetchArgs) => {
|
|
|
139
139
|
/**
|
|
140
140
|
*
|
|
141
141
|
* @param {RouteContextObject} routeContext route context object
|
|
142
|
-
* @returns {import('react-router
|
|
142
|
+
* @returns {import('react-router').RouteMatch[]} Array of matched routes
|
|
143
143
|
*/
|
|
144
144
|
const getMatchedRoutes = ({ matches, outlet }) => {
|
|
145
145
|
if (outlet) {
|
|
@@ -150,7 +150,7 @@ const getMatchedRoutes = ({ matches, outlet }) => {
|
|
|
150
150
|
|
|
151
151
|
/**
|
|
152
152
|
* Generates route key for given route using pathname and query params
|
|
153
|
-
* @param {import('react-router
|
|
153
|
+
* @param {import('react-router').RouteMatch} match Router Match Object
|
|
154
154
|
* @param {string} searchParamsString Query params string
|
|
155
155
|
* @returns {string} routerKey
|
|
156
156
|
*/
|
|
@@ -190,7 +190,7 @@ export const RouterDataProvider = ({ children, initialState, fetcherArgs = {}, c
|
|
|
190
190
|
|
|
191
191
|
/**
|
|
192
192
|
* @description HOF which returns a function to refetch the route data
|
|
193
|
-
* @param {import("react-router
|
|
193
|
+
* @param {import("react-router").RouteObject} route
|
|
194
194
|
* @param {string} routeKey
|
|
195
195
|
* @returns
|
|
196
196
|
*/
|
|
@@ -214,7 +214,7 @@ export const RouterDataProvider = ({ children, initialState, fetcherArgs = {}, c
|
|
|
214
214
|
|
|
215
215
|
/**
|
|
216
216
|
* @description HOF which returns a function to clear the route data immediately or after given time in ms
|
|
217
|
-
* @param {import("react-router
|
|
217
|
+
* @param {import("react-router").RouteObject} route
|
|
218
218
|
* @param {string} routeKey
|
|
219
219
|
* @returns
|
|
220
220
|
*/
|
|
@@ -235,7 +235,7 @@ export const RouterDataProvider = ({ children, initialState, fetcherArgs = {}, c
|
|
|
235
235
|
|
|
236
236
|
/**
|
|
237
237
|
* @description Check the config for refetching the data
|
|
238
|
-
* @param {import('react-router
|
|
238
|
+
* @param {import('react-router').RouteObject} route
|
|
239
239
|
* @returns {boolean}
|
|
240
240
|
*/
|
|
241
241
|
const shouldFetch = (route) => {
|
|
@@ -2,7 +2,7 @@ import { createContext } from "react"
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* @typedef oneMgRouterContext
|
|
5
|
-
* @property {import("react-router
|
|
5
|
+
* @property {import("react-router").RouteMatch<string, import("react-router").RouteObject>[]=} matchedRoutes
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
/**
|
|
@@ -47,11 +47,11 @@ export type RouterFetcherProps = {
|
|
|
47
47
|
/**
|
|
48
48
|
* the current location object, which represents the current URL in web browsers.
|
|
49
49
|
*/
|
|
50
|
-
location: import("react-router
|
|
50
|
+
location: import("react-router").Location
|
|
51
51
|
/**
|
|
52
52
|
* object of key/value pairs of the dynamic params from the current URL that were matched by the route path.
|
|
53
53
|
*/
|
|
54
|
-
params: import("react-router
|
|
54
|
+
params: import("react-router").Params
|
|
55
55
|
/**
|
|
56
56
|
* search parameters via URLSearchParams interface.
|
|
57
57
|
*/
|
|
@@ -59,13 +59,13 @@ export type RouterFetcherProps = {
|
|
|
59
59
|
/**
|
|
60
60
|
* function to navigate to other pages based on response.
|
|
61
61
|
*/
|
|
62
|
-
navigate: import("react-router
|
|
62
|
+
navigate: import("react-router").NavigateFunction
|
|
63
63
|
}
|
|
64
64
|
export type ServerFetchDataProps = {
|
|
65
65
|
/**
|
|
66
66
|
* routes Array
|
|
67
67
|
*/
|
|
68
|
-
routes: import("react-router
|
|
68
|
+
routes: import("react-router").RouteObject[]
|
|
69
69
|
/**
|
|
70
70
|
* current url
|
|
71
71
|
*/
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @typedef oneMgRouterContext
|
|
3
|
-
* @property {import("react-router
|
|
3
|
+
* @property {import("react-router").RouteMatch<string, import("react-router").RouteObject>[]=} matchedRoutes
|
|
4
4
|
*/
|
|
5
5
|
/**
|
|
6
6
|
* @description This is internal context made for sharing data among router components
|
|
@@ -9,6 +9,6 @@
|
|
|
9
9
|
export const OneMgRouterContext: import("react").Context<oneMgRouterContext>
|
|
10
10
|
export type oneMgRouterContext = {
|
|
11
11
|
matchedRoutes?:
|
|
12
|
-
| import("react-router
|
|
12
|
+
| import("react-router").RouteMatch<string, import("react-router").RouteObject>[]
|
|
13
13
|
| undefined
|
|
14
14
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export function useNavigateWithTransition(): import("react-router
|
|
1
|
+
export function useNavigateWithTransition(): import("react-router").NavigateFunction
|
|
@@ -22,3 +22,19 @@ export function deleteHeadTagsByDataAttribute(attributeName: string, attributeVa
|
|
|
22
22
|
* @returns {Array<JSX.Element>} - List of all meta tags for the matched location.
|
|
23
23
|
*/
|
|
24
24
|
export function getMetaData(matchedRoutes: Array<Match>, routeData: Object): Array<JSX.Element>
|
|
25
|
+
/**
|
|
26
|
+
* Static function defined on a route component that returns metadata elements
|
|
27
|
+
* to be rendered in the document head for that route.
|
|
28
|
+
* Must return an array of JSX elements (e.g. <title>, <meta>), NOT a plain
|
|
29
|
+
* object. Returning an object fails silently: no error is thrown, but the
|
|
30
|
+
* tags will not appear in the rendered <head>.
|
|
31
|
+
*
|
|
32
|
+
* @param {any} fetcherData - Data returned from the route's fetcher (serverFetcher/clientFetcher).
|
|
33
|
+
* @returns {Array<JSX.Element>} - Array of JSX head elements (title, meta, link, etc).
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* HomePage.setMetaData = (apiResponse) => [
|
|
37
|
+
* <title key="title">{apiResponse?.title || "Home"}</title>,
|
|
38
|
+
* ];
|
|
39
|
+
*/
|
|
40
|
+
export type SetMetaData = (fetcherData: any) => Array<JSX.Element>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "catalyst-core",
|
|
3
|
-
"version": "0.3.0-beta.
|
|
3
|
+
"version": "0.3.0-beta.5",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.jsx",
|
|
6
6
|
"description": "Web framework that provides great performance out of the box",
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
"scripts": {
|
|
31
31
|
"lint": "eslint .",
|
|
32
32
|
"lint-staged": "lint-staged",
|
|
33
|
+
"test:unit": "node --test \"test/**/*.test.js\"",
|
|
33
34
|
"prettify": "prettier . --write",
|
|
34
35
|
"prepare": "node ../../scripts/safe-rimraf.js ./dist && cp -r ./src ./dist && babel src/native --out-dir dist/native --config-file ./babel.native.config.cjs --ignore '**/.build/**' && babel src/sentry.js --out-file dist/sentry.cjs --config-file ./babel.native.config.cjs",
|
|
35
36
|
"prepublishOnly": "npm run prepare",
|
|
@@ -49,7 +50,7 @@
|
|
|
49
50
|
"react-helmet-async": "^1.3.0",
|
|
50
51
|
"react-redux": "^9.2.0",
|
|
51
52
|
"react-refresh": "^0.14.0",
|
|
52
|
-
"react-router
|
|
53
|
+
"react-router": "^7.18.2",
|
|
53
54
|
"redux": "^4.2.1",
|
|
54
55
|
"sanitize-html": "^2.17.6",
|
|
55
56
|
"sass": "^1.89.2",
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { test } from "node:test"
|
|
2
|
+
import assert from "node:assert/strict"
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
resolveDevBase,
|
|
6
|
+
toMountPathPrefix,
|
|
7
|
+
resolveDevFsAllow,
|
|
8
|
+
buildDevServer,
|
|
9
|
+
} from "../../src/vite/resolveDevServerConfig.js"
|
|
10
|
+
|
|
11
|
+
// ─── resolveDevBase ───────────────────────────────────────────────────────────
|
|
12
|
+
|
|
13
|
+
test("resolveDevBase: resolves from devServer.base", () => {
|
|
14
|
+
assert.equal(resolveDevBase({ base: "/o-mweb" }), "/o-mweb")
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
test("resolveDevBase: defaults to '/' when base is unset", () => {
|
|
18
|
+
assert.equal(resolveDevBase({}), "/")
|
|
19
|
+
assert.equal(resolveDevBase(undefined), "/")
|
|
20
|
+
assert.equal(resolveDevBase({ base: "/" }), "/")
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
test("resolveDevBase: normalizes to a leading slash, no trailing slash", () => {
|
|
24
|
+
assert.equal(resolveDevBase({ base: "o-mweb" }), "/o-mweb")
|
|
25
|
+
assert.equal(resolveDevBase({ base: "/o-mweb/" }), "/o-mweb")
|
|
26
|
+
assert.equal(resolveDevBase({ base: " /o-mweb// " }), "/o-mweb")
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
// ─── toMountPathPrefix ────────────────────────────────────────────────────────
|
|
30
|
+
|
|
31
|
+
test("toMountPathPrefix: root maps to empty string for clean URL joins", () => {
|
|
32
|
+
assert.equal(toMountPathPrefix("/"), "")
|
|
33
|
+
assert.equal(toMountPathPrefix(""), "")
|
|
34
|
+
assert.equal(toMountPathPrefix(undefined), "")
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
test("toMountPathPrefix: trims Vite's trailing slash", () => {
|
|
38
|
+
assert.equal(toMountPathPrefix("/o-mweb/"), "/o-mweb")
|
|
39
|
+
assert.equal(toMountPathPrefix("/o-mweb"), "/o-mweb")
|
|
40
|
+
// The joined result stays single-slashed.
|
|
41
|
+
assert.equal(`${toMountPathPrefix("/o-mweb/")}/client/index.js`, "/o-mweb/client/index.js")
|
|
42
|
+
assert.equal(`${toMountPathPrefix("/")}/client/index.js`, "/client/index.js")
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
// ─── resolveDevFsAllow: security-sensitive allowlist merge ─────────────────────
|
|
46
|
+
|
|
47
|
+
const FRAMEWORK = ["/app/src", "/framework/dist/vite"]
|
|
48
|
+
|
|
49
|
+
test("resolveDevFsAllow: framework paths always present, none provided", () => {
|
|
50
|
+
assert.deepEqual(resolveDevFsAllow(undefined, FRAMEWORK), FRAMEWORK)
|
|
51
|
+
assert.deepEqual(resolveDevFsAllow({}, FRAMEWORK), FRAMEWORK)
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
test("resolveDevFsAllow: apps may EXTEND but never drop framework paths", () => {
|
|
55
|
+
const result = resolveDevFsAllow({ allow: ["/extra/pkg"] }, FRAMEWORK)
|
|
56
|
+
assert.deepEqual(result, ["/app/src", "/framework/dist/vite", "/extra/pkg"])
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
test("resolveDevFsAllow: non-array allow is ignored (no widening via bad input)", () => {
|
|
60
|
+
assert.deepEqual(resolveDevFsAllow({ allow: "/" }, FRAMEWORK), FRAMEWORK)
|
|
61
|
+
assert.deepEqual(resolveDevFsAllow({ allow: null }, FRAMEWORK), FRAMEWORK)
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
// ─── buildDevServer: precedence + security ────────────────────────────────────
|
|
65
|
+
|
|
66
|
+
test("buildDevServer: hmr defaults to true in dev", () => {
|
|
67
|
+
const server = buildDevServer({}, { frameworkPaths: FRAMEWORK, isProduction: false })
|
|
68
|
+
assert.equal(server.hmr, true)
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
test("buildDevServer: hmr is false in production", () => {
|
|
72
|
+
const server = buildDevServer({ hmr: { clientPort: 443 } }, { frameworkPaths: FRAMEWORK, isProduction: true })
|
|
73
|
+
assert.equal(server.hmr, false)
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
test("buildDevServer: app hmr overrides are honored in dev", () => {
|
|
77
|
+
const hmr = { clientPort: 443, path: "__vite_hmr" }
|
|
78
|
+
const server = buildDevServer({ hmr }, { frameworkPaths: FRAMEWORK, isProduction: false })
|
|
79
|
+
assert.deepEqual(server.hmr, hmr)
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
test("buildDevServer: passthrough keys (proxy/allowedHosts) are forwarded", () => {
|
|
83
|
+
const server = buildDevServer(
|
|
84
|
+
{ allowedHosts: ["local.example.com"], proxy: { "/api": "http://localhost:9000" } },
|
|
85
|
+
{ frameworkPaths: FRAMEWORK, isProduction: false }
|
|
86
|
+
)
|
|
87
|
+
assert.deepEqual(server.allowedHosts, ["local.example.com"])
|
|
88
|
+
assert.deepEqual(server.proxy, { "/api": "http://localhost:9000" })
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
test("buildDevServer: app fs cannot replace the allowlist, only extend it", () => {
|
|
92
|
+
const server = buildDevServer(
|
|
93
|
+
{ fs: { allow: ["/extra"] } },
|
|
94
|
+
{ frameworkPaths: FRAMEWORK, isProduction: false }
|
|
95
|
+
)
|
|
96
|
+
assert.deepEqual(server.fs.allow, ["/app/src", "/framework/dist/vite", "/extra"])
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
test("buildDevServer: app cannot relax fs.strict or clobber fs via overrides", () => {
|
|
100
|
+
const server = buildDevServer(
|
|
101
|
+
{ fs: { allow: ["/"], strict: false } },
|
|
102
|
+
{ frameworkPaths: FRAMEWORK, isProduction: false }
|
|
103
|
+
)
|
|
104
|
+
// strict is dropped entirely (framework keeps Vite's secure default), and
|
|
105
|
+
// the framework paths remain — "/" is merely appended, not a replacement.
|
|
106
|
+
assert.equal(server.fs.strict, undefined)
|
|
107
|
+
assert.ok(server.fs.allow.includes("/app/src"))
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
test("buildDevServer: 'base' is not leaked into the server block", () => {
|
|
111
|
+
const server = buildDevServer({ base: "/o-mweb" }, { frameworkPaths: FRAMEWORK, isProduction: false })
|
|
112
|
+
assert.equal(server.base, undefined)
|
|
113
|
+
})
|