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

With Blazor .NET 8, using JSHost.ImportAsync in component in a Razor Class Library (RCL) to load a collocated JavaScript module doesn't appear to work #55817

Closed
1 task done
aventius-software opened this issue May 17, 2024 · 10 comments
Labels
area-blazor Includes: Blazor, Razor Components ✔️ Resolution: Answered Resolved because the question asked by the original author has been answered. question Status: Resolved

Comments

@aventius-software
Copy link

Is there an existing issue for this?

  • I have searched the existing issues

Describe the bug

Since the unmarshalled method of calling JavaScript from Blazor has been deprecated I'm trying to use the new JSImport attribute introduced with .NET 7 (although my project is .NET 8) and of course JSHost.ImportAsync to load a collocated JavaScript module, however I cannot get the module to load. The component and the JavaScript file are in a Razor component library (RCL) which I'm referencing in my Blazor webassembly project. I've followed the steps here with some slight modification and this works when the component and JavaScript are in the webassembly project, but if I move them to the RCL then I get an error in the console as show below:-

Unhandled exception rendering component: TypeError: Failed to fetch dynamically imported module: https://localhost:7293/_framework/Test.razor.js

Using normal JS interop from a RCL works fine, it seems that only the JSHost.ImportAsync method doesn't work when called in a RCL

Expected Behavior

After moving the component and code to the RCL, it should work the same as when the component and JavaScript code are in the webassembly project

Steps To Reproduce

  1. Create 'standalone blazor webassembly' app project called 'JSHostImportIssue'
  2. Add a 'razor class library' project to the solution called 'RazorClassLibrary'
  3. Add the following line to the to the 'RazorClassLibrary' project file
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>

so that contents now looks like this...

<Project Sdk="Microsoft.NET.Sdk.Razor">

	<PropertyGroup>
		<TargetFramework>net8.0</TargetFramework>
		<Nullable>enable</Nullable>
		<ImplicitUsings>enable</ImplicitUsings>
		<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
	</PropertyGroup>

	<ItemGroup>
		<SupportedPlatform Include="browser" />
	</ItemGroup>

	<ItemGroup>
		<PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="8.0.5" />
	</ItemGroup>

</Project>
  1. Add dependency reference for the 'RazorClassLibrary' project to the 'JSHostImportIssue' app project
  2. Create empty razor component in 'RazorClassLibrary' project called 'Test.razor' in the root of the project, not in any sub folders
  3. Add following code to the component (taken and amended from https://learn.microsoft.com/en-us/aspnet/core/blazor/javascript-interoperability/import-export-interop?view=aspnetcore-8.0)
@using System.Runtime.InteropServices.JavaScript

<h1>
    JS <code>[JSImport]</code>/<code>[JSExport]</code> Interop
    (Call JS Example 1)
</h1>

@(message is not null ? message : string.Empty)

@code {
    private string? message;

    protected override async Task OnInitializedAsync()
    {
        await JSHost.ImportAsync("Test", "./Test.razor.js");

        message = GetValueFromJSFunc();
    }
}
  1. Create new cs file called 'Test.razor.cs' in the same location as the 'Test.razor' component
  2. Add following content to the file (taken and amended from https://learn.microsoft.com/en-us/aspnet/core/blazor/javascript-interoperability/import-export-interop?view=aspnetcore-8.0)
using System.Runtime.InteropServices.JavaScript;
using System.Runtime.Versioning;

namespace RazorClassLibrary;

[SupportedOSPlatform("browser")]
public partial class Test
{
    [JSImport("someFunc", "Test")]
    internal static partial string GetValueFromJSFunc();
}
  1. Create a javascript file called 'Test.razor.js' in the same location as the 'Test.razor' component
  2. Add the following contents to the file
export function someFunc() {
	return "hello from js";
}
  1. Add the following code to the 'Home.razor' component in the main webassembly app project (its under 'Pages' folder)
<RazorClassLibrary.Test />
  1. Run app, the following error occurs listed in the console
Unhandled exception rendering component: TypeError: Failed to fetch dynamically imported module: https://localhost:7293/_framework/Test.razor.js
  1. Also, tried changing the component code to the following (see below), but both produce the same error as above
@using System.Runtime.InteropServices.JavaScript

<h1>
    JS <code>[JSImport]</code>/<code>[JSExport]</code> Interop
    (Call JS Example 1)
</h1>

@(message is not null ? message : string.Empty)

@code {
    private string? message;

    protected override async Task OnInitializedAsync()
    {        
        await JSHost.ImportAsync("Test", "./_content/RazorClassLibrary/Test.razor.js");

        message = GetValueFromJSFunc();
    }
}
@using System.Runtime.InteropServices.JavaScript

<h1>
    JS <code>[JSImport]</code>/<code>[JSExport]</code> Interop
    (Call JS Example 1)
</h1>

@(message is not null ? message : string.Empty)

@code {
    private string? message;

    protected override async Task OnInitializedAsync()
    {        
        await JSHost.ImportAsync("Test", "../Test.razor.js");

        message = GetValueFromJSFunc();
    }
}

Exceptions (if any)

No response

.NET Version

8.0.205

Anything else?

@aventius-software
Copy link
Author

For reference, there is a workaround but it means not using collocation of JavaScript. If you copy the JavaScript file to the 'wwwroot' folder in the RCL (I also renamed it to 'Test.js'), then change the JSHost.ImportAsync line to this:-

await JSHost.ImportAsync("Test", "/_content/RazorClassLibrary/Test.js");

then it loads the module fine and all works great, its just the collocated JavaScript loading method that doesn't appear to work (unless of course I'm missing something obvious ;-)

@javiercn
Copy link
Member

@aventius-software thanks for contacting us.

This appears to relate to the native webassembly interop mechanism, not related to blazor.

Collocating JS files only works for files with *.razor.js. This mechanism is something that the runtime will have to add explicit support for.

@javiercn javiercn transferred this issue from dotnet/aspnetcore May 20, 2024
@maraf
Copy link
Member

maraf commented May 20, 2024

I don't think we have enough of context in JSHost class to support collocated file loading.

cc @pavelsavara

@pavelsavara
Copy link
Member

Collocating JS files only works for files with *.razor.js. This mechanism is something that the runtime will have to add explicit support for.

Blazor could add their own API for resolving location of the script and wrap JSHost.ImportAsync. Runtime has no business knowing anything about razor path structure.

Note that JSHost.ImportAsync URL is relative to _framework folder.
So I think that in blazor you typicaly want to start relative path with ../

@aventius-software
Copy link
Author

This appears to relate to the native webassembly interop mechanism, not related to blazor.

Collocating JS files only works for files with *.razor.js. This mechanism is something that the runtime will have to add explicit support for.

Hi @javiercn I was using the *.razor.js naming in the original examples quoted at the top of the page, I only renamed to normal *.js when I moved the file to the RCL's 'wwwroot' folder in my 'workaround' change. The collocating using JSHost method works if the *.razor.js file is in a single webassembly Blazor project, it only seems to fail if you do it in a RCL. It's like something isn't being 'triggered' to include the file or bundle it or whatever... ;-)

Note that JSHost.ImportAsync URL is relative to _framework folder. So I think that in blazor you typicaly want to start relative path with ../

Hi @pavelsavara I did try changing the path to start '../' (see examples at top of page), but that doesn't work either unfortunately.

@maraf
Copy link
Member

maraf commented May 20, 2024

The collocating using JSHost method works if the *.razor.js file is in a single webassembly Blazor project, it only seems to fail if you do it in a RCL. It's like something isn't being 'triggered' to include the file or bundle it or whatever... ;-)

It (coincidentally) works because all *.razor.js ends in wwwroot (during publish) for the "current" project. But for referenced RCL, it ends in wwwroot/_content/{RCL name} and thus it doesn't work

@aventius-software
Copy link
Author

We can of course just use the alternate method of placing the file in the wwwroot folder as I mentioned previously... maybe this isn't a true bug, but rather a current limitation then? Maybe this might be worth mentioning as a limitation in the documentation?

@maraf
Copy link
Member

maraf commented May 21, 2024

Maybe this might be worth mentioning as a limitation in the documentation?

I have opened a request for making the documention more clear dotnet/AspNetCore.Docs#32618 on this subject.

We don't have a context of razor files on runtime level and supporting web files next to general purpose C# files doesn't make a lot of sense. I'm going to move the issue back to aspnetcore in case Blazor would want to add an API for path resolution or something like that.

@maraf maraf transferred this issue from dotnet/runtime May 21, 2024
@dotnet-issue-labeler dotnet-issue-labeler bot added the area-blazor Includes: Blazor, Razor Components label May 21, 2024
@javiercn
Copy link
Member

@maraf thanks for the details.

This seems to be working as expected. We don't have plans to change the way the JS files get placed on the RCL nor to provide a special API to avoid specifying the _content/{PackageId} piece. Since that's just a convention.

@javiercn javiercn added question ✔️ Resolution: Answered Resolved because the question asked by the original author has been answered. labels May 21, 2024
Copy link
Contributor

This issue has been resolved and has not had any activity for 1 day. It will be closed for housekeeping purposes.

See our Issue Management Policies for more information.

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 ✔️ Resolution: Answered Resolved because the question asked by the original author has been answered. question Status: Resolved
Projects
None yet
Development

No branches or pull requests

5 participants
@pavelsavara @javiercn @maraf @aventius-software and others