Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 21e5624

Browse files
committedJan 9, 2024
fix(vite-plugin-angular): adjust compiled output to be more concise
1 parent f348b4b commit 21e5624

File tree

3 files changed

+147
-116
lines changed

3 files changed

+147
-116
lines changed
 

‎packages/vite-plugin-angular/src/lib/authoring/__snapshots__/ng.spec.ts.snap

+15-11
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,15 @@ import { signal } from "@angular/core";
1414
<p>{ a }</p>
1515
<p>{ b }</p>
1616
<p>{ c }</p>
17-
<p>{{ test }}</p>\`,
18-
imports: []
17+
<p>{{ test }}</p>\`
1918
})
20-
export default class AnalogNgEntity {
19+
export default class VirtualAnalogComponent {
2120
constructor() {
22-
let test: string;
21+
let test;
2322
setTimeout(() => {
2423
test = 'test';
2524
}, 1000)
26-
const counter = signal(0);
27-
this.counter = counter;
25+
const counter = (this.counter = signal(0));
2826
const [a, b, , c = 4] = [1, 2, 3];
2927
this.a = a;
3028
this.b = b;
@@ -36,7 +34,6 @@ export default class AnalogNgEntity {
3634
}
3735
3836
protected Math = Math;
39-
protected counter;
4037
protected a;
4138
protected b;
4239
protected c;
@@ -52,16 +49,23 @@ import { inject, ElementRef, afterNextRender } from "@angular/core";
5249
standalone: true,
5350
selector: 'input[directive]'
5451
})
55-
export default class AnalogNgEntity {
52+
export default class VirtualAnalogDirective {
5653
constructor() {
57-
const elRef = inject(ElementRef);
58-
this.elRef = elRef;
54+
const elRef = (this.elRef = inject(ElementRef));
5955
afterNextRender(() => {
6056
elRef.nativeElement.focus();
6157
});
58+
this.onInit = () => {
59+
console.log('init code');
60+
}
61+
effect(() => {
62+
console.log('just some effect');
63+
});
6264
}
6365
64-
protected elRef;
66+
ngOnInit() {
67+
this.onInit();
68+
}
6569
}
6670
"
6771
`;

‎packages/vite-plugin-angular/src/lib/authoring/ng.spec.ts

+8
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,14 @@ const elRef = inject(ElementRef);
5151
afterNextRender(() => {
5252
elRef.nativeElement.focus();
5353
});
54+
55+
onInit(() => {
56+
console.log('init code');
57+
});
58+
59+
effect(() => {
60+
console.log('just some effect');
61+
});
5462
</script>
5563
`;
5664

‎packages/vite-plugin-angular/src/lib/authoring/ng.ts

+124-105
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
SyntaxKind,
1212
VariableDeclarationKind,
1313
} from 'ts-morph';
14+
import { isFunctionDeclaration } from 'typescript';
1415

1516
const SCRIPT_TAG_REGEX = /<script lang="ts">([\s\S]*?)<\/script>/i;
1617
const TEMPLATE_TAG_REGEX = /<template>([\s\S]*?)<\/template>/i;
@@ -19,19 +20,24 @@ const STYLE_TAG_REGEX = /<style>([\s\S]*?)<\/style>/i;
1920
const ON_INIT = 'onInit';
2021
const ON_DESTROY = 'onDestroy';
2122

23+
const HOOKS_MAP = {
24+
[ON_INIT]: 'ngOnInit',
25+
[ON_DESTROY]: 'ngOnDestroy',
26+
} as const;
27+
2228
export function compileNgFile(
2329
filePath: string,
2430
fileContent: string,
2531
shouldFormat = false
2632
) {
27-
// eslint-disable-next-line @typescript-eslint/no-var-requires
28-
const { names } = require('@nx/devkit');
29-
3033
const componentName = filePath.split('/').pop()?.split('.')[0];
3134
if (!componentName) {
3235
throw new Error(`[Analog] Missing component name ${filePath}`);
3336
}
3437

38+
// eslint-disable-next-line @typescript-eslint/no-var-requires
39+
const { names } = require('@nx/devkit');
40+
3541
const {
3642
fileName: componentFileName,
3743
className,
@@ -52,6 +58,7 @@ export function compileNgFile(
5258
}
5359

5460
const ngType = templateContent ? 'Component' : 'Directive';
61+
const entityName = `${className}Analog${ngType}`;
5562

5663
if (styleContent) {
5764
templateContent = `<style>${styleContent.replace(/\n/g, '')}</style>
@@ -73,7 +80,7 @@ import { ${ngType}${
7380
: ''
7481
}
7582
})
76-
export default class AnalogNgEntity {
83+
export default class ${entityName} {
7784
constructor() {}
7885
}`;
7986

@@ -83,7 +90,7 @@ export default class AnalogNgEntity {
8390
project.createSourceFile(filePath, scriptContent);
8491
project.createSourceFile(`${filePath}.virtual.ts`, source);
8592

86-
return processNgScript(filePath, project, ngType, shouldFormat);
93+
return processNgScript(filePath, project, ngType, entityName, shouldFormat);
8794
}
8895

8996
return source;
@@ -93,6 +100,7 @@ function processNgScript(
93100
fileName: string,
94101
project: Project,
95102
ngType: 'Component' | 'Directive',
103+
entityName: string,
96104
isProd?: boolean
97105
) {
98106
const ngSourceFile = project.getSourceFile(fileName);
@@ -103,7 +111,7 @@ function processNgScript(
103111
}
104112

105113
const targetClass = targetSourceFile.getClass(
106-
(classDeclaration) => classDeclaration.getName() === 'AnalogNgEntity'
114+
(classDeclaration) => classDeclaration.getName() === entityName
107115
);
108116

109117
if (!targetClass) {
@@ -146,38 +154,47 @@ function processNgScript(
146154
targetSourceFile.addImportDeclaration(node.getStructure());
147155
}
148156

157+
const nodeFullText = node.getText();
158+
149159
// for VariableStatement (e.g: const ... = ..., let ... = ...)
150160
if (Node.isVariableStatement(node)) {
151161
// NOTE: we do not support multiple declarations (i.e: const a, b, c)
152-
const declarations = node.getDeclarations();
153-
const declaration = declarations[0];
154-
const isLet = node.getDeclarationKind() === VariableDeclarationKind.Let;
155-
const initializer = declaration.getInitializer();
162+
const [declaration, isLet] = [
163+
node.getDeclarations()[0],
164+
node.getDeclarationKind() === VariableDeclarationKind.Let,
165+
];
156166

167+
const [name, initializer] = [
168+
declaration.getName(),
169+
declaration.getInitializer(),
170+
];
171+
172+
// let variable; // no initializer
157173
if (!initializer && isLet) {
158-
targetConstructor.addStatements(node.getText());
159-
getters.push({
160-
propertyName: declaration.getName(),
161-
isFunction: false,
162-
});
174+
// transfer the whole line `let variable;` over
175+
addVariableToConstructor(targetConstructor, '', name, 'let');
176+
// populate getters array for Object.defineProperties
177+
getters.push({ propertyName: name, isFunction: false });
163178
} else if (initializer) {
164-
const declarationNameNode = declaration.getNameNode();
179+
// with initializer
180+
const nameNode = declaration.getNameNode();
165181

166-
// destructures
182+
// if destructured.
183+
// TODO: we don't have a good abstraction for handling destructured variables yet.
167184
if (
168-
Node.isArrayBindingPattern(declarationNameNode) ||
169-
Node.isObjectBindingPattern(declarationNameNode)
185+
Node.isArrayBindingPattern(nameNode) ||
186+
Node.isObjectBindingPattern(nameNode)
170187
) {
171-
targetConstructor.addStatements(node.getText());
188+
targetConstructor.addStatements(nodeFullText);
172189

173-
const bindingElements = declarationNameNode
190+
const bindingElements = nameNode
174191
.getDescendantsOfKind(SyntaxKind.BindingElement)
175192
.map((bindingElement) => bindingElement.getName());
176193

177194
if (isLet) {
178195
getters.push(
179-
...bindingElements.map((bindingElement) => ({
180-
propertyName: bindingElement,
196+
...bindingElements.map((propertyName) => ({
197+
propertyName,
181198
isFunction: false,
182199
}))
183200
);
@@ -194,42 +211,52 @@ function processNgScript(
194211
}
195212
}
196213
} else {
197-
addPropertyToClass(
198-
targetClass,
199-
targetConstructor,
200-
declaration.getName(),
201-
initializer,
202-
isLet,
203-
(isFunction, propertyName) => {
204-
targetConstructor.addStatements(node.getText());
205-
if (isLet) {
206-
getters.push({ propertyName, isFunction });
207-
}
208-
}
209-
);
214+
const isFunctionInitializer = isFunction(initializer);
215+
216+
if (!isLet) {
217+
/**
218+
* normal property
219+
* const variable = initializer;
220+
* We'll create a class property with the same variable name
221+
*/
222+
addVariableToConstructor(
223+
targetConstructor,
224+
initializer.getText(),
225+
name,
226+
'const',
227+
true
228+
);
229+
} else {
230+
/**
231+
* let variable = initializer;
232+
* We will NOT create a class property with the name because we will add it to the getters
233+
*/
234+
addVariableToConstructor(
235+
targetConstructor,
236+
initializer.getText(),
237+
name,
238+
'let'
239+
);
240+
getters.push({
241+
propertyName: name,
242+
isFunction: isFunctionInitializer,
243+
});
244+
}
210245
}
211246
}
212247
}
213248

249+
// function fnName() {}
214250
if (Node.isFunctionDeclaration(node)) {
215-
addPropertyToClass(
216-
targetClass,
217-
targetConstructor,
218-
node.getName() || '',
219-
node,
220-
false,
221-
(_, propertyName) => {
222-
targetConstructor.addFunction({
223-
name: propertyName,
224-
parameters: node
225-
.getParameters()
226-
.map((parameter) => parameter.getStructure()),
227-
statements: node
228-
.getStatements()
229-
.map((statement) => statement.getText()),
230-
});
231-
}
232-
);
251+
const functionName = node.getName();
252+
if (functionName) {
253+
targetConstructor.addStatements([
254+
// bring the function over
255+
nodeFullText,
256+
// assign class property
257+
`this.${functionName} = ${functionName}.bind(this);`,
258+
]);
259+
}
233260
}
234261

235262
if (Node.isExpressionStatement(node)) {
@@ -244,45 +271,38 @@ function processNgScript(
244271
} else if (functionName === ON_INIT || functionName === ON_DESTROY) {
245272
const initFunction = expression.getArguments()[0];
246273
if (Node.isArrowFunction(initFunction)) {
247-
addPropertyToClass(
248-
targetClass,
249-
targetConstructor,
250-
functionName,
251-
initFunction,
252-
false,
253-
(_isFunction, propertyName) => {
254-
targetConstructor.addFunction({
255-
name: propertyName,
256-
statements: initFunction
257-
.getStatements()
258-
.map((statement) => statement.getText()),
259-
});
260-
261-
targetClass.addMethod({
262-
name: functionName === ON_INIT ? 'ngOnInit' : 'ngOnDestroy',
263-
statements: `this.${propertyName}();`,
264-
});
265-
}
274+
// add the function to constructor
275+
targetConstructor.addStatements(
276+
`this.${functionName} = ${initFunction.getText()}`
266277
);
278+
279+
// add life-cycle method to class
280+
targetClass.addMethod({
281+
name: HOOKS_MAP[functionName],
282+
statements: `this.${functionName}();`,
283+
});
267284
}
268285
} else {
286+
// just add the entire node to the constructor. i.e: effect()
269287
targetConstructor.addStatements(node.getText());
270288
}
271289
}
272290
}
273291
});
274292

275-
if (ngType === 'Component') {
293+
if (ngType === 'Component' && declarations.length) {
276294
const importsMetadata = targetMetadataArguments.getProperty('imports');
295+
const declarationSymbols = declarations.filter(Boolean).join(', ');
296+
277297
if (importsMetadata && Node.isPropertyAssignment(importsMetadata)) {
278298
const importsInitializer = importsMetadata.getInitializer();
279299
if (Node.isArrayLiteralExpression(importsInitializer)) {
280-
importsInitializer.addElement(declarations.filter(Boolean).join(', '));
300+
importsInitializer.addElement(declarationSymbols);
281301
}
282302
} else {
283303
targetMetadataArguments.addPropertyAssignment({
284304
name: 'imports',
285-
initializer: `[${declarations.filter(Boolean).join(', ')}]`,
305+
initializer: `[${declarationSymbols}]`,
286306
});
287307
}
288308
}
@@ -353,38 +373,37 @@ function processMetadata(
353373
});
354374
}
355375

356-
function addPropertyToClass<
357-
TInitializer extends Expression | FunctionDeclaration
358-
>(
359-
targetClass: ClassDeclaration,
376+
/**
377+
* const variable = initializer;
378+
* |
379+
* v
380+
* constructor() {
381+
* const variable = (this.variable = initializer);
382+
* }
383+
*
384+
*/
385+
function addVariableToConstructor(
360386
targetConstructor: ConstructorDeclaration,
361-
propertyName: string,
362-
propertyInitializer: TInitializer,
363-
isLet: boolean,
364-
constructorUpdater: (isFunction: boolean, propertyName: string) => void
387+
initializerText: string,
388+
variableName: string,
389+
kind: 'let' | 'const',
390+
withClassProperty = false
365391
) {
366-
if (!isLet) {
367-
// add the empty property the class (e.g: protected propertyName;)
368-
targetClass.addProperty({
369-
name: propertyName,
370-
kind: StructureKind.Property,
371-
scope: Scope.Protected,
372-
});
373-
}
374-
375-
const isFunction =
376-
Node.isArrowFunction(propertyInitializer) ||
377-
Node.isFunctionDeclaration(propertyInitializer);
392+
let statement = `${kind} ${variableName}`;
378393

379-
// update the constructor
380-
constructorUpdater(isFunction, propertyName);
394+
if (initializerText) {
395+
statement += `= ${initializerText}`;
381396

382-
if (!isLet) {
383-
// assign the variable to the property
384-
targetConstructor.addStatements(
385-
isFunction
386-
? `this.${propertyName} = ${propertyName}.bind(this);`
387-
: `this.${propertyName} = ${propertyName};`
388-
);
397+
if (withClassProperty) {
398+
statement = `${kind} ${variableName} = (this.${variableName} = ${initializerText})`;
399+
}
389400
}
401+
402+
targetConstructor.addStatements((statement += ';'));
403+
}
404+
405+
function isFunction(initializer: Node) {
406+
return (
407+
Node.isArrowFunction(initializer) || Node.isFunctionDeclaration(initializer)
408+
);
390409
}

0 commit comments

Comments
 (0)
Please sign in to comment.