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

Improve module typedefs and loader generation #265

Merged
merged 3 commits into from
Apr 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 3 additions & 4 deletions Docs/dynamic-invoke.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,10 @@ For examples of this scenario, see
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<OutDir>bin</OutDir>
<NodeApiAssemblyJSModuleType>commonjs</NodeApiAssemblyJSModuleType>
<GenerateNodeApiTypeDefinitionsForReferences>true</GenerateNodeApiTypeDefinitionsForReferences>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.JavaScript.NodeApi.Generator" Version="0.5.*" />
<PackageReference Include="Microsoft.JavaScript.NodeApi.Generator" Version="0.7.*" />
<PackageReference Include="Example.Package" Version="1.2.3" />
<PackageReference Include="Example.Package.Two" Version="2.3.4" />
</ItemGroup>
Expand All @@ -27,15 +26,15 @@ For examples of this scenario, see
will be placed there.
- The `Microsoft.JavaScript.NodeApi.Generator` package reference enables automatic generation
of TS type-definitions for the referenced assemblies.
- Change `NodeApiAssemblyJSModuleType` to `esm` if using ES modules.

Build the project to restore the packages, place assemblies in the `bin` directory, and generate
type definitions:
```
dotnet build
```

2. Add a dependency on the `node-api-dotnet` npm package to your JavaScript project:
2. Create a `package.json` file for the project, if you haven't already. Then add a dependency on
the `node-api-dotnet` npm package to your JavaScript project:
```
npm install node-api-dotnet
```
Expand Down
4 changes: 2 additions & 2 deletions Docs/node-module.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ For a minimal example of this scenario, see
Afterward you should have the two references in your project file:
```xml
<ItemGroup>
<PackageReference Include="Microsoft.JavaScript.NodeApi" Version="0.4.*-*" />
<PackageReference Include="Microsoft.JavaScript.NodeApi.Generator" Version="0.4.*-*" />
<PackageReference Include="Microsoft.JavaScript.NodeApi" Version="0.7.*-*" />
<PackageReference Include="Microsoft.JavaScript.NodeApi.Generator" Version="0.7.*-*" />
</ItemGroup>
```

Expand Down
4 changes: 2 additions & 2 deletions examples/aot-module/example.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

const Example = require('./bin/aot-module').Example;
import { Example } from './bin/aot-module.js';

// Call a method exported by the .NET module.
const result = Example.hello('.NET AOT');

const assert = require('assert');
import assert from 'assert';
assert.strictEqual(result, 'Hello .NET AOT!');
4 changes: 4 additions & 0 deletions examples/aot-module/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"name": "node-api-dotnet-examples-aot-module",
"type": "module"
}
4 changes: 2 additions & 2 deletions examples/aot-npm-package/app/example.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

const Example = require('aot-npm-package').Example;
import { Example } from 'aot-npm-package';

// Call a method exported by the .NET module.
const result = Example.hello('.NET AOT');

const assert = require('assert');
import assert from 'node:assert';
assert.strictEqual(result, 'Hello .NET AOT!');
3 changes: 2 additions & 1 deletion examples/aot-npm-package/app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@
"author": "Microsoft",
"repository": "github:microsoft/node-api-dotnet",
"main": "./example.js",
"type": "module",
"scripts": {
},
"dependencies": {
"aot-npm-package": "file:../lib/pkg/aot-npm-package-0.1.6.tgz"
"aot-npm-package": "file:../lib"
}
}
7 changes: 5 additions & 2 deletions examples/aot-npm-package/lib/aot-npm-package.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,14 @@
<!-- The C# xmldoc file is converted to comments in the generated TS type definitions. -->
<GenerateDocumentationFile>true</GenerateDocumentationFile>

<!-- `dotnet publish` will produce node module files in $(PublishDir), with
.node native AOT binary files under $(RuntimeIdentifier) subdirectories. -->
<!-- `dotnet publish` will produce node module files in $(PublishDir) -->
<PublishAot>true</PublishAot>
<PublishNodeModule>true</PublishNodeModule>
<PublishDir>bin</PublishDir>

<!-- Place .node native AOT binary files under $(RuntimeIdentifier) subdirectories.
This could support packaging multiple platform-specific binaries in a single npm package
(though that would require building them each separately then merging the results). -->
<PublishMultiPlatformNodeModule>true</PublishMultiPlatformNodeModule>

<!-- `dotnet publish` will produce an npm package in the $(PackageOutputPath) directory. -->
Expand Down
5 changes: 3 additions & 2 deletions examples/aot-npm-package/lib/package.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
{
"name": "aot-npm-package",
"private": true,
"version": "0.1.6",
"version": "0.2",
"description": "Example npm-packaged C# Native AOT node module",
"license": "MIT",
"author": "Microsoft",
"repository": "github:microsoft/node-api-dotnet",
"main": "./bin/aot-npm-package",
"type": "module",
"main": "./bin/aot-npm-package.js",
"scripts": {
}
}
5 changes: 1 addition & 4 deletions examples/dotnet-module/example.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

const dotnet = require('node-api-dotnet');

/** @type {import('./bin/dotnet-module').Example} */
const Example = dotnet.require('./bin/dotnet-module').Example;
const Example = require('./bin/dotnet-module').Example;

// Call a method exported by the .NET module.
const result = Example.hello('.NET');
Expand Down
1 change: 1 addition & 0 deletions examples/dotnet-module/package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"name": "node-api-dotnet-examples-dotnet-module",
"type": "commonjs",
"dependencies": {
"node-api-dotnet": "file:../../out/pkg/node-api-dotnet"
}
Expand Down
1 change: 0 additions & 1 deletion examples/semantic-kernel/semantic-kernel.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
<TargetFramework>net6.0</TargetFramework>
<RestorePackagesPath>$(MSBuildThisFileDirectory)/pkg</RestorePackagesPath>
<OutDir>bin</OutDir>
<NodeApiAssemblyJSModuleType>esm</NodeApiAssemblyJSModuleType>
</PropertyGroup>

<ItemGroup>
Expand Down
1 change: 0 additions & 1 deletion examples/wpf/WpfExample.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
<TargetFramework>net6.0-windows</TargetFramework>
<RestorePackagesPath>$(MSBuildThisFileDirectory)/pkg</RestorePackagesPath>
<OutDir>bin</OutDir>
<NodeApiAssemblyJSModuleType>esm</NodeApiAssemblyJSModuleType>
<UseWPF>true</UseWPF>
<GenerateNodeApiTypeDefinitionsForReferences>true</GenerateNodeApiTypeDefinitionsForReferences>
</PropertyGroup>
Expand Down
31 changes: 14 additions & 17 deletions src/NodeApi.Generator/ModuleGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -316,10 +316,9 @@ private SourceBuilder GenerateModuleInitializer(
"[JSExport] attributes cannot be used with custom init method.");
}

string ns = GetNamespace(moduleInitializerMethod);
string className = moduleInitializerMethod.ContainingType.Name;
string classFullName = GetFullName(moduleInitializerMethod.ContainingType);
string methodName = moduleInitializerMethod.Name;
s += $"return (napi_value){ns}.{className}.{methodName}(";
s += $"return (napi_value){classFullName}.{methodName}(";
s += "context, (JSObject)exportsValue);";
}
else
Expand Down Expand Up @@ -364,8 +363,8 @@ private void ExportModule(
{
if (moduleType != null)
{
string ns = GetNamespace(moduleType);
s += $"exportsValue = new JSModuleBuilder<{ns}.{moduleType.Name}>()";
string typeFullName = GetFullName(moduleType);
s += $"exportsValue = new JSModuleBuilder<{typeFullName}>()";
s.IncreaseIndent();

// Export public non-static members of the module class.
Expand Down Expand Up @@ -421,8 +420,8 @@ private void ExportModule(
{
// Construct an instance of the custom module class when the module is initialized.
// If a no-args constructor is not present then the generated code will not compile.
string ns = GetNamespace(moduleType);
s += $".ExportModule(new {ns}.{moduleType.Name}(), (JSObject)exportsValue);";
string typeFullName = GetFullName(moduleType);
s += $".ExportModule(new {typeFullName}(), (JSObject)exportsValue);";
}
else
{
Expand Down Expand Up @@ -453,7 +452,7 @@ private void ExportType(
s += $".AddProperty(\"{exportName}\",";
s.IncreaseIndent();

string ns = GetNamespace(type);
string typeFullName = GetFullName(type);
if (type.TypeKind == TypeKind.Interface)
{
// Interfaces do not have constructors.
Expand Down Expand Up @@ -483,12 +482,12 @@ private void ExportType(
}
else if (constructors.Length == 1 && constructors[0].GetParameters().Length == 0)
{
s += $"\t() => new {ns}.{type.Name}())";
s += $"\t() => new {typeFullName}())";
}
else if (constructors.Length == 1 &&
constructors[0].GetParameters()[0].ParameterType == typeof(JSCallbackArgs))
{
s += $"\t(args) => new {ns}.{type.Name}(args))";
s += $"\t(args) => new {typeFullName}(args))";
}
else
{
Expand Down Expand Up @@ -607,10 +606,9 @@ private void ExportMethod(
if (methods.Count() == 1 && !IsMethodCallbackAdapterRequired(method))
{
// No adapter is needed for a method with a JSCallback signature.
string ns = GetNamespace(method);
string className = method.ContainingType.Name;
string typeFullName = GetFullName(method.ContainingType);
s += $".AddMethod(\"{exportName}\", " +
$"{ns}.{className}.{method.Name},\n\t{attributes})";
$"{typeFullName}.{method.Name},\n\t{attributes})";
}
else if (methods.Count() == 1)
{
Expand Down Expand Up @@ -656,8 +654,7 @@ private void ExportProperty(
s += $".AddProperty(\"{exportName}\",";
s.IncreaseIndent();

string ns = GetNamespace(property);
string className = property.ContainingType.Name;
string typeFullName = GetFullName(property.ContainingType);

if (property.GetMethod?.DeclaredAccessibility != Accessibility.Public)
{
Expand All @@ -672,7 +669,7 @@ private void ExportProperty(
}
else if (property.IsStatic)
{
s += $"getter: () => {ns}.{className}.{property.Name},";
s += $"getter: () => {typeFullName}.{property.Name},";
}
else
{
Expand All @@ -692,7 +689,7 @@ private void ExportProperty(
}
else if (property.IsStatic)
{
s += $"setter: (value) => {ns}.{className}.{property.Name} = value,";
s += $"setter: (value) => {typeFullName}.{property.Name} = value,";
}
else
{
Expand Down
12 changes: 10 additions & 2 deletions src/NodeApi.Generator/NodeApi.Generator.targets
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,13 @@

<NodeApiGeneratorAssemblyName>Microsoft.JavaScript.NodeApi.Generator</NodeApiGeneratorAssemblyName>
<NodeApiGeneratorAssemblyPath>$(MSBuildThisFileDirectory)../analyzers/dotnet/cs/$(NodeApiGeneratorAssemblyName).dll</NodeApiGeneratorAssemblyPath>
<NodeApiAssemblyJSModuleType Condition=" '$(NodeApiAssemblyJSModuleType)' == '' ">commonjs</NodeApiAssemblyJSModuleType>
<NodeApiTypeDefinitionsGeneratorOptions>--module $(NodeApiAssemblyJSModuleType) $(NodeApiTypedefsGeneratorOptions) --framework $(TargetFramework)</NodeApiTypeDefinitionsGeneratorOptions>

<!-- Try to infer the module type from package.json in the project directory. Otherwise default to generating both module types.-->
<NodeApiPackageJson Condition=" '$(NodeApiPackageJson)' == '' ">$(ProjectDir)package.json</NodeApiPackageJson>
<NodeApiJSModuleType Condition=" '$(NodeApiJSModuleType)' == '' AND Exists('$(NodeApiPackageJson)') ">&quot;$(NodeApiPackageJson)&quot;</NodeApiJSModuleType>
<NodeApiJSModuleType Condition=" '$(NodeApiJSModuleType)' == '' ">commonjs,esm</NodeApiJSModuleType>

<NodeApiTypeDefinitionsGeneratorOptions>--module $(NodeApiJSModuleType) --framework $(TargetFramework) $(NodeApiTypedefsGeneratorOptions)</NodeApiTypeDefinitionsGeneratorOptions>
</PropertyGroup>

<Target Name="ConfigureNodeApiTypeDefinitions"
Expand Down Expand Up @@ -58,6 +63,9 @@
Condition=" '$(GenerateNodeApiTypeDefinitions)' == 'true' AND Exists('$(TargetDir)$(NodeApiTypeDefinitionsFileName)') "
>
<Copy SourceFiles="$(TargetDir)$(NodeApiTypeDefinitionsFileName)" DestinationFolder="$(PublishDir)" />
<Copy SourceFiles="$(TargetDir)$(TargetName).js" DestinationFolder="$(PublishDir)" Condition="Exists('$(TargetDir)$(TargetName).js')" />
<Copy SourceFiles="$(TargetDir)$(TargetName).cjs" DestinationFolder="$(PublishDir)" Condition="Exists('$(TargetDir)$(TargetName).cjs')" />
<Copy SourceFiles="$(TargetDir)$(TargetName).mjs" DestinationFolder="$(PublishDir)" Condition="Exists('$(TargetDir)$(TargetName).mjs')" />
</Target>

<Target Name="CleanNodeApiTypeDefinitions" AfterTargets="CoreClean">
Expand Down
Loading
Loading