UNPKG

@maskedeng-tom/ssrsx

Version:
353 lines 15.8 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.ssrsx = void 0; const mime_types_1 = __importDefault(require("mime-types")); const fs_1 = __importDefault(require("fs")); const path_1 = __importDefault(require("path")); const events_1 = require("events"); const ws_1 = require("ws"); const log_1 = require("./lib/log"); const getDir_1 = require("./lib/getDir"); const compiler_1 = require("./core/compiler"); const errorConsole_1 = require("./lib/errorConsole"); const jsx_parser_1 = require("ssrsxjsx/jsx-parser"); const addSlash_1 = require("./router/lib/addSlash"); const sendData_1 = require("./server/sendData"); const support_1 = require("./server/support"); //////////////////////////////////////////////////////////////////////////////// let HotReloadDefault = 33730; //////////////////////////////////////////////////////////////////////////////// const ssrsx = (ssrsxOption) => __awaiter(void 0, void 0, void 0, function* () { ////////////////////////////////////////////////////////////////////////////// var _a, _b, _c, _d, _e, _f; const option = ssrsxOption !== null && ssrsxOption !== void 0 ? ssrsxOption : {}; if (option === null || option === void 0 ? void 0 : option.development) { option.hotReload = (_a = option.hotReload) !== null && _a !== void 0 ? _a : true; option.sourceMap = (_b = option.sourceMap) !== null && _b !== void 0 ? _b : true; } option.ignoreExtensions = (_c = option.ignoreExtensions) !== null && _c !== void 0 ? _c : [ 'js', 'ts', 'tsx', 'css', 'json', 'png', 'jpg', 'jpeg', 'gif', 'svg', 'ico', 'mp4', 'webm', 'ogg', 'mp3', 'wav', 'woff', 'woff2', 'ttf', 'otf', 'eot', ]; ////////////////////////////////////////////////////////////////////////////// (0, log_1.log)('-----------------------------------------------'); (0, log_1.log)('Start ssrsx'); (0, log_1.log)('-----------------------------------------------'); (0, log_1.log)('option =', option); (0, log_1.log)('-----------------------------------------------'); ////////////////////////////////////////////////////////////////////////////// const clientScriptBaseUrl = (_d = option === null || option === void 0 ? void 0 : option.clientScriptBaseUrl) !== null && _d !== void 0 ? _d : '/ssrsx-client-script/'; const requireJs = 'require.js'; const eventLoaderJs = 'event-loader.js'; ////////////////////////////////////////////////////////////////////////////// const clientRoot = (0, getDir_1.getDir)(option === null || option === void 0 ? void 0 : option.clientRoot, './src/client'); const staticRoot = (0, getDir_1.getDir)(option === null || option === void 0 ? void 0 : option.staticRoot, './src/static'); const requireJsRoot = (0, getDir_1.getDir)(option === null || option === void 0 ? void 0 : option.requireJsRoot, './src/requireJs'); const jsRoot = (0, getDir_1.getDir)(option === null || option === void 0 ? void 0 : option.jsRoot, './src/js'); // const clientOffset = clientRoot.replace(RegExp(`^${process.cwd()}`), ''); // const baseUrl = (0, addSlash_1.removeLastSlash)((0, addSlash_1.addFirstSlash)((_e = option === null || option === void 0 ? void 0 : option.baseUrl) !== null && _e !== void 0 ? _e : '')); (0, log_1.log)('clientRoot:', clientRoot); (0, log_1.log)('clientOffset:', clientOffset); (0, log_1.log)('staticRoot:', staticRoot); (0, log_1.log)('requireJsRoot:', requireJsRoot); (0, log_1.log)('jsRoot:', jsRoot); (0, log_1.log)('baseUrl:', baseUrl); (0, log_1.log)('clientScriptBaseUrl:', clientScriptBaseUrl); (0, log_1.log)('-----------------------------------------------'); ////////////////////////////////////////////////////////////////////////////// let serviceStart = new Date(); ////////////////////////////////////////////////////////////////////////////// // source map handler const sourceMapHandler = (server) => { if (!(option === null || option === void 0 ? void 0 : option.sourceMap)) { return false; } const url = (0, support_1.getPathname)(server); if (url.slice(-3) === '.js' || url.indexOf(clientOffset) < 0) { return false; } const targetMapPath = path_1.default.normalize(path_1.default.join(clientRoot.slice(0, -clientOffset.length), url)); if (!fs_1.default.existsSync(targetMapPath)) { return false; } // for directory traversal attack if (targetMapPath.indexOf(clientRoot) !== 0) { return false; } (0, sendData_1.sendData)(server, { status: 200, type: 'text/plain', body: fs_1.default.readFileSync(targetMapPath).toString(), serviceStart, lastModified: fs_1.default.statSync(targetMapPath).mtime, source: targetMapPath, }); return true; }; ////////////////////////////////////////////////////////////////////////////// // ssrsx handler yield (0, compiler_1.compileAll)(clientRoot, path_1.default.join(jsRoot, clientScriptBaseUrl), { target: 'ES6', module: 'umd', inlineSourceMap: (_f = option === null || option === void 0 ? void 0 : option.sourceMap) !== null && _f !== void 0 ? _f : false, removeComments: true, esModuleInterop: true, forceConsistentCasingInFileNames: true, strict: true, skipLibCheck: true, moduleResolution: 'node', }); const jsHandler = (server) => { // target pathname const url = (0, support_1.getPathname)(server); // ssrsx if (url.indexOf(clientScriptBaseUrl) < 0) { return false; } const targetPathname = url.replace(RegExp(`^${clientScriptBaseUrl}`), ''); // compiled jsRoot const staticJsFile = path_1.default.join(jsRoot, clientScriptBaseUrl, targetPathname); if (fs_1.default.existsSync(staticJsFile)) { if (staticJsFile.indexOf(jsRoot) === 0) { (0, sendData_1.sendData)(server, { status: 200, type: 'text/javascript', body: fs_1.default.readFileSync(staticJsFile).toString(), source: staticJsFile, serviceStart, }); return true; } } // requireJsRoot const requireJsFile = path_1.default.join(requireJsRoot, targetPathname); if (fs_1.default.existsSync(requireJsFile)) { if (requireJsFile.indexOf(requireJsRoot) === 0) { (0, sendData_1.sendData)(server, { status: 200, type: 'text/javascript', body: fs_1.default.readFileSync(requireJsFile).toString(), source: requireJsFile, serviceStart, }); return true; } } // error (0, sendData_1.sendData)(server, { status: 200, type: 'text/javascript', body: (0, errorConsole_1.errorConsole)('Invalid event handler', `${targetPathname.split('.').slice(0, -1).join('.')} (${targetPathname})`), source: targetPathname, serviceStart, errorLog: true, }); return true; }; ////////////////////////////////////////////////////////////////////////////// // static file const staticFileHandlerCore = (server, root) => { const url = (0, support_1.getPathname)(server); // static file route const filepath = path_1.default.normalize(path_1.default.join(root, url.replace(RegExp(`^${baseUrl}`), ''))); // for directory traversal attack if (filepath.indexOf(root) !== 0) { return false; } // check ext const ext = path_1.default.extname(filepath); if (ext === '.ts' || ext === '.tsx' || !fs_1.default.existsSync(filepath) || fs_1.default.lstatSync(filepath).isDirectory()) { return false; } // (0, sendData_1.sendData)(server, { status: 200, type: mime_types_1.default.lookup(path_1.default.extname(filepath).slice(1)) || 'text/plain', body: fs_1.default.readFileSync(filepath), source: filepath, serviceStart, lastModified: fs_1.default.statSync(filepath).mtime, }); return true; }; const staticFileHandler = (server) => { if (staticFileHandlerCore(server, staticRoot)) { return true; } if (staticFileHandlerCore(server, jsRoot)) { // TODO jsRoot -> workRoot return true; } if (staticFileHandlerCore(server, clientRoot)) { return true; } return false; }; ////////////////////////////////////////////////////////////////////////////// // app const addHead = (body, head) => { // find /head const headCloseTag = /<(\s*)\/(\s*)head(\s*)>/i; // insert style return `${String(body).replace(headCloseTag, `${head}</head>`)}`; }; ///////////////////////////////////////// const addStyles = (body, styles) => { // add styles const addStyle = `<style>${styles}</style>`; // find /head const headCloseTag = /<(\s*)\/(\s*)head(\s*)>/i; // insert style return `${String(body).replace(headCloseTag, `${addStyle}</head>`)}`; }; ///////////////////////////////////////// const addScripts = (server, body, events) => { var _a, _b, _c; // requireJs const requireJsOptions = { baseUrl: clientScriptBaseUrl, urlArgs: 't=' + serviceStart.getTime().toString(36), paths: option === null || option === void 0 ? void 0 : option.requireJsPaths, }; // nonce const nonce = (0, support_1.getNonce)(server); // add scripts const addScript = ` <script src="${clientScriptBaseUrl}${requireJs}"></script> <script ${nonce ? `nonce="${nonce}"` : ''}> ssrsxOptions = { events: ${JSON.stringify(events)}, hotReload: ${(option === null || option === void 0 ? void 0 : option.hotReload) ? hotReloadPort : false}, hotReloadWait: ${(_a = option === null || option === void 0 ? void 0 : option.hotReloadWait) !== null && _a !== void 0 ? _a : 1000}, hotReloadWaitMax: ${(_b = option === null || option === void 0 ? void 0 : option.hotReloadWaitMax) !== null && _b !== void 0 ? _b : 1000 * 5}, hotReloadWaitInclement: ${(_c = option === null || option === void 0 ? void 0 : option.hotReloadWaitInclement) !== null && _c !== void 0 ? _c : 500}, requireJsConfig: ${JSON.stringify(requireJsOptions)}, }; </script> <script src="${clientScriptBaseUrl}${eventLoaderJs}"></script> `.split('\n').map((s) => s.trim()).join(''); // find /body const bodyCloseTag = /<(\s*)\/(\s*)body(\s*)>/i; // body tag not found if (bodyCloseTag.test(body) === false) { return `<!DOCTYPE html>${body}${addScript}<script>${(0, errorConsole_1.errorConsole)('body tag not found', 'by ROUTER')}</script>`; } // insert script return `<!DOCTYPE html>${String(body).replace(bodyCloseTag, `${addScript}</body>`)}`; }; ///////////////////////////////////////// const appHandler = (server) => __awaiter(void 0, void 0, void 0, function* () { var _g; const url = (0, support_1.getPathname)(server); if (url.indexOf(baseUrl) !== 0 && !(option === null || option === void 0 ? void 0 : option.app)) { return false; } const ext = url.split('.').slice(-1).join(''); if (((_g = option.ignoreExtensions) !== null && _g !== void 0 ? _g : []).indexOf(ext) >= 0) { return false; } // userContext const userContext = yield (0, support_1.getUserContext)(server, option); // parse const result = yield (0, jsx_parser_1.parse)(option.app, server, userContext, baseUrl); // global context const globalContext = result.context.parseContext.global; // add head if (globalContext.head) { result.body = addHead(result.body, (yield (0, jsx_parser_1.parse)(globalContext.head, server, userContext, baseUrl)).body); } // (0, sendData_1.sendData)(server, { status: 200, type: 'text/html', body: addScripts(server, addStyles(result.body, result.context.styles.join('\n')), result.context.events), source: url, serviceStart, lastModified: globalContext.lastModified, redirect: globalContext.redirect }); // return true; }); ////////////////////////////////////////////////////////////////////////////// // for hot reload const hotReloadPort = HotReloadDefault++; const eventEmitter = new events_1.EventEmitter(); let wsServer; if (option === null || option === void 0 ? void 0 : option.hotReload) { const port = (typeof option.hotReload === 'boolean') ? hotReloadPort : option.hotReload; // eventEmitter.on('hotReload', () => { if (wsServer) { wsServer.clients.forEach((socket) => { socket.close(); process.nextTick(() => { if (socket.readyState === socket.OPEN || socket.readyState === socket.CLOSING) { socket.terminate(); } }); }); wsServer.close(); } (0, log_1.log)('Hot reload start:', port); serviceStart = new Date(); wsServer = new ws_1.Server({ port }); }); // eventEmitter.emit('hotReload'); // } const hotReload = () => { eventEmitter.emit('hotReload'); }; ////////////////////////////////////////////////////////////////////////////// const middleware = (server) => __awaiter(void 0, void 0, void 0, function* () { // source map request if (sourceMapHandler(server)) { return true; } // js handler if (jsHandler(server)) { return true; } // static handler if (staticFileHandler(server)) { return true; } // app if (yield appHandler(server)) { return true; } // next return false; }); return { hotReloadPort, hotReload, middleware, }; }); exports.ssrsx = ssrsx; //////////////////////////////////////////////////////////////////////////////// exports.default = ssrsx; //# sourceMappingURL=core.js.map