fix: update Sapper to latest (#775)

* fix: update to latest sapper

fixes #416

* fix error and debug pages

* requestIdleCallback makes column switching feel way nicer than double rAF

* add export feature

* add better csp info

* workaround for sapper sub-page issue

* clarify in readme about exporting

* fix now config

* switch from rIC to triple raf

* style-loader is no longer used

* update theming guide
This commit is contained in:
Nolan Lawson 2018-12-11 07:31:48 -08:00 committed by GitHub
parent 4d3a2ded2a
commit 4bd181d3cc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
415 changed files with 697 additions and 1325 deletions

9
.gitignore vendored
View file

@ -1,11 +1,14 @@
.DS_Store
node_modules
.sapper
__sapper__
yarn.lock
templates/.*
assets/*.css
static/*.css
/mastodon
mastodon.log
assets/robots.txt
static/robots.txt
/inline-script-checksum.json
/assets/inline-script.js.map
/static/inline-script.js.map
/templates/.*
/src/manifest/

View file

@ -76,6 +76,16 @@ To keep your version of Pinafore up to date, you can use `git` to check out the
git checkout $(git tag -l | sort -Vr | head -n 1)
### Exporting
You can export Pinafore as a static site. Run:
npm run export
Static files will be written to `__sapper__/export`.
Be sure to add the [CSP](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) header printed out in the console to
your server config!
## Developing and testing
See [CONTRIBUTING.md](https://github.com/nolanlawson/pinafore/blob/master/CONTRIBUTING.md) for

View file

@ -8,7 +8,7 @@ import { rollup } from 'rollup'
import { terser } from 'rollup-plugin-terser'
import replace from 'rollup-plugin-replace'
import fromPairs from 'lodash-es/fromPairs'
import { themes } from '../routes/_static/themes'
import { themes } from '../src/routes/_static/themes'
const readFile = pify(fs.readFile.bind(fs))
const writeFile = pify(fs.writeFile.bind(fs))
@ -36,22 +36,22 @@ async function main () {
sourcemap: true
})
let fullCode = `${code}\n//# sourceMappingURL=inline-script.js.map`
let fullCode = `${code}\n//# sourceMappingURL=/inline-script.js.map`
let checksum = crypto.createHash('sha256').update(fullCode).digest('base64')
let checksumFilepath = path.join(__dirname, '../inline-script-checksum.json')
await writeFile(checksumFilepath, JSON.stringify({ checksum }), 'utf8')
let html2xxFilepath = path.join(__dirname, '../templates/2xx.html')
let html2xxFile = await readFile(html2xxFilepath, 'utf8')
html2xxFile = html2xxFile.replace(
let htmlTemplateFilepath = path.join(__dirname, '../src/template.html')
let htmlTemplateFile = await readFile(htmlTemplateFilepath, 'utf8')
htmlTemplateFile = htmlTemplateFile.replace(
/<!-- insert inline script here -->[\s\S]+<!-- end insert inline script here -->/,
'<!-- insert inline script here --><script>' + fullCode + '</script><!-- end insert inline script here -->'
)
await writeFile(html2xxFilepath, html2xxFile, 'utf8')
await writeFile(htmlTemplateFilepath, htmlTemplateFile, 'utf8')
await writeFile(path.resolve(__dirname, '../assets/inline-script.js.map'), map.toString(), 'utf8')
await writeFile(path.resolve(__dirname, '../static/inline-script.js.map'), map.toString(), 'utf8')
}
main().catch(err => {

View file

@ -16,10 +16,10 @@ const globalScss = path.join(__dirname, '../scss/global.scss')
const defaultThemeScss = path.join(__dirname, '../scss/themes/_default.scss')
const offlineThemeScss = path.join(__dirname, '../scss/themes/_offline.scss')
const customScrollbarScss = path.join(__dirname, '../scss/custom-scrollbars.scss')
const html2xxFile = path.join(__dirname, '../templates/2xx.html')
const htmlTemplateFile = path.join(__dirname, '../src/template.html')
const scssDir = path.join(__dirname, '../scss')
const themesScssDir = path.join(__dirname, '../scss/themes')
const assetsDir = path.join(__dirname, '../assets')
const assetsDir = path.join(__dirname, '../static')
function doWatch () {
let start = now()
@ -44,7 +44,7 @@ async function compileGlobalSass () {
let offlineStyle = (await renderCss(offlineThemeScss))
let scrollbarStyle = (await renderCss(customScrollbarScss))
let html = await readFile(html2xxFile, 'utf8')
let html = await readFile(htmlTemplateFile, 'utf8')
html = html.replace(/<!-- begin inline CSS -->[\s\S]+<!-- end inline CSS -->/,
`<!-- begin inline CSS -->\n` +
`<style>\n${mainStyle}</style>\n` +
@ -53,7 +53,7 @@ async function compileGlobalSass () {
`<!-- end inline CSS -->`
)
await writeFile(html2xxFile, html, 'utf8')
await writeFile(htmlTemplateFile, html, 'utf8')
}
async function compileThemesSass () {

View file

@ -28,13 +28,13 @@ async function main () {
result = `<svg xmlns="http://www.w3.org/2000/svg" style="display:none;">\n${result}\n</svg>`
let html2xxFilepath = path.join(__dirname, '../templates/2xx.html')
let html2xxFile = await readFile(html2xxFilepath, 'utf8')
html2xxFile = html2xxFile.replace(
let htmlTemplateFilepath = path.join(__dirname, '../src/template.html')
let htmlTemplateFile = await readFile(htmlTemplateFilepath, 'utf8')
htmlTemplateFile = htmlTemplateFile.replace(
/<!-- insert svg here -->[\s\S]+<!-- end insert svg here -->/,
'<!-- insert svg here -->' + result + '<!-- end insert svg here -->'
)
await writeFile(html2xxFilepath, html2xxFile, 'utf8')
await writeFile(htmlTemplateFilepath, htmlTemplateFile, 'utf8')
}
main().catch(err => {

View file

@ -7,9 +7,9 @@ PATH="$PATH:./node_modules/.bin"
# set up robots.txt
if [[ "$DEPLOY_TYPE" == "dev" ]]; then
printf 'User-agent: *\nDisallow: /' > assets/robots.txt
printf 'User-agent: *\nDisallow: /' > static/robots.txt
else
rm -f assets/robots.txt
rm -f static/robots.txt
fi
# if in travis, use the $NOW_TOKEN

48
bin/print-export-info.js Normal file
View file

@ -0,0 +1,48 @@
const fs = require('fs')
const path = require('path')
const checksum = require('../inline-script-checksum').checksum
const html = fs.readFileSync(path.join(__dirname, '../__sapper__/export/index.html'), 'utf8')
const nonce = html.match(/<script nonce=([^>]+)>/)[1]
const csp = `add_header Content-Security-Policy "script-src 'self' 'sha256-${checksum}' 'nonce-${nonce}'; ` +
`worker-src 'self'; style-src 'self' 'unsafe-inline'; frame-src 'none'; object-src 'none'; manifest-src 'self';`
fs.writeFileSync(path.join(__dirname, '../__sapper__/export/.csp.nginx'), csp, 'utf8')
console.log(`
,((*
,((* (,
,((* (((*
,((* (((((.
* ,((* ((((((*
.(/ ,((* (((((((/
.((/ ,((* ((((((((/
,(((/ ,((* (((((((((*
.(((((/ ,((* ((((((((((
,((*
//////////((((/////////////
/((((((((((((((((((((((((((
/((((((((((((((((((((((((,
*(((((((((((((((((((((/.
./((((((((((((((((.
P I N A F O R E
Export successful! Static files are in:
__sapper__/export/
Be sure to add the CSP header to your nginx config:
server {
include ${path.resolve(__dirname, '..')}/__sapper__/export/.csp.nginx;
}
This file will be updated whenever you do \`npm run export\`.
Enjoy Pinafore!
`)

View file

@ -1,12 +1,12 @@
import { actions } from './mastodon-data'
import { users } from '../tests/users'
import { postStatus } from '../routes/_api/statuses'
import { followAccount } from '../routes/_api/follow'
import { favoriteStatus } from '../routes/_api/favorite'
import { reblogStatus } from '../routes/_api/reblog'
import { postStatus } from '../src/routes/_api/statuses'
import { followAccount } from '../src/routes/_api/follow'
import { favoriteStatus } from '../src/routes/_api/favorite'
import { reblogStatus } from '../src/routes/_api/reblog'
import fetch from 'node-fetch'
import FileApi from 'file-api'
import { pinStatus } from '../routes/_api/pin'
import { pinStatus } from '../src/routes/_api/pin'
import { submitMedia } from '../tests/submitMedia'
global.File = FileApi.File

View file

@ -1,5 +1,5 @@
module.exports = [
{ id: 'pinafore-logo', src: 'original-assets/sailboat.svg', title: 'Home' },
{ id: 'pinafore-logo', src: 'original-static/sailboat.svg', title: 'Home' },
{ id: 'fa-bell', src: 'node_modules/font-awesome-svg-png/white/svg/bell.svg', title: 'Notifications' },
{ id: 'fa-users', src: 'node_modules/font-awesome-svg-png/white/svg/users.svg', title: 'Local' },
{ id: 'fa-globe', src: 'node_modules/font-awesome-svg-png/white/svg/globe.svg', title: 'Federated' },

View file

@ -1,6 +1,9 @@
## Theming
Create a file `scss/themes/foobar.scss`, write some SCSS inside and add the following at the bottom of `scss/themes/foobar.scss`.
This document describes how to write your own theme for Pinafore.
First, create a file `scss/themes/foobar.scss`, write some SCSS inside and add
the following at the bottom of `scss/themes/foobar.scss`.
```scss
@import "_base.scss";
@ -9,9 +12,10 @@ body.theme-foobar {
}
```
> Note: You can find all the SCSS variables available in `scss/themes/_default.scss` while the all CSS Custom Properties available are listed in `scss/themes/_base.scss`.
> Note: You can find all the SCSS variables available in `scss/themes/_default.scss`
> while the all CSS Custom Properties available are listed in `scss/themes/_base.scss`.
Add your theme to `routes/_static/themes.js`
Then, Add your theme to `src/routes/_static/themes.js`
```js
const themes = [
...
@ -24,4 +28,7 @@ const themes = [
]
```
Start the development server (`npm run dev`), go to `http://localhost:4002/settings/instances/your-instance-name` and select your newly created theme. Once you've done that, you can update your theme, and refresh the page to see the change (you don't have to restart the server).
Start the development server (`npm run dev`), go to
`http://localhost:4002/settings/instances/your-instance-name` and select your
newly-created theme. Once you've done that, you can update your theme, and refresh
the page to see the change (you don't have to restart the server).

View file

@ -2,8 +2,8 @@
// To allow CSP to work correctly, we also calculate a sha256 hash during
// the build process and write it to inline-script-checksum.json.
import { testHasLocalStorageOnce } from './routes/_utils/testStorage'
import { switchToTheme } from './routes/_utils/themeEngine'
import { testHasLocalStorageOnce } from './src/routes/_utils/testStorage'
import { switchToTheme } from './src/routes/_utils/themeEngine'
window.__themeColors = process.env.THEME_COLORS

View file

Before

Width:  |  Height:  |  Size: 708 B

After

Width:  |  Height:  |  Size: 708 B

View file

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View file

Before

Width:  |  Height:  |  Size: 403 B

After

Width:  |  Height:  |  Size: 403 B

View file

Before

Width:  |  Height:  |  Size: 326 B

After

Width:  |  Height:  |  Size: 326 B

929
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -3,15 +3,16 @@
"description": "Alternative web client for Mastodon",
"version": "0.13.0",
"scripts": {
"lint": "standard && standard --plugin html 'routes/**/*.html'",
"lint-fix": "standard --fix && standard --fix --plugin html 'routes/**/*.html'",
"lint": "standard && standard --plugin html 'src/routes/**/*.html'",
"lint-fix": "standard --fix && standard --fix --plugin html 'src/routes/**/*.html'",
"dev": "run-s build-svg build-inline-script serve-dev",
"serve-dev": "run-p --race build-sass-watch serve",
"serve": "node server.js",
"serve-dev": "run-p --race build-sass-watch sapper-dev",
"sapper-dev": "cross-env PORT=4002 sapper dev",
"sapper-prod": "cross-env PORT=4002 node __sapper__/build",
"build": "cross-env NODE_ENV=production npm run build-steps",
"build-steps": "run-s globalize-css build-sass build-svg build-inline-script sapper-build deglobalize-css",
"sapper-build": "sapper build",
"start": "cross-env NODE_ENV=production npm run serve",
"start": "cross-env NODE_ENV=production npm run sapper-prod",
"build-and-start": "run-s build start",
"build-svg": "node ./bin/build-svg.js",
"build-inline-script": "node -r esm ./bin/build-inline-script.js",
@ -36,7 +37,10 @@
"deploy-prod": "DEPLOY_TYPE=prod ./bin/deploy.sh",
"deploy-dev": "DEPLOY_TYPE=dev ./bin/deploy.sh",
"deploy-all-travis": "./bin/deploy-all-travis.sh",
"backup-mastodon-data": "./bin/backup-mastodon-data.sh"
"backup-mastodon-data": "./bin/backup-mastodon-data.sh",
"sapper-export": "sapper export",
"print-export-info": "node ./bin/print-export-info.js",
"export": "run-s build sapper-export print-export-info"
},
"dependencies": {
"@gamestdio/websocket": "^0.2.8",
@ -48,6 +52,7 @@
"cross-env": "^5.2.0",
"css-loader": "^2.0.0",
"emoji-regex": "^7.0.1",
"encoding": "^0.1.12",
"escape-html": "^1.0.3",
"esm": "^3.0.84",
"events-light": "^1.0.5",
@ -63,12 +68,10 @@
"localstorage-memory": "^1.0.3",
"lodash-es": "^4.17.11",
"lodash-webpack-plugin": "^0.11.5",
"mini-css-extract-plugin": "^0.5.0",
"mkdirp": "^0.5.1",
"node-fetch": "^2.3.0",
"node-sass": "^4.10.0",
"npm-run-all": "^4.1.5",
"optimize-css-assets-webpack-plugin": "^5.0.1",
"p-any": "^1.1.0",
"page-lifecycle": "^0.1.1",
"performance-now": "^2.1.0",
@ -78,10 +81,9 @@
"rollup": "^0.67.4",
"rollup-plugin-replace": "^2.1.0",
"rollup-plugin-terser": "^3.0.0",
"sapper": "github:nolanlawson/sapper#for-pinafore-9",
"sapper": "github:nolanlawson/sapper#nolan/sw-index-html-built",
"serve-static": "^1.13.2",
"stringz": "^1.0.0",
"style-loader": "^0.23.1",
"svelte": "^2.15.3",
"svelte-extras": "^2.0.2",
"svelte-loader": "^2.11.0",
@ -89,6 +91,7 @@
"svgo": "^1.1.1",
"terser-webpack-plugin": "^1.1.0",
"tiny-queue": "^0.2.1",
"uuid": "^3.3.2",
"web-animations-js": "^2.3.1",
"webpack": "^4.26.1",
"webpack-bundle-analyzer": "^3.0.3"
@ -140,8 +143,8 @@
],
"ignore": [
"dist",
"routes/_utils/asyncModules.js",
"routes/_components/dialog/asyncDialogs.js"
"src/routes/_utils/asyncModules.js",
"src/routes/_components/dialog/asyncDialogs.js"
]
},
"esm": {
@ -154,18 +157,16 @@
"NODE_ENV": "production"
},
"files": [
"assets",
"bin",
"original-assets",
"routes",
"inline-script.js",
"original-static",
"scss",
"templates",
"src",
"static",
"package.json",
"package-lock.json",
"server.js",
"inline-script.js",
"webpack.client.config.js",
"webpack.server.config.js"
"webpack",
"webpack.config.js"
],
"engines": {
"node": "^8.0.0"

View file

@ -1,36 +0,0 @@
<Nav {page} />
<div class="main-content">
<main class="{infiniteScrollPage ? 'infinite-scroll-page' : ''}">
<slot></slot>
</main>
{#if !$isUserLoggedIn && page === 'home'}
<InformationalFooter />
{/if}
</div>
<style>
/* this avoids a flash of the background color when switching timelines */
.infinite-scroll-page {
min-height: 100vh;
}
</style>
<script>
import Nav from './Nav.html'
import { store } from '../_store/store'
import InformationalFooter from './InformationalFooter.html'
export default {
components: {
Nav,
InformationalFooter
},
oncreate () {
let { page } = this.get()
this.store.set({ currentPage: page })
},
store: () => store,
computed: {
infiniteScrollPage: ({ $isUserLoggedIn, page }) => $isUserLoggedIn && page !== 'settings'
}
}
</script>

View file

@ -1,21 +0,0 @@
<Title name="Profile" />
<Layout page='tags'>
<LazyPage {pageComponent} {params} />
</Layout>
<script>
import Layout from '../_components/Layout.html'
import Title from '../_components/Title.html'
import LazyPage from '../_components/LazyPage.html'
import pageComponent from '../_pages/accounts/[accountId].html'
export default {
components: {
Layout,
Title,
LazyPage
},
data: () => ({
pageComponent
})
}
</script>

14
src/client.js Normal file
View file

@ -0,0 +1,14 @@
import * as sapper from '../__sapper__/client.js'
import { loadPolyfills } from './routes/_utils/loadPolyfills'
import './routes/_utils/serviceWorkerClient'
import './routes/_utils/historyEvents'
import './routes/_utils/loadingMask'
loadPolyfills().then(() => {
console.log('init()')
sapper.start({ target: document.querySelector('#sapper') })
})
if (module.hot) {
module.hot.accept()
}

View file

@ -1,6 +1,6 @@
import { getAccessTokenFromAuthCode, registerApplication, generateAuthLink } from '../_api/oauth'
import { getInstanceInfo } from '../_api/instance'
import { goto } from 'sapper/runtime.js'
import { goto } from '../../../__sapper__/client'
import { switchToTheme } from '../_utils/themeEngine'
import { store } from '../_store/store'
import { updateVerifyCredentialsForInstance } from './instances'

View file

@ -2,7 +2,7 @@ import { getVerifyCredentials } from '../_api/user'
import { store } from '../_store/store'
import { switchToTheme } from '../_utils/themeEngine'
import { toast } from '../_utils/toast'
import { goto } from 'sapper/runtime.js'
import { goto } from '../../../__sapper__/client'
import { cacheFirstUpdateAfter } from '../_utils/sync'
import { getInstanceInfo } from '../_api/instance'
import { database } from '../_database/database'

View file

@ -32,8 +32,8 @@
</style>
<script>
import { store } from '../_store/store'
import LoadingPage from '../_components/LoadingPage.html'
import AccountSearchResult from '../_components/search/AccountSearchResult.html'
import LoadingPage from './LoadingPage.html'
import AccountSearchResult from './search/AccountSearchResult.html'
import { toast } from '../_utils/toast'
import { on } from '../_utils/eventBus'
@ -71,4 +71,4 @@
}
}
}
</script>
</script>

View file

@ -1,4 +1,4 @@
<!-- toggled in 2xx.html based on whether the user is logged in or not -->
<!-- toggled in template.html based on whether the user is logged in or not -->
<div class="hidden-from-ssr">
<slot></slot>
</div>
@ -6,4 +6,4 @@
.hidden-from-ssr {
opacity: 0;
}
</style>
</style>

View file

@ -2,6 +2,7 @@
<svelte:component this={pageComponent} {params} />
{/if}
<script>
import { doubleRAF } from '../_utils/doubleRAF'
// On the very first page load, avoid doing a "reveal" because
// it leads to a flash between when the SSR is shown, the two frame we hide it,
// and then when we show it again.
@ -13,14 +14,12 @@
export default {
oncreate () {
firstTime = false
requestAnimationFrame(() => {
requestAnimationFrame(() => {
this.set({ revealed: true })
})
})
// Yes, triple raf. This is to ensure the NavItem animation plays before we
// start rendering the new page.
doubleRAF(() => requestAnimationFrame(() => this.set({ revealed: true })))
},
data: () => ({
revealed: !process.browser || firstTime
})
}
</script>
</script>

View file

Before

Width:  |  Height:  |  Size: 443 B

After

Width:  |  Height:  |  Size: 443 B

Some files were not shown because too many files have changed in this diff Show more