pure-engine
Advanced tools
Comparing version 0.10.3 to 0.10.4
{ | ||
"name": "pure-engine", | ||
"version": "0.10.3", | ||
"version": "0.10.4", | ||
"description": "Compile HTML templates into JS", | ||
@@ -8,3 +8,3 @@ "main": "index.js", | ||
"lint": "standard", | ||
"test": "ava 'test/spec/**/*.js'", | ||
"test": "ava test/spec/**/*.js", | ||
"coverage": "nyc npm test", | ||
@@ -14,5 +14,10 @@ "benchmark": "ava test/benchmark.js", | ||
}, | ||
"husky": { | ||
"hooks": { | ||
"pre-push": "npm run test && npm run lint" | ||
} | ||
}, | ||
"engines": { | ||
"node": ">= 10.14.0", | ||
"npm": ">= 6.4.1" | ||
"node": ">= 10.15.1", | ||
"npm": ">= 6.7.0" | ||
}, | ||
@@ -53,4 +58,5 @@ "repository": { | ||
"negate-sentence": "^0.1.2", | ||
"normalize-newline": "^3.0.0", | ||
"pure-conditions": "^0.1.9", | ||
"pure-utilities": "^1.1.8", | ||
"pure-utilities": "^1.1.10", | ||
"string-hash": "^1.1.3", | ||
@@ -61,31 +67,13 @@ "words-to-numbers": "^1.5.1", | ||
"devDependencies": { | ||
"@babel/core": "^7.1.6", | ||
"@babel/plugin-transform-react-jsx": "^7.1.6", | ||
"ansi-colors": "^3.2.1", | ||
"ava": "^1.2.0", | ||
"babel-loader": "^8.0.4", | ||
"babel-plugin-transform-react-jsx": "^6.24.1", | ||
"@babel/core": "^7.3.3", | ||
"ava": "^1.3.1", | ||
"benchmark": "^2.1.4", | ||
"coffeescript": "^2.3.2", | ||
"escape-html": "^1.0.3", | ||
"handlebars": "^4.0.12", | ||
"husky": "^1.3.1", | ||
"lodash.template": "^4.4.0", | ||
"mustache": "^3.0.0", | ||
"nyc": "^13.1.0", | ||
"preact": "^8.3.1", | ||
"preact-compat": "^3.18.4", | ||
"react": "^16.6.3", | ||
"react-bootstrap": "^0.32.4", | ||
"react-dom": "^16.6.3", | ||
"react-text-mask": "^5.4.3", | ||
"rollup": "^0.67.1", | ||
"rollup-plugin-babel": "^4.0.3", | ||
"rollup-plugin-buble": "^0.19.4", | ||
"rollup-plugin-commonjs": "^9.2.0", | ||
"rollup-plugin-node-resolve": "^3.4.0", | ||
"rollup-plugin-svelte": "^4.3.2", | ||
"nyc": "^13.2.0", | ||
"standard": "^11.0.1", | ||
"svelte": "^2.15.3", | ||
"underscore": "^1.9.1", | ||
"webpack": "^4.26.1" | ||
"underscore": "^1.9.1" | ||
}, | ||
@@ -92,0 +80,0 @@ "standard": { |
# pure-engine | ||
> Compile HTML templates into JS | ||
![npm (scoped)](https://img.shields.io/npm/v/pure-engine.svg) | ||
[![Codeship Status for buxlabs/pure-engine](https://img.shields.io/codeship/0f4ad4f0-3059-0136-f8b6-0ef1398f25bc/master.svg)](https://app.codeship.com/projects/288586) | ||
[REPL](https://buxlabs.pl/en/tools/js/pure-engine) | ||
> Compile HTML templates into JS | ||
## Description | ||
## Table of Contents | ||
- [Background](#background) | ||
- [Install](#install) | ||
- [Usage](#usage) | ||
- [API](#api) | ||
- [Examples](#examples) | ||
- [Benchmarks](#benchmarks) | ||
- [REPL](https://buxlabs.pl/en/tools/js/pure-engine) | ||
- [Maintainers](#maintainers) | ||
- [Contributing](#contributing) | ||
- [License](#license) | ||
## Background | ||
Pure Engine is a library designed to compile HTML templates into JS. It analyses the template and generates an optimal rendering function that can be used on the client and the server. The compilation process should ideally happen in a build step (for the client) or the output could be memoized after first usage (for the server). | ||
@@ -31,16 +42,24 @@ | ||
## Usage | ||
## Install | ||
`npm install pure-engine escape-html` | ||
## Usage | ||
```js | ||
const { compile } = import 'pure-engine' | ||
const escape = import 'escape-html' | ||
// ... later, inside of an async function | ||
const template = await compile('<div>{foo}</div>') | ||
expect(template({ foo: 'bar' }, escape)).to.equal('<div>bar</div>') | ||
async function example () { | ||
const { template } = await compile('<div>{foo}</div>') | ||
console.log(template({ foo: 'bar' }, escape)) | ||
} | ||
example() | ||
``` | ||
## Features | ||
If you're using webpack you should use [pure-engine-loader](https://github.com/buxlabs/pure-engine-loader). | ||
## API | ||
* import and require tags | ||
@@ -50,5 +69,3 @@ | ||
<import layout from="./layouts/default.html"> | ||
<import form from="./components/form.html"> | ||
<import input from="./components/input.html"> | ||
<import button from="./components/button.html"> | ||
<import { form, input, button } from="./components"> | ||
@@ -64,2 +81,4 @@ <layout> | ||
It's possible to import multiple components from a given directory. Curly brackets within the import tag are optional. | ||
* render, partial and include tags | ||
@@ -143,3 +162,3 @@ | ||
## Input / Output | ||
## Examples | ||
@@ -202,4 +221,12 @@ ``` | ||
## Maintainers | ||
[@emilos](https://github.com/emilos), [@pkonieczniak](https://github.com/pkonieczniak). | ||
## Contributing | ||
All contributions are highly appreciated. Please feel free to open new issues and send PRs. | ||
## License | ||
MIT |
@@ -25,3 +25,3 @@ const AbstractSyntaxTree = require('abstract-syntax-tree') | ||
const { readFileSync } = require('fs') | ||
const { dirname } = require('path') | ||
const { join, dirname } = require('path') | ||
const parse = require('./html/parse') | ||
@@ -34,4 +34,6 @@ const size = require('image-size') | ||
const { findFile } = require('./files') | ||
const { extractComponentNames } = require('./extract') | ||
const { wordsToNumbers } = require('words-to-numbers') | ||
const Component = require('./Component') | ||
const normalizeNewline = require('normalize-newline') | ||
let asyncCounter = 0 | ||
@@ -73,4 +75,18 @@ | ||
const attrs = fragment.attributes | ||
const name = attrs[0].key | ||
const path = attrs[1].value | ||
const names = extractComponentNames(attrs) | ||
if (names.length === 1) { | ||
const name = names[0] | ||
const path = attrs[1].value | ||
collectComponent(name, path, statistics, components, component, options) | ||
} else { | ||
const lastAttribute = attrs[attrs.length - 1] | ||
const dir = lastAttribute.value | ||
names.forEach(name => { | ||
const path = join(dir, `${name}.html`) | ||
collectComponent(name, path, statistics, components, component, options) | ||
}) | ||
} | ||
} | ||
function collectComponent (name, path, statistics, components, component, options) { | ||
let paths = [] | ||
@@ -538,3 +554,4 @@ if (options.paths) { | ||
findFile(path, options, location => { | ||
content += readFileSync(location, 'utf8') | ||
const string = readFileSync(location, 'utf8') | ||
content += string.trim() | ||
statistics.scripts.push({ path: location }) | ||
@@ -621,3 +638,4 @@ }) | ||
findFile(path, options, location => { | ||
content += readFileSync(location, 'utf8') | ||
const string = readFileSync(location, 'utf8') | ||
content += string.trim() | ||
statistics.stylesheets.push({ path: location }) | ||
@@ -652,3 +670,4 @@ }) | ||
findFile(path, options, location => { | ||
const content = parse(readFileSync(location, 'utf8'))[0] | ||
const string = readFileSync(location, 'utf8') | ||
const content = parse(normalizeNewline(string).trim())[0] | ||
statistics.svgs.push({ path: location }) | ||
@@ -677,3 +696,4 @@ fragment.attributes = content.attributes | ||
findFile(path, options, location => { | ||
const content = readFileSync(location, 'base64') | ||
const string = readFileSync(location, 'base64') | ||
const content = normalizeNewline(string).trim() | ||
statistics.images.push({ path: location }) | ||
@@ -680,0 +700,0 @@ attr.value = `data:image/${extension};base64, ${content}` |
@@ -16,2 +16,3 @@ const AbstractSyntaxTree = require('abstract-syntax-tree') | ||
const BoxModelPlugin = require('./plugins/BoxModelPlugin') | ||
const InlinePlugin = require('./plugins/InlinePlugin') | ||
@@ -34,2 +35,3 @@ async function render (htmltree, options) { | ||
const plugins = [ | ||
new InlinePlugin(), | ||
new BoxModelPlugin(), | ||
@@ -36,0 +38,0 @@ new CurlyStylesPlugin(), |
@@ -28,3 +28,3 @@ const lexer = require('../lexer') | ||
if (parent.type === 'MemberExpression' && node === parent.property) return node | ||
if (parent.type === 'Property') return node | ||
if (node.type === 'Identifier') { | ||
@@ -72,2 +72,6 @@ const variable = variables.find(variable => variable.key === node.name) | ||
function isObject (value) { | ||
return isCurlyTag(value) | ||
} | ||
function falsyCodeRemoval (node) { | ||
@@ -82,2 +86,3 @@ if (isExpressionStatement(node) && isFalsyNode(node.expression)) { | ||
value = addPlaceholders(value) | ||
if (isObject(value)) value = `(${value})` | ||
const tree = new AbstractSyntaxTree(value) | ||
@@ -145,5 +150,10 @@ // TODO: unify same thing is done in convert.js | ||
}) | ||
return tokens.map(token => token.value).join('') | ||
const value = tokens.map(token => token.value).join('') | ||
return optimizeWhitespace(value) | ||
} | ||
function optimizeWhitespace (text) { | ||
return text.trim().replace(/\s\s+/, ' ') | ||
} | ||
module.exports = curlyTagReduction |
@@ -46,3 +46,4 @@ const assert = require('assert') | ||
assert.deepEqual(curlyTagReduction('<div class="{foo}"></div>', [{ key: 'foo', value: null }]), `<div class='{null}'></div>`) | ||
assert.deepEqual(curlyTagReduction('<div padding="{{bottom:30}}"></div>', []), `<div padding='{({\n bottom: 30\n})}'></div>`) | ||
assert.deepEqual(curlyTagReduction('<style>.foo { color: red }</style>', []), `<style>.foo { color: red }</style>`) |
@@ -82,3 +82,3 @@ const { OBJECT_VARIABLE, ESCAPE_VARIABLE, BOOLEAN_ATTRIBUTES, UNESCAPED_NAMES, GLOBAL_VARIABLES, RESERVED_KEYWORDS } = require('./enum') | ||
} | ||
return modify(getTemplateNode(expression, variables, unescape), filters, translations, languages, translationsPaths) | ||
return modify(getTemplateNode(expression, variables, unescape), variables, filters, translations, languages, translationsPaths) | ||
} else { | ||
@@ -98,5 +98,5 @@ const nodes = values.map(({ value, filters = [] }, index) => { | ||
} | ||
return modify(getTemplateNode(expression, variables, unescape), filters, translations, languages, translationsPaths) | ||
return modify(getTemplateNode(expression, variables, unescape), variables, filters, translations, languages, translationsPaths) | ||
} | ||
return modify(getLiteral(value), filters, translations, languages, translationsPaths) | ||
return modify(getLiteral(value), variables, filters, translations, languages, translationsPaths) | ||
}) | ||
@@ -141,2 +141,67 @@ const expression = convertToBinaryExpression(nodes) | ||
function prependObjectVariableToProperty (node, variables) { | ||
if (!variables.includes(node.key.name)) { | ||
const object = getIdentifier(OBJECT_VARIABLE) | ||
object.omit = true | ||
node.value = { | ||
type: 'MemberExpression', | ||
object, | ||
property: node.value | ||
} | ||
node.shorthand = false | ||
node.value.omit = true | ||
node.value.property.omit = true | ||
} | ||
return node | ||
} | ||
function prependObjectVariableToIdentifier (node, variables) { | ||
if (!variables.includes(node.name)) { | ||
node.omit = true | ||
const object = getIdentifier(OBJECT_VARIABLE) | ||
object.omit = true | ||
node = { | ||
type: 'MemberExpression', | ||
object, | ||
property: node, | ||
omit: true | ||
} | ||
} | ||
return node | ||
} | ||
function isPropertyPrependCandidate (node) { | ||
return node.type === 'Property' && node.key === node.value | ||
} | ||
function isIdentifierPrependCandidate (node) { | ||
return node.type === 'Identifier' && !node.omit && !GLOBAL_VARIABLES.includes(node.name) | ||
} | ||
function isMemberExpression (node) { | ||
return node.type === 'MemberExpression' | ||
} | ||
function replaceCandidates (node, parent, variables) { | ||
if (isPropertyPrependCandidate(node)) { | ||
node = prependObjectVariableToProperty(node, variables) | ||
} else if (parent && parent.type === 'Property' && node.type === 'Identifier' && parent.key === node) { | ||
node.omit = true | ||
} else if (isMemberExpression(node)) { | ||
if (node.property.type === 'Identifier' && !node.computed) node.property.omit = true | ||
} else if (isIdentifierPrependCandidate(node)) { | ||
node = prependObjectVariableToIdentifier(node, variables) | ||
} | ||
return node | ||
} | ||
function prependObjectVariable (expression, variables) { | ||
AbstractSyntaxTree.replace(expression, { | ||
enter: (node, parent) => { | ||
return replaceCandidates(node, parent, variables) | ||
} | ||
}) | ||
return expression | ||
} | ||
function getTemplateNode (expression, variables, unescape) { | ||
@@ -159,37 +224,3 @@ if (expression.type === 'Literal') { | ||
].includes(expression.type)) { | ||
AbstractSyntaxTree.replace(expression, { | ||
enter: (node, parent) => { | ||
if (node.type === 'Property' && node.key === node.value) { | ||
if (!variables.includes(node.key.name)) { | ||
const object = getIdentifier(OBJECT_VARIABLE) | ||
object.omit = true | ||
node.value = { | ||
type: 'MemberExpression', | ||
object, | ||
property: node.value | ||
} | ||
node.shorthand = false | ||
node.value.omit = true | ||
node.value.property.omit = true | ||
} | ||
} else if (parent.type === 'Property' && node.type === 'Identifier' && parent.key === node) { | ||
node.omit = true | ||
} else if (node.type === 'MemberExpression') { | ||
if (node.property.type === 'Identifier' && !node.computed) node.property.omit = true | ||
} else if (node.type === 'Identifier' && !node.omit && !GLOBAL_VARIABLES.includes(node.name)) { | ||
if (!variables.includes(node.name)) { | ||
node.omit = true | ||
const object = getIdentifier(OBJECT_VARIABLE) | ||
object.omit = true | ||
node = { | ||
type: 'MemberExpression', | ||
object, | ||
property: node, | ||
omit: true | ||
} | ||
} | ||
} | ||
return node | ||
} | ||
}) | ||
expression = prependObjectVariable(expression, variables) | ||
if (unescape || isBooleanReturnFromExpression(expression)) { | ||
@@ -227,3 +258,3 @@ return expression | ||
} | ||
return modify(getTemplateNode(expression, variables, unescape), filters, translations, languages, translationsPaths) | ||
return modify(getTemplateNode(expression, variables, unescape), variables, filters, translations, languages, translationsPaths) | ||
} | ||
@@ -239,3 +270,3 @@ return getLiteral(value) | ||
function modify (node, filters, translations, languages, translationsPaths) { | ||
function modify (node, variables, filters, translations, languages, translationsPaths) { | ||
if (filters) { | ||
@@ -246,2 +277,6 @@ return filters.reduce((leaf, filter) => { | ||
if (node.type === 'CallExpression') { | ||
const name = getFilterName(node.callee.name) | ||
node.arguments = node.arguments.map(argument => { | ||
return replaceCandidates(argument, null, variables) | ||
}) | ||
return { | ||
@@ -251,3 +286,3 @@ type: 'CallExpression', | ||
type: 'Identifier', | ||
name: getFilterName(node.callee.name) | ||
name | ||
}, | ||
@@ -273,2 +308,3 @@ arguments: [leaf].concat(node.arguments) | ||
} | ||
const name = getFilterName(node.name) | ||
return { | ||
@@ -278,3 +314,3 @@ type: 'CallExpression', | ||
type: 'Identifier', | ||
name: getFilterName(node.name) | ||
name | ||
}, | ||
@@ -281,0 +317,0 @@ arguments: args |
@@ -1,11 +0,30 @@ | ||
const { parseDefaults } = require('himalaya') | ||
const { parse, parseDefaults } = require('himalaya') | ||
const { VOID_TAGS } = require('../enum') | ||
const { extractComponentNames } = require('../extract') | ||
const walk = require('himalaya-walk') | ||
function regexp (tag, name) { | ||
return new RegExp(`<${tag}\\s+${name}\\s+`) | ||
function matchImportTags (source) { | ||
return source.match(/<import[\s\S]*?>[\s\S]*?>/gi) | ||
} | ||
function getImportTags (source) { | ||
const imports = matchImportTags(source) | ||
if (!imports) return [] | ||
return imports.join('') | ||
} | ||
function deduceVoidTags (source) { | ||
const tags = [] | ||
const imports = getImportTags(source) | ||
const tree = parse(imports) | ||
walk(tree, node => { | ||
if (node.tagName === 'import' || node.tagName === 'require') { | ||
extractComponentNames(node.attributes).forEach(tag => { | ||
tags.push(tag) | ||
}) | ||
} | ||
}) | ||
return VOID_TAGS.concat(parseDefaults.voidTags).filter(name => { | ||
return !regexp('import', name).test(source) && !regexp('require', name).test(source) | ||
return !tags.includes(name) | ||
}) | ||
@@ -12,0 +31,0 @@ } |
const { isCurlyTag, getExpressionFromCurlyTag } = require('../string') | ||
const serialize = require('asttv') | ||
const AbstractSyntaxTree = require('abstract-syntax-tree') | ||
const { flatten } = require('pure-utilities/object') | ||
const { flatten } = require('pure-utilities/collection') | ||
const Plugin = require('./Plugin') | ||
@@ -6,0 +6,0 @@ |
@@ -7,6 +7,8 @@ const { parse, walk, generate } = require('css-tree') | ||
const Plugin = require('./Plugin') | ||
const normalize = require('normalize-newline') | ||
function addScopeToCssSelectors (node, scopes) { | ||
const id = `scope-${hash(node.content)}` | ||
const tree = parse(node.content) | ||
const content = normalize(node.content).trim() | ||
const id = `scope-${hash(content)}` | ||
const tree = parse(content) | ||
walk(tree, node => { | ||
@@ -48,4 +50,3 @@ if (node.type === 'SelectorList') { | ||
class ScopedStylesPlugin extends Plugin { | ||
constructor () { | ||
super() | ||
beforeprerun () { | ||
this.scopes = [] | ||
@@ -63,4 +64,7 @@ } | ||
} | ||
afterrun () { | ||
this.scopes = [] | ||
} | ||
} | ||
module.exports = ScopedStylesPlugin |
122386
11
38
3032
228
14
5
+ Addednormalize-newline@^3.0.0
+ Addednormalize-newline@3.0.0(transitive)
Updatedpure-utilities@^1.1.10