UNPKG

@tririga/tri-bundler

Version:

A tool for bundling Polymer 3 TRIRIGA UX views.

282 lines (253 loc) 9.1 kB
"use strict" const log = require("loglevel"); const PolymerProject = require('polymer-build').PolymerProject; const getOptimizeStreams = require('polymer-build').getOptimizeStreams; const gulp = require('gulp'); const mergeStream = require('merge-stream'); const fs = require('fs-extra'); const gulpif = require('gulp-if'); const rename = require('gulp-rename'); const gulpReplace = require('gulp-replace'); const gulpStringReplace = require('gulp-string-replace'); const terser = require('gulp-terser'); const gulpmatch = require('gulp-match'); const cssSlam = require('css-slam').gulp; const del = require('del'); const gap = require('gulp-append-prepend'); const pkg = require("../package.json"); const path = require("path"); const copy = require('recursive-copy'); const utils = require("./utils.js"); const UxExporter = require("./exporter/ux-exporter.js"); const PackageScanner = require("./scanner/package-scanner.js"); const LoadResourceScanner = require("./scanner/load-resource-scanner.js"); const uniquifySvgIds = require("./uniquify-svg-ids.js"); const currentDir = utils.getCurrentDir(); class TriBundler { constructor(user, password, url, basicuser, basicpassword, view, component, output, minifytemplateliterals, compileToES5, srcDir, useSrcDir, uniqueSvgIds) { this.user = user; this.password = password; this.url = url; this.basicuser = basicuser; this.basicpassword = basicpassword; this.view = view; this.component = component; this.outputFilename = path.normalize(path.basename(output)); const dirname = path.dirname(output); this.outputDirName = path.isAbsolute(dirname) ? path.normalize(dirname) : path.normalize(currentDir.path + path.sep + dirname); this.minifyTemplateLiterals = minifytemplateliterals; this.compileToES5 = compileToES5; this.useSrcDir = useSrcDir; this.uniqueSvgIds = uniqueSvgIds; if (useSrcDir) { const normalizedDirPath = path.normalize(srcDir); if (path.isAbsolute(normalizedDirPath)) { this.srcDir = normalizedDirPath; } else { const absDirPath = path.normalize(currentDir.path + path.sep + normalizedDirPath); if (fs.existsSync(absDirPath)) { this.srcDir = absDirPath; } else { log.error(""); log.error("ERROR: Something went wrong. Invalid source directory."); process.exit(1); } } } this.bundleOutputPath = '__bundle__'; this.componentsPath = '__components__'; } async run() { log.info(`Bundling '${this.view}'`); log.info("Preparing components to bundle..."); const bundleOutputDir = path.normalize(this.outputDirName + path.sep + this.bundleOutputPath); this._removeDir(bundleOutputDir); const compOutputDir = path.normalize(this.outputDirName + path.sep + this.componentsPath); this._removeDir(compOutputDir); if (fs.existsSync(this.outputDirName + path.sep + this.outputFilename)) { fs.unlinkSync(this.outputDirName + path.sep + this.outputFilename); } try { fs.ensureDirSync(this.outputDirName); process.chdir(this.outputDirName); fs.ensureDirSync(compOutputDir); fs.ensureDirSync(bundleOutputDir); process.chdir(bundleOutputDir); } catch (err) { log.error(""); log.error("Something went wrong. Cannot set or access output directory."); log.error(err.message); process.exit(1); } let uxExporter; if (!this.useSrcDir) { uxExporter = new UxExporter( this.user, this.password, this.url, this.basicuser, this.basicpassword, this.view, this.component, compOutputDir ); } else { await copy(this.srcDir, compOutputDir); } const srcRelativePath = this.componentsPath + path.sep + this.view; const config = { "root": this.outputDirName, "entrypoint": srcRelativePath + path.sep + this.component, "shell": srcRelativePath + path.sep + this.component, "sources": [ srcRelativePath + path.sep + "*" ], "lint": { "ignoreWarnings": ["overriding-private", "could-not-resolve-reference"] } } await this._process(uxExporter, config, bundleOutputDir, compOutputDir); } async _checkLoadResource(dir) { if (fs.existsSync(dir)) { let usingLoadResource = false; const packageScanner = await this._scanPackage(dir); let loadResourceScanner = new LoadResourceScanner(); let jsFiles = await packageScanner.getJsFiles(); for (let i = 0; i < jsFiles.length; i++) { const result = await loadResourceScanner.scan(jsFiles[i]); usingLoadResource = usingLoadResource || result; } if (usingLoadResource) { log.warn(""); log.warn("If 'loadResource' usage is from 'TriLazyLoadingBehavior', the bundled application may not function properly. Replace the code with a '<dom-if>' to dynamically load it and import the component resource instead of lazy loading it. See [link TBD] for more details."); log.warn(""); } else { log.info("..."); } } else { log.error(""); log.error("ERROR: Something went wrong. Invalid view name or path to view source."); process.exit(1); } } async _scanPackage(packageDir) { let packageScanner = new PackageScanner(packageDir); await packageScanner.scan(); return packageScanner; } async _bundle(config, bundleOutputDir, compOutputDir) { const htmlLiteralRegex = /html\s*\`([^`]+)\`/g; const cssLiteralRegex = /css\s*\`([^`]+)\`/g; const project = new PolymerProject(config); const bundleOptions = { stripComments: true }; const terserOptions = { module: true, mangle: false, compress: { collapse_vars: false, reduce_vars: false, unused: false, warnings: true } }; log.info("Now bundling, this may take a while, please wait..."); let stream = mergeStream(project.sources(), project.dependencies()).on('error', this._onError) .pipe(project.bundler(bundleOptions)).on('error', this._onError); if (this.compileToES5) { stream = stream.pipe( getOptimizeStreams({ js: { compile: "es5", transformModulesToAmd: true } })[0] ).on('error', this._onError); } else { stream = stream.pipe(gulpif(/\.js$/, terser(terserOptions))).on('error', this._onError) .pipe(gulpif(/\.js$/, gulpReplace(/{\.\.\.import\.meta,url\:new URL\("[^"]+",import\.meta\.url\)\.href}/g, () => { //remove import.meta usage. return "\"\""; }))) .pipe(gulpif(file => this._shouldMinifyJSAndHasJS(file), gulpReplace(/\\\`/g, () => { return "@@@"; }))) .pipe(gulpif(file => this._shouldMinifyJSAndHasJS(file), gulpReplace(htmlLiteralRegex, function(match, p1, offset, string) { const result = utils.minifyTemplateStr(p1); return `html \`${result}\``; }))) .pipe(gulpif(file => this._shouldMinifyJSAndHasJS(file), gulpReplace(cssLiteralRegex, function(match, p1, offset, string) { const result = utils.minifyTemplateStr(p1); return `css \`${result}\``; }))) .pipe(gulpif(file => this._shouldMinifyJSAndHasJS(file), gulpReplace(/@@@/g, () => { return "\\\`"; }))) .pipe(gulpif(/\.css$/, cssSlam())); } stream.pipe(gulpif(/\.js$/, gap.prependText(`/* Bundled by @tririga/tri-bundler version ${pkg.version} */`))) .pipe(gulp.dest(`${this.view}${path.sep}${this.bundleOutputPath}/`)) .on('end', async () => { const outputTempPath = bundleOutputDir + path.sep + this.view + path.sep + this.componentsPath + path.sep + this.view; gulp.src(`${outputTempPath}${path.sep}${this.component}`) .pipe(gulpStringReplace(this.component, this.outputFilename, { "logs": { enabled: false } })) .pipe(rename(this.outputFilename)) .pipe(gulp.dest(this.outputDirName)) .on('end', () => { try { process.chdir(this.outputDirName); } catch (e) { this._onError(e); } this._removeDir(bundleOutputDir); this._removeDir(compOutputDir); const outputFilenamePath = path.normalize(this.outputDirName + path.sep + this.outputFilename); if (this.uniqueSvgIds) { uniquifySvgIds(outputFilenamePath).then(() => { log.info("Bundling complete!"); process.exit(0); }); } else { log.info("Bundling complete!"); process.exit(0); } }); }); } async _process(uxExporter, config, bundleOutputDir, compOutputDir) { if (uxExporter) { uxExporter.export(async (error) => { if (error) { log.error(error.message); process.exit(1); } await this._checkLoadResource(compOutputDir + path.sep + this.view); this._bundle(config, bundleOutputDir, compOutputDir); }); } else { await this._checkLoadResource(compOutputDir + path.sep + this.view); this._bundle(config, bundleOutputDir, compOutputDir); } } _removeDir(dirPath) { if (fs.existsSync(dirPath)) { del.sync([`${dirPath}${path.sep}**`], {force: true}); } } _onError(e) { log.error(""); log.error("Whoops! Something went wrong:"); log.error(e.message); process.exit(1); } _shouldMinifyJSAndHasJS(file) { return this.minifyTemplateLiterals && gulpmatch(file, /\.js$/); } } module.exports = TriBundler;