Skip to content

Commit f8c0dc9

Browse files
authored
Merge pull request #124 from alephjs/tls
Server improvement: * feat: add --cert and --key options for start command. * feat: path rewrite * fix: fix baseUrl not applied
2 parents 8cdc8a4 + e42474c commit f8c0dc9

File tree

14 files changed

+416
-260
lines changed

14 files changed

+416
-260
lines changed

cli/dev.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Application, serve } from '../server/mod.ts'
2-
import { parsePortNumber } from '../server/util.ts'
2+
import { getOptionValue, parsePortNumber } from '../server/util.ts'
33

44
export const helpMessage = `
55
Usage:
@@ -16,7 +16,7 @@ Options:
1616
`
1717

1818
export default async function (workingDir: string, options: Record<string, string | boolean>) {
19-
const port = parsePortNumber(String(options.p || options.port || '8080'))
2019
const app = new Application(workingDir, 'development', Boolean(options.r || options.reload))
21-
serve('localhost', port, app)
20+
const port = parsePortNumber(getOptionValue(options, ['p', 'port'], '8080'))
21+
await serve({ app, port, hostname: 'localhost' })
2222
}

cli/start.ts

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1+
import type { ServeOptions } from '../server/mod.ts'
12
import { Application, serve } from '../server/mod.ts'
2-
import { parsePortNumber } from '../server/util.ts'
3+
import { getOptionValue, parsePortNumber } from '../server/util.ts'
4+
import log from '../shared/log.ts'
35

46
export const helpMessage = `
57
Usage:
@@ -9,16 +11,29 @@ Usage:
911
if the <dir> is empty, the current directory will be used.
1012
1113
Options:
12-
-hn, --hostname <hostname> The address at which the server is to be started
1314
-p, --port <port> A port number to start the aleph.js app, default is 8080
15+
--hostname <hostname> The address at which the server is to be started
16+
--cert <certFile> The server certificate file
17+
--key <keyFile> The server public key file
1418
-L, --log-level <log-level> Set log level [possible values: debug, info]
1519
-r, --reload Reload source code cache
1620
-h, --help Prints help message
1721
`
1822

1923
export default async function (workingDir: string, options: Record<string, string | boolean>) {
20-
const host = String(options.hn || options.hostname || 'localhost')
21-
const port = parsePortNumber(String(options.p || options.port || '8080'))
2224
const app = new Application(workingDir, 'production', Boolean(options.r || options.reload))
23-
serve(host, port, app)
25+
const port = parsePortNumber(getOptionValue(options, ['p', 'port'], '8080'))
26+
const hostname = getOptionValue(options, ['hostname'], 'localhost')
27+
const certFile = getOptionValue(options, ['cert'])
28+
const keyFile = getOptionValue(options, ['key'])
29+
const opts: ServeOptions = { app, port, hostname }
30+
if (certFile && keyFile) {
31+
opts.certFile = certFile
32+
opts.keyFile = keyFile
33+
} else if (certFile) {
34+
log.fatal('missing `--key` option')
35+
} else if (keyFile) {
36+
log.fatal('missing `--cert` option')
37+
}
38+
await serve(opts)
2439
}

deps.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,15 @@ export { ensureDir } from 'https://deno.land/[email protected]/fs/ensure_dir.ts'
77
export { walk } from 'https://deno.land/[email protected]/fs/walk.ts'
88
export { Sha1 } from 'https://deno.land/[email protected]/hash/sha1.ts'
99
export { Sha256 } from 'https://deno.land/[email protected]/hash/sha256.ts'
10-
export { listenAndServe, serve } from 'https://deno.land/[email protected]/http/server.ts'
10+
export { listenAndServe, serve, serveTLS } from 'https://deno.land/[email protected]/http/server.ts'
1111
export * as bufio from 'https://deno.land/[email protected]/io/bufio.ts'
1212
export * as path from 'https://deno.land/[email protected]/path/mod.ts'
1313
export * as ws from 'https://deno.land/[email protected]/ws/mod.ts'
1414
// deno.land/x
1515
export * as brotli from 'https://deno.land/x/[email protected]/mod.ts'
1616
export { gzipDecode, gzipEncode } from 'https://deno.land/x/[email protected]/mod.ts'
1717
// esm.sh
18-
export { default as CleanCSS } from 'https://esm.sh/clean-css@4.2.3?no-check'
18+
export { default as CleanCSS } from 'https://esm.sh/clean-css@5.0.1?no-check'
1919
export { default as postcss } from 'https://esm.sh/[email protected]'
2020
export type { AcceptedPlugin } from 'https://esm.sh/[email protected]'
2121
export { minify } from 'https://esm.sh/[email protected]'

framework/core/routing.ts

Lines changed: 57 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -17,22 +17,33 @@ export type RouteModule = {
1717
readonly asyncDeps?: DependencyDescriptor[]
1818
}
1919

20+
export type RoutingOptions = {
21+
routes?: Route[]
22+
rewrites?: Record<string, string>
23+
baseUrl?: string
24+
defaultLocale?: string
25+
locales?: string[]
26+
}
27+
2028
export class Routing {
21-
private _routes: Route[]
2229
private _baseUrl: string
2330
private _defaultLocale: string
2431
private _locales: string[]
32+
private _routes: Route[]
33+
private _rewrites: Record<string, string>
2534

26-
constructor(
27-
routes: Route[] = [],
28-
baseUrl: string = '/',
29-
defaultLocale: string = 'en',
30-
locales: string[] = []
31-
) {
32-
this._routes = routes
35+
constructor({
36+
baseUrl = '/',
37+
defaultLocale = 'en',
38+
locales = [],
39+
routes = [],
40+
rewrites = {},
41+
}: RoutingOptions) {
3342
this._baseUrl = baseUrl
3443
this._defaultLocale = defaultLocale
3544
this._locales = locales
45+
this._routes = routes
46+
this._rewrites = rewrites
3647
}
3748

3849
get baseUrl() {
@@ -107,10 +118,10 @@ export class Routing {
107118

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

112123
let locale = this._defaultLocale
113-
let pathname = util.cleanPath(util.trimPrefix(loc.pathname, this._baseUrl))
124+
let pathname = decodeURI(url.pathname)
114125
let pagePath = ''
115126
let params: Record<string, string> = {}
116127
let chain: RouteModule[] = []
@@ -139,7 +150,7 @@ export class Routing {
139150
}
140151
}, true)
141152

142-
return [{ locale, pathname, pagePath, params, query }, chain]
153+
return [{ locale, pathname, pagePath, params, query: url.searchParams }, chain]
143154
}
144155

145156
lookup(callback: (path: Route[]) => Boolean | void) {
@@ -149,20 +160,20 @@ export class Routing {
149160
private _lookup(
150161
callback: (path: Route[]) => Boolean | void,
151162
skipNestIndex = false,
152-
__tracing: Route[] = [],
153-
__routes = this._routes
163+
_tracing: Route[] = [],
164+
_routes = this._routes
154165
) {
155-
for (const route of __routes) {
156-
if (skipNestIndex && __tracing.length > 0 && route.path === '/') {
166+
for (const route of _routes) {
167+
if (skipNestIndex && _tracing.length > 0 && route.path === '/') {
157168
continue
158169
}
159-
if (callback([...__tracing, route]) === false) {
170+
if (callback([..._tracing, route]) === false) {
160171
return false
161172
}
162173
}
163-
for (const route of __routes) {
174+
for (const route of _routes) {
164175
if (route.path !== '/' && route.children?.length) {
165-
if (this._lookup(callback, skipNestIndex, [...__tracing, route], route.children) === false) {
176+
if (this._lookup(callback, skipNestIndex, [..._tracing, route], route.children) === false) {
166177
return false
167178
}
168179
}
@@ -201,14 +212,41 @@ function matchPath(routePath: string, locPath: string): [Record<string, string>,
201212
return [params, true]
202213
}
203214

215+
/** `rewriteURL` returns a rewrited URL */
216+
export function rewriteURL(reqUrl: string, baseUrl: string, rewrites: Record<string, string>): URL {
217+
const url = new URL('http://localhost' + reqUrl)
218+
if (baseUrl !== '/') {
219+
url.pathname = util.trimPrefix(decodeURI(url.pathname), baseUrl)
220+
}
221+
for (const path in rewrites) {
222+
const to = rewrites[path]
223+
const [params, ok] = matchPath(path, decodeURI(url.pathname))
224+
if (ok) {
225+
const { searchParams } = url
226+
url.href = 'http://localhost' + util.cleanPath(to.replace(/:(.+)(\/|&|$)/g, (s, k, e) => {
227+
if (k in params) {
228+
return params[k] + e
229+
}
230+
return s
231+
}))
232+
for (const [key, value] of url.searchParams.entries()) {
233+
searchParams.append(key, value)
234+
}
235+
url.search = searchParams.toString()
236+
break
237+
}
238+
}
239+
return url
240+
}
241+
204242
export async function redirect(url: string, replace?: boolean) {
205243
const { location, history } = window as any
206244

207245
if (!util.isNEString(url)) {
208246
return
209247
}
210248

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

225-
export function isHttpUrl(url: string) {
226-
try {
227-
const { protocol } = new URL(url)
228-
return protocol === 'https:' || protocol === 'http:'
229-
} catch (error) {
230-
return false
231-
}
232-
}
233-
234263
export function isModuleURL(url: string) {
235264
for (const ext of moduleExts) {
236265
if (url.endsWith('.' + ext)) {

framework/core/routing_test.ts

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,15 @@ import { assertEquals } from 'https://deno.land/[email protected]/testing/asserts.ts'
22
import { Routing } from './routing.ts'
33

44
Deno.test(`routing`, () => {
5-
const routing = new Routing([], '/', 'en', ['en', 'zh-CN'])
5+
const routing = new Routing({
6+
locales: ['en', 'zh-CN'],
7+
rewrites: {
8+
'/Hello World': '/hello-world',
9+
'/你好世界': '/zh-CN/hello-world',
10+
}
11+
})
612
routing.update({ url: '/pages/index.tsx', hash: '' })
13+
routing.update({ url: '/pages/hello-world.tsx', hash: '' })
714
routing.update({ url: '/pages/blog/index.tsx', hash: '' })
815
routing.update({ url: '/pages/blog/[slug].tsx', hash: '' })
916
routing.update({ url: '/pages/user/index.tsx', hash: '' })
@@ -18,6 +25,7 @@ Deno.test(`routing`, () => {
1825

1926
assertEquals(routing.paths, [
2027
'/',
28+
'/hello-world',
2129
'/blog',
2230
'/user',
2331
'/docs',
@@ -44,6 +52,22 @@ Deno.test(`routing`, () => {
4452
assertEquals(chain, [{ url: '/pages/index.tsx', hash: 'hsidfshy3yhfya49848' }])
4553
}
4654

55+
{
56+
const [router, chain] = routing.createRouter({ pathname: '/Hello World' })
57+
assertEquals(router.locale, 'en')
58+
assertEquals(router.pathname, '/hello-world')
59+
assertEquals(router.pagePath, '/hello-world')
60+
assertEquals(chain, [{ url: '/pages/hello-world.tsx', hash: '' }])
61+
}
62+
63+
{
64+
const [router, chain] = routing.createRouter({ pathname: '/你好世界' })
65+
assertEquals(router.locale, 'zh-CN')
66+
assertEquals(router.pathname, '/hello-world')
67+
assertEquals(router.pagePath, '/hello-world')
68+
assertEquals(chain, [{ url: '/pages/hello-world.tsx', hash: '' }])
69+
}
70+
4771
{
4872
const [router, chain] = routing.createRouter({ pathname: '/blog' })
4973
assertEquals(router.locale, 'en')

framework/react/anchor.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import type { AnchorHTMLAttributes, CSSProperties, MouseEvent, PropsWithChildren
22
import { createElement, useCallback, useEffect, useMemo } from 'https://esm.sh/react'
33
import util from '../../shared/util.ts'
44
import events from '../core/events.ts'
5-
import { isHttpUrl, redirect } from '../core/routing.ts'
5+
import { redirect } from '../core/routing.ts'
66
import { useRouter } from './hooks.ts'
77

88
const prefetchedPages = new Set<string>()
@@ -39,7 +39,7 @@ export default function Anchor(props: AnchorProps) {
3939
if (!util.isNEString(propHref)) {
4040
return ''
4141
}
42-
if (isHttpUrl(propHref)) {
42+
if (util.isLikelyHttpURL(propHref)) {
4343
return propHref
4444
}
4545
let [pathname, search] = util.splitBy(propHref, '?')
@@ -72,7 +72,7 @@ export default function Anchor(props: AnchorProps) {
7272
return undefined
7373
}, [href, propAriaCurrent])
7474
const prefetch = useCallback(() => {
75-
if (href && !isHttpUrl(href) && href !== currentHref && !prefetchedPages.has(href)) {
75+
if (href && !util.isLikelyHttpURL(href) && href !== currentHref && !prefetchedPages.has(href)) {
7676
events.emit('fetch-page-module', { href })
7777
prefetchedPages.add(href)
7878
}

framework/react/bootstrap.ts

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,20 @@
11
import type { ComponentType } from 'https://esm.sh/react'
22
import { createElement } from 'https://esm.sh/react'
33
import { hydrate, render } from 'https://esm.sh/react-dom'
4-
import util from "../../shared/util.ts"
5-
import { Route, RouteModule, Routing } from '../core/routing.ts'
4+
import util from '../../shared/util.ts'
5+
import { RouteModule, Routing, RoutingOptions } from '../core/routing.ts'
66
import type { PageRoute } from './pageprops.ts'
77
import { createPageProps } from './pageprops.ts'
88
import Router from './router.ts'
99
import { importModule, loadPageDataFromTag } from './util.ts'
1010

11-
type Options = {
12-
baseUrl: string
13-
defaultLocale: string
14-
locales: string[]
15-
routes: Route[]
11+
type BootstrapOptions = Required<RoutingOptions> & {
1612
sharedModules: RouteModule[],
1713
renderMode: 'ssr' | 'spa'
1814
}
1915

20-
export default async function bootstrap(options: Options) {
21-
const { baseUrl, defaultLocale, locales, routes, sharedModules, renderMode } = options
16+
export default async function bootstrap(options: BootstrapOptions) {
17+
const { baseUrl, defaultLocale, locales, routes, rewrites, sharedModules, renderMode } = options
2218
const { document } = window as any
2319
const customComponents: Record<string, ComponentType> = {}
2420
await Promise.all(sharedModules.map(async mod => {
@@ -32,7 +28,7 @@ export default async function bootstrap(options: Options) {
3228
break
3329
}
3430
}))
35-
const routing = new Routing(routes, baseUrl, defaultLocale, locales)
31+
const routing = new Routing({ routes, rewrites, baseUrl, defaultLocale, locales })
3632
const [url, pageModuleChain] = routing.createRouter()
3733
const imports = await Promise.all(pageModuleChain.map(async mod => {
3834
const [{ default: Component }] = await Promise.all([

plugins/sass_test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { assertEquals } from 'https://deno.land/std@0.83.0/testing/asserts.ts'
1+
import { assertEquals } from 'https://deno.land/std@0.85.0/testing/asserts.ts'
22
import plugin from './sass.ts'
33

44
Deno.test('scss loader plugin', async () => {

plugins/wasm_test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { assertEquals } from 'https://deno.land/std@0.83.0/testing/asserts.ts'
1+
import { assertEquals } from 'https://deno.land/std@0.85.0/testing/asserts.ts'
22
import plugin from './wasm.ts'
33

44
Deno.test('wasm loader plugin', async () => {

0 commit comments

Comments
 (0)