Open ForbesLindesay opened 9 years ago
In general the idea sounds okay.
- A new keyword is added to JavaScript inside jade called block that must be immediately followed by a block name. e.g.
input(value=(block value))
This sounds messy...
And I assume that you can still add a block default like
block declare content
p Hey!
?
Just came across this and it sounds promising - it's not clear to me whether it would allow you to have multi-level inheritance without specifying a different name for each inner block, but if so that would be brilliant. One of the main use cases I have is a main layout, and then most content included within a div with some padding, but not all of them, so to have some pages be main <- padding div <- content
, and some just main <- content
, without worrying about block names, would be so much cleaner.
Sounds like a great way to consolidate mixins, includes and extends. When you say "Block Context", how does that interact with local JS variables?
I assume that you can still add a block default
Absolutely, you can also do the same thing for the default block:
block
p Default content
[new JavaScript keyword] sounds messy
maybe, the alternative is to just use the block
function like we already do inside mixins. One of the advantages of using a keyword is that we can guarantee that we will be able to statically analyse the location of all calls to request a block. We could just only support calling block
with a string literal name though, which would serve the same purpose. The semantics would be a bit like:
- var x = block('my-block')
is approximately equivalent to (but we'd hopefully find a nicer way to implement it):
- var old_buffer = buf;
block declare my-block
- var x = buf; buf = old_buffer;
would [it] allow you to have multi-level inheritance without specifying a different name for each inner block
Yes, absolutely. This has long been a goal for 2.0.0, and could be implemented with or without this change. It's made achievable by splitting out the linker as a separate component of the compiler pipeline.
how does ["Block Context"] interact with local JS variables?
I'm not sure. I think we need another whole discussion about variable context. We probably need a formal way of marking variables/mixins as local vs. global vs. exposed to the parent's Block Context. Probably a whole separate discussion.
Here's my feedback:
yield
, and don't really know what it does, so can't comment.extends
for extending layouts, etc. because it's such a common use case.include
and mixins.This block declare
stuff confuses me: what's the point of it? I don't think I like it. The example you gave could be written as follows, without the word 'declare':
div.dialog(data-dialog)
div.dialog-heading
block heading
div.dialog-body
block
block prepend
and block append
- they look useful!A couple more questions:
extends
and extends
both keywords? What does each of them do?block foo
in JavaScript? Wouldn't it be better if you could just access blocks via a variable (ie: blocks.foo
), instead of with a keyword?EDIT: extend
and extends
@mikeyhew said:
Are
extends
andextends
both keywords? What does each of them do?
Huh?
@mikeyhew said:
What would be the return value of
block foo
in JavaScript? Wouldn't it be better if you could just access blocks via a variable (ie:blocks.foo
), instead of with a keyword?
It's not that simple. Consider this case:
mixin a
block
+a
- var b = 0
p= b
The block has to be a function.
@mikeyhew said:
The example you gave could be written as follows, without the word 'declare'
This I have to agree with. I don't see the point in requiring the user to put the extraneous declare
, especially when declaring the default block one does not have to put declare
.
@TimothyGu regarding the value of the expression block foo
: OK, so the value of block foo
is a function then, that presumably returns a string. Why would block
need to be a keyword?
@TimothyGu said:
This I have to agree with. I don't see the point in requiring the user to put the extraneous declare, especially when declaring the default block one does not have to put declare.
@ForbesLindesay what do you think? Is there anything we're missing, or can the block declare
syntax be removed?
@TimothyGu said:
Huh?
Huh what? Please elaborate.
The value of block foo
would be a string. But block foo
may potentially have side effects, and may potentially be computationally expensive. As such, it should be evaluated lazily. The reason we may want to make it a keyword, is that by making it a keyword, we can use static analysis to find all usages of a given block. This may lead to a lot of extra performance optimisation opportunities in the future.
The problem I'm attempting to solve with the declare
syntax, is that we currently guess at whether you intended to declare, or replace a block when we see the block keyword. e.g.
extends layout.jade
//- this means "replace content", but it also means "declare content" if another template extends this one.
block content
div.body
//- this means "declare body"
block body
If we unify the block keyword, then what does this mean:
//- layout.jade
mixin foo
.modal
.heading
block heading
.body
block body
+foo
block heading
h1 My Heading
block body
h1 My Body
//- child.jade
extends ./layout.jade
block heading
h1 My New Heading
block body
h1 My New Body
The blocks in the mixin call could mean "replace the block inside the mixin" or "declare the block for when this template is extended". In this example, I've used them to mean both.
Huh?
I assume you meant to ask whether extend
and extends
are both keywords. They are, but they are synonyms.
I'm sorry I still didn't posted my contribution - crazy times at work. Not enough brainpower in the evenings.
Here's few notes about what I'm trying to solve.
Statements declare
and append/prepend
are little too descriptive compared to rest of Jade, but the solution at all makes sense. I'm looking for ways to simplify the syntax while maintaining readability and clarity.
Usage of mixins is perfect example and reference solution I'd like to find for blocks
//- mixin declaration
mixin whatever()
p I'm a mixin.
//- usage
+whatever
We're using keyword mixin
for declaration and +
add sign for usage. Everything is crystal clear.
Example with blocks
mixin whatever()
block header
.content
block //- shorthand for block DEFAULT
//- usage
+whatever
@header
h1 some header
//- synonym for @default
@block
p block content
Usage is consistent with mixins, and @
at sign is descriptive enough to make it clear. block
keyword is used only for declaration.
I'm looking for ways how to make the append/prepend
keyword work with this. Nothing better than @-
or @+
came to my mind and I don't like this.
Does this proposal make sense to you? Any ideas?
how about
block set whatever
| whatever
It's a bit shorter than block declare foo
and means pretty much the same thing, right? Looks prettier imo.
@viktorbezdek I'm not sure if @-
and @+
make for the nicest syntax. I do like the fact that these calls are as short as +mixin
, though I don't think shorter always means more readable (especially in longer, more complex files). I think I just like how the word block
is explicitly used wherever a block is involved.
If you go this road with at-signs, you might end up using signs for includes, extends etc. Might look like this
*./ext.jade
mixin whatever
block whatever
| #{whatever}
&./inc.jade
:markdown
# Markdown
+whatever
@+whatever
| whatever
How about -
block doot
h1 Doot
// elsewhere ...
doot:
| replaces
// or doot:after ?
doot:prepend
| prended ...
terminal-colon isn't an expression so far as I know?
I don't really like the +/- apart from brevity - 'subtract' is not really the semantics of prepend.
@val terminal colon is used when you're using two tags on same line
em: strong Bold and italic text
That's highly annoying, mostly because I remember that feature existing, trying to use it, and it failing, so I thought it had been removed. Was it missing for a few versions?
I share some of the expressed fears on the "declare" notation which - I don't really know why - does not feel natural compared with the overal jade syntax.
I don't know if this can lead somewhere or will ring some bells among contributors, but this problem makes me think of "aspect oriented programming" where people have tried to find a notation to modify the execution of a program :
append
, prepend
also seem close to LISP modifiers before
, after
and around
block
exist in some way, to override the html just like AOP would inject aspects in a program.
for some explanation on AOP, you can read https://en.wikipedia.org/wiki/Aspect-oriented_programming
One of the interesting thing is that AOP defines "primitive pointcut designators (PCDs)." which are expressions that are used to locate specific points in the execution flow of a program. You can find an example on the wikipedia page.
In jade, there could be PCDs that define specific parts of the layout
for exemple, we could have
// definition of the headerscript location
html
head
div#headerscript
// definition of the script PCD
pointcut script() : #headerscript
// append
after() : script()
script(src='/dialog.js')
the notation would need tweeking but this way, we would have a way to target things that the compiler needs to modify at compile time, without being limited to the block notation.
A pointcut could also target things like "all A tags", "just before the end of body", "all DIV inside layout.jade".
@viktorbezdek While I agree that block declare foo
might be too verbose, and definitely doesn't read well, the trend I have been trying to push for is fewer syntactic shorthands rather than more.
We want syntactic shorthands where you can guess what the shorthand does based on similar usage in other parts of the file (e.g. !
always means unescaped, #
followed by an open bracket starts some sort of interpolation, the right hand side of =
is always a JavaScript expression). We also want syntactic shorthands for things that you do extremely frequently in well designed jade templates (e.g. calling a mixin via +
).
An example where a shorthand is counter-productive, is using the syntax !!! 5
as a shorthand for doctype html
. Nobody will need to look the latter up in our docs, whereas everyone will have to look up what !!!
means the first time they see it.
Well designed jade templates should only have a handful of blocks per file, so I don't think they merit such extreme short hands. On the other hand, I do think we could come up with something a little more succinct using @
as a convenient shorthand (see my "Alternative Syntax Proposal")
@Aratramba Using something like set
/replace
on the overriding side would be an alternative to using declare
on the declaration side. I don't know that it offers a huge advantage, but it's a possibility.
@jeromew Thanks for this comparison with AOP. I think the idea of a pointcut is very close to what we are trying to do with jade blocks. I suspect the terminology of block
will be more immediately accessible than pointcut
to our users, although I could well be wrong as it's just my intuition. I don't think it makes sense to radically change the terminology here. I also think it's probably prefereable to keep the extension points limited to explicit declarations, rather than allowing things like injecting code after all A tags. I think allowing arbitrary extension will lead to a very brittle experience when editing layout templates. I was thinking that block append foo
could append to all foo
blocks and that you could declare a block with the same name multiple times in a given file. This would let you do things like:
if ajax
block content
else
doctype html
html
head
title My Page Title
block styles
body
block content
block scripts
Core syntax:
@block <BLOCK NAME>
: declares a named block to be overridden elsewhere.@set <BLOCK NAME>
: override the content of a declared block.@append <BLOCK NAME>
: append to the content of a declared block.@prepend <Block NAME>
: prepend to the content of a declared block.Shorthands:
@block
expands to @block DEFAULT
@set DEFAULT
.Please focus for now on the functionality, rather than the naming of these keywords, but here are some possible alternatives:
@block
could alternatively be @let
(to match JavaScript variables) or @declare
(to make it more obviously a verb)?@set
could alternatively be @replace
?layout.jade
doctype html
html
head
title My Layout
body
@block
script(src="/jquery.js")
@block scripts
To use this layout template, you can use extend
:
child.jade
extend ./layout.jade
h1 My Child Page
Could also have been written as:
include ./layout.jade
h1 My Child Page
Which is, itself a short hand for:
include ./layout.jade
@set DEFAULT
h1 My Child Page
Either way the output would be:
<!DOCTYPE html>
<html>
<head>
<title>My Layout</title>
</head>
<body>
<h1>My Child Page</h1>
<script src="/jquery.js"></script>
</body>
</html>
This lets you write a neatly encapsulated component like:
dialog-component.jade
@append scripts
script(src='/dialog.js')
div.dialog(data-dialog)
div.dialog-heading
@block heading
div.dialog-body
@block
which is equivalent to:
@append scripts
script(src='/dialog.js')
div.dialog(data-dialog)
div.dialog-heading
@block heading
div.dialog-body
@block DEFAULT
It can be used as:
child-2.jade
extend ./layout.jade
h1 My Child Page
include ./component.jade
@set heading
h2 My Dialog Heading
p My Content
child-2.jade
extend ./layout.jade
h1 My Child Page
include ./component.jade
@set heading
h2 My Dialog Heading
@set DEFAULT
p My Content
<!DOCTYPE html>
<html>
<head>
<title>My Layout</title>
</head>
<body>
<h1>My Child Page</h1>
<div data-dialog class="dialog">
<div class="dialog-heading">
<h2>My Dialog Heading</h2>
</div>
<div class="dialog-body">
<p>My Content</p>
</div>
</div>
<script src="/jquery.js"></script>
<script src="/dialog.js"></script>
</body>
</html>
absolutely in favour of everything in that proposal. It solves several orthogonal use cases, and encourages composition.
I certainly see the benefits of having include, extend and mixin create a block context. That's great.
I think the @block
syntax might take a bit of getting used to, but given good syntax highlighting I think this could make the document pretty readable. I'm pretty sure I will be writing @block DEFAULT
a lot though, since it removes a bit of magic while scanning a document.
We also want syntactic shorthands for things that you do extremely frequently in well designed jade templates
Absolutely.
I don't think it makes sense to radically change the terminology here.
Absolutely 2.
bikeshedding
let
matches js variables.@replace
over @set
I just posted a feature request that maybe can be taken into consideration here: https://github.com/jadejs/jade/issues/2127
Another take might be to declare where the parent content goes:
div.card
header
block header
h2 Cool title
That was the same as 1.x, now let's check the other side:
extends ./card.jade
// replace
@header
h2 Much cooler title
// Append
@header
@parent
button.close x
// Prepend
@header
button.back back
@parent
// Any other combination
@header
div.wrapper
@parent
So my proposal is, instead of explicitly declaring a prepend or append which might need more workarounds in the future (as "wrap"), to be able to use the parent value. I am not completely happy with the previous syntax, maybe something like this is better:
include ./card.jade
@header
extends @header
button.close x
This would also allow extending, for example, on buttons:
// buttons.jade
div.buttons
block buttons
button.like Like
// buttons_user.jade
extend ./buttons.jade
@buttons
extends @buttons
button.edit Edit
// buttons_admin.jade (has 3 buttons)
extend ./buttons_user.jade
@buttons
extends @buttons
button.delete Delete
Another take might be to declare where the parent content goes: [...] So my proposal is, instead of explicitly declaring a prepend or append which might need more workarounds in the future (as "wrap"), to be able to use the parent value.
Your notation would mean that you can't create a block with a name of parent. Merging the parent idea (which and also be called inherited or something similar:
@block/let/declare <BLOCK NAME>
: declares a named block to be overridden elsewhere.
@set/replace <BLOCK NAME>
: override the content of a declared block.
@parent/inherited
: when called from inside a @set/replace <BLOCK NAME>
inserts the content of that block previous to this override.
Shorthands:
@block
=== @block/let/declare DEFAULT
content of a mixin call/extending template/include call expands to be inside @set/replace DEFAULT
.
layout.jade
doctype html
html
head
title My Layout
body
@block
script(src="/jquery.js")
To use this layout template, you can use extends
:
extends ./layout.jade
h1 My Child Page
or include
:
include ./layout.jade
h1 My Child Page
HTML generated by both:
<!DOCTYPE html>
<html>
<head>
<title>My Layout</title>
</head>
<body>
<h1>My Child Page</h1>
<script src="/jquery.js"></script>
</body>
</html>
layout.jade
doctype html
html
head
title My Layout
body
@block
script(src="/jquery.js")
dialog-component.jade
div.dialog(data-dialog)
div.dialog-heading
@block/let/declare heading
div.dialog-body
@block
It can be used as: child.jade
extends ./layout.jade
h1 My Child Page
include ./component.jade
@set heading
h2 My Dialog Heading
p My Content
HTML generated:
<!DOCTYPE html>
<html>
<head>
<title>My Layout</title>
</head>
<body>
<h1>My Child Page</h1>
<div data-dialog class="dialog">
<div class="dialog-heading">
<h2>My Dialog Heading</h2>
</div>
<div class="dialog-body">
<p>My Content</p>
</div>
</div>
<script src="/jquery.js"></script>
</body>
</html>
layout.jade
doctype html
html
head
title My Layout
@block/let/declare styles
body
@block
@block/let/declare scripts
child1.jade
extends ./layout.jade
h1 My Child Page
@set/replace styles
link(href='/css/style1.css', rel='stylesheet')
@set/replace scripts
script(src='/js/jquery.js')
child2.jade
extends ./child1.jade
@parent/inherited
h2 My Child Subtitle
//- Override
@set/replace styles
link(href='/css/stile2.css', rel='stylesheet')
//- Prepend
@set/replace styles
link(href='/css/base.css', rel='stylesheet')
@parent/inherited
//- Append
@set/replace scripts
@parent/inherited
script(src='/js/my_script.js')
//- Wrap
@set/replace scripts
// Scripts
@parent/inherited
// Scripts
HTML generated: child1:
<!DOCTYPE html>
<html>
<head>
<title>My Layout</title>
<link href="/css/stile1.css" rel="stylesheet">
</head>
<body>
<h1>My Child Page</h1>
<script src="/js/jquery.js"></script>
</body>
</html>
child2:
<!DOCTYPE html>
<html>
<head>
<title>My Layout</title>
<link href="/css/base.css" rel="stylesheet">
<link href="/css/stile2.css" rel="stylesheet">
</head>
<body>
<h1>My Child Page</h1>
<h2>My Child Subtitle</h2>
<!-- Scripts -->
<script src="/js/jquery.js"></script>
<script src="/js/my_script.js"></script>
<!-- Scripts -->
</body>
</html>
I'm reading through these...my specific case is that I wanted to include a folder of md files, which all need to be wrapped by various markup. I didn't want to have to build my own rendering pipeline, although it appears evident that with jade 1.x that is the only way forward. Jade 2.x may alleviate this, or Jade 3.x even.
I suppose what makes the most sense for me is to go ahead and build said rendering pipeline, then see about porting it to jade, since I'll need something similar, and I will want to think about things like ordering (I know this is a concern which has been brought up before).
I think in my ideal world I can do something like this:
mixin fancy_block(content)
block blah
include content
block blah
h3 Header
section
p Hello World!
each thing in blocks
+fancy_block(thing)
And I'll get something like:
p Hello World!
h3 Header
section
<insert content here>
h3 Header
section
<insert content here>
I've been doing some thinking about how to simplify blocks in jade 2.0.0. I'd love to get people's thoughts.
jade 1.x.x
yield
declares a block to be overridden when the template is includedblock
declares a block to be overridden when the mixin is calledblock <name>
declares or overrides a block by nameblock (append|prepend) <name>
append or prepend onto a blockextends ./path-to-file.jade
Load file, use blocks in this file to override blocks in the file being extendedinclude ./path-to-file.jade
Load file, use body as value foryield
keyword then munge content ofinclude
with current file.This is messy:
content
/body
/default
etc.jade 2.x.x
yield
andblock <name>
block
is a short hand forblock declare DEFAULT
, works in mixins, included files and extended filesblock <name>
overrides a block by name (note that it does not "declare" a block any more)block declare <NAME>
declares a block by name to be overrideninclude ./filename.jade
and+mixin_call
both accept a body. anything in the body that is not inside a block is automatically put inside a singleblock DEFAULT
extends ./filename.jade
is equivalent toinclude ./filename.jade
followed by an indent. It is only allowed as the first thing in a file.block (append|prepend) <name>
append or prepend onto a blockblock
that must be immediately followed by a block name. e.g.input(value=(block value))
This means that
+mixin_call
,include ./filename.jade
andextend ./filename.jade
all do essentially the same thing. They create a "Block Context".Examples
layout.jade
To use this layout template, you can use
extend
:child.jade
Could also have been written as:
Which is, itself a short hand for:
Either way the output would be:
This lets you write a neatly encapsulated component like:
dialog-component.jade
which is equivalent to:
It can be used as:
child-2.jade
child-2.jade