fix: routerBasename setting (#4891)

This commit is contained in:
Bill Wallace authored and GitHub committed 2025-03-27 16:47:16 -04:00
1 parent c368d763b0
commit 459b0d223a
40 files changed
+299 -229

No files matched your search

+6
View File
@@ -153,6 +153,12 @@ module.exports = (env, argv) => {
},
proxy: {
'/dicomweb': 'http://localhost:5000',
'/dicom-microscopy-viewer': {
target: 'http://localhost:3000',
pathRewrite: {
'^/dicom-microscopy-viewer': `/${PUBLIC_URL}/dicom-microscopy-viewer`,
},
},
},
static: [
{
@@ -1,7 +1,6 @@
const pluginConfig = require('../pluginConfig.json');
const fs = require('fs');
const os = require('os');
const glob = require('glob');
const autogenerationDisclaimer = `
// THIS FILE IS AUTOGENERATED AS PART OF THE EXTENSION AND MODE PLUGIN PROCESS.
@@ -9,6 +8,12 @@ const autogenerationDisclaimer = `
const extractName = val => (typeof val === 'string' ? val : val.packageName);
const publicURL = process.env.PUBLIC_URL || '/';
function isAbsolutePath(path) {
return path.startsWith('http') || path.startsWith('/');
}
function constructLines(input, categoryName) {
let pluginCount = 0;
@@ -72,7 +77,7 @@ function getRuntimeLoadModesExtensions(modules) {
if (module.importPath) {
dynamicLoad.push(
` if( module==="${packageName}") {`,
` const imported = await window.browserImportFunction('${module.importPath}');`,
` const imported = await window.browserImportFunction('${isAbsolutePath(module.importPath) ? '' : publicURL}${module.importPath}');`,
' return ' +
(module.globalName
? `window["${module.globalName}"];`
+1 -1
View File
@@ -1,7 +1,7 @@
/** @type {AppTypes.Config} */
window.config = {
routerBasename: '/',
routerBasename: null,
extensions: [],
modes: [],
showStudyList: true,
+1 -1
View File
@@ -1,6 +1,6 @@
/** @type {AppTypes.Config} */
window.config = {
routerBasename: '/',
routerBasename: null,
extensions: [],
modes: ['@ohif/mode-test'],
showStudyList: true,
+1 -1
View File
@@ -2,7 +2,7 @@
window.config = {
name: 'config/default.js',
routerBasename: '/',
routerBasename: null,
// whiteLabeling: {},
extensions: [],
modes: [],
+1 -1
View File
@@ -1,6 +1,6 @@
/** @type {AppTypes.Config} */
window.config = {
routerBasename: '/',
routerBasename: null,
// whiteLabeling: {},
extensions: [],
modes: [],
+1 -1
View File
@@ -1,6 +1,6 @@
/** @type {AppTypes.Config} */
window.config = {
routerBasename: '/',
routerBasename: null,
modes: [],
extensions: [],
showStudyList: true,
@@ -1,6 +1,6 @@
/** @type {AppTypes.Config} */
window.config = {
routerBasename: '/',
routerBasename: null,
showStudyList: true,
extensions: [],
modes: [],
@@ -1,6 +1,6 @@
/** @type {AppTypes.Config} */
window.config = {
routerBasename: '/',
routerBasename: null,
showStudyList: true,
extensions: [],
modes: [],
@@ -1,6 +1,6 @@
/** @type {AppTypes.Config} */
window.config = {
routerBasename: '/',
routerBasename: null,
extensions: [],
modes: [],
showStudyList: true,
@@ -1,6 +1,6 @@
/** @type {AppTypes.Config} */
window.config = {
routerBasename: '/',
routerBasename: null,
extensions: [],
modes: [],
showStudyList: true,
@@ -1,6 +1,6 @@
/** @type {AppTypes.Config} */
window.config = {
routerBasename: '/',
routerBasename: null,
showStudyList: true,
extensions: [],
modes: [],
@@ -1,6 +1,6 @@
/** @type {AppTypes.Config} */
window.config = {
routerBasename: '/',
routerBasename: null,
showStudyList: true,
extensions: [],
modes: [],
+1 -1
View File
@@ -17,7 +17,7 @@ if (window.location.search === '?today') {
/** @type {AppTypes.Config} */
window.config = {
routerBasename: '/',
routerBasename: null,
extensions: [],
modes: ['@ohif/mode-test'],
showStudyList: true,
+1 -1
View File
@@ -1,6 +1,6 @@
/** @type {AppTypes.Config} */
window.config = {
routerBasename: '/',
routerBasename: null,
enableGoogleCloudAdapter: false,
// below flag is for performance reasons, but it might not work for all servers
showWarningMessageForCrossOrigin: true,
+1 -1
View File
@@ -1,6 +1,6 @@
/** @type {AppTypes.Config} */
window.config = {
routerBasename: '/',
routerBasename: null,
enableGoogleCloudAdapter: true,
// below flag is for performance reasons, but it might not work for all servers
showWarningMessageForCrossOrigin: true,
+1 -1
View File
@@ -2,7 +2,7 @@
window.config = {
name: 'config/kheops.js',
routerBasename: '/',
routerBasename: null,
extensions: [],
modes: ['@ohif/mode-test'],
customizationService: {},
+1 -1
View File
@@ -1,6 +1,6 @@
/** @type {AppTypes.Config} */
window.config = {
routerBasename: '/',
routerBasename: null,
showStudyList: true,
extensions: [],
modes: [],
+1 -1
View File
@@ -1,6 +1,6 @@
/** @type {AppTypes.Config} */
window.config = {
routerBasename: '/',
routerBasename: null,
extensions: [],
modes: [],
showStudyList: true,
+1 -1
View File
@@ -1,6 +1,6 @@
/** @type {AppTypes.Config} */
window.config = {
routerBasename: '/',
routerBasename: null,
customizationService: ['@ohif/extension-default.customizationModule.helloPage'],
extensions: [],
modes: [],
+1 -1
View File
@@ -3,7 +3,7 @@ window.config = {
// Activate the new HP mode....
isNewHP: true,
routerBasename: '/',
routerBasename: null,
customizationService: [
'@ohif/extension-default.customizationModule.datasources',
{
+1 -1
View File
@@ -1,7 +1,7 @@
/** @type {AppTypes.Config} */
window.config = {
routerBasename: '/',
routerBasename: null,
extensions: [],
modes: [],
showStudyList: true,
@@ -1,5 +1,5 @@
window.config = {
routerBasename: '/',
routerBasename: null,
showStudyList: true,
// below flag is for performance reasons, but it might not work for all servers
showWarningMessageForCrossOrigin: true,
+1 -1
View File
@@ -53,7 +53,7 @@ function App({
* Hosted at: https://ohif.org/where-i-host-the/viewer/
* Value: `/where-i-host-the/viewer/`
* */
routerBaseName: '/',
routerBasename: '/',
/**
*
*/
+2 -1
View File
@@ -17,15 +17,16 @@ import React from 'react';
*/
import { modes as defaultModes, extensions as defaultExtensions } from './pluginImports';
import loadDynamicConfig from './loadDynamicConfig';
import { publicUrl } from './utils/publicUrl';
export { history } from './utils/history';
export { preserveQueryParameters, preserveQueryStrings } from './utils/preserveQueryParameters';
export { publicUrl } from './utils/publicUrl';
loadDynamicConfig(window.config).then(config_json => {
// Reset Dynamic config if defined
if (config_json !== null) {
window.config = config_json;
}
window.config.routerBasename ||= publicUrl;
/**
* Combine our appConfiguration with installed extensions and modes.
@@ -11,7 +11,7 @@ import filtersMeta from './filtersMeta.js';
import { useAppConfig } from '@state';
import { useDebounce, useSearchParams } from '@hooks';
import { utils } from '@ohif/core';
import publicUrl from '../../utils/publicUrl';
import { routerBasename } from '../../utils/publicUrl';
import {
StudyListExpandedRow,
@@ -209,7 +209,7 @@ function WorkList({
skipEmptyString: true,
});
navigate({
pathname: publicUrl,
pathname: '/',
search: search ? `?${search}` : undefined,
});
// eslint-disable-next-line react-hooks/exhaustive-deps
@@ -417,7 +417,7 @@ function WorkList({
<Link
className={isValidMode ? '' : 'cursor-not-allowed'}
key={i}
to={`${publicUrl}${mode.routeName}${dataPath || ''}?${query.toString()}`}
to={`${mode.routeName}${dataPath || ''}?${query.toString()}`}
onClick={event => {
// In case any event bubbles up for an invalid mode, prevent the navigation.
// For example, the event bubbles up when the icon embedded in the disabled button is clicked.
+2 -3
View File
@@ -1,6 +1,5 @@
import React from 'react';
import ModeRoute from '@routes/Mode';
import publicUrl from '../utils/publicUrl';
/*
Routes uniquely define an entry point to:
@@ -43,7 +42,7 @@ export default function buildModeRoutes({
modes.forEach(mode => {
// todo: for each route. add route to path.
dataSourceNames.forEach(dataSourceName => {
const path = `${publicUrl}${mode.routeName}/${dataSourceName}`;
const path = `${mode.routeName}/${dataSourceName}`;
// TODO move up.
const children = () => (
@@ -66,7 +65,7 @@ export default function buildModeRoutes({
// Add active DataSource route.
// This is the DataSource route for the active data source defined in ExtensionManager.getActiveDataSource
const path = `${publicUrl}${mode.routeName}`;
const path = `${mode.routeName}`;
// TODO move up.
const children = () => (
+14 -9
View File
@@ -11,7 +11,7 @@ import NotFound from './NotFound';
import buildModeRoutes from './buildModeRoutes';
import PrivateRoute from './PrivateRoute';
import PropTypes from 'prop-types';
import publicUrl from '../utils/publicUrl';
import { routerBase, routerBasename } from '../utils/publicUrl';
const NotFoundServer = ({
message = 'Unable to query for studies at this time. Check your data source configuration or network connection',
@@ -55,23 +55,23 @@ NotFoundStudy.propTypes = {
// TODO: Include "routes" debug route if dev build
const bakedInRoutes = [
{
path: `${publicUrl}notfoundserver`,
path: `/notfoundserver`,
children: NotFoundServer,
},
{
path: `${publicUrl}notfoundstudy`,
path: `/notfoundstudy`,
children: NotFoundStudy,
},
{
path: `${publicUrl}debug`,
path: `/debug`,
children: Debug,
},
{
path: `${publicUrl}local`,
path: `/local`,
children: Local.bind(null, { modePath: '' }), // navigate to the worklist
},
{
path: `${publicUrl}localbasic`,
path: `/localbasic`,
children: Local.bind(null, { modePath: 'viewer/dicomlocal' }),
},
];
@@ -86,7 +86,6 @@ const createRoutes = ({
servicesManager,
commandsManager,
hotkeysManager,
routerBasename,
showStudyList,
}: withAppTypes) => {
const routes =
@@ -101,8 +100,15 @@ const createRoutes = ({
const { customizationService } = servicesManager.services;
const path =
routerBasename.length > 1 && routerBasename.endsWith('/')
? routerBasename.substring(0, routerBasename.length - 1)
: routerBasename;
console.log('Registering worklist route', routerBasename, path);
const WorkListRoute = {
path: publicUrl,
path: '/',
children: DataSourceWrapper,
private: true,
props: { children: WorkList, servicesManager, extensionManager },
@@ -113,7 +119,6 @@ const createRoutes = ({
const allRoutes = [
...routes,
...(showStudyList ? [WorkListRoute] : []),
...(publicUrl !== '/' && showStudyList ? [{ ...WorkListRoute, path: publicUrl }] : []),
...(customRoutes?.routes || []),
...bakedInRoutes,
customRoutes?.notFoundRoute || notFoundRoute,
@@ -5,7 +5,7 @@ function preserve(query, current, key) {
}
}
export const preserveKeys = ['configUrl', 'multimonitor', 'screenNumber'];
export const preserveKeys = ['configUrl', 'multimonitor', 'screenNumber', 'hangingProtocolId'];
export function preserveQueryParameters(
query,
+3 -1
View File
@@ -1,4 +1,6 @@
const publicUrl = (window as any).PUBLIC_URL || '/';
const routerBasename = (window as any).config?.routerBasename || publicUrl;
export { publicUrl, routerBasename };
export default publicUrl;
export { publicUrl };
@@ -28,7 +28,7 @@ The simplest way is to update the existing default config:
```js title="platform/app/public/config/default.js"
window.config = {
routerBasename: '/',
routerBasename: null,
extensions: [],
modes: [],
showStudyList: true,
@@ -81,7 +81,7 @@ window.config = ({ servicesManager } = {}) => {
}
},
},
routerBasename: '/',
routerBasename: null,
dataSources: [
{
namespace: '@ohif/extension-default.dataSourcesModule.dicomweb',
@@ -123,7 +123,7 @@ configuration looks like:
```js
window.config = {
routerBasename: '/',
routerBasename: null,
extensions: [],
modes: [],
showStudyList: true,
@@ -4,62 +4,113 @@ title: Custom URL Access/Build
---
## Build for non-root path
# Hosting the Web Viewer on a Custom URL Path
Sometimes it is desired to access the viewer from a non-root path (e.g., `/my-awesome-viewer` instead of `/`).
You can achieve so by using the `PUBLIC_URL` environment variable AND the `routerBasename` configuration option.
1. use a config (e.g. `config/myConfig.js`) file that is using the `routerBasename` of your choice `/my-awesome-viewer` (note there is only one / - it is not /my-awesome-viewer/).
2. build the viewer with `PUBLIC_URL=/my-awesome-viewer/` (note there are two / - it is not /my-awesome-viewer).
You can host the viewer on a subpath like `/abc` instead of the root `/`. There are **two levels** of customization depending on how you want to serve your static assets.
:::tip
The PUBLIC_URL tells the application where to find the static assets and the routerBasename will tell the application how to handle the routes
:::
## Simple Setup (Recommended for Most Use Cases)
If you want to make the viewer accessible from a custom path (e.g. `/abc`) and **don’t care where the assets are loaded from** (they’ll be fetched from the root `/`), this setup is for you.
### Testing in Development
For testing the build locally, you can use the following command:
### What You Get
- Viewer available at `https://yourdomain.com/abc`
- All assets (JS, WASM, etc.) still loaded from the root (`/app.js`, `/viewer.wasm`, etc.)
### How To Set It Up
1. Set `routerBasename` in your config file (e.g., `config/myConfig.js`) to `/abc`
2. Build the viewer with:
```bash
# we use default config file, so we assume you have already set the routerBasename to /my-awesome-viewer in the default config as an example
PUBLIC_URL=/my-awesome-viewer/ APP_CONFIG=config/default.js yarn dev
APP_CONFIG=config/myConfig.js yarn build
```
### Testing in Build (production)
You need to build the viewer with the following command:
### Local Development
```bash
PUBLIC_URL=/my-awesome-viewer/ APP_CONFIG=config/default.js yarn build
APP_CONFIG=config/myConfig.js yarn dev
```
We can use the `npx serve` to serve the build folder. There are two things you need to consider however,
1. You need to change the `public/serve.json` file to reflect the new routerBasename in the destination (see the example below)
---
## Advanced Setup (Custom Asset Path)
```json
// final serve.json
{
"rewrites": [{ "source": "*", "destination": "my-awesome-viewer/index.html" }],
If you want to host the viewer at `/abc` **and** serve static assets from a different location (e.g. `/my-private-assets`), this is a more advanced scenario.
### What You Get
- Viewer accessible from `https://yourdomain.com/abc`
- All assets loaded from `https://yourdomain.com/my-private-assets/`
### Why This is Tricky
Some libraries (especially ones using WASM) load assets using **relative paths**, which can break if not handled carefully. To solve this:
- Set `routerBasename` to `/abc`
- Set `PUBLIC_URL` to `/my-private-assets/`
- Webpack will load assets from the specified public URL.
### Local Development Notes
In development, use proxy rewrites to handle relative asset paths. Example for `dicom-microscopy-viewer`:
```js
proxy: {
'/dicom-microscopy-viewer': {
target: 'http://localhost:3000',
pathRewrite: {
'^/dicom-microscopy-viewer': `/${PUBLIC_URL}/dicom-microscopy-viewer`,
},
},
}
```
This ensures local development can find assets even when libraries expect them at certain paths.
### Building and Serving in Production
To build the viewer for production:
```bash
cd platform/app;
# rename the dist folder to my-awesome-viewer
mv dist my-awesome-viewer
# serve the folder with custom json, note that we are using ../public/serve.json and NOT public/serve.json
npx serve -c ./public/serve.json
PUBLIC_URL=/my-private-assets/ APP_CONFIG=config/myConfig.js yarn build
```
If you're using `npx serve`, make sure to update `serve.json`:
:::note
When you want to authenticate against a sub path, there are a few things you should keep in mind:
```json
{
"rewrites": [{ "source": "*", "destination": "/abc/index.html" }]
}
```
1. Set the `routerBasename` to the sub path and also update the `PUBLIC_URL` to match the sub path.
2. Don't forget to modify the `serve.json` file as mentioned earlier.
3. Ensure that the sub path is included in the list of allowed callback URLs. For example, in the Google Cloud dashboard, you can set it in the `Authorized redirect URIs` field under the `Credentials` section of the `APIs & Services` menu.
:::
Serve the viewer like this:
```bash
cd platform/app
mv dist abc # Rename dist folder to match your viewer route
npx serve -c ./public/serve.json
```
---
### 🐳 Using Docker? You're Covered
If you’re using our Dockerfile, you’re all set — it already handles copying specific asset folders (like `dicom-microscopy-viewer`) to the root:
```Dockerfile
COPY --from=builder /usr/src/app/platform/app/dist/dicom-microscopy-viewer /usr/share/nginx/html/dicom-microscopy-viewer
```
Keep an eye on the browser network tab for any assets that might fail to load — if any other libraries require similar treatment, you’ll need to handle those as well.
---
## Summary
| Goal | routerBasename | PUBLIC_URL | Assets Load From |
|-----------------------------------|----------------|-----------------------|--------------------------|
| Viewer at `/abc`, assets from `/` | `/abc` | default | Root `/` |
| Viewer at `/abc`, assets from `/my-private-assets` | `/abc` | `/my-private-assets/` | `/my-private-assets/` |
@@ -12,6 +12,7 @@ NODE_ENV=< production | development >
DEBUG=< true | false >
APP_CONFIG=< relative path to application configuration file >
PUBLIC_URL=< relative path to application root - default / >
ROUTER_BASENAME=< relative path to application root - default PUBLIC_URL >
VERSION_NUMBER=<Set by CircleCI>
BUILD_NUM=<Set by CircleCI>
# i18n
@@ -172,7 +172,7 @@ Customizations can be declared at three different scopes, each with its own prio
```jsx
window.config = {
name: 'config/default.js',
routerBasename: '/',
routerBasename: null,
customizationService: [
{
'studyBrowser.sortFunctions': {
@@ -194,7 +194,7 @@ Customizations can be declared at three different scopes, each with its own prio
```jsx
window.config = {
name: 'config/default.js',
routerBasename: '/',
routerBasename: null,
customizationService: [
'@ohif/extension-cornerstone.customizationModule.newCustomization',
],