Skip to content

Commit

Permalink
perf(theme:preloader): fix loading order issues (#1691)
Browse files Browse the repository at this point in the history
  • Loading branch information
cipchk committed Nov 10, 2023
1 parent 4472d48 commit f09c324
Show file tree
Hide file tree
Showing 11 changed files with 204 additions and 60 deletions.
2 changes: 2 additions & 0 deletions .eslintignore
Expand Up @@ -38,3 +38,5 @@ node_modules/

# yarn v2
.yarn

**/src/index.html
2 changes: 1 addition & 1 deletion packages/theme/public_api.ts
@@ -1,4 +1,4 @@
export { preloaderFinished } from './src/services/preloader/preloader';
export * from './src/services/preloader/preloader';
export * from './src/services/menu/interface';
export * from './src/services/menu/menu.service';
export * from './src/services/settings/types';
Expand Down
52 changes: 35 additions & 17 deletions packages/theme/src/services/preloader/preloader.spec.ts
@@ -1,6 +1,9 @@
import { DOCUMENT } from '@angular/common';
import { EnvironmentInjector, Injector, runInInjectionContext } from '@angular/core';

import { NzSafeAny } from 'ng-zorro-antd/core/types';

import { preloaderFinished } from './preloader';
import { stepPreloader } from './preloader';

describe('theme: preloader', () => {
let cached: NzSafeAny = {};
Expand All @@ -9,7 +12,7 @@ describe('theme: preloader', () => {
cached = {};
});

it('should be remove preloader', (done: () => void) => {
it('should be remove preloader', () => {
spyOn(document, 'querySelector').and.callFake((type: string) => {
if (cached[type]) return cached[type];
cached[type] = {
Expand All @@ -25,18 +28,25 @@ describe('theme: preloader', () => {
});
const body = document.querySelector('body')!;
const preloader = document.querySelector('.preloader')!;
preloaderFinished();
const injector = Injector.create({
providers: [
{
provide: DOCUMENT,
useFactory: () => {
return document;
}
}
]
}) as EnvironmentInjector;
let preloaderDone!: () => void;
runInInjectionContext(injector, () => (preloaderDone = stepPreloader()));
expect(body.style.overflow).toBe('hidden');

(window as NzSafeAny).appBootstrap();
setTimeout(() => {
expect(body.style.overflow).toBe('');
expect(preloader.className).toContain('preloader-hidden');
done();
}, 200);
preloaderDone();
expect(body.style.overflow).toBe('');
expect(preloader.className).toContain('preloader-hidden');
});

it('preloader value null when running --hmr', (done: () => void) => {
it('preloader value null when running --hmr', () => {
spyOn(document, 'querySelector').and.callFake((type: string) => {
if (type === '.preloader') return null;
if (cached[type]) return cached[type];
Expand All @@ -52,11 +62,19 @@ describe('theme: preloader', () => {
return cached[type];
});

preloaderFinished();
(window as NzSafeAny).appBootstrap();
setTimeout(() => {
expect(document.querySelector('.preloader')).toBeNull();
done();
}, 200);
const injector = Injector.create({
providers: [
{
provide: DOCUMENT,
useFactory: () => {
return document;
}
}
]
}) as EnvironmentInjector;
let preloaderDone!: () => void;
runInInjectionContext(injector, () => (preloaderDone = stepPreloader()));
preloaderDone();
expect(document.querySelector('.preloader')).toBeNull();
});
});
36 changes: 18 additions & 18 deletions packages/theme/src/services/preloader/preloader.ts
@@ -1,25 +1,25 @@
import type { NzSafeAny } from 'ng-zorro-antd/core/types';

export function preloaderFinished(): void {
const body = document.querySelector('body')!;
const preloader = document.querySelector('.preloader')!;
import { DOCUMENT } from '@angular/common';
import { inject } from '@angular/core';

export function stepPreloader(): () => void {
const doc: Document = inject(DOCUMENT);
const body = doc.querySelector('body')!;
body.style.overflow = 'hidden';
let done = false;

function remove(): void {
// preloader value null when running --hmr
if (!preloader) return;
preloader.addEventListener('transitionend', () => {
preloader.className = 'preloader-hidden';
});
return () => {
if (done) return;

preloader.className += ' preloader-hidden-add preloader-hidden-add-active';
}
done = true;
const preloader = doc.querySelector<HTMLElement>('.preloader');
if (preloader == null) return;

(window as NzSafeAny).appBootstrap = () => {
setTimeout(() => {
remove();
body.style.overflow = '';
}, 100);
const CLS = 'preloader-hidden';
preloader.addEventListener('transitionend', () => {
preloader.className = CLS;
});
preloader.className += ` ${CLS}-add ${CLS}-add-active`;
const body = doc.querySelector<HTMLBodyElement>('body')!;
body.style.overflow = '';
};
}
54 changes: 52 additions & 2 deletions schematics/ng-update/upgrade-rules/v17/index.spec.ts
@@ -1,5 +1,6 @@
import { SchematicTestRunner, UnitTestTree } from '@angular-devkit/schematics/testing';

import { tryAddFile } from '../../../utils';
import { createAlainApp, migrationCollection } from '../../../utils/testing';

describe('Schematic: ng-update: v17Rule', () => {
Expand Down Expand Up @@ -52,7 +53,8 @@ describe('Schematic: ng-update: v17Rule', () => {

it('#removeForRoot', async () => {
const globalConfigPath = '/projects/foo/src/app/global-config.module.ts';
tree.create(
tryAddFile(
tree,
globalConfigPath,
`
import { AlainThemeModule } from '@delon/theme';
Expand All @@ -67,9 +69,57 @@ describe('Schematic: ng-update: v17Rule', () => {

it('#replaceProvideAlainConfig', async () => {
const globalConfigPath = '/projects/foo/src/app/global-config.module.ts';
tree.create(globalConfigPath, `const alainProvides = [{ provide: ALAIN_CONFIG, useValue: alainConfig }];`);
tryAddFile(tree, globalConfigPath, `const alainProvides = [{ provide: ALAIN_CONFIG, useValue: alainConfig }];`);
await runMigration();
const content = tree.readContent(globalConfigPath);
expect(content).toContain(`provideAlainConfig(alainConfig)`);
});

it('#preloader', async () => {
const appCompPath = '/projects/foo/src/app/app.component.ts';
tryAddFile(
tree,
appCompPath,
`import { Component, ElementRef, OnInit, Renderer2 } from '@angular/core';
import { NavigationEnd, NavigationError, RouteConfigLoadStart, Router } from '@angular/router';
import { TitleService, VERSION as VERSION_ALAIN } from '@delon/theme';
import { environment } from '@env/environment';
import { NzModalService } from 'ng-zorro-antd/modal';
import { VERSION as VERSION_ZORRO } from 'ng-zorro-antd/version';
@Component({
selector: 'app-root',
})
export class AppComponent implements OnInit {
constructor(
el: ElementRef,
renderer: Renderer2,
private router: Router,
private titleSrv: TitleService,
private modalSrv: NzModalService
) {
renderer.setAttribute(el.nativeElement, 'ng-alain-version', VERSION_ALAIN.full);
renderer.setAttribute(el.nativeElement, 'ng-zorro-version', VERSION_ZORRO.full);
}
ngOnInit(): void {
let configLoad = false;
this.router.events.subscribe(ev => {
if (ev instanceof RouteConfigLoadStart) {
configLoad = true;
}
if (ev instanceof NavigationEnd) {
this.titleSrv.setTitle();
this.modalSrv.closeAll();
}
});
}
}
`
);
await runMigration();
const content = tree.readContent(appCompPath);
expect(content).toContain(`private donePreloader = stepPreloader();`);
expect(content).toContain(`this.donePreloader();`);
});
});
10 changes: 9 additions & 1 deletion schematics/ng-update/upgrade-rules/v17/index.ts
Expand Up @@ -2,6 +2,7 @@ import { chain, Rule, SchematicContext, Tree } from '@angular-devkit/schematics'
import { NodePackageInstallTask } from '@angular-devkit/schematics/tasks';

import { autoRegisterFormWidgets } from './autoRegisterFormWidgets';
import { updatePreloader } from './preloader';
import { removeForRoot } from './removeForRoot';
import { replaceProvideConfig } from './replaceProvideConfig';
import { logFinished, logInfo, logWarn } from '../../../utils';
Expand Down Expand Up @@ -31,6 +32,13 @@ export function v17Rule(): Rule {
return async (tree: Tree, context: SchematicContext) => {
UpgradeMainVersions(tree);
logInfo(context, `Upgrade dependency version number`);
return chain([removeForRoot(), autoRegisterFormWidgets(), replaceProvideConfig(), qr(), finished()]);
return chain([
removeForRoot(),
autoRegisterFormWidgets(),
replaceProvideConfig(),
updatePreloader(),
qr(),
finished()
]);
};
}
73 changes: 73 additions & 0 deletions schematics/ng-update/upgrade-rules/v17/preloader.ts
@@ -0,0 +1,73 @@
import { Rule, SchematicContext, Tree } from '@angular-devkit/schematics';

import { DEFAULT_WORKSPACE_PATH, logWarn, readJSON } from '../../../utils';

export function updatePreloader(): Rule {
return (tree: Tree, context: SchematicContext) => {
addESLintIgnore(tree);

const angularJson = readJSON(tree, DEFAULT_WORKSPACE_PATH);
const projectNames = Object.keys(angularJson.projects);
for (const name of projectNames) {
const sourceRoot = angularJson.projects[name].sourceRoot;
fixIndexHtml(tree, name, sourceRoot, context);
run(tree, name, sourceRoot, context);
}
};
}

function addESLintIgnore(tree: Tree): void {
const filePath = '/.eslintignore';
if (!tree.exists(filePath)) return;
const content = tree.readText(filePath);
if (!content.includes('**/src/index.html')) {
tree.overwrite(filePath, `${content}\n**/src/index.html`);
}
}

function fixIndexHtml(tree: Tree, _: string, sourceRoot: string, __: SchematicContext): void {
const indexPath = `${sourceRoot}/index.html`;
if (!tree.exists(indexPath)) return;

let indexContent = tree.readText(indexPath);

const selfClose = '<app-root />';
if (!indexContent.includes(selfClose)) return;

tree.overwrite(indexPath, indexContent.replace(selfClose, '<app-root></app-root>'));
}

function run(tree: Tree, name: string, sourceRoot: string, context: SchematicContext): void {
// main.ts
const mainPath = `${sourceRoot}/main.ts`;
if (!tree.exists(mainPath)) return;

let mainContent = tree.readText(mainPath);
['preloaderFinished();'].forEach(item => {
if (mainContent.includes(item)) mainContent = mainContent.replace(item, '');
});
tree.overwrite(mainPath, mainContent);

// app
const appPath = `${sourceRoot}/app/app.component.ts`;
if (!tree.exists(appPath)) return;
const appContent = tree.readText(appPath);
if (appContent.includes(', stepPreloader')) return;

const appContentLines = appContent.split('\n');
const importIndex = appContentLines.findIndex(line => line.includes(', VERSION as VERSION_ALAIN'));
const addIndex = appContentLines.findIndex(line => line.includes('export class AppComponent'));
const callDoneIndex = appContentLines.findIndex(line => line.includes('if (ev instanceof NavigationEnd) {'));
if (importIndex === -1 || addIndex === -1 || callDoneIndex === -1) return;

appContentLines[importIndex] = appContentLines[importIndex].replace(
', VERSION as VERSION_ALAIN',
', VERSION as VERSION_ALAIN, stepPreloader'
);
appContentLines.splice(addIndex + 1, 0, 'private donePreloader = stepPreloader();');
appContentLines.splice(callDoneIndex + 2, 0, 'this.donePreloader();');

tree.overwrite(appPath, appContentLines.join('\n'));

logWarn(context, `Upgrade preloader in ${name} project`);
}
4 changes: 2 additions & 2 deletions schematics/ng-update/upgrade-rules/v17/removeForRoot.ts
Expand Up @@ -26,11 +26,11 @@ function removeAlainThemeForRoot(tree: Tree, name: string, sourceRoot: string, c
logInfo(context, `Remove ${forRoot} in ${name} project`);
}

function removeAlainThemeForChild(tree: Tree, name: string, _: string, context: SchematicContext): void {
function removeAlainThemeForChild(tree: Tree, name: string, sourceRoot: string, context: SchematicContext): void {
const forChild = 'AlainThemeModule.forChild()';

tree.visit((path, entry) => {
if (!entry || !path.endsWith('.ts')) return;
if (!entry || !path.endsWith('.ts') || !path.startsWith(sourceRoot)) return;

const content = tree.readText(path);
if (!content.includes(forChild)) return;
Expand Down
6 changes: 5 additions & 1 deletion src/app/app.component.ts
Expand Up @@ -2,7 +2,7 @@ import { BreakpointObserver } from '@angular/cdk/layout';
import { Component, ElementRef, HostBinding, Inject, Renderer2 } from '@angular/core';
import { NavigationEnd, Router } from '@angular/router';

import { ALAIN_I18N_TOKEN, DrawerHelper, TitleService, VERSION as VERSION_ALAIN } from '@delon/theme';
import { ALAIN_I18N_TOKEN, DrawerHelper, TitleService, VERSION as VERSION_ALAIN, stepPreloader } from '@delon/theme';
import { VERSION as VERSION_ZORRO } from 'ng-zorro-antd/version';

import { I18NService, MetaService, MobileService } from '@core';
Expand Down Expand Up @@ -37,9 +37,13 @@ export class AppComponent {
mobileSrv.next(this.isMobile);
});

const done = stepPreloader();

router.events.subscribe(evt => {
if (!(evt instanceof NavigationEnd)) return;

done();

dh.closeAll();

const url = evt.url.split('#')[0].split('?')[0];
Expand Down
10 changes: 6 additions & 4 deletions src/index.html
Expand Up @@ -46,13 +46,16 @@
<link rel="apple-touch-icon-precomposed" sizes="144x144" href="./assets/img/logo-color.png">
<link rel="manifest" href="manifest.json">
<meta name="theme-color" content="#1976d2" />
<style>
.preloader {position:absolute;top:0;right:0;bottom:0;left:0;display:flex;align-items:center;justify-content:center;font-size:14px;font-family:Helvetica;}
.preloader-hidden-add{display:block;opacity:1}.preloader-hidden-add-active{opacity:0}.preloader-hidden{display:none}
</style>
</head>

<body ontouchstart>
<noscript>Please enable JavaScript to continue using this application.</noscript>
<app-root>
<div style="position:absolute;top:0;right:0;bottom:0;left:0;display:flex;align-items:center;justify-content:center;font-size:14px;font-family:Helvetica;">loading...</div>
</app-root>
<app-root></app-root>
<div class="preloader">Loading...</div>
<div id="_ie" style="position:absolute;top:0;right:0;bottom:0;left:0;z-index:9999;display:none;padding-top:32px;font-size:22px;text-align:center;background:#fff;">
Your browser is not supported for NG-ALAIN document site
</div>
Expand All @@ -66,7 +69,6 @@ <h1 style="font-size:22px;">KEEPING LOAD...</h1>
if (!!navigator.userAgent.match(/Trident/g) || !!navigator.userAgent.match(/MSIE/g)) {
document.querySelector('#_ie').style.display = 'block';
}

</script>
<script>
if (location.host !== 'ng-alain-doc.surge.sh') {
Expand Down

0 comments on commit f09c324

Please sign in to comment.