Skip to content

Commit

Permalink
feat: add automic page-map generation (module-federation#101)
Browse files Browse the repository at this point in the history
* refactor(demo): move `3002-checkout` pages to 'src/pages/' folder for testing different nextjs behavior

* feat: add automic page map creation if `./pages-map` entry does not provided in next.config.js

* chore(demo): add page `exposed-pages` in every app

* refactor: fix Menu restoration in main app; clear console.log in package

* refactor: run prettier on all codebase

* refactor: add `extraOptions.exposePages: boolean` option for automatically addition of nextjs pages and their pageMap to NextFederationPlugin

* demo: add some notes about problems on main page of demo app

* fix: problem with image/url loader when `__webpack_require__.p` does not contain hostname

* demo: small cleanup

* flip options to enable from disable

* update build process

* update package build ref

* chore: add lib folder to .gitignore

* refactor(demo): switch on new options enableImageLoaderFix: true, enableUrlLoaderFix: true,

* docs: add info about `extraOptions`

* docs: small improvements

* docs: small improvements

* fix: use endsWith to detect .js/.mjs file paths (module-federation#102)

Co-authored-by: ScriptedAlchemy <[email protected]>
Co-authored-by: Elad Ossadon <[email protected]>
  • Loading branch information
3 people authored Aug 22, 2022
1 parent ee34763 commit 87b61a2
Show file tree
Hide file tree
Showing 59 changed files with 4,048 additions and 519 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -104,4 +104,4 @@ dist
# TernJS port file
.tern-port
/.idea/
/yarn.lock
lib/
1 change: 1 addition & 0 deletions .npmignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ demo
src
rollup.config.js
*.log
/yarn.lock
2 changes: 2 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.next
lib
8 changes: 7 additions & 1 deletion .prettierrc
Original file line number Diff line number Diff line change
@@ -1 +1,7 @@
{}
{
"singleQuote": true,
"arrowParens": "always",
"tabWidth": 2,
"useTabs": false,
"trailingComma": "es5"
}
139 changes: 80 additions & 59 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,49 +23,49 @@ You do not need to share these packages, sharing next internals yourself will ca

```js
const DEFAULT_SHARE_SCOPE = {
react: {
singleton: true,
requiredVersion: false,
},
"react/": {
singleton: true,
requiredVersion: false,
},
"react-dom": {
singleton: true,
requiredVersion: false,
},
"next/dynamic": {
requiredVersion: false,
singleton: true,
},
"styled-jsx": {
requiredVersion: false,
singleton: true,
},
"next/link": {
requiredVersion: false,
singleton: true,
},
"next/router": {
requiredVersion: false,
singleton: true,
},
"next/script": {
requiredVersion: false,
singleton: true,
},
"next/head": {
requiredVersion: false,
singleton: true,
},
react: {
singleton: true,
requiredVersion: false,
},
'react/': {
singleton: true,
requiredVersion: false,
},
'react-dom': {
singleton: true,
requiredVersion: false,
},
'next/dynamic': {
requiredVersion: false,
singleton: true,
},
'styled-jsx': {
requiredVersion: false,
singleton: true,
},
'next/link': {
requiredVersion: false,
singleton: true,
},
'next/router': {
requiredVersion: false,
singleton: true,
},
'next/script': {
requiredVersion: false,
singleton: true,
},
'next/head': {
requiredVersion: false,
singleton: true,
},
};
```

## Usage

```js
const SampleComponent = dynamic(() => import("next2/sampleComponent"), {
const SampleComponent = dynamic(() => import('next2/sampleComponent'), {
ssr: false,
});
```
Expand All @@ -78,10 +78,10 @@ With async boundary installed at the page level. You can then do the following

```js
if (process.browser) {
const SomeHook = require("next2/someHook");
const SomeHook = require('next2/someHook');
}
// if client only file
import SomeComponent from "next2/someComponent";
import SomeComponent from 'next2/someComponent';
```

Make sure you are using `mini-css-extract-plugin@2` - version 2 supports resolving assets through `publicPath:'auto'`
Expand All @@ -93,7 +93,7 @@ You can see it in action here: https://github.com/module-federation/module-feder
## Usage

```js
const SampleComponent = dynamic(() => import("next2/sampleComponent"), {
const SampleComponent = dynamic(() => import('next2/sampleComponent'), {
ssr: false,
});
```
Expand All @@ -106,10 +106,10 @@ With async boundary installed at the page level. You can then do the following

```js
if (process.browser) {
const SomeHook = require("next2/someHook");
const SomeHook = require('next2/someHook');
}
// if client only file
import SomeComponent from "next2/someComponent";
import SomeComponent from 'next2/someComponent';
```

Make sure you are using `mini-css-extract-plugin@2` - version 2 supports resolving assets through `publicPath:'auto'`
Expand All @@ -119,6 +119,27 @@ Make sure you are using `mini-css-extract-plugin@2` - version 2 supports resolvi
This plugin works exactly like ModuleFederationPlugin, use it as you'd normally.
Note that we already share react and next stuff for you automatically.

Also NextFederationPlugin has own optional argument `extraOptions` where you can unlock additional features of this plugin:

```js
new NextFederationPlugin({
name: ...,
filename: ...,
remotes: ...,
exposes: ...,
shared: ...,
extraOptions: {
exposePages: true, // `false` by default
enableImageLoaderFix: true, // `false` by default
enableUrlLoaderFix: true, // `false` by default
},
});
```

- `exposePages` – exposes automatically all nextjs pages for you and theirs `./pages-map`.
- `enableImageLoaderFix` – adds public hostname to all assets bundled by `nextjs-image-loader`. So if you serve remoteEntry from `http://example.com` then all bundled assets will get this hostname in runtime. It's something like Base URL in HTML but for federated modules.
- `enableUrlLoaderFix` – adds public hostname to all assets bundled by `url-loader`.

## Demo

You can see it in action here: https://github.com/module-federation/module-federation-examples/pull/2147
Expand Down Expand Up @@ -149,7 +170,7 @@ module.exports = {
shared: {
// whatever else
},
}),
})
);
}

Expand All @@ -160,11 +181,10 @@ module.exports = {
// _app.js or some other file in as high up in the app (like next's new layouts)
// this ensures various parts of next.js are imported and "used" somewhere so that they wont be tree shaken out
import '@module-federation/nextjs-mf/lib/include-defaults';

```

2. For the consuming application, we'll call it "next1", add an instance of the ModuleFederationPlugin to your webpack config, and ensure you have a [custom Next.js App](https://nextjs.org/docs/advanced-features/custom-app) `pages/_app.js` (or `.tsx`):
Inside that _app.js or layout.js file, ensure you import `include-defaults` file
Inside that \_app.js or layout.js file, ensure you import `include-defaults` file

```js
// next.config.js
Expand All @@ -180,7 +200,7 @@ module.exports = {
remotes: {
next2: `next2@http://localhost:3000/_next/static/chunks/remoteEntry.js`,
},
}),
})
);
}

Expand All @@ -191,46 +211,47 @@ module.exports = {
// _app.js or some other file in as high up in the app (like next's new layouts)
// this ensures various parts of next.js are imported and "used" somewhere so that they wont be tree shaken out
import '@module-federation/nextjs-mf/lib/include-defaults';

```

4. Use next/dynamic or low level api to import remotes.

```js
import dynamic from "next/dynamic";
import dynamic from 'next/dynamic';

const SampleComponent = dynamic(
() => window.next2.get("./sampleComponent").then((factory) => factory()),
() => window.next2.get('./sampleComponent').then((factory) => factory()),
{
ssr: false,
}
);

// or

const SampleComponent = dynamic(() => import("next2/sampleComponent"), {
const SampleComponent = dynamic(() => import('next2/sampleComponent'), {
ssr: false,
});
```

## Utilities

Ive added a util for dynamic chunk loading, in the event you need to load remote containers dynamically.
Ive added a util for dynamic chunk loading, in the event you need to load remote containers dynamically.

```js
import {injectScript} from '@module-federation/nextjs-mf/lib/utils';
import { injectScript } from '@module-federation/nextjs-mf/lib/utils';
// if i have remotes in my federation plugin, i can pass the name of the remote
injectScript('home').then((remoteContainer)=>{
remoteContainer.get('./exposedModule')
})
injectScript('home').then((remoteContainer) => {
remoteContainer.get('./exposedModule');
});
// if i want to load a custom remote not known at build time.

injectScript({global:'home', url:'http://somthing.com/remoteEntry.js'}).then((remoteContainer)=>{
remoteContainer.get('./exposedModule')
})
injectScript({
global: 'home',
url: 'http://somthing.com/remoteEntry.js',
}).then((remoteContainer) => {
remoteContainer.get('./exposedModule');
});
```


## Contact

If you have any questions or need to report a bug
Expand Down
18 changes: 8 additions & 10 deletions demo/3000-home/next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,19 @@ module.exports = {
remotes: {
home: 'home@http://localhost:3000/_next/static/chunks/remoteEntry.js',
shop: 'shop@http://localhost:3001/_next/static/chunks/remoteEntry.js',
checkout: 'checkout@http://localhost:3002/_next/static/chunks/remoteEntry.js',
checkout:
'checkout@http://localhost:3002/_next/static/chunks/remoteEntry.js',
},
exposes: {
// pages
'./pages/index': './pages/index.js',
'./pages/home/test-remote-hook': './pages/home/test-remote-hook.js',
'./pages/home/test-shared-nav': './pages/home/test-shared-nav.js',
// components
'./SharedNav': './components/SharedNav.js',
// utilities
'./menu': './pages/_menu.js',
'./pages-map': './pages-map.js',
},
shared: {},
}),
extraOptions: {
exposePages: true,
enableImageLoaderFix: true,
enableUrlLoaderFix: true,
},
})
);
}

Expand Down
5 changes: 0 additions & 5 deletions demo/3000-home/pages-map.js

This file was deleted.

26 changes: 4 additions & 22 deletions demo/3000-home/pages/_app.js
Original file line number Diff line number Diff line change
@@ -1,35 +1,17 @@
require('@module-federation/nextjs-mf/lib/include-defaults');
// if(process.browser && (typeof __webpack_share_scopes__ === "undefined" || !__webpack_share_scopes__.default)) {
// __webpack_init_sharing__('default');
// }
// require('next/link')
// require('next/router')
// require('next/head')
// require('next/script')
// require('next/dynamic')
// require('styled-jsx')
//
// require('next/dynamic')
// require('next/head')
// require('next/link')
// require('next/router')
// require('next/script')
// require('react')
if (process.env.NODE_ENV === 'development') {
require('react/jsx-dev-runtime');
}

// console.log(__webpack_init_sharing__('default'));
// console.log(__webpack_share_scopes__);

console.log(__webpack_share_scopes__);
import dynamic from 'next/dynamic';
import 'antd/dist/antd.css';

const page = import('./_app.real');
const AppPage = dynamic(() => import('./_app.real'));
const Page = props => {
const Page = (props) => {
return <AppPage {...props} />;
};
Page.getInitialProps = async ctx => {
Page.getInitialProps = async (ctx) => {
const getInitialProps = (await page).default?.getInitialProps;
if (getInitialProps) {
return getInitialProps(ctx);
Expand Down
15 changes: 13 additions & 2 deletions demo/3000-home/pages/_app.real.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useEffect, useState } from 'react';
import App from 'next/app';
import { Layout, version } from 'antd';
import { useRouter } from 'next/router';
import AppMenu from './_menu';
import SharedNav from '../components/SharedNav';

Expand All @@ -16,6 +17,14 @@ function MyApp({ Component, pageProps }) {
};
}, []);

// Return back App menu if federated page does not used
const { pathname } = useRouter();
useEffect(() => {
if (pathname !== '/[...federatedPage]' && MenuComponent !== AppMenu) {
setMenuComponent(() => AppMenu);
}
}, [pathname]);

return (
<Layout style={{ minHeight: '100vh' }}>
<SharedNav />
Expand All @@ -27,7 +36,9 @@ function MyApp({ Component, pageProps }) {
<Layout.Content style={{ background: '#fff', padding: 20 }}>
<Component {...pageProps} />
</Layout.Content>
<Layout.Footer style={{ background: '#fff', color: '#999', textAlign: 'center' }}>
<Layout.Footer
style={{ background: '#fff', color: '#999', textAlign: 'center' }}
>
antd@{version}
</Layout.Footer>
</Layout>
Expand All @@ -36,7 +47,7 @@ function MyApp({ Component, pageProps }) {
);
}

MyApp.getInitialProps = async ctx => {
MyApp.getInitialProps = async (ctx) => {
const appProps = await App.getInitialProps(ctx);
return appProps;
};
Expand Down
1 change: 1 addition & 0 deletions demo/3000-home/pages/_document.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import Document, { Html, Head, Main, NextScript } from 'next/document';

class MyDocument extends Document {
static async getInitialProps(ctx) {
const initialProps = await Document.getInitialProps(ctx);
Expand Down
Loading

0 comments on commit 87b61a2

Please sign in to comment.