Add Marko (#149)

* WIP: add Marko

* WTF, vscode

* Consistent final newlines

* Apply suggestions from code review

Everything except for the TS declarations since I think Dylan ought to weigh in

Co-authored-by: Michael Rawlings <mirawlings@ebay.com>
Co-authored-by: Luke LaValva <lukelavalva@gmail.com>

* Feedback: no space before method parens, no TS, else-if, ColorSelect works now

* Pre-bugbash updates

* This is probably why they want us to provide a linter

* Finishing touches

* Argle

* @rturnq feedback

* Match examples to other frameworks

---------

Co-authored-by: Michael Rawlings <mirawlings@ebay.com>
Co-authored-by: Luke LaValva <lukelavalva@gmail.com>
Co-authored-by: tigt <tigt@mortropolis.com>
This commit is contained in:
Taylor Hunt
2023-04-03 16:36:25 -04:00
committed by GitHub
parent 11975c888a
commit a01e903494
39 changed files with 399 additions and 13 deletions

View File

@@ -1,6 +1,6 @@
# 🧑‍💻 Contributing
This site is built with [Astro](https://docs.astro.build). Site content is written in Markdown format located in `content`. For simple edits, you can directly edit the file on GitHub and generate a Pull Request.
This site is built with [Vite](https://vitejs.dev) and [Svelte](https://svelte.dev). Site content is written in Markdown format located in `content`. For simple edits, you can directly edit the file on GitHub and generate a Pull Request.
## Add a framework
@@ -8,6 +8,11 @@ This site is built with [Astro](https://docs.astro.build). Site content is writt
2. Add the new framework SVG logo in `public/framework`
3. Install the ESLint plugin associated to the framework
4. In `frameworks.mjs`, add a new entry with SVG link and ESLint configuration
5. If the framework needs a language syntax highlight, add it to the call to `getHighlighter`s `langs` argument in `build/lib/generateContent.js`
6. To make a playground link:
1. Add a `create${FRAMEWORK}Playground.js` file in `build/lib/playground`.
2. That file should export a function that returns an object with a `fromContentByFilename` method that accepts an object of filepath keys and file content values, then returns an absolute URL to a frameworks online REPL with those files loaded.
3. Register its export in `build/lib/playground/index.js`
## Improve website

View File

@@ -15,7 +15,7 @@ How do we solve this ? Developers love having framework overview by examples. It
<summary>
<img width="18" height="18" src="public/framework/svelte.svg" />
<b>Svelte</b>
<img src="https://us-central1-progress-markdown.cloudfunctions.net/progress/100" /></summary>
<img alt="100% progress" src="https://us-central1-progress-markdown.cloudfunctions.net/progress/100" /></summary>
- [x] Reactivity
- [x] Declare state
@@ -52,7 +52,7 @@ How do we solve this ? Developers love having framework overview by examples. It
<summary>
<img width="18" height="18" src="public/framework/react.svg" />
<b>React</b>
<img src="https://us-central1-progress-markdown.cloudfunctions.net/progress/100" /></summary>
<img alt="100% progress" src="https://us-central1-progress-markdown.cloudfunctions.net/progress/100" /></summary>
- [x] Reactivity
- [x] Declare state
@@ -89,7 +89,7 @@ How do we solve this ? Developers love having framework overview by examples. It
<summary>
<img width="18" height="18" src="public/framework/vue.svg" />
<b>Vue 3</b>
<img src="https://us-central1-progress-markdown.cloudfunctions.net/progress/96" /></summary>
<img alt="96% progress" src="https://us-central1-progress-markdown.cloudfunctions.net/progress/96" /></summary>
- [x] Reactivity
- [x] Declare state
@@ -126,7 +126,7 @@ How do we solve this ? Developers love having framework overview by examples. It
<summary>
<img width="18" height="18" src="public/framework/solid.svg" />
<b>SolidJS</b>
<img src="https://us-central1-progress-markdown.cloudfunctions.net/progress/92" /></summary>
<img alt="92% progress" src="https://us-central1-progress-markdown.cloudfunctions.net/progress/92" /></summary>
- [x] Reactivity
- [x] Declare state
@@ -163,7 +163,7 @@ How do we solve this ? Developers love having framework overview by examples. It
<summary>
<img width="18" height="18" src="public/framework/qwik.svg" />
<b>Qwik</b>
<img src="https://us-central1-progress-markdown.cloudfunctions.net/progress/92" /></summary>
<img alt="92% progress" src="https://us-central1-progress-markdown.cloudfunctions.net/progress/92" /></summary>
- [x] Reactivity
- [x] Declare state
@@ -200,7 +200,7 @@ How do we solve this ? Developers love having framework overview by examples. It
<summary>
<img width="18" height="18" src="public/framework/angular.svg" />
<b>Angular</b>
<img src="https://us-central1-progress-markdown.cloudfunctions.net/progress/92" /></summary>
<img alt="92% progress" src="https://us-central1-progress-markdown.cloudfunctions.net/progress/92" /></summary>
- [x] Reactivity
- [x] Declare state
@@ -237,7 +237,7 @@ How do we solve this ? Developers love having framework overview by examples. It
<summary>
<img width="18" height="18" src="public/framework/lit.svg" />
<b>Lit</b>
<img src="https://us-central1-progress-markdown.cloudfunctions.net/progress/96" /></summary>
<img alt="96% progress" src="https://us-central1-progress-markdown.cloudfunctions.net/progress/96" /></summary>
- [x] Reactivity
- [x] Declare state
@@ -274,7 +274,7 @@ How do we solve this ? Developers love having framework overview by examples. It
<summary>
<img width="18" height="18" src="public/framework/vue.svg" />
<b>Vue 2</b>
<img src="https://us-central1-progress-markdown.cloudfunctions.net/progress/100" /></summary>
<img alt="100% progress" src="https://us-central1-progress-markdown.cloudfunctions.net/progress/100" /></summary>
- [x] Reactivity
- [x] Declare state
@@ -311,7 +311,7 @@ How do we solve this ? Developers love having framework overview by examples. It
<summary>
<img width="18" height="18" src="public/framework/ember.svg" />
<b>Ember</b>
<img src="https://us-central1-progress-markdown.cloudfunctions.net/progress/92" /></summary>
<img alt="92% progress" src="https://us-central1-progress-markdown.cloudfunctions.net/progress/92" /></summary>
- [x] Reactivity
- [x] Declare state
@@ -348,7 +348,7 @@ How do we solve this ? Developers love having framework overview by examples. It
<summary>
<img width="18" height="18" src="public/framework/alpine.svg" />
<b>Alpine</b>
<img src="https://us-central1-progress-markdown.cloudfunctions.net/progress/96" /></summary>
<img alt="96% progress" src="https://us-central1-progress-markdown.cloudfunctions.net/progress/96" /></summary>
- [x] Reactivity
- [x] Declare state
@@ -385,7 +385,7 @@ How do we solve this ? Developers love having framework overview by examples. It
<summary>
<img width="18" height="18" src="public/framework/aurelia.svg" />
<b>Aurelia 1</b>
<img src="https://us-central1-progress-markdown.cloudfunctions.net/progress/92" /></summary>
<img alt="92% progress" src="https://us-central1-progress-markdown.cloudfunctions.net/progress/92" /></summary>
- [x] Reactivity
- [x] Declare state
@@ -418,6 +418,43 @@ How do we solve this ? Developers love having framework overview by examples. It
- [x] Router link
- [x] Routing
</details><details>
<summary>
<img width="18" height="18" src="public/framework/marko.svg">
<b>Marko</b>
<img alt="100% progress" src="https://us-central1-progress-markdown.cloudfunctions.net/progress/100"></summary>
- [x] Reactivity
- [x] Declare state
- [x] Update state
- [x] Computed state
- [x] Templating
- [x] Minimal template
- [x] Styling
- [x] Loop
- [x] Event click
- [x] Dom ref
- [x] Conditional
- [x] Lifecycle
- [x] On mount
- [x] On unmount
- [x] Component composition
- [x] Props
- [x] Emit to parent
- [x] Slot
- [x] Slot fallback
- [x] Context
- [x] Form input
- [x] Input text
- [x] Checkbox
- [x] Radio
- [x] Select
- [x] Webapp features
- [x] Render app
- [x] Fetch data
- [x] Router link
- [x] Routing
</details>
## 🤝 Contributing
@@ -439,6 +476,11 @@ This project requires Node.js to be `v16.0.0` or higher.
2. Add the new framework SVG logo in `public/framework`
3. Install the ESLint plugin associated to the framework
4. In `frameworks.mjs`, add a new entry with SVG link and ESLint configuration
5. If the framework needs a language syntax highlight, add it to the call to `getHighlighter`s `langs` argument in `build/lib/generateContent.js`
6. To make a playground link:
1. Add a `create${FRAMEWORK}Playground.js` file in `build/lib/playground`.
2. That file should export a function that returns an object with a `fromContentByFilename` method that accepts an object of filepath keys and file content values, then returns an absolute URL to a frameworks online REPL with those files loaded.
3. Register its export in `build/lib/playground/index.js`
## 🧑‍💻 Contributors

View File

@@ -25,7 +25,16 @@ async function pathExists(path) {
export default async function generateContent() {
const highlighter = await getHighlighter({
theme: componentPartyShikiTheme,
langs: ["javascript", "svelte", "html", "hbs", "tsx", "jsx", "vue"],
langs: [
"javascript",
"svelte",
"html",
"hbs",
"tsx",
"jsx",
"vue",
"marko",
],
});
const rootDir = await packageDirectory();

View File

@@ -0,0 +1,18 @@
import nodePath from "node:path";
import { compressToURL } from "@matschik/lz-string";
const BASE = "https://markojs.com/playground/#";
export default function createMarkoPlayground() {
return {
fromContentByFilename(contentByFilename) {
const data = Object.entries(contentByFilename).map(([path, content]) => ({
name: nodePath.parse(path).base,
path: `/components/${path}`,
content,
}));
return BASE + compressToURL(JSON.stringify(data));
},
};
}

View File

@@ -2,10 +2,12 @@ import createAlpinePlayground from "./createAlpinePlayground.js";
import createSveltePlayground from "./createSveltePlayground.js";
import createVue3Playground from "./createVue3Playground.js";
import createSolidPlayground from "./createSolidPlayground.js";
import createMarkoPlayground from "./createMarkoPlayground.js";
export default {
vue3: createVue3Playground(),
svelte: createSveltePlayground(),
alpine: createAlpinePlayground(),
solid: createSolidPlayground(),
marko: createMarkoPlayground(),
};

View File

@@ -0,0 +1,2 @@
<let/name = "John"/>
<h1>Hello ${name}</h1>

View File

@@ -0,0 +1,3 @@
<let/name = "John"/>
<effect() { name = "Jane" }/>
<h1>Hello ${name}</h1>

View File

@@ -0,0 +1,3 @@
<let/count = 10/>
<const/doubleCount = count * 2/>
<div>${doubleCount}</div>

View File

@@ -0,0 +1 @@
<h1>Hello world</h1>

View File

@@ -0,0 +1,14 @@
<h1.title>I am red</h1>
<button style={ fontSize: "10rem" }>I am a button</button>
<button class=scopedButton>I am a style-scoped button</button>
<style>
.title {
color: red;
}
</style>
<style/{ scopedButton }>
.scopedButton {
font-size: 10rem;
}
</style>

View File

@@ -0,0 +1,5 @@
<ul>
<for|color| of=["red", "green", "blue"]>
<li>${color}</li>
</for>
</ul>

View File

@@ -0,0 +1,3 @@
<let/count = 0/>
<p>Counter: ${count}</p>
<button onClick() { count++ }>+1</button>

View File

@@ -0,0 +1,2 @@
<input/inputElement>
<effect() { inputElement().focus() }/>

View File

@@ -0,0 +1,14 @@
static const TRAFFIC_LIGHTS = ["red", "orange", "green"];
<let/lightIndex = 0/>
<const/light = TRAFFIC_LIGHTS[lightIndex]/>
<button onClick() { lightIndex = (lightIndex + 1) % TRAFFIC_LIGHTS.length }>
Next light
</button>
<p>Light is: ${light}</p>
<p>
You must
<if=light === "red">STOP</if>
<else-if=light === "orange">SLOW DOWN</else-if>
<else>GO</else>
</p>

View File

@@ -0,0 +1,3 @@
<let/pageTitle = ""/>
<effect() { pageTitle = document.title }/>
<p>Page title: ${pageTitle}</p>

View File

@@ -0,0 +1,6 @@
<let/time = new Date()/>
<lifecycle
onMount() { this.timer = setInterval(_ => time = new Date(), 1000) }
onDestroy() { clearInterval(this.timer) }
/>
<p>Current time: ${time.toLocaleTimeString()}</p>

View File

@@ -0,0 +1,6 @@
<UserProfile
name="John"
age=20
favouriteColors=["green", "blue", "red"]
isAvailable
/>

View File

@@ -0,0 +1,11 @@
<const/{
name = "",
age = null,
favouriteColors = [],
isAvailable = false,
} = input/>
<p>My name is ${name}!</p>
<p>My age is ${age}!</p>
<p>My favourite colors are ${favouriteColors.join(", ")}!</p>
<p>I am ${isAvailable ? "available" : "not available"}</p>

View File

@@ -0,0 +1,2 @@
<button onClick=input.onYes>YES</button>
<button onClick=input.onNo>NO</button>

View File

@@ -0,0 +1,7 @@
<let/isHappy = true/>
<p>Are you happy?</p>
<AnswerButton
onYes() { isHappy = true }
onNo() { isHappy = false }
/>
<p style={ fontSize: 50 }>${isHappy ? "😀" : "😥"}</p>

View File

@@ -0,0 +1 @@
<FunnyButton>Click me!</FunnyButton>

View File

@@ -0,0 +1,18 @@
<button.${funnyButton}>
<${input.renderBody}/>
</button>
<style.module.css/{ funnyButton }>
.funnyButton {
background: rgba(0, 0, 0, 0.4);
color: #fff;
padding: 10px 20px;
font-size: 30px;
border: 2px solid #fff;
margin: 8px;
transform: scale(0.9);
box-shadow: 4px 4px rgba(0, 0, 0, 0.4);
transition: transform 0.2s cubic-bezier(0.34, 1.65, 0.88, 0.925) 0s;
outline: 0;
}
</style>

View File

@@ -0,0 +1,2 @@
<FunnyButton/>
<FunnyButton>I got content!</FunnyButton>

View File

@@ -0,0 +1,20 @@
<button.${funnyButton}>
<${input.renderBody}>
<span>No content found</span>
</>
</button>
<style.module.css/{ funnyButton }>
.funnyButton {
background: rgba(0, 0, 0, 0.4);
color: #fff;
padding: 10px 20px;
font-size: 30px;
border: 2px solid #fff;
margin: 8px;
transform: scale(0.9);
box-shadow: 4px 4px rgba(0, 0, 0, 0.4);
transition: transform 0.2s cubic-bezier(0.34, 1.65, 0.88, 0.925) 0s;
outline: 0;
}
</style>

View File

@@ -0,0 +1,13 @@
<let/user = { // In a real app, you would fetch the user data from an API
id: 1,
username: "unicorn42",
email: "unicorn42@example.com",
}/>
<const/updateUsername(newUsername) {
user = { ...user, username: newUsername };
}/>
<h1>Welcome back, ${user.username}</h1>
<set={ ...user, updateUsername }>
<UserProfile />
</set>

View File

@@ -0,0 +1,9 @@
<get/{ username, email, updateUsername } = "App"/>
<div>
<h2>My Profile</h2>
<p>Username: ${username}</p>
<p>Email: ${email}</p>
<button onClick() { updateUsername("Jane") }>
Update username to Jane
</button>
</div>

View File

@@ -0,0 +1,3 @@
<let/text = "Hello world"/>
<p>${text}</p>
<input value:=text/>

View File

@@ -0,0 +1,5 @@
<input#is-available
type="checkbox"
checked:=input.isAvailable
/>
<label for="is-available">Is available</label>

View File

@@ -0,0 +1,21 @@
<let/picked = "red"/>
<const/handleChange(event) {
picked = event.target.value;
}/>
<div>Picked: ${picked}</div>
<input#blue-pill
type="radio"
checked=picked === "blue"
value="blue"
onChange=handleChange
/>
<label for="blue-pill">Blue pill</label>
<input#red-pill
type="radio"
checked=picked === "red"
value="red"
onChange=handleChange
/>
<label for="red-pill">Red pill</label>

View File

@@ -0,0 +1,15 @@
static const colors = [
{ id: 1, text: "red" },
{ id: 2, text: "blue" },
{ id: 3, text: "green" },
{ id: 4, text: "gray", isDisabled: true },
];
<let/selectedColorId = 2/>
<select onChange(event) { selectedColorId = event.target.value }>
<for|{ id, isDisabled, text }| of=colors>
<option value=id disabled=isDisabled selected=id === selectedColorId>
${text}
</option>
</for>
</select>

View File

@@ -0,0 +1 @@
<h1>Hello world</h1>

View File

@@ -0,0 +1,4 @@
<!DOCTYPE html>
<html>
<App/>
</html>

View File

@@ -0,0 +1,18 @@
<await(fetch("https://randomuser.me/api/?results=3").then(res => res.json()))>
<@placeholder>
<p>Fetching users...</p>
</@placeholder>
<@catch|error|>
<p>An error occured while fetching users</p>
</@catch>
<@then|{ results: users }|>
<ul>
<for|{ picture, name }| of=users>
<li>
<img src=picture.thumbnail alt="user">
<p>${name.first} ${name.last}</p>
</li>
</for>
</ul>
</@then>
</await>

View File

@@ -0,0 +1,8 @@
With [`@marko/cli`s `build` or `serve`](https://github.com/marko-js/cli/tree/main/packages/serve)
```marko
<nav>
<a href="/">Index</a>
<a href="/contact">Contact Us</a>
</nav>
```

View File

@@ -0,0 +1,8 @@
With [`@marko/run`](https://github.com/marko-js/run/tree/main/packages/serve)
```marko
<nav>
<a href="/">Index</a>
<a href="/contact">Contact Us</a>
</nav>
```

View File

@@ -0,0 +1,9 @@
With [`@marko/cli`s `build` or `serve`](https://github.com/marko-js/cli/tree/main/packages/serve)
```
index.marko // index page "/"
about.marko // about page "/about"
hello/
|-- [name].marko // dynamic Hello page "/hello/Emily"
[rest].marko // dynamic parameter can be used as catch-all to show 404 page
```

View File

@@ -0,0 +1,19 @@
With [`@marko/run`](https://github.com/marko-js/run/tree/main/packages/serve)
```
routes/
|-- +page.marko // index page "/"
|-- about/
|-- +page.marko // about page "/about"
|-- +layout.marko // global app layout
|-- +handler.{js,ts,*} // conditionally render HTML, API route, run arbitrary code…
|-- +middleware.{js,ts,*} // added to HTTP framework middleware chain
|-- +meta.{js,ts,*} // adds metadata to route
|-- +404.marko // shows when no suitable route found
|-- +500.marko // shows when any route throws
|-- /path/_less/
|-- +page.marko // pathless directory, page "/path"
|-- /$dynamic/
|-- +page.marko // dynamic parameter, can be used as a route-specific 404
|-- /$$catchall/ // like dynamic, but consumes all path segments until the end
```

View File

@@ -110,6 +110,19 @@ export default [
return sortAllFilenames(files, ["index.html", "App.tsx"]);
},
},
{
id: "marko",
title: "Marko",
img: "framework/marko.svg",
eslint: {
files: ["!**"], // Markos linter/prettyprinter doesnt use eslint
},
playgroundURL: "https://markojs.com/playground/",
documentationURL: "https://markojs.com/docs/getting-started/",
filesSorter(files) {
return sortAllFilenames(files, ["index.marko", "App.marko"]);
},
},
{
id: "angular",
title: "Angular",

View File

@@ -0,0 +1,41 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2560 1400">
<path fill="url(#a)" d="M427 0h361L361 697l427 697H427L0 698z"/>
<linearGradient id="a" x2="0" y2="1">
<stop stop-color="#04bfc2"/>
<stop offset=".25" stop-color="#06cfe5"/>
<stop offset=".5" stop-color="#3ed6f8"/>
<stop offset=".5" stop-color="#0bbdf8"/>
<stop offset=".75" stop-color="#05a5f0"/>
<stop offset="1" stop-color="#0578c0"/>
</linearGradient>
<path fill="url(#b)" d="M854 697h361L788 0H427z"/>
<linearGradient id="b" x2="0" y2="1">
<stop stop-color="#1de6c5"/>
<stop offset=".5" stop-color="#19d89c"/>
<stop offset="1" stop-color="#16b175"/>
</linearGradient>
<path fill="url(#c)" d="M1281 0h361l-427 697H854z"/>
<linearGradient id="c" x2="0" y2="1">
<stop stop-color="#6bb904"/>
<stop offset=".5" stop-color="#81dc09"/>
<stop offset="1" stop-color="#83e91c"/>
</linearGradient>
<path fill="url(#d)" d="M1642 0h-361l428 697-428 697h361l428-697z"/>
<linearGradient id="d" x2="0" y2="1">
<stop stop-color="#ffeb10"/>
<stop offset=".25" stop-color="#ffd900"/>
<stop offset=".5" stop-color="#fdc601"/>
<stop offset=".5" stop-color="#ffa600"/>
<stop offset=".75" stop-color="#ff9500"/>
<stop offset="1" stop-color="#ef7400"/>
</linearGradient>
<path fill="url(#e)" d="M2132 0h-361l427 697-428 697h361l428-697z"/>
<linearGradient id="e" x2="0" y2="1">
<stop stop-color="#ff425b"/>
<stop offset=".25" stop-color="#f3154d"/>
<stop offset=".5" stop-color="#f1185c"/>
<stop offset=".5" stop-color="#d11661"/>
<stop offset=".75" stop-color="#ce176c"/>
<stop offset="1.1" stop-color="#a31460"/>
</linearGradient>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB