diff --git a/Directory.Packages.props b/Directory.Packages.props index d9578760..b210a49b 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -1,6 +1,7 @@ + diff --git a/README.md b/README.md index 5916aa63..9e8ee4c9 100644 --- a/README.md +++ b/README.md @@ -334,14 +334,6 @@ Table 2. TypeShim support for .NET-JS interop types *For `[TSExport]` classes -## Run the sample - -To build and run the project: -``` -cd Sample/TypeShim.Sample.Client && npm install && npm run build && cd ../TypeShim.Sample.Server && dotnet run -``` -The app should be available on [http://localhost:5012](http://localhost:5012) - ## Installing To use TypeShim all you have to do is install it directly into your `Microsoft.NET.Sdk.WebAssembly`-powered project. Check the [configuration](#configuration) section for configuration you might want to adjust to your project. diff --git a/sample/TypeShim.Sample.Client/.gitignore b/sample/.gitignore similarity index 98% rename from sample/TypeShim.Sample.Client/.gitignore rename to sample/.gitignore index b7447368..da2cd498 100644 --- a/sample/TypeShim.Sample.Client/.gitignore +++ b/sample/.gitignore @@ -97,7 +97,7 @@ temp/ # Preserve root lock !package-lock.json # If individual workspace package-locks accidentally get created (npm -w install inside): -@typeshim/*/package-lock.json +@client/*/package-lock.json # ----------------------------- # Misc bundler artifacts diff --git a/sample/TypeShim.Sample/Dtos.cs b/sample/Library/Dtos.cs similarity index 97% rename from sample/TypeShim.Sample/Dtos.cs rename to sample/Library/Dtos.cs index 8e26aa61..ad017837 100644 --- a/sample/TypeShim.Sample/Dtos.cs +++ b/sample/Library/Dtos.cs @@ -1,4 +1,4 @@ -namespace TypeShim.Sample; +namespace Client.Library; public class PeopleDto { diff --git a/sample/TypeShim.Sample/TypeShim.Sample.csproj b/sample/Library/Library.csproj similarity index 52% rename from sample/TypeShim.Sample/TypeShim.Sample.csproj rename to sample/Library/Library.csproj index 238311ad..d999f574 100644 --- a/sample/TypeShim.Sample/TypeShim.Sample.csproj +++ b/sample/Library/Library.csproj @@ -5,8 +5,11 @@ true enable - true + true + false false + false + false @@ -14,19 +17,23 @@ + ./bin/ - ../../../../TypeShim.Sample.Client/@typeshim/wasm-exports - High + + - - - + + + false + - + + PreserveNewest + diff --git a/sample/TypeShim.Sample/Models.cs b/sample/Library/Models.cs similarity index 96% rename from sample/TypeShim.Sample/Models.cs rename to sample/Library/Models.cs index 20727467..370ecaaa 100644 --- a/sample/TypeShim.Sample/Models.cs +++ b/sample/Library/Models.cs @@ -1,7 +1,8 @@ using System; using System.Threading.Tasks; +using TypeShim; -namespace TypeShim.Sample; +namespace Client.Library; [TSExport] public class People() diff --git a/sample/Library/PeopleApiClient.cs b/sample/Library/PeopleApiClient.cs new file mode 100644 index 00000000..1db6146f --- /dev/null +++ b/sample/Library/PeopleApiClient.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Net.Http.Json; +using System.Text.Json.Serialization; +using System.Threading.Tasks; + +namespace Client.Library; + +public class PeopleApiClient(HttpClient httpClient) +{ + public async Task> GetAllPeopleAsync() + { + PeopleDto? dto = await httpClient.GetFromJsonAsync("/people/all", typeof(PeopleDto), PersonDtoSerializerContext.Default) as PeopleDto; + return dto?.People?.Select(dto => dto.ToPerson()) ?? []; + } +} + +[JsonSourceGenerationOptions( + GenerationMode = JsonSourceGenerationMode.Serialization | JsonSourceGenerationMode.Metadata, + PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase)] +[JsonSerializable(typeof(PeopleDto))] +[JsonSerializable(typeof(PersonDto))] +[JsonSerializable(typeof(PersonDto[]))] +[JsonSerializable(typeof(DogDto))] +[JsonSerializable(typeof(DogDto[]))] +internal partial class PersonDtoSerializerContext : JsonSerializerContext { } diff --git a/sample/Library/PeopleApp.cs b/sample/Library/PeopleApp.cs new file mode 100644 index 00000000..d63ac681 --- /dev/null +++ b/sample/Library/PeopleApp.cs @@ -0,0 +1,37 @@ +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using System; +using System.Net.Http; +using TypeShim; + +namespace Client.Library; + +[TSExport] +public class PeopleAppOptions +{ + public required string BaseAddress { get; init; } +} + +[TSExport] +public class PeopleApp +{ + private readonly IHost _host; + + public PeopleApp(PeopleAppOptions options) + { + // we dont -need- a servicecollection for this demo but its here to show you can use anything on the .net side + _host = new HostBuilder().ConfigureServices(services => + { + services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(options.BaseAddress) }); + services.AddSingleton(); + services.AddSingleton(sp => new PeopleProvider(sp.GetRequiredService())); + }).Build(); + Console.WriteLine($".NET {nameof(PeopleApp)} Constructor completed"); + } + + public PeopleProvider GetPeopleProvider() + { + return _host.Services.GetRequiredService(); + } +} \ No newline at end of file diff --git a/sample/TypeShim.Sample/PeopleProvider.cs b/sample/Library/PeopleProvider.cs similarity index 75% rename from sample/TypeShim.Sample/PeopleProvider.cs rename to sample/Library/PeopleProvider.cs index c41c5920..68fffbb5 100644 --- a/sample/TypeShim.Sample/PeopleProvider.cs +++ b/sample/Library/PeopleProvider.cs @@ -4,7 +4,7 @@ using System.Threading.Tasks; using TypeShim; -namespace TypeShim.Sample; +namespace Client.Library; [TSExport] public class PeopleProvider @@ -17,18 +17,10 @@ internal PeopleProvider(PeopleApiClient apiClient) _apiClient = apiClient; } - public Person[]? PeopleCache => AllPeople; - public Task? DelayTask { get; set; } = null; - public async Task FetchPeopleAsync() { try { - if (DelayTask != null) - { - await Task.Delay((await DelayTask)?.Timeout ?? 0); - } - if (AllPeople == null) { AllPeople = [.. await _apiClient.GetAllPeopleAsync()]; @@ -46,11 +38,4 @@ public async Task FetchPeopleAsync() throw; // hand over to js } } -} - - -[TSExport] -public class TimeoutUnit -{ - public int Timeout { get; set; } = 0; } \ No newline at end of file diff --git a/sample/Library/Program.cs b/sample/Library/Program.cs new file mode 100644 index 00000000..a0cb47e2 --- /dev/null +++ b/sample/Library/Program.cs @@ -0,0 +1,21 @@ +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using System; +using System.Net.Http; +using System.Runtime.InteropServices.JavaScript; + +namespace Client.Library; + +public partial class Program +{ + public static void Main(string[] args) + { + Console.WriteLine(".NET Main method entered."); + + // You can put any startup logic here like any other .NET application + // alternatively you could expose a class that embodies your app and treat the .NET code as a library. + // For this demo we'll go with the latter, PeopleApp will be constructed from the JS side. + Console.WriteLine($"{nameof(PeopleApp)} will be constructed from the JS side in this demo."); + } +} diff --git a/sample/TypeShim.Sample/Properties/AssemblyInfo.cs b/sample/Library/Properties/AssemblyInfo.cs similarity index 100% rename from sample/TypeShim.Sample/Properties/AssemblyInfo.cs rename to sample/Library/Properties/AssemblyInfo.cs diff --git a/sample/Library/Properties/launchSettings.json b/sample/Library/Properties/launchSettings.json new file mode 100644 index 00000000..5b17d376 --- /dev/null +++ b/sample/Library/Properties/launchSettings.json @@ -0,0 +1,12 @@ +{ + "profiles": { + "Client.Library": { + "commandName": "Project", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "https://localhost:51663;http://localhost:51664" + } + } +} \ No newline at end of file diff --git a/sample/TypeShim.Sample/PeopleGenerator.cs b/sample/Library/RandomEntityGenerator.cs similarity index 98% rename from sample/TypeShim.Sample/PeopleGenerator.cs rename to sample/Library/RandomEntityGenerator.cs index 58b61bd0..5b603c20 100644 --- a/sample/TypeShim.Sample/PeopleGenerator.cs +++ b/sample/Library/RandomEntityGenerator.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.Linq; -namespace TypeShim.Sample; +namespace Client.Library; public class RandomEntityGenerator { diff --git a/sample/Library/wwwroot/TypeShimProvider.tsx b/sample/Library/wwwroot/TypeShimProvider.tsx new file mode 100644 index 00000000..082bc45e --- /dev/null +++ b/sample/Library/wwwroot/TypeShimProvider.tsx @@ -0,0 +1,42 @@ +import { createWasmRuntime, TypeShimInitializer } from '@client/wasm-exports'; +import { ReactNode, useEffect, useState } from 'react'; + +export interface AppProviderProps { + children: ReactNode; +} + +export function TypeShimProvider({ children }: AppProviderProps) { + const [runtime, setRuntime] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + let cancelled = false; + setLoading(true); + setError(null); + async function load() { + try { + const runtimeInfo = await createWasmRuntime(); + await TypeShimInitializer.initialize(runtimeInfo); + console.log("WASM Runtime initialized successfully."); + } catch (err: any) { + console.error("Error loading WASM runtime:", err); + if (!cancelled) { + setError(err); + } + } finally { + if (!cancelled) { + setLoading(false); + } + } + } + load(); + + return () => { cancelled = true; console.log("CANCEL"); }; // cleanup + }, []); + return error + ? (
Error: {error}
) + : loading + ? (
Loading...
) + : (<>{children}); +} \ No newline at end of file diff --git a/sample/Library/wwwroot/_framework/dotnet.d.ts b/sample/Library/wwwroot/_framework/dotnet.d.ts new file mode 100644 index 00000000..4fccde41 --- /dev/null +++ b/sample/Library/wwwroot/_framework/dotnet.d.ts @@ -0,0 +1 @@ +export const dotnet: any; \ No newline at end of file diff --git a/sample/Library/wwwroot/main.ts b/sample/Library/wwwroot/main.ts new file mode 100644 index 00000000..eaec42f0 --- /dev/null +++ b/sample/Library/wwwroot/main.ts @@ -0,0 +1,3 @@ +export * from './wasm-bootstrap' +export * from './TypeShimProvider' +export * from './typeshim' \ No newline at end of file diff --git a/sample/TypeShim.Sample.Client/@typeshim/wasm-exports/package.json b/sample/Library/wwwroot/package.json similarity index 87% rename from sample/TypeShim.Sample.Client/@typeshim/wasm-exports/package.json rename to sample/Library/wwwroot/package.json index 58f5e216..5ec943a0 100644 --- a/sample/TypeShim.Sample.Client/@typeshim/wasm-exports/package.json +++ b/sample/Library/wwwroot/package.json @@ -1,5 +1,5 @@ { - "name": "@typeshim/wasm-exports", + "name": "@client/wasm-exports", "version": "1.0.0", "description": "", "main": "main.ts", diff --git a/sample/Library/wwwroot/wasm-bootstrap.ts b/sample/Library/wwwroot/wasm-bootstrap.ts new file mode 100644 index 00000000..2b441bde --- /dev/null +++ b/sample/Library/wwwroot/wasm-bootstrap.ts @@ -0,0 +1,18 @@ +import { dotnet } from './_framework/dotnet' + +let runtime: any = null; +let runtimePromise: Promise | null = null; +export async function createWasmRuntime(): Promise { + console.log("Creating WASM runtime..."); + if (runtimePromise) { + console.warn("WASM runtime is already started. Not creating a new instance."); + return runtimePromise; + } else { + runtimePromise = dotnet.create(); + } + const runtimeInfo = await runtimePromise; + console.log("WASM runtime info:", runtimeInfo); + const { runMain } = runtimeInfo; + runMain(); + return runtime = runtimeInfo; +}; \ No newline at end of file diff --git a/sample/README.md b/sample/README.md new file mode 100644 index 00000000..e483856a --- /dev/null +++ b/sample/README.md @@ -0,0 +1,36 @@ +## TLDR; +To start the vite dev server run: +``` +npm run build +npm run dev +``` + +To start the ASP.NET backend, either start the project from your favorite IDE or: +``` +dotnet run --project Server +``` + + +The app should be available on [http://localhost:5012](http://localhost:5012) + +# .NET WebAssembly + React with TypeShim + +This sample bundles a .NET library into a react app, while TypeShim provides the interop boundary code. This is just _a_ possible way of using TypeShim and .NET Browser Apps. + +The domain of this app is 'people', each person may have a pet (I had to come up with _something_). The classes representing this domain are defined in the `Library` project and consumed in the react `app` project for the UI. The same classes are also consumed in the `Server` project to facilitate an Api endpoint for pulling some generated data. + +> A reasonable use case for .NET browser apps that is worth highlighting is that of the `PeopleApiClient` which defines how to interact with the `PeopleController` in the server project. As the Controller and ApiClient can share code, this requires no duplicate definitions, which is typically the case with JS+.NET mixed stacks. + +#### Library +This project is where most 'logic' resides. This is rather simple stuff with classes like `Person`, `Pet` and `PeopleProvider` which are all part of the interop API (i.e. have `[TSExport]`). + +When built, this project outputs an npm project in the `/Library/bin/wwwroot` directory which contains a combination of files from: +- The source code `Library/wwwroot` +- Dotnet wasm build artifacts +- And ofcourse: `typeshim.ts`, the generated TypeScript library that you are hopefully here to check out. + +#### Server +This is an ASP.NET project that hosts the `PeopleController` and includes a proxy to expose the vite dev server from the `app` project. This provides generated test data for the app to consume. + +#### app +This is the react app that consumes the .NET Library. The usage of interop classes generated by TypeShim can be found for example in `/app/src/people/PeopleRepository.tsx`. \ No newline at end of file diff --git a/sample/Sample.slnx b/sample/Sample.slnx index 096add01..348565aa 100644 --- a/sample/Sample.slnx +++ b/sample/Sample.slnx @@ -1,8 +1,4 @@ - - - - - - + + diff --git a/sample/Server/Controllers/PeopleController.cs b/sample/Server/Controllers/PeopleController.cs new file mode 100644 index 00000000..921d79de --- /dev/null +++ b/sample/Server/Controllers/PeopleController.cs @@ -0,0 +1,22 @@ +using Microsoft.AspNetCore.Mvc; +using Client.Library; + +namespace Server.Controllers; + +[ApiController] +[Route("[controller]")] +public class PeopleController() : ControllerBase +{ + private readonly List _people = new RandomEntityGenerator().GeneratePersons(250); + + [HttpGet] + [Route("all")] + public PeopleDto GetAll() + { + return new PeopleDto + { + People = [.. _people.Select(PersonDto.FromPerson)] + }; + } +} + diff --git a/sample/Server/Program.cs b/sample/Server/Program.cs new file mode 100644 index 00000000..d92738cf --- /dev/null +++ b/sample/Server/Program.cs @@ -0,0 +1,24 @@ +WebApplicationBuilder builder = WebApplication.CreateBuilder(args); + +builder.Services.AddControllers() + .AddApplicationPart(typeof(Program).Assembly); ; + +WebApplication app = builder.Build(); +app.UseRouting(); +app.UseHttpsRedirection(); +app.UseAuthorization(); +app.UseWebAssemblyDebugging(); + +#pragma warning disable ASP0014 // Suggest using top level route registrations - not compatible with usespa and spaproxy is a bit of a hassle to use +app.UseEndpoints(ep => ep.MapControllers()); +#pragma warning restore ASP0014 // Suggest using top level route registrations +app.UseSpa(spa => +{ + // run 'npm start' or 'npm run dev' in the sample directory to start the vite dev server + spa.UseProxyToSpaDevelopmentServer("http://localhost:5173/"); +}); + +app.UseDefaultFiles(); +app.UseStaticFiles(); + +app.Run(); diff --git a/sample/TypeShim.Sample.Server/Properties/launchSettings.json b/sample/Server/Properties/launchSettings.json similarity index 95% rename from sample/TypeShim.Sample.Server/Properties/launchSettings.json rename to sample/Server/Properties/launchSettings.json index 537e840a..d50636f6 100644 --- a/sample/TypeShim.Sample.Server/Properties/launchSettings.json +++ b/sample/Server/Properties/launchSettings.json @@ -4,7 +4,7 @@ "http": { "commandName": "Project", "dotnetRunMessages": true, - "launchBrowser": false, + "launchBrowser": true, "applicationUrl": "http://localhost:5012", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" diff --git a/sample/TypeShim.Sample.Server/TypeShim.Sample.Server.csproj b/sample/Server/Server.csproj similarity index 75% rename from sample/TypeShim.Sample.Server/TypeShim.Sample.Server.csproj rename to sample/Server/Server.csproj index 5322e0af..a0f4c7e3 100644 --- a/sample/TypeShim.Sample.Server/TypeShim.Sample.Server.csproj +++ b/sample/Server/Server.csproj @@ -7,11 +7,12 @@ - + + diff --git a/sample/TypeShim.Sample.Server/TypeShim.Sample.Server.csproj.user b/sample/Server/Server.csproj.user similarity index 100% rename from sample/TypeShim.Sample.Server/TypeShim.Sample.Server.csproj.user rename to sample/Server/Server.csproj.user diff --git a/sample/TypeShim.Sample.Server/appsettings.Development.json b/sample/Server/appsettings.Development.json similarity index 100% rename from sample/TypeShim.Sample.Server/appsettings.Development.json rename to sample/Server/appsettings.Development.json diff --git a/sample/TypeShim.Sample.Server/appsettings.json b/sample/Server/appsettings.json similarity index 100% rename from sample/TypeShim.Sample.Server/appsettings.json rename to sample/Server/appsettings.json diff --git a/sample/TypeShim.Sample.Client/@typeshim/app/src/App.tsx b/sample/TypeShim.Sample.Client/@typeshim/app/src/App.tsx deleted file mode 100644 index e6be545e..00000000 --- a/sample/TypeShim.Sample.Client/@typeshim/app/src/App.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import { useMemo, useState } from 'react'; -import Home from './pages/Home'; -import People from './pages/People'; -import { createWasmRuntime, MyApp, TypeShimInitializer } from '@typeshim/wasm-exports'; - -type Page = 'home' | 'people'; - -function App() { - const [currentPage, setCurrentPage] = useState('home'); - - useMemo(async () => { - const runtimeInfo = await createWasmRuntime(); - await TypeShimInitializer.initialize(runtimeInfo); - MyApp.Initialize(document.baseURI); - }, []); - - return ( - - ); -} - -export default App; \ No newline at end of file diff --git a/sample/TypeShim.Sample.Client/@typeshim/people-ui/package.json b/sample/TypeShim.Sample.Client/@typeshim/people-ui/package.json deleted file mode 100644 index d37a2ec3..00000000 --- a/sample/TypeShim.Sample.Client/@typeshim/people-ui/package.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "name": "@typeshim/people-ui", - "version": "1.0.0", - "description": "", - "types": "src/index.ts", - "main": "src/index.ts", - "module": "src/index.ts", - "exports": { - ".": { - "import": "./src/index.ts", - "types": "./src/index.ts" - } - }, - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1", - "build": "tsc -p tsconfig.json" - }, - "keywords": [], - "author": "", - "license": "ISC", - "dependencies": { - "@typeshim/wasm-exports": "*" - }, - "peerDependencies": { - "react": "^18.2.0", - "react-dom": "^18.2.0" - } -} diff --git a/sample/TypeShim.Sample.Client/@typeshim/people-ui/tsconfig.json b/sample/TypeShim.Sample.Client/@typeshim/people-ui/tsconfig.json deleted file mode 100644 index 1946885d..00000000 --- a/sample/TypeShim.Sample.Client/@typeshim/people-ui/tsconfig.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "compilerOptions": { - "declaration": true, - "allowJs": true, - "declarationDir": "dist", - "emitDeclarationOnly": false, - "outDir": "dist", - "rootDir": ".", - "target": "ES2020", - "module": "esnext", - "moduleResolution": "bundler", - "jsx": "react-jsx", - "strict": true, - "esModuleInterop": true, - "skipLibCheck": true - }, - "include": ["src"] - -} diff --git a/sample/TypeShim.Sample.Client/@typeshim/wasm-exports/main.ts b/sample/TypeShim.Sample.Client/@typeshim/wasm-exports/main.ts deleted file mode 100644 index b895a3ab..00000000 --- a/sample/TypeShim.Sample.Client/@typeshim/wasm-exports/main.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './wasm-bootstrap.js' -export * from './typeshim' \ No newline at end of file diff --git a/sample/TypeShim.Sample.Client/@typeshim/wasm-exports/wasm-bootstrap.d.ts b/sample/TypeShim.Sample.Client/@typeshim/wasm-exports/wasm-bootstrap.d.ts deleted file mode 100644 index da5c917b..00000000 --- a/sample/TypeShim.Sample.Client/@typeshim/wasm-exports/wasm-bootstrap.d.ts +++ /dev/null @@ -1 +0,0 @@ -export function createWasmRuntime(args?: string | undefined): Promise; \ No newline at end of file diff --git a/sample/TypeShim.Sample.Client/@typeshim/wasm-exports/wasm-bootstrap.js b/sample/TypeShim.Sample.Client/@typeshim/wasm-exports/wasm-bootstrap.js deleted file mode 100644 index 570449da..00000000 --- a/sample/TypeShim.Sample.Client/@typeshim/wasm-exports/wasm-bootstrap.js +++ /dev/null @@ -1,15 +0,0 @@ -import { dotnet } from '/_framework/dotnet.js' - -let isStarted = null; - -export async function createWasmRuntime(args) { - if (isStarted) { - throw new Error("The .NET WebAssembly runtime has already been started."); - } - isStarted = true; - - const runtimeInfo = await dotnet.withApplicationArguments(args).create(); - const { runMain } = runtimeInfo; - runMain(); - return runtimeInfo; -}; \ No newline at end of file diff --git a/sample/TypeShim.Sample.Client/TypeShim.Sample.Client.esproj b/sample/TypeShim.Sample.Client/TypeShim.Sample.Client.esproj deleted file mode 100644 index 27047a89..00000000 --- a/sample/TypeShim.Sample.Client/TypeShim.Sample.Client.esproj +++ /dev/null @@ -1,11 +0,0 @@ - - - npm run build - - npm start - $(MSBuildProjectDirectory)\ - - - - - \ No newline at end of file diff --git a/sample/TypeShim.Sample.Client/TypeShim.Sample.Client.esproj.user b/sample/TypeShim.Sample.Client/TypeShim.Sample.Client.esproj.user deleted file mode 100644 index 5791f9f0..00000000 --- a/sample/TypeShim.Sample.Client/TypeShim.Sample.Client.esproj.user +++ /dev/null @@ -1,6 +0,0 @@ - - - - True - - \ No newline at end of file diff --git a/sample/TypeShim.Sample.Server/Controllers/PeopleController.cs b/sample/TypeShim.Sample.Server/Controllers/PeopleController.cs deleted file mode 100644 index c6efdccc..00000000 --- a/sample/TypeShim.Sample.Server/Controllers/PeopleController.cs +++ /dev/null @@ -1,21 +0,0 @@ -using Microsoft.AspNetCore.Mvc; -using TypeShim.Sample; - -namespace TypeShim.Sample.Server.Controllers; - -[ApiController] -[Route("[controller]")] -public class PeopleController(PersonRepository repository) : ControllerBase -{ - [HttpGet] - [Route("all")] - public PeopleDto GetAll() - { - IEnumerable elderlyPeople = repository.GetAll(); - return new PeopleDto - { - People = [.. elderlyPeople.Select(PersonDto.FromPerson)] - }; - } -} - diff --git a/sample/TypeShim.Sample.Server/Program.cs b/sample/TypeShim.Sample.Server/Program.cs deleted file mode 100644 index cd20949c..00000000 --- a/sample/TypeShim.Sample.Server/Program.cs +++ /dev/null @@ -1,21 +0,0 @@ -using TypeShim.Sample; -using TypeShim.Sample.Server.Controllers; - -var builder = WebApplication.CreateBuilder(args); - -builder.Services.AddControllers() - .AddApplicationPart(typeof(PeopleController).Assembly); ; -builder.Services.AddSingleton(); -var app = builder.Build(); - -app.UseHttpsRedirection(); -app.UseAuthorization(); -app.UseBlazorFrameworkFiles(); -app.UseWebAssemblyDebugging(); - -app.UseDefaultFiles(); -app.UseStaticFiles(); - -app.MapControllers(); - -app.Run(); diff --git a/sample/TypeShim.Sample/.gitignore b/sample/TypeShim.Sample/.gitignore deleted file mode 100644 index 2493aaf5..00000000 --- a/sample/TypeShim.Sample/.gitignore +++ /dev/null @@ -1 +0,0 @@ -wwwroot \ No newline at end of file diff --git a/sample/TypeShim.Sample/Capabilities/ArraysDemo.cs b/sample/TypeShim.Sample/Capabilities/ArraysDemo.cs deleted file mode 100644 index faa5fa6d..00000000 --- a/sample/TypeShim.Sample/Capabilities/ArraysDemo.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System.Collections.Generic; - -namespace TypeShim.Sample.Capabilities; - -[TSExport] -public class ArraysDemo(int[] initialArray) -{ - public int[] IntArrayProperty { get; private set; } = initialArray; - - public int SumElements() - { - int sum = 0; - foreach (int item in IntArrayProperty) - { - sum += item; - } - return sum; - } - - public void Append(int value) - { - IntArrayProperty = [.. IntArrayProperty, value]; - } -} \ No newline at end of file diff --git a/sample/TypeShim.Sample/Capabilities/PrimitivesDemo.cs b/sample/TypeShim.Sample/Capabilities/PrimitivesDemo.cs deleted file mode 100644 index a7639fdb..00000000 --- a/sample/TypeShim.Sample/Capabilities/PrimitivesDemo.cs +++ /dev/null @@ -1,56 +0,0 @@ -using System; -using System.Runtime.InteropServices.JavaScript; -using System.Text; -using System.Threading.Tasks; - -namespace TypeShim.Sample.Capabilities; - -[TSExport] -public class PrimitivesDemo -{ - public required string InitialStringProperty { get; init; } - public required string StringProperty { get; set; } - - public int GetStringLength() - { - return StringProperty.Length; - } - - public string ToUpperCase() - { - return StringProperty.ToUpper(); - } - - public string Concat(string str1, string str2) - { - return string.Concat(StringProperty, str1, str2); - } - - public bool ContainsUpperCase() - { - return StringProperty.Equals(StringProperty.ToLowerInvariant(), StringComparison.CurrentCultureIgnoreCase); - } - - public void ResetBaseString() - { - StringProperty = InitialStringProperty; - } - - public void MultiplyString(int times) - { - if (times < 0) - { - throw new ArgumentOutOfRangeException(nameof(times), "times must be non-negative"); - } - if (times * InitialStringProperty.Length > 100_000) - { - throw new InvalidOperationException("Resulting string is too long"); - } - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < times; i++) - { - sb.Append(StringProperty); - } - StringProperty = sb.ToString(); - } -} diff --git a/sample/TypeShim.Sample/DiagnosticsTest.cs b/sample/TypeShim.Sample/DiagnosticsTest.cs deleted file mode 100644 index 0b2b73b4..00000000 --- a/sample/TypeShim.Sample/DiagnosticsTest.cs +++ /dev/null @@ -1,105 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Runtime.InteropServices.JavaScript; -using System.Text; -using System.Threading.Tasks; - -namespace TypeShim.Sample; - -//[TSExport] -//internal class PublicAccessibilityTest -//{ -//} - -//[TSExport] -//public class PropertyAccessibilityTest -//{ -// public int PublicProperty { get; set; } -// private int PrivateProperty { get; set; } - -// public required int PublicRequiredProperty { get; set; } -// public int PublicGetPrivateSetProperty { get; private set; } -//} - -//[TSExport] -//public class PropertyTypeSupportTest -//{ -// public Dictionary P { get; set; } = []; -//} - -//[TSExport] -//public class OverloadMethodTest -//{ -// public int P() => 0; -// public int P(int i) => 0; -// public int P(int i, int j) => 0; -//} -//[TSExport] -//public class OverloadMethodTest2 -//{ -// public int P() => 0; -// internal int P(int i) => 0; -// public int P(int i, int j) => 0; -//} - -//[TSExport] -//public class OverloadConstructorTest() -//{ -// public OverloadConstructorTest(int i) -// { - -// } -//} - -//[TSExport] -//public class OverloadConstructorTest2 -//{ -// public OverloadConstructorTest2(int i) -// { - -// } -// public OverloadConstructorTest2(bool i) -// { - -// } -//} - -//[TSExport] -//public class OverloadConstructorTest3 -//{ -// public OverloadConstructorTest3(int i) -// { - -// } -// internal OverloadConstructorTest3(bool i) -// { - -// } -//} - -//[TSExport] -//public class MethodTypeSupportTest -//{ -// public Task X() { return Task.FromResult(new int[] { 1, 2, 3 }); } -// public Task Z() { return Task.FromResult(1); } -// public Span Y() { return new Span(); } -// public Span BA() { return new Span(); } -// public IEnumerable BBA() { return []; } -// public IDictionary C() { return new Dictionary(); } -// public IReadOnlyList E() { return []; } -// public Dictionary D() { return new Dictionary(); } -// public void DIn(Dictionary p) { } - -// public ArraySegment W() { return new ArraySegment(new string[] { "a", "b", "c" }); } - -// public Func F() { return (i) => i.ToString(); } -//} - -//[TSExport] -//public class FieldTest -//{ -// public int PublicField; -// private int PrivateField; - -// public required int InternalRequiredField; -//} diff --git a/sample/TypeShim.Sample/MyApp.cs b/sample/TypeShim.Sample/MyApp.cs deleted file mode 100644 index 7d5765af..00000000 --- a/sample/TypeShim.Sample/MyApp.cs +++ /dev/null @@ -1,49 +0,0 @@ -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; -using System; -using System.Net.Http; - -namespace TypeShim.Sample; - -[TSExport] -public class MyApp -{ - private static IHost? _host; - - public static void Initialize(string baseAddress) - { - if (_host != null) - { - throw new InvalidOperationException("Module already initialized."); - } - - Console.WriteLine($"Initializing {nameof(MyApp)} in .NET..."); - - IConfigurationRoot config = new ConfigurationBuilder() - // if desired, add configuration sources here, may also be passed through parameters - .Build(); - _host = new HostBuilder() - .ConfigureAppConfiguration((context, builder) => - { - builder.AddConfiguration(config); - }) - .ConfigureServices(services => - { - services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(baseAddress) }); - services.AddSingleton(); - services.AddSingleton(); - }) - .Build(); - Console.WriteLine($"Initialized {nameof(MyApp)} in .NET."); - } - - public static PeopleProvider GetPeopleProvider() - { - if (_host == null) - { - throw new InvalidOperationException("Module not initialized."); - } - return _host.Services.GetRequiredService(); - } -} \ No newline at end of file diff --git a/sample/TypeShim.Sample/PeopleApiClient.cs b/sample/TypeShim.Sample/PeopleApiClient.cs deleted file mode 100644 index 9057fd97..00000000 --- a/sample/TypeShim.Sample/PeopleApiClient.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net.Http; -using System.Net.Http.Json; -using System.Threading.Tasks; -using TypeShim; -using VisageNovel; - -namespace TypeShim.Sample; - -public class PeopleApiClient(HttpClient httpClient) -{ - public async Task> GetElderlyPeopleAsync() - { - PeopleDto? dto = await httpClient.GetFromJsonAsync("/people/elderly", typeof(PeopleDto), PersonDtoSerializerContext.Default) as PeopleDto; - return dto?.People?.Select(dto => dto.ToPerson()) ?? []; - } - - public async Task> GetAllPeopleAsync() - { - PeopleDto? dto = await httpClient.GetFromJsonAsync("/people/all", typeof(PeopleDto), PersonDtoSerializerContext.Default) as PeopleDto; - return dto?.People?.Select(dto => dto.ToPerson()) ?? []; - } -} diff --git a/sample/TypeShim.Sample/PersonDtoSerializerContext.cs b/sample/TypeShim.Sample/PersonDtoSerializerContext.cs deleted file mode 100644 index 46a78fed..00000000 --- a/sample/TypeShim.Sample/PersonDtoSerializerContext.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System.Text.Json.Serialization; -using TypeShim.Sample; - -namespace VisageNovel; - -[JsonSourceGenerationOptions( - GenerationMode = JsonSourceGenerationMode.Serialization | JsonSourceGenerationMode.Metadata, - PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase)] -[JsonSerializable(typeof(PeopleDto))] -[JsonSerializable(typeof(PersonDto))] -[JsonSerializable(typeof(PersonDto[]))] -[JsonSerializable(typeof(DogDto))] -[JsonSerializable(typeof(DogDto[]))] -internal partial class PersonDtoSerializerContext : JsonSerializerContext { } diff --git a/sample/TypeShim.Sample/PersonRepository.cs b/sample/TypeShim.Sample/PersonRepository.cs deleted file mode 100644 index b07b4347..00000000 --- a/sample/TypeShim.Sample/PersonRepository.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; - -namespace TypeShim.Sample; - -public class PersonRepository -{ - private readonly RandomEntityGenerator _generator = new(); - - private readonly List _people; - - public PersonRepository() - { - _people = _generator.GeneratePersons(250); - } - - public IEnumerable GetAll() - { - return _people; - } -} diff --git a/sample/TypeShim.Sample/Program.cs b/sample/TypeShim.Sample/Program.cs deleted file mode 100644 index b41a41da..00000000 --- a/sample/TypeShim.Sample/Program.cs +++ /dev/null @@ -1,24 +0,0 @@ -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; -using System; -using System.Net.Http; -using System.Runtime.InteropServices.JavaScript; - -namespace TypeShim.Sample; - -public partial class Program -{ - public static void Main(string[] args) - { - Console.WriteLine("WASM runtime is alive."); - - // You can put any startup logic here if needed, like set up DI even a whole host builder. - // even make JSImport calls - - // this could then be combined with a static accessor to expose certain services to JS. - - // For this demo however, MyApp has been TSExport'ed and will be initialized from JS. - Console.WriteLine($"{nameof(MyApp)} can be initialized even without calling into Main"); - } -} diff --git a/sample/TypeShim.Sample/Properties/launchSettings.json b/sample/TypeShim.Sample/Properties/launchSettings.json deleted file mode 100644 index 12222cf5..00000000 --- a/sample/TypeShim.Sample/Properties/launchSettings.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "profiles": { - "webassembly standalone": { - "commandName": "Project", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - }, - "applicationUrl": "https://localhost:7200;http://localhost:5295", - "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}" - } - } -} diff --git a/sample/TypeShim.Sample.Client/@typeshim/app/index.html b/sample/app/index.html similarity index 56% rename from sample/TypeShim.Sample.Client/@typeshim/app/index.html rename to sample/app/index.html index 8f199756..7bb81257 100644 --- a/sample/TypeShim.Sample.Client/@typeshim/app/index.html +++ b/sample/app/index.html @@ -4,14 +4,10 @@ - @typeshim/app Demo + @client/app Demo - - - + +
diff --git a/sample/TypeShim.Sample.Client/@typeshim/app/package.json b/sample/app/package.json similarity index 63% rename from sample/TypeShim.Sample.Client/@typeshim/app/package.json rename to sample/app/package.json index 6d04767e..869f8be8 100644 --- a/sample/TypeShim.Sample.Client/@typeshim/app/package.json +++ b/sample/app/package.json @@ -1,18 +1,15 @@ { - "name": "@typeshim/app", + "name": "@client/app", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { - "dev": "vite build --watch", + "dev": "vite", "build": "vite build", "preview": "vite preview", "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", - "license": "ISC", - "dependencies": { - "@typeshim/people-ui": "*" - } + "license": "ISC" } diff --git a/sample/app/src/App.tsx b/sample/app/src/App.tsx new file mode 100644 index 00000000..bc625c5a --- /dev/null +++ b/sample/app/src/App.tsx @@ -0,0 +1,56 @@ +import { useState } from 'react'; +import Home from './pages/Home'; +import People from './pages/People'; +import { TypeShimProvider } from '@client/wasm-exports'; +import { AppProvider } from './people/AppProvider'; + +type Page = 'home' | 'people'; + +function App() { + return ( + + + + + + ); +} + +function Content() { + const [currentPage, setCurrentPage] = useState('home'); + + return ( + ) +} + +export default App; \ No newline at end of file diff --git a/sample/TypeShim.Sample.Client/@typeshim/app/src/index.css b/sample/app/src/index.css similarity index 100% rename from sample/TypeShim.Sample.Client/@typeshim/app/src/index.css rename to sample/app/src/index.css diff --git a/sample/TypeShim.Sample.Client/@typeshim/app/src/main.tsx b/sample/app/src/main.tsx similarity index 100% rename from sample/TypeShim.Sample.Client/@typeshim/app/src/main.tsx rename to sample/app/src/main.tsx diff --git a/sample/TypeShim.Sample.Client/@typeshim/app/src/pages/Home.tsx b/sample/app/src/pages/Home.tsx similarity index 95% rename from sample/TypeShim.Sample.Client/@typeshim/app/src/pages/Home.tsx rename to sample/app/src/pages/Home.tsx index 3b319b06..c8291eb2 100644 --- a/sample/TypeShim.Sample.Client/@typeshim/app/src/pages/Home.tsx +++ b/sample/app/src/pages/Home.tsx @@ -2,7 +2,7 @@ export default function Home() { return (
-

Welcome to @typeshim/app

+

Welcome to @client/app

This is a sample React app that demonstrates how TypeShim extends .NET <> Javascript interop with:

  • Natural object semantics like:
  • diff --git a/sample/TypeShim.Sample.Client/@typeshim/app/src/pages/People.tsx b/sample/app/src/pages/People.tsx similarity index 80% rename from sample/TypeShim.Sample.Client/@typeshim/app/src/pages/People.tsx rename to sample/app/src/pages/People.tsx index 5075a29e..b492bb4a 100644 --- a/sample/TypeShim.Sample.Client/@typeshim/app/src/pages/People.tsx +++ b/sample/app/src/pages/People.tsx @@ -1,20 +1,20 @@ -import React, { useState } from 'react'; -import { PeopleList, PeopleGrid } from '@typeshim/people-ui'; -import { PeopleRepository } from '@typeshim/people-ui'; +import { useState } from 'react'; +import { PeopleList, PeopleGrid } from '../people'; export default function People() { const [view, setView] = useState<'list' | 'grid'>('list'); const toggle = () => setView(v => (v === 'list' ? 'grid' : 'list')); - const repository = new PeopleRepository(); return (

    All data on this screen is accessed through interop calls to the dotnet runtime. Getting the array of people, getting a Person's name, getting their (optional) Pet or getting the Pet's name, these are all examples of interop calls. + + Try clicking the buttons and pet chips to see state being manipulated in .NET.

    There are ~1500 interop calls to methods on about 400 dotnet object instances made to render this page. - The impact of this many calls is not noticable (credits to the dotnet/runtime team!), try using your browser's devtools to profile the app. + The impact of this many calls is not noticable (credits to the dotnet/runtime team!).

    {view === 'list' ? ( - + ) : ( - + )}

    ); diff --git a/sample/app/src/people/AppProvider.tsx b/sample/app/src/people/AppProvider.tsx new file mode 100644 index 00000000..cded1ae9 --- /dev/null +++ b/sample/app/src/people/AppProvider.tsx @@ -0,0 +1,14 @@ +import { PeopleApp } from '@client/wasm-exports'; +import { useMemo, ReactNode } from 'react'; +import AppContext from './appContext'; + +export interface AppProviderProps { + children: ReactNode; +} + +export function AppProvider({ children }: AppProviderProps) { + // TypeShim automatically map the object literal to an PeopleAppOptions instance required for the PeopleApp constructor + const peopleApp = new PeopleApp({ BaseAddress: document.baseURI}); + const value = useMemo(() => (peopleApp), [peopleApp]); + return {children} ; +} \ No newline at end of file diff --git a/sample/TypeShim.Sample.Client/@typeshim/people-ui/src/PeopleGrid.tsx b/sample/app/src/people/PeopleGrid.tsx similarity index 83% rename from sample/TypeShim.Sample.Client/@typeshim/people-ui/src/PeopleGrid.tsx rename to sample/app/src/people/PeopleGrid.tsx index f8a4c8a9..cb20a4bf 100644 --- a/sample/TypeShim.Sample.Client/@typeshim/people-ui/src/PeopleGrid.tsx +++ b/sample/app/src/people/PeopleGrid.tsx @@ -2,19 +2,20 @@ import React, { useEffect, useState } from 'react'; import { PersonCard } from './PersonCard'; -import type { Person } from '@typeshim/wasm-exports'; +import type { Person } from '@client/wasm-exports'; import { PeopleRepository } from './PeopleRepository'; +import AppContext from './appContext'; export interface PeopleGridProps { emptyText?: string; - repository: PeopleRepository; } -export const PeopleGrid: React.FC = ({ emptyText = 'No people found.', repository }) => { +export const PeopleGrid: React.FC = ({ emptyText = 'No people found.' }) => { const [people, setPeople] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); - + const app = React.useContext(AppContext); + const repository = new PeopleRepository(app.GetPeopleProvider()); useEffect(() => { const fetchPeople = async () => { try { diff --git a/sample/TypeShim.Sample.Client/@typeshim/people-ui/src/PeopleList.tsx b/sample/app/src/people/PeopleList.tsx similarity index 82% rename from sample/TypeShim.Sample.Client/@typeshim/people-ui/src/PeopleList.tsx rename to sample/app/src/people/PeopleList.tsx index 70e7d7d0..fc48a07a 100644 --- a/sample/TypeShim.Sample.Client/@typeshim/people-ui/src/PeopleList.tsx +++ b/sample/app/src/people/PeopleList.tsx @@ -2,19 +2,21 @@ import React, { useEffect, useState } from 'react'; import { PersonCard } from './PersonCard'; -import type { Person } from '@typeshim/wasm-exports'; +import type { Person } from '@client/wasm-exports'; import { PeopleRepository } from './PeopleRepository'; +import AppContext from './appContext'; export interface PeopleListProps { emptyText?: string; - repository: PeopleRepository; } -export const PeopleList: React.FC = ({ emptyText = 'No people found.', repository }) => { +export const PeopleList: React.FC = ({ emptyText = 'No people found.' }) => { const [people, setPeople] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); + const app = React.useContext(AppContext); + const repository = new PeopleRepository(app.GetPeopleProvider()); useEffect(() => { const fetchPeople = async () => { try { diff --git a/sample/TypeShim.Sample.Client/@typeshim/people-ui/src/PeopleRepository.tsx b/sample/app/src/people/PeopleRepository.tsx similarity index 54% rename from sample/TypeShim.Sample.Client/@typeshim/people-ui/src/PeopleRepository.tsx rename to sample/app/src/people/PeopleRepository.tsx index 59c02a2f..78e543b3 100644 --- a/sample/TypeShim.Sample.Client/@typeshim/people-ui/src/PeopleRepository.tsx +++ b/sample/app/src/people/PeopleRepository.tsx @@ -1,22 +1,18 @@ -import { People, Person, PeopleProvider, TimeoutUnit, MyApp } from '@typeshim/wasm-exports'; +import { People, Person, PeopleProvider } from '@client/wasm-exports'; export class PeopleRepository { - public async getAllPeople(): Promise { - const peopleProvider: PeopleProvider = MyApp.GetPeopleProvider(); - const people: People = await peopleProvider.FetchPeopleAsync(); - return people.All; + constructor(private readonly peopleProvider: PeopleProvider) { } - public SetDelays(jsTimeout: number, csDelay: number) { - const peopleProvider: PeopleProvider = MyApp.GetPeopleProvider(); - const timeoutUnit: TimeoutUnit.Initializer | null = { Timeout: csDelay }; - peopleProvider.DelayTask = new Promise((resolve) => setTimeout(() => resolve(timeoutUnit), jsTimeout)); + public async getAllPeople(): Promise { + const people: People = await this.peopleProvider.FetchPeopleAsync(); + return people.All; } private async PrintAgeMethodUsage() { console.log("Demonstrating Person.IsOlderThan method:"); - const persons: Person[] = (await MyApp.GetPeopleProvider().FetchPeopleAsync()).All; + const persons: Person[] = (await this.peopleProvider.FetchPeopleAsync()).All; const person1 = persons[(Math.random() * persons.length) | 0]; const person2 = persons[(Math.random() * persons.length) | 0]; const p = Person.materialize(person2); @@ -29,7 +25,7 @@ export class PeopleRepository { }); person3.AdoptPet(); console.log(person1.Name, person1.Age, "isOlderThan", person2.Name, person2.Age, ":", person1.IsOlderThan(person2)); - console.log(person1.Name, person1.Age, "isOlderThan (snapshot)", jsPerson.Name, jsPerson.Age, ":", person1.IsOlderThan(jsPerson)); + console.log(person1.Name, person1.Age, "isOlderThan (initializer)", jsPerson.Name, jsPerson.Age, ":", person1.IsOlderThan(jsPerson)); console.log(person1.Name, person1.Age, "isOlderThan (constructor)", person3.Name, person3.Age, ":", person1.IsOlderThan(person3)); } } diff --git a/sample/TypeShim.Sample.Client/@typeshim/people-ui/src/PersonCard.tsx b/sample/app/src/people/PersonCard.tsx similarity index 98% rename from sample/TypeShim.Sample.Client/@typeshim/people-ui/src/PersonCard.tsx rename to sample/app/src/people/PersonCard.tsx index c63adf8d..3781257e 100644 --- a/sample/TypeShim.Sample.Client/@typeshim/people-ui/src/PersonCard.tsx +++ b/sample/app/src/people/PersonCard.tsx @@ -1,5 +1,5 @@ import React, { useState } from 'react'; -import { Person, Dog } from '@typeshim/wasm-exports'; +import { Person, Dog } from '@client/wasm-exports'; import { PetChip } from './PetChip'; export interface PersonCardProps { diff --git a/sample/TypeShim.Sample.Client/@typeshim/people-ui/src/PetChip.tsx b/sample/app/src/people/PetChip.tsx similarity index 95% rename from sample/TypeShim.Sample.Client/@typeshim/people-ui/src/PetChip.tsx rename to sample/app/src/people/PetChip.tsx index 91093d33..92ac887a 100644 --- a/sample/TypeShim.Sample.Client/@typeshim/people-ui/src/PetChip.tsx +++ b/sample/app/src/people/PetChip.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { Dog } from '@typeshim/wasm-exports'; +import { Dog } from '@client/wasm-exports'; export interface PetChipProps { petProxy: Dog; diff --git a/sample/app/src/people/appContext.ts b/sample/app/src/people/appContext.ts new file mode 100644 index 00000000..6bd69278 --- /dev/null +++ b/sample/app/src/people/appContext.ts @@ -0,0 +1,5 @@ +import { PeopleApp } from '@client/wasm-exports'; +import React from 'react'; + +const AppContext = React.createContext(undefined!); +export default AppContext; diff --git a/sample/TypeShim.Sample.Client/@typeshim/people-ui/src/index.ts b/sample/app/src/people/index.ts similarity index 68% rename from sample/TypeShim.Sample.Client/@typeshim/people-ui/src/index.ts rename to sample/app/src/people/index.ts index a666acb7..6b0a4429 100644 --- a/sample/TypeShim.Sample.Client/@typeshim/people-ui/src/index.ts +++ b/sample/app/src/people/index.ts @@ -2,3 +2,5 @@ export * from './PeopleList'; export * from './PeopleGrid'; export * from './PeopleRepository'; +export * from './appContext'; +export * from './AppProvider'; \ No newline at end of file diff --git a/sample/TypeShim.Sample.Client/@typeshim/app/tsconfig.json b/sample/app/tsconfig.json similarity index 100% rename from sample/TypeShim.Sample.Client/@typeshim/app/tsconfig.json rename to sample/app/tsconfig.json diff --git a/sample/TypeShim.Sample.Client/@typeshim/app/tsconfig.node.json b/sample/app/tsconfig.node.json similarity index 100% rename from sample/TypeShim.Sample.Client/@typeshim/app/tsconfig.node.json rename to sample/app/tsconfig.node.json diff --git a/sample/TypeShim.Sample.Client/@typeshim/app/vite.config.ts b/sample/app/vite.config.ts similarity index 83% rename from sample/TypeShim.Sample.Client/@typeshim/app/vite.config.ts rename to sample/app/vite.config.ts index 9b60748d..cfe0feb9 100644 --- a/sample/TypeShim.Sample.Client/@typeshim/app/vite.config.ts +++ b/sample/app/vite.config.ts @@ -5,10 +5,10 @@ export default defineConfig({ plugins: [ react() ], - assetsInclude: ['**/*.dat', '**/*.wasm'], + assetsInclude: ['**/*.dat', '**/*.wasm', '**/*.pdb'], build: { target: 'es2020', - outDir: '../../../TypeShim.Sample/wwwroot', + outDir: './dist', assetsDir: 'assets', rollupOptions: { external: ['webcil', '/_framework/dotnet.js'], diff --git a/sample/TypeShim.Sample.Client/package-lock.json b/sample/package-lock.json similarity index 98% rename from sample/TypeShim.Sample.Client/package-lock.json rename to sample/package-lock.json index 6d85de1e..028c7fab 100644 --- a/sample/TypeShim.Sample.Client/package-lock.json +++ b/sample/package-lock.json @@ -1,15 +1,16 @@ { - "name": "@typeshim/demo", + "name": "@client/demo", "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "@typeshim/demo", + "name": "@client/demo", "version": "1.0.0", "license": "ISC", "workspaces": [ - "@typeshim/*" + "Library/bin/wwwroot", + "app" ], "dependencies": { "react": "^18.3.1", @@ -25,36 +26,40 @@ "vite-plugin-wasm": "^3.5.0" } }, - "@typeshim/app": { + "@client/app": { "version": "1.0.0", - "license": "ISC", - "dependencies": { - "@typeshim/people-ui": "*" - } + "extraneous": true, + "license": "ISC" }, - "@typeshim/capabilities-ui": { + "@client/capabilities-ui": { "version": "1.0.0", "extraneous": true, "dependencies": { - "@typeshim/wasm-exports": "*" + "@client/wasm-exports": "*" }, "peerDependencies": { "react": "^18.2.0", "react-dom": "^18.2.0" } }, - "@typeshim/people-ui": { + "@client/wasm-exports": { "version": "1.0.0", - "license": "ISC", - "dependencies": { - "@typeshim/wasm-exports": "*" - }, - "peerDependencies": { - "react": "^18.2.0", - "react-dom": "^18.2.0" - } + "extraneous": true, + "license": "ISC" }, - "@typeshim/wasm-exports": { + "app": { + "name": "@client/app", + "version": "1.0.0", + "license": "ISC" + }, + "Client.Library/bin/wwwroot": { + "name": "@client/wasm-exports", + "version": "1.0.0", + "extraneous": true, + "license": "ISC" + }, + "Library/bin/wwwroot": { + "name": "@client/wasm-exports", "version": "1.0.0", "license": "ISC" }, @@ -303,6 +308,14 @@ "node": ">=6.9.0" } }, + "node_modules/@client/app": { + "resolved": "app", + "link": true + }, + "node_modules/@client/wasm-exports": { + "resolved": "Library/bin/wwwroot", + "link": true + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.25.12", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", @@ -1392,18 +1405,6 @@ "@types/react": "^19.2.0" } }, - "node_modules/@typeshim/app": { - "resolved": "@typeshim/app", - "link": true - }, - "node_modules/@typeshim/people-ui": { - "resolved": "@typeshim/people-ui", - "link": true - }, - "node_modules/@typeshim/wasm-exports": { - "resolved": "@typeshim/wasm-exports", - "link": true - }, "node_modules/@vitejs/plugin-react": { "version": "4.7.0", "dev": true, diff --git a/sample/TypeShim.Sample.Client/package.json b/sample/package.json similarity index 58% rename from sample/TypeShim.Sample.Client/package.json rename to sample/package.json index ff7b3ec9..60505d84 100644 --- a/sample/TypeShim.Sample.Client/package.json +++ b/sample/package.json @@ -1,22 +1,22 @@ { - "name": "@typeshim/demo", + "name": "@client/demo", "version": "1.0.0", "description": "", "scripts": { - "start": "npm run dev -w @typeshim/app", - "dev": "npm run dev -w @typeshim/app", + "start": "npm run dev -w @client/app", + "dev": "npm run dev -w @client/app", "test": "echo \"Error: no test specified\" && exit 1", - "build:exports": "npm run build -w @typeshim/wasm-exports", - "build:people": "npm run build -w @typeshim/people-ui", - "build:app": "npm run build -w @typeshim/app", - "build": "npm run build:exports && npm run build:people && npm run build:app" + "build:exports": "npm run build -w @client/wasm-exports", + "build:app": "npm run build -w @client/app", + "build": "dotnet build Client.Library/Client.Library.csproj && npm i && npm run build:exports && npm run build:app" }, "keywords": [], "author": "", "license": "ISC", "private": "true", "workspaces": [ - "@typeshim/*" + "Library/bin/wwwroot", + "app" ], "dependencies": { "react": "^18.3.1", diff --git a/sample/TypeShim.Sample.Client/tsconfig.json b/sample/tsconfig.json similarity index 86% rename from sample/TypeShim.Sample.Client/tsconfig.json rename to sample/tsconfig.json index e8db3099..f8a4b65a 100644 --- a/sample/TypeShim.Sample.Client/tsconfig.json +++ b/sample/tsconfig.json @@ -10,6 +10,5 @@ "skipLibCheck": true }, "references": [ - { "path": "@typeshim/people-ui" } ] } diff --git a/src/TypeShim/TypeShim.csproj b/src/TypeShim/TypeShim.csproj index 7f4afdf2..cfcd4663 100644 --- a/src/TypeShim/TypeShim.csproj +++ b/src/TypeShim/TypeShim.csproj @@ -16,8 +16,8 @@ 0.0.1 ArcadeMode TypeShim - Typesafe .NET ↔︎ TypeScript interop - wasm, csharp, typescript, interop, generator + Seamless, type-safe interop between .NET WebAssembly and TypeScript + wasm, csharp, typescript, interop, code-gen, code-generation, tooling, msbuild https://github.com/ArcadeMode/TypeShim https://github.com/ArcadeMode/TypeShim MIT