UNPKG

@rws-framework/client

Version:

This package provides the core client-side framework for Realtime Web Suit (RWS), enabling modular, asynchronous web components, state management, and integration with backend services. It is located in `.dev/client`.

310 lines (248 loc) 11.5 kB
import { IRWSViteLoader, TSLoaderParams } from "./loader.type"; import fs from 'fs'; import path from 'path'; import chalk from 'chalk'; import md5 from 'md5'; import JSON5 from 'json5' import { RWSScssPlugin } from "../rws_scss_plugin"; interface DecoratorArgsData { template?: string; styles?: string; ignorePackaging?: boolean; debugPackaging?: boolean; fastElementOptions?: any; } interface ViewDecoratorData { decoratorArgs?: DecoratorArgsData; tagName: string; className: string; classNamePrefix?: string | null; // Added this field } interface DecoratorExtract { viewDecoratorData?: ViewDecoratorData | null; replacedDecorator: string; } // Cache manager - możesz to wydzielić do osobnego pliku class CacheManager { private cache: Map<string, string> = new Map(); private customOptions: any; constructor(customOptions: any = null) { this.customOptions = customOptions; } getCachedItem(filePath: string, hash: string): string | null { const key = `${filePath}:${hash}`; return this.cache.get(key) || null; } cacheItem(filePath: string, content: string, originalContent: string): void { const key = `${filePath}:${md5(originalContent)}`; this.cache.set(key, content); } } // Helper functions - też możesz wydzielić class LoadersHelper { static extractRWSViewArgs(content: string, noReplace = false): DecoratorExtract | null { const viewReg = /@RWSView\(\s*["']([^"']+)["'](?:\s*,\s*([\s\S]*?))?\s*\)\s*(.*?\s+)?class\s+([a-zA-Z0-9_-]+)\s+extends\s+RWSViewComponent/gm; let m: RegExpExecArray | null = null;; let tagName: string | null = null; let className: string | null = null; let classNamePrefix: string | null = null; let decoratorArgs: DecoratorArgsData | null = null; const _defaultRWSLoaderOptions = { templatePath: 'template.html', stylesPath: 'styles.scss', fastOptions: { shadowOptions: { mode: 'open' } } } while ((m = viewReg.exec(content)) !== null) { if (m.index === viewReg.lastIndex) { viewReg.lastIndex++; } m.forEach((match, groupIndex) => { if (groupIndex === 1) { tagName = match; } if (groupIndex === 2) { if (match) { try { decoratorArgs = JSON5.parse(match); } catch(e){ console.log(chalk.red('Decorator options parse error: ') + e.message + '\n Problematic line:'); console.log(` @RWSView(${tagName}, ${match}) `); console.log(chalk.yellowBright(`Decorator options failed to parse for "${tagName}" component.`) + ' { decoratorArgs } defaulting to null.'); console.log(match); throw new Error('Failed parsing @RWSView') } } } if (groupIndex === 3) { if(match){ classNamePrefix = match; } } if (groupIndex === 4) { className = match; } }); } if(!tagName || !className){ return null; } let processedContent = content; let fastOptions = _defaultRWSLoaderOptions.fastOptions; decoratorArgs = decoratorArgs as unknown as DecoratorArgsData; if (decoratorArgs?.fastElementOptions) { fastOptions = decoratorArgs.fastElementOptions; } let replacedDecorator: string | null = null; if(!noReplace){ const [addedParamDefs, addedParams] = this._extractRWSViewDefs(fastOptions, decoratorArgs || {}); const replacedViewDecoratorContent = processedContent.replace( viewReg, `@RWSView('$1', null, { template: rwsTemplate, styles${addedParams.length ? ', options: {' + (addedParams.join(', ')) + '}' : ''} })\n$3class $4 extends RWSViewComponent ` ); replacedDecorator = `${addedParamDefs.join('\n')}\n${replacedViewDecoratorContent}`; } return { viewDecoratorData: { tagName, className, classNamePrefix, decoratorArgs }, replacedDecorator: replacedDecorator || '' // Ensure it's never null } } private static _extractRWSViewDefs(fastOptions = {}, decoratorArgs = {}) { const addedParamDefs: string[] = []; const addedParams: string[] = []; for (const key in fastOptions){ addedParamDefs.push(`const ${key} = ${JSON.stringify(fastOptions[key])};`); addedParams.push(key); } return [addedParamDefs, addedParams]; } static async getStyles(plugin: RWSScssPlugin, filePath: string, addDependency: (path: string) => void, templateExists: boolean, stylesPath?: string, isDev?: boolean): Promise<string> { if(!stylesPath){ stylesPath = 'styles/layout.scss'; } let styles = 'const styles: null = null;' const stylesFilePath = path.join(path.dirname(filePath), stylesPath); if (fs.existsSync(stylesFilePath)) { const scsscontent = fs.readFileSync(stylesFilePath, 'utf-8'); const codeData = await plugin.compileScssCode(scsscontent, path.join(path.dirname(filePath), 'styles')); const cssCode = codeData.code; styles = isDev ? `` : ''; if (!templateExists) { styles += `import { css } from '@microsoft/fast-element';\n`; } styles += `const styles = ${templateExists ? 'T.' : ''}css\`${cssCode}\`;\n`; addDependency(path.join(path.dirname(filePath), stylesPath)); } return styles; } static async getTemplate(filePath: string, addDependency: (path: string) => void, templateName?: string, isDev?: boolean) { if(!templateName){ templateName = 'template'; } const templatePath = path.dirname(filePath) + `/${templateName}.html`; let htmlFastImports: string | null = null; const templateExists = fs.existsSync(templatePath); let template = 'const rwsTemplate: null = null;'; if (templateExists) { const templateContent = fs.readFileSync(templatePath, 'utf-8').replace(/<!--[\s\S]*?-->/g, ''); htmlFastImports = `import * as T from '@microsoft/fast-element';\n`; template = ` //@ts-ignore let rwsTemplate: any = T.html\`${templateContent}\`; `; addDependency(templatePath); } return [template, htmlFastImports, templateExists]; } } // Główny loader const loader: IRWSViteLoader<TSLoaderParams> = async (params: TSLoaderParams) => { const cacheManager = new CacheManager(); return { name: 'rws-typescript', enforce: 'pre', async transform(code: string, id: string) { if (!id.endsWith('.ts')) return null; if (id.endsWith('.debug.ts') || id.endsWith('.d.ts')) return null; if (id.includes('node_modules') && !id.includes('@rws-framework')) return null; let processedContent: string = code; const isDev: boolean = params.dev; let isIgnored: boolean = false; let isDebugged: boolean = false; try { const decoratorExtract: DecoratorExtract | null = await LoadersHelper.extractRWSViewArgs(processedContent); const decoratorData: ViewDecoratorData | null = decoratorExtract?.viewDecoratorData || null; const cachedCode: string = processedContent; // const cachedTS = cacheManager.getCachedItem(id, md5(cachedCode as string)); // if (cachedTS) { // return { // code: cachedTS, // map: null // }; // } if (!decoratorData) { return null; } let templateName: string | null = decoratorData.decoratorArgs?.template || null; let stylesPath = decoratorData.decoratorArgs?.styles || null; isIgnored = decoratorData.decoratorArgs?.ignorePackaging || false; isDebugged = decoratorData.decoratorArgs?.debugPackaging || false; const tagName = decoratorData.tagName; const className = decoratorData.className; const defaultTemplatePath = path.resolve(path.dirname(id), 'template.html'); const defaultStylesPath = path.resolve(path.dirname(id), 'styles', 'layout.scss'); if(!templateName && fs.existsSync(defaultTemplatePath)){ templateName ='template'; } if(!stylesPath && fs.existsSync(defaultStylesPath)){ stylesPath ='styles/layout.scss'; } if (tagName && templateName && stylesPath) { const [template, htmlFastImports, templateExists] = await LoadersHelper.getTemplate( id, (path) => this.addWatchFile(path), templateName, isDev ); const styles = await LoadersHelper.getStyles( params.scssPlugin, id, (path) => this.addWatchFile(path), templateExists as boolean, stylesPath, isDev ); if (className && decoratorExtract?.replacedDecorator) { processedContent = `${template}\n${styles}\n${decoratorExtract.replacedDecorator}`; } processedContent = `${htmlFastImports ? htmlFastImports + '\n' : ''}${processedContent}`; } const debugTsPath = id.replace('.ts', '.debug.ts'); if (fs.existsSync(debugTsPath)) { fs.unlinkSync(debugTsPath); } if (isDebugged) { console.log(chalk.red('[RWS BUILD] Debugging into: ' + debugTsPath)); fs.writeFileSync(debugTsPath, processedContent); } cacheManager.cacheItem(id, processedContent, cachedCode); return { code: processedContent, map: null }; } catch (e) { console.log(chalk.red('RWS Typescript loader error:')); console.error(e); throw new Error('RWS Build failed on: ' + id); } } }; }; export default loader;