From b8c9da6262ff4f7d9daef29272af2dd73396f1d8 Mon Sep 17 00:00:00 2001 From: Nathan Tate Date: Wed, 31 Jul 2019 16:44:50 -0700 Subject: [PATCH] feat(youtube-player): initialize component and infrastructure (#16337) --- .github/CODEOWNERS | 3 + package.json | 1 + packages.bzl | 1 + src/youtube-player/BUILD.bazel | 65 +++++ src/youtube-player/README.md | 0 src/youtube-player/fake-youtube-player.ts | 61 +++++ src/youtube-player/index.ts | 1 + src/youtube-player/package.json | 31 +++ src/youtube-player/public-api.ts | 2 + src/youtube-player/tsconfig-build.json | 28 ++ src/youtube-player/tsconfig-tests.json | 26 ++ src/youtube-player/tsconfig.json | 11 + src/youtube-player/youtube-module.ts | 14 + src/youtube-player/youtube-player.md | 0 src/youtube-player/youtube-player.spec.ts | 101 ++++++++ src/youtube-player/youtube-player.ts | 241 ++++++++++++++++++ tools/gulp/gulpfile.ts | 4 +- tools/gulp/packages.ts | 2 + tools/gulp/tasks/aot.ts | 1 + tools/gulp/tasks/development.ts | 8 + tools/gulp/tasks/unit-test.ts | 1 + tools/gulp/tasks/universal.ts | 2 + tools/package-tools/build-release.ts | 1 + tools/package-tools/rollup-globals.ts | 12 + .../release-output/release-packages.ts | 1 + yarn.lock | 5 + 26 files changed, 622 insertions(+), 1 deletion(-) create mode 100644 src/youtube-player/BUILD.bazel create mode 100644 src/youtube-player/README.md create mode 100644 src/youtube-player/fake-youtube-player.ts create mode 100644 src/youtube-player/index.ts create mode 100644 src/youtube-player/package.json create mode 100644 src/youtube-player/public-api.ts create mode 100644 src/youtube-player/tsconfig-build.json create mode 100644 src/youtube-player/tsconfig-tests.json create mode 100644 src/youtube-player/tsconfig.json create mode 100644 src/youtube-player/youtube-module.ts create mode 100644 src/youtube-player/youtube-player.md create mode 100644 src/youtube-player/youtube-player.spec.ts create mode 100644 src/youtube-player/youtube-player.ts diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index ae9cb3173605..c31d5bd66603 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -57,6 +57,9 @@ /src/material/core/typography/** @crisbeto /src/material/core/util/** @jelbourn +# Miscellaneous components +/src/youtube-player/** @nathantate + # CDK /src/cdk/* @jelbourn /src/cdk/a11y/** @jelbourn @devversion diff --git a/package.json b/package.json index dd743a4a73cb..74fc612cbf4d 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ "@angular/elements": "^8.1.0", "@angular/forms": "^8.1.0", "@angular/platform-browser": "^8.1.0", + "@types/youtube": "^0.0.38", "@webcomponents/custom-elements": "^1.1.0", "core-js": "^2.6.1", "material-components-web": "^3.0.0", diff --git a/packages.bzl b/packages.bzl index 1340836350d7..07192fe4fb37 100644 --- a/packages.bzl +++ b/packages.bzl @@ -121,6 +121,7 @@ ROLLUP_GLOBALS = { "@angular/cdk-experimental": "ng.cdkExperimental", "@angular/material": "ng.material", "@angular/material-experimental": "ng.materialExperimental", + "@angular/youtube-player": "ng.youtubePlayer", } # Rollup globals for cdk subpackages in the form of, e.g., {"@angular/cdk/table": "ng.cdk.table"} diff --git a/src/youtube-player/BUILD.bazel b/src/youtube-player/BUILD.bazel new file mode 100644 index 000000000000..cb7349d9ca1b --- /dev/null +++ b/src/youtube-player/BUILD.bazel @@ -0,0 +1,65 @@ +package(default_visibility = ["//visibility:public"]) + +load("//:packages.bzl", "ROLLUP_GLOBALS") +load( + "//tools:defaults.bzl", + "markdown_to_html", + "ng_module", + "ng_package", + "ng_test_library", + "ng_web_test_suite", +) + +ng_module( + name = "youtube-player", + srcs = glob( + ["**/*.ts"], + exclude = [ + "**/*.spec.ts", + "fake-youtube-player.ts", + ], + ), + module_name = "@angular/youtube-player", + deps = [ + "@npm//@angular/common", + "@npm//@angular/core", + "@npm//@types/youtube", + "@npm//rxjs", + ], +) + +ng_package( + name = "npm_package", + srcs = ["package.json"], + entry_point = ":public-api.ts", + entry_point_name = "youtube-player", + globals = ROLLUP_GLOBALS, + deps = [":youtube-player"], +) + +ng_test_library( + name = "unit_test_sources", + srcs = ["fake-youtube-player.ts"] + glob( + ["**/*.spec.ts"], + exclude = ["**/*.e2e.spec.ts"], + ), + deps = [ + ":youtube-player", + "@npm//@angular/platform-browser", + ], +) + +ng_web_test_suite( + name = "unit_tests", + deps = [":unit_test_sources"], +) + +markdown_to_html( + name = "overview", + srcs = [":youtube-player.md"], +) + +filegroup( + name = "source-files", + srcs = glob(["**/*.ts"]), +) diff --git a/src/youtube-player/README.md b/src/youtube-player/README.md new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/src/youtube-player/fake-youtube-player.ts b/src/youtube-player/fake-youtube-player.ts new file mode 100644 index 000000000000..0c11a49a6aa7 --- /dev/null +++ b/src/youtube-player/fake-youtube-player.ts @@ -0,0 +1,61 @@ +// A re-creation of YT.PlayerState since enum values cannot be bound to the window +// object. +const playerState = { + UNSTARTED: -1, + ENDED: 0, + PLAYING: 1, + PAUSED: 2, + BUFFERING: 3, + CUED: 5, +}; + +interface FakeYtNamespace { + playerCtorSpy: jasmine.Spy; + playerSpy: jasmine.SpyObj; + onPlayerReady: () => void; + namespace: typeof YT; +} + +export function createFakeYtNamespace(): FakeYtNamespace { + const playerSpy: jasmine.SpyObj = jasmine.createSpyObj('Player', [ + 'getPlayerState', 'destroy', 'cueVideoById', 'loadVideoById', 'pauseVideo', 'stopVideo', + 'seekTo', 'isMuted', 'mute', 'unMute', 'getVolume', 'getPlaybackRate', + 'getAvailablePlaybackRates', 'getVideoLoadedFraction', 'getPlayerState', 'getCurrentTime', + 'getPlaybackQuality', 'getAvailableQualityLevels', 'getDuration', 'getVideoUrl', + 'getVideoEmbedCode', 'playVideo', 'setSize', 'setVolume', 'setPlaybackQuality', + 'setPlaybackRate', 'addEventListener', 'removeEventListener', + ]); + + let playerConfig: YT.PlayerOptions | undefined; + const playerCtorSpy = jasmine.createSpy('Player Constructor'); + playerCtorSpy.and.callFake((_el: Element, config: YT.PlayerOptions) => { + playerConfig = config; + return playerSpy; + }); + + const onPlayerReady = () => { + if (!playerConfig) { + throw new Error('Player not initialized before onPlayerReady called'); + } + + if (playerConfig && playerConfig.events && playerConfig.events.onReady) { + playerConfig.events.onReady({target: playerSpy}); + } + + for (const [event, callback] of playerSpy.addEventListener.calls.allArgs()) { + if (event === 'onReady') { + callback({target: playerSpy}); + } + } + }; + + return { + playerCtorSpy, + playerSpy, + onPlayerReady, + namespace: { + 'Player': playerCtorSpy as unknown as typeof YT.Player, + 'PlayerState': playerState, + } as typeof YT, + }; +} diff --git a/src/youtube-player/index.ts b/src/youtube-player/index.ts new file mode 100644 index 000000000000..7e1a213e3ea5 --- /dev/null +++ b/src/youtube-player/index.ts @@ -0,0 +1 @@ +export * from './public-api'; diff --git a/src/youtube-player/package.json b/src/youtube-player/package.json new file mode 100644 index 000000000000..cee8ba8a77fa --- /dev/null +++ b/src/youtube-player/package.json @@ -0,0 +1,31 @@ +{ + "name": "@angular/youtube-player", + "version": "0.0.0-PLACEHOLDER", + "description": "Angular YouTube Player", + "main": "./bundles/youtube-player.umd.js", + "module": "./esm5/youtube-player.es5.js", + "es2015": "./esm2015/youtube-player.js", + "typings": "./youtube-player.d.ts", + "repository": { + "type": "git", + "url": "https://github.com/angular/components.git" + }, + "keywords": [ + "angular", + "components", + "youtube" + ], + "license": "MIT", + "bugs": { + "url": "https://github.com/angular/components/issues" + }, + "homepage": "https://github.com/angular/components/tree/master/src/youtube-player#readme", + "dependencies": { + "@types/youtube": "^0.0.38" + }, + "peerDependencies": { + "@angular/core": "0.0.0-NG", + "@angular/common": "0.0.0-NG" + }, + "sideEffects": false +} diff --git a/src/youtube-player/public-api.ts b/src/youtube-player/public-api.ts new file mode 100644 index 000000000000..562612aa5591 --- /dev/null +++ b/src/youtube-player/public-api.ts @@ -0,0 +1,2 @@ +export * from './youtube-module'; +export * from './youtube-player'; diff --git a/src/youtube-player/tsconfig-build.json b/src/youtube-player/tsconfig-build.json new file mode 100644 index 000000000000..18cf0ec575e7 --- /dev/null +++ b/src/youtube-player/tsconfig-build.json @@ -0,0 +1,28 @@ +// TypeScript config file that is used to compile the Material package through Gulp. As the +// long term goal is to switch to Bazel, and we already want to run tests with Bazel, we need to +// ensure the TypeScript build options are the same for Gulp and Bazel. We achieve this by +// extending the generic Bazel build tsconfig which will be used for each entry-point. +{ + "extends": "../bazel-tsconfig-build.json", + "compilerOptions": { + "baseUrl": ".", + "outDir": "../../dist/packages/youtube-player", + "rootDir": ".", + "rootDirs": [ + ".", + "../../dist/packages/youtube-player" + ], + "types": ["youtube"] + }, + "files": [ + "public-api.ts" + ], + "angularCompilerOptions": { + "annotateForClosureCompiler": true, + "strictMetadataEmit": true, + "flatModuleOutFile": "index.js", + "flatModuleId": "@angular/youtube-player", + "skipTemplateCodegen": true, + "fullTemplateTypeCheck": true + } +} diff --git a/src/youtube-player/tsconfig-tests.json b/src/youtube-player/tsconfig-tests.json new file mode 100644 index 000000000000..74272aeb5a3a --- /dev/null +++ b/src/youtube-player/tsconfig-tests.json @@ -0,0 +1,26 @@ +// TypeScript config file that extends the default tsconfig file for the library. This config is +// used to compile the tests for Karma. Since the code will run inside of the browser, the target +// needs to be ES5. The format needs to be CommonJS since Karma only supports that module format. +{ + "extends": "./tsconfig-build", + "compilerOptions": { + "importHelpers": false, + "module": "commonjs", + "target": "es5", + "types": ["jasmine", "youtube"] + }, + "angularCompilerOptions": { + "strictMetadataEmit": true, + "skipTemplateCodegen": true, + "emitDecoratorMetadata": true, + "fullTemplateTypeCheck": true, + + // Unset options inherited from tsconfig-build + "annotateForClosureCompiler": false, + "flatModuleOutFile": null, + "flatModuleId": null + }, + "include": [ + "*.ts" + ] +} diff --git a/src/youtube-player/tsconfig.json b/src/youtube-player/tsconfig.json new file mode 100644 index 000000000000..8e41d2ec562e --- /dev/null +++ b/src/youtube-player/tsconfig.json @@ -0,0 +1,11 @@ +// Configuration for IDEs only. +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "rootDir": "..", + "baseUrl": ".", + "paths": {}, + "types": ["jasmine", "youtube"] + }, + "include": ["*.ts"] +} diff --git a/src/youtube-player/youtube-module.ts b/src/youtube-player/youtube-module.ts new file mode 100644 index 000000000000..8d72d982f1e3 --- /dev/null +++ b/src/youtube-player/youtube-module.ts @@ -0,0 +1,14 @@ +import {CommonModule} from '@angular/common'; +import {NgModule} from '@angular/core'; + +import {YouTubePlayer} from './youtube-player'; + +const COMPONENTS = [YouTubePlayer]; + +@NgModule({ + declarations: COMPONENTS, + exports: COMPONENTS, + imports: [CommonModule], +}) +export class YouTubePlayerModule { +} diff --git a/src/youtube-player/youtube-player.md b/src/youtube-player/youtube-player.md new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/src/youtube-player/youtube-player.spec.ts b/src/youtube-player/youtube-player.spec.ts new file mode 100644 index 000000000000..572716189051 --- /dev/null +++ b/src/youtube-player/youtube-player.spec.ts @@ -0,0 +1,101 @@ +import {async, ComponentFixture, TestBed} from '@angular/core/testing'; +import {Component, ViewChild} from '@angular/core'; +import {YouTubePlayerModule} from './index'; +import {YouTubePlayer} from './youtube-player'; +import {createFakeYtNamespace} from './fake-youtube-player'; + +const VIDEO_ID = 'a12345'; + +declare global { + interface Window { YT: typeof YT | undefined; } +} + +describe('YoutubePlayer', () => { + let playerCtorSpy: jasmine.Spy; + let playerSpy: jasmine.SpyObj; + let onPlayerReady: () => void; + let fixture: ComponentFixture; + let testComponent: TestApp; + + beforeEach(async(() => { + const fake = createFakeYtNamespace(); + playerCtorSpy = fake.playerCtorSpy; + playerSpy = fake.playerSpy; + onPlayerReady = fake.onPlayerReady; + window.YT = fake.namespace; + + TestBed.configureTestingModule({ + imports: [YouTubePlayerModule], + declarations: [TestApp], + }); + + TestBed.compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(TestApp); + testComponent = fixture.debugElement.componentInstance; + fixture.detectChanges(); + }); + + afterEach(() => { + window.YT = undefined; + }); + + it('initializes a youtube player', () => { + let containerElement = fixture.nativeElement.querySelector('div'); + + expect(playerCtorSpy).toHaveBeenCalledWith( + containerElement, jasmine.objectContaining({ + videoId: VIDEO_ID, + })); + }); + + it('destroys the iframe when the component is destroyed', () => { + onPlayerReady(); + + testComponent.visible = false; + fixture.detectChanges(); + + expect(playerSpy.destroy).toHaveBeenCalled(); + }); + + it('responds to changes in video id', () => { + let containerElement = fixture.nativeElement.querySelector('div'); + + testComponent.videoId = 'otherId'; + fixture.detectChanges(); + + expect(playerSpy.cueVideoById).not.toHaveBeenCalled(); + + onPlayerReady(); + + expect(playerSpy.cueVideoById).toHaveBeenCalledWith( + jasmine.objectContaining({videoId: 'otherId'})); + + testComponent.videoId = undefined; + fixture.detectChanges(); + + expect(playerSpy.destroy).toHaveBeenCalled(); + + testComponent.videoId = 'otherId2'; + fixture.detectChanges(); + + expect(playerCtorSpy).toHaveBeenCalledWith( + containerElement, jasmine.objectContaining({videoId: 'otherId2'})); + }); +}); + +/** Test component that contains a YouTubePlayer. */ +@Component({ + selector: 'test-app', + template: ` + + + ` +}) +class TestApp { + videoId: string | undefined = VIDEO_ID; + visible = true; + @ViewChild('player', {static: true}) youtubePlayer: YouTubePlayer; +} diff --git a/src/youtube-player/youtube-player.ts b/src/youtube-player/youtube-player.ts new file mode 100644 index 000000000000..a5a505b153fb --- /dev/null +++ b/src/youtube-player/youtube-player.ts @@ -0,0 +1,241 @@ +import { + AfterViewInit, + ChangeDetectionStrategy, + Component, + ElementRef, + EventEmitter, + Input, + OnDestroy, + ViewChild, +} from '@angular/core'; + +import { + combineLatest, + of as observableOf, + Observable, + ConnectableObservable, + pipe, + MonoTypeOperatorFunction, + merge, + OperatorFunction, +} from 'rxjs'; + +import { + map, + scan, + withLatestFrom, + flatMap, + filter, + startWith, + publish, + first, + distinctUntilChanged, + takeUntil, +} from 'rxjs/operators'; + +declare global { + interface Window { YT: typeof YT | undefined; } +} + +// The native YT.Player doesn't expose the set videoId, but we need it for +// convenience. +interface Player extends YT.Player { + videoId?: string | undefined; +} + +// The player isn't fully initialized when it's constructed. +// The only field available is destroy and addEventListener. +type UninitializedPlayer = Pick; + +/** + * Angular component that renders a YouTube player via the YouTube player + * iframe API. + * @see https://developers.google.com/youtube/iframe_api_reference + */ +@Component({ + selector: 'youtube-player', + changeDetection: ChangeDetectionStrategy.OnPush, + // This div is *replaced* by the YouTube player embed. + template: '
', +}) +export class YouTubePlayer implements AfterViewInit, OnDestroy { + /** YouTube Video ID to view */ + get videoId(): string | undefined { + return this._player && this._player.videoId; + } + + @Input() + set videoId(videoId: string | undefined) { + this._videoId.emit(videoId); + } + + private _videoId = new EventEmitter(); + + /** The element that will be replaced by the iframe. */ + @ViewChild('youtube_container', {static: false}) youtubeContainer: ElementRef | undefined; + private _youtubeContainer = new EventEmitter(); + private _destroyed = new EventEmitter(); + + private _player: Player | undefined; + + constructor() { + if (!window.YT) { + throw new Error('Namespace YT not found, cannot construct embedded youtube player. ' + + 'Please install the YouTube Player API Reference for iframe Embeds: ' + + 'https://developers.google.com/youtube/iframe_api_reference'); + } + + /** An observable of the currently loaded player. */ + const playerObs = + createPlayerObservable( + this._youtubeContainer, + this._videoId, + ).pipe(waitUntilReady(), takeUntil(this._destroyed), publish()); + + /** Set up side effects to bind inputs to the player. */ + playerObs.subscribe(player => this._player = player); + + bindCueVideoCall(playerObs, this._videoId, this._destroyed); + + // After all of the subscriptions are set up, connect the observable. + (playerObs as ConnectableObservable).connect(); + } + + ngAfterViewInit() { + if (!this.youtubeContainer) { + return; + } + this._youtubeContainer.emit(this.youtubeContainer.nativeElement); + } + + ngOnDestroy() { + if (!this._player) { + return; + } + this._player.destroy(); + this._destroyed.emit(); + } +} + +/** + * Returns an observable that emits the loaded player once it's ready. Certain properties/methods + * won't be available until the iframe finishes loading. + */ +function waitUntilReady() + : OperatorFunction { + return flatMap(player => { + if (!player) { + return observableOf(undefined); + } + if ('getPlayerStatus' in player) { + return observableOf(player as Player); + } + // The player is not initialized fully until the ready is called. + return fromPlayerOnReady(player) + .pipe(first(), startWith(undefined)); + }); +} + +/** Since removeEventListener is not on Player when it's initialized, we can't use fromEvent. */ +function fromPlayerOnReady(player: UninitializedPlayer): Observable { + return new Observable(emitter => { + let aborted = false; + + const onReady = (event: YT.PlayerEvent) => { + if (aborted) { + return; + } + event.target.removeEventListener('onReady', onReady); + emitter.next(event.target); + }; + + player.addEventListener('onReady', onReady); + + return () => { + aborted = true; + }; + }); +} + +/** Create an observable for the player based on the given options. */ +function createPlayerObservable( + youtubeContainer: Observable, + videoIdObs: Observable, +): Observable { + + const playerOptions = + videoIdObs + .pipe( + map((videoId) => videoId ? {videoId} : undefined), + ); + + return combineLatest(youtubeContainer, playerOptions) + .pipe(scan(syncPlayerState, undefined), distinctUntilChanged()); +} + +/** Destroy the player if there are no options, or create the player if there are options. */ +function syncPlayerState( + player: UninitializedPlayer | undefined, + [container, videoOptions]: [HTMLElement, YT.PlayerOptions | undefined], +): UninitializedPlayer | undefined { + if (!videoOptions) { + if (player) { + player.destroy(); + } + return; + } + if (player) { + return player; + } + const newPlayer: UninitializedPlayer = new YT.Player(container, videoOptions); + // Bind videoId for future use. + newPlayer.videoId = videoOptions.videoId; + return newPlayer; +} + +/** + * Call cueVideoById if the videoId changes, or when start or end seconds change. cueVideoById will + * change the loaded video id to the given videoId, and set the start and end times to the given + * start/end seconds. + */ +function bindCueVideoCall( + playerObs: Observable, + videoIdObs: Observable, + destroyed: Observable, +) { + + // If the video id changed, there's no reason to run 'cue' unless the player + // was initialized with a different video id. + const changedVideoId = videoIdObs + .pipe(filterOnOther(playerObs, (player, videoId) => !!player && player.videoId !== videoId)); + + // If the player changed, there's no reason to run 'cue' unless there are cue options. + const changedPlayer = playerObs.pipe( + filterOnOther(videoIdObs, (videoId, player) => !!player && videoId != player.videoId)); + + merge(changedPlayer, changedVideoId) + .pipe( + withLatestFrom(combineLatest(playerObs, videoIdObs)), + map(([_, values]) => values), + takeUntil(destroyed), + ) + .subscribe(([player, videoId]) => { + if (!videoId || !player) { + return; + } + player.videoId = videoId; + player.cueVideoById({videoId}); + }); +} + +/** Combines the two observables temporarily for the filter function. */ +function filterOnOther( + otherObs: Observable, + filterFn: (t: T, r?: R) => boolean, +): MonoTypeOperatorFunction { + return pipe( + withLatestFrom(otherObs), + filter(([value, other]) => filterFn(other, value)), + map(([value]) => value), + ); +} diff --git a/tools/gulp/gulpfile.ts b/tools/gulp/gulpfile.ts index eef231f298c6..0ea915c77660 100644 --- a/tools/gulp/gulpfile.ts +++ b/tools/gulp/gulpfile.ts @@ -7,7 +7,8 @@ import { examplesPackage, materialExperimentalPackage, materialPackage, - momentAdapterPackage + momentAdapterPackage, + youTubePlayerPackage } from './packages'; createPackageBuildTasks(cdkPackage); @@ -16,6 +17,7 @@ createPackageBuildTasks(materialPackage); createPackageBuildTasks(materialExperimentalPackage); createPackageBuildTasks(examplesPackage, ['build-examples-module']); createPackageBuildTasks(momentAdapterPackage); +createPackageBuildTasks(youTubePlayerPackage); import './tasks/aot'; import './tasks/breaking-changes'; diff --git a/tools/gulp/packages.ts b/tools/gulp/packages.ts index 8f05b38af16c..d5ce1fc59550 100644 --- a/tools/gulp/packages.ts +++ b/tools/gulp/packages.ts @@ -2,6 +2,7 @@ import {BuildPackage} from 'material2-build-tools'; export const cdkPackage = new BuildPackage('cdk'); export const materialPackage = new BuildPackage('material', [cdkPackage]); +export const youTubePlayerPackage = new BuildPackage('youtube-player'); export const cdkExperimentalPackage = new BuildPackage('cdk-experimental', [cdkPackage]); export const materialExperimentalPackage = new BuildPackage('material-experimental', [cdkPackage, cdkExperimentalPackage, materialPackage]); @@ -32,6 +33,7 @@ materialPackage.hasSchematics = true; export const allBuildPackages = [ cdkPackage, materialPackage, + youTubePlayerPackage, cdkExperimentalPackage, materialExperimentalPackage, momentAdapterPackage, diff --git a/tools/gulp/tasks/aot.ts b/tools/gulp/tasks/aot.ts index e3e5bd8f9d6d..c0f508392d55 100644 --- a/tools/gulp/tasks/aot.ts +++ b/tools/gulp/tasks/aot.ts @@ -32,6 +32,7 @@ task('build-aot:release-packages', sequenceTask( [ 'cdk:build-release', 'material:build-release', + 'youtube-player:build-release', 'cdk-experimental:build-release', 'material-experimental:build-release', 'material-moment-adapter:build-release', diff --git a/tools/gulp/tasks/development.ts b/tools/gulp/tasks/development.ts index 22dc09a25195..5fa800bfde18 100644 --- a/tools/gulp/tasks/development.ts +++ b/tools/gulp/tasks/development.ts @@ -15,6 +15,7 @@ import { materialPackage, momentAdapterPackage, examplesPackage, + youTubePlayerPackage, } from '../packages'; import {watchFilesAndReload} from '../util/watch-files-reload'; @@ -64,6 +65,7 @@ task('build:devapp', sequenceTask( 'material:build-no-bundles', 'cdk-experimental:build-no-bundles', 'material-experimental:build-no-bundles', + 'youtube-player:build-no-bundles', 'material-moment-adapter:build-no-bundles', 'build-examples-module', // The examples module needs to be manually built before building examples package because @@ -95,6 +97,8 @@ task('stage-deploy:devapp', ['build:devapp'], () => { join(deployOutputDir, 'dist/packages/material')); copyFiles(materialExperimentalPackage.outputDir, '**/*.+(js|map)', join(deployOutputDir, 'dist/packages/material-experimental')); + copyFiles(youTubePlayerPackage.outputDir, '**/*.+(js|map)', + join(deployOutputDir, 'dist/packages/youtube-player')); copyFiles(cdkExperimentalPackage.outputDir, '**/*.+(js|map)', join(deployOutputDir, 'dist/packages/cdk-experimental')); copyFiles(materialPackage.outputDir, '**/prebuilt/*.+(css|map)', @@ -164,6 +168,10 @@ task(':watch:devapp', () => { watchFilesAndReload(join(materialExperimentalPackage.sourceDir, '**/*'), ['material-experimental:build-no-bundles']); + // Youtube player package watchers + watchFilesAndReload(join(youTubePlayerPackage.sourceDir, '**/*'), + ['youtube-player:build-no-bundles']); + // CDK experimental package watchers watchFilesAndReload(join(cdkExperimentalPackage.sourceDir, '**/*'), ['cdk-experimental:build-no-bundles']); diff --git a/tools/gulp/tasks/unit-test.ts b/tools/gulp/tasks/unit-test.ts index 074de0457c9b..bdf4f6c6f10c 100644 --- a/tools/gulp/tasks/unit-test.ts +++ b/tools/gulp/tasks/unit-test.ts @@ -19,6 +19,7 @@ task(':test:build', sequenceTask( 'material:build-no-bundles', 'cdk-experimental:build-no-bundles', 'material-experimental:build-no-bundles', + 'youtube-player:build-no-bundles', 'material-moment-adapter:build-no-bundles' )); diff --git a/tools/gulp/tasks/universal.ts b/tools/gulp/tasks/universal.ts index b4876e77e8d4..1456810c1271 100644 --- a/tools/gulp/tasks/universal.ts +++ b/tools/gulp/tasks/universal.ts @@ -41,6 +41,7 @@ task( 'material:build-release', 'cdk-experimental:build-release', 'material-experimental:build-release', + 'youtube-player:build-release', ], ['universal:copy-release', 'universal:copy-files'], ['universal:build-app-ts', 'universal:build-app-scss'], @@ -66,4 +67,5 @@ task('universal:copy-release', () => { copySync(join(releasesDir, 'material'), join(outDir, 'material')); copySync(join(releasesDir, 'cdk-experimental'), join(outDir, 'cdk-experimental')); copySync(join(releasesDir, 'material-experimental'), join(outDir, 'material-experimental')); + copySync(join(releasesDir, 'youtube-player'), join(outDir, 'youtube-player')); }); diff --git a/tools/package-tools/build-release.ts b/tools/package-tools/build-release.ts index 9baa70287c04..270a3bbe8f71 100644 --- a/tools/package-tools/build-release.ts +++ b/tools/package-tools/build-release.ts @@ -135,6 +135,7 @@ const packageDirs: string[] = [ 'cdk', 'material', 'material-experimental', + 'youtube-player', 'cdk-experimental', ]; diff --git a/tools/package-tools/rollup-globals.ts b/tools/package-tools/rollup-globals.ts index fc3ac9496104..4ad801be6195 100644 --- a/tools/package-tools/rollup-globals.ts +++ b/tools/package-tools/rollup-globals.ts @@ -30,6 +30,10 @@ const cdkExperimentalSecondaryEntryPoints = const materialExperimentalSecondaryEntryPoints = getSubdirectoryNames(join(buildConfig.packagesDir, 'material-experimental')); +/** List of potential secondary entry points for the youtube-player package. */ +const youTubePlayerSecondaryEntryPoints = + getSubdirectoryNames(join(buildConfig.packagesDir, 'youtube-player')); + /** Object with all cdk entry points in the format of Rollup globals. */ const rollupCdkEntryPoints = generateRollupEntryPoints('cdk', cdkSecondaryEntryPoints); @@ -40,6 +44,10 @@ const rollupMatEntryPoints = generateRollupEntryPoints('material', matSecondaryE const rollupMaterialExperimentalEntryPoints = generateRollupEntryPoints('material-experimental', materialExperimentalSecondaryEntryPoints); +/** Object with all youtube-player entry points in the format of Rollup globals. */ +const rollupYouTubePlayerEntryPoints = + generateRollupEntryPoints('youtube-player', youTubePlayerSecondaryEntryPoints); + /** Object with all cdk-experimental entry points in the format of Rollup globals. */ const rollupCdkExperimentalEntryPoints = generateRollupEntryPoints('cdk-experimental', cdkExperimentalSecondaryEntryPoints); @@ -106,11 +114,15 @@ export const rollupGlobals = { '@angular/material-experimental': 'ng.materialExperimental', '@angular/material-moment-adapter': 'ng.materialMomentAdapter', + // Miscellaneous components + '@angular/youtube-player': 'ng.youtubePlayer', + // Include secondary entry-points of the cdk and material packages ...rollupCdkEntryPoints, ...rollupMatEntryPoints, ...rollupCdkExperimentalEntryPoints, ...rollupMaterialExperimentalEntryPoints, + ...rollupYouTubePlayerEntryPoints, '@angular/cdk/private/testing': 'ng.cdk.private.testing', '@angular/cdk/private/testing/e2e': 'ng.cdk.private.testing.e2e', diff --git a/tools/release/release-output/release-packages.ts b/tools/release/release-output/release-packages.ts index 2dc47f7b4dea..da4cba1fb603 100644 --- a/tools/release/release-output/release-packages.ts +++ b/tools/release/release-output/release-packages.ts @@ -2,6 +2,7 @@ export const releasePackages = [ 'cdk', 'material', + 'youtube-player', 'cdk-experimental', 'material-experimental', 'material-moment-adapter' diff --git a/yarn.lock b/yarn.lock index 03bf2b7bb45e..99e5b7ee938b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1521,6 +1521,11 @@ dependencies: "@types/node" "*" +"@types/youtube@^0.0.38": + version "0.0.38" + resolved "https://registry.yarnpkg.com/@types/youtube/-/youtube-0.0.38.tgz#04aa5efa61f4d52e9cf682575a9dfed58f31e8c1" + integrity sha512-rS0nLlnnZIvOfBNT4957rkKRMHjHxutoVhSEzowocYgNiTMVBmN+Hbkf/L3TeGMMtts9uKZ9gw7O2JLHREVnIw== + "@types/z-schema@3.16.31": version "3.16.31" resolved "https://registry.yarnpkg.com/@types/z-schema/-/z-schema-3.16.31.tgz#2eb1d00a5e4ec3fa58c76afde12e182b66dc5c1c"