The difference between ember serve and npm run start

The difference between ember serve and npm run start

When you have built a single-page-application using Ember CLI, you have two options for starting your app locally. You can either use the CLI's ember serve command directly or you can use the handy npm alias that is created for you after generating a new Ember app: npm run start

But what's the difference between those two?

What npm run start is used for

First, npm run start allows you to create your own default boot up command for your application by e.g. passing additional parameters to ember serve and hide this modified booting instruction away in one, single, shorthand command via a definition in your package.json. This makes you type less and can make your life easier when developing your app locally.

If you want to learn more about how to modify the ember serve command, check out the official Ember CLI Docs.

In a fresh Ember app though, the functionality of both ember serve and npm run start will be almost identical. Emphasis on almost.

Sometimes ember serve and npm run start don't behave the same way

Recently, while working on one of my side projects, I tried to boot up an Ember application using TailwindCSS by running the ember serve command. The build process was kicked off, but ultimately failed with the following error message:

Object.entries(...).flatMap is not a function

The stack trace pointed to issues with the PostCSS compiler I had been using to integrate TailwindCSS with my app's styles:

Object.entries(...).flatMap is not a function
    at PostcssCompiler.Plugin (/home/jayjayjpg/Documents/projects/my/project/node_modules/broccoli-plugin/index.js:7:31)
    at new CachingWriter (/home/jayjayjpg/Documents/projects/my/project/node_modules/broccoli-caching-writer/index.js:18:10)
    at new PostcssCompiler (/home/jayjayjpg/Documents/projects/my/project/node_modules/broccoli-postcss-single/index.js:20:5)
    at Object.keys.map (/home/jayjayjpg/Documents/projects/my/project/node_modules/ember-cli-postcss/index.js:36:12)
// ...

After some debugging and double-checking my setup that I have followed from Chris Masters' working example for TailwindCSS in an Ember app in my ember-cli-build.js was correct:

// ember-cli-build.js
'use strict';

const EmberApp = require('ember-cli/lib/broccoli/ember-app');
const isProduction = EmberApp.env() === 'production';

const purgeCSS = {
  module: require('@fullhuman/postcss-purgecss'),
  options: {
    content: [
      // add extra paths here for components/controllers which include tailwind classes
      './app/index.html',
      './app/templates/**/*.hbs',
      './app/components/**/*.hbs'
    ],
    defaultExtractor: content => content.match(/[A-Za-z0-9-_:/]+/g) || []
  }
}

module.exports = function(defaults) {
  let app = new EmberApp(defaults, {
    postcssOptions: {
      compile: {
        plugins: [
          {
            module: require('postcss-import'),
            options: {
              path: ['node_modules']
            }
          },
          require('tailwindcss')('./app/tailwind.config.js'),
          ...isProduction ? [purgeCSS] : []
        ]
      }
    }
  });
  return app.toTree();
};

...I started to do a quick google search, to see if any other folks using TailwindCSS ran into a similar issue.

And indeed, I found a couple of issues here and there that pointed to the fact, that the .flatMap method used at build time of my application was a more recent feature of Node and only available from Node v11+.

I confirmed that I was still on an older version of Node, that this would explain the lacking support of the .flatMap function:

node -v
10.16.3

...and was enthusiastic about quickly resolving the build problem with an upgrade of Node. Since I was using nvm my upgrade turned out as follows:

nvm install 14
nvm use 14
rm -rf node_modules/ && npm install

As soon as the installation finished, I tried my luck again by running ember serve, and to my surprise - the build failed again with the very same .flatMap is not a function error!

Still in disbelief, I tried running npm run start instead and lo and behold - my app built successfully?

How could my application build fail when the build command was run directly, but still succeed when its alias was executed?

How Node, NVM and Ember CLI work together

Once I checked on the versions of my Ember CLI and my Node installs, it became more clear, why npm run start and ember serve would behave differently:

node -v
v14.17.1

ember -v
ember-cli: 3.26.1
node: 10.16.3
os: linux x64

How come that Ember CLI was linked to an older version of Node different from my local Node version?

In my case, I've been using nvm to switch between different versions of Node on my machine.

When using nvm, it's important to be mindful on how this will affect the usage of globally installed packages. Nvm ensures that any global installs of binaries end up in a dedicated, versioned directory of nvm in the user's HOME directory. On Linux, you can verify this by checking the Ember CLI's binary location as follows:

whereis ember
ember: /home/jayjayjpg/.nvm/versions/node/v10.16.3/bin/ember

A while ago I had installed Ember CLI globally via npm install -g ember-cli while on Node 10.16.3. This instructed nvm to store the binary in the related 10.16.3 Node directory and make it available via this Node version. Now whenever I would run ember serve on my command line, this outdated binary would be used, running on Node 10.16.3, regardless if I had instructed nvm to switch to Node v.14.17.1 or not:

# Switching to Node 10.16.3
nvm use 10

node -v
v10.16.3

ember -v
node: 10.16.3

# Switching to Node 14.17.1
nvm use 14

node -v
v14.17.1

ember -v
node: 10.16.3

Whenever I would run npm run start though, my project's local Ember CLI version would be used leading to a successful app build.

The behavior of ember serve and npm run start doesn't have to differ this way though, as nvm provides you with a command to migrate all of your already existing, global npm installations over to a newer version of Node and make them available when switching to said version:

nvm install 14 --reinstall-packages-from=10

After the migration I could now see my global Ember CLI binary associated with the newer Node version and residing in the path that I'd expect:

whereis ember
ember: /home/jayjayjpg/.nvm/versions/node/v14.17.1/bin/ember

ember -v
ember-cli: 3.26.1
node: 14.17.1

And finally, upon running ember serve, the app would build successfully using Node v.14.17.1 just as it would using npm run start!

So what's the difference between ember serve and npm run start?

In a fresh Ember app and in nearly all cases, npm run start and ember serve function the exact same way. But if you have a different version of Ember CLI installed globally versus locally or if you're using version managers for Node, the outcome of these two commands may differ from each other.

This post has been inspired by this great response on Stackoverflow to the question "Is there a difference between ember serve and npm start?"

22