@rws-framework/client 2.22.1 → 2.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +160 -872
- package/builder/webpack/loaders/rws_fast_ts_loader.js +14 -21
- package/cfg/build_steps/webpack/_loaders.js +136 -7
- package/package.json +1 -1
- package/src/components/_component.ts +28 -72
- package/src/components/_css_injection.ts +187 -0
|
@@ -11,10 +11,17 @@ module.exports = async function(content) {
|
|
|
11
11
|
let processedContent = content;
|
|
12
12
|
const filePath = this.resourcePath;
|
|
13
13
|
const isDev = this._compiler.options.mode === 'development';
|
|
14
|
-
let isIgnored = false;
|
|
15
|
-
let isDebugged = false;
|
|
16
14
|
// timingStart('decorator_extraction');
|
|
17
|
-
const decoratorExtract = LoadersHelper.
|
|
15
|
+
const decoratorExtract = await LoadersHelper.extractRWSViewArgsAsync(
|
|
16
|
+
processedContent,
|
|
17
|
+
false,
|
|
18
|
+
filePath,
|
|
19
|
+
this.addDependency,
|
|
20
|
+
this.query?.rwsWorkspaceDir,
|
|
21
|
+
this.query?.appRootDir,
|
|
22
|
+
isDev,
|
|
23
|
+
this.query?.publicDir
|
|
24
|
+
);
|
|
18
25
|
const decoratorData = decoratorExtract ? decoratorExtract.viewDecoratorData : null;
|
|
19
26
|
|
|
20
27
|
const cachedCode = processedContent;
|
|
@@ -34,19 +41,11 @@ module.exports = async function(content) {
|
|
|
34
41
|
return content;
|
|
35
42
|
}
|
|
36
43
|
|
|
37
|
-
let
|
|
38
|
-
let
|
|
44
|
+
let isIgnored = false;
|
|
45
|
+
let isDebugged = false;
|
|
39
46
|
|
|
40
47
|
if(decoratorData.decoratorArgs){
|
|
41
48
|
const decoratorArgs = decoratorData.decoratorArgs
|
|
42
|
-
|
|
43
|
-
if(decoratorArgs.template){
|
|
44
|
-
templateName = decoratorData.decoratorArgs.template || null;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
if(decoratorArgs.styles){
|
|
48
|
-
stylesPath = decoratorData.decoratorArgs.styles || null;
|
|
49
|
-
}
|
|
50
49
|
|
|
51
50
|
if(decoratorArgs.ignorePackaging){
|
|
52
51
|
isIgnored = true;
|
|
@@ -64,19 +63,13 @@ module.exports = async function(content) {
|
|
|
64
63
|
|
|
65
64
|
try {
|
|
66
65
|
if(tagName){
|
|
67
|
-
const [template, htmlFastImports, templateExists] = await LoadersHelper.getTemplate(filePath, this.addDependency, className, templateName, isDev);
|
|
68
|
-
|
|
69
|
-
const styles = await LoadersHelper.getStyles(filePath, this.query?.rwsWorkspaceDir, this.query?.appRootDir,this.addDependency, templateExists, stylesPath, isDev, this.query?.publicDir);
|
|
70
|
-
|
|
71
66
|
if(className){
|
|
72
|
-
const replacedViewDecoratorContent =
|
|
67
|
+
const replacedViewDecoratorContent = decoratorExtract.replacedDecorator;
|
|
73
68
|
|
|
74
69
|
if(replacedViewDecoratorContent){
|
|
75
|
-
processedContent =
|
|
70
|
+
processedContent = replacedViewDecoratorContent;
|
|
76
71
|
}
|
|
77
72
|
}
|
|
78
|
-
|
|
79
|
-
processedContent = `${htmlFastImports ? htmlFastImports + '\n' : ''}${processedContent}`;
|
|
80
73
|
}
|
|
81
74
|
|
|
82
75
|
const debugTsPath = filePath.replace('.ts','.debug.ts');
|
|
@@ -125,7 +125,115 @@ function _extractRWSViewDefs(fastOptions = {}, decoratorArgs = {})
|
|
|
125
125
|
return [addedParamDefs, addedParams];
|
|
126
126
|
}
|
|
127
127
|
|
|
128
|
-
function extractRWSViewArgs(content, noReplace = false) {
|
|
128
|
+
function extractRWSViewArgs(content, noReplace = false, filePath = null, addDependency = null, rwsWorkspaceDir = null, appRootDir = null, isDev = false, publicDir = null) {
|
|
129
|
+
// If this is being called with only basic parameters (backward compatibility)
|
|
130
|
+
if (filePath === null || addDependency === null) {
|
|
131
|
+
return extractRWSViewArgsSync(content, noReplace);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Otherwise, call the async version
|
|
135
|
+
return extractRWSViewArgsAsync(content, noReplace, filePath, addDependency, rwsWorkspaceDir, appRootDir, isDev, publicDir);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function extractRWSViewArgsSync(content, noReplace = false) {
|
|
139
|
+
const viewReg = /@RWSView\(\s*["']([^"']+)["'](?:\s*,\s*([\s\S]*?))?\s*\)\s*(.*?\s+)?class\s+([a-zA-Z0-9_-]+)\s+extends\s+RWSViewComponent/gm;
|
|
140
|
+
|
|
141
|
+
let m;
|
|
142
|
+
let tagName = null;
|
|
143
|
+
let className = null;
|
|
144
|
+
let classNamePrefix = null;
|
|
145
|
+
let decoratorArgs = null;
|
|
146
|
+
|
|
147
|
+
const _defaultRWSLoaderOptions = {
|
|
148
|
+
templatePath: 'template.html',
|
|
149
|
+
stylesPath: 'styles.scss',
|
|
150
|
+
fastOptions: { shadowOptions: { mode: 'open' } }
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
while ((m = viewReg.exec(content)) !== null) {
|
|
154
|
+
if (m.index === viewReg.lastIndex) {
|
|
155
|
+
viewReg.lastIndex++;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
m.forEach((match, groupIndex) => {
|
|
159
|
+
if (groupIndex === 1) {
|
|
160
|
+
tagName = match;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (groupIndex === 2) {
|
|
164
|
+
if (match) {
|
|
165
|
+
try {
|
|
166
|
+
decoratorArgs = JSON.parse(JSON.stringify(match));
|
|
167
|
+
} catch(e){
|
|
168
|
+
console.log(chalk.red('Decorator options parse error: ') + e.message + '\n Problematic line:');
|
|
169
|
+
console.log(`
|
|
170
|
+
@RWSView(${tagName}, ${match})
|
|
171
|
+
`);
|
|
172
|
+
console.log(chalk.yellowBright(`Decorator options failed to parse for "${tagName}" component.`) + ' { decoratorArgs } defaulting to null.');
|
|
173
|
+
console.log(match);
|
|
174
|
+
|
|
175
|
+
console.error(e);
|
|
176
|
+
|
|
177
|
+
throw new Error('Failed parsing @RWSView')
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (groupIndex === 3) {
|
|
183
|
+
if(match){
|
|
184
|
+
classNamePrefix = match;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (groupIndex === 4) {
|
|
189
|
+
className = match;
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if(!tagName){
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
let processedContent = content;
|
|
199
|
+
let fastOptions = _defaultRWSLoaderOptions.fastOptions;
|
|
200
|
+
|
|
201
|
+
if(decoratorArgs && decoratorArgs !== ''){
|
|
202
|
+
try {
|
|
203
|
+
decoratorArgs = json5.parse(decoratorArgs);
|
|
204
|
+
}catch(e){
|
|
205
|
+
// ignore parse errors for backward compatibility
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (decoratorArgs && decoratorArgs.fastElementOptions) {
|
|
210
|
+
fastOptions = decoratorArgs.fastElementOptions;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
let replacedDecorator = null;
|
|
214
|
+
|
|
215
|
+
if(!noReplace){
|
|
216
|
+
const [addedParamDefs, addedParams] = _extractRWSViewDefs(fastOptions, decoratorArgs);
|
|
217
|
+
const replacedViewDecoratorContent = processedContent.replace(
|
|
218
|
+
viewReg,
|
|
219
|
+
`@RWSView('$1', null, { template: rwsTemplate, styles${addedParams.length ? ', options: {' + (addedParams.join(', ')) + '}' : ''} })\n$3class $4 extends RWSViewComponent `
|
|
220
|
+
);
|
|
221
|
+
|
|
222
|
+
replacedDecorator = `${addedParamDefs.join('\n')}\n${replacedViewDecoratorContent}`;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return {
|
|
226
|
+
viewDecoratorData: {
|
|
227
|
+
tagName,
|
|
228
|
+
className,
|
|
229
|
+
classNamePrefix,
|
|
230
|
+
decoratorArgs
|
|
231
|
+
},
|
|
232
|
+
replacedDecorator
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
async function extractRWSViewArgsAsync(content, noReplace = false, filePath = null, addDependency = null, rwsWorkspaceDir = null, appRootDir = null, isDev = false, publicDir = null) {
|
|
129
237
|
const viewReg = /@RWSView\(\s*["']([^"']+)["'](?:\s*,\s*([\s\S]*?))?\s*\)\s*(.*?\s+)?class\s+([a-zA-Z0-9_-]+)\s+extends\s+RWSViewComponent/gm;
|
|
130
238
|
|
|
131
239
|
let m;
|
|
@@ -206,16 +314,37 @@ function extractRWSViewArgs(content, noReplace = false) {
|
|
|
206
314
|
|
|
207
315
|
let replacedDecorator = null;
|
|
208
316
|
|
|
209
|
-
if(!noReplace){
|
|
210
|
-
const [addedParamDefs, addedParams] = _extractRWSViewDefs(fastOptions, decoratorArgs);
|
|
211
|
-
|
|
317
|
+
if(!noReplace && filePath && addDependency){
|
|
318
|
+
const [addedParamDefs, addedParams] = _extractRWSViewDefs(fastOptions, decoratorArgs);
|
|
319
|
+
|
|
320
|
+
// Get template name and styles path from decorator args
|
|
321
|
+
let templateName = null;
|
|
322
|
+
let stylesPath = null;
|
|
323
|
+
|
|
324
|
+
if(decoratorArgs && decoratorArgs.template){
|
|
325
|
+
templateName = decoratorArgs.template;
|
|
326
|
+
}
|
|
327
|
+
if(decoratorArgs && decoratorArgs.styles){
|
|
328
|
+
stylesPath = decoratorArgs.styles;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Generate template and styles
|
|
332
|
+
const [template, htmlFastImports, templateExists] = await getTemplate(filePath, addDependency, className, templateName, isDev);
|
|
333
|
+
const styles = await getStyles(filePath, rwsWorkspaceDir, appRootDir, addDependency, templateExists, stylesPath, isDev, publicDir);
|
|
334
|
+
|
|
335
|
+
// Extract original imports (everything before the @RWSView decorator)
|
|
336
|
+
const beforeDecorator = processedContent.substring(0, processedContent.search(/@RWSView/));
|
|
337
|
+
const afterDecoratorMatch = processedContent.match(/@RWSView[\s\S]*$/);
|
|
338
|
+
const afterDecorator = afterDecoratorMatch ? afterDecoratorMatch[0] : '';
|
|
339
|
+
|
|
340
|
+
const replacedViewDecoratorContent = afterDecorator.replace(
|
|
212
341
|
viewReg,
|
|
213
|
-
|
|
342
|
+
`${template}\n${styles}\n${addedParamDefs.join('\n')}\n@RWSView('$1', null, { template: rwsTemplate, styles${addedParams.length ? ', options: {' + (addedParams.join(', ')) + '}' : ''} })\n$3class $4 extends RWSViewComponent `
|
|
214
343
|
);
|
|
215
344
|
|
|
216
345
|
// console.log({replacedViewDecoratorContent});
|
|
217
346
|
|
|
218
|
-
replacedDecorator = `${
|
|
347
|
+
replacedDecorator = `${htmlFastImports ? htmlFastImports + '\n' : ''}${beforeDecorator}${replacedViewDecoratorContent}`;
|
|
219
348
|
}
|
|
220
349
|
|
|
221
350
|
return {
|
|
@@ -290,4 +419,4 @@ let rwsTemplate: any = T.html<${className}>\`${templateContent}\`;
|
|
|
290
419
|
return [template, htmlFastImports, templateExists];
|
|
291
420
|
}
|
|
292
421
|
|
|
293
|
-
module.exports = { getRWSLoaders, extractRWSViewArgs, getTemplate, getStyles }
|
|
422
|
+
module.exports = { getRWSLoaders, extractRWSViewArgs, extractRWSViewArgsAsync, getTemplate, getStyles }
|
package/package.json
CHANGED
|
@@ -14,14 +14,13 @@ import { handleExternalChange } from './_attrs/_external_handler';
|
|
|
14
14
|
import { IFastDefinition, isDefined, defineComponent, getDefinition } from './_definitions';
|
|
15
15
|
import { on, $emitDown, observe, sendEventToOutside } from './_event_handling';
|
|
16
16
|
import { domEvents } from '../events';
|
|
17
|
+
import CSSInjectionManager, { CSSInjectMode, ICSSInjectionOptions } from './_css_injection';
|
|
17
18
|
|
|
18
19
|
type ComposeMethodType<
|
|
19
20
|
T extends FoundationElementDefinition,
|
|
20
21
|
K extends Constructable<RWSViewComponent>
|
|
21
22
|
> = (this: K, elementDefinition: T) => (overrideDefinition?: OverrideFoundationElementDefinition<T>) => FoundationElementRegistry<FoundationElementDefinition, T>;
|
|
22
23
|
|
|
23
|
-
type CSSInjectMode = 'adopted' | 'legacy' | 'both';
|
|
24
|
-
|
|
25
24
|
const _DEFAULT_INJECT_CSS_CACHE_LIMIT_DAYS = 1;
|
|
26
25
|
|
|
27
26
|
export interface IWithCompose<T extends RWSViewComponent> {
|
|
@@ -253,82 +252,39 @@ abstract class RWSViewComponent extends FoundationElement implements IRWSViewCom
|
|
|
253
252
|
return RWSViewComponent.instances;
|
|
254
253
|
}
|
|
255
254
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
const db = await this.indexedDBService.openDB(dbName, storeName);
|
|
260
|
-
const maxAgeMs = 1000 * 60 * 60 * 24; // 24h
|
|
261
|
-
const maxDaysAge = maxDaysExp ? maxDaysExp : _DEFAULT_INJECT_CSS_CACHE_LIMIT_DAYS;
|
|
262
|
-
const maxAgeDays = maxAgeMs * maxDaysAge;
|
|
263
|
-
|
|
264
|
-
let adoptedSheets: CSSStyleSheet[] = [];
|
|
265
|
-
|
|
266
|
-
let doneAdded = false;
|
|
267
|
-
|
|
268
|
-
for (const styleLink of styleLinks) {
|
|
269
|
-
const loadPromise = new Promise<void>(async (resolve, reject) => {
|
|
270
|
-
if (mode === 'legacy' || mode === 'both') {
|
|
271
|
-
const link = document.createElement('link');
|
|
272
|
-
link.rel = 'stylesheet';
|
|
273
|
-
link.href = styleLink;
|
|
274
|
-
this.getShadowRoot().appendChild(link);
|
|
275
|
-
|
|
276
|
-
link.onload = () => {
|
|
277
|
-
doneAdded = true;
|
|
278
|
-
|
|
279
|
-
if(mode === 'legacy'){
|
|
280
|
-
resolve();
|
|
281
|
-
}
|
|
282
|
-
};
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
if (mode === 'adopted' || mode === 'both') {
|
|
286
|
-
const entry = await this.indexedDBService.getFromDB(db, storeName, styleLink);
|
|
287
|
-
|
|
288
|
-
let cssText: string | null = null;
|
|
289
|
-
|
|
290
|
-
if (entry && typeof entry === 'object' && 'css' in entry && 'timestamp' in entry) {
|
|
291
|
-
const expired = Date.now() - entry.timestamp > maxAgeDays;
|
|
292
|
-
if (!expired) {
|
|
293
|
-
cssText = entry.css;
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
if (!cssText) {
|
|
298
|
-
cssText = await fetch(styleLink).then(res => res.text());
|
|
299
|
-
await this.indexedDBService.saveToDB(db, storeName, styleLink, {
|
|
300
|
-
css: cssText,
|
|
301
|
-
timestamp: Date.now()
|
|
302
|
-
});
|
|
303
|
-
console.log(`System saved stylesheet: ${styleLink} to IndexedDB`)
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
const sheet = new CSSStyleSheet();
|
|
307
|
-
await sheet.replace(cssText);
|
|
255
|
+
static getCachedStyles(styleLinks: string[]): CSSStyleSheet[] {
|
|
256
|
+
return CSSInjectionManager.getCachedStyles(styleLinks);
|
|
257
|
+
}
|
|
308
258
|
|
|
309
|
-
|
|
259
|
+
static hasCachedStyles(styleLinks: string[]): boolean {
|
|
260
|
+
return CSSInjectionManager.hasCachedStyles(styleLinks);
|
|
261
|
+
}
|
|
310
262
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
}
|
|
315
|
-
});
|
|
263
|
+
static getStylesOwnerComponent(): any {
|
|
264
|
+
return CSSInjectionManager.getStylesOwnerComponent();
|
|
265
|
+
}
|
|
316
266
|
|
|
317
|
-
|
|
318
|
-
|
|
267
|
+
static clearCachedStyles(): void {
|
|
268
|
+
CSSInjectionManager.clearCachedStyles();
|
|
269
|
+
}
|
|
319
270
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
271
|
+
protected async injectStyles(styleLinks: string[], mode: CSSInjectMode = 'adopted', maxDaysExp?: number) {
|
|
272
|
+
// Create a bridge object that exposes the necessary properties
|
|
273
|
+
const componentBridge = {
|
|
274
|
+
shadowRoot: this.shadowRoot,
|
|
275
|
+
indexedDBService: this.indexedDBService,
|
|
276
|
+
$emit: this.$emit.bind(this)
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
return CSSInjectionManager.injectStyles(componentBridge, styleLinks, { mode, maxDaysExp });
|
|
280
|
+
}
|
|
325
281
|
|
|
326
|
-
|
|
327
|
-
|
|
282
|
+
protected getInjectedStyles(styleLinks: string[]): CSSStyleSheet[] {
|
|
283
|
+
return CSSInjectionManager.getCachedStyles(styleLinks);
|
|
284
|
+
}
|
|
328
285
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
}
|
|
286
|
+
protected hasInjectedStyles(styleLinks: string[]): boolean {
|
|
287
|
+
return CSSInjectionManager.hasCachedStyles(styleLinks);
|
|
332
288
|
}
|
|
333
289
|
}
|
|
334
290
|
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { domEvents } from '../events';
|
|
2
|
+
import IndexedDBService, { IndexedDBServiceInstance } from '../services/IndexedDBService';
|
|
3
|
+
|
|
4
|
+
type CSSInjectMode = 'adopted' | 'legacy' | 'both';
|
|
5
|
+
|
|
6
|
+
const _DEFAULT_INJECT_CSS_CACHE_LIMIT_DAYS = 1;
|
|
7
|
+
|
|
8
|
+
interface ICSSInjectionOptions {
|
|
9
|
+
mode?: CSSInjectMode;
|
|
10
|
+
maxDaysExp?: number;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface ICSSInjectionComponent {
|
|
14
|
+
shadowRoot: ShadowRoot | null;
|
|
15
|
+
indexedDBService: IndexedDBServiceInstance;
|
|
16
|
+
$emit(eventName: string): void;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export class CSSInjectionManager {
|
|
20
|
+
private static CACHED_STYLES: Map<string, CSSStyleSheet> = new Map();
|
|
21
|
+
private static STYLES_OWNER_COMPONENT: ICSSInjectionComponent | null = null;
|
|
22
|
+
|
|
23
|
+
static getCachedStyles(styleLinks: string[]): CSSStyleSheet[] {
|
|
24
|
+
return styleLinks
|
|
25
|
+
.filter(link => CSSInjectionManager.CACHED_STYLES.has(link))
|
|
26
|
+
.map(link => CSSInjectionManager.CACHED_STYLES.get(link)!);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
static hasCachedStyles(styleLinks: string[]): boolean {
|
|
30
|
+
return styleLinks.every(link => CSSInjectionManager.CACHED_STYLES.has(link));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
static getStylesOwnerComponent(): ICSSInjectionComponent | null {
|
|
34
|
+
return CSSInjectionManager.STYLES_OWNER_COMPONENT;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
static clearCachedStyles(): void {
|
|
38
|
+
CSSInjectionManager.CACHED_STYLES.clear();
|
|
39
|
+
CSSInjectionManager.STYLES_OWNER_COMPONENT = null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
static async injectStyles(
|
|
43
|
+
component: ICSSInjectionComponent,
|
|
44
|
+
styleLinks: string[],
|
|
45
|
+
options: ICSSInjectionOptions = {}
|
|
46
|
+
): Promise<void> {
|
|
47
|
+
const { mode = 'adopted', maxDaysExp } = options;
|
|
48
|
+
|
|
49
|
+
if (!component.shadowRoot) {
|
|
50
|
+
throw new Error('Component must have a shadow root for CSS injection');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Add initial transition styles to host element
|
|
54
|
+
const transitionSheet = new CSSStyleSheet();
|
|
55
|
+
await transitionSheet.replace(`
|
|
56
|
+
:host {
|
|
57
|
+
opacity: 0;
|
|
58
|
+
transition: opacity 0.3s ease-in-out;
|
|
59
|
+
}
|
|
60
|
+
`);
|
|
61
|
+
component.shadowRoot.adoptedStyleSheets = [
|
|
62
|
+
transitionSheet,
|
|
63
|
+
...component.shadowRoot.adoptedStyleSheets,
|
|
64
|
+
];
|
|
65
|
+
|
|
66
|
+
let adoptedSheets: CSSStyleSheet[] = [];
|
|
67
|
+
let doneAdded = false;
|
|
68
|
+
|
|
69
|
+
// Check if we already have cached styles from the owner component
|
|
70
|
+
const cachedSheets: CSSStyleSheet[] = [];
|
|
71
|
+
const uncachedLinks: string[] = [];
|
|
72
|
+
|
|
73
|
+
for (const styleLink of styleLinks) {
|
|
74
|
+
if (CSSInjectionManager.CACHED_STYLES.has(styleLink)) {
|
|
75
|
+
cachedSheets.push(CSSInjectionManager.CACHED_STYLES.get(styleLink)!);
|
|
76
|
+
} else {
|
|
77
|
+
uncachedLinks.push(styleLink);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// If we have cached styles, use them immediately
|
|
82
|
+
if (cachedSheets.length > 0) {
|
|
83
|
+
adoptedSheets.push(...cachedSheets);
|
|
84
|
+
doneAdded = true;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Only process uncached styles
|
|
88
|
+
if (uncachedLinks.length > 0) {
|
|
89
|
+
// Set this component as the owner if no owner exists yet
|
|
90
|
+
if (!CSSInjectionManager.STYLES_OWNER_COMPONENT) {
|
|
91
|
+
CSSInjectionManager.STYLES_OWNER_COMPONENT = component;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const dbName = 'css-cache';
|
|
95
|
+
const storeName = 'styles';
|
|
96
|
+
const db = await component.indexedDBService.openDB(dbName, storeName);
|
|
97
|
+
const maxAgeMs = 1000 * 60 * 60 * 24; // 24h
|
|
98
|
+
const maxDaysAge = maxDaysExp ? maxDaysExp : _DEFAULT_INJECT_CSS_CACHE_LIMIT_DAYS;
|
|
99
|
+
const maxAgeDays = maxAgeMs * maxDaysAge;
|
|
100
|
+
|
|
101
|
+
for (const styleLink of uncachedLinks) {
|
|
102
|
+
const loadPromise = new Promise<void>(async (resolve, reject) => {
|
|
103
|
+
if (mode === 'legacy' || mode === 'both') {
|
|
104
|
+
const link = document.createElement('link');
|
|
105
|
+
link.rel = 'stylesheet';
|
|
106
|
+
link.href = styleLink;
|
|
107
|
+
component.shadowRoot!.appendChild(link);
|
|
108
|
+
|
|
109
|
+
link.onload = () => {
|
|
110
|
+
doneAdded = true;
|
|
111
|
+
|
|
112
|
+
if(mode === 'legacy'){
|
|
113
|
+
resolve();
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (mode === 'adopted' || mode === 'both') {
|
|
119
|
+
const entry = await component.indexedDBService.getFromDB(db, storeName, styleLink);
|
|
120
|
+
|
|
121
|
+
let cssText: string | null = null;
|
|
122
|
+
|
|
123
|
+
if (entry && typeof entry === 'object' && 'css' in entry && 'timestamp' in entry) {
|
|
124
|
+
const expired = Date.now() - entry.timestamp > maxAgeDays;
|
|
125
|
+
if (!expired) {
|
|
126
|
+
cssText = entry.css;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (!cssText) {
|
|
131
|
+
cssText = await fetch(styleLink).then(res => res.text());
|
|
132
|
+
await component.indexedDBService.saveToDB(db, storeName, styleLink, {
|
|
133
|
+
css: cssText,
|
|
134
|
+
timestamp: Date.now()
|
|
135
|
+
});
|
|
136
|
+
console.log(`System saved stylesheet: ${styleLink} to IndexedDB`)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const sheet = new CSSStyleSheet();
|
|
140
|
+
await sheet.replace(cssText);
|
|
141
|
+
|
|
142
|
+
// Cache the stylesheet for future use
|
|
143
|
+
CSSInjectionManager.CACHED_STYLES.set(styleLink, sheet);
|
|
144
|
+
|
|
145
|
+
adoptedSheets.push(sheet);
|
|
146
|
+
|
|
147
|
+
if(mode === 'adopted' || mode === 'both'){
|
|
148
|
+
resolve();
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
await loadPromise;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
doneAdded = true;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (adoptedSheets.length) {
|
|
160
|
+
component.shadowRoot.adoptedStyleSheets = [
|
|
161
|
+
...adoptedSheets,
|
|
162
|
+
...component.shadowRoot.adoptedStyleSheets,
|
|
163
|
+
];
|
|
164
|
+
|
|
165
|
+
doneAdded = true;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (doneAdded) {
|
|
169
|
+
// Set opacity to 1 to fade in the component
|
|
170
|
+
const opacitySheet = new CSSStyleSheet();
|
|
171
|
+
await opacitySheet.replace(`
|
|
172
|
+
:host {
|
|
173
|
+
opacity: 1 !important;
|
|
174
|
+
}
|
|
175
|
+
`);
|
|
176
|
+
component.shadowRoot.adoptedStyleSheets = [
|
|
177
|
+
opacitySheet,
|
|
178
|
+
...component.shadowRoot.adoptedStyleSheets,
|
|
179
|
+
];
|
|
180
|
+
|
|
181
|
+
component.$emit(domEvents.loadedLinkedStyles);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export default CSSInjectionManager;
|
|
187
|
+
export { CSSInjectMode, ICSSInjectionOptions, ICSSInjectionComponent };
|