1 | // Copyright (C) 2008 Google Inc. |
2 | // |
3 | // Licensed under the Apache License, Version 2.0 (the "License"); |
4 | // you may not use this file except in compliance with the License. |
5 | // You may obtain a copy of the License at |
6 | // |
7 | // http://www.apache.org/licenses/LICENSE-2.0 |
8 | // |
9 | // Unless required by applicable law or agreed to in writing, software |
10 | // distributed under the License is distributed on an "AS IS" BASIS, |
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
12 | // See the License for the specific language governing permissions and |
13 | // limitations under the License. |
14 | |
15 | package com.google.caja.parser.js; |
16 | |
17 | import com.google.caja.lexer.CharProducer; |
18 | import com.google.caja.lexer.InputSource; |
19 | import com.google.caja.lexer.JsLexer; |
20 | import com.google.caja.lexer.JsTokenQueue; |
21 | import com.google.caja.lexer.ParseException; |
22 | import com.google.caja.render.Concatenator; |
23 | import com.google.caja.render.JsMinimalPrinter; |
24 | import com.google.caja.reporting.EchoingMessageQueue; |
25 | import com.google.caja.reporting.Message; |
26 | import com.google.caja.reporting.MessageContext; |
27 | import com.google.caja.reporting.MessageLevel; |
28 | import com.google.caja.reporting.MessagePart; |
29 | import com.google.caja.reporting.MessageQueue; |
30 | import com.google.caja.reporting.MessageType; |
31 | import com.google.caja.reporting.RenderContext; |
32 | import com.google.caja.util.Callback; |
33 | import com.google.caja.util.Charsets; |
34 | import com.google.caja.util.Pair; |
35 | |
36 | import java.io.File; |
37 | import java.io.FileInputStream; |
38 | import java.io.IOException; |
39 | import java.io.InputStreamReader; |
40 | import java.io.Writer; |
41 | import java.io.PrintWriter; |
42 | import java.util.ArrayList; |
43 | import java.util.List; |
44 | |
45 | /** |
46 | * An executable that compresses the input javascript. |
47 | * Usage |
48 | * <pre> |
49 | * java com.google.caja.parser.js.Minify file1.js file2.js ... > minified.js |
50 | * </pre> |
51 | * |
52 | * <p>This parses and renders JS so guarantees valid output, but does not |
53 | * otherwise change the structure. The output is semantically the same even |
54 | * in the presence of aliased eval. |
55 | * |
56 | * <p>It does strip comments and unnecessary whitespace.</p> |
57 | * |
58 | * <p>It does not rename local variables, inline constants or functions, |
59 | * eliminate dead code, or zip content.</p> |
60 | * |
61 | * @author mikesamuel@gmail.com |
62 | */ |
63 | public class Minify { |
64 | public static void main(String[] jsFilePaths) throws IOException { |
65 | List<Pair<InputSource, File>> inputs = checkInputs(jsFilePaths); |
66 | boolean passed = minify(inputs, new PrintWriter(System.out), |
67 | new PrintWriter(System.err)); |
68 | System.exit(passed ? 0 : -1); |
69 | } |
70 | |
71 | /** Called before opening files to checks that all input are readable. */ |
72 | private static List<Pair<InputSource, File>> checkInputs(String... jsFiles) |
73 | throws IOException { |
74 | List<Pair<InputSource, File>> inputs |
75 | = new ArrayList<Pair<InputSource, File>>(); |
76 | for (String jsFile : jsFiles) { |
77 | File f = new File(jsFile); |
78 | if (!f.canRead()) { throw new IOException("Cannot read " + jsFile); } |
79 | InputSource is = new InputSource(f.getAbsoluteFile().toURI()); |
80 | inputs.add(Pair.pair(is, f)); |
81 | } |
82 | return inputs; |
83 | } |
84 | |
85 | public static boolean minify(Iterable<Pair<InputSource, File>> inputs, |
86 | Writer out, PrintWriter err) |
87 | throws IOException { |
88 | MessageContext mc = new MessageContext(); |
89 | for (Pair<InputSource, File> input : inputs) { |
90 | mc.addInputSource(input.a); |
91 | } |
92 | final MessageQueue errs = new EchoingMessageQueue( |
93 | err, mc, false); |
94 | RenderContext rc = new RenderContext( |
95 | new JsMinimalPrinter(new Concatenator(out, new Callback<IOException>() { |
96 | public void handle(IOException ex) { |
97 | errs.addMessage( |
98 | MessageType.IO_ERROR, |
99 | MessagePart.Factory.valueOf(ex.getMessage())); |
100 | } |
101 | }))); |
102 | |
103 | for (Pair<InputSource, File> input : inputs) { |
104 | CharProducer cp = CharProducer.Factory.create( |
105 | new InputStreamReader(new FileInputStream(input.b), |
106 | Charsets.UTF_8.name()), |
107 | input.a); |
108 | JsLexer lexer = new JsLexer(cp); |
109 | JsTokenQueue tq = new JsTokenQueue(lexer, input.a); |
110 | Parser p = new Parser(tq, errs); |
111 | try { |
112 | while (!tq.isEmpty()) { |
113 | Block b = p.parse(); |
114 | for (Statement topLevelStmt : b.children()) { |
115 | topLevelStmt.render(rc); |
116 | if (!topLevelStmt.isTerminal()) { rc.getOut().consume(";"); } |
117 | } |
118 | } |
119 | } catch (ParseException ex) { |
120 | ex.toMessageQueue(errs); |
121 | } |
122 | } |
123 | rc.getOut().noMoreTokens(); |
124 | out.flush(); |
125 | |
126 | MessageLevel maxMessageLevel = MessageLevel.values()[0]; |
127 | for (Message msg : errs.getMessages()) { |
128 | if (msg.getMessageLevel().compareTo(maxMessageLevel) >= 0) { |
129 | maxMessageLevel = msg.getMessageLevel(); |
130 | } |
131 | } |
132 | return maxMessageLevel.compareTo(MessageLevel.ERROR) < 0; |
133 | } |
134 | } |