Skip to content
This repository was archived by the owner on Jul 6, 2025. It is now read-only.

Commit 55808b3

Browse files
author
Wenjie Xia
committed
feat: support custom server
1 parent 3175ae4 commit 55808b3

File tree

9 files changed

+286
-248
lines changed

9 files changed

+286
-248
lines changed

cli/build.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Project } from '../server/project.ts'
1+
import { Appliaction } from '../server/app.ts'
22

33
export const helpMessage = `
44
Usage:
@@ -13,8 +13,12 @@ Options:
1313
-h, --help Prints help message
1414
`
1515

16-
export default async function (appDir: string, options: Record<string, string | boolean>) {
17-
const project = new Project(appDir, 'production', Boolean(options.r || options.reload))
18-
await project.build()
16+
export default async function (workingDir: string, options: Record<string, string | boolean>) {
17+
const app = new Appliaction({
18+
workingDir,
19+
mode: 'production',
20+
reload: Boolean(options.r || options.reload)
21+
})
22+
await app.build()
1923
Deno.exit(0)
2024
}

cli/dev.ts

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
import { start } from '../server/server.ts'
2-
import log from '../shared/log.ts'
1+
import { Appliaction } from '../server/app.ts'
2+
import { start } from '../server/mod.ts'
3+
import { parsePortNumber } from '../server/util.ts'
34

45
export const helpMessage = `
56
Usage:
@@ -15,11 +16,12 @@ Options:
1516
-h, --help Prints help message
1617
`
1718

18-
export default async function (appDir: string, options: Record<string, string | boolean>) {
19-
const port = parseInt(String(options.p || options.port || '8080'))
20-
if (isNaN(port) || port <= 0 || !Number.isInteger(port)) {
21-
log.error(`invalid port '${options.port || options.p}'`)
22-
Deno.exit(1)
23-
}
24-
start(appDir, 'localhost', port, true, Boolean(options.r || options.reload))
19+
export default async function (workingDir: string, options: Record<string, string | boolean>) {
20+
const port = parsePortNumber(String(options.p || options.port || '8080'))
21+
const app = new Appliaction({
22+
workingDir,
23+
mode: 'development',
24+
reload: Boolean(options.r || options.reload)
25+
})
26+
start('localhost', port, app)
2527
}

cli/init.ts

Lines changed: 36 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -4,33 +4,6 @@ import log from '../shared/log.ts'
44
import util from '../shared/util.ts'
55
import { VERSION } from '../version.ts'
66

7-
const gitignore = [
8-
'.DS_Store',
9-
'Thumbs.db',
10-
'.aleph/',
11-
'dist/',
12-
]
13-
14-
const vscExtensions = {
15-
'recommendations': [
16-
'denoland.vscode-deno'
17-
]
18-
}
19-
20-
const vscSettings = {
21-
'files.eol': '\n',
22-
'files.trimTrailingWhitespace': true,
23-
'files.exclude': {
24-
'**/.git': true,
25-
'**/.DS_Store': true,
26-
'**/Thumbs.db': true,
27-
'**/.aleph': true
28-
},
29-
'deno.enable': true,
30-
'deno.unstable': true,
31-
'deno.import_map': './import_map.json'
32-
}
33-
347
export const helpMessage = `
358
Usage:
369
aleph init <dir> [...options]
@@ -50,6 +23,12 @@ export default async function (appDir: string, options: Record<string, string |
5023
log.info('Saving template...')
5124
const tarData = gzipDecode(gzData)
5225
const entryList = new Untar(new Deno.Buffer(tarData))
26+
const gitignore = [
27+
'.DS_Store',
28+
'Thumbs.db',
29+
'.aleph/',
30+
'dist/',
31+
]
5332

5433
// todo: add template select ui
5534
let template = 'hello-world'
@@ -65,22 +44,33 @@ export default async function (appDir: string, options: Record<string, string |
6544
await Deno.copy(entry, file)
6645
}
6746
}
68-
69-
await ensureDir(path.join(appDir, '.vscode'))
70-
await Deno.writeTextFile(path.join(appDir, '.vscode', 'extensions.json'), JSON.stringify(vscExtensions, undefined, 4))
71-
await Deno.writeTextFile(path.join(appDir, '.vscode', 'settings.json'), JSON.stringify(vscSettings, undefined, 4))
7247
await Deno.writeTextFile(path.join(appDir, '.gitignore'), gitignore.join('\n'))
7348
await Deno.writeTextFile(path.join(appDir, 'import_map.json'), JSON.stringify({
7449
imports: {
7550
'aleph': `https://deno.land/x/aleph@v${VERSION}/mod.ts`,
76-
'aleph/react': `https://deno.land/x/aleph@v${VERSION}/react.ts`,
7751
'aleph/': `https://deno.land/x/aleph@v${VERSION}/`,
7852
'react': 'https://esm.sh/[email protected]',
7953
'react-dom': 'https://esm.sh/[email protected]',
8054
},
8155
scopes: {}
8256
}, undefined, 4))
8357

58+
if (await confirm(`Add VS Code workspace settings?`)) {
59+
const extensions = {
60+
'recommendations': [
61+
'denoland.vscode-deno'
62+
]
63+
}
64+
const settigns = {
65+
'deno.enable': true,
66+
'deno.unstable': true,
67+
'deno.import_map': './import_map.json'
68+
}
69+
await ensureDir(path.join(appDir, '.vscode'))
70+
await Deno.writeTextFile(path.join(appDir, '.vscode', 'extensions.json'), JSON.stringify(extensions, undefined, 4))
71+
await Deno.writeTextFile(path.join(appDir, '.vscode', 'settings.json'), JSON.stringify(settigns, undefined, 4))
72+
}
73+
8474
log.info('Done')
8575
log.info('---')
8676
log.info(colors.dim('Aleph.js is ready to Go.'))
@@ -91,3 +81,17 @@ export default async function (appDir: string, options: Record<string, string |
9181
log.info('---')
9282
Deno.exit(0)
9383
}
84+
85+
async function ask(question: string = ':', stdin = Deno.stdin, stdout = Deno.stdout) {
86+
await stdout.write(new TextEncoder().encode(question + ' '))
87+
const buf = new Uint8Array(1024)
88+
const n = <number>await stdin.read(buf)
89+
const answer = new TextDecoder().decode(buf.subarray(0, n))
90+
return answer.trim()
91+
}
92+
93+
async function confirm(question: string = 'are you sure?') {
94+
let a: string
95+
while (!/^(y(es)?|no?)$/i.test(a = (await ask(question + ' [y/n]')).trim())) { }
96+
return a.charAt(0).toLowerCase() === 'y'
97+
}

cli/start.ts

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
import log from '../shared/log.ts'
1+
import { Appliaction } from '../server/app.ts'
2+
import { start } from '../server/mod.ts'
3+
import { parsePortNumber } from '../server/util.ts'
24

35
export const helpMessage = `
46
Usage:
@@ -15,13 +17,13 @@ Options:
1517
-h, --help Prints help message
1618
`
1719

18-
export default async function (appDir: string, options: Record<string, string | boolean>) {
19-
const { start } = await import('../server/server.ts')
20+
export default async function (workingDir: string, options: Record<string, string | boolean>) {
2021
const host = String(options.hn || options.hostname || 'localhost')
21-
const port = parseInt(String(options.p || options.port || '8080'))
22-
if (isNaN(port) || port <= 0 || !Number.isInteger(port)) {
23-
log.error(`invalid port '${options.port || options.p}'`)
24-
Deno.exit(1)
25-
}
26-
start(appDir, host, port, false, Boolean(options.r || options.reload))
22+
const port = parsePortNumber(String(options.p || options.port || '8080'))
23+
const app = new Appliaction({
24+
workingDir,
25+
mode: 'production',
26+
reload: Boolean(options.r || options.reload)
27+
})
28+
start(host, port, app)
2729
}

server/project.ts renamed to server/app.ts

Lines changed: 24 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -7,25 +7,23 @@ import { hashShort, reFullVersion, reHashJs, reHashResolve, reHttp, reLocaleID,
77
import { ensureTextFile, existsDirSync, existsFileSync } from '../shared/fs.ts'
88
import log from '../shared/log.ts'
99
import util from '../shared/util.ts'
10-
import type { APIHandler, Config, RouterURL, ServerRequest } from '../types.ts'
10+
import type { Config, RouterURL, ServerRequest } from '../types.ts'
1111
import { VERSION } from '../version.ts'
1212
import { Request } from './api.ts'
13-
import type { DependencyDescriptor, ImportMap, Module, RenderResult } from './types.ts'
13+
import type { AppliactionOptions, DependencyDescriptor, ImportMap, Module, RenderResult } from './types.ts'
1414
import { AlephRuntimeCode, cleanupCompilation, createHtml, fixImportMap, fixImportUrl, formatBytesWithColor, getAlephPkgUrl, getRelativePath, newModule, respondError } from './util.ts'
1515

16+
const defaultOptions: Required<AppliactionOptions> = {
17+
workingDir: '.',
18+
mode: 'production',
19+
reload: false
20+
}
21+
1622
/**
17-
* A Project to manage the Aleph.js appliaction.
18-
* core functions include:
19-
* - manage deps
20-
* - compile & bundle
21-
* - apply plugins
22-
* - map page/API routes
23-
* - watch file changes
24-
* - call APIs
25-
* - SSR/SSG
23+
* The Alep Appliaction class.
2624
*/
27-
export class Project {
28-
readonly appRoot: string
25+
export class Appliaction {
26+
readonly workingDir: string
2927
readonly mode: 'development' | 'production'
3028
readonly config: Readonly<Required<Config>>
3129
readonly importMap: Readonly<{ imports: ImportMap, scopes: Record<string, ImportMap> }>
@@ -44,12 +42,13 @@ export class Project {
4442
#postcssReady: Promise<void[]> | null = null
4543
#reloading = false
4644

47-
constructor(appDir: string, mode: 'development' | 'production', reload = false) {
48-
this.appRoot = path.resolve(appDir)
45+
constructor(options: AppliactionOptions = defaultOptions) {
46+
const { workingDir, mode, reload } = Object.assign({}, defaultOptions, options)
47+
this.workingDir = path.resolve(workingDir)
4948
this.mode = mode
5049
this.config = {
5150
framework: 'react',
52-
srcDir: existsDirSync(path.join(this.appRoot, '/src/pages')) ? '/src' : '/',
51+
srcDir: existsDirSync(path.join(this.workingDir, '/src/pages')) ? '/src' : '/',
5352
outputDir: '/dist',
5453
baseUrl: '/',
5554
defaultLocale: 'en',
@@ -74,15 +73,15 @@ export class Project {
7473
}
7574

7675
get srcDir() {
77-
return path.join(this.appRoot, this.config.srcDir)
76+
return path.join(this.workingDir, this.config.srcDir)
7877
}
7978

8079
get buildDir() {
81-
return path.join(this.appRoot, '.aleph', this.mode)
80+
return path.join(this.workingDir, '.aleph', this.mode)
8281
}
8382

8483
get outputDir() {
85-
return path.join(this.appRoot, this.config.outputDir)
84+
return path.join(this.workingDir, this.config.outputDir)
8685
}
8786

8887
isHMRable(url: string) {
@@ -149,7 +148,7 @@ export class Project {
149148
}
150149
}
151150

152-
async callAPI(req: ServerRequest, loc: { pathname: string, search?: string }): Promise<APIHandler | null> {
151+
async callAPI(req: ServerRequest, loc: { pathname: string, search?: string }) {
153152
const [url, chain] = this.#apiRouting.createRouter({
154153
...loc,
155154
pathname: decodeURI(loc.pathname)
@@ -172,7 +171,6 @@ export class Project {
172171
} else {
173172
respondError(req, 404, 'page not found')
174173
}
175-
return null
176174
}
177175

178176
async getSSRData(loc: { pathname: string, search?: string }): Promise<[number, any]> {
@@ -245,7 +243,7 @@ export class Project {
245243
await this.ssg()
246244

247245
// copy public assets
248-
const publicDir = path.join(this.appRoot, 'public')
246+
const publicDir = path.join(this.workingDir, 'public')
249247
if (existsDirSync(publicDir)) {
250248
let n = 0
251249
for await (const { path: p } of walk(publicDir, { includeDirs: false, skip: [/(^|\/)\.DS_Store$/] })) {
@@ -332,7 +330,7 @@ export class Project {
332330

333331
/** load config from `aleph.config.(json|mjs|js|ts)` */
334332
private async loadConfig() {
335-
const importMapFile = path.join(this.appRoot, 'import_map.json')
333+
const importMapFile = path.join(this.workingDir, 'import_map.json')
336334
if (existsFileSync(importMapFile)) {
337335
const importMap = JSON.parse(await Deno.readTextFile(importMapFile))
338336
const imports: ImportMap = fixImportMap(importMap.imports)
@@ -348,7 +346,7 @@ export class Project {
348346
const config: Record<string, any> = {}
349347

350348
for (const name of Array.from(['ts', 'js', 'mjs', 'json']).map(ext => `aleph.config.${ext}`)) {
351-
const p = path.join(this.appRoot, name)
349+
const p = path.join(this.workingDir, name)
352350
if (existsFileSync(p)) {
353351
log.info(' ✓', name)
354352
if (name.endsWith('.json')) {
@@ -425,7 +423,7 @@ export class Project {
425423
Object.assign(this.config, { postcss })
426424
} else {
427425
for (const name of Array.from(['ts', 'js', 'mjs', 'json']).map(ext => `postcss.config.${ext}`)) {
428-
const p = path.join(this.appRoot, name)
426+
const p = path.join(this.workingDir, name)
429427
if (existsFileSync(p)) {
430428
log.info(' ✓', name)
431429
if (name.endsWith('.json')) {
@@ -506,7 +504,7 @@ export class Project {
506504
await this.loadConfig()
507505

508506
// change current work dir to appDoot
509-
Deno.chdir(this.appRoot)
507+
Deno.chdir(this.workingDir)
510508

511509
// inject env variables
512510
Object.entries(this.config.env).forEach(([key, value]) => Deno.env.set(key, value))

0 commit comments

Comments
 (0)