Rendered Webform has no Javascript attached I've created a simple filter that allows webforms to be rendered anywhere in a node with a tag like this: [webform:machine_name]

The code to render the form is as follows:

$webform = \Drupal::entityTypeManager()-&gt;getStorage('webform')-&gt;load($machineName);
$viewBuilder = \Drupal::service('entity_type.manager')-&gt;getViewBuilder('webform');
$formBuild = $viewBuilder-&gt;view($webform);
return render($formBuild);


It works insofar as the form is displayed, client-side validation is working.

BUT none of the webform-specific JS is attached (e.g. input masks don't work). I assume this is because Drupal doesn't know it needs to attach the libraries.

How do I attach those libraries? Preferably in a method that can be invoked from the code that renders the filter, as if it's in a separate place things would start to get more complicated.

EDIT:

Following 4k4's feedback, here's the code that I'm now using. It works as above, without the libraries being attached:

foreach ($renderArrays as $tag =&gt; $renderArray) {
  $renderContext = new RenderContext();         
  $renderedOutput = $this-&gt;renderer-&gt;executeInRenderContext($renderContext, function () use (&amp;$renderArray) {
    return $this-&gt;renderer-&gt;render($renderArray);
  });
  $metaData = BubbleableMetadata::createFromRenderArray($renderArray);
  if (!$renderContext-&gt;isEmpty()) {
    $metaData = $metaData-&gt;merge($renderContext-&gt;pop());
  }
  $result = $result-&gt;merge($metaData);
  $text = str_replace($tag, $renderedOutput, $text);
}
$result-&gt;setProcessedText($text);


For completeness, $renderArrays contains:

['[webform:machine_name]' =&gt; [
  '#type' =&gt; 'webform',
  '#webform' =&gt; 'machine_name',
]]
Now you return bare HTML without any attached meta data for caching, libraries and so on.

Also Webforms provides a render element, so this is all you need to build a webform:

$build = [
  '#type' =&gt; 'webform',
  '#webform' =&gt; $machineName,
];


The important part though is that the filter you put the token in handles the attached metadata correctly. It needs to do it similar to Entity Embed:

    $result = new FilterProcessResult($text);
    ...
    // We need to render the embedded entity:
    // - without replacing placeholders, so that the placeholders are
    //   only replaced at the last possible moment. Hence we cannot use
    //   either renderPlain() or renderRoot(), so we must use render().
    // - without bubbling beyond this filter, because filters must
    //   ensure that the bubbleable metadata for the changes they make
    //   when filtering text makes it onto the FilterProcessResult
    //   object that they return ($result). To prevent that bubbling, we
    //   must wrap the call to render() in a render context.
    $entity_output = $this-&gt;renderer-&gt;executeInRenderContext(new RenderContext(), function () use (&amp;$build) {
      return $this-&gt;renderer-&gt;render($build);
    });
    $result = $result-&gt;merge(BubbleableMetadata::createFromRenderArray($build));


https://cgit.drupalcode.org/entity_embed/tree/src/Plugin/Filter/EntityEmbedFilter.php

Edit: Improved bubbling of metadata:

    $render_context = new RenderContext();
    $entity_output = $this-&gt;renderer-&gt;executeInRenderContext($render_context, function () use (&amp;$build) {
      return $this-&gt;renderer-&gt;render($build);
    });
    $meta_data = BubbleableMetadata::createFromRenderArray($build);
    if (!$render_context-&gt;isEmpty()) {
      $meta_data = $meta_data-&gt;merge($render_context-&gt;pop());
    }
    $result = $result-&gt;merge($meta_data);




If you are looking for an easier solution you could for example add the webform to $variables in preprocess to render it in a node template. Then the attached libraries bubble up in the right place automatically.