Brunch built a script for me that won't run

I would like to use Brunch to build two separate scripts, app.js and admin.js. It took me long enough to figure out how to include node_modules in app.js but I have it working properly now. My current issue is that while I am building admin.js it won’t run. The confusing part is that I can run the exact same setup with /vendor/ and it works as desired.

brunch-config.js

...
joinTo: {
  "js/app.js": /^(js|node_modules)/,
  "js/admin.js": /^(admin)/
}
...
watched: ["static", "css", "js", "vendor", "admin"],
...

…/assets/admin/admin.js

console.log('ADMIN!');

…/lib/admin_web/templates/layout/app.html.eex

...
<script src="<%= static_path(@conn, "/js/app.js") %>"></script>
<script src="<%= static_path(@conn, "/js/admin.js") %>"></script>
...

Whereas what I have working with “vendor” is essentially the same:

brunch-config.js

...
joinTo: {
  "js/app.js": /^(js|node_modules)/,
  "js/vendor.js": /^(vendor)/
}
...

…/assets/vendor/vendor.js

console.log('VENDOR!');

…/lib/vendor_web/templates/layout/app.html.eex

...
<script src="<%= static_path(@conn, "/js/app.js") %>"></script>
<script src="<%= static_path(@conn, "/js/vendor.js") %>"></script>
...

I can log “VENDOR!” to the console all day long but haven’t seen “ADMIN!” once. I’ve tried switching my “admin.js” regex to /^(admin|node_modules)/, no dice. I’ve tried ignore: [/admin/] to skip ES6 transpilation, no dice. I can see my console.log('ADMIN!'); in Chrome’s dev tools but it’s wrapped in some other code and never fires and no errors are thrown.

Am I missing some magic or did I overlook something obvious?

  1. I have no idea how the vendor example worked (which suggests there is something missing from the narrative).
  2. In the end it suggests that you don’t understand how modules work.

CommonJS version:

// file: assets/admin/first.js
// CommonJS module

module.exports = {
  first: 42
};

console.log('ADMIN/FIRST');

and

...
// Import local files
//
// Local files can be imported directly using relative
// paths "./socket" or full ones "web/static/js/socket".

// import socket from "./socket"

const { first } = require('../admin/first');
console.log(first);

// from file: assets/js/app.js
// CommonJS

ES2015 version

// file: assets/admin/first.js
// ES2015 module (requires babel)

export const info = { // named export
  first: 42
};

export default info; // default export

console.log('ADMIN/FIRST');

and

...
// Import local files
//
// Local files can be imported directly using relative
// paths "./socket" or full ones "web/static/js/socket".

// import socket from "./socket"

import { info } from '../admin/first';  // import from export named "info"
import something from '../admin/first'; // import from default export

console.log(info.first);

const { first } = something;            // destructuring assignment
console.log(first);

// from assets/js/app.js
// ES2015

In short that console.log('ADMIN/FIRST') is only run when the file is required or imported - which can only happen if the file exports something in the first place.

In app.html.eex you have to be careful to provide the dependency before it is used i.e.

 <script src="<%= static_path(@conn, "/js/admin.js") %>"></script>
 <script src="<%= static_path(@conn, "/js/app.js") %>"></script>

so admin.js has to come before app.js where it is used.

The only changes necessary in the brunch-config.js were

exports.config = {
  // See http://brunch.io/#documentation for docs.
  files: {
    javascripts: {
      joinTo: {
        "js/app.js": /^(?!admin)/,
        "js/admin.js": /^(admin)/
      }

...

  // Phoenix paths configuration
  paths: {
    // Dependencies and current project directories to watch
    watched: ["static", "css", "js", "admin"],

...

However you don’t have to use regular expressions, you can use an array of file globs as well - see Pattern Matching.

Other points:

2 Likes

Thanks for the help. I feel I have a good grasp on JavaScript modules, but I’m not trying to create a module. I just want Brunch to build a very simple file.

I’ve put together a repository to demonstrate. In the master branch, app.js logs “app.js”. In the vendor branch, vendor.js logs “vendor.js”. In the admin branch, admin.js logs…nothing.

If the answer is to switch to webpack, so be it. It just seems that what I’m attempting to do is so simple that I must be missing something obvious.

1 Like

Both repositories suggest that you have a mental model of concatenating scripts - there is more to JavaScript modules than that.

If the answer is to switch to webpack

It won’t be if you are trying to use JavaScript libraries that haven’t been written with JavaScript modules in mind.

If you are using pre-module-age libraries Brunch and webpack are out of the window.

I switched to webpack. Now everything just works.

webpack branch:

Not to mention, the switch to webpack was easier than I expected.

1 Like

Interesting.

Essentially you are using webpack’s Multi Page Application setup - intended to generate bundles for separate pages - and then concatenating all three bundles in the same page.

My intention was never to run separate bundles on the same page. I only did that to keep the demo as simple as possible.

Whether the Brunch-built admin.js was on a page with app.js or all by itself it never worked. The same script works quite easily when built by webpack.

The question is whether it will keep working as you move forward (it may). From what I can tell you are using the tool in a way it wasn’t intended to be used. That’s all.

The fundamental question is: why are using a module bundler when you don’t intend to use JavaScript modules or libraries that are structured with the JavaScript modules.

1 Like