Custom views field handler with relationship support, but without table
Ok, I found one way to do this: Instead of registering just one handler, you register one handler per entity type. To save time, we can reuse the stuff already registered by Entity API, with entity_views_data(). This means, of course, the module needs to add Entity API as a dependency.

/**
 * Implements hook_views_data_alter().
 */
function EXAMPLE_views_data_alter(&amp;$data) {

  foreach (entity_get_info() as $entity_type =&gt; $info) {
    if (!isset($data['views_entity_' . $entity_type]['rendered_entity'])) {
      // Only register for those entity types where Entity API already did its job.
      continue;
    }
    $data['views_entity_' . $entity_type]['EXAMPLE'] = array(
      'title' =&gt; t('Entity display plugin'),
      'help' =&gt; t('The @entity-type of the current relationship rendered using an entity display plugin.', array('@entity-type' =&gt; $info['label'])),
      'field' =&gt; array(
        'handler' =&gt; ExampleViewsFieldHandler::class,
        'type' =&gt; $entity_type,
        // The EntityFieldHandlerHelper treats the 'entity object' data
        // selector as special case for loading the base entity.
        // @todo Not sure if we really need this.
        'real field' =&gt; 'entity object',
      ),
    );
  }
}


And below is the field handler class, just a bit simplified.

Maybe it does a bit more than we really need here. E.g. we don't really need to create a render array for each row, we could work with html directly. But I think it is a good start.

class ExampleViewsFieldHandler extends \views_handler_field {

  function query() {
    // do nothing -- to override the parent query.
  }

  /**
   * Run before any fields are rendered.
   *
   * This gives the handlers some time to set up before any handler has
   * been rendered.
   *
   * @param object[] $rows
   *   An array of all objects returned from the query.
   */
  function pre_render(&amp;$rows) {
    /**
     * @var string $entityType
     * @var object[] $entities
     */
    list($entityType, $entities) = $this-&gt;getResultEntities($rows);
    // Build the entities.
    $builds = $this-&gt;buildMultiple($entityType, $entities);
    foreach ($rows as $rowIndex =&gt; $row) {
      if (isset($builds[$rowIndex])) {
        /** @noinspection PhpUndefinedFieldInspection */
        $row-&gt;example_field_builds[$this-&gt;position] = $builds[$rowIndex];
      }
      else {
        /** @noinspection PhpUndefinedFieldInspection */
        unset($row-&gt;example_field_builds[$this-&gt;position]);
      }
    }
  }

  /**
   * @param object[] $rows
   *
   * @return mixed[]
   *
   * @see EntityFieldHandlerHelper::pre_render()
   */
  protected function getResultEntities(array $rows) {
    $relationship = !empty($this-&gt;relationship) ? $this-&gt;relationship : NULL;
    $field_alias = isset($this-&gt;real_field) ? $this-&gt;real_field : NULL;
    // Some views query classes want/allow a third parameter specifying the field name.
    /** @noinspection PhpMethodParametersCountMismatchInspection */
    list($entityType, $entities) = $this-&gt;view-&gt;query-&gt;get_result_entities($rows, $relationship, $field_alias);
    return array($entityType, $entities);
  }

  /**
   * @param object $row
   *
   * @return string
   */
  function render($row) {
    /** @noinspection PhpUndefinedFieldInspection */
    return isset($row-&gt;example_field_builds[$this-&gt;position])
      ? drupal_render($row-&gt;example_field_builds[$this-&gt;position])
      : NULL;
  }

  /**
   * @param string $entityType
   * @param object[] $entities
   *
   * @return array[]
   *   A render array for each entity.
   */
  protected function buildMultiple($entityType, array $entities) {
    $builds = array();
    foreach ($entities as $rowIndex =&gt; $entity) {
      $builds[$rowIndex] = array(
        '#markup' =&gt; entity_label($entityType, $entity),
      );
    }
    return $builds;
  }
}