Skip to content
Open
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
78 changes: 78 additions & 0 deletions packages/react-ui/Home.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import React from 'react';

export default class Home extends React.Component {
constructor(props) {
super(props);
}

render() {
const reactVersion = require('./package.json').dependencies['react'];

const styles = {
container: {
fontFamily: 'Arial, sans-serif',
padding: '20px',
backgroundColor: '#f0f2f5',
borderRadius: '8px',
boxShadow: '0 2px 4px rgba(0, 0, 0, 0.1)',
maxWidth: '600px',
margin: '20px auto',
border: '1px solid #ddd'
},
header: {
color: '#1a1a1a',
textAlign: 'center',
borderBottom: '2px solid #e0e0e0',
paddingBottom: '15px',
marginBottom: '20px',
fontSize: '24px'
},
content: {
color: '#333',
},
paragraph: {
lineHeight: '1.6',
fontSize: '16px',
marginBottom: '15px'
},
box: {
backgroundColor: '#ffffff',
border: '1px solid #e0e0e0',
padding: '15px',
borderRadius: '6px',
margin: '20px 0',
boxShadow: '0 1px 2px rgba(0, 0, 0, 0.05)'
},
code: {
fontFamily: 'monospace',
backgroundColor: '#e8e8e8',
padding: '2px 4px',
borderRadius: '3px',
},
strong: {
color: '#0056b3'
}
};

return (
<div style={styles.container}>
<h1 style={styles.header}>React Application as a Web Component</h1>
<div style={styles.content}>
<p style={styles.paragraph}>
This user interface is built using <strong style={styles.strong}>React</strong>.
</p>
<div style={styles.box}>
<p><strong>React Version:</strong> <code style={styles.code}>{reactVersion}</code></p>
</div>
<p style={styles.paragraph}>
The entire React application is encapsulated and delivered as a <strong style={styles.strong}>Web Component</strong>.
This is a standards-based way to create custom, reusable HTML elements that can be used with any JavaScript library or framework, or even with no framework at all.
</p>
<p style={styles.paragraph}>
This approach promotes <strong style={styles.strong}>interoperability</strong> and allows different parts of a larger application to be built with different technologies without conflicts.
</p>
</div>
</div>
);
}
}
46 changes: 46 additions & 0 deletions packages/react-ui/app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter, MemoryRouter, Switch, Route } from 'react-router-dom';

import Home from './Home';

function App({ baseHref }) {
// When the host app uses hash-based routing (e.g. VCFA tenant), Angular owns
// the hash so React cannot use BrowserRouter (pathname mismatch) or HashRouter
// (it would read Angular's hash segment). MemoryRouter is used instead.
const isHashBased = baseHref && !window.location.pathname.startsWith(baseHref);
const Router = isHashBased ? MemoryRouter : BrowserRouter;
const routerProps = isHashBased ? { initialEntries: ['/'] } : { basename: baseHref };

return (
<Router {...routerProps}>
<div className="App">
<div className="content">
<Switch>
<Route path="/" exact component={Home} />
</Switch>
</div>
</div>
</Router>
);
}

class MyReactAppUIElement extends HTMLElement {
connectedCallback() {
const baseHref = this.getAttribute('baseHref');
ReactDOM.render(<App baseHref={baseHref} />, this);
}

static get observedAttributes() {
return ['baseHref'];
}

attributeChangedCallback(name, oldValue, newValue) {
if (name === 'baseHref') {
console.log(`baseHref changed from ${oldValue} to ${newValue}`);
}
}

}

customElements.define('react-element', MyReactAppUIElement);
2 changes: 2 additions & 0 deletions packages/react-ui/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<react-element></react-element>
<script src="./bundle.js"></script>
1 change: 1 addition & 0 deletions packages/react-ui/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import('./app');
30 changes: 30 additions & 0 deletions packages/react-ui/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"name": "react-ui",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "webpack serve --config webpack.config.js --mode=development --devtool=source-map",
"build": "webpack --config webpack.config.js --mode=development",
"build:plugin": "webpack --config webpack.config.plugin.js --mode=development"
},
"author": "",
"license": "MIT",
"devDependencies": {
"@babel/core": "^7.12.10",
"@babel/preset-env": "^7.12.10",
"@babel/preset-react": "^7.12.10",
"babel-runtime": "^6.26.0",
"babel-loader": "^8.2.2",
"babel-plugin-transform-runtime": "^6.23.0",
"webpack": "^5.10.1",
"webpack-cli": "^4.2.0",
"webpack-dev-server": "^3.11.0"
},
"dependencies": {
"copy-webpack-plugin": "^8.1.1",
"react": "^17.0.1",
"react-dom": "^17.0.1",
"react-router-dom": "5.3.3"
}
}
1 change: 1 addition & 0 deletions packages/react-ui/plugin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import('./app');
53 changes: 53 additions & 0 deletions packages/react-ui/webpack.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin");
const CopyWebpackPlugin = require("copy-webpack-plugin");
const path = require("path");

module.exports = options => {
return {
entry: './index.js',
output: {
filename: 'bundle.js',
publicPath: "auto",
uniqueName: "react-example-ui",
path: path.resolve(__dirname, './dist'),
},
module: {
rules: [
{
test: /.js$/,
exclude: /node_modules/,
use: [
{
loader: 'babel-loader',
options: {
cacheDirectory: true,
presets: ['@babel/react', '@babel/env']
}
},
],
},
],
},
plugins: [
new ModuleFederationPlugin({
name: "react_example_ui",
library: { type: "var", name: "react_example_ui" },
filename: "remoteEntry.js",
exposes: {
'./web-components': './app.js',
},
shared: ["react", "react-dom"]
}),
new CopyWebpackPlugin({
patterns: [
{
from: './*.html'
}
]
})
],
devServer: {
port: 4204
}
}
}
53 changes: 53 additions & 0 deletions packages/react-ui/webpack.config.plugin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin");
const CopyWebpackPlugin = require("copy-webpack-plugin");
const path = require("path");

module.exports = options => {
return {
entry: './plugin.js',
output: {
filename: 'bundle.js',
publicPath: "auto",
uniqueName: "react-example-ui",
path: path.resolve(__dirname, '../vcfa-plugin/projects/ui-plugin/public/assets/react-plugin/dist'),
},
module: {
rules: [
{
test: /.js$/,
exclude: /node_modules/,
use: [
{
loader: 'babel-loader',
options: {
cacheDirectory: true,
presets: ['@babel/react', '@babel/env']
}
},
],
},
],
},
plugins: [
new ModuleFederationPlugin({
name: "react_example_ui",
library: { type: "var", name: "react_example_ui" },
filename: "remoteEntry.js",
exposes: {
'./web-components': './app.js',
},
shared: ["react", "react-dom"]
}),
new CopyWebpackPlugin({
patterns: [
{
from: './*.html'
}
]
})
],
devServer: {
port: 4204
}
}
}
30 changes: 30 additions & 0 deletions packages/vcfa-plugin/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Prerequisites
- NodeJS >= v22.14.x

# Start
```
nvm use 22
npm ci
npm run start
```

# Build
```
nvm use 22
npm ci
npm run build
```

# Test
```
nvm use 22
npm ci
npm run test
```

# Build VCFA Plugin
```
nvm use 22
npm ci
npm run build:plugin
```
65 changes: 35 additions & 30 deletions packages/vcfa-plugin/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,45 +6,50 @@
},
"scripts": {
"ng": "ng",
"start": "ng serve",
"start": "cd ../react-ui && npm run build:plugin && cd ../vcfa-plugin && ng serve",
"build": "ng build",
"build:plugin": "ng build --configuration plugin && npm run zip",
"build:plugin": "cd ../react-ui && npm run build:plugin && cd ../vcfa-plugin && ng build --configuration plugin && npm run zip",
"test": "ng test",
"zip": "node ./scripts/zip.js ./dist/quick-start ./dist/plugin.zip"
"zip": "node ./scripts/zip.js ./dist/ui-plugin ./dist/plugin.zip"
},
"private": true,
"dependencies": {
"@angular-architects/module-federation": "19.0.3",
"@angular/animations": "19.2.0",
"@angular/cdk": "19.2.0",
"@angular/common": "19.2.0",
"@angular/compiler": "19.2.0",
"@angular/core": "19.2.0",
"@angular/forms": "19.2.0",
"@angular/platform-browser": "19.2.0",
"@angular/platform-browser-dynamic": "19.2.0",
"@angular/router": "19.2.0",
"@cds/core": "6.15.1",
"@clr/angular": "17.10.0",
"@clr/ui": "17.10.0",
"ansi-escapes": "7.1.0",
"rxjs": "7.8.0",
"tslib": "2.3.0",
"zone.js": "0.15.0"
"@angular-architects/module-federation": "21.2.2",
"@angular/animations": "21.2.17",
"@angular/cdk": "21.2.14",
"@angular/common": "21.2.17",
"@angular/compiler": "21.2.17",
"@angular/core": "21.2.17",
"@angular/forms": "21.2.17",
"@angular/platform-browser": "21.2.17",
"@angular/platform-browser-dynamic": "21.2.17",
"@angular/router": "21.2.17",
"@clr/angular": "18.2.1",
"@clr/ui": "18.2.1",
"@ngx-translate/core": "17.0.0",
"@ngx-translate/http-loader": "17.0.0",
"@vcd/bindings": "9.1.1",
"@vcfa/container-hooks": "19.0.4",
"@vcfa/sdk": "19.1.9",
"ansi-escapes": "7.3.0",
"rxjs": "7.8.2",
"tslib": "2.8.1",
"zone.js": "0.16.2"
},
"devDependencies": {
"@angular-devkit/build-angular": "19.2.0",
"@angular/cli": "19.2.0",
"@angular/compiler-cli": "19.2.0",
"@types/jasmine": "5.1.0",
"@angular-devkit/build-angular": "21.2.17",
"@angular/build": "21.2.17",
"@angular/cli": "21.2.17",
"@angular/compiler-cli": "21.2.17",
"@types/jasmine": "5.1.15",
"archiver": "7.0.1",
"jasmine-core": "5.6.0",
"karma": "6.4.0",
"jasmine-core": "5.13.0",
"karma": "6.4.4",
"karma-chrome-launcher": "3.2.0",
"karma-coverage": "2.2.0",
"karma-coverage": "2.2.1",
"karma-jasmine": "5.1.0",
"karma-jasmine-html-reporter": "2.1.0",
"ngx-build-plus": "19.0.0",
"typescript": "5.7.2"
"karma-jasmine-html-reporter": "2.2.0",
"ngx-build-plus": "20.0.0",
"typescript": "5.9.3"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"subnav.menu.home": "Home",
"home.title": "Welcome to your VCFA Plugin",
"home.description": "The Quick Start Plugin showcases all available extension points in VCFA where you can start building your own plugin."
}
Loading