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

New libNode API #405

Merged
merged 13 commits into from
Feb 11, 2025
Merged
4 changes: 4 additions & 0 deletions Directory.Build.targets
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,17 @@
<Target Name="WriteVersionProps"
BeforeTargets="Pack"
>
<PropertyGroup>
<LibNodePackageVersion Condition="'%(PackageVersion.Identity)' == 'Microsoft.JavaScript.LibNode'">%(PackageVersion.Version)</LibNodePackageVersion>
</PropertyGroup>
<WriteLinesToFile
File="$(PackageOutputPath)version.props"
Overwrite="true"
WriteOnlyWhenDifferent="true"
Lines="
&lt;Project&gt;;
%20%20&lt;PropertyGroup&gt;;
%20%20%20%20&lt;LibNodePackageVersion&gt;$(LibNodePackageVersion)&lt;/LibNodePackageVersion&gt;;
%20%20%20%20&lt;NodeApiDotNetPackageVersion&gt;$(NuGetPackageVersion)&lt;/NodeApiDotNetPackageVersion&gt;;
%20%20&lt;/PropertyGroup&gt;;
&lt;/Project&gt;"
Expand Down
2 changes: 1 addition & 1 deletion Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
<PackageVersion Include="Microsoft.Bcl.AsyncInterfaces" Version="7.0.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.1.0" /><!-- 4.1.0 is compatible with .NET Standard -->
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" />
<PackageVersion Include="Microsoft.JavaScript.LibNode" Version="20.1800.202" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.5.0" />
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="8.0.0" />
<PackageVersion Include="Nerdbank.GitVersioning" Version="3.6.133" />
Expand All @@ -16,7 +17,6 @@
<PackageVersion Include="System.Reflection.MetadataLoadContext" Version="6.0.0" />
<PackageVersion Include="xunit" Version="2.4.2" />
<PackageVersion Include="xunit.runner.visualstudio" Version="2.4.5" />
<PackageVersion Include="Xunit.SkippableFact" Version="1.4.13" />
<PackageVersion Include="XmlDocMarkdown.Core" Version="2.9.0" />
</ItemGroup>
</Project>
29 changes: 18 additions & 11 deletions bench/Benchmarks.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@ public static void Main(string[] args)
GetCurrentPlatformRuntimeIdentifier(),
"libnode" + GetSharedLibraryExtension());

private napi_env _env;
private NodeEmbeddingRuntime? _runtime;
private NodeEmbeddingNodeApiScope? _nodeApiScope;
private JSValue _jsString;
private JSFunction _jsFunction;
private JSFunction _jsFunctionWithArgs;
Expand All @@ -64,6 +65,9 @@ public static void Main(string[] args)
private JSFunction _jsFunctionCallMethod;
private JSFunction _jsFunctionCallMethodWithArgs;
private JSReference _reference = null!;
private static readonly string[] s_settings = new[] { "node", "--expose-gc" };
private static string s_mainScript { get; } =
"globalThis.require = require('module').createRequire(process.execPath);\n";

/// <summary>
/// Simple class that is exported to JS and used in some benchmarks.
Expand All @@ -84,16 +88,19 @@ public static void Method() { }
/// </summary>
protected void Setup()
{
NodejsPlatform platform = new(LibnodePath/*, args: new[] { "node", "--expose-gc" }*/);

// This setup avoids using NodejsEnvironment so benchmarks can run on the same thread.
// NodejsEnvironment creates a separate thread that would slow down the micro-benchmarks.
platform.Runtime.CreateEnvironment(
platform, Console.WriteLine, null, NodejsEnvironment.NodeApiVersion, out _env)
.ThrowIfFailed();

// The new scope instance saves itself as the thread-local JSValueScope.Current.
JSValueScope scope = new(JSValueScopeType.Root, _env, platform.Runtime);
NodeEmbeddingPlatform platform = new(
LibnodePath,
new NodeEmbeddingPlatformSettings { Args = s_settings });

// This setup avoids using NodejsEmbeddingThreadRuntime so benchmarks can run on
// the same thread. NodejsEmbeddingThreadRuntime creates a separate thread that would slow
// down the micro-benchmarks.
_runtime = NodeEmbeddingRuntime.Create(platform,
new NodeEmbeddingRuntimeSettings { MainScript = s_mainScript });

// The nodeApiScope creates JSValueScope instance that saves itself as
// the thread-local JSValueScope.Current.
_nodeApiScope = new(_runtime);

// Create some JS values that will be used by the benchmarks.

Expand Down
11 changes: 8 additions & 3 deletions examples/jsdom/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,13 @@ public static void Main()
{
string appDir = Path.GetDirectoryName(typeof(Program).Assembly.Location)!;
string libnodePath = Path.Combine(appDir, "libnode.dll");
using NodejsPlatform nodejsPlatform = new(libnodePath);
using NodejsEnvironment nodejs = nodejsPlatform.CreateEnvironment(appDir);
using NodeEmbeddingPlatform nodejsPlatform = new(libnodePath, null);
using NodeEmbeddingThreadRuntime nodejs = nodejsPlatform.CreateThreadRuntime(appDir,
new NodeEmbeddingRuntimeSettings
{
MainScript =
"globalThis.require = require('module').createRequire(process.execPath);\n"
});
if (Debugger.IsAttached)
{
int pid = Process.GetCurrentProcess().Id;
Expand All @@ -25,7 +30,7 @@ public static void Main()
Console.WriteLine(content);
}

private static string GetContent(NodejsEnvironment nodejs, string html)
private static string GetContent(NodeEmbeddingThreadRuntime nodejs, string html)
{
JSValue jsdomClass = nodejs.Import(module: "jsdom", property: "JSDOM");
JSValue dom = jsdomClass.CallAsConstructor(html);
Expand Down
5 changes: 1 addition & 4 deletions examples/jsdom/jsdom.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,10 @@
<ItemGroup>
<None Include="README.md" />
<Compile Include="*.cs" />
<Content Include="..\..\bin\win-x64\libnode.dll">
<Visible>false</Visible>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.JavaScript.LibNode" Version="$(LibNodePackageVersion)" />
<PackageReference Include="Microsoft.JavaScript.NodeApi" Version="$(NodeApiDotnetPackageVersion)" />
</ItemGroup>
</Project>
11 changes: 7 additions & 4 deletions examples/winui-fluid/App.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,12 @@ public App()

string appDir = Path.GetDirectoryName(typeof(App).Assembly.Location)!;
string libnodePath = Path.Combine(appDir, "libnode.dll");
NodejsPlatform nodejsPlatform = new(libnodePath);

Nodejs = nodejsPlatform.CreateEnvironment(appDir);
NodeEmbeddingPlatform nodejsPlatform = new(libnodePath, null);
Nodejs = nodejsPlatform.CreateThreadRuntime(appDir, new NodeEmbeddingRuntimeSettings
{
MainScript =
"globalThis.require = require('module').createRequire(process.execPath);\n"
});
if (Debugger.IsAttached)
{
int pid = Process.GetCurrentProcess().Id;
Expand Down Expand Up @@ -60,5 +63,5 @@ private void OnMainWindowClosed(object sender, WindowEventArgs args)

public static new App Current => (App)Application.Current;

public NodejsEnvironment Nodejs { get; }
public NodeEmbeddingThreadRuntime Nodejs { get; }
}
2 changes: 1 addition & 1 deletion examples/winui-fluid/CollabEditBox.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public sealed partial class CollabEditBox : UserControl
private const string FluidServiceUri = "http://localhost:7070/";

private readonly SynchronizationContext uiSyncContext;
private readonly NodejsEnvironment nodejs;
private readonly NodeEmbeddingThreadRuntime nodejs;
private readonly JSMarshaller marshaller;

private ITinyliciousClient fluidClient = null!;
Expand Down
5 changes: 1 addition & 4 deletions examples/winui-fluid/winui-fluid.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,10 @@

<ItemGroup>
<EmbeddedResource Include="README.md" />
<Content Include="..\..\bin\win-x64\libnode.dll">
<Visible>false</Visible>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.JavaScript.LibNode" Version="$(LibNodePackageVersion)" />
<PackageReference Include="Microsoft.JavaScript.NodeApi" Version="$(NodeApiDotnetPackageVersion)" />
<PackageReference Include="Microsoft.JavaScript.NodeApi.DotNetHost" Version="$(NodeApiDotnetPackageVersion)" />
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.*" />
Expand Down
2 changes: 1 addition & 1 deletion src/NodeApi.DotNetHost/JSRuntimeContextExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ public static T Import<T>(
/// <exception cref="ArgumentNullException">Both <paramref cref="module" /> and
/// <paramref cref="property" /> are null.</exception>
public static T Import<T>(
this NodejsEnvironment nodejs,
this NodeEmbeddingThreadRuntime nodejs,
string? module,
string? property,
bool esModule,
Expand Down
17 changes: 17 additions & 0 deletions src/NodeApi/Interop/EmptyAttributes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,23 @@ public CallerArgumentExpressionAttribute(string parameterName)

public string ParameterName { get; }
}

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Field | AttributeTargets.Property
| AttributeTargets.Struct, AllowMultiple = false, Inherited = false)]
public sealed class RequiredMemberAttribute : Attribute
{
}

[AttributeUsage(AttributeTargets.All, AllowMultiple = true, Inherited = false)]
public sealed class CompilerFeatureRequiredAttribute : Attribute
{
public CompilerFeatureRequiredAttribute (string featureName)
{
FeatureName = featureName;
}

public string FeatureName { get; }
}
}

namespace System.Diagnostics
Expand Down
16 changes: 16 additions & 0 deletions src/NodeApi/NodeApiStatusExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using Microsoft.JavaScript.NodeApi.Runtime;
using static Microsoft.JavaScript.NodeApi.Runtime.JSRuntime;
using static Microsoft.JavaScript.NodeApi.Runtime.NodejsRuntime;

namespace Microsoft.JavaScript.NodeApi;

Expand Down Expand Up @@ -59,5 +61,19 @@ public static T ThrowIfFailed<T>(this napi_status status,
status.ThrowIfFailed(memberName, sourceFilePath, sourceLineNumber);
return value;
}

[StackTraceHidden]
public static void ThrowIfFailed([DoesNotReturnIf(true)] this NodeEmbeddingStatus status,
[CallerMemberName] string memberName = "",
[CallerFilePath] string sourceFilePath = "",
[CallerLineNumber] int sourceLineNumber = 0)
{
if (status == NodeEmbeddingStatus.OK)
return;
throw new JSException($"""
Error in {memberName} at {sourceFilePath}:{sourceLineNumber}
{NodeEmbedding.JSRuntime.EmbeddingGetLastErrorMessage()}
""");
}
}

16 changes: 2 additions & 14 deletions src/NodeApi/Runtime/JSRuntime.Types.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

namespace Microsoft.JavaScript.NodeApi.Runtime;

using System;
using System.Runtime.InteropServices;

namespace Microsoft.JavaScript.NodeApi.Runtime;

// Type definitions from Node.JS js_native_api.h and js_native_api_types.h
public unsafe partial class JSRuntime
{
Expand All @@ -31,7 +31,6 @@ public record struct napi_handle_scope(nint Handle);
public record struct napi_escapable_handle_scope(nint Handle);
public record struct napi_callback_info(nint Handle);
public record struct napi_deferred(nint Handle);
public record struct napi_platform(nint Handle);

//===========================================================================
// Enum types
Expand Down Expand Up @@ -141,17 +140,6 @@ public napi_finalize(napi_finalize.Delegate callback)
: this(Marshal.GetFunctionPointerForDelegate(callback)) { }
}

public struct napi_error_message_handler
{
public nint Handle;

[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void Delegate(byte* message);

public napi_error_message_handler(napi_error_message_handler.Delegate handler)
=> Handle = Marshal.GetFunctionPointerForDelegate(handler);
}

public struct napi_property_descriptor
{
// One of utf8name or name should be NULL.
Expand Down
Loading