Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat(react/app): environment variables #2180

Merged
merged 3 commits into from
May 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/add-doc-environment-variables.md
Original file line number Diff line number Diff line change
@@ -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.
13 changes: 13 additions & 0 deletions .changeset/cookbook-environment-variables.md
Original file line number Diff line number Diff line change
@@ -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.




53 changes: 53 additions & 0 deletions .changeset/expose-app-env.md
Original file line number Diff line number Diff line change
@@ -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 <div>Loading environment variables...</div>;
}

if (env.error) {
return <div>Error loading environment variables</div>;
}

return (
<div>
My environment variables: {JSON.stringify(env.value, null, 2)}
</div>
);
};
```


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.

12 changes: 12 additions & 0 deletions cookbooks/app-react-environment-variables/app.config.ts
Original file line number Diff line number Diff line change
@@ -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',
},
}),
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { defineAppManifest, mergeManifests } from '@equinor/fusion-framework-cli';

export default defineAppManifest((env, { base }) => {
return mergeManifests(base, {
key: 'environment-variables',
});
});
24 changes: 24 additions & 0 deletions cookbooks/app-react-environment-variables/package.json
Original file line number Diff line number Diff line change
@@ -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"
}
}
13 changes: 13 additions & 0 deletions cookbooks/app-react-environment-variables/src/App.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { useAppEnvironmentVariables } from '@equinor/fusion-framework-react-app';
export const App = () => {
const { value, complete, error } = useAppEnvironmentVariables();
if (!complete) {
return <div>Loading...</div>;
}
if (error) {
return <pre>Error: {JSON.stringify(error, null, 2)}</pre>;
}
return <pre>{JSON.stringify(value, null, 2)}</pre>;
};

export default App;
18 changes: 18 additions & 0 deletions cookbooks/app-react-environment-variables/src/config.ts
Original file line number Diff line number Diff line change
@@ -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;
30 changes: 30 additions & 0 deletions cookbooks/app-react-environment-variables/src/index.ts
Original file line number Diff line number Diff line change
@@ -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;
22 changes: 22 additions & 0 deletions cookbooks/app-react-environment-variables/tsconfig.json
Original file line number Diff line number Diff line change
@@ -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"
]
}
1 change: 1 addition & 0 deletions packages/react/app/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export type {

export { useAppModule } from './useAppModule';
export { useAppModules } from './useAppModules';
export { useAppEnvironmentVariables } from './useAppEnvironmentVariables';

export { makeComponent, ComponentRenderArgs } from './make-component';

Expand Down
54 changes: 54 additions & 0 deletions packages/react/app/src/useAppEnvironmentVariables.ts
Original file line number Diff line number Diff line change
@@ -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 <div>Loading environment variables...</div>;
* }
* if(env.error) {
* return <div>Error loading environment variables</div>;
* }
* return <div>My environment variables: {JSON.stringify(env.value)}</div>;
* }
*
* @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<TEnvironmentVariables> => {
// 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,
});
};
6 changes: 4 additions & 2 deletions packages/react/app/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@
},
},
"references": [

{
"path": "../../utils/observable"
},
{
"path": "../../app"
},
Expand All @@ -33,7 +35,7 @@
},
{
"path": "../widget"
}
},
],
"include": [
"src/**/*"
Expand Down
24 changes: 24 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

25 changes: 25 additions & 0 deletions vue-press/src/guide/app/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<AppEnvironmentVariables>();
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
Expand Down
Loading