Skip to content

Commit be9365d

Browse files
Merge pull request #34 from puzzle-js/css-assets
Css assets
2 parents 8bb7a43 + 9178c4d commit be9365d

File tree

7 files changed

+182
-49
lines changed

7 files changed

+182
-49
lines changed

package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "@puzzle-js/client-lib",
33
"main": "dist/index.js",
4-
"version": "1.5.0",
4+
"version": "1.6.0",
55
"author": "<emre.kul@trendyol.com>",
66
"license": "MIT",
77
"repository": {

src/assetHelper.ts

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { RESOURCE_TYPE } from "./enums";
12
import {IPageLibAsset} from "./types";
23

34
export class AssetHelper {
@@ -46,15 +47,45 @@ export class AssetHelper {
4647
return this.promises[asset.name].promise;
4748
}
4849

49-
static loadJsSeries(scripts: IPageLibAsset[]) {
50-
for (let i = 0, p: any = Promise.resolve(); i < scripts.length; i++) {
50+
static loadCSS(asset: IPageLibAsset): Promise<any> {
51+
const linkTag: any = window.document.createElement('link');
52+
53+
if (!this.promises[asset.name]) {
54+
this.promises[asset.name] = this.createDeferred();
55+
linkTag.rel = 'stylesheet';
56+
linkTag.setAttribute('puzzle-asset', asset.name);
57+
linkTag.href = asset.link;
58+
linkTag.onload = () => {
59+
this.promises[asset.name].resolve();
60+
};
61+
62+
window.document.head.appendChild(linkTag);
63+
}
64+
65+
return this.promises[asset.name].promise;
66+
}
67+
68+
static loadAssetSeries(assets: IPageLibAsset[], callback?: Function) {
69+
for (let i = 0, p: any = Promise.resolve(); i < assets.length; i++) {
5170
p = p.then(() => new Promise(resolve => {
52-
const assetLoading = AssetHelper.loadJs(scripts[i]);
53-
assetLoading.then(() => {
54-
resolve();
55-
});
71+
const asset = assets[i];
72+
if (asset.type === RESOURCE_TYPE.JS) {
73+
const assetLoading = AssetHelper.loadJs(asset);
74+
assetLoading.then(() => {
75+
resolve(null);
76+
});
77+
} else if (asset.type === RESOURCE_TYPE.CSS) {
78+
const assetLoading = AssetHelper.loadCSS(asset);
79+
assetLoading.then(() => {
80+
resolve(null);
81+
});
82+
}
83+
}
84+
)).then(() => {
85+
if (callback && assets.length - 1 === i) {
86+
callback();
5687
}
57-
));
88+
});
5889
}
5990
}
6091
}

src/core.ts

Lines changed: 44 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import {Module} from "./module";
2-
import {EVENT, RESOURCE_LOADING_TYPE} from "./enums";
2+
import {EVENT, RESOURCE_LOADING_TYPE, RESOURCE_TYPE} from "./enums";
33
import {IPageFragmentConfig, IPageLibAsset, IPageLibConfiguration} from "./types";
44
import {on} from "./decorators";
55
import {AssetHelper} from "./assetHelper";
@@ -63,9 +63,9 @@ export class Core extends Module {
6363
static loadAssetsOnFragment(fragmentName: string) {
6464
const onFragmentRenderAssets = Core.__pageConfiguration.assets.filter(asset => asset.fragment === fragmentName && asset.loadMethod === RESOURCE_LOADING_TYPE.ON_FRAGMENT_RENDER && !asset.preLoaded);
6565

66-
const scripts = Core.createLoadQueue(onFragmentRenderAssets);
66+
const assets = Core.createLoadQueue(onFragmentRenderAssets);
6767

68-
AssetHelper.loadJsSeries(scripts);
68+
AssetHelper.loadAssetSeries(assets);
6969
}
7070

7171
@on(EVENT.ON_PAGE_LOAD)
@@ -78,9 +78,9 @@ export class Core extends Module {
7878
return false;
7979
});
8080

81-
const scripts = Core.createLoadQueue(onFragmentRenderAssets);
81+
const assets = Core.createLoadQueue(onFragmentRenderAssets);
8282

83-
AssetHelper.loadJsSeries(scripts);
83+
AssetHelper.loadAssetSeries(assets);
8484
}
8585

8686
@on(EVENT.ON_PAGE_LOAD)
@@ -174,18 +174,19 @@ export class Core extends Module {
174174
}
175175
}
176176

177-
Object.keys(res).forEach(key => {
178-
if (!key.startsWith('$')) {
179-
const container = document.querySelector(this.getFragmentContainerSelector(fragment, key));
180-
if (container) {
181-
this.setEvalInnerHtml(container, res[key],container.tagName === "META");
177+
const fragmentAssets = Core.__pageConfiguration.assets.filter(asset => asset.fragment === fragment.name);
178+
const assets = Core.createLoadQueue(fragmentAssets, true);
179+
180+
AssetHelper.loadAssetSeries(assets, () => {
181+
Object.keys(res).forEach(key => {
182+
if (!key.startsWith('$')) {
183+
const container = document.querySelector(this.getFragmentContainerSelector(fragment, key));
184+
if (container) {
185+
this.setEvalInnerHtml(container, res[key],container.tagName === "META");
186+
}
182187
}
183-
}
188+
});
184189
});
185-
186-
const fragmentAssets = Core.__pageConfiguration.assets.filter(asset => asset.fragment === fragment.name);
187-
const scripts = Core.createLoadQueue(fragmentAssets, true);
188-
AssetHelper.loadJsSeries(scripts);
189190
}
190191

191192
private static getFragmentContainerSelector(fragment: IPageFragmentConfig, partial: string) {
@@ -231,32 +232,38 @@ export class Core extends Module {
231232
assets.forEach(asset => {
232233
const fragment = Core.__pageConfiguration.fragments.find(i => i.name === asset.fragment);
233234
if (asyncQueue || (fragment && !fragment.clientAsync)) {
234-
if (!asset.preLoaded) {
235-
asset.preLoaded = true;
236-
asset.defer = true;
237-
238-
if (asset.dependent) {
239-
asset.dependent.forEach((dependencyName) => {
240-
const dependency = Core.__pageConfiguration.dependencies.filter(dependency => dependency.name === dependencyName);
241-
const dependencyContent = dependency[0];
242-
if (fragment && fragment.clientAsync) {
243-
if (dependencyContent) {
244-
if (loadList.indexOf(dependencyContent) === -1) {
245-
loadList.push(dependencyContent);
246-
dependencyContent.preLoaded = true;
235+
if (asset.type === RESOURCE_TYPE.JS) {
236+
if (!asset.preLoaded) {
237+
asset.preLoaded = true;
238+
asset.defer = true;
239+
240+
if (asset.dependent) {
241+
asset.dependent.forEach((dependencyName) => {
242+
const dependency = Core.__pageConfiguration.dependencies.filter(dependency => dependency.name === dependencyName);
243+
const dependencyContent = dependency[0];
244+
if (fragment && fragment.clientAsync) {
245+
if (dependencyContent) {
246+
if (loadList.indexOf(dependencyContent) === -1) {
247+
loadList.push(dependencyContent);
248+
dependencyContent.preLoaded = true;
249+
}
247250
}
248-
}
249-
} else {
250-
if (dependencyContent && !dependencyContent.preLoaded) {
251-
if (loadList.indexOf(dependencyContent) === -1) {
252-
loadList.push(dependencyContent);
253-
dependencyContent.preLoaded = true;
251+
} else {
252+
if (dependencyContent && !dependencyContent.preLoaded) {
253+
if (loadList.indexOf(dependencyContent) === -1) {
254+
loadList.push(dependencyContent);
255+
dependencyContent.preLoaded = true;
256+
}
254257
}
255258
}
256-
}
257-
});
259+
});
260+
}
261+
262+
if (loadList.indexOf(asset) === -1) {
263+
loadList.push(asset);
264+
}
258265
}
259-
266+
} else if (asset.type === RESOURCE_TYPE.CSS) {
260267
if (loadList.indexOf(asset) === -1) {
261268
loadList.push(asset);
262269
}

src/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ export interface IPageFragmentConfig {
3232
chunked: boolean;
3333
clientAsync: boolean;
3434
clientAsyncForce: boolean | undefined;
35+
criticalCss: boolean | undefined;
3536
onDemand: boolean | undefined;
3637
asyncDecentralized: boolean;
3738
attributes: { [name: string]: string };

test/assetHelper.spec.ts

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,4 +51,83 @@ describe('Module - Asset Helper', () => {
5151
expect(global.window.document.body.children.length).to.eq(1);
5252
});
5353

54+
it('should append link tag without promise', () => {
55+
// arrange
56+
const asset: IPageLibAsset = {
57+
name: faker.lorem.word(),
58+
loadMethod: RESOURCE_LOADING_TYPE.ON_FRAGMENT_RENDER,
59+
fragment: faker.lorem.word(),
60+
dependent: [],
61+
type: RESOURCE_TYPE.CSS,
62+
link: faker.lorem.word(),
63+
preLoaded: false
64+
};
65+
66+
// act
67+
const result = AssetHelper.loadCSS(asset);
68+
69+
// assert
70+
expect(global.window.document.head.children.length).to.eq(1);
71+
});
72+
73+
74+
it('should load given js assets', async (done) => {
75+
// arrange
76+
const spy = sinon.spy();
77+
const assets: IPageLibAsset[] = [
78+
{
79+
name: faker.lorem.word(),
80+
loadMethod: RESOURCE_LOADING_TYPE.ON_FRAGMENT_RENDER,
81+
fragment: faker.lorem.word(),
82+
dependent: [],
83+
type: RESOURCE_TYPE.JS,
84+
link: faker.lorem.word(),
85+
preLoaded: false,
86+
defer: true,
87+
}
88+
];
89+
90+
// act
91+
await AssetHelper.loadAssetSeries(assets, spy);
92+
93+
AssetHelper.promises[assets[0].name].resolve();
94+
95+
// assert
96+
expect(global.window.document.body.children.length).to.eq(1);
97+
98+
setTimeout(() => {
99+
expect(spy.calledOnce).to.eq(true);
100+
done();
101+
});
102+
});
103+
104+
it('should load given css assets', async (done) => {
105+
// arrange
106+
const spy = sinon.spy();
107+
const assets: IPageLibAsset[] = [
108+
{
109+
name: faker.lorem.word(),
110+
loadMethod: RESOURCE_LOADING_TYPE.ON_FRAGMENT_RENDER,
111+
fragment: faker.lorem.word(),
112+
dependent: [],
113+
type: RESOURCE_TYPE.CSS,
114+
link: faker.lorem.word(),
115+
preLoaded: false
116+
}
117+
];
118+
119+
// act
120+
await AssetHelper.loadAssetSeries(assets, spy);
121+
122+
AssetHelper.promises[assets[0].name].resolve();
123+
124+
// assert
125+
expect(global.window.document.head.children.length).to.eq(1);
126+
127+
setTimeout(() => {
128+
expect(spy.calledOnce).to.eq(true);
129+
done();
130+
});
131+
});
132+
54133
});

test/core.spec.ts

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,6 @@ describe('Module - Core', () => {
6464

6565
Core.onVariables(fragmentName, variableName, windowModel);
6666

67-
console.log(window[variableName]);
68-
6967
expect(window[variableName]).to.deep.eq(windowModel);
7068
});
7169

@@ -87,7 +85,7 @@ describe('Module - Core', () => {
8785
expect(global.window.document.body.innerHTML).to.eq(`<div id="${fragmentContainerId}">${fragmentContent}</div>`);
8886
});
8987

90-
it('should create true load queue for js assets', function () {
88+
it('should create true load queue for js and css assets', function () {
9189
const assets = [
9290
{
9391
name: 'bundle1',
@@ -97,6 +95,13 @@ describe('Module - Core', () => {
9795
fragment: 'test',
9896
loadMethod: RESOURCE_LOADING_TYPE.ON_PAGE_RENDER,
9997
type: RESOURCE_TYPE.JS
98+
},
99+
{
100+
name: 'css1',
101+
link: 'css1.js',
102+
fragment: 'test',
103+
loadMethod: RESOURCE_LOADING_TYPE.ON_PAGE_RENDER,
104+
type: RESOURCE_TYPE.CSS
100105
}
101106
] as IPageLibAsset[];
102107
const dependencies = [
@@ -131,6 +136,13 @@ describe('Module - Core', () => {
131136
loadMethod: 2,
132137
type: 1,
133138
defer: true
139+
},
140+
{
141+
name: 'css1',
142+
link: 'css1.js',
143+
fragment: 'test',
144+
loadMethod: 2,
145+
type: 0
134146
}
135147
]);
136148
});
@@ -203,6 +215,7 @@ describe('Module - Core', () => {
203215
clientAsync: true,
204216
clientAsyncForce: undefined,
205217
onDemand: undefined,
218+
criticalCss: undefined,
206219
source: undefined,
207220
asyncDecentralized: false
208221
}],
@@ -257,6 +270,7 @@ describe('Module - Core', () => {
257270
clientAsync: true,
258271
clientAsyncForce: true,
259272
onDemand: undefined,
273+
criticalCss: undefined,
260274
source: undefined,
261275
asyncDecentralized: false
262276
}],
@@ -328,6 +342,7 @@ describe('Module - Core', () => {
328342
clientAsync: true,
329343
clientAsyncForce: undefined,
330344
onDemand: undefined,
345+
criticalCss: undefined,
331346
source: undefined,
332347
asyncDecentralized: false,
333348
asyncLoaded: true

0 commit comments

Comments
 (0)