mirror of
https://github.com/cupcakearmy/svelte-rest-demo.git
synced 2024-12-22 08:06:29 +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