Skip to content

Commit

Permalink
feat(): support ng add (#15323)
Browse files Browse the repository at this point in the history
* feat(angular): support ng add

* feat(): add build aditions

* chore(): update schematics

* chore(): bad style import
  • Loading branch information
mhartington committed Nov 27, 2018
1 parent 0df9f89 commit 75dd853
Show file tree
Hide file tree
Showing 16 changed files with 1,240 additions and 8 deletions.
10 changes: 9 additions & 1 deletion angular/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,14 @@
"dependencies": {
"@ionic/core": "4.0.0-beta.16"
},
"peerDependencies": {
"@angular-devkit/core": "^7.0.3",
"@angular-devkit/schematics": "^7.0.3"
},
"devDependencies": {
"@angular-devkit/core": "^7.0.3",
"@angular-devkit/schematics": "^7.0.3",
"@types/node": "~10.12.0",
"@angular/common": "^7.0.3",
"@angular/compiler": "^7.0.3",
"@angular/compiler-cli": "^7.0.3",
Expand All @@ -62,5 +69,6 @@
"tslint-ionic-rules": "0.0.17",
"typescript": "3.1.6",
"zone.js": "^0.8.26"
}
},
"schematics": "./dist/schematics/collection.json"
}
39 changes: 39 additions & 0 deletions angular/scripts/build-core.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ const path = require('path');
const spawn = require('child_process').spawn;

const stencilPath = path.join(__dirname, '..', '..', 'core', 'node_modules', '.bin');
const typescriptPath = path.join(__dirname, '..', 'node_modules', '.bin');

function copyIonicons() {
const src = path.join(__dirname, '..', '..', 'core', 'node_modules', 'ionicons');
Expand All @@ -20,5 +21,43 @@ function copyCSS() {
fs.copySync(src, dst);
}

function buildSchematics(){
return new Promise((resolve, reject) => {
const cmd = 'tsc';
const args = [
'--project',
path.join(__dirname, '..', 'tsconfig.schematics.json'),
];

const p = spawn(cmd, args, { cwd: typescriptPath, stdio: 'inherit' });
p.on('close', (code) => {
if (code > 0) {
console.log(`ng-add build exited with ${code}`);
reject();
} else {
resolve();
}
});
});
}

function copySchematicsJson(){
const src = path.join(__dirname, '..', 'src', 'schematics', 'collection.json');
const fileSrc = path.join(__dirname, '..', 'src', 'schematics', 'add', 'files');
const dst = path.join(__dirname, '..', 'dist','schematics', 'collection.json');
const fileDst = path.join(__dirname, '..', 'dist', 'schematics', 'add', 'files');
const schemaSrc = path.join(__dirname, '..', 'src', 'schematics', 'add', 'schema.json');
const schemaDst = path.join(__dirname, '..', 'dist', 'schematics', 'add', 'schema.json');

fs.removeSync(dst);
fs.removeSync(fileDst);
fs.copySync(src, dst);
fs.copySync(fileSrc,fileDst);
fs.copySync(schemaSrc, schemaDst);

}

copyIonicons();
copyCSS();
buildSchematics();
copySchematicsJson()
6 changes: 6 additions & 0 deletions angular/src/schematics/add/files/root/theme/variables.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/* Ionic Variables and Theming. */
/* This is just a placeholder file For more info, please see: */
/* https://beta.ionicframework.com/docs/theming/basics */

/* To quickly generate your own theme, check out the color generator */
/* https://beta.ionicframework.com/docs/theming/color-generator */
136 changes: 136 additions & 0 deletions angular/src/schematics/add/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import {
Rule,
SchematicContext,
SchematicsException,
Tree,
apply,
chain,
mergeWith,
move,
template,
url
} from '@angular-devkit/schematics';
import { Path, join, normalize } from '@angular-devkit/core';
import { NodePackageInstallTask } from '@angular-devkit/schematics/tasks';
import { addPackageToPackageJson } from './../utils/package';
import { addModuleImportToRootModule } from './../utils/ast';
import { addStyle, getWorkspace, addArchitectBuilder } from './../utils/config';
import { Schema as IonAddOptions } from './schema';

function addIonicAngularToPackageJson(): Rule {
return (host: Tree) => {
addPackageToPackageJson(host, 'dependencies', '@ionic/angular', 'latest');
return host;
};
}

function addIonicAngularToolkitToPackageJson(): Rule {
return (host: Tree) => {
addPackageToPackageJson(
host,
'devDependencies',
'@ionic/angular-toolkit',
'latest'
);
return host;
};
}

function addIonicAngularModuleToAppModule(): Rule {
return (host: Tree) => {
addModuleImportToRootModule(
host,
'IonicModule.forRoot()',
'@ionic/angular'
);
return host;
};
}

function addIonicStyles(): Rule {
return (host: Tree) => {
const ionicStyles = [
'node_modules/@ionic/angular/css/normalize.css',
'node_modules/@ionic/angular/css/structure.css',
'node_modules/@ionic/angular/css/typography.css',
'node_modules/@ionic/angular/css/core.css',
'node_modules/@ionic/angular/css/padding.css',
'node_modules/@ionic/angular/css/float-elements.css',
'node_modules/@ionic/angular/css/text-alignment.css',
'node_modules/@ionic/angular/css/text-transformation.css',
'node_modules/@ionic/angular/css/flex-utils.css',
'src/theme/variables.css'
].forEach(entry => {
addStyle(host, entry);
});
return host;
};
}

function addIonicBuilder(): Rule {
return (host: Tree) => {
addArchitectBuilder(host, 'ionic-cordova-serve', {
builder: '@ionic/angular-toolkit:cordova-serve',
options: {
cordovaBuildTarget: 'app:ionic-cordova-build',
devServerTarget: 'app:serve'
},
configurations: {
production: {
cordovaBuildTarget: 'app:ionic-cordova-build:production',
devServerTarget: 'app:serve:production'
}
}
});
addArchitectBuilder(host, 'ionic-cordova-build', {
builder: '@ionic/angular-toolkit:cordova-build',
options: {
browserTarget: 'app:build'
},
configurations: {
production: {
browserTarget: 'app:build:production'
}
}
});
return host;
};
}

function installNodeDeps() {
return (host: Tree, context: SchematicContext) => {
context.addTask(new NodePackageInstallTask());
};
}

export default function ngAdd(options: IonAddOptions): Rule {
return (host: Tree) => {
const workspace = getWorkspace(host);
if (!options.project) {
options.project = Object.keys(workspace.projects)[0];
}
const project = workspace.projects[options.project];
if (project.projectType !== 'application') {
throw new SchematicsException(
`Ionic Add requires a project type of "application".`
);
}

const sourcePath = join(project.root as Path, 'src');
const rootTemplateSource = apply(url('./files/root'), [
template({ ...options }),
move(sourcePath)
]);
return chain([
// @ionic/angular
addIonicAngularToPackageJson(),
addIonicAngularToolkitToPackageJson(),
addIonicAngularModuleToAppModule(),
addIonicBuilder(),
addIonicStyles(),
mergeWith(rootTemplateSource),
// install freshly added dependencies
installNodeDeps()
]);
};
}
3 changes: 3 additions & 0 deletions angular/src/schematics/add/schema.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export interface Schema {
project?: string;
}
16 changes: 16 additions & 0 deletions angular/src/schematics/add/schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"$schema": "http://json-schema.org/schema",
"id": "ionicNgAdd",
"title": "Ionic Add options",
"type": "object",
"properties": {
"project": {
"type": "string",
"description": "The name of the project.",
"$default": {
"$source": "projectName"
}
}
},
"required": []
}
10 changes: 10 additions & 0 deletions angular/src/schematics/collection.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"$schema": "../../node_modules/@angular-devkit/schematics/collection-schema.json",
"schematics": {
"ng-add": {
"description": "Add Ionic to your project",
"factory": "./add",
"schema": "./add/schema.json"
}
}
}
65 changes: 65 additions & 0 deletions angular/src/schematics/utils/ast.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { SchematicsException, Tree } from '@angular-devkit/schematics';
import { normalize } from '@angular-devkit/core';
import * as ts from 'typescript';
import { addImportToModule } from './devkit-utils/ast-utils';
import { InsertChange } from './devkit-utils/change';

/**
* Reads file given path and returns TypeScript source file.
*/
export function getSourceFile(host: Tree, path: string): ts.SourceFile {
const buffer = host.read(path);
if (!buffer) {
throw new SchematicsException(`Could not find file for path: ${path}`);
}
const content = buffer.toString();
const source = ts.createSourceFile(
path,
content,
ts.ScriptTarget.Latest,
true
);
return source;
}

/**
* Import and add module to root app module.
*/
export function addModuleImportToRootModule(
host: Tree,
moduleName: string,
importSrc: string
) {
addModuleImportToModule(
host,
normalize(`src/app/app.module.ts`),
moduleName,
importSrc
);
}

/**
* Import and add module to specific module path.
* @param host the tree we are updating
* @param modulePath src location of the module to import
* @param moduleName name of module to import
* @param src src location to import
*/
export function addModuleImportToModule(
host: Tree,
modulePath: string,
moduleName: string,
src: string
) {
const moduleSource = getSourceFile(host, modulePath);
const changes = addImportToModule(moduleSource, modulePath, moduleName, src);
const recorder = host.beginUpdate(modulePath);

changes.forEach(change => {
if (change instanceof InsertChange) {
recorder.insertLeft(change.pos, change.toAdd);
}
});

host.commitUpdate(recorder);
}
Loading

0 comments on commit 75dd853

Please sign in to comment.