@acdf/build
Version:
Build dependency for Adobe Campaign Developer Framework projects
244 lines (208 loc) • 9.65 kB
JavaScript
const { src, dest } = require("gulp");
const beautify = require("gulp-beautify");
const concat = require("gulp-concat");
const filter = require("gulp-filter");
const foreach = require("gulp-foreach");
const fs = require("fs");
const gcc = require("google-closure-compiler").gulp();
const jsdoc = require("gulp-jsdoc3");
const log = require("gulplog");
const merge = require("merge-stream");
const modifyFile = require("gulp-modify-file");
const path = require("path");
const common = require("./common.js");
const rename = require("gulp-rename");
const GCC_CONFIG = {
env: "CUSTOM",
warning_level: "DEFAULT",
language_in: "ECMASCRIPT6_STRICT",
language_out: "ECMASCRIPT3",
externs: common.resolve("externs.js"),
};
const NAMESPACE_LIBRARY_NAME = `namespace${common.suffix.LIBRARY}.js`;
function isDirectory(file) {
return fs.statSync(file.path).isDirectory();
}
function isLibraryFragment(file) {
const relativeDirname = path.relative(process.cwd(), file.dirname);
return relativeDirname.includes(common.suffix.LIBRARY);
}
function isLibraryDirectory(file) {
return isDirectory(file) && file.basename.includes(common.suffix.LIBRARY);
}
/**
* Concatenates all the library scripts in the directory provided into a single file.
*
* @param {stream} stream
* @param {path} directory
* @returns stream
*/
function concatenateFiles(stream, directory) {
const sourcePath = path.relative(common.resolve("src"), directory.path);
log.info(`Combining contents of ${sourcePath} into single file`);
return stream
.pipe(src(`src/${sourcePath}/*${common.suffix.LIBRARY}.js`))
.pipe(concat(directory.basename))
.pipe(dest(`${common.paths.dist.absolute.STAGING_DIR}/${sourcePath.replace(directory.basename, "")}`));
}
/**
* Converts loadLibrary() statements into their equivalent require() statements which loads the
* contents of the library into the project namespace object. For example:
* loadLibrary("acdf:namespace.library.js")
* becomes:
* global.ACDF = Object.assign(global.ACDF || {}, require("../namespace.library.js").ING);
*
* @param {string} content the file content
* @param {string} filepath the path to the file
* @param {object} libraryMap the map of libraries to JS file paths
* @returns the script content with conversion complete
*/
function convertLoadLibraryToRequire(content, filepath, libraryMap) {
log.debug(`Converting loadLibrary() to require() for file ${filepath}`);
return content.replace(common.REGEX_LOAD_LIBRARY, (match, libraryName) => {
if(!libraryName.startsWith(common.CONFIG.namespace)) {
log.debug(`Ignore loadLibrary call to external library ${libraryName}`)
return match
}
if (!libraryMap[libraryName]) {
throw `Found loadLibrary() pointing to '${libraryName}', but no corresponding JS file exists`;
}
const libraryPath = libraryMap[libraryName];
const fileDir = path.parse(filepath).dir;
const libraryDir = path.parse(libraryPath).dir;
const relativeRequirePath = fileDir === libraryDir ? "./" + path.parse(libraryPath).name : path.relative(path.parse(filepath).dir, libraryPath);
log.debug(`'${match}' => 'require("${relativeRequirePath}")'`);
return `global.${common.CONFIG.namespace.toUpperCase()} = Object.assign(global.${common.CONFIG.namespace.toUpperCase()} || {}, require("${relativeRequirePath}").ING);`;
});
}
module.exports = {
/**
* Moves source scripts to the staging directory. If a library directory is found, the scripts therein are
* concatenated into a single output file.
*
* @returns stream
*/
stageScripts: () => {
return merge(
src(common.GLOB_SRC_SCRIPTS)
.pipe(filter((file) => !isLibraryFragment(file) && !isDirectory(file)))
.pipe(dest(common.paths.dist.absolute.STAGING_DIR)),
src(common.GLOB_SRC_SCRIPTS)
.pipe(filter((file) => isLibraryDirectory(file)))
.pipe(foreach((stream, directory) => concatenateFiles(stream, directory)))
);
},
/**
* Removes all duplicate loadLibrary calls and moves them all to the top of the file, assuring that the
* namespace library is included.
*
* @returns stream
*/
optimizeLoadLibraries: () => {
return src(common.GLOB_STAGING_SCRIPTS)
.pipe(
modifyFile((content, file) => {
// This set will contain all the libraries loaded in the script. Start by adding the default namespace library.
const libraries = new Set();
// By default, always add the namespace library, except to the namespace file itself
const filename = path.basename(file);
if (NAMESPACE_LIBRARY_NAME !== filename) {
libraries.add(`${common.CONFIG.namespace}:namespace.library.js`);
}
// Remove loadLibrary() statements and add libraries to the set
const contentWithoutLoadLibraries = content.replace(common.REGEX_LOAD_LIBRARY, (match, library, offset, string) => {
libraries.add(library);
return "";
});
// Create a loadLibrary() block and add it to the top of the file
const loadLibraryBlock = [...libraries].reduce((accumulator, library) => accumulator + `loadLibrary("${library}");\n`, "");
return loadLibraryBlock + contentWithoutLoadLibraries;
})
)
.pipe(dest(common.paths.dist.absolute.STAGING_DIR));
},
/**
* Converts the scripts in the staging directory into ES6 CommonJS modules.
*
* @returns stream
*/
modulizeLibraries: () => {
const scriptMapStaging = common.buildScriptMap(common.paths.dist.absolute.STAGING_DIR, {});
log.debug("Script map:\n");
log.debug(scriptMapStaging);
return src(common.GLOB_STAGING_SCRIPTS)
.pipe(modifyFile((content, path) => convertLoadLibraryToRequire(content, path, scriptMapStaging)))
.pipe(dest(common.paths.dist.absolute.MODULE_DIR));
},
/**
* Compiles the scripts from ES6 to ES3 for compatibility with Adobe Campaign JS engine. For
* ease of debugging, the compiled code is not minified.
*
* @returns stream
*/
compileScripts: () => {
const compilationFileSuffix = ".compiled";
return src(common.GLOB_STAGING_SCRIPTS)
.pipe(
foreach((stream, file) => {
const localStagingPath = path.relative(process.cwd(), file.path);
const relativePath = localStagingPath.replace(common.paths.dist.relative.STAGING_DIR + "/", "");
const localCompiledPath = common.paths.dist.relative.COMPILED_DIR + "/" + relativePath;
log.debug(`Compiling ${localStagingPath} to ${localCompiledPath}`);
return stream.pipe(
gcc({
...GCC_CONFIG,
js_output_file: relativePath + compilationFileSuffix,
})
);
})
)
.pipe(beautify.js())
.pipe(
rename((file) => {
file.extname = "";
})
)
.pipe(dest(common.paths.dist.absolute.COMPILED_DIR));
},
callTypologyRule: () => {
return src(`${common.paths.dist.absolute.COMPILED_DIR}/**/*${common.suffix.TYPOLOGY}.js`)
.pipe(
modifyFile((content, filepath) => {
const filename = path.parse(filepath).name;
if (filename.includes(common.suffix.TYPOLOGY)) {
return content + ";return typologyRule();"
}
return content;
})
)
.pipe(dest(common.paths.dist.absolute.COMPILED_DIR));
},
prepareDocumentation: () => {
const jsdocConfig = require(path.resolve(__dirname, "../jsdoc.js"));
return merge(
// Add module header to each library file
src(`${common.paths.dist.absolute.MODULE_DIR}/**/*${common.suffix.LIBRARY}.js`)
.pipe(
modifyFile((content, filepath) => {
const libraryName = path.parse(filepath).name.replace(common.suffix.LIBRARY, "");
return ["/**", ` * @module ${libraryName}`, " */", content].join("\n");
})
)
.pipe(beautify.js())
.pipe(dest(common.paths.dist.absolute.MODULE_DIR)),
// Copy the markdown files
src("doc/*.md").pipe(dest(common.paths.dist.absolute.MODULE_DIR))
)
.pipe(src([`${common.paths.dist.absolute.MODULE_DIR}/**/*.js`, `${common.paths.dist.absolute.MODULE_DIR}/**/*.md`], { read: false }))
.pipe(jsdoc(jsdocConfig));
},
concatenateIntegrationTests: function () {
return concatenate("src/test/integration", "dist/js/integration")
.pipe(modifyFile((content) => content + "\nassertionResults();"))
.pipe(dest("dist/js/integration"));
},
compileIntegrationTests: function () {
return compile("dist/js/integration", "dist/compiled/js", false);
},
};