Skip to content

Commit e6fa732

Browse files
committed
feat: WasmLibraryImport
1 parent 47b2449 commit e6fa732

24 files changed

Lines changed: 1051 additions & 45 deletions

AspNetCore.sln

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Zapto.AspNetCore.Wasm.Sourc
2828
EndProject
2929
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Zapto.AspNetCore.Wasm.SourceGenerator.Tests", "tests\Zapto.AspNetCore.Wasm.SourceGenerator.Tests\Zapto.AspNetCore.Wasm.SourceGenerator.Tests.csproj", "{1333E8B4-070C-4C70-A9F2-070CAA66CDAD}"
3030
EndProject
31+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{827E0CD3-B72D-47B6-A68D-7590B98EB39B}"
32+
EndProject
33+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Zapto.AspNetCore.Wasm.Interop", "src\Zapto.AspNetCore.Wasm.Interop\Zapto.AspNetCore.Wasm.Interop.csproj", "{3827F320-31F0-4AE3-A048-8157C66DBEC8}"
34+
EndProject
3135
Global
3236
GlobalSection(SolutionConfigurationPlatforms) = preSolution
3337
Debug|Any CPU = Debug|Any CPU
@@ -158,6 +162,18 @@ Global
158162
{1333E8B4-070C-4C70-A9F2-070CAA66CDAD}.Release|x64.Build.0 = Release|Any CPU
159163
{1333E8B4-070C-4C70-A9F2-070CAA66CDAD}.Release|x86.ActiveCfg = Release|Any CPU
160164
{1333E8B4-070C-4C70-A9F2-070CAA66CDAD}.Release|x86.Build.0 = Release|Any CPU
165+
{3827F320-31F0-4AE3-A048-8157C66DBEC8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
166+
{3827F320-31F0-4AE3-A048-8157C66DBEC8}.Debug|Any CPU.Build.0 = Debug|Any CPU
167+
{3827F320-31F0-4AE3-A048-8157C66DBEC8}.Debug|x64.ActiveCfg = Debug|Any CPU
168+
{3827F320-31F0-4AE3-A048-8157C66DBEC8}.Debug|x64.Build.0 = Debug|Any CPU
169+
{3827F320-31F0-4AE3-A048-8157C66DBEC8}.Debug|x86.ActiveCfg = Debug|Any CPU
170+
{3827F320-31F0-4AE3-A048-8157C66DBEC8}.Debug|x86.Build.0 = Debug|Any CPU
171+
{3827F320-31F0-4AE3-A048-8157C66DBEC8}.Release|Any CPU.ActiveCfg = Release|Any CPU
172+
{3827F320-31F0-4AE3-A048-8157C66DBEC8}.Release|Any CPU.Build.0 = Release|Any CPU
173+
{3827F320-31F0-4AE3-A048-8157C66DBEC8}.Release|x64.ActiveCfg = Release|Any CPU
174+
{3827F320-31F0-4AE3-A048-8157C66DBEC8}.Release|x64.Build.0 = Release|Any CPU
175+
{3827F320-31F0-4AE3-A048-8157C66DBEC8}.Release|x86.ActiveCfg = Release|Any CPU
176+
{3827F320-31F0-4AE3-A048-8157C66DBEC8}.Release|x86.Build.0 = Release|Any CPU
161177
EndGlobalSection
162178
GlobalSection(SolutionProperties) = preSolution
163179
HideSolutionNode = FALSE
@@ -173,5 +189,6 @@ Global
173189
{D3E4F5A6-B7C8-4D9E-0F1A-2B3C4D5E6F7A} = {B3DA0F06-4511-4791-BB12-A84655F02BFA}
174190
{7BEC1F6E-3E97-489C-A134-ECCEA93FC93F} = {7F209FD2-1190-4721-9A08-9569AB4AA8D5}
175191
{1333E8B4-070C-4C70-A9F2-070CAA66CDAD} = {B3DA0F06-4511-4791-BB12-A84655F02BFA}
192+
{3827F320-31F0-4AE3-A048-8157C66DBEC8} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
176193
EndGlobalSection
177194
EndGlobal
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/**
2+
* Greets a person by name.
3+
* @param {string} name - The name of the person to greet.
4+
* @returns {Promise<string>} A greeting message.
5+
*/
6+
export function greet(name) {
7+
return Promise.resolve(`Greetings from Example, ${name}!`);
8+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
using Zapto.AspNetCore.Wasm.Interop;
2+
3+
namespace WasmApp.Library;
4+
5+
public static partial class NativeMethods
6+
{
7+
/// <summary>
8+
/// Calls the JavaScript greet function from the Example module.
9+
/// WASM import: Example_greet(byte* namePtr, int nameLen, int callbackId)
10+
/// </summary>
11+
[WasmLibraryImport("greet", "Example")]
12+
public static partial Task<string?> GreetAsync(string name);
13+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net10.0</TargetFramework>
5+
<RuntimeIdentifier>browser-wasm</RuntimeIdentifier>
6+
<Nullable>enable</Nullable>
7+
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
8+
<ImplicitUsings>enable</ImplicitUsings>
9+
</PropertyGroup>
10+
11+
<ItemGroup>
12+
<ProjectReference Include="..\..\src\Zapto.AspNetCore.Wasm.Interop\Zapto.AspNetCore.Wasm.Interop.csproj" />
13+
<ProjectReference Include="..\..\src\Zapto.AspNetCore.Wasm.SourceGenerator\Zapto.AspNetCore.Wasm.SourceGenerator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
14+
</ItemGroup>
15+
16+
<ItemGroup>
17+
<Content Include="Example.wasmlib.js">
18+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
19+
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
20+
</Content>
21+
</ItemGroup>
22+
23+
</Project>

sandbox/WasmApp/Program.cs

Lines changed: 4 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using Microsoft.AspNetCore.Hosting;
44
using Microsoft.AspNetCore.Http;
55
using Microsoft.Extensions.DependencyInjection;
6+
using WasmApp.Library;
67

78
var builder = WebApplication.CreateEmptyBuilder(new WebApplicationOptions());
89

@@ -13,27 +14,10 @@
1314
options.SerializerOptions.TypeInfoResolverChain.Insert(0, AppJsonContext.Default);
1415
});
1516

16-
builder.WebHost.UseWasmServer(options =>
17-
{
18-
options.IncludeExceptionDetails = true;
19-
});
17+
builder.WebHost.UseWasmServer();
2018

2119
var app = builder.Build();
2220

23-
app.Use(async (context, next) =>
24-
{
25-
try
26-
{
27-
await next();
28-
}
29-
catch (Exception ex)
30-
{
31-
context.Response.StatusCode = StatusCodes.Status500InternalServerError;
32-
context.Response.ContentType = "text/plain";
33-
await context.Response.WriteAsync(ex.ToString());
34-
}
35-
});
36-
3721
app.UseRouting();
3822

3923
app.MapGet("/", () => "Hello from ASP.NET Core on Cloudflare Workers!");
@@ -45,6 +29,8 @@
4529

4630
app.MapGet("/api/greet/{name}", (string name) => $"Hello, {name}!");
4731

32+
app.MapGet("/library/greet/{name}", async (string name) => await NativeMethods.GreetAsync(name));
33+
4834
app.MapPost("/api/echo", async context =>
4935
{
5036
using var reader = new StreamReader(context.Request.Body);

sandbox/WasmApp/WasmApp.csproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@
77
<ZaptoWasmUseProjectReference>true</ZaptoWasmUseProjectReference>
88
</PropertyGroup>
99

10+
<ItemGroup>
11+
<ProjectReference Include="..\WasmApp.Library\WasmApp.Library.csproj" />
12+
</ItemGroup>
13+
1014
<!-- Import SDK targets -->
1115
<Import Project="..\..\sdk\Zapto.AspNetCore.CloudFlare.SDK\Sdk\Sdk.targets" />
1216
</Project>

sandbox/WasmApp/test-wrangler.sh

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
#!/bin/bash
2+
cd /c/Sources/AspNetCore/bin/Release/net10.0/browser-wasm/cloudflare
3+
4+
echo "Starting wrangler..."
5+
npx wrangler dev --port 8792 --show-interactive-dev-session=false 2>&1 &
6+
WRANGLER_PID=$!
7+
8+
echo "Waiting for wrangler to start (PID: $WRANGLER_PID)..."
9+
sleep 2
10+
11+
echo ""
12+
echo "=== Testing root ==="
13+
curl -v --max-time 20 http://127.0.0.1:8792/ 2>&1
14+
echo ""
15+
16+
echo ""
17+
echo "=== Testing library/greet ==="
18+
curl -s --max-time 30 http://127.0.0.1:8792/library/greet/TestUser
19+
CURL_EXIT=$?
20+
echo ""
21+
echo "curl exit code: $CURL_EXIT"
22+
23+
echo ""
24+
echo "=== Stopping wrangler ==="
25+
sleep 2
26+
kill $WRANGLER_PID 2>/dev/null
27+
echo "Done"

sdk/Zapto.AspNetCore.CloudFlare.SDK/Sdk/Sdk.targets

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,13 +105,45 @@
105105
</PropertyGroup>
106106

107107
<Target Name="GenerateCloudflareIndex"
108+
AfterTargets="Publish"
108109
BeforeTargets="PrepareCloudflareOutput"
109110
Condition="'$(RuntimeIdentifier)' == 'browser-wasm'">
110111
<MakeDir Directories="$(CloudflareIndexGeneratedDir)" />
111112

113+
<ItemGroup>
114+
<_DiscoveredWasmLibraryScripts Include="$(PublishDir)*.wasmlib.js" />
115+
</ItemGroup>
116+
117+
<Message Text="Discovered WASM library scripts: @(_DiscoveredWasmLibraryScripts)" Importance="normal"
118+
Condition="'@(_DiscoveredWasmLibraryScripts)' != ''" />
119+
120+
<!--
121+
Build library import statements using batching.
122+
The module name is extracted from the filename by removing .wasmlib suffix.
123+
The variable name uses underscore instead of dots for valid JS identifiers.
124+
Note: Using placeholders for special characters to avoid MSBuild parsing issues.
125+
-->
126+
<ItemGroup>
127+
<_LibraryImportLines Include="import __STAR__ as _lib_$([System.String]::Copy('%(_DiscoveredWasmLibraryScripts.Filename)').Replace('.wasmlib', '').Replace('.', '_').Replace('-', '_')) from __QUOTE__./%(_DiscoveredWasmLibraryScripts.Filename)%(_DiscoveredWasmLibraryScripts.Extension)__QUOTE____SEMI__"
128+
Condition="'@(_DiscoveredWasmLibraryScripts)' != ''" />
129+
<_LibraryModuleLines Include=" __QUOTE__$([System.String]::Copy('%(_DiscoveredWasmLibraryScripts.Filename)').Replace('.wasmlib', ''))__QUOTE__: _lib_$([System.String]::Copy('%(_DiscoveredWasmLibraryScripts.Filename)').Replace('.wasmlib', '').Replace('.', '_').Replace('-', '_')),"
130+
Condition="'@(_DiscoveredWasmLibraryScripts)' != ''" />
131+
</ItemGroup>
132+
133+
<PropertyGroup>
134+
<_LibraryImportStatements>@(_LibraryImportLines, '%0a')</_LibraryImportStatements>
135+
<_LibraryImportStatements>$(_LibraryImportStatements.Replace('__STAR__', '*'))</_LibraryImportStatements>
136+
<_LibraryImportStatements>$(_LibraryImportStatements.Replace('__QUOTE__', "'"))</_LibraryImportStatements>
137+
<_LibraryImportStatements>$(_LibraryImportStatements.Replace('__SEMI__', ';'))</_LibraryImportStatements>
138+
<_LibraryModuleRegistrations>@(_LibraryModuleLines, '%0a')</_LibraryModuleRegistrations>
139+
<_LibraryModuleRegistrations>$(_LibraryModuleRegistrations.Replace('__QUOTE__', "'"))</_LibraryModuleRegistrations>
140+
</PropertyGroup>
141+
112142
<PropertyGroup>
113143
<_CloudflareIndexTemplate>$([System.IO.File]::ReadAllText('$(CloudflareIndexTemplatePath)'))</_CloudflareIndexTemplate>
114144
<_CloudflareIndexContent>$(_CloudflareIndexTemplate.Replace('__MAIN_ASSEMBLY__', '$(AssemblyName)'))</_CloudflareIndexContent>
145+
<_CloudflareIndexContent>$(_CloudflareIndexContent.Replace('// __LIBRARY_IMPORTS__', '$(_LibraryImportStatements)'))</_CloudflareIndexContent>
146+
<_CloudflareIndexContent>$(_CloudflareIndexContent.Replace(' // __LIBRARY_MODULES__', '$(_LibraryModuleRegistrations)'))</_CloudflareIndexContent>
115147
</PropertyGroup>
116148

117149
<WriteLinesToFile File="$(CloudflareIndexGeneratedPath)"
@@ -151,6 +183,7 @@
151183

152184
<Target Name="PrepareCloudflareOutput"
153185
AfterTargets="Publish"
186+
DependsOnTargets="GenerateCloudflareIndex;GenerateCloudflareWrangler"
154187
Condition="'$(RuntimeIdentifier)' == 'browser-wasm'">
155188
<MakeDir Directories="$(CloudflareOutputPath)" />
156189

@@ -184,6 +217,16 @@
184217
SkipUnchangedFiles="true"
185218
Condition="Exists('$(PublishDir)dotnet.runtime.js')" />
186219

220+
<!-- Copy library JavaScript files to cloudflare output (discovered from publish directory) -->
221+
<ItemGroup>
222+
<_WasmLibScriptsToCopy Include="$(PublishDir)*.wasmlib.js" />
223+
</ItemGroup>
224+
225+
<Copy SourceFiles="@(_WasmLibScriptsToCopy)"
226+
DestinationFolder="$(CloudflareOutputPath)"
227+
SkipUnchangedFiles="true"
228+
Condition="'@(_WasmLibScriptsToCopy)' != ''" />
229+
187230
<Copy SourceFiles="$(PublishDir)$(AssemblyName).wasm"
188231
DestinationFolder="$(CloudflareOutputPath)"
189232
SkipUnchangedFiles="true"

0 commit comments

Comments
 (0)