Skip to content
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

dotnet.js cache busting (fingerprinting), Symptom: Cannot read properties of undefined (reading 'out') #102272

Open
1 task done
hakenr opened this issue May 15, 2024 · 36 comments
Assignees
Labels
arch-wasm WebAssembly architecture area-VM-meta-mono
Milestone

Comments

@hakenr
Copy link

hakenr commented May 15, 2024

Is there an existing issue for this?

  • I have searched the existing issues

Describe the bug

When upgrading from 8.0.4 to 8.0.5, the dotnet.js changed.
In opposite to dotnet.native.{fingerprint}.js and dotnet.runtime.{fingerprint}.js, the dotnet.js is not fingerprinted and thus cached. The usual Clear site data in F12-DevTools do not help as the file is cached by the browser and still taken from "(disk cache)".

The 8.0.4 dotnet.js is not compatible with 8.0.5 dotnet.native.{...}.js or dotnet.runtime.{...}.js and the symptom is:

logging.ts:32  MONO_WASM: TypeError: Cannot read properties of undefined (reading 'out')
    at $l (https://localhost:44303/_framework/dotnet.runtime.8.0.5.gongq8hbow.js:3:198913)
    at ze (https://localhost:44303/_framework/dotnet.js:3:30895)
    at https://localhost:44303/_framework/dotnet.js:3:30131
    at async Object.create (https://localhost:44303/_framework/dotnet.js:3:34510)
    at async https://localhost:44303/_framework/blazor.web.js:1:154483
    at async https://localhost:44303/_framework/blazor.web.js:1:164313
startup.ts:43  Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'out')
    at $l (startup.ts:43:17)
    at ze (run.ts:461:11)
    at run.ts:505:11
    at async Object.create (run.ts:361:72)
    at async blazor.web.js:1:154483
    at async blazor.web.js:1:164313

image

(No module parameter set when calling configureRuntimeStartup() from initializeModules().)

Expected Behavior

dotnet.js to be correctly fingerprinted (As it already used to be before splitting to several files?).

Steps To Reproduce

VS 2022 17.9.6 - Create new BWA - WebAssembly project + run locally in browser.
Update VS to 17.9.7 (brings dotnet 8.0.5).
Run the same project again.

Exceptions (if any)

No response

.NET Version

8.0.5 (9.0.0-preview3 still does not resolve this issue)

Anything else?

WORKAROUND: Ctrl+F5 in browser to force reloading the file.

(It seems this is already in progress, as there are some options in tests which enable checking the fingerprinting on dotnet.js.)

@hakenr
Copy link
Author

hakenr commented May 15, 2024

Related:

Failing lines of code:

@MackinnonBuck MackinnonBuck transferred this issue from dotnet/aspnetcore May 15, 2024
@dotnet-issue-labeler dotnet-issue-labeler bot added the needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners label May 15, 2024
@dotnet-policy-service dotnet-policy-service bot added the untriaged New issue has not been triaged by the area owner label May 15, 2024
@jeffschwMSFT jeffschwMSFT added arch-wasm WebAssembly architecture area-VM-meta-mono labels May 16, 2024
@lewing lewing added this to the 8.0.x milestone May 16, 2024
@lewing lewing removed untriaged New issue has not been triaged by the area owner labels May 16, 2024
@lewing
Copy link
Member

lewing commented May 16, 2024

cc @pavelsavara

@pavelsavara
Copy link
Member

perhaps we could somehow attach build timestamp in the blazor code
https://github.com/dotnet/aspnetcore/blob/c91ce3d2601394242866e7beae2b2339ced3a5d9/src/Components/Web.JS/src/Platform/Mono/MonoPlatform.ts#L119-L131

something like import('_framework/dotnet.js?version=' + buildId)

The other option is to bundle dotnet.js inside blazor's JS file. Assuming that it doesn't have the same issue.

cc @maraf @javiercn

@maraf
Copy link
Member

maraf commented May 17, 2024

There is a guidance about hosting and upgrading blazor apps dotnet/aspnetcore#52022 (the title mentions "major" upgrade, but in reality the situation can happen even with servicing release).

The situation will be better for hosted mode in .NET 9 thanks to dotnet/sdk#39405 and dotnet/sdk#39636.

For standalone mode ideally we should be fingerprinting everything (blazor.js, dotnet.js, all dlls/wasms, but also scripts and styles from nuget packages). But that requires rewriting code that references those files. Unlike for webpack and similar we don't have tooling to do it safely. We should review again if we can it do for the most typical scenarios

The other option is to bundle dotnet.js inside blazor's JS file. Assuming that it doesn't have the same issue.

This is unfortunately just about moving the problem to a different file

@TomGathercole
Copy link

Is there any way we can manually introduce any extra cache-busting? Ctrl+F5 is an obvious solution, but many of our customers are non-technical and they'll end up having to raise a support ticket with us to find that out.

For unrelated reasons we have to adopt patch releases of dotnet very quickly, and I'd rather avoid causing our customers grief a bunch more times before .NET 9 releases.

@maraf
Copy link
Member

maraf commented May 17, 2024

Is there any way we can manually introduce any extra cache-busting?

dotnet/aspnetcore#55751 (comment)

"Custom middleware that sets Cache-Control: no-store" for dotnet.js URL

@Bellarmine-Head
Copy link

Bellarmine-Head commented May 17, 2024

"Custom middleware that sets Cache-Control: no-store" for dotnet.js URL

Which isn't strictly cache-busting of course... :-)

It's cache-punting.

@kg
Copy link
Contributor

kg commented May 17, 2024

Is there any way we can manually introduce any extra cache-busting? Ctrl+F5 is an obvious solution, but many of our customers are non-technical and they'll end up having to raise a support ticket with us to find that out.

For unrelated reasons we have to adopt patch releases of dotnet very quickly, and I'd rather avoid causing our customers grief a bunch more times before .NET 9 releases.

My solution for this Chrome 'feature' in the past was for each deployment to go into a different versioned folder, i.e. cdn.invalid/myapp/207/ and then load the whole app from in that directory. That way paths like dotnet.js can stay hardcoded in my HTML and static files, but they don't get incorrectly cached.

Of course this isn't exactly trivial to do, but it eliminates the need to find a way to append ?buster to every URL, or manually rewrite paths in static files, or set cache-control headers. So in the interim I would suggest trying that if you can.

@maraf
Copy link
Member

maraf commented May 17, 2024

perhaps we could somehow attach build timestamp in the blazor code https://github.com/dotnet/aspnetcore/blob/c91ce3d2601394242866e7beae2b2339ced3a5d9/src/Components/Web.JS/src/Platform/Mono/MonoPlatform.ts#L119-L131

something like import('_framework/dotnet.js?version=' + buildId)

We can use loadBootResource to modify a URL resolution for dotnet.js

<script src="_framework/blazor.web.js?v=8.0.5" autostart="false"></script>
<script>
    Blazor.start({
        webAssembly: {
            loadBootResource: function (type, name, defaultUri, integrity) {
                switch (type) {
                    case 'dotnetjs':
                        return `_framework/${name}?v=8.0.5`;
                }
            }
        }
    });
</script>

In the sample I'm adding also a query string for blazor.web.js, which shouldn't be required for this particular issue. Standalone app can be modified similarly. More details in https://learn.microsoft.com/en-us/aspnet/core/blazor/fundamentals/startup?view=aspnetcore-8.0#load-client-side-boot-resources

@vcsjones vcsjones removed the needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners label May 17, 2024
@javiercn
Copy link
Member

@maraf is there a chance that blazor/dotnet.js get out of "sync"? if not, we could on the Blazor side, stamp the version for the import as part of the build process, and we could then also stamp the 8.0.x on the relevant dotnet.js file as part of the build to make sure everything lines up.

@maraf
Copy link
Member

maraf commented May 21, 2024

@maraf is there a chance that blazor/dotnet.js get out of "sync"? if not, we could on the Blazor side, stamp the version for the import as part of the build process, and we could then also stamp the 8.0.x on the relevant dotnet.js file as part of the build to make sure everything lines up.

Ideally no, but blazor.js comes from a package with expicit version in csproj. Dotnet.js version might be affected by presence of workload and currentness of workload manifest. In SDK previews targeting downlevel is also not very stable.

The only 100% safe solution from my POV is to put a placeholder in blazor.js and replace it with actual version (fingerprint) of dotnet.js during app build.

@pavelsavara
Copy link
Member

We need to make sure that the whole chain starting with HTML has ability to bust the cache. It could be hash in the file name or it could be query string.

Trying to solve this problem in the middle of the dependency chain (dotnet.js) would not work.
Because possibly the blazor.js is stale too. We need something which will bust blazor.js too, so that it could bust dotnet.js

Adding Etag to HTML file could make the validation cheap.

@maraf
Copy link
Member

maraf commented May 21, 2024

Yes, using etag + if-none-match for non-per-deployment-unique URLs and cache for ever for unique URLs is the way to go

@Bellarmine-Head
Copy link

For me the two weak points of Blazor WebAssembly have always been cache-busting and the need to set the Server project as the sole startup project in Visual Studio after creating new solution, or cloning solution from repo (since startup project choice is stored in .suo and not in .sln where it belongs).

From my client-side index.html file server-side App.razor component, the following are absolutely essential:-

<link href="css/app.css?v=14" rel="stylesheet" />   <!-- increment ?v=N when file changes -->

<link href="OutThink.ContentEditorWebsite.Server.styles.css?v=26" rel="stylesheet" />   <!-- increment ?v=N when file changes -->

<script src="js/app/app.js?v=35"></script>   <!-- increment ?v=N when file changes -->

@Bellarmine-Head
Copy link

<link href="OutThink.ContentEditorWebsite.Server.styles.css?v=26" rel="stylesheet" />

This synthesized CSS file is sent with an etag in the response headers, and I thought this was for cache-busting purposes, but imo it never worked, and I've always had to set the ?v=N query string.

@maraf
Copy link
Member

maraf commented May 21, 2024

This synthesized CSS file is sent with an etag in the response headers, and I thought this was for cache-busting purposes, but imo it never worked, and I've always had to set the ?v=N query string.

AFAIK the problem is that it does set etag, but it doesn't set cache-control and thus it's up to the browser. Whereas for blazor.js it does add cache-control: no-store and thus a revalidate request is issued by the browser

app.css

image
image

blazor.web.js

image
image

@Bellarmine-Head
Copy link

AFAIK the problem is that it does set etag, but it doesn't set cache-control and thus it's up to the browser.

Ok, that would explain it.

Re. index.html / App.Razor, I've never had a problem with the browser caching the Blazor WebAssembly-served HTML. E.g. all changes I make here are immediately picked up, and I've never had to worry about having to hit ctrl+F5 or anything like that.

@maraf
Copy link
Member

maraf commented May 21, 2024

From my testing it has always served HTML with cache-control: no-cache, no-store

@pingu2k4
Copy link

Hey.

Also getting the issue here.
We are running a .net 8 Blazor WASM standalone app.

Have tried the above workaround regarding the loadBootResource, but doesn't work for us, and we still get the same errors.
(I did make the relevant changes from the code posted, to wasm standalone, whiere were the following:)

<script src="_framework/blazor.webassembly.js?v=8.0.5" autostart="false"></script>
<script>
    Blazor.start({
        loadBootResource: function (type, name, defaultUri, integrity) {
            console.log(`Loading: '${type}', '${name}', '${defaultUri}', '${integrity}'`);
            switch (type) {
                case 'dotnetjs':
                    return `_framework/${name}?v=8.0.5`;
            }
        }
    });
</script>

We are deploying using Azure DevOps, and calling the task AzureStaticWebApp@0. Is there a way for us to work around this issue currently that anyone is aware of?

@maraf
Copy link
Member

maraf commented May 22, 2024

Have tried the above workaround regarding the loadBootResource, but doesn't work for us, and we still get the same errors.
(I did make the relevant changes from the code posted, to wasm standalone, whiere were the following:)

@pingu2k4 Can you please verify that the deployed app contains correct dotnet.js file? We have at the same time an issue with incremental build. Does Ctrl+Shift+R solve the problem (so that the deployed file is correct)? Do you have any intermediate cache in the middle that could strip the query string?

@pingu2k4
Copy link

Have tried the above workaround regarding the loadBootResource, but doesn't work for us, and we still get the same errors.
(I did make the relevant changes from the code posted, to wasm standalone, whiere were the following:)

@pingu2k4 Can you please verify that the deployed app contains correct dotnet.js file? We have at the same time an issue with incremental build. Does Ctrl+Shift+R solve the problem (so that the deployed file is correct)? Do you have any intermediate cache in the middle that could strip the query string?

Hey. :)
I don't know how to verify that the dotnet.js is the correct one or not, however it is downloading what was built and deployed. I had already been clearing browser cache etc.

To be clear, our app still works locally, but we have not updated our local .net to 8.0.5 yet. We are building on Azure devops, and deploying to azure static web app. The build pipeline appears to be successful, but loading the site, we get the same errors as posted in this issue.

All the static assets appear to be correct etc, but the blazor site gets stuck on the initial loading screen, with the errors matching those in this issue.

The cache busting mechanism is also working, as in the files being downloaded have the appended query.
image

Originally posted an issue before we found this issue here: Azure/static-web-apps#1475

@double-dubs
Copy link

I am having the exact same problem. Also deploying to Azure Static Web Apps. Runs fine locally. I also tried the above cache busting work around, but didn't fix my problem either.

@javiercn
Copy link
Member

javiercn commented May 22, 2024

@pingu2k4 @double-dubs could you check that the file in your wwwroot folder (do a local publish) is the same as the one you can get from an in-private browser window navigating to your deployed site? (size and hash)

To compare the hashes, from powershell you can run Get-FileHash and it'll print the hash in Hex

@double-dubs
Copy link

One thing I am noticing, is when I do a local publish and look in the _framework folder the dotnet.js file is dated 4/16/24 and 36KB. Is that correct? Would that be the latest build date? Many of the other files have newer or today's date on them.

@javiercn
Copy link
Member

@double-dubs that file is not modified by the build, it's just copied, so it makes sense for it to have an older timestamp. That said, hashes are the way to know for sure what's going on.

@double-dubs
Copy link

Ok, the hash did NOT match on the dotnet.js file. I did a hash on the blazor.webassebly.js file and that one matched. Is Azure static web apps doing some caching on that file? I looked in the portal UI and don't see any options to clear the cache.

@pingu2k4
Copy link

pingu2k4 commented May 23, 2024

Hey,

The files when locally published to folder, versus published to SWA through our azure pipeline differs.

I made a diff in case that is useful...
(Local publish on the left, SWA published file on the right)

@maraf
Copy link
Member

maraf commented May 23, 2024

Based on the git hashes in the provided diff, your local dotnet.js is 8.0.0 and Azure SWA is 8.0.3, which is very odd based on the screenshot in comment above showing dotnet.runtime.8.0.5.*.js

@pingu2k4
Copy link

Running dotnet --version locally I get 8.0.205, and not sure what would be running on our pipelines as we don't directly specify, but one of the differences that I noted between the last successful deployment and deploying the same commit once it started failing, so it bringing in 8.0.5 related stuff.

A small example, but on the left in this screenshot was the azure devops pipeline logs from the last successful build, and on the right is deploying the same commit once it started failing.

image

@maraf
Copy link
Member

maraf commented May 23, 2024

The 8.0.205 contains 8.0.4 runtime, so it's even more odd that your local publish dir contains 8.0.0. Can you please publish again -bl and share a msbuild.binlog (either here or through a ticket at https://developercommunity.visualstudio.com/ with MSFT only visible attachment)

@maraf
Copy link
Member

maraf commented May 23, 2024

Brotli compressed dotnet.js asset can get stale with incremental publish. It happens when the publish folder already contained previously published assets (so not typical CI scenario, but it could have affect on manual publish). Brotli compressed assets are computed only for publish, during build they are not computed at all.

Setup

  • Have SDK 8.0.204
  • Have SDK 8.0.300

Repro

  1. Create new project (web with wasm interativity or wasm standalone)
  2. Publish with older SDK (8.0.204)
  3. Publish with newer SDK (8.0.300)

Output in bin\Release\net8.0\publish\

  • dotnet.js is updated
  • dotnet.js.gz is updated
  • dotnet.js.br is stale

msbuild.binlog.zip (the asset is obj\Release\net8.0\\compressed\publish\maj14cmhj5.br is not recomputed with second publish)

Skipping 'D:\Development\samples\BuildIncrementalismSdkUpgrade\BlazorWeb\BlazorWeb.Client\bin\Release\net8.0\wwwroot\_framework\dotnet.js' because 'obj\Release\net8.0\\compressed\publish\maj14cmhj5.br' is newer than 'D:\Development\samples\BuildIncrementalismSdkUpgrade\BlazorWeb\BlazorWeb.Client\bin\Release\net8.0\wwwroot\_framework\dotnet.js'.

For build compression, the file hash is computed from runtime pack location, and so it changes when runtime pack version changes (because of different location). For publish compression, the file hash is computed from build output path (eg. bin\Release\net8.0\wwwroot\_framework\dotnet.js), and so it doesn't change when runtime pack version changes. With BuildCompressionFormats="" the same issue manifests also for GZip.

This logic seems to be correct only when "runtime pack file" is newer than "last build compressed file" (the same for GZip) https://github.com/dotnet/sdk/blob/main/src/StaticWebAssetsSdk/Tasks/Compression/BrotliCompress.cs#L93

It can probably happen for any asset coming directly from runtime pack and not modified during publish. Assemblies are now transformed to webcil, ICU and all JS files might be probably affected.

@LaughingJohn
Copy link

LaughingJohn commented May 28, 2024

I've just fired up my Blazor App in VS2022 preview, I think for the first time since upgrading to 17.11 preview 1.0, and have got this which I think might be related? (question mark because I'm not sure I understand the issue). This is just running it up in debug. I tried deleting the bin & obj folders, but that made no difference.

MONO_WASM: TypeError: Cannot read properties of undefined (reading 'out')
    at $l (https://localhost:44376/_framework/dotnet.runtime.8.0.5.vjjqs9rnu5.js:3:198913)
    at ze (https://localhost:44376/_framework/dotnet.js:3:30895)
    at https://localhost:44376/_framework/dotnet.js:3:30131
    at async Object.create (https://localhost:44376/_framework/dotnet.js:3:34510)
    at async https://localhost:44376/_framework/blazor.webassembly.js?v=gvHfnndfEu1tRf0rFb5988rWq7ITIotOaE8-AMbKYbc:1:43466
    at async https://localhost:44376/_framework/blazor.webassembly.js?v=gvHfnndfEu1tRf0rFb5988rWq7ITIotOaE8-AMbKYbc:1:58010
    at async mn (https://localhost:44376/_framework/blazor.webassembly.js?v=gvHfnndfEu1tRf0rFb5988rWq7ITIotOaE8-AMbKYbc:1:57613)
Error in mono_download_assets: TypeError: Cannot read properties of undefined (reading 'out')
TypeError: Cannot read properties of undefined (reading 'out')
Stack trace:
 >  at $l (https://localhost:44376/_framework/dotnet.runtime.8.0.5.vjjqs9rnu5.js:3:198913)
 >    at ze (https://localhost:44376/_framework/dotnet.js:3:30895)
 >    at https://localhost:44376/_framework/dotnet.js:3:30131
 >    at async Object.create (https://localhost:44376/_framework/dotnet.js:3:34510)
 >    at async https://localhost:44376/_framework/blazor.webassembly.js?v=gvHfnndfEu1tRf0rFb5988rWq7ITIotOaE8-AMbKYbc:1:43466
 >    at async https://localhost:44376/_framework/blazor.webassembly.js?v=gvHfnndfEu1tRf0rFb5988rWq7ITIotOaE8-AMbKYbc:1:58010
 >    at async mn (https://localhost:44376/_framework/blazor.webassembly.js?v=gvHfnndfEu1tRf0rFb5988rWq7ITIotOaE8-AMbKYbc:1:57613)

@steamonimo
Copy link

steamonimo commented May 31, 2024

Same problem but with a twist: It runs in development but deployed to the same machine it does not run. It makes no difference if the cache is cleared or not:

http://localhost/_framework/dotnet.runtime.8.0.5.8hv7wyhg5a.js:3:198913
NO_WASM: TypeError: Cannot read properties of undefined (reading 'out')

I find it interesting that after the installation of the visual studio update (17.10.1) AND hosting-package for 8.0.6 the command dotnet --version does not give me 8.0.6 but 8.0.300.

What a bummer. My blazor based release pipeline came to a total standstill and client is not amused. Uninstalling visual studio update 17.10.1 now. Same problem with 17.10.0 and - another bummer - you can not go back to 17.9.x !

@maraf
Copy link
Member

maraf commented May 31, 2024

@steamonimo What do you mean by deployed? Are you doing it from VS or command line? Can you produce a binlog and share it (how to here https://msbuildlog.com)?

@steamonimo
Copy link

@maraf Here are the binlog files. Please let me know if you need more!

@maraf
Copy link
Member

maraf commented Jun 6, 2024

@steamonimo Sorry for late reply. From the build log is visible that it skipped copying dotnet.js as the build has decided it's new enough. Do you have by any change still the build output on disk? Can you upload the _framework/dotnet.js as well?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
arch-wasm WebAssembly architecture area-VM-meta-mono
Projects
None yet
Development

No branches or pull requests