-
-
Notifications
You must be signed in to change notification settings - Fork 915
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Coding splitting child apps #102
Comments
A few questions:
Some resources to look at that may or may not be helpful:
|
Thanks for the quick response! We're using separate webpack configs. In our use case, we'd like for each child application to be managed separately by different teams, each with their own release cycle. Eventually we'd like to load them from a CDN. For now, in our proof of concept using angular-start as a sample child app, it comes with it's own webpack config that generates this in its
In our root application, we're only importing
which leads to the error when trying to load |
That's awesome, having separate projects for each child application with their own release cycle and webpack config really helps. That is how we do it for our production application at Canopy. To pull it off, you have a few options, but first let me explain why what you are doing doesn't work. The reason is that webpack Your options basically fall into a few categories:
singleSpa.declareChildApplication('app-name', () => loadApp('https://url-to-app'), () => true)
function loadApp(url) {
return new Promise((resolve, reject) => {
const script = document.createElement('script')
script.src = url;
script.onload = resolve;
script.onerror = reject;
document.head.appendChild(script)
});
}
|
Actually I had a typo in my snippet. I'm actually doing this: Which I believe still uses webpack. Thank you for the explanation! Makes sense! I will give SystemJS a try. |
No problem, happy to help if you have any more questions. And yeah |
@mbanting we load child applications with a SystemJS plugin called sofe. The child app declarations look like:
Sofe at runtime will figure out a url where |
That's great @blittle. That's exactly what we're going for too. I'll take a look at sofe. Thanks for sharing! |
I was able to solve my issue by building the child app with a fully qualified URL set for its More detail: The root app loads the child-app (angular-starter) as follows:
Child app is mounted successfully, but still fails when unsuccessfully loading its subsequent chunks. The issue was the child app, built with Webpack, attempted to load with a relative URL, resulting in a 404:
Using @joeldenning's hint of setting the publicPath (in this case to Is this how you solved this on your end? |
We solved it a little differently, actually. Since we're using sofe, the urls for our child applications are actually dynamic, so we need a dynamic Also, I noticed in your last comment that your code sample might have a bug: // Code with bug
declareChildApplication('angular', SystemJS.import('http://localhost:8081/dist/main.bundle.js'), () => true)
// Correct code
declareChildApplication('angular', () => SystemJS.import('http://localhost:8081/dist/main.bundle.js'), () => true) This might just be an artifact of writing dummy code, but if you're doing this in real code it won't work because the second argument to declareChildApplication needs to be a function that returns a promise. It's intended for lazy loading. Let us know if there are other questions, feedback, or roadblocks. Happy to help however we can! |
Thanks @joeldenning! |
Hey @mbanting @joeldenning guys sorry about that, I know that the issue has been closed however I would to have some explanation about how to do:
The child app:
It complains that say no bootstrap function exported. |
@daniele-zurico the singleSpa.declareChildApplication('child-app1', () => scriptTagApp('/child-application.js', 'childApp1'), () => true) // child-application.js
window.childApp1 = {
bootstrap: function(props) {...},
mount: function(props) {...},
unmount: function(props) {...},
} Having to create global variables is not ideal, which is why it is recommended to use webpack or systemjs so that you don't have to do that. |
I'm having similar issues. It would be great if I could get some help. The root application is angular 4 (root-app) and I have a child application that is also angular 4 (child-app). They are in two separate directories. We are just using angular cli, but I know that behind the scenes it uses webpack. I have the single-spa-angular-2 code in /child-app/app.js (following the example from the repo). The main module is imported as AppModule from ./src/app/app.module.ts In my root-app, I have the declareChildApplication in app.component.ts. declareChildApplication('child-app', () => System.import('../../../child-app/app.js', ()=> true) At first I tried to just use "import" but it kept telling me "expression expected." Then when I added System, it said cannot find name System. I fixed that by adding 'declare var System: any;' to typings.d.ts. Do you know anything about that? I think it is a typescript related issue. I am currently getting an error: "died in status LOADING_SOURCE_CODE: single-spaangular must be passed opts.mainModule, which is the Angular module to bootstrap. If I console log AppModule in /child-app/app.js, it returns undefined. I'm guessing that's why I'm getting an error and I'm getting undefined because it can't get the module file. Does this have to do with angular cli using webpack? Should I use SystemJS? Do I have to build the child app first and use main.bundle.js like mbanting? I don't understand passing in bundle.js to declareChildApplication. How does it see the Let me know if you need any more details. By the way, this metaframework is super awesome and useful; exactly what we need at my work. |
Thanks for saying you find it awesome and useful! Always good to hear 👍. And thanks for commenting here, we're very happy to try to help
Yeah that sounds like a typescript issue. It needs to know about the System global variable or it thinks there might be a bug in your code.
Could you paste the file for your child application here? Also you can check out https://github.com/joeldenning/simple-single-spa-webpack-example/blob/master/src/app2/app2.js as an example of how to implement an angular2 child application.
I'm actually not sure what you're referring to here. Are you talking about something that was discussed earlier in the thread? |
This is app.js in my child application. I was looking at that link when implementing my child application. import singleSpaAngular2 from 'single-spa-angular2';
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
import AppModule from './src/app/app.module.ts';
console.log(AppModule)
const ngLifecycles = singleSpaAngular2({
domElementGetter,
AppModule,
angularPlatform: platformBrowserDynamic(),
template: `<child-app />`
})
export function bootstrap() {
return ngLifecycles.bootstrap();
}
export function mount() {
return ngLifecycles.mount();
}
export function unmount() {
return ngLifecycles.unmount();
}
function domElementGetter() {
// Make sure there is a div for us to render into
let el = document.getElementById('child-app');
if (!el) {
el = document.createElement('div');
el.id = 'files-delivery';
document.body.appendChild(el);
}
return el;
} This is my root application, which is also Angular 4. import { Component } from '@angular/core';
import { declareChildApplication, start } from 'single-spa';
declareChildApplication('delivery', () => System.import('../../../child-app/app.js'), activityFunction('/child'));
start();
function activityFunction(prefix: string) {
return function(location): boolean {
return location.pathname.startsWith(prefix);
}
}
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent {
title = 'parent-app';
}
Yes, I was referring to what was discussed earlier in this thread. |
@rivamadan in your app.module.ts file, do you |
Ahh, I didn't know about the difference. Yeah, I am exporting a named thing, so I changed my import to But I'm still getting this error:
|
Try changing the following: const ngLifecycles = singleSpaAngular2({
domElementGetter,
AppModule,
angularPlatform: platformBrowserDynamic(),
template: `<child-app />`
}) to this: const ngLifecycles = singleSpaAngular2({
domElementGetter,
mainModule: AppModule,
angularPlatform: platformBrowserDynamic(),
template: `<child-app />`
}) Also, just for reference, the full documentation for using single-spa-angular2 can be found at https://github.com/CanopyTax/single-spa-angular2 |
That solved it! Thanks so much! |
Glad it worked! Let me know if you run into other hiccups! |
@rivamadan Is this project for your company or confidential of some sort? I would love to have a look on your project. Because I'm literally doing the same thing as you, and my child application can only bootstrap, and it's not mounting. BTW, I live on the bleeding edge of frontend, so yes, the beta version packages may be the cause of my frustration. |
@euller88 The project is for my company and confidential, but it's just a simple structure at this point so I can change a few names of things and you can take a look at it. But it might be easier if you just share your project. Could you explain in more detail what you think the problem is? Is your root and child app both angular 4? |
No, both of them are on beta versions, Angular 5 rc2. Most part of the packages are also on beta stages. I have a cool little script on my computer that installs beta packages if they're available. It is fun. The only thing the child app has is the app.js file and the single-spa package for angular 2. And the root app is identical to yours. I will rebuild both projects from scratch this weekend, using only stable components. Maybe that is the solution. If that doesn't work, I will come back to say my results. But thank you anyway. |
@euller88 I see. It might be the single-spa package for angular 2 that doesn't work. I haven't looked into Angular 5 so I don't know the differences between that and Angular 4, but my guess would be something in the single-spa package for angular 2 needs to be changed. |
Hello everyone, it was the beta packages. They were interacting in a way that was breaking the application. |
@euller88 glad to hear you got things working! |
@joeldenning Thanks for your help before! I was wondering if you have any insight into this new situation I'm facing. I'm trying to put angular 1 child app into an angular 4 root app. This angular 1 child app uses bower and gulp. This is my angular 1 child app.js file: import singleSpaAngular1 from 'single-spa-angular1';
import angular from 'angular';
import './app/app.js';
const ng1Lifecycles = singleSpaAngular1({
angular,
domElementGetter,
mainAngularModule: 'analytics',
uiRouter: true,
preserveGlobal: true
});
export const bootstrap = [
ng1Lifecycles.bootstrap,
];
export const mount = [
ng1Lifecycles.mount,
];
export const unmount = [
ng1Lifecycles.unmount,
];
function domElementGetter() {
// Make sure there is a div for us to render into
let el = document.getElementById('analytics');
if (!el) {
el = document.createElement('div');
el.id = 'analytics';
document.body.appendChild(el);
}
return el;
} When I run my parent application, I get this error:
It seems like it was only looking in a8-fe/node_modules, but angular was in a8-fe/bower_components. Is this because my angular 4 root app uses npm? If I could get it to look in bower_components it might fix a lot of my problems. I was able to solve this error by doing
This is my child app's initial module file: angular
.module('analytics', [
'analytics.f4',
'environment',
'ngActionCable',
'ngInflection',
'ngResource',
'ngSanitize',
'ui.router'
]);
angular.module('analytics.f4', []); I tried doing (On a side note, when I run the child app alone, import complies to require and it errors because require isn't defined on a browser. And ideally, I want it to be able to run on it's own too.) I did that for all the dependencies and it didn't error, but the html container for my child app is empty. <div id="analytics"><div id="__single_spa_angular_1" class="ng-scope"><!-- uiView: --><div ui-view="" class="ng-scope"></div></div></div> I'm not sure if what I did is the right way to do it. If I have to do the import, I'd rather just import from the bower_components folder. From looking your and others comments on your article (specifically https://medium.com/@joeldenning/raquel-lira-thats-a-great-question-by-default-webpack-assumes-one-big-webpack-config-for-d31b545fe776), it seems like if my root app uses webpack, all the dependencies needed for the child apps need to go into the root app (because of the way webpack works). Or I have to use one of the two methods you mention in the link above. Update: I think the html is empty because ui-router isn't working correctly. I should have content on '/dashboards'. I added |
@rivamadan Thanks for the detailed description of what's going on. It sounds like there is a lot going on but that part of the problem is some confusion over node_modules vs bower_components and when you should use which one. If I understand correctly (please correct where wrong), you are using angular-cli to build both the root application and the child applications, which is using webpack underneath the hood. And webpack doesn't work with bower_components by default. I would recommend looking into only using node_modules instead of bower_components, even for the AngularJS child application. Bower is pretty outdated now (even their website suggests switching to webpack), so it might be worth moving off of it. If the angularjs child application is not a new project and is already deep into bower, it might not be worth moving off of bower, though. If that's the case, you could try to get webpack to work with bower (see old webpack docs). But, by default, webpack does not ever look into bower_components when creating the bundle. That's why you have had to npm install all of the packages and then import them in your code ( Let me know if you have other questions or if there's something besides the bower_components/node_modules problem that you'd like me to help out with! |
That's a good question - the reason why I import the routes file inside of app.js is because that's the way to tell webpack to include it in the bundle. Any javascript file that is not imported either by the webpack entry file or any of the files imported by the entry file will not be included in the final bundle. This makes AngularJS code be in an interesting situation with modern bundlers -- AngularJS assumed you script tagged each file individually and it does it's own module/dependency-injection stuff. Now that bundlers are a thing, though, AngularJS' module system and dependence on global variables can sometimes feel clunky when using modern tools like webpack. |
So I see that the 'route.js' file imports 'root.component.js' and then the 'root.component.js' imports './root.template.html'. Does this mean to get AngularJS to work we have to import every file so it ends up in the bundler? If I don't use angular-cli to build the root application, could I still get child apps that use webpack/angular-cli to work? |
For angularjs, yes you need to import every file so it ends up in the bundler. You could look into a webpack configuration or plugin that allows you to auto-include every file in a directory into the bundle, but as far as I know you have to import every file individually to get it into the bundle.
Yes, you can (although it is not something I have done myself). If you eject the angular-cli child applications into their own webpack bundles, then you can use a master webpack config (or a module loader like SystemJS) for the root app to load the child applications. And that master webpack config for the root app does not need to use angular-cli. The idea is that each child application produces a javascript bundle loaded by the root application. But the root application itself doesn't use or need a javascript framework like Angular at all -- instead it just knows how to load the child application code into the browser and then single-spa takes care of mounting/unmounting the child applications. Let me know if that doesn't make sense - I'm happy to try and help. https://github.com/joeldenning/simple-single-spa-webpack-example is a good place to start - note that the root application does not use angular-cli but that the child applications could use angular-cli if they wanted to |
If I have all my child applications use webpack and my root application uses webpack, do all the child applications need to have their dependencies in the root application? From previous comments it sounds like that is the case. If that's the case, what's the point of having a webpack bundle for each child application? Sorry, I'm new to webpack so maybe that's why I'm not understanding. If all dependencies need to go in the root, the child applications can't have different versions of dependencies? Although I did some searching and I think yarn allows for different versions. Update |
Reopening since there is still discussion going on here and it's clearly a topic that a lot of people are interested in. |
@rivamadan I'll respond to your questions tomorrow |
@rivamadan apparently tomorrow meant "in four days" :) Your questions are good ones. Child applications do not need to have all of their dependencies in the root application. They can have their own package.json and webpack config. Which means they can have their own versions of dependencies. You can do that with both SystemJS or webpack for the root application, although the implementation details are different depending on which one you choose. If you use webpack for the root application, you could publish the child applications as npm packages who have no dependencies and whose main file is a webpack bundle with everything pre-bundled. If you use systemjs, you would do much the same except you don't have to publish the child applications to npm -- instead you can just deploy them to a web server and have SystemJS load them into the browser. Note that neither https://github.com/joeldenning/simple-single-spa-webpack-example nor https://github.com/CanopyTax/single-spa-examples do this. Instead, they use one package.json for the root application that is shared for all child applications. Also note that #126 is tracking the work to create another example project that will show how to have multiple package.jsons (one for the root app and each of the child apps) Does that answer your questions? Keep more questions coming -- I'm happy to help try and answer and also this is good feedback for me as I rework the docs and examples. |
Yes, that answers those questions. Thanks a lot! There are a few things I want to understand further though. However, when I change something in the child's angular-cli config file it doesn't look like it does anything. It seems like the root application angular-cli config file is the only one that is being recognized. Currently this doesn't matter to me, but I might eventually have to add another child app that could have something specific in the webpack config. I was just wondering if you knew why this is happening and if this is fixed with the method you mentioned above (publish to npm). In a way, I'm wondering why is it necessary to publish the child apps to npm? It seems like it is working for the most part without publishing to npm. I was also looking at #123. You mention:
Is this what is happening the way I'm running the app right now? |
This actually confuses me a little bit -- why does your root app need Angular at all? In general, the root application is usually very very small and just calls singleSpa.declareChildApplication and singleSpa.start (no other framework necessary). See explanation here
It's not necessary to publish the child apps to npm if you have a monorepo where the root app and all child apps live, or if you use a module loader like SystemJS. It would become necessary if you're only using webpack and have separate git repos for the root app and each child app. If everything is in one git repo, you can get away without publishing to npm, but you need to make sure that the root app is importing the child application webpack bundle (not just the source file). Otherwise your child app webpack configs are not doing anything.
Are you talking about when you're running the app locally? My comment that you quoted was related to deployments, not for local development. I'm not sure exactly how you're running your app right now, so I'm not sure I'll be able to answer your question definitively. What I can say though is that statement you quoted was not referring to local development, but rather deployments. |
The reason I made my root application Angular is because there are a few things I want all the applications to share. I have a shared top nav bar, which includes a panel that displays certain data that should accessed in each child app. I also want one authentication system for all the child apps (gets token and puts it into local storage for child apps to access). Is there a reason not to do this for the root application? I tried following https://github.com/CanopyTax/single-spa-examples with JSPM/SystemJS, but I ran into an issue: Update: I talked to my devOps team and they want to use SystemJS and import from URLs actually. (When I first brought this up with my manager, he thought it was overcomplicated and was against it, but I don't think he really understand the difference.) Also, the way I was doing it didn't allow AoT or upgrading to angular 5 (gave me the error "A platform with a different configuration has been created. Please destroy it first"). |
Closing for now -- feel free to reopen with further questions or discussion |
@joeldenning I didn't want to make a new issue for this as this covers the conversation quite closely. I was wondering how you guys handle assets if host and child applications live on different servers? For example, if the host app lives on domain If you publish the host application and child applications on the same server I guess it answers itself really.... |
Along with @TombolaShepless, I am also having difficulty with loading child app assets. Any help would be appreciated. |
I just want to share one thing that fixed multiple webpack config chunk loading issue. Webpack uses function called "webpackJsonp" to load chunks trough window namespace. Configure each webpack to use different function name using output.jsonpFunction config.
|
@TombolaShepless |
@skarjalainen ooo I've noticed that before but didn't know how to fix it. Thanks! |
@joeldenning Thanks for replying, that makes sense. Sorry (again) if this is a silly question, but how do you handle staging/production domains in (Your setup?)stage build ---> stage server OD setupbuild ---> artifacts ---> OD ---> stage server An aside question - do you get any memory pressure over a long period of use with multiple child apps being mounted/unmounted? I found that |
@Shepless good questions
By setting the publicPath dynamically at runtime. You can do so with
Memory leaks are definitely a thing in javascript and something to watch out for in all single page applications. There are maybe three parts to answering your question: the memory single-spa uses, the memory that applications use (usually framework level stuff inside of Vue, etc), and just the memory that a large single page application uses single-spa memory usage: framework memory usage in applications: On a more practical, real-world level, I'm sure there are some unfortunate memory leaks within React/Angular/Vue, with some being exacerbated by mounting multiple instances of the framework and then unmounting regularly. I cannot speak confidently about the specifics of all the frameworks, but I can say that afaik there aren't substantial memory leaks in React, which is what we primarily use at Canopy. I have much less experience with Vue, though. memory usage of large single page applications: Does that help? Let me know if you have any more questions about single-spa memory consumption |
@joeldenning brilliant answer bud, thank you! Thanks for the insights on setting the publicPath dynamically. Not so keen on the I was wondering more about the "framework memory usage in applications" aspects, and just wondered what Canopy Tax's experience had been with this to date. From my experimentation everything LGTM (apart from Thanks for your help - really appreciated 👍 I promise I won't revive this issue again 😆 |
@mbanting hi, I also encountered the same problem, can you tell me finally you solved it? How to solve it? |
@lukalulu we are setting |
Hi! You guys did a great job! Could be so kind and help me in my case: I have two applications: root and child. Both have separate git repos and are served on separate servers. Both are based on create-react-app. As they use the same versions of libraries, i.e:, react, I excluded it from the bundle of the child application. When I try load the child (libraryTarget: 'amd') application with SystemJS, I get the error:
In this case how common libs should be provided? Right now react left in root application bundle. I've created repo with this example: https://github.com/rkolec/single-spa-create-react-app-demo |
wanted to submit a fresh working SystemJS based sample since this took me a bit to get my head around the specifics given SystemJS and everything else has evolved enough for errors to be confusing based on the slightly dated sample code in these threads... not a dig, just the reality of how everything is moving forward. |
Hi there, great framework. Thanks for sharing!
Do child apps need to be a single bundle or can they be code splitted?
As an exercise I converted https://github.com/AngularClass/angular-starter into a single-spa child and it successfully mounted onto my root single-spa app. However, it fails on certain routes within the child app, as it tries loading its chunk from the root app
GET http://127.0.0.1:8080/1.chunk.js
rather than the location of the child app on the filesystem.
What's the best strategy to enable on-demand loading of chunks for child apps? Or do I import every chunk in
declareChildApplication
?The text was updated successfully, but these errors were encountered: