Current section
26 Versions
Jump to
Current section
26 Versions
Compare versions
17
files changed
+645
additions
-108
deletions
| @@ -3,6 +3,7 @@ | |
| 3 3 | import GlobalRegistry from "./global_registry.mjs"; |
| 4 4 | import LiveReload from "./live_reload.mjs"; |
| 5 5 | import Serializer from "./serializer.mjs"; |
| 6 | + import Utils from "./utils.mjs"; |
| 6 7 | |
| 7 8 | export default class Connection { |
| 8 9 | // 1 second |
| @@ -241,7 +242,7 @@ export default class Connection { | |
| 241 242 | {onSuccess, onError, onTimeout, timeout = $.REQUEST_TIMEOUT} = {}, |
| 242 243 | ) { |
| 243 244 | return new Promise((resolve, reject) => { |
| 244 | - const correlationId = crypto.randomUUID(); |
| 245 | + const correlationId = Utils.randomUUID(); |
| 245 246 | |
| 246 247 | const timerId = setTimeout(() => { |
| 247 248 | $.pendingRequests.delete(correlationId); |
| @@ -310,7 +310,7 @@ export default class Hologram { | |
| 310 310 | // Made public to make tests easier |
| 311 311 | static async loadNewPage(pagePath, html) { |
| 312 312 | await $.#savePageSnapshot(); |
| 313 | - $.#historyId = crypto.randomUUID(); |
| 313 | + $.#historyId = Utils.randomUUID(); |
| 314 314 | |
| 315 315 | window.requestAnimationFrame(() => { |
| 316 316 | Hologram.#patchPage(html); |
| @@ -681,7 +681,7 @@ export default class Hologram { | |
| 681 681 | |
| 682 682 | static #ensureDomNodeHasHologramId(eventNode) { |
| 683 683 | if (typeof eventNode.__hologramId__ === "undefined") { |
| 684 | - eventNode.__hologramId__ = crypto.randomUUID(); |
| 684 | + eventNode.__hologramId__ = Utils.randomUUID(); |
| 685 685 | } |
| 686 686 | } |
| 687 687 | |
| @@ -881,7 +881,7 @@ export default class Hologram { | |
| 881 881 | $.#restorePageSnapshot(pageSnapshot); |
| 882 882 | } |
| 883 883 | } else { |
| 884 | - $.#historyId = crypto.randomUUID(); |
| 884 | + $.#historyId = Utils.randomUUID(); |
| 885 885 | history.replaceState($.#historyId, null, window.location.pathname); |
| 886 886 | } |
| @@ -50,6 +50,29 @@ export default class Utils { | |
| 50 50 | } |
| 51 51 | } |
| 52 52 | |
| 53 | + static randomUUID() { |
| 54 | + // crypto.randomUUID() is only exposed in secure contexts (HTTPS or |
| 55 | + // localhost). When the app is served over plain HTTP from another hostname |
| 56 | + // (e.g. a LAN IP) it is unavailable, so fall back to generating an RFC 4122 |
| 57 | + // version 4 UUID from crypto.getRandomValues(), which is not gated on |
| 58 | + // secure contexts and is still cryptographically strong. |
| 59 | + if (typeof crypto.randomUUID === "function") { |
| 60 | + return crypto.randomUUID(); |
| 61 | + } |
| 62 | + |
| 63 | + const bytes = crypto.getRandomValues(new Uint8Array(16)); |
| 64 | + |
| 65 | + // Set the version (4) and variant (RFC 4122) bits. |
| 66 | + bytes[6] = (bytes[6] & 0x0f) | 0x40; |
| 67 | + bytes[8] = (bytes[8] & 0x3f) | 0x80; |
| 68 | + |
| 69 | + const hex = Array.from(bytes, (byte) => |
| 70 | + byte.toString(16).padStart(2, "0"), |
| 71 | + ).join(""); |
| 72 | + |
| 73 | + return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`; |
| 74 | + } |
| 75 | + |
| 53 76 | static randomUint32() { |
| 54 77 | return (Math.random() * 0x100000000) >>> 0; |
| 55 78 | } |
| @@ -9,7 +9,7 @@ | |
| 9 9 | {<<"UI">>,<<"https://hologram.page/ui">>}, |
| 10 10 | {<<"Website">>,<<"https://hologram.page">>}]}. |
| 11 11 | {<<"name">>,<<"hologram">>}. |
| 12 | - {<<"version">>,<<"0.10.0">>}. |
| 12 | + {<<"version">>,<<"0.10.1">>}. |
| 13 13 | {<<"description">>, |
| 14 14 | <<"Full stack isomorphic Elixir web framework that can be used on top of Phoenix.">>}. |
| 15 15 | {<<"elixir">>,<<"~> 1.15">>}. |
| @@ -166,11 +166,26 @@ defmodule Hologram.Compiler do | |
| 166 166 | @doc """ |
| 167 167 | Builds JavaScript code for the given Hologram page. |
| 168 168 | |
| 169 | - Benchmark: https://github.com/bartblast/hologram/blob/master/benchmarks/compiler/build_page_js_4/README.md |
| 169 | + Benchmark: https://github.com/bartblast/hologram/blob/master/benchmarks/elixir/compiler/build_page_js_6/README.md |
| 170 170 | """ |
| 171 | - @spec build_page_js(module, CallGraph.t(), PLT.t(), MapSet.t(mfa), T.file_path()) :: String.t() |
| 172 | - def build_page_js(page_module, call_graph, ir_plt, async_mfas, js_dir) do |
| 173 | - mfas = CallGraph.list_page_mfas(call_graph, page_module) |
| 171 | + @spec build_page_js( |
| 172 | + module, |
| 173 | + CallGraph.t(), |
| 174 | + PLT.t(), |
| 175 | + MapSet.t(mfa), |
| 176 | + %{module => CallGraph.server_callback_analysis()}, |
| 177 | + T.file_path() |
| 178 | + ) :: String.t() |
| 179 | + def build_page_js( |
| 180 | + page_module, |
| 181 | + call_graph, |
| 182 | + ir_plt, |
| 183 | + async_mfas, |
| 184 | + server_callback_analysis_by_templatable, |
| 185 | + js_dir |
| 186 | + ) do |
| 187 | + mfas = |
| 188 | + CallGraph.list_page_mfas(call_graph, page_module, server_callback_analysis_by_templatable) |
| 174 189 | |
| 175 190 | %{imports: imports, bindings: bindings} = aggregate_js_imports(mfas) |
| 176 191 | |
| @@ -362,18 +377,30 @@ defmodule Hologram.Compiler do | |
| 362 377 | @doc """ |
| 363 378 | Creates page bundle entry file. |
| 364 379 | |
| 365 | - Benchmark: https://github.com/bartblast/hologram/blob/master/benchmarks/compiler/create_page_entry_files_4/README.md |
| 380 | + Benchmark: https://github.com/bartblast/hologram/blob/master/benchmarks/elixir/compiler/create_page_entry_files_5/README.md |
| 366 381 | """ |
| 367 382 | @spec create_page_entry_files(list(module), CallGraph.t(), PLT.t(), MapSet.t(mfa), T.opts()) :: |
| 368 383 | list({module, T.file_path()}) |
| 369 384 | def create_page_entry_files(page_modules, call_graph, ir_plt, async_mfas, opts) do |
| 385 | + graph = CallGraph.get_graph(call_graph) |
| 386 | + templatables = page_modules ++ Reflection.list_components() |
| 387 | + |
| 388 | + server_callback_analysis_by_templatable = |
| 389 | + CallGraph.server_callback_analysis_by_templatable(graph, templatables) |
| 390 | + |
| 370 391 | page_modules |
| 371 392 | |> TaskUtils.async_many(fn page_module -> |
| 372 393 | entry_name = Reflection.module_name(page_module) |
| 373 394 | |
| 374 395 | entry_file_path = |
| 375 396 | page_module |
| 376 | - |> build_page_js(call_graph, ir_plt, async_mfas, opts[:js_dir]) |
| 397 | + |> build_page_js( |
| 398 | + call_graph, |
| 399 | + ir_plt, |
| 400 | + async_mfas, |
| 401 | + server_callback_analysis_by_templatable, |
| 402 | + opts[:js_dir] |
| 403 | + ) |
| 377 404 | |> create_entry_file(entry_name, opts[:tmp_dir]) |
| 378 405 | |
| 379 406 | {page_module, entry_file_path} |
| @@ -384,7 +411,7 @@ defmodule Hologram.Compiler do | |
| 384 411 | @doc """ |
| 385 412 | Creates runtime bundle entry file. |
| 386 413 | |
| 387 | - Benchmark: https://github.com/bartblast/hologram/blob/master/benchmarks/compiler/create_runtime_entry_file_3/README.md |
| 414 | + Benchmark: https://github.com/bartblast/hologram/blob/master/benchmarks/elixir/compiler/create_runtime_entry_file_4/README.md |
| 388 415 | """ |
| 389 416 | @spec create_runtime_entry_file(list(mfa), PLT.t(), MapSet.t(mfa), T.opts()) :: T.file_path() |
| 390 417 | def create_runtime_entry_file(runtime_mfas, ir_plt, async_mfas, opts) do |
| @@ -575,6 +602,8 @@ defmodule Hologram.Compiler do | |
| 575 602 | |
| 576 603 | @doc """ |
| 577 604 | Keeps only those IR expressions that are function definitions of the given reachable MFAs. |
| 605 | + For protocol modules, additionally drops the consolidated impl_for/1 and struct_impl_for/1 |
| 606 | + clauses that return implementations not included in the given reachable MFAs. |
| 578 607 | """ |
| 579 608 | @spec prune_module_def(IR.ModuleDefinition.t(), list(mfa)) :: IR.ModuleDefinition.t() |
| 580 609 | def prune_module_def(module_def_ir, reachable_mfas) do |
| @@ -586,13 +615,15 @@ defmodule Hologram.Compiler do | |
| 586 615 | |> MapSet.new() |
| 587 616 | |
| 588 617 | function_defs = |
| 589 | - Enum.filter(module_def_ir.body.expressions, fn |
| 618 | + module_def_ir.body.expressions |
| 619 | + |> Enum.filter(fn |
| 590 620 | %IR.FunctionDefinition{name: function, arity: arity} -> |
| 591 621 | MapSet.member?(module_reachable_mfas, {module, function, arity}) |
| 592 622 | |
| 593 623 | _fallback -> |
| 594 624 | false |
| 595 625 | end) |
| 626 | + |> maybe_prune_protocol_dispatcher_function_defs(module, reachable_mfas) |
| 596 627 | |
| 597 628 | %IR.ModuleDefinition{ |
| 598 629 | module: module_def_ir.module, |
| @@ -665,6 +696,32 @@ defmodule Hologram.Compiler do | |
| 665 696 | |> CryptographicUtils.digest(:sha256, :binary) |
| 666 697 | end |
| 667 698 | |
| 699 | + defp included_protocol_implementations(reachable_mfas, protocol) do |
| 700 | + reachable_mfas |
| 701 | + |> Enum.map(fn {module, _function, _arity} -> module end) |
| 702 | + |> Enum.uniq() |
| 703 | + |> Enum.filter(&(Reflection.protocol_implementation(&1) == protocol)) |
| 704 | + |> MapSet.new() |
| 705 | + end |
| 706 | + |
| 707 | + defp keep_protocol_dispatcher_function_def?( |
| 708 | + %IR.FunctionDefinition{name: function, arity: 1, clause: clause}, |
| 709 | + protocol, |
| 710 | + included_impls |
| 711 | + ) |
| 712 | + when function in [:impl_for, :struct_impl_for] do |
| 713 | + case clause do |
| 714 | + %IR.FunctionClause{body: %IR.Block{expressions: [%IR.AtomType{value: value}]}} -> |
| 715 | + Reflection.protocol_implementation(value) != protocol or |
| 716 | + MapSet.member?(included_impls, value) |
| 717 | + |
| 718 | + _clause -> |
| 719 | + true |
| 720 | + end |
| 721 | + end |
| 722 | + |
| 723 | + defp keep_protocol_dispatcher_function_def?(_function_def, _protocol, _included_impls), do: true |
| 724 | + |
| 668 725 | defp maybe_ensure_bundle_within_size_limit!(entry_name, bundle_path) do |
| 669 726 | max_bundle_size = Application.get_env(:hologram, :max_bundle_size) |
| 670 727 | |
| @@ -686,6 +743,22 @@ defmodule Hologram.Compiler do | |
| 686 743 | end |
| 687 744 | end |
| 688 745 | |
| 746 | + # Consolidated protocol dispatchers list every loaded implementation. Keep only |
| 747 | + # clauses for implementations that ship in the same bundle, so dispatch on other |
| 748 | + # types falls through to the catch-all clause and raises Protocol.UndefinedError. |
| 749 | + defp maybe_prune_protocol_dispatcher_function_defs(function_defs, module, reachable_mfas) do |
| 750 | + if Reflection.protocol?(module) do |
| 751 | + included_impls = included_protocol_implementations(reachable_mfas, module) |
| 752 | + |
| 753 | + Enum.filter( |
| 754 | + function_defs, |
| 755 | + &keep_protocol_dispatcher_function_def?(&1, module, included_impls) |
| 756 | + ) |
| 757 | + else |
| 758 | + function_defs |
| 759 | + end |
| 760 | + end |
| 761 | + |
| 689 762 | defp rebuild_ir_plt_entry!(ir_plt, module) do |
| 690 763 | beam_path = :code.which(module) |
| 691 764 | PLT.put(ir_plt, module, IR.for_module(module, beam_path)) |
| @@ -720,10 +793,10 @@ defmodule Hologram.Compiler do | |
| 720 793 | |> filter_elixir_mfas() |
| 721 794 | |> group_mfas_by_module() |
| 722 795 | |> Enum.sort() |
| 723 | - |> TaskUtils.async_many(fn {module, module_mfas} -> |
| 796 | + |> TaskUtils.async_many(fn {module, _module_mfas} -> |
| 724 797 | ir_plt |
| 725 798 | |> PLT.get!(module) |
| 726 | - |> prune_module_def(module_mfas) |
| 799 | + |> prune_module_def(mfas) |
| 727 800 | |> Encoder.encode_ir(%Context{module: module, async_mfas: async_mfas}) |
| 728 801 | end) |
| 729 802 | |> Task.await_many(:infinity) |
Loading more files…