Skip to content

Commit

Permalink
Improve module typedefs and loader generation (#265)
Browse files Browse the repository at this point in the history
  • Loading branch information
jasongin authored Apr 8, 2024
1 parent 53328f5 commit 348c8fd
Show file tree
Hide file tree
Showing 98 changed files with 831 additions and 189 deletions.
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

0 comments on commit 348c8fd

Please sign in to comment.