. is a principle .. for writing modular web site and web apps ... with node.js |
GlintApp does nothing new, it just follows the unix principle (which node.js follows in general).
The main point is the focus for doing this at the application level:
Small is beautiful, Make each program do one thing well.
fun, fun , fun
|
fancy
|
node.js, npm as well as github are great to work with. The whole environment encourages you to use and write many small modules, and not monoliths. It just takes a couple of minutes, (or even less than a minute with tools at hand) to write a hello world module, test it and publish it. Comming from the Java world, this surely is an improvement.
Small modules help solving (or avoiding) complexity. Which is great. But it does not stop with small modules. Modules are composed again.
found on www.gifbay.com |
level me up
|
Structuring modules in the first two levels is basically solved. How to structure the application (third level) is another thing. It most probably is influenced by the libraries/frameworks you use. If you are not careful, your application ends up beeing a monolith again, that is composed of many modules, but you can't reuse most of it.
GlintApp
focuses on the application level (third level).
Lets first look at node.js and the browser and then come back to how to compose the application.
if you want to take a shortcut to the how
and skip the why
, jump to : Steps to a GlintApp Galaxy
this section gives first a bit of a background, if you know this already, you can skip to the next chapter.
Then there is modules for node.js and modules for the browser
Now we have JavaScript on the Server with node.js and in the browsers:
- But how is it different?
- Can't we just use the same thing on the server as we use in the browser?
The environment is different. (here's just a few differences)
- Node.js does not have a DOM built in (there is the JSDOM Module, but that's a different thing).
- Browsers do not have the node.js API like
http
orfs
etc. - Browsers have different JavaScript Engines and do not always support the same things (worse with old browsers)
- and Browsers do not have a built in
require
mechanism, like node.js has
Because of the differences, it's the same language, but you can't share lots between the server and the browser out of the box.
You need some additional things...
TJ Holowaychuck back in 2012 started component for modules to use in the browser.
There is also several similar things with similar names disambiguation and comparison.
It uses github as the repository, instead of npm, and has got it's own tooling for things like installing building etc.
It has got it's own commonjs and nodejs like require
implementation. It's semantic is similar, but not the same. It add's the username into the naming scheme: require('username/modulename')
.
What I really like about this project is the focus on small reusable modules, that don't require heavy frameworks/toolsets, and the involvement of many people (at least initially). Many of them are very slick and have a nice and simple api. Now many of these modules can be found in node.js as well. Before you build a browser module yourself check the existing ones on component: registry and old registry.
But do we really need another module registry?
Why not writing code for the browser as we do for the server and use a tool to make the browser happy? James Halliday known as substack created such a tool: browserify Browserify works really great and also solves the problem of writing reusable code for node.js and the browser. It even gives you big parts of the node.js core API to use in the browser. And it does not create another 'universum', where the modules written in/for it can only be used within this universum. It is Therefore a great tool to allow code reuse in the server/browser as well as in different projects without adding another big framework, which is great.
With all the problems it solves, it has some things it does not solve by itself, and also adds some new problems:
- the bundled javascript file can become quite big
- it does not handle other assets like images, css files etc.
Over all, it is a great thing, I am really thankful for. One thing I didn't mention so far is, the documentation as well as the eco system are exceptional too:
Sure there is great things you can use for the browser. For example duo, webpack, bower etc.
Most of them are focused on the browser only. And don't have reusing node.js code in mind.
There is a recommendable read on the story of normalize.io, which had the nobal goal to normalize the package management normalize.io.
HTTP/2 has got an influence on how to pack, or not to pack assets for the browser with the Server Push mechanism. Previous best practices (like bundling javascript files) become anti patterns with HTTP/2
support
- HTTP/2 is currently not supported everywhere yet
- there is currently no node.js core support for http2 (but in userland ther is)
- nginx support for example is not yet there (in process) update: it's supported now
- browser support: caniuse http/2
Web Components can help re-using html, css, javascript, they are just a bit late, and support is still not where it should be.
further reading
- documentation: mozilla web components
- browser support: caniuse web components
- Google Polymer is based on web components: polymer
The question often arises in node.js projects:
As mentioned before, often the libraries/frameworks you use influence your structure quite a bit.
Lance Pollard initiated a listing of different structures: example structures. You can also find many blog posts about how to structure a node.js application (us included: our own structure struggles )
We might also ask the question:
. No we don't need to
.. modularize
... enter GlintApp ⎆
1. develop small modules, that contain server as well as browser code
2. every module must declare it's dependencies
3. make the modules configurable with a default configuration and an options argument
4. use widely supported javascript (concerning the browser)
5. if you use transpiler/compiler, make sure you provide the compiled code also
6. assets go into the public folder of your module
7. define the server technology for your GlintApp Galaxy
8. define commonly used modules for common concepts
9. require no special environment settings
that's it.
what follows is just an explanation of these steps...
- in your module's package.json declare the entry point for the server (as always)
- and the entry point for the browser (looked at by browserify)
...
"main": "server.js",
"browser": "browser.js",
...
if your module requires special transformations, declare them in the browserify field:
...
"browserify": {
"transform": [
[ "brfs" ],
[ "envify", {"_": "purge"} ]
]
},
...
but does that work at the application level ?
Yes it does, browserify handles it.
Here's an example of how you can scructue your modules.
- the browser only get's the
upper arrows
, - and the server the
lower arrows
As you can see in the following example, there is browser specific files, server specific ones and commonly (shared) files as well, no matter where in the module hierarchy they are used.
this is truly universal javascript (or isomorphic or whatever...)!
same style on the server as well as in the browser, yay! Big Hug to: @substack
wait, but it all depends on browserify. True, BUT if bundling is not needed anymore in the same way with HTTP/2, you can still follow this structure and come up with something that handles it for the new environment.
ok, here's the example
+-----------------------+
| blog |
+-----------------------+ +-----------------------+
| | | is-browser |
+------> browser.js | +-----------------------+
| | | | |
| | // dom stuff | +---> browser.js |
| | // etc. | | | |
| | | +--+ | return true |
| +---> server.js | | | | |
| | | | | | | |
| | | // data access | | +---> server.js |
| | | // etc. | | | |
| | | | | | return false |
+-----------------------+ | | +-----------------------+ | | |
| app | | | | | |
+-----------------------+ | | | +-----------------------+
| | | | +-----------------------+ |
| browser.js | | | | news | |
| | | | +-----------------------+ +-------------------------------------+
| require('blog')() +----+ | | | |
| require('news')() +------------> browser.js | |
| | | | | +-------------------------------+ |
| | | | require('ctrl')() +----+ | ctrl | |
| server.js | | | // etc. | | +-------------------------------+ |
| | | | | | | | |
| require('blog')() +-------+ | | +---> index.js | |
| require('news')() +------------> server.js | | | | |
| | | | | | // shared | |
| require('auth')() +-----+ | require('ctrl')() +----+ | | |
| | | | // etc. | | if(require('is-browser')){ +----+
+-----------------------+ | | | | |
| | | | // browser stuff |
| +-----------------------+ | |
| | } else { |
| | |
| +-----------------------+ | // server stuff |
| | auth | | |
| +-----------------------+ | } |
| | | | |
+----> server.js | | |
| | +-------------------------------+
| // data access |
| // etc. |
| |
| |
| |
+-----------------------+
created with asciiflow
declare them in
dependencies
anddevDependencies
write the module configuration in it's own file e.g. with a configuration file like
config.js
when requiring the module, allow overriding of the default configurations:
function example:
var defaults = require('defaults');
var config = require('./config');
/**
* Expose MyModule
*/
module.exports = function myModule(options) {
options = defaults(options, config);
// ...
}
constructor example:
var EventEmitter = require('events').EventEmitter;
var merge = require('utils-merge');
var config = require('./config');
/**
* Expose MyModule
*/
exports = module.exports = MyModule;
inherits(MyModule, EventEmitter);
/**
* Initialize a new `MyModule` element.
* @param {Object} options object
*/
function MyModule(options) {
if (!(this instanceof MyModule)) return new MyModule(options);
merge(this, config);
merge(this, options);
// ...
}
be supporting, being a bit conservative might not be wrong here (depending on your Galaxy)
do not throw internal complexities (which should not exist anyway :-) on to the consumer
this lets the GlintApp Galaxy bundle the assets. e.g. with assets-bundler
as much as we like to support everything, architecture also means to make decisions, also to limit our selves to solve the actual problems. e.g. Express or hapi etc.
when you design a system, it consists of structures and concepts. See arc42. typically you want to follow consistent concepts in the different parts of the system (modules). e.g. consistent Logging mechanism.
Therefore, make known the conceptual modules, the individual modules shall require.
be a good neighbour.
When you create many small modules for your application, maybe you don't want to publish them all to npm or have separate git repositories or share them at all.
local_modules could be helpful in this case.
Andi Neck | @andineck | andi.neck@intesso.com | intesso
Any Feedback is highly appreciated. Please create an Issue or PR.
- glintcms
- ... please send a PR which adds your implementation ...
- unix philosophy and node
- linux and the unix philosophy ISBN-10: 1555582737
- browserify handbook
- arc42