initial commit

This commit is contained in:
Ayo 2024-01-21 20:00:22 +01:00
commit ed9650997e
48 changed files with 31481 additions and 0 deletions

120
.gitignore vendored Normal file
View file

@ -0,0 +1,120 @@
# astro build output
dist/
.output/
.astro/
# environment variables
.env
.env.production
# macOS-specific files
.DS_Store
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
pnpm-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
.vscode
.idea
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# TypeScript v1 declaration files
typings/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
.env.test
# parcel-bundler cache (https://parceljs.org/)
.cache
# Next.js build output
.next
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and *not* Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port

21
LICENSE Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2023 Sergio A. Arevalo Soria
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

27
README.md Normal file
View file

@ -0,0 +1,27 @@
# astro-microfrontends
This demo takes advantage of EcmaScript Modules (ESM) and url-imports to do client-side composition of microfrontends. Express is used for serving assets, but ideally one would have a dedicated CDN hosting them.
## Features
- ✨ Client-side composition of microfrontends
- 📦 Multiframeworks with Astro Islands
- 🚀 SSG/SSR supported by Astro
## Usage
### Micro frontends
- Build each micro frontend with `npm run build`
- Start each micro frontend with `npm run preview`
For production you would start the node server in the `server` folder, after building.
### Shell
- Build the shell with `npm run build`
- Start the shell with `npm run preview`
## Shared dependencies
Dependencies such as react and react-dom are shared across applications. They are fetched from [esm.sh](https://esm.sh/) and gets cached in the browser, reducing the bundle size. Each app can share other dependencies as well through url imports.

12
app-cart/index.html Normal file
View file

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="no">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Mikrofrontend</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>

2343
app-cart/package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

26
app-cart/package.json Normal file
View file

@ -0,0 +1,26 @@
{
"name": "app-a",
"version": "1.0.0",
"type": "module",
"scripts": {
"dev": "vite --port 3000",
"build": "vite build",
"preview": "vite preview",
"build:preview": "vite build && vite preview",
"test": "vitest"
},
"dependencies": {
"react": "18.2.0",
"react-dom": "18.2.0",
"utils": "*"
},
"devDependencies": {
"@vitejs/plugin-react": "1.0.7",
"vite": "3.2.3",
"vite-plugin-css-injected-by-js": "1.4.0"
},
"main": "vite.config.js",
"author": "",
"license": "ISC",
"description": ""
}

1053
app-cart/server/package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,15 @@
{
"name": "server",
"version": "1.0.0",
"description": "server",
"main": "index.js",
"scripts": {
"start": "node server.js"
},
"author": "navikt",
"license": "MIT",
"dependencies": {
"cors": "2.8.5",
"express": "4.18.2"
}
}

11
app-cart/server/server.js Normal file
View file

@ -0,0 +1,11 @@
const express = require("express");
const cors = require("cors");
const path = require("path");
const buildPath = path.resolve(__dirname, "../dist");
const server = express();
server.use(cors({ origin: "http://localhost:3000" }));
server.use("/", express.static(buildPath));
server.listen(7100, () => console.log("Server listening on port 7100"));

3
app-cart/src/App.css Normal file
View file

@ -0,0 +1,3 @@
.app-a {
color: #7c3aed;
}

11
app-cart/src/App.jsx Normal file
View file

@ -0,0 +1,11 @@
import React from "react";
import Cart from "./Cart";
import "./App.css";
const App = () => (
<section className="app-a">
<Cart />
</section>
);
export default App;

71
app-cart/src/Cart.jsx Normal file
View file

@ -0,0 +1,71 @@
// @ts-check
// import { broadcast } from 'utils/orchestrator'
import React, { useState } from "react";
const Cart = () => {
const [products, setProducts] = useState([
{
name: "Shoe A",
description: "It is a good shoe",
price: 100,
count: 2,
},
{
name: "Shoe B",
description: "It is a comfortable shoe",
price: 120,
count: 7,
},
]);
// listen to add-to-cart messages
// if item is already in cart, increment count
const getTotal = () => {
return products.reduce((acc, product) => {
return acc + product.price * product.count;
}, 0);
};
const decreaseCount = (index) => {
// if in cart, decrease count
const updatedProducts = [...products];
if (updatedProducts[index].count > 1) {
updatedProducts[index].count--;
} else {
updatedProducts.splice(index, 1);
}
setProducts(updatedProducts);
};
const increaseCount = (index) => {
// if in cart, increase count
const updatedProducts = [...products];
updatedProducts[index].count++;
setProducts(updatedProducts);
};
return (
<div>
<h2>🛒 Cart</h2>
<ul>
{products.map((product, index) => (
<li key={index}>
<span>({product.count}x) </span>
<span>{product.name}</span>
<span> - </span>
<strong>${product.price * product.count}</strong>
<span>
<button onClick={() => decreaseCount(index)}> - </button>
<button onClick={() => increaseCount(index)}> + </button>
</span>
</li>
))}
</ul>
<span>Total: </span>
<strong>${getTotal()}</strong>
</div>
);
};
export default Cart;

22
app-cart/vite.config.js Normal file
View file

@ -0,0 +1,22 @@
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import cssInject from "vite-plugin-css-injected-by-js";
import { resolve } from "path";
export default ({ command }) => ({
plugins: [react(), cssInject()],
preview: {
port: 7100,
},
build: {
rollupOptions: {
input: resolve(__dirname, "src/App.jsx"),
preserveEntrySignatures: "exports-only",
external: ["react", "react-dom"],
output: {
entryFileNames: "bundle.js",
format: "esm",
},
},
},
});

12
app-heading/index.html Normal file
View file

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="no">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Mikrofrontend</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>

2343
app-heading/package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

40
app-heading/package.json Normal file
View file

@ -0,0 +1,40 @@
{
"name": "app-b",
"version": "1.0.0",
"type": "module",
"scripts": {
"dev": "vite --port 3000",
"build": "vite build",
"preview": "vite preview",
"build:preview": "vite build && vite preview",
"test": "vitest"
},
"dependencies": {
"react": "18.2.0",
"react-dom": "18.2.0",
"utils": "*"
},
"devDependencies": {
"@vitejs/plugin-react": "1.0.7",
"vite": "3.2.3",
"vite-plugin-css-injected-by-js": "1.4.0"
},
"prettier": {
"printWidth": 120
},
"lint-staged": {
"*.{js,jsx}": [
"eslint -c .eslintrc.pre-commit.cjs --fix",
"prettier --cache --write",
"git add"
],
"*.{css,md,html,json}": [
"prettier --cache --write",
"git add"
]
},
"main": "vite.config.js",
"author": "",
"license": "ISC",
"description": ""
}

1053
app-heading/server/package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,15 @@
{
"name": "server",
"version": "1.0.0",
"description": "server",
"main": "index.js",
"scripts": {
"start": "node server.js"
},
"author": "navikt",
"license": "MIT",
"dependencies": {
"cors": "2.8.5",
"express": "4.18.2"
}
}

View file

@ -0,0 +1,11 @@
const express = require("express");
const cors = require("cors");
const path = require("path");
const buildPath = path.resolve(__dirname, "../dist");
const server = express();
server.use(cors({ origin: "http://localhost:3000" }));
server.use("/", express.static(buildPath));
server.listen(7200, () => console.log("Server listening on port 7200"));

3
app-heading/src/App.css Normal file
View file

@ -0,0 +1,3 @@
.app-b {
color: #7c3aed;
}

19
app-heading/src/App.jsx Normal file
View file

@ -0,0 +1,19 @@
import React from "react";
import "./App.css";
const App = (props) => {
const { title } = props;
const [count, setCount] = React.useState(0);
// listen to add-to-cart messages
return (
<section className="app-b">
<h1>
{title} <span style={{ background: "red", color: "white", padding: "0 0.25rem" }}>{count}</span>
</h1>
</section>
);
};
export default App;

View file

@ -0,0 +1,22 @@
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import cssInject from "vite-plugin-css-injected-by-js";
import { resolve } from "path";
export default ({ command }) => ({
plugins: [react(), cssInject()],
preview: {
port: 7200,
},
build: {
rollupOptions: {
input: resolve(__dirname, "src/App.jsx"),
preserveEntrySignatures: "exports-only",
external: ["react", "react-dom"],
output: {
entryFileNames: "bundle.js",
format: "esm",
},
},
},
});

2
app-products/.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
node_modules
dist

34
app-products/README.md Normal file
View file

@ -0,0 +1,34 @@
## Usage
Those templates dependencies are maintained via [pnpm](https://pnpm.io) via `pnpm up -Lri`.
This is the reason you see a `pnpm-lock.yaml`. That being said, any package manager will work. This file can be safely be removed once you clone a template.
```bash
$ npm install # or pnpm install or yarn install
```
### Learn more on the [Solid Website](https://solidjs.com) and come chat with us on our [Discord](https://discord.com/invite/solidjs)
## Available Scripts
In the project directory, you can run:
### `npm dev` or `npm start`
Runs the app in the development mode.<br>
Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
The page will reload if you make edits.<br>
### `npm run build`
Builds the app for production to the `dist` folder.<br>
It correctly bundles Solid in production mode and optimizes the build for the best performance.
The build is minified and the filenames include the hashes.<br>
Your app is ready to be deployed!
## Deployment
You can deploy the `dist` folder to any static host provider (netlify, surge, now, etc.)

15
app-products/index.html Normal file
View file

@ -0,0 +1,15 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<title>Solid App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<script src="/src/index.jsx" type="module"></script>
</body>
</html>

View file

@ -0,0 +1,15 @@
{
"compilerOptions": {
"strict": true,
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "node",
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"jsx": "preserve",
"jsxImportSource": "solid-js",
"types": ["vite/client"],
"noEmit": true,
"isolatedModules": true
}
}

2582
app-products/package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

24
app-products/package.json Normal file
View file

@ -0,0 +1,24 @@
{
"name": "vite-template-solid",
"version": "0.0.0",
"description": "Those templates dependencies are maintained via [pnpm](https://pnpm.io) via `pnpm up -Lri`.",
"scripts": {
"start": "vite",
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"build:preview": "vite build && vite preview"
},
"license": "MIT",
"devDependencies": {
"vite": "^4.1.1",
"vite-plugin-css-injected-by-js": "3.0.1",
"vite-plugin-solid": "^2.5.0"
},
"dependencies": {
"solid-js": "^1.6.10",
"utils": "*"
},
"main": "vite.config.js",
"author": ""
}

1053
app-products/server/package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,15 @@
{
"name": "server",
"version": "1.0.0",
"description": "server",
"main": "index.js",
"scripts": {
"start": "node server.js"
},
"author": "navikt",
"license": "MIT",
"dependencies": {
"cors": "2.8.5",
"express": "4.18.2"
}
}

View file

@ -0,0 +1,11 @@
const express = require("express");
const cors = require("cors");
const path = require("path");
const buildPath = path.resolve(__dirname, "../dist");
const server = express();
server.use(cors({ origin: "http://localhost:3000" }));
server.use("/", express.static(buildPath));
server.listen(7300, () => console.log("Server listening on port 7300"));

63
app-products/src/App.jsx Normal file
View file

@ -0,0 +1,63 @@
import styles from "./App.module.css";
function App() {
const products = [
{
name: "Shoe A",
description: "It is a good shoe",
price: 100,
},
{
name: "Shoe B",
description: "It is a comfortable shoe",
price: 120,
},
{
name: "Shoe C",
description: "It is a stylish shoe",
price: 150,
},
{
name: "Shoe D",
description: "It is a durable shoe",
price: 90,
},
];
const addToCart = (product) => {
console.log(">>> add to cart", product);
// broadcast add product to cart
};
return (
<section className={styles["app-c"]}>
<h2>🥾 Products</h2>
<div
style="
display:flex;
flex-wrap:wrap;
"
>
{products.map((product, index) => (
<div
key={index}
style="
border:1px solid #ccc;
padding:30px;
border-radius:5px;
margin: 0 15px 15px 0;
width: calc(50% - 85px)
"
>
<strong>{product.name}</strong>
<p>{product.description}</p>
<p>Price: ${product.price}</p>
<button onClick={() => addToCart(product)}>Add to Cart</button>
</div>
))}
</div>
</section>
);
}
export default App;

View file

@ -0,0 +1,3 @@
.app-c {
color: #7c3aed;
}

View file

@ -0,0 +1,13 @@
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}

View file

@ -0,0 +1,26 @@
import { defineConfig } from "vite";
import solidPlugin from "vite-plugin-solid";
import { resolve } from "path";
import cssInject from "vite-plugin-css-injected-by-js";
export default defineConfig({
plugins: [solidPlugin(), cssInject()],
server: {
port: 3000,
},
preview: {
port: 7300,
},
build: {
target: "esnext",
rollupOptions: {
input: resolve(__dirname, "src/App.jsx"),
preserveEntrySignatures: "exports-only",
external: ["solid-js"],
output: {
entryFileNames: "bundle.js",
format: "esm",
},
},
},
});

View file

@ -0,0 +1,20 @@
import { defineConfig } from "astro/config";
import react from "@astrojs/react";
import solidJs from "@astrojs/solid-js";
export default defineConfig({
integrations: [
react(),
solidJs(),
{
name: 'importmap-externals',
hooks: {
'astro:build:setup': ({ vite, target }) => {
if(target === 'client') {
vite.build.rollupOptions["external"] = ["react", "react-dom", "solid-js"];
}
}
}
},
],
});

12149
app-shell/package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

33
app-shell/package.json Normal file
View file

@ -0,0 +1,33 @@
{
"name": "@example/basics",
"type": "module",
"version": "0.0.1",
"private": true,
"scripts": {
"dev": "astro dev",
"start": "astro dev",
"build": "astro build",
"preview": "astro preview",
"build:preview": "astro build && astro preview",
"astro": "astro",
"prettier": "prettier --write ."
},
"dependencies": {
"@astrojs/react": "3.0.7",
"@astrojs/solid-js": "3.0.2",
"@types/react": "18.0.27",
"@types/react-dom": "18.0.10",
"astro": "4.0.3",
"react": "18.2.0",
"react-dom": "18.2.0",
"solid-js": "1.6.14"
},
"devDependencies": {
"prettier": "2.8.4",
"prettier-plugin-astro": "0.8.0"
},
"main": "index.js",
"author": "",
"license": "ISC",
"description": ""
}

View file

@ -0,0 +1,17 @@
import MicroFrontendA from "http://localhost:7100/bundle.js";
import MicroFrontendB from "http://localhost:7200/bundle.js";
const ReactComponent = (props) => {
const { title } = props;
console.log(">>> title", title);
return (
<>
<MicroFrontendB title={title} />
<MicroFrontendA />
</>
);
};
export default ReactComponent;

View file

@ -0,0 +1,11 @@
import MicroFrontendC from "http://localhost:7300/bundle.js";
function App() {
return (
<>
<MicroFrontendC />
</>
);
}
export default App;

1
app-shell/src/env.d.ts vendored Normal file
View file

@ -0,0 +1 @@
/// <reference types="astro/client" />

View file

@ -0,0 +1,56 @@
---
import ReactComponent from "../components/ReactComponent.jsx";
import SolidComponent from "../components/SolidComponent.jsx";
const appName = 'Scalable Shoe Shop'
---
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width" />
<title>{appName}</title>
<script type="importmap">
{
"imports": {
"react": "https://esm.sh/react",
"react-dom": "https://esm.sh/react-dom",
"solid-js": "https://esm.sh/solid-js"
}
}
</script>
</head>
<body>
<main>
<ReactComponent title={appName} client:only="react" />
<SolidComponent client:only="solid" />
</main>
</body>
</html>
<style>
html {
font-family: system-ui, sans-serif;
background-color: #f6f6f6;
}
main {
margin: auto;
padding: 1.5rem;
max-width: 60ch;
}
h1 {
font-size: 3rem;
font-weight: 800;
margin: 0;
}
</style>
<script>
window.addEventListener('message', (event) => {
if (event.data.source === 'app-a') {
console.log('>>> message', event)
}
})
</script>

7
app-shell/tsconfig.json Normal file
View file

@ -0,0 +1,7 @@
{
"extends": "astro/tsconfigs/strict",
"compilerOptions": {
"jsx": "preserve",
"jsxImportSource": "solid-js"
}
}

8042
package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

21
package.json Normal file
View file

@ -0,0 +1,21 @@
{
"name": "astro-microfrontends",
"version": "1.0.0",
"description": "This demo takes advantage of EcmaScript Modules (ESM) and url-imports to do client-side composition of microfrontends. Express is used for serving assets, but ideally one would have a dedicated CDN hosting them.",
"main": "index.js",
"type": "module",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "npm run build --workspaces --if-present",
"preview": "npm run preview -workspaces --if-present"
},
"author": "",
"license": "ISC",
"workspaces": [
"app-cart",
"app-heading",
"app-products",
"app-shell",
"utils"
]
}

0
utils/index.js Normal file
View file

3
utils/orchestrator.mjs Normal file
View file

@ -0,0 +1,3 @@
// implement messaging system between MF's
// - broadcast message
// - listen to message & attach callback

12
utils/package.json Normal file
View file

@ -0,0 +1,12 @@
{
"name": "utils",
"version": "1.0.0",
"description": "",
"main": "index.js",
"devDependencies": {},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC"
}