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

Next medusa delegates #2793

Open
wants to merge 17 commits into
base: master
Choose a base branch
from
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions apollo-client/package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"name": "apollo-client",
"version": "1.0.0",
"private": true,
"workspaces": [
"app1",
Expand Down
63 changes: 63 additions & 0 deletions nextjs-ssr-medusa/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# Next.js with Module Federation


## Getting Started

2. run `yarn start` and browse to `http://localhost:3001` or one of the others

# We are available to consult

Contact me <a href="mailto:[email protected]">[email protected]</a> or <a href="https://twitter.com/scriptedalchemy">@ScriptedAlchemy</a> on Twitter


## How it works?!

This implementaion leverages our propriatery _Software Streams_ which allow me to stream commonjs modules at runtime to consuming apps.
We have never made software streaming avaliable to the general public, while we have used it for 2 years and run several backends off the technology - its remained a heavily guarded secret. Software Streams is how SSR works, on the client side we are using enhanced federation interfaces to ensure that the top-level api works as expected.

It should allow `import()`, `require`, `import from` to work - this has been tested serverside but i have not yet tested anything else other than import() on the client.

There has been a leaked copy of an alpha from a year and a half ago for software streams. While it does work, there are several security flaws. The federation group has spend signigicant amounts of time enhancing streaming.

In the future, when this plugin is out of beta - we are planning to build in stream encryption to ensure that code streamed has not been manipulated in any way.
This would rely on a salted cypher key that consumer and remote would know at build time.

We are also looking into running streamed software in a WASM isolate that cannot perform any damage, has no access to host resources. This would make it possible to execute untrusted code.

For the time being - I strongly suggest only federating trusted software between servers.

## Security

In order to make this plugin work right out the box, the commonjs modules are exposed via `_next/static/ssr*` i strongly suggest having a CDN or piece of middleware that only allows access to this path from internal network or VPN. You do not want the public internet to be able to reach that path. You are exposing server code, where `process.browser` is not applied to tree shake server secrets since this is server code.

## Context

We have three next.js applications

- `checkout` - port 3000
- `home` - port 3001
- `shop` - port 3002

The applications utilize omnidirectional routing and pages or components are able to be federated between applications like a SPA

I am using hooks here to ensure multiple copies of react are not loaded into scope on server or client.

The omnidirectional routing now hooks into webpack federation loading functions, so when dynamically loading remotes - you can use the same functions that webpack uses to load remotes when theres a know static import like `home/title`

I am using hooks here to ensure multiple copies of react are not loaded into scope on server or client.

### Sharing

Next.js has all its internal modules pre-shared vis `@module-federation/nextjs-mf` you do need to share react via the plugin in order to ensure that the share scope runtime requirements are included - since you cannot share modules in a normal manner, like nextjs internls, the pre-shared modules are attached at runtime to the share scope. Any exta code you share is processed via the plugin which reconfigures sharing properly to work with next.js limitations.

The sharing limit is due to next not having any async boundary, theres no way to "pause" the application while webpack orchestrates share scope.

I am investigating new methods that may solve the module sharing problem in next.js, however this is a complex problem to solve and requires enormus amounts of knowladge around how webpack and federation work inside the module graph.

# Running Cypress E2E Tests

To run tests in interactive mode, run `npm run cypress:debug` from the root directory of the project. It will open Cypress Test Runner and allow to run tests in interactive mode. [More info about "How to run tests"](../../cypress/README.md#how-to-run-tests)

To build app and run test in headless mode, run `yarn e2e:ci`. It will build app and run tests for this workspace in headless mode. If tets failed cypress will create `cypress` directory in sample root folder with screenshots and videos.

["Best Practices, Rules amd more interesting information here](../../cypress/README.md)
18 changes: 18 additions & 0 deletions nextjs-ssr-medusa/checkout/components/exposedTitle.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import React, { useEffect } from 'react';
const ExportredTitle = () => {
console.log('---------loading remote component---------');
useEffect(() => {
console.log('HOOKS WORKS');
}, []);
return (
<div className="hero">
<h1 className="title">
{' '}
This came fom <code>checkout</code> !!!
</h1>
<p className="description">And it works like a charm v2</p>
</div>
);
};

export default ExportredTitle;
259 changes: 259 additions & 0 deletions nextjs-ssr-medusa/checkout/dashboard-merged.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,259 @@
{
"dependencies": [
{
"name": "@module-federation/nextjs-mf",
"version": "6.2.0",
"license": "MIT",
"size": 616
},
{
"name": "@module-federation/dashboard-plugin",
"version": "2.8.0-beta"
},
{
"name": "lodash",
"version": "4.17"
},
{
"name": "next",
"version": "12.2.2",
"license": "MIT",
"size": 24288
},
{
"name": "react",
"version": "18.2.0",
"license": "MIT",
"size": 1073
},
{
"name": "react-dom",
"version": "18.2"
},
{
"name": "webpack-merge",
"version": "5.8"
},
{
"name": "react-dom",
"version": "18.2.0",
"license": "MIT",
"size": 133719
}
],
"devDependencies": [],
"optionalDependencies": [],
"id": "checkout",
"name": "checkout",
"remote": "http://localhost:3001/_next/static/chunks/remoteEntry.js",
"metadata": {
"baseUrl": "http://localhost:3000/_next/static/chunks/",
"source": {
"url": "https://github.com/module-federation/federation-dashboard/tree/master/dashboard-example/home"
},
"remote": "http://localhost:3001/_next/static/chunks/remoteEntry.js"
},
"overrides": [
{
"id": "next/dynamic",
"name": "next/dynamic",
"version": "12.2.2",
"location": "next/dynamic",
"applicationID": "next/dynamic"
},
{
"id": "next/head",
"name": "next/head",
"version": "12.2.2",
"location": "next/head",
"applicationID": "next/head"
},
{
"id": "next/link",
"name": "next/link",
"version": "12.2.2",
"location": "next/link",
"applicationID": "next/link"
},
{
"id": "next/router",
"name": "next/router",
"version": "12.2.2",
"location": "next/router",
"applicationID": "next/router"
},
{
"id": "next/script",
"name": "next/script",
"version": "12.2.2",
"location": "next/script",
"applicationID": "next/script"
},
{
"id": "react-dom",
"name": "react-dom",
"version": "18.2.0",
"location": "react-dom",
"applicationID": "react-dom"
},
{
"id": "react/jsx-runtime",
"name": "react/jsx-runtime",
"version": "18.2.0",
"location": "react/jsx-runtime",
"applicationID": "react/jsx-runtime"
},
{
"id": "react",
"name": "react",
"version": "18.2.0",
"location": "react",
"applicationID": "react"
},
{
"id": "styled-jsx/style",
"name": "styled-jsx/style",
"version": "5.0.2",
"location": "styled-jsx/style",
"applicationID": "styled-jsx/style"
},
{
"id": "styled-jsx",
"name": "styled-jsx",
"version": "5.0.2",
"location": "styled-jsx",
"applicationID": "styled-jsx"
},
{
"id": "react/jsx-runtime",
"name": "react/jsx-runtime",
"version": "",
"location": "react/jsx-runtime",
"applicationID": "checkout"
},
{
"id": "react",
"name": "react",
"version": "18.2.0",
"location": "react",
"applicationID": "checkout"
},
{
"id": "next/head",
"name": "next/head",
"version": "",
"location": "next/head",
"applicationID": "checkout"
},
{
"id": "styled-jsx/style",
"name": "styled-jsx/style",
"version": "",
"location": "styled-jsx/style",
"applicationID": "checkout"
},
{
"id": "react-dom",
"name": "react-dom",
"version": "18.2",
"location": "react-dom",
"applicationID": "checkout"
},
{
"id": "next/link",
"name": "next/link",
"version": "",
"location": "next/link",
"applicationID": "checkout"
},
{
"id": "next/router",
"name": "next/router",
"version": "",
"location": "next/router",
"applicationID": "checkout"
},
{
"id": "next/script",
"name": "next/script",
"version": "",
"location": "next/script",
"applicationID": "checkout"
},
{
"id": "next/dynamic",
"name": "next/dynamic",
"version": "",
"location": "next/dynamic",
"applicationID": "checkout"
},
{
"id": "styled-jsx",
"name": "styled-jsx",
"version": "",
"location": "styled-jsx",
"applicationID": "checkout"
}
],
"consumes": [
{
"consumingApplicationID": "shop",
"applicationID": "shop",
"name": "shop",
"usedIn": [
{
"file": "pages/shop.js?hasBoundary",
"url": "https://github.com/module-federation/federation-dashboard/tree/master/dashboard-example/home/pages/shop.js?hasBoundary"
}
]
},
{
"consumingApplicationID": "home",
"applicationID": "home",
"name": "home",
"usedIn": [
{
"file": "pages/index.js?hasBoundary",
"url": "https://github.com/module-federation/federation-dashboard/tree/master/dashboard-example/home/pages/index.js?hasBoundary"
}
]
},
{
"consumingApplicationID": "nav",
"applicationID": "home",
"name": "nav",
"usedIn": [
{
"file": "pages/_app.js?hasBoundary",
"url": "https://github.com/module-federation/federation-dashboard/tree/master/dashboard-example/home/pages/_app.js?hasBoundary"
}
]
}
],
"modules": [
{
"id": "title:title",
"name": "title",
"applicationID": "title",
"requires": [],
"file": "./components/exposedTitle.js"
},
{
"id": "checkout:checkout",
"name": "checkout",
"applicationID": "checkout",
"requires": [],
"file": "./pages/checkout.js"
},
{
"id": "pages-map:pages-map",
"name": "pages-map",
"applicationID": "pages-map",
"requires": [],
"file": "./pages-map.js"
}
],
"version": "a5a5578d46687e2910bdedc88efe2289c2afb168",
"sha": "a5a5578d46687e2910bdedc88efe2289c2afb168",
"buildHash": "ced4eae489de5011"
}