Typescript support

This commit is contained in:
Michal Vanko 2020-09-26 14:49:58 +02:00
parent 57ed783083
commit dcfc3eccc2
22 changed files with 3132 additions and 1264 deletions

View File

@ -8,7 +8,7 @@
<tr>
<td
rowspan="3"
style="vertical-align: center; border-right: 1px solid #212138; width: 80px"
style="vertical-align: center; width: 80px"
>
<img
src="https://michalvanko.dev/images/m-logo.png"
@ -19,18 +19,18 @@
</td>
<td
style="color: #212138; font-size: 12px; padding-bottom: 4px; padding-left: 8px;"
style="color: #212138; font-size: 12px; padding-bottom: 4px; padding-left: 4px;"
>
<span>Michal Vanko</span>
</td>
</tr>
<tr>
<td style="color: 42436a; font-size: 11px; padding-left: 8px;">
<td style="color: #42436a; font-size: 11px; padding-left: 4px;">
<span>Software Architect and Consultant</span>
</td>
</tr>
<tr>
<td style="font-size: 11px; padding-left: 8px;">
<td style="font-size: 11px; padding-left: 4px;">
<a
target="_blank"
href="https://michalvanko.dev"

4094
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -14,31 +14,41 @@
"dependencies": {
"classnames": "^2.2.6",
"compression": "^1.7.4",
"date-fns": "^2.11.1",
"feed": "^4.2.0",
"front-matter": "^3.1.0",
"marked": "^0.8.2",
"@rollup/plugin-typescript": "^6.0.0",
"date-fns": "^2.16.1",
"feed": "^4.2.1",
"front-matter": "^4.0.2",
"marked": "^1.1.1",
"polka": "^0.5.2",
"ramda": "^0.27.0",
"rollup-plugin-svg": "^2.0.0",
"sirv": "^0.4.2",
"svelte-image": "^0.1.9"
"ramda": "^0.27.1",
"sirv": "^1.0.6",
"svelte-image": "^0.2.7"
},
"devDependencies": {
"npm-run-all": "^4.1.5",
"sapper": "^0.27.12",
"svelte": "^3.20.1",
"@babel/core": "^7.9.0",
"@babel/core": "^7.11.6",
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
"@babel/plugin-transform-runtime": "^7.9.0",
"@babel/preset-env": "^7.9.0",
"@babel/runtime": "^7.9.2",
"rollup": "^2.3.2",
"@babel/plugin-transform-runtime": "^7.11.5",
"@babel/preset-env": "^7.11.5",
"@babel/runtime": "^7.11.2",
"@rollup/plugin-typescript": "^6.0.0",
"@tsconfig/svelte": "^1.0.10",
"@types/classnames": "^2.2.10",
"@types/ramda": "^0.27.19",
"autoprefixer": "^10.0.0",
"npm-run-all": "^4.1.5",
"postcss": "^8.0.9",
"rollup": "^2.28.2",
"rollup-plugin-babel": "^4.4.0",
"rollup-plugin-commonjs": "^10.1.0",
"rollup-plugin-node-resolve": "^5.2.0",
"rollup-plugin-replace": "^2.2.0",
"rollup-plugin-svelte": "^5.2.1",
"rollup-plugin-terser": "^5.3.0"
"rollup-plugin-svelte": "^6.0.1",
"rollup-plugin-svg": "^2.0.0",
"rollup-plugin-terser": "^7.0.2",
"sapper": "^0.28.9",
"svelte": "^3.28.0",
"svelte-check": "^1.0.51",
"svelte-preprocess": "^4.3.2",
"typescript": "^4.0.3"
}
}

View File

@ -7,22 +7,24 @@ import { terser } from 'rollup-plugin-terser'
import config from 'sapper/config/rollup.js'
import pkg from './package.json'
import svg from 'rollup-plugin-svg'
import image from 'svelte-image'
// import image from 'svelte-image'
import sveltePreprocess from 'svelte-preprocess'
import typescript from '@rollup/plugin-typescript'
const mode = process.env.NODE_ENV
const dev = mode === 'development'
const legacy = !!process.env.SAPPER_LEGACY_BUILD
const onwarn = (warning, onwarn) =>
(warning.code === 'CIRCULAR_DEPENDENCY' &&
/[/\\]@sapper[/\\]/.test(warning.message)) ||
(warning.code === 'MISSING_EXPORT' && /'preload'/.test(warning.message)) ||
(warning.code === 'CIRCULAR_DEPENDENCY' && /[/\\]@sapper[/\\]/.test(warning.message)) ||
onwarn(warning)
const dedupe = (importee) =>
importee === 'svelte' || importee.startsWith('svelte/')
export default {
client: {
input: config.client.input(),
input: config.client.input().replace(/\.js$/, ".ts"),
output: config.client.output(),
plugins: [
replace({
@ -33,9 +35,16 @@ export default {
dev,
hydratable: true,
emitCss: true,
// Disabled automatic image compression
// preprocess: {
// ...image(),
// },
preprocess: sveltePreprocess({
sourceMap: dev,
defaults: {
script: 'typescript',
},
}),
}),
resolve({
browser: true,
@ -78,7 +87,7 @@ export default {
},
server: {
input: config.server.input(),
input: config.server.input().server.replace(/\.js$/, ".ts"),
output: config.server.output(),
plugins: [
replace({
@ -88,6 +97,13 @@ export default {
svelte({
generate: 'ssr',
dev,
hydratable: true,
preprocess: sveltePreprocess({
sourceMap: dev,
defaults: {
script: 'typescript',
}
}),
// preprocess: {
// ...image(),
// },
@ -96,6 +112,7 @@ export default {
dedupe,
}),
commonjs(),
typescript({ sourceMap: dev}),
svg(),
],
external: Object.keys(pkg.dependencies).concat(

View File

@ -1,7 +1,8 @@
<script>
<script lang="typescript">
import { format } from 'date-fns'
import type { PostContent } from '../../routes/blog/_content';
export let post
export let post: PostContent
</script>
<style>

View File

@ -1,5 +1,7 @@
<script>
export let project
<script lang="typescript">
import type { ProjectAttributes } from "../../routes/portfolio/index.json";
export let project: ProjectAttributes
</script>
<style>

View File

@ -1,7 +1,7 @@
import marked from 'marked'
export function parseField(field) {
return item => ({
export function parseField<T>(field: string) {
return (item: T) => ({
...item,
[field]: marked(item[field]),
})

View File

@ -2,13 +2,18 @@ import { readFile } from 'fs'
import { promisify } from 'util'
import fm from 'front-matter'
import { parseField } from '../../markdown/parse-markdown'
import type { PostAttributes } from './_content'
export interface SinglePost {
body: string
}
export async function get(req, res, next) {
// the `slug` parameter is available because
// this file is called [slug].json.js
const { slug } = req.params
let postSource
let postSource: string
try {
postSource = await promisify(readFile)(`_posts/blog/${slug}.md`, 'utf-8')
} catch (e) {
@ -22,9 +27,9 @@ export async function get(req, res, next) {
return
}
const parsedPost = fm(postSource)
const parsedPost = fm<PostAttributes>(postSource)
const response = parseField('body')({
const response = parseField<SinglePost>('body')({
...parsedPost.attributes,
body: parsedPost.body,
})

View File

@ -64,8 +64,8 @@
<svelte:head>
<title>{post.title}</title>
<link rel="stylesheet" href="prism.css" />
<script src="prism.js">
<link rel="stylesheet" href="/prism.css" />
<script src="../../../static/prism.js" defer>
</script>
</svelte:head>

View File

@ -7,17 +7,32 @@ import marked from 'marked'
const { NODE_ENV } = process.env
export async function getBlogListing(tag) {
export interface PostAttributes {
layout: string
title: string
published: boolean
date: string
thumbnail: string
tags: string[]
}
export interface PostContent extends PostAttributes {
preview: string
slug: string
published: boolean
}
export async function getBlogListing(tag?: string) {
const files = await promisify(readdir)(`_posts/blog/`, 'utf-8')
const filteredFiles = filterDevelopmentFiles(files)
const contents = await Promise.all(
filteredFiles.map(async file => {
filteredFiles.map(async (file) => {
const fileContent = await promisify(readFile)(
`_posts/blog/${file}`,
'utf-8'
)
const parsedAttributes = fm(fileContent)
const parsedAttributes = fm<PostAttributes>(fileContent)
const lineOfTextRegExp = /^(?:\w|\[).+/gm
const lines = parsedAttributes.body
@ -33,23 +48,30 @@ export async function getBlogListing(tag) {
}
})
)
const filteredContents = pipe(
const filteredContents = pipe<
PostContent[],
PostContent[],
PostContent[],
PostContent[],
PostContent[]
>(
sortBy(prop('date')),
reverse,
filter(article => article.published),
filter<typeof contents[0]>((article) => article.published),
partial(filterByTag, [tag])
)(contents)
return filteredContents
}
function filterDevelopmentFiles(files) {
function filterDevelopmentFiles(files: string[]) {
return NODE_ENV !== 'production'
? files
: files.filter(file => !file.startsWith('dev-'))
: files.filter((file) => !file.startsWith('dev-'))
}
function filterByTag(tag, contents) {
return tag ? contents.filter(content => content.tags.includes(tag)) : contents
function filterByTag(tag: string | undefined, contents: PostContent[]) {
return tag
? contents.filter((content) => content.tags.includes(tag))
: contents
}

View File

@ -1,4 +1,4 @@
<script context="module">
<script context="module" lang="typescript">
export function preload({ params, query }) {
const blogQuery = query
? '?' +
@ -14,10 +14,11 @@
}
</script>
<script>
<script lang="typescript">
import ArticleFooter from '../../components/blog/article-footer.svelte'
import type { PostContent } from './_content';
export let posts
export let posts: PostContent[]
export let query
</script>

View File

@ -25,14 +25,16 @@ export async function getFeed() {
})
const blogListing = await getBlogListing()
blogListing.forEach(post => {
blogListing.forEach((post) => {
feed.addItem({
title: post.title,
id: `https://michalvanko.dev/blog/${post.slug}`,
link: `https://michalvanko.dev/blog/${post.slug}`,
description: post.preview,
date: post.date,
image: post.thumbnail ? `https://michalvanko.dev/${post.thumbnail}` : undefined,
date: new Date(post.date),
image: post.thumbnail
? `https://michalvanko.dev/${post.thumbnail}`
: undefined,
})
})
return feed

View File

@ -8,4 +8,3 @@ export async function get(req, res) {
})
res.end(feed.json1())
}

View File

@ -8,4 +8,3 @@ export async function get(req, res) {
})
res.end(feed.rss2())
}

View File

@ -4,8 +4,26 @@ import fm from 'front-matter'
import marked from 'marked'
import { parseField } from '../../markdown/parse-markdown'
export interface RecordAttributes {
name: string
description: string
displayed: boolean
}
export interface ProjectAttributes extends RecordAttributes {
image: string
}
export interface PortfolioAttributes {
title: string
work_history_prelude: string
work_history: string[]
projects: ProjectAttributes[]
education: RecordAttributes[]
}
export async function get(req, res, next) {
let pageSource
let pageSource: string
try {
pageSource = await promisify(readFile)('_pages/portfolio.md', 'utf-8')
} catch (e) {
@ -14,15 +32,15 @@ export async function get(req, res, next) {
return
}
const parsed = fm(pageSource)
const parsed = fm<PortfolioAttributes>(pageSource)
const workHistory = (parsed.attributes.work_history || []).map(
parseField('description')
)
const projects = (parsed.attributes.projects || [])
.filter(project => project.displayed)
.filter((project) => project.displayed)
.map(parseField('description'))
const education = (parsed.attributes.education || [])
.filter(education => education.displayed)
.filter((education) => education.displayed)
.map(parseField('description'))
const response = {

View File

@ -1,17 +0,0 @@
import sirv from 'sirv';
import polka from 'polka';
import compression from 'compression';
import * as sapper from '@sapper/server';
const { PORT, NODE_ENV } = process.env;
const dev = NODE_ENV === 'development';
polka() // You can also use Express
.use(
compression({ threshold: 0 }),
sirv('static', { dev }),
sapper.middleware()
)
.listen(PORT, err => {
if (err) console.log('error', err);
});

17
src/server.ts Normal file
View File

@ -0,0 +1,17 @@
import sirv from 'sirv'
import polka from 'polka'
import compression from 'compression'
import * as sapper from '@sapper/server'
const { PORT, NODE_ENV } = process.env
const dev = NODE_ENV === 'development'
polka() // You can also use Express
.use(
compression({ threshold: 0 }),
sirv('static', { dev }),
sapper.middleware()
)
.listen(PORT, (err) => {
if (err) console.log('error', err)
})

View File

@ -28,15 +28,16 @@
<!-- This contains the contents of the <svelte:head> component, if
the current page has one -->
%sapper.head%
<!-- Sapper creates a <script> tag containing `app/client.js`
and anything else it needs to hydrate the app and
initialise the router -->
%sapper.scripts%
</head>
<body>
<!-- The application will be rendered inside this element,
because `app/client.js` references it -->
<div id="sapper">%sapper.html%</div>
<!-- Sapper creates a <script> tag containing `app/client.js`
and anything else it needs to hydrate the app and
initialise the router -->
%sapper.scripts%
</body>
</html>

10
tsconfig.json Normal file
View File

@ -0,0 +1,10 @@
{
"extends": "@tsconfig/svelte/tsconfig.json",
"include": ["src/**/*", "src/node_modules"],
"exclude": ["node_modules/*", "__sapper__/*", "static/*"],
"compilerOptions": {
"types": ["svelte", "node", "@sapper"],
"typeRoots": ["typings"],
"target": "ES2017"
}
}

41
typings/@sapper/index.d.ts vendored Normal file
View File

@ -0,0 +1,41 @@
declare module '@sapper/app' {
interface Redirect {
statusCode: number
location: string
}
function goto(
href: string,
opts: { replaceState: boolean; noscroll: boolean }
): Promise<unknown>
function prefetch(
href: string
): Promise<{ redirect?: Redirect; data?: unknown }>
function prefetchRoutes(pathnames: string[]): Promise<unknown>
function start(opts: { target: Node }): Promise<unknown>
const stores: () => unknown
export { goto, prefetch, prefetchRoutes, start, stores }
}
declare module '@sapper/server' {
import { RequestHandler } from 'express'
interface MiddlewareOptions {
session?: (req: Express.Request, res: Express.Response) => unknown
ignore?: unknown
}
function middleware(opts?: MiddlewareOptions): RequestHandler
export { middleware }
}
declare module '@sapper/service-worker' {
const timestamp: number
const files: string[]
const shell: string[]
const routes: { pattern: RegExp }[]
export { timestamp, files, files as assets, shell, routes }
}