Skip to content

Commit 71a6df6

Browse files
authored
support JS dynamic imports (#24)
* support JS dynamic imports * changelog
1 parent 894ae57 commit 71a6df6

File tree

4 files changed

+295
-55
lines changed

4 files changed

+295
-55
lines changed

README.md

+33-31
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ $ napi ui
7070
```
7171

7272
### Commands:
73+
7374
```
7475
init Initialize the NanoAPI project
7576
ui [entrypoint] Inspect the codebase and understand the API endpoints, middleware, and other API-specific code
@@ -87,43 +88,43 @@ You can also refactor your codebase by adding annotations to your code. Here is
8788
```typescript
8889
// src/api.js
8990

90-
import * as express from 'express';
91+
import * as express from "express";
9192

92-
import * as service from './service.js';
93+
import * as service from "./service.js";
9394

9495
const app = express();
9596
const port = 3000;
9697

9798
app.use(express.json());
9899

99100
// @nanoapi endpoint:/api/v2/maths/time method:GET group:Time
100-
app.get('/api/v2/maths/time', (req, res) => {
101-
const result = service.time();
102-
return res.json({
103-
result
104-
});
101+
app.get("/api/v2/maths/time", (req, res) => {
102+
const result = service.time();
103+
return res.json({
104+
result,
105+
});
105106
});
106107

107108
// @nanoapi endpoint:/api/v2/maths/addition method:POST group:Maths
108-
app.post('/api/v2/maths/addition', (req, res) => {
109-
const { body } = req;
110-
const result = service.addition(body.a, body.b);
111-
return res.json({
112-
result
113-
});
109+
app.post("/api/v2/maths/addition", (req, res) => {
110+
const { body } = req;
111+
const result = service.addition(body.a, body.b);
112+
return res.json({
113+
result,
114+
});
114115
});
115116

116117
// @nanoapi endpoint:/api/v2/maths/subtraction method:POST group:Maths
117-
app.post('/api/v2/maths/subtraction', (req, res) => {
118-
const { body } = req;
119-
const result = service.subtraction(body.a, body.b);
120-
return res.json({
121-
result
122-
});
118+
app.post("/api/v2/maths/subtraction", (req, res) => {
119+
const { body } = req;
120+
const result = service.subtraction(body.a, body.b);
121+
return res.json({
122+
result,
123+
});
123124
});
124125

125126
app.listen(port, () => {
126-
console.log(`App listening on port: ${port}`);
127+
console.log(`App listening on port: ${port}`);
127128
});
128129
```
129130

@@ -134,24 +135,24 @@ From the exmaple above, you will get the following output from running a build:
134135
```typescript
135136
// dist/Time/src/api.js
136137

137-
import * as express from 'express';
138+
import * as express from "express";
138139

139-
import * as service from './service.js';
140+
import * as service from "./service.js";
140141

141142
const app = express();
142143
const port = 3000;
143144

144145
app.use(express.json());
145146

146-
app.get('/api/v2/maths/time', (req, res) => {
147-
const result = service.time();
148-
return res.json({
149-
result
150-
});
147+
app.get("/api/v2/maths/time", (req, res) => {
148+
const result = service.time();
149+
return res.json({
150+
result,
151+
});
151152
});
152153

153154
app.listen(port, () => {
154-
console.log(`App listening on port: ${port}`);
155+
console.log(`App listening on port: ${port}`);
155156
});
156157
```
157158

@@ -163,9 +164,10 @@ We welcome contributions from the community. Please read our [contributing guide
163164

164165
This project is in the early stages of development. We are actively working on the project and will be releasing new features and improvements regularly, which may include a rewrite into a more efficient and generic language like Rust or Go. Please check our issues and project board for more information, and don't for.
165166

166-
- [x] Support for NodeJS/Typescript and ExpressJS
167+
- [x] Limited support for NodeJS/Typescript (no require or dynamic imports, no decorators)
167168
- [x] Simple UI
168-
- [ ] NestJS support
169+
- [x] Support NodeJS require and dynamic imports
170+
- [ ] Full support for NodeJS/Typescript
169171
- [ ] Python support with Flask
170172
- [ ] Django support
171173
- [ ] Full python support
@@ -181,4 +183,4 @@ NanoAPI is a fair-source project. Because of this, we feel it would be unethical
181183
- Money from the pool will be distributed to contributors
182184
- At the end of the year, any remaining money will be donated to a charity of the community's choice
183185

184-
We will post regular updates on how much money is in the pool and how it is being distributed.
186+
We will post regular updates on how much money is in the pool and how it is being distributed.

packages/cli/CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
## [Unreleased]
44

5+
Add support for dynamic import in javascript
6+
57
## [0.0.15] - 2024-10-30
68

79
Add support for require import in javascript

packages/cli/src/helper/languages/javascript/cleanup.ts

+111-2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ import {
88
extractFileImportsFromRequireDeclarations,
99
extractIdentifiersFromImportStatement,
1010
extractIdentifiersFromRequireDeclaration,
11+
getDynamicImportDeclarations,
12+
extractFileImportsFromDynamicImportDeclarations,
13+
extractIdentifiersFromDynamicImportDeclaration,
1114
} from "./imports";
1215

1316
function removeAnnotations(
@@ -32,11 +35,16 @@ function removeAnnotations(
3235

3336
if (!endpointToKeep) {
3437
const nextNode = node.nextNamedSibling;
38+
// TODO test this piece of code with decorators on a nestjs project
39+
// // We need to remove all decorators too
40+
// while (nextNode && nextNode.type === "decorator") {
41+
// nextNode = nextNode.nextNamedSibling;
42+
// }
3543
if (!nextNode) {
3644
throw new Error("Could not find next node");
3745
}
38-
// TODO support decorator. Lots of framework uses these (eg: nestjs)
39-
// delete this node (comment) and the next node (api endpoint)
46+
47+
// delete this node (comment) and the next node(s) (api endpoint)
4048
updatedSourceCode = updatedSourceCode.replace(
4149
sourceCode.substring(node.startIndex, nextNode.endIndex + 1),
4250
"",
@@ -116,6 +124,39 @@ function removeInvalidFileRequires(
116124
return { updatedSourceCode, removedIdentifiers };
117125
}
118126

127+
function removeInvalidFileDynamicImports(
128+
rootNode: Parser.SyntaxNode,
129+
sourceCode: string,
130+
invalidDependencies: string[],
131+
) {
132+
let updatedSourceCode = sourceCode;
133+
const removedIdentifiers: Parser.SyntaxNode[] = [];
134+
135+
const dynamicImportStatements = getDynamicImportDeclarations(rootNode);
136+
dynamicImportStatements.forEach((dynamicImportStatement) => {
137+
const importName = extractFileImportsFromDynamicImportDeclarations(
138+
dynamicImportStatement,
139+
);
140+
if (importName && invalidDependencies.includes(importName)) {
141+
const identifiers = extractIdentifiersFromDynamicImportDeclaration(
142+
dynamicImportStatement,
143+
);
144+
removedIdentifiers.push(...identifiers);
145+
146+
// Remove the require statement
147+
updatedSourceCode = updatedSourceCode.replace(
148+
sourceCode.substring(
149+
dynamicImportStatement.startIndex,
150+
dynamicImportStatement.endIndex,
151+
),
152+
"",
153+
);
154+
}
155+
});
156+
157+
return { updatedSourceCode, removedIdentifiers };
158+
}
159+
119160
function removeDeletedImportUsage(
120161
rootNode: Parser.SyntaxNode,
121162
sourceCode: string,
@@ -274,6 +315,55 @@ function cleanUnusedRequireDeclarations(
274315
return updatedSourceCode;
275316
}
276317

318+
function cleanUnusedDynamicImportDeclarations(
319+
rootNode: Parser.SyntaxNode,
320+
sourceCode: string,
321+
) {
322+
let updatedSourceCode = sourceCode;
323+
324+
const dynamicImportDeclarations = getDynamicImportDeclarations(rootNode);
325+
326+
dynamicImportDeclarations.forEach((dynamicImportDeclaration) => {
327+
const dynamicImportIdentifiers =
328+
extractIdentifiersFromDynamicImportDeclaration(dynamicImportDeclaration);
329+
330+
const dynamicImportIdentifiersToRemove: Parser.SyntaxNode[] = [];
331+
332+
dynamicImportIdentifiers.forEach((dynamicImportIdentifier) => {
333+
const isUsed = isIdentifierUsed(rootNode, dynamicImportIdentifier);
334+
if (!isUsed) {
335+
dynamicImportIdentifiersToRemove.push(dynamicImportIdentifier);
336+
}
337+
});
338+
339+
const removeDynamicImportDeclaration =
340+
dynamicImportIdentifiersToRemove.length ===
341+
dynamicImportIdentifiers.length;
342+
343+
if (removeDynamicImportDeclaration) {
344+
updatedSourceCode = updatedSourceCode.replace(
345+
sourceCode.substring(
346+
dynamicImportDeclaration.startIndex,
347+
dynamicImportDeclaration.endIndex + 1,
348+
),
349+
"",
350+
);
351+
}
352+
353+
dynamicImportIdentifiersToRemove.forEach((dynamicImportIdentifier) => {
354+
updatedSourceCode = updatedSourceCode.replace(
355+
sourceCode.substring(
356+
dynamicImportIdentifier.startIndex,
357+
dynamicImportIdentifier.endIndex + 1,
358+
),
359+
"",
360+
);
361+
});
362+
});
363+
364+
return updatedSourceCode;
365+
}
366+
277367
export function cleanupJavascriptFile(
278368
parser: Parser,
279369
sourceCode: string,
@@ -306,6 +396,18 @@ export function cleanupJavascriptFile(
306396

307397
tree = parser.parse(updatedSourceCode);
308398

399+
const resultAfterDynamicImportCleaning = removeInvalidFileDynamicImports(
400+
tree.rootNode,
401+
updatedSourceCode,
402+
invalidDependencies,
403+
);
404+
updatedSourceCode = resultAfterDynamicImportCleaning.updatedSourceCode;
405+
removedIdentifiers.push(
406+
...resultAfterDynamicImportCleaning.removedIdentifiers,
407+
);
408+
409+
tree = parser.parse(updatedSourceCode);
410+
309411
updatedSourceCode = removeDeletedImportUsage(
310412
tree.rootNode,
311413
updatedSourceCode,
@@ -326,5 +428,12 @@ export function cleanupJavascriptFile(
326428
updatedSourceCode,
327429
);
328430

431+
tree = parser.parse(updatedSourceCode);
432+
433+
updatedSourceCode = cleanUnusedDynamicImportDeclarations(
434+
tree.rootNode,
435+
updatedSourceCode,
436+
);
437+
329438
return updatedSourceCode;
330439
}

0 commit comments

Comments
 (0)