
Zack Jackson talking about Next.js Module Federation Version 7
Zack Jackson talking about Next.js Module Federation Version 7
Zack Jackson, creator of Webpack Module Federation shares with Valor Software about what’s coming up in the next version of the Next.js Module Federation plugin including more information on Delegate Modules.
Content
0 -> we can go ahead and go through I think
2.7 -> you you know you made a lot of changes
4.319 -> to module Federation the next plugin
7.919 -> um a lot of simplification so I'm gonna
9.78 -> swap over to the screen share and you
11.46 -> can uh kind of walk us through what
13.74 -> you're able to do
15.96 -> sure
18.18 -> all right so
20.64 -> um
22.38 -> so I think the biggest change that's
24.6 -> coming in so I guess it went to version
26.22 -> seven what I started working on was
29.76 -> um
30.359 -> was dropping
32.3 -> uh my whole promise new promise set up
35.399 -> so in version six and like the last few
37.5 -> versions I'd released this beta for
39.18 -> delegate modules and that was mostly
42 -> what I was focusing on is pulling
44.04 -> delegate modules in making it kind of
45.899 -> the default way that
47.64 -> um
48.78 -> Federation works next
51.32 -> but from that I ended up
56.879 -> trying something new halfway through I
60.059 -> ended up trying something new like over
61.379 -> the weekend where
62.879 -> I tried to see if I was able to
66.72 -> uh if I was able to drop the whole child
70.26 -> compiler design so the reason that we've
72.6 -> had a child compiler is due to the
74.64 -> incompatibilities of next
77.52 -> um the main one was sharing code so if
80.22 -> the Imark a react or something is shared
82.56 -> even if a market is eager
85.38 -> um next will fail to run correctly and
89.46 -> there's another problem with eager
91.74 -> sharing as well is usually that also
94.02 -> hoists the dependency into
96.479 -> the remote entry so in order to make
98.88 -> next sharing work I'd have to eagerly
100.92 -> share all of its internals which means
104.04 -> your remote entry would be like about
105.42 -> 200 kilobytes to download
108.119 -> um
108.78 -> amnex still wouldn't work correctly so
111.6 -> what we've done is use a child compiler
114.479 -> so this is like the main next plugin and
116.28 -> toward the bottom of it I used to have
117.84 -> this child compiler enabled and when you
120.6 -> go into the channel compiler it would do
122.52 -> like quite a lot of stuff but
124.56 -> essentially I'd reconfigure like where
126.899 -> the build output would go of the child
128.459 -> compilation I'd filter a bunch of
130.92 -> plugins off of the build
133.02 -> um
134.04 -> I'd configure module Federation
136.8 -> I'll change the chunking format so they
138.959 -> don't collide with the main build
141.42 -> um
144.08 -> mess around with the sharing mechanism
146.4 -> so in the host build I would share react
149.16 -> under a different name and I could kind
150.66 -> of shim it into
152.239 -> the child compiler
154.68 -> and
157.14 -> then I pretty much just manually
158.94 -> Implement most of what is needed for
162.42 -> like a federation build to work but
164.519 -> overall it was quite complicated to have
167.04 -> to go and
169.26 -> and try and implement this this whole
172.14 -> setup as you can see like the plug-in is
174.54 -> quite long and
176.04 -> the logic around it to like fire off you
178.8 -> know watch mode or rebuild and
180.54 -> development mode was quite uh the flaky
184.08 -> like all of this logic here is just
186.3 -> building it
187.86 -> and running the child compiler not
190.08 -> actually the child compiler doing
191.459 -> anything but starting it in the right
193.2 -> mode and running it at the right time
196.08 -> so in version seven what I've thought
199.26 -> about was would it be possible to just
201.239 -> drop the child compiler completely
204.239 -> and try to run it off the main build and
206.159 -> I've had some success in the past with
208.019 -> it but ultimately
210.3 -> um it's never actually worked out
212.64 -> so this time around I decided to just
214.5 -> give it another go and it actually ends
218.519 -> up mostly working now so the idea behind
222.78 -> uh this new design is
226.62 -> uh let's see if I go up into the top
228.599 -> here the idea behind it is I've got my
230.7 -> you know usual setup that I've got check
233.4 -> if there's certain things there apply it
235.739 -> to the right build
237.48 -> um and then
240.599 -> if I've got my shared modules I've added
243.06 -> some warnings just that users don't
244.92 -> re-share things that I'm already sharing
246.54 -> internally because I do it still in a
248.459 -> specific way
250.5 -> um and then I kind of get down into
254.099 -> configuring the plugin you know put the
256.44 -> node plug-in in there
258.799 -> make sure certain things are always
261.18 -> internalized in the build
263.6 -> and then I kind of go down here and you
266.94 -> know I have my usual loaders
269.16 -> um
270.54 -> and so on but uh what's been the key to
274.68 -> make this actually work is this plugin
278.04 -> here that I've created this add module
279.9 -> plugin
280.919 -> so what ad module lets me do is move the
285.18 -> dependencies around in the webpack graph
287.52 -> and move them into the right location so
289.979 -> usually
291.36 -> um
292.44 -> if I were to share something or whatever
294.06 -> it gets moved somewhere else in the
296.16 -> graph
298.56 -> and oh sorry and wherever it gets moved
301.5 -> that usually isn't the right location
303.12 -> for it next doesn't start right or
306.78 -> um it goes into the wrong container or
308.759 -> something like that always happens so
311.82 -> um what add modules lets me do is just
314.759 -> uh go through the the graph and move
318.18 -> certain dependencies into the chunk that
320.34 -> I want them to be in
322.139 -> so the biggest thing that app modules is
323.88 -> really doing is it's hoisting
326.24 -> the code for the remote entry back into
329.28 -> the host's webpack runtime
331.62 -> so now if it needs something like react
333.84 -> or
335.639 -> um
337.02 -> you know or anything else what ends up
340.68 -> happening is inside the webpack runtime
343.919 -> I've got
345.72 -> a reference point
348.12 -> to react that'll exist when I execute
350.84 -> that specific module so I won't get the
353.82 -> usual eager sharing failure
357.24 -> um
357.9 -> how I'm actually doing that is if I
360.18 -> click into this plugin it's
362.639 -> pretty simple but first what I do is I
366.12 -> find all the delegate modules and I
367.56 -> hoist them into the runtime to make sure
369.36 -> that they're always in the webpack
370.8 -> runtimes
372.84 -> um and then what I do is I go through
374.34 -> and I add a couple other things so the
375.96 -> big one here is this internal hoist
379.02 -> mechanism
381.12 -> um so if I see that I move the module
383.039 -> and pretty much I move it into whatever
386.22 -> the current run time is that I've got
388.02 -> set in the options here
390.06 -> so
392.46 -> why this is super helpful is
396.18 -> one all of my remote entry code that I
398.699 -> need to start the application is
400.02 -> actually in the runtime
402.419 -> that it's supposed to be in from the
404.22 -> get-go the the second big one is this
406.919 -> hoisting mechanism that I've got here
408.78 -> for moving modules of the tree so what I
412.8 -> do with this is I do have an empty file
415.68 -> yeah internal uh I have the container
418.44 -> hoist and then I've got a delegate hoist
421.139 -> but this is pretty much the only thing
423.9 -> that I have sitting inside of it so it's
425.94 -> it's quite empty and then I've developed
428.94 -> out
429.96 -> um a few uh new loaders here
433.5 -> so I have my normal delegate loader
435.24 -> which still does what it what it's
436.74 -> supposed to do and then I've been I've
439.86 -> developed out the a hoist injector so
442.199 -> this is quite simple it just requires
444.3 -> the that empty hoist file somewhere
447.3 -> inside of the host application
449.819 -> and then I get to the share scope
451.56 -> hoisting and this is kind of what tied
452.94 -> it all together is
455.34 -> um I go through here I look in your
457.259 -> shared
458.759 -> uh options of module Federation
462.36 -> and I Loop over all the share keys that
464.759 -> you've got so anything that's set to
466.259 -> eager where import is set to false I
469.08 -> Loop over those I think if if it's just
471.66 -> set to eager yeah if it's just set to
473.22 -> eager I Loop over those and what I'll do
475.68 -> is create the share scope object for it
478.8 -> so in this case you know I've got say
481.319 -> react and I just set it to zero version
483.539 -> because the version doesn't really
484.74 -> matter and a market is loaded and eager
487.979 -> and then it's get call usually this
489.9 -> would be like a dynamic import so when
492.06 -> you can start an application
493.319 -> asynchronously
495 -> uh webpack is going to load all the
496.62 -> share Scopes in see which one has the
498.3 -> best version and then call the get
499.8 -> method and await it to respond with um
503.639 -> with the the module Factory that's got
506.22 -> the dependency at once since next can't
508.74 -> start asynchronously I need to be able
510.24 -> to do this synchronously
511.919 -> so there is one kind of trick to it so
515.339 -> since um
516.959 -> I don't want to actually move react
519.24 -> around on the graph I don't want any of
521.459 -> the remotes to attempt to negotiate a
523.26 -> different version of react or something
524.64 -> like that so
526.58 -> what I'm doing is inside of the internal
530.64 -> file here
532.68 -> I think this is it yeah so inside of
535.5 -> here this is the default share scope so
537.54 -> you can see that I have react as a
539.1 -> Singleton but I have it marked as import
541.56 -> false and it says eager true so when a
544.62 -> market is import false what it'll end up
546.54 -> doing if we go and look in one of the
548.04 -> webpack runtime outputs
552.899 -> um what it ends up giving me is a
557.76 -> okay server and here we go so now if I
560.94 -> go and search for react
563.519 -> inside of here
567.779 -> I should end up getting
570.54 -> let's see just jump past all of this
575.7 -> yeah so I get to the anit external
578.339 -> section and then I've got these two uh
582.72 -> initializers on here but those are just
586.38 -> my my remotes react itself isn't
589.08 -> actually shared from the host it doesn't
591.54 -> register that it's got a copy of react
593.519 -> or anything
594.6 -> and because I've set it to false it's
596.82 -> going to expect react to exist somewhere
599.1 -> in the upper scope so somebody else is
601.019 -> going to give it react even when it's
602.88 -> the host it's treating it the same way
605.339 -> so it expects react to already be
607.2 -> provided through some share scope
609.24 -> mechanism it's not going to
612.42 -> um
613.38 -> do anything or register anything for me
615.72 -> on its own so this makes all the remote
618.54 -> entries not attempt to load react or
620.82 -> double load react or anything like that
622.5 -> so it solves one of the issues that I've
624.24 -> had with initializing these applications
627.18 -> but the side effect of doing this is it
629.76 -> also makes the host not load react
631.5 -> either so now react just doesn't exist
633.6 -> at all it's treated as external and it
636.12 -> expects sharescope to provide it so with
639.48 -> runtime hoisting and my my share
641.88 -> hoisting what I'm doing is I
644.3 -> move react or the the kind of wrapper
647.579 -> code around what react is going to do I
650.64 -> get that moved up so that when I'm
653.64 -> building up the webpack runtime here
656.94 -> it's going to go over these startup
658.8 -> modules because these are all in the
660.3 -> graph already so these are the two
661.86 -> remotes that I use these are each
663.24 -> delegate module and then I go down to
667.76 -> this hoisted one where I've got my
670.32 -> internal module hoist system
673.26 -> and now in here I've got you know a key
675.12 -> with react and whatever else and then
677.7 -> this require call
679.38 -> since react would be external if I just
681.779 -> left this as require react it's just
684.42 -> gonna it's I'm gonna be stuck in the
686.519 -> same problem it's gonna go back to
689.16 -> Federation and the host is going to
691.2 -> believe that it doesn't have react
692.519 -> because somebody else is supposed to
693.959 -> supply it
695.16 -> so what I do over here is I don't
697.74 -> actually call it react but instead I
700.26 -> call it react pop
703.019 -> and I put this in front which disables
705.839 -> any loaders from passing over it so now
708.42 -> when I do that webpack sees this as
710.579 -> something different to require react
713.7 -> so this is not equal to
718.44 -> that so now what ends up happening is I
721.079 -> can essentially double bundle react so
723.48 -> the first react is externalized so it
725.519 -> never actually ends up in the graph but
727.56 -> this top react gets pushed into the
731.16 -> module graph under a different name so
733.019 -> nobody's going to look to require it
735.5 -> so what I end up doing is I assign the
739.079 -> other location where it is I put this
741.3 -> into the share scope here
743.1 -> and then at the bottom of the graph so
745.8 -> once I set up share scope for the
747.3 -> remotes at the bottom of the graph I set
749.16 -> this up for the host where I side load
750.959 -> it so now I take the original react
754.5 -> request which is going to point to ask
757.86 -> Federation for react and I re-assign it
761.88 -> to the actual reference this react pop
766.139 -> that exists
767.7 -> so now when webpack Boots up and it
769.8 -> tries to go and get 6689 it's actually
772.98 -> going to get seven or eight uh 733
776.339 -> so now everything is synchronously there
779.1 -> but I haven't had to mess with the
781.079 -> mechanisms of how Federation works too
783.54 -> much according to webpack it seems to
785.94 -> all function it's going to look in the
787.26 -> module graph it's going to find this and
788.76 -> it's going to exist so it will
792.54 -> um you know it won't see anything's
794.519 -> wrong it thinks it's already there and
796.26 -> I've injected it just in time from a
798.66 -> different location
800.459 -> so if we go back to my loader it's a
803.94 -> little easier to look at it in this
806.459 -> hoist loader so this is essentially what
808.56 -> I do I make the require call with react
811.139 -> and pop and I Loop over this and I apply
813.779 -> this to everything that I'm doing and
815.339 -> then below there I implement the side
817.019 -> loading logic so this is where I side
819.12 -> load whatever the current dependency is
821.04 -> so I go into webpack modules and I call
824.04 -> require resolve week which is going to
826.26 -> pretty much return the module ID
829.38 -> of the dependency so now I'm getting
832.32 -> react and that's going to be you know
834 -> 6687 or whatever and I'm finding that
837 -> inside the webpack module graph
839.459 -> and then what I'll do is I go okay well
841.56 -> now that actually equals and I require
843.6 -> resolve week
845.279 -> and I get the reference that I've loaded
848.1 -> in up here
849.54 -> and then I find this in the graph in
852.06 -> another place so now the only place that
854.339 -> actually uses react pop is this file so
857.94 -> that means the rest of my dependencies
860.18 -> still use react which is supposed to be
862.92 -> external so they won't actually see that
865.079 -> react pop and react to the same thing
867.66 -> so that's what I'm doing down here is I
870.12 -> reassign the modules so everybody who's
872.22 -> looking for react actually gets the
874.139 -> graph reference to react pop which is
876.18 -> synchronously available
877.92 -> so now webpack starts up just fine and
880.5 -> then at the bottom all I do is I go and
882.18 -> I set my share scope default and I
884.339 -> assign whatever exists there with my
887.399 -> eager
888.86 -> object that I've just generated above
891.3 -> over here
892.86 -> um and then you know I pretty much
894 -> inline all of that and load it into my
896.88 -> hoist system along with whatever else is
899.519 -> below it and once that's done it should
902.699 -> it essentially lets me start the
904.98 -> application up
906.44 -> in a synchronous manner with the
910.44 -> dependencies that I want already
911.639 -> available and I don't get any
913.019 -> initialization issues
914.76 -> and everything pretty much works the way
917.04 -> you would expect it
919.56 -> um so so that solves the main problem of
921.899 -> I can't share anything and use
923.279 -> Federation with next on the single build
926.04 -> instance
927.18 -> so with the module hoisting out the way
928.8 -> that's most of the problem solved
930.959 -> but there's still a couple other
932.639 -> interesting scenarios that had to be
935.04 -> um dealt with one of them is
938.82 -> I need to actually get the Hoist code
941.88 -> into
943.38 -> my code base to begin with so when I do
946.98 -> internal delegate hoist I have two
948.839 -> places that I do it on so the first one
950.459 -> is I have a little loader here
952.86 -> and so this loader is a preloader and it
955.8 -> only looks for the document file index
958.5 -> which is like the first file that next
960.54 -> requires and inside of there I inject
964.019 -> the hoisting code so now it requires
967.56 -> this internal delegate hoist file right
970.44 -> after that I have a loader that
971.88 -> processes that hoist file and that's
974.339 -> going through that whole share scope
975.72 -> loader that I just showed you and then I
977.459 -> just passed in the Federation options
979.019 -> here
980.459 -> um so this works for the server but in
982.44 -> the browser
983.76 -> I have a bit of a different strategy to
986.16 -> take in the browser we have the main
989.16 -> right the main chunk so that's anything
991.199 -> common anything that's shared usually
992.94 -> ends up in the main
994.199 -> chunk all the pages depend on the main
996.66 -> chunk so in order for the application to
999 -> start the main chunkcast have been
1000.44 -> parsed and loaded and then the page
1002.36 -> chunks will start being parsed and
1003.86 -> loaded so the nice thing is this gives
1006.62 -> us an opportunity to inject modules
1009.62 -> somewhere into the graph that will exist
1011.66 -> before the application is
1014.6 -> fully initialized so a server side
1018.32 -> doesn't have the concept of a main chunk
1020.18 -> which is why I have to put it into
1021.5 -> document kind of side load things in
1023.66 -> there client side we do so a nice trick
1027.199 -> with webpack is if you create another
1029.299 -> entry point with the same name as a
1031.22 -> different entry point webpack will merge
1033.38 -> the two modules together in the order
1034.819 -> that they were created
1037.16 -> so now I have internal delegate hoist
1040.28 -> and now that's ending up inside the main
1042.26 -> chunk so by loading the main chunk I've
1044.059 -> got my hoisting mechanism already
1045.62 -> activated
1047.36 -> um and then I can go and utilize it
1049.22 -> however I want in a synchronous manner
1052.64 -> so that's kind of like what I'm doing
1054.5 -> for the for the browser side so the
1056.6 -> other really interesting trick here is
1057.98 -> you've done this in the past but I
1059.66 -> didn't need to do it for this scenario I
1061.76 -> believe but I've also done things like
1064.179 -> that where I'll add runtime code to the
1068.299 -> remote entry itself because the remote
1070.34 -> entry is just a name so whatever the
1072.08 -> name is you give it here that's kind of
1073.52 -> the chunk name that comes out underneath
1075.679 -> so if I add another entry point with the
1077.84 -> same name as a different one they merge
1079.46 -> together into one entry point and so I'm
1081.559 -> using that concept here but on the main
1083.72 -> uh the main entry
1086.179 -> um so
1087.919 -> that that takes care of like starting
1090.559 -> the application in host mode mostly all
1093.5 -> the modules are in place and now my my
1095.72 -> synchronous initialization will work
1098.179 -> then I actually get to configuring
1100.16 -> Federation so
1102.14 -> first you know I set it to runtime false
1104.66 -> which means create a new separate remote
1106.64 -> entry that's fully fledged with the
1108.919 -> whole webpack runtime in it then inside
1111.5 -> of here I still want my remote entry to
1116.059 -> have the hoisting code
1118.1 -> for delegates
1120.44 -> so what I end up doing is I actually
1122.66 -> have it expose the the delegate hoist as
1126.26 -> well so now webpack sees that this is
1129.26 -> used somewhere within that entry point
1131.179 -> and I can now uh find chunks that are
1135.08 -> dependent from the entry point down and
1136.88 -> again hoist them back into the actual
1138.74 -> runtime itself
1140.24 -> so let's just make sure that it's
1141.919 -> tracked in there and if it's tracked in
1143.419 -> there I can find it and I can move
1144.799 -> everything around in the graph on either
1146.24 -> runtime
1147.679 -> so the host runtime behaves differently
1150.44 -> to the remote entry that it generates
1153.14 -> and usually Federation does not allow
1155.6 -> that when you configure this it
1157.1 -> configures how the host is going to
1158.36 -> treat dependencies the same way that the
1160.039 -> remote is going to treat those
1161.12 -> dependencies
1162.2 -> what I wanted was a way to say that the
1164.059 -> host treats the dependencies slightly
1166.16 -> different than the remote does but only
1169.1 -> the remote treats them in a different
1170.96 -> manner so this is a this tactic allows
1174.02 -> me to make them operate similar to how
1176.12 -> what I was doing in the child compiler
1177.86 -> where I was saying you know mint a new
1179.78 -> graph for this entry point and
1182.78 -> effectively handle the dependencies
1184.34 -> separately from how the parent is
1185.9 -> handling it but now I don't need to use
1188.6 -> a whole child compiler so some perks
1190.52 -> there are much faster builds because I'm
1192.38 -> not essentially building the app again
1195.08 -> um and the caching is a lot better
1197.6 -> because
1198.88 -> uh the chunk names are the same as the
1201.559 -> chunk names the host uses so if the host
1204.08 -> means react or something else and not
1206 -> technically double loading it depending
1207.5 -> on the combination of how I run it
1209.059 -> because they're actually sharing the
1210.86 -> same chunk ref end to end
1213.74 -> um so bringing this in here pretty much
1215.78 -> got everything to work the way that I
1217.34 -> wanted it to
1219.26 -> and then there was still one other area
1221.84 -> that I had to go and induce so this is
1223.7 -> just for you know my delegate module
1225.2 -> loading and stuff like that I also do it
1227.179 -> at the preload stage so this is before
1229.22 -> loaders start running this ensures that
1231.74 -> everything's put in place before any
1233.48 -> additional processing gets done
1236.419 -> um the other thing that I had to do was
1239.179 -> I had problems with the automatic async
1241.34 -> boundary so that worked before in
1243.98 -> next.js and child compilers
1247.7 -> um
1248.86 -> but this gets tricky when I have to
1253.039 -> apply the boundary loader to
1255.74 -> one file because what boundary loader
1258.14 -> does is it looks at your page import and
1260.24 -> it moves it into a dynamic import so if
1262.28 -> we go and look at boundary loader
1264.559 -> what it does is you know it kind of just
1266.419 -> does this so your page actually ends up
1268.52 -> being this and then this is importing
1271.1 -> the original page underneath and I do it
1273.5 -> with another import like has boundary
1276.5 -> which means that it's a different file
1278.78 -> so now webpack will treat it as a new
1280.82 -> module and re-require it without
1282.44 -> boundary loader so my page is just a
1285.32 -> dynamic import to the real page and that
1288.74 -> works quite well but
1291.86 -> there's a problem with next which is
1294.32 -> when I go and expose these pages
1297.5 -> it's going to run through boundary
1298.94 -> loader as well so this creates a bit of
1301.28 -> a problem because now I have a dynamic
1303.38 -> import for next Dynamic to dynamically
1305.96 -> import whatever I'm doing and if I'm
1308.179 -> dynamically importing a Federated page
1310.22 -> the page I'm exposing to the end user is
1313.52 -> also going to be this same piece of code
1315.679 -> on their end so now I have a next
1318.08 -> Dynamic going into a next Dynamic so I
1320.539 -> double Dynamic import the page and that
1323.419 -> causes some issues with next because
1325.159 -> Dynamic doesn't really know how to wait
1327.74 -> for like suspended modules it runs
1331.1 -> um
1332.179 -> it just uh it expects them to already be
1335.24 -> there and then it triggers a like a kind
1337.58 -> of set State and if the module isn't
1340.34 -> already resolved it'll be null and then
1342.32 -> it'll set State when the promise
1343.7 -> eventually does resolve so double
1345.38 -> loading them is a bit of a problem
1346.76 -> because next doesn't know the weight for
1349.28 -> another Dynamic import that just came
1351.32 -> into it
1352.52 -> inside of another next Dynamic that
1354.5 -> isn't ready so double loading the
1356.539 -> modules is quite uh was quite tricky and
1359.12 -> this one I probably spent like a day
1360.5 -> trying to figure out how to make async
1362.6 -> boundaries still work but not because
1364.46 -> hydration error on the front end when
1366.88 -> one of the Federated modules that I'm
1369.44 -> loading is the async boundary page that
1373.58 -> is usually destined for the host to
1375.44 -> actually use to you know be able to load
1378.02 -> shared modules or whatever else in an
1379.7 -> async manner
1381.2 -> so
1382.52 -> what ended up coming up with here to try
1384.919 -> and solve this was I make all of my
1387.74 -> pages actually just attach
1390.5 -> another uh another key onto the the kind
1395.299 -> of function object which is called
1397.52 -> underlying so now what I can end up
1400.22 -> getting is I can either get async
1403.76 -> boundary which is the default export and
1406.28 -> that's wrapped in next dynamic
1408.46 -> or I can go get the async boundary dot
1412.82 -> underlying which is just the dynamic
1414.919 -> import so I can skip going through
1417.62 -> another next Dynamic which
1420.02 -> is going to want to track that the
1421.52 -> promise is already resolved and then set
1423.26 -> state so even though everything is there
1425.659 -> when it renders it through two next
1427.88 -> Dynamic back-to-back Imports
1431.659 -> um
1432.38 -> it will show up as pending and it will
1434.9 -> fail to hydrate even though its server
1436.88 -> renders correctly and technically
1438.26 -> everything's there it's just because
1439.7 -> there's another promise in it that has
1441.919 -> to immediately resolve that is kind of
1443.96 -> the issue so
1446.24 -> how it worked around that was with this
1448.94 -> kind of underlying thing that I add on
1450.74 -> to it
1451.7 -> so now what happens is
1454.34 -> uh hosts and remotes either expose or
1457.22 -> just consume their own Pages using this
1459.26 -> kind of thing so I go into next Dynamic
1461.659 -> I have my Dynamic import where I go and
1463.28 -> get whatever I'm trying to get
1466.1 -> um and then I'll check if it's got a
1469.64 -> underlying uh inner module attached to
1472.58 -> it the only ones that'll actually have
1474.62 -> an inner module attached to it are ones
1476.48 -> that come over Federation because next
1479.36 -> is going to go in Babel this import and
1483.02 -> turn it into a different object and with
1485.059 -> the object that it's going to give me is
1486.799 -> not going to have the underlying part
1488.299 -> it's going to have already resolved one
1490.28 -> layer into it and give me the the
1492.98 -> default export that's inside of the
1495.32 -> dynamic import so only when I'm pulling
1498.02 -> Federated Pages across does this
1501.08 -> underlying
1503.12 -> um this underlying primitive actually
1505.1 -> exist on the module that I get back from
1508.88 -> the promise
1509.96 -> so now what I can do is I go okay well
1512 -> if it's there then I know that this is
1513.74 -> now a Federated page that I'm pulling in
1515.72 -> and I can await underlying which would
1518.36 -> already exist because this is you know
1520.64 -> this import is the same as that import
1522.38 -> so all I'm doing is saying ensure that
1524.36 -> the promise is still resolved and then
1526.46 -> if an inner module exists
1529.7 -> then use the default export of that
1532.279 -> otherwise use the the next Dynamic one
1536.12 -> itself like you know use the default one
1538.64 -> so then this would have next Dynamic
1540.5 -> wrapped around it this one would not
1542.24 -> have next Dynamic wrapped around it but
1544.46 -> since I'm in next Dynamic right here I
1546.679 -> don't want
1547.76 -> next Dynamic wrapped around what I'm
1549.799 -> about to return to next dynamic so this
1552.44 -> is a way for me to switch between them
1553.88 -> depending on how it's being consumed and
1557.059 -> not introduce nested lazy calls
1561.14 -> um so yeah so so with this in place this
1564.08 -> solved the other issue of allowing
1565.76 -> sharing to still work and still keeping
1567.919 -> my async boundaries so that kind of
1570.2 -> solved the other big problem where I had
1571.7 -> to split the builds and treat them all
1573.08 -> differently
1574.52 -> um and then I believe the last
1576.799 -> the last piece that uh was important to
1580.159 -> tie together was
1584.179 -> let's see ah was this down here
1588.14 -> so I configure module Federation plugin
1590.179 -> and off it goes and then I go if it's
1592.94 -> not the server and I'm exposing stuff I
1596.059 -> actually add another module Federation
1597.98 -> plugin back onto the build so now I've
1600.98 -> configured Federation twice for the
1603.2 -> browser but this time the name is called
1605.659 -> single
1606.86 -> so the idea here is when I say that it's
1611.24 -> runtime false
1613.1 -> it's going to create a whole new webpack
1614.96 -> runtime top to bottom this works fine
1618.44 -> when you're importing remotes from you
1620.9 -> know other builds but
1622.88 -> in the case of let's say I'm on the home
1624.74 -> application and I import checkout and
1628.039 -> check out Imports
1630.74 -> um title from home
1633.26 -> checkout's going to want to use the
1635.24 -> host's own remote or load the the home
1638.059 -> remote again and try to load its expose
1640.82 -> module through it but doing that
1643.1 -> overwrites the chunk loading globals
1646.7 -> inside of the browser for that remote
1649.46 -> entry so now any new chunks coming in
1651.86 -> are pushed into the wrong webpack
1653.96 -> runtime because there's two competing
1655.7 -> runtimes on the page and they're not
1657.14 -> sharing a graph
1658.88 -> so what I have to do is I have to make
1662 -> sure that if a remote is requesting the
1663.86 -> current host's own exposed modules
1666.74 -> I want to still use the main runtime
1669.08 -> chunk that the host has so that I am not
1672.08 -> having to uh load another copy of the of
1675.5 -> the of the host's runtime fully fledged
1679.4 -> on the page so runtime chunk single will
1682.52 -> create a partial webpack runtime so it's
1686.24 -> not the whole thing it's not the whole
1687.62 -> module graph it's just like three or
1689.36 -> four functions
1690.82 -> and that won't work independently but if
1695 -> I'm on the page that's already the host
1696.62 -> then it would work so I needed a way to
1699.2 -> be able to actually figure out am I
1702.02 -> already in the uh am I already in the
1704.059 -> right host
1705.74 -> and if so I want to load the light
1708.44 -> version of that remote but I don't want
1710.419 -> that logic to have to be put on the
1712.76 -> consumers and all of that
1714.919 -> um to try and figure out because it it
1717.26 -> becomes complicated trying to understand
1719.179 -> when something should be used and under
1720.919 -> what scenarios
1723.14 -> so what ended up doing was creating this
1725.179 -> this little single one where I say
1727.1 -> runtime is undefined so now it's going
1729.26 -> to just slice a piece off the main
1731 -> webpack runtime
1732.559 -> and then what I do is somewhere in here
1735.559 -> I've got it hard coded right now and I
1737.24 -> need to
1738.2 -> need to fix that
1741.14 -> yes so inside of here
1744.38 -> what I'm what I'm doing is I say that
1747.38 -> inside the delegate module so since the
1749.36 -> delegate modules are the thing that
1750.679 -> start at the very beginning of the
1752.299 -> application as webpack initializes
1754.64 -> everything
1755.659 -> I can go in here and I can just set
1757.58 -> something on the window called like home
1759.74 -> app
1760.64 -> and I can and then I'm just using the
1763.58 -> webpack chunk loader
1766.039 -> which is
1768.02 -> um the low level way to tell webpack to
1769.94 -> load a chunk that it knows the name of
1771.559 -> and I'm telling it load the single
1774.02 -> runtime chunk so just add the other
1775.82 -> little snippet of the host's remote
1777.74 -> entry
1778.82 -> add that uh to its startup now since
1783.02 -> window home app exists when any remotes
1785.72 -> are looking for
1787.7 -> um when any like sub remotes have an
1790.159 -> import to the home app
1792.2 -> they're going to search and see is it
1794.24 -> already initialized is it already on the
1795.919 -> window if it's on the window then I'm
1797.24 -> not going to inject the script because
1798.559 -> I've already got it somebody already did
1800.12 -> it so I'm telling the host to put window
1802.94 -> home app there and then once this
1805.399 -> webpack chunk load fires off the code
1808.279 -> inside of it will replace window home
1810.44 -> app with
1811.94 -> the actual getting a net interface so it
1814.46 -> starts out as a promise and then once it
1816.14 -> resolves it's now
1817.88 -> um the get internet container so all of
1820.76 -> my room all of my Downstream
1822.74 -> applications that might need to consume
1824.419 -> something from the host they're actually
1826.399 -> going to get the host's own remote and
1829.1 -> the host is the one saying that it's
1830.659 -> provided so they all just get it
1832.58 -> automatically nobody actually tries to
1834.74 -> inject it if they're on if their host is
1837.559 -> the application of the remote that
1839.179 -> they're currently trying to inject
1840.98 -> so tying all that together gets me
1842.96 -> around chunk loading issues it allows me
1845.059 -> to work with the single graph of next JS
1848.12 -> um
1848.84 -> and it still allows me to create a
1851 -> independent remote entry that I can use
1853.279 -> when there's no parent application
1855.559 -> around that has the other half of the
1857.72 -> module graph in it so it's kind of three
1860.299 -> main pieces that get tied together here
1862.22 -> it's module hoisting
1864.62 -> um
1865.46 -> slicing the runtime correctly for the
1867.62 -> consumer
1868.96 -> and then processing how dependencies and
1873.2 -> how pages are loaded depending on how it
1875.539 -> ends up getting consumed so that I'm
1877.399 -> supporting several different conditions
1880.58 -> for the async boundary loader I think
1883.279 -> the interesting thing there is I'll
1885.14 -> probably use a very similar strategy for
1887.779 -> supporting react server components where
1890.299 -> I need two files to behave as one
1893.059 -> or yeah I need two different files to
1894.98 -> behave as one file depending on how it's
1897.02 -> consumed I've now got a pattern for
1899.6 -> being able to determine a different
1901.159 -> strategy for consumption depending on
1903.32 -> what application it is so with that I
1906.32 -> should be maybe with that in a bit of
1907.94 -> delegate module work I should be able to
1910.64 -> support server components where the
1913.279 -> server component is actually one of two
1915.44 -> things and it depends on how
1918.1 -> the um
1920.12 -> how the host or you know how the
1922.159 -> consuming application wants that
1924.38 -> component to be if it's a server
1926.6 -> component it'll return like the proxy uh
1929.84 -> wrapping function and if it's a standard
1933.38 -> like use client or you know just a
1935.299 -> standard SSR component it would just
1936.98 -> return itself the normal way but now
1939.559 -> I've got a several conventions for
1941.899 -> actually detecting what the scenarios
1943.64 -> are and how to treat a module as
1945.5 -> something else
1947.12 -> um you know in a kind of scalable manner
1952.399 -> that's pretty sick
1954.32 -> um did you so you mentioned a couple
1955.58 -> times that's good it's it's more
1957.26 -> performant do you have any ideas roughly
1959.419 -> the difference in bundle size and
1960.98 -> roughly the difference in build size or
1963.02 -> build time
1965.299 -> so the build size should be about half
1968.36 -> the footprint of whatever you were
1969.919 -> putting out before since I was
1971.24 -> essentially just running webpack two
1973.1 -> more times we were creating four builds
1975.98 -> I just stuck them in the same folder as
1977.84 -> the other builds so you didn't really
1978.86 -> notice but when you'd upload that to a
1980.539 -> Lambda you're essentially you know
1981.919 -> doubling the payload size
1983.899 -> whenever you would uh pull down chunks
1986.24 -> from the application
1988.22 -> you would be pulling down you should see
1990.02 -> all of my Federated modules they usually
1992.059 -> end in uh Dash fed Dash Fed so that kind
1996.799 -> of made sure that they don't collide
1998.72 -> with the hosts modules but that means
2001.659 -> that you know if I'm if I start out in
2004.059 -> checkout and I load the component
2005.679 -> Library I'm going to load like component
2007.779 -> Library fed but then if I go to a
2012.1 -> different application and I load
2014.86 -> it again
2016.24 -> component Library could come through as
2018.58 -> not component Library fed over there so
2020.799 -> technically you would have the
2022.24 -> opportunity of downloading these things
2023.919 -> twice depending on how you entered the
2025.72 -> application
2027.46 -> um
2028.419 -> so with that gun now it's as efficient
2030.94 -> as the next build I'm recycling the
2032.86 -> chunks that next itself uses as a host
2035.62 -> so payload size is a lot smaller
2037.84 -> download size is a lot smaller then on
2041.26 -> the
2042.159 -> um on the build timing size
2046 -> it's again way less because I don't have
2050.139 -> to
2052 -> um essentially rebundle an entry point
2054.879 -> with another copy a webpack and make
2057.46 -> unique chunks and you know re-parse the
2059.5 -> whole thing manually
2061.659 -> um so on the memory footprint size I'm
2064.419 -> not sure on how much memory it saves but
2067.06 -> I I believe it should be about three
2068.859 -> times less memory roughly and then on
2071.619 -> build speed it should be twice as fast
2073.54 -> since there's half the number of webpack
2075.46 -> builds being handled and because they're
2077.679 -> not handled in parallel like before
2079.179 -> that's less stress on the on the
2082.24 -> on the threads
2084.04 -> so I'm able to actually process the
2087.099 -> single build quicker because the machine
2088.839 -> isn't spreading its resources so thin
2092.2 -> so overall I'm seeing you know roughly
2094.659 -> three times better memory use and twice
2097.18 -> faster build times as well as twice
2100.859 -> improved uh
2103.72 -> you know runtime performance
2107.8 -> that's pretty awesome
2109.78 -> um so when do you think this is going to
2111.339 -> go live in a place where people can
2113.44 -> consume it
2116.079 -> so right now I'm busy testing some final
2118.24 -> things so I think the new hoisting
2119.8 -> system was basically done the only thing
2122.32 -> I need to do is like you know I need to
2124 -> create a loader out of this so instead
2125.619 -> of me just writing a window home app it
2128.2 -> needs to be templated window name of
2130.24 -> current hosts remote and go find the the
2134.079 -> chunk ID and you know a couple small
2136.9 -> details like that still need to be
2138.579 -> polished up
2140.44 -> um the biggest thing that I'm trying to
2142.3 -> get running now is when
2146.32 -> I use the default delegate so I merge
2148.839 -> this code into this Branch the other day
2152.68 -> um because originally that was what I
2154.54 -> was going to make V7 it's just allow
2156.099 -> delegate modules to be the default and
2158.079 -> then I got rid of the child compiler so
2160.06 -> I went back and got all my default
2162.52 -> delegate stuff that was intended for V7
2164.56 -> and I'm starting to like reconcile
2167.5 -> the two halves
2169.839 -> um
2170.5 -> so bringing that in is has posed a few
2173.26 -> like new challenges that I'm running
2174.76 -> into that I need to go and uh
2177.28 -> figure out okay and now we're just
2179.92 -> timing out well that's a different error
2181.42 -> than before
2182.68 -> um but
2184.3 -> yeah so I think overall
2186.94 -> um
2187.78 -> we are quite close to being able to cut
2189.94 -> a new release
2191.32 -> there's a few last things I need to I
2193.66 -> need to patch up on like making using
2195.7 -> delegates as the default most of it's
2197.619 -> related to the new work that I've done
2199.18 -> where
2200.32 -> webpack parses this a little differently
2202.72 -> when it's a
2204.7 -> npm package versus when it comes from
2207.22 -> userland as the delegate module so I
2210.339 -> need to make some adjustments to kind of
2211.96 -> support uh
2213.88 -> this variance but once that's done I
2217.54 -> think a lot of it a lot of things that
2219.52 -> are left is just deleting code so like
2222.4 -> the whole child Federation plug-in this
2224.56 -> whole thing will be deleted so this huge
2226.72 -> plug-in and most of the things that it
2229 -> Imports is no longer required so I need
2232.18 -> to still come over the code base and
2233.8 -> delete a lot of stuff because the
2235.9 -> footprint of code required has shrunk
2237.94 -> quite a drastic amount but we are toward
2241.18 -> the end of getting ready for release
2242.56 -> like I'd hope maybe in the next week or
2245.14 -> two we would uh drop version seven
2249.88 -> um and then that would most likely
2252.099 -> assuming we don't have any bugs or
2253.42 -> anything that would probably put to bed
2256.06 -> the major engineering efforts that have
2259.119 -> gone into supporting xjs because this is
2261.579 -> quite a stable mechanism now there's
2264.4 -> just not that much
2266.5 -> it's not it's not that different from
2269.32 -> supporting Federation on any other stack
2272.26 -> there's yeah there's a couple loaders
2274.06 -> and module hoisting and things like that
2275.8 -> but a lot of that is to do with delegate
2277.359 -> modules and not entirely related to next
2280.599 -> so we could see all my delegate work
2282.88 -> getting pulled out into its own package
2284.98 -> or its own set of utilities
2287.02 -> and then what's left inside of next is a
2289.48 -> core plug-in is really just going to be
2291.46 -> module hoisting which isn't which isn't
2294.52 -> very big
2295.48 -> so with all of that done it means that
2298.42 -> any progressions that I make on
2300.88 -> Federation elsewhere are quite easy to
2303.339 -> apply back to next.js so then I could
2305.859 -> look at things like react server
2307.3 -> components and supporting that in a
2309.099 -> simpler application once it works there
2312.22 -> I can backport it into next quite easily
2315.16 -> because there's no longer a
2318.52 -> real mechanical difference between
2320.82 -> module Federation on nexjs and module
2323.619 -> Federation on
2325.18 -> a standard vanilla webpack
2327.88 -> styled application
2332.079 -> awesome
2334 -> sweet so I'm gonna get this all cropped
2336.339 -> up and get this shared out because I
2337.839 -> think this is going to get a lot of
2339.22 -> people excited about what's coming with
2341.38 -> module Federation and then we'll make
2344.079 -> sure that we keep everyone posted on uh
2346.119 -> what's going on
2347.859 -> awesome yeah um this is definitely going
2350.44 -> to be a nice release to get out there um
2352.96 -> it's been a lot of work to kind of to
2355.06 -> fine tune it down but I do believe we're
2356.8 -> kind of at the stage where
2359.68 -> you know we've wrangled next into
2362.5 -> behaving like a normal application so
2364.96 -> despite that they don't support it well
2368.16 -> we you know we we've turned it into
2371.04 -> doing things with the module Federation
2373.42 -> plug-in now that these other plugins are
2375.28 -> there is kind of standard so I'm quite
2377.38 -> happy uh to reach this point and I do
2379.839 -> think it's a good
2381.4 -> it's a good uh place to leave the
2383.859 -> project as I start to look at stuff like
2386.56 -> modern JS or other Frameworks that are
2389.8 -> geared better for
2391.92 -> distributed systems or like larger scale
2394.8 -> so you know I'm quite happy to leave it
2397.359 -> there uh still maintain it of course
2399.579 -> it'll still go along with the
2401.079 -> progression of of my ecosystem but it's
2404.02 -> not a it's not like the main thing that
2406.48 -> is the most complicated to work on it
2408.76 -> should just be able to inherit
2409.9 -> improvements that we make elsewhere
2412.42 -> nice
2413.619 -> well cool beans um I I think we'll we'll
2416.859 -> call it a day and then we'll see uh as
2418.66 -> soon as this gets released
2422.079 -> cheers thank you talk to you later
Source: https://www.youtube.com/watch?v=Nw-PNM7jBsg