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 all 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;
14 changes: 10 additions & 4 deletions lib/processors/bundlers/moduleBundler.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
const BundleBuilder = require("../../lbt/bundle/Builder");
const LocatorResourcePool = require("../../lbt/resources/LocatorResourcePool");
const EvoResource = require("@ui5/fs").Resource;

const log = require("@ui5/logger").getLogger("builder:processors:bundlers:moduleBundler");

/**
* A ModuleBundleDefinitionSection specifies the embedding mode ('provided', 'raw', 'preload' or 'require')
Expand Down Expand Up @@ -40,9 +40,9 @@ const EvoResource = require("@ui5/fs").Resource;
* @property {string} mode The embedding mode. Either 'provided', 'raw', 'preload' or 'require'
* @property {string[]} filters List of modules declared as glob patterns (resource name patterns) that should be
* 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
* A pattern ending with a slash '/' will, similarly to the use of a single '*' or double '**' asterisks,
RandomByte marked this conversation as resolved.
Show resolved Hide resolved
* denote an arbitrary number of characters or folder names.
* 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 Expand Up @@ -115,6 +115,12 @@ module.exports = function({resources, options: {bundleDefinition, bundleOptions}
});
const builder = new BundleBuilder(pool);

if (log.isLevelEnabled("verbose")) {
log.verbose(`Generating bundle:`);
log.verbose(`bundleDefinition: ${JSON.stringify(bundleDefinition, null, 2)}`);
log.verbose(`bundleOptions: ${JSON.stringify(bundleOptions, null, 2)}`);
}

return pool.prepare( resources ).
then( () => builder.createBundle(bundleDefinition, bundleOptions) ).
then( (results) => {
Expand Down
78 changes: 57 additions & 21 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,33 @@ 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 ending with a slash '/' will, similarly to the use of a single '*' or double '**' asterisks,
RandomByte marked this conversation as resolved.
Show resolved Hide resolved
* 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,37 +61,63 @@ 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/`,
`!${namespace}/*.html`
];

// Exclude other namespaces
// Add configured excludes for namespace
allFilterExcludes.forEach((filterExclude) => {
// Allow all excludes (!) and limit re-includes (+) to the component namespace
if (filterExclude.startsWith("!") || filterExclude.startsWith(`+${namespace}/`)) {
filters.push(filterExclude);
unusedFilterExcludes.delete(filterExclude);
}
});

// Exclude other namespaces at the end of filter list to override potential re-includes
// from "excludes" config
allNamespaces.forEach((ns) => {
RandomByte marked this conversation as resolved.
Show resolved Hide resolved
if (ns !== namespace && ns.indexOf(namespace) === 0) {
if (ns !== namespace && ns.startsWith(`${namespace}/`)) {
filters.push(`!${ns}/`);
}
});

log.verbose(`Requesting Component-preload.js for namespace ${namespace}...`);
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) => {
log.warn(
`Configured preload exclude contains invalid re-include: !${filterExclude.substr(1)}. ` +
`Re-includes must start with a component namespace (${allNamespaces.join(" or ")})`
);
});
}

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
43 changes: 35 additions & 8 deletions lib/tasks/bundlers/generateLibraryPreload.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const log = require("@ui5/logger").getLogger("builder:tasks:bundlers:generateLibraryPreload");
const moduleBundler = require("../../processors/bundlers/moduleBundler");
const ReaderCollectionPrioritized = require("@ui5/fs").ReaderCollectionPrioritized;
const {negateFilters} = require("../../lbt/resources/ResourceFilterList");

const DEFAULT_FILE_TYPES = [
".js",
Expand All @@ -15,8 +16,8 @@ const DEFAULT_FILE_TYPES = [
".json"
];

function getDefaultLibraryPreloadFilters(namespace) {
return [
function getDefaultLibraryPreloadFilters(namespace, excludes) {
const filters = [
`${namespace}/`,
`!${namespace}/.library`,
`!${namespace}/*-preload.js`, // exclude all bundles
Expand All @@ -26,9 +27,25 @@ function getDefaultLibraryPreloadFilters(namespace) {
`!${namespace}/themes/`,
`!${namespace}/messagebundle*`
];

if (Array.isArray(excludes)) {
const allFilterExcludes = negateFilters(excludes);
// Add configured excludes at the end of filter list
allFilterExcludes.forEach((filterExclude) => {
// Allow all excludes (!) and limit re-includes (+) to the library namespace
if (filterExclude.startsWith("!") || filterExclude.startsWith(`+${namespace}/`)) {
filters.push(filterExclude);
} else {
log.warn(`Configured preload exclude contains invalid re-include: !${filterExclude.substr(1)}. ` +
`Re-includes must start with the library's namespace ${namespace}`);
}
});
}

return filters;
}

function getBundleDefinition(namespace) {
function getBundleDefinition(namespace, excludes) {
// TODO: move to config of actual core project
if (namespace === "sap/ui/core") {
return {
Expand All @@ -47,6 +64,10 @@ function getBundleDefinition(namespace) {
{
mode: "preload",
filters: [
// Note: Don't pass configured preload excludes for sap.ui.core
// as they are already hardcoded below.
// In future the sap/ui/core/library-preload should be configured
// as a custom bundle in the ui5.yaml.
...getDefaultLibraryPreloadFilters(namespace),

`!${namespace}/cldr/`,
Expand Down Expand Up @@ -89,7 +110,7 @@ function getBundleDefinition(namespace) {
sections: [
{
mode: "preload",
filters: getDefaultLibraryPreloadFilters(namespace),
filters: getDefaultLibraryPreloadFilters(namespace, excludes),
resolve: false,
resolveConditional: false,
renderer: true
Expand Down Expand Up @@ -140,11 +161,11 @@ function getSupportFilesBundleDefinition(namespace) {
};
}

function createLibraryBundles(libraryNamespace, resources) {
function createLibraryBundles(libraryNamespace, resources, excludes) {
return Promise.all([
moduleBundler({
options: {
bundleDefinition: getBundleDefinition(libraryNamespace),
bundleDefinition: getBundleDefinition(libraryNamespace, excludes),
bundleOptions: {
optimize: true,
usePredefineCalls: true,
Expand Down Expand Up @@ -259,11 +280,17 @@ function getSapUiCoreBunDef(name, filters, preload) {
* @param {module:@ui5/fs.DuplexCollection} parameters.workspace DuplexCollection to read and write files
* @param {module:@ui5/fs.AbstractReader} parameters.dependencies Reader or Collection to read dependency files
* @param {module:@ui5/builder.tasks.TaskUtil|object} [parameters.taskUtil] TaskUtil
* @param {string[]} [parameters.options.excludes=[]] List of modules declared as glob patterns (resource name patterns)
* that should be excluded from the library-preload.js bundle.
* A pattern ending with a slash '/' will, similarly to the use of a single '*' or double '**' asterisks,
RandomByte marked this conversation as resolved.
Show resolved Hide resolved
* 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 {object} parameters.options Options
* @param {string} parameters.options.projectName Project name
* @returns {Promise<undefined>} Promise resolving with <code>undefined</code> once data has been written
*/
module.exports = function({workspace, dependencies, taskUtil, options: {projectName}}) {
module.exports = function({workspace, dependencies, taskUtil, options: {projectName, excludes = []}}) {
const combo = new ReaderCollectionPrioritized({
name: `libraryBundler - prioritize workspace over dependencies: ${projectName}`,
readers: [workspace, dependencies]
Expand Down Expand Up @@ -356,7 +383,7 @@ module.exports = function({workspace, dependencies, taskUtil, options: {projectN
const libraryNamespaceMatch = libraryIndicatorPath.match(libraryNamespacePattern);
if (libraryNamespaceMatch && libraryNamespaceMatch[1]) {
const libraryNamespace = libraryNamespaceMatch[1];
return createLibraryBundles(libraryNamespace, resources)
return createLibraryBundles(libraryNamespace, resources, excludes)
.then((results) => {
const bundles = Array.prototype.concat.apply([], results);
return Promise.all(bundles.map((bundle) => {
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
9 changes: 7 additions & 2 deletions lib/types/library/LibraryBuilder.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,8 @@ class LibraryBuilder extends AbstractBuilder {
options: {
projectName: project.metadata.name,
paths: componentPreload.paths,
namespaces: componentPreload.namespaces
namespaces: componentPreload.namespaces,
excludes: componentPreload.excludes
}
});
});
Expand Down Expand Up @@ -126,7 +127,11 @@ class LibraryBuilder extends AbstractBuilder {
dependencies: resourceCollections.dependencies,
taskUtil,
options: {
projectName: project.metadata.name
projectName: project.metadata.name,
excludes:
project.builder &&
project.builder.libraryPreload &&
project.builder.libraryPreload.excludes
}
});
});
Expand Down
Loading