diff --git a/README.md b/README.md index 842719d..207add0 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # web-boost - + `web-boost` - boost your static site development. @@ -10,10 +10,18 @@ Web-boost is a static site generator with page-speed optimisations. You build a and routing - web-boost will compile it into highly optimised static pages. * Uglify, concat & minify `javascript` files -* Compile, concat & minify `scss`/`css` files +* Compile, concat & minify `css`/`scss`/`sass` files * Compile & minify `twig` templates * Optimise `.jpg` and `.png` files +> To speed-up compilation, all the minified files (`.min.css` and `.min.js`) will be just concatenated + +### Installation + +`npm install -g web-boost` + +> Global installation is preferable but not required + ### Usage * Generate application with `web-boost-cli` (see [user guide][1]) or create it yourself by following the [structure][2] diff --git a/bin/compile.js b/bin/compile.js index e0d794f..b5d2804 100755 --- a/bin/compile.js +++ b/bin/compile.js @@ -7,6 +7,7 @@ const pngquant = require('imagemin-pngquant'); const jpegRecompress = require('imagemin-jpeg-recompress'); const Route = require('../src/route'); const config = require('../src/config'); +const logger = require('../src/logger'); const appPath = process.cwd(); /** @@ -32,7 +33,7 @@ Promise.all(promises).then(() => { ] }) }).then(() => { - console.log('Compilation finished'); + logger.log('Compilation finished'); }).catch(err => { throw err; }); diff --git a/cli-component/index.js b/cli-component/index.js old mode 100644 new mode 100755 index fe7d94c..1eb7af0 --- a/cli-component/index.js +++ b/cli-component/index.js @@ -17,8 +17,10 @@ const destinationPath = path.resolve(process.cwd(), destination); const appPackageJson = path.join(destination, 'package.json'); fse.copy(sourcePath, destinationPath).then(() => { - let packageSrc = JSON.parse(fs.readFileSync(appPackageJson)); - let packageOut = JSON.stringify(packageSrc, (key, val) => (key === 'name') ? appName : val, 2); + fs.renameSync(`${appPackageJson}.tmpl`, appPackageJson); + + const packageSrc = JSON.parse(fs.readFileSync(appPackageJson)); + const packageOut = JSON.stringify(packageSrc, (key, val) => (key === 'name') ? appName : val, 2); fs.writeFileSync(appPackageJson, packageOut); diff --git a/cli-component/package.json b/cli-component/package.json index 4c674cb..b15408a 100644 --- a/cli-component/package.json +++ b/cli-component/package.json @@ -1,9 +1,9 @@ { "name": "web-boost-cli", - "version": "0.0.5", + "version": "1.0.3", "description": "Web-boost application generator", "bin": { - "web-boost-cli": "index.js" + "web-boost-cli": "./index.js" }, "author": "ddimitrioglo", "license": "MIT", diff --git a/cli-component/skeleton/assets/styles/base.scss b/cli-component/skeleton/assets/styles/base.scss new file mode 100644 index 0000000..75f5254 --- /dev/null +++ b/cli-component/skeleton/assets/styles/base.scss @@ -0,0 +1,5 @@ +.container { + ul { + padding-left: 5px; + } +} diff --git a/cli-component/skeleton/assets/styles/index.scss b/cli-component/skeleton/assets/styles/index.scss index 803e0d1..d13f8e1 100644 --- a/cli-component/skeleton/assets/styles/index.scss +++ b/cli-component/skeleton/assets/styles/index.scss @@ -1,4 +1,4 @@ -$primary-color: green; +@import './styles/variables'; .jumbotron { margin: 40px 20px; diff --git a/cli-component/skeleton/assets/styles/user.scss b/cli-component/skeleton/assets/styles/user.scss index bad8b50..d13f8e1 100644 --- a/cli-component/skeleton/assets/styles/user.scss +++ b/cli-component/skeleton/assets/styles/user.scss @@ -1,4 +1,4 @@ -$primary-color: blue; +@import './styles/variables'; .jumbotron { margin: 40px 20px; diff --git a/cli-component/skeleton/assets/styles/variables.scss b/cli-component/skeleton/assets/styles/variables.scss new file mode 100644 index 0000000..4fc2574 --- /dev/null +++ b/cli-component/skeleton/assets/styles/variables.scss @@ -0,0 +1 @@ +$primary-color: blue; diff --git a/cli-component/skeleton/package.json b/cli-component/skeleton/package.json deleted file mode 100644 index dcc13ab..0000000 --- a/cli-component/skeleton/package.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "name": "web-boost-skeleton", - "version": "0.0.1", - "private": true, - "scripts": { - "start": "node node_modules/.bin/wb-run", - "compile": "node node_modules/.bin/wb-compile" - }, - "devDependencies": { - "web-boost": "^0.0.3" - } -} diff --git a/cli-component/skeleton/package.json.tmpl b/cli-component/skeleton/package.json.tmpl new file mode 100644 index 0000000..5909461 --- /dev/null +++ b/cli-component/skeleton/package.json.tmpl @@ -0,0 +1,12 @@ +{ + "name": "web-boost-skeleton", + "version": "0.0.1", + "private": true, + "scripts": { + "start": "node node_modules/web-boost/bin/run.js", + "compile": "node node_modules/web-boost/bin/compile.js" + }, + "devDependencies": { + "web-boost": "^1.1.0" + } +} diff --git a/cli-component/skeleton/web-boost.json b/cli-component/skeleton/web-boost.json index a913faa..9e422ce 100644 --- a/cli-component/skeleton/web-boost.json +++ b/cli-component/skeleton/web-boost.json @@ -1,4 +1,10 @@ { + "$": { + "common": [ + "styles/bootstrap.min.css", + "styles/base.scss" + ] + }, "routes": { "/": { "view": "index.twig", @@ -11,7 +17,7 @@ "js/index.js" ], "css/index.min.css": [ - "styles/bootstrap.min.css", + "$.common", "styles/index.scss" ] } diff --git a/docs/faq.md b/docs/faq.md index f5d3dc4..9c8200f 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -1,3 +1,9 @@ # Frequently asked questions -To be updated... +* **How do I get the route name from the template?** + + Yes, there is a build-in variable `routeName`, so you can use it in your templates `{{ routeName }}` + +* **Is @import supported in .scss/.sass files?** + + Yes, just provide a path related to "app.assets" directory. Ex: `@import './styles/variables';` diff --git a/docs/structure.md b/docs/structure.md index f78f1de..862ac20 100644 --- a/docs/structure.md +++ b/docs/structure.md @@ -3,43 +3,49 @@ ### App structure ```text -app // Root of your application -├─ assets // Assets source directory -│  ├─ img // Image folder +app/ # Root of your application +├─ assets/ # Source directory for all assets +│  ├─ img/ # Image folder +│  ├─ fonts/ # Fonts folder │  ├─ index.js │  └─ index.scss -├─ views // Templates directory +├─ views/ # Templates directory │  ├─ index.twig │  └─ layout.twig -├─ public // Compiled assets directory -├─ web-boost.json // Web-boost configuration file +├─ public/ # Destination directory for compiled/minimized assets +├─ web-boost.json # Web-boost configuration file └─ package.json ``` -> Image folder is always `img` which is relative to "app.assets" (supported `.jpg` and `.png`) +> Image folder is **always** `img` (**not** `imgs`, `images` etc.) which is relative to "app.assets" (supported `.jpg` and `.png`) ### Configuration file (`web-boost.json`) ```text { - "app": { // Application config object [Optional] - "views": "views", // Templates directory [Optional, default: "views"] - "public": "public", // Compiled assets directory [Optional, default: "public"] - "assets": "assets" // Assets source directory [Optional, default: "assets"] + "$": { # Global config object [Optional] + "common": [ # Common assets block (accessible via "$.common" across config) + "styles/bootstrap.min.css", + "styles/main.scss" + ] }, - "routes": { // A list of web-boost application routes [Required] - "/": { // Route slug [Required] - "view": "index.twig", // View to render (relative to "app.views") [Required] - "vars": { // View variables [Optional, default: {}] + "app": { # Application config object [Optional] + "views": "views", # Templates directory [Optional, default: "views"] + "assets": "assets" # Assets source directory [Optional, default: "assets"] + }, + "routes": { # A list of web-boost application routes [Required] + "/": { # Route slug [Required] + "view": "index.twig", # View to render (relative to "app.views") [Required] + "vars": { # View variables [Optional, default: {}] "name": "John", - "age": "26" // Example: "

{{ age }}-Year-Old {{ name }} liked our repo ;)

" + "age": "26" # Example: "

{{ age }}-Year-Old {{ name }} liked our repo ;)

" }, - "assets": { // Route specific assets [Optional, default: {}] - "js/index.min.js": [ // Assets destination file (relative to "app.public") - "js/main.js", // Assets source files (relative to "app.assets") - "js/index.js" // js/main.js + js/index.js => js/index.min.js + "assets": { # Route specific assets [Optional, default: {}] + "js/index.min.js": [ # Assets destination file (relative to "app.public") + "$.common", # Common assets block == ["styles/bootstrap.min.css", "styles/main.scss"] + "js/index.js" ], "css/index.min.css": [ - "styles/main.scss", // styles/main.scss + styles/index.scss => css/index.min.css + "styles/main.scss", # styles/main.scss + styles/index.scss => css/index.min.css "styles/index.scss" ] } diff --git a/helpers/twig.js b/helpers/twig.js index ec7ef35..5405886 100644 --- a/helpers/twig.js +++ b/helpers/twig.js @@ -1,12 +1,17 @@ 'use strict'; const Twig = require('twig'); +const logger = require('../src/logger'); /** * Twig configuration */ Twig.extendFunction('asset', (asset) => { - return asset.replace(/^@/, '/'); + try { + return asset.replace(/^@/, '/'); + } catch (error) { + logger.error(`'asset()' syntax error: ${error.message}`); + } }); module.exports = Twig; diff --git a/index.js b/index.js index 067ff5d..61b6a36 100644 --- a/index.js +++ b/index.js @@ -4,6 +4,7 @@ const Route = require('./src/route'); const config = require('./src/config'); +const logger = require('./src/logger'); const express = require('express'); const app = express(); @@ -20,6 +21,7 @@ config.init(appPath); app.set('views', config.getPath('app.views')); app.use('/', express.static(config.getPath('app.public'))); app.use('/img', express.static(config.getPath('app.assets', 'img'))); +app.use('/fonts', express.static(config.getPath('app.assets', 'fonts'))); /** * Init web-boost routes @@ -30,7 +32,9 @@ const appRoutes = Object.keys(routes).map(route => new Route(route, routes[route /** * Trigger assets compilation */ -Promise.all(appRoutes.map(route => route.packAssets())).catch(err => { throw err; }); +Promise.all(appRoutes.map(route => route.packAssets())) + .then(() => logger.info('Ok')) + .catch(err => handleError(err)); /** * Init app routes @@ -45,3 +49,15 @@ appRoutes.forEach(route => { * Listen server */ app.listen(port); + +/** + * @param {Error} error + */ +function handleError(error) { + let errorMsg = error.message; + if (error.code === 'ENOENT') { + errorMsg = `${error.path.replace(`${appPath}/`, '@')} does not exist`; + } + + logger.error(errorMsg); +} diff --git a/package.json b/package.json index 41e39c8..4deff5a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "web-boost", - "version": "1.0.0", + "version": "1.1.0", "description": "Static site generator with page-speed optimisations", "main": "index.js", "bin": { diff --git a/src/asset-factory.js b/src/asset-factory.js index 5f8a140..eae9cef 100644 --- a/src/asset-factory.js +++ b/src/asset-factory.js @@ -1,5 +1,6 @@ 'use strict'; +const config = require('./config'); const JsAsset = require('./js-asset'); const ScssAsset = require('./scss-asset'); @@ -9,15 +10,17 @@ class AssetFactory { * @return {*} */ static create(file) { + let assetsPath = config.getPath('app.assets'); let type = file.getExt(); switch (type) { - case '.sass': case '.less': throw Error(`'${type}' is not supported`); + case '.sass': + return new ScssAsset(file, { includePaths: [assetsPath], indentedSyntax: true }); case '.scss': case '.css': - return new ScssAsset(file); + return new ScssAsset(file, { includePaths: [assetsPath] }); case '.js': return new JsAsset(file); default: diff --git a/src/file.js b/src/file.js index 963e4f9..197c4cd 100644 --- a/src/file.js +++ b/src/file.js @@ -73,7 +73,7 @@ class File { */ getLocalContent() { return new Promise((resolve, reject) => { - fse.readFile(this.fullPath(), null, (err, data) => { + fse.readFile(this.fullPath(), (err, data) => { return err ? reject(err) : resolve(data); }); }); diff --git a/src/logger.js b/src/logger.js new file mode 100644 index 0000000..d1fa937 --- /dev/null +++ b/src/logger.js @@ -0,0 +1,71 @@ +'use strict'; + +class Logger { + /** + * @param message + */ + static log(message) { + Logger._log(Logger.LOG, message); + } + + /** + * @param message + */ + static info(message) { + Logger._log(Logger.INFO, message); + } + + /** + * @param {String} message + */ + static error(message) { + Logger._log(Logger.ERROR, message); + } + + /** + * @param {String} type + * @param {String} message + * @private + */ + static _log(type, message) { + const time = `[${(new Date()).toISOString()}]`; + + switch (type) { + case Logger.LOG: + console.log('✅', time, message); + break; + case Logger.INFO: + console.log('💡', time, message); + break; + case Logger.ERROR: + console.log('❌', time, message); + break; + } + } + + /** + * @returns {String} + * @constructor + */ + static get LOG() { + return 'log'; + } + + /** + * @returns {String} + * @constructor + */ + static get INFO() { + return 'info'; + } + + /** + * @returns {String} + * @constructor + */ + static get ERROR() { + return 'error'; + } +} + +module.exports = Logger; diff --git a/src/route.js b/src/route.js index 81a132d..b329f77 100644 --- a/src/route.js +++ b/src/route.js @@ -13,8 +13,8 @@ class Route { constructor(path, options) { this._path = path; this._view = options.view; - this._vars = options.vars || {}; this._assets = options.assets || {}; + this._vars = Object.assign({ routeName: path }, options.vars); } /** @@ -74,10 +74,15 @@ class Route { * @private */ _concatAssetsTo(assets, outAsset) { - let promises = assets.map(assetPath => { - return AssetFactory.create( - new File(config.getPath('app.assets', assetPath)) - ); + let assetsList = []; + assets.forEach(asset => { + assetsList.push(...asset.startsWith('$.') ? config.get(asset) : [asset]); + }); + + let promises = assetsList.map(assetPath => { + const file = new File(config.getPath('app.assets', assetPath)); + + return AssetFactory.create(file); }).map(asset => { return asset.minify().then(content => Promise.resolve(Buffer.from(content, 'utf8'))); }); diff --git a/src/scss-asset.js b/src/scss-asset.js index cf1337d..bda1325 100644 --- a/src/scss-asset.js +++ b/src/scss-asset.js @@ -6,9 +6,12 @@ const AbstractAsset = require('./abstract-asset'); class ScssAsset extends AbstractAsset { /** * @param {File} file + * @param {Object} config */ - constructor(file) { + constructor(file, config = {}) { super(file); + + this._config = config; } /** @@ -24,7 +27,9 @@ class ScssAsset extends AbstractAsset { } return new Promise((resolve, reject) => { - sass.render({ data: styles, outputStyle: 'compressed' }, (err, res) => { + const config = Object.assign({ data: styles, outputStyle: 'compressed' }, this._config); + + sass.render(config, (err, res) => { if (err) { return reject(err); }