Skip to content

Commit 466f026

Browse files
WIP S3 streaming federation (module-federation#19)
* feat: use mock s3 to stream federated code * refactor: use SSR demo as base app
1 parent 0dbe82a commit 466f026

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

65 files changed

+2076
-0
lines changed

streamed-federation/README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Basic One-Way Example
2+
3+
This example demos a basic host application loading remote component.
4+
5+
- `website1` is the host application.
6+
- `website2` standalone application which exposes `Header` component.
7+
8+
# Running Demo
9+
10+
Run `yarn start`. This will build and serve both `website1` and `website2` on ports 3001 and 3002 respectively.
11+
12+
- [localhost:3001](http://localhost:3001/) (HOST)
13+
- [localhost:3002](http://localhost:3002/) (STANDALONE REMOTE)

streamed-federation/package.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"private": true,
3+
"scripts": {
4+
"start": "yarn build && yarn serve",
5+
"build": "lerna run --scope @streamed-federation/* build",
6+
"serve": "lerna run --scope @streamed-federation/* --parallel serve"
7+
}
8+
}

streamed-federation/website1/.babelrc

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
{
2+
"presets": [
3+
"@babel/preset-react",
4+
["@babel/preset-env", {
5+
"targets": {
6+
"browsers": ["last 4 versions", "not ie <= 9"]
7+
},
8+
"modules": false
9+
}],
10+
],
11+
"env": {
12+
"development": {
13+
"presets": [
14+
"@babel/preset-react",
15+
["@babel/preset-env", {
16+
"targets": {
17+
"node": "current"
18+
}
19+
}]
20+
]
21+
},
22+
"test": {
23+
"presets": [
24+
"@babel/preset-react",
25+
["@babel/preset-env", {
26+
"targets": {
27+
"node": "current"
28+
}
29+
}]
30+
],
31+
}
32+
},
33+
"plugins": [
34+
"@babel/plugin-syntax-dynamic-import",
35+
"@babel/plugin-proposal-object-rest-spread",
36+
"@babel/plugin-syntax-import-meta",
37+
["@babel/plugin-proposal-class-properties", { "loose": false }],
38+
"@babel/plugin-proposal-json-strings",
39+
"@loadable/babel-plugin"
40+
]
41+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
PORT=3000
2+
NODE_ENV=development
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
PORT=3001
2+
NODE_ENV=production
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
module.exports = {
2+
parser: "babel-eslint",
3+
parserOptions: {
4+
ecmaFeatures: {
5+
generators: true,
6+
experimentalObjectRestSpread: true,
7+
},
8+
sourceType: "module",
9+
allowImportExportEverywhere: false,
10+
},
11+
extends: ["airbnb"],
12+
settings: {
13+
"import/resolver": {
14+
node: {
15+
extensions: [".js", ".json"],
16+
},
17+
},
18+
},
19+
globals: {
20+
window: true,
21+
document: true,
22+
__dirname: true,
23+
__DEV__: true,
24+
API_URL: true,
25+
CONFIG: true,
26+
process: true,
27+
jest: true,
28+
describe: true,
29+
test: true,
30+
it: true,
31+
expect: true,
32+
beforeEach: true,
33+
fetch: true,
34+
alert: true,
35+
},
36+
rules: {
37+
"import/extensions": [
38+
"error",
39+
"always",
40+
{
41+
js: "never",
42+
jsx: "never",
43+
styl: "never",
44+
css: "never",
45+
},
46+
],
47+
"no-console": ["error", { allow: ["warn", "error", "info"] }],
48+
"no-shadow": 0,
49+
"no-use-before-define": 0,
50+
"no-param-reassign": 0,
51+
"react/prop-types": 0,
52+
"react/no-render-return-value": 0,
53+
"no-confusing-arrow": 0,
54+
"no-underscore-dangle": 0,
55+
"no-plusplus": 0,
56+
camelcase: 1,
57+
"prefer-template": 1,
58+
"react/no-array-index-key": 1,
59+
"global-require": 0,
60+
"no-bitwise": 0,
61+
"react/jsx-indent": 1,
62+
"dot-notation": 1,
63+
"import/no-named-default": 1,
64+
"no-unused-vars": 1,
65+
"consistent-return": 1,
66+
"import/prefer-default-export": 1,
67+
"jsx-a11y/no-static-element-interactions": 0,
68+
"jsx-a11y/label-has-associated-control": 0,
69+
"no-case-declarations": 1,
70+
semi: [2, "never"],
71+
"jsx-quotes": [2, "prefer-single"],
72+
"react/jsx-filename-extension": [2, { extensions: [".jsx", ".js"] }],
73+
"react/destructuring-assignment": 0,
74+
"react/no-danger": 0,
75+
"spaced-comment": [2, "always", { markers: ["?"] }],
76+
"arrow-parens": [2, "as-needed", { requireForBlockBody: false }],
77+
"brace-style": [2, "stroustrup"],
78+
"import/no-unresolved": [
79+
2,
80+
{
81+
commonjs: true,
82+
caseSensitive: true,
83+
},
84+
],
85+
"no-unused-expressions": [
86+
2,
87+
{
88+
allowShortCircuit: true,
89+
allowTernary: true,
90+
allowTaggedTemplates: true,
91+
},
92+
],
93+
"import/no-extraneous-dependencies": [
94+
"error",
95+
{
96+
devDependencies: true,
97+
optionalDependencies: true,
98+
peerDependencies: true,
99+
},
100+
],
101+
"comma-dangle": [
102+
2,
103+
{
104+
arrays: "always-multiline",
105+
objects: "always-multiline",
106+
imports: "always-multiline",
107+
exports: "always-multiline",
108+
functions: "always-multiline",
109+
},
110+
],
111+
"max-len": [
112+
"error",
113+
{
114+
code: 200,
115+
tabWidth: 2,
116+
ignoreUrls: true,
117+
ignoreComments: true,
118+
ignoreRegExpLiterals: true,
119+
ignoreStrings: true,
120+
ignoreTemplateLiterals: true,
121+
},
122+
],
123+
"react/sort-comp": [
124+
2,
125+
{
126+
order: [
127+
"propTypes",
128+
"props",
129+
"state",
130+
"defaultProps",
131+
"contextTypes",
132+
"childContextTypes",
133+
"getChildContext",
134+
"static-methods",
135+
"lifecycle",
136+
"everything-else",
137+
"render",
138+
],
139+
},
140+
],
141+
"linebreak-style": 0,
142+
"react/forbid-prop-types": [
143+
"warning",
144+
{
145+
forbid: ["any", "array", "object"],
146+
checkContextTypes: true,
147+
checkChildContextTypes: true,
148+
},
149+
],
150+
"jsx-a11y/label-has-for": 0,
151+
"jsx-a11y/click-events-have-key-events": 0,
152+
"jsx-a11y/iframe-has-title": 0,
153+
"jsx-a11y/accessible-emoji": 0,
154+
},
155+
};

streamed-federation/website1/.nvmrc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
9.10.0
2+
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
const path = require("path");
2+
const fs = require("fs");
3+
const dotenv = require("dotenv");
4+
5+
const dotenvFile = path.resolve(__dirname, "../.env");
6+
if (fs.existsSync(dotenvFile)) {
7+
dotenv.config({
8+
path: dotenvFile,
9+
});
10+
}
11+
12+
module.exports = {
13+
dotenv: path.resolve(__dirname, "../.env"),
14+
development: {
15+
clientPath: path.resolve(__dirname, "../buildClient"),
16+
serverPath: path.resolve(__dirname, "../buildServer"),
17+
publicPath: "/static/",
18+
},
19+
production: {
20+
clientPath: path.resolve(__dirname, "../buildClient"),
21+
serverPath: path.resolve(__dirname, "../buildServer"),
22+
publicPath: "/static/",
23+
},
24+
};
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
const fs = require("fs");
2+
const path = require("path");
3+
const dotenv = require("dotenv");
4+
const paths = require("./config");
5+
6+
delete require.cache[require.resolve("./config")];
7+
8+
if (!process.env.NODE_ENV) {
9+
throw new Error(
10+
"The process.env.NODE_ENV environment variable is required but was not specified."
11+
);
12+
}
13+
14+
// https://github.com/bkeepers/dotenv#what-other-env-files-can-i-use
15+
const dotenvFiles = [
16+
`${paths.dotenv}.${process.env.NODE_ENV}.local`,
17+
`${paths.dotenv}.${process.env.NODE_ENV}`,
18+
process.env.NODE_ENV !== "test" && `${paths.dotenv}.local`,
19+
paths.dotenv,
20+
].filter(Boolean);
21+
22+
dotenvFiles.forEach((dotenvFile) => {
23+
if (fs.existsSync(dotenvFile)) {
24+
dotenv.config({
25+
path: dotenvFile,
26+
});
27+
}
28+
});
29+
30+
const appDirectory = fs.realpathSync(process.cwd());
31+
process.env.NODE_PATH = (process.env.NODE_PATH || "")
32+
.split(path.delimiter)
33+
.filter((folder) => folder && !path.isAbsolute(folder))
34+
.map((folder) => path.resolve(appDirectory, folder))
35+
.join(path.delimiter);
36+
37+
module.exports = () => {
38+
const raw = {
39+
PORT: process.env.PORT || 3001,
40+
NODE_ENV: process.env.NODE_ENV || "development",
41+
};
42+
43+
// Stringify all values so we can feed into Webpack DefinePlugin
44+
const stringified = {
45+
"process.env": Object.keys(raw).reduce((env, key) => {
46+
env[key] = JSON.stringify(raw[key]);
47+
return env;
48+
}, {}),
49+
};
50+
51+
return { raw, stringified };
52+
};
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
const path = require("path");
2+
const merge = require("webpack-merge");
3+
const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin");
4+
const { client: clientLoaders } = require("./loaders");
5+
const plugins = require("./plugins");
6+
const common = require("./common.base");
7+
8+
module.exports = merge.smart(common, {
9+
name: "client",
10+
target: "web",
11+
entry: ["@babel/polyfill", path.resolve(__dirname, "../../src/index.js")],
12+
output: {
13+
publicPath: "http://localhost:3001/static/",
14+
},
15+
module: {
16+
rules: clientLoaders,
17+
},
18+
plugins: [
19+
...plugins.client,
20+
new ModuleFederationPlugin({
21+
name: "website1",
22+
library: { type: "var", name: "website1" },
23+
filename: "container.js",
24+
remotes: {
25+
website2: "website2",
26+
},
27+
shared: ["react", "react-dom"],
28+
}),
29+
],
30+
});
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
const webpack = require("webpack");
2+
const WriteFileWebpackPlugin = require("write-file-webpack-plugin");
3+
const merge = require("webpack-merge");
4+
const baseConfig = require("./client.base");
5+
6+
const config = merge.smart(baseConfig, {
7+
entry: [
8+
"webpack-hot-middleware/client?path=/__webpack_hmr&timeout=20000&reload=false&quiet=false&noInfo=false",
9+
],
10+
plugins: [new webpack.HotModuleReplacementPlugin()],
11+
mode: "development",
12+
devtool: "source-map",
13+
performance: {
14+
hints: false,
15+
},
16+
});
17+
18+
module.exports = config;
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
const merge = require("webpack-merge");
2+
const path = require("path");
3+
const baseConfig = require("./client.base");
4+
const env = require("../env")();
5+
const config = require("../config");
6+
7+
const { publicPath, clientPath } = config[env.raw.NODE_ENV || "production"];
8+
9+
module.exports = merge.smart(baseConfig, {
10+
mode: "production",
11+
devtool: "source-map",
12+
output: {
13+
path: path.join(clientPath, publicPath),
14+
filename: "[name].[chunkhash].js",
15+
chunkFilename: "[name].[chunkhash].js",
16+
},
17+
});
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
const plugins = require("./plugins");
2+
const resolvers = require("./resolvers");
3+
const config = require("../config");
4+
const env = require("../env")();
5+
6+
const { publicPath, clientPath } = config[env.raw.NODE_ENV || "development"];
7+
8+
module.exports = {
9+
target: "web",
10+
output: {
11+
path: clientPath,
12+
filename: "[name].js",
13+
publicPath,
14+
chunkFilename: "[name].chunk.js",
15+
},
16+
resolve: { ...resolvers },
17+
18+
plugins: plugins.shared,
19+
};

0 commit comments

Comments
 (0)