Skip to content

Commit e8071af

Browse files
authored
[FIX JENKINS-39345] Async bundle startup scripts (#11)
* Add wrapper entry module From here, we'll be able to stage the execution of the entry module around the execution of "startup" scripts * Comment on wrapper entry module core * 0.0.52-tfbeta1 * Added startup script support * 0.0.52-tfbeta2 * handle exporting of modules inside a package i.e. not the top-level "main" module * 0.0.52-tfbeta3 * check for startup scripts before we add them to a bundle * 0.0.52-tfbeta4 * control calling of the onExec callback around whether or not there are startup scripts * 0.0.52-tfbeta5 * 0.0.52
1 parent e6acb84 commit e8071af

10 files changed

+171
-12
lines changed

Diff for: gulpfile.js

+2
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ builder.bundle('spec/testmodule.js', 'testmodule_2')
2929
builder.bundle('spec/testmodule.js', 'testmodule_3')
3030
.import('underscore.string', {addDefaultCSS: true})
3131
.inDir('target/testmodule')
32+
.onStartup('./spec/startup-module-1.js')
33+
.onStartup('./spec/startup-module-2.js')
3234
.generateNoImportsBundle();
3335

3436
// - bundle "as" with a version - no prerelease tag

Diff for: index.js

+10-1
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,8 @@ function bundleJs(moduleToBundle, as) {
319319
bundle.useGlobalImportMappings = true;
320320
bundle.useGlobalExportMappings = true;
321321
bundle.minifyBundle = args.isArgvSpecified('--minify');
322+
bundle.startupModules = [];
323+
322324
bundle.generateNoImportsBundle = function() {
323325
if (skipBundle) {
324326
return bundle;
@@ -441,7 +443,7 @@ function bundleJs(moduleToBundle, as) {
441443
// This is the "traditional" export use case.
442444
bundle.bundleExport = true;
443445
bundle.bundleExportNamespace = packageJson.name;
444-
} else if (dependencies.getDependency(moduleName) !== undefined) {
446+
} else if (dependencies.getDependency(moduleName, false) !== undefined) {
445447
// We are exporting some dependency of this module Vs exporting
446448
// the top/entry level module of the generated bundle. This allows the bundle
447449
// to control loading of a specific dependency (or set of) and then share that with
@@ -491,6 +493,13 @@ function bundleJs(moduleToBundle, as) {
491493
return undefined;
492494
};
493495

496+
bundle.onStartup = function (startupModule) {
497+
if (typeof startupModule === 'string') {
498+
bundle.startupModules.push(startupModule);
499+
}
500+
return bundle;
501+
};
502+
494503
if (skipBundle) {
495504
return bundle;
496505
}

Diff for: internal/bundlegen.js

+50
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
var gulp = require('gulp');
22
var fs = require('fs');
3+
var cwd = process.cwd();
34
var main = require('../index');
45
var langConfig = require('./langConfig');
56
var dependencies = require('./dependecies');
67
var paths = require('./paths');
8+
var path = require('path');
79
var maven = require('./maven');
810
var logger = require('./logger');
911
var args = require('./args');
@@ -15,6 +17,7 @@ var _string = require('underscore.string');
1517
var templates = require('./templates');
1618
var ModuleSpec = require('@jenkins-cd/js-modules/js/ModuleSpec');
1719
var entryModuleTemplate = templates.getTemplate('entry-module.hbs');
20+
var entryModuleWrapperTemplate = templates.getTemplate('entry-module-wrapper.js');
1821
var packageJson = require(process.cwd() + '/package.json');
1922

2023
var hasJenkinsJsModulesDependency = dependencies.hasJenkinsJsModulesDep();
@@ -123,6 +126,53 @@ exports.doJSBundle = function(bundle, applyImports) {
123126
fs.writeFileSync(fileToBundle, "module.exports = require('" + bundle.module + "');");
124127
}
125128

129+
bundle.doOnExecCall = true;
130+
if (bundle.startupModules.length > 0) {
131+
var wrapperFileDir = './target/js-bundle-src';
132+
var relativeStartupModules = [];
133+
134+
for (var ii = 0; ii < bundle.startupModules.length; ii++) {
135+
var startupModule = bundle.startupModules[ii];
136+
if (startupModule.charAt(0) === '.') {
137+
var relativeModulePath = path.relative(wrapperFileDir, startupModule);
138+
if (fs.existsSync(cwd + '/' + startupModule + '.js') || fs.existsSync(cwd + '/' + startupModule)) {
139+
relativeStartupModules.push(relativeModulePath);
140+
} else {
141+
logger.logInfo('Javascript bundle "' + bundle.as + '" will not execute startup script "' + startupModule + '". Unable to find local script.')
142+
}
143+
} else {
144+
if (fs.existsSync(cwd + '/node_modules/' + startupModule + '.js') || fs.existsSync(cwd + '/node_modules/' + startupModule)) {
145+
relativeStartupModules.push(startupModule);
146+
} else {
147+
logger.logInfo('Javascript bundle "' + bundle.as + '" will not execute startup script "' + startupModule + '". Unable to find script in node_modules.')
148+
}
149+
}
150+
}
151+
152+
if (relativeStartupModules.length > 0) {
153+
//
154+
// Lets load the entry module via a "wrapper" module. This wrapper
155+
// module will allow us to "inject" startup scripts (into the bundle) that
156+
// will need to execute and resolve before the entry module is allowed to execute.
157+
// Bundles will use this to async load resources that must be loaded before the
158+
// bundle can execute e.g. i18n plugin resources in Blue Ocean.
159+
//
160+
var fileToBasename = path.basename(fileToBundle);
161+
var wrapperFileName = wrapperFileDir + '/_js_wrapper-' + fileToBasename;
162+
var wrapperFileContent = entryModuleWrapperTemplate({
163+
entrymodule: './' + path.relative(wrapperFileDir, fileToBundle),
164+
hpiPluginId: (maven.isHPI() ? maven.getArtifactId() : undefined),
165+
startupModules: relativeStartupModules
166+
});
167+
// Switch off the calling of the onExec callback. this is done inside
168+
// entryModuleWrapperTemplate, after all startup scripts are "done".
169+
bundle.doOnExecCall = false;
170+
paths.mkdirp(wrapperFileDir);
171+
fs.writeFileSync(wrapperFileName, wrapperFileContent);
172+
fileToBundle = wrapperFileName;
173+
}
174+
}
175+
126176
var browserifyConfig = {
127177
entries: [fileToBundle],
128178
extensions: ['.js', '.es6', '.jsx', '.hbs'],

Diff for: internal/dependecies.js

+63-4
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,18 @@ var packageJson = require(cwd + '/package.json');
44
var logger = require('./logger');
55
var Version = require('@jenkins-cd/js-modules/js/Version');
66

7-
exports.getDependency = function(depName) {
7+
exports.getDependency = function(depName, topLevelOnly) {
8+
if (typeof topLevelOnly !== 'boolean') {
9+
topLevelOnly = true;
10+
}
11+
812
function findDep(onDepMap) {
913
if (onDepMap) {
1014
return onDepMap[depName];
1115
}
1216
return undefined;
1317
}
14-
18+
1519
var version = findDep(packageJson.dependencies);
1620
if (version) {
1721
return {
@@ -36,6 +40,51 @@ exports.getDependency = function(depName) {
3640
// TODO: bundled and optional deps?
3741
}
3842
}
43+
44+
if (!topLevelOnly) {
45+
// If the topLevelOnly flag is set to false,
46+
// try find the module inside one of the installed packages.
47+
48+
function innerModule(onDepMap) {
49+
for (var packageName in onDepMap) {
50+
if (onDepMap.hasOwnProperty(packageName)) {
51+
// Does the sought depName start with this packageName
52+
// followed by a slash. Thiswould indicate that depName
53+
// is a module inside this packageName.
54+
if (depName.indexOf(packageName + '/') === 0) {
55+
// Now lets make sure the module is actually inside that package
56+
// by checking for the file in node_modules.
57+
if (fs.existsSync(cwd + '/node_modules/' + depName + '.js') || fs.existsSync(cwd + '/node_modules/' + depName)) {
58+
return {
59+
packageName: packageName,
60+
version: onDepMap[packageName]
61+
};
62+
}
63+
}
64+
}
65+
}
66+
return undefined;
67+
}
68+
69+
var packageInfo = innerModule(packageJson.dependencies);
70+
if (packageInfo) {
71+
packageInfo.type = 'runtime';
72+
return packageInfo;
73+
} else {
74+
packageInfo = innerModule(packageJson.devDependencies);
75+
if (packageInfo) {
76+
packageInfo.type = 'dev';
77+
return packageInfo;
78+
} else {
79+
packageInfo = innerModule(packageJson.peerDependencies);
80+
if (packageInfo) {
81+
packageInfo.type = 'peer';
82+
return packageInfo;
83+
}
84+
// TODO: bundled and optional deps?
85+
}
86+
}
87+
}
3988

4089
return undefined;
4190
};
@@ -105,13 +154,23 @@ exports.externalizedVersionMetadata = function(depPackageName) {
105154
var packageJsonFile = cwd + '/node_modules/' + depPackageName + '/package.json';
106155

107156
if (!fs.existsSync(packageJsonFile)) {
108-
return undefined;
157+
// Maybe depPackageName is not actually a top level package name.
158+
// It might be a module inside a package.
159+
var packageInfo = exports.getDependency(depPackageName, false);
160+
if (packageInfo) {
161+
packageJsonFile = cwd + '/node_modules/' + packageInfo.packageName + '/package.json';
162+
if (!fs.existsSync(packageJsonFile)) {
163+
return undefined;
164+
}
165+
} else {
166+
return undefined;
167+
}
109168
}
110169

111170
var packageJson = require(packageJsonFile);
112171

113172
var metadata = {};
114-
var declaredDepVersion = exports.getDependency(depPackageName);
173+
var declaredDepVersion = exports.getDependency(depPackageName, false);
115174

116175
if (!declaredDepVersion) {
117176
return undefined;

Diff for: internal/maven.js

+1-2
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,7 @@ exports.getPackaging = function() {
2727
};
2828

2929
exports.isHPI = function() {
30-
assertIsMavenProject();
31-
return (exports.getPackaging() === 'hpi');
30+
return (exports.isMavenProject && exports.getPackaging() === 'hpi');
3231
};
3332

3433
function assertIsMavenProject(preamble) {

Diff for: internal/templates/entry-module-wrapper.js

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
var promise = require("@jenkins-cd/js-modules/js/promise");
2+
3+
var numStartupModules = {{startupModules.length}};
4+
var fullfilled = [];
5+
var config = {
6+
hpiPluginId: {{#if hpiPluginId}}'{{hpiPluginId}}'{{else}}undefined{{/if}}
7+
};
8+
9+
function onFullfilled(moduleName) {
10+
if (fullfilled.indexOf(moduleName) === -1) {
11+
fullfilled.push(moduleName);
12+
}
13+
if (fullfilled.length === numStartupModules) {
14+
require('{{entrymodule}}');
15+
onExec();
16+
}
17+
}
18+
19+
{{#each startupModules}}
20+
promise.make(function(fulfill) {
21+
try {
22+
var startupModule = require('{{this}}');
23+
startupModule.execute(fulfill, config);
24+
} catch (e) {
25+
throw new Error('Unexpected error executing startup module "{{this}}": ' + e);
26+
}
27+
}).onFulfilled(function() {
28+
onFullfilled('{{this}}');
29+
});
30+
{{/each}}

Diff for: internal/templates/entry-module.hbs

+6-4
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@ var ___$$$___jsModules = require('@jenkins-cd/js-modules');
33
___$$$___jsModules.whoami('{{#if bundle.bundleExportNamespace}}{{bundle.bundleExportNamespace}}:{{/if}}{{bundle.as}}');
44

55
/*** Start Module Exec Function ***************************************/
6-
function ___$$$___exec() {
6+
function ___$$$___exec(onExec) {
77
{{content}}
8+
{{#if bundle.doOnExecCall}}onExec();{{/if}}
89
}
910
/*** End Module Exec Function ***************************************/
1011

@@ -39,9 +40,10 @@ function ___$$$___doCSS() {
3940

4041
function ___$$$___doBundleInit() {
4142
try {
42-
___$$$___exec();
43-
___$$$___doExports();
44-
___$$$___doCSS();
43+
___$$$___exec(function() {
44+
___$$$___doExports();
45+
___$$$___doCSS();
46+
});
4547
} catch (e) {
4648
console.error('Error initializing Jenkins JavaScript bundle "{{bundle.as}}"', e);
4749
}

Diff for: package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@jenkins-cd/js-builder",
3-
"version": "0.0.51",
3+
"version": "0.0.52",
44
"description": "Jenkins CI JavaScript Build utilities.",
55
"main": "index.js",
66
"scripts": {

Diff for: spec/startup-module-1.js

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
2+
exports.execute = function(done, config) {
3+
done();
4+
};

Diff for: spec/startup-module-2.js

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
2+
exports.execute = function(done, config) {
3+
done();
4+
};

0 commit comments

Comments
 (0)