From f8edbdd581a978e64aa676111b939a760d5b663b Mon Sep 17 00:00:00 2001 From: Josef Date: Mon, 31 Mar 2025 14:00:55 +0200 Subject: [PATCH] WebPack Update --- browser/dom/PageHandler.ts | 12 +- node/DOMShim.ts | 86 ++++++ node/files/Files.ts | 373 ++++++++++++++++++++++++++ node/files/FilesSync.ts | 4 + node/files/PathFilter.ts | 26 ++ node/files/PathReference.ts | 151 +++++++++++ node/tsconfig.json | 6 +- node/webpack/PHPPagesBuilder.ts | 79 +++++- node/webpack/TemplatesIndexBuilder.ts | 127 +++++++++ 9 files changed, 858 insertions(+), 6 deletions(-) create mode 100644 node/DOMShim.ts create mode 100644 node/files/Files.ts create mode 100644 node/files/FilesSync.ts create mode 100644 node/files/PathFilter.ts create mode 100644 node/files/PathReference.ts create mode 100644 node/webpack/TemplatesIndexBuilder.ts diff --git a/browser/dom/PageHandler.ts b/browser/dom/PageHandler.ts index 033f3ce..bcbab05 100644 --- a/browser/dom/PageHandler.ts +++ b/browser/dom/PageHandler.ts @@ -119,7 +119,7 @@ export class PageHandler private _maxPreloadingDurationMS:number = 2000; private static _localHost = "http://localhost:8080"; - private static _localNetworkRegexPattern = /^(http:\/\/\d\d\d\.\d\d\d\.\d\d\d\.\d\d\d?:XXXX)/ + private static _localNetworkRegexPattern = /^(http:\/\/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}?:XXXX)/ private static _localNetworkRegex = RegExpUtility.createRegExp( this._localNetworkRegexPattern, "XXXX", 8080 + "" ); private _isHTMLMode:boolean = false; private _fileLocation:string = ""; @@ -186,17 +186,25 @@ export class PageHandler get isLocalNetwork() { let adress = this.trimmedLocation; + + // console.log( "Checking adress for localhost", adress, PageHandler._localHost, PageHandler._localNetworkRegex ); if ( adress.startsWith( PageHandler._localHost ) ) { + console.log( "Is starting with localhost" ); return true; } + + // console.log( "Not starting with localhost" ); if ( PageHandler._localNetworkRegex.test( adress ) ) { + // console.log( "Is starting with localhost regex" ); return true; } + // console.log( "Not starting with localhost regex" ); + return false; } @@ -234,7 +242,7 @@ export class PageHandler } else { - console.log( "LOADING PAGE DATA FROM JSON" ); + console.log( "LOADING PAGE DATA FROM JSON", this.isLocalNetwork, pagesPath ); let pagesData = await Loader.loadJSON( pagesPath ); pages = pagesData.pages; } diff --git a/node/DOMShim.ts b/node/DOMShim.ts new file mode 100644 index 0000000..c01b06d --- /dev/null +++ b/node/DOMShim.ts @@ -0,0 +1,86 @@ +import { JSDOM } from "jsdom"; + +export class DOMShim +{ + private static _instance:DOMShim = null; + + + static get $():DOMShim + { + if ( this._instance ) + { + return this._instance + } + + this._instance = new DOMShim(); + return this._instance; + } + + private _applied = false; + private _jsDOM:JSDOM; + + constructor() + { + this._apply(); + } + + setDocument( html:string ) + { + this._jsDOM = new JSDOM( html ); + ( global as any ).window = this._jsDOM.window; + ( global as any ).document = this._jsDOM.window.document; + } + + get jsdom(){ return this._jsDOM; } + + private _apply() + { + if ( this._applied ) + { + return; + } + + this._addHTMLNodeDefinition(); + this._addDOMParser(); + + this._applied = true; + } + + + private _addHTMLNodeDefinition() + { + ( global as any ).Node = + { + ELEMENT_NODE: 1, + ATTRIBUTE_NODE: 2, + TEXT_NODE:3, + CDATA_SECTION_NODE:4, + PROCESSING_INSTRUCTION_NODE:7, + COMMENT_NODE:8, + DOCUMENT_NODE:9, + DOCUMENT_TYPE_NODE:10, + DOCUMENT_FRAGMENT_NODE:11 + } + } + + private _addDOMParser() + { + DOMParserShim.implementation = ( html:string, type:string = "html" )=> + { + return new JSDOM( html, {contentType:type} ).window.document; + } + + global.DOMParser = DOMParserShim; + } + + +} + +export class DOMParserShim +{ + static implementation:( html:string, type:string )=>Document; + parseFromString( html:string, type:string ):Document + { + return DOMParserShim.implementation( html, type ); + } +} \ No newline at end of file diff --git a/node/files/Files.ts b/node/files/Files.ts new file mode 100644 index 0000000..c61d229 --- /dev/null +++ b/node/files/Files.ts @@ -0,0 +1,373 @@ +import { promises as fs } from "fs"; +import * as path from "path"; + +import { RJLog } from "../log/RJLog"; +import { PathReference } from "./PathReference"; +import { DOMShim } from "../DOMShim"; +import { DateMath } from "../../browser/date/DateMath"; + +export class Files +{ + static parentPath( filePath:string ) + { + return path.dirname( filePath ); + } + + static async forAllIn( filePath:string, filter:(p:PathReference)=>Promise = null, action:(p:PathReference)=>Promise = null ):Promise + { + let files = await fs.readdir( filePath ); + + let root = new PathReference( filePath ); + let pathReferences = files.map( f => root.createRelative( f ) ); + + if ( filter ) + { + let filteredPaths = []; + + for ( let p of pathReferences ) + { + if ( await filter( p ) ) + { + filteredPaths.push( p ); + } + } + + pathReferences = filteredPaths; + + }; + + for ( let p of pathReferences ) + { + let isDirectory = await p.isDirectory(); + + if ( isDirectory ) + { + await this.forAllIn( p.absolutePath, filter, action ); + } + } + + if ( action ) + { + for ( let p of pathReferences ) + { + await action( p ); + } + } + + return Promise.resolve( pathReferences ); + } + + static async forDirectChildrenIn( filePath:string, filter:(p:PathReference)=>Promise = null, action:(p:PathReference)=>Promise = null ):Promise + { + let files = await fs.readdir( filePath ); + + let root = new PathReference( filePath ); + let pathReferences = files.map( f => root.createRelative( f ) ); + + if ( filter ) + { + let filteredPaths = []; + + for ( let p of pathReferences ) + { + if ( await filter( p ) ) + { + filteredPaths.push( p ); + } + } + + pathReferences = filteredPaths; + + }; + + if ( action ) + { + for ( let p of pathReferences ) + { + await action( p ); + } + } + + return Promise.resolve( pathReferences ); + + } + + static joinPaths( pathFragments:string[] ) + { + return path.join.apply( path, pathFragments ); + } + + static async existsIn( directoryPath:string, fileName:string ):Promise + { + let combinedPath = Files.joinPaths( [ directoryPath, fileName ] ); + let result = await Files.exists( combinedPath ); + + return Promise.resolve( result ); + } + + static async getStatistics( filePath:string ) + { + return fs.stat( filePath ); + } + + static async getModificationDate( filePath:string ):Promise + { + let stats = await this.getStatistics( filePath ); + + if ( ! stats ) + { + return Promise.resolve( null ); + } + + return new Date( stats.mtimeMs ); + } + + static async isNewerThan( filePath:string, date:Date ):Promise + { + let mDate = await this.getModificationDate( filePath ); + + return Promise.resolve( DateMath.isAfter( mDate, date ) ); + } + + static async isDirectory( filePath:string ):Promise + { + try + { + let stats = await fs.stat( filePath ); + + return Promise.resolve( stats !== null && stats !== undefined && stats.isDirectory() ); + } + catch( e ) + { + return false; + } + + return Promise.resolve( false ); + } + + static async isFile( filePath:string ):Promise + { + try + { + let stats = await fs.stat( filePath ); + + return Promise.resolve( stats !== null && stats !== undefined && stats.isFile() ); + } + catch( e ) + { + return false; + } + + return Promise.resolve( false ); + } + + static async isSymbolicLink( filePath:string ):Promise + { + try + { + let stats = await fs.stat( filePath ); + + return Promise.resolve( stats !== null && stats !== undefined && stats.isSymbolicLink() ); + } + catch( e ) + { + return false; + } + + return Promise.resolve( false ); + } + + static async exists( filePath:string ):Promise + { + try + { + let stats = await fs.stat( filePath ); + + return Promise.resolve( stats !== null && stats !== undefined ); + } + catch( e ) + { + return false; + } + + return Promise.resolve( false ); + } + + static async ensureDirectoryExists( path:string ):Promise + { + let exists = await Files.exists( path ); + + if ( exists ) + { + return Promise.resolve(); + } + + await fs.mkdir( path, { recursive: true } ); + + return Promise.resolve(); + } + + static async ensureParentDirectoryExists( filePath:string ):Promise + { + let path = Files.parentPath( filePath ); + let exists = await Files.exists( path ); + + if ( exists ) + { + return Promise.resolve(); + } + + await fs.mkdir( path ); + + return Promise.resolve(); + } + + static async deleteFile( filePath:string ):Promise + { + try + { + await fs.unlink( filePath ); + } + catch( e ) + { + return Promise.resolve( false ); + } + + return Promise.resolve( true ); + + } + + + static async loadUTF8( filePath:string ):Promise + { + try + { + let data = await fs.readFile( filePath ); + let stringData = data.toString(); + return Promise.resolve( stringData ); + } + catch ( exception ) + { + RJLog.log( exception ); + } + + return Promise.resolve( null ); + + } + + + + static async moveFile( oldPath:string, newPath:string ) + { + try + { + await fs.rename( oldPath, newPath ); + } + catch ( e ) + { + await fs.copyFile( oldPath, newPath ); + await fs.unlink( oldPath ); + } + } + + static async loadJSON( filePath:string ):Promise + { + let text = await Files.loadUTF8( filePath ); + + if ( text === null ) + { + return Promise.resolve( null ); + } + + try + { + let jsonObject = JSON.parse( text ); + + return Promise.resolve( jsonObject as T ); + } + catch ( exception ) + { + RJLog.log( exception ); + } + + return Promise.resolve( null ); + } + + static async saveUTF8( filePath:string, text:string ):Promise + { + try + { + await fs.writeFile( filePath, text ); + return Promise.resolve( true ); + } + catch ( exception ) + { + RJLog.log( exception ); + } + + return Promise.resolve( false ); + + } + + static async saveJSON( filePath:string, data:T ):Promise + { + try + { + let jsonData = JSON.stringify( data ); + let result = await Files.saveUTF8( filePath, jsonData ); + + return Promise.resolve( result ); + } + catch( e ) + { + RJLog.log( e ); + } + + + return Promise.resolve( false ); + } + + static async saveXML( filePath:string, rootElement:Element ):Promise + { + let domShim = DOMShim.$; + + let nodeName = rootElement.nodeName; + let xmlHeader = `` + "\n"; + let serializedXML = `${xmlHeader}<${nodeName}>${rootElement.innerHTML}` ; + + //RJLog.log( rootElement, serializedXML ); + + await Files.saveUTF8( filePath, serializedXML ); + + return Promise.resolve(); + } + + static async loadXML( filePath:string ):Promise + { + let domShim = DOMShim.$; + + let stringData = await Files.loadUTF8( filePath ); + let parser = new DOMParser(); + let xmlDoc = parser.parseFromString( stringData, "text/xml" ); + return xmlDoc; + } + + static async loadHTML( filePath:string ):Promise + { + let domShim = DOMShim.$; + + let stringData = await Files.loadUTF8( filePath ); + let parser = new DOMParser(); + let xmlDoc = parser.parseFromString( stringData, "text/html" ); + return xmlDoc; + } + + static async saveHTML( filePath:string, rootElement:Element ):Promise + { + let domShim = DOMShim.$; + + await Files.saveUTF8( filePath, rootElement.outerHTML ); + + return Promise.resolve(); + } +} \ No newline at end of file diff --git a/node/files/FilesSync.ts b/node/files/FilesSync.ts new file mode 100644 index 0000000..19fe792 --- /dev/null +++ b/node/files/FilesSync.ts @@ -0,0 +1,4 @@ +export class FilesSync +{ + +} \ No newline at end of file diff --git a/node/files/PathFilter.ts b/node/files/PathFilter.ts new file mode 100644 index 0000000..e6261fa --- /dev/null +++ b/node/files/PathFilter.ts @@ -0,0 +1,26 @@ +import { PathReference } from "./PathReference" + +export type PathReferenceFilter = (p:PathReference)=>Promise; + +export class PathFilter +{ + static get HTML() + { + return PathFilter.forFileExtension( ".html" ); + } + + static get JSON() + { + return PathFilter.forFileExtension( ".json" ); + } + + static forFileExtension( extension:string ):PathReferenceFilter + { + let filter = ( p:PathReference )=> + { + return p.isFileWithExtension( extension ); + } + + return filter; + } +} \ No newline at end of file diff --git a/node/files/PathReference.ts b/node/files/PathReference.ts new file mode 100644 index 0000000..3281b57 --- /dev/null +++ b/node/files/PathReference.ts @@ -0,0 +1,151 @@ + +import { RegExpUtility } from "../../browser/text/RegExpUtitlity"; +import { Files } from "./Files"; + + +export class PathReference +{ + _parentReference:PathReference; + _relative:boolean; + _path:string; + _cachedAbsolutePath:string; + + constructor( absolutePath:string ) + { + this._path = RegExpUtility.normalizePath( absolutePath ); + this._relative = false; + this._parentReference = null; + } + + + + createRelative( path:string ) + { + path = RegExpUtility.normalizePath( path ); + let link = new PathReference( path ); + link._relative = true; + link._parentReference = this; + + return link; + } + + createRelativeFromAbsolute( otherAbsolutePath:string, isFile:boolean ) + { + otherAbsolutePath = RegExpUtility.normalizePath( otherAbsolutePath ); + + if ( ! isFile ) + { + let relativePath = RegExpUtility.createRelativeDirectoryPath( this.absolutePath, otherAbsolutePath ); + return this.createRelative( relativePath ) + } + + let filePath = RegExpUtility.fileNameOrLastPath( otherAbsolutePath ); + let parentPath = RegExpUtility.parentPath( otherAbsolutePath ); + + let relativePath = RegExpUtility.createRelativeDirectoryPath( this.absolutePath, parentPath ); + relativePath = RegExpUtility.join( relativePath, filePath ); + + return this.createRelative( relativePath ) + } + + get isRelative() + { + return this._relative; + } + + get relativePath() + { + return this._path; + } + + get fileName() + { + return RegExpUtility.fileNameOrLastPath( this._path ); + } + + get fileNameWithoutExtension() + { + return RegExpUtility.trimFileTypeExtension( this.fileName ); + } + + + pathEndsWith( extension:string ) + { + return this._path.toLowerCase().endsWith( extension.toLowerCase() ); + } + + async isFileWithExtension( extension:string ) + { + if ( ! this.pathEndsWith( extension ) ) + { + return false; + } + + return this.isFile(); + } + + get absolutePath() + { + if ( ! this._relative ) + { + return this._path; + } + + if ( this._cachedAbsolutePath ) + { + return this._cachedAbsolutePath; + } + + let cachedAbsolutePath = RegExpUtility.join( this._parentReference.absolutePath, this._path ); + cachedAbsolutePath = RegExpUtility.resolvePath( cachedAbsolutePath ); + + this._cachedAbsolutePath = cachedAbsolutePath; + + return this._cachedAbsolutePath; + } + + async exists():Promise + { + return Files.exists( this.absolutePath ); + } + + async isFile():Promise + { + return Files.isFile( this.absolutePath ); + } + + async isDirectory():Promise + { + return Files.isDirectory( this.absolutePath ); + } + + async isSymbolicLink():Promise + { + return Files.isSymbolicLink( this.absolutePath ); + } + + async loadUTF8() + { + return Files.loadUTF8( this.absolutePath ); + } + + async loadHTML() + { + return Files.loadHTML( this.absolutePath ); + } + + async saveHTML( rootElement:Element ) + { + return Files.saveHTML( this.absolutePath, rootElement ); + } + + async loadXML() + { + return Files.loadXML( this.absolutePath ); + } + + async loadJSON() + { + return Files.loadJSON( this.absolutePath ); + } +} \ No newline at end of file diff --git a/node/tsconfig.json b/node/tsconfig.json index bcdd693..a2722cd 100644 --- a/node/tsconfig.json +++ b/node/tsconfig.json @@ -1,8 +1,8 @@ { "compilerOptions": { - "target": "ESNext", - "module": "CommonJS", - "lib": ["ESNext"], + + "module": "es2022", + "target": "es2015", "moduleResolution": "Node", "types": ["node"] } diff --git a/node/webpack/PHPPagesBuilder.ts b/node/webpack/PHPPagesBuilder.ts index b91e0a0..53af5b2 100644 --- a/node/webpack/PHPPagesBuilder.ts +++ b/node/webpack/PHPPagesBuilder.ts @@ -2,23 +2,100 @@ import * as fs from "fs"; import * as path from "path"; import { RJLog } from "../log/RJLog"; +import { spawn } from "child_process"; export class PagesInfo { pages:string[] = []; } +export class PHPServerSettings +{ + phpPaths:string[] = [ "D:\\apps\\php\\php.exe", "C:\\apps\\php\\php.exe" ]; + relativeWebPath:string; + routerPath:string; + port:number = 8081; +} + export class PHPPagesBuilder { inputDir:string; outputDir:string; - constructor( source:string, build:string ) + static hasServer = false; + + constructor( source:string, build:string, settings:PHPServerSettings ) { this.inputDir = source; this.outputDir = build; + + this.startServer( settings ); } + startServer( settings:PHPServerSettings ) + { + if ( settings == null ) + { + return; + } + + if ( PHPPagesBuilder.hasServer ) + { + return; + } + + + PHPPagesBuilder.hasServer = true; + + let phpPath = PHPPagesBuilder.getPHPPath( settings ); + + if ( ! phpPath ) + { + RJLog.error( "PHP was not found!"); + RJLog.error( "Ensure PHP is installed and the paths in the settings are configured correctly."); + RJLog.log( "PHP paths from settings of '" + process.argv[ 2 ] + "': "); + RJLog.log( '"' + settings.phpPaths.join( ", " ) + '"' ); + return; + } + + let workdingDir = process.cwd(); + + let webPath = path.join( workdingDir, settings.relativeWebPath ); + let routerPath = path.join( webPath, "_router_.php" ); + + let port = settings.port; + let spawnedProcess = spawn( phpPath,["-S", "0.0.0.0:" + port, routerPath, "-t", webPath ] ); + + spawnedProcess.stdout.on( "data", data => { console.log( data + "" ); } ); + + spawnedProcess.stderr.on( "data", data => { console.log( data + "" ); } ); + + spawnedProcess.on( 'error', (error) => { + console.log(`error: ${error.message}`); + }); + + spawnedProcess.on( "close", code => { + console.log(`child process exited with code ${code}`); + }); + } + + static getPHPPath( settings:PHPServerSettings ) + { + for ( let path of settings.phpPaths ) + { + let exists = fs.existsSync( path ); + + if ( exists ) + { + RJLog.log( "Starting php server from:", path ); + return path; + } + } + + return null; + } + + filterFiles( fileName:string ) { if ( fileName.startsWith( "__" ) ) diff --git a/node/webpack/TemplatesIndexBuilder.ts b/node/webpack/TemplatesIndexBuilder.ts new file mode 100644 index 0000000..03895f8 --- /dev/null +++ b/node/webpack/TemplatesIndexBuilder.ts @@ -0,0 +1,127 @@ + +import * as fs from "fs"; +import * as path from "path"; +import { RJLog } from "../log/RJLog"; +import { Files } from "../files/Files"; +import { PathReference } from "../files/PathReference"; + +export class TemplatesInfo +{ + templates:string[] = []; +} + +export class TemplatesIndexBuilder +{ + inputDir:string; + outputDir:string; + + constructor( source:string, build:string ) + { + this.inputDir = source; + this.outputDir = build; + } + + filterFiles( fileName:string ) + { + if ( fileName.startsWith( "__" ) ) + { + return false; + } + + return fileName.endsWith( ".html" ); + } + + + apply( compiler:any ) + { + compiler.hooks.afterCompile.tapAsync( "TemplatesIndexBuilder", + async ( compilation:any, callback:any ) => + { + await Files.forAllIn( this.inputDir, null, + async ( p:PathReference ) => + { + if ( await p.isDirectory() ) + { + return Promise.resolve(); + } + + let fileName = p.fileName; + + if ( ! this.filterFiles( fileName ) ) + { + return Promise.resolve(); + } + + compilation.fileDependencies.add( p.absolutePath ); + + return Promise.resolve(); + } + ); + + callback(); + + } + ); + + compiler.hooks.emit.tapAsync( "TemplatesIndexBuilder", + async ( compilation:any, callback:any ) => + { + let templates = new TemplatesInfo(); + let templatesPathReference = new PathReference( path.resolve( this.inputDir ) ); + + // RJLog.log( templatesPathReference.absolutePath ); + + await Files.forAllIn( templatesPathReference.absolutePath, null, + async ( p:PathReference ) => + { + if ( await p.isDirectory() ) + { + return Promise.resolve(); + } + + let fileName = p.fileName; + + if ( ! this.filterFiles( fileName ) ) + { + return Promise.resolve(); + } + + let absoluteFilePath = p.absolutePath; + let inputRelativePath = templatesPathReference.createRelativeFromAbsolute( absoluteFilePath, true ); + + // RJLog.log( "Abs to Rel", absoluteFilePath, inputRelativePath.relativePath ); + let outputFileName = path.join( this.outputDir, inputRelativePath.relativePath ); + + templates.templates.push( inputRelativePath.relativePath ); + + let content = fs.readFileSync( p.absolutePath, "utf-8" ); + + // RJLog.log( "Adding", p.absolutePath, outputFileName ); + compilation.assets[ outputFileName ] = + { + source: () => content, + size: () => content.length, + }; + + + return Promise.resolve(); + } + ); + + let templatesPath = path.join( this.outputDir, "index.json" ); + + let templatesJSON = JSON.stringify( templates, null, " " ); + + compilation.assets[ templatesPath ] = + { + source: () => templatesJSON, + size: () => templatesJSON.length, + }; + + callback(); + + return Promise.resolve(); + } + ); + } +}