This commit is contained in:
cupcakearmy 2020-06-24 11:00:41 +02:00
parent 0ab51bea2a
commit 42176ca4fd
No known key found for this signature in database
GPG Key ID: D28129AE5654D9D9
16 changed files with 484 additions and 0 deletions

7
client/.gitignore vendored Normal file
View File

@ -0,0 +1,7 @@
# Node
node_modules
package-lock.json
yarn.lock
# Sapper
__sapper__

26
client/package.json Normal file
View 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
View 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
View 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
View File

@ -0,0 +1,5 @@
import * as sapper from '@sapper/app';
sapper.start({
target: document.querySelector('#sapper')
});

View 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>

View 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>

View 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} />

View 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>

View 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>

View 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>

View 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 />

View 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
View 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);
});

View 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
View 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>