home app fragment
This commit is contained in:
parent
03eba8f996
commit
1b33835b14
27 changed files with 781 additions and 2 deletions
|
@ -1,8 +1,8 @@
|
|||
{
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next",
|
||||
"build": "next build",
|
||||
"dev": "RSPACK_CONFIG_VALIDATE=loose-silent next",
|
||||
"build": "cross-env RSPACK_CONFIG_VALIDATE=loose-silent NEXT_TELEMETRY_DISABLED=1 node --trace-deprecation --enable-source-maps ../../packages/next/dist/bin/next build",
|
||||
"start": "next start"
|
||||
},
|
||||
"dependencies": {
|
||||
|
|
77
examples/federation-single-app/components/SharedNav.tsx
Normal file
77
examples/federation-single-app/components/SharedNav.tsx
Normal file
|
@ -0,0 +1,77 @@
|
|||
import React from "react";
|
||||
import { Menu, Layout } from "antd";
|
||||
import { useRouter } from "next/router";
|
||||
import "./menu";
|
||||
|
||||
const SharedNav = () => {
|
||||
const { asPath, push } = useRouter();
|
||||
let activeMenu;
|
||||
|
||||
if (asPath === "/" || asPath.startsWith("/home")) {
|
||||
activeMenu = "/";
|
||||
} else if (asPath.startsWith("/shop")) {
|
||||
activeMenu = "/shop";
|
||||
} else if (asPath.startsWith("/checkout")) {
|
||||
activeMenu = "/checkout";
|
||||
}
|
||||
|
||||
const menuItems = [
|
||||
{
|
||||
className: "home-menu-link",
|
||||
label: (
|
||||
<>
|
||||
Home <sup>3000</sup>
|
||||
</>
|
||||
),
|
||||
key: "/",
|
||||
onMouseEnter: () => {},
|
||||
},
|
||||
{
|
||||
className: "shop-menu-link",
|
||||
label: (
|
||||
<>
|
||||
Shop <sup>3001</sup>
|
||||
</>
|
||||
),
|
||||
key: "/shop",
|
||||
},
|
||||
{
|
||||
className: "checkout-menu-link",
|
||||
label: (
|
||||
<>
|
||||
Checkout <sup>3002</sup>
|
||||
</>
|
||||
),
|
||||
key: "/checkout",
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<Layout.Header>
|
||||
<div className="header-logo">nextjs-mf</div>
|
||||
<Menu
|
||||
theme="dark"
|
||||
mode="horizontal"
|
||||
selectedKeys={activeMenu ? [activeMenu] : undefined}
|
||||
onClick={({ key }) => {
|
||||
push(key);
|
||||
}}
|
||||
items={menuItems}
|
||||
/>
|
||||
<style jsx>
|
||||
{`
|
||||
.header-logo {
|
||||
float: left;
|
||||
width: 200px;
|
||||
height: 31px;
|
||||
margin-right: 24px;
|
||||
color: white;
|
||||
font-size: 2rem;
|
||||
}
|
||||
`}
|
||||
</style>
|
||||
</Layout.Header>
|
||||
);
|
||||
};
|
||||
|
||||
export default SharedNav;
|
37
examples/federation-single-app/components/menu.tsx
Normal file
37
examples/federation-single-app/components/menu.tsx
Normal file
|
@ -0,0 +1,37 @@
|
|||
import type { ItemType } from "antd/es/menu/interface";
|
||||
|
||||
import { useRouter } from "next/router";
|
||||
import { Menu } from "antd";
|
||||
|
||||
const menuItems: ItemType[] = [
|
||||
{ label: "Main home", key: "/" },
|
||||
{ label: "Test hook from remote", key: "/home/test-remote-hook" },
|
||||
{ label: "Test broken remotes", key: "/home/test-broken-remotes" },
|
||||
{ label: "Exposed pages", key: "/home/exposed-pages" },
|
||||
{
|
||||
label: "Exposed components",
|
||||
type: "group",
|
||||
children: [{ label: "home/SharedNav", key: "/home/test-shared-nav" }],
|
||||
},
|
||||
];
|
||||
|
||||
export default function AppMenu() {
|
||||
const router = useRouter();
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
style={{ padding: "10px", fontWeight: 600, backgroundColor: "#fff" }}
|
||||
>
|
||||
Home App Menu
|
||||
</div>
|
||||
<Menu
|
||||
mode="inline"
|
||||
selectedKeys={[router.asPath]}
|
||||
style={{ height: "100%" }}
|
||||
onClick={({ key }) => router.push(key)}
|
||||
items={menuItems}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
.tjhin {
|
||||
display: flex;
|
||||
}
|
144
examples/federation-single-app/cypress/e2e/app.cy.ts
Normal file
144
examples/federation-single-app/cypress/e2e/app.cy.ts
Normal file
|
@ -0,0 +1,144 @@
|
|||
import { getH1, getH3 } from "../support/app.po";
|
||||
|
||||
describe("3000-home/", () => {
|
||||
beforeEach(() => cy.visit("/"));
|
||||
|
||||
describe("Warmup Next", () => {
|
||||
xit("warms pages concurrently", () => {
|
||||
const urls = [
|
||||
"/shop",
|
||||
"/checkout",
|
||||
"/checkout/test-title",
|
||||
"/checkout/test-check-button",
|
||||
"/api/test",
|
||||
];
|
||||
urls.forEach((url) => {
|
||||
cy.request(url); // This makes a GET request, not a full page visit
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("Welcome message", () => {
|
||||
it("should display welcome message", () => {
|
||||
getH1().contains("This is SPA combined");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Image checks", () => {
|
||||
xit("should check that the home-webpack-png and shop-webpack-png images are not 404", () => {
|
||||
// Get the src attribute of the home-webpack-png image
|
||||
cy.debug()
|
||||
.get("img.home-webpack-png")
|
||||
.invoke("attr", "src")
|
||||
.then((src) => {
|
||||
cy.log(src);
|
||||
cy.request(src).its("status").should("eq", 200);
|
||||
});
|
||||
|
||||
// Get the src attribute of the shop-webpack-png image
|
||||
cy.get("img.shop-webpack-png")
|
||||
.invoke("attr", "src")
|
||||
.then((src) => {
|
||||
// Send a GET request to the src URL
|
||||
cy.request(src).its("status").should("eq", 200);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("Routing checks", () => {
|
||||
it("check that clicking back and forwards in client side routeing still renders the content correctly", () => {
|
||||
cy.visit("/shop");
|
||||
cy.wait(3000);
|
||||
cy.url().should("include", "/shop");
|
||||
getH1().contains("Shop Page");
|
||||
//eslint-disable-next-line
|
||||
cy.wait(3000);
|
||||
cy.get(".home-menu-link").contains("Home 3000");
|
||||
cy.get(".home-menu-link").click();
|
||||
cy.wait(2000);
|
||||
cy.url().should("include", "/");
|
||||
cy.wait(700);
|
||||
getH1().contains("This is SPA combined");
|
||||
});
|
||||
});
|
||||
|
||||
describe("3000-home/checkout", () => {
|
||||
beforeEach(() => {
|
||||
cy.visit("/checkout");
|
||||
cy.visit("/");
|
||||
cy.visit("/checkout");
|
||||
});
|
||||
|
||||
describe("Welcome message", () => {
|
||||
it("should display welcome message", () => {
|
||||
getH1().contains("checkout page");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Tag checks", () => {
|
||||
it("should check that a .description + pre tag exists", () => {
|
||||
cy.get(".description").should("exist");
|
||||
cy.get("main pre").should("exist");
|
||||
});
|
||||
});
|
||||
|
||||
describe("3000-home/checkout/test-title", () => {
|
||||
beforeEach(() => cy.visit("/checkout/test-title"));
|
||||
|
||||
it("should display welcome message", () => {
|
||||
getH3().contains("This title came");
|
||||
});
|
||||
});
|
||||
|
||||
describe("3000-home/checkout/test-check-button", () => {
|
||||
beforeEach(() => cy.visit("/checkout/test-check-button"));
|
||||
|
||||
it("should display welcome message", () => {
|
||||
cy.get("button").contains("Button");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("3000-home/shop", () => {
|
||||
beforeEach(() => cy.visit("/shop"));
|
||||
|
||||
describe("Welcome message", () => {
|
||||
it("should display welcome message", () => {
|
||||
getH1().contains("Shop Page");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Image checks", () => {
|
||||
xit("should check that shop-webpack-png images are not 404", () => {
|
||||
// Get the src attribute of the shop-webpack-png image
|
||||
cy.get("img.shop-webpack-png")
|
||||
.invoke("attr", "src")
|
||||
.then((src) => {
|
||||
// Send a GET request to the src URL
|
||||
cy.request(src).its("status").should("eq", 200);
|
||||
});
|
||||
});
|
||||
it("should check that shop-webpack-png images are not 404 between route clicks", () => {
|
||||
cy.visit("/");
|
||||
cy.visit("/shop");
|
||||
cy.url().should("include", "/shop");
|
||||
getH1().contains("Shop Page");
|
||||
cy.get(".home-menu-link").click();
|
||||
//eslint-disable-next-line
|
||||
cy.wait(2999);
|
||||
cy.get("img.shop-webpack-png")
|
||||
.invoke("attr", "src")
|
||||
.then((src) => {
|
||||
// Send a GET request to the src URL
|
||||
cy.request(src).its("status").should("eq", 200);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("Tag checks", () => {
|
||||
it("should check that a .description + pre tag exists", () => {
|
||||
cy.get(".description + pre").should("exist");
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"name": "Using fixtures to represent data",
|
||||
"email": "hello@cypress.io",
|
||||
"body": "Fixtures are a great way to mock data for responses to routes"
|
||||
}
|
3
examples/federation-single-app/cypress/support/app.po.ts
Normal file
3
examples/federation-single-app/cypress/support/app.po.ts
Normal file
|
@ -0,0 +1,3 @@
|
|||
export const getH1 = () => cy.get("h1");
|
||||
export const getH2 = () => cy.get("h2");
|
||||
export const getH3 = () => cy.get("h3");
|
35
examples/federation-single-app/cypress/support/commands.ts
Normal file
35
examples/federation-single-app/cypress/support/commands.ts
Normal file
|
@ -0,0 +1,35 @@
|
|||
/// <reference types="cypress" />
|
||||
|
||||
// ***********************************************
|
||||
// This example commands.ts shows you how to
|
||||
// create various custom commands and overwrite
|
||||
// existing commands.
|
||||
//
|
||||
// For more comprehensive examples of custom
|
||||
// commands please read more here:
|
||||
// https://on.cypress.io/custom-commands
|
||||
// ***********************************************
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-namespace
|
||||
declare namespace Cypress {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
interface Chainable<Subject> {
|
||||
login(email: string, password: string): void;
|
||||
}
|
||||
}
|
||||
|
||||
// -- This is a parent command --
|
||||
Cypress.Commands.add("login", (email, password) => {
|
||||
console.log("Custom command example: Login", email, password);
|
||||
});
|
||||
//
|
||||
// -- This is a child command --
|
||||
// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This is a dual command --
|
||||
// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This will overwrite an existing command --
|
||||
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
|
17
examples/federation-single-app/cypress/support/e2e.ts
Normal file
17
examples/federation-single-app/cypress/support/e2e.ts
Normal file
|
@ -0,0 +1,17 @@
|
|||
// ***********************************************************
|
||||
// This example support/e2e.ts is processed and
|
||||
// loaded automatically before your test files.
|
||||
//
|
||||
// This is a great place to put global configuration and
|
||||
// behavior that modifies Cypress.
|
||||
//
|
||||
// You can change the location of this file or turn off
|
||||
// automatically serving support files with the
|
||||
// 'supportFile' configuration option.
|
||||
//
|
||||
// You can read more here:
|
||||
// https://on.cypress.io/configuration
|
||||
// ***********************************************************
|
||||
|
||||
// Import commands.ts using ES2015 syntax:
|
||||
import "./commands";
|
20
examples/federation-single-app/cypress/tsconfig.json
Normal file
20
examples/federation-single-app/cypress/tsconfig.json
Normal file
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"extends": "../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"allowJs": true,
|
||||
"outDir": "../../dist/out-tsc",
|
||||
"module": "commonjs",
|
||||
"types": ["cypress", "node"],
|
||||
"sourceMap": false
|
||||
},
|
||||
"include": [
|
||||
"**/*.ts",
|
||||
"**/*.js",
|
||||
"../cypress.config.ts",
|
||||
"../**/*.cy.ts",
|
||||
"../**/*.cy.tsx",
|
||||
"../**/*.cy.js",
|
||||
"../**/*.cy.jsx",
|
||||
"../**/*.d.ts"
|
||||
]
|
||||
}
|
5
examples/federation-single-app/next-env.d.ts
vendored
Normal file
5
examples/federation-single-app/next-env.d.ts
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
/// <reference types="next" />
|
||||
/// <reference types="next/image-types/global" />
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/basic-features/typescript for more information.
|
71
examples/federation-single-app/next.config.js
Normal file
71
examples/federation-single-app/next.config.js
Normal file
|
@ -0,0 +1,71 @@
|
|||
const { withNx } = require("@nx/next/plugins/with-nx");
|
||||
const NextFederationPlugin = require("@module-federation/nextjs-mf");
|
||||
|
||||
/**
|
||||
* @type {import('@nx/next/plugins/with-nx').WithNxOptions}
|
||||
**/
|
||||
const nextConfig = {
|
||||
nx: {
|
||||
// Set this to true if you would like to to use SVGR
|
||||
// See: https://github.com/gregberge/svgr
|
||||
svgr: false,
|
||||
},
|
||||
webpack(config, options) {
|
||||
const { isServer } = options;
|
||||
config.watchOptions = {
|
||||
ignored: ["**/node_modules/**", "**/@mf-types/**"],
|
||||
};
|
||||
// used for testing build output snapshots
|
||||
const remotes = {
|
||||
checkout: `checkout@http://localhost:3002/_next/static/${
|
||||
isServer ? "ssr" : "chunks"
|
||||
}/remoteEntry.js`,
|
||||
home_app: `home_app@http://localhost:3000/_next/static/${
|
||||
isServer ? "ssr" : "chunks"
|
||||
}/remoteEntry.js`,
|
||||
shop: `shop@http://localhost:3001/_next/static/${
|
||||
isServer ? "ssr" : "chunks"
|
||||
}/remoteEntry.js`,
|
||||
};
|
||||
|
||||
config.plugins.push(
|
||||
new NextFederationPlugin({
|
||||
name: "home_app",
|
||||
filename: "static/chunks/remoteEntry.js",
|
||||
remotes: {
|
||||
shop: remotes.shop,
|
||||
checkout: remotes.checkout,
|
||||
},
|
||||
exposes: {
|
||||
"./SharedNav": "./components/SharedNav",
|
||||
"./menu": "./components/menu",
|
||||
},
|
||||
shared: {
|
||||
"lodash/": {},
|
||||
antd: {
|
||||
requiredVersion: "5.19.1",
|
||||
version: "5.19.1",
|
||||
},
|
||||
"@ant-design/": {
|
||||
singleton: true,
|
||||
},
|
||||
},
|
||||
extraOptions: {
|
||||
debug: false,
|
||||
exposePages: true,
|
||||
enableImageLoaderFix: true,
|
||||
enableUrlLoaderFix: true,
|
||||
},
|
||||
}),
|
||||
);
|
||||
config.plugins.push({
|
||||
name: "xxx",
|
||||
apply(compiler) {
|
||||
compiler.options.devtool = false;
|
||||
},
|
||||
});
|
||||
return config;
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = withNx(nextConfig);
|
29
examples/federation-single-app/package.json
Normal file
29
examples/federation-single-app/package.json
Normal file
|
@ -0,0 +1,29 @@
|
|||
{
|
||||
"name": "@module-federation/3000-home",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"next": "latest",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"antd": "5.19.1",
|
||||
"@ant-design/cssinjs": "^1.21.0",
|
||||
"buffer": "5.7.1",
|
||||
"encoding": "0.1.13",
|
||||
"eslint-scope": "7.2.2",
|
||||
"events": "3.3.0",
|
||||
"js-cookie": "3.0.5",
|
||||
"lodash": "4.17.21",
|
||||
"node-fetch": "2.7.0",
|
||||
"schema-utils": "3.3.0",
|
||||
"terser-webpack-plugin": "5.3.10",
|
||||
"typescript": "5.3.3",
|
||||
"upath": "2.0.1",
|
||||
"url": "0.11.3",
|
||||
"util": "0.12.5"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "RSPACK_CONFIG_VALIDATE=loose-silent next",
|
||||
"build": "cross-env RSPACK_CONFIG_VALIDATE=loose-silent NEXT_TELEMETRY_DISABLED=1 node --trace-deprecation --enable-source-maps ../../packages/next/dist/bin/next build"
|
||||
}
|
||||
}
|
70
examples/federation-single-app/pages/_app.tsx
Normal file
70
examples/federation-single-app/pages/_app.tsx
Normal file
|
@ -0,0 +1,70 @@
|
|||
import * as React from "react";
|
||||
import { useState } from "react";
|
||||
import App from "next/app";
|
||||
import { Layout, version, ConfigProvider } from "antd";
|
||||
import { StyleProvider } from "@ant-design/cssinjs";
|
||||
import Router, { useRouter } from "next/router";
|
||||
|
||||
const SharedNav = React.lazy(() => import("../components/SharedNav"));
|
||||
import HostAppMenu from "../components/menu";
|
||||
function MyApp(props) {
|
||||
const { Component, pageProps } = props;
|
||||
const { asPath } = useRouter();
|
||||
const [MenuComponent, setMenuComponent] = useState(() => HostAppMenu);
|
||||
const handleRouteChange = async (url) => {
|
||||
if (url.startsWith("/shop")) {
|
||||
} else if (url.startsWith("/checkout")) {
|
||||
} else {
|
||||
setMenuComponent(() => HostAppMenu);
|
||||
}
|
||||
};
|
||||
// handle first route hit.
|
||||
React.useEffect(() => {
|
||||
handleRouteChange(asPath);
|
||||
}, [asPath]);
|
||||
|
||||
//handle route change
|
||||
React.useEffect(() => {
|
||||
// Step 3: Subscribe on events
|
||||
Router.events.on("routeChangeStart", handleRouteChange);
|
||||
return () => {
|
||||
Router.events.off("routeChangeStart", handleRouteChange);
|
||||
};
|
||||
}, []);
|
||||
return (
|
||||
<StyleProvider layer>
|
||||
<ConfigProvider theme={{ hashed: false }}>
|
||||
<Layout style={{ minHeight: "100vh" }} prefixCls={"dd"}>
|
||||
<React.Suspense>
|
||||
<SharedNav />
|
||||
</React.Suspense>
|
||||
<Layout>
|
||||
<Layout.Sider width={200}>
|
||||
<MenuComponent />
|
||||
</Layout.Sider>
|
||||
<Layout>
|
||||
<Layout.Content style={{ background: "#fff", padding: 20 }}>
|
||||
<Component {...pageProps} />
|
||||
</Layout.Content>
|
||||
<Layout.Footer
|
||||
style={{
|
||||
background: "#fff",
|
||||
color: "#999",
|
||||
textAlign: "center",
|
||||
}}
|
||||
>
|
||||
antd@{version}
|
||||
</Layout.Footer>
|
||||
</Layout>
|
||||
</Layout>
|
||||
</Layout>
|
||||
</ConfigProvider>
|
||||
</StyleProvider>
|
||||
);
|
||||
}
|
||||
|
||||
MyApp.getInitialProps = async (ctx) => {
|
||||
return App.getInitialProps(ctx);
|
||||
};
|
||||
|
||||
export default MyApp;
|
26
examples/federation-single-app/pages/_document.js
Normal file
26
examples/federation-single-app/pages/_document.js
Normal file
|
@ -0,0 +1,26 @@
|
|||
import React from "react";
|
||||
import Document, { Html, Head, Main, NextScript } from "next/document";
|
||||
|
||||
class MyDocument extends Document {
|
||||
static async getInitialProps(ctx) {
|
||||
const initialProps = await Document.getInitialProps(ctx);
|
||||
|
||||
return {
|
||||
...initialProps,
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Html>
|
||||
<Head />
|
||||
<body>
|
||||
<Main />
|
||||
<NextScript />
|
||||
</body>
|
||||
</Html>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default MyDocument;
|
3
examples/federation-single-app/pages/api/test.js
Normal file
3
examples/federation-single-app/pages/api/test.js
Normal file
|
@ -0,0 +1,3 @@
|
|||
export default function handler(req, res) {
|
||||
res.status(200).json({ name: "John Doe" });
|
||||
}
|
19
examples/federation-single-app/pages/home/exposed-pages.tsx
Normal file
19
examples/federation-single-app/pages/home/exposed-pages.tsx
Normal file
|
@ -0,0 +1,19 @@
|
|||
/* eslint-disable */
|
||||
import { useState, useEffect } from "react";
|
||||
|
||||
export default function ExposedPages() {
|
||||
const [pageMap] = useState("");
|
||||
const [pageMapV2] = useState("");
|
||||
|
||||
return (
|
||||
<>
|
||||
<h1>This app exposes the following pages:</h1>
|
||||
|
||||
<h2>./pages-map</h2>
|
||||
<pre>{JSON.stringify(pageMap, undefined, 2)}</pre>
|
||||
|
||||
<h2>./pages-map-v2</h2>
|
||||
<pre>{JSON.stringify(pageMapV2, undefined, 2)}</pre>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
/* eslint-disable */
|
||||
import Link from "next/link";
|
||||
|
||||
export default function TestBrokenRemotes() {
|
||||
return (
|
||||
<div>
|
||||
<h2>This page is a test for broken remoteEntries.js</h2>
|
||||
|
||||
<p>
|
||||
Check unresolved host –{" "}
|
||||
<Link href="/unresolved-host">/unresolved-host</Link> (on
|
||||
http://localhost:<b>3333</b>/_next/static/chunks/remoteEntry.js)
|
||||
</p>
|
||||
<p>
|
||||
Check wrong response for remoteEntry –{" "}
|
||||
<Link href="/wrong-entry">/wrong-entry</Link> (on
|
||||
http://localhost:3000/_next/static/chunks/remoteEntry<b>Wrong</b>
|
||||
.js)
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
import { NextPage } from "next";
|
||||
|
||||
const TestRemoteHook: NextPage = () => {
|
||||
return (
|
||||
<>
|
||||
<div>
|
||||
Page with custom remote hook. You must see text in red box below:
|
||||
</div>
|
||||
<div style={{ border: "1px solid red", padding: 5 }}>
|
||||
blank text for now
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default TestRemoteHook;
|
|
@ -0,0 +1,9 @@
|
|||
import SharedNav from "../../components/SharedNav";
|
||||
|
||||
export default function TestSharedNav() {
|
||||
return (
|
||||
<div>
|
||||
<SharedNav />
|
||||
</div>
|
||||
);
|
||||
}
|
139
examples/federation-single-app/pages/index.tsx
Normal file
139
examples/federation-single-app/pages/index.tsx
Normal file
|
@ -0,0 +1,139 @@
|
|||
/* eslint-disable */
|
||||
import React from "react";
|
||||
import Head from "next/head";
|
||||
|
||||
const Home = () => {
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>Home</title>
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
</Head>
|
||||
|
||||
<h1 style={{ fontSize: "2em" }}>
|
||||
This is SPA combined from 3 different nextjs applications.
|
||||
</h1>
|
||||
<p className="description">
|
||||
They utilize omnidirectional routing and pages or components are able to
|
||||
be federated between applications.
|
||||
</p>
|
||||
<p>You may open any application by clicking on the links below:</p>
|
||||
<ul>
|
||||
<li>
|
||||
<a
|
||||
href="#reloadPage"
|
||||
onClick={() => (window.location.href = "http://localhost:3000")}
|
||||
>
|
||||
localhost:3000
|
||||
</a>
|
||||
{" – "}
|
||||
<b>home</b>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="#reloadPage"
|
||||
onClick={() => (window.location.href = "http://localhost:3001")}
|
||||
>
|
||||
localhost:3001
|
||||
</a>
|
||||
{" – "}
|
||||
<b>shop</b>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="#reloadPage"
|
||||
onClick={() => (window.location.href = "http://localhost:3002")}
|
||||
>
|
||||
localhost:3002
|
||||
</a>
|
||||
{" – "}
|
||||
<b>checkout</b>
|
||||
</li>
|
||||
</ul>
|
||||
<h2 style={{ marginTop: "30px" }}>Federation test cases</h2>
|
||||
<table border={1} cellPadding={5}>
|
||||
<thead>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>Test case</td>
|
||||
<td>Expected</td>
|
||||
<td>Actual</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>✅</td>
|
||||
<td>
|
||||
Loading remote component (CheckoutTitle) from localhost:3002
|
||||
<br />
|
||||
<blockquote>
|
||||
lazy(()=>import('checkout/CheckoutTitle'))
|
||||
</blockquote>
|
||||
</td>
|
||||
<td>
|
||||
<h3>This title came from checkout with hooks data!!!</h3>
|
||||
</td>
|
||||
<td>old</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>✅</td>
|
||||
<td>
|
||||
Load federated component from checkout with old antd version
|
||||
</td>
|
||||
<td>[Button from antd@5.18.3]</td>
|
||||
<td>test</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>✅</td>
|
||||
<td>
|
||||
Loading remote component with PNG image from localhost:3001
|
||||
<br />
|
||||
<blockquote>(check publicPath fix in image-loader)</blockquote>
|
||||
</td>
|
||||
<td>
|
||||
<img className="home-webpack-png" src="./webpack.png" />
|
||||
</td>
|
||||
<td>other thing</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>✅</td>
|
||||
<td>
|
||||
Loading remote component with SVG from localhost:3001
|
||||
<br />
|
||||
<blockquote>(check publicPath fix in url-loader)</blockquote>
|
||||
</td>
|
||||
<td>
|
||||
<img src="./webpack.svg" />
|
||||
</td>
|
||||
<td>anothaaaaaa</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h2 style={{ marginTop: "30px" }}>Other problems to fix:</h2>
|
||||
<ul>
|
||||
<li>
|
||||
🐞 Incorrectly exposed modules in next.config.js (e.g. typo in path)
|
||||
do not throw an error in console
|
||||
</li>
|
||||
<li>
|
||||
📝 Try to introduce a remote entry loading according to prefix path.
|
||||
It will be nice runtime improvement if you have eg 20 apps and load
|
||||
just one remoteEntry instead of all of them.
|
||||
</li>
|
||||
<li>
|
||||
📝 It will be nice to regenerate remoteEntry if new page was added in
|
||||
remote app.
|
||||
</li>
|
||||
<li>
|
||||
📝 Remote components do not regenerate chunks if they were changed.
|
||||
</li>
|
||||
</ul>
|
||||
</>
|
||||
);
|
||||
};
|
||||
Home.getInitialProps = () => {
|
||||
console.log("home calls get initial props");
|
||||
return {};
|
||||
};
|
||||
export default Home;
|
0
examples/federation-single-app/public/.gitkeep
Normal file
0
examples/federation-single-app/public/.gitkeep
Normal file
BIN
examples/federation-single-app/public/favicon.ico
Normal file
BIN
examples/federation-single-app/public/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
BIN
examples/federation-single-app/public/webpack.png
Normal file
BIN
examples/federation-single-app/public/webpack.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 14 KiB |
5
examples/federation-single-app/public/webpack.svg
Normal file
5
examples/federation-single-app/public/webpack.svg
Normal file
|
@ -0,0 +1,5 @@
|
|||
<svg viewBox="0 0 3046.7 875.7" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="m387 0 387 218.9v437.9l-387 218.9-387-218.9v-437.9z" fill="#fff"/>
|
||||
<path d="m704.9 641.7-305.1 172.6v-134.4l190.1-104.6zm20.9-18.9v-360.9l-111.6 64.5v232zm-657.9 18.9 305.1 172.6v-134.4l-190.2-104.6zm-20.9-18.9v-360.9l111.6 64.5v232zm13.1-384.3 312.9-177v129.9l-200.5 110.3-1.6.9zm652.6 0-312.9-177v129.9l200.5 110.2 1.6.9z" fill="#8ed6fb"/>
|
||||
<path d="m373 649.3-187.6-103.2v-204.3l187.6 108.3zm26.8 0 187.6-103.1v-204.4l-187.6 108.3zm-201.7-331.1 188.3-103.5 188.3 103.5-188.3 108.7z" fill="#1c78c0"/>
|
||||
</svg>
|
After Width: | Height: | Size: 592 B |
5
examples/federation-single-app/remotes.d.ts
vendored
Normal file
5
examples/federation-single-app/remotes.d.ts
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
declare module "shop/useCustomRemoteHook";
|
||||
declare module "shop/WebpackSvg";
|
||||
declare module "shop/WebpackPng";
|
||||
declare module "checkout/CheckoutTitle";
|
||||
declare module "checkout/ButtonOldAnt";
|
19
examples/federation-single-app/tsconfig.json
Normal file
19
examples/federation-single-app/tsconfig.json
Normal file
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"target": "es2017",
|
||||
"jsx": "preserve",
|
||||
"module": "esnext",
|
||||
"allowJs": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": false,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noEmit": true,
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"incremental": true
|
||||
},
|
||||
"include": ["**/*.ts", "**/*.tsx", "**/*.js", "**/*.jsx", "next-env.d.ts"],
|
||||
"exclude": ["node_modules", "jest.config.ts"]
|
||||
}
|
Loading…
Reference in a new issue