Skip to content

Latest commit

 

History

History
1361 lines (994 loc) · 26.2 KB

File metadata and controls

1361 lines (994 loc) · 26.2 KB
theme highlighter lineNumbers info drawings transition title layout fonts
apple-basic
shiki
false
## Slidev Starter Template Presentation slides for developers. Learn more at [Sli.dev](https://sli.dev)
persist
slide-left
Getting Started with <template> Tag Components
intro
sans mono
Inter
JetBrains Mono

Getting Started with <template> Tag Components

By Ignace Maes

EmberConf 2023 - Portland, OR


layout: speaker image: ./images/me.jpeg

I'm Ignace Maes

Senior Full-Stack Engineer at OTA Insight
From Ghent, Belgium

@IgnaceMaes
@Ignace_Maes
www.ignacemaes.com


layout: image image: ./images/morpheus.jpeg

What if I told you






there's a new component format in Ember you can use today?

<style> .slidev-layout { position: absolute; height: 100%; width: 100%; } .slidev-layout h1 { font-size: 3rem; line-height: 1; font-weight: 700; text-shadow: 2px 2px #000000; } </style>

layout: section

Ember's next Edition


layout: four-sections

Ember's next Edition

Pillars of Polaris

::topleft::

  • Embroider build system
  • First-class TypeScript support
  • New Router
  • Reactivity
  • <template> tag components

::topright::

octane


Ember's next Edition

Ember 5.0 release post


layout: statement

Should you start using <template> tag components today?*


* We'll get back to this at the end


layout: section

Why a new component format?


Why a new component format?

Template imports

  • Currently Ember uses global string-based resolving
  • This has issues:
    • Naming conflicts
    • No good way to introduce locally-scoped code
    • Wider ecosystem tools don't work out of the box as JS context is assumed
    • Testing format differs from app code
  • Goal: work using references instead
  • Extra benefits
    • Unlocks code splitting
    • Flexible layout for file structure

Problem: How should imports be done in templates?


layout: four-sections

::topleft::

Live example

Text: Hello EmberConf 2023!

::topright::

<CopyToClipboard @text={{"Hello EmberConf 2023!"}} />

application.hbs

<button {{on 'click' this.copyToClipboard}}>
  {{if this.isCopied 'Copied!' 'Click to copy'}}
  <Icon @name={{'clipboard'}} />
</button>

copy-to-clipboard.hbs

import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';

export default class CopyToClipboard extends Component {
  @tracked isCopied = false;

  copyToClipboard = async () => {
    await navigator.clipboard.writeText(this.args.text);
    this.isCopied = true;
  }
}

copy-to-clipboard.js


layout: four-sections

::topleft::

#1 Imports only via frontmatter

---
import Icon from 'example-app/components/icon';
import { on } from '@ember/modifier';
---
<button {{on 'click' this.copyToClipboard}}>
  {{if this.isCopied 'Copied!' 'Click to copy'}}
  <Icon @name={{'clipboard'}} />
</button>

copy-to-clipboard.hbs

import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';

export default class CopyToClipboard extends Component {
  @tracked isCopied = false;

  copyToClipboard = async () => {
    await navigator.clipboard.writeText(this.args.text);
    this.isCopied = true;
  }
}

copy-to-clipboard.js

::topright::

#2 Single File Component (Vue/Svelte type)

<script>
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import Icon from 'example-app/components/icon';
import { on } from '@ember/modifier';

export default class CopyToClipboard extends Component {
  @tracked isCopied = false;

  copyToClipboard = async () => {
    await navigator.clipboard.writeText(this.args.text);
    this.isCopied = true;
  }
}
</script>

<template>
  <button {{on 'click' this.copyToClipboard}}>
    {{if this.isCopied 'Copied!' 'Click to copy'}}
    <Icon @name={{'clipboard'}} />
  </button>
</template>

copy-to-clipboard.glimmer

<style> .slidev-layout p { margin-top: 0; } </style>

layout: four-sections

::topleft::

#3 Template literals

import Component, { hbs } from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import Icon from 'example-app/components/icon';
import { on } from '@ember/modifier';

export default class CopyToClipboard extends Component {
  @tracked isCopied = false;

  copyToClipboard = async () => {
    await navigator.clipboard.writeText(this.args.text);
    this.isCopied = true;
  }

  static template = hbs`
    <button {{on 'click' this.copyToClipboard}}>
      {{if this.isCopied 'Copied!' 'Click to copy'}}
      <Icon @name={{'clipboard'}} />
    </button>
  `
}

copy-to-clipboard.js

::topright::

#4 Template tag component

import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import Icon from 'example-app/components/icon';
import { on } from '@ember/modifier';

export default class CopyToClipboard extends Component {
  @tracked isCopied = false;

  copyToClipboard = async () => {
    await navigator.clipboard.writeText(this.args.text);
    this.isCopied = true;
  }

  <template>
    <button {{on 'click' this.copyToClipboard}}>
      {{if this.isCopied 'Copied!' 'Click to copy'}}
      <Icon @name={{'clipboard'}} />
    </button>
  </template>
}

copy-to-clipboard.gjs


layout: four-sections

::topleft::

Why a new component format?

The path forward

  • All four solutions solve the template import problem
  • Different trade-offs to be made in
    • Semantics
    • Learning
    • Tooling
    • Testing
  • <template> tag components came out as best overall
  • Since RFC #779 the accepted next-gen format

::topright::

blog

See Ember Template Imports blog series by Chris Krycho

layout: section

Semantics of template tag components


Semantics of template tag components

How does it compare to the current format?

  • Single file for both template and JS/TS
    • .gjs and .gts file extensions
    • Short for Glimmer JS and Glimmer TS
  • Imports required for
    • Components
    • Helpers
    • Modifiers
  • Wrapping glimmer template with the <template> tag

layout: four-sections

Semantics of template tag components

Template-only components

::topleft::

Before

Hey <Icon @name={{"waving-hand"}} />

greeting.hbs

::topright::

After

import Icon from 'example-app/components/icon';

<template>
  Hey <Icon @name={{"waving-hand"}} />
</template>

greeting.gjs


layout: four-sections

Semantics of template tag components

Class-based components

::topleft::

Before

<button {{on 'click' this.copyToClipboard}}>
  {{if this.isCopied 'Copied!' 'Click to copy'}}
  <Icon @name={{'clipboard'}} />
</button>

copy-to-clipboard.hbs

import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';

export default class CopyToClipboard extends Component {
  @tracked isCopied = false;

  copyToClipboard = async () => {
    await navigator.clipboard.writeText(this.args.text);
    this.isCopied = true;
  }
}

copy-to-clipboard.js

::topright::

After

import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import Icon from 'example-app/components/icon';
import { on } from '@ember/modifier';

export default class CopyToClipboard extends Component {
  @tracked isCopied = false;

  copyToClipboard = async () => {
    await navigator.clipboard.writeText(this.args.text);
    this.isCopied = true;
  }

  <template>
    <button {{on 'click' this.copyToClipboard}}>
      {{if this.isCopied 'Copied!' 'Click to copy'}}
      <Icon @name={{'clipboard'}} />
    </button>
  </template>
}

copy-to-clipboard.gjs

<style> .slidev-layout h1 { margin-bottom: 0; } </style>

layout: statement

But wait, there's more!


layout: four-sections

Semantics of template tag components

Direct access to imported values

const FEATURE_WORLD = 'some-feature-flag-key';

utils/feature-flags.js

::topleft::

Before

Hello
{{#if (hasFeature this.FEATURE_WORLD)}}
  World
{{/if}}

hello-world.hbs

import { FEATURE_WORLD } from 'app/utils/feature-flags';

export default class HelloWorld extends Component {
  FEATURE_WORLD = FEATURE_WORLD;
}

hello-world.js

::topright::

After

import hasFeature from 'app/helpers/has-feature';
import { FEATURE_WORLD } from 'app/utils/feature-flags';

<template>
  Hello
  {{#if (hasFeature FEATURE_WORLD)}}
    World
  {{/if}}
</template>

hello-world.gjs


Semantics of template tag components

Local helpers, constants, and modifiers

const value = 2;

const square = (number) => {
  return number * number;
};

<template>
  The square of {{value}} equals {{square value}}
</template>

square.gjs



The square of 2 equals 4


Semantics of template tag components

Also locally defined components!

const MyListItem = <template>
  <div class="p-4 rounded text-white bg-blue-700">
    {{yield}}
  </div>
</template>;

const MyList = <template>
  <div class="p-4 text-bold">List of things</div>
  {{#each @items as |item|}}
    <MyListItem>
      {{item.number}} - {{item.value}}
    </MyListItem>
  {{/each}}
</template>;

export default MyList;

layout: four-sections

Semantics of template tag components

Testing

::topleft::

Before

import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { hbs } from 'ember-cli-htmlbars';

module('copy-to-clipboard', function (hooks) {
  setupRenderingTest(hooks);

  test('renders', async function (assert) {
    this.text = 'Hello EmberConf 2023!';
    await render(hbs`
      <CopyToClipboard @text={{this.text}} />
    `);

    assert.dom('[data-test-copy]').exists();
  });
});

::topright::

After

import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import CopyToClipboard from 'components/copy-to-clippy';

module('copy-to-clipboard', function (hooks) {
  setupRenderingTest(hooks);

  test('renders', async function (assert) {
    const text = 'Hello EmberConf 2023!';
    await render(
      <template>
        <CopyToClipboard @text={{text}} />
      </template>
    );

    assert.dom('[data-test-copy]').exists();
  });
});

layout: statement

What about styling?


Semantics of template tag components

Styling

  • Template tag format is not opinionated about styling
  • Everything "Just Works"
    • Separate files for (S)CSS
    • Utility class frameworks (e.g. TailwindCSS)
    • CSS modules
  • Interesting addon developments
    • glimmer-scoped-css, embroider-css-modules, ember-scoped-css, ...

<template>
  <style>
    .danger { color: red; }
  </style>
  <span class="danger">Watch out!</span>
</template>

layout: section

Using template tag components today


layout: four-sections

::topleft::

Using template tag components today

Writing template tags

ember-template-imports

Provides the build tooling required to support Ember's next-gen component authoring format

$ pnpm add --save-dev ember-template-imports

Allows defining .gjs and .gts component files


Compatibility

  • Ember.js v3.27 or above
  • Ember CLI v2.13 or above
  • ember-cli-htmlbars 6.0 or above
  • Node.js v12 or above

::topright::

See ember-template-imports on GitHub

Using template tag components today

Underlying implementation will change

  • ember-template-imports is an exploration addon to use template tags today
  • Content-tag spec for generic language embedding in JS/TS
    • Enables future design extensions, e.g. <gql> for GraphQL
    • Framework agnostic: other tools could reuse this format
  • New content-tag package
    • Preprocessor for rewriting to valid JS
    • Written in Rust on top of Speedy Web Compiler (SWC)



No code impact for end consumer using template tag components!

Using template tag components today

Linting

eslint-plugin-ember

An ESLint plugin that provides a set of rules for Ember applications based on commonly known good practices.

$ pnpm add --save-dev eslint-plugin-ember@^11.6.0

ember-template-lint

ember-template-lint is a library that will lint your handlebars template and return error results.

$ pnpm add --save-dev ember-template-lint@^5.8.0

layout: four-sections

Using template tag components today

Code formatting with Prettier

prettier-plugin-ember-template-tag

A Prettier plugin for formatting Ember template tags in both .gjs and .gts files

Install

$ pnpm add --save-dev prettier-plugin-ember-template-tag

::topleft::

Configure

module.exports = {
  plugins: ['prettier-plugin-ember-template-tag'],
  overrides: [
    {
      files: '*.{js,ts,gjs,gts}',
    },
  ],
};

.prettierrc.js

::topright::

VS Code

{
  "[glimmer-js]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode",
    "editor.formatOnSave": false
  },
  "[glimmer-ts]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode",
    "editor.formatOnSave": false
  }, // and other configs ...
}

settings.json


layout: four-sections

Using template tag components today

Type checking in templates using Glint

@glint/environment-ember-template-imports

This package contains the information necessary for glint to typecheck an ember-template-imports project

Install

$ pnpm add --save-dev @glint/environment-ember-template-imports

Configure

::topleft::

{
  "extends": "@tsconfig/ember/tsconfig.json",
  "glint": {
    "environment": [
      "ember-loose",
      "ember-template-imports"
    ]
  }
}

tsconfig.json

::topright::

import "@glint/environment-ember-loose";
import "@glint/environment-ember-template-imports";

global.d.ts


layout: statement

That was a lot!


layout: four-sections

Using template tag components today

Code editor setup

::topleft::

Visual Studio Code

Registers glimmer-js and glimmer-ts language and grammars


Using another code editor?
Highly likely it's supported, multiple Grammar definitions are available on GitHub
Checkout the ember-template-imports GitHub repository

::topright::

blog

The lifeart.vscode-glimmer-syntax extension on the VS Code Marketplace

layout: four-sections

Using template tag components today

Syntax highlighting on GitHub/GitLab

  • Not yet supported
    • Currently ~1100 .gjs/.gts files on GitHub of the required 2000
  • Configurable to fall back to JS/TS syntax highlighting





::topleft::

GitHub

*.gjs linguist-language=js linguist-detectable
*.gts linguist-language=ts linguist-detectable

.gitattributes

::topright::

GitLab

*.gjs gitlab-language=js
*.gts gitlab-language=ts

.gitattributes


Using template tag components today

Syntax highlighting on the web

  • Highlight.js
  • Shiki

Glimdown


Using template tag components today

Trying template tags in your browser

Interactive tutorial by NullVoxPopuli: https://tutorial.glimdown.com

Glimdown


layout: section

Looking ahead


layout: four-sections

::topleft::

Looking ahead

Usage in routes

  • Currently not possible to use .gjs/.gts for route templates
  • setRouteComponent RFC
    • Unmerged!
    • Polyfill available
  • The Polaris router will unlock this

::topright::

router

The Polaris Router roadmap epic on GitHub

Looking ahead

Codemod


layout: four-sections

::topleft::

Looking ahead

Wishlist

  • Automatic imports in editor for templates
  • Ember CLI blueprints
  • Documentation

::topright::

router

The Polaris roadmap on GitHub

layout: section

Wrapping it up


layout: four-sections

Wrapping it up

Current state of template tag components

::topleft::

  • Officially the next-gen component format for Ember.js
  • Already usable today
  • Can be adopted incrementally
  • Unlocks code splitting
  • Allows access to local scope in templates
  • Offers a streamlined testing experience

::topright::


layout: statement

Should you start using <template> tag components today?


layout: center-with-bottom-notes

Thank you!

::right::

Ignace Maes
Senior Full-Stack Engineer at OTA Insight

::left::

@IgnaceMaes
@Ignace_Maes
www.ignacemaes.com