Skip to content

Commit

Permalink
feat(vite-plugin-angular): add ability to export from .ng files (#856)
Browse files Browse the repository at this point in the history
  • Loading branch information
joshuamorony authored Jan 19, 2024
1 parent 5fab660 commit 633e659
Show file tree
Hide file tree
Showing 7 changed files with 173 additions and 84 deletions.
20 changes: 20 additions & 0 deletions apps/ng-app/src/app/export-stuff.ng
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<script lang="ts">
export interface MyInterface {
title: string;
}

export type MyType = string;

export enum Direction {
Up,
Down,
Left,
Right,
}

export function myFunc() {
return 'hi';
}
</script>

<template> Sharing is caring </template>
4 changes: 4 additions & 0 deletions apps/ng-app/src/app/hello.ng
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,13 @@
afterNextRender,
} from '@angular/core';

import { myFunc } from './export-stuff.ng';

defineMetadata({
queries: {
divElement: new ViewChild('divElement'),
},
exposes: [myFunc],
});

let divElement: ElementRef<HTMLDivElement>;
Expand Down Expand Up @@ -47,6 +50,7 @@
<p>Text from input: {{ text() }}</p>
<button (click)="clicked.emit($event)">Emit The Click</button>
<div #divElement>my div</div>
<p>From imported function: {{ myFunc() }}</p>
</template>

<style>
Expand Down
6 changes: 6 additions & 0 deletions apps/ng-app/src/app/pages/about.page.ng
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
<script lang="ts">
import { RouteMeta } from '@analogjs/router';
import Hello from '../hello.ng';

export const routeMeta: RouteMeta = {
title: 'My page',
canActivate: [() => true],
};
</script>

<template>
Expand Down
1 change: 1 addition & 0 deletions apps/ng-app/src/vite-env.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ declare global {
| 'inputs'
>
) => void;

/**
* Invoke the callback when the component is initialized.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,23 @@ export default class VirtualAnalogComponent {
protected output = new EventEmitter();
protected outputWithType = new EventEmitter<string>();
}
export const routeMeta = {
title: 'My page',
canActivate: [() => true],
}
export interface MyInterface {
title: string
}
export type MyType = string;
export enum Direction {
Up,
Down,
Left,
Right,
}
export function myFunc() {
console.log('hello');
}
"
`;
Expand Down
22 changes: 22 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 @@ -11,6 +11,28 @@ defineMetadata({
}
});
export const routeMeta = {
title: 'My page',
canActivate: [() => true],
}
export interface MyInterface {
title: string
}
export type MyType = string;
export enum Direction {
Up,
Down,
Left,
Right,
}
export function myFunc(){
console.log('hello');
}
let divElement: ElementRef<HTMLDivElement>;
let test: string;
Expand Down
187 changes: 103 additions & 84 deletions packages/vite-plugin-angular/src/lib/authoring/ng.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
Project,
PropertyDeclarationStructure,
Scope,
SourceFile,
StructureKind,
SyntaxKind,
VariableDeclarationKind,
Expand Down Expand Up @@ -164,6 +165,16 @@ function processNgScript(

const nodeFullText = node.getText();

if (
Node.isTypeAliasDeclaration(node) ||
Node.isInterfaceDeclaration(node) ||
Node.isEnumDeclaration(node)
) {
if (node.hasExportKeyword()) {
targetSourceFile.addStatements(nodeFullText);
}
}

// for VariableStatement (e.g: const ... = ..., let ... = ...)
if (Node.isVariableStatement(node)) {
// NOTE: we do not support multiple declarations (i.e: const a, b, c)
Expand All @@ -177,99 +188,103 @@ function processNgScript(
declaration.getInitializer(),
];

// let variable; // no initializer
if (!initializer && isLet) {
// transfer the whole line `let variable;` over
addVariableToConstructor(targetConstructor, '', name, 'let');
// populate getters array for Object.defineProperties
gettersSetters.push({ propertyName: name, isFunction: false });
} else if (initializer) {
// with initializer
const nameNode = declaration.getNameNode();

// if destructured.
// TODO: we don't have a good abstraction for handling destructured variables yet.
if (
Node.isArrayBindingPattern(nameNode) ||
Node.isObjectBindingPattern(nameNode)
) {
targetConstructor.addStatements(nodeFullText);

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

if (isLet) {
gettersSetters.push(
...bindingElements.map((propertyName) => ({
propertyName,
isFunction: false,
}))
);
} else {
for (const bindingElement of bindingElements) {
targetClass.addProperty({
name: bindingElement,
kind: StructureKind.Property,
scope: Scope.Protected,
});
targetConstructor.addStatements(
`this.${bindingElement} = ${bindingElement};`
if (node.hasExportKeyword()) {
targetSourceFile.addStatements(nodeFullText);
} else {
// let variable; // no initializer
if (!initializer && isLet) {
// transfer the whole line `let variable;` over
addVariableToConstructor(targetConstructor, '', name, 'let');
// populate getters array for Object.defineProperties
gettersSetters.push({ propertyName: name, isFunction: false });
} else if (initializer) {
// with initializer
const nameNode = declaration.getNameNode();

// if destructured.
// TODO: we don't have a good abstraction for handling destructured variables yet.
if (
Node.isArrayBindingPattern(nameNode) ||
Node.isObjectBindingPattern(nameNode)
) {
targetConstructor.addStatements(nodeFullText);

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

if (isLet) {
gettersSetters.push(
...bindingElements.map((propertyName) => ({
propertyName,
isFunction: false,
}))
);
} else {
for (const bindingElement of bindingElements) {
targetClass.addProperty({
name: bindingElement,
kind: StructureKind.Property,
scope: Scope.Protected,
});
targetConstructor.addStatements(
`this.${bindingElement} = ${bindingElement};`
);
}
}
}
} else {
const isFunctionInitializer = isFunction(initializer);
} else {
const isFunctionInitializer = isFunction(initializer);

if (!isLet) {
const ioStructure = getIOStructure(initializer);
if (!isLet) {
const ioStructure = getIOStructure(initializer);

if (ioStructure) {
// outputs
if (!!ioStructure.decorators) {
// track output name
outputs.push(name);
}

if (ioStructure) {
// outputs
if (!!ioStructure.decorators) {
// track output name
outputs.push(name);
targetClass.addProperty({
...ioStructure,
decorators: undefined,
name,
scope: Scope.Protected,
});

// assign constructor variable
targetConstructor.addStatements(`const ${name} = this.${name}`);
} else {
/**
* normal property
* const variable = initializer;
* We'll create a class property with the same variable name
*/
addVariableToConstructor(
targetConstructor,
initializer.getText(),
name,
'const',
true
);
}

// track output name
targetClass.addProperty({
...ioStructure,
decorators: undefined,
name,
scope: Scope.Protected,
});

// assign constructor variable
targetConstructor.addStatements(`const ${name} = this.${name}`);
} else {
/**
* normal property
* const variable = initializer;
* We'll create a class property with the same variable name
* let variable = initializer;
* We will NOT create a class property with the name because we will add it to the getters
*/
addVariableToConstructor(
targetConstructor,
initializer.getText(),
name,
'const',
true
'let'
);
gettersSetters.push({
propertyName: name,
isFunction: isFunctionInitializer,
});
}
} else {
/**
* let variable = initializer;
* We will NOT create a class property with the name because we will add it to the getters
*/
addVariableToConstructor(
targetConstructor,
initializer.getText(),
name,
'let'
);
gettersSetters.push({
propertyName: name,
isFunction: isFunctionInitializer,
});
}
}
}
Expand All @@ -279,12 +294,16 @@ function processNgScript(
if (Node.isFunctionDeclaration(node)) {
const functionName = node.getName();
if (functionName) {
targetConstructor.addStatements([
// bring the function over
nodeFullText,
// assign class property
`this.${functionName} = ${functionName}.bind(this);`,
]);
if (node.hasExportKeyword()) {
targetSourceFile.addStatements(nodeFullText);
} else {
targetConstructor.addStatements([
// bring the function over
nodeFullText,
// assign class property
`this.${functionName} = ${functionName}.bind(this);`,
]);
}
}
}

Expand Down

0 comments on commit 633e659

Please sign in to comment.