Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

webpack: Use handlebars-loader to handle frontend templates. #12667

Closed
wants to merge 2 commits into from

Conversation

tommyip
Copy link
Member

@tommyip tommyip commented Jun 28, 2019

And remove the compile-handlebars-templates system.

+ ". If you are developing a new feature, this likely "
+ "means you need to add the file static/templates/"
+ name + ".handlebars");
for (var i = 0; i < template_paths.length; i += 1) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can do this or add the path to all the references to settings/widgets.

Copy link
Member

@andersk andersk Jun 28, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whoa. I didn’t realize webpack has special support for require() on computed strings like that, but yes, apparently it’s smart enough to parse that computation, find all the *.handlebars files within static/templates, compile them, and bundle them into a map for runtime dispatch. Wouldn’t have been my first plan but I guess it works!

We don’t want to catch exceptions from rendering here, so the (arg) should be lifted outside the try…catch. And using require.context might help webpack generate slightly more efficient code:

var template_context = require.context('../templates', true, /\.handlebars$/);
var template_paths = ['./', './settings/', './widgets/'];

exports.render = function (name, arg) {
    for (var i = 0; i < template_paths.length; i += 1) {
        var template;
        try {
            template = template_context(template_paths[i] + name + '.handlebars');
        } catch (_e) {
            continue;
        }
        return template(arg);
    }
    throw new Error('Cannot find template ' + name + '.handlebars anywhere '
        + 'under the static/templates/ folder.');
};

(Edit: require.context doesn’t work in the Node tests, so skip that part.)

I think I’d be in favor of adding explicit paths for settings and widgets so as to reduce the amount of magic here.

Copy link
Sponsor Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, the only reason we didn't have explicit paths for rendering settings/foo.handlebars was because the old system didn't support naming things that way. So I think passing e.g. settings/foo.handlebars into .render is the right approach over doing a loop here.

@@ -16,7 +16,7 @@ import "blueimp-md5/js/md5.js";
import "clipboard/dist/clipboard.js";
import "string.prototype.codepointat/codepointat.js";
import "winchan/winchan.js";
import "handlebars/dist/handlebars.runtime.js";
import "handlebars/dist/cjs/handlebars.runtime.js";
Copy link
Sponsor Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the story with this change? Are those two both files in the module, or what?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When importing the module without explicitly specifying the path, ie import "handlebars/runtime";, webpack actually resolve it to handlebars/dist/cjs/handlebars.runtime.js. Also after adding handlebars-loader, the original Handlebars from handlebars/dist/handlebars.runtime.js can't find the templates anymore for some reason.

Also is there a reason why we specify the exact file we import instead of just giving the name?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In some cases, it makes a difference because whatever/dist/whatever.js adds itself to the global window namespace while whatever is a module that expects to be used the way modules are supposed to be used. We have lots of code that pulls things from window but we should migrate to the module style whenever it’s convenient. In this case, go with handlebars/runtime if you can; that’s the documented way.

Copy link
Sponsor Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, the window hack is a mess but solved the problem of being able to migrate most asset stuff into webpack. If we can do import handlebars/runtime, that'd be great.

// Tell webpack not to explicitly require these.
knownHelpers: ['if', 'unless', 'each', 'with',
'partial', 'plural', 'eq', 'and', 'or', 'not',
't', 'tr'],
Copy link
Sponsor Member

@timabbott timabbott Jun 28, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the story with this list? Does it need to be synchronized with some list elsewhere in the codebase?

If so we should add comments in both places making clear the need to sync the lists, and I'd be curious to know what the exception is if we do it wrong.

Copy link
Member Author

@tommyip tommyip Jun 29, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are the helpers defined in templates.js combined with the ones we originally passed to handlebars in compile-handlebar-templates line 33. This is to let webpack know these helpers are defined at run time and don't try to require them when bundling. Nothing will happen if these list is not correct, handlebar-loader just have to do more guessing work.

I'll add some comments.

Copy link
Sponsor Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK.

@tommyip
Copy link
Member Author

tommyip commented Jun 29, 2019

Removed the loop and lifted the (arg).

@timabbott
Copy link
Sponsor Member

Awesome, I'm going to test-deploy this on chat.zulip.org and merge it if it works, since the remaining comments seem like things we can do as follow-ups.

@timabbott
Copy link
Sponsor Member

Seems to work in production, so merged, thanks @tommyip! I do think it's still worth looking at whether we can do something nicer for our third-party packages with #12667 (comment).

@timabbott timabbott closed this Jul 2, 2019
@tommyip tommyip deleted the use-handlebars-loader branch July 3, 2019 01:22
@tommyip
Copy link
Member Author

tommyip commented Jul 3, 2019

Yeah, the window hack is a mess but solved the problem of being able to migrate most asset stuff into webpack. If we can do import handlebars/runtime, that'd be great.

it makes a difference because whatever/dist/whatever.js adds itself to the global window namespace while whatever is a module that expects to be used the way modules are supposed to be used

We already use webpack expose-loader to attach a lot of our libraries to window, might as well be consistent and do that for everything and use the normal importing style. (And overtime do the importing inside individual modules as part of the typescript migration)

@andersk
Copy link
Member

andersk commented Jul 3, 2019

No, see, this isn’t about consistency vs. inconsistency, it’s about right vs. wrong. Attaching everything to window wasn’t a stylistic choice. It was a necessary transitional step on the path from where Zulip started, with no Webpack or bundler of any kind except the one where you strcat all your libraries together, to where it should be. It’s an antipattern that isn’t type-safe and weakens the ability for both humans and tools to reason about your code. You can configure TypeScript and ESLint to expect that window has been altered, but there’s no static guarantee that your configuration matches reality. Webpack can’t see through this pattern so optimizations like tree shaking are inhibited. IDEs can’t show you their fancy auto-complete menus. Plus it’s just not what modern JS developers do. I’m not saying we need to drop everything and fix it all right now, but we shouldn’t use “consistency” as a justification for perpetuating it.

@tommyip
Copy link
Member Author

tommyip commented Jul 3, 2019

Yeah, that's why I said

overtime do the importing inside individual modules as part of the typescript migration

which I am already in the process of doing (as well as other stuff to give webpack and editors better information)

What I mean by consistency is the way we configure our third party library is inconsistent. Some library such as moment is attached to window as a side effect (import "moment/min/moment.min.js";) while some uses expose-loader, which is confusing.

tommyip added a commit to tommyip/zulip that referenced this pull request Jul 3, 2019
timabbott pushed a commit that referenced this pull request Jul 3, 2019
andersk added a commit to andersk/zulip that referenced this pull request Jul 17, 2019
As of commit 8c199fd (zulip#12667) this
file is no longer generated.  Handlebars compile errors are raised as
webpack errors.

Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
andersk added a commit to andersk/zulip that referenced this pull request Jul 17, 2019
As of commit 8c199fd (zulip#12667) this
file is no longer generated.  Handlebars compile errors are raised as
webpack errors.

Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
andersk added a commit to andersk/zulip that referenced this pull request Jul 17, 2019
As of commit 8c199fd (zulip#12667) this
file is no longer generated.  Handlebars compile errors are raised as
webpack errors.

Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
timabbott pushed a commit that referenced this pull request Jul 17, 2019
As of commit 8c199fd (#12667) this
file is no longer generated.  Handlebars compile errors are raised as
webpack errors.

Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants