Skip to content

Commit

Permalink
update core module
Browse files Browse the repository at this point in the history
  • Loading branch information
lixinyang123 committed Mar 25, 2024
1 parent 8319fe9 commit f0bd13b
Show file tree
Hide file tree
Showing 9 changed files with 358 additions and 9 deletions.
4 changes: 1 addition & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,7 @@
"main": "./src/main.js",
"type": "module",
"dependencies": {
"dotenv": "^16.4.5",
"moment": "^2.30.1",
"pejs": "^0.6.5"
"dotenv": "^16.4.5"
},
"scripts": {
"restore": "npm install",
Expand Down
7 changes: 3 additions & 4 deletions src/core/app.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import fs from 'fs'
import http from 'http'
import path from 'path'
import moment from 'moment'
import { pathToFileURL } from 'url'
import { Group } from './group.js'
import { Middleware } from './middleware.js'
Expand All @@ -17,16 +16,16 @@ export class App {
this.logger = logger

this.server = http.createServer(async (req, res) => {
this.logger.info(`${new Date()}\n${req.method} ${req.url}`)
let start = moment()
let date = new Date()
this.logger.info(`${date}\n${req.method} ${req.url}`)

await this.pipeline.invoke(
Request.object(req, this.host),
Response.object(res),
this.pipeline
)

this.logger.info(moment().diff(start) + "ms\n")
this.logger.info((new Date().getTime() - date.getTime()) + "ms\n")
})
}

Expand Down
69 changes: 69 additions & 0 deletions src/core/ejs/codegen.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
var GLOBAL_FNS = 'function _esc_(s){return (s+"").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;");}\n' +
'function _inline_(fn){var s=fn();return function(){return s;};}\n'

// compile a source tree down to javascript
export default function codegen(tree, opts) {
if (!opts) opts = {}

var global = [GLOBAL_FNS]
var cnt = 0

var compress = function (str) {
return opts.compress ? str.replace(/\s+/g, ' ') : str
}

var wrap = function (vars, body) {
return vars ? 'with(locals){var _r=[];var _b={};\n' + body + '}\n' : 'with(locals){\n' + body + '}\n'
}

var debugable = function (url) {
return '_' + (url || '').split('/').slice(-2).join('_').replace(/[^a-zA-Z]/g, '_') + '_' + (cnt++)
}

var stringify = function (tree) {
var src = ''
var pushBefore = false

var push = function (value) {
if (pushBefore) return src = src.slice(0, -3) + '+' + value + ');\n'
src += '_r.push(' + value + ');\n'
pushBefore = true
}

var logic = function (value) {
pushBefore = false
src += value + '\n'
}

tree.forEach(function (node) {
if (node.type === 'STATIC') return push(JSON.stringify(compress(node.value)))
if (node.type === 'EXPRESSION') return push('(' + node.value + ')')
if (node.type === 'ESCAPE_EXPRESSION') return push('_esc_(' + node.value + ')')
if (node.type === 'LOGIC') return logic(node.value)

var locals = node.locals || 'locals'
var name = node.name && JSON.stringify(node.name)
var decl = node.name && JSON.stringify(node.name + '$decl')
var id = debugable(node.url)

if (node.type === 'BLOCK_ANONYMOUS') {
global.push('function ' + id + '(_r,_b,locals){' + wrap(false, stringify(node.body)) + '}\n')
return logic(id + '(_r,_b,' + locals + ');')
}

if (node.type === 'BLOCK_DECLARE') {
logic('if (_b[' + decl + ']) _b[' + decl + '].toString=_inline_(_b[' + decl + '].toString);')
logic('_r.push(_b[' + decl + ']={toString:function(){return _b[' + name + ']();}});')
}

global.push('function ' + id + '(locals){' + wrap(true, stringify(node.body) + 'return _r;') + '}\n')
logic('_b[' + name + ']=function(){return ' + id + '(' + locals + ').join("");};')
})

return src
}

var main = debugable(opts.name)
var src = stringify(tree)
return global.join('') + 'module.exports=function ' + main + '(locals){locals=locals||{};' + wrap(true, src) + 'return _r.join("");};'
}
177 changes: 177 additions & 0 deletions src/core/ejs/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
import vm from 'vm'
import path from 'path'
import fs from 'fs'
import parse from './parse.js'
import codegen from './codegen.js'
import watch from './watch.js'
import sequence from './sequence.js'

var noop = function () { }

var requireSource = function (source) {
var module = { exports: {} }
vm.runInNewContext(source, { console: console, module: module })
return module.exports
}

export default function pejs(opts) {
if (!opts) opts = {}

opts.basedir = opts.basedir || process.cwd()

var templates = {}
var cache = templates.cache = {}
var compress = opts.compress

var resolvePath = function (name, dirname, callback) {
let fileName = undefined
let error = undefined

try {
fileName = path.resolve(dirname, name)
}
catch (err) {
error = err
}

callback(error, fileName)
}

templates.render = function (name, locals, callback) {
if (typeof locals === 'function') return templates.render(name, {}, locals)

locals = locals || {}

if (cache[name] && cache[name].render) {
var result

try {
result = cache[name].render(locals)
} catch (err) {
return callback(err)
}

return callback(null, result)
}

templates.compile(name, function (err, source) {
if (err) return callback(err)

try {
cache[name].render = cache[name].render || requireSource(source)
} catch (err) {
return callback(err)
}

templates.render(name, locals, callback)
})
}

templates.compile = function (name, opts, callback) {
if (typeof opts === 'function') return templates.compile(name, {}, opts)
if (cache[name] && cache[name].source) return callback(null, cache[name].source)

opts = opts || {}

var maybePrecompiled = function (callback) {
if (opts.cejs === false) return callback()

resolvePath(name, opts.dirname, function (err, path) {
if (!path) return callback()

fs.readFile(path + '.cejs', 'utf-8', function (err, src) {
if (err) return callback(err)

cache[name] = cache[name] || {}
cache[name].source = cache[name].source || src
callback(null, cache[name].source)
})
})
}

maybePrecompiled(function (err, source) {
if (source) return callback(null, source)

templates.parse(name, function (err, tree, url) {
if (err) return callback(err)

cache[name].source = cache[name].source || codegen(tree, { name: url, compress: compress })
callback(null, cache[name].source)
})
})
}

templates.parse = function (name, callback) {
var files = []

var onsource = function (filename, source, callback) {
var dirname = path.dirname(filename)
var tree = parse(source, opts)

files.push(filename)

var nodes = []
var visit = function (node) {
if (node.url) nodes.push(node)
if (node.body) node.body.forEach(visit)
}

tree.forEach(visit)

if (!nodes.length) return callback(null, tree, filename)

var i = 0
var loop = function () {
var node = nodes[i++]

if (!node) return callback(null, tree, filename)

resolveTemplate(node.url, dirname, function (err, resolved, url) {
if (err) return callback(err)

node.url = url
node.body = resolved
loop()
})
}

loop()
}

var resolveTemplate = function (name, dirname, callback) {
resolvePath(name, dirname, function (err, filename) {
if (err) return callback(err)

fs.readFile(filename, 'utf-8', function (err, source) {
if (err) return callback(err)

onsource(filename, source, callback)
})
})
}

sequence(callback, function (free) {
if (cache[name] && cache[name].tree) return free(null, cache[name].tree, cache[name].url)

resolveTemplate(name, opts.basedir, function (err, tree, url) {
if (err) return free(err)

cache[name] = cache[name] || {}
cache[name].tree = tree
cache[name].url = url

if (opts.watch === false) return free(null, tree, url)

watch(files, function () {
delete cache[name]
})

free(null, tree, url)
})
})
}

return templates
}

pejs.__express = pejs().render
56 changes: 56 additions & 0 deletions src/core/ejs/parse.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
var STATIC = 'STATIC'
var LOGIC = 'LOGIC'
var EXPRESSION = 'EXPRESSION'
var ESCAPE_EXPRESSION = 'ESCAPE_EXPRESSION'
var BLOCK_DECLARE = 'BLOCK_DECLARE'
var BLOCK_OVERRIDE = 'BLOCK_OVERRIDE'
var BLOCK_ANONYMOUS = 'BLOCK_ANONYMOUS'

var TOKEN_BEGIN = '<%'
var TOKEN_END = '%>'

var MATCH_BLOCK = /^(\w+)?\s*((?:'(?:(?:\\')|[^'])*')|(?:"(?:(?:\\")|[^"])*"))?(?:\s+(.+))?$/

// parse pejs source into a source tree
export default function parse(src) {
return Array.prototype.concat.apply([], src.split(TOKEN_END).map(function (slice) {
return slice.split(TOKEN_BEGIN)
})).map(function (data, i) {
if (i % 2 === 0) return data && { type: STATIC, value: data }

var pre = (data.match(/^(\S*)/g) || [])[0]
var end = (data.match(/(\S*)$/g) || [])[0]
var line = data.replace(/^\S*/g, '').replace(/\S*$/g, '').trim()
var live = pre[1] === '['
var auto = pre === '{{' ? BLOCK_DECLARE : BLOCK_OVERRIDE
var ctx = (pre + end).replace(/[\{\[]+/g, '{').replace(/[\}\]]+/g, '}')

if (pre === '') return { type: LOGIC, value: line }
if (pre === '#') return null
if (pre === '=') return { type: ESCAPE_EXPRESSION, value: line }
if (pre === '-') return { type: EXPRESSION, value: line }

line = (line.match(MATCH_BLOCK) || []).slice(1)
line = !line.length || (line[2] && !line[2]) ? {} : {
name: line[0],
url: line[1] && line[1].substr(1, line[1].length - 2).replace(/\\(.)/g, '$1'),
locals: line[2] && line[2].trim()
}

if (ctx === '{}' && line.name) return { type: auto, live: live, name: line.name, locals: line.locals, url: line.url, body: [] }
if (ctx === '{}' && line.url) return { type: BLOCK_ANONYMOUS, url: line.url, locals: line.locals }
if (ctx === '{' && line.name) return { type: auto, live: live, name: line.name, locals: line.locals, capture: 1, body: [] }
if (ctx === '}') return { capture: -1 }

throw new SyntaxError('could not parse: <%' + data + '%>')
}).reduce(function reduce(result, node) {
var last = result[result.length - 1]

if (!node) return result
if (!last || !last.capture) return result.concat(node)

last.capture += node.capture || 0
last.body = last.capture ? last.body.concat(node) : last.body.reduce(reduce, [])
return result
}, [])
}
14 changes: 14 additions & 0 deletions src/core/ejs/sequence.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
var free = true
var waiting = []

// "locks" the execution - let everyone else wait for something to finish
export default function lock(callback, fn) { // TODO: move to module
if (!free) return waiting.push(arguments)

free = false
fn(function () {
free = true
callback.apply(null, arguments)
if (waiting.length) lock.apply(null, waiting.shift())
})
}
28 changes: 28 additions & 0 deletions src/core/ejs/watch.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import fs from 'fs'

var noop = function () { }
var watchers = {}

export default function watch(filenames, fn) { // TODO: find or create a module that does caching/watching for us
var onchange = function () {
filenames.forEach(function (filename) {
if (!watchers[filename]) return
watchers[filename].removeListener('change', onchange)
})

fn()
}

filenames.forEach(function watchFile(filename) {
if (watchers[filename]) return watchers[filename].once('change', onchange)

watchers[filename] = fs.watch(filename, { persistent: false }, noop)
watchers[filename].setMaxListeners(0)
watchers[filename].once('change', function () {
delete watchers[filename]
this.close()
})

watchFile(filename, fn)
})
}
4 changes: 2 additions & 2 deletions src/core/response.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import pejs from 'pejs'
import path from 'path'
import ejs from './ejs/index.js'

var views = pejs();
var views = ejs();

export default class Response {

Expand Down
Loading

0 comments on commit f0bd13b

Please sign in to comment.