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); }