google-closure-compiler
Version:
Check, compile, optimize and compress Javascript with Closure-Compiler
149 lines (128 loc) • 4.01 kB
JavaScript
/*
* Copyright 2015 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @fileoverview Low level class for calling the closure-compiler jar
* from nodejs
*
* @author Chad Killingsworth (chadkillingsworth@gmail.com)
*/
import {spawn} from 'node:child_process';
import compilerPath from 'google-closure-compiler-java';
/**
* @type {string}
*/
export const javaPath = 'java';
export default class Compiler {
/**
* @param {Object<string,string|boolean>|Array<string>} args
* @param {Array<String>=} extraCommandArgs
*/
constructor(args, extraCommandArgs) {
this.commandArguments = [];
this.extraCommandArgs = extraCommandArgs;
this.JAR_PATH = compilerPath;
this.javaPath = javaPath;
if (Array.isArray(args)) {
this.commandArguments.push(...args);
} else {
for (const [key, val] of Object.entries(args)) {
if (Array.isArray(val)) {
this.commandArguments.push(...val.map((item) => this.formatArgument(key, item)));
} else {
this.commandArguments.push(this.formatArgument(key, val));
}
}
}
/** @type {function(...*)|null} */
this.logger = null;
/** @type {Object<string, string>} */
this.spawnOptions = undefined;
}
/** @param {function(number, string, string)=} callback */
run(callback) {
if (this.JAR_PATH) {
this.commandArguments.unshift(
'-XX:+IgnoreUnrecognizedVMOptions',
'--sun-misc-unsafe-memory-access=allow',
'-jar',
this.JAR_PATH,
);
if (this.extraCommandArgs) {
this.commandArguments.unshift(...this.extraCommandArgs);
}
}
if (this.logger) {
this.logger(this.getFullCommand() + '\n');
}
let compileProcess = spawn(this.javaPath, this.commandArguments, this.spawnOptions);
let stdOutData = '';
let stdErrData = '';
if (callback) {
if (compileProcess.stdout) {
compileProcess.stdout.setEncoding('utf8');
compileProcess.stdout.on('data', (data) => {
stdOutData += data;
});
compileProcess.stdout.on('error', (err) => {
stdErrData += err.toString();
});
}
if (compileProcess.stderr) {
compileProcess.stderr.setEncoding('utf8');
compileProcess.stderr.on('data', (data) => {
stdErrData += data;
});
}
compileProcess.on('close', (code) => {
if (code !== 0) {
stdErrData = this.prependFullCommand(stdErrData);
}
callback(code, stdOutData, stdErrData);
});
compileProcess.on('error', (err) => {
callback(1, stdOutData,
this.prependFullCommand('Process spawn error. Is java in the path?\n' + err.message));
});
}
return compileProcess;
}
/**
* @return {string}
*/
getFullCommand() {
return `${this.javaPath} ${this.commandArguments.join(' ')}`;
}
/**
* @param {string} msg
* @return {string}
*/
prependFullCommand(msg) {
return `${this.getFullCommand()}\n\n${msg}\n\n`;
}
/**
* @param {string} key
* @param {(string|boolean)=} val
* @return {string}
*/
formatArgument(key, val) {
let normalizedKey = key.replace(/[A-Z]/g, (match) => `_${match.toLowerCase()}`);
normalizedKey = normalizedKey.replace(/^--/, '');
if (val === undefined || val === null) {
return `--${normalizedKey}`;
}
return `--${normalizedKey}=${val}`;
}
};