mitosis icon indicating copy to clipboard operation
mitosis copied to clipboard

Allow components to output multiple files per target

Open sbrow opened this issue 2 years ago • 3 comments

What?

Certain frameworks, (such as Angular) allow you to split your components into multiple files. It would be useful if mitosis could allow components to compile into more than one output file per target.

Why?

Although this would be a helpful feature for teams that prefer multi-file components over single-file components, there are other instances, such as Alpine.js where it can be down-right necessary. When you write an Alpine component that requires importing code, that code needs to rolled into to the js bundle. However, if the component's DOM is rendered by a static templating engine, (which is a common use for Alpine), then you won't be able to put the js and the html in the same file.

How?

The useMetadata hook seems like the ideal place to implement this feature. I think most components would be split by html/js/css, but not all. For example, Marko also supports split components where brower-only code resides in one file, and shared code goes in the other. I'm not sure about other frameworks, but I imagine that trying to add settings for each of them could get quite complicated, so I leave the implementation details up to the fine minds at BuilderIO 😄

Conclusion

I don't think this is a high priority issue, but as I mentioned in issue #11 I do think it's a necessary feature to make certain frameworks (i.e. Alpine.js) a viable target for Mitosis.

I love mitosis and I'm excited to see it grow!

sbrow avatar Oct 13 '22 03:10 sbrow

this is interesting because Mitosis preprocesses the component but it's up to the framework and other tools to optimize steps after. For example qwik, angular, even typescript is processing the files after. I would say the goal for mitosis isn't for an opinionated optimized output just a good enough step for devs to decide those. This will allow anyones pipeline to further optimize in other steps since each framework has their own opinion on what that means. If this is the only way alpine works then it's the only way. HTML output for example can use this too since it should be separate files

PatrickJS avatar Nov 02 '22 19:11 PatrickJS

I think this is fine until we find another/better approach

PatrickJS avatar Nov 02 '22 19:11 PatrickJS

If this is the only way alpine works then it's the only way. HTML output for example can use this too since it should be separate files

I can't tell whether you're for or against this idea, so let me elaboarate a little bit. Alpine.js components typically get rendered in raw html, without any pre-processing.

<!-- index.html -->
<html>
<head>
    <script defer src="https://unpkg.com/[email protected]/dist/cdn.min.js"></script>
</head>
<body>
  <style>
    .input {
      color: red;
    }
  </style>
  <div x-data="myComponent()">
    <input
      class="input"
      x-bind:value="name"
      x-on:change="name = $event.target.value"
    />

    Hello! I can run in React, Vue, Solid, or Liquid!
  </div>
  <script>
    document.addEventListener("alpine:init", () => {
      Alpine.data("myComponent", () => ({ name: "Steve" }));
    });
  </script>
</body>
</html>

This works fine with Mitosis the way that it is; however, once you start needing to import code from another module, things begin to get messy.

<!-- index.html -->
<html>
<head>
    <script defer src="https://unpkg.com/[email protected]/dist/cdn.min.js"></script>
</head>
<body>
  <style>
    .input {
      color: red;
    }
  </style>
  <div x-data="myComponent()">
    <input
      class="input"
      x-bind:value="name"
      x-on:change="name = $event.target.value"
    />

    Hello! I can run in React, Vue, Solid, or Liquid!
  </div>
  <script type="module">
    // Where is axios coming from?
    import axios from 'axios';
    
    document.addEventListener("alpine:init", () => {
      axios.get('/name').then({name} => {
        Alpine.data("myComponent", () => ({ name }));
      });
    });
  </script>
</body>
</html>

The best solution I've found is to write the js for the component in a separate file that is preprocessed by webpack/rollup. I can't think of any way to do this in mitosis without splitting the output into html & js.

However, in the case of Marko's split-components I do agree that the best place to do that optimization is outside the scope of mitosis. I think of this issue not as an optimization feature, but as a prerequisite for certain frameworks including Alpine.js and Elm (see #808).

sbrow avatar Nov 02 '22 20:11 sbrow