How-to
In React, Vue, Svelte and Angular
Use the globe in React, Vue, Svelte and Angular, with the import that keeps a server render working.
<chikyu-earth> is a web component, so every framework can use it. Three things apply everywhere:
- Import the element on the client only. The element extends the browser's
HTMLElement, so importing it on a server fails. Each example below imports it inside the framework's "the page is in a browser" hook (useEffect,onMounted,onMount,ngOnInit), which a server render never runs. A top-levelimport "@johnmorrisdotca/chikyu-earth/element"is fine in a client-only app. - Use
look, notstyle.look="thick"picks the outlines. The olderstyleattribute is read by frameworks as CSS and breaks: React rejects a string forstyle. - Do not name your component
ChikyuEarthin Vue. A single-file component refers to itself by its file name, so<chikyu-earth>inside a file calledChikyuEarth.vueloads the component instead of the element, and it recurses forever. The example is calledEarthGlobe.vue.
React
import { useEffect, useRef } from "react";
export function ChikyuEarth({ center = "139.7,35.7", look = "thick" }: { center?: string; look?: "line" | "thick" }) {
const host = useRef<HTMLDivElement>(null);
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/chikyu-earth/element");
}, []);
return (
<div ref={host}>
<chikyu-earth center={center} look={look} graticule />
</div>
);
}With TypeScript, tell React the tag exists (a file such as chikyu-earth.d.ts):
import type { DetailedHTMLProps, HTMLAttributes } from "react";
declare module "react" {
namespace JSX {
interface IntrinsicElements {
"chikyu-earth": DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement> & { center?: string; size?: number | string; look?: string; graticule?: boolean };
}
}
}The component renders to a string on a server (renderToString), as the package's tests check: the tag is there, the globe is drawn in the browser.
Vue
<script setup lang="ts">
import { onMounted } from "vue";
withDefaults(defineProps<{ center?: string; look?: "line" | "thick" }>(), { center: "139.7,35.7", look: "thick" });
// The element needs a browser: import it once the component is on the page, never while a server renders it.
onMounted(() => {
void import("@johnmorrisdotca/chikyu-earth/element");
});
</script>
<template>
<chikyu-earth :center="center" :look="look" graticule></chikyu-earth>
</template>Tell Vue's compiler that the tag is a custom element, in main.ts:
import { createApp } from "vue";
import EarthGlobe from "./EarthGlobe.vue";
const app = createApp(EarthGlobe);
app.config.compilerOptions.isCustomElement = (tag) => tag === "chikyu-earth";
app.mount("#app");With Vite, the same option goes in vue({ template: { compilerOptions: { isCustomElement: (tag) => tag === "chikyu-earth" } } }).
Svelte
<script lang="ts">
import { onMount } from "svelte";
let { center = "139.7,35.7", look = "thick" }: { center?: string; look?: "line" | "thick" } = $props();
// The element needs a browser: onMount never runs on the server.
onMount(() => {
void import("@johnmorrisdotca/chikyu-earth/element");
});
</script>
<chikyu-earth {center} {look} graticule></chikyu-earth>Angular
Standalone, with CUSTOM_ELEMENTS_SCHEMA so the template may hold an unknown tag. Bind the attribute with [attr.look], since the element reads attributes:
import { Component, CUSTOM_ELEMENTS_SCHEMA, Input, OnInit } from "@angular/core";
@Component({
selector: "app-chikyu-earth",
standalone: true,
schemas: [CUSTOM_ELEMENTS_SCHEMA],
template: `<chikyu-earth [attr.center]="center" [attr.look]="look" graticule></chikyu-earth>`,
})
export class ChikyuEarthComponent implements OnInit {
@Input() center = "139.7,35.7";
@Input() look: "line" | "thick" = "thick";
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/chikyu-earth/element");
}
}Next.js, Nuxt, SvelteKit
They render on a server first, so everything above holds: the element is imported inside a client-only hook, never at the top of a file a server loads. In Next.js's App Router the component file starts with "use client". To draw the globe on the server too, call globe() and put its string in the page: see TypeScript and Node.
Turning, zooming and markers
The shared view controls and the markers are plain DOM: get the element with a ref (useRef, ref, bind:this, @ViewChild) and call setView(...) or attachMarkers(element) from the same hook, after the import has resolved. Remember to call a marker set's detach() when the component goes away.