Skip to content

Commit

Permalink
feat(vite-plugin-angular): support let with getters (#838)
Browse files Browse the repository at this point in the history
  • Loading branch information
nartc committed Jan 5, 2024
1 parent 3bf3384 commit 24d9a86
Show file tree
Hide file tree
Showing 6 changed files with 134 additions and 41 deletions.
18 changes: 18 additions & 0 deletions apps/ng-app/src/app/another-one.ng
Original file line number Diff line number Diff line change
@@ -1,6 +1,17 @@
<script lang="ts">
import { ChangeDetectorRef, inject } from '@angular/core';

const cdr = inject(ChangeDetectorRef);

let lateVariable: string;

onInit(() => {
console.log('anotherOne init');
setTimeout(() => {
lateVariable = 'anotherOne';
cdr.detectChanges();
console.log(this);
}, 2000);
});

onDestroy(() => {
Expand All @@ -10,10 +21,17 @@

<template>
<h1>DJ KHALED!!!</h1>
@if (lateVariable) {
<h2>Late Variable here: {{ lateVariable }}</h2>
}
</template>

<style>
h1 {
color: green;
}

h2 {
color: tomato;
}
</style>
4 changes: 3 additions & 1 deletion apps/ng-app/src/app/app.component.ng
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@
import { JsonPipe } from '@angular/common';
import { delay } from 'rxjs';
import Highlight from './highlight.ng';
import { HelloOriginal } from './hello';

defineMetadata({
selector: 'app-root',
imports: [JsonPipe],
imports: [JsonPipe, HelloOriginal],
exposes: [Math],
});

Expand Down Expand Up @@ -49,6 +50,7 @@
@if (counter() > 5) {
<Hello />
<AnotherOne />
<app-hello-original />
}

<p>Counter: {{ counter() }}</p>
Expand Down
15 changes: 15 additions & 0 deletions apps/ng-app/src/app/hello.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Component } from '@angular/core';
import Hello from './hello.ng';

@Component({
selector: 'app-hello-original',
standalone: true,
template: `
<p>I'm a boring hello</p>
<p>Below me is a cool hello though</p>
<Hello />
`,
imports: [Hello],
})
// eslint-disable-next-line @angular-eslint/component-class-suffix
export class HelloOriginal {}
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,26 @@ import { signal } from "@angular/core";
<p>{{ counter() }}</p>
<p>{ a }</p>
<p>{ b }</p>
<p>{ c }</p>\`,
<p>{ c }</p>
<p>{{ test }}</p>\`,
imports: []
})
export default class AnalogNgEntity {
constructor() {
let test: string;
setTimeout(() => {
test = 'test';
}, 1000)
const counter = signal(0);
this.counter = counter;
const [a, b, , c = 4] = [1, 2, 3];
this.a = a;
this.b = b;
this.c = c;
Object.defineProperties(this, {
test: { get() { return test; } },
});
}
protected Math = Math;
Expand Down
7 changes: 7 additions & 0 deletions packages/vite-plugin-angular/src/lib/authoring/ng.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ defineMetadata({
exposes: [Math]
});
let test: string;
setTimeout(() => {
test = 'test';
}, 1000)
const counter = signal(0);
const [a, b, , c = 4] = [1, 2, 3];
</script>
Expand All @@ -18,6 +24,7 @@ const [a, b, , c = 4] = [1, 2, 3];
<p>{ a }</p>
<p>{ b }</p>
<p>{ c }</p>
<p>{{ test }}</p>
</template>
<style>
Expand Down
120 changes: 81 additions & 39 deletions packages/vite-plugin-angular/src/lib/authoring/ng.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
Scope,
StructureKind,
SyntaxKind,
VariableDeclarationKind,
} from 'ts-morph';

const SCRIPT_TAG_REGEX = /<script lang="ts">([\s\S]*?)<\/script>/i;
Expand Down Expand Up @@ -130,6 +131,7 @@ function processNgScript(
}

const declarations: string[] = [];
const getters: Array<{ propertyName: string; isFunction: boolean }> = [];

ngSourceFile.forEachChild((node) => {
// for ImportDeclaration (e.g: import ... from ...)
Expand All @@ -144,14 +146,21 @@ function processNgScript(
targetSourceFile.addImportDeclaration(node.getStructure());
}

// for VariableStatement (e.g: const ... = ...)
// for VariableStatement (e.g: const ... = ..., let ... = ...)
if (Node.isVariableStatement(node)) {
// NOTE: we do not support multiple declarations (i.e: const a, b, c)
const declarations = node.getDeclarations();
const declaration = declarations[0];
const isLet = node.getDeclarationKind() === VariableDeclarationKind.Let;
const initializer = declaration.getInitializer();

if (initializer) {
if (!initializer && isLet) {
targetConstructor.addStatements(node.getText());
getters.push({
propertyName: declaration.getName(),
isFunction: false,
});
} else if (initializer) {
const declarationNameNode = declaration.getNameNode();

// destructures
Expand All @@ -161,29 +170,41 @@ function processNgScript(
) {
targetConstructor.addStatements(node.getText());

const bindingElements = declarationNameNode.getDescendantsOfKind(
SyntaxKind.BindingElement
);
const bindingElements = declarationNameNode
.getDescendantsOfKind(SyntaxKind.BindingElement)
.map((bindingElement) => bindingElement.getName());

for (const bindingElement of bindingElements) {
const bindingElementName = bindingElement.getName();
targetClass.addProperty({
name: bindingElementName,
kind: StructureKind.Property,
scope: Scope.Protected,
});
targetConstructor.addStatements(
`this.${bindingElementName} = ${bindingElementName};`
if (isLet) {
getters.push(
...bindingElements.map((bindingElement) => ({
propertyName: bindingElement,
isFunction: false,
}))
);
} else {
for (const bindingElement of bindingElements) {
targetClass.addProperty({
name: bindingElement,
kind: StructureKind.Property,
scope: Scope.Protected,
});
targetConstructor.addStatements(
`this.${bindingElement} = ${bindingElement};`
);
}
}
} else {
addPropertyToClass(
targetClass,
targetConstructor,
declaration.getName(),
initializer,
() => {
isLet,
(isFunction, propertyName) => {
targetConstructor.addStatements(node.getText());
if (isLet) {
getters.push({ propertyName, isFunction });
}
}
);
}
Expand All @@ -196,13 +217,14 @@ function processNgScript(
targetConstructor,
node.getName() || '',
node,
(propertyName, propertyInitializer) => {
false,
(_, propertyName) => {
targetConstructor.addFunction({
name: propertyName,
parameters: propertyInitializer
parameters: node
.getParameters()
.map((parameter) => parameter.getStructure()),
statements: propertyInitializer
statements: node
.getStatements()
.map((statement) => statement.getText()),
});
Expand All @@ -227,10 +249,11 @@ function processNgScript(
targetConstructor,
functionName,
initFunction,
(propertyName, propertyInitializer) => {
false,
(_isFunction, propertyName) => {
targetConstructor.addFunction({
name: propertyName,
statements: propertyInitializer
statements: initFunction
.getStatements()
.map((statement) => statement.getText()),
});
Expand Down Expand Up @@ -264,6 +287,20 @@ function processNgScript(
}
}

if (getters.length > 0) {
targetConstructor.addStatements(`
Object.defineProperties(this, {
${getters
.map(
({ isFunction, propertyName }) =>
`${propertyName}:{get(){return ${
!isFunction ? `${propertyName}` : `${propertyName}.bind(this)`
};}},`
)
.join('\n')}
});`);
}

if (!isProd) {
// PROD probably does not need this
targetSourceFile.formatText({ ensureNewLineAtEndOfFile: true });
Expand Down Expand Up @@ -323,26 +360,31 @@ function addPropertyToClass<
targetConstructor: ConstructorDeclaration,
propertyName: string,
propertyInitializer: TInitializer,
constructorUpdater: (
propertyName: string,
propertyInitializer: TInitializer
) => void
isLet: boolean,
constructorUpdater: (isFunction: boolean, propertyName: string) => void
) {
// add the empty property the class (e.g: protected propertyName;)
targetClass.addProperty({
name: propertyName,
kind: StructureKind.Property,
scope: Scope.Protected,
});

// update the constructor
constructorUpdater(propertyName, propertyInitializer);
if (!isLet) {
// add the empty property the class (e.g: protected propertyName;)
targetClass.addProperty({
name: propertyName,
kind: StructureKind.Property,
scope: Scope.Protected,
});
}

// assign the variable to the property
targetConstructor.addStatements(
const isFunction =
Node.isArrowFunction(propertyInitializer) ||
Node.isFunctionDeclaration(propertyInitializer)
? `this.${propertyName} = ${propertyName}.bind(this);`
: `this.${propertyName} = ${propertyName};`
);
Node.isFunctionDeclaration(propertyInitializer);

// update the constructor
constructorUpdater(isFunction, propertyName);

if (!isLet) {
// assign the variable to the property
targetConstructor.addStatements(
isFunction
? `this.${propertyName} = ${propertyName}.bind(this);`
: `this.${propertyName} = ${propertyName};`
);
}
}

0 comments on commit 24d9a86

Please sign in to comment.