Skip to content

Commit

Permalink
Avoid portal dependencies from reference external CDN #4931
Browse files Browse the repository at this point in the history
ref DEV-2357
  • Loading branch information
tung2744 authored Dec 11, 2024
2 parents be83128 + a5fbefe commit 4a3f6c4
Show file tree
Hide file tree
Showing 40 changed files with 360 additions and 239 deletions.
2 changes: 2 additions & 0 deletions cmd/portal/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ ARG GIT_HASH
# If the working directory is /src, Parcel will have some problem with it.
WORKDIR /usr/src/app
COPY ./portal/package.json ./portal/package-lock.json ./
# Copy the scripts
COPY ./portal/scripts/. ./scripts/.
# Copy the vite plugin
COPY ./portal/packages/. ./packages/.
RUN npm ci
Expand Down
2 changes: 2 additions & 0 deletions custombuild/cmd/portalx/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ ARG GIT_HASH
# If the working directory is /src, Parcel will have some problem with it.
WORKDIR /usr/src/app
COPY ./portal/package.json ./portal/package-lock.json ./
# Copy the scripts
COPY ./portal/scripts/. ./scripts/.
# Copy the vite plugin
COPY ./portal/packages/. ./packages/.
RUN npm ci
Expand Down
416 changes: 216 additions & 200 deletions portal/package-lock.json

Large diffs are not rendered by default.

10 changes: 6 additions & 4 deletions portal/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
],
"type": "module",
"scripts": {
"postinstall": "./scripts/npm-postinstall.sh",
"test": "jest",
"start": "vite",
"build": "vite build",
Expand Down Expand Up @@ -60,10 +61,11 @@
"@apollo/client": "3.8.7",
"@authgear/web": "1.0.1",
"@elgorditosalsero/react-gtm-hook": "2.7.2",
"@fluentui/merge-styles": "8.5.13",
"@fluentui/react": "8.112.8",
"@fluentui/react-hooks": "8.6.33",
"@fluentui/react-icons": "^2.0.239",
"@fluentui/font-icons-mdl2": "^8.5.55",
"@fluentui/merge-styles": "^8.6.13",
"@fluentui/react": "^8.121.13",
"@fluentui/react-hooks": "^8.8.16",
"@fluentui/react-icons": "^2.0.266",
"@fortawesome/fontawesome-free": "5.15.4",
"@monaco-editor/react": "4.6.0",
"@oursky/react-messageformat": "2.0.2",
Expand Down
45 changes: 45 additions & 0 deletions portal/scripts/npm-postinstall.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#!/bin/sh

set -x

# This is how we self-host the runtime assets of FluentUI.
#
# We utilize the public directory feature of Vite.
# See https://v2.vitejs.dev/guide/assets.html#the-public-directory
# This feature merely copies the files in the public directory to the root of the outDir.
# Since we use FileServer to serve asset, assets have to be put in the asset directory.
# We automate this copy process with a NPM postinstall script. (This script)
#
# Finally we tell fluentui to load the assets from the portal backend, instead of from the default CDN.
# This is done with window.FabricConfig.

# In docker build, the postinstall runs before the src are copied.
# So we run mkdir -p to ensure the directory exist.
mkdir -p ./src/public/shared-assets/
# In case you wonder why we do not just use shell expansion here,
# if ./src/public/shared-assets is really empty, sh DOES NOT expand, and take '*' literally.
# Since we do not have such a file, the command will fail.
find ./src/public/shared-assets -name 'fabric-icons-*.woff' -print -exec rm '{}' \;
# When window.FabricConfig.iconBaseUrl is set, it loads the font directly in the directory.
# So we just copy the fonts to outDir.
cp -r ./node_modules/@fluentui/font-icons-mdl2/fonts/. ./src/public/shared-assets/.

# When window.FabricConfig.fontBaseUrl is set, it loads the font with a certain structure.
# The original URL is https://static2.sharepointonline.com/files/fabric/assets/fonts/segoeui-westeuropean/segoeui-bold.woff2
# When fontBaseUrl is set, the URL looks like https://origin/shared-assets/fonts/segoeui-westeuropean/segoeui-bold.woff2

# FluentUI actually has support for many fonts.
# For the full list of the fonts it may load at runtime, see ./node_modules/@fluentui/react/dist/css/fabric.css
# Since our site is lang=en, it will ever load "Segoe UI Web (West European)"
# So we just download and copy them.
# Since this process has to be done once only, the following commands are commented out.
# wget https://static2.sharepointonline.com/files/fabric/assets/fonts/segoeui-westeuropean/segoeui-light.woff2 -O ./src/public/shared-assets/fonts/segoeui-westeuropean/segoeui-light.woff2
# wget https://static2.sharepointonline.com/files/fabric/assets/fonts/segoeui-westeuropean/segoeui-light.woff -O ./src/public/shared-assets/fonts/segoeui-westeuropean/segoeui-light.woff
# wget https://static2.sharepointonline.com/files/fabric/assets/fonts/segoeui-westeuropean/segoeui-semilight.woff2 -O ./src/public/shared-assets/fonts/segoeui-westeuropean/segoeui-semilight.woff2
# wget https://static2.sharepointonline.com/files/fabric/assets/fonts/segoeui-westeuropean/segoeui-semilight.woff -O ./src/public/shared-assets/fonts/segoeui-westeuropean/segoeui-semilight.woff
# wget https://static2.sharepointonline.com/files/fabric/assets/fonts/segoeui-westeuropean/segoeui-regular.woff2 -O ./src/public/shared-assets/fonts/segoeui-westeuropean/segoeui-regular.woff2
# wget https://static2.sharepointonline.com/files/fabric/assets/fonts/segoeui-westeuropean/segoeui-regular.woff -O ./src/public/shared-assets/fonts/segoeui-westeuropean/segoeui-regular.woff
# wget https://static2.sharepointonline.com/files/fabric/assets/fonts/segoeui-westeuropean/segoeui-semibold.woff2 -O ./src/public/shared-assets/fonts/segoeui-westeuropean/segoeui-semibold.woff2
# wget https://static2.sharepointonline.com/files/fabric/assets/fonts/segoeui-westeuropean/segoeui-semibold.woff -O ./src/public/shared-assets/fonts/segoeui-westeuropean/segoeui-semibold.woff
# wget https://static2.sharepointonline.com/files/fabric/assets/fonts/segoeui-westeuropean/segoeui-bold.woff2 -O ./src/public/shared-assets/fonts/segoeui-westeuropean/segoeui-bold.woff2
# wget https://static2.sharepointonline.com/files/fabric/assets/fonts/segoeui-westeuropean/segoeui-bold.woff -O ./src/public/shared-assets/fonts/segoeui-westeuropean/segoeui-bold.woff
17 changes: 5 additions & 12 deletions portal/src/ChoiceButton.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,6 @@
import React, {
useCallback,
ReactNode,
ReactElement,
ComponentType,
} from "react";
import React, { useCallback, ReactElement, ComponentType } from "react";
import {
CompoundButton,
IButtonStyles,
IButtonProps,
useTheme,
IRenderFunction,
Expand All @@ -18,12 +12,12 @@ export interface IconComponentProps {
}

export interface ChoiceButtonProps {
className?: string;
styles?: IButtonStyles;
className?: IButtonProps["className"];
styles?: IButtonProps["styles"];
checked?: IButtonProps["checked"];
disabled?: IButtonProps["disabled"];
text?: ReactNode;
secondaryText?: ReactNode;
text?: IButtonProps["text"];
secondaryText?: IButtonProps["secondaryText"];
onClick?: IButtonProps["onClick"];
IconComponent?: ComponentType<IconComponentProps>;
}
Expand Down Expand Up @@ -87,7 +81,6 @@ export default function ChoiceButton(props: ChoiceButtonProps): ReactElement {
<CompoundButton
{...rest}
toggle={true}
// @ts-expect-error
styles={styles}
onRenderIcon={IconComponent == null ? undefined : onRenderIcon}
/>
Expand Down
1 change: 0 additions & 1 deletion portal/src/TextFieldWithCopyButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ const TextFieldWithCopyButton: React.VFC<TextFieldWithCopyButtonProps> =
function TextFieldWithCopyButton(props: TextFieldWithCopyButtonProps) {
const { disabled, additionalIconButtons, ...rest } = props;
const { themes } = useSystemConfig();
// eslint-disable-next-line no-useless-assignment
const { copyButtonProps, Feedback } = useCopyFeedback({
textToCopy: props.value ?? "",
});
Expand Down
18 changes: 8 additions & 10 deletions portal/src/graphql/portal/LoginMethodConfigurationScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1600,6 +1600,8 @@ function AuthenticationButton(props: AuthenticationButtonProps) {
const checked = targetValue === currentValue;
const iconName = AUTHENTICATION_BUTTON_ICON[targetValue];

const { renderToString } = useContext(Context);

const IconComponent = useCallback(
(props) => {
return (
Expand Down Expand Up @@ -1641,16 +1643,12 @@ function AuthenticationButton(props: AuthenticationButtonProps) {
styles={buttonStyles}
disabled={disabled}
checked={checked}
text={
<FormattedMessage
id={`LoginMethodConfigurationScreen.second-level.${targetValue}.title`}
/>
}
secondaryText={
<FormattedMessage
id={`LoginMethodConfigurationScreen.second-level.${targetValue}.description`}
/>
}
text={renderToString(
`LoginMethodConfigurationScreen.second-level.${targetValue}.title`
)}
secondaryText={renderToString(
`LoginMethodConfigurationScreen.second-level.${targetValue}.description`
)}
IconComponent={IconComponent}
onClick={onClick}
/>
Expand Down
8 changes: 8 additions & 0 deletions portal/src/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,14 @@
display: none;
}
</style>
<script>
window.FabricConfig = {
// The lack of trailing slash is important.
fontBaseUrl: window.location.origin + "/shared-assets",
// The trailing slash is important.
iconBaseUrl: window.location.origin + "/shared-assets/",
};
</script>
<link rel="shortcut icon" href="./favicon.png" />
<link rel="stylesheet" href="./index.css" />
<link rel="stylesheet" href="./icons/authgear-portal-icons.css" />
Expand Down
56 changes: 55 additions & 1 deletion portal/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,23 @@ import "@fortawesome/fontawesome-free/css/all.min.css";
import React from "react";
import { render } from "react-dom";
import { initializeIcons, registerIcons } from "@fluentui/react";

// See below for details.
// Monaco editor initialization imports - Start
import { loader } from "@monaco-editor/react";
import * as monaco from "monaco-editor";
// @ts-expect-error
import editorWorker from "monaco-editor/esm/vs/editor/editor.worker?worker";
// @ts-expect-error
import jsonWorker from "monaco-editor/esm/vs/language/json/json.worker?worker";
// @ts-expect-error
import cssWorker from "monaco-editor/esm/vs/language/css/css.worker?worker";
// @ts-expect-error
import htmlWorker from "monaco-editor/esm/vs/language/html/html.worker?worker";
// @ts-expect-error
import tsWorker from "monaco-editor/esm/vs/language/typescript/ts.worker?worker";
// Monaco editor initialization imports - End

import {
Chart as ChartJS,
CategoryScale,
Expand Down Expand Up @@ -62,4 +79,41 @@ ChartJS.register(
// and AnalyticsSignupMethodsWidget
ChartJS.register(ArcElement, Tooltip);

render(<ReactApp />, document.getElementById("react-app-root"));
// See https://github.com/microsoft/monaco-editor/blob/main/docs/integrate-esm.md#using-vite
// See https://github.com/suren-atoyan/monaco-react?tab=readme-ov-file#use-monaco-editor-as-an-npm-package
//
// By using this approach, it is now our own responsibility to keep monaco-editor and @monaco-editor/react compatible.
// @monaco-editor/react uses @monaco-editor/loader, and @monaco-editor/loader uses a specific version of monaco-editor.
// So when you need to update them, you do
// 1. Pick a version of @monaco-editor/react.
// 2. Inspect @monaco-editor/loader in package-lock.json to see the actual version of @monaco-editor/loader
// 3. Inspect https://github.com/suren-atoyan/monaco-loader/blob/master/src/config/index.js to see what version of monaco-editor it is using.
// 4. Install the same version of monaco-editor.
//
// Note that monaco-editor has some breaking changes in 0.45.0
// See https://github.com/microsoft/monaco-editor/blob/main/CHANGELOG.md#0450
// So the highest version we can use is 0.44.0, until @monaco-editor/react supports >= 0.45.0
window.MonacoEnvironment = {
getWorker(_, label) {
switch (label) {
case "json":
return new jsonWorker();
case "css":
case "scss":
case "less":
return new cssWorker();
case "html":
case "handlebars":
case "razor":
return new htmlWorker();
case "javascript":
case "typescript":
return new tsWorker();
}
return new editorWorker();
},
};
loader.config({ monaco });
loader.init().then(() => {
render(<ReactApp />, document.getElementById("react-app-root"));
});
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
24 changes: 13 additions & 11 deletions portal/src/util/mergeStyles.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,32 @@
import { useCallback, useMemo } from "react";
import { IStyleFunctionOrObject, IStyleSet } from "@fluentui/react";
import { IStyleFunctionOrObject } from "@fluentui/react";
import {
concatStyleSetsWithProps,
concatStyleSets,
IConcatenatedStyleSet,
IStyleSetBase,
} from "@fluentui/merge-styles";

export function useMergedStyles<TStylesProps, IStyleSet>(
...styless: (IStyleFunctionOrObject<TStylesProps, IStyleSet> | undefined)[]
): IStyleFunctionOrObject<TStylesProps, IStyleSet> {
export function useMergedStyles<TStylesProps, TStyleSet extends IStyleSetBase>(
...styless: (IStyleFunctionOrObject<TStylesProps, TStyleSet> | undefined)[]
): (
props: TStylesProps
) => ReturnType<typeof concatStyleSetsWithProps<TStylesProps, TStyleSet>> {
return useCallback(
(props) => {
return concatStyleSetsWithProps(props, ...styless);
},
// eslint-disable-next-line
// eslint-disable-next-line react-hooks/exhaustive-deps
[...styless]
);
}

export function useMergedStylesPlain(
// eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents
...styless: (IStyleSet | undefined)[]
): IConcatenatedStyleSet<IStyleSet> {
export function useMergedStylesPlain<TStyleSet extends IStyleSetBase>(
...styless: (TStyleSet | undefined)[]
): IConcatenatedStyleSet<TStyleSet> {
return useMemo(
() => concatStyleSets(...styless),
// eslint-disable-next-line
// eslint-disable-next-line react-hooks/exhaustive-deps
[...styless]
) as IConcatenatedStyleSet<IStyleSet>;
) as IConcatenatedStyleSet<TStyleSet>;
}

0 comments on commit 4a3f6c4

Please sign in to comment.