From 3d7745a00c4973090f6699a2fadc289be8b204b0 Mon Sep 17 00:00:00 2001 From: Tolga Cesur Date: Tue, 31 Aug 2021 09:46:54 +0300 Subject: [PATCH 1/4] Load css assets and Add criticalCss attribute --- src/assetHelper.ts | 45 ++++++++++++++++++++++---- src/core.ts | 81 +++++++++++++++++++++++++--------------------- src/types.ts | 1 + 3 files changed, 83 insertions(+), 44 deletions(-) diff --git a/src/assetHelper.ts b/src/assetHelper.ts index 384f13e..a1dea3e 100644 --- a/src/assetHelper.ts +++ b/src/assetHelper.ts @@ -1,3 +1,4 @@ +import { RESOURCE_TYPE } from "./enums"; import {IPageLibAsset} from "./types"; export class AssetHelper { @@ -46,15 +47,45 @@ export class AssetHelper { return this.promises[asset.name].promise; } - static loadJsSeries(scripts: IPageLibAsset[]) { - for (let i = 0, p: any = Promise.resolve(); i < scripts.length; i++) { + static loadCSS(asset: IPageLibAsset): Promise { + const linkTag: any = window.document.createElement('link'); + + if (!this.promises[asset.name]) { + this.promises[asset.name] = this.createDeferred(); + linkTag.rel = 'stylesheet'; + linkTag.setAttribute('puzzle-asset', asset.name); + linkTag.href = asset.link; + linkTag.onload = () => { + this.promises[asset.name].resolve(); + }; + + window.document.head.appendChild(linkTag); + } + + return this.promises[asset.name].promise; + } + + static loadAssetSeries(assets: IPageLibAsset[], callback?: Function) { + for (let i = 0, p: any = Promise.resolve(); i < assets.length; i++) { p = p.then(() => new Promise(resolve => { - const assetLoading = AssetHelper.loadJs(scripts[i]); - assetLoading.then(() => { - resolve(); - }); + const asset = assets[i]; + if (asset.type === RESOURCE_TYPE.JS) { + const assetLoading = AssetHelper.loadJs(asset); + assetLoading.then(() => { + resolve(null); + }); + } else if (asset.type === RESOURCE_TYPE.CSS) { + const assetLoading = AssetHelper.loadCSS(asset); + assetLoading.then(() => { + resolve(null); + }); + } + } + )).then(() => { + if (callback && assets.length - 1 === i) { + callback(); } - )); + }); } } } diff --git a/src/core.ts b/src/core.ts index 3bb9227..232a76c 100644 --- a/src/core.ts +++ b/src/core.ts @@ -1,5 +1,5 @@ import {Module} from "./module"; -import {EVENT, RESOURCE_LOADING_TYPE} from "./enums"; +import {EVENT, RESOURCE_LOADING_TYPE, RESOURCE_TYPE} from "./enums"; import {IPageFragmentConfig, IPageLibAsset, IPageLibConfiguration} from "./types"; import {on} from "./decorators"; import {AssetHelper} from "./assetHelper"; @@ -63,9 +63,9 @@ export class Core extends Module { static loadAssetsOnFragment(fragmentName: string) { const onFragmentRenderAssets = Core.__pageConfiguration.assets.filter(asset => asset.fragment === fragmentName && asset.loadMethod === RESOURCE_LOADING_TYPE.ON_FRAGMENT_RENDER && !asset.preLoaded); - const scripts = Core.createLoadQueue(onFragmentRenderAssets); + const assets = Core.createLoadQueue(onFragmentRenderAssets); - AssetHelper.loadJsSeries(scripts); + AssetHelper.loadAssetSeries(assets); } @on(EVENT.ON_PAGE_LOAD) @@ -78,9 +78,9 @@ export class Core extends Module { return false; }); - const scripts = Core.createLoadQueue(onFragmentRenderAssets); + const assets = Core.createLoadQueue(onFragmentRenderAssets); - AssetHelper.loadJsSeries(scripts); + AssetHelper.loadAssetSeries(assets); } @on(EVENT.ON_PAGE_LOAD) @@ -174,18 +174,19 @@ export class Core extends Module { } } - Object.keys(res).forEach(key => { - if (!key.startsWith('$')) { - const container = document.querySelector(this.getFragmentContainerSelector(fragment, key)); - if (container) { - this.setEvalInnerHtml(container, res[key],container.tagName === "META"); + const fragmentAssets = Core.__pageConfiguration.assets.filter(asset => asset.fragment === fragment.name); + const assets = Core.createLoadQueue(fragmentAssets, true); + + AssetHelper.loadAssetSeries(assets, () => { + Object.keys(res).forEach(key => { + if (!key.startsWith('$')) { + const container = document.querySelector(this.getFragmentContainerSelector(fragment, key)); + if (container) { + this.setEvalInnerHtml(container, res[key],container.tagName === "META"); + } } - } + }); }); - - const fragmentAssets = Core.__pageConfiguration.assets.filter(asset => asset.fragment === fragment.name); - const scripts = Core.createLoadQueue(fragmentAssets, true); - AssetHelper.loadJsSeries(scripts); } private static getFragmentContainerSelector(fragment: IPageFragmentConfig, partial: string) { @@ -231,32 +232,38 @@ export class Core extends Module { assets.forEach(asset => { const fragment = Core.__pageConfiguration.fragments.find(i => i.name === asset.fragment); if (asyncQueue || (fragment && !fragment.clientAsync)) { - if (!asset.preLoaded) { - asset.preLoaded = true; - asset.defer = true; - - if (asset.dependent) { - asset.dependent.forEach((dependencyName) => { - const dependency = Core.__pageConfiguration.dependencies.filter(dependency => dependency.name === dependencyName); - const dependencyContent = dependency[0]; - if (fragment && fragment.clientAsync) { - if (dependencyContent) { - if (loadList.indexOf(dependencyContent) === -1) { - loadList.push(dependencyContent); - dependencyContent.preLoaded = true; + if (asset.type === RESOURCE_TYPE.JS) { + if (!asset.preLoaded) { + asset.preLoaded = true; + asset.defer = true; + + if (asset.dependent) { + asset.dependent.forEach((dependencyName) => { + const dependency = Core.__pageConfiguration.dependencies.filter(dependency => dependency.name === dependencyName); + const dependencyContent = dependency[0]; + if (fragment && fragment.clientAsync) { + if (dependencyContent) { + if (loadList.indexOf(dependencyContent) === -1) { + loadList.push(dependencyContent); + dependencyContent.preLoaded = true; + } } - } - } else { - if (dependencyContent && !dependencyContent.preLoaded) { - if (loadList.indexOf(dependencyContent) === -1) { - loadList.push(dependencyContent); - dependencyContent.preLoaded = true; + } else { + if (dependencyContent && !dependencyContent.preLoaded) { + if (loadList.indexOf(dependencyContent) === -1) { + loadList.push(dependencyContent); + dependencyContent.preLoaded = true; + } } } - } - }); + }); + } + + if (loadList.indexOf(asset) === -1) { + loadList.push(asset); + } } - + } else if (asset.type === RESOURCE_TYPE.CSS) { if (loadList.indexOf(asset) === -1) { loadList.push(asset); } diff --git a/src/types.ts b/src/types.ts index fde1ce7..7ef4e74 100644 --- a/src/types.ts +++ b/src/types.ts @@ -32,6 +32,7 @@ export interface IPageFragmentConfig { chunked: boolean; clientAsync: boolean; clientAsyncForce: boolean | undefined; + criticalCss: boolean | undefined; onDemand: boolean | undefined; asyncDecentralized: boolean; attributes: { [name: string]: string }; From f416c47213cef48d3b7a34669e9ffb94c64cc7c5 Mon Sep 17 00:00:00 2001 From: Tolga Cesur Date: Tue, 31 Aug 2021 09:47:08 +0300 Subject: [PATCH 2/4] Add tests --- test/assetHelper.spec.ts | 19 +++++++++++++++++++ test/core.spec.ts | 21 ++++++++++++++++++--- 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/test/assetHelper.spec.ts b/test/assetHelper.spec.ts index e12fe93..9f25ef9 100644 --- a/test/assetHelper.spec.ts +++ b/test/assetHelper.spec.ts @@ -51,4 +51,23 @@ describe('Module - Asset Helper', () => { expect(global.window.document.body.children.length).to.eq(1); }); + it('should append link tag without promise', () => { + // arrange + const asset: IPageLibAsset = { + name: faker.lorem.word(), + loadMethod: RESOURCE_LOADING_TYPE.ON_FRAGMENT_RENDER, + fragment: faker.lorem.word(), + dependent: [], + type: RESOURCE_TYPE.CSS, + link: faker.lorem.word(), + preLoaded: false + }; + + // act + const result = AssetHelper.loadCSS(asset); + + // assert + expect(global.window.document.head.children.length).to.eq(1); + }); + }); diff --git a/test/core.spec.ts b/test/core.spec.ts index 183edf8..1c8d014 100644 --- a/test/core.spec.ts +++ b/test/core.spec.ts @@ -64,8 +64,6 @@ describe('Module - Core', () => { Core.onVariables(fragmentName, variableName, windowModel); - console.log(window[variableName]); - expect(window[variableName]).to.deep.eq(windowModel); }); @@ -87,7 +85,7 @@ describe('Module - Core', () => { expect(global.window.document.body.innerHTML).to.eq(`
${fragmentContent}
`); }); - it('should create true load queue for js assets', function () { + it('should create true load queue for js and css assets', function () { const assets = [ { name: 'bundle1', @@ -97,6 +95,13 @@ describe('Module - Core', () => { fragment: 'test', loadMethod: RESOURCE_LOADING_TYPE.ON_PAGE_RENDER, type: RESOURCE_TYPE.JS + }, + { + name: 'css1', + link: 'css1.js', + fragment: 'test', + loadMethod: RESOURCE_LOADING_TYPE.ON_PAGE_RENDER, + type: RESOURCE_TYPE.CSS } ] as IPageLibAsset[]; const dependencies = [ @@ -131,6 +136,13 @@ describe('Module - Core', () => { loadMethod: 2, type: 1, defer: true + }, + { + name: 'css1', + link: 'css1.js', + fragment: 'test', + loadMethod: 2, + type: 0 } ]); }); @@ -203,6 +215,7 @@ describe('Module - Core', () => { clientAsync: true, clientAsyncForce: undefined, onDemand: undefined, + criticalCss: undefined, source: undefined, asyncDecentralized: false }], @@ -257,6 +270,7 @@ describe('Module - Core', () => { clientAsync: true, clientAsyncForce: true, onDemand: undefined, + criticalCss: undefined, source: undefined, asyncDecentralized: false }], @@ -328,6 +342,7 @@ describe('Module - Core', () => { clientAsync: true, clientAsyncForce: undefined, onDemand: undefined, + criticalCss: undefined, source: undefined, asyncDecentralized: false, asyncLoaded: true From 84c704e5e0a170e14b13ffaa51d9e93136bd53d3 Mon Sep 17 00:00:00 2001 From: Tolga Cesur Date: Tue, 31 Aug 2021 09:47:21 +0300 Subject: [PATCH 3/4] Update version --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index a37a966..72adfa9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@puzzle-js/client-lib", - "version": "1.5.0", + "version": "1.6.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 7f24e9e..9bb8fff 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@puzzle-js/client-lib", "main": "dist/index.js", - "version": "1.5.0", + "version": "1.6.0", "author": "", "license": "MIT", "repository": { From 9178c4d99614e0b85cf293bea28af4c62e832efb Mon Sep 17 00:00:00 2001 From: Tolga Cesur Date: Tue, 31 Aug 2021 10:37:55 +0300 Subject: [PATCH 4/4] Add new cases --- test/assetHelper.spec.ts | 60 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/test/assetHelper.spec.ts b/test/assetHelper.spec.ts index 9f25ef9..ed8a68c 100644 --- a/test/assetHelper.spec.ts +++ b/test/assetHelper.spec.ts @@ -70,4 +70,64 @@ describe('Module - Asset Helper', () => { expect(global.window.document.head.children.length).to.eq(1); }); + + it('should load given js assets', async (done) => { + // arrange + const spy = sinon.spy(); + const assets: IPageLibAsset[] = [ + { + name: faker.lorem.word(), + loadMethod: RESOURCE_LOADING_TYPE.ON_FRAGMENT_RENDER, + fragment: faker.lorem.word(), + dependent: [], + type: RESOURCE_TYPE.JS, + link: faker.lorem.word(), + preLoaded: false, + defer: true, + } + ]; + + // act + await AssetHelper.loadAssetSeries(assets, spy); + + AssetHelper.promises[assets[0].name].resolve(); + + // assert + expect(global.window.document.body.children.length).to.eq(1); + + setTimeout(() => { + expect(spy.calledOnce).to.eq(true); + done(); + }); + }); + + it('should load given css assets', async (done) => { + // arrange + const spy = sinon.spy(); + const assets: IPageLibAsset[] = [ + { + name: faker.lorem.word(), + loadMethod: RESOURCE_LOADING_TYPE.ON_FRAGMENT_RENDER, + fragment: faker.lorem.word(), + dependent: [], + type: RESOURCE_TYPE.CSS, + link: faker.lorem.word(), + preLoaded: false + } + ]; + + // act + await AssetHelper.loadAssetSeries(assets, spy); + + AssetHelper.promises[assets[0].name].resolve(); + + // assert + expect(global.window.document.head.children.length).to.eq(1); + + setTimeout(() => { + expect(spy.calledOnce).to.eq(true); + done(); + }); + }); + });