-
-
Notifications
You must be signed in to change notification settings - Fork 44
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(schematics): add fonts & dynamic styles
- Loading branch information
Showing
5 changed files
with
279 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
import { Tree, SchematicsException } from '@angular-devkit/schematics'; | ||
import { Schema } from './schema'; | ||
import { getProjectTargets, targetBuildNotFoundError } from '@schematics/angular/utility/project-targets'; | ||
|
||
/** Adds the Roboto & Material Icons fonts to the index HTML file. */ | ||
export function addFontsToIndex(options: Schema): (host: Tree) => Tree { | ||
return (host: Tree) => { | ||
const projectTargets = getProjectTargets(host, options.project); | ||
|
||
if (!projectTargets.build) { | ||
throw targetBuildNotFoundError(); | ||
} | ||
|
||
const indexPath = projectTargets.build.options.index; | ||
if (indexPath === undefined) { | ||
return host; | ||
} | ||
|
||
const buffer = host.read(indexPath); | ||
|
||
if (buffer === null) { | ||
throw new SchematicsException(`Could not read index file: ${indexPath}`); | ||
} | ||
|
||
const fonts = '<link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500|Material+Icons" rel="stylesheet">'; | ||
const htmlText = buffer.toString().replace(/([\s]+)?\<\/head\>([\s]+)?\<body\>/, (match, token) => { | ||
return match.replace(token, `${token}\n ${fonts}\n`); | ||
}); | ||
host.overwrite(indexPath, htmlText); | ||
|
||
return host; | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
import * as ts from '@schematics/angular/node_modules/typescript'; | ||
import { Tree, SchematicsException } from '@angular-devkit/schematics'; | ||
import { Schema } from './schema'; | ||
import { getProjectTargets, targetBuildNotFoundError } from '@schematics/angular/utility/project-targets'; | ||
import { addImport, getTsSourceFile, prettierConstructorParameters } from '../utils/ast'; | ||
import { findNodes } from '@schematics/angular/utility/ast-utils'; | ||
|
||
/** Adds the styles to the src/app/app.component.ts file. */ | ||
export function setUpStyles(options: Schema, filePath: string): (host: Tree) => Tree { | ||
return (host: Tree) => { | ||
const projectTargets = getProjectTargets(host, options.project); | ||
|
||
if (!projectTargets.build) { | ||
throw targetBuildNotFoundError(); | ||
} | ||
|
||
const buffer = host.read(filePath); | ||
|
||
if (buffer === null) { | ||
throw new SchematicsException(`Could not read index file: ${filePath}`); | ||
} | ||
|
||
// add import style | ||
addImport(host, filePath, ['LyTheme2', 'ThemeVariables'], '@alyle/ui'); | ||
|
||
let component = getComponentOrDirective(host, filePath); | ||
const componentStartPos = component.decorators![0].pos; | ||
|
||
const recorder = host.beginUpdate(filePath); | ||
recorder.insertLeft(componentStartPos, `\n\nconst STYLES = (theme: ThemeVariables) => ({ | ||
'@global': { | ||
body: { | ||
backgroundColor: theme.background.default, | ||
color: theme.text.default, | ||
fontFamily: theme.typography.fontFamily, | ||
margin: 0, | ||
direction: theme.direction | ||
} | ||
} | ||
});`); | ||
host.commitUpdate(recorder); | ||
|
||
component = getComponentOrDirective(host, filePath); | ||
|
||
const hasConstructor = component.members | ||
.some(prop => (prop.kind === ts.SyntaxKind.Constructor) && !!(prop as ts.ConstructorDeclaration).body); | ||
let constructor: ts.ConstructorDeclaration; | ||
let __recorder = host.beginUpdate(filePath); | ||
const propertyValue = `\n readonly classes = this.theme.addStyleSheet(STYLES);\n`; | ||
const constructorCall = ` constructor(\n private theme: LyTheme\n ) { }\n`; | ||
const OpenBraceTokenPos = findNodes(component, ts.SyntaxKind.OpenBraceToken) | ||
.filter(prop => prop.parent === component).map(prop => prop.end)[0]; | ||
|
||
__recorder.insertLeft(OpenBraceTokenPos, propertyValue); | ||
host.commitUpdate(__recorder); | ||
__recorder = host.beginUpdate(filePath); | ||
|
||
|
||
component = getComponentOrDirective(host, filePath); | ||
|
||
if (hasConstructor) { | ||
constructor = getContructor(component); | ||
|
||
const pos = findNodes(constructor, ts.SyntaxKind.OpenParenToken) | ||
.filter(prop => prop.parent === constructor).map(prop => prop.end)[0]; | ||
|
||
if (constructor.parameters.length) { | ||
__recorder.insertLeft(pos, `\n private theme: LyTheme2,\n `); | ||
} else { | ||
__recorder.insertLeft(pos, `\n private theme: LyTheme2\n `); | ||
} | ||
} else if (component.members.length) { | ||
const latestPropertyDeclarationEnd = component.members | ||
.filter(prop => prop.kind === ts.SyntaxKind.PropertyDeclaration) | ||
.map(({ end }: ts.PropertyDeclaration) => end).reverse()[0]; | ||
__recorder.insertLeft(latestPropertyDeclarationEnd, `\n\n${constructorCall}`); | ||
} else { | ||
__recorder.insertLeft(OpenBraceTokenPos, constructorCall); | ||
} | ||
host.commitUpdate(__recorder); | ||
|
||
component = getComponentOrDirective(host, filePath); | ||
constructor = getContructor(component); | ||
|
||
prettierConstructorParameters(host, filePath, constructor); | ||
|
||
console.log(host.read(filePath)!.toString()); | ||
return host; | ||
}; | ||
} | ||
|
||
function getComponentOrDirective(host: Tree, filePath: string) { | ||
const fileSource = getTsSourceFile(host, filePath); | ||
return findNodes(fileSource, ts.SyntaxKind.ClassDeclaration, 1) | ||
.filter(prop => prop.decorators) | ||
.filter(prop => prop.decorators!.filter( | ||
decorator => decorator.getText().startsWith('@Component') || | ||
decorator.getText().startsWith('@Directive') | ||
))[0] as ts.ClassDeclaration; | ||
} | ||
|
||
function getContructor(componentOrDirective: ts.ClassDeclaration) { | ||
return (componentOrDirective.members | ||
.filter(prop => prop.kind === ts.SyntaxKind.Constructor)[0] as ts.ConstructorDeclaration); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
import { Tree, SchematicsException } from '@angular-devkit/schematics'; | ||
import * as ts from '@schematics/angular/node_modules/typescript'; | ||
import { isImported, insertImport } from '@schematics/angular/utility/ast-utils'; | ||
import { InsertChange } from '@schematics/angular/utility/change'; | ||
|
||
export function getTsSourceFile(host: Tree, path: string): ts.SourceFile { | ||
const buffer = host.read(path); | ||
if (!buffer) { | ||
throw new SchematicsException(`Could not read file (${path}).`); | ||
} | ||
const content = buffer.toString(); | ||
const source = ts.createSourceFile(path, content, ts.ScriptTarget.Latest, true); | ||
|
||
return source; | ||
} | ||
|
||
export function addImport(host: Tree, filePath: string, importModule: string | string[], path: string) { | ||
let importModules: string[]; | ||
if (typeof importModule === 'string') { | ||
importModules = [importModule]; | ||
} else { | ||
importModules = importModule; | ||
} | ||
// add import theme | ||
importModules.forEach((val) => { | ||
const fileSource = getTsSourceFile(host, filePath); | ||
const importPath = path; | ||
if (!isImported(fileSource, val, importPath)) { | ||
const change = insertImport | ||
(fileSource, filePath, val, importPath); | ||
if (change) { | ||
const recorder = host.beginUpdate(filePath); | ||
recorder.insertLeft((change as InsertChange).pos, (change as InsertChange).toAdd); | ||
host.commitUpdate(recorder); | ||
} | ||
} | ||
}); | ||
} | ||
|
||
export function prettierConstructorParameters(host: Tree, | ||
filePath: string, | ||
constructor: ts.ConstructorDeclaration): void { | ||
const parenToken = constructor.getChildren() | ||
.filter(prop => prop.kind === ts.SyntaxKind.OpenParenToken || prop.kind === ts.SyntaxKind.CloseParenToken); | ||
// .map(prop => prop.getFullText().trim()); | ||
const buffer = host.read(filePath); | ||
|
||
if (buffer === null) { | ||
throw new SchematicsException(`Could not read index file: ${filePath}`); | ||
} | ||
|
||
const syntaxList = constructor.getChildren() | ||
.filter(prop => prop.kind === ts.SyntaxKind.SyntaxList)[0]; | ||
const contructorBefore = | ||
`${parenToken[0].getFullText()}${syntaxList.getFullText()}${parenToken[1].getFullText()}`; | ||
const parameters = syntaxList.getChildren() | ||
.filter(prop => prop.kind === ts.SyntaxKind.Parameter) | ||
.map((parameter: ts.ParameterDeclaration, index) => { | ||
const param = parameter.getText(); | ||
let comment = parameter.getFullText().replace(param, '').trim(); | ||
if (comment) { | ||
comment += `\n`; | ||
if (index === 0) { | ||
comment += `\n${comment}`; | ||
} | ||
} | ||
return `${comment}${param}`; | ||
}); | ||
const parametersStr = parameters.join(`,\n${` `.repeat(14)}`); | ||
const result = | ||
`${parenToken[0].getFullText().trim()}${parametersStr}${parenToken[1].getFullText().trim()}`; | ||
host.overwrite(filePath, buffer.toString().replace(contructorBefore, result)); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
import { Tree } from '@angular-devkit/schematics'; | ||
import { getProjectTargets, targetBuildNotFoundError } from '@schematics/angular/utility/project-targets'; | ||
import { getAppModulePath } from '@schematics/angular/utility/ng-ast-utils'; | ||
import { getDecoratorMetadata, findNode } from '@schematics/angular/utility/ast-utils'; | ||
import * as ts from '@schematics/angular/node_modules/typescript'; | ||
import { dirname } from 'path'; | ||
import { normalize } from '@angular-devkit/core'; | ||
import { getTsSourceFile } from './ast'; | ||
|
||
export function getAppComponentPath(host: Tree, options: any) { | ||
const projectTargets = getProjectTargets(host, options.project); | ||
if (!projectTargets.build) { | ||
throw targetBuildNotFoundError(); | ||
} | ||
const mainPath = projectTargets.build.options.main; | ||
const modulePath = getAppModulePath(host, mainPath); | ||
const moduleSource = getTsSourceFile(host, modulePath); | ||
const decoratorMetadata = getDecoratorMetadata(moduleSource, 'NgModule', '@angular/core'); | ||
const propertyName = 'bootstrap'; | ||
const { properties } = decoratorMetadata[0] as ts.ObjectLiteralExpression; | ||
const property = properties | ||
.filter(prop => prop.kind === ts.SyntaxKind.PropertyAssignment) | ||
.filter((prop: ts.PropertyAssignment) => { | ||
const name = prop.name; | ||
switch (name.kind) { | ||
case ts.SyntaxKind.Identifier: | ||
return (name as ts.Identifier).getText() === propertyName; | ||
case ts.SyntaxKind.StringLiteral: | ||
return (name as ts.StringLiteral).text === propertyName; | ||
} | ||
|
||
return false; | ||
})[0]; | ||
const bootstrapValue = ((property as ts.PropertyAssignment).initializer.getText()).split(/\[|\]|\,\s?/g).filter(s => s)[0]; | ||
const appComponentPath = moduleSource.statements | ||
.filter(prop => prop.kind === ts.SyntaxKind.ImportDeclaration) | ||
.filter(prop => findNode(prop, ts.SyntaxKind.ImportSpecifier, bootstrapValue)) | ||
.map(({ moduleSpecifier }: ts.ImportDeclaration) => (moduleSpecifier as ts.StringLiteral).text)[0]; | ||
|
||
const mainDir = dirname(modulePath); | ||
return normalize(`/${mainDir}/${appComponentPath}.ts`); | ||
} |