├── src
├── library
│ └── utils.js
├── client.js
├── routes
│ ├── index.svelte
│ ├── _layout.svelte
│ └── _error.svelte
├── components
│ └── Button.svelte
├── server.js
├── template.html
└── service-worker.js
├── static
├── robots.txt
├── favicons
│ ├── favicon.png
│ ├── logo-192.png
│ ├── logo-512.png
│ ├── browserconfig.xml
│ └── safari-pinned-tab.svg
├── global.scss
├── site.webmanifest
└── global.css
├── .gitignore
├── package.json
├── README.md
└── rollup.config.js
/src/library/utils.js:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/static/robots.txt:
--------------------------------------------------------------------------------
1 | User-agent: *
2 | Disallow:
--------------------------------------------------------------------------------
/static/favicons/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mattpilott/sapper-start/HEAD/static/favicons/favicon.png
--------------------------------------------------------------------------------
/static/favicons/logo-192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mattpilott/sapper-start/HEAD/static/favicons/logo-192.png
--------------------------------------------------------------------------------
/static/favicons/logo-512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mattpilott/sapper-start/HEAD/static/favicons/logo-512.png
--------------------------------------------------------------------------------
/src/client.js:
--------------------------------------------------------------------------------
1 | import '../static/global.css';
2 | import { start } from '@sapper/app';
3 |
4 | start({ target: sapper });
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | yarn-error.log
4 | /__sapper__/
5 | pnpm-lock.yaml
6 | pnpm-debug.log
7 | .env
8 | .vscode
9 | .nova
--------------------------------------------------------------------------------
/src/routes/index.svelte:
--------------------------------------------------------------------------------
1 |
Sapper launched successfully! 🚀
2 |
3 |
6 |
7 |
--------------------------------------------------------------------------------
/src/components/Button.svelte:
--------------------------------------------------------------------------------
1 | {#if href}
2 |
3 |
4 |
5 | {:else}
6 |
9 | {/if}
10 |
11 |
--------------------------------------------------------------------------------
/src/routes/_layout.svelte:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
12 |
13 |
--------------------------------------------------------------------------------
/static/favicons/browserconfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | #ffffff
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/static/global.scss:
--------------------------------------------------------------------------------
1 | @import '../node_modules/@neuekit/reboot';
2 |
3 | html {
4 | font-family: -apple-system,
5 | BlinkMacSystemFont,
6 | Segoe UI,
7 | Helvetica,
8 | Arial,
9 | sans-serif,
10 | Apple Color Emoji,
11 | Segoe UI Emoji;
12 | -webkit-font-smoothing: antialiased;
13 | }
14 |
15 | a {
16 | text-decoration: none;
17 | }
--------------------------------------------------------------------------------
/src/server.js:
--------------------------------------------------------------------------------
1 | import sirv from 'sirv';
2 | import polka from 'polka';
3 | import compression from 'compression';
4 | import * as sapper from '@sapper/server';
5 | import { sanslash } from '@neuekit/utilities/src/middleware';
6 | import 'dotenv/config';
7 |
8 | const { PORT, NODE_ENV } = process.env;
9 | const dev = NODE_ENV === 'development';
10 |
11 | polka() // You can also use Express
12 | .use(sanslash)
13 | .use(compression({ threshold: 0 }))
14 | .use(sirv('static', { dev }))
15 | .use(sapper.middleware())
16 | .listen(PORT, err => err && console.log('error', err));
--------------------------------------------------------------------------------
/static/site.webmanifest:
--------------------------------------------------------------------------------
1 | {
2 | "background_color": "#ffffff",
3 | "theme_color": "#333333",
4 | "name": "TODO",
5 | "short_name": "TODO",
6 | "display": "standalone",
7 | "start_url": "/",
8 | "icons": [
9 | {
10 | "src": "/favicons/logo-192.png",
11 | "sizes": "192x192",
12 | "type": "image/png",
13 | "purpose": "any maskable"
14 | },
15 | {
16 | "src": "/favicons/logo-512.png",
17 | "sizes": "512x512",
18 | "type": "image/png",
19 | "purpose": "any maskable"
20 | }
21 | ]
22 | }
--------------------------------------------------------------------------------
/src/routes/_error.svelte:
--------------------------------------------------------------------------------
1 |
7 |
8 |
29 |
30 |
31 | {status}
32 |
33 |
34 | {status}
35 |
36 | {error.message}
37 |
38 | {#if dev && error.stack}
39 | {error.stack}
40 | {/if}
--------------------------------------------------------------------------------
/src/template.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | %sapper.base%
11 |
12 |
13 |
14 |
15 |
16 |
17 |
20 | %sapper.scripts%
21 |
22 |
25 | %sapper.styles%
26 |
27 |
29 | %sapper.head%
30 |
31 |
32 |
34 | %sapper.html%
35 |
36 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "TODO",
3 | "description": "TODO",
4 | "version": "0.0.1",
5 | "scripts": {
6 | "sapper": "sapper dev",
7 | "build": "sapper build --legacy",
8 | "export": "sapper export --legacy",
9 | "start": "node __sapper__/build",
10 | "reload": "touch src/routes/_layout.svelte",
11 | "scss": "sass --no-source-map -s compressed ./static/",
12 | "autoprefixer": "postcss -u autoprefixer -r ./static/global.css --no-map",
13 | "build:css": "run-s 'scss' 'autoprefixer' 'reload'",
14 | "watch:css": "onchange './static/**/*.scss' -- npm run build:css",
15 | "dev": "run-p 'sapper' 'watch:css'"
16 | },
17 | "dependencies": {
18 | "compression": "1.7.4",
19 | "polka": "0.5.2",
20 | "sirv": "1.0.12"
21 | },
22 | "devDependencies": {
23 | "@babel/core": "^7.14.3",
24 | "@babel/plugin-syntax-dynamic-import": "7.8.3",
25 | "@babel/plugin-transform-runtime": "7.14.3",
26 | "@babel/preset-env": "^7.14.4",
27 | "@babel/runtime": "^7.14.0",
28 | "@neuekit/reboot": "1.10.2",
29 | "@neuekit/utilities": "1.8.0",
30 | "@rollup/plugin-alias": "3.1.2",
31 | "@rollup/plugin-babel": "^5.3.0",
32 | "@rollup/plugin-commonjs": "19.0.0",
33 | "@rollup/plugin-node-resolve": "^13.0.0",
34 | "@rollup/plugin-replace": "^2.4.2",
35 | "@rollup/plugin-url": "^6.0.0",
36 | "autoprefixer": "^10.2.6",
37 | "dotenv": "^10.0.0",
38 | "npm-run-all": "4.1.5",
39 | "onchange": "7.1.0",
40 | "postcss": "8.3.0",
41 | "postcss-cli": "8.3.1",
42 | "rollup": "^2.50.4",
43 | "rollup-plugin-svelte": "7.1.0",
44 | "rollup-plugin-terser": "7.0.2",
45 | "sapper": "0.29.1",
46 | "sass": "^1.34.0",
47 | "svelte": "^3.38.2",
48 | "svelte-preprocess": "^4.7.3"
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 |
4 | An opinionated starter template based on the Rollup Sapper template. 🚀
5 |
6 |
7 | ## Hello 👋,
8 |
9 | 
10 |
11 | The purpose of this repo was to save me time by doing all of the things I do on every Sapper project, but hey you might find it useful too right?!
12 |
13 | To clone it and get started:
14 |
15 | ```bash
16 | # for Rollup
17 | npx degit matt3224/sapper-start my-app
18 | cd my-app
19 | npm install
20 | npm run dev
21 | ```
22 |
23 | Open up [localhost:3000](http://localhost:3000) and start clicking around.
24 |
25 | Consult [sapper.svelte.dev](https://sapper.svelte.dev) for help getting started with Sapper itself.
26 |
27 |
28 |
29 | If you use this a lot you can alias it by putting this in your zshrc or bashrc:
30 | ```bash
31 | alias sapper-start='npx degit https://github.com/matt3224/sapper-start .'
32 | ```
33 | Then simply cd into your empty project directory and run `sapper-start`
34 |
35 |
36 | ## How does this differ from sapper-template?
37 |
38 | Good question, I found myself doing these changes on every project and thought I'd save myself and you some time by making a little repo you could import just like the official sapper-template.
39 |
40 | Here are the differences:
41 | * Adds SCSS support out-of-the-box
42 | * Adds css reset based on normalize
43 | * Adds `numworker: 1` in rollup.config to prevent crash on servers with a single processor core
44 | * Adds a light suggested structure for library and component folders
45 | * Adds an optional utility package (Tree-shaken so if you don't use it you don't get it)
46 | * Adds the package version in the html head as a meta tag
47 | * Adds PWA support by default
48 | * Adds standard allow all robots.txt
49 | * Adds server.js middleware to redirect trailing slash urls to non trailing slash
50 | * Adds ability to bypass relative paths with `~` & auto resolves .js, .mjs, .html, .svelte, .scss
51 | * eg. `import { util } from '~/utils';` instead of `import { util } from '../../utils.js';`
52 | * Changes global css link to a client.js import which prevents extraneous caching
53 | * Changes favicons with broader coverage
54 | * Changes single quotes to double in html/svelte files
55 | * Removes default routes, components and emptied index & layout
56 | * Removes custom browserslist config to use default
57 |
58 |
59 | ## Bugs and feedback
60 |
61 | If you run into an issue which you don't see in the normal sapper-template, open an [issue](https://github.com/matt3224/sapper-start/issues).
62 |
--------------------------------------------------------------------------------
/static/global.css:
--------------------------------------------------------------------------------
1 | *{background-size:cover}*,::before,::after{background-repeat:no-repeat;background-position:center;box-sizing:border-box;position:relative}::before,::after{text-decoration:inherit;vertical-align:inherit}html{cursor:default;line-height:1.4;-moz-tab-size:3;-o-tab-size:3;tab-size:3;-webkit-tap-highlight-color:transparent;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}h1{font-size:2em;margin:.6875em 0}dl dl,dl ol,dl ul,ol dl,ul dl,ol ol,ol ul,ul ol,ul ul{margin:0}hr{height:0;overflow:visible}main,details{display:block}nav ol,nav ul{list-style:none;padding:0}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}abbr[title]{text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted}b,strong{font-weight:bolder}small{font-size:83.3333%}audio,canvas,iframe,img,svg,video{vertical-align:middle}img,iframe{border-style:none}img,picture,svg{display:block;max-width:100%}img{height:auto}figure{margin:0}svg:not([fill]){fill:currentColor}svg:not(:root){overflow:hidden}table{border-collapse:collapse}button,input,optgroup,select,textarea{background-color:transparent;border-style:none;font-family:inherit;font-size:inherit;line-height:inherit;margin:0;word-break:normal}button,input{color:inherit;overflow:visible}button,select{text-transform:none}button,[type=button],[type=reset],[type=submit]{cursor:pointer;-webkit-appearance:button}fieldset{border:1px solid silver;padding:.35em .75em .625em}legend{color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{display:inline-block;vertical-align:baseline}textarea{overflow:auto;resize:vertical}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}::-webkit-input-placeholder{color:inherit;opacity:.5}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}::-moz-focus-inner{border-style:none;padding:0}:-moz-focusring{outline:1px dotted ButtonText}:-moz-ui-invalid{box-shadow:none}dialog{background-color:#fff;border:solid;color:#000;display:block;height:-moz-fit-content;height:-webkit-fit-content;height:fit-content;left:0;margin:auto;padding:1em;position:absolute;right:0;width:-moz-fit-content;width:-webkit-fit-content;width:fit-content}dialog:not([open]){display:none}summary{display:list-item}template{display:none}a,area,button,input,label,select,summary,textarea,[tabindex]{touch-action:manipulation}[aria-busy=true]{cursor:progress}[aria-controls]{cursor:pointer}[aria-disabled=true],[disabled]{cursor:not-allowed}a:hover{outline-width:0}[aria-hidden=false][hidden]{display:initial}[aria-hidden=false][hidden]:not(:focus){clip:rect(0, 0, 0, 0);position:absolute}html{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji;-webkit-font-smoothing:antialiased}a{text-decoration:none}
2 |
--------------------------------------------------------------------------------
/src/service-worker.js:
--------------------------------------------------------------------------------
1 | import { timestamp, files, shell } from '@sapper/service-worker';
2 |
3 | const ASSETS = `cache${timestamp}`;
4 |
5 | // `shell` is an array of all the files generated by the bundler,
6 | // `files` is an array of everything in the `static` directory
7 | const to_cache = shell.concat(files);
8 | const staticAssets = new Set(to_cache);
9 |
10 | self.addEventListener('install', event => {
11 | event.waitUntil(
12 | caches
13 | .open(ASSETS)
14 | .then(cache => cache.addAll(to_cache))
15 | .then(() => {
16 | self.skipWaiting();
17 | })
18 | );
19 | });
20 |
21 | self.addEventListener('activate', event => {
22 | event.waitUntil(
23 | caches.keys().then(async keys => {
24 | // delete old caches
25 | for (const key of keys) {
26 | if (key !== ASSETS) await caches.delete(key);
27 | }
28 |
29 | self.clients.claim();
30 | })
31 | );
32 | });
33 |
34 |
35 | /**
36 | * Fetch the asset from the network and store it in the cache.
37 | * Fall back to the cache if the user is offline.
38 | */
39 | async function fetchAndCache(request) {
40 | const cache = await caches.open(`offline${timestamp}`)
41 |
42 | try {
43 | const response = await fetch(request);
44 | cache.put(request, response.clone());
45 | return response;
46 | } catch (err) {
47 | const response = await cache.match(request);
48 | if (response) return response;
49 |
50 | throw err;
51 | }
52 | }
53 |
54 | self.addEventListener('fetch', event => {
55 | if (event.request.method !== 'GET' || event.request.headers.has('range')) return;
56 |
57 | const url = new URL(event.request.url);
58 |
59 | // don't try to handle e.g. data: URIs
60 | const isHttp = url.protocol.startsWith('http');
61 | const isDevServerRequest = url.hostname === self.location.hostname && url.port !== self.location.port;
62 | const isStaticAsset = url.host === self.location.host && staticAssets.has(url.pathname);
63 | const skipBecauseUncached = event.request.cache === 'only-if-cached' && !isStaticAsset;
64 |
65 | if (isHttp && !isDevServerRequest && !skipBecauseUncached) {
66 | event.respondWith(
67 | (async () => {
68 | // always serve static files and bundler-generated assets from cache.
69 | // if your application has other URLs with data that will never change,
70 | // set this variable to true for them and they will only be fetched once.
71 | const cachedAsset = isStaticAsset && await caches.match(event.request);
72 |
73 | // for pages, you might want to serve a shell `service-worker-index.html` file,
74 | // which Sapper has generated for you. It's not right for every
75 | // app, but if it's right for yours then uncomment this section
76 | /*
77 | if (!cachedAsset && url.origin === self.origin && routes.find(route => route.pattern.test(url.pathname))) {
78 | return caches.match('/service-worker-index.html');
79 | }
80 | */
81 |
82 | return cachedAsset || fetchAndCache(event.request);
83 | })()
84 | );
85 | }
86 | });
87 |
--------------------------------------------------------------------------------
/static/favicons/safari-pinned-tab.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | /* Rollup plugins */
2 | import alias from '@rollup/plugin-alias';
3 | import babel from '@rollup/plugin-babel';
4 | import commonjs from '@rollup/plugin-commonjs';
5 | import replace from '@rollup/plugin-replace';
6 | import resolve from '@rollup/plugin-node-resolve';
7 | import url from '@rollup/plugin-url';
8 | import svelte from 'rollup-plugin-svelte';
9 | import { terser } from 'rollup-plugin-terser';
10 |
11 | /* Packages */
12 | import autoprefixer from 'autoprefixer';
13 | import config from 'sapper/config/rollup.js';
14 | import 'dotenv/config';
15 | import path from 'path';
16 | import pkg from './package.json';
17 | import { scss, postcss } from 'svelte-preprocess';
18 |
19 | /* Assignments */
20 | const mode = process.env.NODE_ENV;
21 | const dev = mode === 'development';
22 | const legacy = !!process.env.SAPPER_LEGACY_BUILD;
23 |
24 | const onwarn = (warning, onwarn) =>
25 | (warning.code === 'MISSING_EXPORT' && /'preload'/.test(warning.message)) ||
26 | (warning.code === 'CIRCULAR_DEPENDENCY' && /[/\\]@sapper[/\\]/.test(warning.message)) ||
27 | onwarn(warning);
28 |
29 | const customResolver = resolve({
30 | extensions: ['.js', '.mjs', '.html', '.svelte', '.scss', '.json']
31 | });
32 |
33 | const aliasconfig = {
34 | customResolver,
35 | entries: [{ find: '~', replacement: path.join(__dirname, './src') }]
36 | };
37 |
38 | const preprocess = [
39 | scss({ sourceMap: false }),
40 | postcss({ plugins: [ autoprefixer ] })
41 | ];
42 |
43 | const replaceconfig = {
44 | preventAssignment: false,
45 | values: {
46 | 'process.browser': true,
47 | 'process.env.NODE_ENV': JSON.stringify(mode),
48 | 'pkg.version': pkg.version
49 | }
50 | };
51 |
52 | /* Config */
53 | export default {
54 |
55 | client: {
56 | input: config.client.input(),
57 | output: config.client.output(),
58 | onwarn,
59 | plugins: [
60 | alias(aliasconfig),
61 | commonjs(),
62 | customResolver,
63 | replace(replaceconfig),
64 | resolve({ browser: true, dedupe: ['svelte'] }),
65 | svelte({
66 | compilerOptions: {
67 | dev,
68 | hydratable: true
69 | },
70 | preprocess
71 | }),
72 | url({
73 | sourceDir: path.resolve(__dirname, 'src/node_modules/images'),
74 | publicPath: '/client/'
75 | }),
76 | legacy && babel({
77 | exclude: ['node_modules/@babel/**'],
78 | extensions: ['.js', '.mjs', '.html', '.svelte'],
79 | plugins: [
80 | '@babel/plugin-syntax-dynamic-import',
81 | ['@babel/plugin-transform-runtime', {
82 | useESModules: true
83 | }]
84 | ],
85 | presets: [['@babel/preset-env']],
86 | babelHelpers: 'runtime',
87 | }),
88 |
89 | !dev && terser({ module: true, numWorkers: 1 })
90 | ],
91 | preserveEntrySignatures: false
92 | },
93 |
94 | server: {
95 | input: config.server.input(),
96 | output: config.server.output(),
97 | onwarn,
98 | plugins: [
99 | alias(aliasconfig),
100 | commonjs(),
101 | customResolver,
102 | replace({...replaceconfig, 'process.browser': false}),
103 | resolve({ dedupe: ['svelte'] }),
104 | svelte({
105 | compilerOptions: {
106 | dev,
107 | generate: 'ssr',
108 | hydratable: true
109 | },
110 | emitCss: false,
111 | preprocess
112 | }),
113 | url({
114 | sourceDir: path.resolve(__dirname, 'src/node_modules/images'),
115 | publicPath: '/client/',
116 | emitFiles: false // already emitted by client build
117 | }),
118 | ],
119 | external: Object.keys(pkg.dependencies).concat(require('module').builtinModules),
120 | preserveEntrySignatures: 'strict'
121 | },
122 |
123 | serviceworker: {
124 | input: config.serviceworker.input(),
125 | output: config.serviceworker.output(),
126 | onwarn,
127 | plugins: [
128 | alias(aliasconfig),
129 | commonjs(),
130 | customResolver,
131 | replace(replaceconfig),
132 | resolve(),
133 | !dev && terser({ numWorkers: 1 })
134 | ],
135 | preserveEntrySignatures: false
136 | }
137 | };
--------------------------------------------------------------------------------