1 package jspacker 2 3 import ( 4 "fmt" 5 "path" 6 "strconv" 7 8 "vimagination.zapto.org/javascript" 9 "vimagination.zapto.org/javascript/scope" 10 "vimagination.zapto.org/javascript/walk" 11 ) 12 13 type importBinding struct { 14 *dependency 15 binding string 16 } 17 18 type dependency struct { 19 config *config 20 url string 21 scope *scope.Scope 22 requires map[string]*dependency 23 imports, exports map[string]*importBinding 24 prefix string 25 dynamicRequirement bool 26 needsMeta bool 27 done bool 28 } 29 30 func id2String(id uint) string { 31 var ( 32 p [15]byte 33 n = 14 34 ) 35 p[14] = '_' 36 for id > 0 { 37 n-- 38 id-- 39 p[n] = 'a' + byte(id%26) 40 id /= 26 41 } 42 return string(p[n:]) 43 } 44 45 func (d *dependency) addImport(url string) (*dependency, error) { 46 c := d.config 47 e, ok := c.filesDone[url] 48 if !ok { 49 c.nextID++ 50 id := c.nextID 51 e = &dependency{ 52 config: c, 53 url: url, 54 requires: make(map[string]*dependency), 55 imports: make(map[string]*importBinding), 56 exports: make(map[string]*importBinding), 57 prefix: id2String(id), 58 } 59 c.filesDone[url] = e 60 if err := e.process(); err != nil { 61 return nil, err 62 } 63 } 64 d.requires[url] = e 65 return e, nil 66 } 67 68 func (d *dependency) process() error { 69 module, err := d.config.loader(d.url) 70 if err != nil { 71 return err 72 } 73 d.scope, err = scope.ModuleScope(module, nil) 74 if err != nil { 75 if derr, ok := err.(scope.ErrDuplicateDeclaration); ok { 76 return fmt.Errorf("error processing scope in file %s: %w: %v", d.url, err, derr.Duplicate) 77 } 78 return fmt.Errorf("error processing scope in file %s: %w", d.url, err) 79 } 80 for _, li := range module.ModuleListItems { 81 if li.ImportDeclaration != nil { 82 durl, _ := javascript.Unquote(li.ImportDeclaration.FromClause.ModuleSpecifier.Data) 83 iurl := d.RelTo(durl) 84 e, err := d.addImport(iurl) 85 if err != nil { 86 return err 87 } 88 if li.ImportDeclaration.ImportClause == nil { 89 continue 90 } 91 if li.ImportDeclaration.ImportedDefaultBinding != nil { 92 d.setImportBinding(li.ImportDeclaration.ImportedDefaultBinding.Data, e, "default") 93 } 94 if li.ImportDeclaration.NameSpaceImport != nil { 95 d.setImportBinding(li.ImportDeclaration.NameSpaceImport.Data, e, "*") 96 d.config.statementList = append(d.config.statementList, javascript.StatementListItem{ 97 Declaration: &javascript.Declaration{ 98 LexicalDeclaration: &javascript.LexicalDeclaration{ 99 LetOrConst: javascript.Const, 100 BindingList: []javascript.LexicalBinding{ 101 { 102 BindingIdentifier: li.ImportDeclaration.NameSpaceImport, 103 Initializer: &javascript.AssignmentExpression{ 104 ConditionalExpression: javascript.WrapConditional(javascript.UnaryExpression{ 105 UnaryOperators: []javascript.UnaryOperator{javascript.UnaryAwait}, 106 UpdateExpression: javascript.UpdateExpression{ 107 LeftHandSideExpression: &javascript.LeftHandSideExpression{ 108 CallExpression: &javascript.CallExpression{ 109 MemberExpression: &javascript.MemberExpression{ 110 PrimaryExpression: &javascript.PrimaryExpression{ 111 IdentifierReference: jToken("include"), 112 }, 113 }, 114 Arguments: &javascript.Arguments{ 115 ArgumentList: []javascript.Argument{ 116 { 117 AssignmentExpression: javascript.AssignmentExpression{ 118 ConditionalExpression: javascript.WrapConditional(&javascript.PrimaryExpression{ 119 Literal: jToken(strconv.Quote(iurl)), 120 }), 121 }, 122 }, 123 { 124 AssignmentExpression: javascript.AssignmentExpression{ 125 ConditionalExpression: javascript.WrapConditional(&javascript.PrimaryExpression{ 126 Literal: jToken("true"), 127 }), 128 }, 129 }, 130 }, 131 }, 132 }, 133 }, 134 }, 135 }), 136 }, 137 }, 138 }, 139 }, 140 }, 141 }) 142 } else if li.ImportDeclaration.NamedImports != nil { 143 for _, is := range li.ImportDeclaration.NamedImports.ImportList { 144 tk := is.ImportedBinding 145 if is.IdentifierName != nil { 146 tk = is.IdentifierName 147 } 148 d.setImportBinding(is.ImportedBinding.Data, e, tk.Data) 149 } 150 } 151 } else if li.StatementListItem != nil { 152 d.config.statementList = append(d.config.statementList, *li.StatementListItem) 153 } else if li.ExportDeclaration != nil { 154 ed := li.ExportDeclaration 155 if ed.FromClause != nil { 156 durl, _ := javascript.Unquote(ed.FromClause.ModuleSpecifier.Data) 157 e, err := d.addImport(d.RelTo(durl)) 158 if err != nil { 159 return err 160 } 161 if ed.ExportClause != nil { 162 for _, es := range ed.ExportClause.ExportList { 163 tk := es.IdentifierName.Data 164 if es.EIdentifierName != nil { 165 tk = es.EIdentifierName.Data 166 } 167 d.setExportBinding(tk, e, es.IdentifierName.Data) 168 } 169 } else if ed.ExportFromClause != nil { 170 d.setExportBinding(ed.ExportFromClause.Data, e, "") 171 } else { 172 d.config.exportAllFrom = append(d.config.exportAllFrom, [2]*dependency{d, e}) 173 } 174 } else if ed.ExportClause != nil { 175 for _, es := range ed.ExportClause.ExportList { 176 tk := es.IdentifierName.Data 177 if es.EIdentifierName != nil { 178 tk = es.EIdentifierName.Data 179 } 180 d.setExportBinding(tk, nil, es.IdentifierName.Data) 181 } 182 } else if ed.VariableStatement != nil { 183 for _, vd := range ed.VariableStatement.VariableDeclarationList { 184 d.processBindingElement(vd.BindingIdentifier, vd.ArrayBindingPattern, vd.ObjectBindingPattern) 185 } 186 d.config.statementList = append(d.config.statementList, javascript.StatementListItem{ 187 Statement: &javascript.Statement{ 188 VariableStatement: ed.VariableStatement, 189 }, 190 }) 191 } else if ed.Declaration != nil { 192 if ed.Declaration.FunctionDeclaration != nil { 193 d.setExportBinding(ed.Declaration.FunctionDeclaration.BindingIdentifier.Data, nil, ed.Declaration.FunctionDeclaration.BindingIdentifier.Data) 194 } else if ed.Declaration.ClassDeclaration != nil { 195 d.setExportBinding(ed.Declaration.ClassDeclaration.BindingIdentifier.Data, nil, ed.Declaration.ClassDeclaration.BindingIdentifier.Data) 196 } else if ed.Declaration.LexicalDeclaration != nil { 197 for _, lb := range ed.Declaration.LexicalDeclaration.BindingList { 198 d.processBindingElement(lb.BindingIdentifier, lb.ArrayBindingPattern, lb.ObjectBindingPattern) 199 } 200 } 201 d.config.statementList = append(d.config.statementList, javascript.StatementListItem{ 202 Declaration: ed.Declaration, 203 }) 204 } else { 205 def := jToken("default") 206 if ed.DefaultFunction != nil { 207 if ed.DefaultFunction.BindingIdentifier == nil { 208 ed.DefaultFunction.BindingIdentifier = def 209 } else { 210 def = ed.DefaultFunction.BindingIdentifier 211 d.scope.Bindings["default"] = d.scope.Bindings[def.Data] 212 delete(d.scope.Bindings, def.Data) 213 } 214 d.config.statementList = append(d.config.statementList, javascript.StatementListItem{ 215 Declaration: &javascript.Declaration{ 216 FunctionDeclaration: ed.DefaultFunction, 217 }, 218 }) 219 } else if ed.DefaultClass != nil { 220 if ed.DefaultClass.BindingIdentifier == nil { 221 ed.DefaultClass.BindingIdentifier = def 222 } else { 223 def = ed.DefaultClass.BindingIdentifier 224 d.scope.Bindings["default"] = d.scope.Bindings[def.Data] 225 delete(d.scope.Bindings, def.Data) 226 } 227 d.config.statementList = append(d.config.statementList, javascript.StatementListItem{ 228 Declaration: &javascript.Declaration{ 229 ClassDeclaration: ed.DefaultClass, 230 }, 231 }) 232 } else if ed.DefaultAssignmentExpression != nil { 233 d.config.statementList = append(d.config.statementList, javascript.StatementListItem{ 234 Declaration: &javascript.Declaration{ 235 LexicalDeclaration: &javascript.LexicalDeclaration{ 236 LetOrConst: javascript.Const, 237 BindingList: []javascript.LexicalBinding{ 238 { 239 BindingIdentifier: def, 240 Initializer: ed.DefaultAssignmentExpression, 241 }, 242 }, 243 }, 244 }, 245 }) 246 } 247 if len(d.scope.Bindings["default"]) == 0 { 248 d.scope.Bindings["default"] = []scope.Binding{ 249 { 250 BindingType: scope.BindingLexicalConst, 251 Token: def, 252 }, 253 } 254 } 255 d.setExportBinding("default", nil, "default") 256 } 257 } 258 } 259 if d.needsMeta { 260 d.config.statementList[1].Declaration.LexicalDeclaration.BindingList = append(d.config.statementList[1].Declaration.LexicalDeclaration.BindingList, javascript.LexicalBinding{ 261 BindingIdentifier: jToken(d.prefix + "import"), 262 Initializer: &javascript.AssignmentExpression{ 263 ConditionalExpression: javascript.WrapConditional(&javascript.ObjectLiteral{ 264 PropertyDefinitionList: []javascript.PropertyDefinition{ 265 { 266 PropertyName: &javascript.PropertyName{ 267 LiteralPropertyName: jToken("url"), 268 }, 269 AssignmentExpression: &javascript.AssignmentExpression{ 270 ConditionalExpression: javascript.WrapConditional(&javascript.AdditiveExpression{ 271 AdditiveExpression: &javascript.AdditiveExpression{ 272 MultiplicativeExpression: javascript.MultiplicativeExpression{ 273 ExponentiationExpression: javascript.ExponentiationExpression{ 274 UnaryExpression: javascript.UnaryExpression{ 275 UpdateExpression: javascript.UpdateExpression{ 276 LeftHandSideExpression: &javascript.LeftHandSideExpression{ 277 NewExpression: &javascript.NewExpression{ 278 MemberExpression: javascript.MemberExpression{ 279 PrimaryExpression: &javascript.PrimaryExpression{ 280 IdentifierReference: jToken("o"), 281 }, 282 }, 283 }, 284 }, 285 }, 286 }, 287 }, 288 }, 289 }, 290 AdditiveOperator: javascript.AdditiveAdd, 291 MultiplicativeExpression: javascript.MultiplicativeExpression{ 292 ExponentiationExpression: javascript.ExponentiationExpression{ 293 UnaryExpression: javascript.UnaryExpression{ 294 UpdateExpression: javascript.UpdateExpression{ 295 LeftHandSideExpression: &javascript.LeftHandSideExpression{ 296 NewExpression: &javascript.NewExpression{ 297 MemberExpression: javascript.MemberExpression{ 298 PrimaryExpression: &javascript.PrimaryExpression{ 299 Literal: jToken(strconv.Quote(d.url)), 300 }, 301 }, 302 }, 303 }, 304 }, 305 }, 306 }, 307 }, 308 }), 309 }, 310 }, 311 }, 312 }), 313 }, 314 }) 315 } 316 d.processBindings(d.scope) 317 if d.config.parseDynamic { 318 if err := walk.Walk(module, d); err != nil { 319 return err 320 } 321 } 322 return nil 323 } 324 325 func (d *dependency) Handle(t javascript.Type) error { 326 if ce, ok := t.(*javascript.CallExpression); ok && isConditionalExpression(ce.ImportCall) { 327 d.HandleImportConditional(ce.ImportCall.ConditionalExpression) 328 ce.MemberExpression = &javascript.MemberExpression{ 329 PrimaryExpression: &javascript.PrimaryExpression{ 330 IdentifierReference: jToken("include"), 331 }, 332 } 333 ce.Arguments = &javascript.Arguments{ 334 ArgumentList: []javascript.Argument{ 335 { 336 AssignmentExpression: *ce.ImportCall, 337 }, 338 }, 339 } 340 ce.ImportCall = nil 341 } else if ok && ce.MemberExpression != nil && ce.MemberExpression.PrimaryExpression != nil && ce.MemberExpression.PrimaryExpression.IdentifierReference != nil && ce.MemberExpression.PrimaryExpression.IdentifierReference.Data == "include" && ce.MemberExpression.MemberExpression == nil && ce.MemberExpression.Expression == nil && ce.MemberExpression.IdentifierName == nil && ce.MemberExpression.TemplateLiteral == nil && !ce.MemberExpression.SuperProperty && !ce.MemberExpression.NewTarget && !ce.MemberExpression.ImportMeta && ce.MemberExpression.Arguments == nil && !ce.SuperCall && ce.ImportCall == nil && ce.Arguments != nil && ce.Expression == nil && ce.IdentifierName == nil && ce.TemplateLiteral == nil && len(ce.Arguments.ArgumentList) == 1 { 342 d.HandleImportConditional(ce.Arguments.ArgumentList[0].AssignmentExpression.ConditionalExpression) 343 } else if d.config != nil { 344 if me, ok := t.(*javascript.MemberExpression); ok && me.ImportMeta { 345 d.needsMeta = true 346 me.PrimaryExpression = &javascript.PrimaryExpression{ 347 IdentifierReference: jToken(d.prefix + "import"), 348 } 349 me.ImportMeta = false 350 } 351 } 352 return walk.Walk(t, d) 353 } 354 355 func (d *dependency) HandleImportConditional(ce *javascript.ConditionalExpression) { 356 if ce.True != nil && ce.False != nil { 357 if isConditionalExpression(ce.True) { 358 d.HandleImportConditional(ce.True.ConditionalExpression) 359 } 360 if isConditionalExpression(ce.False) { 361 d.HandleImportConditional(ce.False.ConditionalExpression) 362 } 363 } else if pe, ok := javascript.UnwrapConditional(ce).(*javascript.PrimaryExpression); ok && pe.Literal != nil && pe.Literal.Type == javascript.TokenStringLiteral { 364 durl, _ := javascript.Unquote(pe.Literal.Data) 365 iurl := d.RelTo(durl) 366 pe.Literal.Data = strconv.Quote(iurl) 367 if d.config != nil { 368 d.addImport(iurl) 369 d.dynamicRequirement = true 370 } 371 } 372 } 373 374 func (d *dependency) RelTo(url string) string { 375 if len(url) > 0 && url[0] == '/' { 376 return url 377 } 378 return path.Join(path.Dir(d.url), url) 379 } 380 381 func (d *dependency) setImportBinding(binding string, e *dependency, importedBinding string) { 382 d.imports[binding] = &importBinding{ 383 dependency: e, 384 binding: importedBinding, 385 } 386 } 387 388 func (d *dependency) setExportBinding(binding string, e *dependency, exportedBinding string) { 389 d.exports[binding] = &importBinding{ 390 dependency: e, 391 binding: exportedBinding, 392 } 393 } 394 395 func isConditionalExpression(ae *javascript.AssignmentExpression) bool { 396 return ae != nil && ae.ConditionalExpression != nil && !ae.Yield && !ae.Delegate && (ae.AssignmentOperator == javascript.AssignmentNone || ae.AssignmentOperator == javascript.AssignmentAssign) 397 } 398 399 func (d *dependency) processArrayBinding(binding *javascript.ArrayBindingPattern) { 400 for _, be := range binding.BindingElementList { 401 d.processBindingElement(be.SingleNameBinding, be.ArrayBindingPattern, be.ObjectBindingPattern) 402 } 403 if binding.BindingRestElement != nil { 404 d.processBindingElement(binding.BindingRestElement.SingleNameBinding, binding.BindingRestElement.ArrayBindingPattern, binding.BindingRestElement.ObjectBindingPattern) 405 } 406 } 407 408 func (d *dependency) processObjectBinding(binding *javascript.ObjectBindingPattern) { 409 for _, be := range binding.BindingPropertyList { 410 d.processBindingElement(be.BindingElement.SingleNameBinding, be.BindingElement.ArrayBindingPattern, be.BindingElement.ObjectBindingPattern) 411 } 412 if binding.BindingRestProperty != nil { 413 d.setExportBinding(binding.BindingRestProperty.Data, nil, binding.BindingRestProperty.Data) 414 } 415 } 416 417 func (d *dependency) processBindingElement(snb *javascript.Token, abp *javascript.ArrayBindingPattern, obp *javascript.ObjectBindingPattern) { 418 if snb != nil { 419 d.setExportBinding(snb.Data, nil, snb.Data) 420 } else if abp != nil { 421 d.processArrayBinding(abp) 422 } else if obp != nil { 423 d.processObjectBinding(obp) 424 } 425 } 426 427 func (d *dependency) resolveExport(binding string) *scope.Binding { 428 export, ok := d.exports[binding] 429 if !ok { 430 return nil 431 } 432 if export.dependency != nil { 433 return export.dependency.resolveExport(export.binding) 434 } 435 imp, ok := d.imports[export.binding] 436 if ok { 437 return imp.dependency.resolveExport(imp.binding) 438 } 439 sc, ok := d.scope.Bindings[export.binding] 440 if !ok || len(sc) == 0 { 441 return nil 442 } 443 return &sc[0] 444 } 445 446 func (d *dependency) resolveImports() error { 447 if d.done { 448 return nil 449 } 450 d.done = true 451 for _, r := range d.requires { 452 if err := r.resolveImports(); err != nil { 453 return err 454 } 455 } 456 for name, binding := range d.imports { 457 if binding.binding == "*" { 458 continue 459 } 460 b := binding.dependency.resolveExport(binding.binding) 461 if b == nil { 462 return fmt.Errorf("error resolving import %s (%s): %w", name, d.url, ErrInvalidExport) 463 } 464 for _, c := range d.scope.Bindings[name] { 465 c.Data = b.Data 466 } 467 } 468 return nil 469 } 470 471 func (d *dependency) processBindings(s *scope.Scope) { 472 for name, bindings := range s.Bindings { 473 if len(bindings) == 0 || bindings[0].BindingType == scope.BindingRef || bindings[0].BindingType == scope.BindingBare || bindings[0].BindingType == scope.BindingImport { 474 continue 475 } 476 for n := range bindings { 477 bindings[n].Data = d.prefix + name 478 } 479 } 480 for _, cs := range s.Scopes { 481 d.processBindings(cs) 482 } 483 } 484