Skip to content

Commit

Permalink
feat: use '.tsx' file with default template.
Browse files Browse the repository at this point in the history
  • Loading branch information
chizuki committed Jul 27, 2022
1 parent 444cb6d commit d94465f
Show file tree
Hide file tree
Showing 15 changed files with 197 additions and 139 deletions.
3 changes: 3 additions & 0 deletions packages/core/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Fourze Core

**Node & Browser**
28 changes: 28 additions & 0 deletions packages/core/src/element.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { MaybePromise } from "./types"

export function createElement(tag: string, props: any = {}, ...children: string[]) {
if (!props || typeof props != "object" || Array.isArray(props)) {
props = {}
Expand Down Expand Up @@ -28,3 +30,29 @@ export function createElement(tag: string, props: any = {}, ...children: string[
">"
)
}

export const FourzeComponentSymbol = Symbol("FourzeComponent")

export interface FourzeComponentOption {
name?: string
setup?: () => MaybePromise<this["render"] | Record<string, any>>
render?: () => MaybePromise<JSX.Element>
}

export interface FourzeComponent extends FourzeComponentOption {
[FourzeComponentSymbol]: true
}

export function defineFourzeComponent(component: FourzeComponentOption | FourzeComponentOption["setup"]): FourzeComponent {
if (typeof component === "function") {
component = { setup: component }
}
return {
...component,
[FourzeComponentSymbol]: true
}
}

export function isFourzeComponent(component: any): component is FourzeComponent {
return component && component[FourzeComponentSymbol]
}
1 change: 1 addition & 0 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ export * from "./element"
export * from "./logger"
export * from "./mock"
export * from "./shared"
export * from "./types"
export * from "./utils"
62 changes: 30 additions & 32 deletions packages/core/src/logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,44 +31,42 @@ export interface FourzeLogger extends Logger {
level: number
}

export let logger: Logger = {
level: LOGGER_LEVELS.ALL,
function now() {
return dayjs().format("YYYY-MM-DD HH:mm:ss")
}

export class Logger {
level: number = LOGGER_LEVELS.INFO

readonly scope: string

constructor(scope: string) {
this.scope = scope
}
setLevel(level: number | string) {
this.level = typeof level === "string" ? LOGGER_LEVELS[level as keyof typeof LOGGER_LEVELS] ?? 0 : level
},
this.level = typeof level === "string" ? LOGGER_LEVELS[level as keyof typeof LOGGER_LEVELS] : level
}
info(...args: any[]) {
if (this.level <= LOGGER_LEVELS.INFO) {
console.info(`[INFO ${dayjs().format("YYYY-MM-DD HH:mm:ss")}]`, ...args)
}
},
this.log(LOGGER_LEVELS.INFO, ...args)
}
debug(...args: any[]) {
if (this.level <= LOGGER_LEVELS.DEBUG) {
console.debug(`[DEBUG ${dayjs().format("YYYY-MM-DD HH:mm:ss")}]`, ...args)
}
},
this.log(LOGGER_LEVELS.DEBUG, ...args)
}
warn(...args: any[]) {
if (this.level <= LOGGER_LEVELS.WARN) {
console.warn(`[WARNING ${dayjs().format("YYYY-MM-DD HH:mm:ss")}]`, ...args)
}
},
trace(...args: any[]) {
if (this.level <= LOGGER_LEVELS.TRACE) {
console.trace(`[TRACE ${dayjs().format("YYYY-MM-DD HH:mm:ss")}]`, ...args)
}
},

this.log(LOGGER_LEVELS.WARN, ...args)
}
error(...args: any[]) {
if (this.level <= LOGGER_LEVELS.ERROR) {
console.error(`[ERROR ${dayjs().format("YYYY-MM-DD HH:mm:ss")}]`, ...args)
}
},
this.log(LOGGER_LEVELS.ERROR, ...args)
}
fatal(...args: any[]) {
if (this.level <= LOGGER_LEVELS.FATAL) {
console.error(`[FATAL ${dayjs().format("YYYY-MM-DD HH:mm:ss")}]`, ...args)
this.log(LOGGER_LEVELS.FATAL, ...args)
}
trace(...args: any[]) {
this.log(LOGGER_LEVELS.TRACE, ...args)
}
log(level: number, ...args: any[]) {
if (level >= this.level) {
console.log(`[${now()}] [${this.scope}]`, ...args)
}
}
} as FourzeLogger

export function setLogger(_logger: Logger) {
logger = _logger
}
1 change: 1 addition & 0 deletions packages/core/src/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export type MaybePromise<T = any> = Promise<T> | T
3 changes: 3 additions & 0 deletions packages/server/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Fourze Server

**Only Node**
14 changes: 6 additions & 8 deletions packages/server/src/app.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { createRequest, createResponse, FourzeRequest, FourzeResponse, FOURZE_VERSION, Logger, logger, setLogger } from "@fourze/core"
import { createRequest, createResponse, FourzeRequest, FourzeResponse, FOURZE_VERSION, Logger } from "@fourze/core"
import EventEmitter from "events"
import type { IncomingMessage, OutgoingMessage, Server } from "http"
import http from "http"
Expand Down Expand Up @@ -109,10 +109,6 @@ function injectEventEmitter(app: FourzeApp) {
app.listeners = function (event: string) {
return _emitter.listeners(event)
}

app.on("error", error => {
logger.error(error)
})
}

function normalizeAddress(address: AddressInfo | string | null): string {
Expand All @@ -133,9 +129,7 @@ export function createApp(options: FourzeAppOptions = {}) {

let _protocol = options.protocol ?? "http"

if (options.logger) {
setLogger(options.logger)
}
const logger = options.logger ?? new Logger("@fourze/server")

const middlewareMap = new Map<string, FourzeMiddleware[]>()

Expand Down Expand Up @@ -172,6 +166,10 @@ export function createApp(options: FourzeAppOptions = {}) {

injectEventEmitter(app)

app.on("error", error => {
logger.error(error)
})

app.use = function (param0: FourzeMiddleware | string, ...params: FourzeMiddleware[]) {
const base = typeof param0 === "string" ? param0 : "/"
const arr = (typeof param0 === "string" ? params : [param0, ...params]).filter(e => typeof e == "function")
Expand Down
2 changes: 1 addition & 1 deletion packages/server/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export * from "./app"
export * from "./router"
export * from "./renderer"
export * from "./router"
127 changes: 100 additions & 27 deletions packages/server/src/renderer.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { FourzeRequest, FourzeResponse } from "@fourze/core"
import { logger } from "@fourze/core"
import type { FourzeComponent, FourzeRequest, FourzeResponse } from "@fourze/core"
import { Logger } from "@fourze/core"
import { transform } from "esbuild"
import fs from "fs"
import mime from "mime"
import path from "path"
Expand Down Expand Up @@ -27,18 +28,26 @@ export interface FourzeRendererOptions {
fallbacks?: string[] | Record<string, string>
}

export type FourzeRenderer = FourzeMiddleware
export interface FourzeRenderer extends FourzeMiddleware {
templates: FourzeRenderTemplate[]
use: (...middlewares: FourzeRenderTemplate[]) => this
}

export type FourzeRenderTemplate = (context: FourzeRendererContext) => any
export type FourzeRenderTemplate = (request: FourzeRequest, response: FourzeResponse, context: FourzeRendererContext) => any

export interface FourzeRendererContext {
path: string
request: FourzeRequest
response: FourzeResponse
file: string

/**
* @see FourzeRendererOptions["dir"]
*/
dir: string

logger: Logger
}

export function renderFile(context: FourzeRendererContext) {
let p = context.path
export function renderFile(request: FourzeRequest, response: FourzeResponse, context: FourzeRendererContext) {
let p = context.file
const extensions = ["html", "htm"]
const maybes = [p].concat(extensions.map(ext => path.normalize(`${p}/index.${ext}`)))
do {
Expand All @@ -49,46 +58,99 @@ export function renderFile(context: FourzeRendererContext) {
} while (!!p)
}

export async function renderTsx(request: FourzeRequest, response: FourzeResponse, context: FourzeRendererContext) {
const file = path.normalize(context.file)

const maybes = file.endsWith(".jsx") || file.endsWith(".tsx") ? [file] : []
maybes.push(...["index.tsx", "index.jsx"].map(ext => path.normalize(`${file}/${ext}`)))

for (let maybe of maybes) {
if (fs.existsSync(maybe) && fs.statSync(maybe).isFile()) {
const raw = fs.readFileSync(maybe).toString()
const { code } = await transform(raw, {
target: "esnext",
format: "esm",
loader: "tsx",
banner: "import {createElement} from '@fourze/core'",

tsconfigRaw: {
compilerOptions: {
jsxFactory: "createElement",
jsxFragmentFactory: "Fragment"
}
}
})

const tmp = path.normalize(maybe.replace(".tsx", ".tmp.js"))

delete require.cache[tmp]

await fs.promises.writeFile(tmp, code)

const { default: mod } = await require(tmp)

let { render, setup } = mod as FourzeComponent

await fs.promises.rm(tmp)

if (setup) {
const setupReturn = await setup()
if (typeof setupReturn == "function") {
render = setupReturn as FourzeComponent["render"]
}
}

if (render && typeof render === "function") {
let content = await render()
response.setHeader("Content-Type", "text/html; charset=utf-8")
response.end(content)
}
}
}
}

/**
* @returns
*/
export function createRenderer(options: FourzeRendererOptions | string = {}): FourzeRenderer {
const dir = (options && typeof options === "object" ? options.dir : options) ?? process.cwd()
const templates = (options && typeof options == "object" ? options.templates : []) ?? []
const templates = (options && typeof options == "object" ? options.templates : [renderTsx]) ?? [renderTsx]
const base = typeof options == "string" ? "/" : options.base ?? "/"
const _fallbacks = (options && typeof options == "object" ? options.fallbacks : []) ?? []
const fallbacks = Array.isArray(_fallbacks) ? _fallbacks.map(f => [f, f]) : Object.entries(_fallbacks)
if (!templates.includes(renderFile)) {
templates.push(renderFile)
}

async function render(context: FourzeRendererContext) {
let content: Buffer | undefined
const logger = new Logger("@fourze/renderer")

async function render(request: FourzeRequest, response: FourzeResponse, context: FourzeRendererContext) {
for (let template of templates) {
content = await template(context)
if (!!content) {
break
const content = await template(request, response, context)
if (!!content || response.writableEnded) {
return content
}
}
return content
return renderFile(request, response, context)
}

const renderer = async function (request: FourzeRequest, response: FourzeResponse, next?: () => void | Promise<void>) {
const url = request.relativePath
if (url.startsWith(base)) {
const context = { path: path.join(dir, url), request, response }
const context = { file: path.join(dir, url), logger, dir }

let content = await render(context)
let content = await render(request, response, context)

if (response.writableEnded) {
return
}

if (!content) {
for (let [fr, to] of fallbacks) {
if (url.startsWith(fr)) {
to = path.normalize(path.join(dir, to))
context.path = to
content = await render(context)
context.file = to
content = await render(request, response, context)

if (!content) {
content = renderFile(context)
if (response.writableEnded) {
return
}

if (!!content) {
Expand All @@ -99,7 +161,7 @@ export function createRenderer(options: FourzeRendererOptions | string = {}): Fo
}
}

if (content) {
if (content && !response.writableEnded) {
logger.info("render page", url)
if (!response.hasHeader("Content-Type")) {
response.setHeader("Content-Type", mime.getType(url) ?? "text/html")
Expand All @@ -118,5 +180,16 @@ export function createRenderer(options: FourzeRendererOptions | string = {}): Fo
}
})

return renderer
Object.defineProperty(renderer, "templates", {
get() {
return templates
}
})

renderer.use = function (this: FourzeRenderer, ...arr: FourzeRenderTemplate[]) {
templates.push(...arr)
return this
}

return renderer as FourzeRenderer
}
3 changes: 2 additions & 1 deletion packages/server/src/router.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { FourzeBaseRoute, FourzeRequest, FourzeResponse, FourzeRoute, FourzeSetup } from "@fourze/core"
import { defineFourze, defineRoute, isFourze, isRoute, logger } from "@fourze/core"
import { defineFourze, defineRoute, isFourze, isRoute, Logger } from "@fourze/core"
import type { FSWatcher } from "chokidar"
import fs from "fs"
import { join, resolve } from "path"
Expand Down Expand Up @@ -52,6 +52,7 @@ export function createRouter(params: FourzeRouterOptions | FourzeSetup): FourzeR
const moduleNames = new Set(Array.from(options.moduleNames ?? []))

const routes: FourzeBaseRoute[] = Array.from(options.routes ?? [])
const logger = new Logger("@fourze/router")

const router = async function (request: FourzeRequest, response: FourzeResponse, next?: () => void | Promise<void>) {
const dispatchers = router.routes.map(e => e.dispatch)
Expand Down
1 change: 1 addition & 0 deletions packages/vite/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Fourze Vite Plugin
4 changes: 3 additions & 1 deletion packages/vite/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { Plugin } from "vite"

import { FourzeBaseRoute, logger } from "@fourze/core"
import { FourzeBaseRoute, Logger } from "@fourze/core"

import { createApp, createRouter, FourzeProxyOption, FourzeRouter } from "@fourze/server"
import { mockJs } from "./mock"
Expand Down Expand Up @@ -57,6 +57,8 @@ export function VitePluginFourze(options: VitePluginFourzeOptions = {}): Plugin
const pattern = Array.from(options.filePattern ?? [".ts$", ".js$"])
const hmr = options.hmr ?? true

const logger = new Logger("@fourze/vite")

logger.setLevel(options.logLevel ?? "off")

const proxy = Array.isArray(options.proxy)
Expand Down
Loading

0 comments on commit d94465f

Please sign in to comment.