UNPKG

@acdf/build

Version:

Build dependency for Adobe Campaign Developer Framework projects

244 lines (208 loc) 9.65 kB
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); }, };