-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
#296: first draft of the new cache model
- Loading branch information
1 parent
6a2730c
commit de66ec4
Showing
38 changed files
with
1,360 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
|
||
import { FileManager, Files } from '@jitar/runtime'; | ||
|
||
import { ApplicationReader, ApplicationBuilder } from './application'; | ||
|
||
export default class Builder | ||
{ | ||
#projectFileManager: FileManager; | ||
#appFileManager: FileManager; | ||
|
||
#applicationReader: ApplicationReader; | ||
#applicationBuilder: ApplicationBuilder; | ||
|
||
constructor(projectFileManager: FileManager, appFileManager: FileManager) | ||
{ | ||
this.#projectFileManager = projectFileManager; | ||
this.#appFileManager = appFileManager; | ||
|
||
this.#applicationReader = new ApplicationReader(appFileManager); | ||
this.#applicationBuilder = new ApplicationBuilder(appFileManager); | ||
} | ||
|
||
async build(): Promise<void> | ||
{ | ||
const moduleFiles = await this.#appFileManager.filter(Files.MODULE_PATTERN); | ||
const segmentFiles = await this.#projectFileManager.filter(Files.SEGMENT_PATTERN); | ||
|
||
const application = await this.#applicationReader.read(moduleFiles, segmentFiles); | ||
|
||
return this.#applicationBuilder.build(application); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
|
||
import type { FileManager } from '@jitar/runtime'; | ||
|
||
import type Application from './models/Application'; | ||
|
||
import ModuleBuilder from './ModuleBuilder'; | ||
import SegmentBuilder from './SegmentBuilder'; | ||
|
||
export default class Builder | ||
{ | ||
#moduleBuilder: ModuleBuilder; | ||
#segmentBuilder: SegmentBuilder; | ||
|
||
constructor(fileManager: FileManager) | ||
{ | ||
this.#moduleBuilder = new ModuleBuilder(fileManager); | ||
this.#segmentBuilder = new SegmentBuilder(fileManager); | ||
} | ||
|
||
async build(application: Application): Promise<void> | ||
{ | ||
await Promise.all([ | ||
this.#moduleBuilder.build(application), | ||
this.#segmentBuilder.build(application) | ||
]); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
|
||
import { Module } from '../module'; | ||
|
||
export default class ClassSourceBuilder | ||
{ | ||
#module: Module; | ||
|
||
constructor(module: Module) | ||
{ | ||
this.#module = module; | ||
} | ||
|
||
build(): string | ||
{ | ||
const filename = this.#module.filename; | ||
const classes = this.#module.reflection.exportedClasses; | ||
const classNames = classes.map(clazz => clazz.name); | ||
const sourceCode = classNames.map(className => `${className}.source = "/${filename}";`); | ||
|
||
return sourceCode.join('\n'); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,171 @@ | ||
|
||
import { ReflectionImport, Reflector } from '@jitar/reflection'; | ||
|
||
import { Module } from '../module'; | ||
import { Segmentation, Segment } from '../segment'; | ||
import { FileHelper } from '../utils'; | ||
|
||
const KEYWORD_DEFAULT = 'default'; | ||
const IMPORT_PATTERN = /import\s(?:["'\s]*([\w*{}\n, ]+)from\s*)?["'\s]*([@\w/._-]+)["'\s].*/g; | ||
const APPLICATION_MODULE_INDICATORS = ['.', '/', 'http:', 'https:']; | ||
|
||
const reflector = new Reflector(); | ||
|
||
export default class ImportRewriter | ||
{ | ||
#module: Module; | ||
#segmentation: Segmentation; | ||
#segment: Segment | undefined; | ||
|
||
constructor(module: Module, segmentation: Segmentation, segment?: Segment) | ||
{ | ||
this.#module = module; | ||
this.#segmentation = segmentation; | ||
this.#segment = segment; | ||
} | ||
|
||
rewrite(): string | ||
{ | ||
const replacer = (statement: string) => this.#replaceImport(statement); | ||
|
||
const code = this.#module.code; | ||
|
||
return code.replaceAll(IMPORT_PATTERN, replacer); | ||
} | ||
|
||
#replaceImport(statement: string): string | ||
{ | ||
const dependency = reflector.parseImport(statement); | ||
|
||
return this.#isApplicationModule(dependency) | ||
? this.#rewriteApplicationImport(dependency) | ||
: this.#rewriteRuntimeImport(dependency); | ||
} | ||
|
||
#isApplicationModule(dependency: ReflectionImport): boolean | ||
{ | ||
return APPLICATION_MODULE_INDICATORS.some(indicator => dependency.from.startsWith(indicator, 1)); | ||
} | ||
|
||
#rewriteApplicationImport(dependency: ReflectionImport): string | ||
{ | ||
const targetModuleFilename = this.#getTargetModuleFilename(dependency); | ||
|
||
if (this.#segmentation.isModuleSegmented(targetModuleFilename)) | ||
{ | ||
// import segmented module | ||
|
||
if (this.#segment?.hasModule(targetModuleFilename)) | ||
{ | ||
const from = this.#rewriteApplicationFrom(targetModuleFilename, this.#segment.name); | ||
|
||
return this.#rewriteToStaticImport(dependency, from); // same segment | ||
} | ||
|
||
const from = this.#rewriteApplicationFrom(targetModuleFilename, 'remote'); | ||
|
||
return this.#rewriteToStaticImport(dependency, from); // different segments | ||
} | ||
|
||
// import shared (unsegmented) module | ||
|
||
const from = this.#rewriteApplicationFrom(targetModuleFilename, 'shared'); | ||
|
||
return this.#segment === undefined | ||
? this.#rewriteToStaticImport(dependency, from) // shared to shared | ||
: this.#rewriteToDynamicImport(dependency, from); // segmented to shared (prevent bundling) | ||
} | ||
|
||
#rewriteRuntimeImport(dependency: ReflectionImport): string | ||
{ | ||
const from = this.#rewriteRuntimeFrom(dependency); | ||
|
||
return this.#rewriteToStaticImport(dependency, from); | ||
} | ||
|
||
#rewriteApplicationFrom(filename: string, scope: string): string | ||
{ | ||
const callingModulePath = FileHelper.extractPath(this.#module.filename); | ||
const relativeFilename = FileHelper.makePathRelative(filename, callingModulePath); | ||
|
||
return FileHelper.addSubExtension(relativeFilename, scope); | ||
} | ||
|
||
#rewriteRuntimeFrom(dependency: ReflectionImport): string | ||
{ | ||
return this.#stripFrom(dependency.from); | ||
} | ||
|
||
#rewriteToStaticImport(dependency: ReflectionImport, from: string): string | ||
{ | ||
if (dependency.members.length === 0) | ||
{ | ||
return `import "${from}";`; | ||
} | ||
|
||
const members = this.#rewriteStaticImportMembers(dependency); | ||
|
||
return `import ${members} from "${from}";`; | ||
} | ||
|
||
#rewriteToDynamicImport(dependency: ReflectionImport, from: string): string | ||
{ | ||
if (dependency.members.length === 0) | ||
{ | ||
return `await import("${from}");`; | ||
} | ||
|
||
const members = this.#rewriteDynamicImportMembers(dependency); | ||
|
||
return `const ${members} = await import("${from}");`; | ||
} | ||
|
||
#rewriteStaticImportMembers(dependency: ReflectionImport): string | ||
{ | ||
const defaultMember = dependency.members.find(member => member.name === KEYWORD_DEFAULT); | ||
const hasDefaultMember = defaultMember !== undefined; | ||
const defaultMemberImport = hasDefaultMember ? defaultMember.as : ''; | ||
|
||
const namedMembers = dependency.members.filter(member => member.name !== KEYWORD_DEFAULT); | ||
const namedMemberImports = namedMembers.map(member => member.name !== member.as ? `${member.name} as ${member.as}` : member.name); | ||
const hasNamedMembers = namedMemberImports.length > 0; | ||
const groupedNamedMemberImports = hasNamedMembers ? `{ ${namedMemberImports.join(', ')} }` : ''; | ||
|
||
const separator = hasDefaultMember && hasNamedMembers ? ', ' : ''; | ||
|
||
return `${defaultMemberImport}${separator}${groupedNamedMemberImports}`; | ||
} | ||
|
||
#rewriteDynamicImportMembers(dependency: ReflectionImport): string | ||
{ | ||
if (this.#doesImportAll(dependency)) | ||
{ | ||
return dependency.members[0].as; | ||
} | ||
|
||
const members = dependency.members; | ||
const memberImports = members.map(member => member.name !== member.as ? `${member.name} : ${member.as}` : member.name); | ||
|
||
return `{ ${memberImports.join(', ')} }`; | ||
} | ||
|
||
#getTargetModuleFilename(dependency: ReflectionImport): string | ||
{ | ||
const from = this.#stripFrom(dependency.from); | ||
const callingModulePath = FileHelper.extractPath(this.#module.filename); | ||
const translated = FileHelper.makePathAbsolute(from, callingModulePath); | ||
|
||
return FileHelper.assureExtension(translated); | ||
} | ||
|
||
#doesImportAll(dependency: ReflectionImport): boolean | ||
{ | ||
return dependency.members.length === 1 | ||
&& dependency.members[0].name === '*'; | ||
} | ||
|
||
#stripFrom(from: string): string | ||
{ | ||
return from.substring(1, from.length - 1); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
|
||
import { Module } from '../module'; | ||
import { Segmentation, Segment } from '../segment'; | ||
|
||
import ClassSourceBuilder from './ClassSourceBuilder'; | ||
import ImportRewriter from './ImportRewriter'; | ||
|
||
export default class LocalModuleBuilder | ||
{ | ||
build(module: Module, segmentation: Segmentation, segment?: Segment): string | ||
{ | ||
const classSourceBuilder = new ClassSourceBuilder(module); | ||
const importRewriter = new ImportRewriter(module, segmentation, segment); | ||
|
||
const importCode = importRewriter.rewrite(); | ||
const sourceCode = classSourceBuilder.build(); | ||
|
||
return `${importCode}\n${sourceCode}`; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
|
||
import type { FileManager } from '@jitar/runtime'; | ||
|
||
import { Module } from '../module'; | ||
import { Segmentation, Segment } from '../segment'; | ||
import { FileHelper } from '../utils'; | ||
|
||
import type Application from './models/Application'; | ||
|
||
import RemoteModuleBuilder from './RemoteModuleBuilder'; | ||
import LocalModuleBuilder from './LocalModuleBuilder'; | ||
|
||
export default class ModuleBuilder | ||
{ | ||
#fileManager: FileManager; | ||
#localModuleBuilder: LocalModuleBuilder; | ||
#remoteModuleBuilder: RemoteModuleBuilder; | ||
|
||
constructor(fileManager: FileManager) | ||
{ | ||
this.#fileManager = fileManager; | ||
|
||
this.#localModuleBuilder = new LocalModuleBuilder(); | ||
this.#remoteModuleBuilder = new RemoteModuleBuilder(); | ||
} | ||
|
||
async build(application: Application): Promise<void> | ||
{ | ||
const repository = application.repository; | ||
const segmentation = application.segmentation; | ||
|
||
const builds = repository.modules.map(module => this.#buildModule(module, segmentation)); | ||
|
||
await Promise.all(builds); | ||
} | ||
|
||
async #buildModule(module: Module, segmentation: Segmentation): Promise<void> | ||
{ | ||
const moduleSegments = segmentation.getSegments(module.filename); | ||
|
||
// If the module is not part of any segment, it is an application module | ||
|
||
if (moduleSegments.length === 0) | ||
{ | ||
return this.#buildSharedModule(module, segmentation); | ||
} | ||
|
||
// Otherwise, it is a segment module that can be called remotely | ||
|
||
const segmentBuilds = moduleSegments.map(segment => this.#buildSegmentModule(module, segment, segmentation)); | ||
const remoteBuild = this.#buildRemoteModule(module, moduleSegments); | ||
|
||
await Promise.all([...segmentBuilds, remoteBuild]); | ||
|
||
this.#fileManager.delete(module.filename); | ||
} | ||
|
||
async #buildSharedModule(module: Module, segmentation: Segmentation): Promise<void> | ||
{ | ||
const filename = FileHelper.addSubExtension(module.filename, 'shared'); | ||
const code = this.#localModuleBuilder.build(module, segmentation); | ||
|
||
return this.#fileManager.write(filename, code); | ||
} | ||
|
||
async #buildSegmentModule(module: Module, segment: Segment, segmentation: Segmentation): Promise<void> | ||
{ | ||
const filename = FileHelper.addSubExtension(module.filename, segment.name); | ||
const code = this.#localModuleBuilder.build(module, segmentation, segment); | ||
|
||
return this.#fileManager.write(filename, code); | ||
} | ||
|
||
async #buildRemoteModule(module: Module, segments: Segment[]): Promise<void> | ||
{ | ||
// The remote module contains calls to segmented procedures only | ||
|
||
const segmentModules = segments.map(segment => segment.getModule(module.filename)); | ||
const implementations = segmentModules.flatMap(segmentModule => segmentModule!.implementations); | ||
|
||
// TODO: Make the list of implementations unique | ||
|
||
const filename = FileHelper.addSubExtension(module.filename, 'remote'); | ||
const code = this.#remoteModuleBuilder.build(implementations); | ||
|
||
return this.#fileManager.write(filename, code); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
|
||
import type { FileManager } from '@jitar/runtime'; | ||
|
||
import { ModuleReader } from '../module'; | ||
import { SegmentReader } from '../segment'; | ||
|
||
import Application from './models/Application.js'; | ||
|
||
export default class Reader | ||
{ | ||
#fileManager: FileManager; | ||
|
||
constructor(fileManager: FileManager) | ||
{ | ||
this.#fileManager = fileManager; | ||
} | ||
|
||
async read(moduleFiles: string[], segmentFiles: string[]): Promise<Application> | ||
{ | ||
const moduleReader = new ModuleReader(this.#fileManager); | ||
const repository = await moduleReader.readAll(moduleFiles); | ||
|
||
const segmentReader = new SegmentReader(this.#fileManager, repository); | ||
const segmentation = await segmentReader.readAll(segmentFiles); | ||
|
||
return new Application(repository, segmentation); | ||
} | ||
} |
Oops, something went wrong.