expressjs / express

Fast, unopinionated, minimalist web framework for node.
https://expressjs.com
MIT License
64.35k stars 14.63k forks source link

modular support #3073

Closed zenithtekla closed 4 years ago

zenithtekla commented 7 years ago

Man, your ExpressJS should support something like this: core module configuration file (core.config.js) returns

modules[module] = {
    name: module,
    root: root,
    view_path: view_path,
    routes: routes,
    view_engine: view_engine
  };

and then, in server.js we can do:

_.each(modules, function(module){
      if(module){
        app.set('root', module.root);
        app.set('module_name', module.name);

        require(module.routes)(app);
        app.set('module_view', module.view_path);
        app.set('module_view_engine', module.view_engine); 
        console.log(module.name, module.view_engine);
      }
    });

OR better

_.each(modules, function(module){
      if(module){
        app.moduleSet(module);

So we don't have to hack around! by pushing them (the views) to an array and unable to attain dynamic view engine on render. The views array is like a tiger skin, a unclassified combined mesh of things (views folders of every module) and it is not possible to set view_engine per module (per view_path).

_.each(modules, function(module){
      if(module){
        app.set('root', module.root);
        app.set('module_name', module.name);
        require(module.routes)(app);
        views.push(module.view_path);
        view_engines.push(module.view_engine);        
        // app.engine(module.view_engine, engines[module.view_engine]);
        console.log(module.name, module.view_engine);
      }
    });

    /* swig & handlebars require engine to be declared
    app.engine('html', swig.renderFile);
    */

    app.set('views', views);
    app.set('view engine', 'pug');

Then, in our moduleA.controllers files, we have to do this:

exports.todos = (req,res) => {
  app.set('view engine', 'hjs');
  // or with json
  var config = require('../configs/module.config.json');
  app.set('view engine', config.view_engine);
  var todos = [
    {"task":"task1", "description":"task1_desc"},
    {"task":"task2", "description":"task2_desc"}
  ];

  // res.send('ok');
  // res.json(todos);
 // for todo.hjs
  res.render('todos', {"todos": todos});
};

In moduleB.controllers file, we have to the similar with 'pug' in place of 'hjs'. I don't know how the MEAN.js stack handles this, it seems working smoothly but requiring Grunt|Gulp and a lot of 'grunt work' picking up the views folder per module and render. Please enlighten me. Let me know how you guys handle the issues to be able to render different view engines per module for different views-routes.

current working project: https://github.com/zenithtekla/nodeMySQL

artcommacode commented 7 years ago

As mentioned on IRC, sub-apps will give @zenithtekla all of the capability they require.

zenithtekla commented 7 years ago

Ok, done in r32

lohfu commented 7 years ago

@artcommacode sub apps?

artcommacode commented 7 years ago

@lohfu http://expressjs.com/en/api.html#app.mountpath

lohfu commented 7 years ago

i think a better solution would be to scrap the view folders completely, and instead make templates requirable files that return an object with a render function.

that way, instead of passing a string to res.render, you can pass an object with a render function. that way you do not need to specify a specific view engine.

or at least enable a combination of the two... ie if a res.render receives a string, it uses the view engine and view folder. it it receives an object it renders that object

lohfu commented 7 years ago

@artcommacode wow, i had no idea about sub apps. that looks awesome. still not too fond of the whole view engine concept though

lohfu commented 7 years ago

@zenithtekla just curious what does "r32" mean?

artcommacode commented 7 years ago

You can do something along those lines already @lohfu:

import pug from 'pug'
import {readFileSync} from 'fs'
const template = readFileSync('./template.pug')

router.get('/path', (req, res, next) => {
  res.send(pug.render(template, {...res.locals, options})))
}

And now you have your own templating, completely separate from res.render and views.

lohfu commented 7 years ago

nice!

zetekla commented 7 years ago

@lohfu r32 = revision32 from my repo r32 successfully implemented multiple view_engines r35 added support for configuration of view_engines but I already think I would implement consolidatejs as in MEANJS

hacksparrow commented 7 years ago

@zenithtekla it would be really cool if you can open a PR with the suggested implementation.

Download commented 7 years ago

i think a better solution would be to scrap the view folders completely, and instead make templates requirable files that return an object with a render function.

This!

Many of us are using client side rendering frameworks like React in combination with Express to get universal/isomorphic rendering. But the rendering/templating system of Express is biased towards files and file paths, which makes using it in / porting it to the browser very difficult.

lohfu commented 7 years ago

@Download This is actually very easy to achieve already... all you have to do is override the default render function. I have currently done this like so:

Inside my server.js (app.js):

const express = require('express');

const server = express();

server.response.render = require('./render');

render.js:

'use strict';

const { h } = require('jsx-node');

module.exports = function (Component, Master) {
  const locals = Object.assign({ query: this.req.query }, this.app.locals, this.locals);

  let html;

  if (typeof Master === 'function') {
    if (typeof Component === 'function') {
      return this.send(
        h(Master, locals,
          h(Component, locals)
        )
      );
    }

    Component = Master;
  }

  if (typeof Component !== 'function') {
    throw new Error('Not a Component');
  } else if (Component.prototype && Component.prototype.render) {
    const i = new Component(locals);
    html = i.render(i.props, i.state);
  } else {
    html = Component(locals);
  }

  this.send(html);
};

Now inside my routes I render my JSX components simply by calling res.render(Comonent). I have this fully functioning with both React and Preact components, but you will probably have to modify it to suit your needs better. Hit me up if you need some help to get it working better with official ReactDOMServer or similar.

Download commented 7 years ago

@lohfu Ooooh this looks nice! I am actually working on some middleware to do something like this, but my approach isn't nearly as nice as this!

I am definitely going to look into this. And study jsx-node. I'll need some time but I will get back to you on it. Thanks!

wesleytodd commented 7 years ago

I do something similar, but because I use the default render and a JSX render in the same apps I provide my own method. I use this module for that: https://github.com/wesleytodd/react-express-middleware

lohfu commented 7 years ago

@Download jsx-node is actually my own module, and it is a work in progress without any documentation so you might have some difficulty using it. I intend to set up a similar but pure React solution shortly. I will ping you as soon as i have gotten somewhere.

Download commented 7 years ago

@lohfu To be honest I was actually much interested in the not-pure-React aspect of it. The idea we could make a generic JSX rendering engine that would work for React and Preact components seems very attractive! Why are you giving up on that idea?

lohfu commented 7 years ago

@Download oh i'm not giving up on that idea, i'm just not sure if React binds smoothly to HTML generated without ReactDOMServer. Rendering JSX is so simple that jsx-node should already be pretty feature complete, the only big thing left is handling stores like redux well. And documentation.

If you want to know how it works, the creator of Preact wrote a pretty good article disecting what JSX actually is. Once JSX has been transpiled to hyperscript writing a renderer is very basic.

I wrote a simple module that renders React style JSX. In jsx-node i use the string method from that module.

If you want to require jsx files without having to babelify them first, you can use the install method from jsx-node.

Then you would simply do something like:

require('jsx-node').install({
  alias: {
    'preact': 'jsx-node',
    'react': 'jsx-node',
});

const template = require('./template.jsx');

console.log(template());
Download commented 7 years ago

i'm just not sure if React binds smoothly to HTML generated without ReactDOMServer

No you are right it won't.

I was more thinking along the lines of having 'drivers' so to say to do the actual rendering. So the components can be generic, but at the app level we just pick which system we want by configuring a driver.

dougwilson commented 4 years ago

So there is a lot of discussion in here, though I think though the comments the conversation veered off from the original request a bit. As for the original request, there are a few modules on npm currently that can achieve what you are trying to do. The proposal itself is building upon the express API to construct a higher level framework, which is great!

The API of Express itself is made to be flexible, which as far as I can tell is correct, as the original request is achievable in our current API. Sure, it may not be the specific sugar OP is looking for, but that is while building on top of things are for--express itself exists to build on top of the http APIs in Node.js for example.

I'm mainly closing this for staleness though, and it can be reopened if desired.