From: Nick Downing Date: Sat, 17 Nov 2018 13:40:08 +0000 (+1100) Subject: Switch to clean-css parser inside style {...}, removing need for quoting of CSS X-Git-Url: https://git.ndcode.org/public/gitweb.cgi?p=jst.git;a=commitdiff_plain;h=f49a0c08b0f8bac20524165ecd0e4615df4e3be1 Switch to clean-css parser inside style {...}, removing need for quoting of CSS --- diff --git a/README.md b/README.md index c00c277..6c5835a 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ An NDCODE project. The `jst` package exports a single function `jst(pathname, root, args)`, which loads a file `pathname` from disk, parses it for a superset of JavaScript -(ES6) which can contain HTML-like constructs, and then generates equivalent +(ES7) which can contain HTML-like constructs, and then generates equivalent plain JavaScript code which in turn, can generate the desired HTML. The system is inspired by Pug (formerly Jade) templates, but emphasizing the @@ -103,11 +103,11 @@ automatic minification and/or other transformations, such as ES5 transpilation. As well as browser-oriented transformations, other potential transformations such as TypeScript could be applied to all code rather than just `script` tags. -The HTML `style` tag is currently not handled specially, thus the content will -most likely be given as a backquoted (template) string, to support embedded -newlines. To defeat the HTML escaping, it can be wrapped in `_out(...)`, which -will be explained further below. In the future, we will switch to a dedicated -CSS parser, or potentially the `less` or `sass` compiler, for `style` tags. +The HTML `style` tag is treated specially, because it contains CSS, which we +handle by switching temporarily to another parser that understands CSS. We use +a slightly modified version of the `clean-css` package to do this. The CSS is +automatically minified and then HTML-escaped at the time the template is +compiled. Note: unlike the JST code, it currently cannot contain substitutions. ### HTML class and id shorthand @@ -131,6 +131,14 @@ doesn't compile because "1." is a floating point number, for now we write it `div.'class-1'.class-2 {}` although we expect that this restriction can be lifted in a future version. +Also, the `style` parser for CSS code can be confused by tag, `id` or `class` +names that end with `-style`. So we should be careful of code like this, + `div.my-style {...}` +since the `div.my-` prefix won't be seen until parsing is complete, hence the +parser will switch to CSS parsing inside the braces. Therefore, quote it like + `div.'my-style' {...}` +although again, we expect that this restriction can be lifted in the future. + Another slight limitation of the current parser is that it is more permissive than normal in parsing regular JavaScript code, for instance commas are not required between function arguments, because HTML templates are basically diff --git a/package.json b/package.json index f5de67a..1681148 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "directories": {}, "dependencies": { "@ndcode/build_cache": "^0.1.0", + "@ndcode/clean-css": "^0.1.0", "astring": "^1.3.1", "html-escape": "^2.0.0", "rollup": "^0.45.0", diff --git a/src/expression.js b/src/expression.js index f05ece2..966f90a 100644 --- a/src/expression.js +++ b/src/expression.js @@ -22,6 +22,11 @@ import {DestructuringErrors} from "./parseutil" import {lineBreak} from "./whitespace" import {functionFlags, SCOPE_ARROW, SCOPE_SUPER, SCOPE_DIRECT_SUPER, BIND_OUTSIDE, BIND_VAR} from "./scopeflags" +// Nick +import CleanCSS from "@ndcode/clean-css" +import html_escape from "html-escape" +let clean_css = new CleanCSS() + const pp = Parser.prototype // Check if property name clashes with already added. @@ -302,7 +307,40 @@ pp.parseSubscripts = function(base, startPos, startLoc, noCalls) { } else if (this.type === tt.braceL) { // Nick let node = this.startNodeAt(startPos, startLoc) node.tag = base - node.body = this.parseBlock(false) + if ( + ( + base.type === 'Identifier' && + base.name === 'style' + ) || + ( + base.type === 'CallExpression' && + base.callee.type === 'Identifier' && + base.callee.name === 'style' + ) + ) { + let render = clean_css.minifyEmbedded(this.input, this.pos) + if (render.errors.length) + throw render.errors + for (let i = 0; i < render.warnings.length; ++i) + console.log(`clean-css warning: ${render.warnings[i]}`) + node.body = { + type: 'BlockStatement', + body: [ + { + type: 'ExpressionStatement', + expression: { + type: 'Literal', + value: html_escape(render.styles) + } + } + ] + } + this.pos = render.embeddedEnd + this.next() + this.expect(tt.braceR) + } + else + node.body = this.parseBlock(false) base = this.finishNode(node, "HTMLExpression") } else { return base