Skip to content

Commit c9e1eea

Browse files
Copilotanupriya13
andcommitted
Add setup-module-windows command with cpp-app template support
Co-authored-by: anupriya13 <[email protected]>
1 parent 17864b1 commit c9e1eea

File tree

7 files changed

+1121
-0
lines changed

7 files changed

+1121
-0
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"type": "prerelease",
3+
"comment": "Add setup-module-windows command with cpp-app template support",
4+
"packageName": "@react-native-windows/cli",
5+
"email": "[email protected]",
6+
"dependentChangeType": "patch"
7+
}
Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
# setup-module-windows Command
2+
3+
The `setup-module-windows` command streamlines the process of adding Windows support to React Native community modules. It requires an existing TurboModule spec file and automates the Windows project setup.
4+
5+
## Usage
6+
7+
```bash
8+
yarn react-native setup-module-windows [options]
9+
```
10+
11+
## Options
12+
13+
- `--logging`: Enable verbose output logging
14+
- `--no-telemetry`: Disable telemetry tracking
15+
- `--skip-deps`: Skip dependency upgrades (use current versions)
16+
- `--skip-build`: Skip final build verification step
17+
- `--template <template>`: Project template (cpp-lib or cpp-app), defaults to cpp-lib
18+
19+
## What it does
20+
21+
The command performs the following steps automatically:
22+
23+
1. **TurboModule Spec Validation**: Checks for existing TurboModule spec files and fails if none are found. You must create a TurboModule spec file before running this command.
24+
25+
2. **Package.json Updates**: Adds or updates the `codegenConfig` section in package.json with proper Windows codegen configuration.
26+
27+
3. **Dependency Management**:
28+
- Installs dependencies using yarn
29+
30+
4. **Windows Library Setup**: Runs `init-windows --template <template>` to create the Windows-specific project structure. Supports both `cpp-lib` (default) and `cpp-app` templates.
31+
32+
5. **Code Generation**: Runs `codegen-windows` to generate C++ spec files from TypeScript/JavaScript specs.
33+
34+
6. **C++ Stub Generation**: Creates C++ header (.h) and implementation (.cpp) stub files with:
35+
- Proper method signatures matching the TypeScript spec
36+
- Reference multiply function as commented example (from cpp-lib template)
37+
- Hello World `sayHello` function to verify module functionality
38+
- TODO comments for implementing additional methods
39+
40+
## Generated Files
41+
42+
### C++ Header (ModuleName.h)
43+
Generated with proper module structure:
44+
45+
```cpp
46+
#pragma once
47+
48+
#include "pch.h"
49+
#include "resource.h"
50+
51+
#include "codegen/NativeModuleNameSpec.g.h"
52+
#include "NativeModules.h"
53+
54+
namespace winrt::ModuleNameSpecs
55+
{
56+
57+
REACT_MODULE(ModuleName)
58+
struct ModuleName
59+
{
60+
using ModuleSpec = ModuleNameCodegen::ModuleNameSpec;
61+
62+
REACT_INIT(Initialize)
63+
void Initialize(React::ReactContext const &reactContext) noexcept;
64+
65+
// Reference function for demonstration (from cpp-lib template)
66+
// double multiply(double a, double b) noexcept { return a * b; }
67+
68+
// Hello World example to verify module functionality
69+
REACT_METHOD(sayHello)
70+
void sayHello(std::string name, std::function<void(std::string)> const & callback) noexcept;
71+
72+
// TODO: Add your method implementations here
73+
74+
private:
75+
React::ReactContext m_context;
76+
};
77+
78+
} // namespace winrt::ModuleNameSpecs
79+
```
80+
81+
### C++ Implementation (ModuleName.cpp)
82+
Generated with method stubs including working Hello World example:
83+
84+
```cpp
85+
#include "ModuleName.h"
86+
87+
namespace winrt::ModuleNameSpecs {
88+
89+
void ModuleName::Initialize(React::ReactContext const &reactContext) noexcept {
90+
m_context = reactContext;
91+
}
92+
93+
void ModuleName::sayHello(std::string name, std::function<void(std::string)> const & callback) noexcept {
94+
std::string result = "Hello " + name + "! Module is working.";
95+
callback(result);
96+
}
97+
98+
// TODO: Implement your methods here
99+
100+
} // namespace winrt::ModuleNameSpecs
101+
```
102+
103+
## Package.json Configuration
104+
105+
The command adds this configuration to your package.json:
106+
107+
```json
108+
{
109+
"codegenConfig": {
110+
"name": "ModuleName",
111+
"type": "modules",
112+
"jsSrcsDir": ".",
113+
"windows": {
114+
"namespace": "ModuleNameSpecs",
115+
"outputDirectory": "codegen",
116+
"generators": ["modulesWindows"]
117+
}
118+
}
119+
}
120+
```
121+
122+
## Prerequisites
123+
124+
1. **TurboModule Spec File**: You must create a TurboModule spec file before running this command. The command will fail with an error if no spec file is found.
125+
126+
Example spec file (NativeModuleName.ts):
127+
```typescript
128+
import type {TurboModule} from 'react-native/Libraries/TurboModule/RCTExport';
129+
import {TurboModuleRegistry} from 'react-native';
130+
131+
export interface Spec extends TurboModule {
132+
getString(value: string): Promise<string>;
133+
getNumber(value: number): Promise<number>;
134+
}
135+
136+
export default TurboModuleRegistry.getEnforcing<Spec>('ModuleName');
137+
```
138+
139+
## Template Options
140+
141+
### cpp-lib (Default)
142+
- Creates a library project structure
143+
- Best for native modules that provide functionality to other apps
144+
- Generates files in `windows/ModuleName/` directory
145+
146+
### cpp-app
147+
- Creates an application project structure
148+
- Best for standalone applications with Windows-specific UI
149+
- Generates files in `windows/` directory with app structure
150+
151+
## Next Steps
152+
153+
After running the command:
154+
155+
1. **Implement the methods** in the generated C++ stub files - The Hello World function is already implemented as an example
156+
2. **Build your project** to verify everything works correctly
157+
3. **Add any additional methods** to your TurboModule spec and C++ implementation
158+
159+
## Examples
160+
161+
Basic usage with default cpp-lib template:
162+
```bash
163+
yarn react-native setup-module-windows
164+
```
165+
166+
With cpp-app template:
167+
```bash
168+
yarn react-native setup-module-windows --template cpp-app
169+
```
170+
171+
With verbose logging:
172+
```bash
173+
yarn react-native setup-module-windows --logging
174+
```
175+
176+
Skip dependency upgrades:
177+
```bash
178+
yarn react-native setup-module-windows --skip-deps
179+
```
180+
181+
Skip build verification:
182+
```bash
183+
yarn react-native setup-module-windows --skip-build
184+
```
185+
186+
## Error Messages
187+
188+
**"Create Spec File - TurboModule spec file not found"**: You need to create a TurboModule spec file before running this command. The command will not automatically create spec files.
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
/**
2+
* Copyright (c) Microsoft Corporation.
3+
* Licensed under the MIT License.
4+
* @format
5+
*/
6+
7+
import {SetupModuleWindows} from '../setupModuleWindows';
8+
import type {SetupModuleWindowsOptions} from '../setupModuleWindowsOptions';
9+
10+
// Mock dependencies
11+
jest.mock('@react-native-windows/fs');
12+
jest.mock('glob');
13+
jest.mock('child_process');
14+
15+
describe('SetupModuleWindows', () => {
16+
const mockOptions: SetupModuleWindowsOptions = {
17+
logging: false,
18+
telemetry: false,
19+
skipDeps: false,
20+
skipBuild: false,
21+
template: 'cpp-lib',
22+
};
23+
24+
beforeEach(() => {
25+
jest.clearAllMocks();
26+
});
27+
28+
describe('getModuleName', () => {
29+
it('should convert package names to PascalCase', () => {
30+
const setup = new SetupModuleWindows('/test', mockOptions);
31+
// Access private method for testing
32+
const getModuleName = (setup as any).getModuleName.bind(setup);
33+
34+
expect(getModuleName('react-native-webview')).toBe('ReactNativeWebview');
35+
expect(getModuleName('@react-native-community/slider')).toBe(
36+
'ReactNativeCommunitySlider',
37+
);
38+
expect(getModuleName('simple-module')).toBe('SimpleModule');
39+
expect(getModuleName('@scope/complex-package-name')).toBe(
40+
'ScopeComplexPackageName',
41+
);
42+
});
43+
44+
it('should handle edge cases', () => {
45+
const setup = new SetupModuleWindows('/test', mockOptions);
46+
const getModuleName = (setup as any).getModuleName.bind(setup);
47+
48+
expect(getModuleName('')).toBe('');
49+
expect(getModuleName('single')).toBe('Single');
50+
expect(getModuleName('---multiple---dashes---')).toBe('MultipleDashes');
51+
});
52+
});
53+
54+
describe('constructor', () => {
55+
it('should create instance with correct root and options', () => {
56+
const root = '/test/project';
57+
const options = {
58+
logging: true,
59+
telemetry: true,
60+
skipDeps: true,
61+
skipBuild: true,
62+
template: 'cpp-app' as const,
63+
};
64+
65+
const setup = new SetupModuleWindows(root, options);
66+
67+
expect(setup.root).toBe(root);
68+
expect(setup.options).toBe(options);
69+
});
70+
});
71+
72+
describe('verboseMessage', () => {
73+
it('should log when logging is enabled', () => {
74+
const consoleSpy = jest.spyOn(console, 'log').mockImplementation();
75+
const setup = new SetupModuleWindows('/test', {logging: true});
76+
77+
(setup as any).verboseMessage('test message');
78+
79+
expect(consoleSpy).toHaveBeenCalledWith(
80+
'[SetupModuleWindows] test message',
81+
);
82+
consoleSpy.mockRestore();
83+
});
84+
85+
it('should not log when logging is disabled', () => {
86+
const consoleSpy = jest.spyOn(console, 'log').mockImplementation();
87+
const setup = new SetupModuleWindows('/test', {logging: false});
88+
89+
(setup as any).verboseMessage('test message');
90+
91+
expect(consoleSpy).not.toHaveBeenCalled();
92+
consoleSpy.mockRestore();
93+
});
94+
});
95+
96+
describe('template option', () => {
97+
it('should use cpp-lib as default template', () => {
98+
const setup = new SetupModuleWindows('/test', {});
99+
expect(setup.options.template).toBeUndefined(); // Will default to cpp-lib in implementation
100+
});
101+
102+
it('should accept cpp-app template', () => {
103+
const setup = new SetupModuleWindows('/test', {template: 'cpp-app'});
104+
expect(setup.options.template).toBe('cpp-app');
105+
});
106+
107+
it('should accept cpp-lib template', () => {
108+
const setup = new SetupModuleWindows('/test', {template: 'cpp-lib'});
109+
expect(setup.options.template).toBe('cpp-lib');
110+
});
111+
});
112+
113+
describe('spec file validation', () => {
114+
const fs = require('@react-native-windows/fs');
115+
116+
beforeEach(() => {
117+
jest.clearAllMocks();
118+
});
119+
120+
it('should throw error if no spec file exists', async () => {
121+
const glob = require('glob');
122+
glob.sync.mockReturnValue([]);
123+
124+
const setup = new SetupModuleWindows('/test', {logging: true});
125+
126+
await expect((setup as any).checkForExistingSpec()).rejects.toThrow(
127+
'Create Spec File - TurboModule spec file not found. Please create a TurboModule spec file before running setup-module-windows.'
128+
);
129+
});
130+
131+
it('should accept valid TurboModule spec files', async () => {
132+
const glob = require('glob');
133+
glob.sync.mockReturnValue(['NativeTestModule.ts']);
134+
135+
fs.exists.mockResolvedValue(true);
136+
fs.readFile.mockResolvedValue(`
137+
import type {TurboModule} from 'react-native/Libraries/TurboModule/RCTExport';
138+
import {TurboModuleRegistry} from 'react-native';
139+
140+
export interface Spec extends TurboModule {
141+
getString(): Promise<string>;
142+
}
143+
144+
export default TurboModuleRegistry.getEnforcing<Spec>('TestModule');
145+
`);
146+
147+
const setup = new SetupModuleWindows('/test', {logging: true});
148+
149+
await expect((setup as any).checkForExistingSpec()).resolves.toBeUndefined();
150+
});
151+
});
152+
});
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/**
2+
* Copyright (c) Microsoft Corporation.
3+
* Licensed under the MIT License.
4+
* @format
5+
*/
6+
7+
export {
8+
setupModuleWindowsCommand,
9+
setupModuleWindowsInternal,
10+
} from './setupModuleWindows';
11+
export type {SetupModuleWindowsOptions} from './setupModuleWindowsOptions';

0 commit comments

Comments
 (0)