diff --git a/client/.gitignore b/client/.gitignore new file mode 100644 index 0000000..d20567f --- /dev/null +++ b/client/.gitignore @@ -0,0 +1,7 @@ +# Node +node_modules +package-lock.json +yarn.lock + +# Sapper +__sapper__ diff --git a/client/package.json b/client/package.json new file mode 100644 index 0000000..a1a266a --- /dev/null +++ b/client/package.json @@ -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" + } +} diff --git a/client/rollup.config.js b/client/rollup.config.js new file mode 100644 index 0000000..ef6206b --- /dev/null +++ b/client/rollup.config.js @@ -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, + }, +} diff --git a/client/src/api/index.js b/client/src/api/index.js new file mode 100644 index 0000000..5338045 --- /dev/null +++ b/client/src/api/index.js @@ -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() +} diff --git a/client/src/client.js b/client/src/client.js new file mode 100644 index 0000000..cec9172 --- /dev/null +++ b/client/src/client.js @@ -0,0 +1,5 @@ +import * as sapper from '@sapper/app'; + +sapper.start({ + target: document.querySelector('#sapper') +}); \ No newline at end of file diff --git a/client/src/components/Add.svelte b/client/src/components/Add.svelte new file mode 100644 index 0000000..2948d2e --- /dev/null +++ b/client/src/components/Add.svelte @@ -0,0 +1,31 @@ + + + + +

Add

+ +
+ + diff --git a/client/src/components/Field.svelte b/client/src/components/Field.svelte new file mode 100644 index 0000000..acfb390 --- /dev/null +++ b/client/src/components/Field.svelte @@ -0,0 +1,20 @@ + + + + + diff --git a/client/src/components/List.svelte b/client/src/components/List.svelte new file mode 100644 index 0000000..4dded7f --- /dev/null +++ b/client/src/components/List.svelte @@ -0,0 +1,19 @@ + + + + +

List

+ + diff --git a/client/src/components/Todo.svelte b/client/src/components/Todo.svelte new file mode 100644 index 0000000..c37eff3 --- /dev/null +++ b/client/src/components/Todo.svelte @@ -0,0 +1,34 @@ + + + + +
  • + + +
  • diff --git a/client/src/routes/_layout.svelte b/client/src/routes/_layout.svelte new file mode 100644 index 0000000..7e16760 --- /dev/null +++ b/client/src/routes/_layout.svelte @@ -0,0 +1,31 @@ + + + + +
    + +
    diff --git a/client/src/routes/index.svelte b/client/src/routes/index.svelte new file mode 100644 index 0000000..9709de6 --- /dev/null +++ b/client/src/routes/index.svelte @@ -0,0 +1,20 @@ + + + + + + Todos + + +

    Todos

    + +
    + + +
    + diff --git a/client/src/routes/todo/[id].svelte b/client/src/routes/todo/[id].svelte new file mode 100644 index 0000000..b8429d4 --- /dev/null +++ b/client/src/routes/todo/[id].svelte @@ -0,0 +1,18 @@ + + + + +{#if todo} + +{/if} diff --git a/client/src/server.js b/client/src/server.js new file mode 100644 index 0000000..c77f593 --- /dev/null +++ b/client/src/server.js @@ -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); + }); diff --git a/client/src/service-worker.js b/client/src/service-worker.js new file mode 100644 index 0000000..2289a55 --- /dev/null +++ b/client/src/service-worker.js @@ -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; + } + }) + ); +}); diff --git a/client/src/template.html b/client/src/template.html new file mode 100644 index 0000000..c4f84ae --- /dev/null +++ b/client/src/template.html @@ -0,0 +1,28 @@ + + + + + + + %sapper.base% + + + %sapper.styles% + + + %sapper.head% + + + +
    %sapper.html%
    + + + %sapper.scripts% + +