mirror of
https://github.com/cupcakearmy/svelte-rest-demo.git
synced 2024-12-22 16:16:25 +00:00
client
This commit is contained in:
parent
0ab51bea2a
commit
42176ca4fd
7
client/.gitignore
vendored
Normal file
7
client/.gitignore
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
# Node
|
||||||
|
node_modules
|
||||||
|
package-lock.json
|
||||||
|
yarn.lock
|
||||||
|
|
||||||
|
# Sapper
|
||||||
|
__sapper__
|
26
client/package.json
Normal file
26
client/package.json
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"scripts": {
|
||||||
|
"dev": "sapper dev",
|
||||||
|
"build": "sapper build --legacy",
|
||||||
|
"export": "sapper export --legacy",
|
||||||
|
"start": "node __sapper__/build"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"axios": "^0.19.2",
|
||||||
|
"bootstrap": "^4.5.0",
|
||||||
|
"compression": "^1.7.1",
|
||||||
|
"polka": "next",
|
||||||
|
"sirv": "^0.4.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@rollup/plugin-commonjs": "^12.0.0",
|
||||||
|
"@rollup/plugin-node-resolve": "^8.0.0",
|
||||||
|
"@rollup/plugin-replace": "^2.2.0",
|
||||||
|
"npm-run-all": "^4.1.5",
|
||||||
|
"rollup": "^2.3.4",
|
||||||
|
"rollup-plugin-svelte": "^5.0.1",
|
||||||
|
"rollup-plugin-terser": "^5.3.0",
|
||||||
|
"sapper": "^0.27.0",
|
||||||
|
"svelte": "^3.0.0"
|
||||||
|
}
|
||||||
|
}
|
89
client/rollup.config.js
Normal file
89
client/rollup.config.js
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
import resolve from '@rollup/plugin-node-resolve'
|
||||||
|
import replace from '@rollup/plugin-replace'
|
||||||
|
import commonjs from '@rollup/plugin-commonjs'
|
||||||
|
import svelte from 'rollup-plugin-svelte'
|
||||||
|
import { terser } from 'rollup-plugin-terser'
|
||||||
|
import config from 'sapper/config/rollup.js'
|
||||||
|
import pkg from './package.json'
|
||||||
|
|
||||||
|
const mode = process.env.NODE_ENV
|
||||||
|
const dev = mode === 'development'
|
||||||
|
|
||||||
|
const onwarn = (warning, onwarn) =>
|
||||||
|
(warning.code === 'CIRCULAR_DEPENDENCY' &&
|
||||||
|
/[/\\]@sapper[/\\]/.test(warning.message)) ||
|
||||||
|
onwarn(warning)
|
||||||
|
|
||||||
|
export default {
|
||||||
|
client: {
|
||||||
|
input: config.client.input(),
|
||||||
|
output: config.client.output(),
|
||||||
|
plugins: [
|
||||||
|
replace({
|
||||||
|
'process.browser': true,
|
||||||
|
'process.env.NODE_ENV': JSON.stringify(mode),
|
||||||
|
}),
|
||||||
|
svelte({
|
||||||
|
dev,
|
||||||
|
hydratable: true,
|
||||||
|
emitCss: true,
|
||||||
|
}),
|
||||||
|
resolve({
|
||||||
|
browser: true,
|
||||||
|
dedupe: ['svelte'],
|
||||||
|
}),
|
||||||
|
commonjs(),
|
||||||
|
|
||||||
|
!dev &&
|
||||||
|
terser({
|
||||||
|
module: true,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
|
||||||
|
preserveEntrySignatures: false,
|
||||||
|
onwarn,
|
||||||
|
},
|
||||||
|
|
||||||
|
server: {
|
||||||
|
input: config.server.input(),
|
||||||
|
output: config.server.output(),
|
||||||
|
plugins: [
|
||||||
|
replace({
|
||||||
|
'process.browser': false,
|
||||||
|
'process.env.NODE_ENV': JSON.stringify(mode),
|
||||||
|
}),
|
||||||
|
svelte({
|
||||||
|
generate: 'ssr',
|
||||||
|
dev,
|
||||||
|
}),
|
||||||
|
resolve({
|
||||||
|
dedupe: ['svelte'],
|
||||||
|
}),
|
||||||
|
commonjs(),
|
||||||
|
],
|
||||||
|
external: Object.keys(pkg.dependencies).concat(
|
||||||
|
require('module').builtinModules ||
|
||||||
|
Object.keys(process.binding('natives'))
|
||||||
|
),
|
||||||
|
|
||||||
|
preserveEntrySignatures: 'strict',
|
||||||
|
onwarn,
|
||||||
|
},
|
||||||
|
|
||||||
|
serviceworker: {
|
||||||
|
input: config.serviceworker.input(),
|
||||||
|
output: config.serviceworker.output(),
|
||||||
|
plugins: [
|
||||||
|
resolve(),
|
||||||
|
replace({
|
||||||
|
'process.browser': true,
|
||||||
|
'process.env.NODE_ENV': JSON.stringify(mode),
|
||||||
|
}),
|
||||||
|
commonjs(),
|
||||||
|
!dev && terser(),
|
||||||
|
],
|
||||||
|
|
||||||
|
preserveEntrySignatures: false,
|
||||||
|
onwarn,
|
||||||
|
},
|
||||||
|
}
|
39
client/src/api/index.js
Normal file
39
client/src/api/index.js
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import { writable } from 'svelte/store'
|
||||||
|
import axios from 'axios'
|
||||||
|
|
||||||
|
axios.defaults.baseURL = '//localhost:8000'
|
||||||
|
|
||||||
|
export const todos = writable([])
|
||||||
|
|
||||||
|
export async function refresh() {
|
||||||
|
const { data } = await axios({
|
||||||
|
url: '/todo',
|
||||||
|
})
|
||||||
|
todos.set(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function add(todo) {
|
||||||
|
await axios({
|
||||||
|
url: '/todo',
|
||||||
|
method: 'post',
|
||||||
|
data: todo,
|
||||||
|
})
|
||||||
|
await refresh()
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function remove(id) {
|
||||||
|
await axios({
|
||||||
|
url: '/todo/' + id,
|
||||||
|
method: 'delete',
|
||||||
|
})
|
||||||
|
await refresh()
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function update({ _id, ...todo }) {
|
||||||
|
await axios({
|
||||||
|
url: '/todo/' + _id,
|
||||||
|
method: 'post',
|
||||||
|
data: todo,
|
||||||
|
})
|
||||||
|
await refresh()
|
||||||
|
}
|
5
client/src/client.js
Normal file
5
client/src/client.js
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import * as sapper from '@sapper/app';
|
||||||
|
|
||||||
|
sapper.start({
|
||||||
|
target: document.querySelector('#sapper')
|
||||||
|
});
|
31
client/src/components/Add.svelte
Normal file
31
client/src/components/Add.svelte
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
<script>
|
||||||
|
import Button from './Button.svelte'
|
||||||
|
import Field from './Field.svelte'
|
||||||
|
|
||||||
|
import { add } from '../api'
|
||||||
|
|
||||||
|
let title = ''
|
||||||
|
|
||||||
|
async function submit(e) {
|
||||||
|
// Here generally you would do a first validation before sending it to your service
|
||||||
|
await add({ title, done: false })
|
||||||
|
title = ''
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!-- <style>
|
||||||
|
input {
|
||||||
|
outline: none;
|
||||||
|
border: 3px solid hsl(0, 0%, 0%);
|
||||||
|
border-radius: 1em;
|
||||||
|
font-size: inherit;
|
||||||
|
padding: 0.25em 1em;
|
||||||
|
}
|
||||||
|
</style> -->
|
||||||
|
|
||||||
|
<h2>Add</h2>
|
||||||
|
|
||||||
|
<form on:submit|preventDefault={submit}>
|
||||||
|
<Field bind:value={title} type="text" placeholder="Buy milk" />
|
||||||
|
<Button type="submit" text="Save" />
|
||||||
|
</form>
|
18
client/src/components/Button.svelte
Normal file
18
client/src/components/Button.svelte
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<script>
|
||||||
|
export let text = ''
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
button {
|
||||||
|
outline: none;
|
||||||
|
border: none;
|
||||||
|
color: hsl(0, 0%, 100%);
|
||||||
|
background-color: hsl(0, 0%, 0%);
|
||||||
|
padding: 0.5em 1em;
|
||||||
|
border-radius: 1em;
|
||||||
|
font-size: inherit;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<button on:click {...$$restProps}>{text}</button>
|
20
client/src/components/Field.svelte
Normal file
20
client/src/components/Field.svelte
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<script>
|
||||||
|
export let value
|
||||||
|
export let full = false
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
input {
|
||||||
|
outline: none;
|
||||||
|
border: 3px solid hsl(0, 0%, 0%);
|
||||||
|
border-radius: 1em;
|
||||||
|
font-size: inherit;
|
||||||
|
padding: 0.25em 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.full {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<input bind:value class:full {...$$restProps} />
|
19
client/src/components/List.svelte
Normal file
19
client/src/components/List.svelte
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<script>
|
||||||
|
import Todo from './Todo.svelte'
|
||||||
|
import { todos } from '../api'
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
ul {
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<h2>List</h2>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
{#each $todos as todo (todo._id)}
|
||||||
|
<Todo {todo} />
|
||||||
|
{/each}
|
||||||
|
</ul>
|
34
client/src/components/Todo.svelte
Normal file
34
client/src/components/Todo.svelte
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
<script>
|
||||||
|
import Button from './Button.svelte'
|
||||||
|
import Field from './Field.svelte'
|
||||||
|
|
||||||
|
import { remove, update } from '../api'
|
||||||
|
|
||||||
|
export let todo
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
li {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 2.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
span {
|
||||||
|
display: block;
|
||||||
|
width: 1em;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<li>
|
||||||
|
<Field bind:value={todo.title} full />
|
||||||
|
<span />
|
||||||
|
<Button
|
||||||
|
on:click={() => (window.location.pathname = '/todo/' + todo._id)}
|
||||||
|
text="View" />
|
||||||
|
<span />
|
||||||
|
<Button on:click={() => update(todo)} text="Update" />
|
||||||
|
<span />
|
||||||
|
<Button on:click={() => remove(todo._id)} text="Delete" />
|
||||||
|
</li>
|
31
client/src/routes/_layout.svelte
Normal file
31
client/src/routes/_layout.svelte
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
<script>
|
||||||
|
// import 'bootstrap/dist/css/bootstrap.min.css'
|
||||||
|
import { onMount } from 'svelte'
|
||||||
|
|
||||||
|
import { refresh } from '../api'
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
refresh()
|
||||||
|
console.log('mounted')
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen,
|
||||||
|
Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
main {
|
||||||
|
position: relative;
|
||||||
|
max-width: 45em;
|
||||||
|
width: 100%;
|
||||||
|
padding: 1em;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
<slot />
|
||||||
|
</main>
|
20
client/src/routes/index.svelte
Normal file
20
client/src/routes/index.svelte
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<script>
|
||||||
|
import Add from '../components/Add.svelte'
|
||||||
|
import List from '../components/List.svelte'
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<svelte:head>
|
||||||
|
<title>Todos</title>
|
||||||
|
</svelte:head>
|
||||||
|
|
||||||
|
<h1>Todos</h1>
|
||||||
|
|
||||||
|
<br />
|
||||||
|
<Add />
|
||||||
|
|
||||||
|
<br />
|
||||||
|
<List />
|
18
client/src/routes/todo/[id].svelte
Normal file
18
client/src/routes/todo/[id].svelte
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<script context="module">
|
||||||
|
export async function preload(page, session) {
|
||||||
|
return page.params
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Todo from '../../components/Todo.svelte'
|
||||||
|
import { todos } from '../../api'
|
||||||
|
|
||||||
|
export let id
|
||||||
|
|
||||||
|
$: todo = $todos.find(todo => todo._id === id)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if todo}
|
||||||
|
<Todo {todo} />
|
||||||
|
{/if}
|
17
client/src/server.js
Normal file
17
client/src/server.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import sirv from 'sirv';
|
||||||
|
import polka from 'polka';
|
||||||
|
import compression from 'compression';
|
||||||
|
import * as sapper from '@sapper/server';
|
||||||
|
|
||||||
|
const { PORT, NODE_ENV } = process.env;
|
||||||
|
const dev = NODE_ENV === 'development';
|
||||||
|
|
||||||
|
polka() // You can also use Express
|
||||||
|
.use(
|
||||||
|
compression({ threshold: 0 }),
|
||||||
|
sirv('static', { dev }),
|
||||||
|
sapper.middleware()
|
||||||
|
)
|
||||||
|
.listen(PORT, err => {
|
||||||
|
if (err) console.log('error', err);
|
||||||
|
});
|
82
client/src/service-worker.js
Normal file
82
client/src/service-worker.js
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
import { timestamp, files, shell, routes } from '@sapper/service-worker';
|
||||||
|
|
||||||
|
const ASSETS = `cache${timestamp}`;
|
||||||
|
|
||||||
|
// `shell` is an array of all the files generated by the bundler,
|
||||||
|
// `files` is an array of everything in the `static` directory
|
||||||
|
const to_cache = shell.concat(files);
|
||||||
|
const cached = new Set(to_cache);
|
||||||
|
|
||||||
|
self.addEventListener('install', event => {
|
||||||
|
event.waitUntil(
|
||||||
|
caches
|
||||||
|
.open(ASSETS)
|
||||||
|
.then(cache => cache.addAll(to_cache))
|
||||||
|
.then(() => {
|
||||||
|
self.skipWaiting();
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
self.addEventListener('activate', event => {
|
||||||
|
event.waitUntil(
|
||||||
|
caches.keys().then(async keys => {
|
||||||
|
// delete old caches
|
||||||
|
for (const key of keys) {
|
||||||
|
if (key !== ASSETS) await caches.delete(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.clients.claim();
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
self.addEventListener('fetch', event => {
|
||||||
|
if (event.request.method !== 'GET' || event.request.headers.has('range')) return;
|
||||||
|
|
||||||
|
const url = new URL(event.request.url);
|
||||||
|
|
||||||
|
// don't try to handle e.g. data: URIs
|
||||||
|
if (!url.protocol.startsWith('http')) return;
|
||||||
|
|
||||||
|
// ignore dev server requests
|
||||||
|
if (url.hostname === self.location.hostname && url.port !== self.location.port) return;
|
||||||
|
|
||||||
|
// always serve static files and bundler-generated assets from cache
|
||||||
|
if (url.host === self.location.host && cached.has(url.pathname)) {
|
||||||
|
event.respondWith(caches.match(event.request));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// for pages, you might want to serve a shell `service-worker-index.html` file,
|
||||||
|
// which Sapper has generated for you. It's not right for every
|
||||||
|
// app, but if it's right for yours then uncomment this section
|
||||||
|
/*
|
||||||
|
if (url.origin === self.origin && routes.find(route => route.pattern.test(url.pathname))) {
|
||||||
|
event.respondWith(caches.match('/service-worker-index.html'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (event.request.cache === 'only-if-cached') return;
|
||||||
|
|
||||||
|
// for everything else, try the network first, falling back to
|
||||||
|
// cache if the user is offline. (If the pages never change, you
|
||||||
|
// might prefer a cache-first approach to a network-first one.)
|
||||||
|
event.respondWith(
|
||||||
|
caches
|
||||||
|
.open(`offline${timestamp}`)
|
||||||
|
.then(async cache => {
|
||||||
|
try {
|
||||||
|
const response = await fetch(event.request);
|
||||||
|
cache.put(event.request, response.clone());
|
||||||
|
return response;
|
||||||
|
} catch(err) {
|
||||||
|
const response = await cache.match(event.request);
|
||||||
|
if (response) return response;
|
||||||
|
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
28
client/src/template.html
Normal file
28
client/src/template.html
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang='en'>
|
||||||
|
<head>
|
||||||
|
<meta charset='utf-8'>
|
||||||
|
<meta name='viewport' content='width=device-width,initial-scale=1.0'>
|
||||||
|
|
||||||
|
%sapper.base%
|
||||||
|
|
||||||
|
<!-- Sapper generates a <style> tag containing critical CSS
|
||||||
|
for the current page. CSS for the rest of the app is
|
||||||
|
lazily loaded when it precaches secondary pages -->
|
||||||
|
%sapper.styles%
|
||||||
|
|
||||||
|
<!-- This contains the contents of the <svelte:head> component, if
|
||||||
|
the current page has one -->
|
||||||
|
%sapper.head%
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<!-- The application will be rendered inside this element,
|
||||||
|
because `src/client.js` references it -->
|
||||||
|
<div id='sapper'>%sapper.html%</div>
|
||||||
|
|
||||||
|
<!-- Sapper creates a <script> tag containing `src/client.js`
|
||||||
|
and anything else it needs to hydrate the app and
|
||||||
|
initialise the router -->
|
||||||
|
%sapper.scripts%
|
||||||
|
</body>
|
||||||
|
</html>
|
Loading…
Reference in New Issue
Block a user