Skip to content

Conversation

@javiercn
Copy link
Member

@javiercn javiercn commented Jul 28, 2021

Adds support for custom JS initializers which allows to customize the Blazor boot process from JavaScript.

A concrete scenario is modifying how Blazor webassembly loads resources.
During the publish process, Blazor assets can be transformed and additional elements can be added to the manifest under the extensions path under resources. Like shown below:

{
  "cacheBootResources": true,
  "config": [ ],
  "debugBuild": false,
  "entryAssembly": "blazorwasm-minimal",
  "icuDataMode": 0,
  "linkerEnabled": true,
  "resources": {
    "assembly": {
      "blazorwasm-minimal.dll": "sha256-psGoiPY+YcCvViu3IBI764Mj0zVP7wHYnMLoCdOn1xc=",
      ...
      "System.Threading.ThreadPool.dll": "sha256-xIFRbEifE\/hoQKqqxmQvQX\/FYSVnLf6Mi61CbK5GMVc="
    },
    "extensions": {
      "my-custom-extension": {
        "publish.extension.txt": "sha256-Ztb8hc\/OITLqsL7xtOlFeM+pfPIQ6A6bbbkJF2xHc0Q="
      }
    },
    "lazyAssembly": null,
    "libraryInitializers": {
      "blazorwasm-minimal.lib.module.js": "sha256-W5ZSepq1mBUmWnu7MqTAzO3YGJk7+LTHPaTOHFlY9Fw="
    },
    "pdb": null,
    "runtime": {
      "dotnet.timezones.blat": "sha256-Klww78mQczU8FGEQN5x4eSbjX8OwFHz7X\/OXmVKF8P8=",
      "dotnet.wasm": "sha256-VqE2ngWYg6zFZdUXTRkkNGpwPuryxZo3pRygj9iL\/Ko=",
      "icudt_CJK.dat": "sha256-8+0SUStQbNeoI3hDhcpyKdCWhsndM6zxj6jliMsXeIg=",
      "icudt_EFIGS.dat": "sha256-WqIury5JoQwiCF95WfagtEGBRK2mCvBDAzZEsinsL74=",
      "icudt_no_CJK.dat": "sha256-NepbHl0cZW1DE7TQbLOqzgNcIWnfgUq5RKaQL5ijw\/s=",
      "icudt.dat": "sha256-aAYuBDK\/9aSmctFbfPh62gWNg4mgsbu0rMqA08corAA=",
      "dotnet.6.0.0-rc.1.21376.24.js": "sha256-GSlngmlOn0UU8LJLn+t\/JNTul4dHENeq2y9M42lZNGU="
    },
    "satelliteResources": null
  }
}

The Blazor manifest includes the list of library initializers computed at build/publish time. Library initializers can export a couple of functions:

  • beforeBlazorStarts which Blazor calls providing the options and the manifest and that can be use to tweak the options, for example, to replace the default bootResourceLoader.
  • afterBlazorStarted which is called to let libraries know that Blazor has successfully started in case they need to do some initialization work afterwards.

Here is an example of the MSBuild required to "transform" the output. In this case, we are just emitting the output to a file and not transforming it at all, however you can replace this with whatever transform you want to perform. The target that we define does three things:

  • Collects all Blazor static web assets
  • Defines a new static web asset with the extension file
  • Writes all the blazor assets to the "extension" file.

These new extension assets will automatically get included in the blazor.boot.json, compressed, and added to the service-worker-assets.json in case we are building a PWA.

They also contain the same hash data that is available to other assets, so caching and integrity can be maintained/implemented in the same way.

Finally, we could choose to remove all the dll entries on the manifest and just leave the extensions, however I believe its preferable to just extend the build output with more files and leave the existing ones there. The original dlls won't be downloaded by default (if you are not using a service worker) and if you are building a PWA, you can filter them out on the service-worker.js file.

Update:
I've simplified the process for Blazor webassembly as follows:

  • There's an item group `PublishBlazorBootStaticWebAsset where all blazor resources have been collected.
  • There's a BlazorPublishExtension itemgroup where you can define the files you want to add, specify the relative path and the extension name and we'll do the rest.
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
   ...
  <Target Name="_CustomizeBlazorBootProcess" AfterTargets="ProcessPublishFilesForBlazor">
    <ItemGroup>
      <!-- Define a new static web asset to make sure the new extension file is included in the output -->
      <BlazorPublishExtension Include="$(IntermediateOutputPath)publish.extension.txt">
        <RelativePath>_framework/publish.extension.txt</RelativePath>
        <ExtensionName>extension:my-custom-extension</ExtensionName>
      </BlazorPublishExtension>
      <FileWrites Include="$(IntermediateOutputPath)publish.extension.txt" />
    </ItemGroup>
    <WriteLinesToFile Lines="@(PublishBlazorBootStaticWebAsset)" File="$(IntermediateOutputPath)publish.extension.txt" WriteOnlyWhenDifferent="true" />
  </Target>
</Project>

@javiercn javiercn requested a review from a team as a code owner July 28, 2021 12:31
@ghost ghost added the area-blazor Includes: Blazor, Razor Components label Jul 28, 2021
@SteveSandersonMS
Copy link
Member

One concern I have here is that this makes the precise format of blazor.boot.json become part of the public API surface. That was never the case previously, and means we'll potentially face significant limitations in how we can evolve it in the future.

To mitigate this, would it be reasonable to change the info we pass to the callbacks to only include the extensions part of the data? This way even if we need to completely change the notion of blazor.boot.json in the future we can still do so as long as there remains some way to get the extensions data.

This might also make it a lot more practical to set up a package that includes an initializer that works on WebAssembly and Server, where Server could also have this extensions notion in some way if we wanted but wouldn't have the rest of blazor.boot.json. But even if this is not the case, it's still valuable to to make blazor.boot.json become public API.

@SteveSandersonMS
Copy link
Member

Regarding your example MSBuild code above, could you clarify which parts of it refer to public, documented, supported APIs? For example:

  • The AssetTraitName and the specific values we see for AssetTraitValue (BlazorWebAssemblyResource, Culture, etc.) - would a developer be able to find docs listing the possibilities there, and would it count as breaking if we added further ones in the future, since then the logic here that filters out based on AssetTraitValue would start including extra things?
  • This string format: extension:my-custom-extension
  • What happens if you want to have multiple "asset traits"? Is that meaningful?

The original dlls won't be downloaded by default

What stops the original DLLs being downloaded by default?

@javiercn javiercn force-pushed the javiercn/blazor-initialization branch from 64bf4be to 0b0a98b Compare August 5, 2021 14:13
@javiercn
Copy link
Member Author

javiercn commented Aug 5, 2021

One concern I have here is that this makes the precise format of blazor.boot.json become part of the public API surface. That was never the case previously, and means we'll potentially face significant limitations in how we can evolve it in the future.

To mitigate this, would it be reasonable to change the info we pass to the callbacks to only include the extensions part of the data? This way even if we need to completely change the notion of blazor.boot.json in the future we can still do so as long as there remains some way to get the extensions data.

This might also make it a lot more practical to set up a package that includes an initializer that works on WebAssembly and Server, where Server could also have this extensions notion in some way if we wanted but wouldn't have the rest of blazor.boot.json. But even if this is not the case, it's still valuable to to make blazor.boot.json become public API.

I've modified the process to just pass the extensions part.

@javiercn
Copy link
Member Author

javiercn commented Aug 5, 2021

  • The AssetTraitName and the specific values we see for AssetTraitValue (BlazorWebAssemblyResource, Culture, etc.) - would a developer be able to find docs listing the possibilities there, and would it count as breaking if we added further ones in the future, since then the logic here that filters out based on AssetTraitValue would start including extra things?
  • This string format: extension:my-custom-extension
  • What happens if you want to have multiple "asset traits"? Is that meaningful?

I'm going to give people an ItemGroup with these so that they don't have to worry about them.

@javiercn
Copy link
Member Author

javiercn commented Aug 5, 2021

What stops the original DLLs being downloaded by default?

Your custom loader does by downloading whatever package format you have ahead of time (during the initializer phase), parsing it and creating an already made list of responses that matches that of the requested dlls

@javiercn javiercn force-pushed the javiercn/blazor-initialization branch from 0b0a98b to 0d3f0a9 Compare August 6, 2021 19:33
@javiercn javiercn force-pushed the javiercn/blazor-initialization branch from 0d3f0a9 to 1f94e80 Compare August 7, 2021 18:02
@javiercn
Copy link
Member Author

javiercn commented Aug 9, 2021

Ping

@@ -1,4 +1,5 @@
#nullable enable
Microsoft.AspNetCore.Components.Server.CircuitOptions.JavaScriptInitializers.get -> System.Collections.Generic.IList<string!>!
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How come this is part of the public API? I thought the list of initializers was determined by the presence of .js files on disk. Under what circumstances would a developer mutate the list at runtime by setting things in these options?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can make this an implementation detail, I think it might be ok if we let folks tweak the list of things that we will autoload.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't actually mind which way you go with this, but it still appears to be an open question.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's discuss this in this afternoon's standup and make a decision on it.

@javiercn javiercn force-pushed the javiercn/blazor-initialization branch from 3ef8b66 to a474909 Compare August 16, 2021 18:49
@javiercn javiercn merged commit 0ca2ed9 into main Aug 17, 2021
@javiercn javiercn deleted the javiercn/blazor-initialization branch August 17, 2021 08:52
@ghost ghost added this to the 6.0-rc1 milestone Aug 17, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-blazor Includes: Blazor, Razor Components

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants