Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ability to have seperate html and css files when building a web component #1022

Closed
joelkesler opened this issue Jun 11, 2020 · 5 comments
Closed

Comments

@joelkesler
Copy link

joelkesler commented Jun 11, 2020

Description

When working on more complex components, it is nice to be able to have seperate files for logic (js/ts), structure (html), and styling (css/scss/etc).

Angular.io has a good approach to this, in that you are able to write an all-in-one-file component, or split out the html and css.

Reasoning

The ability to split out html and css is helpful for beginner/intermediate coders, who may find javascript/typescript overwhelming. It is also helpful for people writing more advanced components, who find the one-file approach becomes unwieldy.

Examples

Below is an example of using multiple files to build the component:

@Component({
  selector: 'my-great-component',
  templateUrl: './my-great-component.html',
  styleUrls:  ['./my-great-component.css']
})
export class QuestSummaryComponent { 
// logic goes here
}

Folder structure could be like so:

my-great-component
├─ my-great-component.ts 
├─ my-great-component.html
└─ my-great-component.css

Acceptance criteria

Allow people writing web components with LitElement to split out their html and css from the javascript/typescript file.

@justinfagnani
Copy link
Contributor

The only way we could support this is the HTML and CSS didn't have expressions. I'm not sure who that would be useful for.

@bengfarrell
Copy link

bengfarrell commented Jun 11, 2020

I have some observations that might help on this. Basically, this is already possible, with a couple caveats.

Major caveat is that CSS and HTML files aren't importable modules (yet). So without some sort of front end tooling to turn those into JS/TS, a pure HTML or CSS file cannot be imported.

CSS is easy, though. I prefer not going through front-end tooling and not using TS, so I create a file in my component folder called mycomponent.css.js.

An example file can look like:

import {css} from "lit-element";

export const style = css`
    myrule {
        width: 100%;
    }`

This can then be imported right into your component like so:

import {style} from './mycomponent.css.js';

export default class MyComponent extends LitElement {
    static get styles() {
        return [style];
    }

If you DO use front end tooling, you can create a plugin script in Babel or Webpack, or whatever you're using to wrap up an existing CSS file as the above JS.

HTML is different though. Justin just chimed in with the basic problem as I was writing this. I've separated out my HTML as you're suggesting, just to see if I like it. And it's OK-ish. As Justin says, you'll likely want expressions in your HTML rather than just basic markup.

I've done this in a mycomponent.html.js by exporting a function that accepts the component scope:

export const template = function(scope) { return html`
<div>${scope.somevariable}</div>
`; }

It feels a bit weird, but I guess I like the separation (for now).

I can then use it in LitElement like so:

render() { return template(this); }

Hope that helps, if you really want to explore this

@justinfagnani
Copy link
Contributor

Having templates in .html files would require both some way to import HTML, like HTML Modules, and a HTML-based template which is tracked in lit/lit#867

For CSS, we're waiting on CSS Modules, but until then you can use this rollup plugin

And as @bengfarrell mentions, since all this is just JS right now you can totally split templates and inlined CSS into as many files as you want.

Since this is tracked in other places, I'll close this issue here. Hope the pointers help.

@mdownes
Copy link

mdownes commented Aug 8, 2023

If you want to import lit-html into your lit-element from an external .html file, you can use this Rollup Plugin.
Details can be found here : Separating Lit-Element and lit-html templates

@freshp86
Copy link

freshp86 commented Nov 19, 2023

I've done this in a mycomponent.html.js by exporting a function that accepts the component scope:

export const template = function(scope) { return html`
<div>${scope.somevariable}</div>
`; }

It feels a bit weird, but I guess I like the separation (for now).

I can then use it in LitElement like so:

render() { return template(this); }

Hope that helps, if you really want to explore this

@bengfarrell @justinfagnani I think I have found an improvement to the approach above to have the HTML contents in a separate file, which while not perfect, seems fairly satisfactory (it has worked for cases I've tried so far, but have not exhaustively tried complex cases). Here is how it would look in TypeScript (with explanations in comments)

// my_element.html.ts file
import {html} from 'lit/lit.min.js'; // Or from wherever you import Lit from.

// Import the `MyElement` type (leveraging `import type` feature for clarity).
// This import is stripped by TS compiler, and therefore there are no runtime circular dependencies.
import type {MyElement} from './my_element.js'; 

// Use the "this: ..." TS annotation to be able to use `this` within the function below
// which makes it look as if getHtml was actually part of the `MyElement` class.
export function getHtml(this: MyElement) {
  return html`
<div>${this.someText_}</div>
<input type="file" @change=${this.onChange_} />
`;

The file above can be easily auto-generated by a build step from a my_element.html file.

Then here is how this can be called from my_element.ts which holds the element's definition.

// my_element.ts file
import {LitElement} from 'lit/lit.min.js'; // Or from wherever you import Lit from.

import {getCss} from './my_element.css.js'; // Can be generated from `my_element.css`
import {getHtml} from './my_element.html.js'; // Can be generated from `my_element.html`

export class MyElement extends LitElement {
  ...
  static override get styles() {
    return getCss();
  }

  override render() {
    return getHtml.bind(this)();
  }

  // Use `protected` instead of `private` so that `getHtml()` satisfies TS visibility checks (error TS2341).
  // This is not ideal, but better than making it public.
  protected someText_: string;
  protected onChange_() {...}
}

The above allows declaring a web component in 3 separate .css, .html, .ts files, separating such code as much as possible.

(The protected vs private workaround could be eliminated if/when TypeScript compiler supports "friend classes/functions" (microsoft/TypeScript#7692) or supports suppressing specific errors within a file (microsoft/TypeScript#19139)).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants