1 package main 2 3 import ( 4 "errors" 5 "flag" 6 "fmt" 7 "os" 8 "path" 9 "path/filepath" 10 "sort" 11 12 "vimagination.zapto.org/javascript" 13 "vimagination.zapto.org/jspacker" 14 "vimagination.zapto.org/parser" 15 ) 16 17 type Inputs []string 18 19 func (i *Inputs) Set(v string) error { 20 *i = append(*i, v) 21 return nil 22 } 23 24 func (i *Inputs) String() string { 25 return "" 26 } 27 28 func main() { 29 if err := run(); err != nil { 30 fmt.Fprintln(os.Stderr, err) 31 os.Exit(1) 32 } 33 } 34 35 func run() error { 36 var ( 37 output, base string 38 filesTodo Inputs 39 plugin, noExports, reorder bool 40 err error 41 ) 42 43 flag.Var(&filesTodo, "i", "input file") 44 flag.StringVar(&output, "o", "-", "output file") 45 flag.StringVar(&base, "b", "", "js base dir") 46 flag.BoolVar(&plugin, "p", false, "export file as plugin") 47 flag.BoolVar(&noExports, "n", false, "no exports") 48 flag.BoolVar(&reorder, "x", false, "experimental script re-ordering to enable better minification (BE CAREFUL)") 49 flag.Parse() 50 51 if plugin && len(filesTodo) != 1 { 52 return errors.New("plugin mode requires a single file") 53 } 54 if output == "" { 55 output = "-" 56 } 57 if base == "" { 58 if output == "-" { 59 base = "./" 60 } else { 61 base = path.Dir(output) 62 } 63 } 64 base, err = filepath.Abs(base) 65 if err != nil { 66 return fmt.Errorf("error getting absolute path for base: %w", err) 67 } 68 var s *javascript.Script 69 if plugin { 70 f, err := os.Open(filepath.Join(base, filepath.FromSlash(filesTodo[0]))) 71 if err != nil { 72 return fmt.Errorf("error opening url: %w", err) 73 } 74 tks := parser.NewReaderTokeniser(f) 75 m, err := javascript.ParseModule(&tks) 76 f.Close() 77 if err != nil { 78 return fmt.Errorf("error parsing javascript module: %w", err) 79 } 80 if s, err = jspacker.Plugin(m, filesTodo[0]); err != nil { 81 return fmt.Errorf("error processing javascript plugin: %w", err) 82 } 83 } else { 84 args := make([]jspacker.Option, 1, len(filesTodo)+3) 85 args[0] = jspacker.ParseDynamic 86 if base != "" { 87 args = append(args, jspacker.Loader(jspacker.OSLoad(base))) 88 } 89 if noExports { 90 args = append(args, jspacker.NoExports) 91 } 92 for _, f := range filesTodo { 93 args = append(args, jspacker.File(f)) 94 } 95 s, err = jspacker.Package(args...) 96 if err != nil { 97 return fmt.Errorf("error generating output: %w", err) 98 } 99 } 100 for len(s.StatementList) > 0 && s.StatementList[0].Declaration == nil && s.StatementList[0].Statement == nil { 101 s.StatementList = s.StatementList[1:] 102 } 103 if reorder { 104 sort.Stable(statementSorter(s.StatementList[1:])) 105 } 106 var of *os.File 107 if output == "-" { 108 of = os.Stdout 109 } else { 110 of, err = os.Create(output) 111 if err != nil { 112 return fmt.Errorf("error creating output file: %w", err) 113 } 114 } 115 _, err = fmt.Fprintf(of, "%+s\n", s) 116 if err != nil { 117 return fmt.Errorf("error writing to output: %w", err) 118 } 119 if err := of.Close(); err != nil { 120 return fmt.Errorf("error closing output: %w", err) 121 } 122 return nil 123 } 124 125 type statementSorter []javascript.StatementListItem 126 127 func (s statementSorter) Len() int { 128 return len(s) 129 } 130 131 func (s statementSorter) Less(i, j int) bool { 132 if scoreA, scoreB := score(&s[i]), score(&s[j]); scoreA != scoreB { 133 return scoreA > scoreB 134 } 135 return i < j 136 } 137 138 func (s statementSorter) Swap(i, j int) { 139 s[i], s[j] = s[j], s[i] 140 } 141 142 func score(sli *javascript.StatementListItem) uint8 { 143 if sli.Statement != nil { 144 if sli.Statement.ExpressionStatement != nil && len(sli.Statement.ExpressionStatement.Expressions) == 1 && sli.Statement.ExpressionStatement.Expressions[0].ConditionalExpression != nil && sli.Statement.ExpressionStatement.Expressions[0].AssignmentOperator == javascript.AssignmentNone { 145 if ce, ok := javascript.UnwrapConditional(sli.Statement.ExpressionStatement.Expressions[0].ConditionalExpression).(*javascript.CallExpression); ok { 146 if ce.MemberExpression != nil && ce.MemberExpression.MemberExpression != nil && ce.MemberExpression.MemberExpression.PrimaryExpression != nil && ce.MemberExpression.MemberExpression.PrimaryExpression.IdentifierReference != nil && ce.MemberExpression.MemberExpression.PrimaryExpression.IdentifierReference.Data == "customElements" && ce.MemberExpression.IdentifierName != nil && ce.MemberExpression.IdentifierName.Data == "define" { 147 return 4 148 } 149 } 150 } else if sli.Statement.VariableStatement != nil { 151 return 2 152 } 153 } else if sli.Declaration != nil { 154 if sli.Declaration.ClassDeclaration != nil { 155 return 6 156 } else if sli.Declaration.FunctionDeclaration != nil { 157 return 5 158 } else if sli.Declaration.LexicalDeclaration != nil { 159 if sli.Declaration.LexicalDeclaration.LetOrConst == javascript.Let { 160 return 3 161 } 162 return 1 163 } 164 } 165 return 0 166 } 167