├── .gitignore
├── .travis.yml
├── README.md
├── appveyor.yml
├── cypress.json
├── cypress
├── fixtures
│ └── example.json
├── integration
│ └── spec.js
├── plugins
│ └── index.js
└── support
│ ├── commands.js
│ └── index.js
├── package.json
├── rollup.config.js
├── src
├── client.js
├── routes
│ ├── [list]
│ │ ├── [page].html
│ │ ├── _ItemSummary.html
│ │ └── rss.js
│ ├── _components
│ │ └── Nav.html
│ ├── _error.html
│ ├── _layout.html
│ ├── about.html
│ ├── index.html
│ ├── item
│ │ ├── [id].html
│ │ └── _Comment.html
│ ├── rss.js
│ └── user
│ │ └── [name].html
├── server.js
├── service-worker.js
└── template.html
├── static
├── favicon.png
├── manifest.json
├── svelte-192.png
├── svelte-logo-192.png
└── svelte-logo-512.png
└── yarn.lock
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /node_modules
3 | /cypress/screenshots
4 | /yarn.lock
5 | /yarn-error.log
6 | /__sapper__
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: false
2 | language: node_js
3 | node_js:
4 | - "stable"
5 | env:
6 | global:
7 | - BUILD_TIMEOUT=10000
8 | install:
9 | - npm install
10 | - npm install cypress
11 |
12 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## DEPRECATED — see https://github.com/sveltejs/hn.svelte.dev instead
2 |
3 | ---
4 |
5 | # sapper-hacker-news
6 |
7 | Testing ground for [sapper](https://github.com/rich-harris/sapper), a work-in-progress app development framework based on Svelte.
8 |
9 | ```bash
10 | yarn
11 | yarn run dev
12 | ```
13 |
14 | ...then navigate to [localhost:3000](http://localhost:3000).
15 |
16 | Or to run in production mode, `yarn start`.
17 |
18 |
19 | ## Lots still to do
20 |
21 | Some of these are Svelte things, some of these are Sapper things:
22 |
23 | * [x] Need a declarative way to set the document title on both client and server
24 | * [x] Preloading, on server and client, to avoid the flash of unfetched content
25 | * [x] Critical CSS
26 | * [ ] Sapper doesn't currently watch the `routes` folder, so you have to keep restarting the server. Also, webpack only runs once
27 | * [x] Service worker
28 | * [x] Build optimised production version
29 |
--------------------------------------------------------------------------------
/appveyor.yml:
--------------------------------------------------------------------------------
1 | version: "{build}"
2 |
3 | shallow_clone: true
4 |
5 | init:
6 | - git config --global core.autocrlf false
7 |
8 | build: off
9 |
10 | environment:
11 | matrix:
12 | # node.js
13 | - nodejs_version: stable
14 |
15 | install:
16 | - ps: Install-Product node $env:nodejs_version
17 | - npm install cypress
18 | - npm install
19 |
--------------------------------------------------------------------------------
/cypress.json:
--------------------------------------------------------------------------------
1 | {
2 | "baseUrl": "http://localhost:3000",
3 | "video": false
4 | }
--------------------------------------------------------------------------------
/cypress/fixtures/example.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Using fixtures to represent data",
3 | "email": "hello@cypress.io",
4 | "body": "Fixtures are a great way to mock data for responses to routes"
5 | }
--------------------------------------------------------------------------------
/cypress/integration/spec.js:
--------------------------------------------------------------------------------
1 | describe('Sapper template app', () => {
2 | beforeEach(() => {
3 | cy.visit('/')
4 | });
5 |
6 | it('has the correct
', () => {
7 | cy.contains('h1', 'Great success!')
8 | });
9 |
10 | it('navigates to /about', () => {
11 | cy.get('nav a').contains('about').click();
12 | cy.url().should('include', '/about');
13 | });
14 |
15 | it('navigates to /blog', () => {
16 | cy.get('nav a').contains('blog').click();
17 | cy.url().should('include', '/blog');
18 | });
19 | });
--------------------------------------------------------------------------------
/cypress/plugins/index.js:
--------------------------------------------------------------------------------
1 | // ***********************************************************
2 | // This example plugins/index.js can be used to load plugins
3 | //
4 | // You can change the location of this file or turn off loading
5 | // the plugins file with the 'pluginsFile' configuration option.
6 | //
7 | // You can read more here:
8 | // https://on.cypress.io/plugins-guide
9 | // ***********************************************************
10 |
11 | // This function is called when a project is opened or re-opened (e.g. due to
12 | // the project's config changing)
13 |
14 | module.exports = (on, config) => {
15 | // `on` is used to hook into various events Cypress emits
16 | // `config` is the resolved Cypress config
17 | }
18 |
--------------------------------------------------------------------------------
/cypress/support/commands.js:
--------------------------------------------------------------------------------
1 | // ***********************************************
2 | // This example commands.js shows you how to
3 | // create various custom commands and overwrite
4 | // existing commands.
5 | //
6 | // For more comprehensive examples of custom
7 | // commands please read more here:
8 | // https://on.cypress.io/custom-commands
9 | // ***********************************************
10 | //
11 | //
12 | // -- This is a parent command --
13 | // Cypress.Commands.add("login", (email, password) => { ... })
14 | //
15 | //
16 | // -- This is a child command --
17 | // Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... })
18 | //
19 | //
20 | // -- This is a dual command --
21 | // Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... })
22 | //
23 | //
24 | // -- This is will overwrite an existing command --
25 | // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
26 |
--------------------------------------------------------------------------------
/cypress/support/index.js:
--------------------------------------------------------------------------------
1 | // ***********************************************************
2 | // This example support/index.js is processed and
3 | // loaded automatically before your test files.
4 | //
5 | // This is a great place to put global configuration and
6 | // behavior that modifies Cypress.
7 | //
8 | // You can change the location of this file or turn off
9 | // automatically serving support files with the
10 | // 'supportFile' configuration option.
11 | //
12 | // You can read more here:
13 | // https://on.cypress.io/configuration
14 | // ***********************************************************
15 |
16 | // Import commands.js using ES2015 syntax:
17 | import './commands'
18 |
19 | // Alternatively you can use CommonJS syntax:
20 | // require('./commands')
21 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "sapper-hacker-news",
3 | "description": "Hacker News built with Svelte and Sapper",
4 | "version": "0.0.1",
5 | "author": "Rich Harris",
6 | "scripts": {
7 | "dev": "sapper dev",
8 | "sapper": "sapper build --legacy",
9 | "export": "sapper export",
10 | "start": "node __sapper__/build",
11 | "cy:run": "cypress run",
12 | "cy:open": "cypress open",
13 | "test": "run-p --race dev cy:run",
14 | "stage": "now",
15 | "deploy": "npm run stage && now alias",
16 | "predeploy": "git-branch-is master && git diff --exit-code",
17 | "prestage": "npm run sapper"
18 | },
19 | "dependencies": {
20 | "@babel/runtime": "^7.0.0",
21 | "compression": "^1.7.3",
22 | "express": "^4.16.3",
23 | "node-fetch": "^2.2.0",
24 | "serve-static": "^1.13.2"
25 | },
26 | "devDependencies": {
27 | "@babel/core": "^7.0.0",
28 | "@babel/plugin-syntax-dynamic-import": "^7.0.0",
29 | "@babel/plugin-transform-runtime": "^7.0.0",
30 | "@babel/preset-env": "^7.0.0",
31 | "git-branch-is": "^2.0.0",
32 | "now": "^11.4.1",
33 | "npm-run-all": "^4.1.3",
34 | "rollup": "^0.65.2",
35 | "rollup-plugin-babel": "^4.0.3",
36 | "rollup-plugin-commonjs": "^9.1.6",
37 | "rollup-plugin-node-resolve": "^3.4.0",
38 | "rollup-plugin-replace": "^2.0.0",
39 | "rollup-plugin-svelte": "^4.3.0",
40 | "rollup-plugin-terser": "^2.0.2",
41 | "sapper": "^0.22.2",
42 | "svelte": "^2.13.4"
43 | },
44 | "now": {
45 | "alias": "hn.svelte.technology",
46 | "files": [
47 | "static",
48 | "__sapper__/build"
49 | ],
50 | "env": {
51 | "NODE_ENV": "production"
52 | }
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | import resolve from 'rollup-plugin-node-resolve';
2 | import replace from 'rollup-plugin-replace';
3 | import commonjs from 'rollup-plugin-commonjs';
4 | import svelte from 'rollup-plugin-svelte';
5 | import babel from 'rollup-plugin-babel';
6 | import { terser } from 'rollup-plugin-terser';
7 | import config from 'sapper/config/rollup.js';
8 | import pkg from './package.json';
9 |
10 | const mode = process.env.NODE_ENV;
11 | const dev = mode === 'development';
12 | const legacy = !!process.env.SAPPER_LEGACY_BUILD;
13 |
14 | export default {
15 | client: {
16 | input: config.client.input(),
17 | output: config.client.output(),
18 | plugins: [
19 | svelte({
20 | dev,
21 | hydratable: true,
22 | emitCss: true
23 | }),
24 | resolve(),
25 | replace({
26 | 'process.browser': true,
27 | 'process.env.NODE_ENV': JSON.stringify(mode)
28 | }),
29 | commonjs(),
30 |
31 | legacy && babel({
32 | extensions: ['.js', '.html'],
33 | runtimeHelpers: true,
34 | exclude: ['node_modules/@babel/**'],
35 | presets: [
36 | ['@babel/preset-env', {
37 | targets: '> 0.25%, not dead'
38 | }]
39 | ],
40 | plugins: [
41 | '@babel/plugin-syntax-dynamic-import',
42 | ['@babel/plugin-transform-runtime', {
43 | useESModules: true
44 | }]
45 | ]
46 | }),
47 |
48 | !dev && terser({
49 | module: true
50 | })
51 | ],
52 |
53 | // temporary, pending Rollup 1.0
54 | experimentalCodeSplitting: true
55 | },
56 |
57 | server: {
58 | input: config.server.input(),
59 | output: config.server.output(),
60 | plugins: [
61 | svelte({
62 | generate: 'ssr',
63 | dev
64 | }),
65 | resolve(),
66 | replace({
67 | 'process.browser': false,
68 | 'process.env.NODE_ENV': JSON.stringify(mode)
69 | }),
70 | commonjs()
71 | ],
72 | external: Object.keys(pkg.dependencies).concat(
73 | require('module').builtinModules || Object.keys(process.binding('natives'))
74 | ),
75 |
76 | // temporary, pending Rollup 1.0
77 | experimentalCodeSplitting: true
78 | },
79 |
80 | serviceworker: {
81 | input: config.serviceworker.input(),
82 | output: config.serviceworker.output(),
83 | plugins: [
84 | resolve(),
85 | replace({
86 | 'process.browser': true,
87 | 'process.env.NODE_ENV': JSON.stringify(mode)
88 | }),
89 | commonjs(),
90 | !dev && terser()
91 | ]
92 | }
93 | };
--------------------------------------------------------------------------------
/src/client.js:
--------------------------------------------------------------------------------
1 | import * as sapper from '../__sapper__/client.js';
2 |
3 | sapper.start({
4 | target: document.querySelector('#sapper')
5 | });
--------------------------------------------------------------------------------
/src/routes/[list]/[page].html:
--------------------------------------------------------------------------------
1 |
2 | Svelte Hacker News
3 |
4 |
5 | {#each items as item, i}
6 | {#if item}
7 |
8 | {/if}
9 | {/each}
10 |
11 | {#if next}
12 | More...
13 | {/if}
14 |
15 |
--------------------------------------------------------------------------------
/src/routes/[list]/_ItemSummary.html:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 | {#if item.type === 'job'}
7 | {item.time_ago}
8 | {:else}
9 |
10 | {item.points} points by
11 | {item.user} {item.time_ago}
12 | | {item.comments_count} {item.comments_count === 1 ? 'comment' : 'comments'}
13 |
14 | {/if}
15 |
16 | {index}
17 |
18 |
19 |
--------------------------------------------------------------------------------
/src/routes/[list]/rss.js:
--------------------------------------------------------------------------------
1 | import fetch from 'node-fetch';
2 |
3 | const render = (list, items) => `
4 |
5 |
6 | Svelte HN (${list})
7 | https://hn.svelte.technology/${list}/1
8 | Links from the orange site
9 |
10 | https://hn.svelte.technology/favicon.png
11 | Svelte HN (${list})
12 | https://hn.svelte.technology/${list}/1
13 |
14 | ${items.map(item => `
15 | -
16 | ${item.title}${item.domain ? ` (${item.domain})` : ''}
17 | https://hn.svelte.technology/item/${item.id}
18 | link / ` : ''
20 | }comments
21 | ]]>
22 | ${new Date(item.time * 1000).toUTCString()}
23 |
24 | `).join('\n')}
25 |
26 | `;
27 |
28 | export function get(req, res) {
29 | const list = (
30 | req.params.list === 'top' ? 'news' :
31 | req.params.list === 'new' ? 'newest' :
32 | req.params.list
33 | );
34 |
35 | res.set({
36 | 'Cache-Control': `max-age=0, s-max-age=${600}`, // 10 minutes
37 | 'Content-Type': 'application/rss+xml'
38 | });
39 |
40 | fetch(`https://api.hnpwa.com/v0/${list}/1.json`)
41 | .then(r => r.json())
42 | .then(items => {
43 | const feed = render(list, items);
44 | res.end(feed);
45 | });
46 | }
47 |
--------------------------------------------------------------------------------
/src/routes/_components/Nav.html:
--------------------------------------------------------------------------------
1 |
16 |
17 |
--------------------------------------------------------------------------------
/src/routes/_error.html:
--------------------------------------------------------------------------------
1 |
2 | {status} | Svelte Hacker News
3 |
4 |
5 | {status}
6 |
{error.message}
--------------------------------------------------------------------------------
/src/routes/_layout.html:
--------------------------------------------------------------------------------
1 |