Yes we did. It is a bit hacky because indeed we had to modify the blazor.webassembly.js.
It could be (IMHO) the official implementation though...
It took some steps to set it up but still then it works like a charm
Modify the blazor.webassembly.js on publish
For this we created a command line utility BlazorToAbsolutePath.exe and configured it to be executed on publish
<Target Name="PostPublish" AfterTargets="AfterPublish">
<Exec Command="..\DeveloperTools\BlazorToAbsolutePath\bin\$(Configuration)\$(TargetFramework)\BlazorToAbsolutePath.exe bin\Release\net8.0\publish" />
</Target>
The utility itself replaces every usage of the root url with a variable. And of course first it defines that variable. In essence it just makes the baseUrl for Blazor configurable. After this change the app works exactly as worked before BUT you can define a variable with an absolute url on the document. If that is defined then will be picked up and used for blazor.
Here is the main art of it (the whole code is 110 lines but it does other things as well, like tricks with our css files, recompressing the blazor.webassembly.js into .gz and .br, etc. NO secret in it i can share on request.)
var rootPath = Path.GetFullPath(args[0]);
var wwwroot = Path.Combine(rootPath, "wwwroot");
Console.WriteLine($"root path: {rootPath}");
#region hack the main stylesheet - until flow portal corrects the link
var stylsheet = Path.Combine(wwwroot, "DocumentManager.UI.styles.css");
var legacyStylsheet = Path.Combine(wwwroot, "DocumentManager.UI.styles.css");
File.Copy(stylsheet, legacyStylsheet);
#endregion
#region hack the blazor.webassembly.js
var framework = Path.Combine(wwwroot, "_framework");
var filePath = Path.Combine(framework, "blazor.webassembly.js");
var url = @"typeof(document.dmApiUrl) === ""undefined"" ? document.baseURI:document.dmApiUrl";
var fileText = File.ReadAllText(filePath);
fileText = fileText.Replace("var e,t,n;!", $"var e,t,n;var hostUrl = {url};!")
.Replace("\"_framework/", "hostUrl+\"_framework/")
.Replace("`_framework", "`${hostUrl}_framework")
.Replace("\"string\"==typeof e&&e.startsWith(\"./\")&&(e=new URL(e.substr(2),document.baseURI).toString())", "\"string\"==typeof e&&e.startsWith(\"./\")&&(e=new URL(e.substr(2),hostUrl).toString())||\"string\"==typeof e&&e.startsWith(\"/\")&&(e=new URL(e.substr(1),hostUrl).toString())")
.Replace("const t=document.baseURI;", "const t=hostUrl;")
//.Replace("document.baseURI","hostUrl").Replace("document-baseURI", "document.baseURI") //because we host the UI in the webAPI
;
File.WriteAllText(filePath, fileText);
Compress(filePath);
#endregion
Register web components
for this you ned the Microsoft.AspNetCore.Components.CustomElements nuget package in your Blazor app.
then just add a line for each component you want to use/share
liek for example:
builder.RootComponents.RegisterCustomElement<MyTestComponent>("dm-testcomponent-three");
Publish the Blazor app somewhere
We have it as a WASM application and published it together with a webapi which backs up it with data. Like we would deploy a Blazor WASM anyway. So it is actually usable on it's own as well
Just do not forget to setup CORS for the other application which is going to host your components
Setup the hosting app
On any page you want to use a blazor component (or on the 'master' page index.html whatever)
<head>
<link href=https://url-for-your-blazor-app.styles.css rel="stylesheet" />
<script type="text/javascript">
document.dmApiUrl = https://url-for-your-blazor-app-domain/;
</script>
...
and at the end of that page
<script src=https://url-for-your-blazor-app-domain/_framework/blazor.webassembly.js></script>
After all of these on the page you can just use the custom elements like.
<dm-testcomponent-three />
Gotchas
Styling can be tricky. If you use component libraries which requires to include their css explicitly you have to do it on the hosting app as well - we only use libraries where they inject their css for themselves, so it is not a problem
However the css isolation can be challenging. Because of the css isolation the only way to get global classes is in your app.css (or similar). That is not referenced and not automatically injected. But you don't even want that really, because that could mess up your hosting app's styles. Instead we came up with a practically empty HostingEnvironment
templated component. If we need to share things we register it under that component and create a wrapper for our other components.
Sadly the Blazor architecture makes it practically impossible to use Shadow DOM for your components (except if that component is not interactive) so it is better if you use prefixes for your css classes.