Speed up plugins loading

This commit is contained in:
Chocobozzz 2021-06-04 14:39:47 +02:00
parent 2989628b79
commit fc21ef5c62
No known key found for this signature in database
GPG Key ID: 583A612D890159BE
16 changed files with 63 additions and 95 deletions

View File

@ -11,7 +11,6 @@ export class MyLibraryComponent implements OnInit {
menuEntries: TopMenuDropdownParam[] = [] menuEntries: TopMenuDropdownParam[] = []
user: AuthUser user: AuthUser
private serverConfig: HTMLServerConfig private serverConfig: HTMLServerConfig
constructor ( constructor (

View File

@ -99,7 +99,6 @@ export class SearchFiltersComponent implements OnInit {
ngOnInit () { ngOnInit () {
this.serverConfig = this.serverService.getHTMLConfig() this.serverConfig = this.serverService.getHTMLConfig()
this.serverService.getVideoCategories().subscribe(categories => this.videoCategories = categories) this.serverService.getVideoCategories().subscribe(categories => this.videoCategories = categories)
this.serverService.getVideoLicences().subscribe(licences => this.videoLicences = licences) this.serverService.getVideoLicences().subscribe(licences => this.videoLicences = licences)
this.serverService.getVideoLanguages().subscribe(languages => this.videoLanguages = languages) this.serverService.getVideoLanguages().subscribe(languages => this.videoLanguages = languages)

View File

@ -287,7 +287,7 @@ export class SearchComponent implements OnInit, OnDestroy {
) )
} }
private getDefaultSearchTarget(): SearchTargetType { private getDefaultSearchTarget (): SearchTargetType {
const searchIndexConfig = this.serverConfig.search.searchIndex const searchIndexConfig = this.serverConfig.search.searchIndex
if (searchIndexConfig.enabled && (searchIndexConfig.isDefaultSearch || searchIndexConfig.disableLocalSearch)) { if (searchIndexConfig.enabled && (searchIndexConfig.isDefaultSearch || searchIndexConfig.disableLocalSearch)) {

View File

@ -194,7 +194,6 @@ export class VideoEditComponent implements OnInit, OnDestroy {
} }
}) })
this.serverConfig = this.serverService.getHTMLConfig() this.serverConfig = this.serverService.getHTMLConfig()
this.initialVideoCaptions = this.videoCaptions.map(c => c.language.id) this.initialVideoCaptions = this.videoCaptions.map(c => c.language.id)

View File

@ -181,7 +181,6 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
this.hasAlreadyAcceptedPrivacyConcern = true this.hasAlreadyAcceptedPrivacyConcern = true
} }
this.paramsSub = this.route.params.subscribe(routeParams => { this.paramsSub = this.route.params.subscribe(routeParams => {
const videoId = routeParams[ 'videoId' ] const videoId = routeParams[ 'videoId' ]
if (videoId) this.loadVideo(videoId) if (videoId) this.loadVideo(videoId)

View File

@ -3,7 +3,7 @@ import { RouteReuseStrategy, RouterModule, Routes, UrlMatchResult, UrlSegment }
import { CustomReuseStrategy } from '@app/core/routing/custom-reuse-strategy' import { CustomReuseStrategy } from '@app/core/routing/custom-reuse-strategy'
import { MenuGuards } from '@app/core/routing/menu-guard.service' import { MenuGuards } from '@app/core/routing/menu-guard.service'
import { POSSIBLE_LOCALES } from '@shared/core-utils/i18n' import { POSSIBLE_LOCALES } from '@shared/core-utils/i18n'
import { MetaGuard, PreloadSelectedModulesList } from './core' import { HomepageRedirectComponent, MetaGuard, PreloadSelectedModulesList } from './core'
import { EmptyComponent } from './empty.component' import { EmptyComponent } from './empty.component'
import { USER_USERNAME_REGEX_CHARACTERS } from './shared/form-validators/user-validators' import { USER_USERNAME_REGEX_CHARACTERS } from './shared/form-validators/user-validators'
import { ActorRedirectGuard } from './shared/shared-main' import { ActorRedirectGuard } from './shared/shared-main'
@ -156,7 +156,7 @@ const routes: Routes = [
{ {
path: '', path: '',
component: EmptyComponent // Avoid 404, app component will redirect dynamically component: HomepageRedirectComponent
} }
] ]
@ -164,7 +164,7 @@ const routes: Routes = [
for (const locale of POSSIBLE_LOCALES) { for (const locale of POSSIBLE_LOCALES) {
routes.push({ routes.push({
path: locale, path: locale,
component: EmptyComponent component: HomepageRedirectComponent
}) })
} }

View File

@ -1,6 +1,5 @@
import { Hotkey, HotkeysService } from 'angular2-hotkeys' import { Hotkey, HotkeysService } from 'angular2-hotkeys'
import { concat } from 'rxjs' import { filter, map, pairwise, switchMap } from 'rxjs/operators'
import { filter, first, map, pairwise, switchMap } from 'rxjs/operators'
import { DOCUMENT, PlatformLocation, ViewportScroller } from '@angular/common' import { DOCUMENT, PlatformLocation, ViewportScroller } from '@angular/common'
import { AfterViewInit, Component, Inject, LOCALE_ID, OnInit, ViewChild } from '@angular/core' import { AfterViewInit, Component, Inject, LOCALE_ID, OnInit, ViewChild } from '@angular/core'
import { DomSanitizer, SafeHtml } from '@angular/platform-browser' import { DomSanitizer, SafeHtml } from '@angular/platform-browser'
@ -14,7 +13,7 @@ import { WelcomeModalComponent } from '@app/modal/welcome-modal.component'
import { NgbConfig, NgbModal } from '@ng-bootstrap/ng-bootstrap' import { NgbConfig, NgbModal } from '@ng-bootstrap/ng-bootstrap'
import { LoadingBarService } from '@ngx-loading-bar/core' import { LoadingBarService } from '@ngx-loading-bar/core'
import { peertubeLocalStorage } from '@root-helpers/peertube-web-storage' import { peertubeLocalStorage } from '@root-helpers/peertube-web-storage'
import { getShortLocale, is18nPath } from '@shared/core-utils/i18n' import { getShortLocale } from '@shared/core-utils/i18n'
import { BroadcastMessageLevel, HTMLServerConfig, ServerConfig, UserRole } from '@shared/models' import { BroadcastMessageLevel, HTMLServerConfig, ServerConfig, UserRole } from '@shared/models'
import { MenuService } from './core/menu/menu.service' import { MenuService } from './core/menu/menu.service'
import { POP_STATE_MODAL_DISMISS } from './helpers' import { POP_STATE_MODAL_DISMISS } from './helpers'
@ -75,7 +74,7 @@ export class AppComponent implements OnInit, AfterViewInit {
this.serverConfig = this.serverService.getHTMLConfig() this.serverConfig = this.serverService.getHTMLConfig()
this.loadPlugins() this.hooks.runAction('action:application.init', 'common')
this.themeService.initialize() this.themeService.initialize()
this.authService.loadClientCredentials() this.authService.loadClientCredentials()
@ -190,12 +189,6 @@ export class AppComponent implements OnInit, AfterViewInit {
} }
}) })
// Homepage redirection
navigationEndEvent.pipe(
map(() => window.location.pathname),
filter(pathname => !pathname || pathname === '/' || is18nPath(pathname))
).subscribe(() => this.redirectService.redirectToHomepage(true))
// Plugin hooks // Plugin hooks
navigationEndEvent.subscribe(e => { navigationEndEvent.subscribe(e => {
this.hooks.runAction('action:router.navigation-end', 'common', { path: e.url }) this.hooks.runAction('action:router.navigation-end', 'common', { path: e.url })
@ -268,12 +261,6 @@ export class AppComponent implements OnInit, AfterViewInit {
} }
} }
private async loadPlugins () {
this.pluginService.initializePlugins()
this.hooks.runAction('action:application.init', 'common')
}
private async openModalsIfNeeded () { private async openModalsIfNeeded () {
this.authService.userInformationLoaded this.authService.userInformationLoaded
.pipe( .pipe(

View File

@ -1,4 +1,5 @@
import 'focus-visible' import 'focus-visible'
import { tap } from 'rxjs/operators'
import { environment } from 'src/environments/environment' import { environment } from 'src/environments/environment'
import { APP_BASE_HREF, registerLocaleData } from '@angular/common' import { APP_BASE_HREF, registerLocaleData } from '@angular/common'
import { APP_INITIALIZER, NgModule } from '@angular/core' import { APP_INITIALIZER, NgModule } from '@angular/core'
@ -7,7 +8,7 @@ import { ServiceWorkerModule } from '@angular/service-worker'
import localeOc from '@app/helpers/locales/oc' import localeOc from '@app/helpers/locales/oc'
import { AppRoutingModule } from './app-routing.module' import { AppRoutingModule } from './app-routing.module'
import { AppComponent } from './app.component' import { AppComponent } from './app.component'
import { CoreModule, ServerService } from './core' import { CoreModule, PluginService, ServerService } from './core'
import { EmptyComponent } from './empty.component' import { EmptyComponent } from './empty.component'
import { HeaderComponent, SearchTypeaheadComponent, SuggestionComponent } from './header' import { HeaderComponent, SearchTypeaheadComponent, SuggestionComponent } from './header'
import { HighlightPipe } from './header/highlight.pipe' import { HighlightPipe } from './header/highlight.pipe'
@ -26,8 +27,14 @@ import { SharedUserInterfaceSettingsModule } from './shared/shared-user-settings
registerLocaleData(localeOc, 'oc') registerLocaleData(localeOc, 'oc')
export function loadConfigFactory (server: ServerService) { export function loadConfigFactory (server: ServerService, pluginService: PluginService) {
return () => server.loadHTMLConfig() return () => {
const result = server.loadHTMLConfig()
if (result) return result.pipe(tap(() => pluginService.initializePlugins()))
return pluginService.initializePlugins()
}
} }
@NgModule({ @NgModule({
@ -75,9 +82,9 @@ export function loadConfigFactory (server: ServerService) {
{ {
provide: APP_INITIALIZER, provide: APP_INITIALIZER,
useFactory: loadConfigFactory, useFactory: loadConfigFactory,
deps: [ ServerService ], deps: [ ServerService, PluginService ],
multi: true multi: true
} }
] ]
}) })
export class AppModule {} export class AppModule {}

View File

@ -14,7 +14,7 @@ import { throwIfAlreadyLoaded } from './module-import-guard'
import { Notifier } from './notification' import { Notifier } from './notification'
import { HtmlRendererService, LinkifierService, MarkdownService } from './renderer' import { HtmlRendererService, LinkifierService, MarkdownService } from './renderer'
import { RestExtractor, RestService } from './rest' import { RestExtractor, RestService } from './rest'
import { LoginGuard, MetaGuard, MetaService, RedirectService, UnloggedGuard, UserRightGuard } from './routing' import { HomepageRedirectComponent, LoginGuard, MetaGuard, MetaService, RedirectService, UnloggedGuard, UserRightGuard } from './routing'
import { CanDeactivateGuard } from './routing/can-deactivate-guard.service' import { CanDeactivateGuard } from './routing/can-deactivate-guard.service'
import { ServerConfigResolver } from './routing/server-config-resolver.service' import { ServerConfigResolver } from './routing/server-config-resolver.service'
import { ScopedTokensService } from './scoped-tokens' import { ScopedTokensService } from './scoped-tokens'
@ -36,13 +36,15 @@ import { LocalStorageService, ScreenService, SessionStorageService } from './wra
], ],
declarations: [ declarations: [
CheatSheetComponent CheatSheetComponent,
HomepageRedirectComponent
], ],
exports: [ exports: [
ToastModule, ToastModule,
CheatSheetComponent CheatSheetComponent,
HomepageRedirectComponent
], ],
providers: [ providers: [

View File

@ -1,3 +1,4 @@
import * as debug from 'debug'
import { Observable, of, ReplaySubject } from 'rxjs' import { Observable, of, ReplaySubject } from 'rxjs'
import { catchError, first, map, shareReplay } from 'rxjs/operators' import { catchError, first, map, shareReplay } from 'rxjs/operators'
import { HttpClient } from '@angular/common/http' import { HttpClient } from '@angular/common/http'
@ -24,7 +25,6 @@ import {
} from '@shared/models' } from '@shared/models'
import { environment } from '../../../environments/environment' import { environment } from '../../../environments/environment'
import { RegisterClientHelpers } from '../../../types/register-client-option.model' import { RegisterClientHelpers } from '../../../types/register-client-option.model'
import * as debug from 'debug'
const logger = debug('peertube:plugins') const logger = debug('peertube:plugins')
@ -33,8 +33,6 @@ export class PluginService implements ClientHook {
private static BASE_PLUGIN_API_URL = environment.apiUrl + '/api/v1/plugins' private static BASE_PLUGIN_API_URL = environment.apiUrl + '/api/v1/plugins'
private static BASE_PLUGIN_URL = environment.apiUrl + '/plugins' private static BASE_PLUGIN_URL = environment.apiUrl + '/plugins'
pluginsBuilt = new ReplaySubject<boolean>(1)
pluginsLoaded: { [ scope in PluginClientScope ]: ReplaySubject<boolean> } = { pluginsLoaded: { [ scope in PluginClientScope ]: ReplaySubject<boolean> } = {
common: new ReplaySubject<boolean>(1), common: new ReplaySubject<boolean>(1),
'admin-plugin': new ReplaySubject<boolean>(1), 'admin-plugin': new ReplaySubject<boolean>(1),
@ -79,30 +77,18 @@ export class PluginService implements ClientHook {
} }
initializePlugins () { initializePlugins () {
logger('Building plugin configuration') const config = this.server.getHTMLConfig()
this.plugins = config.plugin.registered
this.server.getConfig() this.buildScopeStruct()
.subscribe(config => {
this.plugins = config.plugin.registered
this.buildScopeStruct() this.ensurePluginsAreLoaded('common')
this.pluginsBuilt.next(true)
logger('Plugin configuration built')
})
} }
initializeCustomModal (customModal: CustomModalComponent) { initializeCustomModal (customModal: CustomModalComponent) {
this.customModal = customModal this.customModal = customModal
} }
ensurePluginsAreBuilt () {
return this.pluginsBuilt.asObservable()
.pipe(first(), shareReplay())
.toPromise()
}
ensurePluginsAreLoaded (scope: PluginClientScope) { ensurePluginsAreLoaded (scope: PluginClientScope) {
this.loadPluginsByScope(scope) this.loadPluginsByScope(scope)
@ -156,8 +142,6 @@ export class PluginService implements ClientHook {
logger('Loading scope %s', scope) logger('Loading scope %s', scope)
try { try {
await this.ensurePluginsAreBuilt()
if (!isReload) this.loadedScopes.push(scope) if (!isReload) this.loadedScopes.push(scope)
const toLoad = this.scopes[ scope ] const toLoad = this.scopes[ scope ]

View File

@ -0,0 +1,30 @@
import { Component, OnInit } from '@angular/core'
import { ActivatedRoute } from '@angular/router'
import { is18nPath } from '@shared/core-utils/i18n/i18n'
import { RedirectService } from './redirect.service'
/*
* We have to use a component instead of an homepage because of a weird issue when using router.navigate in guard
*
* Since we also want to use the `skipLocationChange` option, we cannot use a guard that returns a UrlTree
* See https://github.com/angular/angular/issues/27148
*/
@Component({
template: ''
})
export class HomepageRedirectComponent implements OnInit {
constructor (
private route: ActivatedRoute,
private redirectService: RedirectService
) { }
ngOnInit () {
const url = this.route.snapshot.url
if (url.length === 0 || is18nPath('/' + url[0])) {
this.redirectService.redirectToHomepage(true)
}
}
}

View File

@ -1,6 +1,7 @@
export * from './can-deactivate-guard.service' export * from './can-deactivate-guard.service'
export * from './custom-reuse-strategy' export * from './custom-reuse-strategy'
export * from './disable-for-reuse-hook' export * from './disable-for-reuse-hook'
export * from './homepage-redirect.component'
export * from './login-guard.service' export * from './login-guard.service'
export * from './menu-guard.service' export * from './menu-guard.service'
export * from './meta-guard.service' export * from './meta-guard.service'

View File

@ -42,7 +42,6 @@ export class RedirectService {
return this.defaultRoute return this.defaultRoute
} }
getDefaultTrendingAlgorithm () { getDefaultTrendingAlgorithm () {
return this.defaultTrendingAlgorithm return this.defaultTrendingAlgorithm
} }

View File

@ -44,7 +44,6 @@ export class ActorBannerEditComponent implements OnInit {
this.bannerFormat = $localize`ratio 6/1, recommended size: 1920x317, max size: ${getBytes(this.maxBannerSize)}, extensions: ${this.bannerExtensions}` this.bannerFormat = $localize`ratio 6/1, recommended size: 1920x317, max size: ${getBytes(this.maxBannerSize)}, extensions: ${this.bannerExtensions}`
} }
onBannerChange (input: HTMLInputElement) { onBannerChange (input: HTMLInputElement) {
this.bannerfileInput = new ElementRef(input) this.bannerfileInput = new ElementRef(input)

View File

@ -11,8 +11,8 @@ import {
RegisterClientVideoFieldOptions, RegisterClientVideoFieldOptions,
ServerConfigPlugin ServerConfigPlugin
} from '../../../shared/models' } from '../../../shared/models'
import { environment } from '../environments/environment'
import { ClientScript as ClientScriptModule } from '../types/client-script.model' import { ClientScript as ClientScriptModule } from '../types/client-script.model'
import { importModule } from './utils'
interface HookStructValue extends RegisterClientHookOptions { interface HookStructValue extends RegisterClientHookOptions {
plugin: ServerConfigPlugin plugin: ServerConfigPlugin
@ -101,7 +101,8 @@ function loadPlugin (options: {
console.log('Loading script %s of plugin %s.', clientScript.script, plugin.name) console.log('Loading script %s of plugin %s.', clientScript.script, plugin.name)
return importModule(clientScript.script) const absURL = (environment.apiUrl || window.location.origin) + clientScript.script
return import(/* webpackIgnore: true */ absURL)
.then((script: ClientScriptModule) => script.register({ registerHook, registerVideoField, registerSettingsScript, peertubeHelpers })) .then((script: ClientScriptModule) => script.register({ registerHook, registerVideoField, registerSettingsScript, peertubeHelpers }))
.then(() => sortHooksByPriority(hooks)) .then(() => sortHooksByPriority(hooks))
.catch(err => console.error('Cannot import or register plugin %s.', pluginInfo.plugin.name, err)) .catch(err => console.error('Cannot import or register plugin %s.', pluginInfo.plugin.name, err))

View File

@ -1,5 +1,3 @@
import { environment } from '../environments/environment'
function objectToUrlEncoded (obj: any) { function objectToUrlEncoded (obj: any) {
const str: string[] = [] const str: string[] = []
for (const key of Object.keys(obj)) { for (const key of Object.keys(obj)) {
@ -21,41 +19,6 @@ function copyToClipboard (text: string) {
document.body.removeChild(el) document.body.removeChild(el)
} }
// Thanks: https://github.com/uupaa/dynamic-import-polyfill
function importModule (path: string) {
return new Promise((resolve, reject) => {
const vector = '$importModule$' + Math.random().toString(32).slice(2)
const script = document.createElement('script')
const destructor = () => {
delete window[ vector ]
script.onerror = null
script.onload = null
script.remove()
URL.revokeObjectURL(script.src)
script.src = ''
}
script.defer = true
script.type = 'module'
script.onerror = () => {
reject(new Error(`Failed to import: ${path}`))
destructor()
}
script.onload = () => {
resolve(window[ vector ])
destructor()
}
const absURL = (environment.apiUrl || window.location.origin) + path
const loader = `import * as m from "${absURL}"; window.${vector} = m;` // export Module
const blob = new Blob([ loader ], { type: 'text/javascript' })
script.src = URL.createObjectURL(blob)
document.head.appendChild(script)
})
}
function wait (ms: number) { function wait (ms: number) {
return new Promise<void>(res => { return new Promise<void>(res => {
setTimeout(() => res(), ms) setTimeout(() => res(), ms)
@ -64,7 +27,6 @@ function wait (ms: number) {
export { export {
copyToClipboard, copyToClipboard,
importModule,
objectToUrlEncoded, objectToUrlEncoded,
wait wait
} }