{page.title}
{page.description}
- +diff --git a/.gitignore b/.gitignore
index 9fc1ce65..ee8d6ae4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -10,6 +10,7 @@ lerna-debug.log*
# projects files and directory
dist
+docs
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
diff --git a/.npmignore b/.npmignore
index c619e1ea..0a99329b 100644
--- a/.npmignore
+++ b/.npmignore
@@ -3,6 +3,7 @@ node_modules
.npmignore
.DS_STORE
coverage
+docs
test
.gitignore
website
diff --git a/core/Attribute.spec.js b/core/Attribute.spec.js
new file mode 100644
index 00000000..d6bc14b0
--- /dev/null
+++ b/core/Attribute.spec.js
@@ -0,0 +1,12 @@
+const {Attribute} = require('./Attribute');
+
+describe('Attribute', () => {
+ it('should create', () => {
+ const attr = new Attribute();
+
+ expect(attr.execute).toBe(false);
+ expect(attr.bind).toBe(false);
+ expect(attr.process('val')).toBe('val');
+ expect(attr.render('val', {})).toEqual({});
+ });
+});
\ No newline at end of file
diff --git a/core/PartialFile.js b/core/PartialFile.js
index f4605ec1..a0ed795b 100644
--- a/core/PartialFile.js
+++ b/core/PartialFile.js
@@ -26,7 +26,7 @@ class PartialFile extends File {
render(contextData = {}) {
const parsedHTML = parse(replaceSpecialCharactersInHTML(this.content), {
- comment: this.options.env === 'development'
+ comment: true
});
parsedHTML.context = contextData;
const partialNode = new HTMLNode(parsedHTML, this.options);
diff --git a/core/builder/utils/collect-and-update-node-source-link.js b/core/builder/utils/collect-and-update-node-source-link.js
index dab4ac66..fee0446d 100644
--- a/core/builder/utils/collect-and-update-node-source-link.js
+++ b/core/builder/utils/collect-and-update-node-source-link.js
@@ -1,6 +1,7 @@
const {getFileSourceHashedDestPath} = require("./get-file-source-hashed-dest-path");
const {uniqueAlphaNumericId} = require("../../utils/unique-alpha-numeric-id");
const path = require('path');
+const validUrl = require("valid-url");
function collectAndUpdateNodeSourceLink(node, pageFile, resources, fileDirPath) {
let srcPath = '';
@@ -40,15 +41,7 @@ function collectAndUpdateNodeSourceLink(node, pageFile, resources, fileDirPath)
default:
}
- let isURL = false;
-
- try {
- new URL(srcPath);
- isURL = true;
- } catch (e) {
- }
-
- if (srcPath && typeof srcPath === 'string' && !isURL) {
+ if (srcPath && typeof srcPath === 'string' && !validUrl.isUri(srcPath)) {
if (/node_modules\//.test(srcPath)) {
srcPath = srcPath.replace(/^.+(?=node_modules)/, `${process.cwd()}/`);
} else {
diff --git a/core/engine/extract-partial-and-page-routes.js b/core/engine/extract-partial-and-page-routes.js
deleted file mode 100644
index abd5ae42..00000000
--- a/core/engine/extract-partial-and-page-routes.js
+++ /dev/null
@@ -1,30 +0,0 @@
-const {PartialFile} = require("../PartialFile");
-
-function extractPartialAndPageRoutes(files, pagesDirectoryPath) {
- const partialFileNames = new Set();
-
- return files.reduce((acc, file) => {
- if (file.item.startsWith('_')) {
- if (partialFileNames.has(file.item)) {
- throw new Error(`You have duplicated partial "${file.item}". Partial file names must be unique.`);
- }
-
- acc.partials.push(new PartialFile(file.itemPath, pagesDirectoryPath));
- partialFileNames.add(file.item);
- } else {
- const template = `${file.itemRelativePath.replace(file.ext, '')}`.slice(1);
-
- if (file.itemRelativePath.endsWith('index.html')) {
- acc.pagesRoutes[file.itemRelativePath.replace('index.html', '')] = template;
- } else {
- acc.pagesRoutes[file.itemRelativePath.replace(file.ext, '')] = template;
- }
-
- acc.pagesRoutes[file.itemRelativePath.replace(/\/$/, '')] = template;
- }
-
- return acc;
- }, {partials: [], pagesRoutes: {}});
-}
-
-module.exports.extractPartialAndPageRoutes = extractPartialAndPageRoutes;
\ No newline at end of file
diff --git a/core/engine/index.js b/core/engine/index.js
index 1638d852..a7b9e66a 100644
--- a/core/engine/index.js
+++ b/core/engine/index.js
@@ -1,11 +1,12 @@
const fs = require('fs');
const path = require('path');
const express = require("express");
+const {PartialFile} = require("../PartialFile");
const {pageAndResourcesMiddleware} = require("./page-and-resources-middleware");
-const {extractPartialAndPageRoutes} = require("./extract-partial-and-page-routes");
const {transform} = require('../transform');
const {getDirectoryFilesDetail} = require('../utils/getDirectoryFilesDetail');
const {File} = require('../File');
+const validUrl = require('valid-url');
const defaultOptions = {
staticData: {},
@@ -40,17 +41,39 @@ const engine = (app, pagesDirectoryPath, opt = defaultOptions) => {
console.warn('HTML+ swapped "ENV" to development because the value provided is not a supported one.')
}
- getDirectoryFilesDetail(pagesDirectoryPath, 'html')
- .then(files => {
- const {partials, pagesRoutes} = extractPartialAndPageRoutes(files, pagesDirectoryPath);
+ const partials = [];
+ const pagesRoutes = {};
+
+ return getDirectoryFilesDetail(pagesDirectoryPath, filePath => {
+ if (filePath.endsWith('.html')) {
+ const fileName = path.basename(filePath);
+ if (fileName.startsWith('_')) {
+ partials.push(new PartialFile(filePath, pagesDirectoryPath));
+ } else {
+ filePath = filePath.replace(pagesDirectoryPath, '');
+ const template = `${filePath.replace('.html', '')}`.slice(1);
+
+ if (filePath.endsWith('index.html')) {
+ pagesRoutes[filePath.replace('index.html', '')] = template;
+ } else {
+ pagesRoutes[filePath.replace('.html', '')] = template;
+ }
+
+ pagesRoutes[filePath.replace(/\/$/, '')] = template;
+ }
+ }
+
+ return false;
+ })
+ .then(() => {
app.engine('html', (filePath, {settings, _locals, cache, ...context}, callback) => {
const fileName = path.basename(filePath);
-
+
if (fileName.startsWith('_')) {
callback(new Error(`Cannot render partial(${fileName}) file as page. Partial files can only be included.`));
}
-
+
fs.readFile(filePath, (err, content) => {
if (err) return callback(err);
const file = new File(filePath, settings.views);
@@ -63,26 +86,19 @@ const engine = (app, pagesDirectoryPath, opt = defaultOptions) => {
customTags: opt.customTags,
customAttributes: opt.customAttributes,
partialFiles: partials,
- onBeforeRender: (node, file) => {
+ onBeforeRender: (node, nodeFile) => {
let attrName = '';
-
+
if (node.tagName === 'link') {
attrName = 'href';
} else if (node.tagName === 'script') {
attrName = 'src';
}
-
+
const srcPath = node.attributes[attrName];
- let isURL = false;
-
- try {
- new URL(srcPath);
- isURL = true;
- } catch (e) {
- }
-
- if (srcPath && !isURL) {
- const resourceFullPath = path.resolve(file.fileDirectoryPath, srcPath);
+
+ if (srcPath && !validUrl.isUri(srcPath)) {
+ const resourceFullPath = path.resolve(nodeFile.fileDirectoryPath, srcPath);
if (resourceFullPath.startsWith(pagesDirectoryPath)) {
node.setAttribute(attrName, resourceFullPath.replace(pagesDirectoryPath, ''))
@@ -90,10 +106,10 @@ const engine = (app, pagesDirectoryPath, opt = defaultOptions) => {
}
}
})
-
+
callback(null, result);
} catch (e) {
- console.log(e.message);
+ console.error(e.message);
const cleanMsg = e.message
.replace(/\[\d+m/g, '')
.replace(/(>|<)/g, m => m === '<' ? '<' : '>');
@@ -101,17 +117,17 @@ const engine = (app, pagesDirectoryPath, opt = defaultOptions) => {
}
})
});
-
+
app.set('views', pagesDirectoryPath);
app.set('view engine', 'html');
-
+
app.use(pageAndResourcesMiddleware(
pagesRoutes,
pagesDirectoryPath,
opt
));
app.use(express.static(pagesDirectoryPath))
-
+
console.log('HTML+ engine is ready');
})
};
diff --git a/core/engine/page-and-resources-middleware.js b/core/engine/page-and-resources-middleware.js
index 9a501419..c4aacc0a 100644
--- a/core/engine/page-and-resources-middleware.js
+++ b/core/engine/page-and-resources-middleware.js
@@ -1,5 +1,4 @@
const path = require("path");
-const {readFileContent} = require("../utils/readFileContent");
const {transform: transformResource} = require('../transformers');
const {File} = require('../File');
@@ -10,10 +9,13 @@ const sourcesExtensions = new Set([
'.less',
'.styl',
'.js',
+ '.mjs',
+ '.cjs',
'.ts',
'.tsx',
'.jsx',
-])
+]);
+const cache = {};
function pageAndResourcesMiddleware(pagesRoutes, pagesDirectoryPath, {env, onPageRequest}) {
return async (req, res, next) => {
@@ -21,67 +23,65 @@ function pageAndResourcesMiddleware(pagesRoutes, pagesDirectoryPath, {env, onPag
if (ext && sourcesExtensions.has(ext)) {
let content = '';
+ let contentType = 'text/css';
let resourcePath = '';
+ let file = null;
if (/node_modules/.test(req.path)) {
resourcePath = path.join(process.cwd(), req.path);
-
- try {
- content = readFileContent(resourcePath);
-
- if (ext === '.css') {
- res.setHeader('Content-Type', 'text/css');
- } else if(ext === '.js' || ext === '.mjs') {
- res.setHeader('Content-Type', 'application/javascript');
- }
-
- return res.send(content);
- } catch(e) {
- console.error(`Failed to load page resource "${req.path}"`, e);
- return res.sendStatus(404);
- }
+ } else {
+ resourcePath = path.join(pagesDirectoryPath, req.path);
}
- resourcePath = path.join(pagesDirectoryPath, req.path);
+ if (env === 'production' && cache[resourcePath]) {
+ res.setHeader('Content-Type', cache[resourcePath].contentType);
+ return res.send(cache[resourcePath].content);
+ }
+ file = new File(resourcePath, pagesDirectoryPath);
+
try {
- const file = new File(resourcePath, pagesDirectoryPath);
-
switch (ext) {
case '.scss':
case '.sass':
- content = await transformResource.sass({file, env});
- res.setHeader('Content-Type', 'text/css');
+ content = await transformResource.sass({file});
+ content = (await transformResource.css(content, {file, env})).content;
break;
case '.less':
- content = await transformResource.less({file, env});
- res.setHeader('Content-Type', 'text/css');
+ content = await transformResource.less({file});
+ content = (await transformResource.css(content, {file, env})).content;
break;
case '.styl':
- content = await transformResource.stylus({file, env});
- res.setHeader('Content-Type', 'text/css');
+ content = await transformResource.stylus({file});
+ content = (await transformResource.css(content, {file, env})).content;
break;
case '.css':
- content = await transformResource.css({file, env});
- res.setHeader('Content-Type', 'text/css');
+ content = (await transformResource.css({file, env})).content;
break;
case '.js':
case '.jsx':
case '.ts':
case '.tsx':
case '.mjs':
+ case '.cjs':
const result = await transformResource.js({file, env});
content = result.content;
- res.setHeader('Content-Type', 'application/javascript');
+ contentType = 'application/javascript';
break;
}
+
+ if (env === 'production') {
+ cache[resourcePath] = {content, contentType}
+ }
+
+ res.setHeader('Content-Type', contentType);
return res.send(content);
} catch(e) {
- console.error(`Failed to load page resource "${req.path}"`, e);
+ console.error(`Failed to load style/script content "${req.path}"`, e);
return res.sendStatus(404);
}
- } else {
+ } else if(!ext || ext === '.html') {
const template = pagesRoutes[req.path] ?? pagesRoutes[`${req.path}/`] ?? pagesRoutes['/404'];
if (template) {
diff --git a/core/parser/Comment.js b/core/parser/Comment.js
index 0ec93197..978fa5b1 100644
--- a/core/parser/Comment.js
+++ b/core/parser/Comment.js
@@ -1,6 +1,10 @@
const {Text} = require('./Text');
class Comment extends Text {
+ constructor(value) {
+ super(value);
+ }
+
get type() {
return 'comment';
}
diff --git a/core/parser/Comment.spec.js b/core/parser/Comment.spec.js
new file mode 100644
index 00000000..dc1744e8
--- /dev/null
+++ b/core/parser/Comment.spec.js
@@ -0,0 +1,11 @@
+const {Comment} = require('./Comment');
+
+describe('Comment', () => {
+ it('should create no data comment', () => {
+ const txt = new Comment('some {value}');
+
+ expect(txt.type).toEqual('comment')
+ expect(txt.value).toEqual('some {value}')
+ expect(txt.toString()).toEqual('')
+ });
+});
\ No newline at end of file
diff --git a/core/parser/HTMLNode.js b/core/parser/HTMLNode.js
index 890c8e4a..bc698f8b 100644
--- a/core/parser/HTMLNode.js
+++ b/core/parser/HTMLNode.js
@@ -32,7 +32,7 @@ class HTMLNode {
options = {...defaultOptions, ...options}
this.#node = typeof htmlString === 'string'
? parse(replaceSpecialCharactersInHTML(htmlString), {
- comment: options.env === 'development'
+ comment: true
})
: htmlString;
this.#node.context = {...this.#node.context, ...options.context};
@@ -90,7 +90,7 @@ class HTMLNode {
? composeTagString(this, this.#node.innerHTML)
: `
tag
' + } + } + class Cls extends Attribute { + render(val, node) { + node.setAttribute('class', val) + return node; + } + } + + expect(transform({ + file: { + load() {}, + content: 'attr
' + }, + customTags: [Tag], + customAttributes: [Cls], + })) + .toEqual('tag
attr
') + }); + }); }); \ No newline at end of file diff --git a/core/transformers/css.transformer.js b/core/transformers/css.transformer.js index a9450160..38c87ea8 100644 --- a/core/transformers/css.transformer.js +++ b/core/transformers/css.transformer.js @@ -73,7 +73,6 @@ async function cssTransformer(content, opt = defaultOptions) { postcssPresetEnv({ stage: 0 }), - comments({removeAll: true}), ...opt.plugins ]; @@ -88,6 +87,7 @@ async function cssTransformer(content, opt = defaultOptions) { if (opt.env === 'production') { post = postcss([ ...plugins, + comments({removeAll: true}), purgeCSS({ content: [ `${opt.destPath || opt.file.srcDirectoryPath}/**/*.html` diff --git a/core/transformers/css.transformer.spec.js b/core/transformers/css.transformer.spec.js index aa2a3657..d59f1ead 100644 --- a/core/transformers/css.transformer.spec.js +++ b/core/transformers/css.transformer.spec.js @@ -40,7 +40,7 @@ describe('cssTransformer', () => { }); it('should setup for production minified, purged with image resolved', () => { - // expect.assertions(3); + expect.assertions(4); return cssTransformer(data.css, { env: 'production', @@ -59,7 +59,13 @@ describe('cssTransformer', () => { it('should import', () => { return cssTransformer('@import "./__box.css";', {file}).then(res => { - expect(res.content).toEqual('*{box-sizing:border-box;}'); + expect(res.content).toEqual('* {box-sizing: border-box;}'); + }) + }); + + it('should take options as first arg', () => { + return cssTransformer({file}).then(res => { + expect(res.content.replace(/\s/g, '')).toEqual(data.cssResult.replace(/\s/g, '')); }) }); }); @@ -71,4 +77,8 @@ describe('cssTransformer', () => { expect(res).toEqual(''); }) }); + + it('should throw error if no file provided', () => { + return expect(cssTransformer({})).rejects.toThrowError('If no string content is provided, the "file" option must be provided.') + }); }); \ No newline at end of file diff --git a/core/transformers/js.transformer.spec.js b/core/transformers/js.transformer.spec.js index bd086901..da0a25dd 100644 --- a/core/transformers/js.transformer.spec.js +++ b/core/transformers/js.transformer.spec.js @@ -129,4 +129,8 @@ describe('jsTransformer', () => { }); }); + it('should throw error if no file provided', () => { + return expect(jsTransformer({})).rejects.toThrowError('If no string content is provided, the "file" option must be provided.') + }); + }) \ No newline at end of file diff --git a/core/transformers/less.transformer.spec.js b/core/transformers/less.transformer.spec.js index b606a7c3..4fd100cd 100644 --- a/core/transformers/less.transformer.spec.js +++ b/core/transformers/less.transformer.spec.js @@ -15,19 +15,19 @@ describe('lessTransformer', () => { beforeAll(async () => { await exec(`echo '${data.less}' >> ${cssFile}`); }) - + afterAll(async () => { await exec(`rm ${cssFile}`); }); it('from string', () => { expect.assertions(1); - + return lessTransformer(data.less).then(res => { expect(res.replace(/\s/g, '')).toEqual(data.lessResult.replace(/\s/g, '')); }) }); - + it('from import', () => { expect.assertions(1); @@ -39,7 +39,7 @@ describe('lessTransformer', () => { it('should setup for production', () => { expect.assertions(1); - + return lessTransformer(data.less, {env: 'production'}).then(res => { expect(res.replace(/\s/g, '')).toEqual('a,.link{color:#428bca;}a:hover{color:#3071a9;}.widget{color:#fff;background:#428bca;}.banner{font-weight:bold;line-height:40px;margin:0auto;}.lazy-eval{width:9%;}.widget{color:#efefef;background-color:#efefef;}.button-ok{background-image:url("ok.png");}.button-cancel{background-image:url("cancel.png");}.button-custom{background-image:url("custom.png");}.link+.link{color:red;}.link.link{color:green;}.link.link{color:blue;}.link,.linkish{color:cyan;}.header.menu{border-radius:5px;}.no-borderradius.header.menu{background-image:url("images/button-background.png");}navul{background:blue;}.inline,navul{color:red;}.myclass{box-shadow:inset0010px#555,0020pxblack;}.a,#b{color:red;}.mixin-class{color:red;}.mixin-id{color:red;}.button{-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;}button{color:white;}'); }) @@ -75,4 +75,8 @@ describe('lessTransformer', () => { }) }); }); + + it('should throw error if no file provided', () => { + return expect(lessTransformer({})).rejects.toThrowError('If no string content is provided, the "file" option must be provided.') + }); }); \ No newline at end of file diff --git a/core/transformers/sass.transformer.spec.js b/core/transformers/sass.transformer.spec.js index 475748c5..cb39bb1c 100644 --- a/core/transformers/sass.transformer.spec.js +++ b/core/transformers/sass.transformer.spec.js @@ -83,4 +83,8 @@ describe('sassTransformer', () => { }); }); + it('should throw error if no file provided', () => { + return expect(sassTransformer({})).rejects.toThrowError('If no string content is provided, the "file" option must be provided.') + }); + }); \ No newline at end of file diff --git a/core/transformers/stylus.transformer.spec.js b/core/transformers/stylus.transformer.spec.js index 3c6a4cc7..c77c6e0e 100644 --- a/core/transformers/stylus.transformer.spec.js +++ b/core/transformers/stylus.transformer.spec.js @@ -67,4 +67,8 @@ describe('stylusTransformer', () => { }) }); }); + + it('should throw error if no file provided', () => { + return expect(stylusTransformer({})).rejects.toThrowError('If no string content is provided, the "file" option must be provided.') + }); }); \ No newline at end of file diff --git a/core/utils/define-getter.js b/core/utils/define-getter.js deleted file mode 100644 index a9d9d49e..00000000 --- a/core/utils/define-getter.js +++ /dev/null @@ -1,9 +0,0 @@ -const defineGetter = (obj, name, value) => { - Object.defineProperty(obj, name, { - get() { - return typeof value === 'function' ? value() : value; - } - }) -} - -module.exports.defineGetter = defineGetter; diff --git a/core/utils/define-getter.spec.js b/core/utils/define-getter.spec.js deleted file mode 100644 index 949b2dea..00000000 --- a/core/utils/define-getter.spec.js +++ /dev/null @@ -1,17 +0,0 @@ -const {defineGetter} = require('./define-getter'); - -describe('defineGetter', () => { - const obj = Object.create(null); - - it('should define getter that calls a function to get the value', () => { - defineGetter(obj, 'test', () => 23); - - expect(obj.test).toEqual(23); - }); - - it('should define getter with static value', () => { - defineGetter(obj, 'sample', 45); - - expect(obj.sample).toEqual(45); - }); -}); \ No newline at end of file diff --git a/core/utils/getDirectoryFilesDetail.spec.js b/core/utils/getDirectoryFilesDetail.spec.js index d7fe1286..9871a14f 100644 --- a/core/utils/getDirectoryFilesDetail.spec.js +++ b/core/utils/getDirectoryFilesDetail.spec.js @@ -1,9 +1,18 @@ const {getDirectoryFilesDetail} = require('./getDirectoryFilesDetail'); const path = require('path'); +const {mkdir, rmdir} = require('fs/promises'); describe('getDirectoryFilesDetail', () => { const dir = path.resolve(__dirname); + beforeEach(async () => { + await mkdir(path.join(__dirname, 'subDir')) + }) + + afterEach(async () => { + await rmdir(path.join(__dirname, 'subDir')) + }) + describe('should read all the test files in this directory', () => { it('with extension', () => { expect.assertions(3); diff --git a/core/utils/turn-camel-or-pascal-to-kebab-casing.js b/core/utils/turn-camel-or-pascal-to-kebab-casing.js index 92ad998a..6cea7b07 100644 --- a/core/utils/turn-camel-or-pascal-to-kebab-casing.js +++ b/core/utils/turn-camel-or-pascal-to-kebab-casing.js @@ -1,5 +1,5 @@ const turnCamelOrPascalToKebabCasing = name => { - return name.match(/(^|[A-Z])[^A-Z]+/g).map(p => p.toLowerCase()).join('-') + return name.match(/(?:[a-zA-Z]|[A-Z]+)[a-z]*/g).map(p => p.toLowerCase()).join('-') } module.exports.turnCamelOrPascalToKebabCasing = turnCamelOrPascalToKebabCasing; \ No newline at end of file diff --git a/core/utils/turn-camel-or-pascal-to-kebab-casing.spec.js b/core/utils/turn-camel-or-pascal-to-kebab-casing.spec.js index f0a345c5..c085fd8c 100644 --- a/core/utils/turn-camel-or-pascal-to-kebab-casing.spec.js +++ b/core/utils/turn-camel-or-pascal-to-kebab-casing.spec.js @@ -11,5 +11,7 @@ describe('turnCamelOrPascalToKebabCasing', () => { expect(turnCamelOrPascalToKebabCasing('Some')).toEqual('some') expect(turnCamelOrPascalToKebabCasing('SomeName')).toEqual('some-name') expect(turnCamelOrPascalToKebabCasing('SomeNameTest')).toEqual('some-name-test') + expect(turnCamelOrPascalToKebabCasing('SomeNameTestDST')).toEqual('some-name-test-d-s-t') + expect(turnCamelOrPascalToKebabCasing('DSTTag')).toEqual('d-s-t-tag') }); }); \ No newline at end of file diff --git a/core/utils/unique-alpha-numeric-id.spec.js b/core/utils/unique-alpha-numeric-id.spec.js new file mode 100644 index 00000000..8e8907e4 --- /dev/null +++ b/core/utils/unique-alpha-numeric-id.spec.js @@ -0,0 +1,9 @@ +const {uniqueAlphaNumericId} = require('./unique-alpha-numeric-id'); + +describe('uniqueAlphaNumericId', () => { + it('should create unique sized ids', () => { + expect(uniqueAlphaNumericId()).toMatch(/[a-zA-Z0-9]{24}/) + expect(uniqueAlphaNumericId(6)).toMatch(/[a-zA-Z0-9]{6}/) + expect(uniqueAlphaNumericId(0)).toMatch(/[a-zA-Z0-9]{0}/) + }); +}); \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 401a86d9..833075f8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@beforesemicolon/html-plus", - "version": "0.3.0", + "version": "0.4.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -1284,21 +1284,6 @@ "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=", "dev": true }, - "array-union": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", - "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", - "dev": true, - "requires": { - "array-uniq": "^1.0.1" - } - }, - "array-uniq": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", - "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", - "dev": true - }, "array-unique": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", @@ -1324,15 +1309,6 @@ "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", "dev": true }, - "async": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", - "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", - "dev": true, - "requires": { - "lodash": "^4.17.14" - } - }, "async-foreach": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/async-foreach/-/async-foreach-0.1.3.tgz", @@ -2180,12 +2156,6 @@ "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==" }, - "commondir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", - "dev": true - }, "component-emitter": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", @@ -3280,12 +3250,6 @@ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.757.tgz", "integrity": "sha512-kP0ooyrvavDC+Y9UG6G/pUVxfRNM2VTJwtLQLvgsJeyf1V+7shMCb68Wj0/TETmfx8dWv9pToGkVT39udE87wQ==" }, - "email-addresses": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/email-addresses/-/email-addresses-3.1.0.tgz", - "integrity": "sha512-k0/r7GrWVL32kZlGwfPNgB2Y/mMXVTq/decgLczm/j34whdaspNrZO8CnXPf1laaHxI6ptUlsnAxN+UAPw+fzg==", - "dev": true - }, "emittery": { "version": "0.7.2", "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.7.2.tgz", @@ -3718,23 +3682,6 @@ "bser": "2.1.1" } }, - "filename-reserved-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-2.0.0.tgz", - "integrity": "sha1-q/c9+rc10EVECr/qLZHzieu/oik=", - "dev": true - }, - "filenamify": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/filenamify/-/filenamify-4.3.0.tgz", - "integrity": "sha512-hcFKyUG57yWGAzu1CMt/dPzYZuv+jAJUT85bL8mrXvNe6hWj6yEHEc4EdcgiA6Z3oi1/9wXJdZPXF2dZNgwgOg==", - "dev": true, - "requires": { - "filename-reserved-regex": "^2.0.0", - "strip-outer": "^1.0.1", - "trim-repeated": "^1.0.0" - } - }, "fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -3776,34 +3723,6 @@ } } }, - "find-cache-dir": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.1.tgz", - "integrity": "sha512-t2GDMt3oGC/v+BMwzmllWDuJF/xcDtE5j/fCGbqDD7OLuJkj0cfh1YSA5VKPvwMeLFLNDBkwOKZ2X85jGLVftQ==", - "dev": true, - "requires": { - "commondir": "^1.0.1", - "make-dir": "^3.0.2", - "pkg-dir": "^4.1.0" - }, - "dependencies": { - "make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dev": true, - "requires": { - "semver": "^6.0.0" - } - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - } - } - }, "find-up": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", @@ -3860,17 +3779,6 @@ "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", "dev": true }, - "fs-extra": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", - "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", - "dev": true, - "requires": { - "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - } - }, "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -3977,29 +3885,6 @@ "assert-plus": "^1.0.0" } }, - "gh-pages": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/gh-pages/-/gh-pages-3.2.3.tgz", - "integrity": "sha512-jA1PbapQ1jqzacECfjUaO9gV8uBgU6XNMV0oXLtfCX3haGLe5Atq8BxlrADhbD6/UdG9j6tZLWAkAybndOXTJg==", - "dev": true, - "requires": { - "async": "^2.6.1", - "commander": "^2.18.0", - "email-addresses": "^3.0.1", - "filenamify": "^4.3.0", - "find-cache-dir": "^3.3.1", - "fs-extra": "^8.1.0", - "globby": "^6.1.0" - }, - "dependencies": { - "commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true - } - } - }, "glob": { "version": "7.1.7", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", @@ -4037,27 +3922,6 @@ "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", "dev": true }, - "globby": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", - "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", - "dev": true, - "requires": { - "array-union": "^1.0.1", - "glob": "^7.0.3", - "object-assign": "^4.0.1", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" - }, - "dependencies": { - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true - } - } - }, "globule": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/globule/-/globule-1.3.2.tgz", @@ -5918,15 +5782,6 @@ "minimist": "^1.2.5" } }, - "jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.6" - } - }, "jsprim": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", @@ -12668,15 +12523,6 @@ "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", "dev": true }, - "strip-outer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/strip-outer/-/strip-outer-1.0.1.tgz", - "integrity": "sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg==", - "dev": true, - "requires": { - "escape-string-regexp": "^1.0.2" - } - }, "stylehacks": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-4.0.3.tgz", @@ -13054,15 +12900,6 @@ "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz", "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=" }, - "trim-repeated": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/trim-repeated/-/trim-repeated-1.0.0.tgz", - "integrity": "sha1-42RqLqTokTEr9+rObPsFOAvAHCE=", - "dev": true, - "requires": { - "escape-string-regexp": "^1.0.2" - } - }, "true-case-path": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/true-case-path/-/true-case-path-1.0.3.tgz", @@ -13396,6 +13233,11 @@ } } }, + "valid-url": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/valid-url/-/valid-url-1.0.9.tgz", + "integrity": "sha1-HBRHm0DxOXp1eC8RXkCGRHQzogA=" + }, "validate-npm-package-license": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", diff --git a/package.json b/package.json index 1bcba612..5bea6d37 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@beforesemicolon/html-plus", - "version": "0.3.0", + "version": "0.4.0", "description": "HTML Template Engine/Language", "main": "dist/index.js", "files": [ @@ -37,7 +37,6 @@ "esbuild": "^0.11.1", "html-minifier": "^4.0.0", "less": "^4.1.1", - "node": "^14.17.1", "node-html-parser": "^2.1.0", "node-sass": "^4.14.1", "postcss": "^8.2.9", @@ -45,7 +44,8 @@ "postcss-import": "^14.0.2", "postcss-preset-env": "^6.7.0", "postcss-url": "^10.1.3", - "stylus": "^0.54.8" + "stylus": "^0.54.8", + "valid-url": "^1.0.9" }, "devDependencies": { "@types/express": "^4.17.12", @@ -53,6 +53,7 @@ "express": "^4.17.1", "highlight.js": "^11.0.1", "jest": "^26.6.3", + "node": "^14.17.1", "nodemon": "^2.0.7", "ts-node": "^10.0.0", "tslib": "^2.3.0", diff --git a/website/build.js b/website/build.js index f3a2831a..5354dfdd 100644 --- a/website/build.js +++ b/website/build.js @@ -5,24 +5,9 @@ const documentationPage = require('./data/documentation.page'); const learnPage = require('./data/learn.page'); const site = require('./data/site.json'); const packageJSON = require('./../package.json'); +const {collectPaths} = require("./data/collect-paths"); const {CodeSnippet} = require("./tags/code-snippet"); -const collectPaths = list => { - const paths = new Set(); - - for (let item of list) { - if (item.hasOwnProperty('path')) { - paths.add(item.path); - - if (item.list && item.list.length) { - Array.from(collectPaths(item.list), (p) => paths.add(p)) - } - } - } - - return paths; -} - const paths = collectPaths(documentationPage.menu.list); build({ diff --git a/website/data/collect-paths.js b/website/data/collect-paths.js new file mode 100644 index 00000000..137da858 --- /dev/null +++ b/website/data/collect-paths.js @@ -0,0 +1,17 @@ +const collectPaths = list => { + const paths = new Set(); + + for (let item of list) { + if (item.hasOwnProperty('path')) { + paths.add(item.path); + + if (item.list && item.list.length) { + Array.from(collectPaths(item.list), (p) => paths.add(p)) + } + } + } + + return paths; +} + +module.exports.collectPaths = collectPaths; \ No newline at end of file diff --git a/website/data/documentation.page.js b/website/data/documentation.page.js index 32d7d5da..c866fb95 100644 --- a/website/data/documentation.page.js +++ b/website/data/documentation.page.js @@ -6,6 +6,7 @@ const templatingLink = '/documentation/templating'; const advTemplatingLink = '/documentation/advanced-templating'; const stylingLink = '/documentation/styling'; const scriptingLink = '/documentation/scripting'; +const assetsLink = '/documentation/static-assets'; const buildLink = '/documentation/build'; const faqLink = '/documentation/faq'; const apiReferenceLink = '/documentation/api-reference'; @@ -179,6 +180,11 @@ const data = { path: scriptingLink, partial: 'scripting', }, + { + title: "Static Assets", + path: assetsLink, + partial: 'static-assets', + }, { title: "Build Project", path: buildLink, diff --git a/website/data/home.page.js b/website/data/home.page.js index ab8d92c0..1c166e46 100644 --- a/website/data/home.page.js +++ b/website/data/home.page.js @@ -22,8 +22,8 @@ module.exports = { list: [ { title: "It is just HTML", - description: "There is no weird syntax. You already know this language.", - path: "/documentation", + description: "The syntax is of HTML so you already know this language.", + path: "/documentation/getting-started", partial: "just-html" }, { @@ -41,13 +41,13 @@ module.exports = { { title: "Write Future CSS", description: "HTML+ engine allows you to write modern CSS that works in any browser.", - path: "/documentation/styling", + path: "/documentation/styling/modern-css", partial: "future-css" }, { title: "Powerful data binding and data contextualization", description: "It allows you to reference data in separate files and create data context for specific parts of the page.", - path: "/documentation/data", + path: "/documentation/template-data-binding", partial: "data-binding" }, { @@ -59,13 +59,13 @@ module.exports = { { title: "Static and Dynamic SSR website", description: "Create faster website by rendering on the Server with static or dynamic pages.", - path: "/documentation", + path: "/documentation/routes", partial: "" }, { title: "A site builder", description: "HTML+ builder takes care of exporting production ready site files to be hosted anywhere.", - path: "/documentation", + path: "/documentation/build", partial: "" }, { @@ -77,7 +77,7 @@ module.exports = { { title: "Generates optimal code for production", description: "The site builder will only export optimal and needed CSS, Javascript and HTML for production.", - path: "/documentation", + path: "/documentation/build", partial: "" } ] diff --git a/website/pages/_partials/docs/_advanced-templating.html b/website/pages/_partials/docs/_advanced-templating.html index a8b5c04d..1371913d 100644 --- a/website/pages/_partials/docs/_advanced-templating.html +++ b/website/pages/_partials/docs/_advanced-templating.html @@ -1,8 +1,8 @@ -There a logic that can be too complex to be put on template.
-There are also cases where you keep repeat complex partials everywhere or you may need to use a third party +
There are logic that can be too complex to be put on template.
+There are also cases where you keep repeating complex partials everywhere or you may need to use a third party module to do something specific in your template.
HTML+ allows you to create your own tags and attributes using javascript.
There, you can import third party modules, make expensive calculations and manipulation to then render the markup as you want it be be seen on the page.
-With custom tags and attributes as well as powerful partials, what you can be is pretty much limitless.
+With custom tags and attributes as well as powerful partials, what you can do is pretty much limitless.
HTML+ comes with a static site builder for when you are ready to take your site life.
+HTML+ comes with a static site builder for when you are ready to take your site life to production.
With a simple API, most of the work is done under the hood to ensure your website will work as you intend it to and these are things like:
The builder has only couple of options which share a lot with the engine options already and it is more -like a alternative options for a live handling the engine does.
+like an alternative options for a live handling the engine does.This builder is not yet compatible with other popular site builders but its small size and simple API -it can be just what you need for your project.
+may be just what you need for your project.There are some attributes you may want to be include only under certain conditions and that is what the #attr attribute is for.
-You use comma to separate the attribute name, value and the condition to include it where the value +
You use comma to separate the attribute name, value and the condition to include it where the value is optional for booleans attributes like "hidden", "disabled", "checked", etc.
For even more control over how these files get processed, consider using the transform API to -compile your SASS, LESS or STYLUS into CSS.
Custom attributes must all be a class that extends the Attribute class. This class must - also contain a render method which is called the value of the attribute and the HTMLNode instance.
-A optional method of you custom attribute is the process method which is called with the value +
Custom attributes must all be a class that may extend the Attribute class. This class must + also contain a render method which is called with the value of the attribute and the HTMLNode instance.
+An optional method of your custom attribute is the process method which is called with the value of the attribute which you can tweak into a different string to be bind or executed before the rendering.
The below WrapWith custom attribute will receive a comma separated name of tags and wrap the tag it is placed on with those.
@@ -45,7 +45,7 @@Attributes dont necessary need to render anything. Your attribute can simply change something about the -node provide like setting attribute or update the node context.
+node provided like setting attribute or update the node context.Your custom tags can also contain specific attributes. These attributes won't be marked with hash symbol(#).
You may define them as a static property on a class or function.
@@ -68,6 +68,20 @@If you use a function it would look like this:
+These custom attributes can be simple objects as well. Not necessary a Attribute instance unless you want to calculate something more complex.
A custom tag can be a class or a function which can optionally contain or return a render function.
This render function must return an HTML string, a node or null
-The below search field class will can be used as search-field tag. That means that how you +
The below search field class will be used as search-field tag. That means that how you name your class and function is how the tag name will be called.
The node object will contain things like the attributes object and methods like getAttribute and setAttribute that you can use.
++ Note: custom tags are SSR. This means that only the + HTML returned by the render method will be shipped to the browsers. +
HTML+ templates can be made fully aware of your data files. Injecting or creating - context data is super easy to do either inside the template itself or the initial engine setup.
+ context data is super easy to do either inside the template itself or in the initial engine setup.Another spectacular aspect of the data is that it can be contextualized and there is no need to keep passing data to partial files when you include them.
Data can be provided or created in few ways:
@@ -7,16 +7,16 @@With HTML+ templates, data can be global and scoped(context) which allows for unique templates to be build to fit the exact need of your project.
-The templates are built in a way to enforce data immutability. This is so when you override data in one +
The templates are built in a way that enforces data immutability. This is so when you override data in one template it will never affect how data is in another.
HTML+ errors will point exactly at the tag or text where it happened so you know where - the fix needs o happen.
+ the fix needs to happen.On top of nice and specific error messages, there is a log tag which will log any value so you know under what context and what data the template is rendering.
HTML+ was built to empower you when building websites.
With simple API, small learning curve and batteries included, starting to use it takes almost no time.
-This documentation will provide you with more details. Please refer to the - learn page if you prefer learning by example - with a real world project example.
-You can also watch our essential training tutorial on Before Semicolon Youtube Page - and subscribe for real world project building examples.
+This documentation will provide you with all details you need to start your next project. Please refer to the + learn tutorial if you prefer learning by example + with a real world project tutorial.
{currentPage.next.title} \ No newline at end of file diff --git a/website/pages/_partials/docs/_dynamic-data.html b/website/pages/_partials/docs/_dynamic-data.html index 3380f4e9..27340306 100644 --- a/website/pages/_partials/docs/_dynamic-data.html +++ b/website/pages/_partials/docs/_dynamic-data.html @@ -21,15 +21,14 @@This data can be accessed inside the template as you named them in the object.
+This data can be accessed inside the template as you keyed them in the object.
Page path is {path} with params {params}
You can also setup dynamic custom routes to handle page requests which will give you the opportunity to call the express response render method to render a particular template.
-This render method also takes a second argument which is the object data to be passed to the - template.
+This render method also takes a second argument which is the object data to be passed to the template.
Both options to inject data into templates are great ways to collect data from any data storage source and handle it right inside the templates so it is composed right inside the templates.
-Also, both data are considered to be context data. A data specific to the template being rendered.
+Also, both data are considered to be context data -- a data specific to the template being rendered.
The engine function is ASYNCHRONOUS which means that you can await on it before you start the server.
+If you await on the engine and have some dynamic routes +you may need to set them up before otherwise any matching static route will override.
+If you are not awaiting the engine, you can just set the routes after. The effect is the same but not +awaiting on the engine may throw an error if you get to the page before the engine is done processing your +pages directory.
Another very special tag and attribute is ignore. It signals to the compiler that the markup content does not need to be compiled and should be shown as is.
Both, fragment and ignore tags and attributes, will render the inner content only but ignore - will not compile the content where fragment will.
+ does not compile the content where fragment does.When you are ready to deploy, it comes with a static site builder to build your project for production - or you can simply continue to use it in a live server for a dynamic website with all batteries included.
+ or you can simply continue to use it in a live server for a dynamic website with all batteries included and no extra setup.You can create your node project as you would normal do or executing the following commands:
+You may use any tool or strategy that you know to create a node project. You may also use the terminal or command +line to run the following commands to set it up.
The above project template will look like this:
Welcome to the projects page
- +Welcome to the projects page
+ +As you can see, any markup you put inside inject tag is content to be injected inside - the partial if it has a inject tag.
+As you can see, any markup you put inside include tag is content to be injected inside + the partial if it has an inject tag.
You can also be more specific of what type of content must be inject if you specify an id on the inject tag.
+You can also be more specific of what type of content must be injected if you specify an id on the inject tag.
Take the following layout template
In the include tag content above ywe specified all style links to be inject at style inject id, and the script -to be injected at script inject id. The remainder, unmarked will be injected at a inject tag with no id.
+In the include tag content above, we specified all style links to be inject at style inject id, and the script +to be injected at script inject id. The remainder unmarked tags will be injected at a inject tag with no id.
Note that you can inject multiple tags into the same inject id placeholder.
The above index.html template will resolve to be:
{page.description}
- +Variables declare at the beginning of the template is available everywhere inside the template, even +
Variables declared at the beginning of the template is available everywhere inside the template, even inside the partials you include in your template.
{page.description}
- +Once you have your pages directory, which you can name anything you want, you can start creating HTML files which will be used as pages.
-As mentioned in routes, the way you structure your pages directory files - is how you want your routes. There are a few things you should know when doing so:
+As mentioned in before, the way you structure your pages directory files + is how the routes will be set. There are a few things you should know when doing so:
By default, all item value is available under the $item variable but you can also override it by specifying the as value
-When you call the render method in the express response object, you don't need the initial - forward-slash or to indicate the file extension. Every name of file you specify is know to be under the + forward-slash or to indicate the file extension. Every name of file you specify is known to be under the pages directory path you provided.
This is also a nice way to pass specific data per route as you will learn more about when you explore how data works with templates.
@@ -23,7 +23,10 @@You can refer to deeply nested templates using the forward-slash to indicate nested template file.
HTML+ engine by default will use your pages directory files structure to create your site routes.
This means that all you have to do is organize your HTML page files - in a manner it reflects the routes you want by grouping and nesting HTML files.
+ in a manner it reflects the routes you want by grouping and nesting HTML files as you like.You can add a tsconfig.json file at the root of your project and setup your editor in order to control things and to see errors in the console.
This feature allows you to write future Javascript for the browser and no setup is needed for your projects.
-For even more control over how these files get processed, consider using the transform API.
+The engine will handle even javascript files imported from some third party module and process them.
+When you build your project, these files are also imported, processed and shipped with your site as part of it.
+Only make sure that these files are built to run in the browser.
Any assets you link in your HTML pages, CSS and Javascript files must exist inside the pages directory.
+The engine will resolve relative paths in the best of it abilities even when you want to build your +site for production.
+Relative path to assets are resolve even when you link them from partial files. All paths end up being resolved in relation +to the page template file the partial was included at.
+Relative path of assets in CSS are also resolved even if you import different files. They are all resolved to be +inside the pages directory in relation to the page the css file is linked to.
+Import of assets inside javascript or typescript files are also resolved but what you get may vary from the + actual text or content of the file. Almost all assets import will return simply the name of the file.
+What HTML+ does for you is set everything up for you so you can just start writing CSS using modern Syntax and preprocessors like SASS, LESS and STYLUS.
-For more advanced users, it allows you to extend these setups with plugins or presets easily.
All this is so you can get to coding sooner while your website benefits from modern CSS that works on most browsers.
+The engine will handle even CSS files imported from some third party module and process them. This means that +sass, less, stylus files are all imported and processed as if they are part of the project.
+When you build your project, these files are also imported, processed and shipped with your site
There are many ways to make templates small and reusable and HTML+ has few new and common features to do just that.