├── .eslintignore
├── .prettierignore
├── src
├── styles
│ ├── components
│ │ ├── index.css
│ │ └── button.css
│ └── index.css
├── client.js
├── config.js
├── routes
│ ├── _layout.svelte
│ ├── _error.svelte
│ └── index.svelte
├── server.js
├── components
│ └── ProfileCard.svelte
├── template.html
└── service-worker.js
├── static
├── favicon.png
├── logo-192.png
├── logo-512.png
└── manifest.json
├── .prettierrc
├── .gitignore
├── tailwind.config.js
├── .eslintrc.json
├── README.md
├── package.json
└── rollup.config.js
/.eslintignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | static
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | static
--------------------------------------------------------------------------------
/src/styles/components/index.css:
--------------------------------------------------------------------------------
1 | @import "./button.css";
--------------------------------------------------------------------------------
/static/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EricPKerr/sapper-tailwindcss/HEAD/static/favicon.png
--------------------------------------------------------------------------------
/static/logo-192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EricPKerr/sapper-tailwindcss/HEAD/static/logo-192.png
--------------------------------------------------------------------------------
/static/logo-512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EricPKerr/sapper-tailwindcss/HEAD/static/logo-512.png
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "semi": true,
3 | "tabWidth": 2,
4 | "singleQuote": true,
5 | "printWidth": 80
6 | }
7 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | yarn-error.log
4 | /__sapper__
5 | .vscode
6 | /static/global.css
7 | .env
8 |
--------------------------------------------------------------------------------
/src/client.js:
--------------------------------------------------------------------------------
1 | import * as sapper from '@sapper/app';
2 |
3 | sapper.start({
4 | target: document.querySelector('#app')
5 | });
6 |
--------------------------------------------------------------------------------
/src/styles/index.css:
--------------------------------------------------------------------------------
1 | @import "tailwindcss/base";
2 | @import "tailwindcss/components";
3 |
4 | @import './components';
5 |
6 | @import "tailwindcss/utilities";
--------------------------------------------------------------------------------
/src/config.js:
--------------------------------------------------------------------------------
1 | import dotenv from 'dotenv';
2 |
3 | const result = dotenv.config();
4 |
5 | if (result.error) {
6 | throw result.error;
7 | }
8 |
9 | export default result.parsed;
10 |
--------------------------------------------------------------------------------
/src/routes/_layout.svelte:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | const colors = {
2 | 'primary-blue': '#0e2730'
3 | };
4 |
5 | module.exports = {
6 | theme: {
7 | container: {
8 | center: true,
9 | padding: '2rem'
10 | },
11 | extend: {
12 | colors
13 | }
14 | },
15 | variants: {},
16 | plugins: []
17 | };
18 |
--------------------------------------------------------------------------------
/src/styles/components/button.css:
--------------------------------------------------------------------------------
1 | .btn {
2 | @apply inline-block font-bold py-2 px-4 rounded no-underline;
3 | }
4 |
5 | .btn:disabled {
6 | @apply opacity-50 cursor-not-allowed;
7 | }
8 |
9 | .btn-primary {
10 | @apply bg-blue-500 text-white;
11 | }
12 |
13 | .btn-primary:hover {
14 | @apply bg-blue-600;
15 | }
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["eslint:recommended"],
3 | "parserOptions": {
4 | "ecmaVersion": 2019,
5 | "sourceType": "module"
6 | },
7 | "rules": {
8 | "no-unused-vars": ["error", { "args": "none" }],
9 | "no-console": "off"
10 | },
11 | "env": {
12 | "es6": true,
13 | "browser": true,
14 | "node": true
15 | },
16 | "plugins": ["prettier", "svelte3"]
17 | }
18 |
--------------------------------------------------------------------------------
/static/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "background_color": "#ffffff",
3 | "theme_color": "#333333",
4 | "name": "TODO",
5 | "short_name": "TODO",
6 | "display": "minimal-ui",
7 | "start_url": "/",
8 | "icons": [
9 | {
10 | "src": "logo-192.png",
11 | "sizes": "192x192",
12 | "type": "image/png"
13 | },
14 | {
15 | "src": "logo-512.png",
16 | "sizes": "512x512",
17 | "type": "image/png"
18 | }
19 | ]
20 | }
21 |
--------------------------------------------------------------------------------
/src/routes/_error.svelte:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 | {status}
10 |
11 |
12 |
13 |
{status}
14 |
15 |
{error.message}
16 |
17 | {#if dev && error.stack}
18 |
{error.stack}
19 | {/if}
20 |
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Sapper TailwindCSS Starter
2 |
3 | This pulls together Sapper, Svelte 3, Tailwind CSS, and PurgeCSS to create a simple starter kit for the sveltest apps ever.
4 |
5 | ## Development
6 |
7 | To clone it and get started:
8 |
9 | ```bash
10 | git clone https://github.com/EricPKerr/sapper-tailwindcss.git
11 | cd sapper-tailwindcss
12 | npm install # or yarn!
13 |
14 | touch .env
15 |
16 | npm run dev
17 | ```
18 |
19 | Open up [localhost:3000](http://localhost:3000).
20 |
--------------------------------------------------------------------------------
/src/routes/index.svelte:
--------------------------------------------------------------------------------
1 |
11 |
12 |
17 |
18 |
19 | Sapper + TailwindCSS Starter
20 |
21 |
22 | {#each users as user}
23 |
24 | {/each}
25 |
--------------------------------------------------------------------------------
/src/server.js:
--------------------------------------------------------------------------------
1 | import config from './config';
2 |
3 | import sirv from 'sirv';
4 | import polka from 'polka';
5 | import compression from 'compression';
6 | import * as sapper from '@sapper/server';
7 |
8 | import './styles/index.css';
9 |
10 | const { PORT, NODE_ENV } = process.env;
11 | const dev = NODE_ENV === 'development';
12 |
13 | polka()
14 | .use(
15 | compression({ threshold: 0 }),
16 | sirv('static', { dev }),
17 | sapper.middleware({
18 | session: (req, res) => ({
19 | config
20 | })
21 | })
22 | )
23 | .listen(PORT, err => {
24 | if (err) console.log('error', err);
25 | });
26 |
--------------------------------------------------------------------------------
/src/components/ProfileCard.svelte:
--------------------------------------------------------------------------------
1 |
4 |
5 |
7 |
8 |

12 |
13 |
{user.name.first} {user.name.last}!
14 |
User Profession
15 |
{user.email}
16 |
{user.cell}
17 |
18 |
Email
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/src/template.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | %sapper.base%
9 |
10 |
11 |
12 |
13 |
14 |
17 | %sapper.styles%
18 |
19 |
21 | %sapper.head%
22 |
23 |
24 |
25 |
27 | %sapper.html%
28 |
29 |
32 | %sapper.scripts%
33 |
34 |
35 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "sapper-tailwindcss",
3 | "description": "",
4 | "version": "0.0.1",
5 | "repository": {
6 | "type": "git",
7 | "url": "https://github.com/EricPKerr/sapper-tailwindcss"
8 | },
9 | "scripts": {
10 | "dev": "sapper dev",
11 | "build": "sapper build --legacy",
12 | "export": "sapper export --legacy",
13 | "start": "node __sapper__/build",
14 | "all": "npm run build && npm run start",
15 | "update": "ncu -u && npm install && git add package.json package-lock.json && git commit -m 'Update package.json'",
16 | "precommit": "node -v && lint-staged"
17 | },
18 | "husky": {
19 | "hooks": {
20 | "pre-commit": "lint-staged"
21 | }
22 | },
23 | "lint-staged": {
24 | "*.{js,svelte}": [
25 | "eslint"
26 | ],
27 | "*.{ts,tsx,js,jsx,json,css,md,svelte}": [
28 | "prettier --write",
29 | "git add"
30 | ]
31 | },
32 | "dependencies": {
33 | "compression": "^1.7.4",
34 | "dotenv": "^8.2.0",
35 | "polka": "^0.5.2",
36 | "sirv": "^0.4.2"
37 | },
38 | "devDependencies": {
39 | "@babel/core": "^7.7.7",
40 | "@babel/plugin-syntax-dynamic-import": "^7.7.4",
41 | "@babel/plugin-transform-runtime": "^7.7.6",
42 | "@babel/preset-env": "^7.7.7",
43 | "@babel/runtime": "^7.7.7",
44 | "@fullhuman/postcss-purgecss": "^1.3.0",
45 | "autoprefixer": "^9.7.3",
46 | "classnames": "^2.2.6",
47 | "cssnano": "^4.1.10",
48 | "eslint": "^6.8.0",
49 | "eslint-config-prettier": "^6.9.0",
50 | "eslint-plugin-prettier": "^3.1.2",
51 | "eslint-plugin-svelte3": "^2.7.3",
52 | "husky": "^3.1.0",
53 | "lint-staged": "^9.5.0",
54 | "mixin-deep": ">=2.0.1",
55 | "npm-run-all": "^4.1.5",
56 | "postcss-fail-on-warn": "^0.1.0",
57 | "postcss-import": "^12.0.1",
58 | "prettier": "^1.19.1",
59 | "prettier-plugin-svelte": "^0.7.0",
60 | "ramda": "^0.26.1",
61 | "rollup": "^1.27.14",
62 | "rollup-plugin-babel": "^4.3.3",
63 | "rollup-plugin-commonjs": "^10.1.0",
64 | "rollup-plugin-json": "^4.0.0",
65 | "rollup-plugin-node-resolve": "^5.2.0",
66 | "rollup-plugin-postcss": "^2.0.3",
67 | "rollup-plugin-replace": "^2.2.0",
68 | "rollup-plugin-svelte": "^5.1.1",
69 | "rollup-plugin-terser": "^5.1.3",
70 | "sapper": "^0.27.11",
71 | "set-value": ">=3.0.1",
72 | "svelte": "^3.16.7",
73 | "svelte-transitions": "^1.2.0",
74 | "tailwindcss": "^1.1.4"
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/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 cached = 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 | self.addEventListener('fetch', event => {
35 | if (event.request.method !== 'GET' || event.request.headers.has('range'))
36 | return;
37 |
38 | const url = new URL(event.request.url);
39 |
40 | // don't try to handle e.g. data: URIs
41 | if (!url.protocol.startsWith('http')) return;
42 |
43 | // ignore dev server requests
44 | if (
45 | url.hostname === self.location.hostname &&
46 | url.port !== self.location.port
47 | )
48 | return;
49 |
50 | // always serve static files and bundler-generated assets from cache
51 | if (url.host === self.location.host && cached.has(url.pathname)) {
52 | event.respondWith(caches.match(event.request));
53 | return;
54 | }
55 |
56 | // for pages, you might want to serve a shell `service-worker-index.html` file,
57 | // which Sapper has generated for you. It's not right for every
58 | // app, but if it's right for yours then uncomment this section
59 | /*
60 | if (url.origin === self.origin && routes.find(route => route.pattern.test(url.pathname))) {
61 | event.respondWith(caches.match('/service-worker-index.html'));
62 | return;
63 | }
64 | */
65 |
66 | if (event.request.cache === 'only-if-cached') return;
67 |
68 | // for everything else, try the network first, falling back to
69 | // cache if the user is offline. (If the pages never change, you
70 | // might prefer a cache-first approach to a network-first one.)
71 | event.respondWith(
72 | caches.open(`offline${timestamp}`).then(async cache => {
73 | try {
74 | const response = await fetch(event.request);
75 | cache.put(event.request, response.clone());
76 | return response;
77 | } catch (err) {
78 | const response = await cache.match(event.request);
79 | if (response) return response;
80 |
81 | throw err;
82 | }
83 | })
84 | );
85 | });
86 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | import babel from 'rollup-plugin-babel';
2 | import commonjs from 'rollup-plugin-commonjs';
3 | import json from 'rollup-plugin-json';
4 | import resolve from 'rollup-plugin-node-resolve';
5 | import postcss from 'rollup-plugin-postcss';
6 | import replace from 'rollup-plugin-replace';
7 | import svelte from 'rollup-plugin-svelte';
8 | import { terser } from 'rollup-plugin-terser';
9 | import config from 'sapper/config/rollup.js';
10 | import pkg from './package.json';
11 |
12 | const mode = process.env.NODE_ENV;
13 | const dev = mode === 'development';
14 | const legacy = !!process.env.SAPPER_LEGACY_BUILD;
15 |
16 | const onwarn = (warning, onwarn) =>
17 | (warning.code === 'CIRCULAR_DEPENDENCY' &&
18 | /[/\\]@sapper[/\\]/.test(warning.message)) ||
19 | onwarn(warning);
20 |
21 | const dedupe = importee =>
22 | importee === 'svelte' || importee.startsWith('svelte/');
23 |
24 | const purgecss = require('@fullhuman/postcss-purgecss')({
25 | // Specify the paths to all of the template files in your project
26 | content: ['./src/**/*.html', './src/**/*.svelte', './src/**/*.css'],
27 |
28 | // Include any special characters you're using in this regular expression
29 | defaultExtractor: content => content.match(/[A-Za-z0-9-_:/]+/g) || []
30 | });
31 |
32 | export default {
33 | client: {
34 | input: config.client.input(),
35 | output: config.client.output(),
36 | plugins: [
37 | replace({
38 | 'process.browser': true,
39 | 'process.env.NODE_ENV': JSON.stringify(mode)
40 | }),
41 | svelte({
42 | dev,
43 | hydratable: true,
44 | emitCss: true
45 | }),
46 | resolve({
47 | browser: true,
48 | dedupe
49 | }),
50 | commonjs(),
51 | json(),
52 |
53 | legacy &&
54 | babel({
55 | extensions: ['.js', '.mjs', '.html', '.svelte'],
56 | runtimeHelpers: true,
57 | exclude: ['node_modules/@babel/**'],
58 | presets: [
59 | [
60 | '@babel/preset-env',
61 | {
62 | targets: '> 0.25%, not dead'
63 | }
64 | ]
65 | ],
66 | plugins: [
67 | '@babel/plugin-syntax-dynamic-import',
68 | [
69 | '@babel/plugin-transform-runtime',
70 | {
71 | useESModules: true
72 | }
73 | ]
74 | ]
75 | }),
76 |
77 | !dev &&
78 | terser({
79 | module: true
80 | })
81 | ],
82 | onwarn
83 | },
84 |
85 | server: {
86 | input: config.server.input(),
87 | output: config.server.output(),
88 | plugins: [
89 | replace({
90 | 'process.browser': false,
91 | 'process.env.NODE_ENV': JSON.stringify(mode)
92 | }),
93 | svelte({
94 | generate: 'ssr',
95 | dev
96 | }),
97 | postcss({
98 | extract: './static/global.css',
99 | plugins: [
100 | require('postcss-import'),
101 | require('tailwindcss'), // See tailwind.config.js
102 | require('autoprefixer'),
103 | require('postcss-fail-on-warn'),
104 | // Do not purge the CSS in dev mode to be able to play with classes in the browser dev-tools.
105 | !dev && purgecss,
106 | !dev &&
107 | require('cssnano')({
108 | preset: 'default'
109 | })
110 | ].filter(Boolean)
111 | }),
112 | resolve({
113 | dedupe
114 | }),
115 | commonjs(),
116 | json()
117 | ],
118 | external: Object.keys(pkg.dependencies).concat(
119 | require('module').builtinModules ||
120 | Object.keys(process.binding('natives'))
121 | ),
122 | onwarn
123 | },
124 |
125 | serviceworker: {
126 | input: config.serviceworker.input(),
127 | output: config.serviceworker.output(),
128 | plugins: [
129 | resolve(),
130 | replace({
131 | 'process.browser': true,
132 | 'process.env.NODE_ENV': JSON.stringify(mode)
133 | }),
134 | commonjs(),
135 | !dev && terser()
136 | ],
137 | onwarn
138 | }
139 | };
140 |
--------------------------------------------------------------------------------