11defmodule Phoenix.LiveView.TagCompiler.Compiler do
22 @ moduledoc false
33
4+ alias Phoenix.LiveView.TagCompiler.ParseResult
45 alias Phoenix.LiveView.TagCompiler.Tokenizer.ParseError
56
67 @ doc """
@@ -15,7 +16,7 @@ defmodule Phoenix.LiveView.TagCompiler.Compiler do
1516 text and expression parts and properly invoking the engine
1617 with the correct code for features like components and slots.
1718 """
18- def compile ( nodes , opts ) do
19+ def compile ( % ParseResult { nodes: nodes , directives: directives } , opts ) do
1920 { engine , opts } = Keyword . pop ( opts , :engine , Phoenix.LiveView.Engine )
2021 tag_handler = Keyword . fetch! ( opts , :tag_handler )
2122
@@ -26,8 +27,11 @@ defmodule Phoenix.LiveView.TagCompiler.Compiler do
2627 caller: Keyword . fetch! ( opts , :caller ) ,
2728 source: Keyword . fetch! ( opts , :source ) ,
2829 tag_handler: tag_handler ,
29- # slots is the only key that is updated when traversing nodes
30- slots: [ ]
30+ root_tag_attribute: Application . get_env ( :phoenix_live_view , :root_tag_attribute , false ) ,
31+ root_tag_attributes: Keyword . get_values ( directives , :root_tag_attribute ) ,
32+ # The following keys are updated when traversing nodes
33+ slots: [ ] ,
34+ local_root?: true
3135 }
3236
3337 # Live components require a single, static root tag.
@@ -328,7 +332,7 @@ defmodule Phoenix.LiveView.TagCompiler.Compiler do
328332
329333 with_special_attrs ( attrs , meta , substate , state , fn attrs , meta , substate , state ->
330334 substate = handle_tag_and_attrs ( name , attrs , ">" , to_location ( meta ) , substate , state )
331- { _child_state , substate } = handle_node ( children , substate , state )
335+ { _child_state , substate } = handle_node ( children , substate , % { state | local_root?: false } )
332336 substate = state . engine . handle_text ( substate , [ to_location ( close_meta ) ] , "</#{ name } >" )
333337 { state , substate }
334338 end )
@@ -353,7 +357,7 @@ defmodule Phoenix.LiveView.TagCompiler.Compiler do
353357 meta ,
354358 close_meta ,
355359 substate ,
356- state
360+ % { state | local_root?: true }
357361 )
358362
359363 ast =
@@ -383,7 +387,10 @@ defmodule Phoenix.LiveView.TagCompiler.Compiler do
383387
384388 with_special_attrs ( attrs , meta , substate , state , fn attrs , meta , substate , state ->
385389 { assigns , attr_info , slot_info } =
386- build_component_assigns ( ref , attrs , children , meta , close_meta , substate , state )
390+ build_component_assigns ( ref , attrs , children , meta , close_meta , substate , % {
391+ state
392+ | local_root?: true
393+ } )
387394
388395 store_component_call ( { mod , fun } , attr_info , slot_info , line , state )
389396 call_meta = [ line: line , column: column ]
@@ -420,7 +427,10 @@ defmodule Phoenix.LiveView.TagCompiler.Compiler do
420427 with_special_attrs ( attrs , meta , substate , state , fn attrs , meta , substate , state ->
421428 # Process children in a new nesting
422429 { assigns , attr_info , slot_info } =
423- build_component_assigns ( ref , attrs , children , meta , close_meta , substate , state )
430+ build_component_assigns ( ref , attrs , children , meta , close_meta , substate , % {
431+ state
432+ | local_root?: true
433+ } )
424434
425435 store_component_call ( { mod , fun } , attr_info , slot_info , line , state )
426436 call_meta = [ line: line , column: column + mod_size ]
@@ -479,17 +489,41 @@ defmodule Phoenix.LiveView.TagCompiler.Compiler do
479489
480490 defp handle_tag_and_attrs ( name , attrs , suffix , meta , substate , state ) do
481491 text =
482- if Application . get_env ( :phoenix_live_view , :debug_attributes , false ) do
483- "<#{ name } data-phx-loc=\" #{ meta [ :line ] } \" "
484- else
485- "<#{ name } "
486- end
492+ "<#{ name } "
493+ |> maybe_add_phx_loc ( meta )
494+ |> maybe_add_root_tag_attributes ( state , meta )
487495
488496 substate = state . engine . handle_text ( substate , meta , text )
489497 substate = handle_tag_attrs ( meta , attrs , substate , state )
490498 state . engine . handle_text ( substate , meta , suffix )
491499 end
492500
501+ defp maybe_add_phx_loc ( text , meta ) do
502+ if Application . get_env ( :phoenix_live_view , :debug_attributes , false ) do
503+ "#{ text } data-phx-loc=\" #{ meta [ :line ] } \" "
504+ else
505+ text
506+ end
507+ end
508+
509+ defp maybe_add_root_tag_attributes ( text , % { local_root?: true } = state , _meta ) do
510+ case state do
511+ % { root_tag_attribute: root_tag_attribute } when is_binary ( root_tag_attribute ) ->
512+ attrs =
513+ [ { root_tag_attribute , true } | state . root_tag_attributes ]
514+ |> Phoenix.HTML . attributes_escape ( )
515+ |> Phoenix.HTML . safe_to_string ( )
516+
517+ # Phoenix.HTML.attributes_escape/1 adds a leading space automatically
518+ "#{ text } #{ attrs } "
519+
520+ % { root_tag_attribute: _ } ->
521+ text
522+ end
523+ end
524+
525+ defp maybe_add_root_tag_attributes ( text , _state , _meta ) , do: text
526+
493527 defp handle_tag_attrs ( meta , attrs , substate , state ) do
494528 Enum . reduce ( attrs , substate , fn
495529 { :root , { :expr , _ , _ } = expr , _attr_meta } , substate ->
0 commit comments