UNPKG

@qooxdoo/framework

Version:

The JS Framework for Coders

1,147 lines (1,023 loc) 34.9 kB
/* ************************************************************************ * * qooxdoo-compiler - node.js based replacement for the Qooxdoo python * toolchain * * https://github.com/qooxdoo/qooxdoo * * Copyright: * 2011-2017 Zenesis Limited, http://www.zenesis.com * * License: * MIT: https://opensource.org/licenses/MIT * * This software is provided under the same licensing terms as Qooxdoo, * please see the LICENSE file in the Qooxdoo project's top-level directory * for details. * * Authors: * * John Spackman (john.spackman@zenesis.com, @johnspackman) * * ************************************************************************/ const fs = qx.tool.utils.Promisify.fs; const path = require("upath"); /** * A target for building an application, instances of Target control the generation of transpiled * source and collection into an application, including minifying etc */ qx.Class.define("qx.tool.compiler.targets.Target", { extend: qx.core.Object, /** * Constructor * @param outputDir {String} output directory */ construct(outputDir) { super(); this.setOutputDir(outputDir); }, properties: { /** Type of compilation */ type: { init: "source", nullable: false, check: ["source", "build"] }, /** Output directory (guaranteed to have a trailing slash) */ outputDir: { init: "output", nullable: false, check: "String", transform: "_transformOutputDir" }, /** * Whether to generate the index.html */ generateIndexHtml: { init: true, check: "Boolean" }, /** * Whether the generated artifacts (ie resources and transpiled) are private to the application; this requires * the web server to mount the resources and transpiled directories inside the application directory */ privateArtifacts: { init: false, check: "Boolean" }, /** * Environment property map */ environment: { init: null, nullable: true }, /** * Target type default environment property map */ defaultEnvironment: { init: null, inheritable: true, nullable: true }, /** * List of environment keys to preserve in code, ie reserve for runtime detection * and exclude from code elimination */ preserveEnvironment: { init: null, nullable: true, check: "Array" }, /** * The analyser being generated */ analyser: { nullable: false }, /** * Whether to inline external scripts */ inlineExternalScripts: { init: false, check: "Boolean" }, /** * Whether to prefer local fonts instead of CDNs */ preferLocalFonts: { init: false, check: "Boolean" }, /** * Types of fonts to be included */ fontTypes: { init: ["woff"], check: "Array" }, /** * Whether to add timestamps to all URLs (cache busting) */ addTimestampsToUrls: { init: true, check: "Boolean" }, /** Locales being generated */ locales: { nullable: false, init: ["en"], transform: "_transformLocales" }, /** Whether to break locale & translation data out into separate parts */ i18nAsParts: { init: false, nullable: false, check: "Boolean" }, /** Whether to write all translation strings (as opposed to just those used by the classes) */ writeAllTranslations: { init: false, nullable: false, check: "Boolean" }, /** Whether to update the source .po files with new strings */ updatePoFiles: { init: false, nullable: false, check: "Boolean" }, /** What to do with library transation strings */ libraryPoPolicy: { init: "ignore", check: ["ignore", "untranslated", "all"] }, /** * Whether to write a summary of the compile info to disk, ie everything about dependencies and * resources that are used to create the index.js file, but stored as pure JSON for third party code * to use. */ writeCompileInfo: { init: false, nullable: false, check: "Boolean" }, /** * Whether to write information about the libraries into the boot script */ writeLibraryInfo: { init: true, nullable: false, check: "Boolean" }, /** * Whether to use relative paths in source maps */ sourceMapRelativePaths: { init: false, nullable: false, check: "Boolean" } }, events: { /** * Fired after all enviroment data is collected, but before compilation begins; this * is an opportunity to adjust the environment for the target. The event data contains: * application {qx.tool.compiler.app.Application} the app * enviroment: {Object} enviroment data */ checkEnvironment: "qx.event.type.Data", /** * Fired when an application is about to be serialized to disk; the appMeta is fully * populated, and this is an opportunity to amend the meta data before it is serialized * into files on disk */ writingApplication: "qx.event.type.Event", /** * Fired when an application has been serialized to disk */ writtenApplication: "qx.event.type.Event" }, members: { /** @type {Map} maps filenames to uris */ __pathMappings: null, /** @type {qx.tool.compiler.targets.meta.ApplicationMeta} for the current application */ __appMeta: null, /** * Initialises the target, creating directories etc */ async open() {}, /** * Transforms outputDir so that it always includes a trailing slash * * @param value * @returns {*} * @private */ _transformOutputDir(value) { if (value) { if (value[value.length - 1] != "/") { value += "/"; } } return value; }, /** * Returns the root for applications */ getApplicationRoot(application) { return ( path.join(this.getOutputDir(), this.getProjectDir(application)) + "/" ); }, /** * Returns the project dir * * @returns String */ getProjectDir(application) { return application.getOutputPath() || application.getName(); }, /** * Returns the URI for the root of the output, relative to the application */ _getOutputRootUri(application) { if (this.isPrivateArtifacts()) { return ""; } var dir = this.getApplicationRoot(application); var targetUri = path.relative(dir, this.getOutputDir()) + "/"; return targetUri; }, /** * Adds a path mapping, where any reference to a file in `fromFile` is remapped to be * loaded via the `toUri. * * @param fromFile {String} the directory (or filename) to map * @param toUri {String} the URI to map to */ addPathMapping(fromFile, toUri) { fromFile = path.resolve(fromFile); if (this.__pathMappings === null) { this.__pathMappings = {}; } this.__pathMappings[fromFile] = toUri; }, /** * Converts a filename to a URI, taking into account mappings added via `addMapping`. If there is * no mapping, null is returned * * @param filename {String} the filename to map * @return {String} the URI for the file, null if not found */ getPathMapping(filename) { if (this.__pathMappings) { var absFilename = path.resolve(filename); // Search for (var fromFile in this.__pathMappings) { if (absFilename.startsWith(fromFile)) { var toUri = this.__pathMappings[fromFile]; filename = toUri + absFilename.substring(fromFile.length); return filename; } } } return null; }, /** * Converts a filename to a URI, taking into account mappings added via `addMapping`. If there is * no mapping, the filename can be modified to be relative to a given path (ie the directory where * the index.html is located) * * @param filename {String} the filename to map * @param relativeTo {String?} optional path that the filename needs to be relative to if there is no mapping * @return {String} the URI for the file */ mapToUri(filename, relativeTo) { var mapTo = this.getPathMapping(filename); if (mapTo !== null) { return mapTo; } if (relativeTo) { filename = path.relative(relativeTo, filename); } return filename; }, /** * Generates the application * * @param application {qx.tool.compiler.app.Application} the application * @param environment {Object} the environment */ async generateApplication(application, environment) { var t = this; var analyser = application.getAnalyser(); var rm = analyser.getResourceManager(); let appMeta = (this.__appMeta = new qx.tool.compiler.targets.meta.ApplicationMeta(this, application)); appMeta.setAddTimestampsToUrls(this.getAddTimestampsToUrls()); let targetUri = ""; if (!this.isPrivateArtifacts() || application.getType() != "browser") { let dir = this.getApplicationRoot(application); targetUri = path.relative(dir, this.getOutputDir()) + "/"; } var appRootDir = this.getApplicationRoot(application); let mapTo = this.getPathMapping( path.join(appRootDir, this.getOutputDir(), "transpiled/") ); appMeta.setSourceUri(mapTo ? mapTo : targetUri + "transpiled/"); mapTo = this.getPathMapping( path.join(appRootDir, this.getOutputDir(), "resource") ); appMeta.setResourceUri(mapTo ? mapTo : targetUri + "resource"); const requiredLibs = application.getRequiredLibraries(); await qx.tool.utils.Utils.makeDirs(appRootDir); appMeta.setEnvironment({ "qx.application": application.getClassName(), "qx.revision": "", "qx.theme": application.getTheme() }); let externals = {}; const addExternal = (arr, type) => { if (arr) { arr.forEach(filename => { if (externals[filename.toLowerCase()]) { return; } externals[filename.toLowerCase()] = true; let actualType = type || (filename.endsWith(".js") ? "urisBefore" : "cssBefore"); if (filename.match(/^https?:/)) { appMeta.addExternal(actualType, filename); } else { let asset = rm.getAsset(filename); if (asset) { let str = asset.getDestFilename(t); str = path.relative(appRootDir, str); appMeta.addPreload(actualType, str); } } }); } }; requiredLibs.forEach(libnamespace => { var library = analyser.findLibrary(libnamespace); appMeta.addLibrary(library); if (this.isWriteLibraryInfo()) { let libraryInfoMap = appMeta.getEnvironmentValue( "qx.libraryInfoMap", {} ); libraryInfoMap[libnamespace] = library.getLibraryInfo(); } addExternal(library.getAddScript(), "urisBefore"); addExternal(library.getAddCss(), "cssBefore"); }); /* * Environment */ for (let name in environment) { appMeta.setEnvironmentValue(name, environment[name]); } await t.fireDataEventAsync("checkEnvironment", { application: application, environment: appMeta.getEnvironment() }); /* * Boot files */ let bootJs = new qx.tool.compiler.targets.meta.BootJs(appMeta); let bootPackage = appMeta.createPackage(); appMeta.setBootMetaJs(bootJs); bootPackage.addJavascriptMeta( new qx.tool.compiler.targets.meta.PolyfillJs(appMeta) ); // Add browserified CommonJS modules, if any. The Browserify // class will always bundle local modules specified for an // application in compile.json, but will not bundle `require()`d // modules that are Node modules. if ( appMeta.getEnvironmentValue("qx.compiler.applicationType") == "browser" ) { bootPackage.addJavascriptMeta( new qx.tool.compiler.targets.meta.Browserify(appMeta) ); } /* * Assemble the Parts */ var partsData = application.getPartsDependencies(); let matchBundle = qx.tool.compiler.app.Application.createWildcardMatchFunction( application.getBundleInclude(), application.getBundleExclude() ); let lastPackage = bootPackage; let packages = { boot: bootPackage }; partsData.forEach((partData, index) => { let partMeta = appMeta.createPart(partData.name); if (index == 0) { partMeta.addPackage(bootPackage); } partData.classes.forEach(classname => { let classFilename = classname.replace(/\./g, "/") + ".js"; let transpiledClassFilename = path.join( this.getOutputDir(), "transpiled", classFilename ); let db = analyser.getDatabase(); let dbClassInfo = db.classInfo[classname]; let library = analyser.findLibrary(dbClassInfo.libraryName); let sourcePath = library.getFilename(classFilename); let jsMeta = new qx.tool.compiler.targets.meta.Javascript( appMeta, transpiledClassFilename, sourcePath ); let packageName = matchBundle(classname) ? "__bundle" : partData.name; let pkg = packages[packageName]; if (!pkg || pkg !== lastPackage) { pkg = packages[packageName] = appMeta.createPackage(); if (packageName == "__bundle") { pkg.setEmbedAllJavascript(true); } partMeta.addPackage(pkg); } if (dbClassInfo.externals) { addExternal(dbClassInfo.externals); } pkg.addJavascriptMeta(jsMeta); pkg.addClassname(classname); lastPackage = pkg; }); }); var assetUris = application.getAssetUris(t, rm, appMeta.getEnvironment()); // Save any changes that getAssets collected await rm.saveDatabase(); let cldr = await analyser.getCldr("en"); await bootPackage.addLocale("C", cldr); await this._writeTranslations(); var assets = {}; rm.getAssetsForPaths(assetUris).forEach(asset => { bootPackage.addAsset(asset); assets[asset.getFilename()] = asset.toString(); }); if (analyser.getApplicationTypes().indexOf("browser") > -1) { appMeta.addPreBootCode("qx.$$fontBootstrap={};\n"); await this.__writeDeprecatedWebFonts(application, appMeta, assets); await this.__writeManifestFonts( application, appMeta, assets, bootPackage ); } await this._writeApplication(); this.__appMeta = null; }, /** * Writes the fonts defined in provides.webfonts * @deprecated */ async __writeDeprecatedWebFonts(application, appMeta, assets) { let analyser = application.getAnalyser(); const requiredLibs = application.getRequiredLibraries(); // Get a list of all fonts to load; use the font name as a unique identifier, and // prioritise the application's library's definitions - this allows the application // the opportunity to override the font definitions. This is important when the // library uses the open source/free versions of a font but the application // developer has purchased the commercial/full version of the font (eg FontAwesome) let appLibrary = appMeta.getAppLibrary(); let fontsToLoad = {}; const addLibraryFonts = library => { var fonts = library.getWebFonts(); if (!fonts) { return; } fonts.forEach(font => { fontsToLoad[font.getName()] = { font, library }; }); }; requiredLibs.forEach(libnamespace => { var library = analyser.findLibrary(libnamespace); if (library != appLibrary) { addLibraryFonts(library); } }); if (!addLibraryFonts) { return; } addLibraryFonts(appLibrary); const loadFont = async (library, font) => { try { // check if font is asset somewhere let res = font.getResources().filter(res => assets[res]); if (res.length === 0) { qx.tool.compiler.Console.print( "qx.tool.compiler.webfonts.noResources", font.toString(), application.getName(), font.getResources().join(",") ); return; } font.setResources(res); await font.generateForTarget(this); let resources = await font.generateForApplication(this, application); for (var key in resources) { appMeta.addResource(key, resources[key]); } var code = font.getBootstrapCode(this, application); if (code) { appMeta.addPreBootCode(code); } } catch (ex) { qx.tool.compiler.Console.print( "qx.tool.compiler.webfonts.error", font.toString(), ex.toString() ); } }; for (let fontName of Object.keys(fontsToLoad)) { let { font, library } = fontsToLoad[fontName]; await loadFont(library, font); } }, /** * Writes the fonts defined in provides.fonts */ async __writeManifestFonts(application, appMeta, assets, bootPackage) { let analyser = application.getAnalyser(); let rm = analyser.getResourceManager(); const addResourcesToBuild = resourcePaths => { for (let asset of rm.getAssetsForPaths(resourcePaths)) { bootPackage.addAsset(asset); assets[asset.getFilename()] = asset.toString(); } }; let fontNames = application.getFonts(); for (let fontName of fontNames) { let font = analyser.getFont(fontName); if (!font) { return; } let resources = font.getApplicationFontData(); for (var key in resources) { appMeta.addResource(key, resources[key]); } let fontFaces = font.getFontFaces() || []; // Break out the CSS into local resource files and URLs let fontCss = font.getCss() || []; let cssUrls = []; let cssResources = []; for (let urlOrPath of fontCss) { if (urlOrPath.match(/^https?:/)) { cssUrls.push(urlOrPath); } else { cssResources.push(urlOrPath); } } // Exclude font files that we do not want to include let types = this.getFontTypes(); let hasLocalFontResources = false; let hasUrlFontResources = false; if (types.length) { for (let fontFace of fontFaces) { fontFace.paths = fontFace.paths.filter(filename => { let pos = filename.lastIndexOf("."); if (pos > -1) { let ext = filename.substring(pos + 1); if (types.indexOf(ext) > -1) { return true; } } if (!filename.match(/^https?:/)) { hasLocalFontResources = true; } else { hasUrlFontResources = true; } return false; }); } } // It is important to always prefer local fonts if we have them and are not instructed to prefer CDNs let useLocalFonts = cssUrls.length == 0 && !hasUrlFontResources; if ( this.isPreferLocalFonts() && (cssResources.length > 0 || hasLocalFontResources) ) { useLocalFonts = true; } // Make sure we add any CSS and font resource files to the target output if (useLocalFonts) { addResourcesToBuild(cssResources); for (let fontFace of fontFaces) { addResourcesToBuild(fontFace.paths); } } var code = font.getBootstrapCode(this, application, useLocalFonts); if (code) { appMeta.addPreBootCode(code); } } }, /** * Handles the output of translations and locales */ async _writeTranslations() { let appMeta = this.getAppMeta(); const analyser = appMeta.getAnalyser(); if (this.isUpdatePoFiles()) { let policy = this.getLibraryPoPolicy(); if (policy != "ignore") { await analyser.updateTranslations( appMeta.getAppLibrary(), this.getLocales(), appMeta.getLibraries(), policy == "all" ); } else { await analyser.updateTranslations( appMeta.getAppLibrary(), this.getLocales(), null, false ); } } await this._writeLocales(); if (this.getWriteAllTranslations()) { await this._writeAllTranslations(); } else { await this._writeRequiredTranslations(); } }, /** * Transform method for locales property; ensures that all locales are case correct, ie * have the form aa_BB (for example "en_GB" is correct but "en_gb" is invalid) * * @param value {String[]} array of locale IDs * @return {String[]} the modified array */ _transformLocales(value) { if (!value) { return value; } return value.map(localeId => { localeId = localeId.toLowerCase(); var pos = localeId.indexOf("_"); if (pos > -1) { localeId = localeId.substring(0, pos) + localeId.substring(pos).toUpperCase(); } return localeId; }); }, /** * Writes the required locale CLDR data, incorporating inheritance. Note that locales in CLDR can * have a "parent locale", where the locale inherits all settings from the parent except where explicitly * set in the locale. This is in addition to the inheritance between language and locale, eg where "en_GB" * overrides settings from "en". Qooxdoo client understands that if a setting is not provided in * "en_GB" it must look to "en", but it does not understand the "parent locale" inheritance, so this * method must flatten the "parent locale" inheritance. */ async _writeLocales() { var t = this; let appMeta = this.getAppMeta(); var analyser = appMeta.getAnalyser(); let bootPackage = appMeta.getPackages()[0]; function loadLocaleData(localeId) { var combinedCldr = null; function accumulateCldr(localeId) { return analyser.getCldr(localeId).then(cldr => { if (!combinedCldr) { combinedCldr = cldr; } else { for (var name in cldr) { var value = combinedCldr[name]; if (value === null || value === undefined) { combinedCldr[name] = cldr[name]; } } } var parentLocaleId = qx.tool.compiler.app.Cldr.getParentLocale(localeId); if (parentLocaleId) { return accumulateCldr(parentLocaleId); } return combinedCldr; }); } return accumulateCldr(localeId); } var promises = t.getLocales().map(async localeId => { let cldr = await loadLocaleData(localeId); let pkg = this.isI18nAsParts() ? appMeta.getLocalePackage(localeId) : bootPackage; pkg.addLocale(localeId, cldr); }); await qx.Promise.all(promises); }, /** * Writes all translations */ async _writeAllTranslations() { var t = this; let appMeta = this.getAppMeta(); var analyser = appMeta.getAnalyser(); let bootPackage = appMeta.getPackages()[0]; var translations = {}; var promises = []; t.getLocales().forEach(localeId => { let pkg = this.isI18nAsParts() ? appMeta.getLocalePackage(localeId) : bootPackage; function addTrans(library, localeId) { return analyser .getTranslation(library, localeId) .then(translation => { var id = library.getNamespace() + ":" + localeId; translations[id] = translation; var entries = translation.getEntries(); for (var msgid in entries) { pkg.addTranslationEntry(localeId, entries[msgid]); } }); } appMeta.getLibraries().forEach(function (library) { if (library === appMeta.getAppLibrary()) { return; } promises.push(addTrans(library, localeId)); }); // translation from main app should overwrite package translations promises.push(addTrans(appMeta.getAppLibrary(), localeId)); }); await qx.Promise.all(promises); }, /** * Writes only those translations which are actually required */ async _writeRequiredTranslations() { var t = this; let appMeta = this.getAppMeta(); var analyser = appMeta.getAnalyser(); var db = analyser.getDatabase(); let bootPackage = appMeta.getPackages()[0]; var translations = {}; var promises = []; t.getLocales().forEach(localeId => { let pkg = this.isI18nAsParts() ? appMeta.getLocalePackage(localeId) : bootPackage; appMeta.getLibraries().forEach(function (library) { promises.push( analyser.getTranslation(library, localeId).then(translation => { var id = library.getNamespace() + ":" + localeId; translations[id] = translation; let entry = translation.getEntry(""); if (entry) { pkg.addTranslationEntry(localeId, entry); } }) ); }); }); await qx.Promise.all(promises); appMeta.getPackages().forEach(pkg => { pkg.getClassnames().forEach(classname => { var dbClassInfo = db.classInfo[classname]; if (!dbClassInfo.translations) { return; } t.getLocales().forEach(localeId => { let localePkg = this.isI18nAsParts() ? appMeta.getLocalePackage(localeId) : pkg; dbClassInfo.translations.forEach(transInfo => { let entry; let id = appMeta.getAppLibrary().getNamespace() + ":" + localeId; // search in main app first let translation = translations[id]; if (translation) { entry = translation.getEntry(transInfo.msgid); } let idLib = dbClassInfo.libraryName + ":" + localeId; if (!entry && id !== idLib) { let translation = translations[idLib]; if (translation) { entry = translation.getEntry(transInfo.msgid); } } if (entry) { localePkg.addTranslationEntry(localeId, entry); } }); }); }); }); }, /** * Writes the application */ async _writeApplication() { var t = this; await this.fireEventAsync("writingApplication"); let appMeta = this.getAppMeta(); var application = appMeta.getApplication(); var appRootDir = appMeta.getApplicationRoot(); if (!appMeta.getAppLibrary()) { qx.tool.compiler.Console.print( "qx.tool.compiler.target.missingAppLibrary", application.getName() ); return; } let bootMeta = appMeta.getBootMetaJs(); for (let arr = appMeta.getPackages(), i = 0; i < arr.length; i++) { let pkg = arr[i]; if (pkg.isEmpty()) { pkg.setNeedsWriteToDisk(false); bootMeta.addEmbeddedJs(pkg.getJavascript()); } await pkg.getJavascript().unwrap().writeToDisk(); } await appMeta.getBootMetaJs().unwrap().writeToDisk(); await this._writeIndexHtml(); if (!t.isWriteCompileInfo()) { await this.fireEventAsync("writtenApplication"); return; } let bootPackage = appMeta.getPackages()[0]; let appSummary = { appClass: application.getClassName(), libraries: appMeta.getLibraries().map(lib => lib.getNamespace()), parts: [], resources: bootPackage.getAssets().map(asset => asset.getFilename()), locales: this.getLocales(), environment: appMeta.getEnvironment(), urisBefore: appMeta.getPreloads().urisBefore, cssBefore: appMeta.getPreloads().cssBefore }; application.getPartsDependencies().forEach(partData => { appSummary.parts.push({ classes: partData.classes, include: partData.include, exclude: partData.exclude, minify: partData.minify, name: partData.name }); }); await fs.writeFileAsync( appRootDir + "/compile-info.json", JSON.stringify(appSummary, null, 2) + "\n", { encoding: "utf8" } ); await this.fireEventAsync("writtenApplication"); }, /** * Called to generate index.html */ async _writeIndexHtml() { var t = this; let appMeta = this.getAppMeta(); var application = appMeta.getApplication(); if (!application.isBrowserApp()) { return; } if (!this.isGenerateIndexHtml()) { return; } var resDir = this.getApplicationRoot(application); let timeStamp = new Date().getTime(); let pathToTarget = path.relative( path.join(t.getOutputDir(), t.getProjectDir(application)), t.getOutputDir() ) + "/"; let indexJsTimestamp = ""; if (this.isAddTimestampsToUrls()) { let indexJsFilename = path.join( appMeta.getApplicationRoot(), "index.js" ); indexJsTimestamp = "?" + fs.statSync(indexJsFilename).mtimeMs; } let TEMPLATE_VARS = { resourcePath: pathToTarget + "resource/", targetPath: pathToTarget, appPath: "", preBootJs: "", appTitle: application.getTitle() || "Qooxdoo Application", timeStamp: timeStamp, indexJsTimestamp: indexJsTimestamp }; function replaceVars(code) { for (let varName in TEMPLATE_VARS) { code = code.replace( new RegExp(`\\$\{${varName}\}`, "g"), TEMPLATE_VARS[varName] ); } return code; } /* eslint-disable no-template-curly-in-string */ let defaultIndexHtml = "<!DOCTYPE html>\n" + "<html>\n" + "<head>\n" + ' <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />\n' + " <title>${appTitle}</title>\n" + "</head>\n" + "<body>\n" + " <!-- This index.html can be customised by creating a boot/index.html (do not include Qooxdoo application script tags like\n" + " the one below because they will be added automatically)\n" + " -->\n" + "${preBootJs}\n" + ' <script type="text/javascript" src="${appPath}index.js${indexJsTimestamp}"></script>\n' + "</body>\n" + "</html>\n"; /* eslint-enable no-template-curly-in-string */ var bootDir = application.getBootPath(); let indexHtml = null; if (bootDir) { bootDir = path.join( appMeta.getAppLibrary().getRootDir(), application.getBootPath() ); var stats = await qx.tool.utils.files.Utils.safeStat(bootDir); if (stats && stats.isDirectory()) { await qx.tool.utils.files.Utils.sync( bootDir, resDir, async (from, to) => { if (!from.endsWith(".html")) { return true; } let data = await fs.readFileAsync(from, "utf8"); if (path.basename(from) == "index.html") { if (!data.match(/\$\{\s*preBootJs\s*\}/)) { /* eslint-disable no-template-curly-in-string */ data = data.replace("</body>", "\n${preBootJs}\n</body>"); /* eslint-enable no-template-curly-in-string */ qx.tool.compiler.Console.print( "qx.tool.compiler.target.missingPreBootJs", from ); } if (!data.match(/\s*index.js\s*/)) { /* eslint-disable no-template-curly-in-string */ data = data.replace( "</body>", '\n <script type="text/javascript" src="${appPath}index.js${indexJsTimestamp}"></script>\n</body>' ); /* eslint-enable no-template-curly-in-string */ qx.tool.compiler.Console.print( "qx.tool.compiler.target.missingBootJs", from ); } indexHtml = data; } data = replaceVars(data); await fs.writeFileAsync(to, data, "utf8"); return false; } ); } } if (!indexHtml) { indexHtml = defaultIndexHtml; await fs.writeFileAsync(resDir + "index.html", replaceVars(indexHtml), { encoding: "utf8" }); } if (application.getWriteIndexHtmlToRoot()) { pathToTarget = ""; TEMPLATE_VARS = { resourcePath: "resource/", targetPath: "", appPath: t.getProjectDir(application) + "/", preBootJs: "", appTitle: application.getTitle() || "Qooxdoo Application", timeStamp: timeStamp, indexJsTimestamp: indexJsTimestamp }; await fs.writeFileAsync( t.getOutputDir() + "index.html", replaceVars(indexHtml), { encoding: "utf8" } ); } }, getAppMeta() { return this.__appMeta; } } });