好吧,已经有一段时间了,这是一个很受欢迎的问题,所以我已经向前走了,用JavaScript代码创建了一个脚手架github存储库和一个关于我如何构造一个中型express.js应用程序的长README。
Focusaurus /express_code_structure是带有最新代码的repo。欢迎拉请求。
下面是README的快照,因为stackoverflow不喜欢只有链接的答案。我会做一些更新,因为这是一个新的项目,我将继续更新,但最终github回购将是这个信息的最新地方。
#快速代码结构
这个项目是一个如何组织一个中型express.js web应用程序的例子。
目前至少到2016年12月14日express v4.14
你的应用程序有多大?
Web应用程序并不完全相同,在我看来,没有一种单一的代码结构可以应用于所有express.js应用程序。
如果您的应用程序很小,就不需要像这里所示的这样深的目录结构。只要保持简单,在存储库的根目录中插入一些.js文件就可以了。瞧。
If your application is huge, at some point you need to break it up into distinct npm packages. In general the node.js approach seems to favor many small packages, at least for libraries, and you should build your application up by using several npm packages as that starts to make sense and justify the overhead. So as your application grows and some portion of the code becomes clearly reusable outside of your application or is a clear subsystem, move it to its own git repository and make it into a standalone npm package.
因此,本项目的重点是说明一个中型应用程序的可行结构。
你的整体架构是什么
构建web应用程序有许多方法,例如
服务器端MVC,一种Ruby on Rails
单页应用样式MongoDB/Express/Angular/Node (MEAN)
基本的网站与一些形式
模型/操作/视图/事件风格的MVC已经死了,是时候继续前进了
还有很多现在和历史上的
每一个都适合不同的目录结构。对于这个例子来说,它只是一个脚手架,而不是一个完全工作的应用程序,但我假设以下关键架构点:
该网站有一些传统的静态页面/模板
网站的“应用程序”部分是按单页应用程序样式开发的
应用程序向浏览器公开REST/JSON样式的API
该应用程序模拟了一个简单的业务领域,在本例中,它是一个汽车经销商应用程序
那么Ruby on Rails呢?
这个项目贯穿始终的一个主题是,Ruby on Rails中所体现的许多思想以及它们所采用的“约定优于配置”的决策,虽然被广泛接受和使用,但实际上并没有多大帮助,有时还与这个存储库所推荐的相反。
我在这里的主要观点是,有组织代码的基本原则,并且基于这些原则,Ruby on Rails约定对Ruby on Rails社区(主要)是有意义的。然而,只是不加思索地模仿这些惯例并没有抓住重点。一旦你掌握了基本的原则,你所有的项目都将被很好地组织和清晰:shell脚本,游戏,移动应用程序,企业项目,甚至你的主目录。
For the Rails community, they want to be able to have a single Rails developer switch from app to app to app and be familiar and comfortable with it each time. This makes great sense if you are 37 signals or Pivotal Labs, and has benefits. In the server-side JavaScript world, the overall ethos is just way more wild west anything goes and we don't really have a problem with that. That's how we roll. We're used to it. Even within express.js, it's a close kin of Sinatra, not Rails, and taking conventions from Rails is usually not helping anything. I'd even say Principles over Convention over Configuration.
基本原则和动机
Be mentally manageable
The brain can only deal with and think about a small number of related things at once. That's why we use directories. It helps us deal with complexity by focusing on small portions.
Be size-appropriate
Don't create "Mansion Directories" where there's just 1 file all alone 3 directories down. You can see this happening in the Ansible Best Practices that shames small projects into creating 10+ directories to hold 10+ files when 1 directory with 3 files would be much more appropriate. You don't drive a bus to work (unless you're a bus driver, but even then your driving a bus AT work not TO work), so don't create filesystem structures that aren't justified by the actual files inside them.
Be modular but pragmatic
The node community overall favors small modules. Anything that can cleanly be separated out from your app entirely should be extracted into a module either for internal use or publicly published on npm. However, for the medium-sized applications that are the scope here, the overhead of this can add tedium to your workflow without commensurate value. So for the time when you have some code that is factored out but not enough to justify a completely separate npm module, just consider it a "proto-module" with the expectation that when it crosses some size threshold, it would be extracted out.
Some folks such as @hij1nx even include an app/node_modules directory and have package.json files in the proto-module directories to facilitate that transition and act as a reminder.
Be easy to locate code
Given a feature to build or a bug to fix, our goal is that a developer has no struggle locating the source files involved.
Names are meaningful and accurate
crufty code is fully removed, not left around in an orphan file or just commented out
Be search-friendly
all first-party source code is in the app directory so you can cd there are run find/grep/xargs/ag/ack/etc and not be distracted by third party matches
Use simple and obvious naming
npm now seems to require all-lowercase package names. I find this mostly terrible but I must follow the herd, thus filenames should use kebab-case even though the variable name for that in JavaScript must be camelCase because - is a minus sign in JavaScript.
variable name matches the basename of the module path, but with kebab-case transformed to camelCase
Group by Coupling, Not by Function
This is a major departure from the Ruby on Rails convention of app/views, app/controllers, app/models, etc
Features get added to a full stack, so I want to focus on a full stack of files that are relevant to my feature. When I'm adding a telephone number field to the user model, I don't care about any controller other than the user controller, and I don't care about any model other than the user model.
So instead of editing 6 files that are each in their own directory and ignoring tons of other files in those directories, this repository is organized such that all the files I need to build a feature are colocated
By the nature of MVC, the user view is coupled to the user controller which is coupled to the user model. So when I change the user model, those 3 files will often change together, but the deals controller or customer controller are decoupled and thus not involved. Same applies to non-MVC designs usually as well.
MVC or MOVE style decoupling in terms of which code goes in which module is still encouraged, but spreading the MVC files out into sibling directories is just annoying.
Thus each of my routes files has the portion of the routes it owns. A rails-style routes.rb file is handy if you want an overview of all routes in the app, but when actually building features and fixing bugs, you only care about the routes relevant to the piece you are changing.
Store tests next to the code
This is just an instance of "group by coupling", but I wanted to call it out specifically. I've written many projects where the tests live under a parallel filesystem called "tests" and now that I've started putting my tests in the same directory as their corresponding code, I'm never going back. This is more modular and much easier to work with in text editors and alleviates a lot of the "../../.." path nonsense. If you are in doubt, try it on a few projects and decide for yourself. I'm not going to do anything beyond this to convince you that it's better.
Reduce cross-cutting coupling with Events
It's easy to think "OK, whenever a new Deal is created, I want to send an email to all the Salespeople", and then just put the code to send those emails in the route that creates deals.
However, this coupling will eventually turn your app into a giant ball of mud.
Instead, the DealModel should just fire a "create" event and be entirely unaware of what else the system might do in response to that.
When you code this way, it becomes much more possible to put all the user related code into app/users because there's not a rat's nest of coupled business logic all over the place polluting the purity of the user code base.
Code flow is followable
Don't do magic things. Don't autoload files from magic directories in the filesystem. Don't be Rails. The app starts at app/server.js:1 and you can see everything it loads and executes by following the code.
Don't make DSLs for your routes. Don't do silly metaprogramming when it is not called for.
If your app is so big that doing magicRESTRouter.route(somecontroller, {except: 'POST'}) is a big win for you over 3 basic app.get, app.put, app.del, calls, you're probably building a monolithic app that is too big to effectively work on. Get fancy for BIG wins, not for converting 3 simple lines to 1 complex line.
Use lower-kebab-case filenames
This format avoids filesystem case sensitivity issues across platforms
npm forbids uppercase in new package names, and this works well with that
express.js细节
Don't use app.configure. It's almost entirely useless and you just don't need it. It is in lots of boilerplate due to mindless copypasta.
THE ORDER OF MIDDLEWARE AND ROUTES IN EXPRESS MATTERS!!!
Almost every routing problem I see on stackoverflow is out-of-order express middleware
In general, you want your routes decoupled and not relying on order that much
Don't use app.use for your entire application if you really only need that middleware for 2 routes (I'm looking at you, body-parser)
Make sure when all is said and done you have EXACTLY this order:
Any super-important application-wide middleware
All your routes and assorted route middlewares
THEN error handlers
Sadly, being sinatra-inspired, express.js mostly assumes all your routes will be in server.js and it will be clear how they are ordered. For a medium-sized application, breaking things out into separate routes modules is nice, but it does introduce peril of out-of-order middleware
应用程序符号链接技巧
在Node.js的Better local require()路径中,社区详细地概述和讨论了许多方法。我可能很快就会决定选择“只是处理大量的../../../..”或者使用requireFrom模块。然而,目前,我一直在使用下面详细介绍的符号链接技巧。
因此,避免使用烦人的相对路径(如require("../../../config")的项目内部require的一种方法是使用以下技巧:
create a symlink under node_modules for your app
cd node_modules && ln -nsf ../app
add just the node_modules/app symlink itself, not the entire node_modules folder, to git
git add -f node_modules/app
Yes, you should still have "node_modules" in your .gitignore file
No, you should not put "node_modules" into your git repository. Some people will recommend you do this. They are incorrect.
Now you can require intra-project modules using this prefix
var config = require("app/config");
var DealModel = require("app/deals/deal-model");
Basically, this makes intra-project requires work very similarly to requires for external npm modules.
Sorry, Windows users, you need to stick with parent directory relative paths.
配置
一般来说,代码模块和类只需要传入一个基本的JavaScript选项对象。只有app/server.js应该加载app/config.js模块。在此基础上,它可以根据需要合成小的选项对象来配置子系统,但是将每个子系统耦合到一个充满额外信息的大型全局配置模块是糟糕的耦合。
尝试集中创建DB连接并将其传递到子系统,而不是传递连接参数并让子系统自己进行传出连接。
NODE_ENV
这是另一个从Rails继承而来的诱人但糟糕的想法。app/config.js中应该有1个地方查看NODE_ENV环境变量。其他所有内容都应采用显式选项作为类构造函数参数或模块配置参数。
如果电子邮件模块有一个关于如何发送电子邮件的选项(SMTP,登录到stdout,放入队列等),它应该采用一个像{deliver: 'stdout'}这样的选项,但它绝对不应该检查NODE_ENV。
测试
我现在将测试文件与其对应的代码放在同一个目录中,并使用文件名扩展名命名约定来区分测试和生产代码。
foo.js有模块"foo"的代码
foo.tape.js有针对foo的基于节点的测试,并且位于相同的目录中
Foo.btape.js可用于需要在浏览器环境中执行的测试
我使用文件系统glob和find。-name '*.tape.js'命令来访问我的所有测试。
如何在每个.js模块文件中组织代码
这个项目的作用域主要是关于文件和目录的位置,我不想添加太多其他作用域,但我只想提到,我将代码组织成3个不同的部分。
CommonJS的打开块需要调用状态依赖项
纯javascript的主要代码块。这里没有CommonJS污染。不要引用导出、模块或require。
CommonJS设置导出的关闭块