How to make Meteor append its JS files **after** JS libs from a CDN


#1

In my root HTML, I include react from a CDN in the header.
However, Meteor appends all of its JS files before everything else, so React isn’t available when they run.

Does anyone know any workarounds?

<head>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.13.3/react-with-addons.js"></script>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
  <meta name="description" content="">
  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
  <title>asset-tracker</title>
  <link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.5/css/bootstrap.min.css">
</head>

<body>
  <div id="app"></div>
</body>

#2

I know this doesn’t answer your question, but why load React from a CDN?


#3

Probably no big deal really, would just be nice to reduce the traffic from our servers / take advantage of the fact that probably a lot of people have React 0.13.3 cached. But perhaps the real reason I posted this is I thought it would be easy to do, and it turned out not to be.


#4

I’m just trying to fulfill my boss’ wishes on this. I get angry when I find it so difficult to coerce a tool to do something as simple as put a <script> tag in a certain position in the generated HTML header.


#5

In an application I’m developing, I have to make sure a certain js library (stripe) is loaded before the view renders, but I only want to load the library for people who have to visit that view.

I handle this by loading the library in iron router’s onBeforeAction hook. I load the js library with Jquery’s getScript function so that I get a callback after it is loaded, and know for sure that it is safe to load my view.


#6

There is no an official API for this, one thing that you could do is to fork the boilerplate-generator package from Meteor Core and put a modified copy into your app’s packages folder. And edit the template to what you want.


#7

@wallslide @slava Cool, thanks for the suggestions guys!
The irony is that I’m using react-router, which of course depends on React, which is just the library I’m trying to load via CDN :stuck_out_tongue: But perhaps I can load the script manually. Or when in doubt, fork.


#8

Could it theoretically be done to have a package that does that BUT imports the contents in the package dynamically on each build from say a JSON file?

Not on a PC right now but could it be that the nemo64 bootstrap package is doing something sort of similar already with the only exception being that the sources are fixed in the package?

I’d love to get my hands dirty with trying to write a simple package and that sounds like something I’d use.


#9

@slava I just finally dug into forking boilerplate-generator. This time it’s not to load React from a CDN, but rather to inject script links to http://localhost:9090/assets/... for Webpack bundles in specific places for my meteor-webpack-react project. If I succeed in this I can accomplish full-blown React hot loading.

I’m looking for a more robust solution than hard-coding these links into the boilerplate-generator spacebars template.

I see that WebApp is reading data from .meteor/local/build/programs/web.browser/program.json, which might be built from each package’s web.browser.json, and then passing that to Boilerplate. But I don’t know what creates those files, does anyone know? I’m hoping I can fork something to create an api for Packages to add custom head elements much like addFile.


#10

If I could have it my way, I’d make it work where if package adds an HTML file, the contents of that file’s <head> if any get injected in the generated <head> immediately after that package’s JS bundle. <body> tags could be concatenated likewise.


#11

How do I make sure that my scripts FROM a CDN (many) are loaded before “merged-stylesheets.css” is injected at the top of the HEAD tag?


#12

@jonathanpmartins I finally figured out one way to do it. Any request handler you use in WebApp.rawConnectHandlers runs before the boilerplate generator and so forth:

  // webserver
  var app = connect();

  // Packages and apps can add handlers that run before any other Meteor
  // handlers via WebApp.rawConnectHandlers.
  var rawConnectHandlers = connect();

So there are two ways you could use a custom request handler to solve the problem:

  1. Get the HTML generated by Meteor, then insert your script before the first <script> tag
  2. Just generate the entire HTML yourself

#13

@jedwards Thank you. I think this solves this help post that I opened: Load CDN (many) scripts before "merged-stylesheets.css" on the <HEAD> tag
Lets see if I get it, by inserting my scripts before the first script tag I need to use insertBefore, right?
Something like this:

var cdnStyles = [
    'https://cdnjs.cloudflare.com/ajax/libs/animate.css/3.5.2/animate.min.css',
    'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.6.3/css/font-awesome.min.css',
    'https://cdnjs.cloudflare.com/ajax/libs/simple-line-icons/2.3.2/css/simple-line-icons.min.css',
    'https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap-theme.min.css',
    'https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css',
];
var cdnScripts = [
    'https://ajax.googleapis.com/ajax/libs/jquery/2.2.4/jquery.min.js',
    'https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js',
    'https://ajax.googleapis.com/ajax/libs/angularjs/1.5.7/angular.min.js'
];
var headFirstChild = document.querySelector('head').firstChild;

for (var i = 0; i < cdnStyles.length; i++) {
    var style = document.createElement('link');
    style.setAttribute('href', cdnStyles[i]);
    document.querySelector('head').insertBefore(headFirstChild, style);
}
for (var i = 0; i < cdnScripts.length; i++) {
    var script = document.createElement('script');
    script.setAttribute('src', cdnScripts[i]);
    document.querySelector('head').insertBefore(headFirstChild, style);
}

I tried to use insertBefore, but it doest work properly. I think I’m missing something…
Think I need read more the docs about the rawConnectHandlers


#14

No rawConnectHandlers runs on the server. You’d need to intercept the HTTP that Meteor would send to the client (haven’t looked into how to do that) and do something like

text.replace(
  /<script>/, 
  [
    ...cdnStyles.map(style => `<link rel="stylesheet" type="text/css" href="${style}">`),
    ...cdnScripts.map(script => `<script src="${script}"></script>`)
  ].join('\n') + '<script>'
)

before sending it to the client.


#15

oh… now I get it. Thank you very much @jedwards