Skip to content

Commit

Permalink
[FIX] Bundling: merge dependency analysis results with raw module infos
Browse files Browse the repository at this point in the history
  • Loading branch information
codeworrior committed Sep 30, 2019
1 parent 7a560b4 commit 761e34f
Show file tree
Hide file tree
Showing 6 changed files with 121 additions and 49 deletions.
2 changes: 1 addition & 1 deletion lib/lbt/analyzer/JSModuleAnalyzer.js
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,7 @@ class JSModuleAnalyzer {
if ( currentScope.set.size > 0 ) {
info.requiresTopLevelScope = true;
info.exposedGlobals = Array.from(currentScope.set.keys());
// console.log(info.name, info.exposedGlobals);
// console.log(info.name, "exposed globals", info.exposedGlobals, "ignoredGlobals", info.ignoredGlobals);
}

return;
Expand Down
15 changes: 11 additions & 4 deletions lib/lbt/bundle/Resolver.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,15 @@ class BundleResolver {
return match;
}

function isMultiModule(moduleInfo) {
return moduleInfo && moduleInfo.subModules.length > 0 && !/(?:^|\/)library.js$/.test(moduleInfo.name);
function isDecomposableBundle(moduleInfo) {
if ( moduleInfo == null
|| moduleInfo.subModules.length === 0
|| /(?:^|\/)library.js$/.test(moduleInfo.name) ) {
return false;
}
// it might look more natural to expect 'all' embedded modules to exist in the pool,
// but expecting only one module is a more conservative approach
return moduleInfo.subModules.some((sub) => pool.findResource(sub));
}

function checkAndAddResource(resourceName, depth, msg) {
Expand All @@ -78,8 +85,8 @@ class BundleResolver {
const dependencyInfo = resource && resource.info;
let promises = [];

if ( isMultiModule(dependencyInfo) ) {
// multi modules are not added, only their pieces (sub modules)
if ( isDecomposableBundle(dependencyInfo) ) {
// bundles are not added, only their embedded modules
promises = dependencyInfo.subModules.map( (included) => {
return checkAndAddResource(included, depth + 1,
"**** error: missing submodule " + included + ", included by " + resourceName);
Expand Down
49 changes: 26 additions & 23 deletions lib/lbt/resources/ResourcePool.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,35 +61,38 @@ async function determineDependencyInfo(resource, rawInfo, pool) {
const info = new ModuleInfo(resource.name);
info.size = resource.fileSize;
if ( /\.js$/.test(resource.name) ) {
// console.log("analyzing %s", resource.file);
const code = await resource.buffer();
info.size = code.length;
const promises = [];
try {
const ast = esprima.parseScript(code.toString(), {comment: true});
jsAnalyzer.analyze(ast, resource.name, info);
new XMLCompositeAnalyzer(pool).analyze(ast, resource.name, info);
} catch (error) {
log.error("failed to parse or analyze %s:", resource.name, error);
}
if ( rawInfo ) {
// modules for which a raw-info was modelled should not be analyzed
// otherwise, we detect the internal dependencies of sap-viz.js, but can't handle them
// as we don't have access to the individual modules

info.rawModule = true;
// console.log("adding preconfigured dependencies for %s:", resource.name, rawInfo.dependencies);
rawInfo.dependencies.forEach( (dep) => info.addDependency(dep) );
} else {
// console.log("analyzing %s", resource.file);
const code = await resource.buffer();
info.size = code.length;
const promises = [];
try {
const ast = esprima.parseScript(code.toString(), {comment: true});
jsAnalyzer.analyze(ast, resource.name, info);
new XMLCompositeAnalyzer(pool).analyze(ast, resource.name, info);
} catch (error) {
log.error("failed to parse or analyze %s:", resource.name, error);
if ( rawInfo.requiresTopLevelScope ) {
info.requiresTopLevelScope = true;
}
if ( /(?:^|\/)Component\.js/.test(resource.name) ) {
promises.push(
new ComponentAnalyzer(pool).analyze(resource, info),
new SmartTemplateAnalyzer(pool).analyze(resource, info),
new FioriElementsAnalyzer(pool).analyze(resource, info)
);
if ( rawInfo.ignoredGlobals ) {
info.ignoredGlobals = rawInfo.ignoredGlobals;
}

await Promise.all(promises);
}
if ( /(?:^|\/)Component\.js/.test(resource.name) ) {
promises.push(
new ComponentAnalyzer(pool).analyze(resource, info),
new SmartTemplateAnalyzer(pool).analyze(resource, info),
new FioriElementsAnalyzer(pool).analyze(resource, info)
);
}

await Promise.all(promises);

// console.log(info);
} else if ( /\.view.xml$/.test(resource.name) ) {
const xmlView = await resource.buffer();
Expand Down
47 changes: 35 additions & 12 deletions lib/tasks/bundlers/generateLibraryPreload.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,36 +9,52 @@ function getBundleDefinition(namespace) {
name: `${namespace}/library-preload.js`,
defaultFileTypes: [".js", ".fragment.xml", ".view.xml", ".properties", ".json"],
sections: [
{
// exclude the content of sap-ui-core by declaring it as 'provided'
mode: "provided",
filters: [
"ui5loader-autoconfig.js",
"sap/ui/core/Core.js"
],
resolve: true
},
{
mode: "preload",
filters: [
`${namespace}/`,
`!${namespace}/.library`,
`!${namespace}/designtime/`,
`!${namespace}/**/*.designtime.js`,
`!${namespace}/**/*.support.js`,
`!${namespace}/themes/`,
`!${namespace}/cldr/`,
`!${namespace}/messagebundle*`,

"*.js",
"sap/base/",
"sap/ui/base/",
"sap/ui/xml/",
"sap/ui/dom/",
"sap/ui/events/",
"sap/ui/model/",
"sap/ui/security/",
"sap/ui/util/",
"sap/ui/Global.js",

// files are already part of sap-ui-core.js
"!sap/ui/thirdparty/baseuri.js",
"!sap/ui/thirdparty/es6-promise.js",
"!sap/ui/thirdparty/es6-string-methods.js",
"!sap/ui/thirdparty/mdn-object-assign.js",
"!jquery.sap.global.js",
"!ui5loader-autoconfig.js",
"!ui5loader.js",
"!ui5loader-amd.js",
"!sap-ui-*.js"
// include only thirdparty that is very likely to be used
"sap/ui/thirdparty/crossroads.js",
"sap/ui/thirdparty/caja-htmlsanitizer.js",
"sap/ui/thirdparty/hasher.js",
"sap/ui/thirdparty/signals.js",
"sap/ui/thirdparty/jquery-mobile-custom.js",
"sap/ui/thirdparty/jqueryui/jquery-ui-core.js",
"sap/ui/thirdparty/jqueryui/jquery-ui-position.js",

// other excludes (not required for productive scenarios)
"!sap-ui-*.js",
"!sap/ui/core/support/",
"!sap/ui/core/plugin/DeclarativeSupport.js",
"!sap/ui/core/plugin/LessSupport.js"

],
resolve: false,
resolveConditional: false,
Expand All @@ -56,6 +72,9 @@ function getBundleDefinition(namespace) {
filters: [
`${namespace}/`,
`!${namespace}/.library`,
`!${namespace}/designtime/`,
`!${namespace}/**/*.designtime.js`,
`!${namespace}/**/*.support.js`,
`!${namespace}/themes/`,
`!${namespace}/messagebundle*`
],
Expand Down Expand Up @@ -232,7 +251,11 @@ module.exports = function({workspace, dependencies, options}) {
const libraryNamespace = libraryNamespaceMatch[1];
return moduleBundler({
options: {
bundleDefinition: getBundleDefinition(libraryNamespace)
bundleDefinition: getBundleDefinition(libraryNamespace),
bundleOptions: {
optimize: true,
usePredefineCalls: true
}
},
resources
}).then(([bundle]) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
jQuery.sap.registerPreloadedModules({
"version":"2.0",
"modules":{
"sap/ui/core/Core.js":function(){
},
"sap/ui/core/some.js":function(){/*!
* ${copyright}
*/
Expand Down
55 changes: 48 additions & 7 deletions test/lib/lbt/resources/ResourcePool.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const test = require("ava");
const ModuleInfo = require("../../../../lib/lbt/resources/ModuleInfo");
const ResourcePool = require("../../../../lib/lbt/resources/ResourcePool");
const ResourceFilterList = require("../../../../lib/lbt/resources/ResourceFilterList");

Expand Down Expand Up @@ -203,24 +204,64 @@ test("addResource twice", async (t) => {
t.is(resourcePool._resourcesByName.size, 1, "resource a was added to _resourcesByName map");
});

test.serial("addResource: library", async (t) => {
test.serial("addResource: library and eval raw module info", async (t) => {
const resourcePool = new ResourcePool();

const infoA = new ModuleInfo("moduleA.js");
infoA.rawModule = true;
infoA.addDependency("123.js");
infoA.ignoredGlobals = ["foo", "bar"];
const infoB = new ModuleInfo("moduleB.js");
infoB.rawModule = true;
infoB.addDependency("456.js");

const stubGetDependencyInfos = sinon.stub(LibraryFileAnalyzer, "getDependencyInfos").returns({
myKeyA: "123",
myKeyB: "456"
"moduleA.js": infoA,
"moduleB.js": infoB
});

const library = {
name: "a.library",
buffer: async () => ""
};
await resourcePool.addResource(library);
t.deepEqual(resourcePool._resources, [library], "library a has been added to resources array twice");
const moduleA = {
name: "moduleA.js",
buffer: async () => "var foo,bar,some;"
};
await resourcePool.addResource(moduleA);
const moduleB = {
name: "moduleB.js",
buffer: async () => "var foo,bar,some; jQuery.sap.require(\"moduleC\");"
};
await resourcePool.addResource(moduleB);

t.deepEqual(resourcePool._resources, [library, moduleA, moduleB], "resources have been added to resources array");
t.is(resourcePool._resourcesByName.get("a.library"), library,
"library a has been added to the _resourcesByName map");
t.is(resourcePool._resourcesByName.size, 1, "library a was added to _resourcesByName map");
t.deepEqual(resourcePool._rawModuleInfos.get("myKeyA"), "123", "module info has been added to _rawModuleInfos");
t.deepEqual(resourcePool._rawModuleInfos.get("myKeyB"), "456", "module info has been added to _rawModuleInfos");
t.is(resourcePool._resourcesByName.size, 3, "library a was added to _resourcesByName map");
t.deepEqual(resourcePool._rawModuleInfos.get("moduleA.js"), infoA, "module info has been added to _rawModuleInfos");
t.deepEqual(resourcePool._rawModuleInfos.get("moduleB.js"), infoB, "module info has been added to _rawModuleInfos");

const actualResourceA = await resourcePool.findResourceWithInfo("moduleA.js");
t.true(actualResourceA.info instanceof ModuleInfo);
t.deepEqual(actualResourceA.info.dependencies, ["123.js"],
"configured dependencies should have been dded");
t.true(actualResourceA.info.requiresTopLevelScope);
t.deepEqual(actualResourceA.info.exposedGlobals, ["foo", "bar", "some"],
"global names should be known from analsyis step");
t.deepEqual(actualResourceA.info.ignoredGlobals, ["foo", "bar"],
"ignored globals should have been taken from .library");

const actualResourceB = await resourcePool.findResourceWithInfo("moduleB.js");
t.true(actualResourceB.info instanceof ModuleInfo);
t.deepEqual(actualResourceB.info.dependencies, ["moduleC.js", "jquery.sap.global.js", "456.js"],
"dependencies from analsyis and raw info should have been merged");
t.true(actualResourceB.info.requiresTopLevelScope);
t.deepEqual(actualResourceB.info.exposedGlobals, ["foo", "bar", "some"],
"global names should be known from analsyis step");
t.deepEqual(actualResourceB.info.ignoredGlobals, undefined);

stubGetDependencyInfos.restore();
});

0 comments on commit 761e34f

Please sign in to comment.