@tririga/tri-bundler
Version:
A tool for bundling Polymer 3 TRIRIGA UX views.
282 lines (253 loc) • 9.1 kB
JavaScript
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;