Discourse-common asset availability: difference between development and production?

I’m adapting the locations plugin to work better within the context of the custom wizard plugin.

The locations plugin has a component called location-selector, which is essentially an autocomplete input for geocoding locations. It uses the Discourse autocomplete jQuery plugin, which requires a raw template.

As the raw templating system is not available in the wizard app (e.g. the Discourse.RAW_TEMPLATES constant is not pre-loaded), and I want this component to work across both apps, I’ve assigned the raw template to a constant in the component file and used the various RawHandlebars methods in discourse-common/lib/raw-handlebars, e.g.

import RawHandlebars from 'discourse-common/lib/raw-handlebars';

const autocompleteTemplate = "<div class='autocomplete'><ul>{{#each options as |o|}}{{#if o.no_results}}<div class='no-results'>{{i18n 'location.geo.no_results'}}</div>{{else}}{{#if o.provider}} <label>{{{i18n 'location.geo.desc' provider=o.provider}}}</label> {{else}} <li class='ac-form-result'><label>{{geo-location-format o geoAttrs=o.geoAttrs}}</label> {{#if o.showType}} {{#if o.type}} <div class='ac-type'> {{o.type}} </div> {{/if}} {{/if}} </li> {{/if}} {{/if}} {{/each}} </ul></div>";
...

this.$().val(val).autocomplete({
   template: RawHandlebars.compile(autocompleteTemplate),
...

This works fine in development (in both normal Discourse and the Wizard app), however it fails in production in the normal Discourse app with

n.default.compile is not a function

(you can see this error currently here if you compose a post and click “Add Location”). Note, importing specific functions (e.g. ‘compile’) doesn’t fix it.

For some reason, discourse-common/lib/raw-handlebars is not loaded or required the same in production as it is in development.

I’m sure there’s an obvious reason why this is not working in production, but I’m just not seeing it at the moment. Any ideas welcome.

3 Likes

Ok, I figured this out.

Production includes handlebars.runtime.js instead of handlebars.js (see javascripts/template_include.js), which doesn’t include compile, as it is assumed the templates are pre-compiled (see here).

So I could just include handlebars.js in production, but that would be cheating. Rather I’ll need to figure out the best way to precompile raw templates for the wizard app…

Sorry, I meant to respond to this topic when I saw it last week, but completely forgot. You may be interested in how we compile templates for themes:

We have extended versions of the compilers for some theme-specific functionality, but for basic use you can use Barber::Precompiler for raw templates, and Barber::Ember::Precompiler for ember templates.

It is indeed confusing that the compiler is loaded on the client in dev, but not production :thinking:. It seems to me that it would be better to keep things the same, but maybe there is some reason for it.

5 Likes

Thanks! Yeah I saw that and thought that’d be the way, so good to have confirmation :+1:

3 Likes

The basic implementation of this in the custom wizard will be

Discourse.unofficial_plugins.each do |plugin|
  plugin_name = plugin.metadata.name
  if require_plugin_assets = CustomWizard::Field.require_assets[plugin_name]
    plugin.each_globbed_asset do |f, is_dir|
      if f.include? "raw.hbs"
        name = File.basename(f, ".raw.hbs")
        compiled = Barber::Precompiler.new().compile(File.read(f))
        result << "
          (function() {
            if ('Wizard' in window) {
              Wizard.RAW_TEMPLATES['#{name}'] = requirejs('discourse-common/lib/raw-handlebars').template(#{compiled});
            }
          })();
        "
      end
    end
  end
end

Which just requires a plugin to include ‘templates’ in the assets they add to the wizard asset pipeline, e.g.

CustomWizard::Field.add_assets('location', 'discourse-locations', ['components', 'helpers', 'lib', 'stylesheets', 'templates'])

The upshot being, I can access the pre-compiled a raw template in a common component by just switching the global namespace:

const global = this.get('global');
let template = global.RAW_TEMPLATES['javascripts/location-autocomplete'];
2 Likes