Skip to content

Commit 9d5a672

Browse files
committed
refactor(project): Refactor specification-internal workspace handling
Prerequisite for versioning support Cherry-picked from: SAP/ui5-project@83b5c4f JIRA: CPOUI5FOUNDATION-1174
1 parent 26ae42c commit 9d5a672

File tree

4 files changed

+166
-210
lines changed

4 files changed

+166
-210
lines changed

packages/project/lib/specifications/ComponentProject.js

Lines changed: 49 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -91,39 +91,7 @@ class ComponentProject extends Project {
9191

9292
/* === Resource Access === */
9393

94-
/**
95-
* Get a [ReaderCollection]{@link @ui5/fs/ReaderCollection} for accessing all resources of the
96-
* project in the specified "style":
97-
*
98-
* <ul>
99-
* <li><b>buildtime:</b> Resource paths are always prefixed with <code>/resources/</code>
100-
* or <code>/test-resources/</code> followed by the project's namespace.
101-
* Any configured build-excludes are applied</li>
102-
* <li><b>dist:</b> Resource paths always match with what the UI5 runtime expects.
103-
* This means that paths generally depend on the project type. Applications for example use a "flat"-like
104-
* structure, while libraries use a "buildtime"-like structure.
105-
* Any configured build-excludes are applied</li>
106-
* <li><b>runtime:</b> Resource paths always match with what the UI5 runtime expects.
107-
* This means that paths generally depend on the project type. Applications for example use a "flat"-like
108-
* structure, while libraries use a "buildtime"-like structure.
109-
* This style is typically used for serving resources directly. Therefore, build-excludes are not applied
110-
* <li><b>flat:</b> Resource paths are never prefixed and namespaces are omitted if possible. Note that
111-
* project types like "theme-library", which can have multiple namespaces, can't omit them.
112-
* Any configured build-excludes are applied</li>
113-
* </ul>
114-
*
115-
* If project resources have been changed through the means of a workspace, those changes
116-
* are reflected in the provided reader too.
117-
*
118-
* Resource readers always use POSIX-style paths.
119-
*
120-
* @public
121-
* @param {object} [options]
122-
* @param {string} [options.style=buildtime] Path style to access resources.
123-
* Can be "buildtime", "dist", "runtime" or "flat"
124-
* @returns {@ui5/fs/ReaderCollection} A reader collection instance
125-
*/
126-
getReader({style = "buildtime"} = {}) {
94+
_getStyledReader(style) {
12795
// TODO: Additional style 'ABAP' using "sap.platform.abap".uri from manifest.json?
12896

12997
// Apply builder excludes to all styles but "runtime"
@@ -161,7 +129,7 @@ class ComponentProject extends Project {
161129
throw new Error(`Unknown path mapping style ${style}`);
162130
}
163131

164-
reader = this._addWriter(reader, style);
132+
// reader = this._addWriter(reader, style, writer);
165133
return reader;
166134
}
167135

@@ -183,52 +151,49 @@ class ComponentProject extends Project {
183151
throw new Error(`_getTestReader must be implemented by subclass ${this.constructor.name}`);
184152
}
185153

186-
/**
187-
* Get a resource reader/writer for accessing and modifying a project's resources
188-
*
189-
* @public
190-
* @returns {@ui5/fs/ReaderCollection} A reader collection instance
191-
*/
192-
getWorkspace() {
193-
// Workspace is always of style "buildtime"
194-
// Therefore builder resource-excludes are always to be applied
195-
const excludes = this.getBuilderResourcesExcludes();
196-
return resourceFactory.createWorkspace({
197-
name: `Workspace for project ${this.getName()}`,
198-
reader: this._getReader(excludes),
199-
writer: this._getWriter().collection
200-
});
201-
}
154+
// /**
155+
// * Get a resource reader/writer for accessing and modifying a project's resources
156+
// *
157+
// * @public
158+
// * @returns {@ui5/fs/ReaderCollection} A reader collection instance
159+
// */
160+
// getWorkspace() {
161+
// // Workspace is always of style "buildtime"
162+
// // Therefore builder resource-excludes are always to be applied
163+
// const excludes = this.getBuilderResourcesExcludes();
164+
// return resourceFactory.createWorkspace({
165+
// name: `Workspace for project ${this.getName()}`,
166+
// reader: this._getPlainReader(excludes),
167+
// writer: this._getWriter().collection
168+
// });
169+
// }
202170

203171
_getWriter() {
204-
if (!this._writers) {
205-
// writer is always of style "buildtime"
206-
const namespaceWriter = resourceFactory.createAdapter({
207-
virBasePath: "/",
208-
project: this
209-
});
172+
// writer is always of style "buildtime"
173+
const namespaceWriter = resourceFactory.createAdapter({
174+
virBasePath: "/",
175+
project: this
176+
});
210177

211-
const generalWriter = resourceFactory.createAdapter({
212-
virBasePath: "/",
213-
project: this
214-
});
178+
const generalWriter = resourceFactory.createAdapter({
179+
virBasePath: "/",
180+
project: this
181+
});
215182

216-
const collection = resourceFactory.createWriterCollection({
217-
name: `Writers for project ${this.getName()}`,
218-
writerMapping: {
219-
[`/resources/${this._namespace}/`]: namespaceWriter,
220-
[`/test-resources/${this._namespace}/`]: namespaceWriter,
221-
[`/`]: generalWriter
222-
}
223-
});
183+
const collection = resourceFactory.createWriterCollection({
184+
name: `Writers for project ${this.getName()}`,
185+
writerMapping: {
186+
[`/resources/${this._namespace}/`]: namespaceWriter,
187+
[`/test-resources/${this._namespace}/`]: namespaceWriter,
188+
[`/`]: generalWriter
189+
}
190+
});
224191

225-
this._writers = {
226-
namespaceWriter,
227-
generalWriter,
228-
collection
229-
};
230-
}
231-
return this._writers;
192+
return {
193+
namespaceWriter,
194+
generalWriter,
195+
collection
196+
};
232197
}
233198

234199
_getReader(excludes) {
@@ -243,15 +208,15 @@ class ComponentProject extends Project {
243208
return reader;
244209
}
245210

246-
_addWriter(reader, style) {
247-
const {namespaceWriter, generalWriter} = this._getWriter();
211+
_addReadersFromWriter(style, readers, writer) {
212+
const {namespaceWriter, generalWriter} = writer;
248213

249214
if ((style === "runtime" || style === "dist") && this._isRuntimeNamespaced) {
250215
// If the project's type requires a namespace at runtime, the
251216
// dist- and runtime-style paths are identical to buildtime-style paths
252217
style = "buildtime";
253218
}
254-
const readers = [];
219+
255220
switch (style) {
256221
case "buildtime":
257222
// Writer already uses buildtime style
@@ -279,12 +244,13 @@ class ComponentProject extends Project {
279244
default:
280245
throw new Error(`Unknown path mapping style ${style}`);
281246
}
282-
readers.push(reader);
247+
// return readers;
248+
// readers.push(reader);
283249

284-
return resourceFactory.createReaderCollectionPrioritized({
285-
name: `Reader/Writer collection for project ${this.getName()}`,
286-
readers
287-
});
250+
// return resourceFactory.createReaderCollectionPrioritized({
251+
// name: `Reader/Writer collection for project ${this.getName()}`,
252+
// readers
253+
// });
288254
}
289255

290256
/* === Internals === */

packages/project/lib/specifications/Project.js

Lines changed: 76 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import Specification from "./Specification.js";
22
import ResourceTagCollection from "@ui5/fs/internal/ResourceTagCollection";
3+
import {createWorkspace, createReaderCollectionPrioritized} from "@ui5/fs/resourceFactory";
34

45
/**
56
* Project
@@ -12,6 +13,12 @@ import ResourceTagCollection from "@ui5/fs/internal/ResourceTagCollection";
1213
* @hideconstructor
1314
*/
1415
class Project extends Specification {
16+
#latestWriter;
17+
#latestWorkspace;
18+
#latestReader = new Map();
19+
#writerVersions = [];
20+
#workspaceSealed = false;
21+
1522
constructor(parameters) {
1623
super(parameters);
1724
if (new.target === Project) {
@@ -220,6 +227,7 @@ class Project extends Specification {
220227
}
221228

222229
/* === Resource Access === */
230+
223231
/**
224232
* Get a [ReaderCollection]{@link @ui5/fs/ReaderCollection} for accessing all resources of the
225233
* project in the specified "style":
@@ -241,38 +249,93 @@ class Project extends Specification {
241249
* Any configured build-excludes are applied</li>
242250
* </ul>
243251
*
252+
* If project resources have been changed through the means of a workspace, those changes
253+
* are reflected in the provided reader too.
254+
*
244255
* Resource readers always use POSIX-style paths.
245256
*
246257
* @public
247258
* @param {object} [options]
248259
* @param {string} [options.style=buildtime] Path style to access resources.
249260
* Can be "buildtime", "dist", "runtime" or "flat"
250-
* @returns {@ui5/fs/ReaderCollection} Reader collection allowing access to all resources of the project
261+
* @returns {@ui5/fs/ReaderCollection} A reader collection instance
251262
*/
252-
getReader(options) {
253-
throw new Error(`getReader must be implemented by subclass ${this.constructor.name}`);
263+
getReader({style = "buildtime"} = {}) {
264+
let reader = this.#latestReader.get(style);
265+
if (reader) {
266+
return reader;
267+
}
268+
const readers = [];
269+
this._addReadersFromWriter(style, readers, this.getWriter());
270+
readers.push(this._getStyledReader(style));
271+
reader = createReaderCollectionPrioritized({
272+
name: `Reader collection for project ${this.getName()}`,
273+
readers
274+
});
275+
this.#latestReader.set(style, reader);
276+
return reader;
254277
}
255278

256-
getResourceTagCollection() {
257-
if (!this._resourceTagCollection) {
258-
this._resourceTagCollection = new ResourceTagCollection({
259-
allowedTags: ["ui5:IsDebugVariant", "ui5:HasDebugVariant"],
260-
allowedNamespaces: ["project"],
261-
tags: this.getBuildManifest()?.tags
262-
});
263-
}
264-
return this._resourceTagCollection;
279+
getWriter() {
280+
return this.#latestWriter || this.createNewWriterVersion();
281+
}
282+
283+
createNewWriterVersion() {
284+
const writer = this._getWriter();
285+
this.#writerVersions.push(writer);
286+
this.#latestWriter = writer;
287+
288+
// Invalidate dependents
289+
this.#latestWorkspace = null;
290+
this.#latestReader = new Map();
291+
292+
return writer;
265293
}
266294

267295
/**
268296
* Get a [DuplexCollection]{@link @ui5/fs/DuplexCollection} for accessing and modifying a
269297
* project's resources. This is always of style <code>buildtime</code>.
270298
*
299+
* Once a project has finished building, this method will throw to prevent further modifications
300+
* since those would have no effect. Use the getReader method to access the project's (modified) resources
301+
*
271302
* @public
272303
* @returns {@ui5/fs/DuplexCollection} DuplexCollection
273304
*/
274305
getWorkspace() {
275-
throw new Error(`getWorkspace must be implemented by subclass ${this.constructor.name}`);
306+
if (this.#workspaceSealed) {
307+
throw new Error(
308+
`Workspace of project ${this.getName()} has been sealed. Use method #getReader for read-only access`);
309+
}
310+
if (this.#latestWorkspace) {
311+
return this.#latestWorkspace;
312+
}
313+
const excludes = this.getBuilderResourcesExcludes(); // TODO: Do not apply in server context
314+
const writer = this.getWriter();
315+
this.#latestWorkspace = createWorkspace({
316+
reader: this._getReader(excludes),
317+
writer: writer.collection || writer
318+
});
319+
return this.#latestWorkspace;
320+
}
321+
322+
sealWorkspace() {
323+
this.#workspaceSealed = true;
324+
}
325+
326+
_addReadersFromWriter(style, readers, writer) {
327+
readers.push(writer);
328+
}
329+
330+
getResourceTagCollection() {
331+
if (!this._resourceTagCollection) {
332+
this._resourceTagCollection = new ResourceTagCollection({
333+
allowedTags: ["ui5:IsDebugVariant", "ui5:HasDebugVariant"],
334+
allowedNamespaces: ["project"],
335+
tags: this.getBuildManifest()?.tags
336+
});
337+
}
338+
return this._resourceTagCollection;
276339
}
277340

278341
/* === Internals === */

packages/project/lib/specifications/types/Module.js

Lines changed: 18 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -33,65 +33,29 @@ class Module extends Project {
3333

3434
/* === Resource Access === */
3535

36-
/**
37-
* Get a [ReaderCollection]{@link @ui5/fs/ReaderCollection} for accessing all resources of the
38-
* project in the specified "style":
39-
*
40-
* <ul>
41-
* <li><b>buildtime:</b> Resource paths are always prefixed with <code>/resources/</code>
42-
* or <code>/test-resources/</code> followed by the project's namespace.
43-
* Any configured build-excludes are applied</li>
44-
* <li><b>dist:</b> Resource paths always match with what the UI5 runtime expects.
45-
* This means that paths generally depend on the project type. Applications for example use a "flat"-like
46-
* structure, while libraries use a "buildtime"-like structure.
47-
* Any configured build-excludes are applied</li>
48-
* <li><b>runtime:</b> Resource paths always match with what the UI5 runtime expects.
49-
* This means that paths generally depend on the project type. Applications for example use a "flat"-like
50-
* structure, while libraries use a "buildtime"-like structure.
51-
* This style is typically used for serving resources directly. Therefore, build-excludes are not applied
52-
* <li><b>flat:</b> Resource paths are never prefixed and namespaces are omitted if possible. Note that
53-
* project types like "theme-library", which can have multiple namespaces, can't omit them.
54-
* Any configured build-excludes are applied</li>
55-
* </ul>
56-
*
57-
* If project resources have been changed through the means of a workspace, those changes
58-
* are reflected in the provided reader too.
59-
*
60-
* Resource readers always use POSIX-style paths.
61-
*
62-
* @public
63-
* @param {object} [options]
64-
* @param {string} [options.style=buildtime] Path style to access resources.
65-
* Can be "buildtime", "dist", "runtime" or "flat"
66-
* @returns {@ui5/fs/ReaderCollection} A reader collection instance
67-
*/
68-
getReader({style = "buildtime"} = {}) {
36+
_getStyledReader(style) {
6937
// Apply builder excludes to all styles but "runtime"
7038
const excludes = style === "runtime" ? [] : this.getBuilderResourcesExcludes();
7139

72-
const reader = this._getReader(excludes);
73-
return resourceFactory.createReaderCollectionPrioritized({
74-
name: `Reader/Writer collection for project ${this.getName()}`,
75-
readers: [this._getWriter(), reader]
76-
});
40+
return this._getReader(excludes);
7741
}
7842

79-
/**
80-
* Get a resource reader/writer for accessing and modifying a project's resources
81-
*
82-
* @public
83-
* @returns {@ui5/fs/ReaderCollection} A reader collection instance
84-
*/
85-
getWorkspace() {
86-
const excludes = this.getBuilderResourcesExcludes();
87-
const reader = this._getReader(excludes);
88-
89-
const writer = this._getWriter();
90-
return resourceFactory.createWorkspace({
91-
reader,
92-
writer
93-
});
94-
}
43+
// /**
44+
// * Get a resource reader/writer for accessing and modifying a project's resources
45+
// *
46+
// * @public
47+
// * @returns {@ui5/fs/ReaderCollection} A reader collection instance
48+
// */
49+
// getWorkspace() {
50+
// const excludes = this.getBuilderResourcesExcludes();
51+
// const reader = this._getReader(excludes);
52+
53+
// const writer = this._getWriter();
54+
// return resourceFactory.createWorkspace({
55+
// reader,
56+
// writer
57+
// });
58+
// }
9559

9660
_getReader(excludes) {
9761
const readers = this._paths.map(({name, virBasePath, fsBasePath}) => {

0 commit comments

Comments
 (0)