Skip to content

Commit b0e115f

Browse files
authored
Specify target framework in generated assembly loader script (#394)
1 parent 5fb0a61 commit b0e115f

File tree

26 files changed

+100
-193
lines changed

26 files changed

+100
-193
lines changed

docs/reference/msbuild-props.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ The following properties can be used to customize the build processes for genera
99
| `NodeApiTypeDefinitionsFileName` | Name of the type-definitions file generated for a project. Defaults to `$(TargetName).d.ts`. |
1010
| `NodeApiTypeDefinitionsEnableWarnings` | Set to `true` to enable warnings when [generating type definitions](../features/type-definitions). The warnings are suppressed by default because they can be verbose when referencing system or external assemblies.
1111
| `NodeApiJSModuleType` | Set to either `commonjs` or `esm` to specify the module system used by the generated type definitions. If unspecified, the module type is detected automatically from `package.json`, which is usually correct. |
12+
| `NodeApiTargetFramework` | Target framework moniker that will be loaded at runtime for the Node API .NET host. Default is the project's `$(TargetFramework)`.
1213
| `NodeApiSystemReferenceAssembly` | Item-list of assembly names (not file paths) to be included in typedefs generator. The `System`, `System.Runtime`, and `System.Console` assemblies are included by default. Add system assembly names to the item-list to generate type definitions for them. System assemblies are provided by the installed .NET SDK. |
1314
| `PublishNodeModule` | Set to `true` to produce a Native AOT `.node` binary and `.js` module-loader script when building the `Publish` target. The files will be placed in the directory indicated by the `PublishDir` variable. See [Develop a Node.js addon module with .NET Native AOT](../scenarios/js-aot-module). |
1415
| `PublishMultiPlatformNodeModule` | If `true`, the published `.node` binary file will be placed in a sub-directory according to the targeted [`RuntimeIdentifier`](https://learn.microsoft.com/en-us/dotnet/core/project-sdk/msbuild-props#runtimeidentifier), for example `win-x64`. Then the publish process may be run spearately for multiple runtime-identifiers, and the module-loader script chooses the approrpriate one at runtime. |

src/NodeApi.Generator/NodeApi.Generator.targets

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@
2020
<NodeApiJSModuleType Condition=" '$(NodeApiJSModuleType)' == '' AND Exists('$(NodeApiPackageJson)') ">&quot;$(NodeApiPackageJson)&quot;</NodeApiJSModuleType>
2121
<NodeApiJSModuleType Condition=" '$(NodeApiJSModuleType)' == '' ">commonjs,esm</NodeApiJSModuleType>
2222

23-
<NodeApiTypeDefinitionsGeneratorOptions>--module $(NodeApiJSModuleType) --framework $(TargetFramework) $(NodeApiTypedefsGeneratorOptions)</NodeApiTypeDefinitionsGeneratorOptions>
23+
<NodeApiTargetFramework Condition=" '$(NodeApiTargetFramework)' == '' ">$(TargetFramework)</NodeApiTargetFramework>
24+
25+
<NodeApiTypeDefinitionsGeneratorOptions>--module $(NodeApiJSModuleType) --framework $(NodeApiTargetFramework) $(NodeApiTypedefsGeneratorOptions)</NodeApiTypeDefinitionsGeneratorOptions>
2426
</PropertyGroup>
2527

2628
<!--

src/NodeApi.Generator/Program.cs

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ public static class Program
3232
{
3333
private const char PathSeparator = ';';
3434

35+
private static string? s_targetFramework;
3536
private static readonly List<string> s_assemblyPaths = new();
3637
private static readonly List<string> s_referenceAssemblyDirectories = new();
3738
private static readonly List<string> s_referenceAssemblyPaths = new();
@@ -49,7 +50,7 @@ public static int Main(string[] args)
4950
Console.WriteLine("""
5051
Usage: node-api-dotnet-generator [options...]
5152
-a --asssembly Path to input assembly (required)
52-
-f --framework Target framework of system assemblies (optional)
53+
-f --framework .NET target framework moniker (optional)
5354
-p --pack Targeting pack (optional, multiple)
5455
-r --reference Path to reference assembly (optional, multiple)
5556
-t --typedefs Path to output type definitions file (required)
@@ -105,6 +106,7 @@ public static int Main(string[] args)
105106
s_referenceAssemblyDirectories,
106107
s_typeDefinitionsPaths[i],
107108
modulePaths,
109+
s_targetFramework,
108110
isSystemAssembly: s_systemAssemblyIndexes.Contains(i),
109111
s_suppressWarnings);
110112
}
@@ -119,7 +121,6 @@ private static bool ParseArgs(string[] args)
119121
return false;
120122
}
121123

122-
string? targetFramework = null;
123124
List<string> targetingPacks = new();
124125

125126
for (int i = 0; i < args.Length; i++)
@@ -149,7 +150,7 @@ void AddItems(List<string> list, string items)
149150

150151
case "-f":
151152
case "--framework":
152-
targetFramework = args[++i];
153+
s_targetFramework = args[++i];
153154
break;
154155

155156
case "-p":
@@ -220,7 +221,7 @@ void AddItems(List<string> list, string items)
220221
}
221222
}
222223

223-
ResolveSystemAssemblies(targetFramework, targetingPacks);
224+
ResolveSystemAssemblies(targetingPacks);
224225

225226
bool HasAssemblyExtension(string fileName) =>
226227
fileName.EndsWith(".dll", StringComparison.OrdinalIgnoreCase) ||
@@ -396,20 +397,19 @@ private static IEnumerable<string> SplitWithQuotes(string line)
396397
}
397398

398399
private static void ResolveSystemAssemblies(
399-
string? targetFramework,
400400
List<string> targetingPacks)
401401
{
402-
if (targetFramework == null)
402+
if (s_targetFramework == null)
403403
{
404-
targetFramework = GetCurrentFrameworkTarget();
404+
s_targetFramework = GetCurrentFrameworkTarget();
405405
}
406-
else if (targetFramework.Contains('-'))
406+
else if (s_targetFramework.Contains('-'))
407407
{
408408
// Strip off a platform suffix from a target framework like "net6.0-windows".
409-
targetFramework = targetFramework.Substring(0, targetFramework.IndexOf('-'));
409+
s_targetFramework = s_targetFramework.Substring(0, s_targetFramework.IndexOf('-'));
410410
}
411411

412-
if (targetFramework.StartsWith("net4"))
412+
if (s_targetFramework.StartsWith("net4"))
413413
{
414414
if (targetingPacks.Count > 0)
415415
{
@@ -422,7 +422,7 @@ private static void ResolveSystemAssemblies(
422422
"Microsoft",
423423
"Framework",
424424
".NETFramework",
425-
"v" + string.Join(".", targetFramework.Substring(3).ToArray())); // v4.7.2
425+
"v" + string.Join(".", s_targetFramework.Substring(3).ToArray())); // v4.7.2
426426
if (Directory.Exists(refAssemblyDirectory))
427427
{
428428
s_referenceAssemblyDirectories.Add(refAssemblyDirectory);
@@ -461,7 +461,7 @@ private static void ResolveSystemAssemblies(
461461
{
462462
string? refAssemblyDirectory = Directory.GetDirectories(targetPackDirectory)
463463
.OrderByDescending((d) => Path.GetFileName(d))
464-
.Select((d) => Path.Combine(d, "ref", targetFramework))
464+
.Select((d) => Path.Combine(d, "ref", s_targetFramework))
465465
.FirstOrDefault(Directory.Exists);
466466
if (refAssemblyDirectory != null)
467467
{

src/NodeApi.Generator/TypeDefinitionsGenerator.cs

Lines changed: 82 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,13 @@ public enum ModuleType
4646
ES,
4747
}
4848

49+
private enum AssemblyType
50+
{
51+
JSModule,
52+
ApplicationAssembly,
53+
SystemAssembly,
54+
}
55+
4956
/// <summary>
5057
/// JavaScript (not TypeScript) code that is emitted to a `.js` file alongside the `.d.ts`.
5158
/// Enables application code to load an assembly (containing explicit JS exports) as an ES
@@ -58,7 +65,7 @@ public enum ModuleType
5865
/// An MSBuild task during the AOT publish process sets the `dotnet` variable to undefined.
5966
/// </remarks>
6067
private const string LoadModuleMJS = @"
61-
import dotnet from 'node-api-dotnet';
68+
import dotnet from 'node-api-dotnet/$(TargetFramework)';
6269
import * as path from 'node:path';
6370
import { fileURLToPath } from 'node:url';
6471
// @ts-ignore - https://github.com/DefinitelyTyped/DefinitelyTyped/discussions/65252
@@ -92,7 +99,7 @@ function importAotModule(moduleName) {
9299
/// An MSBuild task during the AOT publish process sets the `dotnet` variable to undefined.
93100
/// </remarks>
94101
private const string LoadModuleCJS = @"
95-
const dotnet = require('node-api-dotnet');
102+
const dotnet = require('node-api-dotnet/$(TargetFramework)');
96103
const path = require('node:path');
97104
// @ts-ignore - https://github.com/DefinitelyTyped/DefinitelyTyped/discussions/65252
98105
const { dlopen, platform, arch } = require('node:process');
@@ -126,7 +133,7 @@ function importAotModule(moduleName) {
126133
/// they are equivalent to those predefined values defined for CommonJS modules.
127134
/// </remarks>
128135
private const string LoadAssemblyMJS = @"
129-
import dotnet from 'node-api-dotnet';
136+
import dotnet from 'node-api-dotnet/$(TargetFramework)';
130137
import * as path from 'node:path';
131138
import { fileURLToPath } from 'node:url';
132139
@@ -143,7 +150,7 @@ function importAotModule(moduleName) {
143150
/// the node-api-dotnet module.
144151
/// </summary>
145152
private const string LoadAssemblyCJS = @"
146-
const dotnet = require('node-api-dotnet');
153+
const dotnet = require('node-api-dotnet/$(TargetFramework)');
147154
const path = require('node:path');
148155
149156
const assemblyName = path.basename(__filename, __filename.match(/(\.[cm]?js)?$/)[0]);
@@ -157,7 +164,7 @@ function importAotModule(moduleName) {
157164
/// the node-api-dotnet module.
158165
/// </summary>
159166
private const string LoadSystemAssemblyMJS = @"
160-
import dotnet from 'node-api-dotnet';
167+
import dotnet from 'node-api-dotnet/$(TargetFramework)';
161168
import * as path from 'node:path';
162169
import { fileURLToPath } from 'node:url';
163170
@@ -172,7 +179,7 @@ function importAotModule(moduleName) {
172179
/// it augments the node-api-dotnet module.
173180
/// </summary>
174181
private const string LoadSystemAssemblyCJS = @"
175-
const dotnet = require('node-api-dotnet');
182+
const dotnet = require('node-api-dotnet/$(TargetFramework)');
176183
const path = require('node:path');
177184
178185
const assemblyName = path.basename(__filename, __filename.match(/(\.[cm]?js)?$/)[0]);
@@ -209,6 +216,7 @@ public static void GenerateTypeDefinitions(
209216
IEnumerable<string> systemReferenceAssemblyDirectories,
210217
string typeDefinitionsPath,
211218
IDictionary<ModuleType, string> modulePaths,
219+
string? targetFramework = null,
212220
bool isSystemAssembly = false,
213221
bool suppressWarnings = false)
214222
{
@@ -286,7 +294,7 @@ public static void GenerateTypeDefinitions(
286294
string moduleFilePath = moduleTypeAndPath.Value;
287295

288296
SourceText generatedModule = generator.GenerateModuleLoader(
289-
moduleType, isSystemAssembly);
297+
moduleType, targetFramework, isSystemAssembly);
290298
File.WriteAllText(moduleFilePath, generatedModule.ToString());
291299
}
292300
}
@@ -466,82 +474,94 @@ public SourceText GenerateTypeDefinitions(bool? autoCamelCase = null)
466474
return s;
467475
}
468476

469-
public SourceText GenerateModuleLoader(ModuleType moduleType, bool isSystemAssembly = false)
477+
public SourceText GenerateModuleLoader(
478+
ModuleType moduleType,
479+
string? targetFramework,
480+
bool isSystemAssembly = false)
470481
{
471482
var s = new SourceBuilder();
472483
s += GetGeneratedFileHeader();
473484

474-
if (_isModule)
485+
s += GetLoaderJS(
486+
moduleType,
487+
_isModule ? AssemblyType.JSModule :
488+
isSystemAssembly ? AssemblyType.SystemAssembly : AssemblyType.ApplicationAssembly,
489+
targetFramework);
490+
491+
if (_isModule && moduleType == ModuleType.ES)
475492
{
476-
if (moduleType == ModuleType.ES)
493+
// Declare ES module exports.
494+
495+
bool isFirstMember = true;
496+
bool hasDefaultExport = false;
497+
foreach (MemberInfo member in _exportedMembers)
477498
{
478-
s += LoadModuleMJS.Replace(" ", ""); // The SourceBuilder will auto-indent.
499+
string exportName = GetExportName(member);
479500

480-
bool isFirstMember = true;
481-
bool hasDefaultExport = false;
482-
foreach (MemberInfo member in _exportedMembers)
501+
if (member is PropertyInfo exportedProperty &&
502+
exportedProperty.SetMethod != null)
483503
{
484-
string exportName = GetExportName(member);
485-
486-
if (member is PropertyInfo exportedProperty &&
487-
exportedProperty.SetMethod != null)
488-
{
489-
ReportWarning(
490-
DiagnosticId.ESModulePropertiesAreConst,
491-
$"Module-level property '{exportName}' with setter will be " +
492-
"exported as read-only because ES module properties are constant.");
493-
}
504+
ReportWarning(
505+
DiagnosticId.ESModulePropertiesAreConst,
506+
$"Module-level property '{exportName}' with setter will be " +
507+
"exported as read-only because ES module properties are constant.");
508+
}
494509

495-
if (exportName == "default")
510+
if (exportName == "default")
511+
{
512+
hasDefaultExport = true;
513+
}
514+
else
515+
{
516+
if (isFirstMember)
496517
{
497-
hasDefaultExport = true;
518+
s++;
519+
isFirstMember = false;
498520
}
499-
else
500-
{
501-
if (isFirstMember)
502-
{
503-
s++;
504-
isFirstMember = false;
505-
}
506521

507-
s += $"export const {exportName} = exports.{exportName};";
508-
}
509-
}
510-
511-
if (hasDefaultExport)
512-
{
513-
s++;
514-
s += $"export default exports['default'];";
522+
s += $"export const {exportName} = exports.{exportName};";
515523
}
516524
}
517-
else if (moduleType == ModuleType.CommonJS)
518-
{
519-
s += LoadModuleCJS.Replace(" ", ""); // The SourceBuilder will auto-indent.
520-
}
521-
else
525+
526+
if (hasDefaultExport)
522527
{
523-
throw new ArgumentException(
524-
"Invalid module type: " + moduleType, nameof(moduleType));
528+
s++;
529+
s += $"export default exports['default'];";
525530
}
526531
}
532+
533+
return s;
534+
}
535+
536+
private static string GetLoaderJS(
537+
ModuleType moduleType,
538+
AssemblyType assemblyType,
539+
string? targetFramework)
540+
{
541+
string loaderJS = (moduleType, assemblyType) switch
542+
{
543+
(ModuleType.CommonJS, AssemblyType.JSModule) => LoadModuleCJS,
544+
(ModuleType.ES, AssemblyType.JSModule) => LoadModuleMJS,
545+
(ModuleType.CommonJS, AssemblyType.ApplicationAssembly) => LoadAssemblyCJS,
546+
(ModuleType.ES, AssemblyType.ApplicationAssembly) => LoadAssemblyMJS,
547+
(ModuleType.CommonJS, AssemblyType.SystemAssembly) => LoadSystemAssemblyCJS,
548+
(ModuleType.ES, AssemblyType.SystemAssembly) => LoadSystemAssemblyMJS,
549+
_ => throw new ArgumentException(
550+
"Invalid module type: " + moduleType, nameof(moduleType)),
551+
};
552+
553+
loaderJS = loaderJS.Replace(" ", ""); // The SourceBuilder will auto-indent.
554+
555+
if (string.IsNullOrEmpty(targetFramework))
556+
{
557+
loaderJS = loaderJS.Replace("/$(TargetFramework)", string.Empty);
558+
}
527559
else
528560
{
529-
if (moduleType == ModuleType.ES)
530-
{
531-
s += isSystemAssembly ? LoadSystemAssemblyMJS : LoadAssemblyMJS;
532-
}
533-
else if (moduleType == ModuleType.CommonJS)
534-
{
535-
s += isSystemAssembly ? LoadSystemAssemblyCJS : LoadAssemblyCJS;
536-
}
537-
else
538-
{
539-
throw new ArgumentException(
540-
"Invalid module type: " + moduleType, nameof(moduleType));
541-
}
561+
loaderJS = loaderJS.Replace("$(TargetFramework)", targetFramework);
542562
}
543563

544-
return s;
564+
return loaderJS;
545565
}
546566

547567
private bool IsExported(MemberInfo member)

src/NodeApi/JSValueScope.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,6 @@ public JSValueScope(
191191
runtime ??= _parentScope?.Runtime ??
192192
throw new ArgumentNullException(nameof(runtime), "A runtime is required.");
193193

194-
_parentScope = null;
195194
_env = env;
196195
ThreadId = Environment.CurrentManagedThreadId;
197196
Runtime = runtime;

test/JSProjectTests.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ public class JSProjectTests
2323

2424
public static IEnumerable<object[]> TestCases { get; } = ListTestCases(
2525
(testCaseName) => testCaseName.StartsWith("projects/") &&
26-
IsCurrentTargetFramework(Path.GetFileName(testCaseName)));
26+
(!testCaseName.Contains("-dynamic") ||
27+
IsCurrentTargetFramework(Path.GetFileName(testCaseName))));
2728

2829
private static bool IsCurrentTargetFramework(string target)
2930
{

test/TestCases/projects/js-cjs-dynamic/default.js

Lines changed: 0 additions & 9 deletions
This file was deleted.

test/TestCases/projects/js-cjs-module/default.js

Lines changed: 0 additions & 5 deletions
This file was deleted.

test/TestCases/projects/js-cjs-module/net472.js

Lines changed: 0 additions & 5 deletions
This file was deleted.

test/TestCases/projects/js-cjs-module/net6.0.js

Lines changed: 0 additions & 5 deletions
This file was deleted.

0 commit comments

Comments
 (0)