Remove closure in favor of a simple JSMin implementation

Closure is a great js-minifier, but js-minification is really
outside the scope of this project.
A simple space/line stripper should be suffcient.
This commit is contained in:
David
2017-01-29 12:55:30 +01:00
parent df1e4da96d
commit 733199cd15
4 changed files with 300 additions and 33 deletions

View File

@@ -51,12 +51,6 @@
<artifactId>commons-lang3</artifactId> <artifactId>commons-lang3</artifactId>
<version>3.4</version> <version>3.4</version>
</dependency> </dependency>
<dependency>
<groupId>com.google.javascript</groupId>
<artifactId>closure-compiler</artifactId>
<version>v20151015</version>
<optional>true</optional>
</dependency>
</dependencies> </dependencies>
<packaging>jar</packaging> <packaging>jar</packaging>

View File

@@ -14,7 +14,7 @@ public class InlineStaticResource {
if(fileString != null) { if(fileString != null) {
switch(format) { switch(format) {
case CSS_MIN : return style().with(unsafeHtml(compressCss(fileString))); case CSS_MIN : return style().with(unsafeHtml(compressCss(fileString)));
case JS_MIN : return script().with(unsafeHtml(compressJs(fileString, path))); case JS_MIN : return script().with(unsafeHtml(compressJs(fileString)));
case CSS : return style().with(unsafeHtml(fileString)); case CSS : return style().with(unsafeHtml(fileString));
case JS : return script().with(unsafeHtml(fileString)); case JS : return script().with(unsafeHtml(fileString));
default : return errorAlert; default : return errorAlert;
@@ -35,8 +35,8 @@ public class InlineStaticResource {
return CSSMin.compress(code); return CSSMin.compress(code);
} }
private static String compressJs(String code, String debugPath) { private static String compressJs(String code) {
return JSMin.compressJs(code, debugPath); return JSMin.compressJs(code);
} }
} }

View File

@@ -1,32 +1,305 @@
package j2html.utils; package j2html.utils;
/*
* JSMin.java 2006-02-13
*
* Copyright (c) 2006 John Reilly (www.inconspicuous.org)
*
* This work is a translation from C to Java of jsmin.c published by
* Douglas Crockford. Permission is hereby granted to use the Java
* version under the same conditions as the jsmin.c on which it is
* based.
*
*
*
*
* jsmin.c 2003-04-21
*
* Copyright (c) 2002 Douglas Crockford (www.crockford.com)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* The Software shall be used for Good, not Evil.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
// package org.inconspicuous.jsmin;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PushbackInputStream;
import com.google.javascript.jscomp.*;
import com.google.javascript.jscomp.Compiler;
public class JSMin { public class JSMin {
private JSMin() {}
public static String compressJs(String code, String sourcePath) { /**
return isPresent("com.google.javascript.jscomp.Compiler") ? compressJsUsingClosureCompiler(code, sourcePath) : code; * Compress a JS-string
} *
* @param code the js-code you want to compress
private static boolean isPresent(String className) { * @return the compressed code
*/
public static String compressJs(String code) {
InputStream inStream = new ByteArrayInputStream(code.getBytes());
ByteArrayOutputStream outStream = new ByteArrayOutputStream();
JSMin jsmin = new JSMin(inStream, outStream);
try { try {
Class.forName(className); jsmin.jsmin();
return true; return outStream.toString().trim();
} catch (Exception e) { } catch (IOException | UnterminatedRegExpLiteralException | UnterminatedCommentException | UnterminatedStringLiteralException e) {
return false; e.printStackTrace();
return "";
} }
} }
private static String compressJsUsingClosureCompiler(String code, String sourcePath) { private static final int EOF = -1;
com.google.javascript.jscomp.Compiler compiler = new Compiler();
CompilerOptions options = new CompilerOptions(); private PushbackInputStream in;
CompilationLevel.SIMPLE_OPTIMIZATIONS.setOptionsForCompilationLevel(options); private OutputStream out;
SourceFile extern = SourceFile.fromCode("externs.js", "");
SourceFile input = SourceFile.fromCode(sourcePath, code); private int theA;
compiler.compile(extern, input, options); private int theB;
return compiler.toSource();
private JSMin(InputStream in, OutputStream out) {
this.in = new PushbackInputStream(in);
this.out = out;
} }
}
/**
* isAlphanum -- return true if the character is a letter, digit,
* underscore, dollar sign, or non-ASCII character.
*/
private static boolean isAlphanum(int c) {
return ((c >= 'a' && c <= 'z') ||
(c >= '0' && c <= '9') ||
(c >= 'A' && c <= 'Z') ||
c == '_' ||
c == '$' ||
c == '\\' ||
c > 126);
}
/**
* get -- return the next character from stdin. Watch out for lookahead. If
* the character is a control character, translate it to a space or
* linefeed.
*/
private int get() throws IOException {
int c = in.read();
if (c >= ' ' || c == '\n' || c == EOF) {
return c;
}
if (c == '\r') {
return '\n';
}
return ' ';
}
/**
* Get the next character without getting it.
*/
private int peek() throws IOException {
int lookaheadChar = in.read();
in.unread(lookaheadChar);
return lookaheadChar;
}
/**
* next -- get the next character, excluding comments. peek() is used to see
* if a '/' is followed by a '/' or '*'.
*/
private int next() throws IOException, UnterminatedCommentException {
int c = get();
if (c == '/') {
switch (peek()) {
case '/':
for (; ; ) {
c = get();
if (c <= '\n') {
return c;
}
}
case '*':
get();
for (; ; ) {
switch (get()) {
case '*':
if (peek() == '/') {
get();
return ' ';
}
break;
case EOF:
throw new UnterminatedCommentException();
}
}
default:
return c;
}
}
return c;
}
/**
* action -- do something! What you do is determined by the argument: 1
* Output A. Copy B to A. Get the next B. 2 Copy B to A. Get the next B.
* (Delete A). 3 Get the next B. (Delete B). action treats a string as a
* single character. Wow! action recognizes a regular expression if it is
* preceded by ( or , or =.
*/
private void action(int d) throws IOException, UnterminatedRegExpLiteralException,
UnterminatedCommentException, UnterminatedStringLiteralException {
switch (d) {
case 1:
out.write(theA);
case 2:
theA = theB;
if (theA == '\'' || theA == '"') {
for (; ; ) {
out.write(theA);
theA = get();
if (theA == theB) {
break;
}
if (theA <= '\n') {
throw new UnterminatedStringLiteralException();
}
if (theA == '\\') {
out.write(theA);
theA = get();
}
}
}
case 3:
theB = next();
if (theB == '/' && (theA == '(' || theA == ',' || theA == '=')) {
out.write(theA);
out.write(theB);
for (; ; ) {
theA = get();
if (theA == '/') {
break;
} else if (theA == '\\') {
out.write(theA);
theA = get();
} else if (theA <= '\n') {
throw new UnterminatedRegExpLiteralException();
}
out.write(theA);
}
theB = next();
}
}
}
/**
* jsmin -- Copy the input to the output, deleting the characters which are
* insignificant to JavaScript. Comments will be removed. Tabs will be
* replaced with spaces. Carriage returns will be replaced with linefeeds.
* Most spaces and linefeeds will be removed.
*/
public void jsmin() throws IOException, UnterminatedRegExpLiteralException, UnterminatedCommentException, UnterminatedStringLiteralException {
theA = '\n';
action(3);
while (theA != EOF) {
switch (theA) {
case ' ':
if (isAlphanum(theB)) {
action(1);
} else {
action(2);
}
break;
case '\n':
switch (theB) {
case '{':
case '[':
case '(':
case '+':
case '-':
action(1);
break;
case ' ':
action(3);
break;
default:
if (isAlphanum(theB)) {
action(1);
} else {
action(2);
}
}
break;
default:
switch (theB) {
case ' ':
if (isAlphanum(theA)) {
action(1);
break;
}
action(3);
break;
case '\n':
switch (theA) {
case '}':
case ']':
case ')':
case '+':
case '-':
case '"':
case '\'':
action(1);
break;
default:
if (isAlphanum(theA)) {
action(1);
} else {
action(3);
}
}
break;
default:
action(1);
break;
}
}
}
out.flush();
}
private class UnterminatedCommentException extends Exception {
}
private class UnterminatedStringLiteralException extends Exception {
}
private class UnterminatedRegExpLiteralException extends Exception {
}
}

View File

@@ -70,7 +70,7 @@ public class TagCreatorTest {
assertEquals(text("<script> and \"</script>\"").render(), "&lt;script&gt; and &quot;&lt;/script&gt;&quot;"); assertEquals(text("<script> and \"</script>\"").render(), "&lt;script&gt; and &quot;&lt;/script&gt;&quot;");
assertEquals(unsafeHtml("<script>").render(), "<script>"); assertEquals(unsafeHtml("<script>").render(), "<script>");
assertEquals(styleWithInlineFile_min("/test.css").render(), "<style>body{background:#daa520;margin-bottom:10px;margin-left:10px;margin-right:10px;margin-top:10px}</style>"); assertEquals(styleWithInlineFile_min("/test.css").render(), "<style>body{background:#daa520;margin-bottom:10px;margin-left:10px;margin-right:10px;margin-top:10px}</style>");
assertEquals(scriptWithInlineFile_min("/test.js").render(), "<script>(function(){console.log(15)})();</script>"); assertEquals(scriptWithInlineFile_min("/test.js").render(), "<script>(function(){var test=5;var tast=10;var testTast=test+tast;console.log(testTast);})();</script>");
assertEquals(fileAsString("/test.html").render(), "<body>" + EOL + " Any content" + EOL + "</body>" + EOL); assertEquals(fileAsString("/test.html").render(), "<body>" + EOL + " Any content" + EOL + "</body>" + EOL);
assertEquals(fileAsEscapedString("/test.html").render(), "&lt;body&gt;" + EOL + " Any content" + EOL + "&lt;/body&gt;" + EOL); assertEquals(fileAsEscapedString("/test.html").render(), "&lt;body&gt;" + EOL + " Any content" + EOL + "&lt;/body&gt;" + EOL);
assertEquals(fileAsString("/AnyContent.java").render(), "public class AnyContent{}" + EOL); assertEquals(fileAsString("/AnyContent.java").render(), "public class AnyContent{}" + EOL);