├── .babelrc
├── .gitignore
├── README.md
├── package.json
├── public
├── global.css
└── index.html
├── server
├── index.js
└── sockets.js
├── src
├── App.html
├── ChatInput.html
├── LoginForm.html
├── Message.html
├── MessageList.html
└── main.js
├── sveltegif.gif
└── webpack.config.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | plugins: [
3 | 'transform-class-properties',
4 | 'transform-object-rest-spread',
5 | 'syntax-async-functions',
6 | 'transform-runtime',
7 | ],
8 | presets: [
9 | 'es2015-node5',
10 | ],
11 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | public/bundle.*
4 | package-lock.json
5 | yarn.lock
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Svelte Chat
2 |
3 | An example app built with [Svelte.js](https://svelte.technology/), using Socket.io to provide the chat functionality.
4 |
5 | To try it out, clone/download this repo, run `yarn`, and then run both `yarn dev` and `yarn start:api`.
6 |
7 | Note that you must have both [yarn](https://yarnpkg.com/en/) and MongoDB installed to run.
8 |
9 | 
10 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "svelte-app",
3 | "version": "1.0.0",
4 | "devDependencies": {
5 | "babel-cli": "^6.26.0",
6 | "babel-plugin-transform-async-functions": "^6.22.0",
7 | "babel-plugin-transform-class-properties": "^6.24.1",
8 | "babel-plugin-transform-object-rest-spread": "^6.26.0",
9 | "babel-plugin-transform-runtime": "^6.23.0",
10 | "babel-preset-es2015-node5": "^1.2.0",
11 | "cross-env": "^5.1.5",
12 | "css-loader": "^1.0.0",
13 | "mini-css-extract-plugin": "^0.4.0",
14 | "nodemon": "^1.18.9",
15 | "serve": "^10.0.2",
16 | "style-loader": "^0.23.1",
17 | "svelte": "^2.0.0",
18 | "svelte-loader": "2.11.0",
19 | "webpack": "^4.8.3",
20 | "webpack-cli": "^3.1.2",
21 | "webpack-dev-server": "^3.1.9"
22 | },
23 | "scripts": {
24 | "build": "cross-env NODE_ENV=production webpack",
25 | "dev": "webpack-dev-server --content-base public",
26 | "start:api": "nodemon --exec babel-node server/index.js"
27 | },
28 | "dependencies": {
29 | "body-parser": "^1.18.3",
30 | "cors": "^2.8.5",
31 | "express": "^4.16.4",
32 | "http": "^0.0.0",
33 | "mime": "^2.4.0",
34 | "mongodb": "^3.1.13",
35 | "socket.io": "^2.2.0",
36 | "socket.io-client": "^2.2.0",
37 | "svelte-transitions": "^1.2.0"
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/public/global.css:
--------------------------------------------------------------------------------
1 | html,
2 | body {
3 | position: relative;
4 | width: 100%;
5 | height: 100%;
6 | }
7 |
8 | * {
9 | box-sizing: border-box;
10 | }
11 |
12 | body {
13 | color: #333;
14 | margin: 0;
15 | padding: 8px;
16 | box-sizing: border-box;
17 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
18 | Oxygen-Sans, Ubuntu, Cantarell, 'Helvetica Neue', sans-serif;
19 | height: 100vh;
20 | background: linear-gradient(to bottom, #fd8271, #a62824);
21 | font-family: 'Khula', sans-serif;
22 | color: #2f2f2f;
23 | overflow: hidden;
24 | }
25 |
26 | #root {
27 | height: 100%;
28 | display: flex;
29 | justify-content: center;
30 | align-items: center;
31 | flex-direction: row;
32 | }
33 |
34 | a {
35 | color: rgb(0, 100, 200);
36 | text-decoration: none;
37 | }
38 |
39 | a:hover {
40 | text-decoration: underline;
41 | }
42 |
43 | a:visited {
44 | color: rgb(0, 80, 160);
45 | }
46 |
47 | label {
48 | display: block;
49 | }
50 |
51 | input,
52 | button,
53 | select,
54 | textarea {
55 | font-family: inherit;
56 | font-size: inherit;
57 | padding: 0.4em;
58 | margin: 0 0 0.5em 0;
59 | box-sizing: border-box;
60 | border: 1px solid #ccc;
61 | border-radius: 2px;
62 | }
63 |
64 | h1,
65 | p {
66 | margin: 0;
67 | }
68 |
69 | input:disabled {
70 | color: #ccc;
71 | }
72 |
73 | input[type='range'] {
74 | height: 0;
75 | }
76 |
77 | button {
78 | background-color: #f4f4f4;
79 | outline: none;
80 | }
81 |
82 | button:active {
83 | background-color: #ddd;
84 | }
85 |
86 | button:focus {
87 | border-color: #666;
88 | }
89 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Svelte app
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/server/index.js:
--------------------------------------------------------------------------------
1 | import express from 'express';
2 | import { MongoClient } from 'mongodb';
3 | import bodyParser from 'body-parser';
4 | import cors from 'cors';
5 | import { createServer } from 'http';
6 | import setUpSockets from './sockets';
7 |
8 | const app = express();
9 | app.use(bodyParser.json());
10 | app.use(cors());
11 | const server = createServer(app);
12 |
13 | const start = async () => {
14 | const client = await MongoClient.connect('mongodb://localhost/realtime');
15 | const db = client.db('realtime');
16 | setUpSockets(server, db);
17 |
18 | server.listen(3000, () => {
19 | console.log('Listening at port', server.address().port);
20 | });
21 | };
22 |
23 | start();
24 |
--------------------------------------------------------------------------------
/server/sockets.js:
--------------------------------------------------------------------------------
1 | import socket from 'socket.io';
2 |
3 | const emitMessages = async (db, emitter) => {
4 | const messages = await getMessages(db);
5 | emitter.emit('messages', messages);
6 | };
7 |
8 | const getMessages = db => {
9 | return db
10 | .collection('messages')
11 | .find()
12 | .toArray();
13 | };
14 |
15 | const handleNewMessage = (db, client, io) => {
16 | client.on('newMessage', async message => {
17 | const { text, author } = message;
18 | await db.collection('messages').insertOne({ text, author });
19 | emitMessages(db, io);
20 | });
21 | };
22 |
23 | const setUpConnection = (io, db) => {
24 | io.on('connection', async client => {
25 | emitMessages(db, client);
26 | handleNewMessage(db, client, io);
27 | });
28 | };
29 |
30 | export default (server, db) => {
31 | const io = socket(server);
32 | setUpConnection(io, db);
33 | };
34 |
--------------------------------------------------------------------------------
/src/App.html:
--------------------------------------------------------------------------------
1 |
2 | {#if username}
3 |
4 |
5 |
6 |
7 | {:else}
8 |
9 | {/if}
10 |
11 |
12 |
30 |
31 |
40 |
--------------------------------------------------------------------------------
/src/ChatInput.html:
--------------------------------------------------------------------------------
1 |
9 |
10 |
51 |
--------------------------------------------------------------------------------
/src/LoginForm.html:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/src/Message.html:
--------------------------------------------------------------------------------
1 |
2 |
{message.text}
3 | {#if showAuthor(message, nextMessage)}
4 |
{message.author}
5 | {/if}
6 |
7 |
8 |
36 |
37 |
48 |
--------------------------------------------------------------------------------
/src/MessageList.html:
--------------------------------------------------------------------------------
1 |
2 | {#each messages as message, i}
3 |
4 | {:else}
5 |
No messages
6 | {/each}
7 |
8 |
9 |
16 |
17 |
24 |
25 |
--------------------------------------------------------------------------------
/src/main.js:
--------------------------------------------------------------------------------
1 | import io from 'socket.io-client';
2 |
3 | import App from './App.html';
4 | const socket = io('http://localhost:3000');
5 |
6 | const app = new App({
7 | target: document.getElementById('root'),
8 | data: {
9 | messages: [],
10 | newMessage: '',
11 | username: ''
12 | }
13 | });
14 |
15 | app.on('submitForm', () => {
16 | const { newMessage, username } = app.get();
17 | socket.emit('newMessage', {
18 | text: newMessage,
19 | author: username
20 | });
21 | app.set({ newMessage: '' });
22 | });
23 |
24 | app.on('setUsername', () => {
25 | app.set({ username: document.getElementById('enter-username').value });
26 | });
27 |
28 | socket.on('messages', messages => {
29 | app.set({
30 | messages
31 | });
32 | });
33 |
34 | window.app = app;
35 |
36 | export default app;
37 |
--------------------------------------------------------------------------------
/sveltegif.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scottdomes/svelte-chat/87c1de706935d21d9d35b629e5edc98c5e854cea/sveltegif.gif
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const webpack = require('webpack');
2 | const MiniCssExtractPlugin = require('mini-css-extract-plugin');
3 |
4 | const mode = process.env.NODE_ENV || 'development';
5 | const prod = mode === 'production';
6 |
7 | module.exports = {
8 | entry: {
9 | bundle: ['./src/main.js']
10 | },
11 | resolve: {
12 | extensions: ['.js', '.html']
13 | },
14 | output: {
15 | path: __dirname + '/public',
16 | filename: '[name].js',
17 | chunkFilename: '[name].[id].js'
18 | },
19 | module: {
20 | rules: [
21 | {
22 | test: /\.html$/,
23 | exclude: /node_modules/,
24 | use: {
25 | loader: 'svelte-loader',
26 | options: {
27 | skipIntroByDefault: true,
28 | nestedTransitions: true,
29 | emitCss: true,
30 | hotReload: true
31 | }
32 | }
33 | },
34 | {
35 | test: /\.css$/,
36 | use: [
37 | /**
38 | * MiniCssExtractPlugin doesn't support HMR.
39 | * For developing, use 'style-loader' instead.
40 | * */
41 | prod ? MiniCssExtractPlugin.loader : 'style-loader',
42 | 'css-loader'
43 | ]
44 | }
45 | ]
46 | },
47 | mode,
48 | plugins: [
49 | new MiniCssExtractPlugin({
50 | filename: '[name].css'
51 | })
52 | ],
53 | devtool: prod ? false: 'source-map'
54 | };
55 |
--------------------------------------------------------------------------------