├── .gitignore ├── .vscode └── extensions.json ├── src ├── repositories │ ├── book │ │ ├── index.ts │ │ ├── BookRepository.ts │ │ └── types.ts │ ├── httpClient.ts │ └── RepositoryFactory.ts ├── Tailwindcss.svelte ├── main.ts ├── router │ └── index.ts ├── components │ ├── Spinner.svelte │ ├── Row.svelte │ ├── SearchBar.svelte │ ├── Header.svelte │ ├── BookCard.svelte │ └── BookInfo.svelte ├── App.svelte ├── store │ └── book │ │ └── index.ts └── pages │ ├── DetailsBook.svelte │ └── SearchBook.svelte ├── public ├── favicon.png └── index.html ├── postcss.config.js ├── tsconfig.json ├── tailwind.config.js ├── README.md ├── package.json └── rollup.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | /public/build/ 3 | 4 | .DS_Store 5 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["svelte.svelte-vscode"] 3 | } 4 | -------------------------------------------------------------------------------- /src/repositories/book/index.ts: -------------------------------------------------------------------------------- 1 | export * from './types' 2 | export * from './BookRepository' -------------------------------------------------------------------------------- /public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azukiazusa1/svelte-book-review-app/HEAD/public/favicon.png -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /src/Tailwindcss.svelte: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import App from './App.svelte'; 2 | 3 | const app = new App({ 4 | target: document.body, 5 | }); 6 | 7 | export default app; -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tsconfig/svelte/tsconfig.json", 3 | 4 | "include": ["src/**/*"], 5 | "exclude": ["node_modules/*", "__sapper__/*", "public/*"] 6 | } -------------------------------------------------------------------------------- /src/repositories/httpClient.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | 3 | const httpClient = axios.create({ 4 | baseURL: 'https://www.googleapis.com/books/v1/volumes' 5 | }) 6 | 7 | export { httpClient } -------------------------------------------------------------------------------- /src/router/index.ts: -------------------------------------------------------------------------------- 1 | import SearchBook from '../pages/SearchBook.svelte' 2 | import DetailsBook from '../pages/DetailsBook.svelte' 3 | 4 | export const routes = { 5 | '/': SearchBook, 6 | '/books/:id': DetailsBook, 7 | } -------------------------------------------------------------------------------- /src/repositories/RepositoryFactory.ts: -------------------------------------------------------------------------------- 1 | import { BookRepository, BookRepositoryInterface } from './book' 2 | 3 | export const BOOK = Symbol('book') 4 | 5 | export interface Repositories { 6 | [BOOK]: BookRepositoryInterface; 7 | } 8 | 9 | export default { 10 | [BOOK]: new BookRepository() 11 | } as Repositories 12 | -------------------------------------------------------------------------------- /src/components/Spinner.svelte: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/components/Row.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 |
6 |
7 | {dt} 8 |
9 |
10 | 11 |
12 |
-------------------------------------------------------------------------------- /src/App.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 |
10 |
11 |
12 | 13 |
14 |
-------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Svelte app 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/repositories/book/BookRepository.ts: -------------------------------------------------------------------------------- 1 | import type { BookItem, BookRepositoryInterface, Params, Result } from './types' 2 | import { httpClient } from '../httpClient' 3 | 4 | export class BookRepository implements BookRepositoryInterface { 5 | async get(params: Params) { 6 | const { data } = await httpClient.get('/', { params }) 7 | return data 8 | } 9 | 10 | async find(id: string) { 11 | const { data } = await httpClient.get(`/${id}`) 12 | return data 13 | } 14 | } -------------------------------------------------------------------------------- /src/components/SearchBar.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 |
6 | 7 | 12 |
-------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | purge: { 3 | enabled: !process.env.ROLLUP_WATCH, 4 | mode: 'all', 5 | content: ['./**/**/*.html', './**/**/*.svelte'], 6 | 7 | options: { 8 | whitelistPatterns: [/svelte-/], 9 | defaultExtractor: (content) => 10 | [...content.matchAll(/(?:class:)*([\w\d-/:%.]+)/gm)].map(([_match, group, ..._rest]) => group), 11 | }, 12 | }, 13 | darkMode: false, // or 'media' or 'class' 14 | theme: { 15 | extend: {}, 16 | }, 17 | variants: { 18 | extend: {}, 19 | }, 20 | plugins: [], 21 | } 22 | -------------------------------------------------------------------------------- /src/store/book/index.ts: -------------------------------------------------------------------------------- 1 | import { writable, derived } from 'svelte/store' 2 | import type { BookItem } from '../../repositories/book' 3 | 4 | const useBookStore = () => { 5 | const { subscribe, set, update } = writable([]) 6 | const reset = () => set([]) 7 | const add = (newBooks: BookItem[]) => update((books: BookItem[]) => { 8 | return [...books, ...newBooks] 9 | }) 10 | 11 | return { 12 | subscribe, 13 | reset, 14 | add 15 | } 16 | } 17 | 18 | export const books = useBookStore() 19 | 20 | export const find = (id: string) => { 21 | return derived(books, $books => $books.find(book => book.id === id)) 22 | } 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Svelte + TypeScript + tailwindcssで本検索サイトのチュートリアルです。 2 | 3 | https://zenn.dev/azukiazusa/books/9dac1257c77f47 4 | 5 | 成果物は以下のようなアプリケーションです。 6 | 7 | 本の検索ページ 8 | 9 | ![svelte-book](https://user-images.githubusercontent.com/59350345/107143910-01fa3200-697b-11eb-84e9-2dd5463076b6.gif) 10 | 11 | 本の詳細ページ 12 | 13 | スクリーンショット 2021-02-07 19 11 10 14 | 15 | 16 | 17 | 18 | Svelteを使ってアプリケーションを作成1から作成することができます。 19 | 以下のことが学べます。 20 | 21 | - Svelteの基礎文法 22 | - Svelteのルーティング 23 | - Svelteのストア 24 | 25 | HTML・CSS・JavaScriptの基礎的な理解がある人が対象です。 26 | -------------------------------------------------------------------------------- /src/repositories/book/types.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Google Books APIのレスポンス 3 | */ 4 | export interface Result { 5 | items: BookItem[]; 6 | kind: string; 7 | totalItems: number; 8 | } 9 | 10 | /** 11 | * 本の情報 12 | */ 13 | export interface BookItem { 14 | id: string; 15 | volumeInfo: { 16 | title: string; 17 | authors?: string[]; 18 | publishedDate?: string; 19 | description?: string; 20 | publisher?: string; 21 | imageLinks?: { 22 | smallThumbnail: string; 23 | thumbnail: string; 24 | }; 25 | pageCount: number; 26 | previewLink?: string; 27 | }; 28 | saleInfo?: { 29 | listPrice: { 30 | amount: number; 31 | }; 32 | }; 33 | } 34 | 35 | /** 36 | * query parameters 37 | */ 38 | export interface Params { 39 | q: string; 40 | startIndex?: number; 41 | } 42 | 43 | export interface BookRepositoryInterface { 44 | get(params: Params): Promise; 45 | find(id: string): Promise; 46 | } 47 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "svelte-app", 3 | "version": "1.0.0", 4 | "scripts": { 5 | "build": "rollup -c", 6 | "dev": "rollup -c -w", 7 | "start": "sirv public", 8 | "validate": "svelte-check" 9 | }, 10 | "devDependencies": { 11 | "@rollup/plugin-commonjs": "^16.0.0", 12 | "@rollup/plugin-node-resolve": "^10.0.0", 13 | "autoprefixer": "^10.1.0", 14 | "postcss": "^8.2.2", 15 | "postcss-load-config": "^3.0.0", 16 | "rollup": "^2.3.4", 17 | "rollup-plugin-css-only": "^3.1.0", 18 | "rollup-plugin-livereload": "^2.0.0", 19 | "rollup-plugin-svelte": "^7.0.0", 20 | "rollup-plugin-terser": "^7.0.0", 21 | "svelte": "^3.0.0", 22 | "svelte-preprocess": "^4.0.0", 23 | "tailwindcss": "^2.0.2", 24 | "svelte-check": "^1.0.0", 25 | "@rollup/plugin-typescript": "^6.0.0", 26 | "typescript": "^3.9.3", 27 | "tslib": "^2.0.0", 28 | "@tsconfig/svelte": "^1.0.0" 29 | }, 30 | "dependencies": { 31 | "axios": "^0.21.1", 32 | "sirv-cli": "^1.0.0", 33 | "svelte-infinite-scroll": "^1.5.2", 34 | "svelte-spa-router": "^3.1.0" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/pages/DetailsBook.svelte: -------------------------------------------------------------------------------- 1 | 25 | 26 |
27 | {#await promise} 28 |
29 | 30 |
31 | {:then} 32 | 33 | {:catch e} 34 | 35 | {e.message} 36 | 37 | {/await} 38 |
39 | -------------------------------------------------------------------------------- /src/components/Header.svelte: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | ブックレビュー App 5 | 6 | 28 |
29 |
30 | -------------------------------------------------------------------------------- /src/components/BookCard.svelte: -------------------------------------------------------------------------------- 1 | 15 | 16 |
17 |
18 |
19 |
20 |
21 | 22 |
{book.volumeInfo.title}
23 |
24 |

25 | {description} 26 |

27 |
28 |
29 |
-------------------------------------------------------------------------------- /src/components/BookInfo.svelte: -------------------------------------------------------------------------------- 1 | 19 | 20 |
21 | 22 |
23 | thumnail 24 |
25 |
26 |
27 |

28 | {book.volumeInfo.title} 29 |

30 |
31 |
32 |
33 | 34 | {book.volumeInfo.authors?.join(',')} 35 | 36 | 37 | {book.volumeInfo.description} 38 | 39 | 40 | {price} 41 | 42 | 43 | {book.volumeInfo.pageCount} 44 | 45 | 46 | {book.volumeInfo.publishedDate} 47 | 48 | 49 | {book.volumeInfo.publisher} 50 | 51 | 52 | {#if book.volumeInfo.previewLink} 53 | 54 | {book.volumeInfo.previewLink} 55 | 56 | {/if} 57 | 58 |
59 |
60 |
61 | 62 |
-------------------------------------------------------------------------------- /src/pages/SearchBook.svelte: -------------------------------------------------------------------------------- 1 | 49 | 50 |
51 | 52 | 53 |
54 | {#if empty} 55 |
検索結果が見つかりませんでした。
56 | {:else} 57 |
58 | {#each $books as book (book.id)} 59 | 60 | {/each} 61 |
62 | 63 | {/if} 64 | {#await promise} 65 |
66 | 67 |
68 | {:catch e} 69 | 70 | {e.message} 71 | 72 | {/await} 73 |
-------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import svelte from 'rollup-plugin-svelte'; 2 | import commonjs from '@rollup/plugin-commonjs'; 3 | import resolve from '@rollup/plugin-node-resolve'; 4 | import livereload from 'rollup-plugin-livereload'; 5 | import { terser } from 'rollup-plugin-terser'; 6 | import sveltePreprocess from 'svelte-preprocess'; 7 | import typescript from '@rollup/plugin-typescript'; 8 | import css from 'rollup-plugin-css-only'; 9 | 10 | const production = !process.env.ROLLUP_WATCH; 11 | 12 | function serve() { 13 | let server; 14 | 15 | function toExit() { 16 | if (server) server.kill(0); 17 | } 18 | 19 | return { 20 | writeBundle() { 21 | if (server) return; 22 | server = require('child_process').spawn('npm', ['run', 'start', '--', '--dev'], { 23 | stdio: ['ignore', 'inherit', 'inherit'], 24 | shell: true 25 | }); 26 | 27 | process.on('SIGTERM', toExit); 28 | process.on('exit', toExit); 29 | } 30 | }; 31 | } 32 | 33 | export default { 34 | input: 'src/main.ts', 35 | output: { 36 | sourcemap: true, 37 | format: 'iife', 38 | name: 'app', 39 | file: 'public/build/bundle.js' 40 | }, 41 | plugins: [ 42 | svelte({ 43 | preprocess: sveltePreprocess({ postcss: true }), 44 | compilerOptions: { 45 | // enable run-time checks when not in production 46 | dev: !production 47 | } 48 | }), 49 | // we'll extract any component CSS out into 50 | // a separate file - better for performance 51 | css({ output: 'bundle.css' }), 52 | 53 | // If you have external dependencies installed from 54 | // npm, you'll most likely need these plugins. In 55 | // some cases you'll need additional configuration - 56 | // consult the documentation for details: 57 | // https://github.com/rollup/plugins/tree/master/packages/commonjs 58 | resolve({ 59 | browser: true, 60 | dedupe: ['svelte'] 61 | }), 62 | commonjs(), 63 | typescript({ 64 | sourceMap: !production, 65 | inlineSources: !production 66 | }), 67 | 68 | // In dev mode, call `npm run start` once 69 | // the bundle has been generated 70 | !production && serve(), 71 | 72 | // Watch the `public` directory and refresh the 73 | // browser on changes when not in production 74 | !production && livereload('public'), 75 | 76 | // If we're building for production (npm run build 77 | // instead of npm run dev), minify 78 | production && terser() 79 | ], 80 | watch: { 81 | clearScreen: false 82 | } 83 | }; 84 | --------------------------------------------------------------------------------