Skip to content

Commit

Permalink
Merge pull request #25 from beforesemicolon/develop
Browse files Browse the repository at this point in the history
Release 0.5.0
  • Loading branch information
ECorreia45 committed Jul 17, 2021
2 parents 3a53c4d + 21cb179 commit baf33a5
Show file tree
Hide file tree
Showing 17 changed files with 324 additions and 65 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/linux.yml
Expand Up @@ -22,7 +22,7 @@ jobs:

strategy:
matrix:
node: [10.x, 12.x, 14.x]
node: [14.x]

# Steps represent a sequence of tasks that will be executed as part of the job
steps:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/mac.yml
Expand Up @@ -22,7 +22,7 @@ jobs:

strategy:
matrix:
node: [10.x, 12.x, 14.x]
node: [14.x]

# Steps represent a sequence of tasks that will be executed as part of the job
steps:
Expand Down
77 changes: 59 additions & 18 deletions core/engine/index.js
Expand Up @@ -7,32 +7,73 @@ const {transform} = require('../transform');
const {getDirectoryFilesDetail} = require('../utils/getDirectoryFilesDetail');
const {File} = require('../File');
const validUrl = require('valid-url');
const {isObject, isArray, isFunction} = require("util");
const {mergeObjects} = require("../utils/merge-objects");

const defaultOptions = {
staticData: {},
customTags: [],
customAttributes: [],
env: 'development',
sass: {
indentWidth: 2,
precision: 5,
indentType: 'space',
linefeed: 'lf',
sourceComments: false,
includePaths: [],
functions: {},
},
less: {
strictUnits: false,
insecure: false,
paths: [],
math: 1,
urlArgs: '',
modifyVars: null,
lint: false,
},
stylus: {
paths: [],
},
postCSS: {
plugins: []
},
onPageRequest() {
}
}

const engine = (app, pagesDirectoryPath, opt = defaultOptions) => {
const engine = (app, pagesDirectoryPath, opt = {}) => {
if (!app) {
throw new Error('engine first argument must be provided and be a valid express app.')
}

opt = {...defaultOptions, ...opt}
let hbConfig = {};

if (typeof opt.staticData !== 'object') {
throw new Error('HTML+ static data must be an javascript object')
try {
hbConfig = require(path.join(process.cwd(), 'hp.config.js'));
} catch (e) {
if (e.code !== 'MODULE_NOT_FOUND') {
e.message = `hp.config.js file loading failed: ${e.message}`
throw new Error(e)
}
}

opt = {...defaultOptions, ...mergeObjects(hbConfig, opt)};

if (!isObject(opt.staticData)) {
throw new Error('HTML+ static data option must be a javascript object')
}

if (!isArray(opt.customTags)) {
throw new Error('HTML+ custom tags option must be an array of valid tags.')
}

if (!Array.isArray(opt.customTags)) {
throw new Error('HTML+ custom tags must be an array of valid tags.')
if (!isArray(opt.customAttributes)) {
throw new Error('HTML+ custom attributes option must be an array of valid attributes.')
}

if (typeof opt.onPageRequest !== 'function') {
if (!isFunction(opt.onPageRequest)) {
throw new Error('"onPageRequest" option must be a function')
}

Expand All @@ -54,7 +95,7 @@ const engine = (app, pagesDirectoryPath, opt = defaultOptions) => {
filePath = filePath.replace(pagesDirectoryPath, '');
const template = `${filePath.replace('.html', '')}`.slice(1);
let tempPath = '';

if (filePath.endsWith('index.html')) {
tempPath = filePath.replace('/index.html', '');
pagesRoutes[tempPath || '/'] = template;
Expand All @@ -73,11 +114,11 @@ const engine = (app, pagesDirectoryPath, opt = defaultOptions) => {
.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);
Expand All @@ -92,25 +133,25 @@ const engine = (app, pagesDirectoryPath, opt = defaultOptions) => {
partialFiles: partials,
onBeforeRender: (node, nodeFile) => {
let attrName = '';

if (node.tagName === 'link') {
attrName = 'href';
} else if (node.tagName === 'script') {
attrName = 'src';
}

const srcPath = node.attributes[attrName];

if (srcPath && !validUrl.isUri(srcPath)) {
const resourceFullPath = path.resolve(nodeFile.fileDirectoryPath, srcPath);

if (resourceFullPath.startsWith(pagesDirectoryPath)) {
node.setAttribute(attrName, resourceFullPath.replace(pagesDirectoryPath, ''))
}
}
}
})

callback(null, result);
} catch (e) {
console.error(e.message);
Expand All @@ -121,17 +162,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');
})
};
Expand Down
6 changes: 3 additions & 3 deletions core/engine/page-and-resources-middleware.js
Expand Up @@ -17,7 +17,7 @@ const sourcesExtensions = new Set([
]);
const cache = {};

function pageAndResourcesMiddleware(pagesRoutes, pagesDirectoryPath, {env, onPageRequest}) {
function pageAndResourcesMiddleware(pagesRoutes, pagesDirectoryPath, {env, onPageRequest, sass, less}) {
return async (req, res, next) => {
if (req.method === 'GET') {
const ext = path.extname(req.path);
Expand Down Expand Up @@ -45,11 +45,11 @@ function pageAndResourcesMiddleware(pagesRoutes, pagesDirectoryPath, {env, onPag
switch (ext) {
case '.scss':
case '.sass':
content = await transformResource.sass({file});
content = await transformResource.sass({file, ...sass});
content = (await transformResource.css(content, {file, env})).content;
break;
case '.less':
content = await transformResource.less({file});
content = await transformResource.less({file, ...less});
content = (await transformResource.css(content, {file, env})).content;
break;
case '.styl':
Expand Down
14 changes: 10 additions & 4 deletions core/transformers/less.transformer.js
Expand Up @@ -5,7 +5,14 @@ const render = promisify(less.render);

const defaultOptions = {
env: 'development',
file: null
file: null,
strictUnits: false,
insecure: false,
paths: [],
math: 1,
urlArgs: '',
modifyVars: null,
lint: false,
}

async function lessTransformer(content, opt = defaultOptions) {
Expand All @@ -22,10 +29,9 @@ async function lessTransformer(content, opt = defaultOptions) {
content = content ?? '';

const options = {
...defaultOptions,
env: opt.env,
...opt,
filename: opt.file?.fileAbsolutePath,
// ...(opt.env === 'development' && {sourceMap: {}})
...(opt.env === 'production' && {sourceMap: {}})
}

return render(content, options).then(res => {
Expand Down
7 changes: 7 additions & 0 deletions core/transformers/sass.transformer.js
Expand Up @@ -6,6 +6,13 @@ const render = promisify(nodeSass.render);
const defaultOptions = {
env: 'development',
file: null,
indentWidth: 2,
precision: 5,
indentType: 'space',
linefeed: 'lf',
sourceComments: false,
functions: {},
includePaths: [],
}

async function sassTransformer(content, opt = defaultOptions) {
Expand Down
57 changes: 57 additions & 0 deletions core/transformers/sass.transformer.spec.js
Expand Up @@ -87,4 +87,61 @@ describe('sassTransformer', () => {
return expect(sassTransformer({})).rejects.toThrowError('If no string content is provided, the "file" option must be provided.')
});

describe('should work with other options', () => {
it('indentWidth', () => {
return sassTransformer(`
body {
background: #edd;
}
`, {
indentWidth: 8
}).then(res => {
expect(res).toEqual('body {\n' +
' background: #edd; }\n');
})
});

it('precision', () => {
return sassTransformer(`
$w: (10 / 3);
body {
width: #{$w}px;
}
`, {
precision: 5
}).then(res => {
expect(res).toEqual('body {\n' +
' width: 3.33333px; }\n');
})
});

it('indentType', () => {
return sassTransformer(`
body {
width: 300px;
}
`, {
indentWidth: 1,
indentType: 'tab'
}).then(res => {
expect(res).toEqual('body {\n' +
'\twidth: 300px; }\n');
})
});

it('sourceComments', () => {
return sassTransformer(`
body {
width: 300px;
}
`, {
sourceComments: true
}).then(res => {
expect(res).toEqual('/* line 2, stdin */\n' +
'body {\n' +
' width: 300px; }\n');
})
});
});

});
28 changes: 18 additions & 10 deletions core/transformers/stylus.transformer.js
@@ -1,18 +1,16 @@
const stylus = require('stylus');
const {promisify} = require('util');

const render = promisify(stylus.render);

const defaultOptions = {
env: 'development',
file: null,
paths: []
}

async function stylusTransformer(content = null, opt = defaultOptions) {
if (content && typeof content === 'object') {
opt = content;
content = null;

if (!opt.file) {
throw new Error('If no string content is provided, the "file" option must be provided.')
}
Expand All @@ -21,13 +19,23 @@ async function stylusTransformer(content = null, opt = defaultOptions) {
opt = {...defaultOptions, ...opt};
content = content ?? '';

return await render(content, {
filename: opt.file?.fileAbsolutePath,
// ...(opt.env === 'development' && {sourceMap: 'inline'}),
})
.then(css => {
return css;
return await (new Promise((res, rej) => {

const styl = stylus(content, {
...opt,
filename: opt.file?.fileAbsolutePath,
...(opt.env === 'production' && {sourceMap: 'inline'}),
});


styl.render((err, css) => {
if (err) {
return rej(err);
}

res(css)
})
}))
}

module.exports.stylusTransformer = stylusTransformer;
Expand Down
17 changes: 17 additions & 0 deletions core/utils/merge-objects.js
@@ -0,0 +1,17 @@
const {isObject, isArray} = require("util");

function mergeObjects(a, b) {
if(!isObject(a) || !isObject(b)) return b ?? {};

const obj = isArray(a) ? [...a] : a;

for(const key in b) {
if(b.hasOwnProperty(key)) {
obj[key] = mergeObjects(obj[key], b[key]);
}
}

return obj;
}

module.exports.mergeObjects = mergeObjects;

0 comments on commit baf33a5

Please sign in to comment.