Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Guidance on server-side includes #94

Closed
mhkeller opened this issue Feb 17, 2016 · 1 comment
Closed

Guidance on server-side includes #94

mhkeller opened this issue Feb 17, 2016 · 1 comment

Comments

@mhkeller
Copy link

I'm working on a build system that would use live-server for local dev work but we are also planning on using server-side includes to handle some boilerplate pieces of html such as the header. For example, the developer would create a partial html file that the apache server would insert into the rest of the html body along with the <head>.

I came across this package which has a browserSync example: https://github.com/kidwm/node-ssi Is there a way to implement custom middleware in the same way using live-server? Or, if not currently possible, I could take a crack at implementing it if you have an idea where to start.

@evanplaice
Copy link
Contributor

@tapio is the maintainer so I have no say on what gets added to this utility. Either way, I have some experience modifying live-server and also writing a preprocessor tool similar to node-ssi.

On Apache

While Apache comes with SSI built-in, I'd argue that it's an artifact that represents the 'way things were done' back when the web was still young. Apache is a very large monolithic HTTPD server with the ability to add/enable (via configuration) additional middleware that follows an Apache-specific extension format. SSI, along with a lot of other webserver functionality provided by Apache is hardcoded into the codebase (ie part of the monolith). Overall, this contributes a significant amount of bloat to what you'll get with a base install of Apache.

There are some prominent downsides to this approach:

  • the greater the monolith, the larger the surface area for bugs and security vulnerabilities
  • many users depend on built-in features so it's not possible to extract them into separate modules without breaking existing code
  • Apache's module format can't be re-used with other webservers
  • supporting Apache-specific features on another platform requires reimplmenting the functionality on your testing platform
  • Apache (ie originally 'A Patchy Server') requires extensive configuration to support all of the built-in features as well as third-party modules

Supporting a large base of built-in features as well as an entire ecosystem of middleware packages requires a lot of configuration; which is part of the reason why Apache is such a PITA to setup, especially for front-end devs who don't have a strong knowledge of back-end server development.

How would this look following Node.js dev practices?

As opposed to Apache's monolithic architecture and approach to extensibility, Node.js frameworks (ex Express) are typically minimalistic by default with the ability to inject middle tiers.

Instead of favoring configuration, extensions are implemented as reusable extensions in code. A middle tier can be easily implemented as a third-party library and be dynamically imported/injected into the code during runtime.

The assumption being that backend devs will implement their own custom server to meet their specific use cases.

Implementing this as a Node.js middle-tier

Requirements:

  • the ability to inject middleware
  • a preprocessor middleware module
  • a way to configure the middleware from the CLI

Dependency Injection

Assuming you have the ability to directly modify the code of the HTTP server it's not too difficult to add a middle-tier. The application will need to be designed with a DI hook in the code where a middle-ware function can be injected. In JS, functions are first-class members so it's trivial to provide DI hooks in code.

function get(request, response, middleware, callback) {
    if (middleware) {
        middleware(options, callback) {
            // do middleware stuff here
            callback(request, response) {
                // send the modified response
            }
    }
    else {
        callback(request, response)
            // default response handler when middleware isn't configured
         } 
    }
}

Preprocessor Implementation

Preprocessors come in many forms. Fundamentally, it's a DSL implementation that reads source, modifies it in memory, and returns the post-processed result. These come in many forms from a basic regular syntax (ex the SSI library you linked) to a fully featured turing-complete language (ex PHP).

I have actually written one such preprocessor library for python called pypreprocessor. It functions in almost the exact manner that node-ssi does except it handles c-style preprocessor statements and dynamically modifies the python import statement to support self-consuming code.

In short:

  • read the source looking for lines that contain a comment
  • pattern match for a SSI statement
  • rewrite the source file to memory including the modifications done by SSI
  • send a response with the modified contents

In a way, this application already includes a very basic preprocessor. It uses a RegEx to locate the closing body tag and inserts the LiveReload snippet just before it. There are many fully-featured tools that can handle parsing and modifying HTML but for this utility manages it in a few lines of code with no dependencies.

In theory, this utility could be extended to support custom middleware, including the node-ssi lib, except there needs to be some way to inject the functionality and dynamically load the third-party module during runtime.

Middle tiers are simple to setup if you only need one. If you require multiple middle-tier steps you'll have to write your own middle-tier middle-tier that properly orders the steps and generates an output that can be consumed by the server's exiting middle-tier API. Taken to a logical extreme, this is essentially what build tools like Gulp/Grunt and HTTP frameworks like Express already provide.

Configuration

This is the deal-breaker. Live-reload isn't designed to be extensible. What little configurability it provides is done via the CLI. By design, it's a very minimal, straight-forward, minimal-configuration utility. It's an amazingly simple and straightforward utility, so much so that even front-end devs should be able to get started with no knowledge of back-end development.

It's not ideal for use cases that require custom configurations and/or source code modifications. Supporting extensibility via DI would dramatically increase the scope of what this project is intended to provide.

Tools like BrowserSync support a kitchen sink of features and functionality but at the expense of a ridiculously large scope, complex configuration, and a daunting tree of required dependencies.

Either way, I'm not the 'final say' on this subject so I could be completely off base.


Bonus: Possible Solution

Since you're creating a custom 'build step' anyway, why not create it to handle middleware processing like server-side includes at build-time rather than depending on server-specific functionality?

Requirements:

  • watch the 'src' directory for changes
  • run node-ssi on any files that change
  • output the result to a 'dist' or 'dev' directory if 'dist' is already reserved for production-only code
  • configure a NPM script to run live-server on the build output

This way live-server can be used as is and you can add any number of preprocessor/middle-tier steps as necessary (ex template preprocessing, ES6 transpilation, etc) and use an existing build tool to handle the processing order.

Many projects written in ES6/Typescript already implement this approach via Gulp/Grunt, especially in the Angular2/ReactJS community.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants