├── development.blocks ├── .gitkeep ├── page │ └── page.deps.js └── livereload │ ├── livereload.bemhtml.js │ └── livereload.md ├── .eslintignore ├── common.blocks ├── root │ ├── root.deps.js │ └── root.bemtree.js ├── header │ ├── header.deps.js │ └── header.bemtree.js ├── logo │ └── logo.deps.js ├── page │ ├── _view │ │ ├── page_view_404.post.css │ │ └── page_view_404.bemtree.js │ ├── page.deps.js │ └── page.bemtree.js ├── footer │ └── footer.bemtree.js ├── body │ └── body.bemtree.js └── page-index │ └── page-index.bemtree.js ├── .travis.yml ├── static └── favicon.ico ├── desktop.bundles └── index │ └── index.bemdecl.js ├── .bem └── templates │ └── bemdecl.js ├── server ├── config.js ├── rebuild.js ├── render.js └── index.js ├── .borschik ├── .gitignore ├── .eslintrc.yml ├── .bemhint.js ├── .stylelintrc ├── nodemon.json ├── .bemrc ├── README.md ├── package.json └── .enb └── make.js /development.blocks/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | **/*.deps.js 2 | *bundles/** 3 | static 4 | -------------------------------------------------------------------------------- /common.blocks/root/root.deps.js: -------------------------------------------------------------------------------- 1 | ({ 2 | shouldDeps: 'page' 3 | }) 4 | -------------------------------------------------------------------------------- /common.blocks/header/header.deps.js: -------------------------------------------------------------------------------- 1 | ({ 2 | shouldDeps: ['logo'] 3 | }) 4 | -------------------------------------------------------------------------------- /common.blocks/logo/logo.deps.js: -------------------------------------------------------------------------------- 1 | ({ 2 | shouldDeps: ['link'] 3 | }) 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "node" 4 | - "4" 5 | - "8" 6 | -------------------------------------------------------------------------------- /development.blocks/page/page.deps.js: -------------------------------------------------------------------------------- 1 | ({ 2 | shouldDeps: 'livereload' 3 | }); 4 | -------------------------------------------------------------------------------- /static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bem/bem-express/HEAD/static/favicon.ico -------------------------------------------------------------------------------- /common.blocks/page/_view/page_view_404.post.css: -------------------------------------------------------------------------------- 1 | .page_view_404 { 2 | color: red; 3 | } 4 | -------------------------------------------------------------------------------- /desktop.bundles/index/index.bemdecl.js: -------------------------------------------------------------------------------- 1 | exports.blocks = [ 2 | { name: 'root' }, 3 | { name: 'page-index' } 4 | ]; 5 | -------------------------------------------------------------------------------- /common.blocks/footer/footer.bemtree.js: -------------------------------------------------------------------------------- 1 | block('footer')({ 2 | content: () => [ 3 | 'footer content' 4 | ] 5 | }); 6 | -------------------------------------------------------------------------------- /common.blocks/body/body.bemtree.js: -------------------------------------------------------------------------------- 1 | block('body')({ 2 | content: node => ({ 3 | block: node.data.view 4 | }) 5 | }); 6 | -------------------------------------------------------------------------------- /common.blocks/page/_view/page_view_404.bemtree.js: -------------------------------------------------------------------------------- 1 | block('page').mod('view', '404').content()(function() { 2 | return '404'; 3 | }); 4 | -------------------------------------------------------------------------------- /common.blocks/page-index/page-index.bemtree.js: -------------------------------------------------------------------------------- 1 | block('page-index')({ 2 | content: () => [ 3 | 'Index page content' 4 | ] 5 | }); 6 | -------------------------------------------------------------------------------- /.bem/templates/bemdecl.js: -------------------------------------------------------------------------------- 1 | module.exports = entity => `exports.blocks = [ 2 | { name: 'root' }, 3 | { name: '${entity.block}' } 4 | ]; 5 | `; 6 | -------------------------------------------------------------------------------- /common.blocks/header/header.bemtree.js: -------------------------------------------------------------------------------- 1 | block('header')({ 2 | content: () => [ 3 | { 4 | block: 'logo' 5 | } 6 | ] 7 | }); 8 | -------------------------------------------------------------------------------- /server/config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | staticFolder: 'static', 3 | defaultPort: 3000, 4 | cacheTTL: 30000, 5 | sessionSecret: 'REPLACE_ME_WITH_RANDOM_STRING' 6 | }; 7 | -------------------------------------------------------------------------------- /common.blocks/page/page.deps.js: -------------------------------------------------------------------------------- 1 | ({ 2 | shouldDeps: [ 3 | { 4 | mods: { view: ['404'] } 5 | }, 6 | 'header', 7 | 'body', 8 | 'footer' 9 | ] 10 | }) 11 | -------------------------------------------------------------------------------- /.borschik: -------------------------------------------------------------------------------- 1 | { 2 | "freeze_paths" : { 3 | "node_modules/**": ":base64:", 4 | "node_modules/**/*.svg": ":encodeURIComponent:", 5 | "*.blocks/**": ":base64:", 6 | "*.blocks/**/*.svg": ":encodeURIComponent:" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /development.blocks/livereload/livereload.bemhtml.js: -------------------------------------------------------------------------------- 1 | block('page').content()(function() { 2 | return [ 3 | applyNext(), 4 | { 5 | elem: 'js', 6 | url: '/livereload.js?snipver=1' 7 | } 8 | ]; 9 | }); 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .DS_Store 3 | .idea 4 | .project 5 | .svn 6 | npm-debug.log 7 | 8 | static/index.min.* 9 | 10 | # bem 11 | .enb/tmp/ 12 | *bundles*/*/*.* 13 | !*bundles*/*/*.bemdecl.js 14 | *bundles*/*/*.tmpl.bemdecl.js 15 | !*bundles*/.bem/* 16 | -------------------------------------------------------------------------------- /common.blocks/page/page.bemtree.js: -------------------------------------------------------------------------------- 1 | block('page')({ 2 | content: () => [ 3 | { 4 | block: 'header' 5 | }, 6 | { 7 | block: 'body' 8 | }, 9 | { 10 | block: 'footer' 11 | } 12 | ] 13 | }); 14 | -------------------------------------------------------------------------------- /.eslintrc.yml: -------------------------------------------------------------------------------- 1 | parser: babel-eslint 2 | 3 | plugins: 4 | - bem-xjst 5 | 6 | env: 7 | browser: true 8 | node: true 9 | es6: true 10 | bem-xjst/bemhtml: true 11 | bem-xjst/bemtree: true 12 | 13 | globals: 14 | # YModules 15 | modules: false 16 | 17 | rules: 18 | curly: [error, multi-line] 19 | 20 | extends: pedant 21 | -------------------------------------------------------------------------------- /.bemhint.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | levels: [ 3 | '*.blocks' 4 | ], 5 | 6 | excludePaths: [ 7 | 'node_modules/**' 8 | ], 9 | 10 | plugins: { 11 | 'bemhint-css-naming': { 12 | techs: { 13 | styl: true, 14 | 'post.css': true, 15 | css: true 16 | } 17 | }, 18 | 'bemhint-fs-naming': true, 19 | 'bemhint-deps-specification': true 20 | } 21 | }; 22 | -------------------------------------------------------------------------------- /development.blocks/livereload/livereload.md: -------------------------------------------------------------------------------- 1 | # livereload 2 | 3 | Livereload is built on top of https://github.com/mklabs/tiny-lr. 4 | 5 | ## How it works 6 | 1. Template injects a [script](https://github.com/livereload/livereload-js) which is served by `tiny-lr` middleware. 7 | 2. The script connects to `tiny-lr` server via WS. 8 | 3. The server watches for changes of built files via `chokidar` and send commands to update styles or refresh the page via WS. 9 | 10 | The template is collected by `page` block dependencies. 11 | `tiny-lr` server starts only in dev mode. 12 | -------------------------------------------------------------------------------- /.stylelintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "stylelint-config-standard", 3 | "rules": { 4 | "indentation": 4, 5 | "declaration-empty-line-before": [ 6 | "always", 7 | { 8 | "except": [ 9 | "first-nested" 10 | ], 11 | "ignore": [ 12 | "after-declaration", 13 | "after-comment" 14 | ] 15 | } 16 | ], 17 | "number-leading-zero": "never" 18 | }, 19 | ignoreFiles: [ "**/*.js", "**/*.json" ] 20 | } 21 | -------------------------------------------------------------------------------- /nodemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "restartable": "rs", 3 | "ignore": [ 4 | ".git", 5 | "node_modules/**/node_modules" 6 | ], 7 | "verbose": true, 8 | "execMap": { 9 | "js": "node" 10 | }, 11 | "watch": [ 12 | "server" 13 | ], 14 | "events": { 15 | "start": "echo 'Server started' | notify -t bem-express", 16 | "restart": "echo 'Server started' | notify -t bem-express", 17 | "crash": "echo 'Server failed to start :(((((((((((((((' | notify -t bem-express" 18 | }, 19 | "env": { 20 | "NODE_ENV": "development" 21 | }, 22 | "ext": "js" 23 | } 24 | -------------------------------------------------------------------------------- /.bemrc: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | 4 | levels: { 5 | 'common.blocks': {}, 6 | 'desktop.bundles': {} 7 | }, 8 | 9 | modules: { 10 | 'bem-tools': { 11 | plugins: { 12 | create: { 13 | templates: { 14 | 'bemdecl.js': '.bem/templates/bemdecl.js', 15 | }, 16 | techs: ['css', 'js'], 17 | levels: { 18 | 'desktop.bundles': { 19 | techs: [ 20 | 'bemdecl.js', 21 | ], 22 | }, 23 | 'common.blocks': { 24 | default: true 25 | } 26 | } 27 | } 28 | } 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /common.blocks/root/root.bemtree.js: -------------------------------------------------------------------------------- 1 | block('root')({ 2 | replace: (node, ctx) => { 3 | const data = node.data = ctx.data; 4 | const meta = data.meta || {}; 5 | const og = meta.og || {}; 6 | 7 | if (ctx.context) return ctx.context; 8 | 9 | return { 10 | block: 'page', 11 | title: data.title, 12 | favicon: '/favicon.ico', 13 | styles: [ 14 | { 15 | elem: 'css', 16 | url: '/index.min.css' 17 | } 18 | ], 19 | scripts: [ 20 | { 21 | elem: 'js', 22 | url: '/index.min.js' 23 | } 24 | ], 25 | head: [ 26 | { elem: 'meta', attrs: { name: 'description', content: meta.description } }, 27 | { elem: 'meta', attrs: { property: 'og:title', content: og.title || data.title } }, 28 | { elem: 'meta', attrs: { property: 'og:url', content: og.url } }, 29 | { elem: 'meta', attrs: { property: 'og:site_name', content: og.siteName } }, 30 | { elem: 'meta', attrs: { property: 'og:locale', content: og.locale || 'en_US' } }, 31 | { elem: 'meta', attrs: { property: 'og:type', content: 'website' } }, 32 | { elem : 'meta', attrs : { name : 'viewport', content : 'width=device-width, initial-scale=1' } } 33 | ], 34 | mods: { 35 | theme: 'islands', 36 | view: data.view 37 | } 38 | }; 39 | } 40 | }); 41 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # bem-express 2 | 3 | Almost the same as [project-stub](https://github.com/bem/project-stub/) but with [BEMTREE](https://en.bem.info/technology/bemtree/) and [Express](http://expressjs.com/). 4 | 5 | [![Build Status](https://travis-ci.org/bem/bem-express.svg?branch=master)](https://travis-ci.org/bem/bem-express) 6 | 7 | ## Installation 8 | 9 | ```sh 10 | git clone https://github.com/bem/bem-express.git 11 | cd bem-express 12 | npm i 13 | ``` 14 | 15 | ## Development 16 | 17 | ```sh 18 | npm run dev 19 | ``` 20 | will run initial `enb make` command and then start the server with `nodemon` which will restart it on any server file update. Also `chokidar` will watch for changes in `*.blocks/**` and rebuild the project automatically. Then livereload will update the page in the browser. 21 | 22 | You may also set `NO_LIVERELOAD` env variable to switch livereload off: 23 | ```sh 24 | NO_LIVERELOAD=1 npm run dev 25 | ``` 26 | 27 | You may also run rebuild manually with `enb make` or with external watcher (e.g. `npm run watch`). To switch off automatic rebuild use `NO_AUTOMAKE` env variable: 28 | ```sh 29 | NO_AUTOMAKE=1 npm run dev 30 | ``` 31 | 32 | ## Production 33 | 34 | ```sh 35 | YENV=production enb make 36 | NODE_ENV=production node server 37 | ``` 38 | 39 | ## Templating 40 | 41 | Templating starts in `root` block which replaces itself with `page` or any other context (if specified as argument to `render` function). 42 | 43 | ## Pro tips 44 | 45 | Run server in dev mode with `NODE_ENV=development` environment variable (`nodemon` will set it for you). 46 | 47 | In dev mode 48 | 49 | * Add `?json=1` to URL to see raw data 50 | * Add `?bemjson=1` to URL to see BEMJSON generated with BEMTREE templates. 51 | -------------------------------------------------------------------------------- /server/rebuild.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const _ = require('lodash'); 4 | const tinyLr = require('tiny-lr'); 5 | const notifier = require('node-notifier'); 6 | const make = require('enb').make; 7 | const watch = require('chokidar').watch; 8 | 9 | const rootDir = path.join(__dirname, '..'); 10 | const watchOpts = { 11 | persistent: true, 12 | ignoreInitial: true, 13 | ignored: '**/.DS_Store' 14 | }; 15 | 16 | // get bundles list 17 | const bundlesDir = path.join(rootDir, 'desktop.bundles'); 18 | const bundles = fs.readdirSync(bundlesDir).filter(function(file) { 19 | return fs.statSync(path.join(bundlesDir, file)).isDirectory(); 20 | }); 21 | 22 | // enb make 23 | function rebuild(event, file) { 24 | // TODO: get target via file extention 25 | // TODO: get current bundle via websocket 26 | // NOTE: use `[path.join('desktop.bundles', 'index')]` to build specific target 27 | 28 | console.time('Rebuild: ' + file); 29 | return make() 30 | .then(function() { 31 | console.timeEnd('Rebuild: ' + file); 32 | notifier.notify({ 33 | title: 'bem-express', 34 | message: 'Build finished' 35 | }); 36 | }) 37 | .fail(function(err) { 38 | notifier.notify({ 39 | title: 'Build failed', 40 | message: err 41 | }); 42 | }); 43 | } 44 | 45 | const debouncedRebuild = _.debounce(rebuild, 30, { leading: true, trailing: true }); 46 | 47 | process.env.NO_AUTOMAKE || watch([ 48 | path.join(rootDir, '*.blocks', '**'), 49 | ].concat(bundles.map(function(bundle) { 50 | return path.join(bundlesDir, bundle, bundle + '.bemdecl.js'); 51 | })), watchOpts).on('all', debouncedRebuild); 52 | 53 | // livereload 54 | process.env.NO_LIVERELOAD || watch([ 55 | path.join(rootDir, 'static', '*.min.*'), 56 | path.join(bundlesDir, '*', '*.bemtree.js'), 57 | ].concat(bundles.map(function(bundle) { 58 | return path.join(bundlesDir, bundle, bundle + '.bemhtml.js'); 59 | })), watchOpts).on('all', function(event, file) { 60 | tinyLr.changed(file); 61 | }); 62 | 63 | module.exports = function(app) { 64 | if (!app) return; 65 | 66 | // livereload middleware 67 | // serves the script injected by development.blocks/livereload template 68 | app.use(tinyLr.middleware({ app: app, dashboard: true })); 69 | }; 70 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Vladimir Grinenko", 3 | "name": "bem-express", 4 | "version": "2.0.0", 5 | "repository": { 6 | "type": "git", 7 | "url": "git@github.com:bem/bem-express.git" 8 | }, 9 | "engines": { 10 | "node": ">=4" 11 | }, 12 | "browserslist": [ 13 | "ie >= 10", 14 | "opera 12.1", 15 | "> 2%", 16 | "last 2 versions" 17 | ], 18 | "dependencies": { 19 | "body-parser": "^1.15.0", 20 | "compression": "^1.6.2", 21 | "connect-slashes": "^1.3.1", 22 | "cookie-parser": "^1.4.1", 23 | "cookie-session": "^1.2.0", 24 | "csurf": "^1.9.0", 25 | "debug-http": "^1.1.0", 26 | "decache": "^4.4.0", 27 | "errorhandler": "^1.4.3", 28 | "express": "^4.13.4", 29 | "express-session": "^1.13.0", 30 | "lodash": "^4.11.2", 31 | "morgan": "^1.7.0", 32 | "passport": "^0.4.0", 33 | "passport-local": "^1.0.0", 34 | "serve-favicon": "^2.3.0", 35 | "serve-static": "^1.10.2" 36 | }, 37 | "devDependencies": { 38 | "autoprefixer": "^7.1.1", 39 | "babel-eslint": "^8.2.2", 40 | "bem": "^2.0.0", 41 | "bem-components": "^6.0.1", 42 | "bem-core": "^4.2.1", 43 | "bem-tools-create": "^2.2.0", 44 | "bemhint": "^0.9.1", 45 | "bemhint-css-naming": "^1.0.1", 46 | "bemhint-deps-specification": "^1.0.0", 47 | "bemhint-fs-naming": "^1.0.0", 48 | "chokidar": "^1.6.1", 49 | "chokidar-cli": "^1.2.0", 50 | "enb": "^1.3.0", 51 | "enb-bem-techs": "^2.2.1", 52 | "enb-bemxjst": "^8.6.7", 53 | "enb-borschik": "^3.0.0", 54 | "enb-js": "^1.1.0", 55 | "enb-postcss": "^2.0.0", 56 | "eslint": "^4.1.1", 57 | "eslint-config-pedant": "^0.10.0", 58 | "eslint-plugin-bem-xjst": "^2.2.0", 59 | "node-notifier": "^5.1.2", 60 | "node-notifier-cli": "^1.0.1", 61 | "nodemon": "^1.9.2", 62 | "postcss-calc": "^6.0.0", 63 | "postcss-each": "^0.10.0", 64 | "postcss-for": "^2.1.1", 65 | "postcss-import": "^10.0.0", 66 | "postcss-nested": "^2.0.2", 67 | "postcss-reporter": "^4.0.0", 68 | "postcss-simple-vars": "^4.0.0", 69 | "postcss-url": "^7.1.1", 70 | "rebem-css": "^0.2.0", 71 | "stylelint": "^7.7.1", 72 | "stylelint-config-standard": "^16.0.0", 73 | "tiny-lr": "^1.0.3", 74 | "ym": "^0.1.2" 75 | }, 76 | "scripts": { 77 | "start": "node server", 78 | "postinstall": "npm run drop-cache && npm run make", 79 | "make": "enb make", 80 | "drop-cache": "rm -rf .enb/tmp", 81 | "dev": "npm run make && nodemon", 82 | "watch": "chokidar '*.blocks/**' --initial -c 'enb make && notify -t bem-express -m Built'", 83 | "test": "npm run lint && npm run make", 84 | "lint": "eslint . && bemhint . && stylelint *.blocks/**/*.css" 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /server/render.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const decache = require('decache'); 3 | const config = require('./config'); 4 | 5 | const bundleName = 'index'; 6 | const pathToBundle = path.resolve(__dirname, '..', 'desktop.bundles', bundleName); 7 | 8 | const isDev = process.env.NODE_ENV === 'development'; 9 | const useCache = !isDev; 10 | const cacheTTL = config.cacheTTL; 11 | let templates = getTemplates(); 12 | let cache = Object.create(null); 13 | 14 | function render(req, res, data, context) { 15 | const query = req.query; 16 | const user = req.user; 17 | const cacheKey = req.originalUrl + (context ? JSON.stringify(context) : '') + (user ? JSON.stringify(user) : ''); 18 | const cached = cache[cacheKey]; 19 | 20 | if (useCache && cached && (new Date() - cached.timestamp < cacheTTL)) { 21 | return res.send(cached.html); 22 | } 23 | 24 | if (isDev && query.json) return res.send('
' + JSON.stringify(data, null, 4) + '
'); 25 | 26 | const bemtreeCtx = { 27 | block: 'root', 28 | context: context, 29 | // extend with data needed for all routes 30 | data: Object.assign({}, { 31 | url: req._parsedUrl, 32 | csrf: req.csrfToken() 33 | }, data) 34 | }; 35 | 36 | if (isDev) templates = getTemplates(); 37 | 38 | let bemjson; 39 | 40 | try { 41 | bemjson = templates.BEMTREE.apply(bemtreeCtx); 42 | } catch(err) { 43 | console.error('BEMTREE error', err.stack); 44 | console.trace('server stack'); 45 | return res.sendStatus(500); 46 | } 47 | 48 | if (isDev && query.bemjson) return res.send('
' + JSON.stringify(bemjson, null, 4) + '
'); 49 | 50 | let html; 51 | 52 | try { 53 | html = templates.BEMHTML.apply(bemjson); 54 | } catch(err) { 55 | console.error('BEMHTML error', err.stack); 56 | return res.sendStatus(500); 57 | } 58 | 59 | useCache && (cache[cacheKey] = { 60 | timestamp: new Date(), 61 | html: html 62 | }); 63 | 64 | res.send(html); 65 | } 66 | 67 | function dropCache() { 68 | cache = Object.create(null); 69 | } 70 | 71 | function evalFile(filename) { 72 | decache(filename); 73 | // Fixes memory leak 74 | // clean module from links to previous parsed files 75 | module.children = module.children.filter(item => item.id !== filename); 76 | return require(filename); 77 | } 78 | 79 | function getTemplates() { 80 | return { 81 | BEMTREE: evalFile(path.join(pathToBundle, bundleName + '.bemtree.js')).BEMTREE, 82 | BEMHTML: evalFile(path.join(pathToBundle, bundleName + '.bemhtml.js')).BEMHTML 83 | }; 84 | } 85 | 86 | module.exports = { 87 | render: render, 88 | dropCache: dropCache 89 | }; 90 | -------------------------------------------------------------------------------- /server/index.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const express = require('express'); 4 | const app = express(); 5 | const bodyParser = require('body-parser'); 6 | const favicon = require('serve-favicon'); 7 | const morgan = require('morgan'); 8 | const serveStatic = require('serve-static'); 9 | const cookieParser = require('cookie-parser'); 10 | const expressSession = require('express-session'); 11 | const slashes = require('connect-slashes'); 12 | const passport = require('passport'); 13 | // LocalStrategy = require('passport-local').Strategy, 14 | const csrf = require('csurf'); 15 | const compression = require('compression'); 16 | 17 | const config = require('./config'); 18 | const staticFolder = path.resolve(__dirname, '..', config.staticFolder); 19 | 20 | const Render = require('./render'); 21 | const render = Render.render; 22 | const dropCache = Render.dropCache; // eslint-disable-line no-unused-vars 23 | 24 | const port = process.env.PORT || config.defaultPort; 25 | const isSocket = isNaN(port); 26 | const isDev = process.env.NODE_ENV === 'development'; 27 | 28 | require('debug-http')(); 29 | 30 | app 31 | .disable('x-powered-by') 32 | .enable('trust proxy') 33 | .use(compression()) 34 | .use(favicon(path.join(staticFolder, 'favicon.ico'))) 35 | .use(serveStatic(staticFolder)) 36 | .use(morgan('combined')) 37 | .use(cookieParser()) 38 | .use(bodyParser.urlencoded({ extended: true })) 39 | .use(expressSession({ 40 | resave: true, 41 | saveUninitialized: true, 42 | secret: config.sessionSecret 43 | })) 44 | .use(passport.initialize()) 45 | .use(passport.session()) 46 | .use(csrf()); 47 | 48 | // NOTE: conflicts with livereload 49 | isDev || app.use(slashes()); 50 | 51 | passport.serializeUser(function(user, done) { 52 | done(null, JSON.stringify(user)); 53 | }); 54 | 55 | passport.deserializeUser(function(user, done) { 56 | done(null, JSON.parse(user)); 57 | }); 58 | 59 | app.get('/ping/', function(req, res) { 60 | res.send('ok'); 61 | }); 62 | 63 | app.get('/', function(req, res) { 64 | render(req, res, { 65 | view: 'page-index', 66 | title: 'Main page', 67 | meta: { 68 | description: 'Page description', 69 | og: { 70 | url: 'https://site.com', 71 | siteName: 'Site name' 72 | } 73 | } 74 | }) 75 | }); 76 | 77 | isDev && require('./rebuild')(app); 78 | 79 | app.get('*', function(req, res) { 80 | res.status(404); 81 | return render(req, res, { view: '404' }); 82 | }); 83 | 84 | if (isDev) { 85 | app.get('/error/', function() { 86 | throw new Error('Uncaught exception from /error'); 87 | }); 88 | 89 | app.use(require('errorhandler')()); 90 | } 91 | 92 | isSocket && fs.existsSync(port) && fs.unlinkSync(port); 93 | 94 | app.listen(port, function() { 95 | isSocket && fs.chmod(port, '0777'); 96 | console.log('server is listening on', this.address().port); 97 | }); 98 | -------------------------------------------------------------------------------- /.enb/make.js: -------------------------------------------------------------------------------- 1 | const techs = { 2 | fileProvider: require('enb/techs/file-provider'), 3 | fileMerge: require('enb/techs/file-merge'), 4 | fileCopy: require('enb/techs/file-copy'), 5 | borschik: require('enb-borschik/techs/borschik'), 6 | postcss: require('enb-postcss/techs/enb-postcss'), 7 | postcssPlugins: [ 8 | require('postcss-import')(), 9 | require('postcss-each'), 10 | require('postcss-for'), 11 | require('postcss-simple-vars')(), 12 | require('postcss-calc')(), 13 | require('postcss-nested'), 14 | require('rebem-css'), 15 | require('postcss-url')({ url: 'inline' }), 16 | require('autoprefixer')() 17 | ], 18 | browserJs: require('enb-js/techs/browser-js'), 19 | bemtree: require('enb-bemxjst/techs/bemtree'), 20 | bemhtml: require('enb-bemxjst/techs/bemhtml') 21 | }; 22 | const enbBemTechs = require('enb-bem-techs'); 23 | const levels = [ 24 | { path: 'node_modules/bem-core/common.blocks', check: false }, 25 | { path: 'node_modules/bem-core/desktop.blocks', check: false }, 26 | { path: 'node_modules/bem-components/common.blocks', check: false }, 27 | { path: 'node_modules/bem-components/desktop.blocks', check: false }, 28 | { path: 'node_modules/bem-components/design/common.blocks', check: false }, 29 | { path: 'node_modules/bem-components/design/desktop.blocks', check: false }, 30 | 'common.blocks' 31 | ]; 32 | 33 | const isProd = process.env.YENV === 'production'; 34 | isProd || levels.push('development.blocks'); 35 | 36 | module.exports = function(config) { 37 | config.nodes('*.bundles/*', function(nodeConfig) { 38 | nodeConfig.addTechs([ 39 | // essential 40 | [enbBemTechs.levels, { levels: levels }], 41 | [techs.fileProvider, { target: '?.bemdecl.js' }], 42 | [enbBemTechs.deps], 43 | [enbBemTechs.files], 44 | 45 | // css 46 | [techs.postcss, { 47 | target: '?.css', 48 | oneOfSourceSuffixes: ['post.css', 'css'], 49 | plugins: techs.postcssPlugins 50 | }], 51 | 52 | // bemtree 53 | [techs.bemtree, { sourceSuffixes: ['bemtree', 'bemtree.js'] }], 54 | 55 | // templates 56 | [techs.bemhtml, { 57 | sourceSuffixes: ['bemhtml', 'bemhtml.js'], 58 | forceBaseTemplates: true, 59 | engineOptions: { elemJsInstances: true } 60 | }], 61 | 62 | // client templates 63 | [enbBemTechs.depsByTechToBemdecl, { 64 | target: '?.tmpl.bemdecl.js', 65 | sourceTech: 'js', 66 | destTech: 'bemhtml' 67 | }], 68 | [enbBemTechs.deps, { 69 | target: '?.tmpl.deps.js', 70 | bemdeclFile: '?.tmpl.bemdecl.js' 71 | }], 72 | [enbBemTechs.files, { 73 | depsFile: '?.tmpl.deps.js', 74 | filesTarget: '?.tmpl.files', 75 | dirsTarget: '?.tmpl.dirs' 76 | }], 77 | [techs.bemhtml, { 78 | target: '?.browser.bemhtml.js', 79 | filesTarget: '?.tmpl.files', 80 | sourceSuffixes: ['bemhtml', 'bemhtml.js'], 81 | engineOptions: { elemJsInstances: true } 82 | }], 83 | 84 | // js 85 | [techs.browserJs, { includeYM: true }], 86 | [techs.fileMerge, { 87 | target: '?.js', 88 | sources: ['?.browser.js', '?.browser.bemhtml.js'] 89 | }], 90 | 91 | // borschik 92 | [techs.borschik, { source: '?.js', target: '?.min.js', minify: isProd }], 93 | [techs.borschik, { source: '?.css', target: '?.min.css', minify: isProd }], 94 | 95 | [techs.fileCopy, { source: '?.min.js', target: '../../static/?.min.js' }], 96 | [techs.fileCopy, { source: '?.min.css', target: '../../static/?.min.css' }] 97 | ]); 98 | 99 | nodeConfig.addTargets(['?.bemtree.js', '?.bemhtml.js', '../../static/?.min.js', '../../static/?.min.css']); 100 | }); 101 | }; 102 | --------------------------------------------------------------------------------