Aurelia: sanity check template html?
Asked Answered
S

2

28

I recently asked why self-closing elements do not work in Aurelia's templating system; and it was because self-closing elements are invalid html.

Yet, today I again made the same mistake (with widgets this time) and was left scratching my head why content was missing.

Question: Is there a away to sanitise Aurelia template html in a gulp task?

I've tried using:

  • gulp-htmlhint: couldn't get it to error on self-closed elements
  • gulp-htmllint: couldn't configure it; with default settings it blows up with errors.
  • gulp-html5-lint: doesn't look configurable and it hates aurelia's attributes.
Spur answered 19/5, 2016 at 12:17 Comment(10)
Great question I'd love to have something to lint my HTML (can't tell you how many times I've been stuck for an hour by changing only the opening tag and not the closing tag)Volvox
htmlhint has options (haven't used it before)... github.com/yaniswang/HTMLHint/wiki/Tag-self-close and you can integrate it into your IDE for example marketplace.visualstudio.com/items?itemName=mkaufman.HTMLHint or create a custom rule github.com/yaniswang/HTMLHint/issues/47Denisdenise
Another possible option github.com/htacg/tidy-html5/issues/204Denisdenise
@MattMcCabe unfortunately that tag-self-close relates to making html4 complaint code: i.e. <br/>, while in html5 you are allowed to do <br>. That custom rules in htmlhint might be the way forward.Spur
@MeirionHughes yes, sorry I realised that after playing around with it. I'm pretty sure there will be a solution soon as custom components become more commonDenisdenise
In general, I'd like to see a set of rules that cover Aurelia templates, if not custom elements or web components in general. I can't imagine a linter couldn't cover proper HTML specs with a sprinkle of Aurelia templating. If we don't get a good answer on this one I'll throw down a bounty.Volvox
@PWKad this question applicable to angular 2 too?Spur
I don't think ng2's templates are spec compliant HTML anymore. I think they have a custom parser.Cassiterite
I don't think it's specific to Aurelia or Angular 2 but I don't see why the additional eyes won't help. I've edited the question to include ng2 and html5 to hope we can get some build tooling from this question that transcends frameworks and works for everyone.Volvox
It looks like angular 2 specifically dealt with this issue already: github.com/angular/angular/issues/5563Spur
S
8

We can solve the issue of finding and of reporting self-closing elements with parse5. It has a SAXParser class that should be quite robust (parse5 conforms to html5 standards). The parser raises an event, upon finding a start-tags, that contains a boolean as to whether the found tag self closes.

var parser = new SAXParser();

parser.on("startTag", (name, attrs, selfClosing)=>{
    if(selfClosing){ 
        //check if name is void, if not report error       
    }
});

parser.push(html);

To make use of this functionality I have set up a project that can be used in order to help sanitize html using the above approach. The developed lint tool is able to run a selection of Rules, collect any errors and return them as a Promise. This can then be reported to the user.

Vanilla Html/Templates

template-lint forms the base of the tool-set. It comprises of the Linter, and a couple of basic rules:

  • SelfClose - ensure non-void elements do not self-close
  • Parser - returns errors for unclosed or ill-matched elements, as captured during parsing

gulp-template-lint is the gulp wrapper for template-lint and can be used like so:

var gulp = require('gulp');
var linter = require('gulp-template-lint');

gulp.task('build-html', function () {
    return gulp.src(['source/**/*.html'])
        .pipe(linter())
        .pipe(gulp.dest('output'));
});

Example

Given the following html:

<template>
  <custom-element/> 
  <svg>
    <rect/>
  </svg>
  <div>
    <div>
    </div>
</template>

produces:

enter image description here

Note: the self-closed <rect/> does not produce an error. svg elements contains xml and Rules can differentiate based on scope.

Aurelia Templates

I initially made aurelia-template-lint, but decided to split out the reusable (outside of aurelia) components into template-lint. While both are currently separate, I will have the aurelia-template-lint extend upon template-lint in due course. Currently has a few proof-of-concept rules:

  • SelfClose - ensure non-void elements do not self-close
  • Parser - returns errors for unclosed or ill-matched elements, as captured during parsing
  • Template - ensure root is a template element, and no more than one template element present
  • RouterView - don't allow router-view element to contain content elements
  • Require - ensure require elments have a 'from' attribute

there is a gulp wrapper that can be installed via:

npm install gulp-aurelia-template-lint

and used in a gulp build:

var linter = require('gulp-aurelia-template-lint');

gulp.task('lint-template-html', function () {
    return gulp.src('**/*.html')
        .pipe(linter())
        .pipe(gulp.dest('output'));
});

this will use the default set of rules.

Example

a simple test with the following ill-formed aurelia template:

<link/>
<template bindable="items">
<require from="foo"/>
<require frm="foo"/>

<br/>
<div></div>

<router-view>
  <div/>
</router-view>

</template>
<template>
</template>

outputs:

enter image description here

Improvements

there are lots of improvements needed; for instance there are a few ways to define vanilla templates without the <template> tag. There are also quite a few specific attributes introduced by Aurelia that could be sanitised.

Spur answered 27/5, 2016 at 0:37 Comment(1)
If anyone has issues with this; I'd be happy to try to resolve them.Spur
S
3

Given no one has answered yet; I present the "Better than nothing (maybe)"™ solution.

var gulp = require('gulp');
var gutil = require('gulp-util');

var voidTags = [
    'area', 'base', 'br', 'col', 'embed', 'hr', 
    'img', 'input', 'keygen', 'link', 'meta', 
    'param', 'source', 'track', 'wbr'];

var checkSelfClose = function () {
  function sanitize(file, cb) { 

    var dirty = String(file.contents);

    var matches = dirty.match(/(?:\<[\/\\\-\"\'!() a-z=.]+\/\>)/g);

    var customTags = [];

    if(matches && matches.length > 0)
    {       
        matches.forEach((match)=>{
            var tag = match.match(/[a-z\-]+/)[0];

            if(voidTags.indexOf(tag) < 0)
                customTags.push(tag);   
        });                   
    };

    if(customTags.length > 0)
        gutil.log('WARNING', 'found ' + customTags.length + " non-void self-closed tags in", 
        file.path.substring(file.cwd.length, file.path.Length),
        "tags:", customTags
        );

    cb(null, file);
  }
  return require('event-stream').map(sanitize);
}

gulp.task('build-html', function () {
    return gulp.src('source/**/*.html')
    .pipe(checkSelfClose())
    .pipe(gulp.dest('output'));
});

tested with:

<template bindable="items">
  <require from="./menu-bar.css" />

  <custom-element/>  
  <custom-element click.delegate="subitem.execute()" repeat.for="item of items" /> 
  <custom-element-moo></custom-element-moo>

  <br>
  <br/>

  <div id="blahblah"/>  

  <button class="dropbtn"/>
</template>

gulp output:

enter image description here

[Update]

Leaving this here as it is a quick, dirty and dependency free way to check for self-closed tags; does answer the question and may be useful.

Spur answered 25/5, 2016 at 13:40 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.