Skip to content

Commit

Permalink
module-federation support
Browse files Browse the repository at this point in the history
  • Loading branch information
mmarifat committed Jan 31, 2023
0 parents commit 8d4dcbd
Show file tree
Hide file tree
Showing 30 changed files with 726 additions and 0 deletions.
29 changes: 29 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
module.exports = {
parser: "@typescript-eslint/parser",
parserOptions: {
files: ["*.ts", "*.tsx"],
project: "tsconfig.json",
sourceType: "module"
},
plugins: ["@typescript-eslint/eslint-plugin", "prettier"],
extends: [
"next",
"plugin:@typescript-eslint/recommended",
"plugin:prettier/recommended"
],
root: true,
env: {
node: true,
jest: true
},
ignorePatterns: [".eslintrc.js"],
rules: {
"@typescript-eslint/interface-name-prefix": "off",
"@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/explicit-module-boundary-types": "off",
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-namespace": "off",
"@typescript-eslint/no-unused-vars": "off",
"@next/next/no-img-element": "off"
}
};
45 changes: 45 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.js
yarn-error.log
yarn.lock
package-lock.json

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*

# local env files
.env*.local
process.json

# vercel
.vercel

# typescript
*.tsbuildinfo

# idea
.idea
.vscode

/.env
4 changes: 4 additions & 0 deletions .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

yarn format
1 change: 1 addition & 0 deletions .npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
engine-strict = true
13 changes: 13 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"printWidth": 150,
"trailingComma": "all",
"singleQuote": true,
"semi": true,
"jsxSingleQuote": false,
"quoteProps": "as-needed",
"bracketSpacing": true,
"bracketSameLine": true,
"useTabs": false,
"tabWidth": 4,
"arrowParens": "avoid"
}
57 changes: 57 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
This is micro-frontend driven Email Builder Application

## Used libraries

```bash
1. @module-federation/nextjs-mf
2. antd
3. react-email-editor
4. sass
5. tailwindcss
6. typescript
```

## Getting Started

To run locally:

```bash
yarn dev
# or
npm run dev
```

## How to use?

=> With NextJs

```bash
1. Install dependencies
yarn add @module-federation/nextjs-mf [email protected]
#or
npm i @module-federation/nextjs-mf [email protected]

2. Add to next.config.js
module.exports = {
webpack(config, options) {
const { webpack, isServer } = options;
Object.assign(config.experiments, { topLevelAwait: true });
config.plugins.push(
new NextFederationPlugin({
name: 'your-project-name',
filename: 'static/chunks/remoteEntry.js',
remotes: {
microEmailBuilder: `microEmailBuilder@http://localhost:7100/_next/static/${isServer ? 'ssr' : 'chunks'}/remoteEntry.js`,
},
shared: {},
}),
);
}

return config;
},
};

3. Import to the component
const EbBuilderComponent = dynamic(() => import('microEmailBuilder/EbBuilderComponent'), {ssr: false})
```
24 changes: 24 additions & 0 deletions components/Error.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import React from 'react';
import Link from 'next/link';
import { Button, Result } from 'antd';

function ErrorCode({ statusCode = 404, statusText = 'Sorry, the page you visited does not exist.' }) {
return (
<div className="min-h-screen grid place-items-center bg-white">
<Result
status={statusCode.toString() as any}
title={statusCode}
subTitle={statusText}
extra={
<Link href="/" passHref>
<a>
<Button>Back Home</Button>
</a>
</Link>
}
/>
</div>
);
}

export default ErrorCode;
26 changes: 26 additions & 0 deletions components/eb-builder/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import React from 'react';
import EmailEditor from 'react-email-editor';

type Props = {
// editor instance is created
// you can load your template here;
// const templateJson = {};
// emailEditorRef.current.editor.loadDesign(templateJson);;
onLoad?: () => void;
// editor is ready
onReady: (ref: React.Ref<any>) => void;
};

const EbBuilderComponent: React.FC<Props> = ({ onReady, onLoad }) => {
const emailEditorRef = React.useRef(null);
const __onLoad = () => {
if (onLoad) onLoad();
};
const __onReady = () => {
if (onReady) onReady(emailEditorRef.current);
};
return <EmailEditor style={{ minHeight: '92vh' }} ref={emailEditorRef} onLoad={__onLoad} onReady={__onReady} />;
};

EbBuilderComponent.displayName = 'EbBuilderComponent';
export default EbBuilderComponent;
23 changes: 23 additions & 0 deletions components/shared/eb-backdrop/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import React from 'react';
import { Modal, Spin } from 'antd';
import { LoadingOutlined } from '@ant-design/icons';

const EbBackdrop: React.FC<{ status: boolean; maskStyle?: Record<string, any> }> = ({ status, maskStyle = {} }) => {
return (
<Modal
destroyOnClose={true}
wrapClassName="ant-modal-content-transparent"
maskStyle={{ backgroundColor: 'rgba(255,255,255,0.05)', ...maskStyle }}
transitionName=""
maskTransitionName=""
centered
open={status}
width="auto"
footer={false}
closable={false}>
<Spin size="large" indicator={<LoadingOutlined spin />} />
</Modal>
);
};

export default EbBackdrop;
34 changes: 34 additions & 0 deletions components/shared/eb-button/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { Button } from 'antd';
import React from 'react';
import { CommonInterface } from '@library/interfaces';
import CaptureButtonInterface = CommonInterface.CaptureButtonInterface;

const EbButton: React.FC<CaptureButtonInterface> = ({
size,
disabled,
label,
htmlType,
buttonType,
className,
onClick,
style,
icon,
loading,
...rest
}) => (
<Button
disabled={disabled}
size={size}
icon={icon}
type={buttonType}
htmlType={!!htmlType ? htmlType : 'button'}
onClick={onClick}
loading={loading}
style={style || {}}
className={`border border-transparent hover:text-primary hover:border-primary hover:border focus:border-transparent focus:text-primary ${className}`}
{...rest}>
{label}
</Button>
);
EbButton.displayName = 'EbButton';
export default EbButton;
13 changes: 13 additions & 0 deletions components/shared/eb-message-alert/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { message as atndMessage } from 'antd';

const EbMessageAlert = (message: string, type: 'success' | 'info' | 'error' | 'warning' | 'loading' = 'success', duration = 5) => {
atndMessage[type](
{
content: message,
style: { textAlign: 'center' },
},
duration,
);
};

export default EbMessageAlert;
11 changes: 11 additions & 0 deletions library/functions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import tailwindConfig from 'tailwind.config.js';
import resolveConfig from 'tailwindcss/resolveConfig';

export const funcGreetings = () => {
const curHr = new Date().getHours();
if (curHr < 12) return 'Good Morning!';
else if (curHr < 18) return 'Good Afternoon!';
else return 'Good Evening!';
};

export const funcTailwindConfig = () => resolveConfig(tailwindConfig as any) as any;
23 changes: 23 additions & 0 deletions library/interfaces.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import React from 'react';
import { SizeType } from 'antd/lib/config-provider/SizeContext';
import { ButtonType } from 'antd/es/button';

export type EmailBuilderResponse = { design: any; html: any; [x: string]: any };

export type ExportType = 'html' | 'design';

export namespace CommonInterface {
export interface CaptureButtonInterface {
size?: SizeType;
disabled?: boolean;
label: string;
loading?: boolean;
icon?: React.ReactElement;
buttonType?: ButtonType;
htmlType?: 'button' | 'reset' | 'submit' | undefined;
className?: string;
style?: any;
onClick?: (e: any) => void;
[x: string]: any;
}
}
5 changes: 5 additions & 0 deletions next-env.d.ts
Original file line number Diff line number Diff line change
@@ -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.
27 changes: 27 additions & 0 deletions next.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
const path = require("path");
const { NextFederationPlugin } = require("@module-federation/nextjs-mf");

/** @type {import("next").NextConfig} */
const nextConfig = {
reactStrictMode: process.env.NODE_ENV !== "development",
sassOptions: {
includePaths: [path.join(__dirname, "styles")]
},
webpack(config) {
config.plugins.push(
new NextFederationPlugin({
filename: "static/chunks/remoteEntry.js",
exposes: {
"./EbBuilderComponent": "./components/eb-builder/index.tsx",
"./interfaces": "./library/interfaces.ts"
},
shared: {
// whatever else
}
})
);
return config;
}
};

module.exports = nextConfig;
Loading

0 comments on commit 8d4dcbd

Please sign in to comment.