Skip to the content

Wakusei Planets惑星

The Sun, the planets and their moons as a clean line drawing, a shaded realistic drawing or a painted surface map, with real sizes, tilt and rotation, and the moons where they are at any date and time.

Wakusei is the Japanese word 惑星 for a planet. About the name

Contents

How-to

In React, Vue, Svelte and Angular

A component for each framework, with the browser-only import done where a server render never reaches it.

<wakusei-body> is a custom element, so it works in any framework. The one rule is that it needs a browser: import it where a server render does not run (an effect, a mounted hook). Every component below was built and opened by the package's tests.

React

import { useEffect } from "react";

export function WakuseiBody({ body = "saturn", look = "line" }: { body?: string; look?: "line" | "realistic" }) {
  useEffect(() => {
    // The element needs a browser, so it is imported here, never at the top of the file: a server render never reaches an effect.
    void import("@johnmorrisdotca/wakusei-planets/element");
    void import("@johnmorrisdotca/wakusei-planets/all");
  }, []);
  return <wakusei-body body={body} look={look} />;
}

React needs to be told the tag's attributes. A type file does it:

import type { DetailedHTMLProps, HTMLAttributes } from "react";

declare module "react" {
  namespace JSX {
    interface IntrinsicElements {
      "wakusei-body": DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement> & { body?: string; look?: string; size?: number | string; time?: string; moons?: boolean };
    }
  }
}

The component renders to a string on a server and the element fills in when the page loads, so Next.js works the same way.

Vue

<script setup lang="ts">
import { onMounted } from "vue";

withDefaults(defineProps<{ body?: string; look?: "line" | "realistic" }>(), { body: "saturn", look: "line" });

// The element needs a browser: import it once the component is on the page, never while a server renders it.
onMounted(() => {
  void import("@johnmorrisdotca/wakusei-planets/element");
});
</script>

<template>
  <wakusei-body :body="body" :look="look"></wakusei-body>
</template>

Tell Vue that the tag is not one of its own:

import { createApp } from "vue";
import PlanetView from "./PlanetView.vue";

const app = createApp(PlanetView);
app.config.compilerOptions.isCustomElement = (tag) => tag === "wakusei-body";
app.mount("#app");

Svelte

<script lang="ts">
  import { onMount } from "svelte";

  let { body = "saturn", look = "line" }: { body?: string; look?: "line" | "realistic" } = $props();

  // The element needs a browser: onMount never runs on the server.
  onMount(() => {
    void import("@johnmorrisdotca/wakusei-planets/element");
  });
</script>

<wakusei-body {body} {look}></wakusei-body>

Angular

import { Component, CUSTOM_ELEMENTS_SCHEMA, Input, OnInit } from "@angular/core";

@Component({
  selector: "app-wakusei-body",
  standalone: true,
  schemas: [CUSTOM_ELEMENTS_SCHEMA],
  template: `<wakusei-body [attr.body]="body" [attr.look]="look"></wakusei-body>`,
})
export class WakuseiBodyComponent implements OnInit {
  @Input() body = "saturn";
  @Input() look: "line" | "realistic" = "line";

  ngOnInit(): void {
    // The element needs a browser: ngOnInit also runs when a server renders, so the import waits for one.
    if (typeof window !== "undefined") void import("@johnmorrisdotca/wakusei-planets/element");
  }
}

Nuxt, SvelteKit, Next.js

The same components apply. Only the import of the element must stay inside the mounted hook, the effect or onMount.

View sourceEdit on GitHubMade from the source of @johnmorrisdotca/wakusei-planets 0.24.0.