Current section

26 Versions

Jump to

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…