Introducing Blaze "modules": locally scoped Blaze templates

When building larger apps with Blaze, I’ve regularly run into one of the problems of global scope: unique names.
For example, I might have to 2 templates that would make sense to be named "asset’; one goes in the admin panel, the other in the main user view (we’ll call it the Asset Library)… Before today, I’d name them something like admin_assetLibrary_asset and assetLibrary_asset. I personally find this to be a pain, especially as the components get deeper.
With 1.3 coming out I had hopes for Blaze getting the modules treatment, but that’s not happening right now so I did it myself! :slight_smile:
Using the nathantreid:blaze-modules package, you can create Blaze templates that are no longer included in the global namespace and must instead be imported in JS. Don’t worry - you can still use normal Blaze templates too!
I have an example app on Github that includes mixing normal templates with the local templates, but following is a quick sample. The local templates use the <component> tag because I needed a different one than <template> and it’s the first one I came up with.

component1.html

<component name="component1">
Testing {{> component2}}
</component>

component2.html

<component name="component2">
1 2 3.
</component>

component1.js

import component1 from './component1.html';
import component2 from './component2.html';

component1.helpers{{ component2 });
24 Likes

Also, this package should be compatible with most Blaze enhancement packages, although I’ve only tested it with manuel:viewmodel. As noted by @manuel below, when used with ViewModel, make sure you expose child components as helpers or use a function to lazily evaluate them:

import hello from './hello.html';
import message from './message.html';

hello.helpers({
  message
});

hello.viewmodel({
  // View model as usual
});

or

import hello from './hello.html';
import message from './message.html';

hello.viewmodel({
  message() {return message; }
});
2 Likes

This is awesome! I bet it would be great to build this into future versions of blaze. Perhaps file an issue here:

2 Likes

Now that is a damn fine improvement for Blaze. Kudos, and thank you!

2 Likes

Curious what @manuel and @mitar think of this, it really brings Blaze into the future in a significant way. It would be cool to ensure it works with their frameworks.

2 Likes

Thanks, just created an issue!

1 Like

Eat this, all you people who claim Blaze has no future. :wink:

12 Likes

I think it’s awesome. I wish I had it when I was making the ViewModel documentation. As @nathantreid mentioned, I wouldn’t have had to name stuff like viewmodelsChildrenPeople

I want to review the whole “turn off hot code push persistence” thing though. It’s taking forever to update to rc.12 so I haven’t tried it yet =(

1 Like

Yeah, turning off state persistence on hot code push would be painful.

I’ve tracked the state persistence issue down to lines 110 / 111 in viewmodel.coffee:

if migrationData = Migration.get(vmHash)
            viewmodel.load(migrationData)

At line 109, viewmodel.message() (from my example app) returns a Template, but Migration.get returns the saved data (which of course is an Object) of that from prior to the reload, and viewmodel.load overwrites the Template with the object. I’ll see if I can fix it and submit a pull request.

1 Like

After further review of the play, I think it’s working as it should. Take the following piece:

import hello from './hello.html';
import message from './message.html';

hello.viewmodel({
  message
});

What you’re telling ViewModel is that it should store the template message in a property of the same name. As far as ViewModel is concerned, that template object is part of the view model’s state. When a HCP occurs ViewModel restores the message property to its previous state (with a template object which is now invalid).

The solution is to use a function to lazily evaluate the message template.

import hello from './hello.html';
import message from './message.html';

hello.viewmodel({
  message() {return message; }
});
1 Like

I’d probably just do:

import hello from './hello.html';
import message from './message.html';

hello.helpers({
  message
});

hello.viewmodel({
  // View model as usual
});
2 Likes

Ahh, can’t believe I missed that - that makes total sense!
“To a man with a hammer, everything looks like a nail”

3 Likes

Thanks a lot @nathantreid for this awesome package! :thumbsup:

Cheers!

1 Like

How far is it from hot module reload for Blaze? :wink:

5 Likes

@gadicc, continuing the discussion from What is import of html file?:

I wanted to bring that into this main discussion since I think it bears more thinking about.

Right now there is a limit of 1 per file. Perhaps I should do away with that in favor of one instance of a name per file (so if you have a <body> tag, you can’t have <component name="body"> in the same file; likewise if you have a <template name="hello"> you can’t also have a in the same file.
Then all templates / components could be imported; the downside is that being able to import a <template> might give the impression that templates are locally scoped when they aren’t.
I’m not sure which is better:

  1. only <component>s can be imported
  2. <template> and <component> can both be imported, but <template>s are still globally scoped, so given
<template name="hello"></template>

These are equivalent:

Template.body.helpers({ // helper code ....
// or
import { hello } from './hello.html';
body.helpers({ // helper code ....

I like having the default export, so perhaps I’d just make the first template / component in the file be the default export. Alternatively there could be a “default” attribute that could be set, although from where I stand right now I don’t see that being worth the effort.

I don’t think the scoping is the end of the world, it’s kind of how import statements are in Meteor 1.3 right now… in theory they’re optional because of the backwards compatibility (with globals), but we know it’s the right practice moving forward, and if you do import the component, you’re sure of what you’re getting. So I quite like the idea that it works on the <template />'s we know already too.

I had a few ideas re the default export, but none seemed like a perfect solution to me. e.g.

<!-- one unnamed component allowed per file, that will be the default export -->
<component>
  <!-- access to the local scope without helpers -->
  {{> component2}}
</component>

<!-- available via named imports -->
<component name="component2">
  ...
</component>

Problems:

  • In a big project, editing multiple files simultaneously, confusing not having named components.

I guess the first template in the file being the default export is ok… my only concern is that it’s not explicit enough. But I guess if you allow both it’s not really a big deal. I also thought about different behaviour depending on how many components were in the file, but obviously it’s a bad idea to have an existing imports in the code no longer getting what they expect if a 2nd component is added.

But yes, I think multiple components per file is a must. And I guess I raised the idea of local scoping inside the file, above. Which in the longer term, makes me wonder also about things like @import something from './someOtherFile.html too :> But now I’m just causing trouble.

P.S. In the interests of full disclosure, I’m no longer developing in Blaze. I wish the community hand off had happened much earlier.

1 Like

Just released nathantreid:blaze-modules@0.0.2, where I’m experimenting with support for multiple components in one file as well as exporting Blaze templates.
I’m still not sure how I feel about it exporting Blaze <template>s, but it could help ease the transition to <component>s.
The first template or component in the file will be the default export. All components, and all templates with a valid JS variable name, will also be exported by name.

components.html

<component name="component1">
Testing {{> component2}}
</component>

<component name="component2">
1 2 3.
</component>

<template name="template1">
a b c
</template>

Component names must be valid JS names, but for backwards-compatibility Template names don’t have to be valid JS names - so a Blaze Components template named Buttons.Red will not be exported by name, because that’s simply not valid, but if it’s the first <template/component> in the file it will be exported as the default.

The above templates can be imported like so:

import component1, { component2, template1 } from './components.html';

The other improvement is that all components below a given template or component in the file will automatically be added as helpers to above templates/components. So in the example above there is no need to do the following because the compiler does it for you:

component1.helpers({ component2 });

This is especially useful for dumb components (for example I regularly stick inline SVGs into their own template so the main markup is still readable). Since only components are added as helpers, it won’t break any existing Blaze templates.

4 Likes

This is awesome! Kudos on all the hard work.

1 Like

maybe we can work this up into a package? I just thought of a name for it: Blazy :stuck_out_tongue:

PS: mean’t to respond this in the lazy load thread… but looks like you’ve got another big piece here

2 Likes