From 8f33287b6f17180cc600aefcf76dc70164749f14 Mon Sep 17 00:00:00 2001 From: Zack Jackson Date: Thu, 22 Apr 2021 17:08:54 -0700 Subject: [PATCH] Major overhaul (#9) --- .gitignore | 3 +- {federated => federated-cross-test}/.babelrc | 0 federated-cross-test/App.js | 13 ++++ federated-cross-test/bootstrap.js | 4 ++ federated-cross-test/form.js | 15 +++++ federated-cross-test/index.js | 1 + federated-cross-test/webpack.build.config.js | 49 +++++++++++++++ federated-cross-test/webpack.test.config.js | 49 +++++++++++++++ federated-test/.babelrc | 15 +++++ federated-test/Button.js | 3 + {federated => federated-test}/index.js | 0 .../webpack.build.config.js | 20 +++++-- federated-test/webpack.test.config.js | 45 ++++++++++++++ federated/Button.js | 3 - index.js | 30 ++++++++++ jest.config.js | 25 ++++++++ jest/file-mock.js | 1 + jest/force-gc.js | 16 +++++ jest/jestSetup.js | 6 ++ package.json | 32 +++++++--- test/another.test.js | 14 ----- test/federated.test.js | 28 +++++++++ test/suspenseRender.js | 22 +++++++ test/webpack.test.config.js | 60 ++++++++----------- test/worker.js | 14 +++++ 25 files changed, 403 insertions(+), 65 deletions(-) rename {federated => federated-cross-test}/.babelrc (100%) create mode 100644 federated-cross-test/App.js create mode 100644 federated-cross-test/bootstrap.js create mode 100644 federated-cross-test/form.js create mode 100644 federated-cross-test/index.js create mode 100644 federated-cross-test/webpack.build.config.js create mode 100644 federated-cross-test/webpack.test.config.js create mode 100644 federated-test/.babelrc create mode 100644 federated-test/Button.js rename {federated => federated-test}/index.js (100%) rename {federated => federated-test}/webpack.build.config.js (51%) create mode 100644 federated-test/webpack.test.config.js delete mode 100644 federated/Button.js create mode 100644 index.js create mode 100644 jest.config.js create mode 100644 jest/file-mock.js create mode 100644 jest/force-gc.js create mode 100644 jest/jestSetup.js delete mode 100644 test/another.test.js create mode 100644 test/federated.test.js create mode 100644 test/suspenseRender.js create mode 100644 test/worker.js diff --git a/.gitignore b/.gitignore index 74be96f..82591e7 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ node_modules npm-debug.log .idea /test/bundle.test.js -/federated/dist/ +dist/ +dist-test/ diff --git a/federated/.babelrc b/federated-cross-test/.babelrc similarity index 100% rename from federated/.babelrc rename to federated-cross-test/.babelrc diff --git a/federated-cross-test/App.js b/federated-cross-test/App.js new file mode 100644 index 0000000..bdb57ff --- /dev/null +++ b/federated-cross-test/App.js @@ -0,0 +1,13 @@ +import React from 'react'; +import Form from "./form"; + +const App = () => { + return ( +
+

hello world

+
+
+) +} + +export default App diff --git a/federated-cross-test/bootstrap.js b/federated-cross-test/bootstrap.js new file mode 100644 index 0000000..e869f3f --- /dev/null +++ b/federated-cross-test/bootstrap.js @@ -0,0 +1,4 @@ +import React from "react"; +import ReactDOM from "react-dom" +import App from './App' +ReactDOM.render(, document.getElementById('app')) diff --git a/federated-cross-test/form.js b/federated-cross-test/form.js new file mode 100644 index 0000000..ccd4aca --- /dev/null +++ b/federated-cross-test/form.js @@ -0,0 +1,15 @@ +import React from "react"; +import lazy from 'react-lazy-ssr'; +const Button = lazy(()=>import('federated/Button'),{chunkId:"federated/Button"}) +// const Button = process.env.NODE_ENV === 'test' ? require('federated/Button').default : React.lazy(()=>import('federated/Button')) +// const Suspense = process.env.NODE_ENV === 'test' ? ({children})=>children : React.Suspense +const Suspense = React.Suspense +const Form = () =>( + + + + ) +export default Button diff --git a/federated/index.js b/federated-test/index.js similarity index 100% rename from federated/index.js rename to federated-test/index.js diff --git a/federated/webpack.build.config.js b/federated-test/webpack.build.config.js similarity index 51% rename from federated/webpack.build.config.js rename to federated-test/webpack.build.config.js index ec31f8b..fe0922b 100644 --- a/federated/webpack.build.config.js +++ b/federated-test/webpack.build.config.js @@ -1,12 +1,16 @@ const path = require('path'); -const glob = require('glob'); -const {ModuleFederationPlugin} = require("webpack").container +const webpack = require("webpack") +const {ModuleFederationPlugin} = webpack.container +const deps = require('../package.json') + module.exports = { entry: require.resolve('./index.js'), output: { path: path.resolve(__dirname, "./dist"), + publicPath: "auto" }, - target: "node", + cache:false, + target: "web", resolve: { fallback: { path: false @@ -26,10 +30,16 @@ module.exports = { new ModuleFederationPlugin({ name: 'federated', filename:"remoteEntry.js", - library: {type: "commonjs", name: "federated"}, exposes: { - "./Button": "./federated/Button.js" + "./Button": "./federated-test/Button.js" + }, + shared: { + react: deps.devDependencies.react, + "react-dom": deps.devDependencies["react-dom"] } + }), + new webpack.DefinePlugin({ + 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || "development"), }) ] }; diff --git a/federated-test/webpack.test.config.js b/federated-test/webpack.test.config.js new file mode 100644 index 0000000..f2c6fbd --- /dev/null +++ b/federated-test/webpack.test.config.js @@ -0,0 +1,45 @@ +const path = require('path'); +const glob = require('glob'); +const webpack = require('webpack') +const {ModuleFederationPlugin} = webpack.container +const deps = require('../package.json') + +module.exports = { + entry: require.resolve('./index.js'), + output: { + path: path.resolve(__dirname, "./dist-test"), + }, + target: "node", + resolve: { + fallback: { + path: false + } + }, + mode: "none", + module: { + rules: [ + { + test: /\.js$/, + exclude: /node_modules/, + loader: 'babel-loader' + }, + ] + }, + plugins: [ + new ModuleFederationPlugin({ + name: 'federated', + filename: "remoteEntry.js", + library: {type: "commonjs-module", name: "federated"}, + exposes: { + "./Button": "./federated-test/Button.js" + }, + shared: { + react: deps.devDependencies.react, + "react-dom": deps.devDependencies["react-dom"] + } + }), + new webpack.DefinePlugin({ + 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || "test"), + }) + ] +}; diff --git a/federated/Button.js b/federated/Button.js deleted file mode 100644 index 86d5499..0000000 --- a/federated/Button.js +++ /dev/null @@ -1,3 +0,0 @@ -import React from "react"; -const Button =()=> () -export default Button diff --git a/index.js b/index.js new file mode 100644 index 0000000..f449225 --- /dev/null +++ b/index.js @@ -0,0 +1,30 @@ +module.exports = function(remotePath,name) { + return `promise new Promise(res => { + let remote + const remotePath = "${remotePath}" + try { + remote = require(remotePath)['${name}'] + } catch (e) { + delete require.cache[remotePath] + remote = require(remotePath)['${name}'] + } + + if(!remote || !remote.get) { + return new Promise(function(delayResolve){ + var interval = setInterval(function(){ + delete require.cache[remotePath] + remote = require(remotePath); + if(require(remotePath)) { + delayResolve(require(remotePath)['${name}']); + clearInterval(interval); + } + }, 100) + }).then(function(){ + setTimeout(function(){res(remote)},0) + }) + } + + const proxy = {get:(request)=> remote.get(request),init:(arg)=>{try {return remote.init(arg)} catch(e){console.log('remote container already initialized')}}} + res(proxy) + })` +} diff --git a/jest.config.js b/jest.config.js new file mode 100644 index 0000000..e271a6c --- /dev/null +++ b/jest.config.js @@ -0,0 +1,25 @@ +process.env.TZ = 'UTC'; + +module.exports = { + moduleNameMapper: { + // moduleNameMapper: { + // '^react-dom$': compat, + // '^react$': compat, + // }, + '\\.(css|scss|png|ico)$': 'identity-obj-proxy', + '\\.(svg|jpg|png)': '/jest /file-mock.js', + }, + testPathIgnorePatterns: ['/node_modules/'], + transformIgnorePatterns: [ + ], + setupFiles: [ + './jest/jestSetup.js', + ], + snapshotSerializers: ['enzyme-to-json/serializer'], + // Used to run Garbage Collection after each describe block + // This will reduce our memory used in CI + // https://dev.to/pustovalov_p/reducing-jest-memory-usage-1ina + setupFilesAfterEnv: [ + './jest/force-gc.js', + ], +}; diff --git a/jest/file-mock.js b/jest/file-mock.js new file mode 100644 index 0000000..9cf3c27 --- /dev/null +++ b/jest/file-mock.js @@ -0,0 +1 @@ +export default ""; diff --git a/jest/force-gc.js b/jest/force-gc.js new file mode 100644 index 0000000..7b3af59 --- /dev/null +++ b/jest/force-gc.js @@ -0,0 +1,16 @@ + +global.testRuns = 0; + +// Used to run Garbage Collection after each describe block +// This will reduce our memory used in CI +// We limit it to run after every 10 decribe blocks to +// maintain the speed of testing all test files +// https://dev.to/pustovalov_p/reducing-jest-memory-usage-1ina +afterEach(() => { + global.testRuns++; + + if (global.testRuns < 10) return null; + if (global.gc) global.gc(); + + global.testRuns = 0; +}); diff --git a/jest/jestSetup.js b/jest/jestSetup.js new file mode 100644 index 0000000..7e0dc7e --- /dev/null +++ b/jest/jestSetup.js @@ -0,0 +1,6 @@ +import Enzyme, { shallow, render, mount } from 'enzyme'; +import Adapter from 'enzyme-adapter-react-16'; + +// React 16 Enzyme adapter +Enzyme.configure({ adapter: new Adapter() }); + diff --git a/package.json b/package.json index 7ba2fe0..963f7d4 100644 --- a/package.json +++ b/package.json @@ -4,21 +4,37 @@ "main": "index.js", "license": "MIT", "scripts": { - "build": "webpack --config federated/webpack.build.config.js", - "webpack:test": "webpack --config test/webpack.test.config.js", - "jest": "jest test/bundle.test.js", - "test": "yarn webpack:test && yarn jest" + "build": "webpack --config federated-test/webpack.test.config.js && webpack --config federated-cross-test/webpack.test.config.js", + "webpack:test": "yarn build && webpack --config test/webpack.test.config.js", + "jest": "jest test/bundle.test.js -u", + "test": "yarn webpack:test && yarn jest", + "build:demo": "cd federated-test && webpack --config ./webpack.build.config", + "serve": "webpack --config federated-cross-test/webpack.build.config.js && webpack --config federated-test/webpack.build.config.js && concurrently \"PORT=3000 serve ./federated-cross-test/dist\" \"PORT=3001 serve ./federated-test/dist\" " }, "devDependencies": { - "jest": "26.6.3", - "react": "17.0.2", - "webpack-cli": "4.6.0" + "jest": "^26.6.3", + "react": "16.14.0", + "react-dom": "16.14.0", + "synchronous-promise": "^2.0.15", + "webpack-cli": "^4.6.0", + "webpack-dev-server": "^3.11.2" }, "dependencies": { "@babel/preset-env": "^7.13.15", "@babel/preset-react": "^7.13.13", + "@webpack-cli/serve": "^1.3.1", "babel-loader": "^8.2.2", + "enzyme": "^3.11.0", + "enzyme-adapter-react-16": "^1.15.6", + "enzyme-to-json": "^3.6.2", + "html-webpack-plugin": "^5.3.1", + "react-async-ssr": "^0.7.2", + "react-lazy-ssr": "^0.2.4", + "react-ssr-prepass": "^1.4.0", + "serve": "^11.3.2", "webpack": "^5.33.2", - "webpack-node-externals": "^2.5.2" + "webpack-node-externals": "^2.5.2", + "webpack-virtual-modules": "^0.4.2", + "workerpool": "^6.1.4" } } diff --git a/test/another.test.js b/test/another.test.js deleted file mode 100644 index 616fb4f..0000000 --- a/test/another.test.js +++ /dev/null @@ -1,14 +0,0 @@ -const Button = import("federated/Button"); - -describe("another", function() { - - it("can run another test", function() { - expect(true).toBe(true); - }); - - it("federated module test", async function() { - const btn = await Button - console.log('HERES BUTTON', btn.default); - expect(true).toBe(true); - }); -}); diff --git a/test/federated.test.js b/test/federated.test.js new file mode 100644 index 0000000..150f8d7 --- /dev/null +++ b/test/federated.test.js @@ -0,0 +1,28 @@ +import React, {createElement} from 'react'; +import { renderToStringAsync } from 'react-async-ssr' +import {shallow, mount, render} from 'enzyme'; +const Form = import("fed_consumer/Form"); +const Button = import("federated/Button"); +import suspenseRender from './suspenseRender' +const workerpool = require('workerpool'); +const pool = workerpool.pool(__dirname + '/worker.js'); + + + +describe("Federation", function () { + it("is rendering Nested Suspense",async()=>{ + const from = await Form + console.log(await suspenseRender(from.default)) + }) + it("Testing Button from Remote", async function () { + const Btn = (await Button).default + const wrapper = render(); + expect(wrapper).toMatchSnapshot() + }); + + it("Testing Button from Form", async function () { + const Frm = (await Form).default + const wrapper = mount(); + expect(wrapper).toMatchSnapshot() + }); +}); diff --git a/test/suspenseRender.js b/test/suspenseRender.js new file mode 100644 index 0000000..cd0f221 --- /dev/null +++ b/test/suspenseRender.js @@ -0,0 +1,22 @@ +import React from 'react' +import {renderToStringAsync} from 'react-async-ssr' +import {ChunkExtractor} from 'react-lazy-ssr/server'; +class BaseApp extends React.Component { + constructor(props) { + super(props); + } + render(){return this.props.children} +} +const renderApp = async (App) => { + const stats = require('./reactLazySsrStats.json'); + const chunkExtractor = new ChunkExtractor( { stats } ); + const app = chunkExtractor.collectChunks( ); + const element = await renderToStringAsync(app) +return element +} +const start = async (Application)=>{ + const App = ()=>; + const html = await renderApp(App); + return html +} +export default start; diff --git a/test/webpack.test.config.js b/test/webpack.test.config.js index d8dfffa..32adbec 100644 --- a/test/webpack.test.config.js +++ b/test/webpack.test.config.js @@ -1,19 +1,20 @@ const path = require('path'); const glob = require('glob'); const thisFile = path.basename(__filename); +const nodeExternals = require('webpack-node-externals') const {ModuleFederationPlugin} = require("webpack").container +const ReactLazySsrPlugin = require('react-lazy-ssr/webpack'); +const reunited = require('../index') const testFiles = glob.sync("!(node_modules)/**/*.test.js").filter(function (element) { - console.log(element, element != "test/bundle.test.js") return element != "test/bundle.test.js" && !element.includes(thisFile); }).map(function (element) { return "./" + element; }); -console.log(testFiles); module.exports = { - entry: testFiles, + entry: {"bundle.test":testFiles}, output: { path: path.resolve(__dirname, "."), - filename: "bundle.test.js" + filename: "[name].js" }, target: "node", resolve: { @@ -21,42 +22,33 @@ module.exports = { path: false } }, + externals: [nodeExternals({ + allowlist: [/^webpack\/container\/reference\//,/react/] + })], mode: "none", + module: { + rules: [ + { + test: /\.js$/, + exclude: /node_modules/, + loader: 'babel-loader' + }, + ] + }, plugins: [ new ModuleFederationPlugin({ name: "test_bundle", - library: {type: "commonjs", name: "test_bundle"}, + library: {type: "commonjs-module", name: "test_bundle"}, + filename: "remoteEntry.js", + exposes: { + "./render":"./test/suspenseRender.js" + }, remotes: { // Tobias, why do i need to do this in order to get the remote to properly resolve - "federated": `promise new Promise(res => { - let remote - const remotePath = "${path.resolve(__dirname,'../federated/dist/remoteEntry.js')}" - try { - remote = require(remotePath)['federated'] - } catch (e) { - delete require.cache[remotePath] - remote = require(remotePath)['federated'] - } - - if(!remote.get) { - return new Promise(function(delayResolve){ - var interval = setInterval(function(){ - delete require.cache[remotePath] - remote = require(remotePath); - if(require(remotePath)) { - console.log(remote); - delayResolve(require(remotePath)['federated']); - clearInterval(interval); - } - }, 50); - - }) - } - - const proxy = {get:(request)=> remote.get(request),init:(arg)=>{try {return remote.init(arg)} catch(e){console.log('remote container already initialized')}}} - res(proxy) - })` + "federated": reunited(path.resolve(__dirname,'../federated-test/dist-test/remoteEntry.js'),"federated"), + "fed_consumer": reunited(path.resolve(__dirname,'../federated-cross-test/dist-test/remoteEntry.js'),'fed_consumer') } - }) + }), + new ReactLazySsrPlugin() ] }; diff --git a/test/worker.js b/test/worker.js new file mode 100644 index 0000000..722f2ca --- /dev/null +++ b/test/worker.js @@ -0,0 +1,14 @@ +const {test_bundle} = require('./remoteEntry'); +const workerpool = require('workerpool'); + +function render(props) { + return test_bundle.get('./render').then((factory)=>{ + const Module = factory(); + return Module.default(props) + }) +} + +// create a worker and register functions +workerpool.worker({ + render: render +});