Skip to content

Commit

Permalink
Merge pull request #629 from derbyjs/racer-2
Browse files Browse the repository at this point in the history
Racer 2
  • Loading branch information
craigbeck authored May 1, 2024
2 parents 25390be + a3c17fd commit e14cc12
Show file tree
Hide file tree
Showing 41 changed files with 777 additions and 722 deletions.
26 changes: 19 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
{
"name": "derby",
"description": "MVC framework making it easy to write realtime, collaborative applications that run in both Node.js and browsers.",
"version": "3.0.2",
"version": "4.0.0-beta.18",
"homepage": "http://derbyjs.com/",
"repository": {
"type": "git",
"url": "git://github.com/derbyjs/derby.git"
},
"publishConfig": {
"access": "public"
},
"main": "dist/index.js",
"exports": {
".": "./dist/index.js",
Expand All @@ -17,8 +20,9 @@
"./AppForServer": "./dist/AppForServer.js",
"./server": "./dist/server.js",
"./Page": "./dist/Page.js",
"./test-utils": "./test-utils/index.js",
"./test-utils/*": "./test-utils/*.js"
"./test-utils": "./dist/test-utils/index.js",
"./test-utils/*": "./dist/test-utils/*.js",
"./file-utils": "./dist/files.js"
},
"files": [
"dist/",
Expand All @@ -27,29 +31,32 @@
"scripts": {
"build": "node_modules/.bin/tsc",
"checks": "npm run lint && npm test",
"lint": "npx eslint src/**/*.js test/**/*.js test-utils/**/*.js",
"lint": "npx eslint src/**/*.ts test/**/*.js",
"lint:ts": "npx eslint src/**/*.ts",
"lint:fix": "npm run lint:ts -- --fix",
"prepare": "npm run build",
"pretest": "npm run build",
"test": "npx mocha 'test/all/**/*.mocha.js' 'test/dom/**/*.mocha.js' 'test/server/**/*.mocha.js'",
"test": "npx mocha -r ts-node/register 'test/all/**/*.mocha.*' 'test/dom/**/*.mocha.*' 'test/server/**/*.mocha.*'",
"test-browser": "node test/server.js"
},
"dependencies": {
"chokidar": "^3.5.3",
"esprima-derby": "^0.1.0",
"html-util": "^0.2.3",
"qs": "^6.11.0",
"racer": "^1.0.3",
"resolve": "^1.22.1",
"serialize-object": "^1.0.0",
"tracks": "^0.5.8"
},
"devDependencies": {
"@types/chai": "^4.3.11",
"@types/esprima-derby": "npm:@types/esprima@^4.0.3",
"@types/estree": "^1.0.1",
"@types/express": "^4.17.18",
"@types/mocha": "^10.0.6",
"@types/node": "^20.3.1",
"@types/qs": "^6.9.11",
"@types/resolve": "^1.20.6",
"@types/sharedb": "^3.3.10",
"@typescript-eslint/eslint-plugin": "^6.2.1",
"@typescript-eslint/parser": "^6.2.1",
"async": "^3.2.4",
Expand All @@ -63,8 +70,13 @@
"jsdom": "^20.0.1",
"mocha": "^10.0.0",
"prettier": "^3.0.1",
"racer": "^v2.0.0-beta.11",
"ts-node": "^10.9.2",
"typescript": "~5.1.3"
},
"peerDependencies": {
"racer": "^v2.0.0-beta.8"
},
"optionalDependencies": {},
"bugs": {
"url": "https://github.com/derbyjs/derby/issues"
Expand Down
86 changes: 55 additions & 31 deletions src/App.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,89 +8,112 @@
import { EventEmitter } from 'events';
import { basename } from 'path';

import { type Model } from 'racer';
import * as util from 'racer/lib/util';
import { type Model, type RootModel, createModel } from 'racer';
import { util } from 'racer';

import components = require('./components');
import * as components from './components';
import { type ComponentConstructor, type SingletonComponentConstructor } from './components';
import { type Derby } from './Derby';
import { Page, type PageBase } from './Page';
import { PageForClient, type Page } from './Page';
import { PageParams, routes } from './routes';
import * as derbyTemplates from './templates';
import { type Views } from './templates/templates';
import { checkKeyIsSafe } from './templates/util';

declare module 'racer/lib/util' {
export let isProduction: boolean;
}

const { templates } = derbyTemplates;

// TODO: Change to Map once we officially drop support for ES5.
global.APPS = global.APPS || {};

export function createAppPage(derby): typeof PageBase {
const pageCtor = ((derby && derby.Page) || Page) as typeof PageBase;
export function createAppPage(derby): typeof Page {
const pageCtor = ((derby && derby.Page) || PageForClient) as typeof Page;
// Inherit from Page/PageForServer so that we can add controller functions as prototype
// methods on this app's pages
class AppPage extends pageCtor { }
return AppPage;
}

interface AppOptions {
export interface AppOptions {
appMetadata?: Record<string, string>,
scriptHash?: string,
}

type OnRouteCallback<T = object> = (arg0: Page, arg1: Page, model: Model<T>, params: PageParams, done?: () => void) => void;
type OnRouteCallback = (this: Page, page: Page, model: Model, params: PageParams, done?: () => void) => void;

type Routes = [string, string, any][];

export abstract class AppBase<T = object> extends EventEmitter {

/*
* APP EVENTS
*
'error', Error
'pageRendered', Page
'destroy'
'model', Model
'route', Page
'routeDone', Page, transition: boolean
'ready', Page
'load', Page
'destroyPage', Page
*/

export abstract class App extends EventEmitter {
derby: Derby;
name: string;
filename: string;
scriptHash: string;
// bundledAt: string;
appMetadata: Record<string, string>;
Page: typeof PageBase;
Page: typeof Page;
proto: any;
views: Views;
tracksRoutes: Routes;
model: Model<T>;
page: PageBase;
protected _pendingComponentMap: Record<string, ComponentConstructor>;
model: RootModel;
page: Page;
protected _pendingComponentMap: Record<string, ComponentConstructor | SingletonComponentConstructor>;
protected _waitForAttach: boolean;
protected _cancelAttach: boolean;

use = util.use;
serverUse = util.serverUse;

constructor(derby, name, filename, options: AppOptions = {}) {
constructor(derby, name?: string, filename?: string, options?: AppOptions) {
super();
if (options == null) {
options = {};
}
this.derby = derby;
this.name = name;
this.filename = filename;
this.scriptHash = options.scriptHash ?? '';
this.appMetadata = options.appMetadata;
this.appMetadata = options.appMetadata ?? {};
this.Page = createAppPage(derby);
this.proto = this.Page.prototype;
this.views = new templates.Views();
this.tracksRoutes = routes(this);
this.model = null;
this.page = null;
this._pendingComponentMap = {};
}

abstract _init(options?: AppOptions);
loadViews(_viewFilename, _viewName) { }
loadStyles(_filename, _options) { }
loadViews(_viewFilename, _viewName?) { }
loadStyles(_filename, _options?) { }

component(constructor: ComponentConstructor | SingletonComponentConstructor): this;
component(name: string, constructor: ComponentConstructor | SingletonComponentConstructor, isDependency?: boolean): this;
component(name: string | ComponentConstructor | SingletonComponentConstructor, constructor?: ComponentConstructor | SingletonComponentConstructor, isDependency?: boolean): this {
component(name: string | ComponentConstructor | SingletonComponentConstructor | null, constructor?: ComponentConstructor | SingletonComponentConstructor, isDependency?: boolean): this {
if (typeof name === 'function') {
constructor = name;
name = null;
}
if (typeof constructor !== 'function') {
throw new Error('Missing component constructor argument');
if (typeof name === 'string') {
throw new Error(`Missing component constructor argument for ${name} with constructor of ${JSON.stringify(constructor)}`);
}
throw new Error(`Missing component constructor argument. Cannot use passed constructor of ${JSON.stringify(constructor)}`);
}

const viewProp = constructor.view;
Expand Down Expand Up @@ -224,12 +247,12 @@ export abstract class AppBase<T = object> extends EventEmitter {
}
}

export class App extends AppBase {
page: Page;
export class AppForClient extends App {
page: PageForClient;
history: {
refresh(): void,
push(): void,
replace(): void,
push: (url: string, render?: boolean, state?: object, e?: any) => void,
replace: (url: string, render?: boolean, state?: object, e?: any) => void,
refresh: () => void,
};

constructor(derby, name, filename, options: AppOptions) {
Expand All @@ -241,7 +264,7 @@ export class App extends AppBase {
_init(_options) {
this._waitForAttach = true;
this._cancelAttach = false;
this.model = new this.derby.Model();
this.model = createModel();
const serializedViews = this._views();
serializedViews(derbyTemplates, this.views);
// Must init async so that app.on('model') listeners can be added.
Expand Down Expand Up @@ -277,7 +300,8 @@ export class App extends AppBase {
this.model.unbundle(data);

const page = this.createPage();
page.params = this.model.get('$render.params');
// @ts-expect-error TODO resolve type error
page.params = this.model.get<Readonly<PageParams>>('$render.params');
this.emit('ready', page);

this._waitForAttach = false;
Expand Down Expand Up @@ -306,7 +330,7 @@ export class App extends AppBase {
private _getAppData() {
const script = this._getAppStateScript();
if (script) {
return App._parseInitialData(script.textContent);
return AppForClient._parseInitialData(script.textContent);
} else {
return global.APPS[this.name].initialState;
}
Expand Down Expand Up @@ -400,7 +424,7 @@ export class App extends AppBase {

createPage() {
this._destroyCurrentPage();
const ClientPage = this.Page as unknown as typeof Page;
const ClientPage = this.Page as unknown as typeof PageForClient;
const page = new ClientPage(this, this.model);
this.page = page;
return page;
Expand Down Expand Up @@ -435,7 +459,7 @@ export class App extends AppBase {
if (action === 'refreshViews') {
const fn = new Function('return ' + message.views)(); // jshint ignore:line
fn(derbyTemplates, this.views);
const ns = this.model.get('$render.ns');
const ns = this.model.get<string>('$render.ns');
this.page.render(ns);

} else if (action === 'refreshStyles') {
Expand Down
29 changes: 17 additions & 12 deletions src/AppForServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,17 @@
*
*/

import racer = require('racer');
import * as racer from 'racer';

const util = racer.util;
import { AppBase } from './App';
import { App } from './App';
import { type Derby } from './Derby';
import { type StyleCompilerOptions } from './files';
import { PageForServer } from './PageForServer';
import parsing = require('./parsing');
import * as derbyTemplates from './templates';

const util = racer.util;

interface Agent {
send(message: Record<string, unknown>): void;
}
Expand Down Expand Up @@ -42,7 +45,7 @@ function htmlCompiler(file) {
return file;
}

type CompilerFunciton = (file: string, filename?: string, options?: unknown) => unknown;
type CompilerFunction = (file: string, filename?: string, options?: unknown) => string;

function watchOnce(filenames, callback) {
const watcher = chokidar.watch(filenames);
Expand All @@ -59,9 +62,9 @@ function watchOnce(filenames, callback) {
});
}

export class AppForServer<T = object> extends AppBase<T> {
export class AppForServer extends App {
agents: Record<string, Agent>;
compilers: Record<string, CompilerFunciton>;
compilers: Record<string, CompilerFunction>;
scriptBaseUrl: string;
scriptCrossOrigin: boolean;
scriptFilename: string;
Expand All @@ -76,7 +79,7 @@ export class AppForServer<T = object> extends AppBase<T> {
watchFiles: boolean;
router: any;

constructor(derby, name: string, filename: string, options) {
constructor(derby: Derby, name: string, filename: string, options) {
super(derby, name, filename, options);
this._init(options);
}
Expand All @@ -103,7 +106,7 @@ export class AppForServer<T = object> extends AppBase<T> {
this.agents = null;
}

private _initLoad() {
_initLoad() {
this.styleExtensions = STYLE_EXTENSIONS.slice();
this.viewExtensions = VIEW_EXTENSIONS.slice();
this.compilers = util.copyObject(COMPILERS);
Expand Down Expand Up @@ -144,7 +147,7 @@ export class AppForServer<T = object> extends AppBase<T> {

// overload w different signatures, but different use cases
createPage(req, res, next) {
const model = req.model || new racer.Model();
const model = req.model || racer.createModel();
this.emit('model', model);

const Page = this.Page as unknown as typeof PageForServer;
Expand All @@ -159,12 +162,14 @@ export class AppForServer<T = object> extends AppBase<T> {
return page;
}

// @DEPRECATED
bundle(_backend, _options, _cb) {
throw new Error(
'bundle implementation missing; use racer-bundler for implementation, or remove call to this method and use another bundler',
);
}

// @DEPRECATED
writeScripts(_backend, _dir, _options, _cb) {
throw new Error(
'writeScripts implementation missing; use racer-bundler for implementation, or remove call to this method and use another bundler',
Expand Down Expand Up @@ -208,23 +213,23 @@ export class AppForServer<T = object> extends AppBase<T> {
this.scriptMapUrl = (this.scriptMapBaseUrl || serialized.scriptMapBaseUrl) + serialized.scriptMapUrl;
}

loadViews(filename, namespace) {
loadViews(filename: string, namespace?: string) {
const data = files.loadViewsSync(this, filename, namespace);
parsing.registerParsedViews(this, data.views);
if (this.watchFiles) this._watchViews(data.files, filename, namespace);
// Make chainable
return this;
}

loadStyles(filename, options) {
loadStyles(filename: string, options?: StyleCompilerOptions) {
this._loadStyles(filename, options);
const stylesView = this.views.find('Styles');
stylesView.source += '<view is="' + filename + '"></view>';
// Make chainable
return this;
}

private _loadStyles(filename, options) {
private _loadStyles(filename: string, options?: StyleCompilerOptions) {
const styles = files.loadStylesSync(this, filename, options);

let filepath = '';
Expand Down
Loading

0 comments on commit e14cc12

Please sign in to comment.