diff --git a/.changeset/add-doc-environment-variables.md b/.changeset/add-doc-environment-variables.md new file mode 100644 index 000000000..dcc1cef95 --- /dev/null +++ b/.changeset/add-doc-environment-variables.md @@ -0,0 +1,7 @@ +--- +'@equinor/fusion-framework-docs': patch +--- + +## @equinor/fusion-framework-docs + +Updated the "Getting started" guide with a new section about using environment variables. diff --git a/.changeset/cookbook-environment-variables.md b/.changeset/cookbook-environment-variables.md new file mode 100644 index 000000000..091125567 --- /dev/null +++ b/.changeset/cookbook-environment-variables.md @@ -0,0 +1,13 @@ +--- +"@equinor/fusion-framework-cookbook-app-react-environment-variables": major +--- + +## @equinor/fusion-framework-cookbook-app-react-environment-variables + +This new cookbook demonstrates how to use the `useAppEnvironmentVariables` hook to access environment variables in a Fusion Framework React application. + +The cookbook uses the newly created hook `useAppEnvironmentVariables` from `@equinor/fusion-framework-react-app` which retrieve environment variables from the application's configuration. + + + + diff --git a/.changeset/expose-app-env.md b/.changeset/expose-app-env.md new file mode 100644 index 000000000..7b372b93c --- /dev/null +++ b/.changeset/expose-app-env.md @@ -0,0 +1,53 @@ +--- +"@equinor/fusion-framework-react-app": minor +--- + +## @equinor/fusion-react + +### What changed? + +The `useAppEnvironmentVariables` hook has been added to the `@equinor/fusion-react` package. +This hook provides access to the application's environment variables, which are retrieved from the app module provided by the framework. + +### Why the change? + +Previously, there was no built-in way to access the application's environment variables from the React components. +This new hook fills that gap, making it easier for developers to retrieve and use the environment configuration in their applications. + +### How to use the new feature + +To use the `useAppEnvironmentVariables` hook, simply import it and call it in your React component: + +```typescript +import { useAppEnvironmentVariables } from '@equinor/fusion-react'; + +const MyComponent = () => { + const env = useAppEnvironmentVariables<{ API_URL: string }>(); + + if (!env.complete) { + return
Loading environment variables...
; + } + + if (env.error) { + return
Error loading environment variables
; + } + + return ( +
+ My environment variables: {JSON.stringify(env.value, null, 2)} +
+ ); +}; +``` + + +The hook returns an observable state object that represents the current environment configuration. +The `value` property of this object contains the environment variables, which can be typed using a generic type parameter. + +If the environment configuration is not yet available (e.g., during the initial load), the `complete` property will be `false`. +If there was an error retrieving the configuration, the `error` property will be set. + +### Migration guide + +There are no breaking changes introduced with this feature. Developers can start using the `useAppEnvironmentVariables` hook immediately to access their application's environment variables. + diff --git a/cookbooks/app-react-environment-variables/app.config.ts b/cookbooks/app-react-environment-variables/app.config.ts new file mode 100644 index 000000000..ecfe248f1 --- /dev/null +++ b/cookbooks/app-react-environment-variables/app.config.ts @@ -0,0 +1,12 @@ +// demo +import { mergeAppConfigs, defineAppConfig } from '@equinor/fusion-framework-cli'; +export default defineAppConfig((_nev, { base }) => + mergeAppConfigs(base, { + environment: { + scope: 'foobar', + }, + endpoints: { + api: 'https://foo.bars', + }, + }), +); diff --git a/cookbooks/app-react-environment-variables/app.manifest.config.ts b/cookbooks/app-react-environment-variables/app.manifest.config.ts new file mode 100644 index 000000000..b895b4765 --- /dev/null +++ b/cookbooks/app-react-environment-variables/app.manifest.config.ts @@ -0,0 +1,7 @@ +import { defineAppManifest, mergeManifests } from '@equinor/fusion-framework-cli'; + +export default defineAppManifest((env, { base }) => { + return mergeManifests(base, { + key: 'environment-variables', + }); +}); diff --git a/cookbooks/app-react-environment-variables/package.json b/cookbooks/app-react-environment-variables/package.json new file mode 100644 index 000000000..4782f22c8 --- /dev/null +++ b/cookbooks/app-react-environment-variables/package.json @@ -0,0 +1,24 @@ +{ + "name": "@equinor/fusion-framework-cookbook-app-react-environment-variables", + "version": "0.0.0", + "description": "", + "private": true, + "type": "module", + "main": "src/index.ts", + "scripts": { + "build": "fusion-framework-cli app build", + "dev": "fusion-framework-cli app dev", + "docker": "cd .. && sh docker-script.sh app-react" + }, + "author": "", + "license": "ISC", + "devDependencies": { + "@equinor/fusion-framework-cli": "workspace:^", + "@equinor/fusion-framework-react-app": "workspace:^", + "@types/react": "^18.2.50", + "@types/react-dom": "^18.2.7", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "typescript": "^5.4.2" + } +} diff --git a/cookbooks/app-react-environment-variables/src/App.tsx b/cookbooks/app-react-environment-variables/src/App.tsx new file mode 100644 index 000000000..232351f43 --- /dev/null +++ b/cookbooks/app-react-environment-variables/src/App.tsx @@ -0,0 +1,13 @@ +import { useAppEnvironmentVariables } from '@equinor/fusion-framework-react-app'; +export const App = () => { + const { value, complete, error } = useAppEnvironmentVariables(); + if (!complete) { + return
Loading...
; + } + if (error) { + return
Error: {JSON.stringify(error, null, 2)}
; + } + return
{JSON.stringify(value, null, 2)}
; +}; + +export default App; diff --git a/cookbooks/app-react-environment-variables/src/config.ts b/cookbooks/app-react-environment-variables/src/config.ts new file mode 100644 index 000000000..3e381c7bf --- /dev/null +++ b/cookbooks/app-react-environment-variables/src/config.ts @@ -0,0 +1,18 @@ +import type { AppModuleInitiator } from '@equinor/fusion-framework-react-app'; + +export const configure: AppModuleInitiator = (configurator, env) => { + /** print render environment arguments */ + console.log('configuring application', env); + + /** callback when configurations is created */ + configurator.onConfigured((config) => { + console.log('application config created', config); + }); + + /** callback when the application modules has initialized */ + configurator.onInitialized((instance) => { + console.log('application config initialized', instance); + }); +}; + +export default configure; diff --git a/cookbooks/app-react-environment-variables/src/index.ts b/cookbooks/app-react-environment-variables/src/index.ts new file mode 100644 index 000000000..7e07c4019 --- /dev/null +++ b/cookbooks/app-react-environment-variables/src/index.ts @@ -0,0 +1,30 @@ +import { createElement } from 'react'; +import { createRoot } from 'react-dom/client'; + +import { ComponentRenderArgs, makeComponent } from '@equinor/fusion-framework-react-app'; + +import configure from './config'; +import App from './App'; + +/** create a render component */ +const appComponent = createElement(App); + +/** create React render root component */ +const createApp = (args: ComponentRenderArgs) => makeComponent(appComponent, args, configure); + +/** Render function */ +export const renderApp = (el: HTMLElement, args: ComponentRenderArgs) => { + /** make render element */ + const app = createApp(args); + + /** create render root from provided element */ + const root = createRoot(el); + + /** render Application */ + root.render(createElement(app)); + + /** Teardown */ + return () => root.unmount(); +}; + +export default renderApp; diff --git a/cookbooks/app-react-environment-variables/tsconfig.json b/cookbooks/app-react-environment-variables/tsconfig.json new file mode 100644 index 000000000..4c1b0a8b4 --- /dev/null +++ b/cookbooks/app-react-environment-variables/tsconfig.json @@ -0,0 +1,22 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "rootDir": "src", + "jsx": "react-jsx", + }, + "references": [ + { + "path": "../../packages/react/app" + }, + { + "path": "../../packages/cli" + }, + ], + "include": [ + "src/**/*" + ], + "exclude": [ + "node_modules", + "lib" + ] +} \ No newline at end of file diff --git a/packages/react/app/src/index.ts b/packages/react/app/src/index.ts index cefd7eb10..60439f388 100644 --- a/packages/react/app/src/index.ts +++ b/packages/react/app/src/index.ts @@ -11,6 +11,7 @@ export type { export { useAppModule } from './useAppModule'; export { useAppModules } from './useAppModules'; +export { useAppEnvironmentVariables } from './useAppEnvironmentVariables'; export { makeComponent, ComponentRenderArgs } from './make-component'; diff --git a/packages/react/app/src/useAppEnvironmentVariables.ts b/packages/react/app/src/useAppEnvironmentVariables.ts new file mode 100644 index 000000000..51396b3fb --- /dev/null +++ b/packages/react/app/src/useAppEnvironmentVariables.ts @@ -0,0 +1,54 @@ +import { useCurrentApp } from '@equinor/fusion-framework-react/app'; +import { + type ObservableStateReturnType, + useObservableSelector, + useObservableState, +} from '@equinor/fusion-observable/react'; + +/** + * A React hook that provides access to the application's environment variables. + * + * This hook returns an observable state object that represents the current environment configuration. + * The environment configuration is retrieved from the app module provided by the framework. + * + * @note This hook is only available when the app module is loaded (should be always for applications). + * This hook in theory should always have a value if config was provided for the application, but is async by nature, hence the `complete` and `error` properties. + * + * @example + * ```typescript + * const MyComponent = () => { + * const env = useAppEnvironmentVariables(); + * if(!env.complete) { + * return
Loading environment variables...
; + * } + * if(env.error) { + * return
Error loading environment variables
; + * } + * return
My environment variables: {JSON.stringify(env.value)}
; + * } + * + * @template TEnvironmentVariables - The type of the environment variables. Defaults to `unknown`. + * @returns An observable state object containing the current environment configuration. + */ +export const useAppEnvironmentVariables = < + TEnvironmentVariables = unknown, +>(): ObservableStateReturnType => { + // Get the current app module instance from the framework + const app = useCurrentApp<[], TEnvironmentVariables>().currentApp; + + // Ensure the app module is available before proceeding + if (!app) { + throw Error('Framework is missing app module'); + } + + // Get the environment configuration observable from the app module + const env$ = useObservableSelector( + app.getConfig(), + (config) => config.environment as TEnvironmentVariables, + ); + + // Return the observable state of the environment configuration + return useObservableState(env$, { + initial: app.config?.environment, + }); +}; diff --git a/packages/react/app/tsconfig.json b/packages/react/app/tsconfig.json index 766989d90..e1c543767 100644 --- a/packages/react/app/tsconfig.json +++ b/packages/react/app/tsconfig.json @@ -9,7 +9,9 @@ }, }, "references": [ - + { + "path": "../../utils/observable" + }, { "path": "../../app" }, @@ -33,7 +35,7 @@ }, { "path": "../widget" - } + }, ], "include": [ "src/**/*" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index be7401e65..d8042d444 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -293,6 +293,30 @@ importers: specifier: ^5.4.2 version: 5.4.2 + cookbooks/app-react-environment-variables: + devDependencies: + '@equinor/fusion-framework-cli': + specifier: workspace:^ + version: link:../../packages/cli + '@equinor/fusion-framework-react-app': + specifier: workspace:^ + version: link:../../packages/react/app + '@types/react': + specifier: ^18.2.50 + version: 18.2.61 + '@types/react-dom': + specifier: ^18.2.7 + version: 18.2.19 + react: + specifier: ^18.2.0 + version: 18.2.0 + react-dom: + specifier: ^18.2.0 + version: 18.3.1(react@18.2.0) + typescript: + specifier: ^5.4.2 + version: 5.4.5 + cookbooks/app-react-feature-flag: dependencies: '@equinor/eds-core-react': diff --git a/vue-press/src/guide/app/getting-started.md b/vue-press/src/guide/app/getting-started.md index 0f2ee5728..2bbf241dd 100644 --- a/vue-press/src/guide/app/getting-started.md +++ b/vue-press/src/guide/app/getting-started.md @@ -122,6 +122,31 @@ export default () => ({ ::: +#### Environment variables + +To access the environment variables in the application, use the hook `useAppEnvironmentVariables` from `@equinor/fusion-framework-react-app`. + +```ts +import { useAppEnvironmentVariables } from '@equinor/fusion-framework-react-app'; + +type AppEnvironmentVariables = { + API_URL: string; + API_SCOPE: string; +} + +const MyComponent = () => { + const env = useAppEnvironmentVariables(); + console.log(env); // { API_URL: 'https://foo.bar', API_SCOPE: 'c5161f15-9930-4cfc-b233-e9dfc5f8ad82/.default' } +} +``` + +> [!INFO] +> The `useAppEnvironmentVariables` hook consumes asyncronous data from the app service. +> In theory the value should always be available, since loaded during configuration. +> +> There will be a future module which manages application state, where during the configuration the +> desired environment variables can be picked. + ## Creating Application ### Main