Blazor WebAssembly caching fixes (#19235)
* Support logging errors that happen really early * Tolerate all the ways caching might be unavailable * Include dotnet.js in blazor.boot.json * Reorganize boot manifest to categorize files by role, not just by filename extension * Enable cache-busting and SRI check on dotnet.js * Change cache-busting to vary filename, not using querystring. Needed to make PWA manifest still work.
This commit is contained in:
parent
c9c06f573d
commit
6fe946e633
File diff suppressed because one or more lines are too long
|
|
@ -52,6 +52,12 @@ async function boot(options?: any): Promise<void> {
|
|||
window['Blazor'].start = boot;
|
||||
if (shouldAutoStart()) {
|
||||
boot().catch(error => {
|
||||
Module.printErr(error); // Logs it, and causes the error UI to appear
|
||||
if (typeof Module !== 'undefined' && Module.printErr) {
|
||||
// Logs it, and causes the error UI to appear
|
||||
Module.printErr(error);
|
||||
} else {
|
||||
// The error must have happened so early we didn't yet set up the error UI, so just log to console
|
||||
console.error(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ export const monoPlatform: Platform = {
|
|||
// before we start loading the WebAssembly files
|
||||
addGlobalModuleScriptTagsToDocument(() => {
|
||||
window['Module'] = createEmscriptenModuleInstance(resourceLoader, resolve, reject);
|
||||
addScriptTagsToDocument();
|
||||
addScriptTagsToDocument(resourceLoader);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
|
@ -112,15 +112,29 @@ export const monoPlatform: Platform = {
|
|||
},
|
||||
};
|
||||
|
||||
function addScriptTagsToDocument() {
|
||||
function addScriptTagsToDocument(resourceLoader: WebAssemblyResourceLoader) {
|
||||
const browserSupportsNativeWebAssembly = typeof WebAssembly !== 'undefined' && WebAssembly.validate;
|
||||
if (!browserSupportsNativeWebAssembly) {
|
||||
throw new Error('This browser does not support WebAssembly.');
|
||||
}
|
||||
|
||||
// The dotnet.*.js file has a version or hash in its name as a form of cache-busting. This is needed
|
||||
// because it's the only part of the loading process that can't use cache:'no-cache' (because it's
|
||||
// not a 'fetch') and isn't controllable by the developer (so they can't put in their own cache-busting
|
||||
// querystring). So, to find out the exact URL we have to search the boot manifest.
|
||||
const dotnetJsResourceName = Object
|
||||
.keys(resourceLoader.bootConfig.resources.runtime)
|
||||
.filter(n => n.startsWith('dotnet.') && n.endsWith('.js'))[0];
|
||||
const scriptElem = document.createElement('script');
|
||||
scriptElem.src = '_framework/wasm/dotnet.js';
|
||||
scriptElem.src = `_framework/wasm/${dotnetJsResourceName}`;
|
||||
scriptElem.defer = true;
|
||||
|
||||
// For consistency with WebAssemblyResourceLoader, we only enforce SRI if caching is allowed
|
||||
if (resourceLoader.bootConfig.cacheBootResources) {
|
||||
const contentHash = resourceLoader.bootConfig.resources.runtime[dotnetJsResourceName];
|
||||
scriptElem.integrity = contentHash;
|
||||
}
|
||||
|
||||
document.body.appendChild(scriptElem);
|
||||
}
|
||||
|
||||
|
|
@ -165,8 +179,8 @@ function createEmscriptenModuleInstance(resourceLoader: WebAssemblyResourceLoade
|
|||
const dotnetWasmResource = await resourceLoader.loadResource(
|
||||
/* name */ dotnetWasmResourceName,
|
||||
/* url */ `_framework/wasm/${dotnetWasmResourceName}`,
|
||||
/* hash */ resourceLoader.bootConfig.resources.wasm[dotnetWasmResourceName]);
|
||||
compiledInstance = await compileWasmModule(dotnetWasmResource, imports);
|
||||
/* hash */ resourceLoader.bootConfig.resources.runtime[dotnetWasmResourceName]);
|
||||
compiledInstance = await compileWasmModule(dotnetWasmResource, imports);
|
||||
} catch (ex) {
|
||||
module.printErr(ex);
|
||||
throw ex;
|
||||
|
|
|
|||
|
|
@ -12,19 +12,13 @@ export class WebAssemblyResourceLoader {
|
|||
credentials: 'include',
|
||||
cache: networkFetchCacheMode
|
||||
});
|
||||
const bootConfig: BootJsonData = await bootConfigResponse.json();
|
||||
const cache = await getCacheToUseIfEnabled(bootConfig);
|
||||
|
||||
// Define a separate cache for each base href, so we're isolated from any other
|
||||
// Blazor application running on the same origin. We need this so that we're free
|
||||
// to purge from the cache anything we're not using and don't let it keep growing,
|
||||
// since we don't want to be worst offenders for space usage.
|
||||
const relativeBaseHref = document.baseURI.substring(document.location.origin.length);
|
||||
const cacheName = `blazor-resources-${relativeBaseHref}`;
|
||||
return new WebAssemblyResourceLoader(
|
||||
await bootConfigResponse.json(),
|
||||
await caches.open(cacheName));
|
||||
return new WebAssemblyResourceLoader(bootConfig, cache);
|
||||
}
|
||||
|
||||
constructor (public readonly bootConfig: BootJsonData, private cache: Cache)
|
||||
constructor (public readonly bootConfig: BootJsonData, private cacheIfUsed: Cache | null)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
@ -34,21 +28,15 @@ export class WebAssemblyResourceLoader {
|
|||
}
|
||||
|
||||
loadResource(name: string, url: string, contentHash: string): LoadingResource {
|
||||
// Setting 'cacheBootResources' to false bypasses the entire cache flow, including integrity checking.
|
||||
// This gives developers an easy opt-out if they don't like anything about the default cache mechanism.
|
||||
// Note that if cacheBootResources was explicitly disabled, we also bypass hash checking
|
||||
// This is to give developers an easy opt-out from the entire caching/validation flow if
|
||||
// there's anything they don't like about it.
|
||||
|
||||
// There's also a Chromium bug we need to work around here: the CacheStorage APIs say that when
|
||||
// caches.open(name) returns a promise that succeeds, the value is meant to be a Cache instance.
|
||||
// However, if the browser was launched with a --user-data-dir param that's "too long" in some sense,
|
||||
// then even through the promise resolves as success, the value given is `undefined`.
|
||||
// See https://stackoverflow.com/a/46626574. We're reporting this to Chromium and others, but in
|
||||
// the meantime, if this.cache isn't set, just proceed without caching.
|
||||
const useCache = this.bootConfig.cacheBootResources && this.cache;
|
||||
const response = this.cacheIfUsed
|
||||
? this.loadResourceWithCaching(this.cacheIfUsed, name, url, contentHash)
|
||||
: fetch(url, { cache: networkFetchCacheMode, integrity: this.bootConfig.cacheBootResources ? contentHash : undefined });
|
||||
|
||||
const response = useCache
|
||||
? this.loadResourceWithCaching(name, url, contentHash)
|
||||
: fetch(url, { cache: networkFetchCacheMode });
|
||||
return { name, url, response };
|
||||
return { name, url, response };
|
||||
}
|
||||
|
||||
logToConsole() {
|
||||
|
|
@ -57,8 +45,12 @@ export class WebAssemblyResourceLoader {
|
|||
const cacheResponseBytes = countTotalBytes(cacheLoadsEntries);
|
||||
const networkResponseBytes = countTotalBytes(networkLoadsEntries);
|
||||
const totalResponseBytes = cacheResponseBytes + networkResponseBytes;
|
||||
const linkerDisabledWarning = this.bootConfig.linkerEnabled ? '%c' : '\n%cThis application was built with linking (tree shaking) disabled. Published applications will be significantly smaller.';
|
||||
if (totalResponseBytes === 0) {
|
||||
// We have no perf stats to display, likely because caching is not in use.
|
||||
return;
|
||||
}
|
||||
|
||||
const linkerDisabledWarning = this.bootConfig.linkerEnabled ? '%c' : '\n%cThis application was built with linking (tree shaking) disabled. Published applications will be significantly smaller.';
|
||||
console.groupCollapsed(`%cblazor%c Loaded ${toDataSizeString(totalResponseBytes)} resources${linkerDisabledWarning}`, 'background: purple; color: white; padding: 1px 3px; border-radius: 3px;', 'font-weight: bold;', 'font-weight: normal;');
|
||||
|
||||
if (cacheLoadsEntries.length) {
|
||||
|
|
@ -79,17 +71,20 @@ export class WebAssemblyResourceLoader {
|
|||
async purgeUnusedCacheEntriesAsync() {
|
||||
// We want to keep the cache small because, even though the browser will evict entries if it
|
||||
// gets too big, we don't want to be considered problematic by the end user viewing storage stats
|
||||
const cachedRequests = await this.cache.keys();
|
||||
const deletionPromises = cachedRequests.map(async cachedRequest => {
|
||||
if (!(cachedRequest.url in this.usedCacheKeys)) {
|
||||
await this.cache.delete(cachedRequest);
|
||||
}
|
||||
});
|
||||
const cache = this.cacheIfUsed;
|
||||
if (cache) {
|
||||
const cachedRequests = await cache.keys();
|
||||
const deletionPromises = cachedRequests.map(async cachedRequest => {
|
||||
if (!(cachedRequest.url in this.usedCacheKeys)) {
|
||||
await cache.delete(cachedRequest);
|
||||
}
|
||||
});
|
||||
|
||||
return Promise.all(deletionPromises);
|
||||
await Promise.all(deletionPromises);
|
||||
}
|
||||
}
|
||||
|
||||
private async loadResourceWithCaching(name: string, url: string, contentHash: string) {
|
||||
private async loadResourceWithCaching(cache: Cache, name: string, url: string, contentHash: string) {
|
||||
// Since we are going to cache the response, we require there to be a content hash for integrity
|
||||
// checking. We don't want to cache bad responses. There should always be a hash, because the build
|
||||
// process generates this data.
|
||||
|
|
@ -100,7 +95,7 @@ export class WebAssemblyResourceLoader {
|
|||
const cacheKey = toAbsoluteUri(`${url}.${contentHash}`);
|
||||
this.usedCacheKeys[cacheKey] = true;
|
||||
|
||||
const cachedResponse = await this.cache.match(cacheKey);
|
||||
const cachedResponse = await cache.match(cacheKey);
|
||||
if (cachedResponse) {
|
||||
// It's in the cache.
|
||||
const responseBytes = parseInt(cachedResponse.headers.get('content-length') || '0');
|
||||
|
|
@ -109,12 +104,12 @@ export class WebAssemblyResourceLoader {
|
|||
} else {
|
||||
// It's not in the cache. Fetch from network.
|
||||
const networkResponse = await fetch(url, { cache: networkFetchCacheMode, integrity: contentHash });
|
||||
this.addToCacheAsync(name, cacheKey, networkResponse); // Don't await - add to cache in background
|
||||
this.addToCacheAsync(cache, name, cacheKey, networkResponse); // Don't await - add to cache in background
|
||||
return networkResponse;
|
||||
}
|
||||
}
|
||||
|
||||
private async addToCacheAsync(name: string, cacheKey: string, response: Response) {
|
||||
private async addToCacheAsync(cache: Cache, name: string, cacheKey: string, response: Response) {
|
||||
// We have to clone in order to put this in the cache *and* not prevent other code from
|
||||
// reading the original response stream.
|
||||
const responseData = await response.clone().arrayBuffer();
|
||||
|
|
@ -129,7 +124,7 @@ export class WebAssemblyResourceLoader {
|
|||
|
||||
// Add to cache as a custom response object so we can track extra data such as responseBytes
|
||||
// We can't rely on the server sending content-length (ASP.NET Core doesn't by default)
|
||||
await this.cache.put(cacheKey, new Response(responseData, {
|
||||
await cache.put(cacheKey, new Response(responseData, {
|
||||
headers: {
|
||||
'content-type': response.headers.get('content-type') || '',
|
||||
'content-length': (responseBytes || response.headers.get('content-length') || '').toString()
|
||||
|
|
@ -138,6 +133,34 @@ export class WebAssemblyResourceLoader {
|
|||
}
|
||||
}
|
||||
|
||||
async function getCacheToUseIfEnabled(bootConfig: BootJsonData): Promise<Cache | null> {
|
||||
// caches will be undefined if we're running on an insecure origin (secure means https or localhost)
|
||||
if (!bootConfig.cacheBootResources || typeof caches === 'undefined') {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Define a separate cache for each base href, so we're isolated from any other
|
||||
// Blazor application running on the same origin. We need this so that we're free
|
||||
// to purge from the cache anything we're not using and don't let it keep growing,
|
||||
// since we don't want to be worst offenders for space usage.
|
||||
const relativeBaseHref = document.baseURI.substring(document.location.origin.length);
|
||||
const cacheName = `blazor-resources-${relativeBaseHref}`;
|
||||
|
||||
try {
|
||||
// There's a Chromium bug we need to be aware of here: the CacheStorage APIs say that when
|
||||
// caches.open(name) returns a promise that succeeds, the value is meant to be a Cache instance.
|
||||
// However, if the browser was launched with a --user-data-dir param that's "too long" in some sense,
|
||||
// then even through the promise resolves as success, the value given is `undefined`.
|
||||
// See https://stackoverflow.com/a/46626574 and https://bugs.chromium.org/p/chromium/issues/detail?id=1054541
|
||||
// If we see this happening, return "null" to mean "proceed without caching".
|
||||
return (await caches.open(cacheName)) || null;
|
||||
} catch {
|
||||
// There's no known scenario where we should get an exception here, but considering the
|
||||
// Chromium bug above, let's tolerate it and treat as "proceed without caching".
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function countTotalBytes(loads: LoadLogEntry[]) {
|
||||
return loads.reduce((prev, item) => prev + (item.responseBytes || 0), 0);
|
||||
}
|
||||
|
|
@ -162,9 +185,9 @@ interface BootJsonData {
|
|||
}
|
||||
|
||||
interface ResourceGroups {
|
||||
readonly wasm: ResourceList;
|
||||
readonly assembly: ResourceList;
|
||||
readonly pdb?: ResourceList;
|
||||
readonly runtime: ResourceList;
|
||||
}
|
||||
|
||||
interface LoadLogEntry {
|
||||
|
|
|
|||
|
|
@ -110,7 +110,7 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Build
|
|||
{
|
||||
// If RelativeOutputPath was not specified, we assume the item will be placed at the
|
||||
// root of whatever directory is used for its resource type (e.g., assemblies go in _bin)
|
||||
outputPath = Path.GetFileName(item.ItemSpec);
|
||||
outputPath = Path.GetFileName(item.GetMetadata("TargetOutputPath"));
|
||||
}
|
||||
|
||||
return outputPath.Replace('\\', '/');
|
||||
|
|
@ -159,7 +159,7 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Build
|
|||
{
|
||||
assembly,
|
||||
pdb,
|
||||
wasm
|
||||
runtime,
|
||||
}
|
||||
#pragma warning restore IDE1006 // Naming Styles
|
||||
}
|
||||
|
|
|
|||
|
|
@ -76,13 +76,15 @@
|
|||
<_BlazorCopyLocalPaths Remove="@(_BlazorManagedRuntimeAssemby)" />
|
||||
|
||||
<BlazorOutputWithTargetPath Include="@(_BlazorCopyLocalPaths)">
|
||||
<BlazorRuntimeFile>true</BlazorRuntimeFile>
|
||||
<_BlazorBootManifestResourceType Condition="'%(Extension)' == '.dll'">assembly</_BlazorBootManifestResourceType>
|
||||
<_BlazorBootManifestResourceType Condition="'%(Extension)' == '.pdb'">pdb</_BlazorBootManifestResourceType>
|
||||
<TargetOutputPath>$(BlazorRuntimeBinOutputPath)%(_BlazorCopyLocalPaths.DestinationSubDirectory)%(FileName)%(Extension)</TargetOutputPath>
|
||||
<RelativeOutputPath>%(_BlazorCopyLocalPaths.DestinationSubDirectory)%(FileName)%(Extension)</RelativeOutputPath>
|
||||
</BlazorOutputWithTargetPath>
|
||||
|
||||
<BlazorOutputWithTargetPath Include="@(_BlazorResolvedAssembly)">
|
||||
<BlazorRuntimeFile>true</BlazorRuntimeFile>
|
||||
<_BlazorBootManifestResourceType Condition="'%(Extension)' == '.dll'">assembly</_BlazorBootManifestResourceType>
|
||||
<_BlazorBootManifestResourceType Condition="'%(Extension)' == '.pdb'">pdb</_BlazorBootManifestResourceType>
|
||||
<TargetOutputPath>$(BlazorRuntimeBinOutputPath)%(FileName)%(Extension)</TargetOutputPath>
|
||||
<RelativeOutputPath>%(FileName)%(Extension)</RelativeOutputPath>
|
||||
</BlazorOutputWithTargetPath>
|
||||
|
|
@ -111,21 +113,41 @@
|
|||
<_BlazorCopyLocalPaths Remove="@(_BlazorManagedRuntimeAssemby)" Condition="'%(Extension)' == '.dll'" />
|
||||
|
||||
<BlazorOutputWithTargetPath Include="@(_BlazorCopyLocalPaths)">
|
||||
<BlazorRuntimeFile>true</BlazorRuntimeFile>
|
||||
<_BlazorBootManifestResourceType Condition="'%(Extension)' == '.dll'">assembly</_BlazorBootManifestResourceType>
|
||||
<_BlazorBootManifestResourceType Condition="'%(Extension)' == '.pdb'">pdb</_BlazorBootManifestResourceType>
|
||||
<TargetOutputPath>$(BlazorRuntimeBinOutputPath)%(_BlazorCopyLocalPaths.DestinationSubDirectory)%(FileName)%(Extension)</TargetOutputPath>
|
||||
<RelativeOutputPath>%(_BlazorCopyLocalPaths.DestinationSubDirectory)%(FileName)%(Extension)</RelativeOutputPath>
|
||||
</BlazorOutputWithTargetPath>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<MonoWasmFile Include="$(ComponentsWebAssemblyRuntimePath)*" />
|
||||
<BlazorJSFile Include="$(BlazorJSPath)" />
|
||||
<BlazorJSFile Include="$(BlazorJSMapPath)" Condition="Exists('$(BlazorJSMapPath)')" />
|
||||
<!--
|
||||
TODO: Instead of dynamically switching the TargetOutputPath for dotnet.js here, actually
|
||||
change the physical filename inside the M.A.C.W.Runtime package so it includes the package's
|
||||
version (e.g., dotnet.3.2.0-preview3.12345.js). Then we can eliminate the following property
|
||||
and recombine the two item groups MonoWasmFile and MonoJsFile below, simply putting all of
|
||||
$(ComponentsWebAssemblyRuntimePath)* into BlazorOutputWithTargetPath using their physical names.
|
||||
|
||||
The actual value 3.2.0-preview2 is hardcoded here until we update M.A.C.W.Runtime to do this.
|
||||
-->
|
||||
<PropertyGroup>
|
||||
<TemporaryDotNetJsFileVersion>3.2.0-preview2</TemporaryDotNetJsFileVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<MonoWasmFile Include="$(ComponentsWebAssemblyRuntimePath)*.wasm" />
|
||||
<BlazorOutputWithTargetPath Include="@(MonoWasmFile)">
|
||||
<TargetOutputPath>$(BlazorRuntimeWasmOutputPath)%(FileName)%(Extension)</TargetOutputPath>
|
||||
<BlazorRuntimeFile>true</BlazorRuntimeFile>
|
||||
<_BlazorBootManifestResourceType>runtime</_BlazorBootManifestResourceType>
|
||||
</BlazorOutputWithTargetPath>
|
||||
|
||||
<MonoJsFile Include="$(ComponentsWebAssemblyRuntimePath)*.js" />
|
||||
<BlazorOutputWithTargetPath Include="@(MonoJsFile)">
|
||||
<TargetOutputPath>$(BlazorRuntimeWasmOutputPath)%(FileName).$(TemporaryDotNetJsFileVersion)%(Extension)</TargetOutputPath>
|
||||
<_BlazorBootManifestResourceType>runtime</_BlazorBootManifestResourceType>
|
||||
</BlazorOutputWithTargetPath>
|
||||
|
||||
<BlazorJSFile Include="$(BlazorJSPath)" />
|
||||
<BlazorJSFile Include="$(BlazorJSMapPath)" Condition="Exists('$(BlazorJSMapPath)')" />
|
||||
<BlazorOutputWithTargetPath Include="@(BlazorJSFile)">
|
||||
<TargetOutputPath>$(BaseBlazorRuntimeOutputPath)%(FileName)%(Extension)</TargetOutputPath>
|
||||
</BlazorOutputWithTargetPath>
|
||||
|
|
@ -300,13 +322,12 @@
|
|||
Inputs="$(MSBuildAllProjects);@(BlazorOutputWithTargetPath)"
|
||||
Outputs="$(BlazorBootJsonIntermediateOutputPath)">
|
||||
<ItemGroup>
|
||||
<_BlazorBootResource Include="@(BlazorOutputWithTargetPath->WithMetadataValue('BlazorRuntimeFile', 'true'))" />
|
||||
<_BlazorBootResource BootResourceType="assembly" Condition="'%(Extension)' == '.dll'" />
|
||||
<_BlazorBootResource BootResourceType="pdb" Condition="'%(Extension)' == '.pdb'" />
|
||||
<_BlazorBootResource BootResourceType="wasm" Condition="'%(Extension)' == '.wasm'" />
|
||||
<_BlazorBootResource Include="@(BlazorOutputWithTargetPath->HasMetadata('_BlazorBootManifestResourceType'))">
|
||||
<BootResourceType>%(BlazorOutputWithTargetPath._BlazorBootManifestResourceType)</BootResourceType>
|
||||
</_BlazorBootResource>
|
||||
</ItemGroup>
|
||||
|
||||
<GetFileHash Files="@(_BlazorBootResource->HasMetadata('BootResourceType'))" Algorithm="SHA256" HashEncoding="base64">
|
||||
<GetFileHash Files="@(_BlazorBootResource)" Algorithm="SHA256" HashEncoding="base64">
|
||||
<Output TaskParameter="Items" ItemName="_BlazorBootResourceWithHash" />
|
||||
</GetFileHash>
|
||||
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Build
|
|||
Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "blazor.boot.json");
|
||||
Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "blazor.webassembly.js");
|
||||
Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "wasm", "dotnet.wasm");
|
||||
Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "wasm", "dotnet.js");
|
||||
Assert.FileCountEquals(result, 1, Path.Combine(buildOutputDirectory, "wwwroot", "_framework", "wasm"), "dotnet.*.js");
|
||||
Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "_bin", "standalone.dll");
|
||||
Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "_bin", "Microsoft.Extensions.Logging.Abstractions.dll"); // Verify dependencies are part of the output.
|
||||
|
||||
|
|
@ -71,7 +71,7 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Build
|
|||
Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "blazor.boot.json");
|
||||
Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "blazor.webassembly.js");
|
||||
Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "wasm", "dotnet.wasm");
|
||||
Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "wasm", "dotnet.js");
|
||||
Assert.FileCountEquals(result, 1, Path.Combine(buildOutputDirectory, "wwwroot", "_framework", "wasm"), "dotnet.*.js");
|
||||
Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "_bin", "standalone.dll");
|
||||
Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "_bin", "Microsoft.Extensions.Logging.Abstractions.dll"); // Verify dependencies are part of the output.
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Build
|
|||
Assert.FileExists(result, blazorPublishDirectory, "_framework", "blazor.boot.json");
|
||||
Assert.FileExists(result, blazorPublishDirectory, "_framework", "blazor.webassembly.js");
|
||||
Assert.FileExists(result, blazorPublishDirectory, "_framework", "wasm", "dotnet.wasm");
|
||||
Assert.FileExists(result, blazorPublishDirectory, "_framework", "wasm", "dotnet.js");
|
||||
Assert.FileCountEquals(result, 1, Path.Combine(blazorPublishDirectory, "_framework", "wasm"), "dotnet.*.js");
|
||||
Assert.FileExists(result, blazorPublishDirectory, "_framework", "_bin", "standalone.dll");
|
||||
Assert.FileExists(result, blazorPublishDirectory, "_framework", "_bin", "Microsoft.Extensions.Logging.Abstractions.dll"); // Verify dependencies are part of the output.
|
||||
|
||||
|
|
@ -64,7 +64,7 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Build
|
|||
Assert.FileExists(result, blazorPublishDirectory, "_framework", "blazor.boot.json");
|
||||
Assert.FileExists(result, blazorPublishDirectory, "_framework", "blazor.webassembly.js");
|
||||
Assert.FileExists(result, blazorPublishDirectory, "_framework", "wasm", "dotnet.wasm");
|
||||
Assert.FileExists(result, blazorPublishDirectory, "_framework", "wasm", "dotnet.js");
|
||||
Assert.FileCountEquals(result, 1, Path.Combine(blazorPublishDirectory, "_framework", "wasm"), "dotnet.*.js");
|
||||
Assert.FileExists(result, blazorPublishDirectory, "_framework", "_bin", "standalone.dll");
|
||||
Assert.FileExists(result, blazorPublishDirectory, "_framework", "_bin", "Microsoft.Extensions.Logging.Abstractions.dll"); // Verify dependencies are part of the output.
|
||||
|
||||
|
|
@ -102,7 +102,7 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Build
|
|||
Assert.FileExists(result, blazorPublishDirectory, "_framework", "blazor.boot.json");
|
||||
Assert.FileExists(result, blazorPublishDirectory, "_framework", "blazor.webassembly.js");
|
||||
Assert.FileExists(result, blazorPublishDirectory, "_framework", "wasm", "dotnet.wasm");
|
||||
Assert.FileExists(result, blazorPublishDirectory, "_framework", "wasm", "dotnet.js");
|
||||
Assert.FileCountEquals(result, 1, Path.Combine(blazorPublishDirectory, "_framework", "wasm"), "dotnet.*.js");
|
||||
Assert.FileExists(result, blazorPublishDirectory, "_framework", "_bin", "standalone.dll");
|
||||
Assert.FileExists(result, blazorPublishDirectory, "_framework", "_bin", "Microsoft.Extensions.Logging.Abstractions.dll"); // Verify dependencies are part of the output.
|
||||
|
||||
|
|
@ -164,7 +164,7 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Build
|
|||
Assert.FileExists(result, blazorPublishDirectory, "_framework", "blazor.boot.json");
|
||||
Assert.FileExists(result, blazorPublishDirectory, "_framework", "blazor.webassembly.js");
|
||||
Assert.FileExists(result, blazorPublishDirectory, "_framework", "wasm", "dotnet.wasm");
|
||||
Assert.FileExists(result, blazorPublishDirectory, "_framework", "wasm", "dotnet.js");
|
||||
Assert.FileCountEquals(result, 1, Path.Combine(blazorPublishDirectory, "_framework", "wasm"), "dotnet.*.js");
|
||||
Assert.FileExists(result, blazorPublishDirectory, "_framework", "_bin", "standalone.dll");
|
||||
Assert.FileExists(result, blazorPublishDirectory, "_framework", "_bin", "Microsoft.Extensions.Logging.Abstractions.dll"); // Verify dependencies are part of the output.
|
||||
|
||||
|
|
@ -208,7 +208,7 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Build
|
|||
Assert.FileExists(result, blazorPublishDirectory, "_framework", "blazor.boot.json");
|
||||
Assert.FileExists(result, blazorPublishDirectory, "_framework", "blazor.webassembly.js");
|
||||
Assert.FileExists(result, blazorPublishDirectory, "_framework", "wasm", "dotnet.wasm");
|
||||
Assert.FileExists(result, blazorPublishDirectory, "_framework", "wasm", "dotnet.js");
|
||||
Assert.FileCountEquals(result, 1, Path.Combine(blazorPublishDirectory, "_framework", "wasm"), "dotnet.*.js");
|
||||
Assert.FileExists(result, blazorPublishDirectory, "_framework", "_bin", "standalone.dll");
|
||||
Assert.FileExists(result, blazorPublishDirectory, "_framework", "_bin", "Microsoft.Extensions.Logging.Abstractions.dll"); // Verify dependencies are part of the output.
|
||||
|
||||
|
|
@ -254,7 +254,7 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Build
|
|||
Assert.FileExists(result, blazorPublishDirectory, "_framework", "blazor.boot.json");
|
||||
Assert.FileExists(result, blazorPublishDirectory, "_framework", "blazor.webassembly.js");
|
||||
Assert.FileExists(result, blazorPublishDirectory, "_framework", "wasm", "dotnet.wasm");
|
||||
Assert.FileExists(result, blazorPublishDirectory, "_framework", "wasm", "dotnet.js");
|
||||
Assert.FileCountEquals(result, 1, Path.Combine(blazorPublishDirectory, "_framework", "wasm"), "dotnet.*.js");
|
||||
Assert.FileExists(result, blazorPublishDirectory, "_framework", "_bin", "standalone.dll");
|
||||
Assert.FileExists(result, blazorPublishDirectory, "_framework", "_bin", "Microsoft.Extensions.Logging.Abstractions.dll"); // Verify dependencies are part of the output.
|
||||
|
||||
|
|
|
|||
|
|
@ -41,10 +41,10 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Build
|
|||
fileHash: "pdbhashpdbhashpdbhash"),
|
||||
|
||||
CreateResourceTaskItem(
|
||||
ResourceType.wasm,
|
||||
itemSpec: "some-wasm-file",
|
||||
ResourceType.runtime,
|
||||
itemSpec: "some-runtime-file",
|
||||
relativeOutputPath: null,
|
||||
fileHash: "wasmhashwasmhashwasmhash")
|
||||
fileHash: "runtimehashruntimehash")
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -75,9 +75,9 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Build
|
|||
resourceListKey =>
|
||||
{
|
||||
var resources = parsedContent.resources[resourceListKey];
|
||||
Assert.Equal(ResourceType.wasm, resourceListKey);
|
||||
Assert.Equal(ResourceType.runtime, resourceListKey);
|
||||
Assert.Single(resources);
|
||||
Assert.Equal("sha256-wasmhashwasmhashwasmhash", resources["some-wasm-file"]);
|
||||
Assert.Equal("sha256-runtimehashruntimehash", resources["some-runtime-file"]);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -145,6 +145,7 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Build
|
|||
{
|
||||
var mock = new Mock<ITaskItem>();
|
||||
mock.Setup(m => m.ItemSpec).Returns(itemSpec);
|
||||
mock.Setup(m => m.GetMetadata("TargetOutputPath")).Returns(itemSpec);
|
||||
mock.Setup(m => m.GetMetadata("BootResourceType")).Returns(type.ToString());
|
||||
mock.Setup(m => m.GetMetadata("RelativeOutputPath")).Returns(relativeOutputPath);
|
||||
mock.Setup(m => m.GetMetadata("FileHash")).Returns(fileHash);
|
||||
|
|
|
|||
|
|
@ -110,14 +110,18 @@
|
|||
}
|
||||
}
|
||||
|
||||
function addScriptTagsToDocument() {
|
||||
async function addScriptTagsToDocument() {
|
||||
var browserSupportsNativeWebAssembly = typeof WebAssembly !== 'undefined' && WebAssembly.validate;
|
||||
if (!browserSupportsNativeWebAssembly) {
|
||||
throw new Error('This browser does not support WebAssembly.');
|
||||
}
|
||||
|
||||
var bootJson = await fetch('/_framework/blazor.boot.json').then(res => res.json());
|
||||
var dotNetJsResourceName = Object.keys(bootJson.resources.runtime)
|
||||
.filter(name => name.endsWith('.js'));
|
||||
|
||||
var scriptElem = document.createElement('script');
|
||||
scriptElem.src = '/_framework/wasm/dotnet.js';
|
||||
scriptElem.src = '/_framework/wasm/' + dotNetJsResourceName;
|
||||
document.body.appendChild(scriptElem);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue