Skip to content
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 Library/Component preload exclude configuration #573

Merged
merged 26 commits into from
Feb 9, 2021
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
df2039d
[FEATURE] Library/Component preload exclude configuration
matz3 Jan 25, 2021
6cbf3f7
[INTERNAL] generateComponentPreload: Only add excludes that start wit…
matz3 Feb 1, 2021
b0acc26
Fix namespace check
matz3 Feb 1, 2021
a98ee17
WIP: Update generateComponentPreload exclude pattern logic
matz3 Feb 1, 2021
44486cd
WIP LibraryFormatter: Add .library fallback for libraryPreload excludes
RandomByte Feb 1, 2021
2e3fae1
Fix WIP code in generateComponentPreload / add test cases
matz3 Feb 2, 2021
3f68f69
WIP: Add generateLibraryPreload test case
matz3 Feb 2, 2021
2ea492e
WIP: Check for unused excludes
matz3 Feb 2, 2021
d8eadb2
Basic implementation for generateLibraryPreload
matz3 Feb 2, 2021
6d037ff
Add generateLibraryPreload test case with invalid excludes
matz3 Feb 2, 2021
e974ec2
Handle nested components
matz3 Feb 2, 2021
1ee5c7f
Add missing assertions
matz3 Feb 2, 2021
21317b5
Fix library preload excludes, add verbose logging
matz3 Feb 3, 2021
f13ded4
LibraryBuilder: Pass excludes to generateComponentPreload task
matz3 Feb 3, 2021
7812a8e
LibraryFormatter: Use explicitCharkey for .library parsing
matz3 Feb 3, 2021
2c49168
Adopt tests
matz3 Feb 3, 2021
6153da0
Add LibraryFormatter tests
matz3 Feb 4, 2021
05697c0
Add application e2e build tests
matz3 Feb 4, 2021
53b83b6
Adopt warning log
matz3 Feb 4, 2021
5f382d7
Remove resolved TODOs
matz3 Feb 4, 2021
852622a
JSDoc: Update bundle filter description based on review
RandomByte Feb 8, 2021
aaee1bb
ResourceFilterList.negateFilters: Always prefix includes with '+'
RandomByte Feb 8, 2021
3f34a14
Adopt comment about filter checks
matz3 Feb 8, 2021
f21a51b
Add moduleBundler unit test
matz3 Feb 8, 2021
d0f9ef0
generateComponentPreload: Add test for excludes without namespace
RandomByte Feb 9, 2021
172bb9d
generateLibraryPreload JSDoc: Clarify that exclude only applies to li…
RandomByte Feb 9, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 23 additions & 1 deletion lib/lbt/resources/ResourceFilterList.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

const log = require("@ui5/logger").getLogger("lbt:resources:ResourceFilterList");

const FILTER_PREFIXES = /^[-!+]/;

function makeFileTypePattern(fileTypes) {
if ( fileTypes == null ) {
return undefined;
Expand All @@ -21,7 +23,7 @@ function makeMatcher(globPattern, fileTypesPattern) {
};

// cut off leading '!', '-' or '+'
if ( /^[-!+]/.test(globPattern) ) {
if ( FILTER_PREFIXES.test(globPattern) ) {
result.include = globPattern[0] === "+";
globPattern = globPattern.slice(1);
}
Expand Down Expand Up @@ -161,4 +163,24 @@ ResourceFilterList.fromString = function(filterStr) {
return result;
};

ResourceFilterList.negateFilters = function(patterns) {
return patterns.map((pattern) => {
let include = true;

// cut off leading '!', '-' or '+'
if (FILTER_PREFIXES.test(pattern)) {
include = pattern[0] === "+";
pattern = pattern.slice(1);
RandomByte marked this conversation as resolved.
Show resolved Hide resolved
}

if (include) {
// include => exclude
return "!" + pattern;
} else {
// exclude => include
return pattern;
}
});
};

module.exports = ResourceFilterList;
2 changes: 1 addition & 1 deletion lib/processors/bundlers/moduleBundler.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ const EvoResource = require("@ui5/fs").Resource;
* in- or excluded.
* A pattern either contains of a trailing slash '/' or single '*' and double '**' asterisks which denote an
* arbitrary number of characters or folder names.
* Exludes should be marked with a leading exclamation mark '!'. The order of filters is relevant, a later
* Excludes should be marked with a leading exclamation mark '!'. The order of filters is relevant, a later
RandomByte marked this conversation as resolved.
Show resolved Hide resolved
* exclusion overrides an earlier inclusion and vice versa.
* @example <caption>List of modules as glob patterns that should be in- or excluded</caption>
* // Includes everything from "some/path/to/module/",
Expand Down
73 changes: 54 additions & 19 deletions lib/tasks/bundlers/generateComponentPreload.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ const path = require("path");
const moduleBundler = require("../../processors/bundlers/moduleBundler");
const log = require("@ui5/logger").getLogger("builder:tasks:bundlers:generateComponentPreload");
const ReaderCollectionPrioritized = require("@ui5/fs").ReaderCollectionPrioritized;
const {negateFilters} = require("../../lbt/resources/ResourceFilterList");

/**
* Task to for application bundling.
Expand All @@ -14,24 +15,32 @@ const ReaderCollectionPrioritized = require("@ui5/fs").ReaderCollectionPrioritiz
* @param {module:@ui5/builder.tasks.TaskUtil|object} [parameters.taskUtil] TaskUtil
* @param {object} parameters.options Options
* @param {string} parameters.options.projectName Project name
* @param {Array} [parameters.options.paths] Array of paths (or glob patterns) for component files
* @param {Array} [parameters.options.namespaces] Array of component namespaces
* @param {string[]} [parameters.options.excludes=[]] List of modules declared as glob patterns (resource name patterns)
* that should be excluded. A pattern either contains of a trailing slash '/' or single '*' and double '**' asterisks
RandomByte marked this conversation as resolved.
Show resolved Hide resolved
* which denote an arbitrary number of characters or folder names.
* Re-includes should be marked with a leading exclamation mark '!'. The order of filters is relevant, a later
RandomByte marked this conversation as resolved.
Show resolved Hide resolved
* inclusion overrides an earlier exclusion and vice versa.
* @param {string[]} [parameters.options.paths] Array of paths (or glob patterns) for component files
* @param {string[]} [parameters.options.namespaces] Array of component namespaces
* @returns {Promise<undefined>} Promise resolving with <code>undefined</code> once data has been written
*/
module.exports = function({workspace, dependencies, taskUtil, options: {projectName, paths, namespaces}}) {
module.exports = function({
workspace, dependencies, taskUtil, options: {projectName, paths, namespaces, excludes = []}
}) {
const combo = new ReaderCollectionPrioritized({
name: `generateComponentPreload - prioritize workspace over dependencies: ${projectName}`,
readers: [workspace, dependencies]
});

// TODO 3.0: Limit to workspace resources?
return combo.byGlob("/resources/**/*.{js,json,xml,html,properties,library}")
.then(async (resources) => {
let allNamespaces = [];
if (paths) {
allNamespaces = await Promise.all(paths.map(async (componentPath) => {
const globPath = "/resources/" + componentPath;
log.verbose(`Globbing for Components directories with configured path ${globPath}...`);
const components = await combo.byGlob(globPath);
const components = await combo.byGlob(globPath); // TODO 3.0: Limit to workspace resources?
return components.map((component) => {
const compDir = path.dirname(component.getPath()).replace(/^\/resources\//i, "");
log.verbose(`Found component namespace ${compDir}`);
Expand All @@ -51,7 +60,10 @@ module.exports = function({workspace, dependencies, taskUtil, options: {projectN
`found for project: ${projectName}`);
}

return Promise.all(allNamespaces.map((namespace) => {
const allFilterExcludes = negateFilters(excludes);
const unusedFilterExcludes = new Set(allFilterExcludes);

const bundleDefinitions = allNamespaces.map((namespace) => {
const filters = [
`${namespace}/`,
`!${namespace}/test/`,
Expand All @@ -65,23 +77,46 @@ module.exports = function({workspace, dependencies, taskUtil, options: {projectN
}
});

log.verbose(`Requesting Component-preload.js for namespace ${namespace}...`);
// TODO: When excluding other namespaces do we also need to check/add custom excludes?
// nested component use case

// Add configured excludes for namespace at the end of filter list
allFilterExcludes.forEach((filterExclude) => {
// Allow all excludes and limit re-includes to the component namespace
if (filterExclude.startsWith("!") || filterExclude.startsWith(`${namespace}/`)) {
RandomByte marked this conversation as resolved.
Show resolved Hide resolved
filters.push(filterExclude);
unusedFilterExcludes.delete(filterExclude);
}
});

return {
name: `${namespace}/Component-preload.js`,
defaultFileTypes: [".js", ".fragment.xml", ".view.xml", ".properties", ".json"],
sections: [
{
mode: "preload",
filters: filters,
resolve: false,
resolveConditional: false,
renderer: false
}
]
};
});

if (unusedFilterExcludes.size > 0) {
unusedFilterExcludes.forEach((filterExclude) => {
// TODO: log warning/error or throw error?
log.warn("Unused exclude: " + filterExclude);
});
}

return Promise.all(bundleDefinitions.map((bundleDefinition) => {
log.verbose(`Generating ${bundleDefinition.name}...`);
return moduleBundler({
resources,
options: {
bundleDefinition: {
name: `${namespace}/Component-preload.js`,
defaultFileTypes: [".js", ".fragment.xml", ".view.xml", ".properties", ".json"],
sections: [
{
mode: "preload",
filters: filters,
resolve: false,
resolveConditional: false,
renderer: false
}
]
},
bundleDefinition,
bundleOptions: {
ignoreMissingModules: true,
optimize: true
Expand Down
8 changes: 5 additions & 3 deletions lib/types/application/ApplicationBuilder.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ class ApplicationBuilder extends AbstractBuilder {
}

const componentPreload = project.builder && project.builder.componentPreload;
if (componentPreload) {
if (componentPreload && (componentPreload.namespaces || componentPreload.paths)) {
this.addTask("generateComponentPreload", async () => {
return getTask("generateComponentPreload").task({
workspace: resourceCollections.workspace,
Expand All @@ -77,7 +77,8 @@ class ApplicationBuilder extends AbstractBuilder {
options: {
projectName: project.metadata.name,
paths: componentPreload.paths,
namespaces: componentPreload.namespaces
namespaces: componentPreload.namespaces,
excludes: componentPreload.excludes
}
});
});
Expand All @@ -90,7 +91,8 @@ class ApplicationBuilder extends AbstractBuilder {
taskUtil,
options: {
projectName: project.metadata.name,
namespaces: [project.metadata.namespace]
namespaces: [project.metadata.namespace],
excludes: componentPreload && componentPreload.excludes
}
});
});
Expand Down
41 changes: 40 additions & 1 deletion lib/types/library/LibraryFormatter.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,24 @@ class LibraryFormatter extends AbstractUi5Formatter {
// TODO 2.0: Make copyright mandatory and just let the error throw
log.verbose(err.message);
}

if (project.id.startsWith("@openui5/") || project.id.startsWith("@sapui5/")) {
if (project.builder && project.builder.libraryPreload && project.builder.libraryPreload.excludes) {
log.verbose(
`Using preload excludes for framework library ${project.metadata.name} from project configuration`);
} else {
log.verbose(
`No preload excludes defined in project configuration of framework library ` +
`${project.metadata.name}. Falling back to .library...`);
if (!project.builder) {
project.builder = {};
}
if (!project.builder.libraryPreload) {
project.builder.libraryPreload = {};
}
project.builder.libraryPreload.excludes = await this.getPreloadExcludesFromDotLibrary();
}
}
}

/**
Expand Down Expand Up @@ -290,6 +308,26 @@ class LibraryFormatter extends AbstractUi5Formatter {
}
}

async getPreloadExcludesFromDotLibrary() {
const {content: dotLibrary, fsPath} = await this.getDotLibrary();
if (dotLibrary && dotLibrary.library && dotLibrary.library.appData &&
dotLibrary.library.appData.packaging && dotLibrary.library.appData.packaging["all-in-one"]) {
log.verbose(`Found preload excludes in .library file of project ${this._project.metadata.name} ` +
`at ${fsPath}`);
let excludes = dotLibrary.library.appData.packaging["all-in-one"].exclude;
if (!Array.isArray(excludes)) {
excludes = [excludes];
}
return excludes.map((exclude) => {
return exclude.name;
});
} else {
throw new Error(
`No preload excludes found in .library of project ${this._project.metadata.name} ` +
`at ${fsPath}`);
}
}

/**
* Reads the projects manifest.json
*
Expand Down Expand Up @@ -353,7 +391,8 @@ class LibraryFormatter extends AbstractUi5Formatter {
const xml2js = require("xml2js");
const parser = new xml2js.Parser({
explicitArray: false,
ignoreAttrs: true
ignoreAttrs: false,
mergeAttrs: true
});
const readXML = promisify(parser.parseString);
const contentJson = await readXML(content);
Expand Down
Loading