Switch to clean-css parser inside style {...}, removing need for quoting of CSS
authorNick Downing <nick@ndcode.org>
Sat, 17 Nov 2018 13:40:08 +0000 (00:40 +1100)
committerNick Downing <nick@ndcode.org>
Sat, 17 Nov 2018 22:05:13 +0000 (09:05 +1100)
README.md
package.json
src/expression.js

index c00c277..6c5835a 100644 (file)
--- 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
index f5de67a..1681148 100644 (file)
@@ -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",
index f05ece2..966f90a 100644 (file)
@@ -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