{"id":4050,"date":"2015-09-02T15:07:05","date_gmt":"2015-09-02T23:07:05","guid":{"rendered":"https:\/\/blog.mozilla.org\/webdev\/?p=4050"},"modified":"2015-09-02T15:07:05","modified_gmt":"2015-09-02T23:07:05","slug":"node-js-static-file-build-steps-in-python-heroku-apps","status":"publish","type":"post","link":"https:\/\/blog.mozilla.org\/webdev\/2015\/09\/02\/node-js-static-file-build-steps-in-python-heroku-apps\/","title":{"rendered":"Node.js static file build steps in Python Heroku apps"},"content":{"rendered":"<p>I write a lot of webapps. I like to use Python for the backend, but most<br \/>\nfrontend tools are written in Node.js. LESS gives me nicer style sheets, Babel<br \/>\nlets me write next-generation JavaScript, and NPM helps manage dependencies<br \/>\nnicely. As a result, most of my projects are polyglots that can be difficult to<br \/>\ndeploy.<\/p>\n<p>Modern workflows have already figured this out: <em>Run all the tools<\/em>. Most<br \/>\nREADMEs I&#8217;ve written lately tend to look like this:<\/p>\n<pre><code>$ git clone https:\/\/github.example.com\/foo\/bar.git\n$ cd git\n$ pip install -r requirements.txt\n$ npm install\n$ gulp static-assets\n$ python .\/manage.py runserver\n<\/code><\/pre>\n<p>I like to deploy my projects using Heroku. They take care of the messy details<br \/>\nabout deployment, but they don&#8217;t seem to support multi-language projects easily.<br \/>\nThere are Python and Node buildpacks, but no clear way of combining the two.<\/p>\n<h1>Multi Buildpack<\/h1>\n<p><a href=\"https:\/\/github.com\/search?utf8=%E2%9C%93&amp;q=heroku+buildpack+python+node&amp;type=Repositories&amp;ref=searchresults\">GitHub is littered with attempts to fix this by building new buildpacks.<\/a><br \/>\nThe problem is they invariable fall out of compatibility with Heroku. I could<br \/>\nprobably fix, but then <em>I&#8217;d<\/em> have to maintain them. I use Heroku to avoid<br \/>\nmaintaining infrastructure; custom buildpacks are one step forward, but two<br \/>\nsteps back.<\/p>\n<p>Enter <a href=\"https:\/\/github.com\/heroku\/heroku-buildpack-multi\">Multi Buildpack<\/a>, which runs multiple buildpacks at once.<\/p>\n<p>It is simple enough that it is unlike to fall out of compatibility. Heroku has a<br \/>\nfork of the project on their GitHub account, which implies that it will be<br \/>\nmaintained in the future.<\/p>\n<p>To configure the buildpack, first tell Heroku you want to use it:<\/p>\n<pre><code>$ heroku buildpacks:set https:\/\/github.com\/heroku\/heroku-buildpack-multi.git\n<\/code><\/pre>\n<p>Next, add a <code>.buildpacks<\/code> file to your project that lists the buildpacks to run:<\/p>\n<pre><code>https:\/\/github.com\/heroku\/heroku-buildpack-nodejs.git\nhttps:\/\/github.com\/heroku\/heroku-buildpack-python.git\n<\/code><\/pre>\n<p>Buildpacks are executed in the order they&#8217;re listed in, allowing later<br \/>\nbuildpacks to use the tools and scripts installed by earlier buildpacks.<\/p>\n<h1>The Problem With Python<\/h1>\n<p>There&#8217;s one problem: The Python buildpack moves files around, which makes it<br \/>\nincompatible with the way the Node buildpack installs commands. This means that<br \/>\nany asset compilation or minification done as a step of the Python buildpack<br \/>\nthat depends on Node will fail.<\/p>\n<p>The Python buildpack automatically detects a Django project and runs<br \/>\n<code>.\/manage.py collectstatic<\/code>. But the Node environment isn&#8217;t available, so this<br \/>\nfails. No static files get built.<\/p>\n<p>There is a solution: <code>bin\/post_compile<\/code>! If present in your repository, this<br \/>\nscript will be run at the end of the build process. Because it runs outside of<br \/>\nthe Python buildpack, commands installed by the Node buildpack are available and<br \/>\nwill work correctly.<\/p>\n<p>This trick works with any Python webapp, but lets use a Django project as an<br \/>\nexample. I often use <a href=\"https:\/\/github.com\/cyberdelia\/django-pipeline\">Django Pipeline<\/a> for static asset compilation. Assets<br \/>\nare compiled using the command <code>.\/manage.py collectstatic<\/code>, which, when properly<br \/>\nconfigured, will call all the Node commands.<\/p>\n<pre><code>#!\/bin\/bash\nexport PATH=\/app\/.heroku\/node\/bin:$PATH\n.\/manage.py collectstatic --noinput\n<\/code><\/pre>\n<p>Alternatively, you could call Node tools like Gulp or Webpack directly.<\/p>\n<p>In the case of Django Pipeline, it is also useful to disable the Python<br \/>\nbuildpack from running <code>collectstatic<\/code>, since it will fail anyways. This is done<br \/>\nusing an environment variable:<\/p>\n<pre><code>heroku config:set DISABLE_COLLECTSTATIC 1\n<\/code><\/pre>\n<blockquote>\n<p>Okay, so there is a little hack here. We still had to append the Node binary<br \/>\n  folder to <code>PATH<\/code>. Pretend you didn&#8217;t see that! Or don&#8217;t, because you&#8217;ll need<br \/>\n  to do it in your script too.<\/p>\n<\/blockquote>\n<h1>That&#8217;s it<\/h1>\n<p>To recap, this approach:<\/p>\n<ol>\n<li>Only uses buildpacks available from Heroku<\/li>\n<li>Supports any sort of Python and\/or Node build steps<\/li>\n<li>Doesn&#8217;t require vendoring or pre-compiling any static assets<\/li>\n<\/ol>\n<p>Woot!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>I write a lot of webapps. I like to use Python for the backend, but most frontend tools are written in Node.js. LESS gives me nicer style sheets, Babel lets me write next-generation JavaScript, and NPM helps manage dependencies nicely. &hellip; <a class=\"go\" href=\"https:\/\/blog.mozilla.org\/webdev\/2015\/09\/02\/node-js-static-file-build-steps-in-python-heroku-apps\/\">Continue reading<\/a><\/p>\n","protected":false},"author":522,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[4730,4712,288],"tags":[],"coauthors":[28310],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v22.5 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>Node.js static file build steps in Python Heroku apps - Mozilla Web Development<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/blog.mozilla.org\/webdev\/2015\/09\/02\/node-js-static-file-build-steps-in-python-heroku-apps\/\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Mike Cooper\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"3 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"WebPage\",\"@id\":\"https:\/\/blog.mozilla.org\/webdev\/2015\/09\/02\/node-js-static-file-build-steps-in-python-heroku-apps\/\",\"url\":\"https:\/\/blog.mozilla.org\/webdev\/2015\/09\/02\/node-js-static-file-build-steps-in-python-heroku-apps\/\",\"name\":\"Node.js static file build steps in Python Heroku apps - Mozilla Web Development\",\"isPartOf\":{\"@id\":\"https:\/\/blog.mozilla.org\/webdev\/#website\"},\"datePublished\":\"2015-09-02T23:07:05+00:00\",\"dateModified\":\"2015-09-02T23:07:05+00:00\",\"author\":{\"@id\":\"https:\/\/blog.mozilla.org\/webdev\/#\/schema\/person\/bb6958c7ff0ffcccfd5c08782784de8a\"},\"breadcrumb\":{\"@id\":\"https:\/\/blog.mozilla.org\/webdev\/2015\/09\/02\/node-js-static-file-build-steps-in-python-heroku-apps\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/blog.mozilla.org\/webdev\/2015\/09\/02\/node-js-static-file-build-steps-in-python-heroku-apps\/\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/blog.mozilla.org\/webdev\/2015\/09\/02\/node-js-static-file-build-steps-in-python-heroku-apps\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/blog.mozilla.org\/webdev\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Node.js static file build steps in Python Heroku apps\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/blog.mozilla.org\/webdev\/#website\",\"url\":\"https:\/\/blog.mozilla.org\/webdev\/\",\"name\":\"Mozilla Web Development\",\"description\":\"For make benefit of glorious tubes\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/blog.mozilla.org\/webdev\/?s={search_term_string}\"},\"query-input\":\"required name=search_term_string\"}],\"inLanguage\":\"en-US\"},{\"@type\":\"Person\",\"@id\":\"https:\/\/blog.mozilla.org\/webdev\/#\/schema\/person\/bb6958c7ff0ffcccfd5c08782784de8a\",\"name\":\"Mike Cooper\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/blog.mozilla.org\/webdev\/#\/schema\/person\/image\/34a178a4b71dd2041f19aa86aa1c830b\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/a971e3a64dcecd173a23edb1e8118a6c?s=96&d=mm&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/a971e3a64dcecd173a23edb1e8118a6c?s=96&d=mm&r=g\",\"caption\":\"Mike Cooper\"},\"description\":\"Mike is one of the web developers on the SUMO team. He writes Python and Javascript, and runs Linux, much to the detriment of his focus.\",\"sameAs\":[\"http:\/\/mythmon.com\",\"https:\/\/x.com\/mythmon\"],\"url\":\"https:\/\/blog.mozilla.org\/webdev\/author\/mcoopermozilla-com\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Node.js static file build steps in Python Heroku apps - Mozilla Web Development","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/blog.mozilla.org\/webdev\/2015\/09\/02\/node-js-static-file-build-steps-in-python-heroku-apps\/","twitter_misc":{"Written by":"Mike Cooper","Est. reading time":"3 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/blog.mozilla.org\/webdev\/2015\/09\/02\/node-js-static-file-build-steps-in-python-heroku-apps\/","url":"https:\/\/blog.mozilla.org\/webdev\/2015\/09\/02\/node-js-static-file-build-steps-in-python-heroku-apps\/","name":"Node.js static file build steps in Python Heroku apps - Mozilla Web Development","isPartOf":{"@id":"https:\/\/blog.mozilla.org\/webdev\/#website"},"datePublished":"2015-09-02T23:07:05+00:00","dateModified":"2015-09-02T23:07:05+00:00","author":{"@id":"https:\/\/blog.mozilla.org\/webdev\/#\/schema\/person\/bb6958c7ff0ffcccfd5c08782784de8a"},"breadcrumb":{"@id":"https:\/\/blog.mozilla.org\/webdev\/2015\/09\/02\/node-js-static-file-build-steps-in-python-heroku-apps\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/blog.mozilla.org\/webdev\/2015\/09\/02\/node-js-static-file-build-steps-in-python-heroku-apps\/"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/blog.mozilla.org\/webdev\/2015\/09\/02\/node-js-static-file-build-steps-in-python-heroku-apps\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/blog.mozilla.org\/webdev\/"},{"@type":"ListItem","position":2,"name":"Node.js static file build steps in Python Heroku apps"}]},{"@type":"WebSite","@id":"https:\/\/blog.mozilla.org\/webdev\/#website","url":"https:\/\/blog.mozilla.org\/webdev\/","name":"Mozilla Web Development","description":"For make benefit of glorious tubes","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/blog.mozilla.org\/webdev\/?s={search_term_string}"},"query-input":"required name=search_term_string"}],"inLanguage":"en-US"},{"@type":"Person","@id":"https:\/\/blog.mozilla.org\/webdev\/#\/schema\/person\/bb6958c7ff0ffcccfd5c08782784de8a","name":"Mike Cooper","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/blog.mozilla.org\/webdev\/#\/schema\/person\/image\/34a178a4b71dd2041f19aa86aa1c830b","url":"https:\/\/secure.gravatar.com\/avatar\/a971e3a64dcecd173a23edb1e8118a6c?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/a971e3a64dcecd173a23edb1e8118a6c?s=96&d=mm&r=g","caption":"Mike Cooper"},"description":"Mike is one of the web developers on the SUMO team. He writes Python and Javascript, and runs Linux, much to the detriment of his focus.","sameAs":["http:\/\/mythmon.com","https:\/\/x.com\/mythmon"],"url":"https:\/\/blog.mozilla.org\/webdev\/author\/mcoopermozilla-com\/"}]}},"jetpack_featured_media_url":"","_links":{"self":[{"href":"https:\/\/blog.mozilla.org\/webdev\/wp-json\/wp\/v2\/posts\/4050"}],"collection":[{"href":"https:\/\/blog.mozilla.org\/webdev\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.mozilla.org\/webdev\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.mozilla.org\/webdev\/wp-json\/wp\/v2\/users\/522"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.mozilla.org\/webdev\/wp-json\/wp\/v2\/comments?post=4050"}],"version-history":[{"count":0,"href":"https:\/\/blog.mozilla.org\/webdev\/wp-json\/wp\/v2\/posts\/4050\/revisions"}],"wp:attachment":[{"href":"https:\/\/blog.mozilla.org\/webdev\/wp-json\/wp\/v2\/media?parent=4050"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.mozilla.org\/webdev\/wp-json\/wp\/v2\/categories?post=4050"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.mozilla.org\/webdev\/wp-json\/wp\/v2\/tags?post=4050"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/blog.mozilla.org\/webdev\/wp-json\/wp\/v2\/coauthors?post=4050"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}