-
Notifications
You must be signed in to change notification settings - Fork 22
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[FEATURE] Add 'libraryLessGenerator' processor #560
Merged
Merged
Changes from 10 commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
e12bd25
[FEATURE] Add 'libraryLessGenerator' processor
matz3 45812c3
Resolve import paths
matz3 449f4cb
WIP: Handle special imports
matz3 d2d76a6
Add more test cases / Minor fixes
matz3 3b058f4
Finish rewrite logic / fix processor
matz3 3eee114
Refactoring
matz3 ab65b69
Improve processor test
matz3 fe6d539
Review feedback
matz3 7f8f07d
Add index.js export
matz3 09e2272
Rename private class export
matz3 0818828
Enhance JSDoc description
matz3 6c1c582
Remove handling of global.css / Add error handling
matz3 80ad4ee
JSDoc feedback
matz3 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
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,151 @@ | ||
const log = require("@ui5/logger").getLogger("builder:processors:libraryLessGenerator"); | ||
|
||
const {promisify} = require("util"); | ||
const posixPath = require("path").posix; | ||
const Resource = require("@ui5/fs").Resource; | ||
|
||
const IMPORT_PATTERN = /@import .*"(.*)";/g; | ||
const BASE_LESS_PATTERN = /^\/resources\/sap\/ui\/core\/themes\/([^/]+)\/base\.less$/; | ||
const GLOBAL_LESS_PATTERN = /^\/resources\/sap\/ui\/core\/themes\/([^/]+)\/global\.(less|css)$/; | ||
|
||
class LibraryLessGenerator { | ||
constructor({fs}) { | ||
const readFile = promisify(fs.readFile); | ||
this.readFile = async (filePath) => readFile(filePath, {encoding: "utf8"}); | ||
} | ||
async generate({filePath, fileContent}) { | ||
return `/* NOTE: This file was generated as an optimized version of ` + | ||
`"library.source.less" for the Theme Designer. */\n\n` + | ||
await this.resolveLessImports({ | ||
filePath, | ||
fileContent | ||
}); | ||
} | ||
getPathToRoot(filePath) { | ||
return posixPath.relative(posixPath.dirname(filePath), "/"); | ||
} | ||
async resolveLessImports({filePath, fileContent}) { | ||
const imports = this.findLessImports(fileContent); | ||
if (!imports.length) { | ||
// Skip processing when no imports are found | ||
return fileContent; | ||
} | ||
const replacements = await Promise.all(imports.map(async (importMatch) => { | ||
const baseDir = posixPath.dirname(filePath); | ||
const resolvedFilePath = posixPath.resolve(baseDir, importMatch.path); | ||
importMatch.content = await this.resolveLessImport(importMatch.path, resolvedFilePath, baseDir); | ||
return importMatch; | ||
})); | ||
|
||
// Apply replacements in reverse order to not modify the relevant indices | ||
const array = Array.from(fileContent); | ||
for (let i = replacements.length - 1; i >= 0; i--) { | ||
const replacement = replacements[i]; | ||
if (!replacement.content) { | ||
continue; | ||
} | ||
array.splice( | ||
/* index */ replacement.matchStart, | ||
/* count */ replacement.matchLength, | ||
/* insert */ replacement.content | ||
); | ||
} | ||
return array.join(""); | ||
} | ||
async resolveLessImport(originalFilePath, resolvedFilePath, baseDir) { | ||
// Rewrite base.less imports | ||
const baseLessMatch = BASE_LESS_PATTERN.exec(resolvedFilePath); | ||
if (baseLessMatch) { | ||
let baseLessThemeName = baseLessMatch[1]; | ||
if (baseLessThemeName === "base") { | ||
baseLessThemeName = "baseTheme"; | ||
} | ||
const baseLessPath = this.getPathToRoot(resolvedFilePath) + | ||
"/Base/baseLib/" + baseLessThemeName + "/base.less"; | ||
return "@import \"" + baseLessPath + "\"; /* ORIGINAL IMPORT PATH: \"" + originalFilePath + "\" */\n"; | ||
} | ||
|
||
// Rewrite library imports to correct file name | ||
if (posixPath.basename(resolvedFilePath) === "library.source.less") { | ||
return `@import "${originalFilePath.replace(/library\.source\.less$/, "library.less")}";`; | ||
} | ||
|
||
// Excluding global.(css|less) within sap.ui.core | ||
// It must be imported by the Theme Designer (also see declaration in sap/ui/core/.theming) | ||
if (GLOBAL_LESS_PATTERN.test(resolvedFilePath)) { | ||
return null; | ||
RandomByte marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
/* | ||
* Throw error in case of files which are not in the same directory as the current file because | ||
* inlining them would break relative URLs. | ||
* Keeping the import is also not possible since only "library.less" and "global.less" are | ||
* configured to be available to the Theme Designer (.theming generated in generateThemeDesignerResources). | ||
* | ||
* A possible solution would be to rewrite relative URLs when inlining the content. | ||
*/ | ||
const relativeFilePath = posixPath.relative(baseDir, resolvedFilePath); | ||
if (relativeFilePath.includes(posixPath.sep)) { | ||
throw new Error( | ||
`libraryLessGenerator: Unsupported import of file '${resolvedFilePath}'. ` + | ||
`Stylesheets must be located in the theme directory '${baseDir}' (no sub-directories)` | ||
); | ||
} | ||
|
||
const importedFileContent = await this.readFile(resolvedFilePath); | ||
return `/* START "${originalFilePath}" */\n` + | ||
await this.resolveLessImports({ | ||
filePath: resolvedFilePath, | ||
fileContent: importedFileContent | ||
}) + | ||
`/* END "${originalFilePath}" */\n`; | ||
} | ||
findLessImports(fileContent) { | ||
const imports = []; | ||
let match; | ||
while ((match = IMPORT_PATTERN.exec(fileContent)) !== null) { | ||
imports.push({ | ||
path: match[1], | ||
matchStart: match.index, | ||
matchLength: match[0].length | ||
}); | ||
} | ||
return imports; | ||
} | ||
} | ||
|
||
/** | ||
* Creates a library.less file for the SAP Theme Designer based on a library.source.less file. | ||
* | ||
* @public | ||
* @alias module:@ui5/builder.processors.libraryLessGenerator | ||
* @param {object} parameters Parameters | ||
* @param {module:@ui5/fs.Resource[]} parameters.resources List of <code>library.source.less</code> | ||
* resources | ||
* @param {fs|module:@ui5/fs.fsInterface} parameters.fs Node fs or custom | ||
* [fs interface]{@link module:resources/module:@ui5/fs.fsInterface} | ||
* @returns {Promise<module:@ui5/fs.Resource[]>} Promise resolving with library.less resources | ||
*/ | ||
module.exports = async function({resources, fs}) { | ||
const generator = new LibraryLessGenerator({fs}); | ||
return Promise.all(resources.map(async (librarySourceLessResource) => { | ||
const filePath = librarySourceLessResource.getPath(); | ||
const fileContent = await librarySourceLessResource.getString(); | ||
|
||
log.verbose(`Generating library.less file based on ${filePath}`); | ||
|
||
const libraryLessFileContent = await generator.generate({filePath, fileContent}); | ||
const libraryLessFilePath = posixPath.join(posixPath.dirname(filePath), "library.less"); | ||
|
||
return new Resource({ | ||
path: libraryLessFilePath, | ||
string: libraryLessFileContent | ||
}); | ||
})); | ||
}; | ||
|
||
// Export class for testing only | ||
/* istanbul ignore else */ | ||
if (process.env.NODE_ENV === "test") { | ||
module.exports._LibraryLessGenerator = LibraryLessGenerator; | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
global.css
was only relevant for old themes.The only reference I could still find is in "sap_platinum" which has been removed with 1.48, so I think it's save to also not handle it in here.