UNPKG

atomatic

Version:

An easy to use build and development tool for Atomic Design Systems, that works with rollup.js, Browserify, webpack and many more...

150 lines (125 loc) 4.11 kB
const path = require('path'), fs = require('fs'), express = require('express'), sgUtil = require('./util'); class DevServer { constructor({conf, CollectorStore: {getSections, getUrls, getCollectedData}}) { this.conf = conf; this.getSections = getSections; this.getUrls = getUrls; this.getCollectedData = getCollectedData; this.name = conf.get('package.name'); this.htmlExt = conf.get('htmlExt'); this.viewerRootPath = conf.get('app.viewerRootPath'); this.baseDir = conf.get('dest'); this.app = express(); this.enableNoCache(); this.allowCrossOriginAccess(); this.handleTrailinglessRequests(); } start(cb) { this.bsConfig = this.conf.util.extendDeep({}, { server: {baseDir: this.baseDir}, port: 3000, reloadOnRestart: true, notify: false, https: false, open: true, startPath: null, reloadDelay: 0, reloadDebounce: 0, injectChanges: true, middleware: [this.app], logLevel: 'info', logPrefix: 'BS', logConnections: false, logFileChanges: true, logSnippet: true, snippetOptions: { rule: { match: /<\/head>/i, fn: function (snippet, match) { return snippet + match; } } } }, this.conf.get('server')); this.instanciate().init(this.bsConfig, cb); return this; } instanciate() { const browserSync = require(this.getBrowserSyncPath()); this.browserSync = browserSync[browserSync.has(this.name) ? 'get' : 'create'](this.name); return this.browserSync; } getBrowserSyncPath() { const parentBrowserSyncPath = path.join(process.cwd(), 'node_modules', 'browser-sync'); return fs.existsSync(parentBrowserSyncPath) ? parentBrowserSyncPath : 'browser-sync'; } addRoutes() { const {getSections, getUrls} = this; getSections().forEach((section) => { this.app.get(section.route, async (req, res) => { const ext = sgUtil.getFileExtension(req.url), type = sgUtil.getFileExtension(sgUtil.removeFileExtension(req.url)) || ext, basePath = type === ext ? sgUtil.removeFileExtension(req.url) : sgUtil.removeFileExtension(sgUtil.removeFileExtension(req.url), true); if (getUrls().has(basePath)) { const file = getUrls().get(basePath), renderOutput = await file.render; res.send(renderOutput[type]); } }); }); this.app.get(['/', `/${this.viewerRootPath}`], (req, res) => { res.writeHead(301, { Location: `${req.socket.encrypted ? 'https' : 'http'}://${req.headers.host}/${this.viewerRootPath}/index.html` }); res.end(); }); this.app.get(`/${this.viewerRootPath}/structure.json`, (req, res) => { res.json(this.getCollectedData()); res.end(); }); } enableNoCache() { this.app.use((req, res, next) => { res.setHeader('Cache-Control', 'private, no-cache, no-store, must-revalidate'); res.setHeader('Expires', '-1'); res.setHeader('Pragma', 'no-cache'); next(); }); } allowCrossOriginAccess() { this.app.use((req, res, next) => { res.setHeader('Access-Control-Allow-Origin', '*'); next(); }); } handleTrailinglessRequests() { this.app.use((req, res, next) => { const {pathname, path} = req._parsedUrl; if (pathname.substr(-1) !== '/' && pathname.length > 1 && pathname.match(/\/[^.\/]+$/)) { const newPath = path.replace(new RegExp(pathname), `${pathname}/`); res.writeHead(301, { Location: `${req.socket.encrypted ? 'https' : 'http'}://${req.headers.host}${newPath}` }); res.end(); } else { next(); } }); } rewriteUrls(url) { const {urlRewriteRules = {}} = this.bsConfig; urlRewriteRules[`/([^/]+)/$`] = `/$1/$1.${this.htmlExt}`; Object.keys(urlRewriteRules).map((key) => { url = url.replace(new RegExp(key, 'i'), urlRewriteRules[key]); }); return url; } } module.exports = DevServer;