Todd Gandee - Big Nerd Ranch Tue, 19 Oct 2021 17:46:16 +0000 en-US hourly 1 https://wordpress.org/?v=6.5.5 Is Your Ember App Too Big? Split it up with Ember Engines, Part 3 https://bignerdranch.com/blog/is-your-ember-app-too-big-split-it-up-with-ember-engines-part-3/ https://bignerdranch.com/blog/is-your-ember-app-too-big-split-it-up-with-ember-engines-part-3/#respond Sun, 02 Apr 2017 10:00:53 +0000 https://nerdranchighq.wpengine.com/blog/is-your-ember-app-too-big-split-it-up-with-ember-engines-part-3/ In the previous two posts on Ember Engines, we started building an Ember "umbrella" application for mounting a couple Ember Engines. In this post, you will share links and services, like store and session, between the parent app and the engines.

The post Is Your Ember App Too Big? Split it up with Ember Engines, Part 3 appeared first on Big Nerd Ranch.

]]>

In the previous two posts on Ember Engines, we started building an Ember “umbrella” application for mounting a couple Ember Engines. You can catch up on the series in Part 1 and Part 2. Ember Engines help a development team to separate UX concerns and split compiled files into separate .js bundles that can be lazily loaded. When developing Engines, you can create an in-repo-engine or an Ember Addon as an external repository.

In this post, you will share links and services, like store and session, between the parent app and the engines. That way, Engines can integrate with the parent app’s model and authentication layer without duplicating the Model, Adapter and Serializer classes defined in the parent app. The parent application will also control where engine routes are mounted.

Within an Engine, links to internal routes work the same as any other Ember application: {{link-to "Home" "application"}}, {{#link-to "application"}}{{/link-to}}, [route].transitionTo('application') and [controller].transitionToRoute('application').

To add external links to an engine app, there are new components and methods: {{#link-to-external}}{{/link-to-external}}, {{link-to-external}}, [route].replaceWithExternal and [route].transitionToExternal. In the external-admin engine, add some links to the posts template:

<!-- external-admin/addon/templates/posts.hbs -->
<h1>List of Posts</h1>
<ul>
  <li>Post 1</li>
  <li>Post 2</li>
  <li>Post 3</li>
  <li>Post 4</li>
</ul>
{{#link-to-external "home"}}Go home{{/link-to-external}}
{{#link-to-external "blog"}}Go to Blog{{/link-to-external}}
{{outlet}}

We now have external links to “home” and “blog”. These will navigate the user to the parent app’s “home” route and the “blog” route from another engine. Now our external-admin engine needs to export these dependencies to the parent application. You will need to add those keys to the engine definition in external-admin/addon/engine.js:

 const Eng = Engine.extend({
   modulePrefix,
-  Resolver,
+  Resolver,
+  dependencies: {
+    externalRoutes: [
+      'home',
+      'blog'
+    ]
+  }
 });

Once you have committed these changes, you have to reinstall the engine in the parent application.

cd ../large-company-site
ember install ember install https://github.com/bignerdranch/ember-engine-external-admin.git

In the parent application’s package.json file you can check the commit revision hash against the latest revision from your engine’s GitHub page to make sure you loaded the latest:

"external-admin": "git+https://github.com/bignerdranch/ember-engine-external-admin.git#5e3601eebfcb4e82477b1f6e7e75355df8cbd1a1",

The commit hash (everything after “#” sign) tells you which revision of external-admin your parent app is loading. Packages often use release numbers or tags instead of hashes.

Now that your engine has declared its dependency on the parent app’s links and other engine routes, you need to “inject” these dependencies into the engine. Add the following to large-company-site/app/app.js:

 App = Ember.Application.extend({
   modulePrefix: config.modulePrefix,
   podModulePrefix: config.podModulePrefix,
-  Resolver
+  Resolver,
+   engines: {
+     externalAdmin: {
+      dependencies: {
+        externalRoutes: {
+          home: 'application',
+          blog: 'in-app-blog.posts'
+        }
+      }
+    }
+  }
 });

These dependency mappings allow the parent app to fully control the engine’s external routing to the parent app, or to another engine!

Share Services

Services are shared in exactly the same way as links: by adding a dependency mapping to a local object in the parent application. If you want your engine to retrieve data via the parent application’s store, you can add a dependency to the external-admin/addon/engine.js:

 const Eng = Engine.extend({
   modulePrefix,
   Resolver,
   dependencies: {
+    services: [
+      'data-store'
+    ],
     externalRoutes: [
       'home',
       'blog'
     ]
   }
 });

This data-store dependency can be accessed like any other service, this.get('dataStore').*, but it will need to be added to each route, component or controller with Ember.inject.service():

export default Ember.[Object Type].extend({
  dataStore: Ember.inject.service(),
  fakeFunc() {
    this.get('dataStore').[store method]()
  }
});

Finally, the service dependency needs to mapped from the parent to the engine in large-company-site/app/app.js:

 App = Ember.Application.extend({
   modulePrefix: config.modulePrefix,
   podModulePrefix: config.podModulePrefix,
   Resolver,
   engines: {
     externalAdmin: {
       dependencies: {
+        services: [
+          { 'data-store': 'store' }
+        ],
         externalRoutes: {
           home: 'application',
           blog: 'in-app-blog.posts'
         }
       }
     }
   }
 });

Here, we mapped the engine’s data-store service to the parent app’s store service.

You can use this technique to share any service from your parent app to an engine. The parent application needs to inject all the dependencies required to load an engine, which should be documented in the engine repo’s README.md. Once you know the dependencies, you can map your routes and services to the engine. And if you make changes to the parent app’s services or routes, you just have to update the dependency mapping in app.js!.

And best yet, the structure of your build, environment, development or deployment process is not affected by this giant shift. It’s still just an Ember app with some Ember Addons! So whether you are splitting an existing application for better performance or creating teams for a complex ground-up application, Ember Engines are the solution you’ve been waiting for.

Happy building!

The post Is Your Ember App Too Big? Split it up with Ember Engines, Part 3 appeared first on Big Nerd Ranch.

]]>
https://bignerdranch.com/blog/is-your-ember-app-too-big-split-it-up-with-ember-engines-part-3/feed/ 0
Is Your Ember App Too Big? Split it up with Ember Engines, Part 2 https://bignerdranch.com/blog/is-your-ember-app-too-big-split-it-up-with-ember-engines-part-2/ https://bignerdranch.com/blog/is-your-ember-app-too-big-split-it-up-with-ember-engines-part-2/#respond Sun, 26 Mar 2017 10:00:00 +0000 https://nerdranchighq.wpengine.com/blog/is-your-ember-app-too-big-split-it-up-with-ember-engines-part-2/ In this post, you will learn to create an Ember Engine in a separate repository and link the engine to the umbrella application.

The post Is Your Ember App Too Big? Split it up with Ember Engines, Part 2 appeared first on Big Nerd Ranch.

]]>

In the last post, we started building an “umbrella” Ember application for mounting a couple Ember Engine applications. Engines make it easier for a development team to separate UX concerns and split the codebase into separate .js files that can be lazy-loaded.

Last time, we were working on an Ember app called large-company-site. In this post, you will create an Ember Engine in a separate repository and link the engine to the umbrella application.

Start by changing directories in the terminal to leave the large-company-site directory, then create a new Ember Addon:

cd ../
ember addon external-admin
cd external-admin

Ember-CLI blueprints don’t yet support creating an engine, but it will soon with the command ember engine [engine-name].

The nice thing about creating an external Ember Addon is you can use the Ember-CLI generator to add application files from the command-line. However, the first file we need to add is an engine.js file in the external-admin/addon directory. At this point, it has to be done manually.

Open the new external-admin project in your editor, then create a file at external-admin/addon/engines.js and add the following:

import Engine from 'ember-engines/engine';
import Resolver from 'ember-resolver';
import loadInitializers from 'ember-load-initializers';
import config from './config/environment';

const { modulePrefix } = config;
const Eng = Engine.extend({
  modulePrefix,
  Resolver
});

loadInitializers(Eng, modulePrefix);

export default Eng;

Next, install the HTMLBars NPM module via the terminal in the external-admin project:

npm install ember-cli-htmlbars --save

The last option --save will add HTMLBars as a dependency entry in package.json.

Next, add a routes.js to the addon directory with the following bootstrap code:

import buildRoutes from 'ember-engines/routes';

export default buildRoutes(function() {
// route map
});

Once you have a router file, you can add some routes to your engine. Our admin application should have access to “posts” and other website details, so we will need routes to list, show, create and edit posts. From the terminal, use ember g to generate routes for “posts”, “post”, “posts/new” and “post/edit”:

ember g route posts
ember g route post
ember g route posts/new
ember g route posts/edit

With the route and template files created, you need to manually add routes to the routes file. Open external-admin/addon/routes.js and add the following:

 import buildRoutes from 'ember-engines/routes';

 export default buildRoutes(function() {
   // route map
+  this.route('posts', function() {
+      this.route('new');
+  });
+  this.route('post', function() {
+      this.route('edit');
+  });

 });

The last two setup files for ember engines are external-admin/config/environment.js and external-admin/index.js. These files need to pass a reference to the engine module to the parent application after the addon is loaded. Add the following to environment.js:

 /*jshint node:true*/
 'use strict';

-module.exports = function(/* environment, appConfig */) {
-return { };

+module.exports = function(environment) {
+  const ENV = {
+    modulePrefix: 'external-admin',
+    environment: environment
+  }
+
+  return ENV;
+};

Add the following to index.js:

 /* jshint node: true */
+const EngineAddon = require('ember-engines/lib/engine-addon');
 'use strict';

-module.exports = {
+module.exports = EngineAddon.extend({
+  name: 'external-admin',
+  lazyLoading: true
-};
+});

The environment.js and index.js files will pass the engine’s name and settings to the parent application for “lazily” mounting the engine inside the parent’s router. With these in place, you can use the engine’s routes as if they were built into the parent application.

To finish the view layer of our admin engine, edit the *.hbs files in external-admin/addon/templates by adding simple headline elements describing the route page:

<!-- templates/posts.hbs -->
<h1>List of Posts</h1>

<!-- templates/posts/new.hbs -->
<h1>New Post Form</h1>

<!-- templates/post.hbs -->
<h1>Post</h1>

<!-- templates/post/edit.hbs -->
<h1>Edit Post Form</h1>

You’ve created the basic shell of an Ember Engine! Now it’s time to save it to Github (or any cloud storage service) to be loaded via npm. We have uploaded our example external-admin to a public github repo so you can install the ember-engine from our repo to test. In the terminal, switch from the external-admin directory back into the large-company-site directory.

cd ../large-company-site

Install the engine via ember install using the remote git repo:

ember install https://github.com/bignerdranch/ember-engine-external-admin.git

This will download the ember engine into node_modules from the master branch. Note that every time you commit changes changes to the external-admin repo, you will have to reinstall external-admin in the parent application.

Now for the moment of truth! Run ember s from large-company-site:

ember s

Open a browser and navigate to localhost:4200/admin/posts, localhost:4200/admin/posts/new, localhost:4200/admin/post, localhost:4200/admin/post/edit. You should recognize the headlines you created in your engine route templates!

The noble task of breaking down your monolithic Ember application into engines has just begun. In the next installment, you will learn about sharing services and links between engines to facilitate communication between engines and the umbrella application.

The post Is Your Ember App Too Big? Split it up with Ember Engines, Part 2 appeared first on Big Nerd Ranch.

]]>
https://bignerdranch.com/blog/is-your-ember-app-too-big-split-it-up-with-ember-engines-part-2/feed/ 0
Is Your Ember App Too Big? Split It Up with Ember Engines https://bignerdranch.com/blog/is-your-ember-app-too-big-split-it-up-with-ember-engines/ https://bignerdranch.com/blog/is-your-ember-app-too-big-split-it-up-with-ember-engines/#respond Tue, 07 Feb 2017 10:00:00 +0000 https://nerdranchighq.wpengine.com/blog/is-your-ember-app-too-big-split-it-up-with-ember-engines/ For applications that have sections with different business concerns, Ember engines provide a structure for scaling without the threat of exponential file size growth.

The post Is Your Ember App Too Big? Split It Up with Ember Engines appeared first on Big Nerd Ranch.

]]>

The Ember team has does an excellent job giving proper names to most their components, tools and libraries. For example, the rendering engine is called Glimmer, while it uses HTMLBars as the template language. Singletons in Ember applications are called Services. The build tool is called Ember-CLI, and a external application module is called an Addon, generally stored in NPM with the prefix ember-cli-[addon-name]. Having recognizable names makes talking about them a lot easier.

This is very intentional for the community. There are specific terms for developers to discuss and an easy way to market changes to the framework. The latest is Engines, or Ember Engines.

The Ember Engines RFC started in October 2014 and was merged in April 2016. The goal was to allow large Ember applications to be split into consumable Addons allowing development teams to build logically-grouped pieces of an application in separate repositories and mount each micro application as a route or container in a parent application. In the table below are links to resources for EmberEngines for more history and details:

Resources URLS
Ember Engines Guide http://ember-engines.com/guide
Github Repo https://github.com/ember-engines/ember-engines
Engines – RFC https://github.com/emberjs/rfcs/pull/10

Engines and the flow to setup Engines were added to Ember fairly early in the Engines-RFC process. The most recent feature to be added, and what I think to be a crucial piece, is lazy-loading. This allows the core application to load with the initial page request while mounted engine sections of the application will be requested as needed in separate .js files.

Engine Structure

For applications that have sections with different business concerns, engines provide a structure for scaling without the threat of exponential file size growth. From the image above, the admin section of the site will only be used by a select group of individuals maintaining the site content. Allowing these user to load a separate file will shed weight from the readers using the blog app. The benefit lies in the main app maintaining the session, styles and global components.

To achieve the separation of concerns with engines, there are two ways to create the sandbox needed for mounting engines: in-repo-engine and external repo Addon. In this post, we’ll walk through building a basic application that uses an in-repo-engine and lazy-loading. In a the next post in the series, you’ll learn about making an Ember Addon an engine in an external git repository. In the final post of the series, we’ll bring it all together with shared services and links.

Create a New Ember App


  ember new large-company-site
  cd large-company-site
  ember install ember-engines

This assumes you have ember-cli install (npm install -g ember-cli). Also, it assumes you are running Ember@2.10

These commands will setup your umbrella application with the appropriate addon to mount engines. The next step is creating an engine to mount. We will start with the in-app engine. While in the large-company-site directory add an engine with ember generate:

  ember g in-repo-engine in-app-blog

This has added a directory named “lib” and an app addon directory structure named for “in-app-blog”.

In-Repo Engine Directory Structure

Using the blueprint in-repo-engine, ember-cli has added all the appropriate files to create a new app structure with its own routes. Open lib/in-app-blog/addon/routes.js to add new routes:

  import buildRoutes from 'ember-engines/routes';

  export default buildRoutes(function() {
-     // Define your engine's route map here
+     this.route('new');
+     this.route('posts');
    });
  });

Once the routes are added in the engine’s addon/routes.js file, it is time to create route and template files for each. For this simple example, add the route and template files for new and posts in the addon/routes and addon/templates directories.

Engine Routes and Templates

The next step is to add some content to see the routes working between the parent app and engine. In the following code examples you’ll add simple headlines to each .hbs file. The file name will be in italics above the code block.

lib/in-app-blog/addon/templates/application.hbs

  <h1>Blog Engine App</h1>
  {{outlet}}

lib/in-app-blog/addon/templates/new.hbs

  <h1>New Form</h1>
  <form>
    {{input type="text" value=title}}
    {{textarea value=post}}
  <form>

lib/in-app-blog/addon/templates/posts.hbs

  <h1>All Posts</h1>
  <ul>
    <!-- will insert items here -->
  </ul>

Now, add an application template to the umbrella application:

app/templates/application.hbs

  <h1>Large Company Umbrella</h1>
  {{outlet}}

Finally, add a path to the mount route for the engine in app/route.js:

  import Ember from 'ember';
  import config from './config/environment';

  const Router = Ember.Router.extend({
  location: config.locationType,
  rootURL: config.rootURL
  });

  Router.map(function() {
-    this.mount('in-app-blog');
+    this.mount('in-app-blog', {path: "blog"});
  });

  export default Router;

At this point, the structure is in place to create new routes, templates, controllers, and components for the blog engine application. The last change you need to make is in the lib/in-app-blog/index.js file. You will change the application to lazy-load the blog engine. Add the following to the index.js file in the lib/in-app-blog:

  /*jshint node:true*/
  var EngineAddon = require('ember-engines/lib/engine-addon');
  module.exports = EngineAddon.extend({
    name: 'in-app-blog',
+   lazyLoading: true,
    isDevelopingAddon: function() {
      return true;
    }
  });

In the terminal, run ember s, and open your browser to the location localhost:4200/blog/posts.

Using the Chrome browser and the Developer Tools, you can open the Network tab to see the network traffic. What you’ll see is multiple .js files being loaded.

Highlighted in the developer console is engine.js which is a separate file from large-company-site.js. This is it, this is what we’ve been waiting for. You can now built the largest Ember site ever and separate concerns with engines and know your users will get script and style resources efficiently. The benefit to you and your team is huge—you don’t have to spend all of your time configuring the build process. That’s Ember’s gift to you.

This example will be on Github at https://github.com/bignerdranch/ember-engine-example. As the series continues, the GitHub repo will include tags/commits for each blog post.

In the next post of the series, you will create an external addon as an engine and link it to your project. The final post will add shared services and links to tie the application together.

The post Is Your Ember App Too Big? Split It Up with Ember Engines appeared first on Big Nerd Ranch.

]]>
https://bignerdranch.com/blog/is-your-ember-app-too-big-split-it-up-with-ember-engines/feed/ 0
Creating Ember Blueprints https://bignerdranch.com/blog/creating-ember-blueprints/ https://bignerdranch.com/blog/creating-ember-blueprints/#respond Mon, 26 Sep 2016 10:00:53 +0000 https://nerdranchighq.wpengine.com/blog/creating-ember-blueprints/ Ember-CLI Blueprints are a powerful tool to help your team move efficiently and effectively when working on large projects. In this post, we'll walk through the basics of creating your own blueprint, adding some command-line prompts and creating a custom file structure for it.

The post Creating Ember Blueprints appeared first on Big Nerd Ranch.

]]>

Thousands of years ago before the dawn of… wait, sorry. In 2012, Yeoman was introduced at Google I/O. Yeoman was an easy way to generate baseline html projects, paving the way for command-line-interface(CLI) tools like Ember-CLI. And one of best features of Ember-CLI is the built-in command ember generate or ember g, which adds files, directories, test files and important lines of code to your project.

Most tutorials for Ember will tell you to generate files when you are first starting out, and for good reason. Ember Blueprints create basic modules extending the correct Ember objects and put the file in the correct directory. Most blueprints will create corresponding test files. When you are new to Ember, these steps save a lot of time.

Even as you work with Ember more frequently, the ~36 built-in blueprints will help you save some time on smaller projects. For bigger projects or tasks you find yourself doing over and over, Ember allows you to build your own. In this post, we will walk through the basics of creating your own blueprint, adding some command-line prompts and creating a custom file structure for your blueprint.

Name Links
Ember-CLI API https://ember-cli.com/api/
Blueprints https://ember-cli.com/api/classes/Blueprint.html
UI https://ember-cli.com/api/classes/UI.html

Create a New Ember App

For this example, you can create a new Ember application or skip this step to add the blueprint directly to your existing project:

ember new my-first-blueprint
cd my-first-blueprint

Once inside your application directory, use the generate command to create a new blueprint:

Create Blueprint

ember g blueprint my-special-blueprint

This command will add a new directory /blueprints/my-special-blueprint to the project. It will also add a file called index.js. This is a node file to be run when your blueprint is called.

Creating a new directory

Try running:

ember g my-special-blueprint
> installing my-special-blueprint

> The `ember generate <entity-name>` command requires an entity name to be specified. For more details, use `ember help`.

The output will be an error telling you that blueprints require a name option. Let’s dive into the inner workings of how blueprints set this value.

Three Main Callbacks

normalizeEntityName

The command returned an error asking for an entity-name argument. This argument can be set when calling the command. Also, it can be edited or created from the index.js file with normalizeEntityName method hook. Add the following to the blueprints/my-special-blueprint/index.js:

module.exports = {
  description: 'My Special Blueprint',
  normalizeEntityName: function(entityName){
    return entityName || "special-entity-name";
  }
};

Run the generate command again, ember g my-special-blueprint. This time there are no errors, and we’re finally making progress. Adding this function actually did nothing for the application. Instead, adding this function set the entityName property to the value passed in from the command line, or “special-entity-name”, on the object that is passed around during the lifecycle of calling ember g my-special-blueprint.

fileMapTokens

To see this blueprint’s object value in use, let’s jump to the next hook in the process, fileMapTokens. Tokens are not for taking the subway today; they are for naming files and directories dynamically in your blueprint. Let’s have the blueprint install a style file in your app’s styles directory. We want the style file’s name to match the generated entity’s normalized name, so we’ll need to rename it before it gets installed in place. This is a job for fileMapTokens. Let’s see how! In the filesystem, add the following directories and .scss file to the /blueprints/my-special-blueprint directory:

blueprints
  my-special-blueprint
    files
      app
        styles
          __styleToken__.scss

In the file __styleToken__.scss, add the following code:

// -----------------------------
// Style Module
// -----------------------------

Now, back in the /blueprints/my-special-blueprint/index.js the fileMapTokens() hook needs to be added. To properly name the style file with the token __styleToken__, the blueprint will need a function to return a value to replace the file’s name token. Add the following to the index.js file:

module.exports = {
  description: 'My Special Blueprint',
  normalizeEntityName: function(){
    return "special-entity-name";
  },
  fileMapTokens: function(options) {
    // Return custom tokens to be replaced in your files
    return {
      __styleToken__: function(options) {
        console.log(options.dasherizedModuleName);
        return "_" + options.dasherizedModuleName;
      }
    }
  }
};

The naming of the file and the fileMapToken key, __styleToken__, with double underscores before and after name might seem a bit weird. It is common convention rather than a requirement. Naming the file and fileMapToken styleToken without underscores will work. Having the double underscores will signal a name that will be replaced rather than a static file that will be copied to the user’s app directory.

Once again, run ember g my-special-blueprint to see the output of the blueprint. This time the terminal console should print:

$ ember g my-special-blueprint
installing my-special-blueprint
special-entity-name
  create app/styles/_special-entity-name.scss

This time text was printed to the command-line and a file was created in the directory app/styles/ with the name _special-entity-name.scss. The fileMapTokens function can return multiple naming tokens for any directory as well as files. If you name your directory __someDirectoryToken__ you can create a file token callback:


fileMapTokens: function(options) {
  // Return custom tokens to be replaced in your files
  return {
    __someDirectoryToken__: function(options) {
      return options.dasherizedModuleName + "-directory";
    }
  }
}

Creating file map tokens allow you create dynamically generated files and nested directories in your project.

locals

The last built-in hook to discuss is locals. This function will be used to return objects to template files created when executing the blueprint. The file created above has the following contents:

// -----------------------------
// Style Module
// -----------------------------

When blueprints install template files, they get run through an Embedded JavaScript (EJS) interpreter, first. This runs any code embedded between <%= and %>, like <%= “I am a string!” %> and puts its output in place of the code snippet tag. Use this to inject information into the style file installed by our blueprint, like so:

// -----------------------------
// Style Module <%= classifiedModuleName %>
// File Location: /styles/<%= entity.styleModule.moduleName %>
// Made for Ember App: <%= dasherizedPackageName %>
// -----------------------------

Running the blueprint command again will yield an error. When creating the template the object entity has not been defined. This is where locals comes in. In the blueprints/my-special-blueprint/index.js file add the following:


fileMapTokens: function(options) {
  . . .
},
locals: function(options) {
  var fileName = options.entity.name;
  options.entity.styleModule = { moduleName: "_" + fileName + ".scss" };
  return options;
}

Finally, run the blueprint command ember g my-special-blueprint in the terminal. There will be a prompt to overwrite the file, type “Y” to overwrite. Once complete, open the newly created file in your app/styles directory. It should have the following contents:

// -----------------------------
// Style Module SpecialEntityName
// File Location: /styles/_special-entity-name.scss
// Made for Ember App: my-first-blueprint
// -----------------------------

The object entity held the key styleModule.moduleName. The template also used values passed in with the options object for the keys classifiedModuleName and dasherizedPackageName. These are commonly used strings passed around in the callback arguments for blueprints. In summary, when creating files with blueprints, you have access to commonly used strings created from the blueprint name, and you have the ability to dynamically create string values in the blueprint index.js file with the method locals.

Beyond the Structured Callbacks

The main methods of creating blueprints are displayed above: normalizeEntityName, fileMapTokens, locals. There are also bookend methods that allow you to freeform the scaffolding process with beforeInstall and afterInstall. These methods take the same options object passed to the other callback methods and they should return that object or a promise. These functions are a good place to add Bower, NPM packages or other Ember Addons to the project, prompt the user with questions, generate other blueprints and do any file clean-up needed.

Adding Modules with Blueprints

Ember-CLI relies on NPM and Bower to install module packages. When creating blueprints, external packages can be essential to the usage of scripts or components. Ember-CLI provides methods to add one or many Addons or packages from Bower or NPM with good descriptive names: addAddonToProject, addAddonsToProject, addBowerPackageToProject, addBowerPackagesToProject, addPackageToProject, addPackagesToProject.

Kind of Package Add One (name, target) Add Many [{name:,target?:}]
Ember CLI Addon addAddonToProject addAddonsToProject
Bower Package addBowerPackageToProject addBowerPackagesToProject
NPM Package addPackageToProject addPackagesToProject

The singular method names accept 2 arguments “name” and “target”, like “jQuery” and “~1.11.1” where name is the registered name of the package and target is the version, tag or github release. The methods for multiple packages have a “s” in the name for noun, “Addons” or “Packages” and accept an array of objects each with a key for “name” and an optional key for the target. Each of these methods returns a promise making them good candidates for the return statement for either beforeInstall or afterInstall.

Try out adding a package within the function beforeInstall:

locals: function(options) {
  . . .
},
beforeInstall: function() {
  return this.addAddonToProject("ember-moment");
}

Finally, run ember g my-special-blueprint to see the Addon added using generate. While installing you will see the command-line console print these lines:

install addon ember-moment
Installed packages for tooling via npm.
installing ember-cli-moment-shim
Installed addon package.
Installed addon package.
  identical app/styles/_special-entity-name.scss

This read-out shows your Addon was installed then the styles/_special-entity-name.scss file was created. Next, add a package in afterInstall:

beforeInstall: function() {
  return this.addAddonToProject("ember-moment");
},
afterInstall: function(){
  return this.addBowerPackageToProject("bootstrap", "~3.3.7");
}

The console now reads:

installing my-special-blueprint
  install addon ember-moment
Installed packages for tooling via npm.
installing ember-moment
  install addon ember-cli-moment-shim
Installed packages for tooling via npm.
installing ember-cli-moment-shim
Installed addon package.
Installed addon package.
  identical app/styles/_special-entity-name.scss
  install bower package bootstrap
  cached https://github.com/twbs/bootstrap.git#3.3.7
Installed browser packages via Bower.

Bootstrap was installed via Bower after the file was created. Considering the .add__Package__ToProject methods return a promise, you can chain .then() calls to trigger other function calls after installs. This is a good place to call other methods like insertIntoFile or lookupBlueprint. Those methods are for another post about blueprints. If you are looking to nest blueprint calls, calling a built-in blueprint like ember g route or ember g component, lookupBlueprint().install() is the method you are looking for. Using insertIntoFile is the way to add text to existing files. Look for a future post to see those methods in depth.

Prompting the User

The last topic is prompting the user to answer questions about the scaffold process. The Ember-CLI object that handles the command-line user interface is aptly named UI. The method in this object is ui.prompt(). Ember-CLI extends the NPM module Inquirer.js for this method. It accepts a question object with variety of keys. The first 3 keys are required: type, name, message. The default prompt type is “input,” assigned as a string, and I found it was a required argument. (So much for defaults!) Start with a basic question with the following format to see where the user input is retrieved. Add the following to the blueprint’s index.js file:

  afterInstall: function(){
    var self = this;
    return this.ui.prompt({
      type: "input",
      name: "framework",
      message: "Do you want to install Bootstrap?"
    }).then(function(data) {
      console.dir(data);
      if(data.framework === "Y") {
          return self.addBowerPackageToProject("bootstrap", "~3.3.7");
      }
      return null;
    });
  }

Run the blueprint to see the prompt. In the callback function for then() the data argument is printed as { framework: 'Y' }. At this point, there is a conditional checking for the match on the string “Y”. This basic example of user input shows the possibilities of prompting the user. This isn’t very useful for the case of adding a style framework to any project via a blueprint.

Next, create a prompt with choices:


  afterInstall: function(){
    var self = this;
    return this.ui.prompt({
      type: "list",
      name: "framework",
      message: "Which framework would you like to install?",
      choices: [
        {name: "SCSS - Bootstrap-Sass", value: {name: "bootstrap-sass"}},
        {name: "CSS - Bootstrap 3.3", value: {name: "bootstrap", target: "~3.3.7"}},
        {name: "SCSS - Bootstrap 4-alpha", value: {name: "bootstrap", target: "4.0.0-alpha.3"}},
        {name: "none", value: null}
      ]
    }).then(function(data) {
      console.dir(data);
      if(data.framework) {
          return self.addBowerPackageToProject(data.framework.name, data.framework.target);
      }
    });
  }

What you just wrote, or copied, was a lot. These prompt calls can get bulky if you want to program in complex choices for scaffolding. Let’s walk through all that you typed. First, you declared a variable self to be used in the promise callback. You will see this in a number of examples as a common practice. Next, change the question type to a “list”. The list type needs choices, which can be added as an array or a function that returns a choices array. The array can contain strings for simple answers or an object with 2 keys: name and value. You have added values that are also objects to be parsed to load specific style frameworks modules from Bower. Using console.dir() or console.log() has been the best way to debug the data passed around all the promise callbacks.

What’s Next?

This has been an introduction into creating a blueprint with the built-in hooks and utilizing some of the blueprint methods for adding libraries and prompting the user for choices. normalizeEntityName, fileMapTokens and locals are the main functions to control how your files are created in your app tree structure and the text that will fill those files. beforeInstall and afterInstall are the wrapper functions to do anything else in the blueprint like install dependencies, like add text to existing files and any other pre-processing action your blueprint needs to be effective.

Look for a future post about nesting other built-in blueprint installs, removing files and adding text to existing files.

The post Creating Ember Blueprints appeared first on Big Nerd Ranch.

]]>
https://bignerdranch.com/blog/creating-ember-blueprints/feed/ 0
Learning from CSSConf https://bignerdranch.com/blog/learning-from-cssconf/ https://bignerdranch.com/blog/learning-from-cssconf/#respond Thu, 19 Jun 2014 10:10:22 +0000 https://nerdranchighq.wpengine.com/blog/learning-from-cssconf/

This year’s CSSConf was filled with incredible talks from seasoned speakers and amazing first-time speakers alike. Ever since I made it back home, I’ve been thinking about this event.

The post Learning from CSSConf appeared first on Big Nerd Ranch.

]]>

This year’s CSSConf was filled with incredible talks from seasoned speakers and amazing first-time speakers alike. Ever since I made it back home, I’ve been thinking about this event.

Although I didn’t make it to last year’s CSSConf, I heard it was a great start to a new tradition of pairing CSSConf and JSConf. Both events sell out quickly and have a great mix of leaders who talk about the next big thing, alongside relatively unknown speakers sharing their incredible research and work.

Nicole Sullivan: Style Guide Driven Development

Conference Video

The day started off with CSSConf co-organizer Nicole Sullivan. For the last couple of years, I’ve sought out any talk from Nicole to find the next trend in CSS development—and this talk did not disappoint. Her major projects for the last couple of years have included redesigning styles, restructuring CSS and performing code audits for Trulia and Pivotal Labs.

Through these projects, and probably others not mentioned, she has worked at building a process called Style Guide Driven Development. This process involves breaking out the view/pages structure of design to see individual pieces of applications as components. When iterating on features, a styled component is just another piece of the feature that needs to be completed. Designers deciding on style, backend devs on server data for features, and frontend devs structuring and coding styles all need to communicate with a consistent language in order to move a project forward. Designing everything on the page, then asking if that will work with code, is a slow process and many iterations can be lost through tossing a page over fences.

On a project with Trulia, Nicole created a documentation tool that builds a living style guide. The resulting HTML document lists all the current components, styled, with code samples to show how it looks in a browser, as well as how to create each component. As she stated in her talk, Trulia built a better version and integrated it into their build process, and open-sourced it too. The project is called Hologram, and is similar to other documentation software, using markdown to create code samples and formatted comments.

Coming home to an existing project, I decided it was time to leverage these tools and development styles. Our frontend web development team works hard to improve our processes and adapt to new tools and flows, and we have been working with living style guides for the last year, starting with the Big Nerd Ranch website. On my existing project, we are now using Hologram to generate the living style guide.

Antoine Butler: CSS Can Solve Problems We Don’t Know We Have

Conference Video

As a first-time speaker, Antoine Butler engaged with and amazed the crowd. His topic was height-based media queries. If you’ve ever written an @media , most likely the conditions for the style were min-width, max-width, orientation: landscape or orientation: portrait.

Antoine talked about making media queries for min-height and max-height. His example was for a left-aligned navigation bar. The complete navigation list could be toggled open and closed, but the critical items on the list were displayed as icons. If the height of the page was less than the needed space for the icons, the navigation item moved to the bigger list. The first and last list items, the brand logo and the “more” button for toggling the full list, were kept in the icon list. Antoine’s example and code walkthrough were eye-opening. It help me realize that we think of most UI space issues in one dimension: how wide it is. The takeaway for me was that CSS can solve problems we don’t even know we have. As developers, we need to translate the capabilities of our language to designers, and that is a career-long journey.

Patrick Hamann: Finding the Critical Path

Conference Video

Patrick Hamann works for The Guardian, where he and his team are currently developing a more performant version of The Guardian online. His talk was about the journey to find “the critical path,” the most important information to get in front of the user as quickly as possible, and the code needed to style and structure that article or information.

His journey began with research on what users want, and the acceptable speed to get it. It turns out that users think applications work if they see it within 300ms-1000ms. Seeing it in 10,000ms meant the site was broken to the user. Patrick shared useful tips for how his team met their goal of a 1000ms time budget:

For his team, this meant fewer requests, or fewer blocking requests, for styles and JavaScript beyond the request for the HTML page. One solution (that other sites use as well) was to inline the critical style definitions in a <style> tag. This reduced requests while declaring styles for the markup that the user needed to see. Putting that code inline meant one less blocking request and the browser could paint. The next solution was adding an async attribute to the JavaScript files. This also made the script loading non-blocking. The scripts could also load the remaining styles as an Ajax call, and store the content in localStorage for the next visit. Combined, these techniques reduced the load time of the new Guardian website so readers can get their articles in an acceptable amount of time.

Watch the Videos

These were just some of the amazing speakers at the conference. JSConf and CSSConf usually post the video of the talks a couple weeks after the event here, so be sure to check them out.

The post Learning from CSSConf appeared first on Big Nerd Ranch.

]]>
https://bignerdranch.com/blog/learning-from-cssconf/feed/ 0
Journey into Gulp https://bignerdranch.com/blog/journey-into-gulp/ https://bignerdranch.com/blog/journey-into-gulp/#respond Wed, 14 May 2014 10:10:22 +0000 https://nerdranchighq.wpengine.com/blog/journey-into-gulp/

Workflows for front-end software development are nothing new. Every developer has his or her preferred toolset and build process. Trying to keep up with the latest workflows and tools can be difficult, however. As legacy tools become bloated with syntax or configurations for the plethora of plugins, retooling needs to happen.

The post Journey into Gulp appeared first on Big Nerd Ranch.

]]>

Workflows for front-end software development are nothing new. Every developer has his or her preferred toolset and build process. Trying to keep up with the latest workflows and tools can be difficult, however. As legacy tools become bloated with syntax or configurations for the plethora of plugins, retooling needs to happen.

Our JavaScript developers have been using Grunt for the majority of our client-side applications. If you haven’t seen a Gruntfile , it is a JavaScript module with task definitions and configurations. These tasks will be called from a Node process named Grunt. As the Gruntfile acquires more processes and tasks, the configurations start to look cryptic to anyone other than the original author, requiring a detailed explanation of what each configuration does. When we were just minifying JavaScript and CSS files, Gruntfiles were probably easy to read. Over time, however, that’s no longer the case.

Enter Gulp. Gulp is a Node task runner for building next-generation applications or just accomplishing simple development tasks. Gulp promotes code over configuration: instead of writing a configuration file, you write and call functions that perform your tasks. Some developers find the syntax easier to read and learn. Another nice benefit is that tasks run more quickly, as Gulp can run tasks in parallel.

Gulp has five main methods, and leverages Node syntax and Node streams. src, dest, task, watch and pipe are all you need in order to work with Gulp.

Getting Started with Gulp

To install Gulp, you must have Node.js and npm already installed. If you need to, grab the installer from nodejs.org. Then, on the command line, type:

    npm install -g gulp

This will install Gulp globally. Next, create your project directory or navigator to the existing project. At this point, you will need to create a node package configuration, a package.json file, if one does not exist:

    npm init

This npm command will ask you a series of questions to help you create a package.json file that will manage your project’s node modules. Next, you will add your node dependencies, beginning with ‘gulp’:

    npm install --save-dev gulp

When you use the option --save-dev, npm will install the module locally in your ‘node_modules’ directory and save the reference to the module in your package.json file. If you open your package.json file, it should look like this:

package-json

Now that you have added Gulp, you can create a Gulp file named “gulpfile.js” and enter the following JavaScript code:

    var gulp = require('gulp');
    gulp.task('default', function() {
      // place code for your default task here
    });

Of course, this does not do much of anything. What you have written (or copied and pasted) is the basis for creating a Gulp build system. You have loaded the Gulp module by calling require('gulp') and saving a reference to the module in a variable. That module provides a method named task, and you are calling it to create a task. task accepts two arguments: a name for your task and a callback containing the code for that task. The name ‘default’ is the task that will run when you call gulp from the command line with no other arguments.

Let’s add a simple console.log() to the task, and run Gulp. The ‘gulpfile.js’ should look like this:

    var gulp = require('gulp');
    gulp.task('default', function() {
      // place code for your default task here
      console.log('[gulp]: default task');
    });

And now from the the command line:

    gulp

gulp default

Running the command gulp default should yield the same result. Gulp expects to execute either the ‘default’ task or a task whose name you specify as an argument to the ‘gulp [task]’ command.

Tasks can be a series of synchronous actions, or streams, using the pipe method. pipe will take the results of the previous function innvocation and send them to the function passed as the argument to ‘pipe’. Here is a common pattern when using pipe, and two of Gulp’s other methods, src and dest:

    gulp.src('application.js')
      .pipe(gulp.dest('build/js'));

In this example, the src function would retrieve the contents of the file application.js, and pass those contents to gulp.dest, along with its existing arguments or configuration (‘build/js’). This specific pattern of getting source, processing it and sending it to a destination is a way to create production builds of your app. Specifically, you write some code and then check it (linting), process it (minify) and copy it to a production directory. For each stage of processing, you just add another .pipe(someFunction()) call in between gulp.src() and .pipe(gulp.dest()).

Let’s talk about src and dest. In the last example, you used pipe to see how a single file can be copied to a production folder. Now, try to take an entire directory and move it to the ‘build’ directory.

    var gulp = require('gulp');
    var scripts = 'scripts/*.js';
    gulp.task('copy', function() {
      // place code for your default task here
      gulp.src(scripts)
          .pipe(gulp.dest('build/js'));
    });

gulp copy

src can take String or an Array of strings as an argument. In Node, it’s called the glob pattern. Those strings are patterns for the src method to match. In the above example, scripts/*.js is a pattern for finding all the .js files in the scripts directory. Another example of a common pattern is scripts/**/*.js, which would find all the .js files in all the sub-directories of scripts. This type of recursion is computationally expensive, so use the /**/*.* pattern sparingly. When using an array of string patterns, you can list files to include and files to exclude in the same array.

    var gulp = require('gulp');
    var scripts = ['scripts/**/.js', '!scripts/someDirectory'];
    gulp.task('copy', function() {
      // place code for your default task here
      gulp.src(scripts)
          .pipe(gulp.dest('build/js'));
    });

Using ! is a signal in the pattern to make sure you do not retrieve those files. You may have numerous arrays of string patterns for varying steps in your build process. Some files might need to be sourced from the development code and returned to that same directory, while others need to be piped to the production directory without processing.

From all the previous examples, you probably have a good idea of what gulp.dest() does. It takes a path argument as a string, and will pipe the contents from previous operations to the destination path directory. It will create the directory if it does not exist.

The last Gulp function is watch. It will keep a node server running and watch a glob (a string pattern or array of string patterns) of files and process tasks as files change. watch will take a glob, an optional array of tasks to be run when the server starts, and a callback for when a change events occurs.

    gulp.watch('scripts/*.js', ['default'], function(event) {
      console.log('File '+event.path+' was '+event.type+', running tasks...')
      gulp.start('copy');
    });

This simple invocation of watch will run the ‘default’ task and watch for any changes to .js files in the ‘scripts’ directory. When a change occurs, the console will log the path of the file that triggered the change event and event type. Then, it will start the ‘copy’ task.

gulp watch

In the screenshot, the console reads out the actions that took place when watch was started. The ‘default’ task was run, a change occured, and then the ‘copy’ task was run. At this point, the watch server is still running and watching for changes.

Fine-Grained Control with Gulp

In the course of this post, you have installed Gulp, run the ‘default’ task, written a task to move files from the development enviroment to the production environment, and spun up a watch server to watch of changes while you development your project. The five methods of Gulp allow you fine-grained control over every aspect of your build process with clear language and simple, familiar JavaScript syntax. Your tasks are written as plain JavaScript functions, making it straightforward to modularize and organize your task code.

Now that you know how Gulp works, start looking for ‘Gulp-*’ plugins (npm modules) to add to your process. It is easy to see the power of pipe when chaining together multiple steps in processing your files.

Want to learn more about frontend web development? Sign up for our HTML5 Apps with jQuery bootcamp. Todd will be teaching June 9-13.

The post Journey into Gulp appeared first on Big Nerd Ranch.

]]>
https://bignerdranch.com/blog/journey-into-gulp/feed/ 0