Produce .d.ts files from our typescript compilation.
Deliver them into our npm module output so users can consume them directly. Fixes #3082
This commit is contained in:
parent
393b0526b4
commit
95f984615b
|
@ -50,7 +50,7 @@ module.exports = new Package('angular-v2-docs', [jsdocPackage, nunjucksPackage,
|
|||
createTypeDefinitionFile.typeDefinitions = [
|
||||
{
|
||||
id: 'angular2/angular2',
|
||||
references: ['../es6-shim/es6-shim.d.ts'],
|
||||
references: [],
|
||||
modules: {
|
||||
'angular2/angular2': {namespace: 'ng', id: 'angular2/angular2'},
|
||||
'angular2/web_worker/worker': {namespace: 'ngWorker', id: 'angular2/web_worker/worker'},
|
||||
|
@ -71,7 +71,7 @@ module.exports = new Package('angular-v2-docs', [jsdocPackage, nunjucksPackage,
|
|||
},
|
||||
{
|
||||
id: 'angular2/test_lib',
|
||||
references: ['./angular2.d.ts', '../jasmine/jasmine.d.ts'],
|
||||
references: ['./angular2.d.ts'],
|
||||
remapTypes: { Type: 'ng.Type', Binding: 'ng.Binding', ViewMetadata: 'ng.ViewMetadata', Injector: 'ng.Injector',
|
||||
Predicate: 'ng.Predicate', ElementRef: 'ng.ElementRef', DebugElement: 'ng.DebugElement',
|
||||
InjectableReference: 'ng.InjectableReference', ComponentRef: 'ng.ComponentRef' },
|
||||
|
|
44
gulpfile.js
44
gulpfile.js
|
@ -446,7 +446,7 @@ function runKarma(configFile, done) {
|
|||
|
||||
gulp.task('test.js', function(done) {
|
||||
runSequence('test.unit.tools/ci', 'test.transpiler.unittest', 'test.unit.js/ci',
|
||||
'test.unit.cjs/ci', 'test.typings', sequenceComplete(done));
|
||||
'test.unit.cjs/ci', 'test.typings', 'test.typings.npm', sequenceComplete(done));
|
||||
});
|
||||
|
||||
gulp.task('test.dart', function(done) {
|
||||
|
@ -771,8 +771,25 @@ gulp.task('!pre.test.typings', ['docs/typings'], function() {
|
|||
});
|
||||
|
||||
// -----------------
|
||||
// Tests for the typings we deliver for TS users
|
||||
//
|
||||
// There are currently two mechanisms for this.
|
||||
// The first is the legacy, bundled .d.ts file produced by dgeni.
|
||||
// This is tested by 'test.typings'.
|
||||
//
|
||||
// The second is individual .d.ts files produced by the compiler,
|
||||
// distributed in our npm package, and loaded from node_modules by
|
||||
// the typescript compiler.
|
||||
// This is tested by 'test.typings.npm'.
|
||||
//
|
||||
// During the transition, we support both packaging/delivery types.
|
||||
// TODO(alexeagle): remove the dgeni bundle when users have switched
|
||||
|
||||
gulp.task('test.typings', ['!pre.test.typings'], function() {
|
||||
return gulp.src(['typing_spec/*.ts', 'dist/docs/typings/angular2/*.d.ts', 'dist/docs/typings/http.d.ts'])
|
||||
return gulp.src(['typing_spec/*.ts', 'dist/docs/typings/angular2/*.d.ts',
|
||||
'dist/docs/typings/http.d.ts',
|
||||
'dist/docs/typings/es6-shim/es6-shim.d.ts',
|
||||
'dist/docs/typings/jasmine/jasmine.d.ts'])
|
||||
.pipe(tsc({target: 'ES5', module: 'commonjs',
|
||||
experimentalDecorators: true,
|
||||
noImplicitAny: true,
|
||||
|
@ -781,6 +798,29 @@ gulp.task('test.typings', ['!pre.test.typings'], function() {
|
|||
typescript: require('typescript')}));
|
||||
});
|
||||
|
||||
// Make sure the two typings tests are isolated, by running this one in a tempdir
|
||||
var tmpdir = path.join(os.tmpdir(), 'test.typings', new Date().getTime().toString());
|
||||
gulp.task('!pre.test.typings.layoutNodeModule', function() {
|
||||
return gulp
|
||||
.src(['dist/js/dev/es5/angular2/**/*'], {base: 'dist/js/dev/es5'})
|
||||
.pipe(gulp.dest(path.join(tmpdir, 'node_modules')));
|
||||
});
|
||||
gulp.task('!pre.test.typings.copyTypingsSpec', function() {
|
||||
return gulp
|
||||
.src(['typing_spec/basic_spec.ts'], {base: 'typing_spec'})
|
||||
.pipe(gulp.dest(path.join(tmpdir)));
|
||||
});
|
||||
gulp.task('test.typings.npm', [
|
||||
'!pre.test.typings.layoutNodeModule',
|
||||
'!pre.test.typings.copyTypingsSpec'
|
||||
], function() {
|
||||
return gulp.src([tmpdir + '/**'])
|
||||
.pipe(tsc({target: 'ES5', module: 'commonjs',
|
||||
experimentalDecorators: true,
|
||||
noImplicitAny: true,
|
||||
typescript: require('typescript')}));
|
||||
});
|
||||
|
||||
// -----------------
|
||||
// orchestrated targets
|
||||
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
/**
|
||||
* Declarations angular depends on for compilation to ES6.
|
||||
* This file is also used to propagate our transitive typings
|
||||
* to users.
|
||||
*/
|
||||
|
||||
/// <reference path="../typings/zone/zone.d.ts"/>
|
||||
/// <reference path="../typings/hammerjs/hammerjs.d.ts"/>
|
||||
/// <reference path="../typings/jasmine/jasmine.d.ts"/>
|
||||
/// <reference path="../typings/angular-protractor/angular-protractor.d.ts"/>
|
||||
declare var assert: any;
|
||||
|
||||
interface BrowserNodeGlobal {
|
||||
Object: typeof Object;
|
||||
Array: typeof Array;
|
||||
Map: typeof Map;
|
||||
Set: typeof Set;
|
||||
Date: typeof Date;
|
||||
RegExp: typeof RegExp;
|
||||
JSON: typeof JSON;
|
||||
Math: typeof Math;
|
||||
assert(condition: any): void;
|
||||
Reflect: any;
|
||||
zone: Zone;
|
||||
getAngularTestability: Function;
|
||||
getAllAngularTestabilities: Function;
|
||||
setTimeout: Function;
|
||||
clearTimeout: Function;
|
||||
setInterval: Function;
|
||||
clearInterval: Function;
|
||||
}
|
|
@ -1,26 +1,7 @@
|
|||
/**
|
||||
* This file contains declarations of global symbols we reference in our code
|
||||
* Declarations angular depends on for compilation to ES6.
|
||||
* This file is also used to propagate our transitive typings
|
||||
* to users.
|
||||
*/
|
||||
|
||||
/// <reference path="../typings/zone/zone.d.ts"/>
|
||||
declare var assert: any;
|
||||
|
||||
interface BrowserNodeGlobal {
|
||||
Object: typeof Object;
|
||||
Array: typeof Array;
|
||||
Map: typeof Map;
|
||||
Set: typeof Set;
|
||||
Date: typeof Date;
|
||||
RegExp: typeof RegExp;
|
||||
JSON: typeof JSON;
|
||||
Math: typeof Math;
|
||||
assert(condition): void;
|
||||
Reflect: any;
|
||||
zone: Zone;
|
||||
getAngularTestability: Function;
|
||||
getAllAngularTestabilities: Function;
|
||||
setTimeout: Function;
|
||||
clearTimeout: Function;
|
||||
setInterval: Function;
|
||||
clearInterval: Function;
|
||||
}
|
||||
/// <reference path="../typings/es6-shim/es6-shim.d.ts"/>
|
||||
/// <reference path="./globals-es6.d.ts"/>
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
"zone.js": "<%= packageJson.dependencies['zone.js'] %>"
|
||||
},
|
||||
"devDependencies": <%= JSON.stringify(packageJson.defaultDevDependencies) %>,
|
||||
"typings": "./angular2.d.ts",
|
||||
"typescript": {
|
||||
"definitions": [
|
||||
"bundles/typings/angular2/angular2.d.ts",
|
||||
|
|
|
@ -117,9 +117,6 @@ export function createNgZone(): NgZone {
|
|||
|
||||
var _platform: PlatformRef;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export function platformCommon(bindings?: Array<Type | Binding | any[]>, initializer?: () => void):
|
||||
PlatformRef {
|
||||
if (isPresent(_platform)) {
|
||||
|
|
|
@ -2,9 +2,6 @@ import {isPresent} from 'angular2/src/core/facade/lang';
|
|||
import {BaseException} from 'angular2/src/core/facade/exceptions';
|
||||
import {ListWrapper, MapWrapper} from 'angular2/src/core/facade/collection';
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export class Locals {
|
||||
constructor(public parent: Locals, public current: Map<any, any>) {}
|
||||
|
||||
|
|
|
@ -30,9 +30,6 @@ import {
|
|||
} from './exceptions';
|
||||
import {resolveForwardRef} from './forward_ref';
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export class Dependency {
|
||||
constructor(public key: Key, public optional: boolean, public lowerBoundVisibility: any,
|
||||
public upperBoundVisibility: any, public properties: any[]) {}
|
||||
|
@ -280,7 +277,6 @@ export class ResolvedBinding_ implements ResolvedBinding {
|
|||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* An internal resolved representation of a factory function created by resolving {@link Binding}.
|
||||
*/
|
||||
export class ResolvedFactory {
|
||||
|
|
|
@ -387,7 +387,6 @@ export class BindingWithVisibility {
|
|||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* Used to provide dependencies that cannot be easily expressed as bindings.
|
||||
*/
|
||||
export interface DependencyProvider {
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
/**
|
||||
* @internal
|
||||
* Type literals is a Dart-only feature. This is here only so we can x-compile
|
||||
* to multiple languages.
|
||||
*/
|
||||
|
|
|
@ -2,3 +2,4 @@
|
|||
|
||||
// I need to be here to make TypeScript think this is a module.
|
||||
import {} from 'angular2/src/core/facade/lang';
|
||||
export var workaround_empty_observable_list_diff: any;
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
///<reference path="../../../typings/tsd.d.ts" />
|
||||
import {global, isPresent} from 'angular2/src/core/facade/lang';
|
||||
// TODO(jeffbcross): use ES6 import once typings are available
|
||||
var Subject = require('@reactivex/rxjs/dist/cjs/Subject');
|
||||
|
@ -56,14 +55,18 @@ export class PromiseWrapper {
|
|||
}
|
||||
}
|
||||
|
||||
export class TimerWrapper {
|
||||
static setTimeout(fn: Function, millis: number): number { return global.setTimeout(fn, millis); }
|
||||
static clearTimeout(id: number): void { global.clearTimeout(id); }
|
||||
export namespace NodeJS {
|
||||
export interface Timer {}
|
||||
}
|
||||
|
||||
static setInterval(fn: Function, millis: number): number {
|
||||
export class TimerWrapper {
|
||||
static setTimeout(fn: (...args: any[]) => void, millis: number): NodeJS.Timer { return global.setTimeout(fn, millis); }
|
||||
static clearTimeout(id: NodeJS.Timer): void { global.clearTimeout(id); }
|
||||
|
||||
static setInterval(fn: (...args: any[]) => void, millis: number): NodeJS.Timer {
|
||||
return global.setInterval(fn, millis);
|
||||
}
|
||||
static clearInterval(id: number): void { global.clearInterval(id); }
|
||||
static clearInterval(id: NodeJS.Timer): void { global.clearInterval(id); }
|
||||
}
|
||||
|
||||
export class ObservableWrapper {
|
||||
|
|
|
@ -259,7 +259,7 @@ export class ListWrapper {
|
|||
static toString<T>(l: T[]): string { return l.toString(); }
|
||||
static toJSON<T>(l: T[]): string { return JSON.stringify(l); }
|
||||
|
||||
static maximum<T>(list: T[], predicate: (T) => number): T {
|
||||
static maximum<T>(list: T[], predicate: (t: T) => number): T {
|
||||
if (list.length == 0) {
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
/// <reference path="../../../manual_typings/globals.d.ts" />
|
||||
|
||||
import {ExceptionHandler} from './exception_handler';
|
||||
|
||||
export {ExceptionHandler} from './exception_handler';
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
/// <reference path="../../../manual_typings/globals.d.ts" />
|
||||
|
||||
// TODO(jteplitz602): Load WorkerGlobalScope from lib.webworker.d.ts file #3492
|
||||
declare var WorkerGlobalScope;
|
||||
var globalScope: BrowserNodeGlobal;
|
||||
|
@ -196,7 +194,7 @@ export class StringJoiner {
|
|||
export class NumberParseError extends Error {
|
||||
name: string;
|
||||
|
||||
constructor(public message: string) { super(message); }
|
||||
constructor(public message: string) { super(); }
|
||||
|
||||
toString(): string { return this.message; }
|
||||
}
|
||||
|
@ -251,7 +249,7 @@ export class RegExpWrapper {
|
|||
flags = flags.replace(/g/g, '');
|
||||
return new _global.RegExp(regExpStr, flags + 'g');
|
||||
}
|
||||
static firstMatch(regExp: RegExp, input: string): string[] {
|
||||
static firstMatch(regExp: RegExp, input: string): RegExpExecArray {
|
||||
// Reset multimatch regex state
|
||||
regExp.lastIndex = 0;
|
||||
return regExp.exec(input);
|
||||
|
@ -277,7 +275,7 @@ export class RegExpMatcherWrapper {
|
|||
static next(matcher: {
|
||||
re: RegExp;
|
||||
input: string
|
||||
}): string[] {
|
||||
}): RegExpExecArray {
|
||||
return matcher.re.exec(matcher.input);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,6 @@
|
|||
import {MapWrapper} from 'angular2/src/core/facade/collection';
|
||||
import {SimpleChange} from 'angular2/src/core/change_detection/change_detection_util';
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export enum LifecycleHooks {
|
||||
OnInit,
|
||||
OnDestroy,
|
||||
|
|
|
@ -13,13 +13,13 @@ interface Trace {
|
|||
endTimeRange(range: Range);
|
||||
}
|
||||
|
||||
interface Range {}
|
||||
export interface Range {}
|
||||
|
||||
interface Events {
|
||||
createScope(signature: string, flags: any): Scope;
|
||||
}
|
||||
|
||||
interface Scope {
|
||||
export interface Scope {
|
||||
(...args): any;
|
||||
}
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ import {
|
|||
import {BaseException, WrappedException} from 'angular2/src/core/facade/exceptions';
|
||||
import {ListWrapper} from 'angular2/src/core/facade/collection';
|
||||
import {GetterFn, SetterFn, MethodFn} from './types';
|
||||
import {PlatformReflectionCapabilities} from 'platform_reflection_capabilities';
|
||||
import {PlatformReflectionCapabilities} from './platform_reflection_capabilities';
|
||||
|
||||
export class ReflectionCapabilities implements PlatformReflectionCapabilities {
|
||||
private _reflect: any;
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
/// <reference path="../../../../../typings/hammerjs/hammerjs.d.ts"/>
|
||||
|
||||
import {HammerGesturesPluginCommon} from './hammer_common';
|
||||
import {isPresent} from 'angular2/src/core/facade/lang';
|
||||
import {BaseException, WrappedException} from 'angular2/src/core/facade/exceptions';
|
||||
|
|
|
@ -90,7 +90,7 @@ export class KeyEventsPlugin extends EventManagerPlugin {
|
|||
return fullKey;
|
||||
}
|
||||
|
||||
static eventCallback(element: HTMLElement, fullKey: any, handler: (Event) => any, zone: NgZone):
|
||||
static eventCallback(element: HTMLElement, fullKey: any, handler: (e: Event) => any, zone: NgZone):
|
||||
(event: KeyboardEvent) => void {
|
||||
return (event) => {
|
||||
if (StringWrapper.equals(KeyEventsPlugin.getEventFullKey(event), fullKey)) {
|
||||
|
|
|
@ -236,7 +236,7 @@ if (!(Reflect && Reflect.getMetadata)) {
|
|||
throw 'reflect-metadata shim is required when using class decorators';
|
||||
}
|
||||
|
||||
export function makeDecorator(annotationCls, chainFn: (fn: Function) => void = null): (...args) =>
|
||||
export function makeDecorator(annotationCls, chainFn: (fn: Function) => void = null): (...args: any[]) =>
|
||||
(cls: any) => any {
|
||||
function DecoratorFactory(objOrType): (cls: any) => any {
|
||||
var annotationInstance = new (<any>annotationCls)(objOrType);
|
||||
|
|
|
@ -1,7 +1,3 @@
|
|||
/// <reference path="../../typings/node/node.d.ts" />
|
||||
/// <reference path="../../typings/angular-protractor/angular-protractor.d.ts" />
|
||||
/// <reference path="../../typings/jasmine/jasmine"/>
|
||||
|
||||
import * as webdriver from 'selenium-webdriver';
|
||||
|
||||
export var browser: protractor.IBrowser = global['browser'];
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
/// <reference path="../../typings/jasmine/jasmine.d.ts"/>
|
||||
|
||||
import {global} from 'angular2/src/core/facade/lang';
|
||||
import {BaseException, WrappedException} from 'angular2/src/core/facade/exceptions';
|
||||
import {ListWrapper} from 'angular2/src/core/facade/collection';
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
/// <reference path="../../typings/jasmine/jasmine.d.ts"/>
|
||||
|
||||
import {DOM} from 'angular2/src/core/dom/dom_adapter';
|
||||
import {StringMapWrapper} from 'angular2/src/core/facade/collection';
|
||||
import {global, isFunction, Math} from 'angular2/src/core/facade/lang';
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
/// <reference path="../../../manual_typings/globals.d.ts" />
|
||||
import {MessageBus} from "angular2/src/web_workers/shared/message_bus";
|
||||
import {print, isPresent, DateWrapper, stringify} from "angular2/src/core/facade/lang";
|
||||
import {
|
||||
|
|
|
@ -23,7 +23,7 @@ var needsLongerTimers = browserDetection.isSlow || browserDetection.isEdge;
|
|||
var resultTimer = 1000;
|
||||
var testTimeout = browserDetection.isEdge ? 1200 : 100;
|
||||
// Schedules a macrotask (using a timer)
|
||||
function macroTask(fn: Function, timer = 1): void {
|
||||
function macroTask(fn: (...args: any[]) => void, timer = 1): void {
|
||||
// adds longer timers for passing tests in IE and Edge
|
||||
_zone.runOutsideAngular(() => TimerWrapper.setTimeout(fn, needsLongerTimers ? timer : 1));
|
||||
}
|
||||
|
|
|
@ -39,7 +39,7 @@ export class ImageDemo {
|
|||
}
|
||||
}
|
||||
|
||||
private _filter(i: number): Function {
|
||||
private _filter(i: number): (...args: any[]) => void {
|
||||
return () => {
|
||||
var imageData = this._bitmapService.convertToImageData(this.images[i].buffer);
|
||||
imageData = this._bitmapService.applySepia(imageData);
|
||||
|
|
|
@ -118,6 +118,7 @@ module.exports = function makeBrowserTree(options, destinationPath) {
|
|||
mapRoot: '', // force sourcemaps to use relative path
|
||||
noEmitOnError: false,
|
||||
rootDir: '.',
|
||||
rootFilePaths: ['angular2/manual_typings/globals-es6.d.ts'],
|
||||
sourceMap: true,
|
||||
sourceRoot: '.',
|
||||
target: 'ES6'
|
||||
|
@ -126,7 +127,8 @@ module.exports = function makeBrowserTree(options, destinationPath) {
|
|||
// Use TypeScript to transpile the *.ts files to ES5
|
||||
var typescriptOptions = {
|
||||
allowNonTsExtensions: false,
|
||||
declaration: false,
|
||||
declaration: true,
|
||||
stripInternal: true,
|
||||
emitDecoratorMetadata: true,
|
||||
experimentalDecorators: true,
|
||||
mapRoot: '', // force sourcemaps to use relative path
|
||||
|
@ -134,6 +136,7 @@ module.exports = function makeBrowserTree(options, destinationPath) {
|
|||
moduleResolution: 1 /* classic */,
|
||||
noEmitOnError: true,
|
||||
rootDir: '.',
|
||||
rootFilePaths: ['angular2/manual_typings/globals.d.ts'],
|
||||
sourceMap: true,
|
||||
sourceRoot: '.',
|
||||
target: 'ES5'
|
||||
|
@ -228,8 +231,29 @@ module.exports = function makeBrowserTree(options, destinationPath) {
|
|||
|
||||
htmlTree = mergeTrees([htmlTree, scripts, polymer, react]);
|
||||
|
||||
es5Tree = mergeTrees([es5Tree, htmlTree, assetsTree, clientModules]);
|
||||
es6Tree = mergeTrees([es6Tree, htmlTree, assetsTree, clientModules]);
|
||||
var typingsTree = new Funnel('modules', {
|
||||
include: ['angular2/typings/**/*.d.ts',
|
||||
'angular2/manual_typings/*.d.ts'],
|
||||
destDir: '/'});
|
||||
|
||||
// Add a line to the end of our top-level .d.ts file.
|
||||
// This HACK for transitive typings is a workaround for
|
||||
// https://github.com/Microsoft/TypeScript/issues/5097
|
||||
//
|
||||
// This allows users to get our top-level dependencies like es6-shim.d.ts
|
||||
// to appear when they compile against angular2.
|
||||
//
|
||||
// This carries the risk that the user brings their own copy of that file
|
||||
// (or any other symbols exported here) and they will get a compiler error
|
||||
// because of the duplicate definitions.
|
||||
// TODO(alexeagle): remove this when typescript releases a fix
|
||||
es5Tree = replace(es5Tree, {
|
||||
files: ['angular2/angular2.d.ts'],
|
||||
patterns: [{match: /$/, replacement: 'import "./manual_typings/globals.d.ts";\n'}]
|
||||
});
|
||||
|
||||
es5Tree = mergeTrees([es5Tree, htmlTree, assetsTree, clientModules, typingsTree]);
|
||||
es6Tree = mergeTrees([es6Tree, htmlTree, assetsTree, clientModules, typingsTree]);
|
||||
|
||||
var mergedTree = mergeTrees([stew.mv(es6Tree, '/es6'), stew.mv(es5Tree, '/es5')]);
|
||||
|
||||
|
|
|
@ -41,7 +41,7 @@ module.exports = function makeNodeTree(destinationPath) {
|
|||
moduleResolution: 1 /* classic */,
|
||||
noEmitOnError: true,
|
||||
rootDir: '.',
|
||||
rootFilePaths: ['angular2/manual_typings/globals.d.ts'],
|
||||
rootFilePaths: ['angular2/manual_typings/globals.d.ts', 'angular2/typings/es6-shim/es6-shim.d.ts'],
|
||||
sourceMap: true,
|
||||
sourceRoot: '.',
|
||||
target: 'ES5'
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
///<reference path="../dist/docs/typings/angular2/angular2.d.ts"/>
|
||||
|
||||
import {Component, bootstrap, View} from 'angular2/angular2'
|
||||
|
||||
@Component({
|
||||
|
@ -15,4 +13,4 @@ class MyAppComponent {
|
|||
constructor() { this.name = 'Alice'; }
|
||||
}
|
||||
|
||||
bootstrap(MyAppComponent);
|
||||
bootstrap(MyAppComponent);
|
||||
|
|
Loading…
Reference in New Issue