Skip to content

Server improvement #124

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Feb 5, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions cli/dev.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Application, serve } from '../server/mod.ts'
import { parsePortNumber } from '../server/util.ts'
import { getOptionValue, parsePortNumber } from '../server/util.ts'

export const helpMessage = `
Usage:
Expand All @@ -16,7 +16,7 @@ Options:
`

export default async function (workingDir: string, options: Record<string, string | boolean>) {
const port = parsePortNumber(String(options.p || options.port || '8080'))
const app = new Application(workingDir, 'development', Boolean(options.r || options.reload))
serve('localhost', port, app)
const port = parsePortNumber(getOptionValue(options, ['p', 'port'], '8080'))
await serve({ app, port, hostname: 'localhost' })
}
25 changes: 20 additions & 5 deletions cli/start.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import type { ServeOptions } from '../server/mod.ts'
import { Application, serve } from '../server/mod.ts'
import { parsePortNumber } from '../server/util.ts'
import { getOptionValue, parsePortNumber } from '../server/util.ts'
import log from '../shared/log.ts'

export const helpMessage = `
Usage:
Expand All @@ -9,16 +11,29 @@ Usage:
if the <dir> is empty, the current directory will be used.

Options:
-hn, --hostname <hostname> The address at which the server is to be started
-p, --port <port> A port number to start the aleph.js app, default is 8080
--hostname <hostname> The address at which the server is to be started
--cert <certFile> The server certificate file
--key <keyFile> The server public key file
-L, --log-level <log-level> Set log level [possible values: debug, info]
-r, --reload Reload source code cache
-h, --help Prints help message
`

export default async function (workingDir: string, options: Record<string, string | boolean>) {
const host = String(options.hn || options.hostname || 'localhost')
const port = parsePortNumber(String(options.p || options.port || '8080'))
const app = new Application(workingDir, 'production', Boolean(options.r || options.reload))
serve(host, port, app)
const port = parsePortNumber(getOptionValue(options, ['p', 'port'], '8080'))
const hostname = getOptionValue(options, ['hostname'], 'localhost')
const certFile = getOptionValue(options, ['cert'])
const keyFile = getOptionValue(options, ['key'])
const opts: ServeOptions = { app, port, hostname }
if (certFile && keyFile) {
opts.certFile = certFile
opts.keyFile = keyFile
} else if (certFile) {
log.fatal('missing `--key` option')
} else if (keyFile) {
log.fatal('missing `--cert` option')
}
await serve(opts)
}
4 changes: 2 additions & 2 deletions deps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@ export { ensureDir } from 'https://deno.land/[email protected]/fs/ensure_dir.ts'
export { walk } from 'https://deno.land/[email protected]/fs/walk.ts'
export { Sha1 } from 'https://deno.land/[email protected]/hash/sha1.ts'
export { Sha256 } from 'https://deno.land/[email protected]/hash/sha256.ts'
export { listenAndServe, serve } from 'https://deno.land/[email protected]/http/server.ts'
export { listenAndServe, serve, serveTLS } from 'https://deno.land/[email protected]/http/server.ts'
export * as bufio from 'https://deno.land/[email protected]/io/bufio.ts'
export * as path from 'https://deno.land/[email protected]/path/mod.ts'
export * as ws from 'https://deno.land/[email protected]/ws/mod.ts'
// deno.land/x
export * as brotli from 'https://deno.land/x/[email protected]/mod.ts'
export { gzipDecode, gzipEncode } from 'https://deno.land/x/[email protected]/mod.ts'
// esm.sh
export { default as CleanCSS } from 'https://esm.sh/clean-css@4.2.3?no-check'
export { default as CleanCSS } from 'https://esm.sh/clean-css@5.0.1?no-check'
export { default as postcss } from 'https://esm.sh/[email protected]'
export type { AcceptedPlugin } from 'https://esm.sh/[email protected]'
export { minify } from 'https://esm.sh/[email protected]'
Expand Down
85 changes: 57 additions & 28 deletions framework/core/routing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,22 +17,33 @@ export type RouteModule = {
readonly asyncDeps?: DependencyDescriptor[]
}

export type RoutingOptions = {
routes?: Route[]
rewrites?: Record<string, string>
baseUrl?: string
defaultLocale?: string
locales?: string[]
}

export class Routing {
private _routes: Route[]
private _baseUrl: string
private _defaultLocale: string
private _locales: string[]
private _routes: Route[]
private _rewrites: Record<string, string>

constructor(
routes: Route[] = [],
baseUrl: string = '/',
defaultLocale: string = 'en',
locales: string[] = []
) {
this._routes = routes
constructor({
baseUrl = '/',
defaultLocale = 'en',
locales = [],
routes = [],
rewrites = {},
}: RoutingOptions) {
this._baseUrl = baseUrl
this._defaultLocale = defaultLocale
this._locales = locales
this._routes = routes
this._rewrites = rewrites
}

get baseUrl() {
Expand Down Expand Up @@ -107,10 +118,10 @@ export class Routing {

createRouter(location?: { pathname: string, search?: string }): [RouterURL, RouteModule[]] {
const loc = location || (window as any).location || { pathname: '/' }
const query = new URLSearchParams(loc.search)
const url = rewriteURL(loc.pathname + (loc.search || ''), this._baseUrl, this._rewrites)

let locale = this._defaultLocale
let pathname = util.cleanPath(util.trimPrefix(loc.pathname, this._baseUrl))
let pathname = decodeURI(url.pathname)
let pagePath = ''
let params: Record<string, string> = {}
let chain: RouteModule[] = []
Expand Down Expand Up @@ -139,7 +150,7 @@ export class Routing {
}
}, true)

return [{ locale, pathname, pagePath, params, query }, chain]
return [{ locale, pathname, pagePath, params, query: url.searchParams }, chain]
}

lookup(callback: (path: Route[]) => Boolean | void) {
Expand All @@ -149,20 +160,20 @@ export class Routing {
private _lookup(
callback: (path: Route[]) => Boolean | void,
skipNestIndex = false,
__tracing: Route[] = [],
__routes = this._routes
_tracing: Route[] = [],
_routes = this._routes
) {
for (const route of __routes) {
if (skipNestIndex && __tracing.length > 0 && route.path === '/') {
for (const route of _routes) {
if (skipNestIndex && _tracing.length > 0 && route.path === '/') {
continue
}
if (callback([...__tracing, route]) === false) {
if (callback([..._tracing, route]) === false) {
return false
}
}
for (const route of __routes) {
for (const route of _routes) {
if (route.path !== '/' && route.children?.length) {
if (this._lookup(callback, skipNestIndex, [...__tracing, route], route.children) === false) {
if (this._lookup(callback, skipNestIndex, [..._tracing, route], route.children) === false) {
return false
}
}
Expand Down Expand Up @@ -201,14 +212,41 @@ function matchPath(routePath: string, locPath: string): [Record<string, string>,
return [params, true]
}

/** `rewriteURL` returns a rewrited URL */
export function rewriteURL(reqUrl: string, baseUrl: string, rewrites: Record<string, string>): URL {
const url = new URL('http://localhost' + reqUrl)
if (baseUrl !== '/') {
url.pathname = util.trimPrefix(decodeURI(url.pathname), baseUrl)
}
for (const path in rewrites) {
const to = rewrites[path]
const [params, ok] = matchPath(path, decodeURI(url.pathname))
if (ok) {
const { searchParams } = url
url.href = 'http://localhost' + util.cleanPath(to.replace(/:(.+)(\/|&|$)/g, (s, k, e) => {
if (k in params) {
return params[k] + e
}
return s
}))
for (const [key, value] of url.searchParams.entries()) {
searchParams.append(key, value)
}
url.search = searchParams.toString()
break
}
}
return url
}

export async function redirect(url: string, replace?: boolean) {
const { location, history } = window as any

if (!util.isNEString(url)) {
return
}

if (isHttpUrl(url)) {
if (util.isLikelyHttpURL(url) || url.startsWith('file://') || url.startsWith('mailto:')) {
location.href = url
return
}
Expand All @@ -222,15 +260,6 @@ export async function redirect(url: string, replace?: boolean) {
events.emit('popstate', { type: 'popstate', resetScroll: true })
}

export function isHttpUrl(url: string) {
try {
const { protocol } = new URL(url)
return protocol === 'https:' || protocol === 'http:'
} catch (error) {
return false
}
}

export function isModuleURL(url: string) {
for (const ext of moduleExts) {
if (url.endsWith('.' + ext)) {
Expand Down
26 changes: 25 additions & 1 deletion framework/core/routing_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,15 @@ import { assertEquals } from 'https://deno.land/[email protected]/testing/asserts.ts'
import { Routing } from './routing.ts'

Deno.test(`routing`, () => {
const routing = new Routing([], '/', 'en', ['en', 'zh-CN'])
const routing = new Routing({
locales: ['en', 'zh-CN'],
rewrites: {
'/Hello World': '/hello-world',
'/你好世界': '/zh-CN/hello-world',
}
})
routing.update({ url: '/pages/index.tsx', hash: '' })
routing.update({ url: '/pages/hello-world.tsx', hash: '' })
routing.update({ url: '/pages/blog/index.tsx', hash: '' })
routing.update({ url: '/pages/blog/[slug].tsx', hash: '' })
routing.update({ url: '/pages/user/index.tsx', hash: '' })
Expand All @@ -18,6 +25,7 @@ Deno.test(`routing`, () => {

assertEquals(routing.paths, [
'/',
'/hello-world',
'/blog',
'/user',
'/docs',
Expand All @@ -44,6 +52,22 @@ Deno.test(`routing`, () => {
assertEquals(chain, [{ url: '/pages/index.tsx', hash: 'hsidfshy3yhfya49848' }])
}

{
const [router, chain] = routing.createRouter({ pathname: '/Hello World' })
assertEquals(router.locale, 'en')
assertEquals(router.pathname, '/hello-world')
assertEquals(router.pagePath, '/hello-world')
assertEquals(chain, [{ url: '/pages/hello-world.tsx', hash: '' }])
}

{
const [router, chain] = routing.createRouter({ pathname: '/你好世界' })
assertEquals(router.locale, 'zh-CN')
assertEquals(router.pathname, '/hello-world')
assertEquals(router.pagePath, '/hello-world')
assertEquals(chain, [{ url: '/pages/hello-world.tsx', hash: '' }])
}

{
const [router, chain] = routing.createRouter({ pathname: '/blog' })
assertEquals(router.locale, 'en')
Expand Down
6 changes: 3 additions & 3 deletions framework/react/anchor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { AnchorHTMLAttributes, CSSProperties, MouseEvent, PropsWithChildren
import { createElement, useCallback, useEffect, useMemo } from 'https://esm.sh/react'
import util from '../../shared/util.ts'
import events from '../core/events.ts'
import { isHttpUrl, redirect } from '../core/routing.ts'
import { redirect } from '../core/routing.ts'
import { useRouter } from './hooks.ts'

const prefetchedPages = new Set<string>()
Expand Down Expand Up @@ -39,7 +39,7 @@ export default function Anchor(props: AnchorProps) {
if (!util.isNEString(propHref)) {
return ''
}
if (isHttpUrl(propHref)) {
if (util.isLikelyHttpURL(propHref)) {
return propHref
}
let [pathname, search] = util.splitBy(propHref, '?')
Expand Down Expand Up @@ -72,7 +72,7 @@ export default function Anchor(props: AnchorProps) {
return undefined
}, [href, propAriaCurrent])
const prefetch = useCallback(() => {
if (href && !isHttpUrl(href) && href !== currentHref && !prefetchedPages.has(href)) {
if (href && !util.isLikelyHttpURL(href) && href !== currentHref && !prefetchedPages.has(href)) {
events.emit('fetch-page-module', { href })
prefetchedPages.add(href)
}
Expand Down
16 changes: 6 additions & 10 deletions framework/react/bootstrap.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,20 @@
import type { ComponentType } from 'https://esm.sh/react'
import { createElement } from 'https://esm.sh/react'
import { hydrate, render } from 'https://esm.sh/react-dom'
import util from "../../shared/util.ts"
import { Route, RouteModule, Routing } from '../core/routing.ts'
import util from '../../shared/util.ts'
import { RouteModule, Routing, RoutingOptions } from '../core/routing.ts'
import type { PageRoute } from './pageprops.ts'
import { createPageProps } from './pageprops.ts'
import Router from './router.ts'
import { importModule, loadPageDataFromTag } from './util.ts'

type Options = {
baseUrl: string
defaultLocale: string
locales: string[]
routes: Route[]
type BootstrapOptions = Required<RoutingOptions> & {
sharedModules: RouteModule[],
renderMode: 'ssr' | 'spa'
}

export default async function bootstrap(options: Options) {
const { baseUrl, defaultLocale, locales, routes, sharedModules, renderMode } = options
export default async function bootstrap(options: BootstrapOptions) {
const { baseUrl, defaultLocale, locales, routes, rewrites, sharedModules, renderMode } = options
const { document } = window as any
const customComponents: Record<string, ComponentType> = {}
await Promise.all(sharedModules.map(async mod => {
Expand All @@ -32,7 +28,7 @@ export default async function bootstrap(options: Options) {
break
}
}))
const routing = new Routing(routes, baseUrl, defaultLocale, locales)
const routing = new Routing({ routes, rewrites, baseUrl, defaultLocale, locales })
const [url, pageModuleChain] = routing.createRouter()
const imports = await Promise.all(pageModuleChain.map(async mod => {
const [{ default: Component }] = await Promise.all([
Expand Down
2 changes: 1 addition & 1 deletion plugins/sass_test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { assertEquals } from 'https://deno.land/std@0.83.0/testing/asserts.ts'
import { assertEquals } from 'https://deno.land/std@0.85.0/testing/asserts.ts'
import plugin from './sass.ts'

Deno.test('scss loader plugin', async () => {
Expand Down
2 changes: 1 addition & 1 deletion plugins/wasm_test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { assertEquals } from 'https://deno.land/std@0.83.0/testing/asserts.ts'
import { assertEquals } from 'https://deno.land/std@0.85.0/testing/asserts.ts'
import plugin from './wasm.ts'

Deno.test('wasm loader plugin', async () => {
Expand Down
Loading