├── .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 | Avatar 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 | --------------------------------------------------------------------------------